summaryrefslogtreecommitdiffstats
path: root/dom/html
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/html
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/html')
-rw-r--r--dom/html/HTMLAllCollection.cpp211
-rw-r--r--dom/html/HTMLAllCollection.h87
-rw-r--r--dom/html/HTMLAnchorElement.cpp469
-rw-r--r--dom/html/HTMLAnchorElement.h242
-rw-r--r--dom/html/HTMLAreaElement.cpp263
-rw-r--r--dom/html/HTMLAreaElement.h194
-rw-r--r--dom/html/HTMLAudioElement.cpp98
-rw-r--r--dom/html/HTMLAudioElement.h52
-rw-r--r--dom/html/HTMLBRElement.cpp99
-rw-r--r--dom/html/HTMLBRElement.h56
-rw-r--r--dom/html/HTMLBodyElement.cpp530
-rw-r--r--dom/html/HTMLBodyElement.h153
-rw-r--r--dom/html/HTMLButtonElement.cpp519
-rw-r--r--dom/html/HTMLButtonElement.h183
-rw-r--r--dom/html/HTMLCanvasElement.cpp1482
-rw-r--r--dom/html/HTMLCanvasElement.h452
-rw-r--r--dom/html/HTMLContentElement.cpp393
-rw-r--r--dom/html/HTMLContentElement.h135
-rw-r--r--dom/html/HTMLDataElement.cpp34
-rw-r--r--dom/html/HTMLDataElement.h45
-rw-r--r--dom/html/HTMLDataListElement.cpp46
-rw-r--r--dom/html/HTMLDataListElement.h57
-rw-r--r--dom/html/HTMLDetailsElement.cpp111
-rw-r--r--dom/html/HTMLDetailsElement.h74
-rw-r--r--dom/html/HTMLDivElement.cpp113
-rw-r--r--dom/html/HTMLDivElement.h53
-rw-r--r--dom/html/HTMLElement.cpp54
-rw-r--r--dom/html/HTMLFieldSetElement.cpp370
-rw-r--r--dom/html/HTMLFieldSetElement.h150
-rw-r--r--dom/html/HTMLFontElement.cpp140
-rw-r--r--dom/html/HTMLFontElement.h69
-rw-r--r--dom/html/HTMLFormControlsCollection.cpp416
-rw-r--r--dom/html/HTMLFormControlsCollection.h125
-rw-r--r--dom/html/HTMLFormElement.cpp2585
-rw-r--r--dom/html/HTMLFormElement.h650
-rw-r--r--dom/html/HTMLFormSubmission.cpp980
-rw-r--r--dom/html/HTMLFormSubmission.h251
-rw-r--r--dom/html/HTMLFormSubmissionConstants.h31
-rw-r--r--dom/html/HTMLFrameElement.cpp85
-rw-r--r--dom/html/HTMLFrameElement.h108
-rw-r--r--dom/html/HTMLFrameSetElement.cpp381
-rw-r--r--dom/html/HTMLFrameSetElement.h177
-rw-r--r--dom/html/HTMLHRElement.cpp267
-rw-r--r--dom/html/HTMLHRElement.h86
-rw-r--r--dom/html/HTMLHeadingElement.cpp74
-rw-r--r--dom/html/HTMLHeadingElement.h54
-rw-r--r--dom/html/HTMLIFrameElement.cpp253
-rw-r--r--dom/html/HTMLIFrameElement.h209
-rw-r--r--dom/html/HTMLImageElement.cpp1354
-rw-r--r--dom/html/HTMLImageElement.h377
-rw-r--r--dom/html/HTMLInputElement.cpp8806
-rw-r--r--dom/html/HTMLInputElement.h1691
-rw-r--r--dom/html/HTMLLIElement.cpp119
-rw-r--r--dom/html/HTMLLIElement.h72
-rw-r--r--dom/html/HTMLLabelElement.cpp304
-rw-r--r--dom/html/HTMLLabelElement.h86
-rw-r--r--dom/html/HTMLLegendElement.cpp157
-rw-r--r--dom/html/HTMLLegendElement.h102
-rw-r--r--dom/html/HTMLLinkElement.cpp584
-rw-r--r--dom/html/HTMLLinkElement.h193
-rw-r--r--dom/html/HTMLMapElement.cpp77
-rw-r--r--dom/html/HTMLMapElement.h55
-rw-r--r--dom/html/HTMLMediaElement.cpp6883
-rw-r--r--dom/html/HTMLMediaElement.h1756
-rw-r--r--dom/html/HTMLMenuElement.cpp268
-rw-r--r--dom/html/HTMLMenuElement.h99
-rw-r--r--dom/html/HTMLMenuItemElement.cpp495
-rw-r--r--dom/html/HTMLMenuItemElement.h154
-rw-r--r--dom/html/HTMLMetaElement.cpp172
-rw-r--r--dom/html/HTMLMetaElement.h75
-rw-r--r--dom/html/HTMLMeterElement.cpp266
-rw-r--r--dom/html/HTMLMeterElement.h101
-rw-r--r--dom/html/HTMLModElement.cpp34
-rw-r--r--dom/html/HTMLModElement.h50
-rw-r--r--dom/html/HTMLObjectElement.cpp601
-rw-r--r--dom/html/HTMLObjectElement.h280
-rw-r--r--dom/html/HTMLOptGroupElement.cpp146
-rw-r--r--dom/html/HTMLOptGroupElement.h85
-rw-r--r--dom/html/HTMLOptionElement.cpp458
-rw-r--r--dom/html/HTMLOptionElement.h141
-rw-r--r--dom/html/HTMLOptionsCollection.cpp375
-rw-r--r--dom/html/HTMLOptionsCollection.h170
-rw-r--r--dom/html/HTMLOutputElement.cpp226
-rw-r--r--dom/html/HTMLOutputElement.h119
-rw-r--r--dom/html/HTMLParagraphElement.cpp78
-rw-r--r--dom/html/HTMLParagraphElement.h62
-rw-r--r--dom/html/HTMLPictureElement.cpp98
-rw-r--r--dom/html/HTMLPictureElement.h42
-rw-r--r--dom/html/HTMLPreElement.cpp101
-rw-r--r--dom/html/HTMLPreElement.h66
-rw-r--r--dom/html/HTMLProgressElement.cpp109
-rw-r--r--dom/html/HTMLProgressElement.h67
-rw-r--r--dom/html/HTMLScriptElement.cpp316
-rw-r--r--dom/html/HTMLScriptElement.h104
-rw-r--r--dom/html/HTMLSelectElement.cpp1917
-rw-r--r--dom/html/HTMLSelectElement.h659
-rw-r--r--dom/html/HTMLShadowElement.cpp373
-rw-r--r--dom/html/HTMLShadowElement.h96
-rw-r--r--dom/html/HTMLSharedElement.cpp360
-rw-r--r--dom/html/HTMLSharedElement.h189
-rw-r--r--dom/html/HTMLSharedListElement.cpp157
-rw-r--r--dom/html/HTMLSharedListElement.h92
-rw-r--r--dom/html/HTMLSharedObjectElement.cpp419
-rw-r--r--dom/html/HTMLSharedObjectElement.h243
-rw-r--r--dom/html/HTMLSourceElement.cpp176
-rw-r--r--dom/html/HTMLSourceElement.h126
-rw-r--r--dom/html/HTMLSpanElement.cpp33
-rw-r--r--dom/html/HTMLSpanElement.h40
-rw-r--r--dom/html/HTMLStyleElement.cpp266
-rw-r--r--dom/html/HTMLStyleElement.h103
-rw-r--r--dom/html/HTMLSummaryElement.cpp165
-rw-r--r--dom/html/HTMLSummaryElement.h59
-rw-r--r--dom/html/HTMLTableCaptionElement.cpp91
-rw-r--r--dom/html/HTMLTableCaptionElement.h55
-rw-r--r--dom/html/HTMLTableCellElement.cpp552
-rw-r--r--dom/html/HTMLTableCellElement.h171
-rw-r--r--dom/html/HTMLTableColElement.cpp155
-rw-r--r--dom/html/HTMLTableColElement.h97
-rw-r--r--dom/html/HTMLTableElement.cpp1015
-rw-r--r--dom/html/HTMLTableElement.h237
-rw-r--r--dom/html/HTMLTableRowElement.cpp329
-rw-r--r--dom/html/HTMLTableRowElement.h110
-rw-r--r--dom/html/HTMLTableSectionElement.cpp231
-rw-r--r--dom/html/HTMLTableSectionElement.h92
-rw-r--r--dom/html/HTMLTemplateElement.cpp73
-rw-r--r--dom/html/HTMLTemplateElement.h48
-rw-r--r--dom/html/HTMLTextAreaElement.cpp1676
-rw-r--r--dom/html/HTMLTextAreaElement.h402
-rw-r--r--dom/html/HTMLTimeElement.cpp36
-rw-r--r--dom/html/HTMLTimeElement.h44
-rw-r--r--dom/html/HTMLTitleElement.cpp136
-rw-r--r--dom/html/HTMLTitleElement.h65
-rw-r--r--dom/html/HTMLTrackElement.cpp471
-rw-r--r--dom/html/HTMLTrackElement.h146
-rw-r--r--dom/html/HTMLUnknownElement.cpp29
-rw-r--r--dom/html/HTMLUnknownElement.h46
-rw-r--r--dom/html/HTMLVideoElement.cpp329
-rw-r--r--dom/html/HTMLVideoElement.h158
-rw-r--r--dom/html/ImageDocument.cpp851
-rw-r--r--dom/html/ImageDocument.h136
-rw-r--r--dom/html/MediaDocument.cpp440
-rw-r--r--dom/html/MediaDocument.h105
-rw-r--r--dom/html/MediaError.cpp44
-rw-r--r--dom/html/MediaError.h57
-rw-r--r--dom/html/PluginDocument.cpp302
-rw-r--r--dom/html/RadioNodeList.cpp70
-rw-r--r--dom/html/RadioNodeList.h41
-rw-r--r--dom/html/TextTrackManager.cpp848
-rw-r--r--dom/html/TextTrackManager.h191
-rw-r--r--dom/html/TimeRanges.cpp202
-rw-r--r--dom/html/TimeRanges.h119
-rw-r--r--dom/html/ValidityState.cpp115
-rw-r--r--dom/html/ValidityState.h101
-rw-r--r--dom/html/VideoDocument.cpp155
-rw-r--r--dom/html/crashtests/1032654.html1
-rw-r--r--dom/html/crashtests/1141260.html4
-rw-r--r--dom/html/crashtests/1228876.html21
-rw-r--r--dom/html/crashtests/1230110.html19
-rw-r--r--dom/html/crashtests/1237633.html1
-rw-r--r--dom/html/crashtests/1281972-1.html5
-rw-r--r--dom/html/crashtests/1282894.html17
-rw-r--r--dom/html/crashtests/1290904.html37
-rw-r--r--dom/html/crashtests/1386905.html13
-rw-r--r--dom/html/crashtests/257818-1.html82
-rw-r--r--dom/html/crashtests/285166-1.html3
-rw-r--r--dom/html/crashtests/294235-1.html14
-rw-r--r--dom/html/crashtests/307616-1.html8
-rw-r--r--dom/html/crashtests/324918-1.xhtml26
-rw-r--r--dom/html/crashtests/338649-1.xhtml22
-rw-r--r--dom/html/crashtests/339501-1.xhtml33
-rw-r--r--dom/html/crashtests/339501-2.xhtml33
-rw-r--r--dom/html/crashtests/378993-1.xhtml7
-rw-r--r--dom/html/crashtests/382568-1-inner.xhtml52
-rw-r--r--dom/html/crashtests/382568-1.html9
-rw-r--r--dom/html/crashtests/383137.xhtml13
-rw-r--r--dom/html/crashtests/388183-1.html8
-rw-r--r--dom/html/crashtests/395340-1.html28
-rw-r--r--dom/html/crashtests/399694-1.html20
-rw-r--r--dom/html/crashtests/407053.html6
-rw-r--r--dom/html/crashtests/423371-1.html9
-rw-r--r--dom/html/crashtests/448564.html7
-rw-r--r--dom/html/crashtests/451123-1.html7
-rw-r--r--dom/html/crashtests/453406-1.html34
-rw-r--r--dom/html/crashtests/464197-1.html23
-rw-r--r--dom/html/crashtests/465466-1.xhtml23
-rw-r--r--dom/html/crashtests/468562-1.html6
-rw-r--r--dom/html/crashtests/468562-2.html6
-rw-r--r--dom/html/crashtests/494225.html10
-rw-r--r--dom/html/crashtests/495543.svg16
-rw-r--r--dom/html/crashtests/495546-1.html19
-rw-r--r--dom/html/crashtests/504183-1.html12
-rw-r--r--dom/html/crashtests/515829-1.html7
-rw-r--r--dom/html/crashtests/515829-2.html7
-rw-r--r--dom/html/crashtests/564461.xhtml26
-rw-r--r--dom/html/crashtests/570566-1.html2
-rw-r--r--dom/html/crashtests/571428-1.html14
-rw-r--r--dom/html/crashtests/580507-1.xhtml18
-rw-r--r--dom/html/crashtests/590387.html8
-rw-r--r--dom/html/crashtests/596785-1.html9
-rw-r--r--dom/html/crashtests/596785-2.html9
-rw-r--r--dom/html/crashtests/601422.html23
-rw-r--r--dom/html/crashtests/602117.html8
-rw-r--r--dom/html/crashtests/604807.html9
-rw-r--r--dom/html/crashtests/605264.html8
-rw-r--r--dom/html/crashtests/606430-1.html31
-rw-r--r--dom/html/crashtests/613027.html21
-rw-r--r--dom/html/crashtests/614279.html18
-rw-r--r--dom/html/crashtests/614988-1.html5
-rw-r--r--dom/html/crashtests/616401.html8
-rw-r--r--dom/html/crashtests/620078-1.html20
-rw-r--r--dom/html/crashtests/620078-2.html6
-rw-r--r--dom/html/crashtests/631421.html34
-rw-r--r--dom/html/crashtests/631421.pngbin0 -> 38661 bytes
-rw-r--r--dom/html/crashtests/673853.html20
-rw-r--r--dom/html/crashtests/680922-1.xul9
-rw-r--r--dom/html/crashtests/680922-binding.xml7
-rw-r--r--dom/html/crashtests/682058.xhtml11
-rw-r--r--dom/html/crashtests/682460.html21
-rw-r--r--dom/html/crashtests/68912-1.html24
-rw-r--r--dom/html/crashtests/738744.xhtml4
-rw-r--r--dom/html/crashtests/741218.json1
-rw-r--r--dom/html/crashtests/741218.json^headers^1
-rw-r--r--dom/html/crashtests/741250.xhtml9
-rw-r--r--dom/html/crashtests/795221-1.html7
-rw-r--r--dom/html/crashtests/795221-2.html9
-rw-r--r--dom/html/crashtests/795221-3.html14
-rw-r--r--dom/html/crashtests/795221-4.html9
-rw-r--r--dom/html/crashtests/795221-5.xml6
-rw-r--r--dom/html/crashtests/798802-1.html18
-rw-r--r--dom/html/crashtests/811226.html6
-rw-r--r--dom/html/crashtests/819745.html5
-rw-r--r--dom/html/crashtests/828180.html5
-rw-r--r--dom/html/crashtests/828472.html6
-rw-r--r--dom/html/crashtests/837033.html4
-rw-r--r--dom/html/crashtests/838256-1.html20
-rw-r--r--dom/html/crashtests/862084.html9
-rw-r--r--dom/html/crashtests/865147.html7
-rw-r--r--dom/html/crashtests/877910.html1
-rw-r--r--dom/html/crashtests/903106.html3
-rw-r--r--dom/html/crashtests/916322-1.html10
-rw-r--r--dom/html/crashtests/916322-2.html10
-rw-r--r--dom/html/crashtests/crashtests.list81
-rw-r--r--dom/html/htmlMenuBuilder.js132
-rw-r--r--dom/html/htmlMenuBuilder.manifest3
-rw-r--r--dom/html/moz.build247
-rw-r--r--dom/html/nsBrowserElement.cpp727
-rw-r--r--dom/html/nsBrowserElement.h140
-rw-r--r--dom/html/nsDOMStringMap.cpp261
-rw-r--r--dom/html/nsDOMStringMap.h59
-rw-r--r--dom/html/nsGenericHTMLElement.cpp3032
-rw-r--r--dom/html/nsGenericHTMLElement.h1724
-rw-r--r--dom/html/nsGenericHTMLFrameElement.cpp690
-rw-r--r--dom/html/nsGenericHTMLFrameElement.h135
-rw-r--r--dom/html/nsHTMLContentSink.cpp1106
-rw-r--r--dom/html/nsHTMLDNSPrefetch.cpp474
-rw-r--r--dom/html/nsHTMLDNSPrefetch.h125
-rw-r--r--dom/html/nsHTMLDocument.cpp3667
-rw-r--r--dom/html/nsHTMLDocument.h375
-rw-r--r--dom/html/nsIConstraintValidation.cpp263
-rw-r--r--dom/html/nsIConstraintValidation.h178
-rw-r--r--dom/html/nsIDateTimeInputArea.idl36
-rw-r--r--dom/html/nsIForm.h68
-rw-r--r--dom/html/nsIFormControl.h315
-rw-r--r--dom/html/nsIFormProcessor.h88
-rw-r--r--dom/html/nsIFormSubmitObserver.idl29
-rw-r--r--dom/html/nsIHTMLCollection.h96
-rw-r--r--dom/html/nsIHTMLDocument.h124
-rw-r--r--dom/html/nsIHTMLMenu.idl42
-rw-r--r--dom/html/nsIImageDocument.idl47
-rw-r--r--dom/html/nsIMenuBuilder.idl76
-rw-r--r--dom/html/nsIPhonetic.idl22
-rw-r--r--dom/html/nsIRadioGroupContainer.h103
-rw-r--r--dom/html/nsIRadioVisitor.h43
-rw-r--r--dom/html/nsITextControlElement.h205
-rw-r--r--dom/html/nsRadioVisitor.cpp69
-rw-r--r--dom/html/nsRadioVisitor.h111
-rw-r--r--dom/html/nsTextEditorState.cpp2354
-rw-r--r--dom/html/nsTextEditorState.h364
-rw-r--r--dom/html/reftests/41464-1-ref.html5
-rw-r--r--dom/html/reftests/41464-1a.html8
-rw-r--r--dom/html/reftests/41464-1b.html8
-rw-r--r--dom/html/reftests/468263-1a.html6
-rw-r--r--dom/html/reftests/468263-1b.html6
-rw-r--r--dom/html/reftests/468263-1c.html6
-rw-r--r--dom/html/reftests/468263-1d.html6
-rw-r--r--dom/html/reftests/468263-2-alternate-ref.html7
-rw-r--r--dom/html/reftests/468263-2-ref.html10
-rw-r--r--dom/html/reftests/468263-2.html10
-rw-r--r--dom/html/reftests/484200-1-ref.html11
-rw-r--r--dom/html/reftests/484200-1.html11
-rw-r--r--dom/html/reftests/485377-ref.html3
-rw-r--r--dom/html/reftests/485377.html3
-rw-r--r--dom/html/reftests/52019-1-ref.html11
-rw-r--r--dom/html/reftests/52019-1.html11
-rw-r--r--dom/html/reftests/557840-ref.html3
-rw-r--r--dom/html/reftests/557840.html3
-rw-r--r--dom/html/reftests/560059-video-dimensions-ref.html3
-rw-r--r--dom/html/reftests/560059-video-dimensions.html3
-rw-r--r--dom/html/reftests/573322-no-quirks-ref.html28
-rw-r--r--dom/html/reftests/573322-no-quirks.html28
-rw-r--r--dom/html/reftests/573322-quirks-ref.html27
-rw-r--r--dom/html/reftests/573322-quirks.html27
-rw-r--r--dom/html/reftests/596455-1a.html14
-rw-r--r--dom/html/reftests/596455-1b.html14
-rw-r--r--dom/html/reftests/596455-2a.html14
-rw-r--r--dom/html/reftests/596455-2b.html14
-rw-r--r--dom/html/reftests/596455-ref-1.html6
-rw-r--r--dom/html/reftests/596455-ref-2.html6
-rw-r--r--dom/html/reftests/610935-ref.html12
-rw-r--r--dom/html/reftests/610935.html11
-rw-r--r--dom/html/reftests/649134-1.html31
-rw-r--r--dom/html/reftests/649134-2-ref.html25
-rw-r--r--dom/html/reftests/649134-2.html31
-rw-r--r--dom/html/reftests/649134-ref.html13
-rw-r--r--dom/html/reftests/82711-1-ref.html15
-rw-r--r--dom/html/reftests/82711-1.html15
-rw-r--r--dom/html/reftests/82711-2-ref.html15
-rw-r--r--dom/html/reftests/82711-2.html8
-rw-r--r--dom/html/reftests/autofocus/autofocus-after-body-focus-ref.html7
-rw-r--r--dom/html/reftests/autofocus/autofocus-after-body-focus.html10
-rw-r--r--dom/html/reftests/autofocus/autofocus-after-load-ref.html7
-rw-r--r--dom/html/reftests/autofocus/autofocus-after-load.html21
-rw-r--r--dom/html/reftests/autofocus/autofocus-leaves-iframe-ref.html17
-rw-r--r--dom/html/reftests/autofocus/autofocus-leaves-iframe.html16
-rw-r--r--dom/html/reftests/autofocus/button-create.html23
-rw-r--r--dom/html/reftests/autofocus/button-load.html13
-rw-r--r--dom/html/reftests/autofocus/button-ref.html7
-rw-r--r--dom/html/reftests/autofocus/input-create.html23
-rw-r--r--dom/html/reftests/autofocus/input-load.html13
-rw-r--r--dom/html/reftests/autofocus/input-number-ref.html17
-rw-r--r--dom/html/reftests/autofocus/input-number.html26
-rw-r--r--dom/html/reftests/autofocus/input-ref.html7
-rw-r--r--dom/html/reftests/autofocus/input-time-ref.html22
-rw-r--r--dom/html/reftests/autofocus/input-time.html22
-rw-r--r--dom/html/reftests/autofocus/reftest-stylo.list36
-rw-r--r--dom/html/reftests/autofocus/reftest.list14
-rw-r--r--dom/html/reftests/autofocus/select-create.html23
-rw-r--r--dom/html/reftests/autofocus/select-load.html13
-rw-r--r--dom/html/reftests/autofocus/select-ref.html7
-rw-r--r--dom/html/reftests/autofocus/style.css7
-rw-r--r--dom/html/reftests/autofocus/textarea-create.html23
-rw-r--r--dom/html/reftests/autofocus/textarea-load.html13
-rw-r--r--dom/html/reftests/autofocus/textarea-ref.html7
-rw-r--r--dom/html/reftests/bug1106522-1.html11
-rw-r--r--dom/html/reftests/bug1106522-2.html11
-rw-r--r--dom/html/reftests/bug1106522-ref.html8
-rw-r--r--dom/html/reftests/bug1196784-no-srcset.html6
-rw-r--r--dom/html/reftests/bug1196784-with-srcset.html6
-rw-r--r--dom/html/reftests/bug1196784.pngbin0 -> 294 bytes
-rw-r--r--dom/html/reftests/bug1228601-video-rotated-ref.html13
-rw-r--r--dom/html/reftests/bug1228601-video-rotation-90.html13
-rw-r--r--dom/html/reftests/bug448564-1_ideal.html13
-rw-r--r--dom/html/reftests/bug448564-1_malformed.html19
-rw-r--r--dom/html/reftests/bug448564-1_well-formed.html11
-rw-r--r--dom/html/reftests/bug448564-4a.html10
-rw-r--r--dom/html/reftests/bug448564-4b.html6
-rw-r--r--dom/html/reftests/bug448564_forms.css2
-rw-r--r--dom/html/reftests/bug502168-1_malformed.html10
-rw-r--r--dom/html/reftests/bug502168-1_well-formed.html9
-rw-r--r--dom/html/reftests/bug917595-1-ref.html18
-rw-r--r--dom/html/reftests/bug917595-exif-rotated.jpgbin0 -> 90700 bytes
-rw-r--r--dom/html/reftests/bug917595-iframe-1.html18
-rw-r--r--dom/html/reftests/bug917595-pixel-rotated.jpgbin0 -> 91596 bytes
-rw-r--r--dom/html/reftests/bug917595-unrotated.jpgbin0 -> 90864 bytes
-rw-r--r--dom/html/reftests/figure-ref.html11
-rw-r--r--dom/html/reftests/figure.html8
-rw-r--r--dom/html/reftests/href-attr-change-restyles-ref.html33
-rw-r--r--dom/html/reftests/href-attr-change-restyles.html48
-rw-r--r--dom/html/reftests/image-load-shortcircuit-1.html8
-rw-r--r--dom/html/reftests/image-load-shortcircuit-2.html10
-rw-r--r--dom/html/reftests/image-load-shortcircuit-ref.html1
-rw-r--r--dom/html/reftests/lime100x100.svg4
-rw-r--r--dom/html/reftests/pass.pngbin0 -> 1036 bytes
-rw-r--r--dom/html/reftests/pre-1-ref.html22
-rw-r--r--dom/html/reftests/pre-1.html22
-rw-r--r--dom/html/reftests/red.pngbin0 -> 82 bytes
-rw-r--r--dom/html/reftests/reftest-stylo.list66
-rw-r--r--dom/html/reftests/reftest.list64
-rw-r--r--dom/html/reftests/responsive-image-load-shortcircuit-ref.html1
-rw-r--r--dom/html/reftests/responsive-image-load-shortcircuit.html15
-rw-r--r--dom/html/reftests/table-border-1-ref.html46
-rw-r--r--dom/html/reftests/table-border-1.html36
-rw-r--r--dom/html/reftests/table-border-2-notref.html40
-rw-r--r--dom/html/reftests/table-border-2-ref.html30
-rw-r--r--dom/html/reftests/table-border-2.html30
-rw-r--r--dom/html/reftests/toblob-todataurl/blob.js68
-rw-r--r--dom/html/reftests/toblob-todataurl/dataurl.js56
-rw-r--r--dom/html/reftests/toblob-todataurl/images/original.pngbin0 -> 50613 bytes
-rw-r--r--dom/html/reftests/toblob-todataurl/images/q0.jpgbin0 -> 2165 bytes
-rw-r--r--dom/html/reftests/toblob-todataurl/images/q100.jpgbin0 -> 54323 bytes
-rw-r--r--dom/html/reftests/toblob-todataurl/images/q25.jpgbin0 -> 3898 bytes
-rw-r--r--dom/html/reftests/toblob-todataurl/images/q50.jpgbin0 -> 4924 bytes
-rw-r--r--dom/html/reftests/toblob-todataurl/images/q75.jpgbin0 -> 6405 bytes
-rw-r--r--dom/html/reftests/toblob-todataurl/images/q92.jpgbin0 -> 13931 bytes
-rw-r--r--dom/html/reftests/toblob-todataurl/quality-0-ref.html2
-rw-r--r--dom/html/reftests/toblob-todataurl/quality-100-ref.html2
-rw-r--r--dom/html/reftests/toblob-todataurl/quality-25-ref.html2
-rw-r--r--dom/html/reftests/toblob-todataurl/quality-50-ref.html2
-rw-r--r--dom/html/reftests/toblob-todataurl/quality-75-ref.html2
-rw-r--r--dom/html/reftests/toblob-todataurl/quality-92-ref.html2
-rw-r--r--dom/html/reftests/toblob-todataurl/reftest-stylo.list17
-rw-r--r--dom/html/reftests/toblob-todataurl/reftest.list16
-rw-r--r--dom/html/reftests/toblob-todataurl/sample.js2
-rw-r--r--dom/html/reftests/toblob-todataurl/toblob-quality-0.html10
-rw-r--r--dom/html/reftests/toblob-todataurl/toblob-quality-100.html10
-rw-r--r--dom/html/reftests/toblob-todataurl/toblob-quality-25.html10
-rw-r--r--dom/html/reftests/toblob-todataurl/toblob-quality-50.html10
-rw-r--r--dom/html/reftests/toblob-todataurl/toblob-quality-75.html10
-rw-r--r--dom/html/reftests/toblob-todataurl/toblob-quality-92.html10
-rw-r--r--dom/html/reftests/toblob-todataurl/toblob-quality-default.html7
-rw-r--r--dom/html/reftests/toblob-todataurl/toblob-quality-undefined.html10
-rw-r--r--dom/html/reftests/toblob-todataurl/todataurl-quality-0.html10
-rw-r--r--dom/html/reftests/toblob-todataurl/todataurl-quality-100.html10
-rw-r--r--dom/html/reftests/toblob-todataurl/todataurl-quality-25.html10
-rw-r--r--dom/html/reftests/toblob-todataurl/todataurl-quality-50.html10
-rw-r--r--dom/html/reftests/toblob-todataurl/todataurl-quality-75.html10
-rw-r--r--dom/html/reftests/toblob-todataurl/todataurl-quality-92.html10
-rw-r--r--dom/html/reftests/toblob-todataurl/todataurl-quality-default.html7
-rw-r--r--dom/html/reftests/toblob-todataurl/todataurl-quality-undefined.html10
-rw-r--r--dom/html/reftests/video_rotated.mp4bin0 -> 1543 bytes
-rw-r--r--dom/html/reftests/video_rotation_90.mp4bin0 -> 1541 bytes
-rw-r--r--dom/html/test/347174transform.xsl41
-rw-r--r--dom/html/test/347174transformable.xml3
-rw-r--r--dom/html/test/allowMedia.sjs12
-rw-r--r--dom/html/test/browser.ini25
-rw-r--r--dom/html/test/browser_DOMDocElementInserted.js24
-rw-r--r--dom/html/test/browser_bug1081537.js11
-rw-r--r--dom/html/test/browser_bug1108547.js113
-rw-r--r--dom/html/test/browser_bug592641.js61
-rw-r--r--dom/html/test/browser_bug649778.js82
-rw-r--r--dom/html/test/browser_content_contextmenu_userinput.js50
-rw-r--r--dom/html/test/browser_fullscreen-api-keys.js170
-rw-r--r--dom/html/test/browser_fullscreen-contextmenu-esc.js107
-rw-r--r--dom/html/test/bug100533_iframe.html8
-rw-r--r--dom/html/test/bug100533_load.html14
-rw-r--r--dom/html/test/bug1260704_iframe.html38
-rw-r--r--dom/html/test/bug1260704_iframe_empty.html15
-rw-r--r--dom/html/test/bug1292522_iframe.html10
-rw-r--r--dom/html/test/bug1292522_page.html14
-rw-r--r--dom/html/test/bug1315146-iframe.html4
-rw-r--r--dom/html/test/bug1315146-main.html15
-rw-r--r--dom/html/test/bug196523-subframe.html37
-rw-r--r--dom/html/test/bug199692-nested-d2.html14
-rw-r--r--dom/html/test/bug199692-nested.html15
-rw-r--r--dom/html/test/bug199692-popup.html188
-rw-r--r--dom/html/test/bug199692-scrolled.html34
-rw-r--r--dom/html/test/bug242709_iframe.html20
-rw-r--r--dom/html/test/bug242709_load.html11
-rw-r--r--dom/html/test/bug277724_iframe1.html28
-rw-r--r--dom/html/test/bug277724_iframe2.xhtml27
-rw-r--r--dom/html/test/bug277890_iframe.html20
-rw-r--r--dom/html/test/bug277890_load.html11
-rw-r--r--dom/html/test/bug340800_iframe.txt4
-rw-r--r--dom/html/test/bug369370-popup.pngbin0 -> 4073 bytes
-rw-r--r--dom/html/test/bug372098-link-target.html7
-rw-r--r--dom/html/test/bug392567.jarbin0 -> 120 bytes
-rw-r--r--dom/html/test/bug392567.jar^headers^1
-rw-r--r--dom/html/test/bug441930_iframe.html27
-rw-r--r--dom/html/test/bug445004-inner.html14
-rw-r--r--dom/html/test/bug445004-inner.js23
-rw-r--r--dom/html/test/bug445004-outer-abs.html11
-rw-r--r--dom/html/test/bug445004-outer-rel.html11
-rw-r--r--dom/html/test/bug445004-outer-write.html11
-rw-r--r--dom/html/test/bug446483-iframe.html10
-rw-r--r--dom/html/test/bug448564-echo.sjs6
-rw-r--r--dom/html/test/bug448564-iframe-1.html16
-rw-r--r--dom/html/test/bug448564-iframe-2.html16
-rw-r--r--dom/html/test/bug448564-iframe-3.html16
-rw-r--r--dom/html/test/bug448564-submit.js4
-rw-r--r--dom/html/test/bug499092.html6
-rw-r--r--dom/html/test/bug499092.xml4
-rw-r--r--dom/html/test/bug514856_iframe.html21
-rw-r--r--dom/html/test/bug592641_img.jpgbin0 -> 42018 bytes
-rw-r--r--dom/html/test/bug649134/file_bug649134-1.sjs12
-rw-r--r--dom/html/test/bug649134/file_bug649134-2.sjs12
-rw-r--r--dom/html/test/bug649134/index.html3
-rw-r--r--dom/html/test/chrome.ini10
-rw-r--r--dom/html/test/dummy_page.html10
-rw-r--r--dom/html/test/file_anchor_ping.html13
-rw-r--r--dom/html/test/file_bug1108547-1.html4
-rw-r--r--dom/html/test/file_bug1108547-2.html6
-rw-r--r--dom/html/test/file_bug1108547-3.html5
-rw-r--r--dom/html/test/file_bug1166138_1x.pngbin0 -> 91 bytes
-rw-r--r--dom/html/test/file_bug1166138_2x.pngbin0 -> 100 bytes
-rw-r--r--dom/html/test/file_bug1166138_def.pngbin0 -> 85 bytes
-rw-r--r--dom/html/test/file_bug1260704.pngbin0 -> 91 bytes
-rw-r--r--dom/html/test/file_bug209275_1.html28
-rw-r--r--dom/html/test/file_bug209275_2.html23
-rw-r--r--dom/html/test/file_bug209275_3.html23
-rw-r--r--dom/html/test/file_bug297761.html13
-rw-r--r--dom/html/test/file_bug417760.pngbin0 -> 1991 bytes
-rw-r--r--dom/html/test/file_bug649778.html11
-rw-r--r--dom/html/test/file_bug649778.html^headers^1
-rw-r--r--dom/html/test/file_bug871161-1.html16
-rw-r--r--dom/html/test/file_bug871161-2.html14
-rw-r--r--dom/html/test/file_bug893537.html9
-rw-r--r--dom/html/test/file_content_contextmenu.html20
-rw-r--r--dom/html/test/file_cookiemanager.js20
-rw-r--r--dom/html/test/file_formSubmission_img.jpgbin0 -> 2711 bytes
-rw-r--r--dom/html/test/file_formSubmission_text.txt1
-rw-r--r--dom/html/test/file_fullscreen-api-keys.html32
-rw-r--r--dom/html/test/file_fullscreen-api.html317
-rw-r--r--dom/html/test/file_fullscreen-backdrop.html107
-rw-r--r--dom/html/test/file_fullscreen-denied-inner.html24
-rw-r--r--dom/html/test/file_fullscreen-denied.html129
-rw-r--r--dom/html/test/file_fullscreen-esc-exit-inner.html58
-rw-r--r--dom/html/test/file_fullscreen-esc-exit.html63
-rw-r--r--dom/html/test/file_fullscreen-hidden.html55
-rw-r--r--dom/html/test/file_fullscreen-lenient-setters.html61
-rw-r--r--dom/html/test/file_fullscreen-multiple-inner.html28
-rw-r--r--dom/html/test/file_fullscreen-multiple.html64
-rw-r--r--dom/html/test/file_fullscreen-navigation.html52
-rw-r--r--dom/html/test/file_fullscreen-nested.html120
-rw-r--r--dom/html/test/file_fullscreen-plugins.html160
-rw-r--r--dom/html/test/file_fullscreen-prefixed.html153
-rw-r--r--dom/html/test/file_fullscreen-rollback.html128
-rw-r--r--dom/html/test/file_fullscreen-scrollbar.html126
-rw-r--r--dom/html/test/file_fullscreen-selector.html178
-rw-r--r--dom/html/test/file_fullscreen-svg-element.html49
-rw-r--r--dom/html/test/file_fullscreen-top-layer.html160
-rw-r--r--dom/html/test/file_fullscreen-unprefix-disabled-inner.html96
-rw-r--r--dom/html/test/file_fullscreen-unprefix-disabled.html34
-rw-r--r--dom/html/test/file_fullscreen-utils.js82
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if1.html13
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if10.html12
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if11.html23
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if12.html23
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if13.html13
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if14.html34
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if15.html33
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if16.html25
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if17.html27
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if18.html26
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if19.html21
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if2.html21
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if3.html24
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if4.html30
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if5.html22
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if6.html21
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if7.html20
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if8.html26
-rw-r--r--dom/html/test/file_iframe_sandbox_a_if9.html18
-rw-r--r--dom/html/test/file_iframe_sandbox_b_if1.html11
-rw-r--r--dom/html/test/file_iframe_sandbox_b_if2.html49
-rw-r--r--dom/html/test/file_iframe_sandbox_b_if3.html92
-rw-r--r--dom/html/test/file_iframe_sandbox_c_if1.html35
-rw-r--r--dom/html/test/file_iframe_sandbox_c_if2.html23
-rw-r--r--dom/html/test/file_iframe_sandbox_c_if3.html26
-rw-r--r--dom/html/test/file_iframe_sandbox_c_if4.html45
-rw-r--r--dom/html/test/file_iframe_sandbox_c_if5.html20
-rw-r--r--dom/html/test/file_iframe_sandbox_c_if6.html24
-rw-r--r--dom/html/test/file_iframe_sandbox_c_if7.html27
-rw-r--r--dom/html/test/file_iframe_sandbox_c_if8.html27
-rw-r--r--dom/html/test/file_iframe_sandbox_c_if9.html17
-rw-r--r--dom/html/test/file_iframe_sandbox_close.html3
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if1.html19
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if10.html17
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if11.html26
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if12.html16
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if13.html35
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if14.html35
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if15.html14
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if16.html22
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if17.html24
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if18.html33
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if19.html13
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if2.html28
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if20.html25
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if21.html14
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if22.html25
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if23.html61
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if3.html13
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if4.html20
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if5.html20
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if6.html19
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if7.html20
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if8.html18
-rw-r--r--dom/html/test/file_iframe_sandbox_d_if9.html17
-rw-r--r--dom/html/test/file_iframe_sandbox_e_if1.html20
-rw-r--r--dom/html/test/file_iframe_sandbox_e_if10.html19
-rw-r--r--dom/html/test/file_iframe_sandbox_e_if11.html24
-rw-r--r--dom/html/test/file_iframe_sandbox_e_if12.html19
-rw-r--r--dom/html/test/file_iframe_sandbox_e_if13.html19
-rw-r--r--dom/html/test/file_iframe_sandbox_e_if14.html24
-rw-r--r--dom/html/test/file_iframe_sandbox_e_if15.html17
-rw-r--r--dom/html/test/file_iframe_sandbox_e_if16.html27
-rw-r--r--dom/html/test/file_iframe_sandbox_e_if2.html12
-rw-r--r--dom/html/test/file_iframe_sandbox_e_if3.html11
-rw-r--r--dom/html/test/file_iframe_sandbox_e_if4.html11
-rw-r--r--dom/html/test/file_iframe_sandbox_e_if5.html19
-rw-r--r--dom/html/test/file_iframe_sandbox_e_if6.html20
-rw-r--r--dom/html/test/file_iframe_sandbox_e_if7.html17
-rw-r--r--dom/html/test/file_iframe_sandbox_e_if8.html23
-rw-r--r--dom/html/test/file_iframe_sandbox_e_if9.html19
-rw-r--r--dom/html/test/file_iframe_sandbox_f_if1.html13
-rw-r--r--dom/html/test/file_iframe_sandbox_f_if2.html11
-rw-r--r--dom/html/test/file_iframe_sandbox_f_if2.html^headers^1
-rw-r--r--dom/html/test/file_iframe_sandbox_fail.js1
-rw-r--r--dom/html/test/file_iframe_sandbox_form_fail.html19
-rw-r--r--dom/html/test/file_iframe_sandbox_form_pass.html17
-rw-r--r--dom/html/test/file_iframe_sandbox_g_if1.html60
-rw-r--r--dom/html/test/file_iframe_sandbox_h_if1.html34
-rw-r--r--dom/html/test/file_iframe_sandbox_j_if1.html30
-rw-r--r--dom/html/test/file_iframe_sandbox_j_if2.html28
-rw-r--r--dom/html/test/file_iframe_sandbox_j_if3.html27
-rw-r--r--dom/html/test/file_iframe_sandbox_k_if1.html47
-rw-r--r--dom/html/test/file_iframe_sandbox_k_if2.html50
-rw-r--r--dom/html/test/file_iframe_sandbox_k_if3.html20
-rw-r--r--dom/html/test/file_iframe_sandbox_k_if4.html34
-rw-r--r--dom/html/test/file_iframe_sandbox_k_if5.html33
-rw-r--r--dom/html/test/file_iframe_sandbox_k_if6.html21
-rw-r--r--dom/html/test/file_iframe_sandbox_k_if7.html26
-rw-r--r--dom/html/test/file_iframe_sandbox_k_if8.html36
-rw-r--r--dom/html/test/file_iframe_sandbox_k_if9.html20
-rw-r--r--dom/html/test/file_iframe_sandbox_navigation_fail.html17
-rw-r--r--dom/html/test/file_iframe_sandbox_navigation_pass.html17
-rw-r--r--dom/html/test/file_iframe_sandbox_navigation_start.html11
-rw-r--r--dom/html/test/file_iframe_sandbox_open_window_fail.html19
-rw-r--r--dom/html/test/file_iframe_sandbox_open_window_pass.html25
-rw-r--r--dom/html/test/file_iframe_sandbox_pass.js1
-rw-r--r--dom/html/test/file_iframe_sandbox_redirect.html2
-rw-r--r--dom/html/test/file_iframe_sandbox_redirect.html^headers^2
-rw-r--r--dom/html/test/file_iframe_sandbox_redirect_target.html9
-rw-r--r--dom/html/test/file_iframe_sandbox_refresh.html2
-rw-r--r--dom/html/test/file_iframe_sandbox_refresh.html^headers^1
-rw-r--r--dom/html/test/file_iframe_sandbox_top_navigation_fail.html18
-rw-r--r--dom/html/test/file_iframe_sandbox_top_navigation_pass.html18
-rw-r--r--dom/html/test/file_iframe_sandbox_window_form_fail.html20
-rw-r--r--dom/html/test/file_iframe_sandbox_window_form_pass.html20
-rw-r--r--dom/html/test/file_iframe_sandbox_window_navigation_fail.html20
-rw-r--r--dom/html/test/file_iframe_sandbox_window_navigation_pass.html20
-rw-r--r--dom/html/test/file_iframe_sandbox_window_top_navigation_fail.html24
-rw-r--r--dom/html/test/file_iframe_sandbox_window_top_navigation_pass.html20
-rw-r--r--dom/html/test/file_iframe_sandbox_worker.js3
-rw-r--r--dom/html/test/file_ignoreuserfocus.html10
-rw-r--r--dom/html/test/file_imports_basics.html22
-rw-r--r--dom/html/test/file_imports_redirect.html5
-rw-r--r--dom/html/test/file_imports_redirect.html^headers^2
-rw-r--r--dom/html/test/file_imports_redirected.html6
-rw-r--r--dom/html/test/file_mozaudiochannel.html91
-rw-r--r--dom/html/test/file_srcdoc-2.html10
-rw-r--r--dom/html/test/file_srcdoc.html16
-rw-r--r--dom/html/test/file_window_open_close_inner.html7
-rw-r--r--dom/html/test/file_window_open_close_outer.html5
-rw-r--r--dom/html/test/formData_test.js212
-rw-r--r--dom/html/test/formData_worker.js19
-rw-r--r--dom/html/test/formSubmission_chrome.js6
-rw-r--r--dom/html/test/form_submit_server.sjs71
-rw-r--r--dom/html/test/forms/chrome.ini5
-rw-r--r--dom/html/test/forms/mochitest.ini107
-rw-r--r--dom/html/test/forms/save_restore_radio_groups.sjs50
-rw-r--r--dom/html/test/forms/submit_invalid_file.sjs14
-rw-r--r--dom/html/test/forms/test_autocompleteinfo.html121
-rw-r--r--dom/html/test/forms/test_bug1039548.html55
-rw-r--r--dom/html/test/forms/test_bug1283915.html67
-rw-r--r--dom/html/test/forms/test_bug1286509.html49
-rw-r--r--dom/html/test/forms/test_button_attributes_reflection.html137
-rw-r--r--dom/html/test/forms/test_change_event.html287
-rw-r--r--dom/html/test/forms/test_datalist_element.html118
-rw-r--r--dom/html/test/forms/test_form_attribute-1.html473
-rw-r--r--dom/html/test/forms/test_form_attribute-2.html53
-rw-r--r--dom/html/test/forms/test_form_attribute-3.html68
-rw-r--r--dom/html/test/forms/test_form_attribute-4.html48
-rw-r--r--dom/html/test/forms/test_form_attributes_reflection.html88
-rw-r--r--dom/html/test/forms/test_form_named_getter_dynamic.html54
-rw-r--r--dom/html/test/forms/test_formaction_attribute.html176
-rw-r--r--dom/html/test/forms/test_formnovalidate_attribute.html142
-rw-r--r--dom/html/test/forms/test_input_attributes_reflection.html275
-rw-r--r--dom/html/test/forms/test_input_autocomplete.html106
-rw-r--r--dom/html/test/forms/test_input_color_input_change_events.html120
-rw-r--r--dom/html/test/forms/test_input_color_picker_initial.html79
-rw-r--r--dom/html/test/forms/test_input_color_picker_popup.html140
-rw-r--r--dom/html/test/forms/test_input_color_picker_update.html87
-rw-r--r--dom/html/test/forms/test_input_datetime_focus_blur.html58
-rw-r--r--dom/html/test/forms/test_input_datetime_tabindex.html72
-rw-r--r--dom/html/test/forms/test_input_defaultValue.html81
-rw-r--r--dom/html/test/forms/test_input_email.html237
-rw-r--r--dom/html/test/forms/test_input_event.html234
-rw-r--r--dom/html/test/forms/test_input_file_picker.html267
-rw-r--r--dom/html/test/forms/test_input_list_attribute.html253
-rw-r--r--dom/html/test/forms/test_input_number_data.js38
-rw-r--r--dom/html/test/forms/test_input_number_focus.html54
-rw-r--r--dom/html/test/forms/test_input_number_key_events.html244
-rw-r--r--dom/html/test/forms/test_input_number_l10n.html75
-rw-r--r--dom/html/test/forms/test_input_number_mouse_events.html196
-rw-r--r--dom/html/test/forms/test_input_number_rounding.html120
-rw-r--r--dom/html/test/forms/test_input_number_validation.html143
-rw-r--r--dom/html/test/forms/test_input_radio_indeterminate.html109
-rw-r--r--dom/html/test/forms/test_input_radio_radiogroup.html75
-rw-r--r--dom/html/test/forms/test_input_radio_required.html31
-rw-r--r--dom/html/test/forms/test_input_range_attr_order.html48
-rw-r--r--dom/html/test/forms/test_input_range_key_events.html210
-rw-r--r--dom/html/test/forms/test_input_range_mouse_and_touch_events.html199
-rw-r--r--dom/html/test/forms/test_input_range_rounding.html106
-rw-r--r--dom/html/test/forms/test_input_sanitization.html565
-rw-r--r--dom/html/test/forms/test_input_textarea_set_value_no_scroll.html122
-rw-r--r--dom/html/test/forms/test_input_time_focus_blur_events.html82
-rw-r--r--dom/html/test/forms/test_input_time_key_events.html197
-rw-r--r--dom/html/test/forms/test_input_types_pref.html114
-rw-r--r--dom/html/test/forms/test_input_typing_sanitization.html260
-rw-r--r--dom/html/test/forms/test_input_untrusted_key_events.html96
-rw-r--r--dom/html/test/forms/test_input_url.html89
-rw-r--r--dom/html/test/forms/test_interactive_content_in_label.html83
-rw-r--r--dom/html/test/forms/test_label_control_attribute.html100
-rw-r--r--dom/html/test/forms/test_label_input_controls.html84
-rw-r--r--dom/html/test/forms/test_max_attribute.html437
-rw-r--r--dom/html/test/forms/test_maxlength_attribute.html129
-rw-r--r--dom/html/test/forms/test_meter_element.html384
-rw-r--r--dom/html/test/forms/test_meter_pseudo-classes.html170
-rw-r--r--dom/html/test/forms/test_min_attribute.html437
-rw-r--r--dom/html/test/forms/test_minlength_attribute.html130
-rw-r--r--dom/html/test/forms/test_mozistextfield.html111
-rw-r--r--dom/html/test/forms/test_novalidate_attribute.html87
-rw-r--r--dom/html/test/forms/test_option_disabled.html123
-rw-r--r--dom/html/test/forms/test_option_index_attribute.html76
-rw-r--r--dom/html/test/forms/test_option_text.html57
-rw-r--r--dom/html/test/forms/test_output_element.html182
-rw-r--r--dom/html/test/forms/test_pattern_attribute.html324
-rw-r--r--dom/html/test/forms/test_progress_element.html314
-rw-r--r--dom/html/test/forms/test_radio_in_label.html54
-rw-r--r--dom/html/test/forms/test_radio_radionodelist.html57
-rw-r--r--dom/html/test/forms/test_reportValidation_preventDefault.html93
-rw-r--r--dom/html/test/forms/test_required_attribute.html382
-rw-r--r--dom/html/test/forms/test_restore_form_elements.html174
-rw-r--r--dom/html/test/forms/test_save_restore_radio_groups.html73
-rw-r--r--dom/html/test/forms/test_select_change_event.html54
-rw-r--r--dom/html/test/forms/test_select_input_change_event.html122
-rw-r--r--dom/html/test/forms/test_select_selectedOptions.html120
-rw-r--r--dom/html/test/forms/test_select_validation.html39
-rw-r--r--dom/html/test/forms/test_set_range_text.html244
-rw-r--r--dom/html/test/forms/test_step_attribute.html965
-rw-r--r--dom/html/test/forms/test_stepup_stepdown.html1018
-rw-r--r--dom/html/test/forms/test_submit_invalid_file.html53
-rw-r--r--dom/html/test/forms/test_textarea_attributes_reflection.html104
-rw-r--r--dom/html/test/forms/test_validation.html358
-rw-r--r--dom/html/test/forms/test_validation_not_in_doc.html19
-rw-r--r--dom/html/test/forms/test_valueAsDate_pref.html57
-rw-r--r--dom/html/test/forms/test_valueasdate_attribute.html649
-rw-r--r--dom/html/test/forms/test_valueasnumber_attribute.html744
-rw-r--r--dom/html/test/head.js54
-rw-r--r--dom/html/test/image-allow-credentials.pngbin0 -> 844 bytes
-rw-r--r--dom/html/test/image-allow-credentials.png^headers^2
-rw-r--r--dom/html/test/image.pngbin0 -> 268 bytes
-rw-r--r--dom/html/test/imports/file_CSP_sandbox.html7
-rw-r--r--dom/html/test/imports/file_CSP_sandbox_import.html9
-rw-r--r--dom/html/test/imports/file_blocking_DOMContentLoaded_A.html11
-rw-r--r--dom/html/test/imports/file_blocking_DOMContentLoaded_B.html11
-rw-r--r--dom/html/test/imports/file_blocking_DOMContentLoaded_C.html11
-rw-r--r--dom/html/test/imports/file_blocking_DOMContentLoaded_D.html10
-rw-r--r--dom/html/test/imports/file_cycle_1_A.html11
-rw-r--r--dom/html/test/imports/file_cycle_1_B.html12
-rw-r--r--dom/html/test/imports/file_cycle_1_C.html12
-rw-r--r--dom/html/test/imports/file_cycle_2_A.html11
-rw-r--r--dom/html/test/imports/file_cycle_2_B.html13
-rw-r--r--dom/html/test/imports/file_cycle_2_C.html10
-rw-r--r--dom/html/test/imports/file_cycle_2_D.html10
-rw-r--r--dom/html/test/imports/file_cycle_3_A.html11
-rw-r--r--dom/html/test/imports/file_cycle_3_B.html11
-rw-r--r--dom/html/test/imports/file_cycle_3_C.html12
-rw-r--r--dom/html/test/imports/file_cycle_4_A.html11
-rw-r--r--dom/html/test/imports/file_cycle_4_B.html11
-rw-r--r--dom/html/test/imports/file_cycle_4_C.html11
-rw-r--r--dom/html/test/imports/file_cycle_4_D.html12
-rw-r--r--dom/html/test/imports/file_cycle_4_E.html11
-rw-r--r--dom/html/test/imports/file_element_upgrade.html15
-rw-r--r--dom/html/test/imports/file_encoding.html5
-rw-r--r--dom/html/test/imports/file_importA1.html11
-rw-r--r--dom/html/test/imports/file_importA2.html10
-rw-r--r--dom/html/test/imports/file_importB1.html11
-rw-r--r--dom/html/test/imports/file_importB2.html10
-rw-r--r--dom/html/test/imports/file_importC1.html11
-rw-r--r--dom/html/test/imports/file_importC10.html11
-rw-r--r--dom/html/test/imports/file_importC2.html11
-rw-r--r--dom/html/test/imports/file_importC3.html11
-rw-r--r--dom/html/test/imports/file_importC4.html11
-rw-r--r--dom/html/test/imports/file_importC5.html11
-rw-r--r--dom/html/test/imports/file_importC6.html11
-rw-r--r--dom/html/test/imports/file_importC7.html11
-rw-r--r--dom/html/test/imports/file_importC8.html11
-rw-r--r--dom/html/test/imports/file_importC9.html11
-rw-r--r--dom/html/test/imports/file_importD.html8
-rw-r--r--dom/html/test/imports/file_importE.html11
-rw-r--r--dom/html/test/imports/file_simple_import.html4
-rw-r--r--dom/html/test/imports/mochitest.ini52
-rw-r--r--dom/html/test/imports/test_CSP_sandbox.html26
-rw-r--r--dom/html/test/imports/test_blocking_DOMContentLoaded.html36
-rw-r--r--dom/html/test/imports/test_cycle_1.html36
-rw-r--r--dom/html/test/imports/test_cycle_2.html36
-rw-r--r--dom/html/test/imports/test_cycle_3.html37
-rw-r--r--dom/html/test/imports/test_cycle_4.html37
-rw-r--r--dom/html/test/imports/test_defaultView.html31
-rw-r--r--dom/html/test/imports/test_element_upgrade.html34
-rw-r--r--dom/html/test/imports/test_encoding.html30
-rw-r--r--dom/html/test/mochitest.ini608
-rw-r--r--dom/html/test/nnc_lockup.gifbin0 -> 732 bytes
-rw-r--r--dom/html/test/reflect.js625
-rw-r--r--dom/html/test/simpleFileOpener.js32
-rw-r--r--dom/html/test/test_a_text.html44
-rw-r--r--dom/html/test/test_allowMedia.html99
-rw-r--r--dom/html/test/test_anchor_href_cache_invalidation.html30
-rw-r--r--dom/html/test/test_anchor_ping.html309
-rw-r--r--dom/html/test/test_applet_attributes_reflection.html86
-rw-r--r--dom/html/test/test_audio_wakelock.html125
-rw-r--r--dom/html/test/test_base_attributes_reflection.html34
-rw-r--r--dom/html/test/test_bug1003539.html37
-rw-r--r--dom/html/test/test_bug100533.html47
-rw-r--r--dom/html/test/test_bug1013316.html46
-rw-r--r--dom/html/test/test_bug1045270.html46
-rw-r--r--dom/html/test/test_bug1081037.html133
-rw-r--r--dom/html/test/test_bug109445.html55
-rw-r--r--dom/html/test/test_bug109445.xhtml55
-rw-r--r--dom/html/test/test_bug1146116.html59
-rw-r--r--dom/html/test/test_bug1166138.html130
-rw-r--r--dom/html/test/test_bug1203668.html62
-rw-r--r--dom/html/test/test_bug1230665.html46
-rw-r--r--dom/html/test/test_bug1233598.html35
-rw-r--r--dom/html/test/test_bug1250401.html97
-rw-r--r--dom/html/test/test_bug1260664.html54
-rw-r--r--dom/html/test/test_bug1260704.html90
-rw-r--r--dom/html/test/test_bug1261673.html77
-rw-r--r--dom/html/test/test_bug1261674-1.html77
-rw-r--r--dom/html/test/test_bug1261674-2.html70
-rw-r--r--dom/html/test/test_bug1264157.html90
-rw-r--r--dom/html/test/test_bug1287321.html57
-rw-r--r--dom/html/test/test_bug1292522_same_domain_with_different_port_number.html43
-rw-r--r--dom/html/test/test_bug1295719_event_sequence_for_arrow_keys.html67
-rw-r--r--dom/html/test/test_bug1295719_event_sequence_for_number_keys.html65
-rw-r--r--dom/html/test/test_bug1297.html46
-rw-r--r--dom/html/test/test_bug1310865.html18
-rw-r--r--dom/html/test/test_bug1315146.html33
-rw-r--r--dom/html/test/test_bug1366.html35
-rw-r--r--dom/html/test/test_bug1400.html42
-rw-r--r--dom/html/test/test_bug143220.html72
-rw-r--r--dom/html/test/test_bug1682.html37
-rw-r--r--dom/html/test/test_bug172261.html67
-rw-r--r--dom/html/test/test_bug182279.html35
-rw-r--r--dom/html/test/test_bug1823.html30
-rw-r--r--dom/html/test/test_bug196523.html41
-rw-r--r--dom/html/test/test_bug199692.html21
-rw-r--r--dom/html/test/test_bug2082.html30
-rw-r--r--dom/html/test/test_bug209275.xhtml261
-rw-r--r--dom/html/test/test_bug237071.html28
-rw-r--r--dom/html/test/test_bug242709.html33
-rw-r--r--dom/html/test/test_bug24958.html31
-rw-r--r--dom/html/test/test_bug255820.html123
-rw-r--r--dom/html/test/test_bug259332.html64
-rw-r--r--dom/html/test/test_bug274626.html97
-rw-r--r--dom/html/test/test_bug277724.html141
-rw-r--r--dom/html/test/test_bug277890.html33
-rw-r--r--dom/html/test/test_bug287465.html45
-rw-r--r--dom/html/test/test_bug295561.html86
-rw-r--r--dom/html/test/test_bug297761.html77
-rw-r--r--dom/html/test/test_bug300691-1.html120
-rw-r--r--dom/html/test/test_bug300691-2.html142
-rw-r--r--dom/html/test/test_bug300691-3.xhtml48
-rw-r--r--dom/html/test/test_bug311681.html99
-rw-r--r--dom/html/test/test_bug311681.xhtml102
-rw-r--r--dom/html/test/test_bug324378.html76
-rw-r--r--dom/html/test/test_bug330705-1.html41
-rw-r--r--dom/html/test/test_bug332246.html75
-rw-r--r--dom/html/test/test_bug332848.xhtml86
-rw-r--r--dom/html/test/test_bug332893-1.html38
-rw-r--r--dom/html/test/test_bug332893-2.html53
-rw-r--r--dom/html/test/test_bug332893-3.html58
-rw-r--r--dom/html/test/test_bug332893-4.html29
-rw-r--r--dom/html/test/test_bug332893-5.html29
-rw-r--r--dom/html/test/test_bug332893-6.html27
-rw-r--r--dom/html/test/test_bug332893-7.html69
-rw-r--r--dom/html/test/test_bug3348.html33
-rw-r--r--dom/html/test/test_bug340017.xhtml27
-rw-r--r--dom/html/test/test_bug340800.html55
-rw-r--r--dom/html/test/test_bug347174.html64
-rw-r--r--dom/html/test/test_bug347174_write.html71
-rw-r--r--dom/html/test/test_bug347174_xsl.html55
-rw-r--r--dom/html/test/test_bug347174_xslp.html61
-rw-r--r--dom/html/test/test_bug353415-1.html42
-rw-r--r--dom/html/test/test_bug353415-2.html67
-rw-r--r--dom/html/test/test_bug359657.html40
-rw-r--r--dom/html/test/test_bug369370.html151
-rw-r--r--dom/html/test/test_bug371375.html58
-rw-r--r--dom/html/test/test_bug372098.html75
-rw-r--r--dom/html/test/test_bug373589.html29
-rw-r--r--dom/html/test/test_bug375003-1.html156
-rw-r--r--dom/html/test/test_bug375003-2.html109
-rw-r--r--dom/html/test/test_bug377624.html25
-rw-r--r--dom/html/test/test_bug380383.html39
-rw-r--r--dom/html/test/test_bug383383.html41
-rw-r--r--dom/html/test/test_bug383383_2.xhtml20
-rw-r--r--dom/html/test/test_bug384419.html56
-rw-r--r--dom/html/test/test_bug386496.html53
-rw-r--r--dom/html/test/test_bug386728.html45
-rw-r--r--dom/html/test/test_bug386996.html43
-rw-r--r--dom/html/test/test_bug388558.html76
-rw-r--r--dom/html/test/test_bug388746.html62
-rw-r--r--dom/html/test/test_bug388794.html104
-rw-r--r--dom/html/test/test_bug389797.html267
-rw-r--r--dom/html/test/test_bug390975.html61
-rw-r--r--dom/html/test/test_bug391777.html25
-rw-r--r--dom/html/test/test_bug391994.html184
-rw-r--r--dom/html/test/test_bug392567.html88
-rw-r--r--dom/html/test/test_bug394700.html49
-rw-r--r--dom/html/test/test_bug395107.html108
-rw-r--r--dom/html/test/test_bug401160.xhtml27
-rw-r--r--dom/html/test/test_bug402680.html50
-rw-r--r--dom/html/test/test_bug403868.html87
-rw-r--r--dom/html/test/test_bug403868.xhtml86
-rw-r--r--dom/html/test/test_bug405242.html35
-rw-r--r--dom/html/test/test_bug406596.html83
-rw-r--r--dom/html/test/test_bug417760.html71
-rw-r--r--dom/html/test/test_bug421640.html56
-rw-r--r--dom/html/test/test_bug424698.html94
-rw-r--r--dom/html/test/test_bug428135.xhtml156
-rw-r--r--dom/html/test/test_bug430351.html523
-rw-r--r--dom/html/test/test_bug430392.html47
-rw-r--r--dom/html/test/test_bug435128.html42
-rw-r--r--dom/html/test/test_bug441930.html29
-rw-r--r--dom/html/test/test_bug442801.html63
-rw-r--r--dom/html/test/test_bug445004.html138
-rw-r--r--dom/html/test/test_bug446483.html47
-rw-r--r--dom/html/test/test_bug448166.html35
-rw-r--r--dom/html/test/test_bug448564.html53
-rw-r--r--dom/html/test/test_bug456229.html30
-rw-r--r--dom/html/test/test_bug458037.xhtml112
-rw-r--r--dom/html/test/test_bug460568.html147
-rw-r--r--dom/html/test/test_bug463104.html25
-rw-r--r--dom/html/test/test_bug478251.html74
-rw-r--r--dom/html/test/test_bug481335.xhtml120
-rw-r--r--dom/html/test/test_bug481440.html30
-rw-r--r--dom/html/test/test_bug481647.html42
-rw-r--r--dom/html/test/test_bug482659.html64
-rw-r--r--dom/html/test/test_bug486741.html43
-rw-r--r--dom/html/test/test_bug489532.html33
-rw-r--r--dom/html/test/test_bug497242.xhtml41
-rw-r--r--dom/html/test/test_bug499092.html43
-rw-r--r--dom/html/test/test_bug500885.html63
-rw-r--r--dom/html/test/test_bug512367.html47
-rw-r--r--dom/html/test/test_bug514856.html61
-rw-r--r--dom/html/test/test_bug518122.html126
-rw-r--r--dom/html/test/test_bug519987.html33
-rw-r--r--dom/html/test/test_bug523771.html106
-rw-r--r--dom/html/test/test_bug529819.html32
-rw-r--r--dom/html/test/test_bug529859.html42
-rw-r--r--dom/html/test/test_bug535043.html90
-rw-r--r--dom/html/test/test_bug536891.html67
-rw-r--r--dom/html/test/test_bug536895.html54
-rw-r--r--dom/html/test/test_bug546995.html40
-rw-r--r--dom/html/test/test_bug547850.html45
-rw-r--r--dom/html/test/test_bug551846.html164
-rw-r--r--dom/html/test/test_bug555567.html42
-rw-r--r--dom/html/test/test_bug556645.html50
-rw-r--r--dom/html/test/test_bug557087-1.html126
-rw-r--r--dom/html/test/test_bug557087-2.html359
-rw-r--r--dom/html/test/test_bug557087-3.html215
-rw-r--r--dom/html/test/test_bug557087-4.html90
-rw-r--r--dom/html/test/test_bug557087-5.html93
-rw-r--r--dom/html/test/test_bug557087-6.html44
-rw-r--r--dom/html/test/test_bug557620.html30
-rw-r--r--dom/html/test/test_bug558788-1.html211
-rw-r--r--dom/html/test/test_bug558788-2.html174
-rw-r--r--dom/html/test/test_bug560112.html211
-rw-r--r--dom/html/test/test_bug561634.html126
-rw-r--r--dom/html/test/test_bug561636.html118
-rw-r--r--dom/html/test/test_bug561640.html72
-rw-r--r--dom/html/test/test_bug564001.html48
-rw-r--r--dom/html/test/test_bug566046.html207
-rw-r--r--dom/html/test/test_bug567938-1.html69
-rw-r--r--dom/html/test/test_bug567938-2.html70
-rw-r--r--dom/html/test/test_bug567938-3.html70
-rw-r--r--dom/html/test/test_bug567938-4.html44
-rw-r--r--dom/html/test/test_bug569955.html37
-rw-r--r--dom/html/test/test_bug573969.html37
-rw-r--r--dom/html/test/test_bug57600.html42
-rw-r--r--dom/html/test/test_bug579079.html43
-rw-r--r--dom/html/test/test_bug582412-1.html209
-rw-r--r--dom/html/test/test_bug582412-2.html209
-rw-r--r--dom/html/test/test_bug583514.html71
-rw-r--r--dom/html/test/test_bug583533.html81
-rw-r--r--dom/html/test/test_bug586763.html43
-rw-r--r--dom/html/test/test_bug586786.html57
-rw-r--r--dom/html/test/test_bug587469.html41
-rw-r--r--dom/html/test/test_bug589.html42
-rw-r--r--dom/html/test/test_bug590353-1.html36
-rw-r--r--dom/html/test/test_bug590353-2.html79
-rw-r--r--dom/html/test/test_bug590363.html133
-rw-r--r--dom/html/test/test_bug592802.html97
-rw-r--r--dom/html/test/test_bug593689.html50
-rw-r--r--dom/html/test/test_bug595429.html56
-rw-r--r--dom/html/test/test_bug595447.html29
-rw-r--r--dom/html/test/test_bug595449.html95
-rw-r--r--dom/html/test/test_bug596350.html65
-rw-r--r--dom/html/test/test_bug596511.html229
-rw-r--r--dom/html/test/test_bug598643.html80
-rw-r--r--dom/html/test/test_bug598833-1.html45
-rw-r--r--dom/html/test/test_bug600155.html44
-rw-r--r--dom/html/test/test_bug601030.html54
-rw-r--r--dom/html/test/test_bug605124-1.html107
-rw-r--r--dom/html/test/test_bug605124-2.html112
-rw-r--r--dom/html/test/test_bug605125-1.html113
-rw-r--r--dom/html/test/test_bug605125-2.html145
-rw-r--r--dom/html/test/test_bug606817.html64
-rw-r--r--dom/html/test/test_bug607145.html82
-rw-r--r--dom/html/test/test_bug610212.html42
-rw-r--r--dom/html/test/test_bug610687.html201
-rw-r--r--dom/html/test/test_bug611189.html45
-rw-r--r--dom/html/test/test_bug612730.html53
-rw-r--r--dom/html/test/test_bug613019.html84
-rw-r--r--dom/html/test/test_bug613113.html57
-rw-r--r--dom/html/test/test_bug613722.html32
-rw-r--r--dom/html/test/test_bug613979.html50
-rw-r--r--dom/html/test/test_bug615595.htmlbin0 -> 2704 bytes
-rw-r--r--dom/html/test/test_bug615833.html156
-rw-r--r--dom/html/test/test_bug617528.html73
-rw-r--r--dom/html/test/test_bug618948.html88
-rw-r--r--dom/html/test/test_bug619278.html58
-rw-r--r--dom/html/test/test_bug622558.html89
-rw-r--r--dom/html/test/test_bug622597.html115
-rw-r--r--dom/html/test/test_bug623291.html46
-rw-r--r--dom/html/test/test_bug6296.html31
-rw-r--r--dom/html/test/test_bug629801.html50
-rw-r--r--dom/html/test/test_bug633058.html68
-rw-r--r--dom/html/test/test_bug636336.html40
-rw-r--r--dom/html/test/test_bug641219.html34
-rw-r--r--dom/html/test/test_bug643051.html42
-rw-r--r--dom/html/test/test_bug646157.html95
-rw-r--r--dom/html/test/test_bug649134.html54
-rw-r--r--dom/html/test/test_bug651956.html51
-rw-r--r--dom/html/test/test_bug658746.html97
-rw-r--r--dom/html/test/test_bug659596.html96
-rw-r--r--dom/html/test/test_bug659743.xml55
-rw-r--r--dom/html/test/test_bug660663.html30
-rw-r--r--dom/html/test/test_bug660959-1.html26
-rw-r--r--dom/html/test/test_bug660959-2.html31
-rw-r--r--dom/html/test/test_bug660959-3.html29
-rw-r--r--dom/html/test/test_bug666200.html43
-rw-r--r--dom/html/test/test_bug666666.html32
-rw-r--r--dom/html/test/test_bug669012.html45
-rw-r--r--dom/html/test/test_bug674558.html290
-rw-r--r--dom/html/test/test_bug674927.html55
-rw-r--r--dom/html/test/test_bug677463.html38
-rw-r--r--dom/html/test/test_bug677495-1.html34
-rw-r--r--dom/html/test/test_bug677495.html34
-rw-r--r--dom/html/test/test_bug677658.html41
-rw-r--r--dom/html/test/test_bug682886.html33
-rw-r--r--dom/html/test/test_bug691.html62
-rw-r--r--dom/html/test/test_bug694.html30
-rw-r--r--dom/html/test/test_bug694503.html75
-rw-r--r--dom/html/test/test_bug696.html28
-rw-r--r--dom/html/test/test_bug717819.html37
-rw-r--r--dom/html/test/test_bug741266.html44
-rw-r--r--dom/html/test/test_bug742030.html31
-rw-r--r--dom/html/test/test_bug742549.html47
-rw-r--r--dom/html/test/test_bug745685.html105
-rw-r--r--dom/html/test/test_bug763626.html29
-rw-r--r--dom/html/test/test_bug765780.html46
-rw-r--r--dom/html/test/test_bug780993.html39
-rw-r--r--dom/html/test/test_bug787134.html29
-rw-r--r--dom/html/test/test_bug797113.html39
-rw-r--r--dom/html/test/test_bug803677.html50
-rw-r--r--dom/html/test/test_bug821307.html41
-rw-r--r--dom/html/test/test_bug827126.html28
-rw-r--r--dom/html/test/test_bug838582.html35
-rw-r--r--dom/html/test/test_bug839371.html44
-rw-r--r--dom/html/test/test_bug839913.html14
-rw-r--r--dom/html/test/test_bug841466.html37
-rw-r--r--dom/html/test/test_bug845057.html59
-rw-r--r--dom/html/test/test_bug869040.html36
-rw-r--r--dom/html/test/test_bug870787.html84
-rw-r--r--dom/html/test/test_bug871161.html37
-rw-r--r--dom/html/test/test_bug874758.html31
-rw-r--r--dom/html/test/test_bug879319.html92
-rw-r--r--dom/html/test/test_bug885024.html46
-rw-r--r--dom/html/test/test_bug893537.html45
-rw-r--r--dom/html/test/test_bug95530.html38
-rw-r--r--dom/html/test/test_bug969346.html33
-rw-r--r--dom/html/test/test_bug982039.html46
-rw-r--r--dom/html/test/test_change_crossorigin.html89
-rw-r--r--dom/html/test/test_checked.html358
-rw-r--r--dom/html/test/test_dir_attributes_reflection.html27
-rw-r--r--dom/html/test/test_dl_attributes_reflection.html27
-rw-r--r--dom/html/test/test_document-element-inserted.html54
-rw-r--r--dom/html/test/test_document.watch.html129
-rw-r--r--dom/html/test/test_documentAll.html167
-rw-r--r--dom/html/test/test_element_prototype.html32
-rw-r--r--dom/html/test/test_embed_attributes_reflection.html57
-rw-r--r--dom/html/test/test_filepicker_default_directory.html83
-rw-r--r--dom/html/test/test_focusshift_button.html40
-rw-r--r--dom/html/test/test_form-parsing.html35
-rw-r--r--dom/html/test/test_formData.html50
-rw-r--r--dom/html/test/test_formSubmission.html825
-rw-r--r--dom/html/test/test_formSubmission2.html221
-rw-r--r--dom/html/test/test_formelements.html68
-rw-r--r--dom/html/test/test_fragment_form_pointer.html27
-rw-r--r--dom/html/test/test_fullscreen-api-race.html166
-rw-r--r--dom/html/test/test_fullscreen-api.html106
-rw-r--r--dom/html/test/test_hash_encoded.html118
-rw-r--r--dom/html/test/test_hidden.html52
-rw-r--r--dom/html/test/test_html_attributes_reflection.html27
-rw-r--r--dom/html/test/test_htmlcollection.html55
-rw-r--r--dom/html/test/test_iframe_sandbox_general.html283
-rw-r--r--dom/html/test/test_iframe_sandbox_inheritance.html203
-rw-r--r--dom/html/test/test_iframe_sandbox_modal.html122
-rw-r--r--dom/html/test/test_iframe_sandbox_navigation.html281
-rw-r--r--dom/html/test/test_iframe_sandbox_navigation2.html212
-rw-r--r--dom/html/test/test_iframe_sandbox_plugins.html141
-rw-r--r--dom/html/test/test_iframe_sandbox_popups.html78
-rw-r--r--dom/html/test/test_iframe_sandbox_popups_inheritance.html157
-rw-r--r--dom/html/test/test_iframe_sandbox_redirect.html45
-rw-r--r--dom/html/test/test_iframe_sandbox_refresh.html101
-rw-r--r--dom/html/test/test_iframe_sandbox_same_origin.html108
-rw-r--r--dom/html/test/test_iframe_sandbox_workers.html74
-rw-r--r--dom/html/test/test_ignoreuserfocus.html158
-rw-r--r--dom/html/test/test_imageSrcSet.html38
-rw-r--r--dom/html/test/test_image_clone_load.html21
-rw-r--r--dom/html/test/test_img_attributes_reflection.html103
-rw-r--r--dom/html/test/test_imports_basics.html68
-rw-r--r--dom/html/test/test_imports_nested.html41
-rw-r--r--dom/html/test/test_imports_nested_2.html56
-rw-r--r--dom/html/test/test_imports_nonhttp.html56
-rw-r--r--dom/html/test/test_imports_redirect.html39
-rw-r--r--dom/html/test/test_input_files_not_nsIFile.html48
-rw-r--r--dom/html/test/test_li_attributes_reflection.html34
-rw-r--r--dom/html/test/test_link_attributes_reflection.html84
-rw-r--r--dom/html/test/test_link_sizes.html35
-rw-r--r--dom/html/test/test_map_attributes_reflection.html27
-rw-r--r--dom/html/test/test_meta_attributes_reflection.html45
-rw-r--r--dom/html/test/test_mod_attributes_reflection.html41
-rw-r--r--dom/html/test/test_mozaudiochannel.html31
-rw-r--r--dom/html/test/test_named_options.html61
-rw-r--r--dom/html/test/test_nested_invalid_fieldsets.html47
-rw-r--r--dom/html/test/test_non-ascii-cookie.html60
-rw-r--r--dom/html/test/test_non-ascii-cookie.html^headers^1
-rw-r--r--dom/html/test/test_object_attributes_reflection.html117
-rw-r--r--dom/html/test/test_object_plugin_nav.html99
-rw-r--r--dom/html/test/test_ol_attributes_reflection.html65
-rw-r--r--dom/html/test/test_option_defaultSelected.html47
-rw-r--r--dom/html/test/test_option_selected_state.html61
-rw-r--r--dom/html/test/test_param_attributes_reflection.html45
-rw-r--r--dom/html/test/test_plugin.tst1
-rw-r--r--dom/html/test/test_q_attributes_reflection.html32
-rw-r--r--dom/html/test/test_restore_from_parser_fragment.html59
-rw-r--r--dom/html/test/test_rowscollection.html69
-rw-r--r--dom/html/test/test_srcdoc-2.html57
-rw-r--r--dom/html/test/test_srcdoc.html118
-rw-r--r--dom/html/test/test_style_attributes_reflection.html41
-rw-r--r--dom/html/test/test_track.html62
-rw-r--r--dom/html/test/test_ul_attributes_reflection.html33
-rw-r--r--dom/html/test/test_video_wakelock.html198
-rw-r--r--dom/html/test/test_viewport.html57
-rw-r--r--dom/html/test/test_viewport_resize.html44
-rw-r--r--dom/html/test/test_window_open_close.html53
-rw-r--r--dom/html/test/wakelock.oggbin0 -> 16521 bytes
-rw-r--r--dom/html/test/wakelock.ogvbin0 -> 28942 bytes
1152 files changed, 130271 insertions, 0 deletions
diff --git a/dom/html/HTMLAllCollection.cpp b/dom/html/HTMLAllCollection.cpp
new file mode 100644
index 000000000..afa160e0c
--- /dev/null
+++ b/dom/html/HTMLAllCollection.cpp
@@ -0,0 +1,211 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLAllCollection.h"
+
+#include "mozilla/dom/HTMLAllCollectionBinding.h"
+#include "mozilla/dom/Nullable.h"
+#include "nsHTMLDocument.h"
+
+namespace mozilla {
+namespace dom {
+
+HTMLAllCollection::HTMLAllCollection(nsHTMLDocument* aDocument)
+ : mDocument(aDocument)
+{
+ MOZ_ASSERT(mDocument);
+}
+
+HTMLAllCollection::~HTMLAllCollection()
+{
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HTMLAllCollection,
+ mDocument,
+ mCollection,
+ mNamedMap)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLAllCollection)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLAllCollection)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLAllCollection)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+nsINode*
+HTMLAllCollection::GetParentObject() const
+{
+ return mDocument;
+}
+
+uint32_t
+HTMLAllCollection::Length()
+{
+ return Collection()->Length(true);
+}
+
+nsIContent*
+HTMLAllCollection::Item(uint32_t aIndex)
+{
+ return Collection()->Item(aIndex);
+}
+
+nsContentList*
+HTMLAllCollection::Collection()
+{
+ if (!mCollection) {
+ nsIDocument* document = mDocument;
+ mCollection = document->GetElementsByTagName(NS_LITERAL_STRING("*"));
+ MOZ_ASSERT(mCollection);
+ }
+ return mCollection;
+}
+
+static bool
+IsAllNamedElement(nsIContent* aContent)
+{
+ return aContent->IsAnyOfHTMLElements(nsGkAtoms::a,
+ nsGkAtoms::applet,
+ nsGkAtoms::button,
+ nsGkAtoms::embed,
+ nsGkAtoms::form,
+ nsGkAtoms::iframe,
+ nsGkAtoms::img,
+ nsGkAtoms::input,
+ nsGkAtoms::map,
+ nsGkAtoms::meta,
+ nsGkAtoms::object,
+ nsGkAtoms::select,
+ nsGkAtoms::textarea,
+ nsGkAtoms::frame,
+ nsGkAtoms::frameset);
+}
+
+static bool
+DocAllResultMatch(nsIContent* aContent, int32_t aNamespaceID, nsIAtom* aAtom,
+ void* aData)
+{
+ if (aContent->GetID() == aAtom) {
+ return true;
+ }
+
+ nsGenericHTMLElement* elm = nsGenericHTMLElement::FromContent(aContent);
+ if (!elm) {
+ return false;
+ }
+
+ if (!IsAllNamedElement(elm)) {
+ return false;
+ }
+
+ const nsAttrValue* val = elm->GetParsedAttr(nsGkAtoms::name);
+ return val && val->Type() == nsAttrValue::eAtom &&
+ val->GetAtomValue() == aAtom;
+}
+
+nsContentList*
+HTMLAllCollection::GetDocumentAllList(const nsAString& aID)
+{
+ if (nsContentList* docAllList = mNamedMap.GetWeak(aID)) {
+ return docAllList;
+ }
+
+ nsCOMPtr<nsIAtom> id = NS_Atomize(aID);
+ RefPtr<nsContentList> docAllList =
+ new nsContentList(mDocument, DocAllResultMatch, nullptr, nullptr, true, id);
+ mNamedMap.Put(aID, docAllList);
+ return docAllList;
+}
+
+void
+HTMLAllCollection::NamedGetter(const nsAString& aID,
+ bool& aFound,
+ Nullable<OwningNodeOrHTMLCollection>& aResult)
+{
+ if (aID.IsEmpty()) {
+ aFound = false;
+ aResult.SetNull();
+ return;
+ }
+
+ nsContentList* docAllList = GetDocumentAllList(aID);
+ if (!docAllList) {
+ aFound = false;
+ aResult.SetNull();
+ return;
+ }
+
+ // Check if there are more than 1 entries. Do this by getting the second one
+ // rather than the length since getting the length always requires walking
+ // the entire document.
+ if (docAllList->Item(1, true)) {
+ aFound = true;
+ aResult.SetValue().SetAsHTMLCollection() = docAllList;
+ return;
+ }
+
+ // There's only 0 or 1 items. Return the first one or null.
+ if (nsIContent* node = docAllList->Item(0, true)) {
+ aFound = true;
+ aResult.SetValue().SetAsNode() = node;
+ return;
+ }
+
+ aFound = false;
+ aResult.SetNull();
+}
+
+void
+HTMLAllCollection::GetSupportedNames(nsTArray<nsString>& aNames)
+{
+ // XXXbz this is very similar to nsContentList::GetSupportedNames,
+ // but has to check IsAllNamedElement for the name case.
+ AutoTArray<nsIAtom*, 8> atoms;
+ for (uint32_t i = 0; i < Length(); ++i) {
+ nsIContent *content = Item(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) {
+ // 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 &&
+ IsAllNamedElement(content)) {
+ 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]);
+ }
+}
+
+
+JSObject*
+HTMLAllCollection::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLAllCollectionBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLAllCollection.h b/dom/html/HTMLAllCollection.h
new file mode 100644
index 000000000..aed72eaaa
--- /dev/null
+++ b/dom/html/HTMLAllCollection.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLAllCollection_h
+#define mozilla_dom_HTMLAllCollection_h
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupportsImpl.h"
+#include "nsRefPtrHashtable.h"
+#include "nsWrapperCache.h"
+
+#include <stdint.h>
+
+class nsContentList;
+class nsHTMLDocument;
+class nsIContent;
+class nsINode;
+
+namespace mozilla {
+namespace dom {
+
+class OwningNodeOrHTMLCollection;
+template<typename> struct Nullable;
+
+class HTMLAllCollection final : public nsISupports
+ , public nsWrapperCache
+{
+ ~HTMLAllCollection();
+
+public:
+ explicit HTMLAllCollection(nsHTMLDocument* aDocument);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(HTMLAllCollection)
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+ nsINode* GetParentObject() const;
+
+ uint32_t Length();
+ nsIContent* Item(uint32_t aIndex);
+ void Item(const nsAString& aName, Nullable<OwningNodeOrHTMLCollection>& aResult)
+ {
+ NamedItem(aName, aResult);
+ }
+ nsIContent* IndexedGetter(uint32_t aIndex, bool& aFound)
+ {
+ nsIContent* result = Item(aIndex);
+ aFound = !!result;
+ return result;
+ }
+
+ void NamedItem(const nsAString& aName,
+ Nullable<OwningNodeOrHTMLCollection>& aResult)
+ {
+ bool found = false;
+ NamedGetter(aName, found, aResult);
+ }
+ void NamedGetter(const nsAString& aName,
+ bool& aFound,
+ Nullable<OwningNodeOrHTMLCollection>& aResult);
+ void GetSupportedNames(nsTArray<nsString>& aNames);
+ void LegacyCall(JS::Handle<JS::Value>, const nsAString& aName,
+ Nullable<OwningNodeOrHTMLCollection>& aResult)
+ {
+ NamedItem(aName, aResult);
+ }
+
+private:
+ nsContentList* Collection();
+
+ /**
+ * Returns the HTMLCollection for document.all[aID], or null if there isn't one.
+ */
+ nsContentList* GetDocumentAllList(const nsAString& aID);
+
+ RefPtr<nsHTMLDocument> mDocument;
+ RefPtr<nsContentList> mCollection;
+ nsRefPtrHashtable<nsStringHashKey, nsContentList> mNamedMap;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLAllCollection_h
diff --git a/dom/html/HTMLAnchorElement.cpp b/dom/html/HTMLAnchorElement.cpp
new file mode 100644
index 000000000..a6cfacc53
--- /dev/null
+++ b/dom/html/HTMLAnchorElement.cpp
@@ -0,0 +1,469 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLAnchorElement.h"
+
+#include "mozilla/dom/HTMLAnchorElementBinding.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsGkAtoms.h"
+#include "nsHTMLDNSPrefetch.h"
+#include "nsIDocument.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsIURI.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Anchor)
+
+namespace mozilla {
+namespace dom {
+
+#define ANCHOR_ELEMENT_FLAG_BIT(n_) NODE_FLAG_BIT(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + (n_))
+
+// Anchor element specific bits
+enum {
+ // Indicates that a DNS Prefetch has been requested from this Anchor elem
+ HTML_ANCHOR_DNS_PREFETCH_REQUESTED = ANCHOR_ELEMENT_FLAG_BIT(0),
+
+ // Indicates that a DNS Prefetch was added to the deferral queue
+ HTML_ANCHOR_DNS_PREFETCH_DEFERRED = ANCHOR_ELEMENT_FLAG_BIT(1)
+};
+
+ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 2);
+
+#undef ANCHOR_ELEMENT_FLAG_BIT
+
+// static
+const DOMTokenListSupportedToken HTMLAnchorElement::sSupportedRelValues[] = {
+ "noreferrer",
+ "noopener",
+ nullptr
+};
+
+HTMLAnchorElement::~HTMLAnchorElement()
+{
+}
+
+bool
+HTMLAnchorElement::IsInteractiveHTMLContent(bool aIgnoreTabindex) const
+{
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::href) ||
+ nsGenericHTMLElement::IsInteractiveHTMLContent(aIgnoreTabindex);
+}
+
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLAnchorElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLAnchorElement,
+ nsIDOMHTMLAnchorElement,
+ Link)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLElement)
+
+NS_IMPL_ADDREF_INHERITED(HTMLAnchorElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLAnchorElement, Element)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLAnchorElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLAnchorElement,
+ nsGenericHTMLElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelList)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLAnchorElement,
+ nsGenericHTMLElement)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRelList)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ELEMENT_CLONE(HTMLAnchorElement)
+
+JSObject*
+HTMLAnchorElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLAnchorElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_STRING_ATTR(HTMLAnchorElement, Charset, charset)
+NS_IMPL_STRING_ATTR(HTMLAnchorElement, Coords, coords)
+NS_IMPL_URI_ATTR(HTMLAnchorElement, Href, href)
+NS_IMPL_STRING_ATTR(HTMLAnchorElement, Hreflang, hreflang)
+NS_IMPL_STRING_ATTR(HTMLAnchorElement, Name, name)
+NS_IMPL_STRING_ATTR(HTMLAnchorElement, Rel, rel)
+NS_IMPL_STRING_ATTR(HTMLAnchorElement, Rev, rev)
+NS_IMPL_STRING_ATTR(HTMLAnchorElement, Shape, shape)
+NS_IMPL_STRING_ATTR(HTMLAnchorElement, Type, type)
+NS_IMPL_STRING_ATTR(HTMLAnchorElement, Download, download)
+
+int32_t
+HTMLAnchorElement::TabIndexDefault()
+{
+ return 0;
+}
+
+bool
+HTMLAnchorElement::Draggable() const
+{
+ // links can be dragged as long as there is an href and the
+ // draggable attribute isn't false
+ if (!HasAttr(kNameSpaceID_None, nsGkAtoms::href)) {
+ // no href, so just use the same behavior as other elements
+ return nsGenericHTMLElement::Draggable();
+ }
+
+ return !AttrValueIs(kNameSpaceID_None, nsGkAtoms::draggable,
+ nsGkAtoms::_false, eIgnoreCase);
+}
+
+void
+HTMLAnchorElement::OnDNSPrefetchRequested()
+{
+ UnsetFlags(HTML_ANCHOR_DNS_PREFETCH_DEFERRED);
+ SetFlags(HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
+}
+
+void
+HTMLAnchorElement::OnDNSPrefetchDeferred()
+{
+ UnsetFlags(HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
+ SetFlags(HTML_ANCHOR_DNS_PREFETCH_DEFERRED);
+}
+
+bool
+HTMLAnchorElement::HasDeferredDNSPrefetchRequest()
+{
+ return HasFlag(HTML_ANCHOR_DNS_PREFETCH_DEFERRED);
+}
+
+nsresult
+HTMLAnchorElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ Link::ResetLinkState(false, Link::ElementHasHref());
+
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Prefetch links
+ nsIDocument* doc = GetComposedDoc();
+ if (doc) {
+ doc->RegisterPendingLinkUpdate(this);
+ TryDNSPrefetch();
+ }
+
+ return rv;
+}
+
+void
+HTMLAnchorElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ // Cancel any DNS prefetches
+ // Note: Must come before ResetLinkState. If called after, it will recreate
+ // mCachedURI based on data that is invalid - due to a call to GetHostname.
+ CancelDNSPrefetch(HTML_ANCHOR_DNS_PREFETCH_DEFERRED,
+ HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
+
+ // If this link is ever reinserted into a document, it might
+ // be under a different xml:base, so forget the cached state now.
+ Link::ResetLinkState(false, Link::ElementHasHref());
+
+ // Note, we need to use OwnerDoc() here, since GetComposedDoc() might
+ // return null.
+ nsIDocument* doc = OwnerDoc();
+ if (doc) {
+ doc->UnregisterPendingLinkUpdate(this);
+ }
+
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+static bool
+IsNodeInEditableRegion(nsINode* aNode)
+{
+ while (aNode) {
+ if (aNode->IsEditable()) {
+ return true;
+ }
+ aNode = aNode->GetParent();
+ }
+ return false;
+}
+
+bool
+HTMLAnchorElement::IsHTMLFocusable(bool aWithMouse,
+ bool *aIsFocusable, int32_t *aTabIndex)
+{
+ if (nsGenericHTMLElement::IsHTMLFocusable(aWithMouse, aIsFocusable, aTabIndex)) {
+ return true;
+ }
+
+ // cannot focus links if there is no link handler
+ nsIDocument* doc = GetComposedDoc();
+ if (doc) {
+ nsIPresShell* presShell = doc->GetShell();
+ if (presShell) {
+ nsPresContext* presContext = presShell->GetPresContext();
+ if (presContext && !presContext->GetLinkHandler()) {
+ *aIsFocusable = false;
+ return false;
+ }
+ }
+ }
+
+ // Links that are in an editable region should never be focusable, even if
+ // they are in a contenteditable="false" region.
+ if (IsNodeInEditableRegion(this)) {
+ if (aTabIndex) {
+ *aTabIndex = -1;
+ }
+
+ *aIsFocusable = false;
+
+ return true;
+ }
+
+ if (!HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) {
+ // check whether we're actually a link
+ if (!Link::HasURI()) {
+ // Not tabbable or focusable without href (bug 17605), unless
+ // forced to be via presence of nonnegative tabindex attribute
+ if (aTabIndex) {
+ *aTabIndex = -1;
+ }
+
+ *aIsFocusable = false;
+
+ return false;
+ }
+ }
+
+ if (aTabIndex && (sTabFocusModel & eTabFocus_linksMask) == 0) {
+ *aTabIndex = -1;
+ }
+
+ *aIsFocusable = true;
+
+ return false;
+}
+
+nsresult
+HTMLAnchorElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ return PreHandleEventForAnchors(aVisitor);
+}
+
+nsresult
+HTMLAnchorElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ return PostHandleEventForAnchors(aVisitor);
+}
+
+bool
+HTMLAnchorElement::IsLink(nsIURI** aURI) const
+{
+ return IsHTMLLink(aURI);
+}
+
+void
+HTMLAnchorElement::GetLinkTarget(nsAString& aTarget)
+{
+ GetAttr(kNameSpaceID_None, nsGkAtoms::target, aTarget);
+ if (aTarget.IsEmpty()) {
+ GetBaseTarget(aTarget);
+ }
+}
+
+NS_IMETHODIMP
+HTMLAnchorElement::GetTarget(nsAString& aValue)
+{
+ if (!GetAttr(kNameSpaceID_None, nsGkAtoms::target, aValue)) {
+ GetBaseTarget(aValue);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLAnchorElement::SetTarget(const nsAString& aValue)
+{
+ return SetAttr(kNameSpaceID_None, nsGkAtoms::target, aValue, true);
+}
+
+nsDOMTokenList*
+HTMLAnchorElement::RelList()
+{
+ if (!mRelList) {
+ mRelList = new nsDOMTokenList(this, nsGkAtoms::rel, sSupportedRelValues);
+ }
+ return mRelList;
+}
+
+#define IMPL_URI_PART(_part) \
+ NS_IMETHODIMP \
+ HTMLAnchorElement::Get##_part(nsAString& a##_part) \
+ { \
+ Link::Get##_part(a##_part); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP \
+ HTMLAnchorElement::Set##_part(const nsAString& a##_part) \
+ { \
+ Link::Set##_part(a##_part); \
+ return NS_OK; \
+ }
+
+IMPL_URI_PART(Protocol)
+IMPL_URI_PART(Host)
+IMPL_URI_PART(Hostname)
+IMPL_URI_PART(Pathname)
+IMPL_URI_PART(Search)
+IMPL_URI_PART(Port)
+IMPL_URI_PART(Hash)
+
+#undef IMPL_URI_PART
+
+NS_IMETHODIMP
+HTMLAnchorElement::GetText(nsAString& aText)
+{
+ if(!nsContentUtils::GetNodeTextContent(this, true, aText, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLAnchorElement::SetText(const nsAString& aText)
+{
+ return nsContentUtils::SetNodeTextContent(this, aText, false);
+}
+
+NS_IMETHODIMP
+HTMLAnchorElement::ToString(nsAString& aSource)
+{
+ return GetHref(aSource);
+}
+
+NS_IMETHODIMP
+HTMLAnchorElement::GetPing(nsAString& aValue)
+{
+ GetAttr(kNameSpaceID_None, nsGkAtoms::ping, aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLAnchorElement::SetPing(const nsAString& aValue)
+{
+ return SetAttr(kNameSpaceID_None, nsGkAtoms::ping, aValue, true);
+}
+
+already_AddRefed<nsIURI>
+HTMLAnchorElement::GetHrefURI() const
+{
+ nsCOMPtr<nsIURI> uri = Link::GetCachedURI();
+ if (uri) {
+ return uri.forget();
+ }
+
+ return GetHrefURIForAnchors();
+}
+
+nsresult
+HTMLAnchorElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify)
+{
+ bool reset = false;
+ if (aName == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
+ // If we do not have a cached URI, we have some value here so we must reset
+ // our link state after calling the parent.
+ if (!Link::HasCachedURI()) {
+ reset = true;
+ }
+ // However, if we have a cached URI, we'll want to see if the value changed.
+ else {
+ nsAutoString val;
+ GetHref(val);
+ if (!val.Equals(aValue)) {
+ reset = true;
+ }
+ }
+ if (reset) {
+ CancelDNSPrefetch(HTML_ANCHOR_DNS_PREFETCH_DEFERRED,
+ HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
+ }
+ }
+
+ nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
+ aValue, aNotify);
+
+ // The ordering of the parent class's SetAttr call and Link::ResetLinkState
+ // is important here! The attribute is not set until SetAttr returns, and
+ // we will need the updated attribute value because notifying the document
+ // that content states have changed will call IntrinsicState, which will try
+ // to get updated information about the visitedness from Link.
+ if (reset) {
+ Link::ResetLinkState(!!aNotify, true);
+ if (IsInComposedDoc()) {
+ TryDNSPrefetch();
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+HTMLAnchorElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify)
+{
+ bool href =
+ (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID);
+
+ if (href) {
+ CancelDNSPrefetch(HTML_ANCHOR_DNS_PREFETCH_DEFERRED,
+ HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
+ }
+
+ nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttribute,
+ aNotify);
+
+ // The ordering of the parent class's UnsetAttr call and Link::ResetLinkState
+ // is important here! The attribute is not unset until UnsetAttr returns, and
+ // we will need the updated attribute value because notifying the document
+ // that content states have changed will call IntrinsicState, which will try
+ // to get updated information about the visitedness from Link.
+ if (href) {
+ Link::ResetLinkState(!!aNotify, false);
+ }
+
+ return rv;
+}
+
+bool
+HTMLAnchorElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+EventStates
+HTMLAnchorElement::IntrinsicState() const
+{
+ return Link::LinkState() | nsGenericHTMLElement::IntrinsicState();
+}
+
+size_t
+HTMLAnchorElement::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ return nsGenericHTMLElement::SizeOfExcludingThis(aMallocSizeOf) +
+ Link::SizeOfExcludingThis(aMallocSizeOf);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLAnchorElement.h b/dom/html/HTMLAnchorElement.h
new file mode 100644
index 000000000..2cb04ad93
--- /dev/null
+++ b/dom/html/HTMLAnchorElement.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/. */
+
+#ifndef mozilla_dom_HTMLAnchorElement_h
+#define mozilla_dom_HTMLAnchorElement_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Link.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIDOMHTMLAnchorElement.h"
+#include "nsDOMTokenList.h"
+
+namespace mozilla {
+class EventChainPostVisitor;
+class EventChainPreVisitor;
+namespace dom {
+
+class HTMLAnchorElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLAnchorElement,
+ public Link
+{
+public:
+ using Element::GetText;
+ using Element::SetText;
+
+ explicit HTMLAnchorElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ , Link(this)
+ {
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // CC
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLAnchorElement,
+ nsGenericHTMLElement)
+
+ virtual int32_t TabIndexDefault() override;
+ virtual bool Draggable() const override;
+
+ // Element
+ virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
+
+ // nsIDOMHTMLAnchorElement
+ NS_DECL_NSIDOMHTMLANCHORELEMENT
+
+ // DOM memory reporter participant
+ NS_DECL_SIZEOF_EXCLUDING_THIS
+
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+ virtual bool IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable, int32_t *aTabIndex) override;
+
+ virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) override;
+ virtual nsresult PostHandleEvent(
+ EventChainPostVisitor& aVisitor) override;
+ virtual bool IsLink(nsIURI** aURI) const override;
+ virtual void GetLinkTarget(nsAString& aTarget) override;
+ virtual already_AddRefed<nsIURI> GetHrefURI() const 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* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify) override;
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify) override;
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ virtual EventStates IntrinsicState() const override;
+
+ virtual void OnDNSPrefetchDeferred() override;
+ virtual void OnDNSPrefetchRequested() override;
+ virtual bool HasDeferredDNSPrefetchRequest() override;
+
+ // WebIDL API
+
+ // The XPCOM GetHref is OK for us
+ void SetHref(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::href, aValue, rv);
+ }
+ // The XPCOM GetTarget is OK for us
+ void SetTarget(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::target, aValue, rv);
+ }
+ void GetDownload(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::download, aValue);
+ }
+ void SetDownload(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::download, aValue, rv);
+ }
+ // The XPCOM GetPing is OK for us
+ void SetPing(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::ping, aValue, rv);
+ }
+ void GetRel(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::rel, aValue);
+ }
+ void SetRel(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::rel, aValue, rv);
+ }
+ void SetReferrerPolicy(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::referrerpolicy, aValue, rv);
+ }
+ void GetReferrerPolicy(nsAString& aReferrer)
+ {
+ GetEnumAttr(nsGkAtoms::referrerpolicy, EmptyCString().get(), aReferrer);
+ }
+ nsDOMTokenList* RelList();
+ void GetHreflang(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::hreflang, aValue);
+ }
+ void SetHreflang(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::hreflang, aValue, rv);
+ }
+ void GetType(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::type, aValue);
+ }
+ void SetType(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::type, aValue, rv);
+ }
+ // The XPCOM GetText is OK for us
+ void SetText(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ rv = SetText(aValue);
+ }
+
+ // Link::GetOrigin is OK for us
+
+ // Link::GetProtocol is OK for us
+ // Link::SetProtocol is OK for us
+
+ // Link::GetUsername is OK for us
+ // Link::SetUsername is OK for us
+
+ // Link::GetPassword is OK for us
+ // Link::SetPassword is OK for us
+
+ // Link::Link::GetHost is OK for us
+ // Link::Link::SetHost is OK for us
+
+ // Link::Link::GetHostname is OK for us
+ // Link::Link::SetHostname is OK for us
+
+ // Link::Link::GetPort is OK for us
+ // Link::Link::SetPort is OK for us
+
+ // Link::Link::GetPathname is OK for us
+ // Link::Link::SetPathname is OK for us
+
+ // Link::Link::GetSearch is OK for us
+ // Link::Link::SetSearch is OK for us
+
+ // Link::Link::GetHash is OK for us
+ // Link::Link::SetHash is OK for us
+
+ // The XPCOM URI decomposition attributes are fine for us
+ void GetCoords(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::coords, aValue);
+ }
+ void SetCoords(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::coords, aValue, rv);
+ }
+ void GetCharset(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::charset, aValue);
+ }
+ void SetCharset(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::charset, aValue, rv);
+ }
+ void GetName(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::name, aValue);
+ }
+ void SetName(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::name, aValue, rv);
+ }
+ void GetRev(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::rev, aValue);
+ }
+ void SetRev(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::rev, aValue, rv);
+ }
+ void GetShape(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::shape, aValue);
+ }
+ void SetShape(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::shape, aValue, rv);
+ }
+ void Stringify(nsAString& aResult)
+ {
+ GetHref(aResult);
+ }
+
+ static DOMTokenListSupportedToken sSupportedRelValues[];
+
+protected:
+ virtual ~HTMLAnchorElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+ RefPtr<nsDOMTokenList > mRelList;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLAnchorElement_h
diff --git a/dom/html/HTMLAreaElement.cpp b/dom/html/HTMLAreaElement.cpp
new file mode 100644
index 000000000..098081b8b
--- /dev/null
+++ b/dom/html/HTMLAreaElement.cpp
@@ -0,0 +1,263 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/HTMLAreaElement.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/HTMLAnchorElement.h"
+#include "mozilla/dom/HTMLAreaElementBinding.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/MemoryReporting.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Area)
+
+namespace mozilla {
+namespace dom {
+
+HTMLAreaElement::HTMLAreaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ , Link(this)
+{
+}
+
+HTMLAreaElement::~HTMLAreaElement()
+{
+}
+
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLAreaElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLAreaElement,
+ nsIDOMHTMLAreaElement,
+ Link)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLElement)
+
+NS_IMPL_ADDREF_INHERITED(HTMLAreaElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLAreaElement, Element)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLAreaElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLAreaElement,
+ nsGenericHTMLElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelList)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLAreaElement,
+ nsGenericHTMLElement)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRelList)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ELEMENT_CLONE(HTMLAreaElement)
+
+
+NS_IMPL_STRING_ATTR(HTMLAreaElement, Alt, alt)
+NS_IMPL_STRING_ATTR(HTMLAreaElement, Coords, coords)
+NS_IMPL_URI_ATTR(HTMLAreaElement, Href, href)
+NS_IMPL_BOOL_ATTR(HTMLAreaElement, NoHref, nohref)
+NS_IMPL_STRING_ATTR(HTMLAreaElement, Shape, shape)
+NS_IMPL_STRING_ATTR(HTMLAreaElement, Download, download)
+
+int32_t
+HTMLAreaElement::TabIndexDefault()
+{
+ return 0;
+}
+
+NS_IMETHODIMP
+HTMLAreaElement::GetTarget(nsAString& aValue)
+{
+ if (!GetAttr(kNameSpaceID_None, nsGkAtoms::target, aValue)) {
+ GetBaseTarget(aValue);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLAreaElement::SetTarget(const nsAString& aValue)
+{
+ return SetAttr(kNameSpaceID_None, nsGkAtoms::target, aValue, true);
+}
+
+nsresult
+HTMLAreaElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ return PreHandleEventForAnchors(aVisitor);
+}
+
+nsresult
+HTMLAreaElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ return PostHandleEventForAnchors(aVisitor);
+}
+
+bool
+HTMLAreaElement::IsLink(nsIURI** aURI) const
+{
+ return IsHTMLLink(aURI);
+}
+
+void
+HTMLAreaElement::GetLinkTarget(nsAString& aTarget)
+{
+ GetAttr(kNameSpaceID_None, nsGkAtoms::target, aTarget);
+ if (aTarget.IsEmpty()) {
+ GetBaseTarget(aTarget);
+ }
+}
+
+nsDOMTokenList*
+HTMLAreaElement::RelList()
+{
+ if (!mRelList) {
+ mRelList = new nsDOMTokenList(this, nsGkAtoms::rel,
+ HTMLAnchorElement::sSupportedRelValues);
+ }
+ return mRelList;
+}
+
+nsresult
+HTMLAreaElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ Link::ResetLinkState(false, Link::ElementHasHref());
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIDocument* doc = GetComposedDoc();
+ if (doc) {
+ doc->RegisterPendingLinkUpdate(this);
+ }
+ return rv;
+}
+
+void
+HTMLAreaElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ // If this link is ever reinserted into a document, it might
+ // be under a different xml:base, so forget the cached state now.
+ Link::ResetLinkState(false, Link::ElementHasHref());
+
+ // Note, we need to use OwnerDoc() here, since GetComposedDoc() might
+ // return null.
+ nsIDocument* doc = OwnerDoc();
+ if (doc) {
+ doc->UnregisterPendingLinkUpdate(this);
+ }
+
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+nsresult
+HTMLAreaElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify)
+{
+ nsresult rv =
+ nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue, aNotify);
+
+ // The ordering of the parent class's SetAttr call and Link::ResetLinkState
+ // is important here! The attribute is not set until SetAttr returns, and
+ // we will need the updated attribute value because notifying the document
+ // that content states have changed will call IntrinsicState, which will try
+ // to get updated information about the visitedness from Link.
+ if (aName == nsGkAtoms::href && aNameSpaceID == kNameSpaceID_None) {
+ Link::ResetLinkState(!!aNotify, true);
+ }
+
+ return rv;
+}
+
+nsresult
+HTMLAreaElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify)
+{
+ nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttribute,
+ aNotify);
+
+ // The ordering of the parent class's UnsetAttr call and Link::ResetLinkState
+ // is important here! The attribute is not unset until UnsetAttr returns, and
+ // we will need the updated attribute value because notifying the document
+ // that content states have changed will call IntrinsicState, which will try
+ // to get updated information about the visitedness from Link.
+ if (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
+ Link::ResetLinkState(!!aNotify, false);
+ }
+
+ return rv;
+}
+
+#define IMPL_URI_PART(_part) \
+ NS_IMETHODIMP \
+ HTMLAreaElement::Get##_part(nsAString& a##_part) \
+ { \
+ Link::Get##_part(a##_part); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP \
+ HTMLAreaElement::Set##_part(const nsAString& a##_part) \
+ { \
+ Link::Set##_part(a##_part); \
+ return NS_OK; \
+ }
+
+IMPL_URI_PART(Protocol)
+IMPL_URI_PART(Host)
+IMPL_URI_PART(Hostname)
+IMPL_URI_PART(Pathname)
+IMPL_URI_PART(Search)
+IMPL_URI_PART(Port)
+IMPL_URI_PART(Hash)
+
+#undef IMPL_URI_PART
+
+NS_IMETHODIMP
+HTMLAreaElement::ToString(nsAString& aSource)
+{
+ return GetHref(aSource);
+}
+
+NS_IMETHODIMP
+HTMLAreaElement::GetPing(nsAString& aValue)
+{
+ GetAttr(kNameSpaceID_None, nsGkAtoms::ping, aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLAreaElement::SetPing(const nsAString& aValue)
+{
+ return SetAttr(kNameSpaceID_None, nsGkAtoms::ping, aValue, true);
+}
+
+already_AddRefed<nsIURI>
+HTMLAreaElement::GetHrefURI() const
+{
+ return GetHrefURIForAnchors();
+}
+
+EventStates
+HTMLAreaElement::IntrinsicState() const
+{
+ return Link::LinkState() | nsGenericHTMLElement::IntrinsicState();
+}
+
+size_t
+HTMLAreaElement::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ return nsGenericHTMLElement::SizeOfExcludingThis(aMallocSizeOf) +
+ Link::SizeOfExcludingThis(aMallocSizeOf);
+}
+
+JSObject*
+HTMLAreaElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLAreaElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLAreaElement.h b/dom/html/HTMLAreaElement.h
new file mode 100644
index 000000000..650c0fd8f
--- /dev/null
+++ b/dom/html/HTMLAreaElement.h
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLAreaElement_h
+#define mozilla_dom_HTMLAreaElement_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Link.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+#include "nsDOMTokenList.h"
+#include "nsIDOMHTMLAreaElement.h"
+#include "nsIURL.h"
+
+class nsIDocument;
+
+namespace mozilla {
+class EventChainPostVisitor;
+class EventChainPreVisitor;
+namespace dom {
+
+class HTMLAreaElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLAreaElement,
+ public Link
+{
+public:
+ explicit HTMLAreaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // CC
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLAreaElement,
+ nsGenericHTMLElement)
+
+ // DOM memory reporter participant
+ NS_DECL_SIZEOF_EXCLUDING_THIS
+
+ virtual int32_t TabIndexDefault() override;
+
+ // nsIDOMHTMLAreaElement
+ NS_DECL_NSIDOMHTMLAREAELEMENT
+
+ virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) override;
+ virtual nsresult PostHandleEvent(EventChainPostVisitor& aVisitor) override;
+ virtual bool IsLink(nsIURI** aURI) const override;
+ virtual void GetLinkTarget(nsAString& aTarget) override;
+ virtual already_AddRefed<nsIURI> GetHrefURI() 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 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;
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify) override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+
+ virtual EventStates IntrinsicState() const override;
+
+ // WebIDL
+
+ // The XPCOM GetAlt is OK for us
+ void SetAlt(const nsAString& aAlt, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::alt, aAlt, aError);
+ }
+
+ // The XPCOM GetCoords is OK for us
+ void SetCoords(const nsAString& aCoords, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::coords, aCoords, aError);
+ }
+
+ // The XPCOM GetShape is OK for us
+ void SetShape(const nsAString& aShape, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::shape, aShape, aError);
+ }
+
+ // The XPCOM GetHref is OK for us
+ void SetHref(const nsAString& aHref, ErrorResult& aError)
+ {
+ aError = SetHref(aHref);
+ }
+
+ // The XPCOM GetTarget is OK for us
+ void SetTarget(const nsAString& aTarget, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::target, aTarget, aError);
+ }
+
+ // The XPCOM GetDownload is OK for us
+ void SetDownload(const nsAString& aDownload, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::download, aDownload, aError);
+ }
+
+ // The XPCOM GetPing is OK for us
+ void SetPing(const nsAString& aPing, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::ping, aPing, aError);
+ }
+
+ void GetRel(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::rel, aValue);
+ }
+
+ void SetRel(const nsAString& aRel, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::rel, aRel, aError);
+ }
+ nsDOMTokenList* RelList();
+
+ void SetReferrerPolicy(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::referrerpolicy, aValue, rv);
+ }
+ void GetReferrerPolicy(nsAString& aReferrer)
+ {
+ GetEnumAttr(nsGkAtoms::referrerpolicy, EmptyCString().get(), aReferrer);
+ }
+
+ // The Link::GetOrigin is OK for us
+
+ // Link::Link::GetProtocol is OK for us
+ // Link::Link::SetProtocol is OK for us
+
+ // The Link::GetUsername is OK for us
+ // The Link::SetUsername is OK for us
+
+ // The Link::GetPassword is OK for us
+ // The Link::SetPassword is OK for us
+
+ // Link::Link::GetHost is OK for us
+ // Link::Link::SetHost is OK for us
+
+ // Link::Link::GetHostname is OK for us
+ // Link::Link::SetHostname is OK for us
+
+ // Link::Link::GetPort is OK for us
+ // Link::Link::SetPort is OK for us
+
+ // Link::Link::GetPathname is OK for us
+ // Link::Link::SetPathname is OK for us
+
+ // Link::Link::GetSearch is OK for us
+ // Link::Link::SetSearch is OK for us
+
+ // Link::Link::GetHash is OK for us
+ // Link::Link::SetHash is OK for us
+
+ // The Link::GetSearchParams is OK for us
+
+ bool NoHref() const
+ {
+ return GetBoolAttr(nsGkAtoms::nohref);
+ }
+
+ void SetNoHref(bool aValue, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::nohref, aValue, aError);
+ }
+
+ void Stringify(nsAString& aResult)
+ {
+ GetHref(aResult);
+ }
+
+protected:
+ virtual ~HTMLAreaElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ RefPtr<nsDOMTokenList > mRelList;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_HTMLAreaElement_h */
diff --git a/dom/html/HTMLAudioElement.cpp b/dom/html/HTMLAudioElement.cpp
new file mode 100644
index 000000000..0722c7b15
--- /dev/null
+++ b/dom/html/HTMLAudioElement.cpp
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/HTMLAudioElement.h"
+#include "mozilla/dom/HTMLAudioElementBinding.h"
+#include "nsError.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+#include "nsIDocument.h"
+#include "jsfriendapi.h"
+#include "nsContentUtils.h"
+#include "nsJSUtils.h"
+#include "AudioSampleFormat.h"
+#include <algorithm>
+#include "nsComponentManagerUtils.h"
+#include "nsIHttpChannel.h"
+#include "mozilla/dom/TimeRanges.h"
+#include "AudioStream.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Audio)
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ELEMENT_CLONE(HTMLAudioElement)
+
+HTMLAudioElement::HTMLAudioElement(already_AddRefed<NodeInfo>& aNodeInfo)
+ : HTMLMediaElement(aNodeInfo)
+{
+}
+
+HTMLAudioElement::~HTMLAudioElement()
+{
+}
+
+bool
+HTMLAudioElement::IsInteractiveHTMLContent(bool aIgnoreTabindex) const
+{
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::controls) ||
+ HTMLMediaElement::IsInteractiveHTMLContent(aIgnoreTabindex);
+}
+
+already_AddRefed<HTMLAudioElement>
+HTMLAudioElement::Audio(const GlobalObject& aGlobal,
+ const Optional<nsAString>& aSrc,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal.GetAsSupports());
+ nsIDocument* doc;
+ if (!win || !(doc = win->GetExtantDoc())) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ already_AddRefed<mozilla::dom::NodeInfo> nodeInfo =
+ doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::audio, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+
+ RefPtr<HTMLAudioElement> audio = new HTMLAudioElement(nodeInfo);
+ audio->SetHTMLAttr(nsGkAtoms::preload, NS_LITERAL_STRING("auto"), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (aSrc.WasPassed()) {
+ aRv = audio->SetSrc(aSrc.Value());
+ }
+
+ return audio.forget();
+}
+
+nsresult HTMLAudioElement::SetAcceptHeader(nsIHttpChannel* aChannel)
+{
+ nsAutoCString value(
+ "audio/webm,"
+ "audio/ogg,"
+ "audio/wav,"
+ "audio/*;q=0.9,"
+ "application/ogg;q=0.7,"
+ "video/*;q=0.6,*/*;q=0.5");
+
+ return aChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
+ value,
+ false);
+}
+
+JSObject*
+HTMLAudioElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLAudioElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLAudioElement.h b/dom/html/HTMLAudioElement.h
new file mode 100644
index 000000000..138131cd9
--- /dev/null
+++ b/dom/html/HTMLAudioElement.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef mozilla_dom_HTMLAudioElement_h
+#define mozilla_dom_HTMLAudioElement_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/TypedArray.h"
+
+typedef uint16_t nsMediaNetworkState;
+typedef uint16_t nsMediaReadyState;
+
+namespace mozilla {
+namespace dom {
+
+class HTMLAudioElement final : public HTMLMediaElement
+{
+public:
+ typedef mozilla::dom::NodeInfo NodeInfo;
+
+ explicit HTMLAudioElement(already_AddRefed<NodeInfo>& aNodeInfo);
+
+ // Element
+ virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
+
+ // nsIDOMHTMLMediaElement
+ using HTMLMediaElement::GetPaused;
+
+ virtual nsresult Clone(NodeInfo *aNodeInfo, nsINode **aResult) const override;
+ virtual nsresult SetAcceptHeader(nsIHttpChannel* aChannel) override;
+
+ virtual nsIDOMNode* AsDOMNode() override { return this; }
+
+ // WebIDL
+
+ static already_AddRefed<HTMLAudioElement>
+ Audio(const GlobalObject& aGlobal,
+ const Optional<nsAString>& aSrc, ErrorResult& aRv);
+
+protected:
+ virtual ~HTMLAudioElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLAudioElement_h
diff --git a/dom/html/HTMLBRElement.cpp b/dom/html/HTMLBRElement.cpp
new file mode 100644
index 000000000..db51a4d7e
--- /dev/null
+++ b/dom/html/HTMLBRElement.cpp
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/HTMLBRElement.h"
+#include "mozilla/dom/HTMLBRElementBinding.h"
+
+#include "nsAttrValueInlines.h"
+#include "nsStyleConsts.h"
+#include "nsMappedAttributes.h"
+#include "nsRuleData.h"
+
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(BR)
+
+namespace mozilla {
+namespace dom {
+
+HTMLBRElement::HTMLBRElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+{
+}
+
+HTMLBRElement::~HTMLBRElement()
+{
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLBRElement)
+
+static const nsAttrValue::EnumTable kClearTable[] = {
+ { "left", StyleClear::Left },
+ { "right", StyleClear::Right },
+ { "all", StyleClear::Both },
+ { "both", StyleClear::Both },
+ { nullptr, 0 }
+};
+
+bool
+HTMLBRElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aAttribute == nsGkAtoms::clear && aNamespaceID == kNameSpaceID_None) {
+ return aResult.ParseEnumValue(aValue, kClearTable, false);
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLBRElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Display)) {
+ nsCSSValue* clear = aData->ValueForClear();
+ if (clear->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::clear);
+ if (value && value->Type() == nsAttrValue::eEnum)
+ clear->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
+ }
+ }
+
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLBRElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::clear },
+ { nullptr }
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+nsMapRuleToAttributesFunc
+HTMLBRElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+JSObject*
+HTMLBRElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLBRElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLBRElement.h b/dom/html/HTMLBRElement.h
new file mode 100644
index 000000000..cf7aaaedc
--- /dev/null
+++ b/dom/html/HTMLBRElement.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_HTMLBRElement_h
+#define mozilla_dom_HTMLBRElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLBRElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLBRElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ bool Clear()
+ {
+ return GetBoolAttr(nsGkAtoms::clear);
+ }
+ void SetClear(const nsAString& aClear, ErrorResult& aError)
+ {
+ return SetHTMLAttr(nsGkAtoms::clear, aClear, aError);
+ }
+ void GetClear(DOMString& aClear) const
+ {
+ return GetHTMLAttr(nsGkAtoms::clear, aClear);
+ }
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ virtual ~HTMLBRElement();
+
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
+
diff --git a/dom/html/HTMLBodyElement.cpp b/dom/html/HTMLBodyElement.cpp
new file mode 100644
index 000000000..6230cb6ca
--- /dev/null
+++ b/dom/html/HTMLBodyElement.cpp
@@ -0,0 +1,530 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HTMLBodyElement.h"
+#include "mozilla/dom/HTMLBodyElementBinding.h"
+#include "nsAttrValueInlines.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsIPresShell.h"
+#include "nsIDocument.h"
+#include "nsHTMLStyleSheet.h"
+#include "nsIEditor.h"
+#include "nsMappedAttributes.h"
+#include "nsRuleData.h"
+#include "nsIDocShell.h"
+#include "nsRuleWalker.h"
+#include "nsGlobalWindow.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Body)
+
+namespace mozilla {
+namespace dom {
+
+//----------------------------------------------------------------------
+
+BodyRule::BodyRule(HTMLBodyElement* aPart)
+ : mPart(aPart)
+{
+}
+
+BodyRule::~BodyRule()
+{
+}
+
+NS_IMPL_ISUPPORTS(BodyRule, nsIStyleRule)
+
+/* virtual */ void
+BodyRule::MapRuleInfoInto(nsRuleData* aData)
+{
+ if (!(aData->mSIDs & NS_STYLE_INHERIT_BIT(Margin)) || !mPart)
+ return; // We only care about margins.
+
+ int32_t bodyMarginWidth = -1;
+ int32_t bodyMarginHeight = -1;
+ int32_t bodyTopMargin = -1;
+ int32_t bodyBottomMargin = -1;
+ int32_t bodyLeftMargin = -1;
+ int32_t bodyRightMargin = -1;
+
+ // check the mode (fortunately, the ruleData has a presContext for us to use!)
+ NS_ASSERTION(aData->mPresContext, "null presContext in ruleNode was unexpected");
+ nsCompatibility mode = aData->mPresContext->CompatibilityMode();
+
+
+ const nsAttrValue* value;
+ if (mPart->GetAttrCount() > 0) {
+ // if marginwidth/marginheight are set, reflect them as 'margin'
+ value = mPart->GetParsedAttr(nsGkAtoms::marginwidth);
+ if (value && value->Type() == nsAttrValue::eInteger) {
+ bodyMarginWidth = value->GetIntegerValue();
+ if (bodyMarginWidth < 0) bodyMarginWidth = 0;
+ nsCSSValue* marginLeft = aData->ValueForMarginLeft();
+ if (marginLeft->GetUnit() == eCSSUnit_Null)
+ marginLeft->SetFloatValue((float)bodyMarginWidth, eCSSUnit_Pixel);
+ nsCSSValue* marginRight = aData->ValueForMarginRight();
+ if (marginRight->GetUnit() == eCSSUnit_Null)
+ marginRight->SetFloatValue((float)bodyMarginWidth, eCSSUnit_Pixel);
+ }
+
+ value = mPart->GetParsedAttr(nsGkAtoms::marginheight);
+ if (value && value->Type() == nsAttrValue::eInteger) {
+ bodyMarginHeight = value->GetIntegerValue();
+ if (bodyMarginHeight < 0) bodyMarginHeight = 0;
+ nsCSSValue* marginTop = aData->ValueForMarginTop();
+ if (marginTop->GetUnit() == eCSSUnit_Null)
+ marginTop->SetFloatValue((float)bodyMarginHeight, eCSSUnit_Pixel);
+ nsCSSValue* marginBottom = aData->ValueForMarginBottom();
+ if (marginBottom->GetUnit() == eCSSUnit_Null)
+ marginBottom->SetFloatValue((float)bodyMarginHeight, eCSSUnit_Pixel);
+ }
+
+ // topmargin (IE-attribute)
+ value = mPart->GetParsedAttr(nsGkAtoms::topmargin);
+ if (value && value->Type() == nsAttrValue::eInteger) {
+ bodyTopMargin = value->GetIntegerValue();
+ if (bodyTopMargin < 0) bodyTopMargin = 0;
+ nsCSSValue* marginTop = aData->ValueForMarginTop();
+ if (marginTop->GetUnit() == eCSSUnit_Null)
+ marginTop->SetFloatValue((float)bodyTopMargin, eCSSUnit_Pixel);
+ }
+
+ // bottommargin (IE-attribute)
+ value = mPart->GetParsedAttr(nsGkAtoms::bottommargin);
+ if (value && value->Type() == nsAttrValue::eInteger) {
+ bodyBottomMargin = value->GetIntegerValue();
+ if (bodyBottomMargin < 0) bodyBottomMargin = 0;
+ nsCSSValue* marginBottom = aData->ValueForMarginBottom();
+ if (marginBottom->GetUnit() == eCSSUnit_Null)
+ marginBottom->SetFloatValue((float)bodyBottomMargin, eCSSUnit_Pixel);
+ }
+
+ // leftmargin (IE-attribute)
+ value = mPart->GetParsedAttr(nsGkAtoms::leftmargin);
+ if (value && value->Type() == nsAttrValue::eInteger) {
+ bodyLeftMargin = value->GetIntegerValue();
+ if (bodyLeftMargin < 0) bodyLeftMargin = 0;
+ nsCSSValue* marginLeft = aData->ValueForMarginLeft();
+ if (marginLeft->GetUnit() == eCSSUnit_Null)
+ marginLeft->SetFloatValue((float)bodyLeftMargin, eCSSUnit_Pixel);
+ }
+
+ // rightmargin (IE-attribute)
+ value = mPart->GetParsedAttr(nsGkAtoms::rightmargin);
+ if (value && value->Type() == nsAttrValue::eInteger) {
+ bodyRightMargin = value->GetIntegerValue();
+ if (bodyRightMargin < 0) bodyRightMargin = 0;
+ nsCSSValue* marginRight = aData->ValueForMarginRight();
+ if (marginRight->GetUnit() == eCSSUnit_Null)
+ marginRight->SetFloatValue((float)bodyRightMargin, eCSSUnit_Pixel);
+ }
+
+ }
+
+ // if marginwidth or marginheight is set in the <frame> and not set in the <body>
+ // reflect them as margin in the <body>
+ if (bodyMarginWidth == -1 || bodyMarginHeight == -1) {
+ nsCOMPtr<nsIDocShell> docShell(aData->mPresContext->GetDocShell());
+ if (docShell) {
+ nscoord frameMarginWidth=-1; // default value
+ nscoord frameMarginHeight=-1; // default value
+ docShell->GetMarginWidth(&frameMarginWidth); // -1 indicates not set
+ docShell->GetMarginHeight(&frameMarginHeight);
+ if ((frameMarginWidth >= 0) && (bodyMarginWidth == -1)) { // set in <frame> & not in <body>
+ if (eCompatibility_NavQuirks == mode) {
+ if ((bodyMarginHeight == -1) && (0 > frameMarginHeight)) // nav quirk
+ frameMarginHeight = 0;
+ }
+ }
+ if ((frameMarginHeight >= 0) && (bodyMarginHeight == -1)) { // set in <frame> & not in <body>
+ if (eCompatibility_NavQuirks == mode) {
+ if ((bodyMarginWidth == -1) && (0 > frameMarginWidth)) // nav quirk
+ frameMarginWidth = 0;
+ }
+ }
+
+ if ((bodyMarginWidth == -1) && (frameMarginWidth >= 0)) {
+ nsCSSValue* marginLeft = aData->ValueForMarginLeft();
+ if (marginLeft->GetUnit() == eCSSUnit_Null)
+ marginLeft->SetFloatValue((float)frameMarginWidth, eCSSUnit_Pixel);
+ nsCSSValue* marginRight = aData->ValueForMarginRight();
+ if (marginRight->GetUnit() == eCSSUnit_Null)
+ marginRight->SetFloatValue((float)frameMarginWidth, eCSSUnit_Pixel);
+ }
+
+ if ((bodyMarginHeight == -1) && (frameMarginHeight >= 0)) {
+ nsCSSValue* marginTop = aData->ValueForMarginTop();
+ if (marginTop->GetUnit() == eCSSUnit_Null)
+ marginTop->SetFloatValue((float)frameMarginHeight, eCSSUnit_Pixel);
+ nsCSSValue* marginBottom = aData->ValueForMarginBottom();
+ if (marginBottom->GetUnit() == eCSSUnit_Null)
+ marginBottom->SetFloatValue((float)frameMarginHeight, eCSSUnit_Pixel);
+ }
+ }
+ }
+}
+
+/* virtual */ bool
+BodyRule::MightMapInheritedStyleData()
+{
+ return false;
+}
+
+/* virtual */ bool
+BodyRule::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue)
+{
+ MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet");
+ return false;
+}
+
+#ifdef DEBUG
+/* virtual */ void
+BodyRule::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString indent;
+ for (int32_t index = aIndent; --index >= 0; ) {
+ indent.AppendLiteral(" ");
+ }
+ fprintf_stderr(out, "%s[body rule] {}\n", indent.get());
+}
+#endif
+
+//----------------------------------------------------------------------
+
+HTMLBodyElement::~HTMLBodyElement()
+{
+ if (mContentStyleRule) {
+ mContentStyleRule->mPart = nullptr;
+ }
+}
+
+JSObject*
+HTMLBodyElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLBodyElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLBodyElement, nsGenericHTMLElement,
+ nsIDOMHTMLBodyElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLBodyElement)
+
+NS_IMETHODIMP
+HTMLBodyElement::SetBackground(const nsAString& aBackground)
+{
+ ErrorResult rv;
+ SetBackground(aBackground, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLBodyElement::GetBackground(nsAString& aBackground)
+{
+ DOMString background;
+ GetBackground(background);
+ background.ToString(aBackground);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLBodyElement::SetVLink(const nsAString& aVLink)
+{
+ ErrorResult rv;
+ SetVLink(aVLink, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLBodyElement::GetVLink(nsAString& aVLink)
+{
+ DOMString vLink;
+ GetVLink(vLink);
+ vLink.ToString(aVLink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLBodyElement::SetALink(const nsAString& aALink)
+{
+ ErrorResult rv;
+ SetALink(aALink, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLBodyElement::GetALink(nsAString& aALink)
+{
+ DOMString aLink;
+ GetALink(aLink);
+ aLink.ToString(aALink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLBodyElement::SetLink(const nsAString& aLink)
+{
+ ErrorResult rv;
+ SetLink(aLink, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLBodyElement::GetLink(nsAString& aLink)
+{
+ DOMString link;
+ GetLink(link);
+ link.ToString(aLink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLBodyElement::SetText(const nsAString& aText)
+{
+ ErrorResult rv;
+ SetText(aText, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLBodyElement::GetText(nsAString& aText)
+{
+ DOMString text;
+ GetText(text);
+ text.ToString(aText);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLBodyElement::SetBgColor(const nsAString& aBgColor)
+{
+ ErrorResult rv;
+ SetBgColor(aBgColor, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLBodyElement::GetBgColor(nsAString& aBgColor)
+{
+ DOMString bgColor;
+ GetBgColor(bgColor);
+ bgColor.ToString(aBgColor);
+ return NS_OK;
+}
+
+bool
+HTMLBodyElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::bgcolor ||
+ aAttribute == nsGkAtoms::text ||
+ aAttribute == nsGkAtoms::link ||
+ aAttribute == nsGkAtoms::alink ||
+ aAttribute == nsGkAtoms::vlink) {
+ return aResult.ParseColor(aValue);
+ }
+ if (aAttribute == nsGkAtoms::marginwidth ||
+ aAttribute == nsGkAtoms::marginheight ||
+ aAttribute == nsGkAtoms::topmargin ||
+ aAttribute == nsGkAtoms::bottommargin ||
+ aAttribute == nsGkAtoms::leftmargin ||
+ aAttribute == nsGkAtoms::rightmargin) {
+ return aResult.ParseIntWithBounds(aValue, 0);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseBackgroundAttribute(aNamespaceID,
+ aAttribute, aValue,
+ aResult) ||
+ nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLBodyElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ if (mContentStyleRule) {
+ mContentStyleRule->mPart = nullptr;
+ mContentStyleRule = nullptr;
+ }
+
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+void
+HTMLBodyElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Display)) {
+ // When display if first asked for, go ahead and get our colors set up.
+ nsIPresShell *presShell = aData->mPresContext->GetPresShell();
+ if (presShell) {
+ nsIDocument *doc = presShell->GetDocument();
+ if (doc) {
+ nsHTMLStyleSheet* styleSheet = doc->GetAttributeStyleSheet();
+ if (styleSheet) {
+ const nsAttrValue* value;
+ nscolor color;
+ value = aAttributes->GetAttr(nsGkAtoms::link);
+ if (value && value->GetColorValue(color)) {
+ styleSheet->SetLinkColor(color);
+ }
+
+ value = aAttributes->GetAttr(nsGkAtoms::alink);
+ if (value && value->GetColorValue(color)) {
+ styleSheet->SetActiveLinkColor(color);
+ }
+
+ value = aAttributes->GetAttr(nsGkAtoms::vlink);
+ if (value && value->GetColorValue(color)) {
+ styleSheet->SetVisitedLinkColor(color);
+ }
+ }
+ }
+ }
+ }
+
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Color)) {
+ nsCSSValue *colorValue = aData->ValueForColor();
+ if (colorValue->GetUnit() == eCSSUnit_Null &&
+ aData->mPresContext->UseDocumentColors()) {
+ // color: color
+ nscolor color;
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::text);
+ if (value && value->GetColorValue(color))
+ colorValue->SetColorValue(color);
+ }
+ }
+
+ nsGenericHTMLElement::MapBackgroundAttributesInto(aAttributes, aData);
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+nsMapRuleToAttributesFunc
+HTMLBodyElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+NS_IMETHODIMP
+HTMLBodyElement::WalkContentStyleRules(nsRuleWalker* aRuleWalker)
+{
+ nsGenericHTMLElement::WalkContentStyleRules(aRuleWalker);
+
+ if (!mContentStyleRule && IsInUncomposedDoc()) {
+ // XXXbz should this use OwnerDoc() or GetComposedDoc()?
+ // sXBL/XBL2 issue!
+ mContentStyleRule = new BodyRule(this);
+ }
+ if (aRuleWalker && mContentStyleRule) {
+ aRuleWalker->Forward(mContentStyleRule);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+HTMLBodyElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::link },
+ { &nsGkAtoms::vlink },
+ { &nsGkAtoms::alink },
+ { &nsGkAtoms::text },
+ // These aren't mapped through attribute mapping, but they are
+ // mapped through a style rule, so it is attribute dependent style.
+ // XXXldb But we don't actually replace the body rule when we have
+ // dynamic changes...
+ { &nsGkAtoms::marginwidth },
+ { &nsGkAtoms::marginheight },
+ { nullptr },
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap,
+ sBackgroundAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+already_AddRefed<nsIEditor>
+HTMLBodyElement::GetAssociatedEditor()
+{
+ nsCOMPtr<nsIEditor> editor = GetEditorInternal();
+ if (editor) {
+ return editor.forget();
+ }
+
+ // Make sure this is the actual body of the document
+ if (!IsCurrentBodyElement()) {
+ return nullptr;
+ }
+
+ // For designmode, try to get document's editor
+ nsPresContext* presContext = GetPresContext(eForComposedDoc);
+ if (!presContext) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = presContext->GetDocShell();
+ if (!docShell) {
+ return nullptr;
+ }
+
+ docShell->GetEditor(getter_AddRefs(editor));
+ return editor.forget();
+}
+
+bool
+HTMLBodyElement::IsEventAttributeName(nsIAtom *aName)
+{
+ return nsContentUtils::IsEventAttributeName(aName,
+ EventNameType_HTML |
+ EventNameType_HTMLBodyOrFramesetOnly);
+}
+
+#define EVENT(name_, id_, type_, struct_) /* nothing; handled by the superclass */
+// nsGenericHTMLElement::GetOnError returns
+// already_AddRefed<EventHandlerNonNull> while other getters return
+// EventHandlerNonNull*, so allow passing in the type to use here.
+#define WINDOW_EVENT_HELPER(name_, type_) \
+ type_* \
+ HTMLBodyElement::GetOn##name_() \
+ { \
+ if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \
+ nsGlobalWindow* globalWin = nsGlobalWindow::Cast(win); \
+ return globalWin->GetOn##name_(); \
+ } \
+ return nullptr; \
+ } \
+ void \
+ HTMLBodyElement::SetOn##name_(type_* handler) \
+ { \
+ nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \
+ if (!win) { \
+ return; \
+ } \
+ \
+ nsGlobalWindow* globalWin = nsGlobalWindow::Cast(win); \
+ return globalWin->SetOn##name_(handler); \
+ }
+#define WINDOW_EVENT(name_, id_, type_, struct_) \
+ WINDOW_EVENT_HELPER(name_, EventHandlerNonNull)
+#define BEFOREUNLOAD_EVENT(name_, id_, type_, struct_) \
+ WINDOW_EVENT_HELPER(name_, OnBeforeUnloadEventHandlerNonNull)
+#include "mozilla/EventNameList.h" // IWYU pragma: keep
+#undef BEFOREUNLOAD_EVENT
+#undef WINDOW_EVENT
+#undef WINDOW_EVENT_HELPER
+#undef EVENT
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLBodyElement.h b/dom/html/HTMLBodyElement.h
new file mode 100644
index 000000000..436dc4cba
--- /dev/null
+++ b/dom/html/HTMLBodyElement.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 HTMLBodyElement_h___
+#define HTMLBodyElement_h___
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIDOMHTMLBodyElement.h"
+#include "nsIStyleRule.h"
+
+namespace mozilla {
+namespace dom {
+
+class OnBeforeUnloadEventHandlerNonNull;
+class HTMLBodyElement;
+
+class BodyRule: public nsIStyleRule
+{
+ virtual ~BodyRule();
+
+public:
+ explicit BodyRule(HTMLBodyElement* aPart);
+
+ NS_DECL_ISUPPORTS
+
+ // nsIStyleRule interface
+ 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
+
+ HTMLBodyElement* mPart; // not ref-counted, cleared by content
+};
+
+class HTMLBodyElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLBodyElement
+{
+public:
+ using Element::GetText;
+ using Element::SetText;
+
+ explicit HTMLBodyElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLBodyElement
+ NS_DECL_NSIDOMHTMLBODYELEMENT
+
+ // Event listener stuff; we need to declare only the ones we need to
+ // forward to window that don't come from nsIDOMHTMLBodyElement.
+#define EVENT(name_, id_, type_, struct_) /* nothing; handled by the shim */
+#define WINDOW_EVENT_HELPER(name_, type_) \
+ type_* GetOn##name_(); \
+ void SetOn##name_(type_* handler);
+#define WINDOW_EVENT(name_, id_, type_, struct_) \
+ WINDOW_EVENT_HELPER(name_, EventHandlerNonNull)
+#define BEFOREUNLOAD_EVENT(name_, id_, type_, struct_) \
+ WINDOW_EVENT_HELPER(name_, OnBeforeUnloadEventHandlerNonNull)
+#include "mozilla/EventNameList.h" // IWYU pragma: keep
+#undef BEFOREUNLOAD_EVENT
+#undef WINDOW_EVENT
+#undef WINDOW_EVENT_HELPER
+#undef EVENT
+
+ void GetText(DOMString& aText)
+ {
+ GetHTMLAttr(nsGkAtoms::text, aText);
+ }
+ void SetText(const nsAString& aText, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::text, aText, aError);
+ }
+ void GetLink(DOMString& aLink)
+ {
+ GetHTMLAttr(nsGkAtoms::link, aLink);
+ }
+ void SetLink(const nsAString& aLink, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::link, aLink, aError);
+ }
+ void GetVLink(DOMString& aVLink)
+ {
+ GetHTMLAttr(nsGkAtoms::vlink, aVLink);
+ }
+ void SetVLink(const nsAString& aVLink, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::vlink, aVLink, aError);
+ }
+ void GetALink(DOMString& aALink)
+ {
+ GetHTMLAttr(nsGkAtoms::alink, aALink);
+ }
+ void SetALink(const nsAString& aALink, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::alink, aALink, aError);
+ }
+ void GetBgColor(DOMString& aBgColor)
+ {
+ GetHTMLAttr(nsGkAtoms::bgcolor, aBgColor);
+ }
+ void SetBgColor(const nsAString& aBgColor, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::bgcolor, aBgColor, aError);
+ }
+ void GetBackground(DOMString& aBackground)
+ {
+ GetHTMLAttr(nsGkAtoms::background, aBackground);
+ }
+ void SetBackground(const nsAString& aBackground, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::background, aBackground, aError);
+ }
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ NS_IMETHOD WalkContentStyleRules(nsRuleWalker* aRuleWalker) override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+ virtual already_AddRefed<nsIEditor> GetAssociatedEditor() override;
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ virtual bool IsEventAttributeName(nsIAtom* aName) override;
+
+protected:
+ virtual ~HTMLBodyElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ RefPtr<BodyRule> mContentStyleRule;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* HTMLBodyElement_h___ */
diff --git a/dom/html/HTMLButtonElement.cpp b/dom/html/HTMLButtonElement.cpp
new file mode 100644
index 000000000..435aa9f7f
--- /dev/null
+++ b/dom/html/HTMLButtonElement.cpp
@@ -0,0 +1,519 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLButtonElement.h"
+
+#include "HTMLFormSubmissionConstants.h"
+#include "mozilla/dom/HTMLButtonElementBinding.h"
+#include "mozilla/dom/HTMLFormSubmission.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "nsAttrValueInlines.h"
+#include "nsGkAtoms.h"
+#include "nsIPresShell.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsIFormControl.h"
+#include "nsIURL.h"
+#include "nsIFrame.h"
+#include "nsIFormControlFrame.h"
+#include "nsIDOMEvent.h"
+#include "nsIDocument.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEvents.h"
+#include "nsUnicharUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsPresState.h"
+#include "nsError.h"
+#include "nsFocusManager.h"
+#include "mozilla/dom/HTMLFormElement.h"
+#include "mozAutoDocUpdate.h"
+
+#define NS_IN_SUBMIT_CLICK (1 << 0)
+#define NS_OUTER_ACTIVATE_EVENT (1 << 1)
+
+NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Button)
+
+namespace mozilla {
+namespace dom {
+
+static const nsAttrValue::EnumTable kButtonTypeTable[] = {
+ { "button", NS_FORM_BUTTON_BUTTON },
+ { "reset", NS_FORM_BUTTON_RESET },
+ { "submit", NS_FORM_BUTTON_SUBMIT },
+ { nullptr, 0 }
+};
+
+// Default type is 'submit'.
+static const nsAttrValue::EnumTable* kButtonDefaultType = &kButtonTypeTable[2];
+
+
+// Construction, destruction
+HTMLButtonElement::HTMLButtonElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser)
+ : nsGenericHTMLFormElementWithState(aNodeInfo),
+ mType(kButtonDefaultType->value),
+ mDisabledChanged(false),
+ mInInternalActivate(false),
+ mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT))
+{
+ // Set up our default state: enabled
+ AddStatesSilently(NS_EVENT_STATE_ENABLED);
+}
+
+HTMLButtonElement::~HTMLButtonElement()
+{
+}
+
+// nsISupports
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLButtonElement,
+ nsGenericHTMLFormElementWithState,
+ mValidity)
+
+NS_IMPL_ADDREF_INHERITED(HTMLButtonElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLButtonElement, Element)
+
+
+// QueryInterface implementation for HTMLButtonElement
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLButtonElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLButtonElement,
+ nsIDOMHTMLButtonElement,
+ nsIConstraintValidation)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElementWithState)
+
+// nsIConstraintValidation
+NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(HTMLButtonElement)
+
+NS_IMETHODIMP
+HTMLButtonElement::SetCustomValidity(const nsAString& aError)
+{
+ nsIConstraintValidation::SetCustomValidity(aError);
+
+ UpdateState(true);
+
+ return NS_OK;
+}
+
+void
+HTMLButtonElement::UpdateBarredFromConstraintValidation()
+{
+ SetBarredFromConstraintValidation(mType == NS_FORM_BUTTON_BUTTON ||
+ mType == NS_FORM_BUTTON_RESET ||
+ IsDisabled());
+}
+
+void
+HTMLButtonElement::FieldSetDisabledChanged(bool aNotify)
+{
+ UpdateBarredFromConstraintValidation();
+
+ nsGenericHTMLFormElementWithState::FieldSetDisabledChanged(aNotify);
+}
+
+// nsIDOMHTMLButtonElement
+
+NS_IMPL_ELEMENT_CLONE(HTMLButtonElement)
+
+
+// nsIDOMHTMLButtonElement
+
+NS_IMETHODIMP
+HTMLButtonElement::GetForm(nsIDOMHTMLFormElement** aForm)
+{
+ return nsGenericHTMLFormElementWithState::GetForm(aForm);
+}
+
+NS_IMPL_BOOL_ATTR(HTMLButtonElement, Autofocus, autofocus)
+NS_IMPL_BOOL_ATTR(HTMLButtonElement, Disabled, disabled)
+NS_IMPL_ACTION_ATTR(HTMLButtonElement, FormAction, formaction)
+NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLButtonElement, FormEnctype, formenctype,
+ "", kFormDefaultEnctype->tag)
+NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLButtonElement, FormMethod, formmethod,
+ "", kFormDefaultMethod->tag)
+NS_IMPL_BOOL_ATTR(HTMLButtonElement, FormNoValidate, formnovalidate)
+NS_IMPL_STRING_ATTR(HTMLButtonElement, FormTarget, formtarget)
+NS_IMPL_STRING_ATTR(HTMLButtonElement, Name, name)
+NS_IMPL_STRING_ATTR(HTMLButtonElement, Value, value)
+NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLButtonElement, Type, type,
+ kButtonDefaultType->tag)
+
+int32_t
+HTMLButtonElement::TabIndexDefault()
+{
+ return 0;
+}
+
+bool
+HTMLButtonElement::IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable, int32_t *aTabIndex)
+{
+ if (nsGenericHTMLFormElementWithState::IsHTMLFocusable(aWithMouse, aIsFocusable, aTabIndex)) {
+ return true;
+ }
+
+ *aIsFocusable =
+#ifdef XP_MACOSX
+ (!aWithMouse || nsFocusManager::sMouseFocusesFormControl) &&
+#endif
+ !IsDisabled();
+
+ return false;
+}
+
+bool
+HTMLButtonElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::type) {
+ // XXX ARG!! This is major evilness. ParseAttribute
+ // shouldn't set members. Override SetAttr instead
+ bool success = aResult.ParseEnumValue(aValue, kButtonTypeTable, false);
+ if (success) {
+ mType = aResult.GetEnumValue();
+ } else {
+ mType = kButtonDefaultType->value;
+ }
+
+ return success;
+ }
+
+ if (aAttribute == nsGkAtoms::formmethod) {
+ return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
+ }
+ if (aAttribute == nsGkAtoms::formenctype) {
+ return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+bool
+HTMLButtonElement::IsDisabledForEvents(EventMessage aMessage)
+{
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
+ nsIFrame* formFrame = do_QueryFrame(formControlFrame);
+ return IsElementDisabledForEvents(aMessage, formFrame);
+}
+
+nsresult
+HTMLButtonElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ aVisitor.mCanHandle = false;
+ if (IsDisabledForEvents(aVisitor.mEvent->mMessage)) {
+ return NS_OK;
+ }
+
+ // Track whether we're in the outermost Dispatch invocation that will
+ // cause activation of the input. That is, if we're a click event, or a
+ // DOMActivate that was dispatched directly, this will be set, but if we're
+ // a DOMActivate dispatched from click handling, it will not be set.
+ WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
+ bool outerActivateEvent =
+ ((mouseEvent && mouseEvent->IsLeftClickEvent()) ||
+ (aVisitor.mEvent->mMessage == eLegacyDOMActivate &&
+ !mInInternalActivate));
+
+ if (outerActivateEvent) {
+ aVisitor.mItemFlags |= NS_OUTER_ACTIVATE_EVENT;
+ if (mType == NS_FORM_BUTTON_SUBMIT && mForm) {
+ aVisitor.mItemFlags |= NS_IN_SUBMIT_CLICK;
+ // tell the form that we are about to enter a click handler.
+ // that means that if there are scripted submissions, the
+ // latest one will be deferred until after the exit point of the handler.
+ mForm->OnSubmitClickBegin(this);
+ }
+ }
+
+ return nsGenericHTMLElement::PreHandleEvent(aVisitor);
+}
+
+nsresult
+HTMLButtonElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ nsresult rv = NS_OK;
+ if (!aVisitor.mPresContext) {
+ return rv;
+ }
+
+ if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
+ WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
+ if (mouseEvent && mouseEvent->IsLeftClickEvent()) {
+ // 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;
+
+ nsCOMPtr<nsIPresShell> shell = aVisitor.mPresContext->GetPresShell();
+ if (shell) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ mInInternalActivate = true;
+ shell->HandleDOMEventWithTarget(this, &actEvent, &status);
+ mInInternalActivate = false;
+
+ // If activate is cancelled, we must do the same as when click is
+ // cancelled (revert the checkbox to its original value).
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ aVisitor.mEventStatus = status;
+ }
+ }
+ }
+ }
+
+ // mForm is null if the event handler removed us from the document (bug 194582).
+ if ((aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK) && mForm) {
+ // tell the form that we are about to exit a click handler
+ // so the form knows not to defer subsequent submissions
+ // the pending ones that were created during the handler
+ // will be flushed or forgoten.
+ mForm->OnSubmitClickEnd();
+ }
+
+ if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
+ switch (aVisitor.mEvent->mMessage) {
+ case eKeyPress:
+ case eKeyUp:
+ {
+ // For backwards compat, trigger buttons with space or enter
+ // (bug 25300)
+ WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
+ if ((keyEvent->mKeyCode == NS_VK_RETURN &&
+ eKeyPress == aVisitor.mEvent->mMessage) ||
+ (keyEvent->mKeyCode == NS_VK_SPACE &&
+ eKeyUp == aVisitor.mEvent->mMessage)) {
+ DispatchSimulatedClick(this, aVisitor.mEvent->IsTrusted(),
+ aVisitor.mPresContext);
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ if (aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT) {
+ if (mForm && (mType == NS_FORM_BUTTON_SUBMIT ||
+ mType == NS_FORM_BUTTON_RESET)) {
+ InternalFormEvent event(true,
+ (mType == NS_FORM_BUTTON_RESET) ? eFormReset : eFormSubmit);
+ event.mOriginator = this;
+ nsEventStatus status = nsEventStatus_eIgnore;
+
+ nsCOMPtr<nsIPresShell> presShell =
+ aVisitor.mPresContext->GetPresShell();
+ // If |nsIPresShell::Destroy| has been called due to
+ // handling the event, the pres context will return
+ // a null pres shell. See bug 125624.
+ //
+ // Using presShell to dispatch the event. It makes sure that
+ // event is not handled if the window is being destroyed.
+ if (presShell && (event.mMessage != eFormSubmit ||
+ mForm->SubmissionCanProceed(this))) {
+ // TODO: removing this code and have the submit event sent by the form
+ // see bug 592124.
+ // Hold a strong ref while dispatching
+ RefPtr<HTMLFormElement> form(mForm);
+ presShell->HandleDOMEventWithTarget(form, &event, &status);
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ }
+ } else if ((aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK) && mForm) {
+ // Tell the form to flush a possible pending submission.
+ // the reason is that the script returned false (the event was
+ // not ignored) so if there is a stored submission, it needs to
+ // be submitted immediatelly.
+ // Note, NS_IN_SUBMIT_CLICK is set only when we're in outer activate event.
+ mForm->FlushPendingSubmission();
+ } //if
+
+ return rv;
+}
+
+nsresult
+HTMLButtonElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv =
+ nsGenericHTMLFormElementWithState::BindToTree(aDocument, aParent, aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Update our state; we may now be the default submit element
+ UpdateState(false);
+
+ return NS_OK;
+}
+
+void
+HTMLButtonElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ nsGenericHTMLFormElementWithState::UnbindFromTree(aDeep, aNullParent);
+
+ // Update our state; we may no longer be the default submit element
+ UpdateState(false);
+}
+
+NS_IMETHODIMP
+HTMLButtonElement::Reset()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLButtonElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission)
+{
+ //
+ // We only submit if we were the button pressed
+ //
+ if (aFormSubmission->GetOriginatingElement() != this) {
+ return NS_OK;
+ }
+
+ // Disabled elements don't submit
+ if (IsDisabled()) {
+ return NS_OK;
+ }
+
+ //
+ // Get the name (if no name, no submit)
+ //
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+ if (name.IsEmpty()) {
+ return NS_OK;
+ }
+
+ //
+ // Get the value
+ //
+ nsAutoString value;
+ nsresult rv = GetValue(value);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ //
+ // Submit
+ //
+ return aFormSubmission->AddNameValuePair(name, value);
+}
+
+void
+HTMLButtonElement::DoneCreatingElement()
+{
+ if (!mInhibitStateRestoration) {
+ nsresult rv = GenerateStateKey();
+ if (NS_SUCCEEDED(rv)) {
+ RestoreFormControlState();
+ }
+ }
+}
+
+nsresult
+HTMLButtonElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify)
+{
+ if (aNotify && aName == nsGkAtoms::disabled &&
+ aNameSpaceID == kNameSpaceID_None) {
+ mDisabledChanged = true;
+ }
+
+ return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+nsresult
+HTMLButtonElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aName == nsGkAtoms::type) {
+ if (!aValue) {
+ mType = kButtonDefaultType->value;
+ }
+ }
+
+ if (aName == nsGkAtoms::type || aName == nsGkAtoms::disabled) {
+ UpdateBarredFromConstraintValidation();
+ UpdateState(aNotify);
+ }
+ }
+
+ return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+NS_IMETHODIMP
+HTMLButtonElement::SaveState()
+{
+ if (!mDisabledChanged) {
+ return NS_OK;
+ }
+
+ nsPresState* state = GetPrimaryPresState();
+ if (state) {
+ // We do not want to save the real disabled state but the disabled
+ // attribute.
+ state->SetDisabled(HasAttr(kNameSpaceID_None, nsGkAtoms::disabled));
+ }
+
+ return NS_OK;
+}
+
+bool
+HTMLButtonElement::RestoreState(nsPresState* aState)
+{
+ if (aState && aState->IsDisabledSet()) {
+ SetDisabled(aState->GetDisabled());
+ }
+
+ return false;
+}
+
+EventStates
+HTMLButtonElement::IntrinsicState() const
+{
+ EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState();
+
+ if (IsCandidateForConstraintValidation()) {
+ if (IsValid()) {
+ state |= NS_EVENT_STATE_VALID;
+ if (!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) {
+ state |= NS_EVENT_STATE_MOZ_UI_VALID;
+ }
+ } else {
+ state |= NS_EVENT_STATE_INVALID;
+ if (!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) {
+ state |= NS_EVENT_STATE_MOZ_UI_INVALID;
+ }
+ }
+ }
+
+ if (mForm && !mForm->GetValidity() && IsSubmitControl()) {
+ state |= NS_EVENT_STATE_MOZ_SUBMITINVALID;
+ }
+
+ return state;
+}
+
+JSObject*
+HTMLButtonElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLButtonElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLButtonElement.h b/dom/html/HTMLButtonElement.h
new file mode 100644
index 000000000..ecd9e03d7
--- /dev/null
+++ b/dom/html/HTMLButtonElement.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 mozilla_dom_HTMLButtonElement_h
+#define mozilla_dom_HTMLButtonElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIDOMHTMLButtonElement.h"
+#include "nsIConstraintValidation.h"
+
+namespace mozilla {
+class EventChainPostVisitor;
+class EventChainPreVisitor;
+namespace dom {
+
+class HTMLButtonElement final : public nsGenericHTMLFormElementWithState,
+ public nsIDOMHTMLButtonElement,
+ public nsIConstraintValidation
+{
+public:
+ using nsIConstraintValidation::GetValidationMessage;
+
+ explicit HTMLButtonElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser = NOT_FROM_PARSER);
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLButtonElement,
+ nsGenericHTMLFormElementWithState)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual int32_t TabIndexDefault() override;
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLButtonElement, button)
+
+ // Element
+ virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override
+ {
+ return true;
+ }
+
+ // nsIDOMHTMLButtonElement
+ NS_DECL_NSIDOMHTMLBUTTONELEMENT
+
+ // overriden nsIFormControl methods
+ NS_IMETHOD_(uint32_t) GetType() const override { return mType; }
+ NS_IMETHOD Reset() override;
+ NS_IMETHOD SubmitNamesValues(HTMLFormSubmission* aFormSubmission) override;
+ NS_IMETHOD SaveState() override;
+ bool RestoreState(nsPresState* aState) override;
+ virtual bool IsDisabledForEvents(EventMessage aMessage) override;
+
+ virtual void FieldSetDisabledChanged(bool aNotify) override;
+
+ // nsIDOMEventTarget
+ virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) override;
+ virtual nsresult PostHandleEvent(
+ EventChainPostVisitor& aVisitor) override;
+
+ // nsINode
+ virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // nsIContent
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+ virtual void DoneCreatingElement() override;
+
+ void UpdateBarredFromConstraintValidation();
+ // Element
+ EventStates IntrinsicState() const override;
+ /**
+ * Called when an attribute is about to be changed
+ */
+ virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify) override;
+ /**
+ * Called when an attribute has just been changed
+ */
+ nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+
+ // nsGenericHTMLElement
+ virtual bool IsHTMLFocusable(bool aWithMouse,
+ bool* aIsFocusable,
+ int32_t* aTabIndex) override;
+
+ // WebIDL
+ bool Autofocus() const
+ {
+ return GetBoolAttr(nsGkAtoms::autofocus);
+ }
+ void SetAutofocus(bool aAutofocus, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::autofocus, aAutofocus, aError);
+ }
+ bool Disabled() const
+ {
+ return GetBoolAttr(nsGkAtoms::disabled);
+ }
+ void SetDisabled(bool aDisabled, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::disabled, aDisabled, aError);
+ }
+ // nsGenericHTMLFormElement::GetForm is fine.
+ using nsGenericHTMLFormElement::GetForm;
+ // XPCOM GetFormAction is fine.
+ void SetFormAction(const nsAString& aFormAction, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::formaction, aFormAction, aRv);
+ }
+ // XPCOM GetFormEnctype is fine.
+ void SetFormEnctype(const nsAString& aFormEnctype, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::formenctype, aFormEnctype, aRv);
+ }
+ // XPCOM GetFormMethod is fine.
+ void SetFormMethod(const nsAString& aFormMethod, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::formmethod, aFormMethod, aRv);
+ }
+ bool FormNoValidate() const
+ {
+ return GetBoolAttr(nsGkAtoms::formnovalidate);
+ }
+ void SetFormNoValidate(bool aFormNoValidate, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::formnovalidate, aFormNoValidate, aError);
+ }
+ // XPCOM GetFormTarget is fine.
+ void SetFormTarget(const nsAString& aFormTarget, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::formtarget, aFormTarget, aRv);
+ }
+ // XPCOM GetName is fine.
+ void SetName(const nsAString& aName, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::name, aName, aRv);
+ }
+ // XPCOM GetType is fine.
+ void SetType(const nsAString& aType, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::type, aType, aRv);
+ }
+ // XPCOM GetValue is fine.
+ void SetValue(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::value, aValue, aRv);
+ }
+
+ // nsIConstraintValidation::WillValidate is fine.
+ // nsIConstraintValidation::Validity() is fine.
+ // nsIConstraintValidation::GetValidationMessage() is fine.
+ // nsIConstraintValidation::CheckValidity() is fine.
+ using nsIConstraintValidation::CheckValidity;
+ using nsIConstraintValidation::ReportValidity;
+ // nsIConstraintValidation::SetCustomValidity() is fine.
+
+protected:
+ virtual ~HTMLButtonElement();
+
+ uint8_t mType;
+ bool mDisabledChanged;
+ bool mInInternalActivate;
+ bool mInhibitStateRestoration;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLButtonElement_h
diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp
new file mode 100644
index 000000000..88b41bce0
--- /dev/null
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -0,0 +1,1482 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLCanvasElement.h"
+
+#include "ImageEncoder.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "Layers.h"
+#include "MediaSegment.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Base64.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/dom/CanvasCaptureMediaStream.h"
+#include "mozilla/dom/CanvasRenderingContext2D.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/HTMLCanvasElementBinding.h"
+#include "mozilla/dom/MediaStreamTrack.h"
+#include "mozilla/dom/MouseEvent.h"
+#include "mozilla/dom/OffscreenCanvas.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/layers/AsyncCanvasRenderer.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "nsAttrValueInlines.h"
+#include "nsContentUtils.h"
+#include "nsDisplayList.h"
+#include "nsDOMJSUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsITimer.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsIXPConnect.h"
+#include "nsJSUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsMathUtils.h"
+#include "nsNetUtil.h"
+#include "nsRefreshDriver.h"
+#include "nsStreamUtils.h"
+#include "ActiveLayerTracker.h"
+#include "VRManagerChild.h"
+#include "WebGL1Context.h"
+#include "WebGL2Context.h"
+
+using namespace mozilla::layers;
+using namespace mozilla::gfx;
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Canvas)
+
+namespace mozilla {
+namespace dom {
+
+class RequestedFrameRefreshObserver : public nsARefreshObserver
+{
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RequestedFrameRefreshObserver, override)
+
+public:
+ RequestedFrameRefreshObserver(HTMLCanvasElement* const aOwningElement,
+ nsRefreshDriver* aRefreshDriver)
+ : mRegistered(false),
+ mOwningElement(aOwningElement),
+ mRefreshDriver(aRefreshDriver)
+ {
+ MOZ_ASSERT(mOwningElement);
+ }
+
+ static already_AddRefed<DataSourceSurface>
+ CopySurface(const RefPtr<SourceSurface>& aSurface)
+ {
+ RefPtr<DataSourceSurface> data = aSurface->GetDataSurface();
+ if (!data) {
+ return nullptr;
+ }
+
+ DataSourceSurface::ScopedMap read(data, DataSourceSurface::READ);
+ if (!read.IsMapped()) {
+ return nullptr;
+ }
+
+ RefPtr<DataSourceSurface> copy =
+ Factory::CreateDataSourceSurfaceWithStride(data->GetSize(),
+ data->GetFormat(),
+ read.GetStride());
+ if (!copy) {
+ return nullptr;
+ }
+
+ DataSourceSurface::ScopedMap write(copy, DataSourceSurface::WRITE);
+ if (!write.IsMapped()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(read.GetStride() == write.GetStride());
+ MOZ_ASSERT(data->GetSize() == copy->GetSize());
+ MOZ_ASSERT(data->GetFormat() == copy->GetFormat());
+
+ memcpy(write.GetData(), read.GetData(),
+ write.GetStride() * copy->GetSize().height);
+
+ return copy.forget();
+ }
+
+ void WillRefresh(TimeStamp aTime) override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mOwningElement) {
+ return;
+ }
+
+ if (mOwningElement->IsWriteOnly()) {
+ return;
+ }
+
+ if (mOwningElement->IsContextCleanForFrameCapture()) {
+ return;
+ }
+
+ mOwningElement->ProcessDestroyedFrameListeners();
+
+ if (!mOwningElement->IsFrameCaptureRequested()) {
+ return;
+ }
+
+ RefPtr<SourceSurface> snapshot = mOwningElement->GetSurfaceSnapshot(nullptr);
+ if (!snapshot) {
+ return;
+ }
+
+ RefPtr<DataSourceSurface> copy = CopySurface(snapshot);
+ if (!copy) {
+ return;
+ }
+
+ mOwningElement->SetFrameCapture(copy.forget());
+ mOwningElement->MarkContextCleanForFrameCapture();
+ }
+
+ void DetachFromRefreshDriver()
+ {
+ MOZ_ASSERT(mOwningElement);
+ MOZ_ASSERT(mRefreshDriver);
+
+ Unregister();
+ mRefreshDriver = nullptr;
+ }
+
+ void Register()
+ {
+ if (mRegistered) {
+ return;
+ }
+
+ MOZ_ASSERT(mRefreshDriver);
+ if (mRefreshDriver) {
+ mRefreshDriver->AddRefreshObserver(this, Flush_Display);
+ mRegistered = true;
+ }
+ }
+
+ void Unregister()
+ {
+ if (!mRegistered) {
+ return;
+ }
+
+ MOZ_ASSERT(mRefreshDriver);
+ if (mRefreshDriver) {
+ mRefreshDriver->RemoveRefreshObserver(this, Flush_Display);
+ mRegistered = false;
+ }
+ }
+
+private:
+ virtual ~RequestedFrameRefreshObserver()
+ {
+ MOZ_ASSERT(!mRefreshDriver);
+ MOZ_ASSERT(!mRegistered);
+ }
+
+ bool mRegistered;
+ HTMLCanvasElement* const mOwningElement;
+ RefPtr<nsRefreshDriver> mRefreshDriver;
+};
+
+// ---------------------------------------------------------------------------
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HTMLCanvasPrintState, mCanvas,
+ mContext, mCallback)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(HTMLCanvasPrintState, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(HTMLCanvasPrintState, Release)
+
+HTMLCanvasPrintState::HTMLCanvasPrintState(HTMLCanvasElement* aCanvas,
+ nsICanvasRenderingContextInternal* aContext,
+ nsITimerCallback* aCallback)
+ : mIsDone(false), mPendingNotify(false), mCanvas(aCanvas),
+ mContext(aContext), mCallback(aCallback)
+{
+}
+
+HTMLCanvasPrintState::~HTMLCanvasPrintState()
+{
+}
+
+/* virtual */ JSObject*
+HTMLCanvasPrintState::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MozCanvasPrintStateBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsISupports*
+HTMLCanvasPrintState::Context() const
+{
+ return mContext;
+}
+
+void
+HTMLCanvasPrintState::Done()
+{
+ if (!mPendingNotify && !mIsDone) {
+ // The canvas needs to be invalidated for printing reftests on linux to
+ // work.
+ if (mCanvas) {
+ mCanvas->InvalidateCanvas();
+ }
+ RefPtr<nsRunnableMethod<HTMLCanvasPrintState> > doneEvent =
+ NewRunnableMethod(this, &HTMLCanvasPrintState::NotifyDone);
+ if (NS_SUCCEEDED(NS_DispatchToCurrentThread(doneEvent))) {
+ mPendingNotify = true;
+ }
+ }
+}
+
+void
+HTMLCanvasPrintState::NotifyDone()
+{
+ mIsDone = true;
+ mPendingNotify = false;
+ if (mCallback) {
+ mCallback->Notify(nullptr);
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+HTMLCanvasElementObserver::HTMLCanvasElementObserver(HTMLCanvasElement* aElement)
+ : mElement(aElement)
+{
+ RegisterVisibilityChangeEvent();
+ RegisterMemoryPressureEvent();
+}
+
+HTMLCanvasElementObserver::~HTMLCanvasElementObserver()
+{
+ Destroy();
+}
+
+void
+HTMLCanvasElementObserver::Destroy()
+{
+ UnregisterMemoryPressureEvent();
+ UnregisterVisibilityChangeEvent();
+ mElement = nullptr;
+}
+
+void
+HTMLCanvasElementObserver::RegisterVisibilityChangeEvent()
+{
+ if (!mElement) {
+ return;
+ }
+
+ nsIDocument* document = mElement->OwnerDoc();
+ document->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
+ this, true, false);
+}
+
+void
+HTMLCanvasElementObserver::UnregisterVisibilityChangeEvent()
+{
+ if (!mElement) {
+ return;
+ }
+
+ nsIDocument* document = mElement->OwnerDoc();
+ document->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
+ this, true);
+}
+
+void
+HTMLCanvasElementObserver::RegisterMemoryPressureEvent()
+{
+ if (!mElement) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ MOZ_ASSERT(observerService);
+
+ if (observerService)
+ observerService->AddObserver(this, "memory-pressure", false);
+}
+
+void
+HTMLCanvasElementObserver::UnregisterMemoryPressureEvent()
+{
+ if (!mElement) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ // Do not assert on observerService here. This might be triggered by
+ // the cycle collector at a late enough time, that XPCOM services are
+ // no longer available. See bug 1029504.
+ if (observerService)
+ observerService->RemoveObserver(this, "memory-pressure");
+}
+
+NS_IMETHODIMP
+HTMLCanvasElementObserver::Observe(nsISupports*, const char* aTopic, const char16_t*)
+{
+ if (!mElement || strcmp(aTopic, "memory-pressure")) {
+ return NS_OK;
+ }
+
+ mElement->OnMemoryPressure();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLCanvasElementObserver::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsAutoString type;
+ aEvent->GetType(type);
+ if (!mElement || !type.EqualsLiteral("visibilitychange")) {
+ return NS_OK;
+ }
+
+ mElement->OnVisibilityChange();
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(HTMLCanvasElementObserver, nsIObserver)
+
+// ---------------------------------------------------------------------------
+
+HTMLCanvasElement::HTMLCanvasElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo),
+ mResetLayer(true) ,
+ mVRPresentationActive(false),
+ mWriteOnly(false)
+{}
+
+HTMLCanvasElement::~HTMLCanvasElement()
+{
+ if (mContextObserver) {
+ mContextObserver->Destroy();
+ mContextObserver = nullptr;
+ }
+
+ ResetPrintCallback();
+ if (mRequestedFrameRefreshObserver) {
+ mRequestedFrameRefreshObserver->DetachFromRefreshDriver();
+ }
+
+ if (mAsyncCanvasRenderer) {
+ mAsyncCanvasRenderer->mHTMLCanvasElement = nullptr;
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLCanvasElement, nsGenericHTMLElement,
+ mCurrentContext, mPrintCallback,
+ mPrintState, mOriginalCanvas,
+ mOffscreenCanvas)
+
+NS_IMPL_ADDREF_INHERITED(HTMLCanvasElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLCanvasElement, Element)
+
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLCanvasElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLCanvasElement, nsIDOMHTMLCanvasElement)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLCanvasElement)
+
+/* virtual */ JSObject*
+HTMLCanvasElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLCanvasElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<nsICanvasRenderingContextInternal>
+HTMLCanvasElement::CreateContext(CanvasContextType aContextType)
+{
+ // Note that the compositor backend will be LAYERS_NONE if there is no widget.
+ RefPtr<nsICanvasRenderingContextInternal> ret =
+ CreateContextHelper(aContextType, GetCompositorBackendType());
+
+ // Add Observer for webgl canvas.
+ if (aContextType == CanvasContextType::WebGL1 ||
+ aContextType == CanvasContextType::WebGL2) {
+ if (!mContextObserver) {
+ mContextObserver = new HTMLCanvasElementObserver(this);
+ }
+ }
+
+ ret->SetCanvasElement(this);
+ return ret.forget();
+}
+
+nsIntSize
+HTMLCanvasElement::GetWidthHeight()
+{
+ nsIntSize size(DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT);
+ const nsAttrValue* value;
+
+ if ((value = GetParsedAttr(nsGkAtoms::width)) &&
+ value->Type() == nsAttrValue::eInteger)
+ {
+ size.width = value->GetIntegerValue();
+ }
+
+ if ((value = GetParsedAttr(nsGkAtoms::height)) &&
+ value->Type() == nsAttrValue::eInteger)
+ {
+ size.height = value->GetIntegerValue();
+ }
+
+ MOZ_ASSERT(size.width >= 0 && size.height >= 0,
+ "we should've required <canvas> width/height attrs to be "
+ "unsigned (non-negative) values");
+
+ return size;
+}
+
+NS_IMPL_UINT_ATTR_DEFAULT_VALUE(HTMLCanvasElement, Width, width, DEFAULT_CANVAS_WIDTH)
+NS_IMPL_UINT_ATTR_DEFAULT_VALUE(HTMLCanvasElement, Height, height, DEFAULT_CANVAS_HEIGHT)
+NS_IMPL_BOOL_ATTR(HTMLCanvasElement, MozOpaque, moz_opaque)
+
+nsresult
+HTMLCanvasElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify)
+{
+ nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
+ aNotify);
+ if (NS_SUCCEEDED(rv) && mCurrentContext &&
+ aNameSpaceID == kNameSpaceID_None &&
+ (aName == nsGkAtoms::width || aName == nsGkAtoms::height || aName == nsGkAtoms::moz_opaque))
+ {
+ ErrorResult dummy;
+ rv = UpdateContext(nullptr, JS::NullHandleValue, dummy);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return rv;
+}
+
+nsresult
+HTMLCanvasElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ bool aNotify)
+{
+ nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aName, aNotify);
+ if (NS_SUCCEEDED(rv) && mCurrentContext &&
+ aNameSpaceID == kNameSpaceID_None &&
+ (aName == nsGkAtoms::width || aName == nsGkAtoms::height || aName == nsGkAtoms::moz_opaque))
+ {
+ ErrorResult dummy;
+ rv = UpdateContext(nullptr, JS::NullHandleValue, dummy);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return rv;
+}
+
+void
+HTMLCanvasElement::HandlePrintCallback(nsPresContext::nsPresContextType aType)
+{
+ // Only call the print callback here if 1) we're in a print testing mode or
+ // print preview mode, 2) the canvas has a print callback and 3) the callback
+ // hasn't already been called. For real printing the callback is handled in
+ // nsSimplePageSequenceFrame::PrePrintNextPage.
+ if ((aType == nsPresContext::eContext_PageLayout ||
+ aType == nsPresContext::eContext_PrintPreview) &&
+ !mPrintState && GetMozPrintCallback()) {
+ DispatchPrintCallback(nullptr);
+ }
+}
+
+nsresult
+HTMLCanvasElement::DispatchPrintCallback(nsITimerCallback* aCallback)
+{
+ // For print reftests the context may not be initialized yet, so get a context
+ // so mCurrentContext is set.
+ if (!mCurrentContext) {
+ nsresult rv;
+ nsCOMPtr<nsISupports> context;
+ rv = GetContext(NS_LITERAL_STRING("2d"), getter_AddRefs(context));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ mPrintState = new HTMLCanvasPrintState(this, mCurrentContext, aCallback);
+
+ RefPtr<nsRunnableMethod<HTMLCanvasElement> > renderEvent =
+ NewRunnableMethod(this, &HTMLCanvasElement::CallPrintCallback);
+ return NS_DispatchToCurrentThread(renderEvent);
+}
+
+void
+HTMLCanvasElement::CallPrintCallback()
+{
+ ErrorResult rv;
+ GetMozPrintCallback()->Call(*mPrintState, rv);
+}
+
+void
+HTMLCanvasElement::ResetPrintCallback()
+{
+ if (mPrintState) {
+ mPrintState = nullptr;
+ }
+}
+
+bool
+HTMLCanvasElement::IsPrintCallbackDone()
+{
+ if (mPrintState == nullptr) {
+ return true;
+ }
+
+ return mPrintState->mIsDone;
+}
+
+HTMLCanvasElement*
+HTMLCanvasElement::GetOriginalCanvas()
+{
+ return mOriginalCanvas ? mOriginalCanvas.get() : this;
+}
+
+nsresult
+HTMLCanvasElement::CopyInnerTo(Element* aDest)
+{
+ nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aDest->OwnerDoc()->IsStaticDocument()) {
+ HTMLCanvasElement* dest = static_cast<HTMLCanvasElement*>(aDest);
+ dest->mOriginalCanvas = this;
+
+ nsCOMPtr<nsISupports> cxt;
+ dest->GetContext(NS_LITERAL_STRING("2d"), getter_AddRefs(cxt));
+ RefPtr<CanvasRenderingContext2D> context2d =
+ static_cast<CanvasRenderingContext2D*>(cxt.get());
+ if (context2d && !mPrintCallback) {
+ CanvasImageSource source;
+ source.SetAsHTMLCanvasElement() = this;
+ ErrorResult err;
+ context2d->DrawImage(source,
+ 0.0, 0.0, err);
+ rv = err.StealNSResult();
+ }
+ }
+ return rv;
+}
+
+nsresult HTMLCanvasElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ if (aVisitor.mEvent->mClass == eMouseEventClass) {
+ WidgetMouseEventBase* evt = (WidgetMouseEventBase*)aVisitor.mEvent;
+ if (mCurrentContext) {
+ nsIFrame *frame = GetPrimaryFrame();
+ if (!frame)
+ return NS_OK;
+ nsPoint ptInRoot = nsLayoutUtils::GetEventCoordinatesRelativeTo(evt, frame);
+ nsRect paddingRect = frame->GetContentRectRelativeToSelf();
+ Point hitpoint;
+ hitpoint.x = (ptInRoot.x - paddingRect.x) / AppUnitsPerCSSPixel();
+ hitpoint.y = (ptInRoot.y - paddingRect.y) / AppUnitsPerCSSPixel();
+
+ evt->region = mCurrentContext->GetHitRegion(hitpoint);
+ aVisitor.mCanHandle = true;
+ }
+ }
+ return nsGenericHTMLElement::PreHandleEvent(aVisitor);
+}
+
+nsChangeHint
+HTMLCanvasElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const
+{
+ nsChangeHint retval =
+ nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
+ if (aAttribute == nsGkAtoms::width ||
+ aAttribute == nsGkAtoms::height)
+ {
+ retval |= NS_STYLE_HINT_REFLOW;
+ } else if (aAttribute == nsGkAtoms::moz_opaque)
+ {
+ retval |= NS_STYLE_HINT_VISUAL;
+ }
+ return retval;
+}
+
+bool
+HTMLCanvasElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height)) {
+ return aResult.ParseNonNegativeIntValue(aValue);
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+
+// HTMLCanvasElement::toDataURL
+
+NS_IMETHODIMP
+HTMLCanvasElement::ToDataURL(const nsAString& aType, JS::Handle<JS::Value> aParams,
+ JSContext* aCx, nsAString& aDataURL)
+{
+ // do a trust check if this is a write-only canvas
+ if (mWriteOnly && !nsContentUtils::IsCallerChrome()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ return ToDataURLImpl(aCx, aType, aParams, aDataURL);
+}
+
+void
+HTMLCanvasElement::SetMozPrintCallback(PrintCallback* aCallback)
+{
+ mPrintCallback = aCallback;
+}
+
+PrintCallback*
+HTMLCanvasElement::GetMozPrintCallback() const
+{
+ if (mOriginalCanvas) {
+ return mOriginalCanvas->GetMozPrintCallback();
+ }
+ return mPrintCallback;
+}
+
+class CanvasCaptureTrackSource : public MediaStreamTrackSource
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CanvasCaptureTrackSource,
+ MediaStreamTrackSource)
+
+ CanvasCaptureTrackSource(nsIPrincipal* aPrincipal,
+ CanvasCaptureMediaStream* aCaptureStream)
+ : MediaStreamTrackSource(aPrincipal, nsString())
+ , mCaptureStream(aCaptureStream) {}
+
+ MediaSourceEnum GetMediaSource() const override
+ {
+ return MediaSourceEnum::Other;
+ }
+
+ void Stop() override
+ {
+ if (!mCaptureStream) {
+ NS_ERROR("No stream");
+ return;
+ }
+
+ mCaptureStream->StopCapture();
+ }
+
+private:
+ virtual ~CanvasCaptureTrackSource() {}
+
+ RefPtr<CanvasCaptureMediaStream> mCaptureStream;
+};
+
+NS_IMPL_ADDREF_INHERITED(CanvasCaptureTrackSource,
+ MediaStreamTrackSource)
+NS_IMPL_RELEASE_INHERITED(CanvasCaptureTrackSource,
+ MediaStreamTrackSource)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(CanvasCaptureTrackSource)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(CanvasCaptureTrackSource,
+ MediaStreamTrackSource,
+ mCaptureStream)
+
+already_AddRefed<CanvasCaptureMediaStream>
+HTMLCanvasElement::CaptureStream(const Optional<double>& aFrameRate,
+ ErrorResult& aRv)
+{
+ if (IsWriteOnly()) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ if (!mCurrentContext) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return nullptr;
+ }
+
+ RefPtr<CanvasCaptureMediaStream> stream =
+ CanvasCaptureMediaStream::CreateSourceStream(window, this);
+ if (!stream) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ TrackID videoTrackId = 1;
+ nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
+ nsresult rv =
+ stream->Init(aFrameRate, videoTrackId, principal);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ RefPtr<MediaStreamTrack> track =
+ stream->CreateDOMTrack(videoTrackId, MediaSegment::VIDEO,
+ new CanvasCaptureTrackSource(principal, stream));
+ stream->AddTrackInternal(track);
+
+ rv = RegisterFrameCaptureListener(stream->FrameCaptureListener());
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ return stream.forget();
+}
+
+nsresult
+HTMLCanvasElement::ExtractData(nsAString& aType,
+ const nsAString& aOptions,
+ nsIInputStream** aStream)
+{
+ return ImageEncoder::ExtractData(aType,
+ aOptions,
+ GetSize(),
+ mCurrentContext,
+ mAsyncCanvasRenderer,
+ aStream);
+}
+
+nsresult
+HTMLCanvasElement::ToDataURLImpl(JSContext* aCx,
+ const nsAString& aMimeType,
+ const JS::Value& aEncoderOptions,
+ nsAString& aDataURL)
+{
+ nsIntSize size = GetWidthHeight();
+ if (size.height == 0 || size.width == 0) {
+ aDataURL = NS_LITERAL_STRING("data:,");
+ return NS_OK;
+ }
+
+ nsAutoString type;
+ nsContentUtils::ASCIIToLower(aMimeType, type);
+
+ nsAutoString params;
+ bool usingCustomParseOptions;
+ nsresult rv =
+ ParseParams(aCx, type, aEncoderOptions, params, &usingCustomParseOptions);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = ExtractData(type, params, getter_AddRefs(stream));
+
+ // 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 && usingCustomParseOptions) {
+ rv = ExtractData(type, EmptyString(), getter_AddRefs(stream));
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // build data URL string
+ aDataURL = NS_LITERAL_STRING("data:") + type + NS_LITERAL_STRING(";base64,");
+
+ uint64_t count;
+ rv = stream->Available(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(count <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
+
+ return Base64EncodeInputStream(stream, aDataURL, (uint32_t)count, aDataURL.Length());
+}
+
+void
+HTMLCanvasElement::ToBlob(JSContext* aCx,
+ BlobCallback& aCallback,
+ const nsAString& aType,
+ JS::Handle<JS::Value> aParams,
+ ErrorResult& aRv)
+{
+ // do a trust check if this is a write-only canvas
+ if (mWriteOnly && !nsContentUtils::IsCallerChrome()) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
+ MOZ_ASSERT(global);
+
+ CanvasRenderingContextHelper::ToBlob(aCx, global, aCallback, aType,
+ aParams, aRv);
+
+}
+
+OffscreenCanvas*
+HTMLCanvasElement::TransferControlToOffscreen(ErrorResult& aRv)
+{
+ if (mCurrentContext) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ if (!mOffscreenCanvas) {
+ nsIntSize sz = GetWidthHeight();
+ RefPtr<AsyncCanvasRenderer> renderer = GetAsyncCanvasRenderer();
+ renderer->SetWidth(sz.width);
+ renderer->SetHeight(sz.height);
+
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(OwnerDoc()->GetInnerWindow());
+ mOffscreenCanvas = new OffscreenCanvas(global,
+ sz.width,
+ sz.height,
+ GetCompositorBackendType(),
+ renderer);
+ if (mWriteOnly) {
+ mOffscreenCanvas->SetWriteOnly();
+ }
+
+ if (!mContextObserver) {
+ mContextObserver = new HTMLCanvasElementObserver(this);
+ }
+ } else {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+
+ return mOffscreenCanvas;
+}
+
+already_AddRefed<File>
+HTMLCanvasElement::MozGetAsFile(const nsAString& aName,
+ const nsAString& aType,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsISupports> file;
+ aRv = MozGetAsFile(aName, aType, getter_AddRefs(file));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(file);
+ RefPtr<Blob> domBlob = static_cast<Blob*>(blob.get());
+ MOZ_ASSERT(domBlob->IsFile());
+ return domBlob->ToFile();
+}
+
+NS_IMETHODIMP
+HTMLCanvasElement::MozGetAsFile(const nsAString& aName,
+ const nsAString& aType,
+ nsISupports** aResult)
+{
+ OwnerDoc()->WarnOnceAbout(nsIDocument::eMozGetAsFile);
+
+ // do a trust check if this is a write-only canvas
+ if ((mWriteOnly) &&
+ !nsContentUtils::IsCallerChrome()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ return MozGetAsBlobImpl(aName, aType, aResult);
+}
+
+nsresult
+HTMLCanvasElement::MozGetAsBlobImpl(const nsAString& aName,
+ const nsAString& aType,
+ nsISupports** aResult)
+{
+ nsCOMPtr<nsIInputStream> stream;
+ nsAutoString type(aType);
+ nsresult rv = ExtractData(type, EmptyString(), getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t imgSize;
+ rv = stream->Available(&imgSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(imgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
+
+ void* imgData = nullptr;
+ rv = NS_ReadInputStreamToBuffer(stream, &imgData, (uint32_t)imgSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JSContext* cx = nsContentUtils::GetCurrentJSContext();
+ if (cx) {
+ JS_updateMallocCounter(cx, imgSize);
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(OwnerDoc()->GetScopeObject());
+
+ // The File takes ownership of the buffer
+ nsCOMPtr<nsIDOMBlob> file =
+ File::CreateMemoryFile(win, imgData, (uint32_t)imgSize, aName, type,
+ PR_Now());
+
+ file.forget(aResult);
+ return NS_OK;
+}
+
+nsresult
+HTMLCanvasElement::GetContext(const nsAString& aContextId,
+ nsISupports** aContext)
+{
+ ErrorResult rv;
+ *aContext = GetContext(nullptr, aContextId, JS::NullHandleValue, rv).take();
+ return rv.StealNSResult();
+}
+
+already_AddRefed<nsISupports>
+HTMLCanvasElement::GetContext(JSContext* aCx,
+ const nsAString& aContextId,
+ JS::Handle<JS::Value> aContextOptions,
+ ErrorResult& aRv)
+{
+ if (mOffscreenCanvas) {
+ return nullptr;
+ }
+
+ return CanvasRenderingContextHelper::GetContext(aCx, aContextId,
+ aContextOptions.isObject() ? aContextOptions : JS::NullHandleValue,
+ aRv);
+}
+
+NS_IMETHODIMP
+HTMLCanvasElement::MozGetIPCContext(const nsAString& aContextId,
+ nsISupports **aContext)
+{
+ if(!nsContentUtils::IsCallerChrome()) {
+ // XXX ERRMSG we need to report an error to developers here! (bug 329026)
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ // We only support 2d shmem contexts for now.
+ if (!aContextId.EqualsLiteral("2d"))
+ return NS_ERROR_INVALID_ARG;
+
+ CanvasContextType contextType = CanvasContextType::Canvas2D;
+
+ if (!mCurrentContext) {
+ // This canvas doesn't have a context yet.
+
+ RefPtr<nsICanvasRenderingContextInternal> context;
+ context = CreateContext(contextType);
+ if (!context) {
+ *aContext = nullptr;
+ return NS_OK;
+ }
+
+ mCurrentContext = context;
+ mCurrentContext->SetIsIPC(true);
+ mCurrentContextType = contextType;
+
+ ErrorResult dummy;
+ nsresult rv = UpdateContext(nullptr, JS::NullHandleValue, dummy);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // We already have a context of some type.
+ if (contextType != mCurrentContextType)
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ NS_ADDREF (*aContext = mCurrentContext);
+ return NS_OK;
+}
+
+
+nsIntSize
+HTMLCanvasElement::GetSize()
+{
+ return GetWidthHeight();
+}
+
+bool
+HTMLCanvasElement::IsWriteOnly()
+{
+ return mWriteOnly;
+}
+
+void
+HTMLCanvasElement::SetWriteOnly()
+{
+ mWriteOnly = true;
+}
+
+void
+HTMLCanvasElement::InvalidateCanvasContent(const gfx::Rect* damageRect)
+{
+ // We don't need to flush anything here; if there's no frame or if
+ // we plan to reframe we don't need to invalidate it anyway.
+ nsIFrame *frame = GetPrimaryFrame();
+ if (!frame)
+ return;
+
+ ActiveLayerTracker::NotifyContentChange(frame);
+
+ Layer* layer = nullptr;
+ if (damageRect) {
+ nsIntSize size = GetWidthHeight();
+ if (size.width != 0 && size.height != 0) {
+ gfx::IntRect invalRect = gfx::IntRect::Truncate(*damageRect);
+ layer = frame->InvalidateLayer(nsDisplayItem::TYPE_CANVAS, &invalRect);
+ }
+ } else {
+ layer = frame->InvalidateLayer(nsDisplayItem::TYPE_CANVAS);
+ }
+ if (layer) {
+ static_cast<CanvasLayer*>(layer)->Updated();
+ }
+
+ /*
+ * Treat canvas invalidations as animation activity for JS. Frequently
+ * invalidating a canvas will feed into heuristics and cause JIT code to be
+ * kept around longer, for smoother animations.
+ */
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(OwnerDoc()->GetInnerWindow());
+
+ if (global) {
+ if (JSObject *obj = global->GetGlobalJSObject()) {
+ js::NotifyAnimationActivity(obj);
+ }
+ }
+}
+
+void
+HTMLCanvasElement::InvalidateCanvas()
+{
+ // We don't need to flush anything here; if there's no frame or if
+ // we plan to reframe we don't need to invalidate it anyway.
+ nsIFrame *frame = GetPrimaryFrame();
+ if (!frame)
+ return;
+
+ frame->InvalidateFrame();
+}
+
+int32_t
+HTMLCanvasElement::CountContexts()
+{
+ if (mCurrentContext)
+ return 1;
+
+ return 0;
+}
+
+nsICanvasRenderingContextInternal *
+HTMLCanvasElement::GetContextAtIndex(int32_t index)
+{
+ if (mCurrentContext && index == 0)
+ return mCurrentContext;
+
+ return nullptr;
+}
+
+bool
+HTMLCanvasElement::GetIsOpaque()
+{
+ if (mCurrentContext) {
+ return mCurrentContext->GetIsOpaque();
+ }
+
+ return GetOpaqueAttr();
+}
+
+bool
+HTMLCanvasElement::GetOpaqueAttr()
+{
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::moz_opaque);
+}
+
+already_AddRefed<Layer>
+HTMLCanvasElement::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
+ Layer *aOldLayer,
+ LayerManager *aManager)
+{
+ // The address of sOffscreenCanvasLayerUserDataDummy is used as the user
+ // data key for retained LayerManagers managed by FrameLayerBuilder.
+ // We don't much care about what value in it, so just assign a dummy
+ // value for it.
+ static uint8_t sOffscreenCanvasLayerUserDataDummy = 0;
+
+ if (mCurrentContext) {
+ return mCurrentContext->GetCanvasLayer(aBuilder, aOldLayer, aManager, mVRPresentationActive);
+ }
+
+ if (mOffscreenCanvas) {
+ if (!mResetLayer &&
+ aOldLayer && aOldLayer->HasUserData(&sOffscreenCanvasLayerUserDataDummy)) {
+ RefPtr<Layer> ret = aOldLayer;
+ return ret.forget();
+ }
+
+ RefPtr<CanvasLayer> layer = aManager->CreateCanvasLayer();
+ if (!layer) {
+ NS_WARNING("CreateCanvasLayer failed!");
+ return nullptr;
+ }
+
+ LayerUserData* userData = nullptr;
+ layer->SetUserData(&sOffscreenCanvasLayerUserDataDummy, userData);
+
+ CanvasLayer::Data data;
+ data.mRenderer = GetAsyncCanvasRenderer();
+ data.mSize = GetWidthHeight();
+ layer->Initialize(data);
+
+ layer->Updated();
+ return layer.forget();
+ }
+
+ return nullptr;
+}
+
+bool
+HTMLCanvasElement::ShouldForceInactiveLayer(LayerManager* aManager)
+{
+ if (mCurrentContext) {
+ return mCurrentContext->ShouldForceInactiveLayer(aManager);
+ }
+
+ if (mOffscreenCanvas) {
+ // TODO: We should handle offscreen canvas case.
+ return false;
+ }
+
+ return true;
+}
+
+void
+HTMLCanvasElement::MarkContextClean()
+{
+ if (!mCurrentContext)
+ return;
+
+ mCurrentContext->MarkContextClean();
+}
+
+void
+HTMLCanvasElement::MarkContextCleanForFrameCapture()
+{
+ if (!mCurrentContext)
+ return;
+
+ mCurrentContext->MarkContextCleanForFrameCapture();
+}
+
+bool
+HTMLCanvasElement::IsContextCleanForFrameCapture()
+{
+ return mCurrentContext && mCurrentContext->IsContextCleanForFrameCapture();
+}
+
+nsresult
+HTMLCanvasElement::RegisterFrameCaptureListener(FrameCaptureListener* aListener)
+{
+ WeakPtr<FrameCaptureListener> listener = aListener;
+
+ if (mRequestedFrameListeners.Contains(listener)) {
+ return NS_OK;
+ }
+
+ if (!mRequestedFrameRefreshObserver) {
+ nsIDocument* doc = OwnerDoc();
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ while (doc->GetParentDocument()) {
+ doc = doc->GetParentDocument();
+ }
+
+ nsIPresShell* shell = doc->GetShell();
+ if (!shell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsPresContext* context = shell->GetPresContext();
+ if (!context) {
+ return NS_ERROR_FAILURE;
+ }
+
+ context = context->GetRootPresContext();
+ if (!context) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsRefreshDriver* driver = context->RefreshDriver();
+ if (!driver) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mRequestedFrameRefreshObserver =
+ new RequestedFrameRefreshObserver(this, driver);
+ }
+
+ mRequestedFrameListeners.AppendElement(listener);
+ mRequestedFrameRefreshObserver->Register();
+ return NS_OK;
+}
+
+bool
+HTMLCanvasElement::IsFrameCaptureRequested() const
+{
+ for (WeakPtr<FrameCaptureListener> listener : mRequestedFrameListeners) {
+ if (!listener) {
+ continue;
+ }
+
+ if (listener->FrameCaptureRequested()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+HTMLCanvasElement::ProcessDestroyedFrameListeners()
+{
+ // Loop backwards to allow removing elements in the loop.
+ for (int i = mRequestedFrameListeners.Length() - 1; i >= 0; --i) {
+ WeakPtr<FrameCaptureListener> listener = mRequestedFrameListeners[i];
+ if (!listener) {
+ // listener was destroyed. Remove it from the list.
+ mRequestedFrameListeners.RemoveElementAt(i);
+ continue;
+ }
+ }
+
+ if (mRequestedFrameListeners.IsEmpty()) {
+ mRequestedFrameRefreshObserver->Unregister();
+ }
+}
+
+void
+HTMLCanvasElement::SetFrameCapture(already_AddRefed<SourceSurface> aSurface)
+{
+ RefPtr<SourceSurface> surface = aSurface;
+ RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(surface->GetSize(), surface);
+
+ for (WeakPtr<FrameCaptureListener> listener : mRequestedFrameListeners) {
+ if (!listener) {
+ continue;
+ }
+
+ RefPtr<Image> imageRefCopy = image.get();
+ listener->NewFrame(imageRefCopy.forget());
+ }
+}
+
+already_AddRefed<SourceSurface>
+HTMLCanvasElement::GetSurfaceSnapshot(bool* aPremultAlpha)
+{
+ if (!mCurrentContext)
+ return nullptr;
+
+ return mCurrentContext->GetSurfaceSnapshot(aPremultAlpha);
+}
+
+AsyncCanvasRenderer*
+HTMLCanvasElement::GetAsyncCanvasRenderer()
+{
+ if (!mAsyncCanvasRenderer) {
+ mAsyncCanvasRenderer = new AsyncCanvasRenderer();
+ mAsyncCanvasRenderer->mHTMLCanvasElement = this;
+ }
+
+ return mAsyncCanvasRenderer;
+}
+
+layers::LayersBackend
+HTMLCanvasElement::GetCompositorBackendType() const
+{
+ nsIWidget* docWidget = nsContentUtils::WidgetForDocument(OwnerDoc());
+ if (docWidget) {
+ layers::LayerManager* layerManager = docWidget->GetLayerManager();
+ if (layerManager) {
+ return layerManager->GetCompositorBackendType();
+ }
+ }
+
+ return LayersBackend::LAYERS_NONE;
+}
+
+void
+HTMLCanvasElement::OnVisibilityChange()
+{
+ if (OwnerDoc()->Hidden()) {
+ return;
+ }
+
+ if (mOffscreenCanvas) {
+ class Runnable final : public CancelableRunnable
+ {
+ public:
+ explicit Runnable(AsyncCanvasRenderer* aRenderer)
+ : mRenderer(aRenderer)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ if (mRenderer && mRenderer->mContext) {
+ mRenderer->mContext->OnVisibilityChange();
+ }
+
+ return NS_OK;
+ }
+
+ void Revoke()
+ {
+ mRenderer = nullptr;
+ }
+
+ private:
+ RefPtr<AsyncCanvasRenderer> mRenderer;
+ };
+
+ RefPtr<nsIRunnable> runnable = new Runnable(mAsyncCanvasRenderer);
+ nsCOMPtr<nsIThread> activeThread = mAsyncCanvasRenderer->GetActiveThread();
+ if (activeThread) {
+ activeThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL);
+ }
+ return;
+ }
+
+ if (mCurrentContext) {
+ mCurrentContext->OnVisibilityChange();
+ }
+}
+
+void
+HTMLCanvasElement::OnMemoryPressure()
+{
+ if (mOffscreenCanvas) {
+ class Runnable final : public CancelableRunnable
+ {
+ public:
+ explicit Runnable(AsyncCanvasRenderer* aRenderer)
+ : mRenderer(aRenderer)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ if (mRenderer && mRenderer->mContext) {
+ mRenderer->mContext->OnMemoryPressure();
+ }
+
+ return NS_OK;
+ }
+
+ void Revoke()
+ {
+ mRenderer = nullptr;
+ }
+
+ private:
+ RefPtr<AsyncCanvasRenderer> mRenderer;
+ };
+
+ RefPtr<nsIRunnable> runnable = new Runnable(mAsyncCanvasRenderer);
+ nsCOMPtr<nsIThread> activeThread = mAsyncCanvasRenderer->GetActiveThread();
+ if (activeThread) {
+ activeThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL);
+ }
+ return;
+ }
+
+ if (mCurrentContext) {
+ mCurrentContext->OnMemoryPressure();
+ }
+}
+
+/* static */ void
+HTMLCanvasElement::SetAttrFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer)
+{
+ HTMLCanvasElement *element = aRenderer->mHTMLCanvasElement;
+ if (!element) {
+ return;
+ }
+
+ if (element->GetWidthHeight() == aRenderer->GetSize()) {
+ return;
+ }
+
+ gfx::IntSize asyncCanvasSize = aRenderer->GetSize();
+
+ ErrorResult rv;
+ element->SetUnsignedIntAttr(nsGkAtoms::width, asyncCanvasSize.width,
+ DEFAULT_CANVAS_WIDTH, rv);
+ if (rv.Failed()) {
+ NS_WARNING("Failed to set width attribute to a canvas element asynchronously.");
+ }
+
+ element->SetUnsignedIntAttr(nsGkAtoms::height, asyncCanvasSize.height,
+ DEFAULT_CANVAS_HEIGHT, rv);
+ if (rv.Failed()) {
+ NS_WARNING("Failed to set height attribute to a canvas element asynchronously.");
+ }
+
+ element->mResetLayer = true;
+}
+
+/* static */ void
+HTMLCanvasElement::InvalidateFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer)
+{
+ HTMLCanvasElement *element = aRenderer->mHTMLCanvasElement;
+ if (!element) {
+ return;
+ }
+
+ element->InvalidateCanvasContent(nullptr);
+}
+
+void
+HTMLCanvasElement::StartVRPresentation()
+{
+ WebGLContext* webgl = static_cast<WebGLContext*>(GetContextAtIndex(0));
+ if (!webgl) {
+ return;
+ }
+
+ if (!webgl->StartVRPresentation()) {
+ return;
+ }
+
+ mVRPresentationActive = true;
+}
+
+void
+HTMLCanvasElement::StopVRPresentation()
+{
+ mVRPresentationActive = false;
+}
+
+already_AddRefed<layers::SharedSurfaceTextureClient>
+HTMLCanvasElement::GetVRFrame()
+{
+ if (GetCurrentContextType() != CanvasContextType::WebGL1 &&
+ GetCurrentContextType() != CanvasContextType::WebGL2) {
+ return nullptr;
+ }
+
+ WebGLContext* webgl = static_cast<WebGLContext*>(GetContextAtIndex(0));
+ if (!webgl) {
+ return nullptr;
+ }
+
+ return webgl->GetVRFrame();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLCanvasElement.h b/dom/html/HTMLCanvasElement.h
new file mode 100644
index 000000000..81c141d3c
--- /dev/null
+++ b/dom/html/HTMLCanvasElement.h
@@ -0,0 +1,452 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#if !defined(mozilla_dom_HTMLCanvasElement_h)
+#define mozilla_dom_HTMLCanvasElement_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/WeakPtr.h"
+#include "nsIDOMEventListener.h"
+#include "nsIDOMHTMLCanvasElement.h"
+#include "nsIObserver.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+#include "nsSize.h"
+#include "nsError.h"
+
+#include "mozilla/dom/CanvasRenderingContextHelper.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/layers/LayersTypes.h"
+
+class nsICanvasRenderingContextInternal;
+class nsITimerCallback;
+
+namespace mozilla {
+
+class WebGLContext;
+
+namespace layers {
+class AsyncCanvasRenderer;
+class CanvasLayer;
+class Image;
+class Layer;
+class LayerManager;
+class SharedSurfaceTextureClient;
+} // namespace layers
+namespace gfx {
+class SourceSurface;
+class VRLayerChild;
+} // namespace gfx
+
+namespace dom {
+class BlobCallback;
+class CanvasCaptureMediaStream;
+class File;
+class HTMLCanvasPrintState;
+class OffscreenCanvas;
+class PrintCallback;
+class RequestedFrameRefreshObserver;
+
+// Listen visibilitychange and memory-pressure event and inform
+// context when event is fired.
+class HTMLCanvasElementObserver final : public nsIObserver
+ , public nsIDOMEventListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ explicit HTMLCanvasElementObserver(HTMLCanvasElement* aElement);
+ void Destroy();
+
+ void RegisterVisibilityChangeEvent();
+ void UnregisterVisibilityChangeEvent();
+
+ void RegisterMemoryPressureEvent();
+ void UnregisterMemoryPressureEvent();
+
+private:
+ ~HTMLCanvasElementObserver();
+
+ HTMLCanvasElement* mElement;
+};
+
+/*
+ * FrameCaptureListener is used by captureStream() as a way of getting video
+ * frames from the canvas. On a refresh driver tick after something has been
+ * drawn to the canvas since the last such tick, all registered
+ * FrameCaptureListeners whose `mFrameCaptureRequested` equals `true`,
+ * will be given a copy of the just-painted canvas.
+ * All FrameCaptureListeners get the same copy.
+ */
+class FrameCaptureListener : public SupportsWeakPtr<FrameCaptureListener>
+{
+public:
+ MOZ_DECLARE_WEAKREFERENCE_TYPENAME(FrameCaptureListener)
+
+ FrameCaptureListener()
+ : mFrameCaptureRequested(false) {}
+
+ /*
+ * Called when a frame capture is desired on next paint.
+ */
+ void RequestFrameCapture() { mFrameCaptureRequested = true; }
+
+ /*
+ * Indicates to the canvas whether or not this listener has requested a frame.
+ */
+ bool FrameCaptureRequested() const { return mFrameCaptureRequested; }
+
+ /*
+ * Interface through which new video frames will be provided while
+ * `mFrameCaptureRequested` is `true`.
+ */
+ virtual void NewFrame(already_AddRefed<layers::Image> aImage) = 0;
+
+protected:
+ virtual ~FrameCaptureListener() {}
+
+ bool mFrameCaptureRequested;
+};
+
+class HTMLCanvasElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLCanvasElement,
+ public CanvasRenderingContextHelper
+{
+ enum {
+ DEFAULT_CANVAS_WIDTH = 300,
+ DEFAULT_CANVAS_HEIGHT = 150
+ };
+
+ typedef layers::AsyncCanvasRenderer AsyncCanvasRenderer;
+ typedef layers::CanvasLayer CanvasLayer;
+ typedef layers::Layer Layer;
+ typedef layers::LayerManager LayerManager;
+
+public:
+ explicit HTMLCanvasElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLCanvasElement, canvas)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLCanvasElement
+ NS_DECL_NSIDOMHTMLCANVASELEMENT
+
+ // CC
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLCanvasElement,
+ nsGenericHTMLElement)
+
+ // WebIDL
+ uint32_t Height()
+ {
+ return GetUnsignedIntAttr(nsGkAtoms::height, DEFAULT_CANVAS_HEIGHT);
+ }
+ void SetHeight(uint32_t aHeight, ErrorResult& aRv)
+ {
+ if (mOffscreenCanvas) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ SetUnsignedIntAttr(nsGkAtoms::height, aHeight, DEFAULT_CANVAS_HEIGHT, aRv);
+ }
+ uint32_t Width()
+ {
+ return GetUnsignedIntAttr(nsGkAtoms::width, DEFAULT_CANVAS_WIDTH);
+ }
+ void SetWidth(uint32_t aWidth, ErrorResult& aRv)
+ {
+ if (mOffscreenCanvas) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ SetUnsignedIntAttr(nsGkAtoms::width, aWidth, DEFAULT_CANVAS_WIDTH, aRv);
+ }
+
+ virtual already_AddRefed<nsISupports>
+ GetContext(JSContext* aCx, const nsAString& aContextId,
+ JS::Handle<JS::Value> aContextOptions,
+ ErrorResult& aRv) override;
+
+ void ToDataURL(JSContext* aCx, const nsAString& aType,
+ JS::Handle<JS::Value> aParams,
+ nsAString& aDataURL, ErrorResult& aRv)
+ {
+ aRv = ToDataURL(aType, aParams, aCx, aDataURL);
+ }
+
+ void ToBlob(JSContext* aCx,
+ BlobCallback& aCallback,
+ const nsAString& aType,
+ JS::Handle<JS::Value> aParams,
+ ErrorResult& aRv);
+
+ OffscreenCanvas* TransferControlToOffscreen(ErrorResult& aRv);
+
+ bool MozOpaque() const
+ {
+ return GetBoolAttr(nsGkAtoms::moz_opaque);
+ }
+ void SetMozOpaque(bool aValue, ErrorResult& aRv)
+ {
+ if (mOffscreenCanvas) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ SetHTMLBoolAttr(nsGkAtoms::moz_opaque, aValue, aRv);
+ }
+ already_AddRefed<File> MozGetAsFile(const nsAString& aName,
+ const nsAString& aType,
+ ErrorResult& aRv);
+ already_AddRefed<nsISupports> MozGetIPCContext(const nsAString& aContextId,
+ ErrorResult& aRv)
+ {
+ nsCOMPtr<nsISupports> context;
+ aRv = MozGetIPCContext(aContextId, getter_AddRefs(context));
+ return context.forget();
+ }
+ PrintCallback* GetMozPrintCallback() const;
+ void SetMozPrintCallback(PrintCallback* aCallback);
+
+ already_AddRefed<CanvasCaptureMediaStream>
+ CaptureStream(const Optional<double>& aFrameRate, ErrorResult& aRv);
+
+ /**
+ * Get the size in pixels of this canvas element
+ */
+ nsIntSize GetSize();
+
+ /**
+ * Determine whether the canvas is write-only.
+ */
+ bool IsWriteOnly();
+
+ /**
+ * Force the canvas to be write-only.
+ */
+ void SetWriteOnly();
+
+ /**
+ * Notify that some canvas content has changed and the window may
+ * need to be updated. aDamageRect is in canvas coordinates.
+ */
+ void InvalidateCanvasContent(const mozilla::gfx::Rect* aDamageRect);
+ /*
+ * Notify that we need to repaint the entire canvas, including updating of
+ * the layer tree.
+ */
+ void InvalidateCanvas();
+
+ /*
+ * Get the number of contexts in this canvas, and request a context at
+ * an index.
+ */
+ int32_t CountContexts ();
+ nsICanvasRenderingContextInternal *GetContextAtIndex (int32_t index);
+
+ /*
+ * Returns true if the canvas context content is guaranteed to be opaque
+ * across its entire area.
+ */
+ bool GetIsOpaque();
+ virtual bool GetOpaqueAttr() override;
+
+ virtual already_AddRefed<gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr);
+
+ /*
+ * Register a FrameCaptureListener with this canvas.
+ * The canvas hooks into the RefreshDriver while there are
+ * FrameCaptureListeners registered.
+ * The registered FrameCaptureListeners are stored as WeakPtrs, thus it's the
+ * caller's responsibility to keep them alive. Once a registered
+ * FrameCaptureListener is destroyed it will be automatically deregistered.
+ */
+ nsresult RegisterFrameCaptureListener(FrameCaptureListener* aListener);
+
+ /*
+ * Returns true when there is at least one registered FrameCaptureListener
+ * that has requested a frame capture.
+ */
+ bool IsFrameCaptureRequested() const;
+
+ /*
+ * Processes destroyed FrameCaptureListeners and removes them if necessary.
+ * Should there be none left, the FrameRefreshObserver will be unregistered.
+ */
+ void ProcessDestroyedFrameListeners();
+
+ /*
+ * Called by the RefreshDriver hook when a frame has been captured.
+ * Makes a copy of the provided surface and hands it to all
+ * FrameCaptureListeners having requested frame capture.
+ */
+ void SetFrameCapture(already_AddRefed<gfx::SourceSurface> aSurface);
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute, int32_t aModType) const override;
+
+ // SetAttr override. C++ is stupid, so have to override both
+ // overloaded methods.
+ 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;
+
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ bool aNotify) override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+ nsresult CopyInnerTo(mozilla::dom::Element* aDest);
+
+ virtual nsresult PreHandleEvent(mozilla::EventChainPreVisitor& aVisitor) override;
+
+ /*
+ * Helpers called by various users of Canvas
+ */
+
+ already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
+ Layer *aOldLayer,
+ LayerManager *aManager);
+ // Should return true if the canvas layer should always be marked inactive.
+ // We should return true here if we can't do accelerated compositing with
+ // a non-BasicCanvasLayer.
+ bool ShouldForceInactiveLayer(LayerManager *aManager);
+
+ // Call this whenever we need future changes to the canvas
+ // to trigger fresh invalidation requests. This needs to be called
+ // whenever we render the canvas contents to the screen, or whenever we
+ // take a snapshot of the canvas that needs to be "live" (e.g. -moz-element).
+ void MarkContextClean();
+
+ // Call this after capturing a frame, so we can avoid unnecessary surface
+ // copies for future frames when no drawing has occurred.
+ void MarkContextCleanForFrameCapture();
+
+ // Starts returning false when something is drawn.
+ bool IsContextCleanForFrameCapture();
+
+ nsresult GetContext(const nsAString& aContextId, nsISupports** aContext);
+
+ layers::LayersBackend GetCompositorBackendType() const;
+
+ void OnVisibilityChange();
+
+ void OnMemoryPressure();
+
+ static void SetAttrFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer);
+ static void InvalidateFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer);
+
+ void StartVRPresentation();
+ void StopVRPresentation();
+ already_AddRefed<layers::SharedSurfaceTextureClient> GetVRFrame();
+
+protected:
+ virtual ~HTMLCanvasElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual nsIntSize GetWidthHeight() override;
+
+ virtual already_AddRefed<nsICanvasRenderingContextInternal>
+ CreateContext(CanvasContextType aContextType) override;
+
+ nsresult ExtractData(nsAString& aType,
+ const nsAString& aOptions,
+ nsIInputStream** aStream);
+ nsresult ToDataURLImpl(JSContext* aCx,
+ const nsAString& aMimeType,
+ const JS::Value& aEncoderOptions,
+ nsAString& aDataURL);
+ nsresult MozGetAsBlobImpl(const nsAString& aName,
+ const nsAString& aType,
+ nsISupports** aResult);
+ void CallPrintCallback();
+
+ AsyncCanvasRenderer* GetAsyncCanvasRenderer();
+
+ bool mResetLayer;
+ RefPtr<HTMLCanvasElement> mOriginalCanvas;
+ RefPtr<PrintCallback> mPrintCallback;
+ RefPtr<HTMLCanvasPrintState> mPrintState;
+ nsTArray<WeakPtr<FrameCaptureListener>> mRequestedFrameListeners;
+ RefPtr<RequestedFrameRefreshObserver> mRequestedFrameRefreshObserver;
+ RefPtr<AsyncCanvasRenderer> mAsyncCanvasRenderer;
+ RefPtr<OffscreenCanvas> mOffscreenCanvas;
+ RefPtr<HTMLCanvasElementObserver> mContextObserver;
+ bool mVRPresentationActive;
+
+public:
+ // Record whether this canvas should be write-only or not.
+ // We set this when script paints an image from a different origin.
+ // We also transitively set it when script paints a canvas which
+ // is itself write-only.
+ bool mWriteOnly;
+
+ bool IsPrintCallbackDone();
+
+ void HandlePrintCallback(nsPresContext::nsPresContextType aType);
+
+ nsresult DispatchPrintCallback(nsITimerCallback* aCallback);
+
+ void ResetPrintCallback();
+
+ HTMLCanvasElement* GetOriginalCanvas();
+
+ CanvasContextType GetCurrentContextType() {
+ return mCurrentContextType;
+ }
+};
+
+class HTMLCanvasPrintState final : public nsWrapperCache
+{
+public:
+ HTMLCanvasPrintState(HTMLCanvasElement* aCanvas,
+ nsICanvasRenderingContextInternal* aContext,
+ nsITimerCallback* aCallback);
+
+ nsISupports* Context() const;
+
+ void Done();
+
+ void NotifyDone();
+
+ bool mIsDone;
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(HTMLCanvasPrintState)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(HTMLCanvasPrintState)
+
+ virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+
+ HTMLCanvasElement* GetParentObject()
+ {
+ return mCanvas;
+ }
+
+private:
+ ~HTMLCanvasPrintState();
+ bool mPendingNotify;
+
+protected:
+ RefPtr<HTMLCanvasElement> mCanvas;
+ nsCOMPtr<nsICanvasRenderingContextInternal> mContext;
+ nsCOMPtr<nsITimerCallback> mCallback;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_HTMLCanvasElement_h */
diff --git a/dom/html/HTMLContentElement.cpp b/dom/html/HTMLContentElement.cpp
new file mode 100644
index 000000000..01c0158a0
--- /dev/null
+++ b/dom/html/HTMLContentElement.cpp
@@ -0,0 +1,393 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLContentElement.h"
+#include "mozilla/dom/HTMLContentElementBinding.h"
+#include "mozilla/dom/HTMLUnknownElement.h"
+#include "mozilla/dom/NodeListBinding.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/css/StyleRule.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsIAtom.h"
+#include "nsCSSRuleProcessor.h"
+#include "nsRuleData.h"
+#include "nsRuleProcessorData.h"
+#include "nsRuleWalker.h"
+#include "nsCSSParser.h"
+#include "nsDocument.h"
+
+// Expand NS_IMPL_NS_NEW_HTML_ELEMENT(Content) to add check for web components
+// being enabled.
+nsGenericHTMLElement*
+NS_NewHTMLContentElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ mozilla::dom::FromParser aFromParser)
+{
+ // When this check is removed, remove the nsDocument.h and
+ // HTMLUnknownElement.h includes. Also remove nsINode::IsHTMLContentElement.
+ //
+ // We have to jump through some hoops to be able to produce both NodeInfo* and
+ // already_AddRefed<NodeInfo>& for our callees.
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo);
+ if (!nsDocument::IsWebComponentsEnabled(nodeInfo)) {
+ already_AddRefed<mozilla::dom::NodeInfo> nodeInfoArg(nodeInfo.forget());
+ return new mozilla::dom::HTMLUnknownElement(nodeInfoArg);
+ }
+
+ already_AddRefed<mozilla::dom::NodeInfo> nodeInfoArg(nodeInfo.forget());
+ return new mozilla::dom::HTMLContentElement(nodeInfoArg);
+}
+
+using namespace mozilla::dom;
+
+HTMLContentElement::HTMLContentElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo), mValidSelector(true), mIsInsertionPoint(false)
+{
+}
+
+HTMLContentElement::~HTMLContentElement()
+{
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLContentElement,
+ nsGenericHTMLElement,
+ mMatchedNodes)
+
+NS_IMPL_ADDREF_INHERITED(HTMLContentElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLContentElement, Element)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLContentElement)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLContentElement)
+
+JSObject*
+HTMLContentElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLContentElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsresult
+HTMLContentElement::BindToTree(nsIDocument* aDocument,
+ nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ RefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow();
+
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ShadowRoot* containingShadow = GetContainingShadow();
+ if (containingShadow && !oldContainingShadow) {
+ nsINode* parentNode = nsINode::GetParentNode();
+ while (parentNode && parentNode != containingShadow) {
+ if (parentNode->IsHTMLContentElement()) {
+ // Content element in fallback content is not an insertion point.
+ return NS_OK;
+ }
+ parentNode = parentNode->GetParentNode();
+ }
+
+ // If the content element is being inserted into a ShadowRoot,
+ // add this element to the list of insertion points.
+ mIsInsertionPoint = true;
+ containingShadow->AddInsertionPoint(this);
+ containingShadow->SetInsertionPointChanged();
+ }
+
+ return NS_OK;
+}
+
+void
+HTMLContentElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ RefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow();
+
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+
+ if (oldContainingShadow && !GetContainingShadow() && mIsInsertionPoint) {
+ oldContainingShadow->RemoveInsertionPoint(this);
+
+ // Remove all the matched nodes now that the
+ // insertion point is no longer an insertion point.
+ ClearMatchedNodes();
+ oldContainingShadow->SetInsertionPointChanged();
+
+ mIsInsertionPoint = false;
+ }
+}
+
+void
+HTMLContentElement::AppendMatchedNode(nsIContent* aContent)
+{
+ mMatchedNodes.AppendElement(aContent);
+ nsTArray<nsIContent*>& destInsertionPoint = aContent->DestInsertionPoints();
+ destInsertionPoint.AppendElement(this);
+
+ if (mMatchedNodes.Length() == 1) {
+ // Fallback content gets dropped so we need to updated fallback
+ // content distribution.
+ UpdateFallbackDistribution();
+ }
+}
+
+void
+HTMLContentElement::UpdateFallbackDistribution()
+{
+ for (nsIContent* child = nsINode::GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ nsTArray<nsIContent*>& destInsertionPoint = child->DestInsertionPoints();
+ destInsertionPoint.Clear();
+ if (mMatchedNodes.IsEmpty()) {
+ destInsertionPoint.AppendElement(this);
+ }
+ }
+}
+
+void
+HTMLContentElement::RemoveMatchedNode(nsIContent* aContent)
+{
+ mMatchedNodes.RemoveElement(aContent);
+ ShadowRoot::RemoveDestInsertionPoint(this, aContent->DestInsertionPoints());
+
+ if (mMatchedNodes.IsEmpty()) {
+ // Fallback content is activated so we need to update fallback
+ // content distribution.
+ UpdateFallbackDistribution();
+ }
+}
+
+void
+HTMLContentElement::InsertMatchedNode(uint32_t aIndex, nsIContent* aContent)
+{
+ mMatchedNodes.InsertElementAt(aIndex, aContent);
+ nsTArray<nsIContent*>& destInsertionPoint = aContent->DestInsertionPoints();
+ destInsertionPoint.AppendElement(this);
+
+ if (mMatchedNodes.Length() == 1) {
+ // Fallback content gets dropped so we need to updated fallback
+ // content distribution.
+ UpdateFallbackDistribution();
+ }
+}
+
+void
+HTMLContentElement::ClearMatchedNodes()
+{
+ for (uint32_t i = 0; i < mMatchedNodes.Length(); i++) {
+ ShadowRoot::RemoveDestInsertionPoint(this, mMatchedNodes[i]->DestInsertionPoints());
+ }
+
+ mMatchedNodes.Clear();
+
+ UpdateFallbackDistribution();
+}
+
+static bool
+IsValidContentSelectors(nsCSSSelector* aSelector)
+{
+ nsCSSSelector* currentSelector = aSelector;
+ while (currentSelector) {
+ // Blacklist invalid selector fragments.
+ if (currentSelector->IsPseudoElement() ||
+ currentSelector->mPseudoClassList ||
+ currentSelector->mNegations ||
+ currentSelector->mOperator) {
+ return false;
+ }
+
+ currentSelector = currentSelector->mNext;
+ }
+
+ return true;
+}
+
+nsresult
+HTMLContentElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify)
+{
+ nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
+ aValue, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::select) {
+ // Select attribute was updated, the insertion point may match different
+ // elements.
+ nsIDocument* doc = OwnerDoc();
+ nsCSSParser parser(doc->CSSLoader());
+
+ mValidSelector = true;
+ mSelectorList = nullptr;
+
+ nsresult rv = parser.ParseSelectorString(aValue,
+ doc->GetDocumentURI(),
+ // Bug 11240
+ 0, // XXX get the line number!
+ getter_Transfers(mSelectorList));
+
+ // We don't want to return an exception if parsing failed because
+ // the spec does not define it as an exception case.
+ if (NS_SUCCEEDED(rv)) {
+ // Ensure that all the selectors are valid
+ nsCSSSelectorList* selectors = mSelectorList;
+ while (selectors) {
+ if (!IsValidContentSelectors(selectors->mSelectors)) {
+ // If we have an invalid selector, we can not match anything.
+ mValidSelector = false;
+ mSelectorList = nullptr;
+ break;
+ }
+ selectors = selectors->mNext;
+ }
+ }
+
+ ShadowRoot* containingShadow = GetContainingShadow();
+ if (containingShadow) {
+ containingShadow->DistributeAllNodes();
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLContentElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify)
+{
+ nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID,
+ aAttribute, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::select) {
+ // The select attribute was removed. This insertion point becomes
+ // a universal selector.
+ mValidSelector = true;
+ mSelectorList = nullptr;
+
+ ShadowRoot* containingShadow = GetContainingShadow();
+ if (containingShadow) {
+ containingShadow->DistributeAllNodes();
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+HTMLContentElement::Match(nsIContent* aContent)
+{
+ if (!mValidSelector) {
+ return false;
+ }
+
+ if (mSelectorList) {
+ nsIDocument* doc = OwnerDoc();
+ ShadowRoot* containingShadow = GetContainingShadow();
+ nsIContent* host = containingShadow->GetHost();
+
+ TreeMatchContext matchingContext(false, nsRuleWalker::eRelevantLinkUnvisited,
+ doc, TreeMatchContext::eNeverMatchVisited);
+ doc->FlushPendingLinkUpdates();
+ matchingContext.SetHasSpecifiedScope();
+ matchingContext.AddScopeElement(host->AsElement());
+
+ if (!aContent->IsElement()) {
+ return false;
+ }
+
+ return nsCSSRuleProcessor::SelectorListMatches(aContent->AsElement(),
+ matchingContext,
+ mSelectorList);
+ }
+
+ return true;
+}
+
+already_AddRefed<DistributedContentList>
+HTMLContentElement::GetDistributedNodes()
+{
+ RefPtr<DistributedContentList> list = new DistributedContentList(this);
+ return list.forget();
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DistributedContentList, mParent,
+ mDistributedNodes)
+
+NS_INTERFACE_TABLE_HEAD(DistributedContentList)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE(DistributedContentList, nsINodeList, nsIDOMNodeList)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(DistributedContentList)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DistributedContentList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DistributedContentList)
+
+DistributedContentList::DistributedContentList(HTMLContentElement* aHostElement)
+ : mParent(aHostElement)
+{
+ MOZ_COUNT_CTOR(DistributedContentList);
+
+ if (aHostElement->IsInsertionPoint()) {
+ if (aHostElement->MatchedNodes().IsEmpty()) {
+ // Fallback content.
+ nsINode* contentNode = aHostElement;
+ for (nsIContent* content = contentNode->GetFirstChild();
+ content;
+ content = content->GetNextSibling()) {
+ mDistributedNodes.AppendElement(content);
+ }
+ } else {
+ mDistributedNodes.AppendElements(aHostElement->MatchedNodes());
+ }
+ }
+}
+
+DistributedContentList::~DistributedContentList()
+{
+ MOZ_COUNT_DTOR(DistributedContentList);
+}
+
+nsIContent*
+DistributedContentList::Item(uint32_t aIndex)
+{
+ return mDistributedNodes.SafeElementAt(aIndex);
+}
+
+NS_IMETHODIMP
+DistributedContentList::Item(uint32_t aIndex, nsIDOMNode** aReturn)
+{
+ nsIContent* item = Item(aIndex);
+ if (!item) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return CallQueryInterface(item, aReturn);
+}
+
+NS_IMETHODIMP
+DistributedContentList::GetLength(uint32_t* aLength)
+{
+ *aLength = mDistributedNodes.Length();
+ return NS_OK;
+}
+
+int32_t
+DistributedContentList::IndexOf(nsIContent* aContent)
+{
+ return mDistributedNodes.IndexOf(aContent);
+}
+
+JSObject*
+DistributedContentList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return NodeListBinding::Wrap(aCx, this, aGivenProto);
+}
+
diff --git a/dom/html/HTMLContentElement.h b/dom/html/HTMLContentElement.h
new file mode 100644
index 000000000..f019e56cd
--- /dev/null
+++ b/dom/html/HTMLContentElement.h
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLContentElement_h__
+#define mozilla_dom_HTMLContentElement_h__
+
+#include "nsAutoPtr.h"
+#include "nsINodeList.h"
+#include "nsGenericHTMLElement.h"
+
+struct nsCSSSelectorList;
+
+namespace mozilla {
+namespace dom {
+
+class DistributedContentList;
+
+class HTMLContentElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLContentElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLContentElement,
+ nsGenericHTMLElement)
+
+ static HTMLContentElement* FromContent(nsIContent* aContent)
+ {
+ if (aContent->IsHTMLContentElement()) {
+ return static_cast<HTMLContentElement*>(aContent);
+ }
+
+ return nullptr;
+ }
+
+ virtual bool IsHTMLContentElement() const override { return true; }
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ virtual nsIDOMNode* AsDOMNode() override { return this; }
+
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+
+ /**
+ * Returns whether if the selector of this insertion point
+ * matches the provided content.
+ */
+ bool Match(nsIContent* aContent);
+ bool IsInsertionPoint() const { return mIsInsertionPoint; }
+ nsCOMArray<nsIContent>& MatchedNodes() { return mMatchedNodes; }
+ void AppendMatchedNode(nsIContent* aContent);
+ void RemoveMatchedNode(nsIContent* aContent);
+ void InsertMatchedNode(uint32_t aIndex, nsIContent* aContent);
+ void ClearMatchedNodes();
+
+ virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify) override;
+
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify) override;
+
+ // WebIDL methods.
+ already_AddRefed<DistributedContentList> GetDistributedNodes();
+ void GetSelect(nsAString& aSelect)
+ {
+ Element::GetAttr(kNameSpaceID_None, nsGkAtoms::select, aSelect);
+ }
+ void SetSelect(const nsAString& aSelect)
+ {
+ Element::SetAttr(kNameSpaceID_None, nsGkAtoms::select, aSelect, true);
+ }
+
+protected:
+ virtual ~HTMLContentElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ /**
+ * Updates the destination insertion points of the fallback
+ * content of this insertion point. If there are nodes matched
+ * to this insertion point, then destination insertion points
+ * of fallback are cleared, otherwise, this insertion point
+ * is a destination insertion point.
+ */
+ void UpdateFallbackDistribution();
+
+ /**
+ * An array of nodes from the ShadowRoot host that match the
+ * content insertion selector.
+ */
+ nsCOMArray<nsIContent> mMatchedNodes;
+
+ nsAutoPtr<nsCSSSelectorList> mSelectorList;
+ bool mValidSelector;
+ bool mIsInsertionPoint;
+};
+
+class DistributedContentList : public nsINodeList
+{
+public:
+ explicit DistributedContentList(HTMLContentElement* aHostElement);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DistributedContentList)
+
+ // 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 JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+protected:
+ virtual ~DistributedContentList();
+ RefPtr<HTMLContentElement> mParent;
+ nsCOMArray<nsIContent> mDistributedNodes;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLContentElement_h__
+
diff --git a/dom/html/HTMLDataElement.cpp b/dom/html/HTMLDataElement.cpp
new file mode 100644
index 000000000..e9d53e573
--- /dev/null
+++ b/dom/html/HTMLDataElement.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 "HTMLDataElement.h"
+#include "mozilla/dom/HTMLDataElementBinding.h"
+#include "nsGenericHTMLElement.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Data)
+
+namespace mozilla {
+namespace dom {
+
+HTMLDataElement::HTMLDataElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+{
+}
+
+HTMLDataElement::~HTMLDataElement()
+{
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLDataElement)
+
+JSObject*
+HTMLDataElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLDataElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLDataElement.h b/dom/html/HTMLDataElement.h
new file mode 100644
index 000000000..4fd26a017
--- /dev/null
+++ b/dom/html/HTMLDataElement.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLDataElement_h
+#define mozilla_dom_HTMLDataElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLDataElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLDataElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ // HTMLDataElement WebIDL
+ void GetValue(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::value, aValue);
+ }
+
+ void SetValue(const nsAString& aValue, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::value, aValue, aError);
+ }
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+
+protected:
+ virtual ~HTMLDataElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLDataElement_h
diff --git a/dom/html/HTMLDataListElement.cpp b/dom/html/HTMLDataListElement.cpp
new file mode 100644
index 000000000..d9ad4da09
--- /dev/null
+++ b/dom/html/HTMLDataListElement.cpp
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HTMLDataListElement.h"
+#include "mozilla/dom/HTMLDataListElementBinding.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(DataList)
+
+namespace mozilla {
+namespace dom {
+
+HTMLDataListElement::~HTMLDataListElement()
+{
+}
+
+JSObject*
+HTMLDataListElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLDataListElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLDataListElement, nsGenericHTMLElement,
+ mOptions)
+
+NS_IMPL_ADDREF_INHERITED(HTMLDataListElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLDataListElement, Element)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLDataListElement)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
+
+
+NS_IMPL_ELEMENT_CLONE(HTMLDataListElement)
+
+bool
+HTMLDataListElement::MatchOptions(nsIContent* aContent, int32_t aNamespaceID,
+ nsIAtom* aAtom, void* aData)
+{
+ return aContent->NodeInfo()->Equals(nsGkAtoms::option, kNameSpaceID_XHTML) &&
+ !aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLDataListElement.h b/dom/html/HTMLDataListElement.h
new file mode 100644
index 000000000..e0aff818b
--- /dev/null
+++ b/dom/html/HTMLDataListElement.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef HTMLDataListElement_h___
+#define HTMLDataListElement_h___
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsContentList.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLDataListElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLDataListElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsContentList* Options()
+ {
+ if (!mOptions) {
+ mOptions = new nsContentList(this, MatchOptions, nullptr, nullptr, true);
+ }
+
+ return mOptions;
+ }
+
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ // This function is used to generate the nsContentList (option elements).
+ static bool MatchOptions(nsIContent* aContent, int32_t aNamespaceID,
+ nsIAtom* aAtom, void* aData);
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLDataListElement,
+ nsGenericHTMLElement)
+protected:
+ virtual ~HTMLDataListElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // <option>'s list inside the datalist element.
+ RefPtr<nsContentList> mOptions;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* HTMLDataListElement_h___ */
diff --git a/dom/html/HTMLDetailsElement.cpp b/dom/html/HTMLDetailsElement.cpp
new file mode 100644
index 000000000..ed20b50ca
--- /dev/null
+++ b/dom/html/HTMLDetailsElement.cpp
@@ -0,0 +1,111 @@
+/* -*- 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 "mozilla/dom/HTMLDetailsElement.h"
+
+#include "mozilla/dom/HTMLDetailsElementBinding.h"
+#include "mozilla/dom/HTMLUnknownElement.h"
+#include "mozilla/Preferences.h"
+
+// Expand NS_IMPL_NS_NEW_HTML_ELEMENT(Details) to add pref check.
+nsGenericHTMLElement*
+NS_NewHTMLDetailsElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ mozilla::dom::FromParser aFromParser)
+{
+ if (!mozilla::dom::HTMLDetailsElement::IsDetailsEnabled()) {
+ return new mozilla::dom::HTMLUnknownElement(aNodeInfo);
+ }
+
+ return new mozilla::dom::HTMLDetailsElement(aNodeInfo);
+}
+
+namespace mozilla {
+namespace dom {
+
+/* static */ bool
+HTMLDetailsElement::IsDetailsEnabled()
+{
+ static bool isDetailsEnabled = false;
+ static bool added = false;
+
+ if (!added) {
+ Preferences::AddBoolVarCache(&isDetailsEnabled,
+ "dom.details_element.enabled");
+ added = true;
+ }
+
+ return isDetailsEnabled;
+}
+
+HTMLDetailsElement::~HTMLDetailsElement()
+{
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLDetailsElement)
+
+nsIContent*
+HTMLDetailsElement::GetFirstSummary() const
+{
+ // XXX: Bug 1245032: Might want to cache the first summary element.
+ for (nsIContent* child = nsINode::GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->IsHTMLElement(nsGkAtoms::summary)) {
+ return child;
+ }
+ }
+ return nullptr;
+}
+
+nsChangeHint
+HTMLDetailsElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const
+{
+ nsChangeHint hint =
+ nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
+ if (aAttribute == nsGkAtoms::open) {
+ hint |= nsChangeHint_ReconstructFrame;
+ }
+ return hint;
+}
+
+nsresult
+HTMLDetailsElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::open) {
+ bool setOpen = aValue != nullptr;
+ if (Open() != setOpen) {
+ if (mToggleEventDispatcher) {
+ mToggleEventDispatcher->Cancel();
+ }
+ // According to the html spec, a 'toggle' event is a simple event which
+ // does not bubble.
+ mToggleEventDispatcher =
+ new AsyncEventDispatcher(this, NS_LITERAL_STRING("toggle"), false);
+ mToggleEventDispatcher->PostDOMEvent();
+ }
+ }
+
+ return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue,
+ aNotify);
+}
+
+void
+HTMLDetailsElement::AsyncEventRunning(AsyncEventDispatcher* aEvent)
+{
+ if (mToggleEventDispatcher == aEvent) {
+ mToggleEventDispatcher = nullptr;
+ }
+}
+
+JSObject*
+HTMLDetailsElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLDetailsElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLDetailsElement.h b/dom/html/HTMLDetailsElement.h
new file mode 100644
index 000000000..5a3af27b4
--- /dev/null
+++ b/dom/html/HTMLDetailsElement.h
@@ -0,0 +1,74 @@
+/* -*- 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_HTMLDetailsElement_h
+#define mozilla_dom_HTMLDetailsElement_h
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+namespace dom {
+
+// HTMLDetailsElement implements the <details> tag, which is used as a
+// disclosure widget from which the user can obtain additional information or
+// controls. Please see the spec for more information.
+// https://html.spec.whatwg.org/multipage/forms.html#the-details-element
+//
+class HTMLDetailsElement final : public nsGenericHTMLElement
+{
+public:
+ using NodeInfo = mozilla::dom::NodeInfo;
+
+ static bool IsDetailsEnabled();
+
+ explicit HTMLDetailsElement(already_AddRefed<NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ }
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLDetailsElement, details)
+
+ nsIContent* GetFirstSummary() const;
+
+ nsresult Clone(NodeInfo* aNodeInfo, nsINode** aResult) const override;
+
+ nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const override;
+
+ nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue, bool aNotify) override;
+
+ // HTMLDetailsElement WebIDL
+ bool Open() const { return GetBoolAttr(nsGkAtoms::open); }
+
+ void SetOpen(bool aOpen, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::open, aOpen, aError);
+ }
+
+ void ToggleOpen()
+ {
+ ErrorResult rv;
+ SetOpen(!Open(), rv);
+ rv.SuppressException();
+ }
+
+ virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;
+
+protected:
+ virtual ~HTMLDetailsElement();
+
+ JSObject* WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ RefPtr<AsyncEventDispatcher> mToggleEventDispatcher;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_HTMLDetailsElement_h */
diff --git a/dom/html/HTMLDivElement.cpp b/dom/html/HTMLDivElement.cpp
new file mode 100644
index 000000000..b56187e29
--- /dev/null
+++ b/dom/html/HTMLDivElement.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 "HTMLDivElement.h"
+#include "nsGenericHTMLElement.h"
+#include "nsStyleConsts.h"
+#include "nsMappedAttributes.h"
+#include "mozilla/dom/HTMLDivElementBinding.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Div)
+
+namespace mozilla {
+namespace dom {
+
+HTMLDivElement::~HTMLDivElement()
+{
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLDivElement)
+
+JSObject*
+HTMLDivElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return dom::HTMLDivElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+bool
+HTMLDivElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (mNodeInfo->Equals(nsGkAtoms::marquee)) {
+ if ((aAttribute == nsGkAtoms::width) ||
+ (aAttribute == nsGkAtoms::height)) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::bgcolor) {
+ return aResult.ParseColor(aValue);
+ }
+ if ((aAttribute == nsGkAtoms::hspace) ||
+ (aAttribute == nsGkAtoms::vspace)) {
+ return aResult.ParseIntWithBounds(aValue, 0);
+ }
+ }
+
+ if (mNodeInfo->Equals(nsGkAtoms::div) &&
+ aAttribute == nsGkAtoms::align) {
+ return ParseDivAlignValue(aValue, aResult);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLDivElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ nsGenericHTMLElement::MapDivAlignAttributeInto(aAttributes, aData);
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+static void
+MapMarqueeAttributesIntoRule(const nsMappedAttributes* aAttributes, nsRuleData* aData)
+{
+ nsGenericHTMLElement::MapImageMarginAttributeInto(aAttributes, aData);
+ nsGenericHTMLElement::MapImageSizeAttributesInto(aAttributes, aData);
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+ nsGenericHTMLElement::MapBGColorInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLDivElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ if (mNodeInfo->Equals(nsGkAtoms::div)) {
+ static const MappedAttributeEntry* const map[] = {
+ sDivAlignAttributeMap,
+ sCommonAttributeMap
+ };
+ return FindAttributeDependence(aAttribute, map);
+ }
+ if (mNodeInfo->Equals(nsGkAtoms::marquee)) {
+ static const MappedAttributeEntry* const map[] = {
+ sImageMarginSizeAttributeMap,
+ sBackgroundColorAttributeMap,
+ sCommonAttributeMap
+ };
+ return FindAttributeDependence(aAttribute, map);
+ }
+
+ return nsGenericHTMLElement::IsAttributeMapped(aAttribute);
+}
+
+nsMapRuleToAttributesFunc
+HTMLDivElement::GetAttributeMappingFunction() const
+{
+ if (mNodeInfo->Equals(nsGkAtoms::div)) {
+ return &MapAttributesIntoRule;
+ }
+ if (mNodeInfo->Equals(nsGkAtoms::marquee)) {
+ return &MapMarqueeAttributesIntoRule;
+ }
+ return nsGenericHTMLElement::GetAttributeMappingFunction();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLDivElement.h b/dom/html/HTMLDivElement.h
new file mode 100644
index 000000000..9cd0f84b9
--- /dev/null
+++ b/dom/html/HTMLDivElement.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 HTMLDivElement_h___
+#define HTMLDivElement_h___
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLDivElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLDivElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ }
+
+ void GetAlign(DOMString& aAlign)
+ {
+ GetHTMLAttr(nsGkAtoms::align, aAlign);
+ }
+ void SetAlign(const nsAString& aAlign, mozilla::ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::align, aAlign, aError);
+ }
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+protected:
+ virtual ~HTMLDivElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* HTMLDivElement_h___ */
diff --git a/dom/html/HTMLElement.cpp b/dom/html/HTMLElement.cpp
new file mode 100644
index 000000000..b2f23b931
--- /dev/null
+++ b/dom/html/HTMLElement.cpp
@@ -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/. */
+
+#include "nsGenericHTMLElement.h"
+#include "mozilla/dom/HTMLElementBinding.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+ virtual ~HTMLElement();
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo,
+ nsINode** aResult) const override;
+
+protected:
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+};
+
+HTMLElement::HTMLElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+{
+}
+
+HTMLElement::~HTMLElement()
+{
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLElement)
+
+JSObject*
+HTMLElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return dom::HTMLElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
+
+// Here, we expand 'NS_IMPL_NS_NEW_HTML_ELEMENT()' by hand.
+// (Calling the macro directly (with no args) produces compiler warnings.)
+nsGenericHTMLElement*
+NS_NewHTMLElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ mozilla::dom::FromParser aFromParser)
+{
+ return new mozilla::dom::HTMLElement(aNodeInfo);
+}
diff --git a/dom/html/HTMLFieldSetElement.cpp b/dom/html/HTMLFieldSetElement.cpp
new file mode 100644
index 000000000..865d3c9cf
--- /dev/null
+++ b/dom/html/HTMLFieldSetElement.cpp
@@ -0,0 +1,370 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/EventStates.h"
+#include "mozilla/dom/HTMLFieldSetElement.h"
+#include "mozilla/dom/HTMLFieldSetElementBinding.h"
+#include "nsContentList.h"
+#include "nsQueryObject.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(FieldSet)
+
+namespace mozilla {
+namespace dom {
+
+HTMLFieldSetElement::HTMLFieldSetElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLFormElement(aNodeInfo)
+ , mElements(nullptr)
+ , mFirstLegend(nullptr)
+ , mInvalidElementsCount(0)
+{
+ // <fieldset> is always barred from constraint validation.
+ SetBarredFromConstraintValidation(true);
+
+ // We start out enabled and valid.
+ AddStatesSilently(NS_EVENT_STATE_ENABLED | NS_EVENT_STATE_VALID);
+}
+
+HTMLFieldSetElement::~HTMLFieldSetElement()
+{
+ uint32_t length = mDependentElements.Length();
+ for (uint32_t i = 0; i < length; ++i) {
+ mDependentElements[i]->ForgetFieldSet(this);
+ }
+}
+
+// nsISupports
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLFieldSetElement, nsGenericHTMLFormElement,
+ mValidity, mElements)
+
+NS_IMPL_ADDREF_INHERITED(HTMLFieldSetElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLFieldSetElement, Element)
+
+// QueryInterface implementation for HTMLFieldSetElement
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLFieldSetElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLFieldSetElement,
+ nsIDOMHTMLFieldSetElement,
+ nsIConstraintValidation)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLFieldSetElement)
+
+
+NS_IMPL_BOOL_ATTR(HTMLFieldSetElement, Disabled, disabled)
+NS_IMPL_STRING_ATTR(HTMLFieldSetElement, Name, name)
+
+// nsIConstraintValidation
+NS_IMPL_NSICONSTRAINTVALIDATION(HTMLFieldSetElement)
+
+bool
+HTMLFieldSetElement::IsDisabledForEvents(EventMessage aMessage)
+{
+ return IsElementDisabledForEvents(aMessage, nullptr);
+}
+
+// nsIContent
+nsresult
+HTMLFieldSetElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ // Do not process any DOM events if the element is disabled.
+ aVisitor.mCanHandle = false;
+ if (IsDisabledForEvents(aVisitor.mEvent->mMessage)) {
+ return NS_OK;
+ }
+
+ return nsGenericHTMLFormElement::PreHandleEvent(aVisitor);
+}
+
+nsresult
+HTMLFieldSetElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::disabled &&
+ nsINode::GetFirstChild()) {
+ if (!mElements) {
+ mElements = new nsContentList(this, MatchListedElements, nullptr, nullptr,
+ true);
+ }
+
+ uint32_t length = mElements->Length(true);
+ for (uint32_t i=0; i<length; ++i) {
+ static_cast<nsGenericHTMLFormElement*>(mElements->Item(i))
+ ->FieldSetDisabledChanged(aNotify);
+ }
+ }
+
+ return nsGenericHTMLFormElement::AfterSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+// nsIDOMHTMLFieldSetElement
+
+NS_IMETHODIMP
+HTMLFieldSetElement::GetForm(nsIDOMHTMLFormElement** aForm)
+{
+ return nsGenericHTMLFormElement::GetForm(aForm);
+}
+
+NS_IMETHODIMP
+HTMLFieldSetElement::GetType(nsAString& aType)
+{
+ aType.AssignLiteral("fieldset");
+ return NS_OK;
+}
+
+/* static */
+bool
+HTMLFieldSetElement::MatchListedElements(nsIContent* aContent, int32_t aNamespaceID,
+ nsIAtom* aAtom, void* aData)
+{
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(aContent);
+ return formControl;
+}
+
+NS_IMETHODIMP
+HTMLFieldSetElement::GetElements(nsIDOMHTMLCollection** aElements)
+{
+ NS_ADDREF(*aElements = Elements());
+ return NS_OK;
+}
+
+nsIHTMLCollection*
+HTMLFieldSetElement::Elements()
+{
+ if (!mElements) {
+ mElements = new nsContentList(this, MatchListedElements, nullptr, nullptr,
+ true);
+ }
+
+ return mElements;
+}
+
+// nsIFormControl
+
+nsresult
+HTMLFieldSetElement::Reset()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLFieldSetElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission)
+{
+ return NS_OK;
+}
+
+nsresult
+HTMLFieldSetElement::InsertChildAt(nsIContent* aChild, uint32_t aIndex,
+ bool aNotify)
+{
+ bool firstLegendHasChanged = false;
+
+ if (aChild->IsHTMLElement(nsGkAtoms::legend)) {
+ if (!mFirstLegend) {
+ mFirstLegend = aChild;
+ // We do not want to notify the first time mFirstElement is set.
+ } else {
+ // If mFirstLegend is before aIndex, we do not change it.
+ // Otherwise, mFirstLegend is now aChild.
+ if (int32_t(aIndex) <= IndexOf(mFirstLegend)) {
+ mFirstLegend = aChild;
+ firstLegendHasChanged = true;
+ }
+ }
+ }
+
+ nsresult rv = nsGenericHTMLFormElement::InsertChildAt(aChild, aIndex, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (firstLegendHasChanged) {
+ NotifyElementsForFirstLegendChange(aNotify);
+ }
+
+ return rv;
+}
+
+void
+HTMLFieldSetElement::RemoveChildAt(uint32_t aIndex, bool aNotify)
+{
+ bool firstLegendHasChanged = false;
+
+ if (mFirstLegend && (GetChildAt(aIndex) == mFirstLegend)) {
+ // If we are removing the first legend we have to found another one.
+ nsIContent* child = mFirstLegend->GetNextSibling();
+ mFirstLegend = nullptr;
+ firstLegendHasChanged = true;
+
+ for (; child; child = child->GetNextSibling()) {
+ if (child->IsHTMLElement(nsGkAtoms::legend)) {
+ mFirstLegend = child;
+ break;
+ }
+ }
+ }
+
+ nsGenericHTMLFormElement::RemoveChildAt(aIndex, aNotify);
+
+ if (firstLegendHasChanged) {
+ NotifyElementsForFirstLegendChange(aNotify);
+ }
+}
+
+void
+HTMLFieldSetElement::AddElement(nsGenericHTMLFormElement* aElement)
+{
+ mDependentElements.AppendElement(aElement);
+
+ // If the element that we are adding aElement is a fieldset, then all the
+ // invalid elements in aElement are also invalid elements of this.
+ HTMLFieldSetElement* fieldSet = FromContent(aElement);
+ if (fieldSet) {
+ for (int32_t i = 0; i < fieldSet->mInvalidElementsCount; i++) {
+ UpdateValidity(false);
+ }
+ return;
+ }
+
+ // We need to update the validity of the fieldset.
+ nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aElement);
+ if (cvElmt &&
+ cvElmt->IsCandidateForConstraintValidation() && !cvElmt->IsValid()) {
+ UpdateValidity(false);
+ }
+
+#if DEBUG
+ int32_t debugInvalidElementsCount = 0;
+ for (uint32_t i = 0; i < mDependentElements.Length(); i++) {
+ HTMLFieldSetElement* fieldSet = FromContent(mDependentElements[i]);
+ if (fieldSet) {
+ debugInvalidElementsCount += fieldSet->mInvalidElementsCount;
+ continue;
+ }
+ nsCOMPtr<nsIConstraintValidation>
+ cvElmt = do_QueryObject(mDependentElements[i]);
+ if (cvElmt &&
+ cvElmt->IsCandidateForConstraintValidation() &&
+ !(cvElmt->IsValid())) {
+ debugInvalidElementsCount += 1;
+ }
+ }
+ MOZ_ASSERT(debugInvalidElementsCount == mInvalidElementsCount);
+#endif
+}
+
+void
+HTMLFieldSetElement::RemoveElement(nsGenericHTMLFormElement* aElement)
+{
+ mDependentElements.RemoveElement(aElement);
+
+ // If the element that we are removing aElement is a fieldset, then all the
+ // invalid elements in aElement are also removed from this.
+ HTMLFieldSetElement* fieldSet = FromContent(aElement);
+ if (fieldSet) {
+ for (int32_t i = 0; i < fieldSet->mInvalidElementsCount; i++) {
+ UpdateValidity(true);
+ }
+ return;
+ }
+
+ // We need to update the validity of the fieldset.
+ nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aElement);
+ if (cvElmt &&
+ cvElmt->IsCandidateForConstraintValidation() && !cvElmt->IsValid()) {
+ UpdateValidity(true);
+ }
+
+#if DEBUG
+ int32_t debugInvalidElementsCount = 0;
+ for (uint32_t i = 0; i < mDependentElements.Length(); i++) {
+ HTMLFieldSetElement* fieldSet = FromContent(mDependentElements[i]);
+ if (fieldSet) {
+ debugInvalidElementsCount += fieldSet->mInvalidElementsCount;
+ continue;
+ }
+ nsCOMPtr<nsIConstraintValidation>
+ cvElmt = do_QueryObject(mDependentElements[i]);
+ if (cvElmt &&
+ cvElmt->IsCandidateForConstraintValidation() &&
+ !(cvElmt->IsValid())) {
+ debugInvalidElementsCount += 1;
+ }
+ }
+ MOZ_ASSERT(debugInvalidElementsCount == mInvalidElementsCount);
+#endif
+}
+
+void
+HTMLFieldSetElement::NotifyElementsForFirstLegendChange(bool aNotify)
+{
+ /**
+ * NOTE: this could be optimized if only call when the fieldset is currently
+ * disabled.
+ * This should also make sure that mElements is set when we happen to be here.
+ * However, this method shouldn't be called very often in normal use cases.
+ */
+ if (!mElements) {
+ mElements = new nsContentList(this, MatchListedElements, nullptr, nullptr,
+ true);
+ }
+
+ uint32_t length = mElements->Length(true);
+ for (uint32_t i = 0; i < length; ++i) {
+ static_cast<nsGenericHTMLFormElement*>(mElements->Item(i))
+ ->FieldSetFirstLegendChanged(aNotify);
+ }
+}
+
+void
+HTMLFieldSetElement::UpdateValidity(bool aElementValidity)
+{
+ if (aElementValidity) {
+ --mInvalidElementsCount;
+ } else {
+ ++mInvalidElementsCount;
+ }
+
+ MOZ_ASSERT(mInvalidElementsCount >= 0);
+
+ // The fieldset validity has just changed if:
+ // - there are no more invalid elements ;
+ // - or there is one invalid elmement and an element just became invalid.
+ if (!mInvalidElementsCount || (mInvalidElementsCount == 1 && !aElementValidity)) {
+ UpdateState(true);
+ }
+
+ // We should propagate the change to the fieldset parent chain.
+ if (mFieldSet) {
+ mFieldSet->UpdateValidity(aElementValidity);
+ }
+
+ return;
+}
+
+EventStates
+HTMLFieldSetElement::IntrinsicState() const
+{
+ EventStates state = nsGenericHTMLFormElement::IntrinsicState();
+
+ if (mInvalidElementsCount) {
+ state |= NS_EVENT_STATE_INVALID;
+ } else {
+ state |= NS_EVENT_STATE_VALID;
+ }
+
+ return state;
+}
+
+JSObject*
+HTMLFieldSetElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLFieldSetElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLFieldSetElement.h b/dom/html/HTMLFieldSetElement.h
new file mode 100644
index 000000000..d169434ae
--- /dev/null
+++ b/dom/html/HTMLFieldSetElement.h
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLFieldSetElement_h
+#define mozilla_dom_HTMLFieldSetElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIDOMHTMLFieldSetElement.h"
+#include "nsIConstraintValidation.h"
+#include "mozilla/dom/HTMLFormElement.h"
+#include "mozilla/dom/ValidityState.h"
+
+namespace mozilla {
+class EventChainPreVisitor;
+namespace dom {
+
+class HTMLFieldSetElement final : public nsGenericHTMLFormElement,
+ public nsIDOMHTMLFieldSetElement,
+ public nsIConstraintValidation
+{
+public:
+ using nsGenericHTMLFormElement::GetForm;
+ using nsIConstraintValidation::Validity;
+ using nsIConstraintValidation::CheckValidity;
+ using nsIConstraintValidation::ReportValidity;
+ using nsIConstraintValidation::GetValidationMessage;
+
+ explicit HTMLFieldSetElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLFieldSetElement, fieldset)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLFieldSetElement
+ NS_DECL_NSIDOMHTMLFIELDSETELEMENT
+
+ // nsIContent
+ virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) override;
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+
+ virtual nsresult InsertChildAt(nsIContent* aChild, uint32_t aIndex,
+ bool aNotify) override;
+ virtual void RemoveChildAt(uint32_t aIndex, bool aNotify) override;
+
+ // nsIFormControl
+ NS_IMETHOD_(uint32_t) GetType() const override { return NS_FORM_FIELDSET; }
+ NS_IMETHOD Reset() override;
+ NS_IMETHOD SubmitNamesValues(HTMLFormSubmission* aFormSubmission) override;
+ virtual bool IsDisabledForEvents(EventMessage aMessage) override;
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ const nsIContent* GetFirstLegend() const { return mFirstLegend; }
+
+ void AddElement(nsGenericHTMLFormElement* aElement);
+
+ void RemoveElement(nsGenericHTMLFormElement* aElement);
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLFieldSetElement,
+ nsGenericHTMLFormElement)
+
+ // WebIDL
+ bool Disabled() const
+ {
+ return GetBoolAttr(nsGkAtoms::disabled);
+ }
+ void SetDisabled(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::disabled, aValue, aRv);
+ }
+
+ // XPCOM GetName is OK for us
+
+ void SetName(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::name, aValue, aRv);
+ }
+
+ // XPCOM GetType is OK for us
+
+ nsIHTMLCollection* Elements();
+
+ // XPCOM WillValidate is OK for us
+
+ // XPCOM Validity is OK for us
+
+ // XPCOM GetValidationMessage is OK for us
+
+ // XPCOM CheckValidity is OK for us
+
+ // XPCOM SetCustomValidity is OK for us
+
+ virtual EventStates IntrinsicState() const override;
+
+
+ /*
+ * This method will update the fieldset's validity. This method has to be
+ * called by fieldset elements whenever their validity state or status regarding
+ * constraint validation changes.
+ *
+ * @note If an element becomes barred from constraint validation, it has to
+ * be considered as valid.
+ *
+ * @param aElementValidityState the new validity state of the element
+ */
+ void UpdateValidity(bool aElementValidityState);
+
+protected:
+ virtual ~HTMLFieldSetElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+
+ /**
+ * Notify all elements (in mElements) that the first legend of the fieldset
+ * has now changed.
+ */
+ void NotifyElementsForFirstLegendChange(bool aNotify);
+
+ // This function is used to generate the nsContentList (listed form elements).
+ static bool MatchListedElements(nsIContent* aContent, int32_t aNamespaceID,
+ nsIAtom* aAtom, void* aData);
+
+ // listed form controls elements.
+ RefPtr<nsContentList> mElements;
+
+ // List of elements which have this fieldset as first fieldset ancestor.
+ nsTArray<nsGenericHTMLFormElement*> mDependentElements;
+
+ nsIContent* mFirstLegend;
+
+ /**
+ * Number of invalid and candidate for constraint validation
+ * elements in the fieldSet the last time UpdateValidity has been called.
+ *
+ * @note Should only be used by UpdateValidity() and IntrinsicState()!
+ */
+ int32_t mInvalidElementsCount;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_HTMLFieldSetElement_h */
diff --git a/dom/html/HTMLFontElement.cpp b/dom/html/HTMLFontElement.cpp
new file mode 100644
index 000000000..30cb9de8b
--- /dev/null
+++ b/dom/html/HTMLFontElement.cpp
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HTMLFontElement.h"
+#include "mozilla/dom/HTMLFontElementBinding.h"
+#include "nsAttrValueInlines.h"
+#include "nsMappedAttributes.h"
+#include "nsRuleData.h"
+#include "nsContentUtils.h"
+#include "nsCSSParser.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Font)
+
+namespace mozilla {
+namespace dom {
+
+HTMLFontElement::~HTMLFontElement()
+{
+}
+
+JSObject*
+HTMLFontElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLFontElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLFontElement)
+
+bool
+HTMLFontElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::size) {
+ int32_t size = nsContentUtils::ParseLegacyFontSize(aValue);
+ if (size) {
+ aResult.SetTo(size, &aValue);
+ return true;
+ }
+ return false;
+ }
+ if (aAttribute == nsGkAtoms::color) {
+ return aResult.ParseColor(aValue);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLFontElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Font)) {
+ // face: string list
+ nsCSSValue* family = aData->ValueForFontFamily();
+ if (family->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::face);
+ if (value && value->Type() == nsAttrValue::eString &&
+ !value->IsEmptyString()) {
+ nsCSSParser parser;
+ parser.ParseFontFamilyListString(value->GetStringValue(),
+ nullptr, 0, *family);
+ }
+ }
+
+ // size: int
+ nsCSSValue* fontSize = aData->ValueForFontSize();
+ if (fontSize->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::size);
+ if (value && value->Type() == nsAttrValue::eInteger) {
+ fontSize->SetIntValue(value->GetIntegerValue(), eCSSUnit_Enumerated);
+ }
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Color)) {
+ nsCSSValue* colorValue = aData->ValueForColor();
+ if (colorValue->GetUnit() == eCSSUnit_Null &&
+ aData->mPresContext->UseDocumentColors()) {
+ // color: color
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::color);
+ nscolor color;
+ if (value && value->GetColorValue(color)) {
+ colorValue->SetColorValue(color);
+ }
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(TextReset) &&
+ aData->mPresContext->CompatibilityMode() == eCompatibility_NavQuirks) {
+ // Make <a><font color="red">text</font></a> give the text a red underline
+ // in quirks mode. The NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL flag only
+ // affects quirks mode rendering.
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::color);
+ nscolor color;
+ if (value && value->GetColorValue(color)) {
+ nsCSSValue* decoration = aData->ValueForTextDecorationLine();
+ int32_t newValue = NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL;
+ if (decoration->GetUnit() == eCSSUnit_Enumerated) {
+ newValue |= decoration->GetIntValue();
+ }
+ decoration->SetIntValue(newValue, eCSSUnit_Enumerated);
+ }
+ }
+
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLFontElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::face },
+ { &nsGkAtoms::size },
+ { &nsGkAtoms::color },
+ { nullptr }
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+
+nsMapRuleToAttributesFunc
+HTMLFontElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLFontElement.h b/dom/html/HTMLFontElement.h
new file mode 100644
index 000000000..3af54c3ef
--- /dev/null
+++ b/dom/html/HTMLFontElement.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 HTMLFontElement_h___
+#define HTMLFontElement_h___
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLFontElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLFontElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ }
+
+ void GetColor(DOMString& aColor)
+ {
+ GetHTMLAttr(nsGkAtoms::color, aColor);
+ }
+ void SetColor(const nsAString& aColor, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::color, aColor, aError);
+ }
+ void GetFace(DOMString& aFace)
+ {
+ GetHTMLAttr(nsGkAtoms::face, aFace);
+ }
+ void SetFace(const nsAString& aFace, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::face, aFace, aError);
+ }
+ void GetSize(DOMString& aSize)
+ {
+ GetHTMLAttr(nsGkAtoms::size, aSize);
+ }
+ void SetSize(const nsAString& aSize, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::size, aSize, aError);
+ }
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+protected:
+ virtual ~HTMLFontElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* HTMLFontElement_h___ */
diff --git a/dom/html/HTMLFormControlsCollection.cpp b/dom/html/HTMLFormControlsCollection.cpp
new file mode 100644
index 000000000..d91a6b5de
--- /dev/null
+++ b/dom/html/HTMLFormControlsCollection.cpp
@@ -0,0 +1,416 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLFormControlsCollection.h"
+
+#include "mozFlushType.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLFormControlsCollectionBinding.h"
+#include "mozilla/dom/HTMLFormElement.h"
+#include "nsGenericHTMLElement.h" // nsGenericHTMLFormElement
+#include "nsIDocument.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMNodeList.h"
+#include "nsIFormControl.h"
+#include "RadioNodeList.h"
+#include "jsfriendapi.h"
+
+namespace mozilla {
+namespace dom {
+
+/* static */ bool
+HTMLFormControlsCollection::ShouldBeInElements(nsIFormControl* aFormControl)
+{
+ // For backwards compatibility (with 4.x and IE) we must not add
+ // <input type=image> elements to the list of form controls in a
+ // form.
+
+ switch (aFormControl->GetType()) {
+ case NS_FORM_BUTTON_BUTTON :
+ case NS_FORM_BUTTON_RESET :
+ case NS_FORM_BUTTON_SUBMIT :
+ case NS_FORM_INPUT_BUTTON :
+ case NS_FORM_INPUT_CHECKBOX :
+ case NS_FORM_INPUT_COLOR :
+ case NS_FORM_INPUT_EMAIL :
+ case NS_FORM_INPUT_FILE :
+ case NS_FORM_INPUT_HIDDEN :
+ case NS_FORM_INPUT_RESET :
+ case NS_FORM_INPUT_PASSWORD :
+ case NS_FORM_INPUT_RADIO :
+ case NS_FORM_INPUT_SEARCH :
+ case NS_FORM_INPUT_SUBMIT :
+ case NS_FORM_INPUT_TEXT :
+ case NS_FORM_INPUT_TEL :
+ case NS_FORM_INPUT_URL :
+ case NS_FORM_INPUT_NUMBER :
+ case NS_FORM_INPUT_RANGE :
+ case NS_FORM_INPUT_DATE :
+ case NS_FORM_INPUT_TIME :
+ case NS_FORM_INPUT_MONTH :
+ case NS_FORM_INPUT_WEEK :
+ case NS_FORM_INPUT_DATETIME_LOCAL :
+ case NS_FORM_SELECT :
+ case NS_FORM_TEXTAREA :
+ case NS_FORM_FIELDSET :
+ case NS_FORM_OBJECT :
+ case NS_FORM_OUTPUT :
+ return true;
+ }
+
+ // These form control types are not supposed to end up in the
+ // form.elements array
+ //
+ // NS_FORM_INPUT_IMAGE
+ //
+ // XXXbz maybe we should just check for that type here instead of the big
+ // switch?
+
+ return false;
+}
+
+HTMLFormControlsCollection::HTMLFormControlsCollection(HTMLFormElement* aForm)
+ : mForm(aForm)
+ // Initialize the elements list to have an initial capacity
+ // of 8 to reduce allocations on small forms.
+ , mElements(8)
+ , mNameLookupTable(HTMLFormElement::FORM_CONTROL_LIST_HASHTABLE_LENGTH)
+{
+}
+
+HTMLFormControlsCollection::~HTMLFormControlsCollection()
+{
+ mForm = nullptr;
+ Clear();
+}
+
+void
+HTMLFormControlsCollection::DropFormReference()
+{
+ mForm = nullptr;
+ Clear();
+}
+
+void
+HTMLFormControlsCollection::Clear()
+{
+ // Null out childrens' pointer to me. No refcounting here
+ for (int32_t i = mElements.Length() - 1; i >= 0; i--) {
+ mElements[i]->ClearForm(false);
+ }
+ mElements.Clear();
+
+ for (int32_t i = mNotInElements.Length() - 1; i >= 0; i--) {
+ mNotInElements[i]->ClearForm(false);
+ }
+ mNotInElements.Clear();
+
+ mNameLookupTable.Clear();
+}
+
+void
+HTMLFormControlsCollection::FlushPendingNotifications()
+{
+ if (mForm) {
+ nsIDocument* doc = mForm->GetUncomposedDoc();
+ if (doc) {
+ doc->FlushPendingNotifications(Flush_Content);
+ }
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLFormControlsCollection)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HTMLFormControlsCollection)
+ // Note: We intentionally don't set tmp->mForm to nullptr here, since doing
+ // so may result in crashes because of inconsistent null-checking after the
+ // object gets unlinked.
+ tmp->Clear();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HTMLFormControlsCollection)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNameLookupTable)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(HTMLFormControlsCollection)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+// XPConnect interface list for HTMLFormControlsCollection
+NS_INTERFACE_TABLE_HEAD(HTMLFormControlsCollection)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE(HTMLFormControlsCollection,
+ nsIHTMLCollection,
+ nsIDOMHTMLCollection)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(HTMLFormControlsCollection)
+NS_INTERFACE_MAP_END
+
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLFormControlsCollection)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLFormControlsCollection)
+
+
+// nsIDOMHTMLCollection interface
+
+NS_IMETHODIMP
+HTMLFormControlsCollection::GetLength(uint32_t* aLength)
+{
+ FlushPendingNotifications();
+ *aLength = mElements.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLFormControlsCollection::Item(uint32_t aIndex, nsIDOMNode** aReturn)
+{
+ nsISupports* item = GetElementAt(aIndex);
+ if (!item) {
+ *aReturn = nullptr;
+
+ return NS_OK;
+ }
+
+ return CallQueryInterface(item, aReturn);
+}
+
+NS_IMETHODIMP
+HTMLFormControlsCollection::NamedItem(const nsAString& aName,
+ nsIDOMNode** aReturn)
+{
+ FlushPendingNotifications();
+
+ *aReturn = nullptr;
+
+ nsCOMPtr<nsISupports> supports;
+
+ if (!mNameLookupTable.Get(aName, getter_AddRefs(supports))) {
+ // key not found
+ return NS_OK;
+ }
+
+ if (!supports) {
+ return NS_OK;
+ }
+
+ // We found something, check if it's a node
+ CallQueryInterface(supports, aReturn);
+ if (*aReturn) {
+ return NS_OK;
+ }
+
+ // If not, we check if it's a node list.
+ nsCOMPtr<nsIDOMNodeList> nodeList = do_QueryInterface(supports);
+ NS_ASSERTION(nodeList, "Huh, what's going one here?");
+ if (!nodeList) {
+ return NS_OK;
+ }
+
+ // And since we're only asking for one node here, we return the first
+ // one from the list.
+ return nodeList->Item(0, aReturn);
+}
+
+nsISupports*
+HTMLFormControlsCollection::NamedItemInternal(const nsAString& aName,
+ bool aFlushContent)
+{
+ if (aFlushContent) {
+ FlushPendingNotifications();
+ }
+
+ return mNameLookupTable.GetWeak(aName);
+}
+
+nsresult
+HTMLFormControlsCollection::AddElementToTable(nsGenericHTMLFormElement* aChild,
+ const nsAString& aName)
+{
+ if (!ShouldBeInElements(aChild)) {
+ return NS_OK;
+ }
+
+ return mForm->AddElementToTableInternal(mNameLookupTable, aChild, aName);
+}
+
+nsresult
+HTMLFormControlsCollection::IndexOfControl(nsIFormControl* aControl,
+ int32_t* aIndex)
+{
+ // Note -- not a DOM method; callers should handle flushing themselves
+
+ NS_ENSURE_ARG_POINTER(aIndex);
+
+ *aIndex = mElements.IndexOf(aControl);
+
+ return NS_OK;
+}
+
+nsresult
+HTMLFormControlsCollection::RemoveElementFromTable(
+ nsGenericHTMLFormElement* aChild, const nsAString& aName)
+{
+ if (!ShouldBeInElements(aChild)) {
+ return NS_OK;
+ }
+
+ return mForm->RemoveElementFromTableInternal(mNameLookupTable, aChild, aName);
+}
+
+nsresult
+HTMLFormControlsCollection::GetSortedControls(
+ nsTArray<nsGenericHTMLFormElement*>& aControls) const
+{
+#ifdef DEBUG
+ HTMLFormElement::AssertDocumentOrder(mElements, mForm);
+ HTMLFormElement::AssertDocumentOrder(mNotInElements, mForm);
+#endif
+
+ aControls.Clear();
+
+ // Merge the elements list and the not in elements list. Both lists are
+ // already sorted.
+ uint32_t elementsLen = mElements.Length();
+ uint32_t notInElementsLen = mNotInElements.Length();
+ aControls.SetCapacity(elementsLen + notInElementsLen);
+
+ uint32_t elementsIdx = 0;
+ uint32_t notInElementsIdx = 0;
+
+ while (elementsIdx < elementsLen || notInElementsIdx < notInElementsLen) {
+ // Check whether we're done with mElements
+ if (elementsIdx == elementsLen) {
+ NS_ASSERTION(notInElementsIdx < notInElementsLen,
+ "Should have remaining not-in-elements");
+ // Append the remaining mNotInElements elements
+ if (!aControls.AppendElements(mNotInElements.Elements() +
+ notInElementsIdx,
+ notInElementsLen -
+ notInElementsIdx)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ break;
+ }
+ // Check whether we're done with mNotInElements
+ if (notInElementsIdx == notInElementsLen) {
+ NS_ASSERTION(elementsIdx < elementsLen,
+ "Should have remaining in-elements");
+ // Append the remaining mElements elements
+ if (!aControls.AppendElements(mElements.Elements() +
+ elementsIdx,
+ elementsLen -
+ elementsIdx)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ break;
+ }
+ // Both lists have elements left.
+ NS_ASSERTION(mElements[elementsIdx] &&
+ mNotInElements[notInElementsIdx],
+ "Should have remaining elements");
+ // Determine which of the two elements should be ordered
+ // first and add it to the end of the list.
+ nsGenericHTMLFormElement* elementToAdd;
+ if (HTMLFormElement::CompareFormControlPosition(
+ mElements[elementsIdx], mNotInElements[notInElementsIdx], mForm) < 0) {
+ elementToAdd = mElements[elementsIdx];
+ ++elementsIdx;
+ } else {
+ elementToAdd = mNotInElements[notInElementsIdx];
+ ++notInElementsIdx;
+ }
+ // Add the first element to the list.
+ if (!aControls.AppendElement(elementToAdd)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ NS_ASSERTION(aControls.Length() == elementsLen + notInElementsLen,
+ "Not all form controls were added to the sorted list");
+#ifdef DEBUG
+ HTMLFormElement::AssertDocumentOrder(aControls, mForm);
+#endif
+
+ return NS_OK;
+}
+
+Element*
+HTMLFormControlsCollection::GetElementAt(uint32_t aIndex)
+{
+ FlushPendingNotifications();
+
+ return mElements.SafeElementAt(aIndex, nullptr);
+}
+
+/* virtual */ nsINode*
+HTMLFormControlsCollection::GetParentObject()
+{
+ return mForm;
+}
+
+/* virtual */ Element*
+HTMLFormControlsCollection::GetFirstNamedElement(const nsAString& aName, bool& aFound)
+{
+ Nullable<OwningRadioNodeListOrElement> maybeResult;
+ NamedGetter(aName, aFound, maybeResult);
+ if (!aFound) {
+ return nullptr;
+ }
+ MOZ_ASSERT(!maybeResult.IsNull());
+ const OwningRadioNodeListOrElement& result = maybeResult.Value();
+ if (result.IsElement()) {
+ return result.GetAsElement().get();
+ }
+ if (result.IsRadioNodeList()) {
+ RadioNodeList& nodelist = result.GetAsRadioNodeList();
+ return nodelist.Item(0)->AsElement();
+ }
+ MOZ_ASSERT_UNREACHABLE("Should only have Elements and NodeLists here.");
+ return nullptr;
+}
+
+void
+HTMLFormControlsCollection::NamedGetter(const nsAString& aName,
+ bool& aFound,
+ Nullable<OwningRadioNodeListOrElement>& aResult)
+{
+ nsISupports* item = NamedItemInternal(aName, true);
+ if (!item) {
+ aFound = false;
+ return;
+ }
+ aFound = true;
+ if (nsCOMPtr<Element> element = do_QueryInterface(item)) {
+ aResult.SetValue().SetAsElement() = element;
+ return;
+ }
+ if (nsCOMPtr<RadioNodeList> nodelist = do_QueryInterface(item)) {
+ aResult.SetValue().SetAsRadioNodeList() = nodelist;
+ return;
+ }
+ MOZ_ASSERT_UNREACHABLE("Should only have Elements and NodeLists here.");
+}
+
+void
+HTMLFormControlsCollection::GetSupportedNames(nsTArray<nsString>& aNames)
+{
+ FlushPendingNotifications();
+ // Just enumerate mNameLookupTable. This won't guarantee order, but
+ // that's OK, because the HTML5 spec doesn't define an order for
+ // this enumeration.
+ for (auto iter = mNameLookupTable.Iter(); !iter.Done(); iter.Next()) {
+ aNames.AppendElement(iter.Key());
+ }
+}
+
+/* virtual */ JSObject*
+HTMLFormControlsCollection::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLFormControlsCollectionBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLFormControlsCollection.h b/dom/html/HTMLFormControlsCollection.h
new file mode 100644
index 000000000..1b8e1e62b
--- /dev/null
+++ b/dom/html/HTMLFormControlsCollection.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLFormControlsCollection_h
+#define mozilla_dom_HTMLFormControlsCollection_h
+
+#include "mozilla/dom/Element.h" // DOMProxyHandler::getOwnPropertyDescriptor
+#include "nsIHTMLCollection.h"
+#include "nsInterfaceHashtable.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+class nsGenericHTMLFormElement;
+class nsIFormControl;
+
+namespace mozilla {
+namespace dom {
+class HTMLFormElement;
+class HTMLImageElement;
+class OwningRadioNodeListOrElement;
+template<typename> struct Nullable;
+
+class HTMLFormControlsCollection final : public nsIHTMLCollection
+ , public nsWrapperCache
+{
+public:
+ explicit HTMLFormControlsCollection(HTMLFormElement* aForm);
+
+ void DropFormReference();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ // nsIDOMHTMLCollection interface
+ NS_DECL_NSIDOMHTMLCOLLECTION
+
+ virtual Element* GetElementAt(uint32_t index) override;
+ virtual nsINode* GetParentObject() override;
+
+ virtual Element*
+ GetFirstNamedElement(const nsAString& aName, bool& aFound) override;
+
+ void
+ NamedGetter(const nsAString& aName,
+ bool& aFound,
+ Nullable<OwningRadioNodeListOrElement>& aResult);
+ void
+ NamedItem(const nsAString& aName,
+ Nullable<OwningRadioNodeListOrElement>& aResult)
+ {
+ bool dummy;
+ NamedGetter(aName, dummy, aResult);
+ }
+ virtual void GetSupportedNames(nsTArray<nsString>& aNames) override;
+
+ nsresult AddElementToTable(nsGenericHTMLFormElement* aChild,
+ const nsAString& aName);
+ nsresult AddImageElementToTable(HTMLImageElement* aChild,
+ const nsAString& aName);
+ nsresult RemoveElementFromTable(nsGenericHTMLFormElement* aChild,
+ const nsAString& aName);
+ nsresult IndexOfControl(nsIFormControl* aControl,
+ int32_t* aIndex);
+
+ nsISupports* NamedItemInternal(const nsAString& aName, bool aFlushContent);
+
+ /**
+ * Create a sorted list of form control elements. This list is sorted
+ * in document order and contains the controls in the mElements and
+ * mNotInElements list. This function does not add references to the
+ * elements.
+ *
+ * @param aControls The list of sorted controls[out].
+ * @return NS_OK or NS_ERROR_OUT_OF_MEMORY.
+ */
+ nsresult GetSortedControls(nsTArray<nsGenericHTMLFormElement*>& aControls) const;
+
+ // nsWrapperCache
+ using nsWrapperCache::GetWrapperPreserveColor;
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+protected:
+ virtual ~HTMLFormControlsCollection();
+ virtual JSObject* GetWrapperPreserveColorInternal() override
+ {
+ return nsWrapperCache::GetWrapperPreserveColor();
+ }
+public:
+
+ static bool ShouldBeInElements(nsIFormControl* aFormControl);
+
+ HTMLFormElement* mForm; // WEAK - the form owns me
+
+ nsTArray<nsGenericHTMLFormElement*> mElements; // Holds WEAK references - bug 36639
+
+ // This array holds on to all form controls that are not contained
+ // in mElements (form.elements in JS, see ShouldBeInFormControl()).
+ // This is needed to properly clean up the bi-directional references
+ // (both weak and strong) between the form and its form controls.
+
+ nsTArray<nsGenericHTMLFormElement*> mNotInElements; // Holds WEAK references
+
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(HTMLFormControlsCollection)
+
+protected:
+ // Drop all our references to the form elements
+ void Clear();
+
+ // Flush out the content model so it's up to date.
+ void FlushPendingNotifications();
+
+ // A map from an ID or NAME attribute to the form control(s), this
+ // hash holds strong references either to the named form control, or
+ // to a list of named form controls, in the case where this hash
+ // holds on to a list of named form controls the list has weak
+ // references to the form control.
+
+ nsInterfaceHashtable<nsStringHashKey,nsISupports> mNameLookupTable;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLFormControlsCollection_h
diff --git a/dom/html/HTMLFormElement.cpp b/dom/html/HTMLFormElement.cpp
new file mode 100644
index 000000000..5164391f8
--- /dev/null
+++ b/dom/html/HTMLFormElement.cpp
@@ -0,0 +1,2585 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLFormElement.h"
+
+#include "jsapi.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/AutocompleteErrorEvent.h"
+#include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/HTMLFormControlsCollection.h"
+#include "mozilla/dom/HTMLFormElementBinding.h"
+#include "mozilla/Move.h"
+#include "nsIHTMLDocument.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsIDocument.h"
+#include "nsIFormControlFrame.h"
+#include "nsError.h"
+#include "nsContentUtils.h"
+#include "nsInterfaceHashtable.h"
+#include "nsContentList.h"
+#include "nsCOMArray.h"
+#include "nsAutoPtr.h"
+#include "nsTArray.h"
+#include "nsIMutableArray.h"
+#include "nsIFormAutofillContentService.h"
+#include "mozilla/BinarySearch.h"
+#include "nsQueryObject.h"
+
+// form submission
+#include "HTMLFormSubmissionConstants.h"
+#include "mozilla/dom/FormData.h"
+#include "mozilla/Telemetry.h"
+#include "nsIFormSubmitObserver.h"
+#include "nsIObserverService.h"
+#include "nsICategoryManager.h"
+#include "nsCategoryManagerUtils.h"
+#include "nsISimpleEnumerator.h"
+#include "nsRange.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsNetUtil.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWebProgress.h"
+#include "nsIDocShell.h"
+#include "nsIPrompt.h"
+#include "nsISecurityUITelemetry.h"
+#include "nsIStringBundle.h"
+
+// radio buttons
+#include "mozilla/dom/HTMLInputElement.h"
+#include "nsIRadioVisitor.h"
+#include "RadioNodeList.h"
+
+#include "nsLayoutUtils.h"
+
+#include "mozAutoDocUpdate.h"
+#include "nsIHTMLCollection.h"
+
+#include "nsIConstraintValidation.h"
+
+#include "nsIDOMHTMLButtonElement.h"
+#include "nsSandboxFlags.h"
+
+#include "nsIContentSecurityPolicy.h"
+
+// images
+#include "mozilla/dom/HTMLImageElement.h"
+
+// construction, destruction
+NS_IMPL_NS_NEW_HTML_ELEMENT(Form)
+
+namespace mozilla {
+namespace dom {
+
+static const uint8_t NS_FORM_AUTOCOMPLETE_ON = 1;
+static const uint8_t NS_FORM_AUTOCOMPLETE_OFF = 0;
+
+static const nsAttrValue::EnumTable kFormAutocompleteTable[] = {
+ { "on", NS_FORM_AUTOCOMPLETE_ON },
+ { "off", NS_FORM_AUTOCOMPLETE_OFF },
+ { nullptr, 0 }
+};
+// Default autocomplete value is 'on'.
+static const nsAttrValue::EnumTable* kFormDefaultAutocomplete = &kFormAutocompleteTable[0];
+
+bool HTMLFormElement::gFirstFormSubmitted = false;
+bool HTMLFormElement::gPasswordManagerInitialized = false;
+
+HTMLFormElement::HTMLFormElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo),
+ mControls(new HTMLFormControlsCollection(this)),
+ mSelectedRadioButtons(2),
+ mRequiredRadioButtonCounts(2),
+ mValueMissingRadioGroups(2),
+ mGeneratingSubmit(false),
+ mGeneratingReset(false),
+ mIsSubmitting(false),
+ mDeferSubmission(false),
+ mNotifiedObservers(false),
+ mNotifiedObserversResult(false),
+ mSubmitPopupState(openAbused),
+ mSubmitInitiatedFromUserInput(false),
+ mPendingSubmission(nullptr),
+ mSubmittingRequest(nullptr),
+ mDefaultSubmitElement(nullptr),
+ mFirstSubmitInElements(nullptr),
+ mFirstSubmitNotInElements(nullptr),
+ mImageNameLookupTable(FORM_CONTROL_LIST_HASHTABLE_LENGTH),
+ mPastNameLookupTable(FORM_CONTROL_LIST_HASHTABLE_LENGTH),
+ mInvalidElementsCount(0),
+ mEverTriedInvalidSubmit(false)
+{
+ // We start out valid.
+ AddStatesSilently(NS_EVENT_STATE_VALID);
+}
+
+HTMLFormElement::~HTMLFormElement()
+{
+ if (mControls) {
+ mControls->DropFormReference();
+ }
+
+ Clear();
+}
+
+// nsISupports
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLFormElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLFormElement,
+ nsGenericHTMLElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControls)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageNameLookupTable)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPastNameLookupTable)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedRadioButtons)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLFormElement,
+ nsGenericHTMLElement)
+ tmp->Clear();
+ tmp->mExpandoAndGeneration.OwnerUnlinked();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(HTMLFormElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLFormElement, Element)
+
+
+// QueryInterface implementation for HTMLFormElement
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLFormElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLFormElement,
+ nsIDOMHTMLFormElement,
+ nsIForm,
+ nsIWebProgressListener,
+ nsIRadioGroupContainer)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLElement)
+
+// EventTarget
+void
+HTMLFormElement::AsyncEventRunning(AsyncEventDispatcher* aEvent)
+{
+ if (mFormPasswordEventDispatcher == aEvent) {
+ mFormPasswordEventDispatcher = nullptr;
+ }
+}
+
+// nsIDOMHTMLFormElement
+
+NS_IMPL_ELEMENT_CLONE(HTMLFormElement)
+
+nsIHTMLCollection*
+HTMLFormElement::Elements()
+{
+ return mControls;
+}
+
+NS_IMETHODIMP
+HTMLFormElement::GetElements(nsIDOMHTMLCollection** aElements)
+{
+ *aElements = Elements();
+ NS_ADDREF(*aElements);
+ return NS_OK;
+}
+
+nsresult
+HTMLFormElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify)
+{
+ if ((aName == nsGkAtoms::action || aName == nsGkAtoms::target) &&
+ aNameSpaceID == kNameSpaceID_None) {
+ if (mPendingSubmission) {
+ // aha, there is a pending submission that means we're in
+ // the script and we need to flush it. let's tell it
+ // that the event was ignored to force the flush.
+ // the second argument is not playing a role at all.
+ FlushPendingSubmission();
+ }
+ // Don't forget we've notified the password manager already if the
+ // page sets the action/target in the during submit. (bug 343182)
+ bool notifiedObservers = mNotifiedObservers;
+ ForgetCurrentSubmission();
+ mNotifiedObservers = notifiedObservers;
+ }
+ return nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
+ aNotify);
+}
+
+nsresult
+HTMLFormElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aName == nsGkAtoms::novalidate && aNameSpaceID == kNameSpaceID_None) {
+ // Update all form elements states because they might be [no longer]
+ // affected by :-moz-ui-valid or :-moz-ui-invalid.
+ for (uint32_t i = 0, length = mControls->mElements.Length();
+ i < length; ++i) {
+ mControls->mElements[i]->UpdateState(true);
+ }
+
+ for (uint32_t i = 0, length = mControls->mNotInElements.Length();
+ i < length; ++i) {
+ mControls->mNotInElements[i]->UpdateState(true);
+ }
+ }
+
+ return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue, aNotify);
+}
+
+NS_IMPL_STRING_ATTR(HTMLFormElement, AcceptCharset, acceptcharset)
+NS_IMPL_ACTION_ATTR(HTMLFormElement, Action, action)
+NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLFormElement, Autocomplete, autocomplete,
+ kFormDefaultAutocomplete->tag)
+NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLFormElement, Enctype, enctype,
+ kFormDefaultEnctype->tag)
+NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLFormElement, Method, method,
+ kFormDefaultMethod->tag)
+NS_IMPL_BOOL_ATTR(HTMLFormElement, NoValidate, novalidate)
+NS_IMPL_STRING_ATTR(HTMLFormElement, Name, name)
+NS_IMPL_STRING_ATTR(HTMLFormElement, Target, target)
+
+void
+HTMLFormElement::Submit(ErrorResult& aRv)
+{
+ // Send the submit event
+ if (mPendingSubmission) {
+ // aha, we have a pending submission that was not flushed
+ // (this happens when form.submit() is called twice)
+ // we have to delete it and build a new one since values
+ // might have changed inbetween (we emulate IE here, that's all)
+ mPendingSubmission = nullptr;
+ }
+
+ aRv = DoSubmitOrReset(nullptr, eFormSubmit);
+}
+
+NS_IMETHODIMP
+HTMLFormElement::Submit()
+{
+ ErrorResult rv;
+ Submit(rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLFormElement::Reset()
+{
+ InternalFormEvent event(true, eFormReset);
+ EventDispatcher::Dispatch(static_cast<nsIContent*>(this), nullptr, &event);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLFormElement::CheckValidity(bool* retVal)
+{
+ *retVal = CheckValidity();
+ return NS_OK;
+}
+
+void
+HTMLFormElement::RequestAutocomplete()
+{
+ bool dummy;
+ nsCOMPtr<nsIDOMWindow> window =
+ do_QueryInterface(OwnerDoc()->GetScriptHandlingObject(dummy));
+ nsCOMPtr<nsIFormAutofillContentService> formAutofillContentService =
+ do_GetService("@mozilla.org/formautofill/content-service;1");
+
+ if (!formAutofillContentService || !window) {
+ AutocompleteErrorEventInit init;
+ init.mBubbles = true;
+ init.mCancelable = false;
+ init.mReason = AutoCompleteErrorReason::Disabled;
+
+ RefPtr<AutocompleteErrorEvent> event =
+ AutocompleteErrorEvent::Constructor(this, NS_LITERAL_STRING("autocompleteerror"), init);
+
+ (new AsyncEventDispatcher(this, event))->PostDOMEvent();
+ return;
+ }
+
+ formAutofillContentService->RequestAutocomplete(this, window);
+}
+
+bool
+HTMLFormElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::method) {
+ return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
+ }
+ if (aAttribute == nsGkAtoms::enctype) {
+ return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
+ }
+ if (aAttribute == nsGkAtoms::autocomplete) {
+ return aResult.ParseEnumValue(aValue, kFormAutocompleteTable, false);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+nsresult
+HTMLFormElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHTMLDocument> htmlDoc(do_QueryInterface(aDocument));
+ if (htmlDoc) {
+ htmlDoc->AddedForm();
+ }
+
+ return rv;
+}
+
+template<typename T>
+static void
+MarkOrphans(const nsTArray<T*>& aArray)
+{
+ uint32_t length = aArray.Length();
+ for (uint32_t i = 0; i < length; ++i) {
+ aArray[i]->SetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
+ }
+}
+
+static void
+CollectOrphans(nsINode* aRemovalRoot,
+ const nsTArray<nsGenericHTMLFormElement*>& aArray
+#ifdef DEBUG
+ , nsIDOMHTMLFormElement* aThisForm
+#endif
+ )
+{
+ // Put a script blocker around all the notifications we're about to do.
+ nsAutoScriptBlocker scriptBlocker;
+
+ // Walk backwards so that if we remove elements we can just keep iterating
+ uint32_t length = aArray.Length();
+ for (uint32_t i = length; i > 0; --i) {
+ nsGenericHTMLFormElement* node = aArray[i-1];
+
+ // Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the
+ // node is in fact a descendant of the form and hence should stay in the
+ // form. If it _is_ set, then we need to check whether the node is a
+ // descendant of aRemovalRoot. If it is, we leave it in the form.
+#ifdef DEBUG
+ bool removed = false;
+#endif
+ if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) {
+ node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
+ if (!nsContentUtils::ContentIsDescendantOf(node, aRemovalRoot)) {
+ node->ClearForm(true);
+
+ // When a form control loses its form owner, its state can change.
+ node->UpdateState(true);
+#ifdef DEBUG
+ removed = true;
+#endif
+ }
+ }
+
+#ifdef DEBUG
+ if (!removed) {
+ nsCOMPtr<nsIDOMHTMLFormElement> form;
+ node->GetForm(getter_AddRefs(form));
+ NS_ASSERTION(form == aThisForm, "How did that happen?");
+ }
+#endif /* DEBUG */
+ }
+}
+
+static void
+CollectOrphans(nsINode* aRemovalRoot,
+ const nsTArray<HTMLImageElement*>& aArray
+#ifdef DEBUG
+ , nsIDOMHTMLFormElement* aThisForm
+#endif
+ )
+{
+ // Walk backwards so that if we remove elements we can just keep iterating
+ uint32_t length = aArray.Length();
+ for (uint32_t i = length; i > 0; --i) {
+ HTMLImageElement* node = aArray[i-1];
+
+ // Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the
+ // node is in fact a descendant of the form and hence should stay in the
+ // form. If it _is_ set, then we need to check whether the node is a
+ // descendant of aRemovalRoot. If it is, we leave it in the form.
+#ifdef DEBUG
+ bool removed = false;
+#endif
+ if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) {
+ node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
+ if (!nsContentUtils::ContentIsDescendantOf(node, aRemovalRoot)) {
+ node->ClearForm(true);
+
+#ifdef DEBUG
+ removed = true;
+#endif
+ }
+ }
+
+#ifdef DEBUG
+ if (!removed) {
+ nsCOMPtr<nsIDOMHTMLFormElement> form = node->GetForm();
+ NS_ASSERTION(form == aThisForm, "How did that happen?");
+ }
+#endif /* DEBUG */
+ }
+}
+
+void
+HTMLFormElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ nsCOMPtr<nsIHTMLDocument> oldDocument = do_QueryInterface(GetUncomposedDoc());
+
+ // Mark all of our controls as maybe being orphans
+ MarkOrphans(mControls->mElements);
+ MarkOrphans(mControls->mNotInElements);
+ MarkOrphans(mImageElements);
+
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+
+ nsINode* ancestor = this;
+ nsINode* cur;
+ do {
+ cur = ancestor->GetParentNode();
+ if (!cur) {
+ break;
+ }
+ ancestor = cur;
+ } while (1);
+
+ CollectOrphans(ancestor, mControls->mElements
+#ifdef DEBUG
+ , this
+#endif
+ );
+ CollectOrphans(ancestor, mControls->mNotInElements
+#ifdef DEBUG
+ , this
+#endif
+ );
+ CollectOrphans(ancestor, mImageElements
+#ifdef DEBUG
+ , this
+#endif
+ );
+
+ if (oldDocument) {
+ oldDocument->RemovedForm();
+ }
+ ForgetCurrentSubmission();
+}
+
+nsresult
+HTMLFormElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ aVisitor.mWantsWillHandleEvent = true;
+ if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this)) {
+ uint32_t msg = aVisitor.mEvent->mMessage;
+ if (msg == eFormSubmit) {
+ if (mGeneratingSubmit) {
+ aVisitor.mCanHandle = false;
+ return NS_OK;
+ }
+ mGeneratingSubmit = true;
+
+ // let the form know that it needs to defer the submission,
+ // that means that if there are scripted submissions, the
+ // latest one will be deferred until after the exit point of the handler.
+ mDeferSubmission = true;
+ } else if (msg == eFormReset) {
+ if (mGeneratingReset) {
+ aVisitor.mCanHandle = false;
+ return NS_OK;
+ }
+ mGeneratingReset = true;
+ }
+ }
+ return nsGenericHTMLElement::PreHandleEvent(aVisitor);
+}
+
+nsresult
+HTMLFormElement::WillHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ // If this is the bubble stage and there is a nested form below us which
+ // received a submit event we do *not* want to handle the submit event
+ // for this form too.
+ if ((aVisitor.mEvent->mMessage == eFormSubmit ||
+ aVisitor.mEvent->mMessage == eFormReset) &&
+ aVisitor.mEvent->mFlags.mInBubblingPhase &&
+ aVisitor.mEvent->mOriginalTarget != static_cast<nsIContent*>(this)) {
+ aVisitor.mEvent->StopPropagation();
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLFormElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this)) {
+ EventMessage msg = aVisitor.mEvent->mMessage;
+ if (msg == eFormSubmit) {
+ // let the form know not to defer subsequent submissions
+ mDeferSubmission = false;
+ }
+
+ if (aVisitor.mEventStatus == nsEventStatus_eIgnore) {
+ switch (msg) {
+ case eFormReset:
+ case eFormSubmit: {
+ if (mPendingSubmission && msg == eFormSubmit) {
+ // tell the form to forget a possible pending submission.
+ // the reason is that the script returned true (the event was
+ // ignored) so if there is a stored submission, it will miss
+ // the name/value of the submitting element, thus we need
+ // to forget it and the form element will build a new one
+ mPendingSubmission = nullptr;
+ }
+ DoSubmitOrReset(aVisitor.mEvent, msg);
+ break;
+ }
+ default:
+ break;
+ }
+ } else {
+ if (msg == eFormSubmit) {
+ // tell the form to flush a possible pending submission.
+ // the reason is that the script returned false (the event was
+ // not ignored) so if there is a stored submission, it needs to
+ // be submitted immediatelly.
+ FlushPendingSubmission();
+ }
+ }
+
+ if (msg == eFormSubmit) {
+ mGeneratingSubmit = false;
+ } else if (msg == eFormReset) {
+ mGeneratingReset = false;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLFormElement::DoSubmitOrReset(WidgetEvent* aEvent,
+ EventMessage aMessage)
+{
+ // Make sure the presentation is up-to-date
+ nsIDocument* doc = GetComposedDoc();
+ if (doc) {
+ doc->FlushPendingNotifications(Flush_ContentAndNotify);
+ }
+
+ // JBK Don't get form frames anymore - bug 34297
+
+ // Submit or Reset the form
+ if (eFormReset == aMessage) {
+ return DoReset();
+ }
+
+ if (eFormSubmit == aMessage) {
+ // Don't submit if we're not in a document or if we're in
+ // a sandboxed frame and form submit is disabled.
+ if (!doc || (doc->GetSandboxFlags() & SANDBOXED_FORMS)) {
+ return NS_OK;
+ }
+ return DoSubmit(aEvent);
+ }
+
+ MOZ_ASSERT(false);
+ return NS_OK;
+}
+
+nsresult
+HTMLFormElement::DoReset()
+{
+ // JBK walk the elements[] array instead of form frame controls - bug 34297
+ uint32_t numElements = GetElementCount();
+ for (uint32_t elementX = 0; elementX < numElements; ++elementX) {
+ // Hold strong ref in case the reset does something weird
+ nsCOMPtr<nsIFormControl> controlNode = GetElementAt(elementX);
+ if (controlNode) {
+ controlNode->Reset();
+ }
+ }
+
+ return NS_OK;
+}
+
+#define NS_ENSURE_SUBMIT_SUCCESS(rv) \
+ if (NS_FAILED(rv)) { \
+ ForgetCurrentSubmission(); \
+ return rv; \
+ }
+
+nsresult
+HTMLFormElement::DoSubmit(WidgetEvent* aEvent)
+{
+ NS_ASSERTION(GetComposedDoc(), "Should never get here without a current doc");
+
+ if (mIsSubmitting) {
+ NS_WARNING("Preventing double form submission");
+ // XXX Should this return an error?
+ return NS_OK;
+ }
+
+ // Mark us as submitting so that we don't try to submit again
+ mIsSubmitting = true;
+ NS_ASSERTION(!mWebProgress && !mSubmittingRequest, "Web progress / submitting request should not exist here!");
+
+ nsAutoPtr<HTMLFormSubmission> submission;
+
+ //
+ // prepare the submission object
+ //
+ nsresult rv = BuildSubmission(getter_Transfers(submission), aEvent);
+ if (NS_FAILED(rv)) {
+ mIsSubmitting = false;
+ return rv;
+ }
+
+ // XXXbz if the script global is that for an sXBL/XBL2 doc, it won't
+ // be a window...
+ nsPIDOMWindowOuter *window = OwnerDoc()->GetWindow();
+
+ if (window) {
+ mSubmitPopupState = window->GetPopupControlState();
+ } else {
+ mSubmitPopupState = openAbused;
+ }
+
+ mSubmitInitiatedFromUserInput = EventStateManager::IsHandlingUserInput();
+
+ if(mDeferSubmission) {
+ // we are in an event handler, JS submitted so we have to
+ // defer this submission. let's remember it and return
+ // without submitting
+ mPendingSubmission = submission;
+ // ensure reentrancy
+ mIsSubmitting = false;
+ return NS_OK;
+ }
+
+ //
+ // perform the submission
+ //
+ return SubmitSubmission(submission);
+}
+
+nsresult
+HTMLFormElement::BuildSubmission(HTMLFormSubmission** aFormSubmission,
+ WidgetEvent* aEvent)
+{
+ NS_ASSERTION(!mPendingSubmission, "tried to build two submissions!");
+
+ // Get the originating frame (failure is non-fatal)
+ nsGenericHTMLElement* originatingElement = nullptr;
+ if (aEvent) {
+ InternalFormEvent* formEvent = aEvent->AsFormEvent();
+ if (formEvent) {
+ nsIContent* originator = formEvent->mOriginator;
+ if (originator) {
+ if (!originator->IsHTMLElement()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ originatingElement = static_cast<nsGenericHTMLElement*>(originator);
+ }
+ }
+ }
+
+ nsresult rv;
+
+ //
+ // Get the submission object
+ //
+ rv = HTMLFormSubmission::GetFromForm(this, originatingElement,
+ aFormSubmission);
+ NS_ENSURE_SUBMIT_SUCCESS(rv);
+
+ //
+ // Dump the data into the submission object
+ //
+ rv = WalkFormElements(*aFormSubmission);
+ NS_ENSURE_SUBMIT_SUCCESS(rv);
+
+ return NS_OK;
+}
+
+nsresult
+HTMLFormElement::SubmitSubmission(HTMLFormSubmission* aFormSubmission)
+{
+ nsresult rv;
+ nsIContent* originatingElement = aFormSubmission->GetOriginatingElement();
+
+ //
+ // Get the action and target
+ //
+ nsCOMPtr<nsIURI> actionURI;
+ rv = GetActionURL(getter_AddRefs(actionURI), originatingElement);
+ NS_ENSURE_SUBMIT_SUCCESS(rv);
+
+ if (!actionURI) {
+ mIsSubmitting = false;
+ return NS_OK;
+ }
+
+ // If there is no link handler, then we won't actually be able to submit.
+ nsIDocument* doc = GetComposedDoc();
+ nsCOMPtr<nsISupports> container = doc ? doc->GetContainer() : nullptr;
+ nsCOMPtr<nsILinkHandler> linkHandler(do_QueryInterface(container));
+ if (!linkHandler || IsEditable()) {
+ mIsSubmitting = false;
+ return NS_OK;
+ }
+
+ // javascript URIs are not really submissions; they just call a function.
+ // Also, they may synchronously call submit(), and we want them to be able to
+ // do so while still disallowing other double submissions. (Bug 139798)
+ // Note that any other URI types that are of equivalent type should also be
+ // added here.
+ // XXXbz this is a mess. The real issue here is that nsJSChannel sets the
+ // LOAD_BACKGROUND flag, so doesn't notify us, compounded by the fact that
+ // the JS executes before we forget the submission in OnStateChange on
+ // STATE_STOP. As a result, we have to make sure that we simply pretend
+ // we're not submitting when submitting to a JS URL. That's kinda bogus, but
+ // there we are.
+ bool schemeIsJavaScript = false;
+ if (NS_SUCCEEDED(actionURI->SchemeIs("javascript", &schemeIsJavaScript)) &&
+ schemeIsJavaScript) {
+ mIsSubmitting = false;
+ }
+
+ // The target is the originating element formtarget attribute if the element
+ // is a submit control and has such an attribute.
+ // Otherwise, the target is the form owner's target attribute,
+ // if it has such an attribute.
+ // Finally, if one of the child nodes of the head element is a base element
+ // with a target attribute, then the value of the target attribute of the
+ // first such base element; or, if there is no such element, the empty string.
+ nsAutoString target;
+ if (!(originatingElement && originatingElement->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::formtarget,
+ target)) &&
+ !GetAttr(kNameSpaceID_None, nsGkAtoms::target, target)) {
+ GetBaseTarget(target);
+ }
+
+ //
+ // Notify observers of submit
+ //
+ bool cancelSubmit = false;
+ if (mNotifiedObservers) {
+ cancelSubmit = mNotifiedObserversResult;
+ } else {
+ rv = NotifySubmitObservers(actionURI, &cancelSubmit, true);
+ NS_ENSURE_SUBMIT_SUCCESS(rv);
+ }
+
+ if (cancelSubmit) {
+ mIsSubmitting = false;
+ return NS_OK;
+ }
+
+ cancelSubmit = false;
+ rv = NotifySubmitObservers(actionURI, &cancelSubmit, false);
+ NS_ENSURE_SUBMIT_SUCCESS(rv);
+
+ if (cancelSubmit) {
+ mIsSubmitting = false;
+ return NS_OK;
+ }
+
+ //
+ // Submit
+ //
+ nsCOMPtr<nsIDocShell> docShell;
+
+ {
+ nsAutoPopupStatePusher popupStatePusher(mSubmitPopupState);
+
+ AutoHandlingUserInputStatePusher userInpStatePusher(
+ mSubmitInitiatedFromUserInput,
+ nullptr, doc);
+
+ nsCOMPtr<nsIInputStream> postDataStream;
+ rv = aFormSubmission->GetEncodedSubmission(actionURI,
+ getter_AddRefs(postDataStream));
+ NS_ENSURE_SUBMIT_SUCCESS(rv);
+
+ rv = linkHandler->OnLinkClickSync(this, actionURI,
+ target.get(),
+ NullString(),
+ postDataStream, nullptr,
+ getter_AddRefs(docShell),
+ getter_AddRefs(mSubmittingRequest));
+ NS_ENSURE_SUBMIT_SUCCESS(rv);
+ }
+
+ // Even if the submit succeeds, it's possible for there to be no docshell
+ // or request; for example, if it's to a named anchor within the same page
+ // the submit will not really do anything.
+ if (docShell) {
+ // If the channel is pending, we have to listen for web progress.
+ bool pending = false;
+ mSubmittingRequest->IsPending(&pending);
+ if (pending && !schemeIsJavaScript) {
+ nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
+ NS_ASSERTION(webProgress, "nsIDocShell not converted to nsIWebProgress!");
+ rv = webProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_ALL);
+ NS_ENSURE_SUBMIT_SUCCESS(rv);
+ mWebProgress = do_GetWeakReference(webProgress);
+ NS_ASSERTION(mWebProgress, "can't hold weak ref to webprogress!");
+ } else {
+ ForgetCurrentSubmission();
+ }
+ } else {
+ ForgetCurrentSubmission();
+ }
+
+ return rv;
+}
+
+nsresult
+HTMLFormElement::DoSecureToInsecureSubmitCheck(nsIURI* aActionURL,
+ bool* aCancelSubmit)
+{
+ *aCancelSubmit = false;
+
+ // Only ask the user about posting from a secure URI to an insecure URI if
+ // this element is in the root document. When this is not the case, the mixed
+ // content blocker will take care of security for us.
+ nsIDocument* parent = OwnerDoc()->GetParentDocument();
+ bool isRootDocument = (!parent || nsContentUtils::IsChromeDoc(parent));
+ if (!isRootDocument) {
+ return NS_OK;
+ }
+
+ nsIPrincipal* principal = NodePrincipal();
+ if (!principal) {
+ *aCancelSubmit = true;
+ return NS_OK;
+ }
+ nsCOMPtr<nsIURI> principalURI;
+ nsresult rv = principal->GetURI(getter_AddRefs(principalURI));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!principalURI) {
+ principalURI = OwnerDoc()->GetDocumentURI();
+ }
+ bool formIsHTTPS;
+ rv = principalURI->SchemeIs("https", &formIsHTTPS);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ bool actionIsHTTPS;
+ rv = aActionURL->SchemeIs("https", &actionIsHTTPS);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ bool actionIsJS;
+ rv = aActionURL->SchemeIs("javascript", &actionIsJS);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!formIsHTTPS || actionIsHTTPS || actionIsJS) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = OwnerDoc()->GetWindow();
+ if (!window) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ if (!docShell) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIPrompt> prompt = do_GetInterface(docShell);
+ if (!prompt) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+ if (!stringBundleService) {
+ return NS_ERROR_FAILURE;
+ }
+ rv = stringBundleService->CreateBundle(
+ "chrome://global/locale/browser.properties",
+ getter_AddRefs(stringBundle));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsAutoString title;
+ nsAutoString message;
+ nsAutoString cont;
+ stringBundle->GetStringFromName(
+ u"formPostSecureToInsecureWarning.title", getter_Copies(title));
+ stringBundle->GetStringFromName(
+ u"formPostSecureToInsecureWarning.message",
+ getter_Copies(message));
+ stringBundle->GetStringFromName(
+ u"formPostSecureToInsecureWarning.continue",
+ getter_Copies(cont));
+ int32_t buttonPressed;
+ bool checkState = false; // this is unused (ConfirmEx requires this parameter)
+ rv = prompt->ConfirmEx(title.get(), message.get(),
+ (nsIPrompt::BUTTON_TITLE_IS_STRING *
+ nsIPrompt::BUTTON_POS_0) +
+ (nsIPrompt::BUTTON_TITLE_CANCEL *
+ nsIPrompt::BUTTON_POS_1),
+ cont.get(), nullptr, nullptr, nullptr,
+ &checkState, &buttonPressed);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *aCancelSubmit = (buttonPressed == 1);
+ uint32_t telemetryBucket =
+ nsISecurityUITelemetry::WARNING_CONFIRM_POST_TO_INSECURE_FROM_SECURE;
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI,
+ telemetryBucket);
+ if (!*aCancelSubmit) {
+ // The user opted to continue, so note that in the next telemetry bucket.
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI,
+ telemetryBucket + 1);
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLFormElement::NotifySubmitObservers(nsIURI* aActionURL,
+ bool* aCancelSubmit,
+ bool aEarlyNotify)
+{
+ // If this is the first form, bring alive the first form submit
+ // category observers
+ if (!gFirstFormSubmitted) {
+ gFirstFormSubmitted = true;
+ NS_CreateServicesFromCategory(NS_FIRST_FORMSUBMIT_CATEGORY,
+ nullptr,
+ NS_FIRST_FORMSUBMIT_CATEGORY);
+ }
+
+ if (!aEarlyNotify) {
+ nsresult rv = DoSecureToInsecureSubmitCheck(aActionURL, aCancelSubmit);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (*aCancelSubmit) {
+ return NS_OK;
+ }
+ }
+
+ // Notify observers that the form is being submitted.
+ nsCOMPtr<nsIObserverService> service =
+ mozilla::services::GetObserverService();
+ if (!service)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsISimpleEnumerator> theEnum;
+ nsresult rv = service->EnumerateObservers(aEarlyNotify ?
+ NS_EARLYFORMSUBMIT_SUBJECT :
+ NS_FORMSUBMIT_SUBJECT,
+ getter_AddRefs(theEnum));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (theEnum) {
+ nsCOMPtr<nsISupports> inst;
+ *aCancelSubmit = false;
+
+ // XXXbz what do the submit observers actually want? The window
+ // of the document this is shown in? Or something else?
+ // sXBL/XBL2 issue
+ nsCOMPtr<nsPIDOMWindowOuter> window = OwnerDoc()->GetWindow();
+
+ bool loop = true;
+ while (NS_SUCCEEDED(theEnum->HasMoreElements(&loop)) && loop) {
+ theEnum->GetNext(getter_AddRefs(inst));
+
+ nsCOMPtr<nsIFormSubmitObserver> formSubmitObserver(
+ do_QueryInterface(inst));
+ if (formSubmitObserver) {
+ rv = formSubmitObserver->Notify(this,
+ window ? window->GetCurrentInnerWindow() : nullptr,
+ aActionURL,
+ aCancelSubmit);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (*aCancelSubmit) {
+ return NS_OK;
+ }
+ }
+ }
+
+ return rv;
+}
+
+
+nsresult
+HTMLFormElement::WalkFormElements(HTMLFormSubmission* aFormSubmission)
+{
+ nsTArray<nsGenericHTMLFormElement*> sortedControls;
+ nsresult rv = mControls->GetSortedControls(sortedControls);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t len = sortedControls.Length();
+
+ // Hold a reference to the elements so they can't be deleted while
+ // calling SubmitNamesValues().
+ for (uint32_t i = 0; i < len; ++i) {
+ static_cast<nsGenericHTMLElement*>(sortedControls[i])->AddRef();
+ }
+
+ //
+ // Walk the list of nodes and call SubmitNamesValues() on the controls
+ //
+ for (uint32_t i = 0; i < len; ++i) {
+ // Tell the control to submit its name/value pairs to the submission
+ sortedControls[i]->SubmitNamesValues(aFormSubmission);
+ }
+
+ // Release the references.
+ for (uint32_t i = 0; i < len; ++i) {
+ static_cast<nsGenericHTMLElement*>(sortedControls[i])->Release();
+ }
+
+ return NS_OK;
+}
+
+// nsIForm
+
+NS_IMETHODIMP_(uint32_t)
+HTMLFormElement::GetElementCount() const
+{
+ uint32_t count = 0;
+ mControls->GetLength(&count);
+ return count;
+}
+
+Element*
+HTMLFormElement::IndexedGetter(uint32_t aIndex, bool &aFound)
+{
+ Element* element = mControls->mElements.SafeElementAt(aIndex, nullptr);
+ aFound = element != nullptr;
+ return element;
+}
+
+NS_IMETHODIMP_(nsIFormControl*)
+HTMLFormElement::GetElementAt(int32_t aIndex) const
+{
+ return mControls->mElements.SafeElementAt(aIndex, nullptr);
+}
+
+/**
+ * Compares the position of aControl1 and aControl2 in the document
+ * @param aControl1 First control to compare.
+ * @param aControl2 Second control to compare.
+ * @param aForm Parent form of the controls.
+ * @return < 0 if aControl1 is before aControl2,
+ * > 0 if aControl1 is after aControl2,
+ * 0 otherwise
+ */
+/* static */ int32_t
+HTMLFormElement::CompareFormControlPosition(Element* aElement1,
+ Element* aElement2,
+ const nsIContent* aForm)
+{
+ NS_ASSERTION(aElement1 != aElement2, "Comparing a form control to itself");
+
+ // If an element has a @form, we can assume it *might* be able to not have
+ // a parent and still be in the form.
+ NS_ASSERTION((aElement1->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
+ aElement1->GetParent()) &&
+ (aElement2->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
+ aElement2->GetParent()),
+ "Form controls should always have parents");
+
+ // If we pass aForm, we are assuming both controls are form descendants which
+ // is not always the case. This function should work but maybe slower.
+ // However, checking if both elements are form descendants may be slow too...
+ // TODO: remove the prevent asserts fix, see bug 598468.
+#ifdef DEBUG
+ nsLayoutUtils::gPreventAssertInCompareTreePosition = true;
+ int32_t rVal = nsLayoutUtils::CompareTreePosition(aElement1, aElement2, aForm);
+ nsLayoutUtils::gPreventAssertInCompareTreePosition = false;
+
+ return rVal;
+#else // DEBUG
+ return nsLayoutUtils::CompareTreePosition(aElement1, aElement2, aForm);
+#endif // DEBUG
+}
+
+#ifdef DEBUG
+/**
+ * Checks that all form elements are in document order. Asserts if any pair of
+ * consecutive elements are not in increasing document order.
+ *
+ * @param aControls List of form controls to check.
+ * @param aForm Parent form of the controls.
+ */
+/* static */ void
+HTMLFormElement::AssertDocumentOrder(
+ const nsTArray<nsGenericHTMLFormElement*>& aControls, nsIContent* aForm)
+{
+ // TODO: remove the return statement with bug 598468.
+ // This is done to prevent asserts in some edge cases.
+ return;
+
+ // Only iterate if aControls is not empty, since otherwise
+ // |aControls.Length() - 1| will be a very large unsigned number... not what
+ // we want here.
+ if (!aControls.IsEmpty()) {
+ for (uint32_t i = 0; i < aControls.Length() - 1; ++i) {
+ NS_ASSERTION(CompareFormControlPosition(aControls[i], aControls[i + 1],
+ aForm) < 0,
+ "Form controls not ordered correctly");
+ }
+ }
+}
+#endif
+
+void
+HTMLFormElement::PostPasswordEvent()
+{
+ // Don't fire another add event if we have a pending add event.
+ if (mFormPasswordEventDispatcher.get()) {
+ return;
+ }
+
+ mFormPasswordEventDispatcher =
+ new AsyncEventDispatcher(this, NS_LITERAL_STRING("DOMFormHasPassword"),
+ true, true);
+ mFormPasswordEventDispatcher->PostDOMEvent();
+}
+
+namespace {
+
+struct FormComparator
+{
+ Element* const mChild;
+ HTMLFormElement* const mForm;
+ FormComparator(Element* aChild, HTMLFormElement* aForm)
+ : mChild(aChild), mForm(aForm) {}
+ int operator()(Element* aElement) const {
+ return HTMLFormElement::CompareFormControlPosition(mChild, aElement, mForm);
+ }
+};
+
+} // namespace
+
+// This function return true if the element, once appended, is the last one in
+// the array.
+template<typename ElementType>
+static bool
+AddElementToList(nsTArray<ElementType*>& aList, ElementType* aChild,
+ HTMLFormElement* aForm)
+{
+ NS_ASSERTION(aList.IndexOf(aChild) == aList.NoIndex,
+ "aChild already in aList");
+
+ const uint32_t count = aList.Length();
+ ElementType* element;
+ bool lastElement = false;
+
+ // Optimize most common case where we insert at the end.
+ int32_t position = -1;
+ if (count > 0) {
+ element = aList[count - 1];
+ position =
+ HTMLFormElement::CompareFormControlPosition(aChild, element, aForm);
+ }
+
+ // If this item comes after the last element, or the elements array is
+ // empty, we append to the end. Otherwise, we do a binary search to
+ // determine where the element should go.
+ if (position >= 0 || count == 0) {
+ // WEAK - don't addref
+ aList.AppendElement(aChild);
+ lastElement = true;
+ }
+ else {
+ size_t idx;
+ BinarySearchIf(aList, 0, count, FormComparator(aChild, aForm), &idx);
+
+ // WEAK - don't addref
+ aList.InsertElementAt(idx, aChild);
+ }
+
+ return lastElement;
+}
+
+nsresult
+HTMLFormElement::AddElement(nsGenericHTMLFormElement* aChild,
+ bool aUpdateValidity, bool aNotify)
+{
+ // If an element has a @form, we can assume it *might* be able to not have
+ // a parent and still be in the form.
+ NS_ASSERTION(aChild->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
+ aChild->GetParent(),
+ "Form control should have a parent");
+
+ // Determine whether to add the new element to the elements or
+ // the not-in-elements list.
+ bool childInElements = HTMLFormControlsCollection::ShouldBeInElements(aChild);
+ nsTArray<nsGenericHTMLFormElement*>& controlList = childInElements ?
+ mControls->mElements : mControls->mNotInElements;
+
+ bool lastElement = AddElementToList(controlList, aChild, this);
+
+#ifdef DEBUG
+ AssertDocumentOrder(controlList, this);
+#endif
+
+ int32_t type = aChild->GetType();
+
+ //
+ // If it is a password control, and the password manager has not yet been
+ // initialized, initialize the password manager
+ //
+ if (type == NS_FORM_INPUT_PASSWORD) {
+ if (!gPasswordManagerInitialized) {
+ gPasswordManagerInitialized = true;
+ NS_CreateServicesFromCategory(NS_PASSWORDMANAGER_CATEGORY,
+ nullptr,
+ NS_PASSWORDMANAGER_CATEGORY);
+ }
+ PostPasswordEvent();
+ }
+
+ // Default submit element handling
+ if (aChild->IsSubmitControl()) {
+ // Update mDefaultSubmitElement, mFirstSubmitInElements,
+ // mFirstSubmitNotInElements.
+
+ nsGenericHTMLFormElement** firstSubmitSlot =
+ childInElements ? &mFirstSubmitInElements : &mFirstSubmitNotInElements;
+
+ // The new child is the new first submit in its list if the firstSubmitSlot
+ // is currently empty or if the child is before what's currently in the
+ // slot. Note that if we already have a control in firstSubmitSlot and
+ // we're appending this element can't possibly replace what's currently in
+ // the slot. Also note that aChild can't become the mDefaultSubmitElement
+ // unless it replaces what's in the slot. If it _does_ replace what's in
+ // the slot, it becomes the default submit if either the default submit is
+ // what's in the slot or the child is earlier than the default submit.
+ nsGenericHTMLFormElement* oldDefaultSubmit = mDefaultSubmitElement;
+ if (!*firstSubmitSlot ||
+ (!lastElement &&
+ CompareFormControlPosition(aChild, *firstSubmitSlot, this) < 0)) {
+ // Update mDefaultSubmitElement if it's currently in a valid state.
+ // Valid state means either non-null or null because there are in fact
+ // no submit elements around.
+ if ((mDefaultSubmitElement ||
+ (!mFirstSubmitInElements && !mFirstSubmitNotInElements)) &&
+ (*firstSubmitSlot == mDefaultSubmitElement ||
+ CompareFormControlPosition(aChild,
+ mDefaultSubmitElement, this) < 0)) {
+ mDefaultSubmitElement = aChild;
+ }
+ *firstSubmitSlot = aChild;
+ }
+ NS_POSTCONDITION(mDefaultSubmitElement == mFirstSubmitInElements ||
+ mDefaultSubmitElement == mFirstSubmitNotInElements ||
+ !mDefaultSubmitElement,
+ "What happened here?");
+
+ // Notify that the state of the previous default submit element has changed
+ // if the element which is the default submit element has changed. The new
+ // default submit element is responsible for its own state update.
+ if (oldDefaultSubmit && oldDefaultSubmit != mDefaultSubmitElement) {
+ oldDefaultSubmit->UpdateState(aNotify);
+ }
+ }
+
+ // If the element is subject to constraint validaton and is invalid, we need
+ // to update our internal counter.
+ if (aUpdateValidity) {
+ nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aChild);
+ if (cvElmt &&
+ cvElmt->IsCandidateForConstraintValidation() && !cvElmt->IsValid()) {
+ UpdateValidity(false);
+ }
+ }
+
+ // Notify the radio button it's been added to a group
+ // This has to be done _after_ UpdateValidity() call to prevent the element
+ // being count twice.
+ if (type == NS_FORM_INPUT_RADIO) {
+ RefPtr<HTMLInputElement> radio =
+ static_cast<HTMLInputElement*>(aChild);
+ radio->AddedToRadioGroup();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLFormElement::AddElementToTable(nsGenericHTMLFormElement* aChild,
+ const nsAString& aName)
+{
+ return mControls->AddElementToTable(aChild, aName);
+}
+
+
+nsresult
+HTMLFormElement::RemoveElement(nsGenericHTMLFormElement* aChild,
+ bool aUpdateValidity)
+{
+ //
+ // Remove it from the radio group if it's a radio button
+ //
+ nsresult rv = NS_OK;
+ if (aChild->GetType() == NS_FORM_INPUT_RADIO) {
+ RefPtr<HTMLInputElement> radio =
+ static_cast<HTMLInputElement*>(aChild);
+ radio->WillRemoveFromRadioGroup();
+ }
+
+ // Determine whether to remove the child from the elements list
+ // or the not in elements list.
+ bool childInElements = HTMLFormControlsCollection::ShouldBeInElements(aChild);
+ nsTArray<nsGenericHTMLFormElement*>& controls = childInElements ?
+ mControls->mElements : mControls->mNotInElements;
+
+ // Find the index of the child. This will be used later if necessary
+ // to find the default submit.
+ size_t index = controls.IndexOf(aChild);
+ NS_ENSURE_STATE(index != controls.NoIndex);
+
+ controls.RemoveElementAt(index);
+
+ // Update our mFirstSubmit* values.
+ nsGenericHTMLFormElement** firstSubmitSlot =
+ childInElements ? &mFirstSubmitInElements : &mFirstSubmitNotInElements;
+ if (aChild == *firstSubmitSlot) {
+ *firstSubmitSlot = nullptr;
+
+ // We are removing the first submit in this list, find the new first submit
+ uint32_t length = controls.Length();
+ for (uint32_t i = index; i < length; ++i) {
+ nsGenericHTMLFormElement* currentControl = controls[i];
+ if (currentControl->IsSubmitControl()) {
+ *firstSubmitSlot = currentControl;
+ break;
+ }
+ }
+ }
+
+ if (aChild == mDefaultSubmitElement) {
+ // Need to reset mDefaultSubmitElement. Do this asynchronously so
+ // that we're not doing it while the DOM is in flux.
+ mDefaultSubmitElement = nullptr;
+ nsContentUtils::AddScriptRunner(new RemoveElementRunnable(this));
+
+ // Note that we don't need to notify on the old default submit (which is
+ // being removed) because it's either being removed from the DOM or
+ // changing attributes in a way that makes it responsible for sending its
+ // own notifications.
+ }
+
+ // If the element was subject to constraint validaton and is invalid, we need
+ // to update our internal counter.
+ if (aUpdateValidity) {
+ nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aChild);
+ if (cvElmt &&
+ cvElmt->IsCandidateForConstraintValidation() && !cvElmt->IsValid()) {
+ UpdateValidity(true);
+ }
+ }
+
+ return rv;
+}
+
+void
+HTMLFormElement::HandleDefaultSubmitRemoval()
+{
+ if (mDefaultSubmitElement) {
+ // Already got reset somehow; nothing else to do here
+ return;
+ }
+
+ if (!mFirstSubmitNotInElements) {
+ mDefaultSubmitElement = mFirstSubmitInElements;
+ } else if (!mFirstSubmitInElements) {
+ mDefaultSubmitElement = mFirstSubmitNotInElements;
+ } else {
+ NS_ASSERTION(mFirstSubmitInElements != mFirstSubmitNotInElements,
+ "How did that happen?");
+ // Have both; use the earlier one
+ mDefaultSubmitElement =
+ CompareFormControlPosition(mFirstSubmitInElements,
+ mFirstSubmitNotInElements, this) < 0 ?
+ mFirstSubmitInElements : mFirstSubmitNotInElements;
+ }
+
+ NS_POSTCONDITION(mDefaultSubmitElement == mFirstSubmitInElements ||
+ mDefaultSubmitElement == mFirstSubmitNotInElements,
+ "What happened here?");
+
+ // Notify about change if needed.
+ if (mDefaultSubmitElement) {
+ mDefaultSubmitElement->UpdateState(true);
+ }
+}
+
+nsresult
+HTMLFormElement::RemoveElementFromTableInternal(
+ nsInterfaceHashtable<nsStringHashKey,nsISupports>& aTable,
+ nsIContent* aChild, const nsAString& aName)
+{
+ nsCOMPtr<nsISupports> supports;
+
+ if (!aTable.Get(aName, getter_AddRefs(supports)))
+ return NS_OK;
+
+ // Single element in the hash, just remove it if it's the one
+ // we're trying to remove...
+ if (supports == aChild) {
+ aTable.Remove(aName);
+ ++mExpandoAndGeneration.generation;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIContent> content(do_QueryInterface(supports));
+ if (content) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMNodeList> nodeList(do_QueryInterface(supports));
+ NS_ENSURE_TRUE(nodeList, NS_ERROR_FAILURE);
+
+ // Upcast, uggly, but it works!
+ nsBaseContentList *list = static_cast<nsBaseContentList*>(nodeList.get());
+
+ list->RemoveElement(aChild);
+
+ uint32_t length = 0;
+ list->GetLength(&length);
+
+ if (!length) {
+ // If the list is empty we remove if from our hash, this shouldn't
+ // happen tho
+ aTable.Remove(aName);
+ ++mExpandoAndGeneration.generation;
+ } else if (length == 1) {
+ // Only one element left, replace the list in the hash with the
+ // single element.
+ nsIContent* node = list->Item(0);
+ if (node) {
+ aTable.Put(aName, node);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLFormElement::RemoveElementFromTable(nsGenericHTMLFormElement* aElement,
+ const nsAString& aName,
+ RemoveElementReason aRemoveReason)
+{
+ // If the element is being removed from the form, we have to remove it from
+ // the past names map.
+ if (aRemoveReason == ElementRemoved) {
+ uint32_t oldCount = mPastNameLookupTable.Count();
+ for (auto iter = mPastNameLookupTable.Iter(); !iter.Done(); iter.Next()) {
+ if (static_cast<void*>(aElement) == iter.Data()) {
+ iter.Remove();
+ }
+ }
+ if (oldCount != mPastNameLookupTable.Count()) {
+ ++mExpandoAndGeneration.generation;
+ }
+ }
+
+ return mControls->RemoveElementFromTable(aElement, aName);
+}
+
+already_AddRefed<nsISupports>
+HTMLFormElement::NamedGetter(const nsAString& aName, bool &aFound)
+{
+ aFound = true;
+
+ nsCOMPtr<nsISupports> result = DoResolveName(aName, true);
+ if (result) {
+ AddToPastNamesMap(aName, result);
+ return result.forget();
+ }
+
+ result = mImageNameLookupTable.GetWeak(aName);
+ if (result) {
+ AddToPastNamesMap(aName, result);
+ return result.forget();
+ }
+
+ result = mPastNameLookupTable.GetWeak(aName);
+ if (result) {
+ return result.forget();
+ }
+
+ aFound = false;
+ return nullptr;
+}
+
+void
+HTMLFormElement::GetSupportedNames(nsTArray<nsString >& aRetval)
+{
+ // TODO https://github.com/whatwg/html/issues/1731
+}
+
+already_AddRefed<nsISupports>
+HTMLFormElement::FindNamedItem(const nsAString& aName,
+ nsWrapperCache** aCache)
+{
+ // FIXME Get the wrapper cache from DoResolveName.
+
+ bool found;
+ nsCOMPtr<nsISupports> result = NamedGetter(aName, found);
+ if (result) {
+ *aCache = nullptr;
+ return result.forget();
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<nsISupports>
+HTMLFormElement::DoResolveName(const nsAString& aName,
+ bool aFlushContent)
+{
+ nsCOMPtr<nsISupports> result =
+ mControls->NamedItemInternal(aName, aFlushContent);
+ return result.forget();
+}
+
+void
+HTMLFormElement::OnSubmitClickBegin(nsIContent* aOriginatingElement)
+{
+ mDeferSubmission = true;
+
+ // Prepare to run NotifySubmitObservers early before the
+ // scripts on the page get to modify the form data, possibly
+ // throwing off any password manager. (bug 257781)
+ nsCOMPtr<nsIURI> actionURI;
+ nsresult rv;
+
+ rv = GetActionURL(getter_AddRefs(actionURI), aOriginatingElement);
+ if (NS_FAILED(rv) || !actionURI)
+ return;
+
+ // Notify observers of submit if the form is valid.
+ // TODO: checking for mInvalidElementsCount is a temporary fix that should be
+ // removed with bug 610402.
+ if (mInvalidElementsCount == 0) {
+ bool cancelSubmit = false;
+ rv = NotifySubmitObservers(actionURI, &cancelSubmit, true);
+ if (NS_SUCCEEDED(rv)) {
+ mNotifiedObservers = true;
+ mNotifiedObserversResult = cancelSubmit;
+ }
+ }
+}
+
+void
+HTMLFormElement::OnSubmitClickEnd()
+{
+ mDeferSubmission = false;
+}
+
+void
+HTMLFormElement::FlushPendingSubmission()
+{
+ if (mPendingSubmission) {
+ // Transfer owning reference so that the submissioin doesn't get deleted
+ // if we reenter
+ nsAutoPtr<HTMLFormSubmission> submission = Move(mPendingSubmission);
+
+ SubmitSubmission(submission);
+ }
+}
+
+nsresult
+HTMLFormElement::GetActionURL(nsIURI** aActionURL,
+ nsIContent* aOriginatingElement)
+{
+ nsresult rv = NS_OK;
+
+ *aActionURL = nullptr;
+
+ //
+ // Grab the URL string
+ //
+ // If the originating element is a submit control and has the formaction
+ // attribute specified, it should be used. Otherwise, the action attribute
+ // from the form element should be used.
+ //
+ nsAutoString action;
+
+ if (aOriginatingElement &&
+ aOriginatingElement->HasAttr(kNameSpaceID_None, nsGkAtoms::formaction)) {
+#ifdef DEBUG
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(aOriginatingElement);
+ NS_ASSERTION(formControl && formControl->IsSubmitControl(),
+ "The originating element must be a submit form control!");
+#endif // DEBUG
+
+ nsCOMPtr<nsIDOMHTMLInputElement> inputElement = do_QueryInterface(aOriginatingElement);
+ if (inputElement) {
+ inputElement->GetFormAction(action);
+ } else {
+ nsCOMPtr<nsIDOMHTMLButtonElement> buttonElement = do_QueryInterface(aOriginatingElement);
+ if (buttonElement) {
+ buttonElement->GetFormAction(action);
+ } else {
+ NS_ERROR("Originating element must be an input or button element!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ } else {
+ GetAction(action);
+ }
+
+ //
+ // Form the full action URL
+ //
+
+ // Get the document to form the URL.
+ // We'll also need it later to get the DOM window when notifying form submit
+ // observers (bug 33203)
+ if (!IsInUncomposedDoc()) {
+ return NS_OK; // No doc means don't submit, see Bug 28988
+ }
+
+ // Get base URL
+ nsIDocument *document = OwnerDoc();
+ nsIURI *docURI = document->GetDocumentURI();
+ NS_ENSURE_TRUE(docURI, NS_ERROR_UNEXPECTED);
+
+ // If an action is not specified and we are inside
+ // a HTML document then reload the URL. This makes us
+ // compatible with 4.x browsers.
+ // If we are in some other type of document such as XML or
+ // XUL, do nothing. This prevents undesirable reloading of
+ // a document inside XUL.
+
+ nsCOMPtr<nsIURI> actionURL;
+ if (action.IsEmpty()) {
+ nsCOMPtr<nsIHTMLDocument> htmlDoc(do_QueryInterface(document));
+ if (!htmlDoc) {
+ // Must be a XML, XUL or other non-HTML document type
+ // so do nothing.
+ return NS_OK;
+ }
+
+ rv = docURI->Clone(getter_AddRefs(actionURL));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ nsCOMPtr<nsIURI> baseURL = GetBaseURI();
+ NS_ASSERTION(baseURL, "No Base URL found in Form Submit!\n");
+ if (!baseURL) {
+ return NS_OK; // No base URL -> exit early, see Bug 30721
+ }
+ rv = NS_NewURI(getter_AddRefs(actionURL), action, nullptr, baseURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ //
+ // Verify the URL should be reached
+ //
+ // Get security manager, check to see if access to action URI is allowed.
+ //
+ nsIScriptSecurityManager *securityManager =
+ nsContentUtils::GetSecurityManager();
+ rv = securityManager->
+ CheckLoadURIWithPrincipal(NodePrincipal(), actionURL,
+ nsIScriptSecurityManager::STANDARD);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if CSP allows this form-action
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ rv = NodePrincipal()->GetCsp(getter_AddRefs(csp));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (csp) {
+ bool permitsFormAction = true;
+
+ // form-action is only enforced if explicitly defined in the
+ // policy - do *not* consult default-src, see:
+ // http://www.w3.org/TR/CSP2/#directive-default-src
+ rv = csp->Permits(actionURL, nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE,
+ true, &permitsFormAction);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!permitsFormAction) {
+ return NS_ERROR_CSP_FORM_ACTION_VIOLATION;
+ }
+ }
+
+ // Potentially the page uses the CSP directive 'upgrade-insecure-requests'. In
+ // such a case we have to upgrade the action url from http:// to https://.
+ // If the actionURL is not http, then there is nothing to do.
+ bool isHttpScheme = false;
+ rv = actionURL->SchemeIs("http", &isHttpScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isHttpScheme && document->GetUpgradeInsecureRequests(false)) {
+ // let's use the old specification before the upgrade for logging
+ nsAutoCString spec;
+ rv = actionURL->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ConvertUTF8toUTF16 reportSpec(spec);
+
+ // upgrade the actionURL from http:// to use https://
+ nsCOMPtr<nsIURI> upgradedActionURL;
+ rv = NS_GetSecureUpgradedURI(actionURL, getter_AddRefs(upgradedActionURL));
+ NS_ENSURE_SUCCESS(rv, rv);
+ actionURL = upgradedActionURL.forget();
+
+ // let's log a message to the console that we are upgrading a request
+ nsAutoCString scheme;
+ rv = actionURL->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ConvertUTF8toUTF16 reportScheme(scheme);
+
+ const char16_t* params[] = { reportSpec.get(), reportScheme.get() };
+ CSP_LogLocalizedStr(u"upgradeInsecureRequest",
+ params, ArrayLength(params),
+ EmptyString(), // aSourceFile
+ EmptyString(), // aScriptSample
+ 0, // aLineNumber
+ 0, // aColumnNumber
+ nsIScriptError::warningFlag, "CSP",
+ document->InnerWindowID());
+ }
+
+ //
+ // Assign to the output
+ //
+ actionURL.forget(aActionURL);
+
+ return rv;
+}
+
+NS_IMETHODIMP_(nsIFormControl*)
+HTMLFormElement::GetDefaultSubmitElement() const
+{
+ NS_PRECONDITION(mDefaultSubmitElement == mFirstSubmitInElements ||
+ mDefaultSubmitElement == mFirstSubmitNotInElements,
+ "What happened here?");
+
+ return mDefaultSubmitElement;
+}
+
+bool
+HTMLFormElement::IsDefaultSubmitElement(const nsIFormControl* aControl) const
+{
+ NS_PRECONDITION(aControl, "Unexpected call");
+
+ if (aControl == mDefaultSubmitElement) {
+ // Yes, it is
+ return true;
+ }
+
+ if (mDefaultSubmitElement ||
+ (aControl != mFirstSubmitInElements &&
+ aControl != mFirstSubmitNotInElements)) {
+ // It isn't
+ return false;
+ }
+
+ // mDefaultSubmitElement is null, but we have a non-null submit around
+ // (aControl, in fact). figure out whether it's in fact the default submit
+ // and just hasn't been set that way yet. Note that we can't just call
+ // HandleDefaultSubmitRemoval because we might need to notify to handle that
+ // correctly and we don't know whether that's safe right here.
+ if (!mFirstSubmitInElements || !mFirstSubmitNotInElements) {
+ // We only have one first submit; aControl has to be it
+ return true;
+ }
+
+ // We have both kinds of submits. Check which comes first.
+ nsIFormControl* defaultSubmit =
+ CompareFormControlPosition(mFirstSubmitInElements,
+ mFirstSubmitNotInElements, this) < 0 ?
+ mFirstSubmitInElements : mFirstSubmitNotInElements;
+ return aControl == defaultSubmit;
+}
+
+bool
+HTMLFormElement::ImplicitSubmissionIsDisabled() const
+{
+ // Input text controls are always in the elements list.
+ uint32_t numDisablingControlsFound = 0;
+ uint32_t length = mControls->mElements.Length();
+ for (uint32_t i = 0; i < length && numDisablingControlsFound < 2; ++i) {
+ if (mControls->mElements[i]->IsSingleLineTextControl(false) ||
+ mControls->mElements[i]->GetType() == NS_FORM_INPUT_NUMBER) {
+ numDisablingControlsFound++;
+ }
+ }
+ return numDisablingControlsFound != 1;
+}
+
+NS_IMETHODIMP
+HTMLFormElement::GetEncoding(nsAString& aEncoding)
+{
+ return GetEnctype(aEncoding);
+}
+
+NS_IMETHODIMP
+HTMLFormElement::SetEncoding(const nsAString& aEncoding)
+{
+ return SetEnctype(aEncoding);
+}
+
+int32_t
+HTMLFormElement::Length()
+{
+ return mControls->Length();
+}
+
+NS_IMETHODIMP
+HTMLFormElement::GetLength(int32_t* aLength)
+{
+ *aLength = Length();
+ return NS_OK;
+}
+
+void
+HTMLFormElement::ForgetCurrentSubmission()
+{
+ mNotifiedObservers = false;
+ mIsSubmitting = false;
+ mSubmittingRequest = nullptr;
+ nsCOMPtr<nsIWebProgress> webProgress = do_QueryReferent(mWebProgress);
+ if (webProgress) {
+ webProgress->RemoveProgressListener(this);
+ }
+ mWebProgress = nullptr;
+}
+
+bool
+HTMLFormElement::CheckFormValidity(nsIMutableArray* aInvalidElements) const
+{
+ bool ret = true;
+
+ nsTArray<nsGenericHTMLFormElement*> sortedControls;
+ if (NS_FAILED(mControls->GetSortedControls(sortedControls))) {
+ return false;
+ }
+
+ uint32_t len = sortedControls.Length();
+
+ // Hold a reference to the elements so they can't be deleted while calling
+ // the invalid events.
+ for (uint32_t i = 0; i < len; ++i) {
+ sortedControls[i]->AddRef();
+ }
+
+ for (uint32_t i = 0; i < len; ++i) {
+ nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(sortedControls[i]);
+ if (cvElmt && cvElmt->IsCandidateForConstraintValidation() &&
+ !cvElmt->IsValid()) {
+ ret = false;
+ bool defaultAction = true;
+ nsContentUtils::DispatchTrustedEvent(sortedControls[i]->OwnerDoc(),
+ static_cast<nsIContent*>(sortedControls[i]),
+ NS_LITERAL_STRING("invalid"),
+ false, true, &defaultAction);
+
+ // Add all unhandled invalid controls to aInvalidElements if the caller
+ // requested them.
+ if (defaultAction && aInvalidElements) {
+ aInvalidElements->AppendElement(ToSupports(sortedControls[i]),
+ false);
+ }
+ }
+ }
+
+ // Release the references.
+ for (uint32_t i = 0; i < len; ++i) {
+ static_cast<nsGenericHTMLElement*>(sortedControls[i])->Release();
+ }
+
+ return ret;
+}
+
+bool
+HTMLFormElement::CheckValidFormSubmission()
+{
+ /**
+ * Check for form validity: do not submit a form if there are unhandled
+ * invalid controls in the form.
+ * This should not be done if the form has been submitted with .submit().
+ *
+ * NOTE: for the moment, we are also checking that there is an observer for
+ * NS_INVALIDFORMSUBMIT_SUBJECT so it will prevent blocking form submission
+ * if the browser does not have implemented a UI yet.
+ *
+ * TODO: the check for observer should be removed later when HTML5 Forms will
+ * be spread enough and authors will assume forms can't be submitted when
+ * invalid. See bug 587671.
+ */
+
+ NS_ASSERTION(!HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate),
+ "We shouldn't be there if novalidate is set!");
+
+ // When .submit() is called aEvent = nullptr so we can rely on that to know if
+ // we have to check the validity of the form.
+ nsCOMPtr<nsIObserverService> service =
+ mozilla::services::GetObserverService();
+ if (!service) {
+ NS_WARNING("No observer service available!");
+ return true;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> theEnum;
+ nsresult rv = service->EnumerateObservers(NS_INVALIDFORMSUBMIT_SUBJECT,
+ getter_AddRefs(theEnum));
+ // Return true on error here because that's what we always did
+ NS_ENSURE_SUCCESS(rv, true);
+
+ bool hasObserver = false;
+ rv = theEnum->HasMoreElements(&hasObserver);
+
+ // Do not check form validity if there is no observer for
+ // NS_INVALIDFORMSUBMIT_SUBJECT.
+ if (NS_SUCCEEDED(rv) && hasObserver) {
+ nsCOMPtr<nsIMutableArray> invalidElements =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ // Return true on error here because that's what we always did
+ NS_ENSURE_SUCCESS(rv, true);
+
+ if (!CheckFormValidity(invalidElements.get())) {
+ // For the first invalid submission, we should update element states.
+ // We have to do that _before_ calling the observers so we are sure they
+ // will not interfere (like focusing the element).
+ if (!mEverTriedInvalidSubmit) {
+ mEverTriedInvalidSubmit = true;
+
+ /*
+ * We are going to call update states assuming elements want to
+ * be notified because we can't know.
+ * Submissions shouldn't happen during parsing so it _should_ be safe.
+ */
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ for (uint32_t i = 0, length = mControls->mElements.Length();
+ i < length; ++i) {
+ // Input elements can trigger a form submission and we want to
+ // update the style in that case.
+ if (mControls->mElements[i]->IsHTMLElement(nsGkAtoms::input) &&
+ nsContentUtils::IsFocusedContent(mControls->mElements[i])) {
+ static_cast<HTMLInputElement*>(mControls->mElements[i])
+ ->UpdateValidityUIBits(true);
+ }
+
+ mControls->mElements[i]->UpdateState(true);
+ }
+
+ // Because of backward compatibility, <input type='image'> is not in
+ // elements but can be invalid.
+ // TODO: should probably be removed when bug 606491 will be fixed.
+ for (uint32_t i = 0, length = mControls->mNotInElements.Length();
+ i < length; ++i) {
+ mControls->mNotInElements[i]->UpdateState(true);
+ }
+ }
+
+ nsCOMPtr<nsISupports> inst;
+ nsCOMPtr<nsIFormSubmitObserver> observer;
+ bool more = true;
+ while (NS_SUCCEEDED(theEnum->HasMoreElements(&more)) && more) {
+ theEnum->GetNext(getter_AddRefs(inst));
+ observer = do_QueryInterface(inst);
+
+ if (observer) {
+ observer->NotifyInvalidSubmit(this,
+ static_cast<nsIArray*>(invalidElements));
+ }
+ }
+
+ // The form is invalid. Observers have been alerted. Do not submit.
+ return false;
+ }
+ } else {
+ NS_WARNING("There is no observer for \"invalidformsubmit\". \
+One should be implemented!");
+ }
+
+ return true;
+}
+
+bool
+HTMLFormElement::SubmissionCanProceed(Element* aSubmitter)
+{
+#ifdef DEBUG
+ if (aSubmitter) {
+ nsCOMPtr<nsIFormControl> fc = do_QueryInterface(aSubmitter);
+ MOZ_ASSERT(fc);
+
+ uint32_t type = fc->GetType();
+ MOZ_ASSERT(type == NS_FORM_INPUT_SUBMIT ||
+ type == NS_FORM_INPUT_IMAGE ||
+ type == NS_FORM_BUTTON_SUBMIT,
+ "aSubmitter is not a submit control?");
+ }
+#endif
+
+ // Modified step 2 of
+ // https://html.spec.whatwg.org/multipage/forms.html#concept-form-submit --
+ // we're not checking whether the node document is disconnected yet...
+ if (OwnerDoc()->GetSandboxFlags() & SANDBOXED_FORMS) {
+ return false;
+ }
+
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) {
+ return true;
+ }
+
+ if (aSubmitter &&
+ aSubmitter->HasAttr(kNameSpaceID_None, nsGkAtoms::formnovalidate)) {
+ return true;
+ }
+
+ return CheckValidFormSubmission();
+}
+
+void
+HTMLFormElement::UpdateValidity(bool aElementValidity)
+{
+ if (aElementValidity) {
+ --mInvalidElementsCount;
+ } else {
+ ++mInvalidElementsCount;
+ }
+
+ NS_ASSERTION(mInvalidElementsCount >= 0, "Something went seriously wrong!");
+
+ // The form validity has just changed if:
+ // - there are no more invalid elements ;
+ // - or there is one invalid elmement and an element just became invalid.
+ // If we have invalid elements and we used to before as well, do nothing.
+ if (mInvalidElementsCount &&
+ (mInvalidElementsCount != 1 || aElementValidity)) {
+ return;
+ }
+
+ /*
+ * We are going to update states assuming submit controls want to
+ * be notified because we can't know.
+ * UpdateValidity shouldn't be called so much during parsing so it _should_
+ * be safe.
+ */
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ // Inform submit controls that the form validity has changed.
+ for (uint32_t i = 0, length = mControls->mElements.Length();
+ i < length; ++i) {
+ if (mControls->mElements[i]->IsSubmitControl()) {
+ mControls->mElements[i]->UpdateState(true);
+ }
+ }
+
+ // Because of backward compatibility, <input type='image'> is not in elements
+ // so we have to check for controls not in elements too.
+ uint32_t length = mControls->mNotInElements.Length();
+ for (uint32_t i = 0; i < length; ++i) {
+ if (mControls->mNotInElements[i]->IsSubmitControl()) {
+ mControls->mNotInElements[i]->UpdateState(true);
+ }
+ }
+
+ UpdateState(true);
+}
+
+// nsIWebProgressListener
+NS_IMETHODIMP
+HTMLFormElement::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aStateFlags,
+ nsresult aStatus)
+{
+ // If STATE_STOP is never fired for any reason (redirect? Failed state
+ // change?) the form element will leak. It will be kept around by the
+ // nsIWebProgressListener (assuming it keeps a strong pointer). We will
+ // consequently leak the request.
+ if (aRequest == mSubmittingRequest &&
+ aStateFlags & nsIWebProgressListener::STATE_STOP) {
+ ForgetCurrentSubmission();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLFormElement::OnProgressChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLFormElement::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI* location,
+ uint32_t aFlags)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLFormElement::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLFormElement::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t state)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(int32_t)
+HTMLFormElement::IndexOfControl(nsIFormControl* aControl)
+{
+ int32_t index = 0;
+ return mControls->IndexOfControl(aControl, &index) == NS_OK ? index : 0;
+}
+
+void
+HTMLFormElement::SetCurrentRadioButton(const nsAString& aName,
+ HTMLInputElement* aRadio)
+{
+ mSelectedRadioButtons.Put(aName, aRadio);
+}
+
+HTMLInputElement*
+HTMLFormElement::GetCurrentRadioButton(const nsAString& aName)
+{
+ return mSelectedRadioButtons.GetWeak(aName);
+}
+
+NS_IMETHODIMP
+HTMLFormElement::GetNextRadioButton(const nsAString& aName,
+ const bool aPrevious,
+ HTMLInputElement* aFocusedRadio,
+ HTMLInputElement** aRadioOut)
+{
+ // Return the radio button relative to the focused radio button.
+ // If no radio is focused, get the radio relative to the selected one.
+ *aRadioOut = nullptr;
+
+ RefPtr<HTMLInputElement> currentRadio;
+ if (aFocusedRadio) {
+ currentRadio = aFocusedRadio;
+ }
+ else {
+ mSelectedRadioButtons.Get(aName, getter_AddRefs(currentRadio));
+ }
+
+ nsCOMPtr<nsISupports> itemWithName = DoResolveName(aName, true);
+ nsCOMPtr<nsINodeList> radioGroup(do_QueryInterface(itemWithName));
+
+ if (!radioGroup) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t index = radioGroup->IndexOf(currentRadio);
+ if (index < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t numRadios;
+ radioGroup->GetLength(&numRadios);
+ RefPtr<HTMLInputElement> radio;
+
+ bool isRadio = false;
+ do {
+ if (aPrevious) {
+ if (--index < 0) {
+ index = numRadios -1;
+ }
+ }
+ else if (++index >= (int32_t)numRadios) {
+ index = 0;
+ }
+ radio = HTMLInputElement::FromContentOrNull(radioGroup->Item(index));
+ isRadio = radio && radio->GetType() == NS_FORM_INPUT_RADIO;
+ if (!isRadio) {
+ continue;
+ }
+
+ nsAutoString name;
+ radio->GetName(name);
+ isRadio = aName.Equals(name);
+ } while (!isRadio || (radio->Disabled() && radio != currentRadio));
+
+ NS_IF_ADDREF(*aRadioOut = radio);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLFormElement::WalkRadioGroup(const nsAString& aName,
+ nsIRadioVisitor* aVisitor,
+ bool aFlushContent)
+{
+ if (aName.IsEmpty()) {
+ //
+ // XXX If the name is empty, it's not stored in the control list. There
+ // *must* be a more efficient way to do this.
+ //
+ nsCOMPtr<nsIFormControl> control;
+ uint32_t len = GetElementCount();
+ for (uint32_t i = 0; i < len; i++) {
+ control = GetElementAt(i);
+ if (control->GetType() == NS_FORM_INPUT_RADIO) {
+ nsCOMPtr<nsIContent> controlContent = do_QueryInterface(control);
+ if (controlContent &&
+ controlContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+ EmptyString(), eCaseMatters) &&
+ !aVisitor->Visit(control)) {
+ break;
+ }
+ }
+ }
+ return NS_OK;
+ }
+
+ // Get the control / list of controls from the form using form["name"]
+ nsCOMPtr<nsISupports> item = DoResolveName(aName, aFlushContent);
+ if (!item) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If it's just a lone radio button, then select it.
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(item);
+ if (formControl) {
+ if (formControl->GetType() == NS_FORM_INPUT_RADIO) {
+ aVisitor->Visit(formControl);
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMNodeList> nodeList = do_QueryInterface(item);
+ if (!nodeList) {
+ return NS_OK;
+ }
+ uint32_t length = 0;
+ nodeList->GetLength(&length);
+ for (uint32_t i = 0; i < length; i++) {
+ nsCOMPtr<nsIDOMNode> node;
+ nodeList->Item(i, getter_AddRefs(node));
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(node);
+ if (formControl && formControl->GetType() == NS_FORM_INPUT_RADIO &&
+ !aVisitor->Visit(formControl)) {
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+void
+HTMLFormElement::AddToRadioGroup(const nsAString& aName,
+ nsIFormControl* aRadio)
+{
+ nsCOMPtr<nsIContent> element = do_QueryInterface(aRadio);
+ NS_ASSERTION(element, "radio controls have to be content elements!");
+
+ if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
+ mRequiredRadioButtonCounts.Put(aName,
+ mRequiredRadioButtonCounts.Get(aName)+1);
+ }
+}
+
+void
+HTMLFormElement::RemoveFromRadioGroup(const nsAString& aName,
+ nsIFormControl* aRadio)
+{
+ nsCOMPtr<nsIContent> element = do_QueryInterface(aRadio);
+ NS_ASSERTION(element, "radio controls have to be content elements!");
+
+ if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
+ uint32_t requiredNb = mRequiredRadioButtonCounts.Get(aName);
+ NS_ASSERTION(requiredNb >= 1,
+ "At least one radio button has to be required!");
+
+ if (requiredNb == 1) {
+ mRequiredRadioButtonCounts.Remove(aName);
+ } else {
+ mRequiredRadioButtonCounts.Put(aName, requiredNb-1);
+ }
+ }
+}
+
+uint32_t
+HTMLFormElement::GetRequiredRadioCount(const nsAString& aName) const
+{
+ return mRequiredRadioButtonCounts.Get(aName);
+}
+
+void
+HTMLFormElement::RadioRequiredWillChange(const nsAString& aName,
+ bool aRequiredAdded)
+{
+ if (aRequiredAdded) {
+ mRequiredRadioButtonCounts.Put(aName,
+ mRequiredRadioButtonCounts.Get(aName)+1);
+ } else {
+ uint32_t requiredNb = mRequiredRadioButtonCounts.Get(aName);
+ NS_ASSERTION(requiredNb >= 1,
+ "At least one radio button has to be required!");
+ if (requiredNb == 1) {
+ mRequiredRadioButtonCounts.Remove(aName);
+ } else {
+ mRequiredRadioButtonCounts.Put(aName, requiredNb-1);
+ }
+ }
+}
+
+bool
+HTMLFormElement::GetValueMissingState(const nsAString& aName) const
+{
+ return mValueMissingRadioGroups.Get(aName);
+}
+
+void
+HTMLFormElement::SetValueMissingState(const nsAString& aName, bool aValue)
+{
+ mValueMissingRadioGroups.Put(aName, aValue);
+}
+
+EventStates
+HTMLFormElement::IntrinsicState() const
+{
+ EventStates state = nsGenericHTMLElement::IntrinsicState();
+
+ if (mInvalidElementsCount) {
+ state |= NS_EVENT_STATE_INVALID;
+ } else {
+ state |= NS_EVENT_STATE_VALID;
+ }
+
+ return state;
+}
+
+void
+HTMLFormElement::Clear()
+{
+ for (int32_t i = mImageElements.Length() - 1; i >= 0; i--) {
+ mImageElements[i]->ClearForm(false);
+ }
+ mImageElements.Clear();
+ mImageNameLookupTable.Clear();
+ mPastNameLookupTable.Clear();
+}
+
+namespace {
+
+struct PositionComparator
+{
+ nsIContent* const mElement;
+ explicit PositionComparator(nsIContent* const aElement) : mElement(aElement) {}
+
+ int operator()(nsIContent* aElement) const {
+ if (mElement == aElement) {
+ return 0;
+ }
+ if (nsContentUtils::PositionIsBefore(mElement, aElement)) {
+ return -1;
+ }
+ return 1;
+ }
+};
+
+struct NodeListAdaptor
+{
+ nsINodeList* const mList;
+ explicit NodeListAdaptor(nsINodeList* aList) : mList(aList) {}
+ nsIContent* operator[](size_t aIdx) const {
+ return mList->Item(aIdx);
+ }
+};
+
+} // namespace
+
+nsresult
+HTMLFormElement::AddElementToTableInternal(
+ nsInterfaceHashtable<nsStringHashKey,nsISupports>& aTable,
+ nsIContent* aChild, const nsAString& aName)
+{
+ nsCOMPtr<nsISupports> supports;
+ aTable.Get(aName, getter_AddRefs(supports));
+
+ if (!supports) {
+ // No entry found, add the element
+ aTable.Put(aName, aChild);
+ ++mExpandoAndGeneration.generation;
+ } else {
+ // Found something in the hash, check its type
+ nsCOMPtr<nsIContent> content = do_QueryInterface(supports);
+
+ if (content) {
+ // Check if the new content is the same as the one we found in the
+ // hash, if it is then we leave it in the hash as it is, this will
+ // happen if a form control has both a name and an id with the same
+ // value
+ if (content == aChild) {
+ return NS_OK;
+ }
+
+ // Found an element, create a list, add the element to the list and put
+ // the list in the hash
+ RadioNodeList *list = new RadioNodeList(this);
+
+ // If an element has a @form, we can assume it *might* be able to not have
+ // a parent and still be in the form.
+ NS_ASSERTION(content->HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
+ content->GetParent(), "Item in list without parent");
+
+ // Determine the ordering between the new and old element.
+ bool newFirst = nsContentUtils::PositionIsBefore(aChild, content);
+
+ list->AppendElement(newFirst ? aChild : content.get());
+ list->AppendElement(newFirst ? content.get() : aChild);
+
+
+ nsCOMPtr<nsISupports> listSupports = do_QueryObject(list);
+
+ // Replace the element with the list.
+ aTable.Put(aName, listSupports);
+ } else {
+ // There's already a list in the hash, add the child to the list
+ nsCOMPtr<nsIDOMNodeList> nodeList = do_QueryInterface(supports);
+ NS_ENSURE_TRUE(nodeList, NS_ERROR_FAILURE);
+
+ // Upcast, uggly, but it works!
+ RadioNodeList *list =
+ static_cast<RadioNodeList*>(nodeList.get());
+
+ NS_ASSERTION(list->Length() > 1,
+ "List should have been converted back to a single element");
+
+ // Fast-path appends; this check is ok even if the child is
+ // already in the list, since if it tests true the child would
+ // have come at the end of the list, and the PositionIsBefore
+ // will test false.
+ if (nsContentUtils::PositionIsBefore(list->Item(list->Length() - 1), aChild)) {
+ list->AppendElement(aChild);
+ return NS_OK;
+ }
+
+ // If a control has a name equal to its id, it could be in the
+ // list already.
+ if (list->IndexOf(aChild) != -1) {
+ return NS_OK;
+ }
+
+ size_t idx;
+ DebugOnly<bool> found = BinarySearchIf(NodeListAdaptor(list), 0, list->Length(),
+ PositionComparator(aChild), &idx);
+ MOZ_ASSERT(!found, "should not have found an element");
+
+ list->InsertElementAt(aChild, idx);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLFormElement::AddImageElement(HTMLImageElement* aChild)
+{
+ AddElementToList(mImageElements, aChild, this);
+ return NS_OK;
+}
+
+nsresult
+HTMLFormElement::AddImageElementToTable(HTMLImageElement* aChild,
+ const nsAString& aName)
+{
+ return AddElementToTableInternal(mImageNameLookupTable, aChild, aName);
+}
+
+nsresult
+HTMLFormElement::RemoveImageElement(HTMLImageElement* aChild)
+{
+ size_t index = mImageElements.IndexOf(aChild);
+ NS_ENSURE_STATE(index != mImageElements.NoIndex);
+
+ mImageElements.RemoveElementAt(index);
+ return NS_OK;
+}
+
+nsresult
+HTMLFormElement::RemoveImageElementFromTable(HTMLImageElement* aElement,
+ const nsAString& aName,
+ RemoveElementReason aRemoveReason)
+{
+ // If the element is being removed from the form, we have to remove it from
+ // the past names map.
+ if (aRemoveReason == ElementRemoved) {
+ for (auto iter = mPastNameLookupTable.Iter(); !iter.Done(); iter.Next()) {
+ if (static_cast<void*>(aElement) == iter.Data()) {
+ iter.Remove();
+ }
+ }
+ }
+
+ return RemoveElementFromTableInternal(mImageNameLookupTable, aElement, aName);
+}
+
+void
+HTMLFormElement::AddToPastNamesMap(const nsAString& aName,
+ nsISupports* aChild)
+{
+ // If candidates contains exactly one node. Add a mapping from name to the
+ // node in candidates in the form element's past names map, replacing the
+ // previous entry with the same name, if any.
+ nsCOMPtr<nsIContent> node = do_QueryInterface(aChild);
+ if (node) {
+ mPastNameLookupTable.Put(aName, aChild);
+ }
+}
+
+JSObject*
+HTMLFormElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLFormElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLFormElement.h b/dom/html/HTMLFormElement.h
new file mode 100644
index 000000000..b3e836f5f
--- /dev/null
+++ b/dom/html/HTMLFormElement.h
@@ -0,0 +1,650 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_HTMLFormElement_h
+#define mozilla_dom_HTMLFormElement_h
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/HTMLFormSubmission.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsIForm.h"
+#include "nsIFormControl.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "nsIWebProgressListener.h"
+#include "nsIRadioGroupContainer.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsThreadUtils.h"
+#include "nsInterfaceHashtable.h"
+#include "nsRefPtrHashtable.h"
+#include "nsDataHashtable.h"
+#include "jsfriendapi.h" // For js::ExpandoAndGeneration
+
+class nsIMutableArray;
+class nsIURI;
+
+namespace mozilla {
+class EventChainPostVisitor;
+class EventChainPreVisitor;
+namespace dom {
+class HTMLFormControlsCollection;
+class HTMLImageElement;
+
+class HTMLFormElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLFormElement,
+ public nsIWebProgressListener,
+ public nsIForm,
+ public nsIRadioGroupContainer
+{
+ friend class HTMLFormControlsCollection;
+
+public:
+ explicit HTMLFormElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ enum {
+ FORM_CONTROL_LIST_HASHTABLE_LENGTH = 8
+ };
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLFormElement
+ NS_DECL_NSIDOMHTMLFORMELEMENT
+
+ // nsIWebProgressListener
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ // nsIForm
+ NS_IMETHOD_(nsIFormControl*) GetElementAt(int32_t aIndex) const override;
+ NS_IMETHOD_(uint32_t) GetElementCount() const override;
+ NS_IMETHOD_(int32_t) IndexOfControl(nsIFormControl* aControl) override;
+ NS_IMETHOD_(nsIFormControl*) GetDefaultSubmitElement() const override;
+
+ // nsIRadioGroupContainer
+ void SetCurrentRadioButton(const nsAString& aName,
+ HTMLInputElement* aRadio) override;
+ HTMLInputElement* GetCurrentRadioButton(const nsAString& aName) override;
+ NS_IMETHOD GetNextRadioButton(const nsAString& aName,
+ const bool aPrevious,
+ HTMLInputElement* aFocusedRadio,
+ HTMLInputElement** aRadioOut) override;
+ NS_IMETHOD WalkRadioGroup(const nsAString& aName, nsIRadioVisitor* aVisitor,
+ bool aFlushContent) override;
+ void AddToRadioGroup(const nsAString& aName, nsIFormControl* aRadio) override;
+ 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;
+
+ virtual EventStates IntrinsicState() const override;
+
+ // EventTarget
+ virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;
+
+ // nsIContent
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) override;
+ virtual nsresult WillHandleEvent(
+ EventChainPostVisitor& aVisitor) override;
+ virtual nsresult PostHandleEvent(
+ EventChainPostVisitor& aVisitor) override;
+
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) 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* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify) override;
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+
+ /**
+ * Forget all information about the current submission (and the fact that we
+ * are currently submitting at all).
+ */
+ void ForgetCurrentSubmission();
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLFormElement,
+ nsGenericHTMLElement)
+
+ /**
+ * Remove an element from this form's list of elements
+ *
+ * @param aElement the element to remove
+ * @param aUpdateValidity If true, updates the form validity.
+ * @return NS_OK if the element was successfully removed.
+ */
+ nsresult RemoveElement(nsGenericHTMLFormElement* aElement,
+ bool aUpdateValidity);
+
+ /**
+ * Remove an element from the lookup table maintained by the form.
+ * We can't fold this method into RemoveElement() because when
+ * RemoveElement() is called it doesn't know if the element is
+ * removed because the id attribute has changed, or bacause the
+ * name attribute has changed.
+ *
+ * @param aElement the element to remove
+ * @param aName the name or id of the element to remove
+ * @param aRemoveReason describe why this element is removed. If the element
+ * is removed because it's removed from the form, it will be removed
+ * from the past names map too, otherwise it will stay in the past
+ * names map.
+ * @return NS_OK if the element was successfully removed.
+ */
+ enum RemoveElementReason {
+ AttributeUpdated,
+ ElementRemoved
+ };
+ nsresult RemoveElementFromTable(nsGenericHTMLFormElement* aElement,
+ const nsAString& aName,
+ RemoveElementReason aRemoveReason);
+
+ /**
+ * Add an element to end of this form's list of elements
+ *
+ * @param aElement the element to add
+ * @param aUpdateValidity If true, the form validity will be updated.
+ * @param aNotify If true, send nsIDocumentObserver notifications as needed.
+ * @return NS_OK if the element was successfully added
+ */
+ nsresult AddElement(nsGenericHTMLFormElement* aElement, bool aUpdateValidity,
+ bool aNotify);
+
+ /**
+ * Add an element to the lookup table maintained by the form.
+ *
+ * We can't fold this method into AddElement() because when
+ * AddElement() is called, the form control has no
+ * attributes. The name or id attributes of the form control
+ * are used as a key into the table.
+ */
+ nsresult AddElementToTable(nsGenericHTMLFormElement* aChild,
+ const nsAString& aName);
+
+ /**
+ * Remove an image element from this form's list of image elements
+ *
+ * @param aElement the image element to remove
+ * @return NS_OK if the element was successfully removed.
+ */
+ nsresult RemoveImageElement(mozilla::dom::HTMLImageElement* aElement);
+
+ /**
+ * Remove an image element from the lookup table maintained by the form.
+ * We can't fold this method into RemoveImageElement() because when
+ * RemoveImageElement() is called it doesn't know if the element is
+ * removed because the id attribute has changed, or because the
+ * name attribute has changed.
+ *
+ * @param aElement the image element to remove
+ * @param aName the name or id of the element to remove
+ * @return NS_OK if the element was successfully removed.
+ */
+ nsresult RemoveImageElementFromTable(mozilla::dom::HTMLImageElement* aElement,
+ const nsAString& aName,
+ RemoveElementReason aRemoveReason);
+ /**
+ * Add an image element to the end of this form's list of image elements
+ *
+ * @param aElement the element to add
+ * @return NS_OK if the element was successfully added
+ */
+ nsresult AddImageElement(mozilla::dom::HTMLImageElement* aElement);
+
+ /**
+ * Add an image element to the lookup table maintained by the form.
+ *
+ * We can't fold this method into AddImageElement() because when
+ * AddImageElement() is called, the image attributes can change.
+ * The name or id attributes of the image are used as a key into the table.
+ */
+ nsresult AddImageElementToTable(mozilla::dom::HTMLImageElement* aChild,
+ const nsAString& aName);
+
+ /**
+ * Returns true if implicit submission of this form is disabled. For more
+ * on implicit submission see:
+ *
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#implicit-submission
+ */
+ bool ImplicitSubmissionIsDisabled() const;
+
+ /**
+ * Check whether a given nsIFormControl is the default submit
+ * element. This is different from just comparing to
+ * GetDefaultSubmitElement() in certain situations inside an update
+ * when GetDefaultSubmitElement() might not be up to date. aControl
+ * is expected to not be null.
+ */
+ bool IsDefaultSubmitElement(const nsIFormControl* aControl) const;
+
+ /**
+ * Flag the form to know that a button or image triggered scripted form
+ * submission. In that case the form will defer the submission until the
+ * script handler returns and the return value is known.
+ */
+ void OnSubmitClickBegin(nsIContent* aOriginatingElement);
+ void OnSubmitClickEnd();
+
+ /**
+ * This method will update the form validity so the submit controls states
+ * will be updated (for -moz-submit-invalid pseudo-class).
+ * This method has to be called by form elements whenever their validity state
+ * or status regarding constraint validation changes.
+ *
+ * @note This method isn't used for CheckValidity().
+ * @note If an element becomes barred from constraint validation, it has to be
+ * considered as valid.
+ *
+ * @param aElementValidityState the new validity state of the element
+ */
+ void UpdateValidity(bool aElementValidityState);
+
+ /**
+ * Returns the form validity based on the last UpdateValidity() call.
+ *
+ * @return Whether the form was valid the last time UpdateValidity() was called.
+ *
+ * @note This method may not return the *current* validity state!
+ */
+ bool GetValidity() const { return !mInvalidElementsCount; }
+
+ /**
+ * This method check the form validity and make invalid form elements send
+ * invalid event if needed.
+ *
+ * @return Whether the form is valid.
+ *
+ * @note Do not call this method if novalidate/formnovalidate is used.
+ * @note This method might disappear with bug 592124, hopefuly.
+ */
+ bool CheckValidFormSubmission();
+
+ /**
+ * Check whether submission can proceed for this form. This basically
+ * implements steps 1-4 (more or less) of
+ * <https://html.spec.whatwg.org/multipage/forms.html#concept-form-submit>.
+ * aSubmitter, if not null, is the "submitter" from that algorithm. Therefore
+ * it must be a valid submit control.
+ */
+ bool SubmissionCanProceed(Element* aSubmitter);
+
+ /**
+ * Walk over the form elements and call SubmitNamesValues() on them to get
+ * their data pumped into the FormSubmitter.
+ *
+ * @param aFormSubmission the form submission object
+ */
+ nsresult WalkFormElements(HTMLFormSubmission* aFormSubmission);
+
+ /**
+ * Whether the submission of this form has been ever prevented because of
+ * being invalid.
+ *
+ * @return Whether the submission of this form has been prevented because of
+ * being invalid.
+ */
+ bool HasEverTriedInvalidSubmit() const { return mEverTriedInvalidSubmit; }
+
+ /**
+ * Implements form[name]. Returns form controls in this form with the correct
+ * value of the name attribute.
+ */
+ already_AddRefed<nsISupports>
+ FindNamedItem(const nsAString& aName, nsWrapperCache** aCache);
+
+ // WebIDL
+
+ void GetAcceptCharset(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::acceptcharset, aValue);
+ }
+
+ void SetAcceptCharset(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::acceptcharset, aValue, aRv);
+ }
+
+ // XPCOM GetAction() is OK
+ void SetAction(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::action, aValue, aRv);
+ }
+
+ // XPCOM GetAutocomplete() is OK
+ void SetAutocomplete(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::autocomplete, aValue, aRv);
+ }
+
+ // XPCOM GetEnctype() is OK
+ void SetEnctype(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::enctype, aValue, aRv);
+ }
+
+ // XPCOM GetEncoding() is OK
+ void SetEncoding(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetEnctype(aValue, aRv);
+ }
+
+ // XPCOM GetMethod() is OK
+ void SetMethod(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::method, aValue, aRv);
+ }
+
+ void GetName(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::name, aValue);
+ }
+
+ void SetName(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::name, aValue, aRv);
+ }
+
+ bool NoValidate() const
+ {
+ return GetBoolAttr(nsGkAtoms::novalidate);
+ }
+
+ void SetNoValidate(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::novalidate, aValue, aRv);
+ }
+
+ void GetTarget(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::target, aValue);
+ }
+
+ void SetTarget(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::target, aValue, aRv);
+ }
+
+ // it's only out-of-line because the class definition is not available in the
+ // header
+ nsIHTMLCollection* Elements();
+
+ int32_t Length();
+
+ void Submit(ErrorResult& aRv);
+
+ // XPCOM Reset() is OK
+
+ bool CheckValidity()
+ {
+ return CheckFormValidity(nullptr);
+ }
+
+ bool ReportValidity()
+ {
+ return CheckValidFormSubmission();
+ }
+
+ Element*
+ IndexedGetter(uint32_t aIndex, bool &aFound);
+
+ already_AddRefed<nsISupports>
+ NamedGetter(const nsAString& aName, bool &aFound);
+
+ void GetSupportedNames(nsTArray<nsString>& aRetval);
+
+ static int32_t
+ CompareFormControlPosition(Element* aElement1, Element* aElement2,
+ const nsIContent* aForm);
+#ifdef DEBUG
+ static void
+ AssertDocumentOrder(const nsTArray<nsGenericHTMLFormElement*>& aControls,
+ nsIContent* aForm);
+#endif
+
+ js::ExpandoAndGeneration mExpandoAndGeneration;
+
+ void RequestAutocomplete();
+
+protected:
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void PostPasswordEvent();
+
+ RefPtr<AsyncEventDispatcher> mFormPasswordEventDispatcher;
+
+ class RemoveElementRunnable;
+ friend class RemoveElementRunnable;
+
+ class RemoveElementRunnable : public Runnable {
+ public:
+ explicit RemoveElementRunnable(HTMLFormElement* aForm)
+ : mForm(aForm)
+ {}
+
+ NS_IMETHOD Run() override {
+ mForm->HandleDefaultSubmitRemoval();
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<HTMLFormElement> mForm;
+ };
+
+ nsresult DoSubmitOrReset(WidgetEvent* aEvent,
+ EventMessage aMessage);
+ nsresult DoReset();
+
+ // Async callback to handle removal of our default submit
+ void HandleDefaultSubmitRemoval();
+
+ //
+ // Submit Helpers
+ //
+ //
+ /**
+ * Attempt to submit (submission might be deferred)
+ * (called by DoSubmitOrReset)
+ *
+ * @param aPresContext the presentation context
+ * @param aEvent the DOM event that was passed to us for the submit
+ */
+ nsresult DoSubmit(WidgetEvent* aEvent);
+
+ /**
+ * Prepare the submission object (called by DoSubmit)
+ *
+ * @param aFormSubmission the submission object
+ * @param aEvent the DOM event that was passed to us for the submit
+ */
+ nsresult BuildSubmission(HTMLFormSubmission** aFormSubmission,
+ WidgetEvent* aEvent);
+ /**
+ * Perform the submission (called by DoSubmit and FlushPendingSubmission)
+ *
+ * @param aFormSubmission the submission object
+ */
+ nsresult SubmitSubmission(HTMLFormSubmission* aFormSubmission);
+
+ /**
+ * Notify any submit observers of the submit.
+ *
+ * @param aActionURL the URL being submitted to
+ * @param aCancelSubmit out param where submit observers can specify that the
+ * submit should be cancelled.
+ */
+ nsresult NotifySubmitObservers(nsIURI* aActionURL, bool* aCancelSubmit,
+ bool aEarlyNotify);
+
+ /**
+ * If this form submission is secure -> insecure, ask the user if they want
+ * to continue.
+ *
+ * @param aActionURL the URL being submitted to
+ * @param aCancelSubmit out param: will be true if the user wants to cancel
+ */
+ nsresult DoSecureToInsecureSubmitCheck(nsIURI* aActionURL,
+ bool* aCancelSubmit);
+
+ /**
+ * Find form controls in this form with the correct value in the name
+ * attribute.
+ */
+ already_AddRefed<nsISupports> DoResolveName(const nsAString& aName, bool aFlushContent);
+
+ /**
+ * Get the full URL to submit to. Do not submit if the returned URL is null.
+ *
+ * @param aActionURL the full, unadulterated URL you'll be submitting to [OUT]
+ * @param aOriginatingElement the originating element of the form submission [IN]
+ */
+ nsresult GetActionURL(nsIURI** aActionURL, nsIContent* aOriginatingElement);
+
+ /**
+ * Check the form validity following this algorithm:
+ * http://www.whatwg.org/specs/web-apps/current-work/#statically-validate-the-constraints
+ *
+ * @param aInvalidElements [out] parameter containing the list of unhandled
+ * invalid controls.
+ *
+ * @return Whether the form is currently valid.
+ */
+ bool CheckFormValidity(nsIMutableArray* aInvalidElements) const;
+
+ // Clear the mImageNameLookupTable and mImageElements.
+ void Clear();
+
+ // Insert a element into the past names map.
+ void AddToPastNamesMap(const nsAString& aName, nsISupports* aChild);
+
+ nsresult
+ AddElementToTableInternal(
+ nsInterfaceHashtable<nsStringHashKey,nsISupports>& aTable,
+ nsIContent* aChild, const nsAString& aName);
+
+ nsresult
+ RemoveElementFromTableInternal(
+ nsInterfaceHashtable<nsStringHashKey,nsISupports>& aTable,
+ nsIContent* aChild, const nsAString& aName);
+
+public:
+ /**
+ * Flush a possible pending submission. If there was a scripted submission
+ * triggered by a button or image, the submission was defered. This method
+ * forces the pending submission to be submitted. (happens when the handler
+ * returns false or there is an action/target change in the script)
+ */
+ void FlushPendingSubmission();
+protected:
+
+ //
+ // Data members
+ //
+ /** The list of controls (form.elements as well as stuff not in elements) */
+ RefPtr<HTMLFormControlsCollection> mControls;
+ /** The currently selected radio button of each group */
+ nsRefPtrHashtable<nsStringCaseInsensitiveHashKey, HTMLInputElement> mSelectedRadioButtons;
+ /** The number of required radio button of each group */
+ nsDataHashtable<nsStringCaseInsensitiveHashKey,uint32_t> mRequiredRadioButtonCounts;
+ /** The value missing state of each group */
+ nsDataHashtable<nsStringCaseInsensitiveHashKey,bool> mValueMissingRadioGroups;
+ /** Whether we are currently processing a submit event or not */
+ bool mGeneratingSubmit;
+ /** Whether we are currently processing a reset event or not */
+ bool mGeneratingReset;
+ /** Whether we are submitting currently */
+ bool mIsSubmitting;
+ /** Whether the submission is to be deferred in case a script triggers it */
+ bool mDeferSubmission;
+ /** Whether we notified NS_FORMSUBMIT_SUBJECT listeners already */
+ bool mNotifiedObservers;
+ /** If we notified the listeners early, what was the result? */
+ bool mNotifiedObserversResult;
+ /** Keep track of what the popup state was when the submit was initiated */
+ PopupControlState mSubmitPopupState;
+ /** Keep track of whether a submission was user-initiated or not */
+ bool mSubmitInitiatedFromUserInput;
+
+ /** The pending submission object */
+ nsAutoPtr<HTMLFormSubmission> mPendingSubmission;
+ /** The request currently being submitted */
+ nsCOMPtr<nsIRequest> mSubmittingRequest;
+ /** The web progress object we are currently listening to */
+ nsWeakPtr mWebProgress;
+
+ /** The default submit element -- WEAK */
+ nsGenericHTMLFormElement* mDefaultSubmitElement;
+
+ /** The first submit element in mElements -- WEAK */
+ nsGenericHTMLFormElement* mFirstSubmitInElements;
+
+ /** The first submit element in mNotInElements -- WEAK */
+ nsGenericHTMLFormElement* mFirstSubmitNotInElements;
+
+ // This array holds on to all HTMLImageElement(s).
+ // This is needed to properly clean up the bi-directional references
+ // (both weak and strong) between the form and its HTMLImageElements.
+
+ nsTArray<mozilla::dom::HTMLImageElement*> mImageElements; // Holds WEAK references
+
+ // A map from an ID or NAME attribute to the HTMLImageElement(s), this
+ // hash holds strong references either to the named HTMLImageElement, or
+ // to a list of named HTMLImageElement(s), in the case where this hash
+ // holds on to a list of named HTMLImageElement(s) the list has weak
+ // references to the HTMLImageElement.
+
+ nsInterfaceHashtable<nsStringHashKey,nsISupports> mImageNameLookupTable;
+
+ // A map from names to elements that were gotten by those names from this
+ // form in that past. See "past names map" in the HTML5 specification.
+
+ nsInterfaceHashtable<nsStringHashKey,nsISupports> mPastNameLookupTable;
+
+ /**
+ * Number of invalid and candidate for constraint validation elements in the
+ * form the last time UpdateValidity has been called.
+ * @note Should only be used by UpdateValidity() and GetValidity()!
+ */
+ int32_t mInvalidElementsCount;
+
+ /**
+ * Whether the submission of this form has been ever prevented because of
+ * being invalid.
+ */
+ bool mEverTriedInvalidSubmit;
+
+protected:
+ /** Detection of first form to notify observers */
+ static bool gFirstFormSubmitted;
+ /** Detection of first password input to initialize the password manager */
+ static bool gPasswordManagerInitialized;
+
+private:
+ ~HTMLFormElement();
+};
+
+} // namespace dom
+
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLFormElement_h
diff --git a/dom/html/HTMLFormSubmission.cpp b/dom/html/HTMLFormSubmission.cpp
new file mode 100644
index 000000000..d6269a7ca
--- /dev/null
+++ b/dom/html/HTMLFormSubmission.cpp
@@ -0,0 +1,980 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HTMLFormSubmission.h"
+
+#include "nsCOMPtr.h"
+#include "nsIForm.h"
+#include "nsILinkHandler.h"
+#include "nsIDocument.h"
+#include "nsGkAtoms.h"
+#include "nsIFormControl.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "nsError.h"
+#include "nsGenericHTMLElement.h"
+#include "nsAttrValueInlines.h"
+#include "nsISaveAsCharset.h"
+#include "nsIFile.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsStringStream.h"
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "nsLinebreakConverter.h"
+#include "nsEscape.h"
+#include "nsUnicharUtils.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsIMIMEInputStream.h"
+#include "nsIMIMEService.h"
+#include "nsIConsoleService.h"
+#include "nsIScriptError.h"
+#include "nsIStringBundle.h"
+#include "nsCExternalHandlerService.h"
+#include "nsIFileStreams.h"
+#include "nsContentUtils.h"
+
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/File.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+void
+SendJSWarning(nsIDocument* aDocument,
+ const char* aWarningName,
+ const char16_t** aWarningArgs, uint32_t aWarningArgsLen)
+{
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("HTML"), aDocument,
+ nsContentUtils::eFORMS_PROPERTIES,
+ aWarningName,
+ aWarningArgs, aWarningArgsLen);
+}
+
+void
+RetrieveFileName(Blob* aBlob, nsAString& aFilename)
+{
+ if (!aBlob) {
+ return;
+ }
+
+ RefPtr<File> file = aBlob->ToFile();
+ if (file) {
+ file->GetName(aFilename);
+ }
+}
+
+void
+RetrieveDirectoryName(Directory* aDirectory, nsAString& aDirname)
+{
+ MOZ_ASSERT(aDirectory);
+
+ ErrorResult rv;
+ aDirectory->GetName(aDirname, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ aDirname.Truncate();
+ }
+}
+
+// --------------------------------------------------------------------------
+
+class FSURLEncoded : public EncodingFormSubmission
+{
+public:
+ /**
+ * @param aCharset the charset of the form as a string
+ * @param aMethod the method of the submit (either NS_FORM_METHOD_GET or
+ * NS_FORM_METHOD_POST).
+ */
+ FSURLEncoded(const nsACString& aCharset,
+ int32_t aMethod,
+ nsIDocument* aDocument,
+ nsIContent* aOriginatingElement)
+ : EncodingFormSubmission(aCharset, aOriginatingElement),
+ mMethod(aMethod),
+ mDocument(aDocument),
+ mWarnedFileControl(false)
+ {
+ }
+
+ virtual nsresult
+ AddNameValuePair(const nsAString& aName, const nsAString& aValue) override;
+
+ virtual nsresult
+ AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override;
+
+ virtual nsresult
+ AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override;
+
+ virtual nsresult
+ GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
+
+ virtual bool SupportsIsindexSubmission() override
+ {
+ return true;
+ }
+
+ virtual nsresult AddIsindex(const nsAString& aValue) override;
+
+protected:
+
+ /**
+ * URL encode a Unicode string by encoding it to bytes, converting linebreaks
+ * properly, and then escaping many bytes as %xx.
+ *
+ * @param aStr the string to encode
+ * @param aEncoded the encoded string [OUT]
+ * @throws NS_ERROR_OUT_OF_MEMORY if we run out of memory
+ */
+ nsresult URLEncode(const nsAString& aStr, nsACString& aEncoded);
+
+private:
+ /**
+ * The method of the submit (either NS_FORM_METHOD_GET or
+ * NS_FORM_METHOD_POST).
+ */
+ int32_t mMethod;
+
+ /** The query string so far (the part after the ?) */
+ nsCString mQueryString;
+
+ /** The document whose URI to use when reporting errors */
+ nsCOMPtr<nsIDocument> mDocument;
+
+ /** Whether or not we have warned about a file control not being submitted */
+ bool mWarnedFileControl;
+};
+
+nsresult
+FSURLEncoded::AddNameValuePair(const nsAString& aName,
+ const nsAString& aValue)
+{
+ // Encode value
+ nsCString convValue;
+ nsresult rv = URLEncode(aValue, convValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Encode name
+ nsAutoCString convName;
+ rv = URLEncode(aName, convName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+
+ // Append data to string
+ if (mQueryString.IsEmpty()) {
+ mQueryString += convName + NS_LITERAL_CSTRING("=") + convValue;
+ } else {
+ mQueryString += NS_LITERAL_CSTRING("&") + convName
+ + NS_LITERAL_CSTRING("=") + convValue;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FSURLEncoded::AddIsindex(const nsAString& aValue)
+{
+ // Encode value
+ nsCString convValue;
+ nsresult rv = URLEncode(aValue, convValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Append data to string
+ if (mQueryString.IsEmpty()) {
+ mQueryString.Assign(convValue);
+ } else {
+ mQueryString += NS_LITERAL_CSTRING("&isindex=") + convValue;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FSURLEncoded::AddNameBlobOrNullPair(const nsAString& aName,
+ Blob* aBlob)
+{
+ if (!mWarnedFileControl) {
+ SendJSWarning(mDocument, "ForgotFileEnctypeWarning", nullptr, 0);
+ mWarnedFileControl = true;
+ }
+
+ nsAutoString filename;
+ RetrieveFileName(aBlob, filename);
+ return AddNameValuePair(aName, filename);
+}
+
+nsresult
+FSURLEncoded::AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory)
+{
+ // No warning about because Directory objects are never sent via form.
+
+ nsAutoString dirname;
+ RetrieveDirectoryName(aDirectory, dirname);
+ return AddNameValuePair(aName, dirname);
+}
+
+void
+HandleMailtoSubject(nsCString& aPath)
+{
+ // Walk through the string and see if we have a subject already.
+ bool hasSubject = false;
+ bool hasParams = false;
+ int32_t paramSep = aPath.FindChar('?');
+ while (paramSep != kNotFound && paramSep < (int32_t)aPath.Length()) {
+ hasParams = true;
+
+ // Get the end of the name at the = op. If it is *after* the next &,
+ // assume that someone made a parameter without an = in it
+ int32_t nameEnd = aPath.FindChar('=', paramSep+1);
+ int32_t nextParamSep = aPath.FindChar('&', paramSep+1);
+ if (nextParamSep == kNotFound) {
+ nextParamSep = aPath.Length();
+ }
+
+ // If the = op is after the &, this parameter is a name without value.
+ // If there is no = op, same thing.
+ if (nameEnd == kNotFound || nextParamSep < nameEnd) {
+ nameEnd = nextParamSep;
+ }
+
+ if (nameEnd != kNotFound) {
+ if (Substring(aPath, paramSep+1, nameEnd-(paramSep+1)).
+ LowerCaseEqualsLiteral("subject")) {
+ hasSubject = true;
+ break;
+ }
+ }
+
+ paramSep = nextParamSep;
+ }
+
+ // If there is no subject, append a preformed subject to the mailto line
+ if (!hasSubject) {
+ if (hasParams) {
+ aPath.Append('&');
+ } else {
+ aPath.Append('?');
+ }
+
+ // Get the default subject
+ nsXPIDLString brandName;
+ nsresult rv =
+ nsContentUtils::GetLocalizedString(nsContentUtils::eBRAND_PROPERTIES,
+ "brandShortName", brandName);
+ if (NS_FAILED(rv))
+ return;
+ const char16_t *formatStrings[] = { brandName.get() };
+ nsXPIDLString subjectStr;
+ rv = nsContentUtils::FormatLocalizedString(
+ nsContentUtils::eFORMS_PROPERTIES,
+ "DefaultFormSubject",
+ formatStrings,
+ subjectStr);
+ if (NS_FAILED(rv))
+ return;
+ aPath.AppendLiteral("subject=");
+ nsCString subjectStrEscaped;
+ rv = NS_EscapeURL(NS_ConvertUTF16toUTF8(subjectStr), esc_Query,
+ subjectStrEscaped, mozilla::fallible);
+ if (NS_FAILED(rv))
+ return;
+
+ aPath.Append(subjectStrEscaped);
+ }
+}
+
+nsresult
+FSURLEncoded::GetEncodedSubmission(nsIURI* aURI,
+ nsIInputStream** aPostDataStream)
+{
+ nsresult rv = NS_OK;
+
+ *aPostDataStream = nullptr;
+
+ if (mMethod == NS_FORM_METHOD_POST) {
+
+ bool isMailto = false;
+ aURI->SchemeIs("mailto", &isMailto);
+ if (isMailto) {
+
+ nsAutoCString path;
+ rv = aURI->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ HandleMailtoSubject(path);
+
+ // Append the body to and force-plain-text args to the mailto line
+ nsAutoCString escapedBody;
+ if (NS_WARN_IF(!NS_Escape(mQueryString, escapedBody, url_XAlphas))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ path += NS_LITERAL_CSTRING("&force-plain-text=Y&body=") + escapedBody;
+
+ rv = aURI->SetPath(path);
+
+ } else {
+
+ nsCOMPtr<nsIInputStream> dataStream;
+ // XXX We *really* need to either get the string to disown its data (and
+ // not destroy it), or make a string input stream that owns the CString
+ // that is passed to it. Right now this operation does a copy.
+ rv = NS_NewCStringInputStream(getter_AddRefs(dataStream), mQueryString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMIMEInputStream> mimeStream(
+ do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef SPECIFY_CHARSET_IN_CONTENT_TYPE
+ mimeStream->AddHeader("Content-Type",
+ PromiseFlatString(
+ "application/x-www-form-urlencoded; charset="
+ + mCharset
+ ).get());
+#else
+ mimeStream->AddHeader("Content-Type",
+ "application/x-www-form-urlencoded");
+#endif
+ mimeStream->SetAddContentLength(true);
+ mimeStream->SetData(dataStream);
+
+ *aPostDataStream = mimeStream;
+ NS_ADDREF(*aPostDataStream);
+ }
+
+ } else {
+ // Get the full query string
+ bool schemeIsJavaScript;
+ rv = aURI->SchemeIs("javascript", &schemeIsJavaScript);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (schemeIsJavaScript) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
+ if (url) {
+ url->SetQuery(mQueryString);
+ }
+ else {
+ nsAutoCString path;
+ rv = aURI->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Bug 42616: Trim off named anchor and save it to add later
+ int32_t namedAnchorPos = path.FindChar('#');
+ nsAutoCString namedAnchor;
+ if (kNotFound != namedAnchorPos) {
+ path.Right(namedAnchor, (path.Length() - namedAnchorPos));
+ path.Truncate(namedAnchorPos);
+ }
+
+ // Chop off old query string (bug 25330, 57333)
+ // Only do this for GET not POST (bug 41585)
+ int32_t queryStart = path.FindChar('?');
+ if (kNotFound != queryStart) {
+ path.Truncate(queryStart);
+ }
+
+ path.Append('?');
+ // Bug 42616: Add named anchor to end after query string
+ path.Append(mQueryString + namedAnchor);
+
+ aURI->SetPath(path);
+ }
+ }
+
+ return rv;
+}
+
+// i18n helper routines
+nsresult
+FSURLEncoded::URLEncode(const nsAString& aStr, nsACString& aEncoded)
+{
+ // convert to CRLF breaks
+ int32_t convertedBufLength = 0;
+ char16_t* convertedBuf =
+ nsLinebreakConverter::ConvertUnicharLineBreaks(aStr.BeginReading(),
+ nsLinebreakConverter::eLinebreakAny,
+ nsLinebreakConverter::eLinebreakNet,
+ aStr.Length(),
+ &convertedBufLength);
+ NS_ENSURE_TRUE(convertedBuf, NS_ERROR_OUT_OF_MEMORY);
+
+ nsAutoString convertedString;
+ convertedString.Adopt(convertedBuf, convertedBufLength);
+
+ nsAutoCString encodedBuf;
+ nsresult rv = EncodeVal(convertedString, encodedBuf, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (NS_WARN_IF(!NS_Escape(encodedBuf, aEncoded, url_XPAlphas))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+} // anonymous namespace
+
+// --------------------------------------------------------------------------
+
+FSMultipartFormData::FSMultipartFormData(const nsACString& aCharset,
+ nsIContent* aOriginatingElement)
+ : EncodingFormSubmission(aCharset, aOriginatingElement)
+{
+ mPostDataStream =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+ mTotalLength = 0;
+
+ mBoundary.AssignLiteral("---------------------------");
+ mBoundary.AppendInt(rand());
+ mBoundary.AppendInt(rand());
+ mBoundary.AppendInt(rand());
+}
+
+FSMultipartFormData::~FSMultipartFormData()
+{
+ NS_ASSERTION(mPostDataChunk.IsEmpty(), "Left unsubmitted data");
+}
+
+nsIInputStream*
+FSMultipartFormData::GetSubmissionBody(uint64_t* aContentLength)
+{
+ // Finish data
+ mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary
+ + NS_LITERAL_CSTRING("--" CRLF);
+
+ // Add final data input stream
+ AddPostDataStream();
+
+ *aContentLength = mTotalLength;
+ return mPostDataStream;
+}
+
+nsresult
+FSMultipartFormData::AddNameValuePair(const nsAString& aName,
+ const nsAString& aValue)
+{
+ nsCString valueStr;
+ nsAutoCString encodedVal;
+ nsresult rv = EncodeVal(aValue, encodedVal, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ valueStr.Adopt(nsLinebreakConverter::
+ ConvertLineBreaks(encodedVal.get(),
+ nsLinebreakConverter::eLinebreakAny,
+ nsLinebreakConverter::eLinebreakNet));
+
+ nsAutoCString nameStr;
+ rv = EncodeVal(aName, nameStr, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make MIME block for name/value pair
+
+ // XXX: name parameter should be encoded per RFC 2231
+ // RFC 2388 specifies that RFC 2047 be used, but I think it's not
+ // consistent with MIME standard.
+ mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary
+ + NS_LITERAL_CSTRING(CRLF)
+ + NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"")
+ + nameStr + NS_LITERAL_CSTRING("\"" CRLF CRLF)
+ + valueStr + NS_LITERAL_CSTRING(CRLF);
+
+ return NS_OK;
+}
+
+nsresult
+FSMultipartFormData::AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob)
+{
+ // Encode the control name
+ nsAutoCString nameStr;
+ nsresult rv = EncodeVal(aName, nameStr, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ErrorResult error;
+
+ uint64_t size = 0;
+ nsAutoCString filename;
+ nsAutoCString contentType;
+ nsCOMPtr<nsIInputStream> fileStream;
+
+ if (aBlob) {
+ nsAutoString filename16;
+
+ RefPtr<File> file = aBlob->ToFile();
+ if (file) {
+ nsAutoString relativePath;
+ file->GetRelativePath(relativePath);
+ if (Directory::WebkitBlinkDirectoryPickerEnabled(nullptr, nullptr) &&
+ !relativePath.IsEmpty()) {
+ filename16 = relativePath;
+ }
+
+ if (filename16.IsEmpty()) {
+ RetrieveFileName(aBlob, filename16);
+ }
+ }
+
+ rv = EncodeVal(filename16, filename, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get content type
+ nsAutoString contentType16;
+ aBlob->GetType(contentType16);
+ if (contentType16.IsEmpty()) {
+ contentType16.AssignLiteral("application/octet-stream");
+ }
+
+ contentType.Adopt(nsLinebreakConverter::
+ ConvertLineBreaks(NS_ConvertUTF16toUTF8(contentType16).get(),
+ nsLinebreakConverter::eLinebreakAny,
+ nsLinebreakConverter::eLinebreakSpace));
+
+ // Get input stream
+ aBlob->GetInternalStream(getter_AddRefs(fileStream), error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+
+ // Get size
+ size = aBlob->GetSize(error);
+ if (error.Failed()) {
+ error.SuppressException();
+ fileStream = nullptr;
+ }
+
+ if (fileStream) {
+ // Create buffered stream (for efficiency)
+ nsCOMPtr<nsIInputStream> bufferedStream;
+ rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
+ fileStream, 8192);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ fileStream = bufferedStream;
+ }
+ } else {
+ contentType.AssignLiteral("application/octet-stream");
+ }
+
+ AddDataChunk(nameStr, filename, contentType, fileStream, size);
+ return NS_OK;
+}
+
+nsresult
+FSMultipartFormData::AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory)
+{
+ if (!Directory::WebkitBlinkDirectoryPickerEnabled(nullptr, nullptr)) {
+ return NS_OK;
+ }
+
+ // Encode the control name
+ nsAutoCString nameStr;
+ nsresult rv = EncodeVal(aName, nameStr, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString dirname;
+ nsAutoString dirname16;
+
+ ErrorResult error;
+ nsAutoString path;
+ aDirectory->GetPath(path, error);
+ if (NS_WARN_IF(error.Failed())) {
+ error.SuppressException();
+ } else {
+ dirname16 = path;
+ }
+
+ if (dirname16.IsEmpty()) {
+ RetrieveDirectoryName(aDirectory, dirname16);
+ }
+
+ rv = EncodeVal(dirname16, dirname, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AddDataChunk(nameStr, dirname,
+ NS_LITERAL_CSTRING("application/octet-stream"),
+ nullptr, 0);
+ return NS_OK;
+}
+
+void
+FSMultipartFormData::AddDataChunk(const nsACString& aName,
+ const nsACString& aFilename,
+ const nsACString& aContentType,
+ nsIInputStream* aInputStream,
+ uint64_t aInputStreamSize)
+{
+ //
+ // Make MIME block for name/value pair
+ //
+ // more appropriate than always using binary?
+ mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary
+ + NS_LITERAL_CSTRING(CRLF);
+ // XXX: name/filename parameter should be encoded per RFC 2231
+ // RFC 2388 specifies that RFC 2047 be used, but I think it's not
+ // consistent with the MIME standard.
+ mPostDataChunk +=
+ NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"")
+ + aName + NS_LITERAL_CSTRING("\"; filename=\"")
+ + aFilename + NS_LITERAL_CSTRING("\"" CRLF)
+ + NS_LITERAL_CSTRING("Content-Type: ")
+ + aContentType + NS_LITERAL_CSTRING(CRLF CRLF);
+
+ // We should not try to append an invalid stream. That will happen for example
+ // if we try to update a file that actually do not exist.
+ if (aInputStream) {
+ // We need to dump the data up to this point into the POST data stream
+ // here, since we're about to add the file input stream
+ AddPostDataStream();
+
+ mPostDataStream->AppendStream(aInputStream);
+ mTotalLength += aInputStreamSize;
+ }
+
+ // CRLF after file
+ mPostDataChunk.AppendLiteral(CRLF);
+}
+
+nsresult
+FSMultipartFormData::GetEncodedSubmission(nsIURI* aURI,
+ nsIInputStream** aPostDataStream)
+{
+ nsresult rv;
+
+ // Make header
+ nsCOMPtr<nsIMIMEInputStream> mimeStream
+ = do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString contentType;
+ GetContentType(contentType);
+ mimeStream->AddHeader("Content-Type", contentType.get());
+ mimeStream->SetAddContentLength(true);
+ uint64_t unused;
+ mimeStream->SetData(GetSubmissionBody(&unused));
+
+ mimeStream.forget(aPostDataStream);
+
+ return NS_OK;
+}
+
+nsresult
+FSMultipartFormData::AddPostDataStream()
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIInputStream> postDataChunkStream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(postDataChunkStream),
+ mPostDataChunk);
+ NS_ASSERTION(postDataChunkStream, "Could not open a stream for POST!");
+ if (postDataChunkStream) {
+ mPostDataStream->AppendStream(postDataChunkStream);
+ mTotalLength += mPostDataChunk.Length();
+ }
+
+ mPostDataChunk.Truncate();
+
+ return rv;
+}
+
+// --------------------------------------------------------------------------
+
+namespace {
+
+class FSTextPlain : public EncodingFormSubmission
+{
+public:
+ FSTextPlain(const nsACString& aCharset, nsIContent* aOriginatingElement)
+ : EncodingFormSubmission(aCharset, aOriginatingElement)
+ {
+ }
+
+ virtual nsresult
+ AddNameValuePair(const nsAString& aName, const nsAString& aValue) override;
+
+ virtual nsresult
+ AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override;
+
+ virtual nsresult
+ AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override;
+
+ virtual nsresult
+ GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
+
+private:
+ nsString mBody;
+};
+
+nsresult
+FSTextPlain::AddNameValuePair(const nsAString& aName, const nsAString& aValue)
+{
+ // XXX This won't work well with a name like "a=b" or "a\nb" but I suppose
+ // text/plain doesn't care about that. Parsers aren't built for escaped
+ // values so we'll have to live with it.
+ mBody.Append(aName + NS_LITERAL_STRING("=") + aValue +
+ NS_LITERAL_STRING(CRLF));
+
+ return NS_OK;
+}
+
+nsresult
+FSTextPlain::AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob)
+{
+ nsAutoString filename;
+ RetrieveFileName(aBlob, filename);
+ AddNameValuePair(aName, filename);
+ return NS_OK;
+}
+
+nsresult
+FSTextPlain::AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory)
+{
+ nsAutoString dirname;
+ RetrieveDirectoryName(aDirectory, dirname);
+ AddNameValuePair(aName, dirname);
+ return NS_OK;
+}
+
+nsresult
+FSTextPlain::GetEncodedSubmission(nsIURI* aURI,
+ nsIInputStream** aPostDataStream)
+{
+ nsresult rv = NS_OK;
+
+ // XXX HACK We are using the standard URL mechanism to give the body to the
+ // mailer instead of passing the post data stream to it, since that sounds
+ // hard.
+ bool isMailto = false;
+ aURI->SchemeIs("mailto", &isMailto);
+ if (isMailto) {
+ nsAutoCString path;
+ rv = aURI->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ HandleMailtoSubject(path);
+
+ // Append the body to and force-plain-text args to the mailto line
+ nsAutoCString escapedBody;
+ if (NS_WARN_IF(!NS_Escape(NS_ConvertUTF16toUTF8(mBody), escapedBody,
+ url_XAlphas))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ path += NS_LITERAL_CSTRING("&force-plain-text=Y&body=") + escapedBody;
+
+ rv = aURI->SetPath(path);
+
+ } else {
+ // Create data stream.
+ // We do want to send the data through the charset encoder and we want to
+ // normalize linebreaks to use the "standard net" format (\r\n), but we
+ // don't want to perform any other encoding. This means that names and
+ // values which contains '=' or newlines are potentially ambigiously
+ // encoded, but that how text/plain is specced.
+ nsCString cbody;
+ EncodeVal(mBody, cbody, false);
+ cbody.Adopt(nsLinebreakConverter::
+ ConvertLineBreaks(cbody.get(),
+ nsLinebreakConverter::eLinebreakAny,
+ nsLinebreakConverter::eLinebreakNet));
+ nsCOMPtr<nsIInputStream> bodyStream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(bodyStream), cbody);
+ if (!bodyStream) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Create mime stream with headers and such
+ nsCOMPtr<nsIMIMEInputStream> mimeStream
+ = do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mimeStream->AddHeader("Content-Type", "text/plain");
+ mimeStream->SetAddContentLength(true);
+ mimeStream->SetData(bodyStream);
+ CallQueryInterface(mimeStream, aPostDataStream);
+ }
+
+ return rv;
+}
+
+} // anonymous namespace
+
+// --------------------------------------------------------------------------
+
+EncodingFormSubmission::EncodingFormSubmission(const nsACString& aCharset,
+ nsIContent* aOriginatingElement)
+ : HTMLFormSubmission(aCharset, aOriginatingElement)
+ , mEncoder(aCharset)
+{
+ if (!(aCharset.EqualsLiteral("UTF-8") || aCharset.EqualsLiteral("gb18030"))) {
+ NS_ConvertUTF8toUTF16 charsetUtf16(aCharset);
+ const char16_t* charsetPtr = charsetUtf16.get();
+ SendJSWarning(aOriginatingElement ? aOriginatingElement->GetOwnerDocument()
+ : nullptr,
+ "CannotEncodeAllUnicode",
+ &charsetPtr,
+ 1);
+ }
+}
+
+EncodingFormSubmission::~EncodingFormSubmission()
+{
+}
+
+// i18n helper routines
+nsresult
+EncodingFormSubmission::EncodeVal(const nsAString& aStr, nsCString& aOut,
+ bool aHeaderEncode)
+{
+ if (!mEncoder.Encode(aStr, aOut)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (aHeaderEncode) {
+ aOut.Adopt(nsLinebreakConverter::
+ ConvertLineBreaks(aOut.get(),
+ nsLinebreakConverter::eLinebreakAny,
+ nsLinebreakConverter::eLinebreakSpace));
+ aOut.ReplaceSubstring(NS_LITERAL_CSTRING("\""),
+ NS_LITERAL_CSTRING("\\\""));
+ }
+
+
+ return NS_OK;
+}
+
+// --------------------------------------------------------------------------
+
+namespace {
+
+void
+GetSubmitCharset(nsGenericHTMLElement* aForm,
+ nsACString& oCharset)
+{
+ oCharset.AssignLiteral("UTF-8"); // default to utf-8
+
+ nsAutoString acceptCharsetValue;
+ aForm->GetAttr(kNameSpaceID_None, nsGkAtoms::acceptcharset,
+ acceptCharsetValue);
+
+ int32_t charsetLen = acceptCharsetValue.Length();
+ if (charsetLen > 0) {
+ int32_t offset=0;
+ int32_t spPos=0;
+ // get charset from charsets one by one
+ do {
+ spPos = acceptCharsetValue.FindChar(char16_t(' '), offset);
+ int32_t cnt = ((-1==spPos)?(charsetLen-offset):(spPos-offset));
+ if (cnt > 0) {
+ nsAutoString uCharset;
+ acceptCharsetValue.Mid(uCharset, offset, cnt);
+
+ if (EncodingUtils::FindEncodingForLabelNoReplacement(uCharset, oCharset))
+ return;
+ }
+ offset = spPos + 1;
+ } while (spPos != -1);
+ }
+ // if there are no accept-charset or all the charset are not supported
+ // Get the charset from document
+ nsIDocument* doc = aForm->GetComposedDoc();
+ if (doc) {
+ oCharset = doc->GetDocumentCharacterSet();
+ }
+}
+
+void
+GetEnumAttr(nsGenericHTMLElement* aContent,
+ nsIAtom* atom, int32_t* aValue)
+{
+ const nsAttrValue* value = aContent->GetParsedAttr(atom);
+ if (value && value->Type() == nsAttrValue::eEnum) {
+ *aValue = value->GetEnumValue();
+ }
+}
+
+} // anonymous namespace
+
+/* static */ nsresult
+HTMLFormSubmission::GetFromForm(nsGenericHTMLElement* aForm,
+ nsGenericHTMLElement* aOriginatingElement,
+ HTMLFormSubmission** aFormSubmission)
+{
+ // Get all the information necessary to encode the form data
+ NS_ASSERTION(aForm->GetComposedDoc(),
+ "Should have doc if we're building submission!");
+
+ // Get encoding type (default: urlencoded)
+ int32_t enctype = NS_FORM_ENCTYPE_URLENCODED;
+ if (aOriginatingElement &&
+ aOriginatingElement->HasAttr(kNameSpaceID_None, nsGkAtoms::formenctype)) {
+ GetEnumAttr(aOriginatingElement, nsGkAtoms::formenctype, &enctype);
+ } else {
+ GetEnumAttr(aForm, nsGkAtoms::enctype, &enctype);
+ }
+
+ // Get method (default: GET)
+ int32_t method = NS_FORM_METHOD_GET;
+ if (aOriginatingElement &&
+ aOriginatingElement->HasAttr(kNameSpaceID_None, nsGkAtoms::formmethod)) {
+ GetEnumAttr(aOriginatingElement, nsGkAtoms::formmethod, &method);
+ } else {
+ GetEnumAttr(aForm, nsGkAtoms::method, &method);
+ }
+
+ // Get charset
+ nsAutoCString charset;
+ GetSubmitCharset(aForm, charset);
+
+ // We now have a canonical charset name, so we only have to check it
+ // against canonical names.
+
+ // use UTF-8 for UTF-16* (per WHATWG and existing practice of
+ // MS IE/Opera).
+ if (StringBeginsWith(charset, NS_LITERAL_CSTRING("UTF-16"))) {
+ charset.AssignLiteral("UTF-8");
+ }
+
+ // Choose encoder
+ if (method == NS_FORM_METHOD_POST &&
+ enctype == NS_FORM_ENCTYPE_MULTIPART) {
+ *aFormSubmission = new FSMultipartFormData(charset, aOriginatingElement);
+ } else if (method == NS_FORM_METHOD_POST &&
+ enctype == NS_FORM_ENCTYPE_TEXTPLAIN) {
+ *aFormSubmission = new FSTextPlain(charset, aOriginatingElement);
+ } else {
+ nsIDocument* doc = aForm->OwnerDoc();
+ if (enctype == NS_FORM_ENCTYPE_MULTIPART ||
+ enctype == NS_FORM_ENCTYPE_TEXTPLAIN) {
+ nsAutoString enctypeStr;
+ if (aOriginatingElement &&
+ aOriginatingElement->HasAttr(kNameSpaceID_None,
+ nsGkAtoms::formenctype)) {
+ aOriginatingElement->GetAttr(kNameSpaceID_None, nsGkAtoms::formenctype,
+ enctypeStr);
+ } else {
+ aForm->GetAttr(kNameSpaceID_None, nsGkAtoms::enctype, enctypeStr);
+ }
+ const char16_t* enctypeStrPtr = enctypeStr.get();
+ SendJSWarning(doc, "ForgotPostWarning",
+ &enctypeStrPtr, 1);
+ }
+ *aFormSubmission = new FSURLEncoded(charset, method, doc,
+ aOriginatingElement);
+ }
+
+ return NS_OK;
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/html/HTMLFormSubmission.h b/dom/html/HTMLFormSubmission.h
new file mode 100644
index 000000000..2a07143c7
--- /dev/null
+++ b/dom/html/HTMLFormSubmission.h
@@ -0,0 +1,251 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLFormSubmission_h
+#define mozilla_dom_HTMLFormSubmission_h
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+#include "nsNCRFallbackEncoderWrapper.h"
+#include "nsString.h"
+
+class nsIURI;
+class nsIInputStream;
+class nsGenericHTMLElement;
+class nsIMultiplexInputStream;
+
+namespace mozilla {
+namespace dom {
+
+class Blob;
+class Directory;
+
+/**
+ * Class for form submissions; encompasses the function to call to submit as
+ * well as the form submission name/value pairs
+ */
+class HTMLFormSubmission
+{
+public:
+ /**
+ * Get a submission object based on attributes in the form (ENCTYPE and
+ * METHOD)
+ *
+ * @param aForm the form to get a submission object based on
+ * @param aOriginatingElement the originating element (can be null)
+ * @param aFormSubmission the form submission object (out param)
+ */
+ static nsresult
+ GetFromForm(nsGenericHTMLElement* aForm,
+ nsGenericHTMLElement* aOriginatingElement,
+ HTMLFormSubmission** aFormSubmission);
+
+ virtual ~HTMLFormSubmission()
+ {
+ MOZ_COUNT_DTOR(HTMLFormSubmission);
+ }
+
+ /**
+ * Submit a name/value pair
+ *
+ * @param aName the name of the parameter
+ * @param aValue the value of the parameter
+ */
+ virtual nsresult
+ AddNameValuePair(const nsAString& aName, const nsAString& aValue) = 0;
+
+ /**
+ * Submit a name/blob pair
+ *
+ * @param aName the name of the parameter
+ * @param aBlob the blob to submit. The file's name will be used if the Blob
+ * is actually a File, otherwise 'blob' string is used instead if the aBlob is
+ * not null.
+ */
+ virtual nsresult
+ AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) = 0;
+
+ /**
+ * Submit a name/directory pair
+ *
+ * @param aName the name of the parameter
+ * @param aBlob the directory to submit.
+ */
+ virtual nsresult AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory) = 0;
+
+ /**
+ * Reports whether the instance supports AddIsindex().
+ *
+ * @return true if supported.
+ */
+ virtual bool SupportsIsindexSubmission()
+ {
+ return false;
+ }
+
+ /**
+ * Adds an isindex value to the submission.
+ *
+ * @param aValue the field value
+ */
+ virtual nsresult AddIsindex(const nsAString& aValue)
+ {
+ NS_NOTREACHED("AddIsindex called when not supported");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ /**
+ * Given a URI and the current submission, create the final URI and data
+ * stream that will be submitted. Subclasses *must* implement this.
+ *
+ * @param aURI the URI being submitted to [INOUT]
+ * @param aPostDataStream a data stream for POST data [OUT]
+ */
+ virtual nsresult
+ GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) = 0;
+
+ /**
+ * Get the charset that will be used for submission.
+ */
+ void GetCharset(nsACString& aCharset)
+ {
+ aCharset = mCharset;
+ }
+
+ nsIContent* GetOriginatingElement() const
+ {
+ return mOriginatingElement.get();
+ }
+
+protected:
+ /**
+ * Can only be constructed by subclasses.
+ *
+ * @param aCharset the charset of the form as a string
+ * @param aOriginatingElement the originating element (can be null)
+ */
+ HTMLFormSubmission(const nsACString& aCharset,
+ nsIContent* aOriginatingElement)
+ : mCharset(aCharset)
+ , mOriginatingElement(aOriginatingElement)
+ {
+ MOZ_COUNT_CTOR(HTMLFormSubmission);
+ }
+
+ // The name of the encoder charset
+ nsCString mCharset;
+
+ // Originating element.
+ nsCOMPtr<nsIContent> mOriginatingElement;
+};
+
+class EncodingFormSubmission : public HTMLFormSubmission
+{
+public:
+ EncodingFormSubmission(const nsACString& aCharset,
+ nsIContent* aOriginatingElement);
+
+ virtual ~EncodingFormSubmission();
+
+ /**
+ * Encode a Unicode string to bytes using the encoder (or just copy the input
+ * if there is no encoder).
+ * @param aStr the string to encode
+ * @param aResult the encoded string [OUT]
+ * @param aHeaderEncode If true, turns all linebreaks into spaces and escapes
+ * all quotes
+ * @throws an error if UnicodeToNewBytes fails
+ */
+ nsresult EncodeVal(const nsAString& aStr, nsCString& aResult,
+ bool aHeaderEncode);
+
+private:
+ // The encoder that will encode Unicode names and values
+ nsNCRFallbackEncoderWrapper mEncoder;
+};
+
+/**
+ * Handle multipart/form-data encoding, which does files as well as normal
+ * inputs. This always does POST.
+ */
+class FSMultipartFormData : public EncodingFormSubmission
+{
+public:
+ /**
+ * @param aCharset the charset of the form as a string
+ */
+ FSMultipartFormData(const nsACString& aCharset,
+ nsIContent* aOriginatingElement);
+ ~FSMultipartFormData();
+
+ virtual nsresult
+ AddNameValuePair(const nsAString& aName, const nsAString& aValue) override;
+
+ virtual nsresult
+ AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override;
+
+ virtual nsresult
+ AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override;
+
+ virtual nsresult
+ GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override;
+
+ void GetContentType(nsACString& aContentType)
+ {
+ aContentType =
+ NS_LITERAL_CSTRING("multipart/form-data; boundary=") + mBoundary;
+ }
+
+ nsIInputStream* GetSubmissionBody(uint64_t* aContentLength);
+
+protected:
+
+ /**
+ * Roll up the data we have so far and add it to the multiplexed data stream.
+ */
+ nsresult AddPostDataStream();
+
+private:
+ void AddDataChunk(const nsACString& aName,
+ const nsACString& aFilename,
+ const nsACString& aContentType,
+ nsIInputStream* aInputStream,
+ uint64_t aInputStreamSize);
+ /**
+ * The post data stream as it is so far. This is a collection of smaller
+ * chunks--string streams and file streams interleaved to make one big POST
+ * stream.
+ */
+ nsCOMPtr<nsIMultiplexInputStream> mPostDataStream;
+
+ /**
+ * The current string chunk. When a file is hit, the string chunk gets
+ * wrapped up into an input stream and put into mPostDataStream so that the
+ * file input stream can then be appended and everything is in the right
+ * order. Then the string chunk gets appended to again as we process more
+ * name/value pairs.
+ */
+ nsCString mPostDataChunk;
+
+ /**
+ * The boundary string to use after each "part" (the boundary that marks the
+ * end of a value). This is computed randomly and is different for each
+ * submission.
+ */
+ nsCString mBoundary;
+
+ /**
+ * The total length in bytes of the streams that make up mPostDataStream
+ */
+ uint64_t mTotalLength;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_HTMLFormSubmission_h */
diff --git a/dom/html/HTMLFormSubmissionConstants.h b/dom/html/HTMLFormSubmissionConstants.h
new file mode 100644
index 000000000..860083d88
--- /dev/null
+++ b/dom/html/HTMLFormSubmissionConstants.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_HTMLFormSubmissionConstants_h
+#define mozilla_dom_HTMLFormSubmissionConstants_h
+
+#include "nsIForm.h"
+
+static const nsAttrValue::EnumTable kFormMethodTable[] = {
+ { "get", NS_FORM_METHOD_GET },
+ { "post", NS_FORM_METHOD_POST },
+ { nullptr, 0 }
+};
+
+// Default method is 'get'.
+static const nsAttrValue::EnumTable* kFormDefaultMethod = &kFormMethodTable[0];
+
+static const nsAttrValue::EnumTable kFormEnctypeTable[] = {
+ { "multipart/form-data", NS_FORM_ENCTYPE_MULTIPART },
+ { "application/x-www-form-urlencoded", NS_FORM_ENCTYPE_URLENCODED },
+ { "text/plain", NS_FORM_ENCTYPE_TEXTPLAIN },
+ { nullptr, 0 }
+};
+
+// Default method is 'application/x-www-form-urlencoded'.
+static const nsAttrValue::EnumTable* kFormDefaultEnctype = &kFormEnctypeTable[1];
+
+#endif // mozilla_dom_HTMLFormSubmissionConstants_h
diff --git a/dom/html/HTMLFrameElement.cpp b/dom/html/HTMLFrameElement.cpp
new file mode 100644
index 000000000..989022b0c
--- /dev/null
+++ b/dom/html/HTMLFrameElement.cpp
@@ -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/. */
+
+#include "mozilla/dom/HTMLFrameElement.h"
+#include "mozilla/dom/HTMLFrameElementBinding.h"
+
+class nsIDOMDocument;
+
+NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Frame)
+
+namespace mozilla {
+namespace dom {
+
+HTMLFrameElement::HTMLFrameElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser)
+ : nsGenericHTMLFrameElement(aNodeInfo, aFromParser)
+{
+}
+
+HTMLFrameElement::~HTMLFrameElement()
+{
+}
+
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLFrameElement, nsGenericHTMLFrameElement,
+ nsIDOMHTMLFrameElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLFrameElement)
+
+
+NS_IMPL_STRING_ATTR(HTMLFrameElement, FrameBorder, frameborder)
+NS_IMPL_URI_ATTR(HTMLFrameElement, LongDesc, longdesc)
+NS_IMPL_STRING_ATTR(HTMLFrameElement, MarginHeight, marginheight)
+NS_IMPL_STRING_ATTR(HTMLFrameElement, MarginWidth, marginwidth)
+NS_IMPL_STRING_ATTR(HTMLFrameElement, Name, name)
+NS_IMPL_BOOL_ATTR(HTMLFrameElement, NoResize, noresize)
+NS_IMPL_STRING_ATTR(HTMLFrameElement, Scrolling, scrolling)
+NS_IMPL_URI_ATTR(HTMLFrameElement, Src, src)
+
+
+NS_IMETHODIMP
+HTMLFrameElement::GetContentDocument(nsIDOMDocument** aContentDocument)
+{
+ return nsGenericHTMLFrameElement::GetContentDocument(aContentDocument);
+}
+
+bool
+HTMLFrameElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::bordercolor) {
+ return aResult.ParseColor(aValue);
+ }
+ if (aAttribute == nsGkAtoms::frameborder) {
+ return ParseFrameborderValue(aValue, aResult);
+ }
+ if (aAttribute == nsGkAtoms::marginwidth) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::marginheight) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::scrolling) {
+ return ParseScrollingValue(aValue, aResult);
+ }
+ }
+
+ return nsGenericHTMLFrameElement::ParseAttribute(aNamespaceID, aAttribute,
+ aValue, aResult);
+}
+
+JSObject*
+HTMLFrameElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLFrameElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLFrameElement.h b/dom/html/HTMLFrameElement.h
new file mode 100644
index 000000000..c5a7120b3
--- /dev/null
+++ b/dom/html/HTMLFrameElement.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_HTMLFrameElement_h
+#define mozilla_dom_HTMLFrameElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsIDOMHTMLFrameElement.h"
+#include "nsGenericHTMLFrameElement.h"
+#include "nsGkAtoms.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLFrameElement final : public nsGenericHTMLFrameElement,
+ public nsIDOMHTMLFrameElement
+{
+public:
+ using nsGenericHTMLFrameElement::SwapFrameLoaders;
+
+ explicit HTMLFrameElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser = NOT_FROM_PARSER);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLFrameElement
+ NS_DECL_NSIDOMHTMLFRAMEELEMENT
+
+ // nsIContent
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ // WebIDL API
+ // The XPCOM GetFrameBorder is OK for us
+ void SetFrameBorder(const nsAString& aFrameBorder, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::frameborder, aFrameBorder, aError);
+ }
+
+ // The XPCOM GetLongDesc is OK for us
+ void SetLongDesc(const nsAString& aLongDesc, ErrorResult& aError)
+ {
+ SetAttrHelper(nsGkAtoms::longdesc, aLongDesc);
+ }
+
+ // The XPCOM GetMarginHeight is OK for us
+ void SetMarginHeight(const nsAString& aMarginHeight, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::marginheight, aMarginHeight, aError);
+ }
+
+ // The XPCOM GetMarginWidth is OK for us
+ void SetMarginWidth(const nsAString& aMarginWidth, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::marginwidth, aMarginWidth, aError);
+ }
+
+ // The XPCOM GetName is OK for us
+ void SetName(const nsAString& aName, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::name, aName, aError);
+ }
+
+ bool NoResize() const
+ {
+ return GetBoolAttr(nsGkAtoms::noresize);
+ }
+ void SetNoResize(bool& aNoResize, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::noresize, aNoResize, aError);
+ }
+
+ // The XPCOM GetScrolling is OK for us
+ void SetScrolling(const nsAString& aScrolling, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::scrolling, aScrolling, aError);
+ }
+
+ // The XPCOM GetSrc is OK for us
+ void SetSrc(const nsAString& aSrc, ErrorResult& aError)
+ {
+ SetAttrHelper(nsGkAtoms::src, aSrc);
+ }
+
+ using nsGenericHTMLFrameElement::GetContentDocument;
+ using nsGenericHTMLFrameElement::GetContentWindow;
+
+protected:
+ virtual ~HTMLFrameElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLFrameElement_h
diff --git a/dom/html/HTMLFrameSetElement.cpp b/dom/html/HTMLFrameSetElement.cpp
new file mode 100644
index 000000000..6d39caa19
--- /dev/null
+++ b/dom/html/HTMLFrameSetElement.cpp
@@ -0,0 +1,381 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HTMLFrameSetElement.h"
+#include "mozilla/dom/HTMLFrameSetElementBinding.h"
+#include "mozilla/dom/EventHandlerBinding.h"
+#include "nsGlobalWindow.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(FrameSet)
+
+namespace mozilla {
+namespace dom {
+
+HTMLFrameSetElement::~HTMLFrameSetElement()
+{
+}
+
+JSObject*
+HTMLFrameSetElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLFrameSetElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLFrameSetElement, nsGenericHTMLElement,
+ nsIDOMHTMLFrameSetElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLFrameSetElement)
+
+NS_IMETHODIMP
+HTMLFrameSetElement::SetCols(const nsAString& aCols)
+{
+ ErrorResult rv;
+ SetCols(aCols, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLFrameSetElement::GetCols(nsAString& aCols)
+{
+ DOMString cols;
+ GetCols(cols);
+ cols.ToString(aCols);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLFrameSetElement::SetRows(const nsAString& aRows)
+{
+ ErrorResult rv;
+ SetRows(aRows, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLFrameSetElement::GetRows(nsAString& aRows)
+{
+ DOMString rows;
+ GetRows(rows);
+ rows.ToString(aRows);
+ return NS_OK;
+}
+
+nsresult
+HTMLFrameSetElement::SetAttr(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ nsIAtom* aPrefix,
+ const nsAString& aValue,
+ bool aNotify)
+{
+ nsresult rv;
+ /* The main goal here is to see whether the _number_ of rows or
+ * columns has changed. If it has, we need to reframe; otherwise
+ * we want to reflow. So we set mCurrentRowColHint here, then call
+ * nsGenericHTMLElement::SetAttr, which will end up calling
+ * GetAttributeChangeHint and notifying layout with that hint.
+ * Once nsGenericHTMLElement::SetAttr returns, we want to go back to our
+ * normal hint, which is NS_STYLE_HINT_REFLOW.
+ */
+ if (aAttribute == nsGkAtoms::rows && aNameSpaceID == kNameSpaceID_None) {
+ int32_t oldRows = mNumRows;
+ ParseRowCol(aValue, mNumRows, &mRowSpecs);
+
+ if (mNumRows != oldRows) {
+ mCurrentRowColHint = nsChangeHint_ReconstructFrame;
+ }
+ } else if (aAttribute == nsGkAtoms::cols &&
+ aNameSpaceID == kNameSpaceID_None) {
+ int32_t oldCols = mNumCols;
+ ParseRowCol(aValue, mNumCols, &mColSpecs);
+
+ if (mNumCols != oldCols) {
+ mCurrentRowColHint = nsChangeHint_ReconstructFrame;
+ }
+ }
+
+ rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aAttribute, aPrefix,
+ aValue, aNotify);
+ mCurrentRowColHint = NS_STYLE_HINT_REFLOW;
+
+ return rv;
+}
+
+nsresult
+HTMLFrameSetElement::GetRowSpec(int32_t *aNumValues,
+ const nsFramesetSpec** aSpecs)
+{
+ NS_PRECONDITION(aNumValues, "Must have a pointer to an integer here!");
+ NS_PRECONDITION(aSpecs, "Must have a pointer to an array of nsFramesetSpecs");
+ *aNumValues = 0;
+ *aSpecs = nullptr;
+
+ if (!mRowSpecs) {
+ const nsAttrValue* value = GetParsedAttr(nsGkAtoms::rows);
+ if (value && value->Type() == nsAttrValue::eString) {
+ nsresult rv = ParseRowCol(value->GetStringValue(), mNumRows,
+ &mRowSpecs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!mRowSpecs) { // we may not have had an attr or had an empty attr
+ mRowSpecs = MakeUnique<nsFramesetSpec[]>(1);
+ mNumRows = 1;
+ mRowSpecs[0].mUnit = eFramesetUnit_Relative;
+ mRowSpecs[0].mValue = 1;
+ }
+ }
+
+ *aSpecs = mRowSpecs.get();
+ *aNumValues = mNumRows;
+ return NS_OK;
+}
+
+nsresult
+HTMLFrameSetElement::GetColSpec(int32_t *aNumValues,
+ const nsFramesetSpec** aSpecs)
+{
+ NS_PRECONDITION(aNumValues, "Must have a pointer to an integer here!");
+ NS_PRECONDITION(aSpecs, "Must have a pointer to an array of nsFramesetSpecs");
+ *aNumValues = 0;
+ *aSpecs = nullptr;
+
+ if (!mColSpecs) {
+ const nsAttrValue* value = GetParsedAttr(nsGkAtoms::cols);
+ if (value && value->Type() == nsAttrValue::eString) {
+ nsresult rv = ParseRowCol(value->GetStringValue(), mNumCols,
+ &mColSpecs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!mColSpecs) { // we may not have had an attr or had an empty attr
+ mColSpecs = MakeUnique<nsFramesetSpec[]>(1);
+ mNumCols = 1;
+ mColSpecs[0].mUnit = eFramesetUnit_Relative;
+ mColSpecs[0].mValue = 1;
+ }
+ }
+
+ *aSpecs = mColSpecs.get();
+ *aNumValues = mNumCols;
+ return NS_OK;
+}
+
+
+bool
+HTMLFrameSetElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::bordercolor) {
+ return aResult.ParseColor(aValue);
+ }
+ if (aAttribute == nsGkAtoms::frameborder) {
+ return nsGenericHTMLElement::ParseFrameborderValue(aValue, aResult);
+ }
+ if (aAttribute == nsGkAtoms::border) {
+ return aResult.ParseIntWithBounds(aValue, 0, 100);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+nsChangeHint
+HTMLFrameSetElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const
+{
+ nsChangeHint retval =
+ nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
+ if (aAttribute == nsGkAtoms::rows ||
+ aAttribute == nsGkAtoms::cols) {
+ retval |= mCurrentRowColHint;
+ }
+ return retval;
+}
+
+/**
+ * Translate a "rows" or "cols" spec into an array of nsFramesetSpecs
+ */
+nsresult
+HTMLFrameSetElement::ParseRowCol(const nsAString & aValue,
+ int32_t& aNumSpecs,
+ UniquePtr<nsFramesetSpec[]>* aSpecs)
+{
+ if (aValue.IsEmpty()) {
+ aNumSpecs = 0;
+ *aSpecs = nullptr;
+ return NS_OK;
+ }
+
+ static const char16_t sAster('*');
+ static const char16_t sPercent('%');
+ static const char16_t sComma(',');
+
+ nsAutoString spec(aValue);
+ // remove whitespace (Bug 33699) and quotation marks (bug 224598)
+ // also remove leading/trailing commas (bug 31482)
+ spec.StripChars(" \n\r\t\"\'");
+ spec.Trim(",");
+
+ // Count the commas. Don't count more than X commas (bug 576447).
+ static_assert(NS_MAX_FRAMESET_SPEC_COUNT * sizeof(nsFramesetSpec) < (1 << 30),
+ "Too many frameset specs allowed to allocate");
+ int32_t commaX = spec.FindChar(sComma);
+ int32_t count = 1;
+ while (commaX != kNotFound && count < NS_MAX_FRAMESET_SPEC_COUNT) {
+ count++;
+ commaX = spec.FindChar(sComma, commaX + 1);
+ }
+
+ auto specs = MakeUniqueFallible<nsFramesetSpec[]>(count);
+ if (!specs) {
+ *aSpecs = nullptr;
+ aNumSpecs = 0;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Pre-grab the compat mode; we may need it later in the loop.
+ bool isInQuirks = InNavQuirksMode(OwnerDoc());
+
+ // Parse each comma separated token
+
+ int32_t start = 0;
+ int32_t specLen = spec.Length();
+
+ for (int32_t i = 0; i < count; i++) {
+ // Find our comma
+ commaX = spec.FindChar(sComma, start);
+ NS_ASSERTION(i == count - 1 || commaX != kNotFound,
+ "Failed to find comma, somehow");
+ int32_t end = (commaX == kNotFound) ? specLen : commaX;
+
+ // Note: If end == start then it means that the token has no
+ // data in it other than a terminating comma (or the end of the spec).
+ // So default to a fixed width of 0.
+ specs[i].mUnit = eFramesetUnit_Fixed;
+ specs[i].mValue = 0;
+ if (end > start) {
+ int32_t numberEnd = end;
+ char16_t ch = spec.CharAt(numberEnd - 1);
+ if (sAster == ch) {
+ specs[i].mUnit = eFramesetUnit_Relative;
+ numberEnd--;
+ } else if (sPercent == ch) {
+ specs[i].mUnit = eFramesetUnit_Percent;
+ numberEnd--;
+ // check for "*%"
+ if (numberEnd > start) {
+ ch = spec.CharAt(numberEnd - 1);
+ if (sAster == ch) {
+ specs[i].mUnit = eFramesetUnit_Relative;
+ numberEnd--;
+ }
+ }
+ }
+
+ // Translate value to an integer
+ nsAutoString token;
+ spec.Mid(token, start, numberEnd - start);
+
+ // Treat * as 1*
+ if ((eFramesetUnit_Relative == specs[i].mUnit) &&
+ (0 == token.Length())) {
+ specs[i].mValue = 1;
+ }
+ else {
+ // Otherwise just convert to integer.
+ nsresult err;
+ specs[i].mValue = token.ToInteger(&err);
+ if (NS_FAILED(err)) {
+ specs[i].mValue = 0;
+ }
+ }
+
+ // Treat 0* as 1* in quirks mode (bug 40383)
+ if (isInQuirks) {
+ if ((eFramesetUnit_Relative == specs[i].mUnit) &&
+ (0 == specs[i].mValue)) {
+ specs[i].mValue = 1;
+ }
+ }
+
+ // Catch zero and negative frame sizes for Nav compatibility
+ // Nav resized absolute and relative frames to "1" and
+ // percent frames to an even percentage of the width
+ //
+ //if (isInQuirks && (specs[i].mValue <= 0)) {
+ // if (eFramesetUnit_Percent == specs[i].mUnit) {
+ // specs[i].mValue = 100 / count;
+ // } else {
+ // specs[i].mValue = 1;
+ // }
+ //} else {
+
+ // In standards mode, just set negative sizes to zero
+ if (specs[i].mValue < 0) {
+ specs[i].mValue = 0;
+ }
+ start = end + 1;
+ }
+ }
+
+ aNumSpecs = count;
+ // Transfer ownership to caller here
+ *aSpecs = Move(specs);
+
+ return NS_OK;
+}
+
+bool
+HTMLFrameSetElement::IsEventAttributeName(nsIAtom *aName)
+{
+ return nsContentUtils::IsEventAttributeName(aName,
+ EventNameType_HTML |
+ EventNameType_HTMLBodyOrFramesetOnly);
+}
+
+
+#define EVENT(name_, id_, type_, struct_) /* nothing; handled by the shim */
+// nsGenericHTMLElement::GetOnError returns
+// already_AddRefed<EventHandlerNonNull> while other getters return
+// EventHandlerNonNull*, so allow passing in the type to use here.
+#define WINDOW_EVENT_HELPER(name_, type_) \
+ type_* \
+ HTMLFrameSetElement::GetOn##name_() \
+ { \
+ if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \
+ nsGlobalWindow* globalWin = nsGlobalWindow::Cast(win); \
+ return globalWin->GetOn##name_(); \
+ } \
+ return nullptr; \
+ } \
+ void \
+ HTMLFrameSetElement::SetOn##name_(type_* handler) \
+ { \
+ nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \
+ if (!win) { \
+ return; \
+ } \
+ \
+ nsGlobalWindow* globalWin = nsGlobalWindow::Cast(win); \
+ return globalWin->SetOn##name_(handler); \
+ }
+#define WINDOW_EVENT(name_, id_, type_, struct_) \
+ WINDOW_EVENT_HELPER(name_, EventHandlerNonNull)
+#define BEFOREUNLOAD_EVENT(name_, id_, type_, struct_) \
+ WINDOW_EVENT_HELPER(name_, OnBeforeUnloadEventHandlerNonNull)
+#include "mozilla/EventNameList.h" // IWYU pragma: keep
+#undef BEFOREUNLOAD_EVENT
+#undef WINDOW_EVENT
+#undef WINDOW_EVENT_HELPER
+#undef EVENT
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLFrameSetElement.h b/dom/html/HTMLFrameSetElement.h
new file mode 100644
index 000000000..b6bbe5d95
--- /dev/null
+++ b/dom/html/HTMLFrameSetElement.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/. */
+
+#ifndef HTMLFrameSetElement_h
+#define HTMLFrameSetElement_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIDOMHTMLFrameSetElement.h"
+#include "nsGenericHTMLElement.h"
+
+/**
+ * The nsFramesetUnit enum is used to denote the type of each entry
+ * in the row or column spec.
+ */
+enum nsFramesetUnit {
+ eFramesetUnit_Fixed = 0,
+ eFramesetUnit_Percent,
+ eFramesetUnit_Relative
+};
+
+/**
+ * The nsFramesetSpec struct is used to hold a single entry in the
+ * row or column spec.
+ */
+struct nsFramesetSpec {
+ nsFramesetUnit mUnit;
+ nscoord mValue;
+};
+
+/**
+ * The maximum number of entries allowed in the frame set element row
+ * or column spec.
+ */
+#define NS_MAX_FRAMESET_SPEC_COUNT 16000
+
+//----------------------------------------------------------------------
+
+namespace mozilla {
+namespace dom {
+
+class OnBeforeUnloadEventHandlerNonNull;
+
+class HTMLFrameSetElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLFrameSetElement
+{
+public:
+ explicit HTMLFrameSetElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo),
+ mNumRows(0),
+ mNumCols(0),
+ mCurrentRowColHint(NS_STYLE_HINT_REFLOW)
+ {
+ SetHasWeirdParserInsertionMode();
+ }
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLFrameSetElement, frameset)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLFrameSetElement
+ NS_DECL_NSIDOMHTMLFRAMESETELEMENT
+
+ void GetCols(DOMString& aCols)
+ {
+ GetHTMLAttr(nsGkAtoms::cols, aCols);
+ }
+ void SetCols(const nsAString& aCols, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::cols, aCols, aError);
+ }
+ void GetRows(DOMString& aRows)
+ {
+ GetHTMLAttr(nsGkAtoms::rows, aRows);
+ }
+ void SetRows(const nsAString& aRows, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::rows, aRows, aError);
+ }
+
+ virtual bool IsEventAttributeName(nsIAtom *aName) override;
+
+ // Event listener stuff; we need to declare only the ones we need to
+ // forward to window that don't come from nsIDOMHTMLFrameSetElement.
+#define EVENT(name_, id_, type_, struct_) /* nothing; handled by the superclass */
+#define WINDOW_EVENT_HELPER(name_, type_) \
+ type_* GetOn##name_(); \
+ void SetOn##name_(type_* handler);
+#define WINDOW_EVENT(name_, id_, type_, struct_) \
+ WINDOW_EVENT_HELPER(name_, EventHandlerNonNull)
+#define BEFOREUNLOAD_EVENT(name_, id_, type_, struct_) \
+ WINDOW_EVENT_HELPER(name_, OnBeforeUnloadEventHandlerNonNull)
+#include "mozilla/EventNameList.h" // IWYU pragma: keep
+#undef BEFOREUNLOAD_EVENT
+#undef WINDOW_EVENT
+#undef WINDOW_EVENT_HELPER
+#undef EVENT
+
+ // These override the SetAttr methods in nsGenericHTMLElement (need
+ // both here to silence compiler warnings).
+ 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;
+
+ /**
+ * GetRowSpec is used to get the "rows" spec.
+ * @param out int32_t aNumValues The number of row sizes specified.
+ * @param out nsFramesetSpec* aSpecs The array of size specifications.
+ This is _not_ owned by the caller, but by the nsFrameSetElement
+ implementation. DO NOT DELETE IT.
+ */
+ nsresult GetRowSpec(int32_t *aNumValues, const nsFramesetSpec** aSpecs);
+ /**
+ * GetColSpec is used to get the "cols" spec
+ * @param out int32_t aNumValues The number of row sizes specified.
+ * @param out nsFramesetSpec* aSpecs The array of size specifications.
+ This is _not_ owned by the caller, but by the nsFrameSetElement
+ implementation. DO NOT DELETE IT.
+ */
+ nsresult GetColSpec(int32_t *aNumValues, const nsFramesetSpec** aSpecs);
+
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+protected:
+ virtual ~HTMLFrameSetElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ nsresult ParseRowCol(const nsAString& aValue,
+ int32_t& aNumSpecs,
+ UniquePtr<nsFramesetSpec[]>* aSpecs);
+
+ /**
+ * The number of size specs in our "rows" attr
+ */
+ int32_t mNumRows;
+ /**
+ * The number of size specs in our "cols" attr
+ */
+ int32_t mNumCols;
+ /**
+ * The style hint to return for the rows/cols attrs in
+ * GetAttributeChangeHint
+ */
+ nsChangeHint mCurrentRowColHint;
+ /**
+ * The parsed representation of the "rows" attribute
+ */
+ UniquePtr<nsFramesetSpec[]> mRowSpecs; // parsed, non-computed dimensions
+ /**
+ * The parsed representation of the "cols" attribute
+ */
+ UniquePtr<nsFramesetSpec[]> mColSpecs; // parsed, non-computed dimensions
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // HTMLFrameSetElement_h
diff --git a/dom/html/HTMLHRElement.cpp b/dom/html/HTMLHRElement.cpp
new file mode 100644
index 000000000..fb5dbe618
--- /dev/null
+++ b/dom/html/HTMLHRElement.cpp
@@ -0,0 +1,267 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLHRElement.h"
+#include "mozilla/dom/HTMLHRElementBinding.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(HR)
+
+namespace mozilla {
+namespace dom {
+
+HTMLHRElement::HTMLHRElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+{
+}
+
+HTMLHRElement::~HTMLHRElement()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLHRElement, nsGenericHTMLElement,
+ nsIDOMHTMLHRElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLHRElement)
+
+
+NS_IMPL_STRING_ATTR(HTMLHRElement, Align, align)
+NS_IMPL_BOOL_ATTR(HTMLHRElement, NoShade, noshade)
+NS_IMPL_STRING_ATTR(HTMLHRElement, Size, size)
+NS_IMPL_STRING_ATTR(HTMLHRElement, Width, width)
+NS_IMPL_STRING_ATTR(HTMLHRElement, Color, color)
+
+bool
+HTMLHRElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ static const nsAttrValue::EnumTable kAlignTable[] = {
+ { "left", NS_STYLE_TEXT_ALIGN_LEFT },
+ { "right", NS_STYLE_TEXT_ALIGN_RIGHT },
+ { "center", NS_STYLE_TEXT_ALIGN_CENTER },
+ { nullptr, 0 }
+ };
+
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::width) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::size) {
+ return aResult.ParseIntWithBounds(aValue, 1, 1000);
+ }
+ if (aAttribute == nsGkAtoms::align) {
+ return aResult.ParseEnumValue(aValue, kAlignTable, false);
+ }
+ if (aAttribute == nsGkAtoms::color) {
+ return aResult.ParseColor(aValue);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLHRElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ bool noshade = false;
+
+ const nsAttrValue* colorValue = aAttributes->GetAttr(nsGkAtoms::color);
+ nscolor color;
+ bool colorIsSet = colorValue && colorValue->GetColorValue(color);
+
+ if (aData->mSIDs & (NS_STYLE_INHERIT_BIT(Position) |
+ NS_STYLE_INHERIT_BIT(Border))) {
+ if (colorIsSet) {
+ noshade = true;
+ } else {
+ noshade = !!aAttributes->GetAttr(nsGkAtoms::noshade);
+ }
+ }
+
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Margin)) {
+ // align: enum
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align);
+ if (value && value->Type() == nsAttrValue::eEnum) {
+ // Map align attribute into auto side margins
+ nsCSSValue* marginLeft = aData->ValueForMarginLeft();
+ nsCSSValue* marginRight = aData->ValueForMarginRight();
+ switch (value->GetEnumValue()) {
+ case NS_STYLE_TEXT_ALIGN_LEFT:
+ if (marginLeft->GetUnit() == eCSSUnit_Null)
+ marginLeft->SetFloatValue(0.0f, eCSSUnit_Pixel);
+ if (marginRight->GetUnit() == eCSSUnit_Null)
+ marginRight->SetAutoValue();
+ break;
+ case NS_STYLE_TEXT_ALIGN_RIGHT:
+ if (marginLeft->GetUnit() == eCSSUnit_Null)
+ marginLeft->SetAutoValue();
+ if (marginRight->GetUnit() == eCSSUnit_Null)
+ marginRight->SetFloatValue(0.0f, eCSSUnit_Pixel);
+ break;
+ case NS_STYLE_TEXT_ALIGN_CENTER:
+ if (marginLeft->GetUnit() == eCSSUnit_Null)
+ marginLeft->SetAutoValue();
+ if (marginRight->GetUnit() == eCSSUnit_Null)
+ marginRight->SetAutoValue();
+ break;
+ }
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Position)) {
+ // width: integer, percent
+ nsCSSValue* width = aData->ValueForWidth();
+ if (width->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::width);
+ if (value && value->Type() == nsAttrValue::eInteger) {
+ width->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ } else if (value && value->Type() == nsAttrValue::ePercent) {
+ width->SetPercentValue(value->GetPercentValue());
+ }
+ }
+
+ nsCSSValue* height = aData->ValueForHeight();
+ if (height->GetUnit() == eCSSUnit_Null) {
+ // size: integer
+ if (noshade) {
+ // noshade case: size is set using the border
+ height->SetAutoValue();
+ } else {
+ // normal case
+ // the height includes the top and bottom borders that are initially 1px.
+ // for size=1, html.css has a special case rule that makes this work by
+ // removing all but the top border.
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::size);
+ if (value && value->Type() == nsAttrValue::eInteger) {
+ height->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ } // else use default value from html.css
+ }
+ }
+ }
+ if ((aData->mSIDs & NS_STYLE_INHERIT_BIT(Border)) && noshade) { // if not noshade, border styles are dealt with by html.css
+ // size: integer
+ // if a size is set, use half of it per side, otherwise, use 1px per side
+ float sizePerSide;
+ bool allSides = true;
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::size);
+ if (value && value->Type() == nsAttrValue::eInteger) {
+ sizePerSide = (float)value->GetIntegerValue() / 2.0f;
+ if (sizePerSide < 1.0f) {
+ // XXX When the pixel bug is fixed, all the special casing for
+ // subpixel borders should be removed.
+ // In the meantime, this makes http://www.microsoft.com/ look right.
+ sizePerSide = 1.0f;
+ allSides = false;
+ }
+ } else {
+ sizePerSide = 1.0f; // default to a 2px high line
+ }
+ nsCSSValue* borderTopWidth = aData->ValueForBorderTopWidth();
+ if (borderTopWidth->GetUnit() == eCSSUnit_Null) {
+ borderTopWidth->SetFloatValue(sizePerSide, eCSSUnit_Pixel);
+ }
+ if (allSides) {
+ nsCSSValue* borderRightWidth = aData->ValueForBorderRightWidth();
+ if (borderRightWidth->GetUnit() == eCSSUnit_Null) {
+ borderRightWidth->SetFloatValue(sizePerSide, eCSSUnit_Pixel);
+ }
+ nsCSSValue* borderBottomWidth = aData->ValueForBorderBottomWidth();
+ if (borderBottomWidth->GetUnit() == eCSSUnit_Null) {
+ borderBottomWidth->SetFloatValue(sizePerSide, eCSSUnit_Pixel);
+ }
+ nsCSSValue* borderLeftWidth = aData->ValueForBorderLeftWidth();
+ if (borderLeftWidth->GetUnit() == eCSSUnit_Null) {
+ borderLeftWidth->SetFloatValue(sizePerSide, eCSSUnit_Pixel);
+ }
+ }
+
+ nsCSSValue* borderTopStyle = aData->ValueForBorderTopStyle();
+ if (borderTopStyle->GetUnit() == eCSSUnit_Null) {
+ borderTopStyle->SetIntValue(NS_STYLE_BORDER_STYLE_SOLID,
+ eCSSUnit_Enumerated);
+ }
+ if (allSides) {
+ nsCSSValue* borderRightStyle = aData->ValueForBorderRightStyle();
+ if (borderRightStyle->GetUnit() == eCSSUnit_Null) {
+ borderRightStyle->SetIntValue(NS_STYLE_BORDER_STYLE_SOLID,
+ eCSSUnit_Enumerated);
+ }
+ nsCSSValue* borderBottomStyle = aData->ValueForBorderBottomStyle();
+ if (borderBottomStyle->GetUnit() == eCSSUnit_Null) {
+ borderBottomStyle->SetIntValue(NS_STYLE_BORDER_STYLE_SOLID,
+ eCSSUnit_Enumerated);
+ }
+ nsCSSValue* borderLeftStyle = aData->ValueForBorderLeftStyle();
+ if (borderLeftStyle->GetUnit() == eCSSUnit_Null) {
+ borderLeftStyle->SetIntValue(NS_STYLE_BORDER_STYLE_SOLID,
+ eCSSUnit_Enumerated);
+ }
+
+ // If it would be noticeable, set the border radius to
+ // 10000px on all corners; this triggers the clamping to make
+ // circular ends. This assumes the <hr> isn't larger than
+ // that in *both* dimensions.
+ for (const nsCSSPropertyID* props =
+ nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_radius);
+ *props != eCSSProperty_UNKNOWN; ++props) {
+ nsCSSValue* dimen = aData->ValueFor(*props);
+ if (dimen->GetUnit() == eCSSUnit_Null) {
+ dimen->SetFloatValue(10000.0f, eCSSUnit_Pixel);
+ }
+ }
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Color)) {
+ // color: a color
+ // (we got the color attribute earlier)
+ nsCSSValue* colorValue = aData->ValueForColor();
+ if (colorIsSet &&
+ colorValue->GetUnit() == eCSSUnit_Null &&
+ aData->mPresContext->UseDocumentColors()) {
+ colorValue->SetColorValue(color);
+ }
+ }
+
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLHRElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::align },
+ { &nsGkAtoms::width },
+ { &nsGkAtoms::size },
+ { &nsGkAtoms::color },
+ { &nsGkAtoms::noshade },
+ { nullptr },
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+
+nsMapRuleToAttributesFunc
+HTMLHRElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+JSObject*
+HTMLHRElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLHRElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLHRElement.h b/dom/html/HTMLHRElement.h
new file mode 100644
index 000000000..c06a473bd
--- /dev/null
+++ b/dom/html/HTMLHRElement.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef mozilla_dom_HTMLHRElement_h
+#define mozilla_dom_HTMLHRElement_h
+
+#include "nsGenericHTMLElement.h"
+#include "nsIDOMHTMLHRElement.h"
+#include "nsMappedAttributes.h"
+#include "nsAttrValueInlines.h"
+#include "nsRuleData.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLHRElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLHRElement
+{
+public:
+ explicit HTMLHRElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLHRElement
+ NS_DECL_NSIDOMHTMLHRELEMENT
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ // WebIDL API
+ void SetAlign(const nsAString& aAlign, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::align, aAlign, aError);
+ }
+
+ // The XPCOM GetColor is OK for us
+ void SetColor(const nsAString& aColor, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::color, aColor, aError);
+ }
+
+ bool NoShade() const
+ {
+ return GetBoolAttr(nsGkAtoms::noshade);
+ }
+ void SetNoShade(bool aNoShade, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::noshade, aNoShade, aError);
+ }
+
+ // The XPCOM GetSize is OK for us
+ void SetSize(const nsAString& aSize, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::size, aSize, aError);
+ }
+
+ // The XPCOM GetWidth is OK for us
+ void SetWidth(const nsAString& aWidth, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::width, aWidth, aError);
+ }
+
+protected:
+ virtual ~HTMLHRElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLHRElement_h
diff --git a/dom/html/HTMLHeadingElement.cpp b/dom/html/HTMLHeadingElement.cpp
new file mode 100644
index 000000000..091e60abb
--- /dev/null
+++ b/dom/html/HTMLHeadingElement.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 "mozilla/dom/HTMLHeadingElement.h"
+#include "mozilla/dom/HTMLHeadingElementBinding.h"
+
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsMappedAttributes.h"
+#include "nsRuleData.h"
+#include "mozAutoDocUpdate.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Heading)
+
+namespace mozilla {
+namespace dom {
+
+HTMLHeadingElement::~HTMLHeadingElement()
+{
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLHeadingElement)
+
+JSObject*
+HTMLHeadingElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLHeadingElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+bool
+HTMLHeadingElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aAttribute == nsGkAtoms::align && aNamespaceID == kNameSpaceID_None) {
+ return ParseDivAlignValue(aValue, aResult);
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLHeadingElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ nsGenericHTMLElement::MapDivAlignAttributeInto(aAttributes, aData);
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLHeadingElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry* const map[] = {
+ sDivAlignAttributeMap,
+ sCommonAttributeMap
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+
+nsMapRuleToAttributesFunc
+HTMLHeadingElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLHeadingElement.h b/dom/html/HTMLHeadingElement.h
new file mode 100644
index 000000000..2abf5bbb5
--- /dev/null
+++ b/dom/html/HTMLHeadingElement.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_HTMLHeadingElement_h
+#define mozilla_dom_HTMLHeadingElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLHeadingElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLHeadingElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ }
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+ nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ void SetAlign(const nsAString& aAlign, ErrorResult& aError)
+ {
+ return SetHTMLAttr(nsGkAtoms::align, aAlign, aError);
+ }
+ void GetAlign(DOMString& aAlign) const
+ {
+ return GetHTMLAttr(nsGkAtoms::align, aAlign);
+ }
+
+protected:
+ virtual ~HTMLHeadingElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLHeadingElement_h
diff --git a/dom/html/HTMLIFrameElement.cpp b/dom/html/HTMLIFrameElement.cpp
new file mode 100644
index 000000000..0d5a209c0
--- /dev/null
+++ b/dom/html/HTMLIFrameElement.cpp
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLIFrameElement.h"
+#include "mozilla/dom/HTMLIFrameElementBinding.h"
+#include "nsMappedAttributes.h"
+#include "nsAttrValueInlines.h"
+#include "nsError.h"
+#include "nsRuleData.h"
+#include "nsStyleConsts.h"
+#include "nsContentUtils.h"
+#include "nsSandboxFlags.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(IFrame)
+
+namespace mozilla {
+namespace dom {
+
+// static
+const DOMTokenListSupportedToken HTMLIFrameElement::sSupportedSandboxTokens[] = {
+#define SANDBOX_KEYWORD(string, atom, flags) string,
+#include "IframeSandboxKeywordList.h"
+#undef SANDBOX_KEYWORD
+ nullptr
+};
+
+HTMLIFrameElement::HTMLIFrameElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser)
+ : nsGenericHTMLFrameElement(aNodeInfo, aFromParser)
+{
+}
+
+HTMLIFrameElement::~HTMLIFrameElement()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLIFrameElement, nsGenericHTMLFrameElement,
+ nsIDOMHTMLIFrameElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLIFrameElement)
+
+NS_IMPL_STRING_ATTR(HTMLIFrameElement, Align, align)
+NS_IMPL_STRING_ATTR(HTMLIFrameElement, FrameBorder, frameborder)
+NS_IMPL_STRING_ATTR(HTMLIFrameElement, Height, height)
+NS_IMPL_URI_ATTR(HTMLIFrameElement, LongDesc, longdesc)
+NS_IMPL_STRING_ATTR(HTMLIFrameElement, MarginHeight, marginheight)
+NS_IMPL_STRING_ATTR(HTMLIFrameElement, MarginWidth, marginwidth)
+NS_IMPL_STRING_ATTR(HTMLIFrameElement, Name, name)
+NS_IMPL_STRING_ATTR(HTMLIFrameElement, Scrolling, scrolling)
+NS_IMPL_URI_ATTR(HTMLIFrameElement, Src, src)
+NS_IMPL_STRING_ATTR(HTMLIFrameElement, Width, width)
+NS_IMPL_BOOL_ATTR(HTMLIFrameElement, AllowFullscreen, allowfullscreen)
+NS_IMPL_STRING_ATTR(HTMLIFrameElement, Srcdoc, srcdoc)
+
+NS_IMETHODIMP
+HTMLIFrameElement::GetContentDocument(nsIDOMDocument** aContentDocument)
+{
+ return nsGenericHTMLFrameElement::GetContentDocument(aContentDocument);
+}
+
+bool
+HTMLIFrameElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::marginwidth) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::marginheight) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::width) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::height) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::frameborder) {
+ return ParseFrameborderValue(aValue, aResult);
+ }
+ if (aAttribute == nsGkAtoms::scrolling) {
+ return ParseScrollingValue(aValue, aResult);
+ }
+ if (aAttribute == nsGkAtoms::align) {
+ return ParseAlignValue(aValue, aResult);
+ }
+ if (aAttribute == nsGkAtoms::sandbox) {
+ aResult.ParseAtomArray(aValue);
+ return true;
+ }
+ }
+
+ return nsGenericHTMLFrameElement::ParseAttribute(aNamespaceID, aAttribute,
+ aValue, aResult);
+}
+
+void
+HTMLIFrameElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Border)) {
+ // frameborder: 0 | 1 (| NO | YES in quirks mode)
+ // If frameborder is 0 or No, set border to 0
+ // else leave it as the value set in html.css
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::frameborder);
+ if (value && value->Type() == nsAttrValue::eEnum) {
+ int32_t frameborder = value->GetEnumValue();
+ if (NS_STYLE_FRAME_0 == frameborder ||
+ NS_STYLE_FRAME_NO == frameborder ||
+ NS_STYLE_FRAME_OFF == frameborder) {
+ nsCSSValue* borderLeftWidth = aData->ValueForBorderLeftWidth();
+ if (borderLeftWidth->GetUnit() == eCSSUnit_Null)
+ borderLeftWidth->SetFloatValue(0.0f, eCSSUnit_Pixel);
+ nsCSSValue* borderRightWidth = aData->ValueForBorderRightWidth();
+ if (borderRightWidth->GetUnit() == eCSSUnit_Null)
+ borderRightWidth->SetFloatValue(0.0f, eCSSUnit_Pixel);
+ nsCSSValue* borderTopWidth = aData->ValueForBorderTopWidth();
+ if (borderTopWidth->GetUnit() == eCSSUnit_Null)
+ borderTopWidth->SetFloatValue(0.0f, eCSSUnit_Pixel);
+ nsCSSValue* borderBottomWidth = aData->ValueForBorderBottomWidth();
+ if (borderBottomWidth->GetUnit() == eCSSUnit_Null)
+ borderBottomWidth->SetFloatValue(0.0f, eCSSUnit_Pixel);
+ }
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Position)) {
+ // width: value
+ nsCSSValue* width = aData->ValueForWidth();
+ if (width->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::width);
+ if (value && value->Type() == nsAttrValue::eInteger)
+ width->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ else if (value && value->Type() == nsAttrValue::ePercent)
+ width->SetPercentValue(value->GetPercentValue());
+ }
+
+ // height: value
+ nsCSSValue* height = aData->ValueForHeight();
+ if (height->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::height);
+ if (value && value->Type() == nsAttrValue::eInteger)
+ height->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ else if (value && value->Type() == nsAttrValue::ePercent)
+ height->SetPercentValue(value->GetPercentValue());
+ }
+ }
+
+ nsGenericHTMLElement::MapImageAlignAttributeInto(aAttributes, aData);
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLIFrameElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::width },
+ { &nsGkAtoms::height },
+ { &nsGkAtoms::frameborder },
+ { nullptr },
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sImageAlignAttributeMap,
+ sCommonAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+
+
+nsMapRuleToAttributesFunc
+HTMLIFrameElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+nsresult
+HTMLIFrameElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify)
+{
+ nsresult rv = nsGenericHTMLFrameElement::SetAttr(aNameSpaceID, aName,
+ aPrefix, aValue, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::srcdoc) {
+ // Don't propagate error here. The attribute was successfully set, that's
+ // what we should reflect.
+ LoadSrc();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLIFrameElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue,
+ bool aNotify)
+{
+ if (aName == nsGkAtoms::sandbox &&
+ aNameSpaceID == kNameSpaceID_None && mFrameLoader) {
+ // If we have an nsFrameLoader, apply the new sandbox flags.
+ // Since this is called after the setter, the sandbox flags have
+ // alreay been updated.
+ mFrameLoader->ApplySandboxFlags(GetSandboxFlags());
+ }
+ return nsGenericHTMLFrameElement::AfterSetAttr(aNameSpaceID, aName, aValue,
+ aNotify);
+}
+
+nsresult
+HTMLIFrameElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify)
+{
+ // Invoke on the superclass.
+ nsresult rv = nsGenericHTMLFrameElement::UnsetAttr(aNameSpaceID, aAttribute, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aNameSpaceID == kNameSpaceID_None &&
+ aAttribute == nsGkAtoms::srcdoc) {
+ // Fall back to the src attribute, if any
+ LoadSrc();
+ }
+
+ return NS_OK;
+}
+
+uint32_t
+HTMLIFrameElement::GetSandboxFlags()
+{
+ const nsAttrValue* sandboxAttr = GetParsedAttr(nsGkAtoms::sandbox);
+ // No sandbox attribute, no sandbox flags.
+ if (!sandboxAttr) {
+ return SANDBOXED_NONE;
+ }
+ return nsContentUtils::ParseSandboxAttributeToFlags(sandboxAttr);
+}
+
+JSObject*
+HTMLIFrameElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLIFrameElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLIFrameElement.h b/dom/html/HTMLIFrameElement.h
new file mode 100644
index 000000000..5bfda2470
--- /dev/null
+++ b/dom/html/HTMLIFrameElement.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 mozilla_dom_HTMLIFrameElement_h
+#define mozilla_dom_HTMLIFrameElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLFrameElement.h"
+#include "nsIDOMHTMLIFrameElement.h"
+#include "nsDOMTokenList.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLIFrameElement final : public nsGenericHTMLFrameElement
+ , public nsIDOMHTMLIFrameElement
+{
+public:
+ explicit HTMLIFrameElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser = NOT_FROM_PARSER);
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLIFrameElement, iframe)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Element
+ virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override
+ {
+ return true;
+ }
+
+ // nsIDOMHTMLIFrameElement
+ NS_DECL_NSIDOMHTMLIFRAMEELEMENT
+
+ // nsIContent
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const 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* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify) override;
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue,
+ bool aNotify) override;
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify) override;
+
+ uint32_t GetSandboxFlags();
+
+ // Web IDL binding methods
+ // The XPCOM GetSrc is fine for our purposes
+ void SetSrc(const nsAString& aSrc, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::src, aSrc, aError);
+ }
+ void GetSrcdoc(DOMString& aSrcdoc)
+ {
+ GetHTMLAttr(nsGkAtoms::srcdoc, aSrcdoc);
+ }
+ void SetSrcdoc(const nsAString& aSrcdoc, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::srcdoc, aSrcdoc, aError);
+ }
+ void GetName(DOMString& aName)
+ {
+ GetHTMLAttr(nsGkAtoms::name, aName);
+ }
+ void SetName(const nsAString& aName, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::name, aName, aError);
+ }
+ nsDOMTokenList* Sandbox()
+ {
+ return GetTokenList(nsGkAtoms::sandbox, sSupportedSandboxTokens);
+ }
+ bool AllowFullscreen() const
+ {
+ return GetBoolAttr(nsGkAtoms::allowfullscreen);
+ }
+ void SetAllowFullscreen(bool aAllow, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::allowfullscreen, aAllow, aError);
+ }
+ void GetWidth(DOMString& aWidth)
+ {
+ GetHTMLAttr(nsGkAtoms::width, aWidth);
+ }
+ void SetWidth(const nsAString& aWidth, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::width, aWidth, aError);
+ }
+ void GetHeight(DOMString& aHeight)
+ {
+ GetHTMLAttr(nsGkAtoms::height, aHeight);
+ }
+ void SetHeight(const nsAString& aHeight, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::height, aHeight, aError);
+ }
+ using nsGenericHTMLFrameElement::GetContentDocument;
+ using nsGenericHTMLFrameElement::GetContentWindow;
+ void GetAlign(DOMString& aAlign)
+ {
+ GetHTMLAttr(nsGkAtoms::align, aAlign);
+ }
+ void SetAlign(const nsAString& aAlign, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::align, aAlign, aError);
+ }
+ void GetScrolling(DOMString& aScrolling)
+ {
+ GetHTMLAttr(nsGkAtoms::scrolling, aScrolling);
+ }
+ void SetScrolling(const nsAString& aScrolling, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::scrolling, aScrolling, aError);
+ }
+ void GetFrameBorder(DOMString& aFrameBorder)
+ {
+ GetHTMLAttr(nsGkAtoms::frameborder, aFrameBorder);
+ }
+ void SetFrameBorder(const nsAString& aFrameBorder, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::frameborder, aFrameBorder, aError);
+ }
+ // The XPCOM GetLongDesc is fine
+ void SetLongDesc(const nsAString& aLongDesc, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::longdesc, aLongDesc, aError);
+ }
+ void GetMarginWidth(DOMString& aMarginWidth)
+ {
+ GetHTMLAttr(nsGkAtoms::marginwidth, aMarginWidth);
+ }
+ void SetMarginWidth(const nsAString& aMarginWidth, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::marginwidth, aMarginWidth, aError);
+ }
+ void GetMarginHeight(DOMString& aMarginHeight)
+ {
+ GetHTMLAttr(nsGkAtoms::marginheight, aMarginHeight);
+ }
+ void SetMarginHeight(const nsAString& aMarginHeight, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::marginheight, aMarginHeight, aError);
+ }
+ void SetReferrerPolicy(const nsAString& aReferrer, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::referrerpolicy, aReferrer, aError);
+ }
+ void GetReferrerPolicy(nsAString& aReferrer)
+ {
+ GetEnumAttr(nsGkAtoms::referrerpolicy, EmptyCString().get(), aReferrer);
+ }
+ nsIDocument*
+ GetSVGDocument(nsIPrincipal& aSubjectPrincipal)
+ {
+ return GetContentDocument(aSubjectPrincipal);
+ }
+ bool Mozbrowser() const
+ {
+ return GetBoolAttr(nsGkAtoms::mozbrowser);
+ }
+ void SetMozbrowser(bool aAllow, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::mozbrowser, aAllow, aError);
+ }
+ using nsGenericHTMLFrameElement::SetMozbrowser;
+ // nsGenericHTMLFrameElement::GetFrameLoader is fine
+ // nsGenericHTMLFrameElement::GetAppManifestURL is fine
+
+ // The fullscreen flag is set to true only when requestFullscreen is
+ // explicitly called on this <iframe> element. In case this flag is
+ // set, the fullscreen state of this element will not be reverted
+ // automatically when its subdocument exits fullscreen.
+ bool FullscreenFlag() const { return mFullscreenFlag; }
+ void SetFullscreenFlag(bool aValue) { mFullscreenFlag = aValue; }
+
+protected:
+ virtual ~HTMLIFrameElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+
+ static const DOMTokenListSupportedToken sSupportedSandboxTokens[];
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/html/HTMLImageElement.cpp b/dom/html/HTMLImageElement.cpp
new file mode 100644
index 000000000..4b2e7a07b
--- /dev/null
+++ b/dom/html/HTMLImageElement.cpp
@@ -0,0 +1,1354 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLImageElement.h"
+#include "mozilla/dom/HTMLImageElementBinding.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsMappedAttributes.h"
+#include "nsSize.h"
+#include "nsDocument.h"
+#include "nsIDocument.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsIScriptContext.h"
+#include "nsIURL.h"
+#include "nsIIOService.h"
+#include "nsIServiceManager.h"
+#include "nsContentUtils.h"
+#include "nsContainerFrame.h"
+#include "nsNodeInfoManager.h"
+#include "mozilla/MouseEvents.h"
+#include "nsContentPolicyUtils.h"
+#include "nsIDOMWindow.h"
+#include "nsFocusManager.h"
+#include "mozilla/dom/HTMLFormElement.h"
+#include "nsAttrValueOrString.h"
+#include "imgLoader.h"
+#include "Image.h"
+
+// Responsive images!
+#include "mozilla/dom/HTMLSourceElement.h"
+#include "mozilla/dom/ResponsiveImageSelector.h"
+
+#include "imgIContainer.h"
+#include "imgILoader.h"
+#include "imgINotificationObserver.h"
+#include "imgRequestProxy.h"
+
+#include "nsILoadGroup.h"
+
+#include "nsRuleData.h"
+
+#include "nsIDOMHTMLMapElement.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/net/ReferrerPolicy.h"
+
+#include "nsLayoutUtils.h"
+
+using namespace mozilla::net;
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Image)
+
+#ifdef DEBUG
+// Is aSubject a previous sibling of aNode.
+static bool IsPreviousSibling(nsINode *aSubject, nsINode *aNode)
+{
+ if (aSubject == aNode) {
+ return false;
+ }
+
+ nsINode *parent = aSubject->GetParentNode();
+ if (parent && parent == aNode->GetParentNode()) {
+ return parent->IndexOf(aSubject) < parent->IndexOf(aNode);
+ }
+
+ return false;
+}
+#endif
+
+namespace mozilla {
+namespace dom {
+
+// Calls LoadSelectedImage on host element unless it has been superseded or
+// canceled -- this is the synchronous section of "update the image data".
+// https://html.spec.whatwg.org/multipage/embedded-content.html#update-the-image-data
+class ImageLoadTask : public Runnable
+{
+public:
+ ImageLoadTask(HTMLImageElement *aElement, bool aAlwaysLoad)
+ : mElement(aElement)
+ , mAlwaysLoad(aAlwaysLoad)
+ {
+ mDocument = aElement->OwnerDoc();
+ mDocument->BlockOnload();
+ }
+
+ NS_IMETHOD Run() override
+ {
+ if (mElement->mPendingImageLoadTask == this) {
+ mElement->mPendingImageLoadTask = nullptr;
+ mElement->LoadSelectedImage(true, true, mAlwaysLoad);
+ }
+ mDocument->UnblockOnload(false);
+ return NS_OK;
+ }
+
+ bool AlwaysLoad() {
+ return mAlwaysLoad;
+ }
+
+private:
+ ~ImageLoadTask() {}
+ RefPtr<HTMLImageElement> mElement;
+ nsCOMPtr<nsIDocument> mDocument;
+ bool mAlwaysLoad;
+};
+
+HTMLImageElement::HTMLImageElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ , mForm(nullptr)
+ , mInDocResponsiveContent(false)
+ , mCurrentDensity(1.0)
+{
+ // We start out broken
+ AddStatesSilently(NS_EVENT_STATE_BROKEN);
+}
+
+HTMLImageElement::~HTMLImageElement()
+{
+ DestroyImageLoadingContent();
+}
+
+
+NS_IMPL_ADDREF_INHERITED(HTMLImageElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLImageElement, Element)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLImageElement,
+ nsGenericHTMLElement,
+ mResponsiveSelector)
+
+// QueryInterface implementation for HTMLImageElement
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLImageElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLImageElement,
+ nsIDOMHTMLImageElement,
+ nsIImageLoadingContent,
+ imgIOnloadBlocker,
+ imgINotificationObserver)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLElement)
+
+
+NS_IMPL_ELEMENT_CLONE(HTMLImageElement)
+
+
+NS_IMPL_STRING_ATTR(HTMLImageElement, Name, name)
+NS_IMPL_STRING_ATTR(HTMLImageElement, Align, align)
+NS_IMPL_STRING_ATTR(HTMLImageElement, Alt, alt)
+NS_IMPL_STRING_ATTR(HTMLImageElement, Border, border)
+NS_IMPL_INT_ATTR(HTMLImageElement, Hspace, hspace)
+NS_IMPL_BOOL_ATTR(HTMLImageElement, IsMap, ismap)
+NS_IMPL_URI_ATTR(HTMLImageElement, LongDesc, longdesc)
+NS_IMPL_STRING_ATTR(HTMLImageElement, Sizes, sizes)
+NS_IMPL_URI_ATTR(HTMLImageElement, Lowsrc, lowsrc)
+NS_IMPL_URI_ATTR(HTMLImageElement, Src, src)
+NS_IMPL_STRING_ATTR(HTMLImageElement, Srcset, srcset)
+NS_IMPL_STRING_ATTR(HTMLImageElement, UseMap, usemap)
+NS_IMPL_INT_ATTR(HTMLImageElement, Vspace, vspace)
+
+bool
+HTMLImageElement::IsInteractiveHTMLContent(bool aIgnoreTabindex) const
+{
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::usemap) ||
+ nsGenericHTMLElement::IsInteractiveHTMLContent(aIgnoreTabindex);
+}
+
+void
+HTMLImageElement::AsyncEventRunning(AsyncEventDispatcher* aEvent)
+{
+ nsImageLoadingContent::AsyncEventRunning(aEvent);
+}
+
+nsresult
+HTMLImageElement::GetCurrentSrc(nsAString& aValue)
+{
+ nsCOMPtr<nsIURI> currentURI;
+ GetCurrentURI(getter_AddRefs(currentURI));
+ if (currentURI) {
+ nsAutoCString spec;
+ currentURI->GetSpec(spec);
+ CopyUTF8toUTF16(spec, aValue);
+ } else {
+ SetDOMStringToNull(aValue);
+ }
+
+ return NS_OK;
+}
+
+bool
+HTMLImageElement::Draggable() const
+{
+ // images may be dragged unless the draggable attribute is false
+ return !AttrValueIs(kNameSpaceID_None, nsGkAtoms::draggable,
+ nsGkAtoms::_false, eIgnoreCase);
+}
+
+bool
+HTMLImageElement::Complete()
+{
+ if (!mCurrentRequest) {
+ return true;
+ }
+
+ if (mPendingRequest) {
+ return false;
+ }
+
+ uint32_t status;
+ mCurrentRequest->GetImageStatus(&status);
+ return
+ (status &
+ (imgIRequest::STATUS_LOAD_COMPLETE | imgIRequest::STATUS_ERROR)) != 0;
+}
+
+NS_IMETHODIMP
+HTMLImageElement::GetComplete(bool* aComplete)
+{
+ NS_PRECONDITION(aComplete, "Null out param!");
+
+ *aComplete = Complete();
+
+ return NS_OK;
+}
+
+CSSIntPoint
+HTMLImageElement::GetXY()
+{
+ nsIFrame* frame = GetPrimaryFrame(Flush_Layout);
+ if (!frame) {
+ return CSSIntPoint(0, 0);
+ }
+
+ nsIFrame* layer = nsLayoutUtils::GetClosestLayer(frame->GetParent());
+ return CSSIntPoint::FromAppUnitsRounded(frame->GetOffsetTo(layer));
+}
+
+int32_t
+HTMLImageElement::X()
+{
+ return GetXY().x;
+}
+
+int32_t
+HTMLImageElement::Y()
+{
+ return GetXY().y;
+}
+
+NS_IMETHODIMP
+HTMLImageElement::GetX(int32_t* aX)
+{
+ *aX = X();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLImageElement::GetY(int32_t* aY)
+{
+ *aY = Y();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLImageElement::GetHeight(uint32_t* aHeight)
+{
+ *aHeight = Height();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLImageElement::SetHeight(uint32_t aHeight)
+{
+ ErrorResult rv;
+ SetHeight(aHeight, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLImageElement::GetWidth(uint32_t* aWidth)
+{
+ *aWidth = Width();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLImageElement::SetWidth(uint32_t aWidth)
+{
+ ErrorResult rv;
+ SetWidth(aWidth, rv);
+ return rv.StealNSResult();
+}
+
+bool
+HTMLImageElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::align) {
+ return ParseAlignValue(aValue, aResult);
+ }
+ if (aAttribute == nsGkAtoms::crossorigin) {
+ ParseCORSValue(aValue, aResult);
+ return true;
+ }
+ if (ParseImageAttribute(aAttribute, aValue, aResult)) {
+ return true;
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLImageElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ nsGenericHTMLElement::MapImageAlignAttributeInto(aAttributes, aData);
+ nsGenericHTMLElement::MapImageBorderAttributeInto(aAttributes, aData);
+ nsGenericHTMLElement::MapImageMarginAttributeInto(aAttributes, aData);
+ nsGenericHTMLElement::MapImageSizeAttributesInto(aAttributes, aData);
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+nsChangeHint
+HTMLImageElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const
+{
+ nsChangeHint retval =
+ nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
+ if (aAttribute == nsGkAtoms::usemap ||
+ aAttribute == nsGkAtoms::ismap) {
+ retval |= nsChangeHint_ReconstructFrame;
+ } else if (aAttribute == nsGkAtoms::alt) {
+ if (aModType == nsIDOMMutationEvent::ADDITION ||
+ aModType == nsIDOMMutationEvent::REMOVAL) {
+ retval |= nsChangeHint_ReconstructFrame;
+ }
+ }
+ return retval;
+}
+
+NS_IMETHODIMP_(bool)
+HTMLImageElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry* const map[] = {
+ sCommonAttributeMap,
+ sImageMarginSizeAttributeMap,
+ sImageBorderAttributeMap,
+ sImageAlignAttributeMap
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+
+nsMapRuleToAttributesFunc
+HTMLImageElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+
+nsresult
+HTMLImageElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify)
+{
+
+ if (aNameSpaceID == kNameSpaceID_None && mForm &&
+ (aName == nsGkAtoms::name || aName == nsGkAtoms::id)) {
+ // remove the image from the hashtable as needed
+ nsAutoString tmp;
+ GetAttr(kNameSpaceID_None, aName, tmp);
+
+ if (!tmp.IsEmpty()) {
+ mForm->RemoveImageElementFromTable(this, tmp,
+ HTMLFormElement::AttributeUpdated);
+ }
+ }
+
+ return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+nsresult
+HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None && mForm &&
+ (aName == nsGkAtoms::name || aName == nsGkAtoms::id) &&
+ aValue && !aValue->IsEmptyString()) {
+ // add the image to the hashtable as needed
+ MOZ_ASSERT(aValue->Type() == nsAttrValue::eAtom,
+ "Expected atom value for name/id");
+ mForm->AddImageElementToTable(this,
+ nsDependentAtomString(aValue->GetAtomValue()));
+ }
+
+ // Handle src/srcset updates. If aNotify is false, we are coming from the
+ // parser or some such place; we'll get bound after all the attributes have
+ // been set, so we'll do the image load from BindToTree.
+
+ nsAttrValueOrString attrVal(aValue);
+
+ if (aName == nsGkAtoms::src &&
+ aNameSpaceID == kNameSpaceID_None &&
+ !aValue) {
+ // SetAttr handles setting src since it needs to catch img.src =
+ // img.src, so we only need to handle the unset case
+ if (InResponsiveMode()) {
+ if (mResponsiveSelector &&
+ mResponsiveSelector->Content() == this) {
+ mResponsiveSelector->SetDefaultSource(NullString());
+ }
+ QueueImageLoadTask(true);
+ } else {
+ // Bug 1076583 - We still behave synchronously in the non-responsive case
+ CancelImageRequests(aNotify);
+ }
+ } else if (aName == nsGkAtoms::srcset &&
+ aNameSpaceID == kNameSpaceID_None) {
+ PictureSourceSrcsetChanged(this, attrVal.String(), aNotify);
+ } else if (aName == nsGkAtoms::sizes &&
+ aNameSpaceID == kNameSpaceID_None) {
+ PictureSourceSizesChanged(this, attrVal.String(), aNotify);
+ }
+
+ return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+nsresult
+HTMLImageElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ // We handle image element with attribute ismap in its corresponding frame
+ // element. Set mMultipleActionsPrevented here to prevent the click event
+ // trigger the behaviors in Element::PostHandleEventForLinks
+ WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
+ if (mouseEvent && mouseEvent->IsLeftClickEvent() && IsMap()) {
+ mouseEvent->mFlags.mMultipleActionsPrevented = true;
+ }
+ return nsGenericHTMLElement::PreHandleEvent(aVisitor);
+}
+
+bool
+HTMLImageElement::IsHTMLFocusable(bool aWithMouse,
+ bool *aIsFocusable, int32_t *aTabIndex)
+{
+ int32_t tabIndex = TabIndex();
+
+ if (IsInUncomposedDoc()) {
+ nsAutoString usemap;
+ GetUseMap(usemap);
+ // XXXbz which document should this be using? sXBL/XBL2 issue! I
+ // think that OwnerDoc() is right, since we don't want to
+ // assume stuff about the document we're bound to.
+ if (OwnerDoc()->FindImageMap(usemap)) {
+ if (aTabIndex) {
+ // Use tab index on individual map areas
+ *aTabIndex = (sTabFocusModel & eTabFocus_linksMask)? 0 : -1;
+ }
+ // Image map is not focusable itself, but flag as tabbable
+ // so that image map areas get walked into.
+ *aIsFocusable = false;
+
+ return false;
+ }
+ }
+
+ if (aTabIndex) {
+ // Can be in tab order if tabindex >=0 and form controls are tabbable.
+ *aTabIndex = (sTabFocusModel & eTabFocus_formElementsMask)? tabIndex : -1;
+ }
+
+ *aIsFocusable =
+#ifdef XP_MACOSX
+ (!aWithMouse || nsFocusManager::sMouseFocusesFormControl) &&
+#endif
+ (tabIndex >= 0 || HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex));
+
+ return false;
+}
+
+nsresult
+HTMLImageElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify)
+{
+ bool forceReload = false;
+ // We need to force our image to reload. This must be done here, not in
+ // AfterSetAttr or BeforeSetAttr, because we want to do it even if the attr is
+ // being set to its existing value, which is normally optimized away as a
+ // no-op.
+ //
+ // If we are in responsive mode, we drop the forced reload behavior,
+ // but still trigger a image load task for img.src = img.src per
+ // spec.
+ //
+ // Both cases handle unsetting src in AfterSetAttr
+ if (aNameSpaceID == kNameSpaceID_None &&
+ aName == nsGkAtoms::src) {
+
+ if (InResponsiveMode()) {
+ if (mResponsiveSelector &&
+ mResponsiveSelector->Content() == this) {
+ mResponsiveSelector->SetDefaultSource(aValue);
+ }
+ QueueImageLoadTask(true);
+ } else if (aNotify) {
+ // If aNotify is false, we are coming from the parser or some such place;
+ // we'll get bound after all the attributes have been set, so we'll do the
+ // sync image load from BindToTree. Skip the LoadImage call in that case.
+
+ // Note that this sync behavior is partially removed from the spec, bug 1076583
+
+ // A hack to get animations to reset. See bug 594771.
+ mNewRequestsWillNeedAnimationReset = true;
+
+ // Force image loading here, so that we'll try to load the image from
+ // network if it's set to be not cacheable... If we change things so that
+ // the state gets in Element's attr-setting happen around this
+ // LoadImage call, we could start passing false instead of aNotify
+ // here.
+ LoadImage(aValue, true, aNotify, eImageLoadType_Normal);
+
+ mNewRequestsWillNeedAnimationReset = false;
+ }
+ } else if (aNameSpaceID == kNameSpaceID_None &&
+ aName == nsGkAtoms::crossorigin &&
+ aNotify) {
+ nsAttrValue attrValue;
+ ParseCORSValue(aValue, attrValue);
+ if (GetCORSMode() != AttrValueToCORSMode(&attrValue)) {
+ // Force a new load of the image with the new cross origin policy.
+ forceReload = true;
+ }
+ } else if (aName == nsGkAtoms::referrerpolicy &&
+ aNameSpaceID == kNameSpaceID_None &&
+ aNotify) {
+ ReferrerPolicy referrerPolicy = AttributeReferrerPolicyFromString(aValue);
+ if (!InResponsiveMode() &&
+ referrerPolicy != RP_Unset &&
+ referrerPolicy != GetImageReferrerPolicy()) {
+ // XXX: Bug 1076583 - We still use the older synchronous algorithm
+ // Because referrerPolicy is not treated as relevant mutations, setting
+ // the attribute will neither trigger a reload nor update the referrer
+ // policy of the loading channel (whether it has previously completed or
+ // not). Force a new load of the image with the new referrerpolicy.
+ forceReload = true;
+ }
+ }
+
+ nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
+ aValue, aNotify);
+
+ // Because we load image synchronously in non-responsive-mode, we need to do
+ // reload after the attribute has been set if the reload is triggerred by
+ // cross origin changing.
+ if (forceReload) {
+ if (InResponsiveMode()) {
+ // per spec, full selection runs when this changes, even though
+ // it doesn't directly affect the source selection
+ QueueImageLoadTask(true);
+ } else {
+ // Bug 1076583 - We still use the older synchronous algorithm in
+ // non-responsive mode. Force a new load of the image with the
+ // new cross origin policy
+ ForceReload(aNotify);
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+HTMLImageElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsImageLoadingContent::BindToTree(aDocument, aParent, aBindingParent,
+ aCompileEventHandlers);
+
+ if (aParent) {
+ UpdateFormOwner();
+ }
+
+ if (HaveSrcsetOrInPicture()) {
+ if (aDocument && !mInDocResponsiveContent) {
+ aDocument->AddResponsiveContent(this);
+ mInDocResponsiveContent = true;
+ }
+
+ // Run selection algorithm when an img element is inserted into a document
+ // in order to react to changes in the environment. See note of
+ // https://html.spec.whatwg.org/multipage/embedded-content.html#img-environment-changes
+ QueueImageLoadTask(false);
+ } else if (!InResponsiveMode() &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
+ // We skip loading when our attributes were set from parser land,
+ // so trigger a aForce=false load now to check if things changed.
+ // This isn't necessary for responsive mode, since creating the
+ // image load task is asynchronous we don't need to take special
+ // care to avoid doing so when being filled by the parser.
+
+ // FIXME: Bug 660963 it would be nice if we could just have
+ // ClearBrokenState update our state and do it fast...
+ ClearBrokenState();
+ RemoveStatesSilently(NS_EVENT_STATE_BROKEN);
+
+ // We still act synchronously for the non-responsive case (Bug
+ // 1076583), but still need to delay if it is unsafe to run
+ // script.
+
+ // If loading is temporarily disabled, don't even launch MaybeLoadImage.
+ // Otherwise MaybeLoadImage may run later when someone has reenabled
+ // loading.
+ if (LoadingEnabled()) {
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod(this, &HTMLImageElement::MaybeLoadImage));
+ }
+ }
+
+ return rv;
+}
+
+void
+HTMLImageElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ if (mForm) {
+ if (aNullParent || !FindAncestorForm(mForm)) {
+ ClearForm(true);
+ } else {
+ UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
+ }
+ }
+
+ if (mInDocResponsiveContent) {
+ nsIDocument* doc = GetOurOwnerDoc();
+ MOZ_ASSERT(doc);
+ if (doc) {
+ doc->RemoveResponsiveContent(this);
+ mInDocResponsiveContent = false;
+ }
+ }
+
+ mLastSelectedSource = nullptr;
+
+ nsImageLoadingContent::UnbindFromTree(aDeep, aNullParent);
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+void
+HTMLImageElement::UpdateFormOwner()
+{
+ if (!mForm) {
+ mForm = FindAncestorForm();
+ }
+
+ if (mForm && !HasFlag(ADDED_TO_FORM)) {
+ // Now we need to add ourselves to the form
+ nsAutoString nameVal, idVal;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, nameVal);
+ GetAttr(kNameSpaceID_None, nsGkAtoms::id, idVal);
+
+ SetFlags(ADDED_TO_FORM);
+
+ mForm->AddImageElement(this);
+
+ if (!nameVal.IsEmpty()) {
+ mForm->AddImageElementToTable(this, nameVal);
+ }
+
+ if (!idVal.IsEmpty()) {
+ mForm->AddImageElementToTable(this, idVal);
+ }
+ }
+}
+
+void
+HTMLImageElement::MaybeLoadImage()
+{
+ // Our base URI may have changed, or we may have had responsive parameters
+ // change while not bound to the tree. Re-parse src/srcset and call LoadImage,
+ // which is a no-op if it resolves to the same effective URI without aForce.
+
+ // Note, check LoadingEnabled() after LoadImage call.
+
+ LoadSelectedImage(false, true, false);
+
+ if (!LoadingEnabled()) {
+ CancelImageRequests(true);
+ }
+}
+
+EventStates
+HTMLImageElement::IntrinsicState() const
+{
+ return nsGenericHTMLElement::IntrinsicState() |
+ nsImageLoadingContent::ImageState();
+}
+
+void
+HTMLImageElement::NodeInfoChanged()
+{
+ // Resetting the last selected source if adoption steps are run.
+ mLastSelectedSource = nullptr;
+}
+
+// static
+already_AddRefed<HTMLImageElement>
+HTMLImageElement::Image(const GlobalObject& aGlobal,
+ const Optional<uint32_t>& aWidth,
+ const Optional<uint32_t>& aHeight,
+ ErrorResult& aError)
+{
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal.GetAsSupports());
+ nsIDocument* doc;
+ if (!win || !(doc = win->GetExtantDoc())) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ already_AddRefed<mozilla::dom::NodeInfo> nodeInfo =
+ doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::img, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+
+ RefPtr<HTMLImageElement> img = new HTMLImageElement(nodeInfo);
+
+ if (aWidth.WasPassed()) {
+ img->SetWidth(aWidth.Value(), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ if (aHeight.WasPassed()) {
+ img->SetHeight(aHeight.Value(), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ }
+ }
+
+ return img.forget();
+}
+
+uint32_t
+HTMLImageElement::NaturalHeight()
+{
+ uint32_t height;
+ nsresult rv = nsImageLoadingContent::GetNaturalHeight(&height);
+
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false, "GetNaturalHeight should not fail");
+ return 0;
+ }
+
+ if (mResponsiveSelector) {
+ double density = mResponsiveSelector->GetSelectedImageDensity();
+ MOZ_ASSERT(density >= 0.0);
+ height = NSToIntRound(double(height) / density);
+ height = std::max(height, 0u);
+ }
+
+ return height;
+}
+
+NS_IMETHODIMP
+HTMLImageElement::GetNaturalHeight(uint32_t* aNaturalHeight)
+{
+ *aNaturalHeight = NaturalHeight();
+ return NS_OK;
+}
+
+uint32_t
+HTMLImageElement::NaturalWidth()
+{
+ uint32_t width;
+ nsresult rv = nsImageLoadingContent::GetNaturalWidth(&width);
+
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false, "GetNaturalWidth should not fail");
+ return 0;
+ }
+
+ if (mResponsiveSelector) {
+ double density = mResponsiveSelector->GetSelectedImageDensity();
+ MOZ_ASSERT(density >= 0.0);
+ width = NSToIntRound(double(width) / density);
+ width = std::max(width, 0u);
+ }
+
+ return width;
+}
+
+NS_IMETHODIMP
+HTMLImageElement::GetNaturalWidth(uint32_t* aNaturalWidth)
+{
+ *aNaturalWidth = NaturalWidth();
+ return NS_OK;
+}
+
+nsresult
+HTMLImageElement::CopyInnerTo(Element* aDest)
+{
+ bool destIsStatic = aDest->OwnerDoc()->IsStaticDocument();
+ auto dest = static_cast<HTMLImageElement*>(aDest);
+ if (destIsStatic) {
+ CreateStaticImageClone(dest);
+ }
+
+ nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!destIsStatic) {
+ // In SetAttr (called from nsGenericHTMLElement::CopyInnerTo), dest skipped
+ // doing the image load because we passed in false for aNotify. But we
+ // really do want it to do the load, so set it up to happen once the cloning
+ // reaches a stable state.
+ if (!dest->InResponsiveMode() &&
+ dest->HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod(dest, &HTMLImageElement::MaybeLoadImage));
+ }
+ }
+
+ return NS_OK;
+}
+
+CORSMode
+HTMLImageElement::GetCORSMode()
+{
+ return AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin));
+}
+
+JSObject*
+HTMLImageElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLImageElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+#ifdef DEBUG
+nsIDOMHTMLFormElement*
+HTMLImageElement::GetForm() const
+{
+ return mForm;
+}
+#endif
+
+void
+HTMLImageElement::SetForm(nsIDOMHTMLFormElement* aForm)
+{
+ NS_PRECONDITION(aForm, "Don't pass null here");
+ NS_ASSERTION(!mForm,
+ "We don't support switching from one non-null form to another.");
+
+ mForm = static_cast<HTMLFormElement*>(aForm);
+}
+
+void
+HTMLImageElement::ClearForm(bool aRemoveFromForm)
+{
+ NS_ASSERTION((mForm != nullptr) == HasFlag(ADDED_TO_FORM),
+ "Form control should have had flag set correctly");
+
+ if (!mForm) {
+ return;
+ }
+
+ if (aRemoveFromForm) {
+ nsAutoString nameVal, idVal;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, nameVal);
+ GetAttr(kNameSpaceID_None, nsGkAtoms::id, idVal);
+
+ mForm->RemoveImageElement(this);
+
+ if (!nameVal.IsEmpty()) {
+ mForm->RemoveImageElementFromTable(this, nameVal,
+ HTMLFormElement::ElementRemoved);
+ }
+
+ if (!idVal.IsEmpty()) {
+ mForm->RemoveImageElementFromTable(this, idVal,
+ HTMLFormElement::ElementRemoved);
+ }
+ }
+
+ UnsetFlags(ADDED_TO_FORM);
+ mForm = nullptr;
+}
+
+void
+HTMLImageElement::QueueImageLoadTask(bool aAlwaysLoad)
+{
+ // If loading is temporarily disabled, we don't want to queue tasks
+ // that may then run when loading is re-enabled.
+ if (!LoadingEnabled() || !this->OwnerDoc()->IsCurrentActiveDocument()) {
+ return;
+ }
+
+ // Ensure that we don't overwrite a previous load request that requires
+ // a complete load to occur.
+ bool alwaysLoad = aAlwaysLoad;
+ if (mPendingImageLoadTask) {
+ alwaysLoad = alwaysLoad || mPendingImageLoadTask->AlwaysLoad();
+ }
+ RefPtr<ImageLoadTask> task = new ImageLoadTask(this, alwaysLoad);
+ // The task checks this to determine if it was the last
+ // queued event, and so earlier tasks are implicitly canceled.
+ mPendingImageLoadTask = task;
+ nsContentUtils::RunInStableState(task.forget());
+}
+
+bool
+HTMLImageElement::HaveSrcsetOrInPicture()
+{
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::srcset)) {
+ return true;
+ }
+
+ Element *parent = nsINode::GetParentElement();
+ return (parent && parent->IsHTMLElement(nsGkAtoms::picture));
+}
+
+bool
+HTMLImageElement::InResponsiveMode()
+{
+ // When we lose srcset or leave a <picture> element, the fallback to img.src
+ // will happen from the microtask, and we should behave responsively in the
+ // interim
+ return mResponsiveSelector ||
+ mPendingImageLoadTask ||
+ HaveSrcsetOrInPicture();
+}
+
+bool
+HTMLImageElement::SelectedSourceMatchesLast(nsIURI* aSelectedSource, double aSelectedDensity)
+{
+ // If there was no selected source previously, we don't want to short-circuit the load.
+ // Similarly for if there is no newly selected source.
+ if (!mLastSelectedSource || !aSelectedSource) {
+ return false;
+ }
+ bool equal = false;
+ return NS_SUCCEEDED(mLastSelectedSource->Equals(aSelectedSource, &equal)) && equal &&
+ aSelectedDensity == mCurrentDensity;
+}
+
+nsresult
+HTMLImageElement::LoadSelectedImage(bool aForce, bool aNotify, bool aAlwaysLoad)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (aForce) {
+ // In responsive mode we generally want to re-run the full
+ // selection algorithm whenever starting a new load, per
+ // spec. This also causes us to re-resolve the URI as appropriate.
+ if (!UpdateResponsiveSource() && !aAlwaysLoad) {
+ return NS_OK;
+ }
+ }
+
+ nsCOMPtr<nsIURI> selectedSource;
+ double currentDensity = 1.0; // default to 1.0 for the src attribute case
+ if (mResponsiveSelector) {
+ nsCOMPtr<nsIURI> url = mResponsiveSelector->GetSelectedImageURL();
+ selectedSource = url;
+ currentDensity = mResponsiveSelector->GetSelectedImageDensity();
+ if (!aAlwaysLoad && SelectedSourceMatchesLast(selectedSource, currentDensity)) {
+ return NS_OK;
+ }
+ if (url) {
+ rv = LoadImage(url, aForce, aNotify, eImageLoadType_Imageset);
+ }
+ } else {
+ nsAutoString src;
+ if (!GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
+ CancelImageRequests(aNotify);
+ rv = NS_OK;
+ } else {
+ nsIDocument* doc = GetOurOwnerDoc();
+ if (doc) {
+ StringToURI(src, doc, getter_AddRefs(selectedSource));
+ if (!aAlwaysLoad && SelectedSourceMatchesLast(selectedSource, currentDensity)) {
+ return NS_OK;
+ }
+ }
+
+ // If we have a srcset attribute or are in a <picture> element,
+ // we always use the Imageset load type, even if we parsed no
+ // valid responsive sources from either, per spec.
+ rv = LoadImage(src, aForce, aNotify,
+ HaveSrcsetOrInPicture() ? eImageLoadType_Imageset
+ : eImageLoadType_Normal);
+ }
+ }
+ mLastSelectedSource = selectedSource;
+ mCurrentDensity = currentDensity;
+
+ if (NS_FAILED(rv)) {
+ CancelImageRequests(aNotify);
+ }
+ return rv;
+}
+
+void
+HTMLImageElement::PictureSourceSrcsetChanged(nsIContent *aSourceNode,
+ const nsAString& aNewValue,
+ bool aNotify)
+{
+ MOZ_ASSERT(aSourceNode == this ||
+ IsPreviousSibling(aSourceNode, this),
+ "Should not be getting notifications for non-previous-siblings");
+
+ nsIContent *currentSrc =
+ mResponsiveSelector ? mResponsiveSelector->Content() : nullptr;
+
+ if (aSourceNode == currentSrc) {
+ // We're currently using this node as our responsive selector
+ // source.
+ mResponsiveSelector->SetCandidatesFromSourceSet(aNewValue);
+ }
+
+ if (!mInDocResponsiveContent && IsInComposedDoc()) {
+ nsIDocument* doc = GetOurOwnerDoc();
+ if (doc) {
+ doc->AddResponsiveContent(this);
+ mInDocResponsiveContent = true;
+ }
+ }
+
+ // This always triggers the image update steps per the spec, even if
+ // we are not using this source.
+ QueueImageLoadTask(true);
+}
+
+void
+HTMLImageElement::PictureSourceSizesChanged(nsIContent *aSourceNode,
+ const nsAString& aNewValue,
+ bool aNotify)
+{
+ MOZ_ASSERT(aSourceNode == this ||
+ IsPreviousSibling(aSourceNode, this),
+ "Should not be getting notifications for non-previous-siblings");
+
+ nsIContent *currentSrc =
+ mResponsiveSelector ? mResponsiveSelector->Content() : nullptr;
+
+ if (aSourceNode == currentSrc) {
+ // We're currently using this node as our responsive selector
+ // source.
+ mResponsiveSelector->SetSizesFromDescriptor(aNewValue);
+ }
+
+ // This always triggers the image update steps per the spec, even if
+ // we are not using this source.
+ QueueImageLoadTask(true);
+}
+
+void
+HTMLImageElement::PictureSourceMediaOrTypeChanged(nsIContent *aSourceNode,
+ bool aNotify)
+{
+ MOZ_ASSERT(IsPreviousSibling(aSourceNode, this),
+ "Should not be getting notifications for non-previous-siblings");
+
+ // This always triggers the image update steps per the spec, even if
+ // we are not switching to/from this source
+ QueueImageLoadTask(true);
+}
+
+void
+HTMLImageElement::PictureSourceAdded(nsIContent *aSourceNode)
+{
+ MOZ_ASSERT(aSourceNode == this ||
+ IsPreviousSibling(aSourceNode, this),
+ "Should not be getting notifications for non-previous-siblings");
+
+ QueueImageLoadTask(true);
+}
+
+void
+HTMLImageElement::PictureSourceRemoved(nsIContent *aSourceNode)
+{
+ MOZ_ASSERT(aSourceNode == this ||
+ IsPreviousSibling(aSourceNode, this),
+ "Should not be getting notifications for non-previous-siblings");
+
+ QueueImageLoadTask(true);
+}
+
+bool
+HTMLImageElement::UpdateResponsiveSource()
+{
+ bool hadSelector = !!mResponsiveSelector;
+
+ nsIContent *currentSource =
+ mResponsiveSelector ? mResponsiveSelector->Content() : nullptr;
+ Element *parent = nsINode::GetParentElement();
+
+ nsINode *candidateSource = nullptr;
+ if (parent && parent->IsHTMLElement(nsGkAtoms::picture)) {
+ // Walk source nodes previous to ourselves
+ candidateSource = parent->GetFirstChild();
+ } else {
+ candidateSource = this;
+ }
+
+ while (candidateSource) {
+ if (candidateSource == currentSource) {
+ // found no better source before current, re-run selection on
+ // that and keep it if it's still usable.
+ bool changed = mResponsiveSelector->SelectImage(true);
+ if (mResponsiveSelector->NumCandidates()) {
+ bool isUsableCandidate = true;
+
+ // an otherwise-usable source element may still have a media query that may not
+ // match any more.
+ if (candidateSource->IsHTMLElement(nsGkAtoms::source) &&
+ !SourceElementMatches(candidateSource->AsContent())) {
+ isUsableCandidate = false;
+ }
+
+ if (isUsableCandidate) {
+ return changed;
+ }
+ }
+
+ // no longer valid
+ mResponsiveSelector = nullptr;
+ if (candidateSource == this) {
+ // No further possibilities
+ break;
+ }
+ } else if (candidateSource == this) {
+ // We are the last possible source
+ if (!TryCreateResponsiveSelector(candidateSource->AsContent())) {
+ // Failed to find any source
+ mResponsiveSelector = nullptr;
+ }
+ break;
+ } else if (candidateSource->IsHTMLElement(nsGkAtoms::source) &&
+ TryCreateResponsiveSelector(candidateSource->AsContent())) {
+ // This led to a valid source, stop
+ break;
+ }
+ candidateSource = candidateSource->GetNextSibling();
+ }
+
+ if (!candidateSource) {
+ // Ran out of siblings without finding ourself, e.g. XBL magic.
+ mResponsiveSelector = nullptr;
+ }
+
+ // If we reach this point, either:
+ // - there was no selector originally, and there is not one now
+ // - there was no selector originally, and there is one now
+ // - there was a selector, and there is a different one now
+ // - there was a selector, and there is not one now
+ return hadSelector || mResponsiveSelector;
+}
+
+/*static */ bool
+HTMLImageElement::SupportedPictureSourceType(const nsAString& aType)
+{
+ nsAutoString type;
+ nsAutoString params;
+
+ nsContentUtils::SplitMimeType(aType, type, params);
+ if (type.IsEmpty()) {
+ return true;
+ }
+
+ return
+ imgLoader::SupportImageWithMimeType(NS_ConvertUTF16toUTF8(type).get(),
+ AcceptedMimeTypes::IMAGES_AND_DOCUMENTS);
+}
+
+bool
+HTMLImageElement::SourceElementMatches(nsIContent* aSourceNode)
+{
+ MOZ_ASSERT(aSourceNode->IsHTMLElement(nsGkAtoms::source));
+
+ DebugOnly<Element *> parent(nsINode::GetParentElement());
+ MOZ_ASSERT(parent && parent->IsHTMLElement(nsGkAtoms::picture));
+ MOZ_ASSERT(IsPreviousSibling(aSourceNode, this));
+
+ // Check media and type
+ HTMLSourceElement *src = static_cast<HTMLSourceElement*>(aSourceNode);
+ if (!src->MatchesCurrentMedia()) {
+ return false;
+ }
+
+ nsAutoString type;
+ if (aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type) &&
+ !SupportedPictureSourceType(type)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+HTMLImageElement::TryCreateResponsiveSelector(nsIContent *aSourceNode)
+{
+ // Skip if this is not a <source> with matching media query
+ bool isSourceTag = aSourceNode->IsHTMLElement(nsGkAtoms::source);
+ if (isSourceTag) {
+ if (!SourceElementMatches(aSourceNode)) {
+ return false;
+ }
+ } else if (aSourceNode->IsHTMLElement(nsGkAtoms::img)) {
+ // Otherwise this is the <img> tag itself
+ MOZ_ASSERT(aSourceNode == this);
+ }
+
+ // Skip if has no srcset or an empty srcset
+ nsString srcset;
+ if (!aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::srcset, srcset)) {
+ return false;
+ }
+
+ if (srcset.IsEmpty()) {
+ return false;
+ }
+
+
+ // Try to parse
+ RefPtr<ResponsiveImageSelector> sel = new ResponsiveImageSelector(aSourceNode);
+ if (!sel->SetCandidatesFromSourceSet(srcset)) {
+ // No possible candidates, don't need to bother parsing sizes
+ return false;
+ }
+
+ nsAutoString sizes;
+ aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::sizes, sizes);
+ sel->SetSizesFromDescriptor(sizes);
+
+ // If this is the <img> tag, also pull in src as the default source
+ if (!isSourceTag) {
+ MOZ_ASSERT(aSourceNode == this);
+ nsAutoString src;
+ if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src) && !src.IsEmpty()) {
+ sel->SetDefaultSource(src);
+ }
+ }
+
+ mResponsiveSelector = sel;
+ return true;
+}
+
+/* static */ bool
+HTMLImageElement::SelectSourceForTagWithAttrs(nsIDocument *aDocument,
+ bool aIsSourceTag,
+ const nsAString& aSrcAttr,
+ const nsAString& aSrcsetAttr,
+ const nsAString& aSizesAttr,
+ const nsAString& aTypeAttr,
+ const nsAString& aMediaAttr,
+ nsAString& aResult)
+{
+ MOZ_ASSERT(aIsSourceTag || (aTypeAttr.IsEmpty() && aMediaAttr.IsEmpty()),
+ "Passing type or media attrs makes no sense without aIsSourceTag");
+ MOZ_ASSERT(!aIsSourceTag || aSrcAttr.IsEmpty(),
+ "Passing aSrcAttr makes no sense with aIsSourceTag set");
+
+ if (aSrcsetAttr.IsEmpty()) {
+ if (!aIsSourceTag) {
+ // For an <img> with no srcset, we would always select the src attr.
+ aResult.Assign(aSrcAttr);
+ return true;
+ }
+ // Otherwise, a <source> without srcset is never selected
+ return false;
+ }
+
+ // Would not consider source tags with unsupported media or type
+ if (aIsSourceTag &&
+ ((!aMediaAttr.IsVoid() &&
+ !HTMLSourceElement::WouldMatchMediaForDocument(aMediaAttr, aDocument)) ||
+ (!aTypeAttr.IsVoid() &&
+ !SupportedPictureSourceType(aTypeAttr)))) {
+ return false;
+ }
+
+ // Using srcset or picture <source>, build a responsive selector for this tag.
+ RefPtr<ResponsiveImageSelector> sel =
+ new ResponsiveImageSelector(aDocument);
+
+ sel->SetCandidatesFromSourceSet(aSrcsetAttr);
+ if (!aSizesAttr.IsEmpty()) {
+ sel->SetSizesFromDescriptor(aSizesAttr);
+ }
+ if (!aIsSourceTag) {
+ sel->SetDefaultSource(aSrcAttr);
+ }
+
+ if (sel->GetSelectedImageURLSpec(aResult)) {
+ return true;
+ }
+
+ if (!aIsSourceTag) {
+ // <img> tag with no match would definitively load nothing.
+ aResult.Truncate();
+ return true;
+ }
+
+ // <source> tags with no match would leave source yet-undetermined.
+ return false;
+}
+
+void
+HTMLImageElement::DestroyContent()
+{
+ mResponsiveSelector = nullptr;
+
+ nsGenericHTMLElement::DestroyContent();
+}
+
+void
+HTMLImageElement::MediaFeatureValuesChanged()
+{
+ QueueImageLoadTask(false);
+}
+
+void
+HTMLImageElement::FlushUseCounters()
+{
+ nsCOMPtr<imgIRequest> request;
+ GetRequest(CURRENT_REQUEST, getter_AddRefs(request));
+
+ nsCOMPtr<imgIContainer> container;
+ request->GetImage(getter_AddRefs(container));
+
+ static_cast<image::Image*>(container.get())->ReportUseCounters();
+}
+
+} // namespace dom
+} // namespace mozilla
+
diff --git a/dom/html/HTMLImageElement.h b/dom/html/HTMLImageElement.h
new file mode 100644
index 000000000..62323e801
--- /dev/null
+++ b/dom/html/HTMLImageElement.h
@@ -0,0 +1,377 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_HTMLImageElement_h
+#define mozilla_dom_HTMLImageElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsImageLoadingContent.h"
+#include "nsIDOMHTMLImageElement.h"
+#include "imgRequestProxy.h"
+#include "Units.h"
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla {
+class EventChainPreVisitor;
+namespace dom {
+
+class ImageLoadTask;
+
+class ResponsiveImageSelector;
+class HTMLImageElement final : public nsGenericHTMLElement,
+ public nsImageLoadingContent,
+ public nsIDOMHTMLImageElement
+{
+ friend class HTMLSourceElement;
+ friend class HTMLPictureElement;
+ friend class ImageLoadTask;
+public:
+ explicit HTMLImageElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ static already_AddRefed<HTMLImageElement>
+ Image(const GlobalObject& aGlobal,
+ const Optional<uint32_t>& aWidth,
+ const Optional<uint32_t>& aHeight,
+ ErrorResult& aError);
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLImageElement,
+ nsGenericHTMLElement)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual bool Draggable() const override;
+
+ // Element
+ virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
+
+ // EventTarget
+ virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;
+
+ // nsIDOMHTMLImageElement
+ NS_DECL_NSIDOMHTMLIMAGEELEMENT
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLImageElement, img)
+
+ // override from nsImageLoadingContent
+ CORSMode GetCORSMode() override;
+
+ // nsIContent
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+
+ virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) override;
+
+ bool IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable, int32_t *aTabIndex) override;
+
+ // SetAttr override. C++ is stupid, so have to override both
+ // overloaded methods.
+ 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;
+
+ 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 Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ virtual void NodeInfoChanged() override;
+
+ nsresult CopyInnerTo(Element* aDest);
+
+ void MaybeLoadImage();
+
+ bool IsMap()
+ {
+ return GetBoolAttr(nsGkAtoms::ismap);
+ }
+ void SetIsMap(bool aIsMap, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::ismap, aIsMap, aError);
+ }
+ uint32_t Width()
+ {
+ return GetWidthHeightForImage(mCurrentRequest).width;
+ }
+ void SetWidth(uint32_t aWidth, ErrorResult& aError)
+ {
+ SetUnsignedIntAttr(nsGkAtoms::width, aWidth, 0, aError);
+ }
+ uint32_t Height()
+ {
+ return GetWidthHeightForImage(mCurrentRequest).height;
+ }
+ void SetHeight(uint32_t aHeight, ErrorResult& aError)
+ {
+ SetUnsignedIntAttr(nsGkAtoms::height, aHeight, 0, aError);
+ }
+ uint32_t NaturalWidth();
+ uint32_t NaturalHeight();
+ bool Complete();
+ uint32_t Hspace()
+ {
+ return GetUnsignedIntAttr(nsGkAtoms::hspace, 0);
+ }
+ void SetHspace(uint32_t aHspace, ErrorResult& aError)
+ {
+ SetUnsignedIntAttr(nsGkAtoms::hspace, aHspace, 0, aError);
+ }
+ uint32_t Vspace()
+ {
+ return GetUnsignedIntAttr(nsGkAtoms::vspace, 0);
+ }
+ void SetVspace(uint32_t aVspace, ErrorResult& aError)
+ {
+ SetUnsignedIntAttr(nsGkAtoms::vspace, aVspace, 0, aError);
+ }
+
+ // The XPCOM versions of the following getters work for Web IDL bindings as well
+ void SetAlt(const nsAString& aAlt, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::alt, aAlt, aError);
+ }
+ void SetSrc(const nsAString& aSrc, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::src, aSrc, aError);
+ }
+ void SetSrcset(const nsAString& aSrcset, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::srcset, aSrcset, aError);
+ }
+ void GetCrossOrigin(nsAString& aResult)
+ {
+ // Null for both missing and invalid defaults is ok, since we
+ // always parse to an enum value, so we don't need an invalid
+ // default, and we _want_ the missing default to be null.
+ GetEnumAttr(nsGkAtoms::crossorigin, nullptr, aResult);
+ }
+ void SetCrossOrigin(const nsAString& aCrossOrigin, ErrorResult& aError)
+ {
+ SetOrRemoveNullableStringAttr(nsGkAtoms::crossorigin, aCrossOrigin, aError);
+ }
+ void SetUseMap(const nsAString& aUseMap, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::usemap, aUseMap, aError);
+ }
+ void SetName(const nsAString& aName, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::name, aName, aError);
+ }
+ void SetAlign(const nsAString& aAlign, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::align, aAlign, aError);
+ }
+ void SetLongDesc(const nsAString& aLongDesc, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::longdesc, aLongDesc, aError);
+ }
+ void SetSizes(const nsAString& aSizes, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::sizes, aSizes, aError);
+ }
+ void SetBorder(const nsAString& aBorder, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::border, aBorder, aError);
+ }
+ void SetReferrerPolicy(const nsAString& aReferrer, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::referrerpolicy, aReferrer, aError);
+ }
+ void GetReferrerPolicy(nsAString& aReferrer)
+ {
+ GetEnumAttr(nsGkAtoms::referrerpolicy, EmptyCString().get(), aReferrer);
+ }
+
+ net::ReferrerPolicy
+ GetImageReferrerPolicy() override
+ {
+ return GetReferrerPolicyAsEnum();
+ }
+
+ int32_t X();
+ int32_t Y();
+ // Uses XPCOM GetLowsrc.
+ void SetLowsrc(const nsAString& aLowsrc, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::lowsrc, aLowsrc, aError);
+ }
+
+#ifdef DEBUG
+ nsIDOMHTMLFormElement* GetForm() const;
+#endif
+ void SetForm(nsIDOMHTMLFormElement* aForm);
+ void ClearForm(bool aRemoveFromForm);
+
+ virtual void DestroyContent() override;
+
+ void MediaFeatureValuesChanged();
+
+ /**
+ * Given a hypothetical <img> or <source> tag with the given parameters,
+ * return what URI we would attempt to use, if any. Used by the preloader to
+ * resolve sources prior to DOM creation.
+ *
+ * @param aDocument The document this image would be for, for referencing
+ * viewport width and DPI/zoom
+ * @param aIsSourceTag If these parameters are for a <source> tag (as in a
+ * <picture>) rather than an <img> tag. Note that some attrs are unused
+ * when this is true an vice versa
+ * @param aSrcAttr [ignored if aIsSourceTag] The src attr for this image.
+ * @param aSrcsetAttr The srcset attr for this image/source
+ * @param aSizesAttr The sizes attr for this image/source
+ * @param aTypeAttr [ignored if !aIsSourceTag] The type attr for this source.
+ * Should be a void string to differentiate no type attribute
+ * from an empty one.
+ * @param aMediaAttr [ignored if !aIsSourceTag] The media attr for this
+ * source. Should be a void string to differentiate no
+ * media attribute from an empty one.
+ * @param aResult A reference to store the resulting URL spec in if we
+ * selected a source. This value is not guaranteed to parse to
+ * a valid URL, merely the URL that the tag would attempt to
+ * resolve and load (which may be the empty string). This
+ * parameter is not modified if return value is false.
+ * @return True if we were able to select a final source, false if further
+ * sources would be considered. It follows that this always returns
+ * true if !aIsSourceTag.
+ *
+ * Note that the return value may be true with an empty string as the result,
+ * which implies that the parameters provided describe a tag that would select
+ * no source. This is distinct from a return of false which implies that
+ * further <source> or <img> tags would be considered.
+ */
+ static bool
+ SelectSourceForTagWithAttrs(nsIDocument *aDocument,
+ bool aIsSourceTag,
+ const nsAString& aSrcAttr,
+ const nsAString& aSrcsetAttr,
+ const nsAString& aSizesAttr,
+ const nsAString& aTypeAttr,
+ const nsAString& aMediaAttr,
+ nsAString& aResult);
+
+ /**
+ * If this image's src pointers to an SVG document, flush the SVG document's
+ * use counters to telemetry. Only used for testing purposes.
+ */
+ void FlushUseCounters();
+
+protected:
+ virtual ~HTMLImageElement();
+
+ // Queues a task to run LoadSelectedImage pending stable state.
+ //
+ // Pending Bug 1076583 this is only used by the responsive image
+ // algorithm (InResponsiveMode()) -- synchronous actions when just
+ // using img.src will bypass this, and update source and kick off
+ // image load synchronously.
+ void QueueImageLoadTask(bool aAlwaysLoad);
+
+ // True if we have a srcset attribute or a <picture> parent, regardless of if
+ // any valid responsive sources were parsed from either.
+ bool HaveSrcsetOrInPicture();
+
+ // True if we are using the newer image loading algorithm. This will be the
+ // only mode after Bug 1076583
+ bool InResponsiveMode();
+
+ // True if the given URL and density equal the last URL and density that was loaded by this element.
+ bool SelectedSourceMatchesLast(nsIURI* aSelectedSource, double aSelectedDensity);
+
+ // Resolve and load the current mResponsiveSelector (responsive mode) or src
+ // attr image.
+ nsresult LoadSelectedImage(bool aForce, bool aNotify, bool aAlwaysLoad);
+
+ // True if this string represents a type we would support on <source type>
+ static bool SupportedPictureSourceType(const nsAString& aType);
+
+ // Update/create/destroy mResponsiveSelector
+ void PictureSourceSrcsetChanged(nsIContent *aSourceNode,
+ const nsAString& aNewValue, bool aNotify);
+ void PictureSourceSizesChanged(nsIContent *aSourceNode,
+ const nsAString& aNewValue, bool aNotify);
+ // As we re-run the source selection on these mutations regardless,
+ // we don't actually care which changed or to what
+ void PictureSourceMediaOrTypeChanged(nsIContent *aSourceNode, bool aNotify);
+
+ void PictureSourceAdded(nsIContent *aSourceNode);
+ // This should be called prior to the unbind, such that nextsibling works
+ void PictureSourceRemoved(nsIContent *aSourceNode);
+
+ // Re-evaluates all source nodes (picture <source>,<img>) and finds
+ // the best source set for mResponsiveSelector. If a better source
+ // is found, creates a new selector and feeds the source to it. If
+ // the current ResponsiveSelector is not changed, runs
+ // SelectImage(true) to re-evaluate its candidates.
+ //
+ // Because keeping the existing selector is the common case (and we
+ // often do no-op reselections), this does not re-parse values for
+ // the existing mResponsiveSelector, meaning you need to update its
+ // parameters as appropriate before calling (or null it out to force
+ // recreation)
+ //
+ // Returns true if the source has changed, and false otherwise.
+ bool UpdateResponsiveSource();
+
+ // Given a <source> node that is a previous sibling *or* ourselves, try to
+ // create a ResponsiveSelector.
+
+ // If the node's srcset/sizes make for an invalid selector, returns
+ // false. This does not guarantee the resulting selector matches an image,
+ // only that it is valid.
+ bool TryCreateResponsiveSelector(nsIContent *aSourceNode);
+
+ CSSIntPoint GetXY();
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+ void UpdateFormOwner();
+
+ virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify) override;
+
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+
+ // This is a weak reference that this element and the HTMLFormElement
+ // cooperate in maintaining.
+ HTMLFormElement* mForm;
+
+ // Created when we're tracking responsive image state
+ RefPtr<ResponsiveImageSelector> mResponsiveSelector;
+
+private:
+ bool SourceElementMatches(nsIContent* aSourceNode);
+
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+
+ bool mInDocResponsiveContent;
+ RefPtr<ImageLoadTask> mPendingImageLoadTask;
+
+ // Last URL that was attempted to load by this element.
+ nsCOMPtr<nsIURI> mLastSelectedSource;
+ // Last pixel density that was selected.
+ double mCurrentDensity;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_HTMLImageElement_h */
diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp
new file mode 100644
index 000000000..78f74ae0c
--- /dev/null
+++ b/dom/html/HTMLInputElement.cpp
@@ -0,0 +1,8806 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLInputElement.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/Date.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/HTMLFormSubmission.h"
+#include "mozilla/dom/FileSystemUtils.h"
+#include "mozilla/dom/GetFilesHelper.h"
+#include "nsAttrValueInlines.h"
+#include "nsCRTGlue.h"
+
+#include "nsIDOMHTMLInputElement.h"
+#include "nsITextControlElement.h"
+#include "nsIDOMNSEditableElement.h"
+#include "nsIRadioVisitor.h"
+#include "nsIPhonetic.h"
+
+#include "HTMLFormSubmissionConstants.h"
+#include "mozilla/Telemetry.h"
+#include "nsIControllers.h"
+#include "nsIStringBundle.h"
+#include "nsFocusManager.h"
+#include "nsColorControlFrame.h"
+#include "nsNumberControlFrame.h"
+#include "nsPIDOMWindow.h"
+#include "nsRepeatService.h"
+#include "nsContentCID.h"
+#include "nsIComponentManager.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "mozilla/dom/ProgressEvent.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsMappedAttributes.h"
+#include "nsIFormControl.h"
+#include "nsIDocument.h"
+#include "nsIPresShell.h"
+#include "nsIFormControlFrame.h"
+#include "nsITextControlFrame.h"
+#include "nsIFrame.h"
+#include "nsRangeFrame.h"
+#include "nsIServiceManager.h"
+#include "nsError.h"
+#include "nsIEditor.h"
+#include "nsIIOService.h"
+#include "nsDocument.h"
+#include "nsAttrValueOrString.h"
+#include "nsDateTimeControlFrame.h"
+
+#include "nsPresState.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMHTMLCollection.h"
+#include "nsLinebreakConverter.h" //to strip out carriage returns
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsVariant.h"
+
+#include "nsIDOMMutationEvent.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TouchEvents.h"
+
+#include "nsRuleData.h"
+#include <algorithm>
+
+// input type=radio
+#include "nsIRadioGroupContainer.h"
+
+// input type=file
+#include "mozilla/dom/FileSystemEntry.h"
+#include "mozilla/dom/FileSystem.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FileList.h"
+#include "nsIFile.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIContentPrefService.h"
+#include "nsIMIMEService.h"
+#include "nsIObserverService.h"
+#include "nsIPopupWindowManager.h"
+#include "nsGlobalWindow.h"
+
+// input type=image
+#include "nsImageLoadingContent.h"
+#include "imgRequestProxy.h"
+
+#include "mozAutoDocUpdate.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/DirectionalityUtils.h"
+#include "nsRadioVisitor.h"
+#include "nsTextEditorState.h"
+
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/MathAlgorithms.h"
+
+#include "nsIIDNService.h"
+
+#include <limits>
+
+#include "nsIColorPicker.h"
+#include "nsIDatePicker.h"
+#include "nsIStringEnumerator.h"
+#include "HTMLSplitOnSpacesTokenizer.h"
+#include "nsIController.h"
+#include "nsIMIMEInfo.h"
+#include "nsFrameSelection.h"
+
+#include "nsIConsoleService.h"
+
+// input type=date
+#include "js/Date.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Input)
+
+// XXX align=left, hspace, vspace, border? other nav4 attrs
+
+static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID);
+
+// This must come outside of any namespace, or else it won't overload with the
+// double based version in nsMathUtils.h
+inline mozilla::Decimal
+NS_floorModulo(mozilla::Decimal x, mozilla::Decimal y)
+{
+ return (x - y * (x / y).floor());
+}
+
+namespace mozilla {
+namespace dom {
+
+// First bits are needed for the control type.
+#define NS_OUTER_ACTIVATE_EVENT (1 << 9)
+#define NS_ORIGINAL_CHECKED_VALUE (1 << 10)
+#define NS_NO_CONTENT_DISPATCH (1 << 11)
+#define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
+#define NS_CONTROL_TYPE(bits) ((bits) & ~( \
+ NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | NS_NO_CONTENT_DISPATCH | \
+ NS_ORIGINAL_INDETERMINATE_VALUE))
+
+// whether textfields should be selected once focused:
+// -1: no, 1: yes, 0: uninitialized
+static int32_t gSelectTextFieldOnFocus;
+UploadLastDir* HTMLInputElement::gUploadLastDir;
+
+static const nsAttrValue::EnumTable kInputTypeTable[] = {
+ { "button", NS_FORM_INPUT_BUTTON },
+ { "checkbox", NS_FORM_INPUT_CHECKBOX },
+ { "color", NS_FORM_INPUT_COLOR },
+ { "date", NS_FORM_INPUT_DATE },
+ { "datetime-local", NS_FORM_INPUT_DATETIME_LOCAL },
+ { "email", NS_FORM_INPUT_EMAIL },
+ { "file", NS_FORM_INPUT_FILE },
+ { "hidden", NS_FORM_INPUT_HIDDEN },
+ { "reset", NS_FORM_INPUT_RESET },
+ { "image", NS_FORM_INPUT_IMAGE },
+ { "month", NS_FORM_INPUT_MONTH },
+ { "number", NS_FORM_INPUT_NUMBER },
+ { "password", NS_FORM_INPUT_PASSWORD },
+ { "radio", NS_FORM_INPUT_RADIO },
+ { "range", NS_FORM_INPUT_RANGE },
+ { "search", NS_FORM_INPUT_SEARCH },
+ { "submit", NS_FORM_INPUT_SUBMIT },
+ { "tel", NS_FORM_INPUT_TEL },
+ { "text", NS_FORM_INPUT_TEXT },
+ { "time", NS_FORM_INPUT_TIME },
+ { "url", NS_FORM_INPUT_URL },
+ { "week", NS_FORM_INPUT_WEEK },
+ { nullptr, 0 }
+};
+
+// Default type is 'text'.
+static const nsAttrValue::EnumTable* kInputDefaultType = &kInputTypeTable[18];
+
+static const uint8_t NS_INPUT_INPUTMODE_AUTO = 0;
+static const uint8_t NS_INPUT_INPUTMODE_NUMERIC = 1;
+static const uint8_t NS_INPUT_INPUTMODE_DIGIT = 2;
+static const uint8_t NS_INPUT_INPUTMODE_UPPERCASE = 3;
+static const uint8_t NS_INPUT_INPUTMODE_LOWERCASE = 4;
+static const uint8_t NS_INPUT_INPUTMODE_TITLECASE = 5;
+static const uint8_t NS_INPUT_INPUTMODE_AUTOCAPITALIZED = 6;
+
+static const nsAttrValue::EnumTable kInputInputmodeTable[] = {
+ { "auto", NS_INPUT_INPUTMODE_AUTO },
+ { "numeric", NS_INPUT_INPUTMODE_NUMERIC },
+ { "digit", NS_INPUT_INPUTMODE_DIGIT },
+ { "uppercase", NS_INPUT_INPUTMODE_UPPERCASE },
+ { "lowercase", NS_INPUT_INPUTMODE_LOWERCASE },
+ { "titlecase", NS_INPUT_INPUTMODE_TITLECASE },
+ { "autocapitalized", NS_INPUT_INPUTMODE_AUTOCAPITALIZED },
+ { nullptr, 0 }
+};
+
+// Default inputmode value is "auto".
+static const nsAttrValue::EnumTable* kInputDefaultInputmode = &kInputInputmodeTable[0];
+
+const Decimal HTMLInputElement::kStepScaleFactorDate = Decimal(86400000);
+const Decimal HTMLInputElement::kStepScaleFactorNumberRange = Decimal(1);
+const Decimal HTMLInputElement::kStepScaleFactorTime = Decimal(1000);
+const Decimal HTMLInputElement::kStepScaleFactorMonth = Decimal(1);
+const Decimal HTMLInputElement::kStepScaleFactorWeek = Decimal(7 * 86400000);
+const Decimal HTMLInputElement::kDefaultStepBase = Decimal(0);
+const Decimal HTMLInputElement::kDefaultStepBaseWeek = Decimal(-259200000);
+const Decimal HTMLInputElement::kDefaultStep = Decimal(1);
+const Decimal HTMLInputElement::kDefaultStepTime = Decimal(60);
+const Decimal HTMLInputElement::kStepAny = Decimal(0);
+
+const double HTMLInputElement::kMinimumYear = 1;
+const double HTMLInputElement::kMaximumYear = 275760;
+const double HTMLInputElement::kMaximumWeekInMaximumYear = 37;
+const double HTMLInputElement::kMaximumDayInMaximumYear = 13;
+const double HTMLInputElement::kMaximumMonthInMaximumYear = 9;
+const double HTMLInputElement::kMaximumWeekInYear = 53;
+const double HTMLInputElement::kMsPerDay = 24 * 60 * 60 * 1000;
+
+#define NS_INPUT_ELEMENT_STATE_IID \
+{ /* dc3b3d14-23e2-4479-b513-7b369343e3a0 */ \
+ 0xdc3b3d14, \
+ 0x23e2, \
+ 0x4479, \
+ {0xb5, 0x13, 0x7b, 0x36, 0x93, 0x43, 0xe3, 0xa0} \
+}
+
+#define PROGRESS_STR "progress"
+static const uint32_t kProgressEventInterval = 50; // ms
+
+// An helper class for the dispatching of the 'change' event.
+// This class is used when the FilePicker finished its task (or when files and
+// directories are set by some chrome/test only method).
+// The task of this class is to postpone the dispatching of 'change' and 'input'
+// events at the end of the exploration of the directories.
+class DispatchChangeEventCallback final : public GetFilesCallback
+{
+public:
+ explicit DispatchChangeEventCallback(HTMLInputElement* aInputElement)
+ : mInputElement(aInputElement)
+ {
+ MOZ_ASSERT(aInputElement);
+ }
+
+ virtual void
+ Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) override
+ {
+ nsTArray<OwningFileOrDirectory> array;
+ for (uint32_t i = 0; i < aFiles.Length(); ++i) {
+ OwningFileOrDirectory* element = array.AppendElement();
+ element->SetAsFile() = aFiles[i];
+ }
+
+ mInputElement->SetFilesOrDirectories(array, true);
+ Unused << NS_WARN_IF(NS_FAILED(DispatchEvents()));
+ }
+
+ nsresult
+ DispatchEvents()
+ {
+ nsresult rv = NS_OK;
+ rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(mInputElement.get()),
+ NS_LITERAL_STRING("input"), true,
+ false);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchTrustedEvent failed");
+
+ rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(mInputElement.get()),
+ NS_LITERAL_STRING("change"), true,
+ false);
+
+ return rv;
+ }
+
+private:
+ RefPtr<HTMLInputElement> mInputElement;
+};
+
+class HTMLInputElementState final : public nsISupports
+{
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_ELEMENT_STATE_IID)
+ NS_DECL_ISUPPORTS
+
+ bool IsCheckedSet()
+ {
+ return mCheckedSet;
+ }
+
+ bool GetChecked()
+ {
+ return mChecked;
+ }
+
+ void SetChecked(bool aChecked)
+ {
+ mChecked = aChecked;
+ mCheckedSet = true;
+ }
+
+ const nsString& GetValue()
+ {
+ return mValue;
+ }
+
+ void SetValue(const nsAString& aValue)
+ {
+ mValue = aValue;
+ }
+
+ void
+ GetFilesOrDirectories(nsPIDOMWindowInner* aWindow,
+ nsTArray<OwningFileOrDirectory>& aResult) const
+ {
+ for (uint32_t i = 0; i < mBlobImplsOrDirectoryPaths.Length(); ++i) {
+ if (mBlobImplsOrDirectoryPaths[i].mType == BlobImplOrDirectoryPath::eBlobImpl) {
+ RefPtr<File> file =
+ File::Create(aWindow,
+ mBlobImplsOrDirectoryPaths[i].mBlobImpl);
+ MOZ_ASSERT(file);
+
+ OwningFileOrDirectory* element = aResult.AppendElement();
+ element->SetAsFile() = file;
+ } else {
+ MOZ_ASSERT(mBlobImplsOrDirectoryPaths[i].mType == BlobImplOrDirectoryPath::eDirectoryPath);
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv =
+ NS_NewLocalFile(mBlobImplsOrDirectoryPaths[i].mDirectoryPath,
+ true, getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ RefPtr<Directory> directory = Directory::Create(aWindow, file);
+ MOZ_ASSERT(directory);
+
+ OwningFileOrDirectory* element = aResult.AppendElement();
+ element->SetAsDirectory() = directory;
+ }
+ }
+ }
+
+ void SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& aArray)
+ {
+ mBlobImplsOrDirectoryPaths.Clear();
+ for (uint32_t i = 0; i < aArray.Length(); ++i) {
+ if (aArray[i].IsFile()) {
+ BlobImplOrDirectoryPath* data = mBlobImplsOrDirectoryPaths.AppendElement();
+
+ data->mBlobImpl = aArray[i].GetAsFile()->Impl();
+ data->mType = BlobImplOrDirectoryPath::eBlobImpl;
+ } else {
+ MOZ_ASSERT(aArray[i].IsDirectory());
+ nsAutoString fullPath;
+ nsresult rv = aArray[i].GetAsDirectory()->GetFullRealPath(fullPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ BlobImplOrDirectoryPath* data =
+ mBlobImplsOrDirectoryPaths.AppendElement();
+
+ data->mDirectoryPath = fullPath;
+ data->mType = BlobImplOrDirectoryPath::eDirectoryPath;
+ }
+ }
+ }
+
+ HTMLInputElementState()
+ : mValue()
+ , mChecked(false)
+ , mCheckedSet(false)
+ {}
+
+ protected:
+ ~HTMLInputElementState() {}
+
+ nsString mValue;
+
+ struct BlobImplOrDirectoryPath
+ {
+ RefPtr<BlobImpl> mBlobImpl;
+ nsString mDirectoryPath;
+
+ enum {
+ eBlobImpl,
+ eDirectoryPath
+ } mType;
+ };
+
+ nsTArray<BlobImplOrDirectoryPath> mBlobImplsOrDirectoryPaths;
+
+ bool mChecked;
+ bool mCheckedSet;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HTMLInputElementState, NS_INPUT_ELEMENT_STATE_IID)
+
+NS_IMPL_ISUPPORTS(HTMLInputElementState, HTMLInputElementState)
+
+HTMLInputElement::nsFilePickerShownCallback::nsFilePickerShownCallback(
+ HTMLInputElement* aInput, nsIFilePicker* aFilePicker)
+ : mFilePicker(aFilePicker)
+ , mInput(aInput)
+{
+}
+
+NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback, nsIContentPrefCallback2)
+
+NS_IMETHODIMP
+UploadLastDir::ContentPrefCallback::HandleCompletion(uint16_t aReason)
+{
+ nsCOMPtr<nsIFile> localFile;
+ nsAutoString prefStr;
+
+ if (aReason == nsIContentPrefCallback2::COMPLETE_ERROR || !mResult) {
+ prefStr = Preferences::GetString("dom.input.fallbackUploadDir");
+ if (prefStr.IsEmpty()) {
+ // If no custom directory was set through the pref, default to
+ // "desktop" directory for each platform.
+ NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(localFile));
+ }
+ }
+
+ if (!localFile) {
+ if (prefStr.IsEmpty() && mResult) {
+ nsCOMPtr<nsIVariant> pref;
+ mResult->GetValue(getter_AddRefs(pref));
+ pref->GetAsAString(prefStr);
+ }
+ localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+ localFile->InitWithPath(prefStr);
+ }
+
+ mFilePicker->SetDisplayDirectory(localFile);
+ mFilePicker->Open(mFpCallback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UploadLastDir::ContentPrefCallback::HandleResult(nsIContentPref* pref)
+{
+ mResult = pref;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UploadLastDir::ContentPrefCallback::HandleError(nsresult error)
+{
+ // HandleCompletion is always called (even with HandleError was called),
+ // so we don't need to do anything special here.
+ return NS_OK;
+}
+
+namespace {
+
+/**
+ * This may return nullptr if the DOM File's implementation of
+ * File::mozFullPathInternal does not successfully return a non-empty
+ * string that is a valid path. This can happen on Firefox OS, for example,
+ * where the file picker can create Blobs.
+ */
+static already_AddRefed<nsIFile>
+LastUsedDirectory(const OwningFileOrDirectory& aData)
+{
+ if (aData.IsFile()) {
+ nsAutoString path;
+ ErrorResult error;
+ aData.GetAsFile()->GetMozFullPathInternal(path, error);
+ if (error.Failed() || path.IsEmpty()) {
+ error.SuppressException();
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true,
+ getter_AddRefs(localFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIFile> parentFile;
+ rv = localFile->GetParent(getter_AddRefs(parentFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ return parentFile.forget();
+ }
+
+ MOZ_ASSERT(aData.IsDirectory());
+
+ nsCOMPtr<nsIFile> localFile = aData.GetAsDirectory()->GetInternalNsIFile();
+ MOZ_ASSERT(localFile);
+
+ return localFile.forget();
+}
+
+void
+GetDOMFileOrDirectoryName(const OwningFileOrDirectory& aData,
+ nsAString& aName)
+{
+ if (aData.IsFile()) {
+ aData.GetAsFile()->GetName(aName);
+ } else {
+ MOZ_ASSERT(aData.IsDirectory());
+ ErrorResult rv;
+ aData.GetAsDirectory()->GetName(aName, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ }
+ }
+}
+
+void
+GetDOMFileOrDirectoryPath(const OwningFileOrDirectory& aData,
+ nsAString& aPath,
+ ErrorResult& aRv)
+{
+ if (aData.IsFile()) {
+ aData.GetAsFile()->GetMozFullPathInternal(aPath, aRv);
+ } else {
+ MOZ_ASSERT(aData.IsDirectory());
+ aData.GetAsDirectory()->GetFullRealPath(aPath);
+ }
+}
+
+} // namespace
+
+/* static */
+bool
+HTMLInputElement::ValueAsDateEnabled(JSContext* cx, JSObject* obj)
+{
+ return Preferences::GetBool("dom.experimental_forms", false) ||
+ Preferences::GetBool("dom.forms.datepicker", false) ||
+ Preferences::GetBool("dom.forms.datetime", false);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult)
+{
+ mInput->PickerClosed();
+
+ if (aResult == nsIFilePicker::returnCancel) {
+ return NS_OK;
+ }
+
+ int16_t mode;
+ mFilePicker->GetMode(&mode);
+
+ // Collect new selected filenames
+ nsTArray<OwningFileOrDirectory> newFilesOrDirectories;
+ if (mode == static_cast<int16_t>(nsIFilePicker::modeOpenMultiple)) {
+ nsCOMPtr<nsISimpleEnumerator> iter;
+ nsresult rv =
+ mFilePicker->GetDomFileOrDirectoryEnumerator(getter_AddRefs(iter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!iter) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISupports> tmp;
+ bool hasMore = true;
+
+ while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
+ iter->GetNext(getter_AddRefs(tmp));
+ nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(tmp);
+ MOZ_ASSERT(domBlob,
+ "Null file object from FilePicker's file enumerator?");
+ if (!domBlob) {
+ continue;
+ }
+
+ OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
+ element->SetAsFile() = static_cast<File*>(domBlob.get());
+ }
+ } else {
+ MOZ_ASSERT(mode == static_cast<int16_t>(nsIFilePicker::modeOpen) ||
+ mode == static_cast<int16_t>(nsIFilePicker::modeGetFolder));
+ nsCOMPtr<nsISupports> tmp;
+ nsresult rv = mFilePicker->GetDomFileOrDirectory(getter_AddRefs(tmp));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(tmp);
+ if (blob) {
+ RefPtr<File> file = static_cast<Blob*>(blob.get())->ToFile();
+ MOZ_ASSERT(file);
+
+ OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
+ element->SetAsFile() = file;
+ } else if (tmp) {
+ RefPtr<Directory> directory = static_cast<Directory*>(tmp.get());
+ OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
+ element->SetAsDirectory() = directory;
+ }
+ }
+
+ if (newFilesOrDirectories.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // Store the last used directory using the content pref service:
+ nsCOMPtr<nsIFile> lastUsedDir = LastUsedDirectory(newFilesOrDirectories[0]);
+
+ if (lastUsedDir) {
+ HTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(
+ mInput->OwnerDoc(), lastUsedDir);
+ }
+
+ // The text control frame (if there is one) isn't going to send a change
+ // event because it will think this is done by a script.
+ // So, we can safely send one by ourself.
+ mInput->SetFilesOrDirectories(newFilesOrDirectories, true);
+
+ RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
+ new DispatchChangeEventCallback(mInput);
+
+ if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+ mInput->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
+ ErrorResult error;
+ GetFilesHelper* helper = mInput->GetOrCreateGetFilesHelper(true, error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+
+ helper->AddCallback(dispatchChangeEventCallback);
+ return NS_OK;
+ }
+
+ return dispatchChangeEventCallback->DispatchEvents();
+}
+
+NS_IMPL_ISUPPORTS(HTMLInputElement::nsFilePickerShownCallback,
+ nsIFilePickerShownCallback)
+
+class nsColorPickerShownCallback final
+ : public nsIColorPickerShownCallback
+{
+ ~nsColorPickerShownCallback() {}
+
+public:
+ nsColorPickerShownCallback(HTMLInputElement* aInput,
+ nsIColorPicker* aColorPicker)
+ : mInput(aInput)
+ , mColorPicker(aColorPicker)
+ , mValueChanged(false)
+ {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Update(const nsAString& aColor) override;
+ NS_IMETHOD Done(const nsAString& aColor) override;
+
+private:
+ /**
+ * Updates the internals of the object using aColor as the new value.
+ * If aTrustedUpdate is true, it will consider that aColor is a new value.
+ * Otherwise, it will check that aColor is different from the current value.
+ */
+ nsresult UpdateInternal(const nsAString& aColor, bool aTrustedUpdate);
+
+ RefPtr<HTMLInputElement> mInput;
+ nsCOMPtr<nsIColorPicker> mColorPicker;
+ bool mValueChanged;
+};
+
+nsresult
+nsColorPickerShownCallback::UpdateInternal(const nsAString& aColor,
+ bool aTrustedUpdate)
+{
+ bool valueChanged = false;
+
+ nsAutoString oldValue;
+ if (aTrustedUpdate) {
+ valueChanged = true;
+ } else {
+ mInput->GetValue(oldValue);
+ }
+
+ mInput->SetValue(aColor);
+
+ if (!aTrustedUpdate) {
+ nsAutoString newValue;
+ mInput->GetValue(newValue);
+ if (!oldValue.Equals(newValue)) {
+ valueChanged = true;
+ }
+ }
+
+ if (valueChanged) {
+ mValueChanged = true;
+ return nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
+ NS_LITERAL_STRING("input"), true,
+ false);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsColorPickerShownCallback::Update(const nsAString& aColor)
+{
+ return UpdateInternal(aColor, true);
+}
+
+NS_IMETHODIMP
+nsColorPickerShownCallback::Done(const nsAString& aColor)
+{
+ /**
+ * When Done() is called, we might be at the end of a serie of Update() calls
+ * in which case mValueChanged is set to true and a change event will have to
+ * be fired but we might also be in a one shot Done() call situation in which
+ * case we should fire a change event iif the value actually changed.
+ * UpdateInternal(bool) is taking care of that logic for us.
+ */
+ nsresult rv = NS_OK;
+
+ mInput->PickerClosed();
+
+ if (!aColor.IsEmpty()) {
+ UpdateInternal(aColor, false);
+ }
+
+ if (mValueChanged) {
+ rv = nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
+ NS_LITERAL_STRING("change"), true,
+ false);
+ }
+
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsColorPickerShownCallback, nsIColorPickerShownCallback)
+
+class DatePickerShownCallback final : public nsIDatePickerShownCallback
+{
+ ~DatePickerShownCallback() {}
+public:
+ DatePickerShownCallback(HTMLInputElement* aInput,
+ nsIDatePicker* aDatePicker)
+ : mInput(aInput)
+ , mDatePicker(aDatePicker)
+ {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Done(const nsAString& aDate) override;
+ NS_IMETHOD Cancel() override;
+
+private:
+ RefPtr<HTMLInputElement> mInput;
+ nsCOMPtr<nsIDatePicker> mDatePicker;
+};
+
+NS_IMETHODIMP
+DatePickerShownCallback::Cancel()
+{
+ mInput->PickerClosed();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DatePickerShownCallback::Done(const nsAString& aDate)
+{
+ nsAutoString oldValue;
+
+ mInput->PickerClosed();
+ mInput->GetValue(oldValue);
+
+ if(!oldValue.Equals(aDate)){
+ mInput->SetValue(aDate);
+ nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
+ NS_LITERAL_STRING("input"), true,
+ false);
+ return nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
+ NS_LITERAL_STRING("change"), true,
+ false);
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(DatePickerShownCallback, nsIDatePickerShownCallback)
+
+
+bool
+HTMLInputElement::IsPopupBlocked() const
+{
+ nsCOMPtr<nsPIDOMWindowOuter> win = OwnerDoc()->GetWindow();
+ MOZ_ASSERT(win, "window should not be null");
+ if (!win) {
+ return true;
+ }
+
+ // Check if page is allowed to open the popup
+ if (win->GetPopupControlState() <= openControlled) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPopupWindowManager> pm = do_GetService(NS_POPUPWINDOWMANAGER_CONTRACTID);
+ if (!pm) {
+ return true;
+ }
+
+ uint32_t permission;
+ pm->TestPermission(OwnerDoc()->NodePrincipal(), &permission);
+ return permission == nsIPopupWindowManager::DENY_POPUP;
+}
+
+nsresult
+HTMLInputElement::InitDatePicker()
+{
+ if (!Preferences::GetBool("dom.forms.datepicker", false)) {
+ return NS_OK;
+ }
+
+ if (mPickerRunning) {
+ NS_WARNING("Just one nsIDatePicker is allowed");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocument> doc = OwnerDoc();
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
+ if (!win) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (IsPopupBlocked()) {
+ win->FirePopupBlockedEvent(doc, nullptr, EmptyString(), EmptyString());
+ return NS_OK;
+ }
+
+ // Get Loc title
+ nsXPIDLString title;
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "DatePicker", title);
+
+ nsresult rv;
+ nsCOMPtr<nsIDatePicker> datePicker = do_CreateInstance("@mozilla.org/datepicker;1", &rv);
+ if (!datePicker) {
+ return rv;
+ }
+
+ nsAutoString initialValue;
+ GetValueInternal(initialValue);
+ rv = datePicker->Init(win, title, initialValue);
+
+ nsCOMPtr<nsIDatePickerShownCallback> callback =
+ new DatePickerShownCallback(this, datePicker);
+
+ rv = datePicker->Open(callback);
+ if (NS_SUCCEEDED(rv)) {
+ mPickerRunning = true;
+ }
+
+ return rv;
+}
+
+nsresult
+HTMLInputElement::InitColorPicker()
+{
+ if (mPickerRunning) {
+ NS_WARNING("Just one nsIColorPicker is allowed");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocument> doc = OwnerDoc();
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
+ if (!win) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (IsPopupBlocked()) {
+ win->FirePopupBlockedEvent(doc, nullptr, EmptyString(), EmptyString());
+ return NS_OK;
+ }
+
+ // Get Loc title
+ nsXPIDLString title;
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "ColorPicker", title);
+
+ nsCOMPtr<nsIColorPicker> colorPicker = do_CreateInstance("@mozilla.org/colorpicker;1");
+ if (!colorPicker) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString initialValue;
+ GetValueInternal(initialValue);
+ nsresult rv = colorPicker->Init(win, title, initialValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIColorPickerShownCallback> callback =
+ new nsColorPickerShownCallback(this, colorPicker);
+
+ rv = colorPicker->Open(callback);
+ if (NS_SUCCEEDED(rv)) {
+ mPickerRunning = true;
+ }
+
+ return rv;
+}
+
+nsresult
+HTMLInputElement::InitFilePicker(FilePickerType aType)
+{
+ if (mPickerRunning) {
+ NS_WARNING("Just one nsIFilePicker is allowed");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get parent nsPIDOMWindow object.
+ nsCOMPtr<nsIDocument> doc = OwnerDoc();
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
+ if (!win) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (IsPopupBlocked()) {
+ win->FirePopupBlockedEvent(doc, nullptr, EmptyString(), EmptyString());
+ return NS_OK;
+ }
+
+ // Get Loc title
+ nsXPIDLString title;
+ nsXPIDLString okButtonLabel;
+ if (aType == FILE_PICKER_DIRECTORY) {
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "DirectoryUpload", title);
+
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "DirectoryPickerOkButtonLabel",
+ okButtonLabel);
+ } else {
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "FileUpload", title);
+ }
+
+ nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1");
+ if (!filePicker)
+ return NS_ERROR_FAILURE;
+
+ int16_t mode;
+
+ if (aType == FILE_PICKER_DIRECTORY) {
+ mode = static_cast<int16_t>(nsIFilePicker::modeGetFolder);
+ } else if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
+ mode = static_cast<int16_t>(nsIFilePicker::modeOpenMultiple);
+ } else {
+ mode = static_cast<int16_t>(nsIFilePicker::modeOpen);
+ }
+
+ nsresult rv = filePicker->Init(win, title, mode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!okButtonLabel.IsEmpty()) {
+ filePicker->SetOkButtonLabel(okButtonLabel);
+ }
+
+ // Native directory pickers ignore file type filters, so we don't spend
+ // cycles adding them for FILE_PICKER_DIRECTORY.
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::accept) &&
+ aType != FILE_PICKER_DIRECTORY) {
+ SetFilePickerFiltersFromAccept(filePicker);
+ } else {
+ filePicker->AppendFilters(nsIFilePicker::filterAll);
+ }
+
+ // Set default directory and filename
+ nsAutoString defaultName;
+
+ const nsTArray<OwningFileOrDirectory>& oldFiles =
+ GetFilesOrDirectoriesInternal();
+
+ nsCOMPtr<nsIFilePickerShownCallback> callback =
+ new HTMLInputElement::nsFilePickerShownCallback(this, filePicker);
+
+ if (!oldFiles.IsEmpty() &&
+ aType != FILE_PICKER_DIRECTORY) {
+ nsAutoString path;
+
+ nsCOMPtr<nsIFile> parentFile = LastUsedDirectory(oldFiles[0]);
+ if (parentFile) {
+ filePicker->SetDisplayDirectory(parentFile);
+ }
+
+ // Unfortunately nsIFilePicker doesn't allow multiple files to be
+ // default-selected, so only select something by default if exactly
+ // one file was selected before.
+ if (oldFiles.Length() == 1) {
+ nsAutoString leafName;
+ GetDOMFileOrDirectoryName(oldFiles[0], leafName);
+
+ if (!leafName.IsEmpty()) {
+ filePicker->SetDefaultString(leafName);
+ }
+ }
+
+ rv = filePicker->Open(callback);
+ if (NS_SUCCEEDED(rv)) {
+ mPickerRunning = true;
+ }
+
+ return rv;
+ }
+
+ HTMLInputElement::gUploadLastDir->FetchDirectoryAndDisplayPicker(doc, filePicker, callback);
+ mPickerRunning = true;
+ return NS_OK;
+}
+
+#define CPS_PREF_NAME NS_LITERAL_STRING("browser.upload.lastDir")
+
+NS_IMPL_ISUPPORTS(UploadLastDir, nsIObserver, nsISupportsWeakReference)
+
+void
+HTMLInputElement::InitUploadLastDir() {
+ gUploadLastDir = new UploadLastDir();
+ NS_ADDREF(gUploadLastDir);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService && gUploadLastDir) {
+ observerService->AddObserver(gUploadLastDir, "browser:purge-session-history", true);
+ }
+}
+
+void
+HTMLInputElement::DestroyUploadLastDir() {
+ NS_IF_RELEASE(gUploadLastDir);
+}
+
+nsresult
+UploadLastDir::FetchDirectoryAndDisplayPicker(nsIDocument* aDoc,
+ nsIFilePicker* aFilePicker,
+ nsIFilePickerShownCallback* aFpCallback)
+{
+ NS_PRECONDITION(aDoc, "aDoc is null");
+ NS_PRECONDITION(aFilePicker, "aFilePicker is null");
+ NS_PRECONDITION(aFpCallback, "aFpCallback is null");
+
+ nsIURI* docURI = aDoc->GetDocumentURI();
+ NS_PRECONDITION(docURI, "docURI is null");
+
+ nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
+ nsCOMPtr<nsIContentPrefCallback2> prefCallback =
+ new UploadLastDir::ContentPrefCallback(aFilePicker, aFpCallback);
+
+#ifdef MOZ_B2G
+ if (XRE_IsContentProcess()) {
+ prefCallback->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR);
+ return NS_OK;
+ }
+#endif
+
+ // Attempt to get the CPS, if it's not present we'll fallback to use the Desktop folder
+ nsCOMPtr<nsIContentPrefService2> contentPrefService =
+ do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+ if (!contentPrefService) {
+ prefCallback->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR);
+ return NS_OK;
+ }
+
+ nsAutoCString cstrSpec;
+ docURI->GetSpec(cstrSpec);
+ NS_ConvertUTF8toUTF16 spec(cstrSpec);
+
+ contentPrefService->GetByDomainAndName(spec, CPS_PREF_NAME, loadContext, prefCallback);
+ return NS_OK;
+}
+
+nsresult
+UploadLastDir::StoreLastUsedDirectory(nsIDocument* aDoc, nsIFile* aDir)
+{
+ NS_PRECONDITION(aDoc, "aDoc is null");
+ if (!aDir) {
+ return NS_OK;
+ }
+
+#ifdef MOZ_B2G
+ if (XRE_IsContentProcess()) {
+ return NS_OK;
+ }
+#endif
+
+ nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
+ NS_PRECONDITION(docURI, "docURI is null");
+
+ // Attempt to get the CPS, if it's not present we'll just return
+ nsCOMPtr<nsIContentPrefService2> contentPrefService =
+ do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+ if (!contentPrefService)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsAutoCString cstrSpec;
+ docURI->GetSpec(cstrSpec);
+ NS_ConvertUTF8toUTF16 spec(cstrSpec);
+
+ // Find the parent of aFile, and store it
+ nsString unicodePath;
+ aDir->GetPath(unicodePath);
+ if (unicodePath.IsEmpty()) // nothing to do
+ return NS_OK;
+ RefPtr<nsVariantCC> prefValue = new nsVariantCC();
+ prefValue->SetAsAString(unicodePath);
+
+ // Use the document's current load context to ensure that the content pref
+ // service doesn't persistently store this directory for this domain if the
+ // user is using private browsing:
+ nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
+ return contentPrefService->Set(spec, CPS_PREF_NAME, prefValue, loadContext, nullptr);
+}
+
+NS_IMETHODIMP
+UploadLastDir::Observe(nsISupports* aSubject, char const* aTopic, char16_t const* aData)
+{
+ if (strcmp(aTopic, "browser:purge-session-history") == 0) {
+ nsCOMPtr<nsIContentPrefService2> contentPrefService =
+ do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+ if (contentPrefService)
+ contentPrefService->RemoveByName(CPS_PREF_NAME, nullptr, nullptr);
+ }
+ return NS_OK;
+}
+
+#ifdef ACCESSIBILITY
+//Helper method
+static nsresult FireEventForAccessibility(nsIDOMHTMLInputElement* aTarget,
+ nsPresContext* aPresContext,
+ const nsAString& aEventType);
+#endif
+
+//
+// construction, destruction
+//
+
+HTMLInputElement::HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser, FromClone aFromClone)
+ : nsGenericHTMLFormElementWithState(aNodeInfo)
+ , mType(kInputDefaultType->value)
+ , mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown)
+ , mDisabledChanged(false)
+ , mValueChanged(false)
+ , mLastValueChangeWasInteractive(false)
+ , mCheckedChanged(false)
+ , mChecked(false)
+ , mHandlingSelectEvent(false)
+ , mShouldInitChecked(false)
+ , mDoneCreating(aFromParser == NOT_FROM_PARSER &&
+ aFromClone == FromClone::no)
+ , mInInternalActivate(false)
+ , mCheckedIsToggled(false)
+ , mIndeterminate(false)
+ , mInhibitRestoration(aFromParser & FROM_PARSER_FRAGMENT)
+ , mCanShowValidUI(true)
+ , mCanShowInvalidUI(true)
+ , mHasRange(false)
+ , mIsDraggingRange(false)
+ , mNumberControlSpinnerIsSpinning(false)
+ , mNumberControlSpinnerSpinsUp(false)
+ , mPickerRunning(false)
+ , mSelectionCached(true)
+{
+ // We are in a type=text so we now we currenty need a nsTextEditorState.
+ mInputData.mState = new nsTextEditorState(this);
+
+ if (!gUploadLastDir)
+ HTMLInputElement::InitUploadLastDir();
+
+ // Set up our default state. By default we're enabled (since we're
+ // a control type that can be disabled but not actually disabled
+ // right now), optional, and valid. We are NOT readwrite by default
+ // until someone calls UpdateEditableState on us, apparently! Also
+ // by default we don't have to show validity UI and so forth.
+ AddStatesSilently(NS_EVENT_STATE_ENABLED |
+ NS_EVENT_STATE_OPTIONAL |
+ NS_EVENT_STATE_VALID);
+ UpdateApzAwareFlag();
+}
+
+HTMLInputElement::~HTMLInputElement()
+{
+ if (mNumberControlSpinnerIsSpinning) {
+ StopNumberControlSpinnerSpin(eDisallowDispatchingEvents);
+ }
+ DestroyImageLoadingContent();
+ FreeData();
+}
+
+void
+HTMLInputElement::FreeData()
+{
+ if (!IsSingleLineTextControl(false)) {
+ free(mInputData.mValue);
+ mInputData.mValue = nullptr;
+ } else {
+ UnbindFromFrame(nullptr);
+ delete mInputData.mState;
+ mInputData.mState = nullptr;
+ }
+}
+
+nsTextEditorState*
+HTMLInputElement::GetEditorState() const
+{
+ if (!IsSingleLineTextControl(false)) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(mInputData.mState, "Single line text controls need to have a state"
+ " associated with them");
+
+ return mInputData.mState;
+}
+
+
+// nsISupports
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLInputElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement,
+ nsGenericHTMLFormElementWithState)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
+ if (tmp->IsSingleLineTextControl(false)) {
+ tmp->mInputData.mState->Traverse(cb);
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilesOrDirectories)
+
+ if (tmp->mGetFilesRecursiveHelper) {
+ tmp->mGetFilesRecursiveHelper->Traverse(cb);
+ }
+
+ if (tmp->mGetFilesNonRecursiveHelper) {
+ tmp->mGetFilesNonRecursiveHelper->Traverse(cb);
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEntries)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement,
+ nsGenericHTMLFormElementWithState)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilesOrDirectories)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mEntries)
+ if (tmp->IsSingleLineTextControl(false)) {
+ tmp->mInputData.mState->Unlink();
+ }
+
+ tmp->ClearGetFilesHelpers();
+
+ //XXX should unlink more?
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(HTMLInputElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLInputElement, Element)
+
+// QueryInterface implementation for HTMLInputElement
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLInputElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLInputElement,
+ nsIDOMHTMLInputElement,
+ nsITextControlElement,
+ nsIPhonetic,
+ imgINotificationObserver,
+ nsIImageLoadingContent,
+ imgIOnloadBlocker,
+ nsIDOMNSEditableElement,
+ nsIConstraintValidation)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElementWithState)
+
+// nsIConstraintValidation
+NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(HTMLInputElement)
+
+// nsIDOMNode
+
+nsresult
+HTMLInputElement::Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const
+{
+ *aResult = nullptr;
+
+ already_AddRefed<mozilla::dom::NodeInfo> ni = RefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget();
+ RefPtr<HTMLInputElement> it = new HTMLInputElement(ni, NOT_FROM_PARSER,
+ FromClone::yes);
+
+ nsresult rv = const_cast<HTMLInputElement*>(this)->CopyInnerTo(it);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch (GetValueMode()) {
+ case VALUE_MODE_VALUE:
+ if (mValueChanged) {
+ // We don't have our default value anymore. Set our value on
+ // the clone.
+ nsAutoString value;
+ GetValueInternal(value);
+ // SetValueInternal handles setting the VALUE_CHANGED bit for us
+ rv = it->SetValueInternal(value, nsTextEditorState::eSetValue_Notify);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ break;
+ case VALUE_MODE_FILENAME:
+ if (it->OwnerDoc()->IsStaticDocument()) {
+ // We're going to be used in print preview. Since the doc is static
+ // we can just grab the pretty string and use it as wallpaper
+ GetDisplayFileName(it->mStaticDocFileList);
+ } else {
+ it->ClearGetFilesHelpers();
+ it->mFilesOrDirectories.Clear();
+ it->mFilesOrDirectories.AppendElements(mFilesOrDirectories);
+ }
+ break;
+ case VALUE_MODE_DEFAULT_ON:
+ if (mCheckedChanged) {
+ // We no longer have our original checked state. Set our
+ // checked state on the clone.
+ it->DoSetChecked(mChecked, false, true);
+ // Then tell DoneCreatingElement() not to overwrite:
+ it->mShouldInitChecked = false;
+ }
+ break;
+ case VALUE_MODE_DEFAULT:
+ if (mType == NS_FORM_INPUT_IMAGE && it->OwnerDoc()->IsStaticDocument()) {
+ CreateStaticImageClone(it);
+ }
+ break;
+ }
+
+ it->DoneCreatingElement();
+
+ it->mLastValueChangeWasInteractive = mLastValueChangeWasInteractive;
+ it.forget(aResult);
+ return NS_OK;
+}
+
+nsresult
+HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None) {
+ //
+ // When name or type changes, radio should be removed from radio group.
+ // (type changes are handled in the form itself currently)
+ // If we are not done creating the radio, we also should not do it.
+ //
+ if ((aName == nsGkAtoms::name ||
+ (aName == nsGkAtoms::type && !mForm)) &&
+ mType == NS_FORM_INPUT_RADIO &&
+ (mForm || mDoneCreating)) {
+ WillRemoveFromRadioGroup();
+ } else if (aNotify && aName == nsGkAtoms::src &&
+ mType == NS_FORM_INPUT_IMAGE) {
+ if (aValue) {
+ LoadImage(aValue->String(), true, aNotify, eImageLoadType_Normal);
+ } else {
+ // Null value means the attr got unset; drop the image
+ CancelImageRequests(aNotify);
+ }
+ } else if (aNotify && aName == nsGkAtoms::disabled) {
+ mDisabledChanged = true;
+ } else if (aName == nsGkAtoms::dir &&
+ AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
+ nsGkAtoms::_auto, eIgnoreCase)) {
+ SetDirectionIfAuto(false, aNotify);
+ } else if (mType == NS_FORM_INPUT_RADIO && aName == nsGkAtoms::required) {
+ nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
+
+ if (container &&
+ ((aValue && !HasAttr(aNameSpaceID, aName)) ||
+ (!aValue && HasAttr(aNameSpaceID, aName)))) {
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+ container->RadioRequiredWillChange(name, !!aValue);
+ }
+ }
+
+ if (aName == nsGkAtoms::webkitdirectory) {
+ Telemetry::Accumulate(Telemetry::WEBKIT_DIRECTORY_USED, true);
+ }
+ }
+
+ return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+nsresult
+HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None) {
+ //
+ // When name or type changes, radio should be added to radio group.
+ // (type changes are handled in the form itself currently)
+ // If we are not done creating the radio, we also should not do it.
+ //
+ if ((aName == nsGkAtoms::name ||
+ (aName == nsGkAtoms::type && !mForm)) &&
+ mType == NS_FORM_INPUT_RADIO &&
+ (mForm || mDoneCreating)) {
+ AddedToRadioGroup();
+ UpdateValueMissingValidityStateForRadio(false);
+ }
+
+ // If @value is changed and BF_VALUE_CHANGED is false, @value is the value
+ // of the element so, if the value of the element is different than @value,
+ // we have to re-set it. This is only the case when GetValueMode() returns
+ // VALUE_MODE_VALUE.
+ if (aName == nsGkAtoms::value &&
+ !mValueChanged && GetValueMode() == VALUE_MODE_VALUE) {
+ SetDefaultValueAsValue();
+ }
+
+ //
+ // Checked must be set no matter what type of control it is, since
+ // mChecked must reflect the new value
+ if (aName == nsGkAtoms::checked && !mCheckedChanged) {
+ // Delay setting checked if we are creating this element (wait
+ // until everything is set)
+ if (!mDoneCreating) {
+ mShouldInitChecked = true;
+ } else {
+ DoSetChecked(DefaultChecked(), true, true);
+ SetCheckedChanged(false);
+ }
+ }
+
+ if (aName == nsGkAtoms::type) {
+ if (!aValue) {
+ // We're now a text input. Note that we have to handle this manually,
+ // since removing an attribute (which is what happened, since aValue is
+ // null) doesn't call ParseAttribute.
+ HandleTypeChange(kInputDefaultType->value);
+ }
+
+ UpdateBarredFromConstraintValidation();
+
+ if (mType != NS_FORM_INPUT_IMAGE) {
+ // We're no longer an image input. Cancel our image requests, if we have
+ // any. Note that doing this when we already weren't an image is ok --
+ // just does nothing.
+ CancelImageRequests(aNotify);
+ } else if (aNotify) {
+ // We just got switched to be an image input; we should see
+ // whether we have an image to load;
+ nsAutoString src;
+ if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
+ LoadImage(src, false, aNotify, eImageLoadType_Normal);
+ }
+ }
+
+ if (mType == NS_FORM_INPUT_PASSWORD && IsInComposedDoc()) {
+ AsyncEventDispatcher* dispatcher =
+ new AsyncEventDispatcher(this,
+ NS_LITERAL_STRING("DOMInputPasswordAdded"),
+ true,
+ true);
+ dispatcher->PostDOMEvent();
+ }
+ }
+
+ if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
+ aName == nsGkAtoms::readonly) {
+ UpdateValueMissingValidityState();
+
+ // This *has* to be called *after* validity has changed.
+ if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
+ UpdateBarredFromConstraintValidation();
+ }
+ } else if (MinOrMaxLengthApplies() && aName == nsGkAtoms::maxlength) {
+ UpdateTooLongValidityState();
+ } else if (MinOrMaxLengthApplies() && aName == nsGkAtoms::minlength) {
+ UpdateTooShortValidityState();
+ } else if (aName == nsGkAtoms::pattern && mDoneCreating) {
+ UpdatePatternMismatchValidityState();
+ } else if (aName == nsGkAtoms::multiple) {
+ UpdateTypeMismatchValidityState();
+ } else if (aName == nsGkAtoms::max) {
+ UpdateHasRange();
+ if (mType == NS_FORM_INPUT_RANGE) {
+ // The value may need to change when @max changes since the value may
+ // have been invalid and can now change to a valid value, or vice
+ // versa. For example, consider:
+ // <input type=range value=-1 max=1 step=3>. The valid range is 0 to 1
+ // while the nearest valid steps are -1 and 2 (the max value having
+ // prevented there being a valid step in range). Changing @max to/from
+ // 1 and a number greater than on equal to 3 should change whether we
+ // have a step mismatch or not.
+ // The value may also need to change between a value that results in
+ // a step mismatch and a value that results in overflow. For example,
+ // if @max in the example above were to change from 1 to -1.
+ nsAutoString value;
+ GetValue(value);
+ nsresult rv =
+ SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Validity state must be updated *after* the SetValueInternal call above
+ // or else the following assert will not be valid.
+ // We don't assert the state of underflow during creation since
+ // DoneCreatingElement sanitizes.
+ UpdateRangeOverflowValidityState();
+ MOZ_ASSERT(!mDoneCreating ||
+ mType != NS_FORM_INPUT_RANGE ||
+ !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
+ "HTML5 spec does not allow underflow for type=range");
+ } else if (aName == nsGkAtoms::min) {
+ UpdateHasRange();
+ if (mType == NS_FORM_INPUT_RANGE) {
+ // See @max comment
+ nsAutoString value;
+ GetValue(value);
+ nsresult rv =
+ SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // See corresponding @max comment
+ UpdateRangeUnderflowValidityState();
+ UpdateStepMismatchValidityState();
+ MOZ_ASSERT(!mDoneCreating ||
+ mType != NS_FORM_INPUT_RANGE ||
+ !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
+ "HTML5 spec does not allow underflow for type=range");
+ } else if (aName == nsGkAtoms::step) {
+ if (mType == NS_FORM_INPUT_RANGE) {
+ // See @max comment
+ nsAutoString value;
+ GetValue(value);
+ nsresult rv =
+ SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // See corresponding @max comment
+ UpdateStepMismatchValidityState();
+ MOZ_ASSERT(!mDoneCreating ||
+ mType != NS_FORM_INPUT_RANGE ||
+ !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
+ "HTML5 spec does not allow underflow for type=range");
+ } else if (aName == nsGkAtoms::dir &&
+ aValue && aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
+ SetDirectionIfAuto(true, aNotify);
+ } else if (aName == nsGkAtoms::lang) {
+ if (mType == NS_FORM_INPUT_NUMBER) {
+ // Update the value that is displayed to the user to the new locale:
+ nsAutoString value;
+ GetValueInternal(value);
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ numberControlFrame->SetValueOfAnonTextControl(value);
+ }
+ }
+ } else if (aName == nsGkAtoms::autocomplete) {
+ // Clear the cached @autocomplete attribute state.
+ mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
+ }
+
+ UpdateState(aNotify);
+ }
+
+ return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+// nsIDOMHTMLInputElement
+
+NS_IMETHODIMP
+HTMLInputElement::GetForm(nsIDOMHTMLFormElement** aForm)
+{
+ return nsGenericHTMLFormElementWithState::GetForm(aForm);
+}
+
+NS_IMPL_STRING_ATTR(HTMLInputElement, DefaultValue, value)
+NS_IMPL_BOOL_ATTR(HTMLInputElement, DefaultChecked, checked)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Accept, accept)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Align, align)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Alt, alt)
+NS_IMPL_BOOL_ATTR(HTMLInputElement, Autofocus, autofocus)
+//NS_IMPL_BOOL_ATTR(HTMLInputElement, Checked, checked)
+NS_IMPL_BOOL_ATTR(HTMLInputElement, Disabled, disabled)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Max, max)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Min, min)
+NS_IMPL_ACTION_ATTR(HTMLInputElement, FormAction, formaction)
+NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLInputElement, FormEnctype, formenctype,
+ "", kFormDefaultEnctype->tag)
+NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLInputElement, FormMethod, formmethod,
+ "", kFormDefaultMethod->tag)
+NS_IMPL_BOOL_ATTR(HTMLInputElement, FormNoValidate, formnovalidate)
+NS_IMPL_STRING_ATTR(HTMLInputElement, FormTarget, formtarget)
+NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLInputElement, InputMode, inputmode,
+ kInputDefaultInputmode->tag)
+NS_IMPL_BOOL_ATTR(HTMLInputElement, Multiple, multiple)
+NS_IMPL_NON_NEGATIVE_INT_ATTR(HTMLInputElement, MaxLength, maxlength)
+NS_IMPL_NON_NEGATIVE_INT_ATTR(HTMLInputElement, MinLength, minlength)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Name, name)
+NS_IMPL_BOOL_ATTR(HTMLInputElement, ReadOnly, readonly)
+NS_IMPL_BOOL_ATTR(HTMLInputElement, Required, required)
+NS_IMPL_URI_ATTR(HTMLInputElement, Src, src)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Step, step)
+NS_IMPL_STRING_ATTR(HTMLInputElement, UseMap, usemap)
+//NS_IMPL_STRING_ATTR(HTMLInputElement, Value, value)
+NS_IMPL_UINT_ATTR_NON_ZERO_DEFAULT_VALUE(HTMLInputElement, Size, size, DEFAULT_COLS)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Pattern, pattern)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Placeholder, placeholder)
+NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLInputElement, Type, type,
+ kInputDefaultType->tag)
+
+NS_IMETHODIMP
+HTMLInputElement::GetAutocomplete(nsAString& aValue)
+{
+ if (!DoesAutocompleteApply()) {
+ return NS_OK;
+ }
+
+ aValue.Truncate(0);
+ const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
+
+ mAutocompleteAttrState =
+ nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue,
+ mAutocompleteAttrState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetAutocomplete(const nsAString& aValue)
+{
+ return SetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, nullptr, aValue, true);
+}
+
+void
+HTMLInputElement::GetAutocompleteInfo(Nullable<AutocompleteInfo>& aInfo)
+{
+ if (!DoesAutocompleteApply()) {
+ aInfo.SetNull();
+ return;
+ }
+
+ const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
+ mAutocompleteAttrState =
+ nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aInfo.SetValue(),
+ mAutocompleteAttrState);
+}
+
+int32_t
+HTMLInputElement::TabIndexDefault()
+{
+ return 0;
+}
+
+uint32_t
+HTMLInputElement::Height()
+{
+ if (mType != NS_FORM_INPUT_IMAGE) {
+ return 0;
+ }
+ return GetWidthHeightForImage(mCurrentRequest).height;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetHeight(uint32_t* aHeight)
+{
+ *aHeight = Height();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetHeight(uint32_t aHeight)
+{
+ ErrorResult rv;
+ SetHeight(aHeight, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetIndeterminate(bool* aValue)
+{
+ *aValue = Indeterminate();
+ return NS_OK;
+}
+
+void
+HTMLInputElement::SetIndeterminateInternal(bool aValue,
+ bool aShouldInvalidate)
+{
+ mIndeterminate = aValue;
+
+ if (aShouldInvalidate) {
+ // Repaint the frame
+ nsIFrame* frame = GetPrimaryFrame();
+ if (frame)
+ frame->InvalidateFrameSubtree();
+ }
+
+ UpdateState(true);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetIndeterminate(bool aValue)
+{
+ SetIndeterminateInternal(aValue, true);
+ return NS_OK;
+}
+
+uint32_t
+HTMLInputElement::Width()
+{
+ if (mType != NS_FORM_INPUT_IMAGE) {
+ return 0;
+ }
+ return GetWidthHeightForImage(mCurrentRequest).width;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetWidth(uint32_t* aWidth)
+{
+ *aWidth = Width();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetWidth(uint32_t aWidth)
+{
+ ErrorResult rv;
+ SetWidth(aWidth, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetValue(nsAString& aValue)
+{
+ nsresult rv = GetValueInternal(aValue);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Don't return non-sanitized value for types that are experimental on mobile
+ // or datetime types
+ if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
+ SanitizeValue(aValue);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLInputElement::GetValueInternal(nsAString& aValue) const
+{
+ switch (GetValueMode()) {
+ case VALUE_MODE_VALUE:
+ if (IsSingleLineTextControl(false)) {
+ mInputData.mState->GetValue(aValue, true);
+ } else if (!aValue.Assign(mInputData.mValue, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+
+ case VALUE_MODE_FILENAME:
+ if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ aValue.Assign(mFirstFilePath);
+ } else {
+ // Just return the leaf name
+ if (mFilesOrDirectories.IsEmpty()) {
+ aValue.Truncate();
+ } else {
+ GetDOMFileOrDirectoryName(mFilesOrDirectories[0], aValue);
+ }
+ }
+
+ return NS_OK;
+
+ case VALUE_MODE_DEFAULT:
+ // Treat defaultValue as value.
+ GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue);
+ return NS_OK;
+
+ case VALUE_MODE_DEFAULT_ON:
+ // Treat default value as value and returns "on" if no value.
+ if (!GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue)) {
+ aValue.AssignLiteral("on");
+ }
+ return NS_OK;
+ }
+
+ // This return statement is required for some compilers.
+ return NS_OK;
+}
+
+bool
+HTMLInputElement::IsValueEmpty() const
+{
+ nsAutoString value;
+ GetValueInternal(value);
+
+ return value.IsEmpty();
+}
+
+void
+HTMLInputElement::ClearFiles(bool aSetValueChanged)
+{
+ nsTArray<OwningFileOrDirectory> data;
+ SetFilesOrDirectories(data, aSetValueChanged);
+}
+
+int32_t
+HTMLInputElement::MonthsSinceJan1970(uint32_t aYear, uint32_t aMonth) const
+{
+ return (aYear - 1970) * 12 + aMonth - 1;
+}
+
+/* static */ Decimal
+HTMLInputElement::StringToDecimal(const nsAString& aValue)
+{
+ if (!IsASCII(aValue)) {
+ return Decimal::nan();
+ }
+ NS_LossyConvertUTF16toASCII asciiString(aValue);
+ std::string stdString = asciiString.get();
+ return Decimal::fromString(stdString);
+}
+
+bool
+HTMLInputElement::ConvertStringToNumber(nsAString& aValue,
+ Decimal& aResultValue) const
+{
+ MOZ_ASSERT(DoesValueAsNumberApply(),
+ "ConvertStringToNumber only applies if .valueAsNumber applies");
+
+ switch (mType) {
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_RANGE:
+ {
+ aResultValue = StringToDecimal(aValue);
+ if (!aResultValue.isFinite()) {
+ return false;
+ }
+ return true;
+ }
+ case NS_FORM_INPUT_DATE:
+ {
+ uint32_t year, month, day;
+ if (!ParseDate(aValue, &year, &month, &day)) {
+ return false;
+ }
+
+ JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day));
+ if (!time.isValid()) {
+ return false;
+ }
+
+ aResultValue = Decimal::fromDouble(time.toDouble());
+ return true;
+ }
+ case NS_FORM_INPUT_TIME:
+ uint32_t milliseconds;
+ if (!ParseTime(aValue, &milliseconds)) {
+ return false;
+ }
+
+ aResultValue = Decimal(int32_t(milliseconds));
+ return true;
+ case NS_FORM_INPUT_MONTH:
+ {
+ uint32_t year, month;
+ if (!ParseMonth(aValue, &year, &month)) {
+ return false;
+ }
+
+ if (year < kMinimumYear || year > kMaximumYear) {
+ return false;
+ }
+
+ // Maximum valid month is 275760-09.
+ if (year == kMaximumYear && month > kMaximumMonthInMaximumYear) {
+ return false;
+ }
+
+ int32_t months = MonthsSinceJan1970(year, month);
+ aResultValue = Decimal(int32_t(months));
+ return true;
+ }
+ case NS_FORM_INPUT_WEEK:
+ {
+ uint32_t year, week;
+ if (!ParseWeek(aValue, &year, &week)) {
+ return false;
+ }
+
+ if (year < kMinimumYear || year > kMaximumYear) {
+ return false;
+ }
+
+ // Maximum week is 275760-W37, the week of 275760-09-13.
+ if (year == kMaximumYear && week > kMaximumWeekInMaximumYear) {
+ return false;
+ }
+
+ double days = DaysSinceEpochFromWeek(year, week);
+ aResultValue = Decimal::fromDouble(days * kMsPerDay);
+ return true;
+ }
+ default:
+ MOZ_ASSERT(false, "Unrecognized input type");
+ return false;
+ }
+}
+
+Decimal
+HTMLInputElement::GetValueAsDecimal() const
+{
+ Decimal decimalValue;
+ nsAutoString stringValue;
+
+ GetValueInternal(stringValue);
+
+ return !ConvertStringToNumber(stringValue, decimalValue) ? Decimal::nan()
+ : decimalValue;
+}
+
+void
+HTMLInputElement::GetValue(nsAString& aValue, ErrorResult& aRv)
+{
+ nsresult rv = GetValue(aValue);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+void
+HTMLInputElement::SetValue(const nsAString& aValue, ErrorResult& aRv)
+{
+ // check security. Note that setting the value to the empty string is always
+ // OK and gives pages a way to clear a file input if necessary.
+ if (mType == NS_FORM_INPUT_FILE) {
+ if (!aValue.IsEmpty()) {
+ if (!nsContentUtils::IsCallerChrome()) {
+ // setting the value of a "FILE" input widget requires
+ // chrome privilege
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+ Sequence<nsString> list;
+ if (!list.AppendElement(aValue, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ MozSetFileNameArray(list, aRv);
+ return;
+ }
+ else {
+ ClearFiles(true);
+ }
+ }
+ else {
+ if (MayFireChangeOnBlur()) {
+ // If the value has been set by a script, we basically want to keep the
+ // current change event state. If the element is ready to fire a change
+ // event, we should keep it that way. Otherwise, we should make sure the
+ // element will not fire any event because of the script interaction.
+ //
+ // NOTE: this is currently quite expensive work (too much string
+ // manipulation). We should probably optimize that.
+ nsAutoString currentValue;
+ GetValue(currentValue);
+
+ nsresult rv =
+ SetValueInternal(aValue, nsTextEditorState::eSetValue_ByContent |
+ nsTextEditorState::eSetValue_Notify);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ if (mFocusedValue.Equals(currentValue)) {
+ GetValue(mFocusedValue);
+ }
+ } else {
+ nsresult rv =
+ SetValueInternal(aValue, nsTextEditorState::eSetValue_ByContent |
+ nsTextEditorState::eSetValue_Notify);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetValue(const nsAString& aValue)
+{
+ ErrorResult rv;
+ SetValue(aValue, rv);
+ return rv.StealNSResult();
+}
+
+nsGenericHTMLElement*
+HTMLInputElement::GetList() const
+{
+ nsAutoString dataListId;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::list, dataListId);
+ if (dataListId.IsEmpty()) {
+ return nullptr;
+ }
+
+ //XXXsmaug How should this all work in case input element is in Shadow DOM.
+ nsIDocument* doc = GetUncomposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ Element* element = doc->GetElementById(dataListId);
+ if (!element || !element->IsHTMLElement(nsGkAtoms::datalist)) {
+ return nullptr;
+ }
+
+ return static_cast<nsGenericHTMLElement*>(element);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetList(nsIDOMHTMLElement** aValue)
+{
+ *aValue = nullptr;
+
+ RefPtr<nsGenericHTMLElement> element = GetList();
+ if (!element) {
+ return NS_OK;
+ }
+
+ element.forget(aValue);
+ return NS_OK;
+}
+
+void
+HTMLInputElement::SetValue(Decimal aValue)
+{
+ MOZ_ASSERT(!aValue.isInfinity(), "aValue must not be Infinity!");
+
+ if (aValue.isNaN()) {
+ SetValue(EmptyString());
+ return;
+ }
+
+ nsAutoString value;
+ ConvertNumberToString(aValue, value);
+ SetValue(value);
+}
+
+bool
+HTMLInputElement::ConvertNumberToString(Decimal aValue,
+ nsAString& aResultString) const
+{
+ MOZ_ASSERT(DoesValueAsNumberApply(),
+ "ConvertNumberToString is only implemented for types implementing .valueAsNumber");
+ MOZ_ASSERT(aValue.isFinite(),
+ "aValue must be a valid non-Infinite number.");
+
+ aResultString.Truncate();
+
+ switch (mType) {
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_RANGE:
+ {
+ char buf[32];
+ bool ok = aValue.toString(buf, ArrayLength(buf));
+ aResultString.AssignASCII(buf);
+ MOZ_ASSERT(ok, "buf not big enough");
+ return ok;
+ }
+ case NS_FORM_INPUT_DATE:
+ {
+ // The specs (and our JS APIs) require |aValue| to be truncated.
+ aValue = aValue.floor();
+
+ double year = JS::YearFromTime(aValue.toDouble());
+ double month = JS::MonthFromTime(aValue.toDouble());
+ double day = JS::DayFromTime(aValue.toDouble());
+
+ if (IsNaN(year) || IsNaN(month) || IsNaN(day)) {
+ return false;
+ }
+
+ aResultString.AppendPrintf("%04.0f-%02.0f-%02.0f", year,
+ month + 1, day);
+
+ return true;
+ }
+ case NS_FORM_INPUT_TIME:
+ {
+ // Per spec, we need to truncate |aValue| and we should only represent
+ // times inside a day [00:00, 24:00[, which means that we should do a
+ // modulo on |aValue| using the number of milliseconds in a day (86400000).
+ uint32_t value = NS_floorModulo(aValue.floor(), Decimal(86400000)).toDouble();
+
+ uint16_t milliseconds = value % 1000;
+ value /= 1000;
+
+ uint8_t seconds = value % 60;
+ value /= 60;
+
+ uint8_t minutes = value % 60;
+ value /= 60;
+
+ uint8_t hours = value;
+
+ if (milliseconds != 0) {
+ aResultString.AppendPrintf("%02d:%02d:%02d.%03d",
+ hours, minutes, seconds, milliseconds);
+ } else if (seconds != 0) {
+ aResultString.AppendPrintf("%02d:%02d:%02d",
+ hours, minutes, seconds);
+ } else {
+ aResultString.AppendPrintf("%02d:%02d", hours, minutes);
+ }
+
+ return true;
+ }
+ case NS_FORM_INPUT_MONTH:
+ {
+ aValue = aValue.floor();
+
+ double month = NS_floorModulo(aValue, Decimal(12)).toDouble();
+ month = (month < 0 ? month + 12 : month);
+
+ double year = 1970 + (aValue.toDouble() - month) / 12;
+
+ // Maximum valid month is 275760-09.
+ if (year < kMinimumYear || year > kMaximumYear) {
+ return false;
+ }
+
+ if (year == kMaximumYear && month > 8) {
+ return false;
+ }
+
+ aResultString.AppendPrintf("%04.0f-%02.0f", year, month + 1);
+ return true;
+ }
+ case NS_FORM_INPUT_WEEK:
+ {
+ aValue = aValue.floor();
+
+ // Based on ISO 8601 date.
+ double year = JS::YearFromTime(aValue.toDouble());
+ double month = JS::MonthFromTime(aValue.toDouble());
+ double day = JS::DayFromTime(aValue.toDouble());
+ // Adding 1 since day starts from 0.
+ double dayInYear = JS::DayWithinYear(aValue.toDouble(), year) + 1;
+
+ // Adding 1 since month starts from 0.
+ uint32_t isoWeekday = DayOfWeek(year, month + 1, day, true);
+ // Target on Wednesday since ISO 8601 states that week 1 is the week
+ // with the first Thursday of that year.
+ uint32_t week = (dayInYear - isoWeekday + 10) / 7;
+
+ if (week < 1) {
+ year--;
+ if (year < 1) {
+ return false;
+ }
+ week = MaximumWeekInYear(year);
+ } else if (week > MaximumWeekInYear(year)) {
+ year++;
+ if (year > kMaximumYear ||
+ (year == kMaximumYear && week > kMaximumWeekInMaximumYear)) {
+ return false;
+ }
+ week = 1;
+ }
+
+ aResultString.AppendPrintf("%04.0f-W%02d", year, week);
+ return true;
+ }
+ default:
+ MOZ_ASSERT(false, "Unrecognized input type");
+ return false;
+ }
+}
+
+
+Nullable<Date>
+HTMLInputElement::GetValueAsDate(ErrorResult& aRv)
+{
+ // TODO: this is temporary until bug 888331 is fixed.
+ if (!IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
+ return Nullable<Date>();
+ }
+
+ switch (mType) {
+ case NS_FORM_INPUT_DATE:
+ {
+ uint32_t year, month, day;
+ nsAutoString value;
+ GetValueInternal(value);
+ if (!ParseDate(value, &year, &month, &day)) {
+ return Nullable<Date>();
+ }
+
+ JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day));
+ return Nullable<Date>(Date(time));
+ }
+ case NS_FORM_INPUT_TIME:
+ {
+ uint32_t millisecond;
+ nsAutoString value;
+ GetValueInternal(value);
+ if (!ParseTime(value, &millisecond)) {
+ return Nullable<Date>();
+ }
+
+ JS::ClippedTime time = JS::TimeClip(millisecond);
+ MOZ_ASSERT(time.toDouble() == millisecond,
+ "HTML times are restricted to the day after the epoch and "
+ "never clip");
+ return Nullable<Date>(Date(time));
+ }
+ case NS_FORM_INPUT_MONTH:
+ {
+ uint32_t year, month;
+ nsAutoString value;
+ GetValueInternal(value);
+ if (!ParseMonth(value, &year, &month)) {
+ return Nullable<Date>();
+ }
+
+ JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, 1));
+ return Nullable<Date>(Date(time));
+ }
+ case NS_FORM_INPUT_WEEK:
+ {
+ uint32_t year, week;
+ nsAutoString value;
+ GetValueInternal(value);
+ if (!ParseWeek(value, &year, &week)) {
+ return Nullable<Date>();
+ }
+
+ double days = DaysSinceEpochFromWeek(year, week);
+ JS::ClippedTime time = JS::TimeClip(days * kMsPerDay);
+
+ return Nullable<Date>(Date(time));
+ }
+ }
+
+ MOZ_ASSERT(false, "Unrecognized input type");
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return Nullable<Date>();
+}
+
+void
+HTMLInputElement::SetValueAsDate(Nullable<Date> aDate, ErrorResult& aRv)
+{
+ // TODO: this is temporary until bug 888331 is fixed.
+ if (!IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (aDate.IsNull() || aDate.Value().IsUndefined()) {
+ aRv = SetValue(EmptyString());
+ return;
+ }
+
+ double milliseconds = aDate.Value().TimeStamp().toDouble();
+
+ if (mType != NS_FORM_INPUT_MONTH) {
+ SetValue(Decimal::fromDouble(milliseconds));
+ return;
+ }
+
+ // type=month expects the value to be number of months.
+ double year = JS::YearFromTime(milliseconds);
+ double month = JS::MonthFromTime(milliseconds);
+
+ if (IsNaN(year) || IsNaN(month)) {
+ SetValue(EmptyString());
+ return;
+ }
+
+ int32_t months = MonthsSinceJan1970(year, month + 1);
+ SetValue(Decimal(int32_t(months)));
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetValueAsNumber(double* aValueAsNumber)
+{
+ *aValueAsNumber = ValueAsNumber();
+ return NS_OK;
+}
+
+void
+HTMLInputElement::SetValueAsNumber(double aValueAsNumber, ErrorResult& aRv)
+{
+ // TODO: return TypeError when HTMLInputElement is converted to WebIDL, see
+ // bug 825197.
+ if (IsInfinite(aValueAsNumber)) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ if (!DoesValueAsNumberApply()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ SetValue(Decimal::fromDouble(aValueAsNumber));
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetValueAsNumber(double aValueAsNumber)
+{
+ ErrorResult rv;
+ SetValueAsNumber(aValueAsNumber, rv);
+ return rv.StealNSResult();
+}
+
+Decimal
+HTMLInputElement::GetMinimum() const
+{
+ MOZ_ASSERT(DoesValueAsNumberApply(),
+ "GetMinimum() should only be used for types that allow .valueAsNumber");
+
+ // Only type=range has a default minimum
+ Decimal defaultMinimum =
+ mType == NS_FORM_INPUT_RANGE ? Decimal(0) : Decimal::nan();
+
+ if (!HasAttr(kNameSpaceID_None, nsGkAtoms::min)) {
+ return defaultMinimum;
+ }
+
+ nsAutoString minStr;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
+
+ Decimal min;
+ return ConvertStringToNumber(minStr, min) ? min : defaultMinimum;
+}
+
+Decimal
+HTMLInputElement::GetMaximum() const
+{
+ MOZ_ASSERT(DoesValueAsNumberApply(),
+ "GetMaximum() should only be used for types that allow .valueAsNumber");
+
+ // Only type=range has a default maximum
+ Decimal defaultMaximum =
+ mType == NS_FORM_INPUT_RANGE ? Decimal(100) : Decimal::nan();
+
+ if (!HasAttr(kNameSpaceID_None, nsGkAtoms::max)) {
+ return defaultMaximum;
+ }
+
+ nsAutoString maxStr;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
+
+ Decimal max;
+ return ConvertStringToNumber(maxStr, max) ? max : defaultMaximum;
+}
+
+Decimal
+HTMLInputElement::GetStepBase() const
+{
+ MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER ||
+ mType == NS_FORM_INPUT_DATE ||
+ mType == NS_FORM_INPUT_TIME ||
+ mType == NS_FORM_INPUT_MONTH ||
+ mType == NS_FORM_INPUT_WEEK ||
+ mType == NS_FORM_INPUT_RANGE,
+ "Check that kDefaultStepBase is correct for this new type");
+
+ Decimal stepBase;
+
+ // Do NOT use GetMinimum here - the spec says to use "the min content
+ // attribute", not "the minimum".
+ nsAutoString minStr;
+ if (GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr) &&
+ ConvertStringToNumber(minStr, stepBase)) {
+ return stepBase;
+ }
+
+ // If @min is not a double, we should use @value.
+ nsAutoString valueStr;
+ if (GetAttr(kNameSpaceID_None, nsGkAtoms::value, valueStr) &&
+ ConvertStringToNumber(valueStr, stepBase)) {
+ return stepBase;
+ }
+
+ if (mType == NS_FORM_INPUT_WEEK) {
+ return kDefaultStepBaseWeek;
+ }
+
+ return kDefaultStepBase;
+}
+
+nsresult
+HTMLInputElement::GetValueIfStepped(int32_t aStep,
+ StepCallerType aCallerType,
+ Decimal* aNextStep)
+{
+ if (!DoStepDownStepUpApply()) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ Decimal stepBase = GetStepBase();
+ Decimal step = GetStep();
+ if (step == kStepAny) {
+ if (aCallerType != CALLED_FOR_USER_EVENT) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+ // Allow the spin buttons and up/down arrow keys to do something sensible:
+ step = GetDefaultStep();
+ }
+
+ Decimal minimum = GetMinimum();
+ Decimal maximum = GetMaximum();
+
+ if (!maximum.isNaN()) {
+ // "max - (max - stepBase) % step" is the nearest valid value to max.
+ maximum = maximum - NS_floorModulo(maximum - stepBase, step);
+ if (!minimum.isNaN()) {
+ if (minimum > maximum) {
+ // Either the minimum was greater than the maximum prior to our
+ // adjustment to align maximum on a step, or else (if we adjusted
+ // maximum) there is no valid step between minimum and the unadjusted
+ // maximum.
+ return NS_OK;
+ }
+ }
+ }
+
+ Decimal value = GetValueAsDecimal();
+ bool valueWasNaN = false;
+ if (value.isNaN()) {
+ value = Decimal(0);
+ valueWasNaN = true;
+ }
+ Decimal valueBeforeStepping = value;
+
+ Decimal deltaFromStep = NS_floorModulo(value - stepBase, step);
+
+ if (deltaFromStep != Decimal(0)) {
+ if (aStep > 0) {
+ value += step - deltaFromStep; // partial step
+ value += step * Decimal(aStep - 1); // then remaining steps
+ } else if (aStep < 0) {
+ value -= deltaFromStep; // partial step
+ value += step * Decimal(aStep + 1); // then remaining steps
+ }
+ } else {
+ value += step * Decimal(aStep);
+ }
+
+ if (value < minimum) {
+ value = minimum;
+ deltaFromStep = NS_floorModulo(value - stepBase, step);
+ if (deltaFromStep != Decimal(0)) {
+ value += step - deltaFromStep;
+ }
+ }
+ if (value > maximum) {
+ value = maximum;
+ deltaFromStep = NS_floorModulo(value - stepBase, step);
+ if (deltaFromStep != Decimal(0)) {
+ value -= deltaFromStep;
+ }
+ }
+
+ if (!valueWasNaN && // value="", resulting in us using "0"
+ ((aStep > 0 && value < valueBeforeStepping) ||
+ (aStep < 0 && value > valueBeforeStepping))) {
+ // We don't want step-up to effectively step down, or step-down to
+ // effectively step up, so return;
+ return NS_OK;
+ }
+
+ *aNextStep = value;
+ return NS_OK;
+}
+
+nsresult
+HTMLInputElement::ApplyStep(int32_t aStep)
+{
+ Decimal nextStep = Decimal::nan(); // unchanged if value will not change
+
+ nsresult rv = GetValueIfStepped(aStep, CALLED_FOR_SCRIPT, &nextStep);
+
+ if (NS_SUCCEEDED(rv) && nextStep.isFinite()) {
+ SetValue(nextStep);
+ }
+
+ return rv;
+}
+
+/* static */
+bool
+HTMLInputElement::IsExperimentalMobileType(uint8_t aType)
+{
+ return (aType == NS_FORM_INPUT_DATE &&
+ !Preferences::GetBool("dom.forms.datetime", false) &&
+ !Preferences::GetBool("dom.forms.datepicker", false)) ||
+ (aType == NS_FORM_INPUT_TIME &&
+ !Preferences::GetBool("dom.forms.datetime", false));
+}
+
+bool
+HTMLInputElement::IsDateTimeInputType(uint8_t aType)
+{
+ return aType == NS_FORM_INPUT_DATE ||
+ aType == NS_FORM_INPUT_TIME ||
+ aType == NS_FORM_INPUT_MONTH ||
+ aType == NS_FORM_INPUT_WEEK ||
+ aType == NS_FORM_INPUT_DATETIME_LOCAL;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::StepDown(int32_t n, uint8_t optional_argc)
+{
+ return ApplyStep(optional_argc ? -n : -1);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::StepUp(int32_t n, uint8_t optional_argc)
+{
+ return ApplyStep(optional_argc ? n : 1);
+}
+
+void
+HTMLInputElement::FlushFrames()
+{
+ if (GetComposedDoc()) {
+ GetComposedDoc()->FlushPendingNotifications(Flush_Frames);
+ }
+}
+
+void
+HTMLInputElement::MozGetFileNameArray(nsTArray<nsString>& aArray,
+ ErrorResult& aRv)
+{
+ for (uint32_t i = 0; i < mFilesOrDirectories.Length(); i++) {
+ nsAutoString str;
+ GetDOMFileOrDirectoryPath(mFilesOrDirectories[i], str, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ aArray.AppendElement(str);
+ }
+}
+
+
+NS_IMETHODIMP
+HTMLInputElement::MozGetFileNameArray(uint32_t* aLength, char16_t*** aFileNames)
+{
+ if (!nsContentUtils::IsCallerChrome()) {
+ // Since this function returns full paths it's important that normal pages
+ // can't call it.
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ ErrorResult rv;
+ nsTArray<nsString> array;
+ MozGetFileNameArray(array, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ *aLength = array.Length();
+ char16_t** ret =
+ static_cast<char16_t**>(moz_xmalloc(*aLength * sizeof(char16_t*)));
+ if (!ret) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < *aLength; ++i) {
+ ret[i] = NS_strdup(array[i].get());
+ }
+
+ *aFileNames = ret;
+
+ return NS_OK;
+}
+
+void
+HTMLInputElement::MozSetFileArray(const Sequence<OwningNonNull<File>>& aFiles)
+{
+ nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
+ MOZ_ASSERT(global);
+ if (!global) {
+ return;
+ }
+
+ nsTArray<OwningFileOrDirectory> files;
+ for (uint32_t i = 0; i < aFiles.Length(); ++i) {
+ RefPtr<File> file = File::Create(global, aFiles[i].get()->Impl());
+ MOZ_ASSERT(file);
+
+ OwningFileOrDirectory* element = files.AppendElement();
+ element->SetAsFile() = file;
+ }
+
+ SetFilesOrDirectories(files, true);
+}
+
+void
+HTMLInputElement::MozSetFileNameArray(const Sequence<nsString>& aFileNames,
+ ErrorResult& aRv)
+{
+ if (XRE_IsContentProcess()) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ nsTArray<OwningFileOrDirectory> files;
+ for (uint32_t i = 0; i < aFileNames.Length(); ++i) {
+ nsCOMPtr<nsIFile> file;
+
+ if (StringBeginsWith(aFileNames[i], NS_LITERAL_STRING("file:"),
+ nsASCIICaseInsensitiveStringComparator())) {
+ // Converts the URL string into the corresponding nsIFile if possible
+ // A local file will be created if the URL string begins with file://
+ NS_GetFileFromURLSpec(NS_ConvertUTF16toUTF8(aFileNames[i]),
+ getter_AddRefs(file));
+ }
+
+ if (!file) {
+ // this is no "file://", try as local file
+ NS_NewLocalFile(aFileNames[i], false, getter_AddRefs(file));
+ }
+
+ if (!file) {
+ continue; // Not much we can do if the file doesn't exist
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
+ if (!global) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<File> domFile = File::CreateFromFile(global, file);
+
+ OwningFileOrDirectory* element = files.AppendElement();
+ element->SetAsFile() = domFile;
+ }
+
+ SetFilesOrDirectories(files, true);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::MozSetFileNameArray(const char16_t** aFileNames,
+ uint32_t aLength)
+{
+ if (!nsContentUtils::IsCallerChrome()) {
+ // setting the value of a "FILE" input widget requires chrome privilege
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ Sequence<nsString> list;
+ nsString* names = list.AppendElements(aLength, fallible);
+ if (!names) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ for (uint32_t i = 0; i < aLength; ++i) {
+ const char16_t* filename = aFileNames[i];
+ names[i].Rebind(filename, nsCharTraits<char16_t>::length(filename));
+ }
+
+ ErrorResult rv;
+ MozSetFileNameArray(list, rv);
+ return rv.StealNSResult();
+}
+
+void
+HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIFile> file;
+ aRv = NS_NewLocalFile(aDirectoryPath, true, getter_AddRefs(file));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
+ if (NS_WARN_IF(!window)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<Directory> directory = Directory::Create(window, file);
+ MOZ_ASSERT(directory);
+
+ nsTArray<OwningFileOrDirectory> array;
+ OwningFileOrDirectory* element = array.AppendElement();
+ element->SetAsDirectory() = directory;
+
+ SetFilesOrDirectories(array, true);
+}
+
+void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue& aValue)
+{
+ if (NS_WARN_IF(!IsDateTimeInputType(mType)) || !mDateTimeInputBoxValue) {
+ return;
+ }
+
+ aValue = *mDateTimeInputBoxValue;
+}
+
+void
+HTMLInputElement::UpdateDateTimeInputBox(const DateTimeValue& aValue)
+{
+ if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+ return;
+ }
+
+ nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->SetValueFromPicker(aValue);
+ }
+}
+
+void
+HTMLInputElement::SetDateTimePickerState(bool aOpen)
+{
+ if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+ return;
+ }
+
+ nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->SetPickerState(aOpen);
+ }
+}
+
+void
+HTMLInputElement::OpenDateTimePicker(const DateTimeValue& aInitialValue)
+{
+ if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+ return;
+ }
+
+ mDateTimeInputBoxValue = new DateTimeValue(aInitialValue);
+ nsContentUtils::DispatchChromeEvent(OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(this),
+ NS_LITERAL_STRING("MozOpenDateTimePicker"),
+ true, true);
+}
+
+void
+HTMLInputElement::UpdateDateTimePicker(const DateTimeValue& aValue)
+{
+ if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+ return;
+ }
+
+ mDateTimeInputBoxValue = new DateTimeValue(aValue);
+ nsContentUtils::DispatchChromeEvent(OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(this),
+ NS_LITERAL_STRING("MozUpdateDateTimePicker"),
+ true, true);
+}
+
+void
+HTMLInputElement::CloseDateTimePicker()
+{
+ if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+ return;
+ }
+
+ nsContentUtils::DispatchChromeEvent(OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(this),
+ NS_LITERAL_STRING("MozCloseDateTimePicker"),
+ true, true);
+}
+
+bool
+HTMLInputElement::MozIsTextField(bool aExcludePassword)
+{
+ // TODO: temporary until bug 888320 is fixed.
+ if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
+ return false;
+ }
+
+ return IsSingleLineTextControl(aExcludePassword);
+}
+
+HTMLInputElement*
+HTMLInputElement::GetOwnerNumberControl()
+{
+ if (IsInNativeAnonymousSubtree() &&
+ mType == NS_FORM_INPUT_TEXT &&
+ GetParent() && GetParent()->GetParent()) {
+ HTMLInputElement* grandparent =
+ HTMLInputElement::FromContentOrNull(GetParent()->GetParent());
+ if (grandparent && grandparent->mType == NS_FORM_INPUT_NUMBER) {
+ return grandparent;
+ }
+ }
+ return nullptr;
+}
+
+HTMLInputElement*
+HTMLInputElement::GetOwnerDateTimeControl()
+{
+ if (IsInNativeAnonymousSubtree() &&
+ mType == NS_FORM_INPUT_TEXT &&
+ GetParent() &&
+ GetParent()->GetParent() &&
+ GetParent()->GetParent()->GetParent() &&
+ GetParent()->GetParent()->GetParent()->GetParent()) {
+ // Yes, this is very very deep.
+ HTMLInputElement* ownerDateTimeControl =
+ HTMLInputElement::FromContentOrNull(
+ GetParent()->GetParent()->GetParent()->GetParent());
+ if (ownerDateTimeControl &&
+ ownerDateTimeControl->mType == NS_FORM_INPUT_TIME) {
+ return ownerDateTimeControl;
+ }
+ }
+ return nullptr;
+}
+
+
+NS_IMETHODIMP
+HTMLInputElement::MozIsTextField(bool aExcludePassword, bool* aResult)
+{
+ *aResult = MozIsTextField(aExcludePassword);
+ return NS_OK;
+}
+
+void
+HTMLInputElement::SetUserInput(const nsAString& aInput,
+ nsIPrincipal& aSubjectPrincipal) {
+ if (mType == NS_FORM_INPUT_FILE &&
+ !nsContentUtils::IsSystemPrincipal(&aSubjectPrincipal)) {
+ return;
+ }
+
+ SetUserInput(aInput);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetUserInput(const nsAString& aValue)
+{
+ if (mType == NS_FORM_INPUT_FILE)
+ {
+ Sequence<nsString> list;
+ if (!list.AppendElement(aValue, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ ErrorResult rv;
+ MozSetFileNameArray(list, rv);
+ return rv.StealNSResult();
+ } else {
+ nsresult rv =
+ SetValueInternal(aValue, nsTextEditorState::eSetValue_BySetUserInput |
+ nsTextEditorState::eSetValue_Notify);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(this),
+ NS_LITERAL_STRING("input"), true,
+ true);
+
+ // If this element is not currently focused, it won't receive a change event for this
+ // update through the normal channels. So fire a change event immediately, instead.
+ if (!ShouldBlur(this)) {
+ FireChangeEventIfNeeded();
+ }
+
+ return NS_OK;
+}
+
+nsIEditor*
+HTMLInputElement::GetEditor()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ return state->GetEditor();
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP_(nsIEditor*)
+HTMLInputElement::GetTextEditor()
+{
+ return GetEditor();
+}
+
+NS_IMETHODIMP_(nsISelectionController*)
+HTMLInputElement::GetSelectionController()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ return state->GetSelectionController();
+ }
+ return nullptr;
+}
+
+nsFrameSelection*
+HTMLInputElement::GetConstFrameSelection()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ return state->GetConstFrameSelection();
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::BindToFrame(nsTextControlFrame* aFrame)
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ return state->BindToFrame(aFrame);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP_(void)
+HTMLInputElement::UnbindFromFrame(nsTextControlFrame* aFrame)
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state && aFrame) {
+ state->UnbindFromFrame(aFrame);
+ }
+}
+
+NS_IMETHODIMP
+HTMLInputElement::CreateEditor()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ return state->PrepareEditor();
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP_(nsIContent*)
+HTMLInputElement::GetRootEditorNode()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ return state->GetRootNode();
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP_(Element*)
+HTMLInputElement::CreatePlaceholderNode()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ NS_ENSURE_SUCCESS(state->CreatePlaceholderNode(), nullptr);
+ return state->GetPlaceholderNode();
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP_(Element*)
+HTMLInputElement::GetPlaceholderNode()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ return state->GetPlaceholderNode();
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP_(void)
+HTMLInputElement::UpdatePlaceholderVisibility(bool aNotify)
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ state->UpdatePlaceholderVisibility(aNotify);
+ }
+}
+
+NS_IMETHODIMP_(bool)
+HTMLInputElement::GetPlaceholderVisibility()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (!state) {
+ return false;
+ }
+
+ return state->GetPlaceholderVisibility();
+}
+
+void
+HTMLInputElement::GetDisplayFileName(nsAString& aValue) const
+{
+ if (OwnerDoc()->IsStaticDocument()) {
+ aValue = mStaticDocFileList;
+ return;
+ }
+
+ if (mFilesOrDirectories.Length() == 1) {
+ GetDOMFileOrDirectoryName(mFilesOrDirectories[0], aValue);
+ return;
+ }
+
+ nsXPIDLString value;
+
+ if (mFilesOrDirectories.IsEmpty()) {
+ if ((Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs()) ||
+ (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "NoDirSelected", value);
+ } else if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "NoFilesSelected", value);
+ } else {
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "NoFileSelected", value);
+ }
+ } else {
+ nsString count;
+ count.AppendInt(int(mFilesOrDirectories.Length()));
+
+ const char16_t* params[] = { count.get() };
+ nsContentUtils::FormatLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "XFilesSelected", params, value);
+ }
+
+ aValue = value;
+}
+
+void
+HTMLInputElement::SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories,
+ bool aSetValueChanged)
+{
+ ClearGetFilesHelpers();
+
+ if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
+ HTMLInputElementBinding::ClearCachedWebkitEntriesValue(this);
+ mEntries.Clear();
+ }
+
+ mFilesOrDirectories.Clear();
+ mFilesOrDirectories.AppendElements(aFilesOrDirectories);
+
+ AfterSetFilesOrDirectories(aSetValueChanged);
+}
+
+void
+HTMLInputElement::SetFiles(nsIDOMFileList* aFiles,
+ bool aSetValueChanged)
+{
+ RefPtr<FileList> files = static_cast<FileList*>(aFiles);
+ mFilesOrDirectories.Clear();
+ ClearGetFilesHelpers();
+
+ if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
+ HTMLInputElementBinding::ClearCachedWebkitEntriesValue(this);
+ mEntries.Clear();
+ }
+
+ if (aFiles) {
+ uint32_t listLength;
+ aFiles->GetLength(&listLength);
+ for (uint32_t i = 0; i < listLength; i++) {
+ OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement();
+ element->SetAsFile() = files->Item(i);
+ }
+ }
+
+ AfterSetFilesOrDirectories(aSetValueChanged);
+}
+
+// This method is used for testing only.
+void
+HTMLInputElement::MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
+{
+ SetFilesOrDirectories(aFilesOrDirectories, true);
+
+ if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
+ UpdateEntries(aFilesOrDirectories);
+ }
+
+ RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
+ new DispatchChangeEventCallback(this);
+
+ if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
+ ErrorResult rv;
+ GetFilesHelper* helper = GetOrCreateGetFilesHelper(true /* recursionFlag */,
+ rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return;
+ }
+
+ helper->AddCallback(dispatchChangeEventCallback);
+ } else {
+ dispatchChangeEventCallback->DispatchEvents();
+ }
+}
+
+void
+HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged)
+{
+ // No need to flush here, if there's no frame at this point we
+ // don't need to force creation of one just to tell it about this
+ // new value. We just want the display to update as needed.
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
+ if (formControlFrame) {
+ nsAutoString readableValue;
+ GetDisplayFileName(readableValue);
+ formControlFrame->SetFormProperty(nsGkAtoms::value, readableValue);
+ }
+
+ // Grab the full path here for any chrome callers who access our .value via a
+ // CPOW. This path won't be called from a CPOW meaning the potential sync IPC
+ // call under GetMozFullPath won't be rejected for not being urgent.
+ // XXX Protected by the ifndef because the blob code doesn't allow us to send
+ // this message in b2g.
+ if (mFilesOrDirectories.IsEmpty()) {
+ mFirstFilePath.Truncate();
+ } else {
+ ErrorResult rv;
+ GetDOMFileOrDirectoryPath(mFilesOrDirectories[0], mFirstFilePath, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ }
+ }
+
+ UpdateFileList();
+
+ if (aSetValueChanged) {
+ SetValueChanged(true);
+ }
+
+ UpdateAllValidityStates(true);
+}
+
+void
+HTMLInputElement::FireChangeEventIfNeeded()
+{
+ nsAutoString value;
+ GetValue(value);
+
+ if (!MayFireChangeOnBlur() || mFocusedValue.Equals(value)) {
+ return;
+ }
+
+ // Dispatch the change event.
+ mFocusedValue = value;
+ nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+ static_cast<nsIContent*>(this),
+ NS_LITERAL_STRING("change"), true,
+ false);
+}
+
+FileList*
+HTMLInputElement::GetFiles()
+{
+ if (mType != NS_FORM_INPUT_FILE) {
+ return nullptr;
+ }
+
+ if (Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs() &&
+ (!Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) ||
+ !HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
+ return nullptr;
+ }
+
+ if (!mFileList) {
+ mFileList = new FileList(static_cast<nsIContent*>(this));
+ UpdateFileList();
+ }
+
+ return mFileList;
+}
+
+/* static */ void
+HTMLInputElement::HandleNumberControlSpin(void* aData)
+{
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(aData);
+
+ NS_ASSERTION(input->mNumberControlSpinnerIsSpinning,
+ "Should have called nsRepeatService::Stop()");
+
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(input->GetPrimaryFrame());
+ if (input->mType != NS_FORM_INPUT_NUMBER || !numberControlFrame) {
+ // Type has changed (and possibly our frame type hasn't been updated yet)
+ // or else we've lost our frame. Either way, stop the timer and don't do
+ // anything else.
+ input->StopNumberControlSpinnerSpin();
+ } else {
+ input->StepNumberControlForUserEvent(input->mNumberControlSpinnerSpinsUp ? 1 : -1);
+ }
+}
+
+void
+HTMLInputElement::UpdateFileList()
+{
+ if (mFileList) {
+ mFileList->Clear();
+
+ const nsTArray<OwningFileOrDirectory>& array =
+ GetFilesOrDirectoriesInternal();
+
+ for (uint32_t i = 0; i < array.Length(); ++i) {
+ if (array[i].IsFile()) {
+ mFileList->Append(array[i].GetAsFile());
+ }
+ }
+ }
+}
+
+nsresult
+HTMLInputElement::SetValueInternal(const nsAString& aValue, uint32_t aFlags)
+{
+ NS_PRECONDITION(GetValueMode() != VALUE_MODE_FILENAME,
+ "Don't call SetValueInternal for file inputs");
+
+ switch (GetValueMode()) {
+ case VALUE_MODE_VALUE:
+ {
+ // At the moment, only single line text control have to sanitize their value
+ // Because we have to create a new string for that, we should prevent doing
+ // it if it's useless.
+ nsAutoString value(aValue);
+
+ if (mDoneCreating) {
+ SanitizeValue(value);
+ }
+ // else DoneCreatingElement calls us again once mDoneCreating is true
+
+ bool setValueChanged = !!(aFlags & nsTextEditorState::eSetValue_Notify);
+ if (setValueChanged) {
+ SetValueChanged(true);
+ }
+
+ if (IsSingleLineTextControl(false)) {
+ if (!mInputData.mState->SetValue(value, aFlags)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ if (mType == NS_FORM_INPUT_EMAIL) {
+ UpdateAllValidityStates(!mDoneCreating);
+ }
+ } else {
+ free(mInputData.mValue);
+ mInputData.mValue = ToNewUnicode(value);
+ if (setValueChanged) {
+ SetValueChanged(true);
+ }
+ if (mType == NS_FORM_INPUT_NUMBER) {
+ // This has to happen before OnValueChanged is called because that
+ // method needs the new value of our frame's anon text control.
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ numberControlFrame->SetValueOfAnonTextControl(value);
+ }
+ } else if (mType == NS_FORM_INPUT_RANGE) {
+ nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->UpdateForValueChange();
+ }
+ } else if (mType == NS_FORM_INPUT_TIME &&
+ !IsExperimentalMobileType(mType)) {
+ nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->UpdateInputBoxValue();
+ }
+ }
+ if (mDoneCreating) {
+ OnValueChanged(/* aNotify = */ true,
+ /* aWasInteractiveUserChange = */ false);
+ }
+ // else DoneCreatingElement calls us again once mDoneCreating is true
+ }
+
+ if (mType == NS_FORM_INPUT_COLOR) {
+ // Update color frame, to reflect color changes
+ nsColorControlFrame* colorControlFrame = do_QueryFrame(GetPrimaryFrame());
+ if (colorControlFrame) {
+ colorControlFrame->UpdateColor();
+ }
+ }
+
+ // This call might be useless in some situations because if the element is
+ // a single line text control, nsTextEditorState::SetValue will call
+ // nsHTMLInputElement::OnValueChanged which is going to call UpdateState()
+ // if the element is focused. This bug 665547.
+ if (PlaceholderApplies() &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
+ UpdateState(true);
+ }
+
+ return NS_OK;
+ }
+
+ case VALUE_MODE_DEFAULT:
+ case VALUE_MODE_DEFAULT_ON:
+ // If the value of a hidden input was changed, we mark it changed so that we
+ // will know we need to save / restore the value. Yes, we are overloading
+ // the meaning of ValueChanged just a teensy bit to save a measly byte of
+ // storage space in HTMLInputElement. Yes, you are free to make a new flag,
+ // NEED_TO_SAVE_VALUE, at such time as mBitField becomes a 16-bit value.
+ if (mType == NS_FORM_INPUT_HIDDEN) {
+ SetValueChanged(true);
+ }
+
+ // Treat value == defaultValue for other input elements.
+ return nsGenericHTMLFormElementWithState::SetAttr(kNameSpaceID_None,
+ nsGkAtoms::value, aValue,
+ true);
+
+ case VALUE_MODE_FILENAME:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // This return statement is required for some compilers.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetValueChanged(bool aValueChanged)
+{
+ bool valueChangedBefore = mValueChanged;
+
+ mValueChanged = aValueChanged;
+
+ if (valueChangedBefore != aValueChanged) {
+ UpdateState(true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetChecked(bool* aChecked)
+{
+ *aChecked = Checked();
+ return NS_OK;
+}
+
+void
+HTMLInputElement::SetCheckedChanged(bool aCheckedChanged)
+{
+ DoSetCheckedChanged(aCheckedChanged, true);
+}
+
+void
+HTMLInputElement::DoSetCheckedChanged(bool aCheckedChanged,
+ bool aNotify)
+{
+ if (mType == NS_FORM_INPUT_RADIO) {
+ if (mCheckedChanged != aCheckedChanged) {
+ nsCOMPtr<nsIRadioVisitor> visitor =
+ new nsRadioSetCheckedChangedVisitor(aCheckedChanged);
+ VisitGroup(visitor, aNotify);
+ }
+ } else {
+ SetCheckedChangedInternal(aCheckedChanged);
+ }
+}
+
+void
+HTMLInputElement::SetCheckedChangedInternal(bool aCheckedChanged)
+{
+ bool checkedChangedBefore = mCheckedChanged;
+
+ mCheckedChanged = aCheckedChanged;
+
+ // This method can't be called when we are not authorized to notify
+ // so we do not need a aNotify parameter.
+ if (checkedChangedBefore != aCheckedChanged) {
+ UpdateState(true);
+ }
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetChecked(bool aChecked)
+{
+ DoSetChecked(aChecked, true, true);
+ return NS_OK;
+}
+
+void
+HTMLInputElement::DoSetChecked(bool aChecked, bool aNotify,
+ bool aSetValueChanged)
+{
+ // If the user or JS attempts to set checked, whether it actually changes the
+ // value or not, we say the value was changed so that defaultValue don't
+ // affect it no more.
+ if (aSetValueChanged) {
+ DoSetCheckedChanged(true, aNotify);
+ }
+
+ // Don't do anything if we're not changing whether it's checked (it would
+ // screw up state actually, especially when you are setting radio button to
+ // false)
+ if (mChecked == aChecked) {
+ return;
+ }
+
+ // Set checked
+ if (mType != NS_FORM_INPUT_RADIO) {
+ SetCheckedInternal(aChecked, aNotify);
+ return;
+ }
+
+ // For radio button, we need to do some extra fun stuff
+ if (aChecked) {
+ RadioSetChecked(aNotify);
+ return;
+ }
+
+ nsIRadioGroupContainer* container = GetRadioGroupContainer();
+ if (container) {
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+ container->SetCurrentRadioButton(name, nullptr);
+ }
+ // SetCheckedInternal is going to ask all radios to update their
+ // validity state. We have to be sure the radio group container knows
+ // the currently selected radio.
+ SetCheckedInternal(false, aNotify);
+}
+
+void
+HTMLInputElement::RadioSetChecked(bool aNotify)
+{
+ // Find the selected radio button so we can deselect it
+ nsCOMPtr<nsIDOMHTMLInputElement> currentlySelected = GetSelectedRadioButton();
+
+ // Deselect the currently selected radio button
+ if (currentlySelected) {
+ // Pass true for the aNotify parameter since the currently selected
+ // button is already in the document.
+ static_cast<HTMLInputElement*>(currentlySelected.get())
+ ->SetCheckedInternal(false, true);
+ }
+
+ // Let the group know that we are now the One True Radio Button
+ nsIRadioGroupContainer* container = GetRadioGroupContainer();
+ if (container) {
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+ container->SetCurrentRadioButton(name, this);
+ }
+
+ // SetCheckedInternal is going to ask all radios to update their
+ // validity state.
+ SetCheckedInternal(true, aNotify);
+}
+
+nsIRadioGroupContainer*
+HTMLInputElement::GetRadioGroupContainer() const
+{
+ NS_ASSERTION(mType == NS_FORM_INPUT_RADIO,
+ "GetRadioGroupContainer should only be called when type='radio'");
+
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+
+ if (name.IsEmpty()) {
+ return nullptr;
+ }
+
+ if (mForm) {
+ return mForm;
+ }
+
+ //XXXsmaug It isn't clear how this should work in Shadow DOM.
+ return static_cast<nsDocument*>(GetUncomposedDoc());
+}
+
+already_AddRefed<nsIDOMHTMLInputElement>
+HTMLInputElement::GetSelectedRadioButton() const
+{
+ nsIRadioGroupContainer* container = GetRadioGroupContainer();
+ if (!container) {
+ return nullptr;
+ }
+
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+
+ nsCOMPtr<nsIDOMHTMLInputElement> selected = container->GetCurrentRadioButton(name);
+ return selected.forget();
+}
+
+nsresult
+HTMLInputElement::MaybeSubmitForm(nsPresContext* aPresContext)
+{
+ if (!mForm) {
+ // Nothing to do here.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPresShell> shell = aPresContext->GetPresShell();
+ if (!shell) {
+ return NS_OK;
+ }
+
+ // Get the default submit element
+ nsIFormControl* submitControl = mForm->GetDefaultSubmitElement();
+ if (submitControl) {
+ nsCOMPtr<nsIContent> submitContent = do_QueryInterface(submitControl);
+ NS_ASSERTION(submitContent, "Form control not implementing nsIContent?!");
+ // Fire the button's onclick handler and let the button handle
+ // submitting the form.
+ WidgetMouseEvent event(true, eMouseClick, nullptr, WidgetMouseEvent::eReal);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ shell->HandleDOMEventWithTarget(submitContent, &event, &status);
+ } else if (!mForm->ImplicitSubmissionIsDisabled() &&
+ mForm->SubmissionCanProceed(nullptr)) {
+ // TODO: removing this code and have the submit event sent by the form,
+ // bug 592124.
+ // If there's only one text control, just submit the form
+ // Hold strong ref across the event
+ RefPtr<mozilla::dom::HTMLFormElement> form = mForm;
+ InternalFormEvent event(true, eFormSubmit);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ shell->HandleDOMEventWithTarget(form, &event, &status);
+ }
+
+ return NS_OK;
+}
+
+void
+HTMLInputElement::SetCheckedInternal(bool aChecked, bool aNotify)
+{
+ // Set the value
+ mChecked = aChecked;
+
+ // Notify the frame
+ if (mType == NS_FORM_INPUT_CHECKBOX || mType == NS_FORM_INPUT_RADIO) {
+ nsIFrame* frame = GetPrimaryFrame();
+ if (frame) {
+ frame->InvalidateFrameSubtree();
+ }
+ }
+
+ UpdateAllValidityStates(aNotify);
+
+ // Notify the document that the CSS :checked pseudoclass for this element
+ // has changed state.
+ UpdateState(aNotify);
+
+ // Notify all radios in the group that value has changed, this is to let
+ // radios to have the chance to update its states, e.g., :indeterminate.
+ if (mType == NS_FORM_INPUT_RADIO) {
+ nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
+ VisitGroup(visitor, aNotify);
+ }
+}
+
+void
+HTMLInputElement::Blur(ErrorResult& aError)
+{
+ if (mType == NS_FORM_INPUT_NUMBER) {
+ // Blur our anonymous text control, if we have one. (DOM 'change' event
+ // firing and other things depend on this.)
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ HTMLInputElement* textControl = numberControlFrame->GetAnonTextControl();
+ if (textControl) {
+ textControl->Blur(aError);
+ return;
+ }
+ }
+ }
+
+ if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) {
+ nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->HandleBlurEvent();
+ return;
+ }
+ }
+
+ nsGenericHTMLElement::Blur(aError);
+}
+
+void
+HTMLInputElement::Focus(ErrorResult& aError)
+{
+ if (mType == NS_FORM_INPUT_NUMBER) {
+ // Focus our anonymous text control, if we have one.
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ HTMLInputElement* textControl = numberControlFrame->GetAnonTextControl();
+ if (textControl) {
+ textControl->Focus(aError);
+ return;
+ }
+ }
+ }
+
+ if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) {
+ nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->HandleFocusEvent();
+ return;
+ }
+ }
+
+ if (mType != NS_FORM_INPUT_FILE) {
+ nsGenericHTMLElement::Focus(aError);
+ return;
+ }
+
+ // For file inputs, focus the first button instead. In the case of there
+ // being two buttons (when the picker is a directory picker) the user can
+ // tab to the next one.
+ nsIFrame* frame = GetPrimaryFrame();
+ if (frame) {
+ for (nsIFrame* childFrame : frame->PrincipalChildList()) {
+ // See if the child is a button control.
+ nsCOMPtr<nsIFormControl> formCtrl =
+ do_QueryInterface(childFrame->GetContent());
+ if (formCtrl && formCtrl->GetType() == NS_FORM_BUTTON_BUTTON) {
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(formCtrl);
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm && element) {
+ fm->SetFocus(element, 0);
+ }
+ break;
+ }
+ }
+ }
+
+ return;
+}
+
+#if !defined(ANDROID) && !defined(XP_MACOSX)
+bool
+HTMLInputElement::IsNodeApzAwareInternal() const
+{
+ // Tell APZC we may handle mouse wheel event and do preventDefault when input
+ // type is number.
+ return (mType == NS_FORM_INPUT_NUMBER) || (mType == NS_FORM_INPUT_RANGE) ||
+ nsINode::IsNodeApzAwareInternal();
+}
+#endif
+
+bool
+HTMLInputElement::IsInteractiveHTMLContent(bool aIgnoreTabindex) const
+{
+ return mType != NS_FORM_INPUT_HIDDEN ||
+ nsGenericHTMLFormElementWithState::IsInteractiveHTMLContent(aIgnoreTabindex);
+}
+
+void
+HTMLInputElement::AsyncEventRunning(AsyncEventDispatcher* aEvent)
+{
+ nsImageLoadingContent::AsyncEventRunning(aEvent);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::Select()
+{
+ if (mType == NS_FORM_INPUT_NUMBER) {
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ return numberControlFrame->HandleSelectCall();
+ }
+ return NS_OK;
+ }
+
+ if (!IsSingleLineTextControl(false)) {
+ return NS_OK;
+ }
+
+ // XXX Bug? We have to give the input focus before contents can be
+ // selected
+
+ FocusTristate state = FocusState();
+ if (state == eUnfocusable) {
+ return NS_OK;
+ }
+
+ nsTextEditorState* tes = GetEditorState();
+ if (tes) {
+ RefPtr<nsFrameSelection> fs = tes->GetConstFrameSelection();
+ if (fs && fs->MouseDownRecorded()) {
+ // This means that we're being called while the frame selection has a mouse
+ // down event recorded to adjust the caret during the mouse up event.
+ // We are probably called from the focus event handler. We should override
+ // the delayed caret data in this case to ensure that this select() call
+ // takes effect.
+ fs->SetDelayedCaretData(nullptr);
+ }
+ }
+
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+
+ RefPtr<nsPresContext> presContext = GetPresContext(eForComposedDoc);
+ if (state == eInactiveWindow) {
+ if (fm)
+ fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL);
+ SelectAll(presContext);
+ return NS_OK;
+ }
+
+ if (DispatchSelectEvent(presContext) && fm) {
+ fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL);
+
+ // ensure that the element is actually focused
+ nsCOMPtr<nsIDOMElement> focusedElement;
+ fm->GetFocusedElement(getter_AddRefs(focusedElement));
+ if (SameCOMIdentity(static_cast<nsIDOMNode*>(this), focusedElement)) {
+ // Now Select all the text!
+ SelectAll(presContext);
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+HTMLInputElement::DispatchSelectEvent(nsPresContext* aPresContext)
+{
+ nsEventStatus status = nsEventStatus_eIgnore;
+
+ // If already handling select event, don't dispatch a second.
+ if (!mHandlingSelectEvent) {
+ WidgetEvent event(nsContentUtils::LegacyIsCallerChromeOrNativeCode(), eFormSelect);
+
+ mHandlingSelectEvent = true;
+ EventDispatcher::Dispatch(static_cast<nsIContent*>(this),
+ aPresContext, &event, nullptr, &status);
+ mHandlingSelectEvent = false;
+ }
+
+ // If the DOM event was not canceled (e.g. by a JS event handler
+ // returning false)
+ return (status == nsEventStatus_eIgnore);
+}
+
+void
+HTMLInputElement::SelectAll(nsPresContext* aPresContext)
+{
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+
+ if (formControlFrame) {
+ formControlFrame->SetFormProperty(nsGkAtoms::select, EmptyString());
+ }
+}
+
+bool
+HTMLInputElement::NeedToInitializeEditorForEvent(
+ EventChainPreVisitor& aVisitor) const
+{
+ // We only need to initialize the editor for single line input controls because they
+ // are lazily initialized. We don't need to initialize the control for
+ // certain types of events, because we know that those events are safe to be
+ // handled without the editor being initialized. These events include:
+ // mousein/move/out, overflow/underflow, and DOM mutation events.
+ if (!IsSingleLineTextControl(false) ||
+ aVisitor.mEvent->mClass == eMutationEventClass) {
+ return false;
+ }
+
+ switch (aVisitor.mEvent->mMessage) {
+ case eMouseMove:
+ case eMouseEnterIntoWidget:
+ case eMouseExitFromWidget:
+ case eMouseOver:
+ case eMouseOut:
+ case eScrollPortUnderflow:
+ case eScrollPortOverflow:
+ return false;
+ default:
+ return true;
+ }
+}
+
+bool
+HTMLInputElement::IsDisabledForEvents(EventMessage aMessage)
+{
+ return IsElementDisabledForEvents(aMessage, GetPrimaryFrame());
+}
+
+nsresult
+HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ // Do not process any DOM events if the element is disabled
+ aVisitor.mCanHandle = false;
+ if (IsDisabledForEvents(aVisitor.mEvent->mMessage)) {
+ return NS_OK;
+ }
+
+ // Initialize the editor if needed.
+ if (NeedToInitializeEditorForEvent(aVisitor)) {
+ nsITextControlFrame* textControlFrame = do_QueryFrame(GetPrimaryFrame());
+ if (textControlFrame)
+ textControlFrame->EnsureEditorInitialized();
+ }
+
+ //FIXME Allow submission etc. also when there is no prescontext, Bug 329509.
+ if (!aVisitor.mPresContext) {
+ return nsGenericHTMLElement::PreHandleEvent(aVisitor);
+ }
+ //
+ // Web pages expect the value of a radio button or checkbox to be set
+ // *before* onclick and DOMActivate fire, and they expect that if they set
+ // the value explicitly during onclick or DOMActivate it will not be toggled
+ // or any such nonsense.
+ // In order to support that (bug 57137 and 58460 are examples) we toggle
+ // the checked attribute *first*, and then fire onclick. If the user
+ // returns false, we reset the control to the old checked value. Otherwise,
+ // we dispatch DOMActivate. If DOMActivate is cancelled, we also reset
+ // the control to the old checked value. We need to keep track of whether
+ // we've already toggled the state from onclick since the user could
+ // explicitly dispatch DOMActivate on the element.
+ //
+ // This is a compatibility hack.
+ //
+
+ // Track whether we're in the outermost Dispatch invocation that will
+ // cause activation of the input. That is, if we're a click event, or a
+ // DOMActivate that was dispatched directly, this will be set, but if we're
+ // a DOMActivate dispatched from click handling, it will not be set.
+ WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
+ bool outerActivateEvent =
+ ((mouseEvent && mouseEvent->IsLeftClickEvent()) ||
+ (aVisitor.mEvent->mMessage == eLegacyDOMActivate && !mInInternalActivate));
+
+ if (outerActivateEvent) {
+ aVisitor.mItemFlags |= NS_OUTER_ACTIVATE_EVENT;
+ }
+
+ bool originalCheckedValue = false;
+
+ if (outerActivateEvent) {
+ mCheckedIsToggled = false;
+
+ switch(mType) {
+ case NS_FORM_INPUT_CHECKBOX:
+ {
+ if (mIndeterminate) {
+ // indeterminate is always set to FALSE when the checkbox is toggled
+ SetIndeterminateInternal(false, false);
+ aVisitor.mItemFlags |= NS_ORIGINAL_INDETERMINATE_VALUE;
+ }
+
+ GetChecked(&originalCheckedValue);
+ DoSetChecked(!originalCheckedValue, true, true);
+ mCheckedIsToggled = true;
+ }
+ break;
+
+ case NS_FORM_INPUT_RADIO:
+ {
+ nsCOMPtr<nsIDOMHTMLInputElement> selectedRadioButton = GetSelectedRadioButton();
+ aVisitor.mItemData = selectedRadioButton;
+
+ originalCheckedValue = mChecked;
+ if (!originalCheckedValue) {
+ DoSetChecked(true, true, true);
+ mCheckedIsToggled = true;
+ }
+ }
+ break;
+
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_IMAGE:
+ if (mForm) {
+ // tell the form that we are about to enter a click handler.
+ // that means that if there are scripted submissions, the
+ // latest one will be deferred until after the exit point of the handler.
+ mForm->OnSubmitClickBegin(this);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (originalCheckedValue) {
+ aVisitor.mItemFlags |= NS_ORIGINAL_CHECKED_VALUE;
+ }
+
+ // If mNoContentDispatch is true we will not allow content to handle
+ // this event. But to allow middle mouse button paste to work we must allow
+ // middle clicks to go to text fields anyway.
+ if (aVisitor.mEvent->mFlags.mNoContentDispatch) {
+ aVisitor.mItemFlags |= NS_NO_CONTENT_DISPATCH;
+ }
+ if (IsSingleLineTextControl(false) &&
+ aVisitor.mEvent->mMessage == eMouseClick &&
+ aVisitor.mEvent->AsMouseEvent()->button ==
+ WidgetMouseEvent::eMiddleButton) {
+ aVisitor.mEvent->mFlags.mNoContentDispatch = false;
+ }
+
+ // We must cache type because mType may change during JS event (bug 2369)
+ aVisitor.mItemFlags |= mType;
+
+ if (aVisitor.mEvent->mMessage == eFocus &&
+ aVisitor.mEvent->IsTrusted() &&
+ MayFireChangeOnBlur() &&
+ // StartRangeThumbDrag already set mFocusedValue on 'mousedown' before
+ // we get the 'focus' event.
+ !mIsDraggingRange) {
+ GetValue(mFocusedValue);
+ }
+
+ // Fire onchange (if necessary), before we do the blur, bug 357684.
+ if (aVisitor.mEvent->mMessage == eBlur) {
+ // Experimental mobile types rely on the system UI to prevent users to not
+ // set invalid values but we have to be extra-careful. Especially if the
+ // option has been enabled on desktop.
+ if (IsExperimentalMobileType(mType)) {
+ nsAutoString aValue;
+ GetValueInternal(aValue);
+ nsresult rv =
+ SetValueInternal(aValue, nsTextEditorState::eSetValue_Internal);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ FireChangeEventIfNeeded();
+ }
+
+ if (mType == NS_FORM_INPUT_RANGE &&
+ (aVisitor.mEvent->mMessage == eFocus ||
+ aVisitor.mEvent->mMessage == eBlur)) {
+ // Just as nsGenericHTMLFormElementWithState::PreHandleEvent calls
+ // nsIFormControlFrame::SetFocus, we handle focus here.
+ nsIFrame* frame = GetPrimaryFrame();
+ if (frame) {
+ frame->InvalidateFrameSubtree();
+ }
+ }
+
+ if (mType == NS_FORM_INPUT_TIME &&
+ !IsExperimentalMobileType(mType) &&
+ aVisitor.mEvent->mMessage == eFocus &&
+ aVisitor.mEvent->mOriginalTarget == this) {
+ // If original target is this and not the anonymous text control, we should
+ // pass the focus to the anonymous text control.
+ nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->HandleFocusEvent();
+ }
+ }
+
+ if (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted()) {
+ if (mNumberControlSpinnerIsSpinning) {
+ // If the timer is running the user has depressed the mouse on one of the
+ // spin buttons. If the mouse exits the button we either want to reverse
+ // the direction of spin if it has moved over the other button, or else
+ // we want to end the spin. We do this here (rather than in
+ // PostHandleEvent) because we don't want to let content preventDefault()
+ // the end of the spin.
+ if (aVisitor.mEvent->mMessage == eMouseMove) {
+ // Be aggressive about stopping the spin:
+ bool stopSpin = true;
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ bool oldNumberControlSpinTimerSpinsUpValue =
+ mNumberControlSpinnerSpinsUp;
+ switch (numberControlFrame->GetSpinButtonForPointerEvent(
+ aVisitor.mEvent->AsMouseEvent())) {
+ case nsNumberControlFrame::eSpinButtonUp:
+ mNumberControlSpinnerSpinsUp = true;
+ stopSpin = false;
+ break;
+ case nsNumberControlFrame::eSpinButtonDown:
+ mNumberControlSpinnerSpinsUp = false;
+ stopSpin = false;
+ break;
+ }
+ if (mNumberControlSpinnerSpinsUp !=
+ oldNumberControlSpinTimerSpinsUpValue) {
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ numberControlFrame->SpinnerStateChanged();
+ }
+ }
+ }
+ if (stopSpin) {
+ StopNumberControlSpinnerSpin();
+ }
+ } else if (aVisitor.mEvent->mMessage == eMouseUp) {
+ StopNumberControlSpinnerSpin();
+ }
+ }
+ if (aVisitor.mEvent->mMessage == eFocus ||
+ aVisitor.mEvent->mMessage == eBlur) {
+ if (aVisitor.mEvent->mMessage == eFocus) {
+ // Tell our frame it's getting focus so that it can make sure focus
+ // is moved to our anonymous text control.
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ // This could kill the frame!
+ numberControlFrame->HandleFocusEvent(aVisitor.mEvent);
+ }
+ }
+ nsIFrame* frame = GetPrimaryFrame();
+ if (frame && frame->IsThemed()) {
+ // Our frame's nested <input type=text> will be invalidated when it
+ // loses focus, but since we are also native themed we need to make
+ // sure that our entire area is repainted since any focus highlight
+ // from the theme should be removed from us (the repainting of the
+ // sub-area occupied by the anon text control is not enough to do
+ // that).
+ frame->InvalidateFrame();
+ }
+ }
+ }
+
+ nsresult rv = nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor);
+
+ // We do this after calling the base class' PreHandleEvent so that
+ // nsIContent::PreHandleEvent doesn't reset any change we make to mCanHandle.
+ if (mType == NS_FORM_INPUT_NUMBER &&
+ aVisitor.mEvent->IsTrusted() &&
+ aVisitor.mEvent->mOriginalTarget != this) {
+ // <input type=number> has an anonymous <input type=text> descendant. If
+ // 'input' or 'change' events are fired at that text control then we need
+ // to do some special handling here.
+ HTMLInputElement* textControl = nullptr;
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ textControl = numberControlFrame->GetAnonTextControl();
+ }
+ if (textControl && aVisitor.mEvent->mOriginalTarget == textControl) {
+ if (aVisitor.mEvent->mMessage == eEditorInput) {
+ // Propogate the anon text control's new value to our HTMLInputElement:
+ nsAutoString value;
+ numberControlFrame->GetValueOfAnonTextControl(value);
+ numberControlFrame->HandlingInputEvent(true);
+ nsWeakFrame weakNumberControlFrame(numberControlFrame);
+ rv = SetValueInternal(value,
+ nsTextEditorState::eSetValue_BySetUserInput |
+ nsTextEditorState::eSetValue_Notify);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (weakNumberControlFrame.IsAlive()) {
+ numberControlFrame->HandlingInputEvent(false);
+ }
+ }
+ else if (aVisitor.mEvent->mMessage == eFormChange) {
+ // We cancel the DOM 'change' event that is fired for any change to our
+ // anonymous text control since we fire our own 'change' events and
+ // content shouldn't be seeing two 'change' events. Besides that we
+ // (as a number) control have tighter restrictions on when our internal
+ // value changes than our anon text control does, so in some cases
+ // (if our text control's value doesn't parse as a number) we don't
+ // want to fire a 'change' event at all.
+ aVisitor.mCanHandle = false;
+ }
+ }
+ }
+
+ // Stop the event if the related target's first non-native ancestor is the
+ // same as the original target's first non-native ancestor (we are moving
+ // inside of the same element).
+ if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType) &&
+ (aVisitor.mEvent->mMessage == eFocus ||
+ aVisitor.mEvent->mMessage == eFocusIn ||
+ aVisitor.mEvent->mMessage == eFocusOut ||
+ aVisitor.mEvent->mMessage == eBlur)) {
+ nsCOMPtr<nsIContent> originalTarget =
+ do_QueryInterface(aVisitor.mEvent->AsFocusEvent()->mRelatedTarget);
+ nsCOMPtr<nsIContent> relatedTarget =
+ do_QueryInterface(aVisitor.mEvent->AsFocusEvent()->mRelatedTarget);
+
+ if (originalTarget && relatedTarget &&
+ originalTarget->FindFirstNonChromeOnlyAccessContent() ==
+ relatedTarget->FindFirstNonChromeOnlyAccessContent()) {
+ aVisitor.mCanHandle = false;
+ }
+ }
+
+ return rv;
+}
+
+void
+HTMLInputElement::StartRangeThumbDrag(WidgetGUIEvent* aEvent)
+{
+ mIsDraggingRange = true;
+ mRangeThumbDragStartValue = GetValueAsDecimal();
+ // Don't use CAPTURE_RETARGETTOELEMENT, as that breaks pseudo-class styling
+ // of the thumb.
+ nsIPresShell::SetCapturingContent(this, CAPTURE_IGNOREALLOWED);
+ nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
+
+ // Before we change the value, record the current value so that we'll
+ // correctly send a 'change' event if appropriate. We need to do this here
+ // because the 'focus' event is handled after the 'mousedown' event that
+ // we're being called for (i.e. too late to update mFocusedValue, since we'll
+ // have changed it by then).
+ GetValue(mFocusedValue);
+
+ SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent));
+}
+
+void
+HTMLInputElement::FinishRangeThumbDrag(WidgetGUIEvent* aEvent)
+{
+ MOZ_ASSERT(mIsDraggingRange);
+
+ if (nsIPresShell::GetCapturingContent() == this) {
+ nsIPresShell::SetCapturingContent(nullptr, 0); // cancel capture
+ }
+ if (aEvent) {
+ nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
+ SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent));
+ }
+ mIsDraggingRange = false;
+ FireChangeEventIfNeeded();
+}
+
+void
+HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent)
+{
+ MOZ_ASSERT(mIsDraggingRange);
+
+ mIsDraggingRange = false;
+ if (nsIPresShell::GetCapturingContent() == this) {
+ nsIPresShell::SetCapturingContent(nullptr, 0); // cancel capture
+ }
+ if (aIsForUserEvent) {
+ SetValueOfRangeForUserEvent(mRangeThumbDragStartValue);
+ } else {
+ // Don't dispatch an 'input' event - at least not using
+ // DispatchTrustedEvent.
+ // TODO: decide what we should do here - bug 851782.
+ nsAutoString val;
+ ConvertNumberToString(mRangeThumbDragStartValue, val);
+ // TODO: What should we do if SetValueInternal fails? (The allocation
+ // is small, so we should be fine here.)
+ SetValueInternal(val, nsTextEditorState::eSetValue_BySetUserInput |
+ nsTextEditorState::eSetValue_Notify);
+ nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->UpdateForValueChange();
+ }
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, NS_LITERAL_STRING("input"), true, false);
+ asyncDispatcher->RunDOMEventWhenSafe();
+ }
+}
+
+void
+HTMLInputElement::SetValueOfRangeForUserEvent(Decimal aValue)
+{
+ MOZ_ASSERT(aValue.isFinite());
+
+ Decimal oldValue = GetValueAsDecimal();
+
+ nsAutoString val;
+ ConvertNumberToString(aValue, val);
+ // TODO: What should we do if SetValueInternal fails? (The allocation
+ // is small, so we should be fine here.)
+ SetValueInternal(val, nsTextEditorState::eSetValue_BySetUserInput |
+ nsTextEditorState::eSetValue_Notify);
+ nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->UpdateForValueChange();
+ }
+
+ if (GetValueAsDecimal() != oldValue) {
+ nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(this),
+ NS_LITERAL_STRING("input"), true,
+ false);
+ }
+}
+
+void
+HTMLInputElement::StartNumberControlSpinnerSpin()
+{
+ MOZ_ASSERT(!mNumberControlSpinnerIsSpinning);
+
+ mNumberControlSpinnerIsSpinning = true;
+
+ nsRepeatService::GetInstance()->Start(HandleNumberControlSpin, this);
+
+ // Capture the mouse so that we can tell if the pointer moves from one
+ // spin button to the other, or to some other element:
+ nsIPresShell::SetCapturingContent(this, CAPTURE_IGNOREALLOWED);
+
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ numberControlFrame->SpinnerStateChanged();
+ }
+}
+
+void
+HTMLInputElement::StopNumberControlSpinnerSpin(SpinnerStopState aState)
+{
+ if (mNumberControlSpinnerIsSpinning) {
+ if (nsIPresShell::GetCapturingContent() == this) {
+ nsIPresShell::SetCapturingContent(nullptr, 0); // cancel capture
+ }
+
+ nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin, this);
+
+ mNumberControlSpinnerIsSpinning = false;
+
+ if (aState == eAllowDispatchingEvents) {
+ FireChangeEventIfNeeded();
+ }
+
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ MOZ_ASSERT(aState == eAllowDispatchingEvents,
+ "Shouldn't have primary frame for the element when we're not "
+ "allowed to dispatch events to it anymore.");
+ numberControlFrame->SpinnerStateChanged();
+ }
+ }
+}
+
+void
+HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection)
+{
+ // We can't use GetValidityState here because the validity state is not set
+ // if the user hasn't previously taken an action to set or change the value,
+ // according to the specs.
+ if (HasBadInput()) {
+ // If the user has typed a value into the control and inadvertently made a
+ // mistake (e.g. put a thousand separator at the wrong point) we do not
+ // want to wipe out what they typed if they try to increment/decrement the
+ // value. Better is to highlight the value as being invalid so that they
+ // can correct what they typed.
+ // We only do this if there actually is a value typed in by/displayed to
+ // the user. (IsValid() can return false if the 'required' attribute is
+ // set and the value is the empty string.)
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame &&
+ !numberControlFrame->AnonTextControlIsEmpty()) {
+ // We pass 'true' for UpdateValidityUIBits' aIsFocused argument
+ // regardless because we need the UI to update _now_ or the user will
+ // wonder why the step behavior isn't functioning.
+ UpdateValidityUIBits(true);
+ UpdateState(true);
+ return;
+ }
+ }
+
+ Decimal newValue = Decimal::nan(); // unchanged if value will not change
+
+ nsresult rv = GetValueIfStepped(aDirection, CALLED_FOR_USER_EVENT, &newValue);
+
+ if (NS_FAILED(rv) || !newValue.isFinite()) {
+ return; // value should not or will not change
+ }
+
+ nsAutoString newVal;
+ ConvertNumberToString(newValue, newVal);
+ // TODO: What should we do if SetValueInternal fails? (The allocation
+ // is small, so we should be fine here.)
+ SetValueInternal(newVal, nsTextEditorState::eSetValue_BySetUserInput |
+ nsTextEditorState::eSetValue_Notify);
+
+ nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(this),
+ NS_LITERAL_STRING("input"), true,
+ false);
+}
+
+static bool
+SelectTextFieldOnFocus()
+{
+ if (!gSelectTextFieldOnFocus) {
+ int32_t selectTextfieldsOnKeyFocus = -1;
+ nsresult rv =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_SelectTextfieldsOnKeyFocus,
+ &selectTextfieldsOnKeyFocus);
+ if (NS_FAILED(rv)) {
+ gSelectTextFieldOnFocus = -1;
+ } else {
+ gSelectTextFieldOnFocus = selectTextfieldsOnKeyFocus != 0 ? 1 : -1;
+ }
+ }
+
+ return gSelectTextFieldOnFocus == 1;
+}
+
+bool
+HTMLInputElement::ShouldPreventDOMActivateDispatch(EventTarget* aOriginalTarget)
+{
+ /*
+ * For the moment, there is only one situation where we actually want to
+ * prevent firing a DOMActivate event:
+ * - we are a <input type='file'> that just got a click event,
+ * - the event was targeted to our button which should have sent a
+ * DOMActivate event.
+ */
+
+ if (mType != NS_FORM_INPUT_FILE) {
+ return false;
+ }
+
+ nsCOMPtr<nsIContent> target = do_QueryInterface(aOriginalTarget);
+ if (!target) {
+ return false;
+ }
+
+ return target->GetParent() == this &&
+ target->IsRootOfNativeAnonymousSubtree() &&
+ target->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::button, eCaseMatters);
+}
+
+nsresult
+HTMLInputElement::MaybeInitPickers(EventChainPostVisitor& aVisitor)
+{
+ // Open a file picker when we receive a click on a <input type='file'>, or
+ // open a color picker when we receive a click on a <input type='color'>.
+ // A click is handled in the following cases:
+ // - preventDefault() has not been called (or something similar);
+ // - it's the left mouse button.
+ // We do not prevent non-trusted click because authors can already use
+ // .click(). However, the pickers will follow the rules of popup-blocking.
+ if (aVisitor.mEvent->DefaultPrevented()) {
+ return NS_OK;
+ }
+ WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
+ if (!(mouseEvent && mouseEvent->IsLeftClickEvent())) {
+ return NS_OK;
+ }
+ if (mType == NS_FORM_INPUT_FILE) {
+ // If the user clicked on the "Choose folder..." button we open the
+ // directory picker, else we open the file picker.
+ FilePickerType type = FILE_PICKER_FILE;
+ nsCOMPtr<nsIContent> target =
+ do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
+ if (target &&
+ target->FindFirstNonChromeOnlyAccessContent() == this &&
+ ((Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs()) ||
+ (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)))) {
+ type = FILE_PICKER_DIRECTORY;
+ }
+ return InitFilePicker(type);
+ }
+ if (mType == NS_FORM_INPUT_COLOR) {
+ return InitColorPicker();
+ }
+ if (mType == NS_FORM_INPUT_DATE) {
+ return InitDatePicker();
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Return true if the input event should be ignore because of it's modifiers
+ */
+static bool
+IgnoreInputEventWithModifier(WidgetInputEvent* aEvent)
+{
+ return aEvent->IsShift() || aEvent->IsControl() || aEvent->IsAlt() ||
+ aEvent->IsMeta() || aEvent->IsAltGraph() || aEvent->IsFn() ||
+ aEvent->IsOS();
+}
+
+nsresult
+HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ if (!aVisitor.mPresContext) {
+ // Hack alert! In order to open file picker even in case the element isn't
+ // in document, try to init picker even without PresContext.
+ return MaybeInitPickers(aVisitor);
+ }
+
+ if (aVisitor.mEvent->mMessage == eFocus ||
+ aVisitor.mEvent->mMessage == eBlur) {
+ if (aVisitor.mEvent->mMessage == eBlur) {
+ if (mIsDraggingRange) {
+ FinishRangeThumbDrag();
+ } else if (mNumberControlSpinnerIsSpinning) {
+ StopNumberControlSpinnerSpin();
+ }
+ }
+
+ UpdateValidityUIBits(aVisitor.mEvent->mMessage == eFocus);
+
+ UpdateState(true);
+ }
+
+ nsresult rv = NS_OK;
+ bool outerActivateEvent = !!(aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT);
+ bool originalCheckedValue =
+ !!(aVisitor.mItemFlags & NS_ORIGINAL_CHECKED_VALUE);
+ bool noContentDispatch = !!(aVisitor.mItemFlags & NS_NO_CONTENT_DISPATCH);
+ uint8_t oldType = NS_CONTROL_TYPE(aVisitor.mItemFlags);
+
+ // Ideally we would make the default action for click and space just dispatch
+ // DOMActivate, and the default action for DOMActivate flip the checkbox/
+ // radio state and fire onchange. However, for backwards compatibility, we
+ // need to flip the state before firing click, and we need to fire click
+ // when space is pressed. So, we just nest the firing of DOMActivate inside
+ // the click event handling, and allow cancellation of DOMActivate to cancel
+ // the click.
+ if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault &&
+ !IsSingleLineTextControl(true) &&
+ mType != NS_FORM_INPUT_NUMBER) {
+ WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
+ if (mouseEvent && mouseEvent->IsLeftClickEvent() &&
+ !ShouldPreventDOMActivateDispatch(aVisitor.mEvent->mOriginalTarget)) {
+ // 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;
+
+ nsCOMPtr<nsIPresShell> shell = aVisitor.mPresContext->GetPresShell();
+ if (shell) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ mInInternalActivate = true;
+ rv = shell->HandleDOMEventWithTarget(this, &actEvent, &status);
+ mInInternalActivate = false;
+
+ // If activate is cancelled, we must do the same as when click is
+ // cancelled (revert the checkbox to its original value).
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ aVisitor.mEventStatus = status;
+ }
+ }
+ }
+ }
+
+ if (outerActivateEvent) {
+ switch(oldType) {
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_IMAGE:
+ if (mForm) {
+ // tell the form that we are about to exit a click handler
+ // so the form knows not to defer subsequent submissions
+ // the pending ones that were created during the handler
+ // will be flushed or forgoten.
+ mForm->OnSubmitClickEnd();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Reset the flag for other content besides this text field
+ aVisitor.mEvent->mFlags.mNoContentDispatch = noContentDispatch;
+
+ // now check to see if the event was "cancelled"
+ if (mCheckedIsToggled && outerActivateEvent) {
+ if (aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault) {
+ // if it was cancelled and a radio button, then set the old
+ // selected btn to TRUE. if it is a checkbox then set it to its
+ // original value
+ if (oldType == NS_FORM_INPUT_RADIO) {
+ nsCOMPtr<nsIDOMHTMLInputElement> selectedRadioButton =
+ do_QueryInterface(aVisitor.mItemData);
+ if (selectedRadioButton) {
+ selectedRadioButton->SetChecked(true);
+ }
+ // If there was no checked radio button or this one is no longer a
+ // radio button we must reset it back to false to cancel the action.
+ // See how the web of hack grows?
+ if (!selectedRadioButton || mType != NS_FORM_INPUT_RADIO) {
+ DoSetChecked(false, true, true);
+ }
+ } else if (oldType == NS_FORM_INPUT_CHECKBOX) {
+ bool originalIndeterminateValue =
+ !!(aVisitor.mItemFlags & NS_ORIGINAL_INDETERMINATE_VALUE);
+ SetIndeterminateInternal(originalIndeterminateValue, false);
+ DoSetChecked(originalCheckedValue, true, true);
+ }
+ } else {
+ // Fire input event and then change event.
+ nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(this),
+ NS_LITERAL_STRING("input"), true,
+ false);
+
+ nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(this),
+ NS_LITERAL_STRING("change"), true,
+ false);
+#ifdef ACCESSIBILITY
+ // Fire an event to notify accessibility
+ if (mType == NS_FORM_INPUT_CHECKBOX) {
+ FireEventForAccessibility(this, aVisitor.mPresContext,
+ NS_LITERAL_STRING("CheckboxStateChange"));
+ } else {
+ FireEventForAccessibility(this, aVisitor.mPresContext,
+ NS_LITERAL_STRING("RadioStateChange"));
+ // Fire event for the previous selected radio.
+ nsCOMPtr<nsIDOMHTMLInputElement> previous =
+ do_QueryInterface(aVisitor.mItemData);
+ if (previous) {
+ FireEventForAccessibility(previous, aVisitor.mPresContext,
+ NS_LITERAL_STRING("RadioStateChange"));
+ }
+ }
+#endif
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
+ if (mType == NS_FORM_INPUT_NUMBER &&
+ keyEvent && keyEvent->mMessage == eKeyPress &&
+ aVisitor.mEvent->IsTrusted() &&
+ (keyEvent->mKeyCode == NS_VK_UP || keyEvent->mKeyCode == NS_VK_DOWN) &&
+ !IgnoreInputEventWithModifier(keyEvent)) {
+ // We handle the up/down arrow keys specially for <input type=number>.
+ // On some platforms the editor for the nested text control will
+ // process these keys to send the cursor to the start/end of the text
+ // control and as a result aVisitor.mEventStatus will already have been
+ // set to nsEventStatus_eConsumeNoDefault. However, we know that
+ // whenever the up/down arrow keys cause the value of the number
+ // control to change the string in the text control will change, and
+ // the cursor will be moved to the end of the text control, overwriting
+ // the editor's handling of up/down keypress events. For that reason we
+ // just ignore aVisitor.mEventStatus here and go ahead and handle the
+ // event to increase/decrease the value of the number control.
+ if (!aVisitor.mEvent->DefaultPreventedByContent() && IsMutable()) {
+ StepNumberControlForUserEvent(keyEvent->mKeyCode == NS_VK_UP ? 1 : -1);
+ FireChangeEventIfNeeded();
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ } else if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
+ switch (aVisitor.mEvent->mMessage) {
+ case eFocus: {
+ // see if we should select the contents of the textbox. This happens
+ // for text and password fields when the field was focused by the
+ // keyboard or a navigation, the platform allows it, and it wasn't
+ // just because we raised a window.
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm && IsSingleLineTextControl(false) &&
+ !aVisitor.mEvent->AsFocusEvent()->mFromRaise &&
+ SelectTextFieldOnFocus()) {
+ nsIDocument* document = GetComposedDoc();
+ if (document) {
+ uint32_t lastFocusMethod;
+ fm->GetLastFocusMethod(document->GetWindow(), &lastFocusMethod);
+ if (lastFocusMethod &
+ (nsIFocusManager::FLAG_BYKEY | nsIFocusManager::FLAG_BYMOVEFOCUS)) {
+ RefPtr<nsPresContext> presContext =
+ GetPresContext(eForComposedDoc);
+ if (DispatchSelectEvent(presContext)) {
+ SelectAll(presContext);
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ case eKeyPress:
+ case eKeyUp:
+ {
+ // For backwards compat, trigger checks/radios/buttons with
+ // space or enter (bug 25300)
+ WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
+ if ((aVisitor.mEvent->mMessage == eKeyPress &&
+ keyEvent->mKeyCode == NS_VK_RETURN) ||
+ (aVisitor.mEvent->mMessage == eKeyUp &&
+ keyEvent->mKeyCode == NS_VK_SPACE)) {
+ switch(mType) {
+ case NS_FORM_INPUT_CHECKBOX:
+ case NS_FORM_INPUT_RADIO:
+ {
+ // Checkbox and Radio try to submit on Enter press
+ if (keyEvent->mKeyCode != NS_VK_SPACE) {
+ MaybeSubmitForm(aVisitor.mPresContext);
+
+ break; // If we are submitting, do not send click event
+ }
+ // else fall through and treat Space like click...
+ MOZ_FALLTHROUGH;
+ }
+ case NS_FORM_INPUT_BUTTON:
+ case NS_FORM_INPUT_RESET:
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_IMAGE: // Bug 34418
+ case NS_FORM_INPUT_COLOR:
+ {
+ DispatchSimulatedClick(this, aVisitor.mEvent->IsTrusted(),
+ aVisitor.mPresContext);
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ } // case
+ } // switch
+ }
+ if (aVisitor.mEvent->mMessage == eKeyPress &&
+ mType == NS_FORM_INPUT_RADIO && !keyEvent->IsAlt() &&
+ !keyEvent->IsControl() && !keyEvent->IsMeta()) {
+ bool isMovingBack = false;
+ switch (keyEvent->mKeyCode) {
+ case NS_VK_UP:
+ case NS_VK_LEFT:
+ isMovingBack = true;
+ MOZ_FALLTHROUGH;
+ case NS_VK_DOWN:
+ case NS_VK_RIGHT:
+ // Arrow key pressed, focus+select prev/next radio button
+ nsIRadioGroupContainer* container = GetRadioGroupContainer();
+ if (container) {
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+ RefPtr<HTMLInputElement> selectedRadioButton;
+ container->GetNextRadioButton(name, isMovingBack, this,
+ getter_AddRefs(selectedRadioButton));
+ if (selectedRadioButton) {
+ rv = selectedRadioButton->Focus();
+ if (NS_SUCCEEDED(rv)) {
+ rv = DispatchSimulatedClick(selectedRadioButton,
+ aVisitor.mEvent->IsTrusted(),
+ aVisitor.mPresContext);
+ if (NS_SUCCEEDED(rv)) {
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * For some input types, if the user hits enter, the form is submitted.
+ *
+ * Bug 99920, bug 109463 and bug 147850:
+ * (a) if there is a submit control in the form, click the first
+ * submit control in the form.
+ * (b) if there is just one text control in the form, submit by
+ * sending a submit event directly to the form
+ * (c) if there is more than one text input and no submit buttons, do
+ * not submit, period.
+ */
+
+ if (aVisitor.mEvent->mMessage == eKeyPress &&
+ keyEvent->mKeyCode == NS_VK_RETURN &&
+ (IsSingleLineTextControl(false, mType) ||
+ mType == NS_FORM_INPUT_NUMBER ||
+ IsExperimentalMobileType(mType) ||
+ IsDateTimeInputType(mType))) {
+ FireChangeEventIfNeeded();
+ rv = MaybeSubmitForm(aVisitor.mPresContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aVisitor.mEvent->mMessage == eKeyPress &&
+ mType == NS_FORM_INPUT_RANGE && !keyEvent->IsAlt() &&
+ !keyEvent->IsControl() && !keyEvent->IsMeta() &&
+ (keyEvent->mKeyCode == NS_VK_LEFT ||
+ keyEvent->mKeyCode == NS_VK_RIGHT ||
+ keyEvent->mKeyCode == NS_VK_UP ||
+ keyEvent->mKeyCode == NS_VK_DOWN ||
+ keyEvent->mKeyCode == NS_VK_PAGE_UP ||
+ keyEvent->mKeyCode == NS_VK_PAGE_DOWN ||
+ keyEvent->mKeyCode == NS_VK_HOME ||
+ keyEvent->mKeyCode == NS_VK_END)) {
+ Decimal minimum = GetMinimum();
+ Decimal maximum = GetMaximum();
+ MOZ_ASSERT(minimum.isFinite() && maximum.isFinite());
+ if (minimum < maximum) { // else the value is locked to the minimum
+ Decimal value = GetValueAsDecimal();
+ Decimal step = GetStep();
+ if (step == kStepAny) {
+ step = GetDefaultStep();
+ }
+ MOZ_ASSERT(value.isFinite() && step.isFinite());
+ Decimal newValue;
+ switch (keyEvent->mKeyCode) {
+ case NS_VK_LEFT:
+ newValue = value + (GetComputedDirectionality() == eDir_RTL
+ ? step : -step);
+ break;
+ case NS_VK_RIGHT:
+ newValue = value + (GetComputedDirectionality() == eDir_RTL
+ ? -step : step);
+ break;
+ case NS_VK_UP:
+ // Even for horizontal range, "up" means "increase"
+ newValue = value + step;
+ break;
+ case NS_VK_DOWN:
+ // Even for horizontal range, "down" means "decrease"
+ newValue = value - step;
+ break;
+ case NS_VK_HOME:
+ newValue = minimum;
+ break;
+ case NS_VK_END:
+ newValue = maximum;
+ break;
+ case NS_VK_PAGE_UP:
+ // For PgUp/PgDn we jump 10% of the total range, unless step
+ // requires us to jump more.
+ newValue = value + std::max(step, (maximum - minimum) / Decimal(10));
+ break;
+ case NS_VK_PAGE_DOWN:
+ newValue = value - std::max(step, (maximum - minimum) / Decimal(10));
+ break;
+ }
+ SetValueOfRangeForUserEvent(newValue);
+ FireChangeEventIfNeeded();
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+
+ } break; // eKeyPress || eKeyUp
+
+ case eMouseDown:
+ case eMouseUp:
+ case eMouseDoubleClick: {
+ // cancel all of these events for buttons
+ //XXXsmaug Why?
+ WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
+ if (mouseEvent->button == WidgetMouseEvent::eMiddleButton ||
+ mouseEvent->button == WidgetMouseEvent::eRightButton) {
+ if (mType == NS_FORM_INPUT_BUTTON ||
+ mType == NS_FORM_INPUT_RESET ||
+ mType == NS_FORM_INPUT_SUBMIT) {
+ if (aVisitor.mDOMEvent) {
+ aVisitor.mDOMEvent->StopPropagation();
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ }
+ if (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted()) {
+ if (mouseEvent->button == WidgetMouseEvent::eLeftButton &&
+ !IgnoreInputEventWithModifier(mouseEvent)) {
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ if (aVisitor.mEvent->mMessage == eMouseDown &&
+ IsMutable()) {
+ switch (numberControlFrame->GetSpinButtonForPointerEvent(
+ aVisitor.mEvent->AsMouseEvent())) {
+ case nsNumberControlFrame::eSpinButtonUp:
+ StepNumberControlForUserEvent(1);
+ mNumberControlSpinnerSpinsUp = true;
+ StartNumberControlSpinnerSpin();
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ break;
+ case nsNumberControlFrame::eSpinButtonDown:
+ StepNumberControlForUserEvent(-1);
+ mNumberControlSpinnerSpinsUp = false;
+ StartNumberControlSpinnerSpin();
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ break;
+ }
+ }
+ }
+ }
+ if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
+ // We didn't handle this to step up/down. Whatever this was, be
+ // aggressive about stopping the spin. (And don't set
+ // nsEventStatus_eConsumeNoDefault after doing so, since that
+ // might prevent, say, the context menu from opening.)
+ StopNumberControlSpinnerSpin();
+ }
+ }
+ break;
+ }
+#if !defined(ANDROID) && !defined(XP_MACOSX)
+ case eWheel: {
+ // Handle wheel events as increasing / decreasing the input element's
+ // value when it's focused and it's type is number or range.
+ WidgetWheelEvent* wheelEvent = aVisitor.mEvent->AsWheelEvent();
+ if (!aVisitor.mEvent->DefaultPrevented() &&
+ aVisitor.mEvent->IsTrusted() && IsMutable() && wheelEvent &&
+ wheelEvent->mDeltaY != 0 &&
+ wheelEvent->mDeltaMode != nsIDOMWheelEvent::DOM_DELTA_PIXEL) {
+ if (mType == NS_FORM_INPUT_NUMBER) {
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame && numberControlFrame->IsFocused()) {
+ StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1 : 1);
+ FireChangeEventIfNeeded();
+ aVisitor.mEvent->PreventDefault();
+ }
+ } else if (mType == NS_FORM_INPUT_RANGE &&
+ nsContentUtils::IsFocusedContent(this) &&
+ GetMinimum() < GetMaximum()) {
+ Decimal value = GetValueAsDecimal();
+ Decimal step = GetStep();
+ if (step == kStepAny) {
+ step = GetDefaultStep();
+ }
+ MOZ_ASSERT(value.isFinite() && step.isFinite());
+ SetValueOfRangeForUserEvent(wheelEvent->mDeltaY < 0 ?
+ value + step : value - step);
+ FireChangeEventIfNeeded();
+ aVisitor.mEvent->PreventDefault();
+ }
+ }
+ break;
+ }
+#endif
+ default:
+ break;
+ }
+
+ if (outerActivateEvent) {
+ if (mForm && (oldType == NS_FORM_INPUT_SUBMIT ||
+ oldType == NS_FORM_INPUT_IMAGE)) {
+ if (mType != NS_FORM_INPUT_SUBMIT && mType != NS_FORM_INPUT_IMAGE) {
+ // If the type has changed to a non-submit type, then we want to
+ // flush the stored submission if there is one (as if the submit()
+ // was allowed to succeed)
+ mForm->FlushPendingSubmission();
+ }
+ }
+ switch(mType) {
+ case NS_FORM_INPUT_RESET:
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_IMAGE:
+ if (mForm) {
+ InternalFormEvent event(true,
+ (mType == NS_FORM_INPUT_RESET) ? eFormReset : eFormSubmit);
+ event.mOriginator = this;
+ nsEventStatus status = nsEventStatus_eIgnore;
+
+ nsCOMPtr<nsIPresShell> presShell =
+ aVisitor.mPresContext->GetPresShell();
+
+ // If |nsIPresShell::Destroy| has been called due to
+ // handling the event the pres context will return a null
+ // pres shell. See bug 125624.
+ // TODO: removing this code and have the submit event sent by the
+ // form, see bug 592124.
+ if (presShell && (event.mMessage != eFormSubmit ||
+ mForm->SubmissionCanProceed(this))) {
+ // Hold a strong ref while dispatching
+ RefPtr<mozilla::dom::HTMLFormElement> form(mForm);
+ presShell->HandleDOMEventWithTarget(form, &event, &status);
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ break;
+
+ default:
+ break;
+ } //switch
+ } //click or outer activate event
+ } else if (outerActivateEvent &&
+ (oldType == NS_FORM_INPUT_SUBMIT ||
+ oldType == NS_FORM_INPUT_IMAGE) &&
+ mForm) {
+ // tell the form to flush a possible pending submission.
+ // the reason is that the script returned false (the event was
+ // not ignored) so if there is a stored submission, it needs to
+ // be submitted immediately.
+ mForm->FlushPendingSubmission();
+ }
+ } // if
+
+ if (NS_SUCCEEDED(rv) && mType == NS_FORM_INPUT_RANGE) {
+ PostHandleEventForRangeThumb(aVisitor);
+ }
+
+ return MaybeInitPickers(aVisitor);
+}
+
+void
+HTMLInputElement::PostHandleEventForRangeThumb(EventChainPostVisitor& aVisitor)
+{
+ MOZ_ASSERT(mType == NS_FORM_INPUT_RANGE);
+
+ if (nsEventStatus_eConsumeNoDefault == aVisitor.mEventStatus ||
+ !(aVisitor.mEvent->mClass == eMouseEventClass ||
+ aVisitor.mEvent->mClass == eTouchEventClass ||
+ aVisitor.mEvent->mClass == eKeyboardEventClass)) {
+ return;
+ }
+
+ nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
+ if (!rangeFrame && mIsDraggingRange) {
+ CancelRangeThumbDrag();
+ return;
+ }
+
+ switch (aVisitor.mEvent->mMessage)
+ {
+ case eMouseDown:
+ case eTouchStart: {
+ if (mIsDraggingRange) {
+ break;
+ }
+ if (nsIPresShell::GetCapturingContent()) {
+ break; // don't start drag if someone else is already capturing
+ }
+ WidgetInputEvent* inputEvent = aVisitor.mEvent->AsInputEvent();
+ if (IgnoreInputEventWithModifier(inputEvent)) {
+ break; // ignore
+ }
+ if (aVisitor.mEvent->mMessage == eMouseDown) {
+ if (aVisitor.mEvent->AsMouseEvent()->buttons ==
+ WidgetMouseEvent::eLeftButtonFlag) {
+ StartRangeThumbDrag(inputEvent);
+ } else if (mIsDraggingRange) {
+ CancelRangeThumbDrag();
+ }
+ } else {
+ if (aVisitor.mEvent->AsTouchEvent()->mTouches.Length() == 1) {
+ StartRangeThumbDrag(inputEvent);
+ } else if (mIsDraggingRange) {
+ CancelRangeThumbDrag();
+ }
+ }
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
+ } break;
+
+ case eMouseMove:
+ case eTouchMove:
+ if (!mIsDraggingRange) {
+ break;
+ }
+ if (nsIPresShell::GetCapturingContent() != this) {
+ // Someone else grabbed capture.
+ CancelRangeThumbDrag();
+ break;
+ }
+ SetValueOfRangeForUserEvent(
+ rangeFrame->GetValueAtEventPoint(aVisitor.mEvent->AsInputEvent()));
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
+ break;
+
+ case eMouseUp:
+ case eTouchEnd:
+ if (!mIsDraggingRange) {
+ break;
+ }
+ // We don't check to see whether we are the capturing content here and
+ // call CancelRangeThumbDrag() if that is the case. We just finish off
+ // the drag and set our final value (unless someone has called
+ // preventDefault() and prevents us getting here).
+ FinishRangeThumbDrag(aVisitor.mEvent->AsInputEvent());
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
+ break;
+
+ case eKeyPress:
+ if (mIsDraggingRange &&
+ aVisitor.mEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) {
+ CancelRangeThumbDrag();
+ }
+ break;
+
+ case eTouchCancel:
+ if (mIsDraggingRange) {
+ CancelRangeThumbDrag();
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+void
+HTMLInputElement::MaybeLoadImage()
+{
+ // Our base URI may have changed; claim that our URI changed, and the
+ // nsImageLoadingContent will decide whether a new image load is warranted.
+ nsAutoString uri;
+ if (mType == NS_FORM_INPUT_IMAGE &&
+ GetAttr(kNameSpaceID_None, nsGkAtoms::src, uri) &&
+ (NS_FAILED(LoadImage(uri, false, true, eImageLoadType_Normal)) ||
+ !LoadingEnabled())) {
+ CancelImageRequests(true);
+ }
+}
+
+nsresult
+HTMLInputElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLFormElementWithState::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsImageLoadingContent::BindToTree(aDocument, aParent, aBindingParent,
+ aCompileEventHandlers);
+
+ if (mType == NS_FORM_INPUT_IMAGE) {
+ // Our base URI may have changed; claim that our URI changed, and the
+ // nsImageLoadingContent will decide whether a new image load is warranted.
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
+ // FIXME: Bug 660963 it would be nice if we could just have
+ // ClearBrokenState update our state and do it fast...
+ ClearBrokenState();
+ RemoveStatesSilently(NS_EVENT_STATE_BROKEN);
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod(this, &HTMLInputElement::MaybeLoadImage));
+ }
+ }
+
+ // Add radio to document if we don't have a form already (if we do it's
+ // already been added into that group)
+ if (aDocument && !mForm && mType == NS_FORM_INPUT_RADIO) {
+ AddedToRadioGroup();
+ }
+
+ // Set direction based on value if dir=auto
+ SetDirectionIfAuto(HasDirAuto(), false);
+
+ // An element can't suffer from value missing if it is not in a document.
+ // We have to check if we suffer from that as we are now in a document.
+ UpdateValueMissingValidityState();
+
+ // If there is a disabled fieldset in the parent chain, the element is now
+ // barred from constraint validation and can't suffer from value missing
+ // (call done before).
+ UpdateBarredFromConstraintValidation();
+
+ // And now make sure our state is up to date
+ UpdateState(false);
+
+ if (mType == NS_FORM_INPUT_PASSWORD) {
+ if (IsInComposedDoc()) {
+ AsyncEventDispatcher* dispatcher =
+ new AsyncEventDispatcher(this,
+ NS_LITERAL_STRING("DOMInputPasswordAdded"),
+ true,
+ true);
+ dispatcher->PostDOMEvent();
+ }
+
+#ifdef EARLY_BETA_OR_EARLIER
+ Telemetry::Accumulate(Telemetry::PWMGR_PASSWORD_INPUT_IN_FORM, !!mForm);
+#endif
+ }
+
+ return rv;
+}
+
+void
+HTMLInputElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ // If we have a form and are unbound from it,
+ // nsGenericHTMLFormElementWithState::UnbindFromTree() will unset the form and
+ // that takes care of form's WillRemove so we just have to take care
+ // of the case where we're removing from the document and we don't
+ // have a form
+ if (!mForm && mType == NS_FORM_INPUT_RADIO) {
+ WillRemoveFromRadioGroup();
+ }
+
+ nsImageLoadingContent::UnbindFromTree(aDeep, aNullParent);
+ nsGenericHTMLFormElementWithState::UnbindFromTree(aDeep, aNullParent);
+
+ // GetCurrentDoc is returning nullptr so we can update the value
+ // missing validity state to reflect we are no longer into a doc.
+ UpdateValueMissingValidityState();
+ // We might be no longer disabled because of parent chain changed.
+ UpdateBarredFromConstraintValidation();
+
+ // And now make sure our state is up to date
+ UpdateState(false);
+}
+
+void
+HTMLInputElement::HandleTypeChange(uint8_t aNewType)
+{
+ if (mType == NS_FORM_INPUT_RANGE && mIsDraggingRange) {
+ CancelRangeThumbDrag(false);
+ }
+
+ ValueModeType aOldValueMode = GetValueMode();
+ uint8_t oldType = mType;
+ nsAutoString aOldValue;
+
+ if (aOldValueMode == VALUE_MODE_VALUE) {
+ GetValue(aOldValue);
+ }
+
+ nsTextEditorState::SelectionProperties sp;
+
+ if (GetEditorState()) {
+ sp = mInputData.mState->GetSelectionProperties();
+ }
+
+ // We already have a copy of the value, lets free it and changes the type.
+ FreeData();
+ mType = aNewType;
+
+ if (IsSingleLineTextControl()) {
+
+ mInputData.mState = new nsTextEditorState(this);
+ if (!sp.IsDefault()) {
+ mInputData.mState->SetSelectionProperties(sp);
+ }
+ }
+
+ /**
+ * The following code is trying to reproduce the algorithm described here:
+ * http://www.whatwg.org/specs/web-apps/current-work/complete.html#input-type-change
+ */
+ switch (GetValueMode()) {
+ case VALUE_MODE_DEFAULT:
+ case VALUE_MODE_DEFAULT_ON:
+ // If the previous value mode was value, we need to set the value content
+ // attribute to the previous value.
+ // There is no value sanitizing algorithm for elements in this mode.
+ if (aOldValueMode == VALUE_MODE_VALUE && !aOldValue.IsEmpty()) {
+ SetAttr(kNameSpaceID_None, nsGkAtoms::value, aOldValue, true);
+ }
+ break;
+ case VALUE_MODE_VALUE:
+ // If the previous value mode wasn't value, we have to set the value to
+ // the value content attribute.
+ // SetValueInternal is going to sanitize the value.
+ {
+ nsAutoString value;
+ if (aOldValueMode != VALUE_MODE_VALUE) {
+ GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
+ } else {
+ value = aOldValue;
+ }
+ // TODO: What should we do if SetValueInternal fails? (The allocation
+ // may potentially be big, but most likely we've failed to allocate
+ // before the type change.)
+ SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
+ }
+ break;
+ case VALUE_MODE_FILENAME:
+ default:
+ // We don't care about the value.
+ // There is no value sanitizing algorithm for elements in this mode.
+ break;
+ }
+
+ // Updating mFocusedValue in consequence:
+ // If the new type fires a change event on blur, but the previous type
+ // doesn't, we should set mFocusedValue to the current value.
+ // Otherwise, if the new type doesn't fire a change event on blur, but the
+ // previous type does, we should clear out mFocusedValue.
+ if (MayFireChangeOnBlur(mType) && !MayFireChangeOnBlur(oldType)) {
+ GetValue(mFocusedValue);
+ } else if (!IsSingleLineTextControl(false, mType) &&
+ IsSingleLineTextControl(false, oldType)) {
+ mFocusedValue.Truncate();
+ }
+
+ UpdateHasRange();
+
+ // Do not notify, it will be done after if needed.
+ UpdateAllValidityStates(false);
+
+ UpdateApzAwareFlag();
+}
+
+void
+HTMLInputElement::SanitizeValue(nsAString& aValue)
+{
+ NS_ASSERTION(mDoneCreating, "The element creation should be finished!");
+
+ switch (mType) {
+ case NS_FORM_INPUT_TEXT:
+ case NS_FORM_INPUT_SEARCH:
+ case NS_FORM_INPUT_TEL:
+ case NS_FORM_INPUT_PASSWORD:
+ {
+ char16_t crlf[] = { char16_t('\r'), char16_t('\n'), 0 };
+ aValue.StripChars(crlf);
+ }
+ break;
+ case NS_FORM_INPUT_EMAIL:
+ case NS_FORM_INPUT_URL:
+ {
+ char16_t crlf[] = { char16_t('\r'), char16_t('\n'), 0 };
+ aValue.StripChars(crlf);
+
+ aValue = nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(aValue);
+ }
+ break;
+ case NS_FORM_INPUT_NUMBER:
+ {
+ Decimal value;
+ bool ok = ConvertStringToNumber(aValue, value);
+ if (!ok) {
+ aValue.Truncate();
+ }
+ }
+ break;
+ case NS_FORM_INPUT_RANGE:
+ {
+ Decimal minimum = GetMinimum();
+ Decimal maximum = GetMaximum();
+ MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(),
+ "type=range should have a default maximum/minimum");
+
+ // We use this to avoid modifying the string unnecessarily, since that
+ // may introduce rounding. This is set to true only if the value we
+ // parse out from aValue needs to be sanitized.
+ bool needSanitization = false;
+
+ Decimal value;
+ bool ok = ConvertStringToNumber(aValue, value);
+ if (!ok) {
+ needSanitization = true;
+ // Set value to midway between minimum and maximum.
+ value = maximum <= minimum ? minimum : minimum + (maximum - minimum)/Decimal(2);
+ } else if (value < minimum || maximum < minimum) {
+ needSanitization = true;
+ value = minimum;
+ } else if (value > maximum) {
+ needSanitization = true;
+ value = maximum;
+ }
+
+ Decimal step = GetStep();
+ if (step != kStepAny) {
+ Decimal stepBase = GetStepBase();
+ // There could be rounding issues below when dealing with fractional
+ // numbers, but let's ignore that until ECMAScript supplies us with a
+ // decimal number type.
+ Decimal deltaToStep = NS_floorModulo(value - stepBase, step);
+ if (deltaToStep != Decimal(0)) {
+ // "suffering from a step mismatch"
+ // Round the element's value to the nearest number for which the
+ // element would not suffer from a step mismatch, and which is
+ // greater than or equal to the minimum, and, if the maximum is not
+ // less than the minimum, which is less than or equal to the
+ // maximum, if there is a number that matches these constraints:
+ MOZ_ASSERT(deltaToStep > Decimal(0), "stepBelow/stepAbove will be wrong");
+ Decimal stepBelow = value - deltaToStep;
+ Decimal stepAbove = value - deltaToStep + step;
+ Decimal halfStep = step / Decimal(2);
+ bool stepAboveIsClosest = (stepAbove - value) <= halfStep;
+ bool stepAboveInRange = stepAbove >= minimum &&
+ stepAbove <= maximum;
+ bool stepBelowInRange = stepBelow >= minimum &&
+ stepBelow <= maximum;
+
+ if ((stepAboveIsClosest || !stepBelowInRange) && stepAboveInRange) {
+ needSanitization = true;
+ value = stepAbove;
+ } else if ((!stepAboveIsClosest || !stepAboveInRange) && stepBelowInRange) {
+ needSanitization = true;
+ value = stepBelow;
+ }
+ }
+ }
+
+ if (needSanitization) {
+ char buf[32];
+ DebugOnly<bool> ok = value.toString(buf, ArrayLength(buf));
+ aValue.AssignASCII(buf);
+ MOZ_ASSERT(ok, "buf not big enough");
+ }
+ }
+ break;
+ case NS_FORM_INPUT_DATE:
+ {
+ if (!aValue.IsEmpty() && !IsValidDate(aValue)) {
+ aValue.Truncate();
+ }
+ }
+ break;
+ case NS_FORM_INPUT_TIME:
+ {
+ if (!aValue.IsEmpty() && !IsValidTime(aValue)) {
+ aValue.Truncate();
+ }
+ }
+ break;
+ case NS_FORM_INPUT_MONTH:
+ {
+ if (!aValue.IsEmpty() && !IsValidMonth(aValue)) {
+ aValue.Truncate();
+ }
+ }
+ break;
+ case NS_FORM_INPUT_WEEK:
+ {
+ if (!aValue.IsEmpty() && !IsValidWeek(aValue)) {
+ aValue.Truncate();
+ }
+ }
+ break;
+ case NS_FORM_INPUT_DATETIME_LOCAL:
+ {
+ if (!aValue.IsEmpty() && !IsValidDateTimeLocal(aValue)) {
+ aValue.Truncate();
+ } else {
+ NormalizeDateTimeLocal(aValue);
+ }
+ }
+ break;
+ case NS_FORM_INPUT_COLOR:
+ {
+ if (IsValidSimpleColor(aValue)) {
+ ToLowerCase(aValue);
+ } else {
+ // Set default (black) color, if aValue wasn't parsed correctly.
+ aValue.AssignLiteral("#000000");
+ }
+ }
+ break;
+ }
+}
+
+bool HTMLInputElement::IsValidSimpleColor(const nsAString& aValue) const
+{
+ if (aValue.Length() != 7 || aValue.First() != '#') {
+ return false;
+ }
+
+ for (int i = 1; i < 7; ++i) {
+ if (!nsCRT::IsAsciiDigit(aValue[i]) &&
+ !(aValue[i] >= 'a' && aValue[i] <= 'f') &&
+ !(aValue[i] >= 'A' && aValue[i] <= 'F')) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+HTMLInputElement::IsLeapYear(uint32_t aYear) const
+{
+ if ((aYear % 4 == 0 && aYear % 100 != 0) || ( aYear % 400 == 0)) {
+ return true;
+ }
+ return false;
+}
+
+uint32_t
+HTMLInputElement::DayOfWeek(uint32_t aYear, uint32_t aMonth, uint32_t aDay,
+ bool isoWeek) const
+{
+ // Tomohiko Sakamoto algorithm.
+ int monthTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
+ aYear -= aMonth < 3;
+
+ uint32_t day = (aYear + aYear / 4 - aYear / 100 + aYear / 400 +
+ monthTable[aMonth - 1] + aDay) % 7;
+
+ if (isoWeek) {
+ return ((day + 6) % 7) + 1;
+ }
+
+ return day;
+}
+
+uint32_t
+HTMLInputElement::MaximumWeekInYear(uint32_t aYear) const
+{
+ int day = DayOfWeek(aYear, 1, 1, true); // January 1.
+ // A year starting on Thursday or a leap year starting on Wednesday has 53
+ // weeks. All other years have 52 weeks.
+ return day == 4 || (day == 3 && IsLeapYear(aYear)) ?
+ kMaximumWeekInYear : kMaximumWeekInYear - 1;
+}
+
+bool
+HTMLInputElement::IsValidWeek(const nsAString& aValue) const
+{
+ uint32_t year, week;
+ return ParseWeek(aValue, &year, &week);
+}
+
+bool
+HTMLInputElement::IsValidMonth(const nsAString& aValue) const
+{
+ uint32_t year, month;
+ return ParseMonth(aValue, &year, &month);
+}
+
+bool
+HTMLInputElement::IsValidDate(const nsAString& aValue) const
+{
+ uint32_t year, month, day;
+ return ParseDate(aValue, &year, &month, &day);
+}
+
+bool
+HTMLInputElement::IsValidDateTimeLocal(const nsAString& aValue) const
+{
+ uint32_t year, month, day, time;
+ return ParseDateTimeLocal(aValue, &year, &month, &day, &time);
+}
+
+bool
+HTMLInputElement::ParseYear(const nsAString& aValue, uint32_t* aYear) const
+{
+ if (aValue.Length() < 4) {
+ return false;
+ }
+
+ return DigitSubStringToNumber(aValue, 0, aValue.Length(), aYear) &&
+ *aYear > 0;
+}
+
+bool
+HTMLInputElement::ParseMonth(const nsAString& aValue, uint32_t* aYear,
+ uint32_t* aMonth) const
+{
+ // Parse the year, month values out a string formatted as 'yyyy-mm'.
+ if (aValue.Length() < 7) {
+ return false;
+ }
+
+ uint32_t endOfYearOffset = aValue.Length() - 3;
+ if (aValue[endOfYearOffset] != '-') {
+ return false;
+ }
+
+ const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
+ if (!ParseYear(yearStr, aYear)) {
+ return false;
+ }
+
+ return DigitSubStringToNumber(aValue, endOfYearOffset + 1, 2, aMonth) &&
+ *aMonth > 0 && *aMonth <= 12;
+}
+
+bool
+HTMLInputElement::ParseWeek(const nsAString& aValue, uint32_t* aYear,
+ uint32_t* aWeek) const
+{
+ // Parse the year, month values out a string formatted as 'yyyy-Www'.
+ if (aValue.Length() < 8) {
+ return false;
+ }
+
+ uint32_t endOfYearOffset = aValue.Length() - 4;
+ if (aValue[endOfYearOffset] != '-') {
+ return false;
+ }
+
+ if (aValue[endOfYearOffset + 1] != 'W') {
+ return false;
+ }
+
+ const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
+ if (!ParseYear(yearStr, aYear)) {
+ return false;
+ }
+
+ return DigitSubStringToNumber(aValue, endOfYearOffset + 2, 2, aWeek) &&
+ *aWeek > 0 && *aWeek <= MaximumWeekInYear(*aYear);
+
+}
+
+bool
+HTMLInputElement::ParseDate(const nsAString& aValue, uint32_t* aYear,
+ uint32_t* aMonth, uint32_t* aDay) const
+{
+/*
+ * Parse the year, month, day values out a date string formatted as 'yyyy-mm-dd'.
+ * -The year must be 4 or more digits long, and year > 0
+ * -The month must be exactly 2 digits long, and 01 <= month <= 12
+ * -The day must be exactly 2 digit long, and 01 <= day <= maxday
+ * Where maxday is the number of days in the month 'month' and year 'year'
+ */
+ if (aValue.Length() < 10) {
+ return false;
+ }
+
+ uint32_t endOfMonthOffset = aValue.Length() - 3;
+ if (aValue[endOfMonthOffset] != '-') {
+ return false;
+ }
+
+ const nsAString& yearMonthStr = Substring(aValue, 0, endOfMonthOffset);
+ if (!ParseMonth(yearMonthStr, aYear, aMonth)) {
+ return false;
+ }
+
+ return DigitSubStringToNumber(aValue, endOfMonthOffset + 1, 2, aDay) &&
+ *aDay > 0 && *aDay <= NumberOfDaysInMonth(*aMonth, *aYear);
+}
+
+bool
+HTMLInputElement::ParseDateTimeLocal(const nsAString& aValue, uint32_t* aYear,
+ uint32_t* aMonth, uint32_t* aDay,
+ uint32_t* aTime) const
+{
+ // Parse the year, month, day and time values out a string formatted as
+ // 'yyyy-mm-ddThh:mm[:ss.s] or 'yyyy-mm-dd hh:mm[:ss.s]', where fractions of
+ // seconds can be 1 to 3 digits.
+ // The minimum length allowed is 16, which is of the form 'yyyy-mm-ddThh:mm'
+ // or 'yyyy-mm-dd hh:mm'.
+ if (aValue.Length() < 16) {
+ return false;
+ }
+
+ const uint32_t sepIndex = 10;
+ if (aValue[sepIndex] != 'T' && aValue[sepIndex] != ' ') {
+ return false;
+ }
+
+ const nsAString& dateStr = Substring(aValue, 0, sepIndex);
+ if (!ParseDate(dateStr, aYear, aMonth, aDay)) {
+ return false;
+ }
+
+ const nsAString& timeStr = Substring(aValue, sepIndex + 1,
+ aValue.Length() - sepIndex + 1);
+ if (!ParseTime(timeStr, aTime)) {
+ return false;
+ }
+
+ return true;
+}
+
+void
+HTMLInputElement::NormalizeDateTimeLocal(nsAString& aValue) const
+{
+ if (aValue.IsEmpty()) {
+ return;
+ }
+
+ // Use 'T' as the separator between date string and time string.
+ const uint32_t sepIndex = 10;
+ if (aValue[sepIndex] == ' ') {
+ aValue.Replace(sepIndex, 1, NS_LITERAL_STRING("T"));
+ }
+
+ // Time expressed as the shortest possible string.
+ if (aValue.Length() == 16) {
+ return;
+ }
+
+ // Fractions of seconds part is optional, ommit it if it's 0.
+ if (aValue.Length() > 19) {
+ uint32_t milliseconds;
+ if (!DigitSubStringToNumber(aValue, 20, aValue.Length() - 20,
+ &milliseconds)) {
+ return;
+ }
+
+ if (milliseconds != 0) {
+ return;
+ }
+
+ aValue.Cut(19, aValue.Length() - 19);
+ }
+
+ // Seconds part is optional, ommit it if it's 0.
+ uint32_t seconds;
+ if (!DigitSubStringToNumber(aValue, 17, aValue.Length() - 17, &seconds)) {
+ return;
+ }
+
+ if (seconds != 0) {
+ return;
+ }
+
+ aValue.Cut(16, aValue.Length() - 16);
+}
+
+double
+HTMLInputElement::DaysSinceEpochFromWeek(uint32_t aYear, uint32_t aWeek) const
+{
+ double days = JS::DayFromYear(aYear) + (aWeek - 1) * 7;
+ uint32_t dayOneIsoWeekday = DayOfWeek(aYear, 1, 1, true);
+
+ // If day one of that year is on/before Thursday, we should subtract the
+ // days that belong to last year in our first week, otherwise, our first
+ // days belong to last year's last week, and we should add those days
+ // back.
+ if (dayOneIsoWeekday <= 4) {
+ days -= (dayOneIsoWeekday - 1);
+ } else {
+ days += (7 - dayOneIsoWeekday + 1);
+ }
+
+ return days;
+}
+
+uint32_t
+HTMLInputElement::NumberOfDaysInMonth(uint32_t aMonth, uint32_t aYear) const
+{
+/*
+ * Returns the number of days in a month.
+ * Months that are |longMonths| always have 31 days.
+ * Months that are not |longMonths| have 30 days except February (month 2).
+ * February has 29 days during leap years which are years that are divisible by 400.
+ * or divisible by 100 and 4. February has 28 days otherwise.
+ */
+
+ static const bool longMonths[] = { true, false, true, false, true, false,
+ true, true, false, true, false, true };
+ MOZ_ASSERT(aMonth <= 12 && aMonth > 0);
+
+ if (longMonths[aMonth-1]) {
+ return 31;
+ }
+
+ if (aMonth != 2) {
+ return 30;
+ }
+
+ return IsLeapYear(aYear) ? 29 : 28;
+}
+
+/* static */ bool
+HTMLInputElement::DigitSubStringToNumber(const nsAString& aStr,
+ uint32_t aStart, uint32_t aLen,
+ uint32_t* aRetVal)
+{
+ MOZ_ASSERT(aStr.Length() > (aStart + aLen - 1));
+
+ for (uint32_t offset = 0; offset < aLen; ++offset) {
+ if (!NS_IsAsciiDigit(aStr[aStart + offset])) {
+ return false;
+ }
+ }
+
+ nsresult ec;
+ *aRetVal = static_cast<uint32_t>(PromiseFlatString(Substring(aStr, aStart, aLen)).ToInteger(&ec));
+
+ return NS_SUCCEEDED(ec);
+}
+
+bool
+HTMLInputElement::IsValidTime(const nsAString& aValue) const
+{
+ return ParseTime(aValue, nullptr);
+}
+
+/* static */ bool
+HTMLInputElement::ParseTime(const nsAString& aValue, uint32_t* aResult)
+{
+ /* The string must have the following parts:
+ * - HOURS: two digits, value being in [0, 23];
+ * - Colon (:);
+ * - MINUTES: two digits, value being in [0, 59];
+ * - Optional:
+ * - Colon (:);
+ * - SECONDS: two digits, value being in [0, 59];
+ * - Optional:
+ * - DOT (.);
+ * - FRACTIONAL SECONDS: one to three digits, no value range.
+ */
+
+ // The following format is the shorter one allowed: "HH:MM".
+ if (aValue.Length() < 5) {
+ return false;
+ }
+
+ uint32_t hours;
+ if (!DigitSubStringToNumber(aValue, 0, 2, &hours) || hours > 23) {
+ return false;
+ }
+
+ // Hours/minutes separator.
+ if (aValue[2] != ':') {
+ return false;
+ }
+
+ uint32_t minutes;
+ if (!DigitSubStringToNumber(aValue, 3, 2, &minutes) || minutes > 59) {
+ return false;
+ }
+
+ if (aValue.Length() == 5) {
+ if (aResult) {
+ *aResult = ((hours * 60) + minutes) * 60000;
+ }
+ return true;
+ }
+
+ // The following format is the next shorter one: "HH:MM:SS".
+ if (aValue.Length() < 8 || aValue[5] != ':') {
+ return false;
+ }
+
+ uint32_t seconds;
+ if (!DigitSubStringToNumber(aValue, 6, 2, &seconds) || seconds > 59) {
+ return false;
+ }
+
+ if (aValue.Length() == 8) {
+ if (aResult) {
+ *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000;
+ }
+ return true;
+ }
+
+ // The string must follow this format now: "HH:MM:SS.{s,ss,sss}".
+ // There can be 1 to 3 digits for the fractions of seconds.
+ if (aValue.Length() == 9 || aValue.Length() > 12 || aValue[8] != '.') {
+ return false;
+ }
+
+ uint32_t fractionsSeconds;
+ if (!DigitSubStringToNumber(aValue, 9, aValue.Length() - 9, &fractionsSeconds)) {
+ return false;
+ }
+
+ if (aResult) {
+ *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000 +
+ // NOTE: there is 10.0 instead of 10 and static_cast<int> because
+ // some old [and stupid] compilers can't just do the right thing.
+ fractionsSeconds * pow(10.0, static_cast<int>(3 - (aValue.Length() - 9)));
+ }
+
+ return true;
+}
+
+static bool
+IsDateTimeEnabled(int32_t aNewType)
+{
+ return (aNewType == NS_FORM_INPUT_DATE &&
+ (Preferences::GetBool("dom.forms.datetime", false) ||
+ Preferences::GetBool("dom.experimental_forms", false) ||
+ Preferences::GetBool("dom.forms.datepicker", false))) ||
+ (aNewType == NS_FORM_INPUT_TIME &&
+ (Preferences::GetBool("dom.forms.datetime", false) ||
+ Preferences::GetBool("dom.experimental_forms", false))) ||
+ ((aNewType == NS_FORM_INPUT_MONTH ||
+ aNewType == NS_FORM_INPUT_WEEK ||
+ aNewType == NS_FORM_INPUT_DATETIME_LOCAL) &&
+ Preferences::GetBool("dom.forms.datetime", false));
+}
+
+bool
+HTMLInputElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::type) {
+ // XXX ARG!! This is major evilness. ParseAttribute
+ // shouldn't set members. Override SetAttr instead
+ int32_t newType;
+ bool success = aResult.ParseEnumValue(aValue, kInputTypeTable, false);
+ if (success) {
+ newType = aResult.GetEnumValue();
+ if ((IsExperimentalMobileType(newType) &&
+ !Preferences::GetBool("dom.experimental_forms", false)) ||
+ (newType == NS_FORM_INPUT_NUMBER &&
+ !Preferences::GetBool("dom.forms.number", false)) ||
+ (newType == NS_FORM_INPUT_COLOR &&
+ !Preferences::GetBool("dom.forms.color", false)) ||
+ (IsDateTimeInputType(newType) && !IsDateTimeEnabled(newType))) {
+ newType = kInputDefaultType->value;
+ aResult.SetTo(newType, &aValue);
+ }
+ } else {
+ newType = kInputDefaultType->value;
+ }
+
+ if (newType != mType) {
+ // Make sure to do the check for newType being NS_FORM_INPUT_FILE and
+ // the corresponding SetValueInternal() call _before_ we set mType.
+ // That way the logic in SetValueInternal() will work right (that logic
+ // makes assumptions about our frame based on mType, but we won't have
+ // had time to recreate frames yet -- that happens later in the
+ // SetAttr() process).
+ if (newType == NS_FORM_INPUT_FILE || mType == NS_FORM_INPUT_FILE) {
+ // This call isn't strictly needed any more since we'll never
+ // confuse values and filenames. However it's there for backwards
+ // compat.
+ ClearFiles(false);
+ }
+
+ HandleTypeChange(newType);
+ }
+
+ return success;
+ }
+ if (aAttribute == nsGkAtoms::width) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::height) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::maxlength) {
+ return aResult.ParseNonNegativeIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::minlength) {
+ return aResult.ParseNonNegativeIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::size) {
+ return aResult.ParsePositiveIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::border) {
+ return aResult.ParseIntWithBounds(aValue, 0);
+ }
+ if (aAttribute == nsGkAtoms::align) {
+ return ParseAlignValue(aValue, aResult);
+ }
+ if (aAttribute == nsGkAtoms::formmethod) {
+ return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
+ }
+ if (aAttribute == nsGkAtoms::formenctype) {
+ return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
+ }
+ if (aAttribute == nsGkAtoms::autocomplete) {
+ aResult.ParseAtomArray(aValue);
+ return true;
+ }
+ if (aAttribute == nsGkAtoms::inputmode) {
+ return aResult.ParseEnumValue(aValue, kInputInputmodeTable, false);
+ }
+ if (ParseImageAttribute(aAttribute, aValue, aResult)) {
+ // We have to call |ParseImageAttribute| unconditionally since we
+ // don't know if we're going to have a type="image" attribute yet,
+ // (or could have it set dynamically in the future). See bug
+ // 214077.
+ return true;
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLInputElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::type);
+ if (value && value->Type() == nsAttrValue::eEnum &&
+ value->GetEnumValue() == NS_FORM_INPUT_IMAGE) {
+ nsGenericHTMLFormElementWithState::MapImageBorderAttributeInto(aAttributes, aData);
+ nsGenericHTMLFormElementWithState::MapImageMarginAttributeInto(aAttributes, aData);
+ nsGenericHTMLFormElementWithState::MapImageSizeAttributesInto(aAttributes, aData);
+ // Images treat align as "float"
+ nsGenericHTMLFormElementWithState::MapImageAlignAttributeInto(aAttributes, aData);
+ }
+
+ nsGenericHTMLFormElementWithState::MapCommonAttributesInto(aAttributes, aData);
+}
+
+nsChangeHint
+HTMLInputElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const
+{
+ nsChangeHint retval =
+ nsGenericHTMLFormElementWithState::GetAttributeChangeHint(aAttribute, aModType);
+ if (aAttribute == nsGkAtoms::type ||
+ // The presence or absence of the 'directory' attribute determines what
+ // buttons we show for type=file.
+ aAttribute == nsGkAtoms::allowdirs ||
+ aAttribute == nsGkAtoms::webkitdirectory) {
+ retval |= nsChangeHint_ReconstructFrame;
+ } else if (mType == NS_FORM_INPUT_IMAGE &&
+ (aAttribute == nsGkAtoms::alt ||
+ aAttribute == nsGkAtoms::value)) {
+ // We might need to rebuild our alt text. Just go ahead and
+ // reconstruct our frame. This should be quite rare..
+ retval |= nsChangeHint_ReconstructFrame;
+ } else if (aAttribute == nsGkAtoms::value) {
+ retval |= NS_STYLE_HINT_REFLOW;
+ } else if (aAttribute == nsGkAtoms::size &&
+ IsSingleLineTextControl(false)) {
+ retval |= NS_STYLE_HINT_REFLOW;
+ } else if (PlaceholderApplies() && aAttribute == nsGkAtoms::placeholder) {
+ retval |= nsChangeHint_ReconstructFrame;
+ }
+ return retval;
+}
+
+NS_IMETHODIMP_(bool)
+HTMLInputElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::align },
+ { &nsGkAtoms::type },
+ { nullptr },
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap,
+ sImageMarginSizeAttributeMap,
+ sImageBorderAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+nsMapRuleToAttributesFunc
+HTMLInputElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+
+// Directory picking methods:
+
+bool
+HTMLInputElement::IsFilesAndDirectoriesSupported() const
+{
+ // This method is supposed to return true if a file and directory picker
+ // supports the selection of both files and directories *at the same time*.
+ // Only Mac currently supports that. We could implement it for Mac, but
+ // currently we do not.
+ return false;
+}
+
+void
+HTMLInputElement::ChooseDirectory(ErrorResult& aRv)
+{
+ if (mType != NS_FORM_INPUT_FILE) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+ // Script can call this method directly, so even though we don't show the
+ // "Pick Folder..." button on platforms that don't have a directory picker
+ // we have to redirect to the file picker here.
+ InitFilePicker(
+#if defined(ANDROID) || defined(MOZ_B2G)
+ // No native directory picker - redirect to plain file picker
+ FILE_PICKER_FILE
+#else
+ FILE_PICKER_DIRECTORY
+#endif
+ );
+}
+
+already_AddRefed<Promise>
+HTMLInputElement::GetFilesAndDirectories(ErrorResult& aRv)
+{
+ if (mType != NS_FORM_INPUT_FILE) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
+ MOZ_ASSERT(global);
+ if (!global) {
+ return nullptr;
+ }
+
+ RefPtr<Promise> p = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ const nsTArray<OwningFileOrDirectory>& filesAndDirs =
+ GetFilesOrDirectoriesInternal();
+
+ Sequence<OwningFileOrDirectory> filesAndDirsSeq;
+
+ if (!filesAndDirsSeq.SetLength(filesAndDirs.Length(),
+ mozilla::fallible_t())) {
+ p->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ return p.forget();
+ }
+
+ for (uint32_t i = 0; i < filesAndDirs.Length(); ++i) {
+ if (filesAndDirs[i].IsDirectory()) {
+ RefPtr<Directory> directory = filesAndDirs[i].GetAsDirectory();
+
+ // In future we could refactor SetFilePickerFiltersFromAccept to return a
+ // semicolon separated list of file extensions and include that in the
+ // filter string passed here.
+ directory->SetContentFilters(NS_LITERAL_STRING("filter-out-sensitive"));
+ filesAndDirsSeq[i].SetAsDirectory() = directory;
+ } else {
+ MOZ_ASSERT(filesAndDirs[i].IsFile());
+
+ // This file was directly selected by the user, so don't filter it.
+ filesAndDirsSeq[i].SetAsFile() = filesAndDirs[i].GetAsFile();
+ }
+ }
+
+ p->MaybeResolve(filesAndDirsSeq);
+ return p.forget();
+}
+
+already_AddRefed<Promise>
+HTMLInputElement::GetFiles(bool aRecursiveFlag, ErrorResult& aRv)
+{
+ if (mType != NS_FORM_INPUT_FILE) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ GetFilesHelper* helper = GetOrCreateGetFilesHelper(aRecursiveFlag, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ MOZ_ASSERT(helper);
+
+ nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
+ MOZ_ASSERT(global);
+ if (!global) {
+ return nullptr;
+ }
+
+ RefPtr<Promise> p = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ helper->AddPromise(p);
+ return p.forget();
+}
+
+
+// Controllers Methods
+
+nsIControllers*
+HTMLInputElement::GetControllers(ErrorResult& aRv)
+{
+ //XXX: what about type "file"?
+ if (IsSingleLineTextControl(false))
+ {
+ if (!mControllers)
+ {
+ nsresult rv;
+ mControllers = do_CreateInstance(kXULControllersCID, &rv);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIController>
+ controller(do_CreateInstance("@mozilla.org/editor/editorcontroller;1",
+ &rv));
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ mControllers->AppendController(controller);
+
+ controller = do_CreateInstance("@mozilla.org/editor/editingcontroller;1",
+ &rv);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ mControllers->AppendController(controller);
+ }
+ }
+
+ return mControllers;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetControllers(nsIControllers** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ ErrorResult rv;
+ RefPtr<nsIControllers> controller = GetControllers(rv);
+ controller.forget(aResult);
+ return rv.StealNSResult();
+}
+
+int32_t
+HTMLInputElement::GetTextLength(ErrorResult& aRv)
+{
+ nsAutoString val;
+ GetValue(val);
+ return val.Length();
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetTextLength(int32_t* aTextLength)
+{
+ ErrorResult rv;
+ *aTextLength = GetTextLength(rv);
+ return rv.StealNSResult();
+}
+
+void
+HTMLInputElement::SetSelectionRange(int32_t aSelectionStart,
+ int32_t aSelectionEnd,
+ const Optional<nsAString>& aDirection,
+ ErrorResult& aRv)
+{
+ if (!SupportsTextSelection()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ nsresult rv = SetSelectionRange(aSelectionStart, aSelectionEnd,
+ aDirection.WasPassed() ? aDirection.Value() : NullString());
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetSelectionRange(int32_t aSelectionStart,
+ int32_t aSelectionEnd,
+ const nsAString& aDirection)
+{
+ nsresult rv = NS_OK;
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+ nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
+ if (textControlFrame) {
+ // Default to forward, even if not specified.
+ // Note that we don't currently support directionless selections, so
+ // "none" is treated like "forward".
+ nsITextControlFrame::SelectionDirection dir = nsITextControlFrame::eForward;
+ if (!aDirection.IsEmpty() && aDirection.EqualsLiteral("backward")) {
+ dir = nsITextControlFrame::eBackward;
+ }
+
+ rv = textControlFrame->SetSelectionRange(aSelectionStart, aSelectionEnd, dir);
+ if (NS_SUCCEEDED(rv)) {
+ rv = textControlFrame->ScrollSelectionIntoView();
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, NS_LITERAL_STRING("select"),
+ true, false);
+ asyncDispatcher->PostDOMEvent();
+ }
+ }
+
+ return rv;
+}
+
+void
+HTMLInputElement::SetRangeText(const nsAString& aReplacement, ErrorResult& aRv)
+{
+ if (!SupportsTextSelection()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ int32_t start, end;
+ aRv = GetSelectionRange(&start, &end);
+ if (aRv.Failed()) {
+ nsTextEditorState* state = GetEditorState();
+ if (state && state->IsSelectionCached()) {
+ start = state->GetSelectionProperties().GetStart();
+ end = state->GetSelectionProperties().GetEnd();
+ aRv = NS_OK;
+ }
+ }
+
+ SetRangeText(aReplacement, start, end, mozilla::dom::SelectionMode::Preserve,
+ aRv, start, end);
+}
+
+void
+HTMLInputElement::SetRangeText(const nsAString& aReplacement, uint32_t aStart,
+ uint32_t aEnd, const SelectionMode& aSelectMode,
+ ErrorResult& aRv, int32_t aSelectionStart,
+ int32_t aSelectionEnd)
+{
+ if (!SupportsTextSelection()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (aStart > aEnd) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ nsAutoString value;
+ GetValueInternal(value);
+ uint32_t inputValueLength = value.Length();
+
+ if (aStart > inputValueLength) {
+ aStart = inputValueLength;
+ }
+
+ if (aEnd > inputValueLength) {
+ aEnd = inputValueLength;
+ }
+
+ if (aSelectionStart == -1 && aSelectionEnd == -1) {
+ aRv = GetSelectionRange(&aSelectionStart, &aSelectionEnd);
+ if (aRv.Failed()) {
+ nsTextEditorState* state = GetEditorState();
+ if (state && state->IsSelectionCached()) {
+ aSelectionStart = state->GetSelectionProperties().GetStart();
+ aSelectionEnd = state->GetSelectionProperties().GetEnd();
+ aRv = NS_OK;
+ }
+ }
+ }
+
+ if (aStart <= aEnd) {
+ value.Replace(aStart, aEnd - aStart, aReplacement);
+ nsresult rv =
+ SetValueInternal(value, nsTextEditorState::eSetValue_ByContent);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+ }
+
+ uint32_t newEnd = aStart + aReplacement.Length();
+ int32_t delta = aReplacement.Length() - (aEnd - aStart);
+
+ switch (aSelectMode) {
+ case mozilla::dom::SelectionMode::Select:
+ {
+ aSelectionStart = aStart;
+ aSelectionEnd = newEnd;
+ }
+ break;
+ case mozilla::dom::SelectionMode::Start:
+ {
+ aSelectionStart = aSelectionEnd = aStart;
+ }
+ break;
+ case mozilla::dom::SelectionMode::End:
+ {
+ aSelectionStart = aSelectionEnd = newEnd;
+ }
+ break;
+ case mozilla::dom::SelectionMode::Preserve:
+ {
+ if ((uint32_t)aSelectionStart > aEnd) {
+ aSelectionStart += delta;
+ } else if ((uint32_t)aSelectionStart > aStart) {
+ aSelectionStart = aStart;
+ }
+
+ if ((uint32_t)aSelectionEnd > aEnd) {
+ aSelectionEnd += delta;
+ } else if ((uint32_t)aSelectionEnd > aStart) {
+ aSelectionEnd = newEnd;
+ }
+ }
+ break;
+ default:
+ MOZ_CRASH("Unknown mode!");
+ }
+
+ Optional<nsAString> direction;
+ SetSelectionRange(aSelectionStart, aSelectionEnd, direction, aRv);
+}
+
+Nullable<int32_t>
+HTMLInputElement::GetSelectionStart(ErrorResult& aRv)
+{
+ if (!SupportsTextSelection()) {
+ return Nullable<int32_t>();
+ }
+
+ int32_t selStart;
+ nsresult rv = GetSelectionStart(&selStart);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+
+ return Nullable<int32_t>(selStart);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetSelectionStart(int32_t* aSelectionStart)
+{
+ NS_ENSURE_ARG_POINTER(aSelectionStart);
+
+ int32_t selEnd, selStart;
+ nsresult rv = GetSelectionRange(&selStart, &selEnd);
+
+ if (NS_FAILED(rv)) {
+ nsTextEditorState* state = GetEditorState();
+ if (state && state->IsSelectionCached()) {
+ *aSelectionStart = state->GetSelectionProperties().GetStart();
+ return NS_OK;
+ }
+ return rv;
+ }
+
+ *aSelectionStart = selStart;
+ return NS_OK;
+}
+
+void
+HTMLInputElement::SetSelectionStart(const Nullable<int32_t>& aSelectionStart,
+ ErrorResult& aRv)
+{
+ if (!SupportsTextSelection()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ int32_t selStart = 0;
+ if (!aSelectionStart.IsNull()) {
+ selStart = aSelectionStart.Value();
+ }
+
+ nsTextEditorState* state = GetEditorState();
+ if (state && state->IsSelectionCached()) {
+ state->GetSelectionProperties().SetStart(selStart);
+ return;
+ }
+
+ nsAutoString direction;
+ aRv = GetSelectionDirection(direction);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ int32_t start, end;
+ aRv = GetSelectionRange(&start, &end);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ start = selStart;
+ if (end < start) {
+ end = start;
+ }
+
+ aRv = SetSelectionRange(start, end, direction);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetSelectionStart(int32_t aSelectionStart)
+{
+ ErrorResult rv;
+ Nullable<int32_t> selStart(aSelectionStart);
+ SetSelectionStart(selStart, rv);
+ return rv.StealNSResult();
+}
+
+Nullable<int32_t>
+HTMLInputElement::GetSelectionEnd(ErrorResult& aRv)
+{
+ if (!SupportsTextSelection()) {
+ return Nullable<int32_t>();
+ }
+
+ int32_t selEnd;
+ nsresult rv = GetSelectionEnd(&selEnd);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+
+ return Nullable<int32_t>(selEnd);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetSelectionEnd(int32_t* aSelectionEnd)
+{
+ NS_ENSURE_ARG_POINTER(aSelectionEnd);
+
+ int32_t selEnd, selStart;
+ nsresult rv = GetSelectionRange(&selStart, &selEnd);
+
+ if (NS_FAILED(rv)) {
+ nsTextEditorState* state = GetEditorState();
+ if (state && state->IsSelectionCached()) {
+ *aSelectionEnd = state->GetSelectionProperties().GetEnd();
+ return NS_OK;
+ }
+ return rv;
+ }
+
+ *aSelectionEnd = selEnd;
+ return NS_OK;
+}
+
+void
+HTMLInputElement::SetSelectionEnd(const Nullable<int32_t>& aSelectionEnd,
+ ErrorResult& aRv)
+{
+ if (!SupportsTextSelection()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ int32_t selEnd = 0;
+ if (!aSelectionEnd.IsNull()) {
+ selEnd = aSelectionEnd.Value();
+ }
+
+ nsTextEditorState* state = GetEditorState();
+ if (state && state->IsSelectionCached()) {
+ state->GetSelectionProperties().SetEnd(selEnd);
+ return;
+ }
+
+ nsAutoString direction;
+ aRv = GetSelectionDirection(direction);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ int32_t start, end;
+ aRv = GetSelectionRange(&start, &end);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ end = selEnd;
+ if (start > end) {
+ start = end;
+ }
+
+ aRv = SetSelectionRange(start, end, direction);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetSelectionEnd(int32_t aSelectionEnd)
+{
+ ErrorResult rv;
+ Nullable<int32_t> selEnd(aSelectionEnd);
+ SetSelectionEnd(selEnd, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetFiles(nsIDOMFileList** aFileList)
+{
+ RefPtr<FileList> list = GetFiles();
+ list.forget(aFileList);
+ return NS_OK;
+}
+
+nsresult
+HTMLInputElement::GetSelectionRange(int32_t* aSelectionStart,
+ int32_t* aSelectionEnd)
+{
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+ nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
+ if (textControlFrame) {
+ return textControlFrame->GetSelectionRange(aSelectionStart, aSelectionEnd);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+static void
+DirectionToName(nsITextControlFrame::SelectionDirection dir, nsAString& aDirection)
+{
+ if (dir == nsITextControlFrame::eNone) {
+ aDirection.AssignLiteral("none");
+ } else if (dir == nsITextControlFrame::eForward) {
+ aDirection.AssignLiteral("forward");
+ } else if (dir == nsITextControlFrame::eBackward) {
+ aDirection.AssignLiteral("backward");
+ } else {
+ NS_NOTREACHED("Invalid SelectionDirection value");
+ }
+}
+
+void
+HTMLInputElement::GetSelectionDirection(nsAString& aDirection, ErrorResult& aRv)
+{
+ if (!SupportsTextSelection()) {
+ aDirection.SetIsVoid(true);
+ return;
+ }
+
+ nsresult rv = NS_ERROR_FAILURE;
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+ nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
+ if (textControlFrame) {
+ nsITextControlFrame::SelectionDirection dir;
+ rv = textControlFrame->GetSelectionRange(nullptr, nullptr, &dir);
+ if (NS_SUCCEEDED(rv)) {
+ DirectionToName(dir, aDirection);
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ nsTextEditorState* state = GetEditorState();
+ if (state && state->IsSelectionCached()) {
+ DirectionToName(state->GetSelectionProperties().GetDirection(), aDirection);
+ return;
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetSelectionDirection(nsAString& aDirection)
+{
+ ErrorResult rv;
+ GetSelectionDirection(aDirection, rv);
+ return rv.StealNSResult();
+}
+
+void
+HTMLInputElement::SetSelectionDirection(const nsAString& aDirection, ErrorResult& aRv)
+{
+ if (!SupportsTextSelection()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ nsTextEditorState* state = GetEditorState();
+ if (state && state->IsSelectionCached()) {
+ nsITextControlFrame::SelectionDirection dir = nsITextControlFrame::eNone;
+ if (aDirection.EqualsLiteral("forward")) {
+ dir = nsITextControlFrame::eForward;
+ } else if (aDirection.EqualsLiteral("backward")) {
+ dir = nsITextControlFrame::eBackward;
+ }
+ state->GetSelectionProperties().SetDirection(dir);
+ return;
+ }
+
+ int32_t start, end;
+ aRv = GetSelectionRange(&start, &end);
+ if (!aRv.Failed()) {
+ aRv = SetSelectionRange(start, end, aDirection);
+ }
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetSelectionDirection(const nsAString& aDirection)
+{
+ ErrorResult rv;
+ SetSelectionDirection(aDirection, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetPhonetic(nsAString& aPhonetic)
+{
+ aPhonetic.Truncate();
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+ nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
+ if (textControlFrame) {
+ textControlFrame->GetPhonetic(aPhonetic);
+ }
+
+ return NS_OK;
+}
+
+#ifdef ACCESSIBILITY
+/*static*/ nsresult
+FireEventForAccessibility(nsIDOMHTMLInputElement* aTarget,
+ nsPresContext* aPresContext,
+ const nsAString& aEventType)
+{
+ nsCOMPtr<mozilla::dom::Element> element = do_QueryInterface(aTarget);
+ RefPtr<Event> event = NS_NewDOMEvent(element, aPresContext, nullptr);
+ event->InitEvent(aEventType, true, true);
+ event->SetTrusted(true);
+
+ EventDispatcher::DispatchDOMEvent(aTarget, nullptr, event, aPresContext,
+ nullptr);
+
+ return NS_OK;
+}
+#endif
+
+void
+HTMLInputElement::UpdateApzAwareFlag()
+{
+#if !defined(ANDROID) && !defined(XP_MACOSX)
+ if ((mType == NS_FORM_INPUT_NUMBER) || (mType == NS_FORM_INPUT_RANGE)) {
+ SetMayBeApzAware();
+ }
+#endif
+}
+
+nsresult
+HTMLInputElement::SetDefaultValueAsValue()
+{
+ NS_ASSERTION(GetValueMode() == VALUE_MODE_VALUE,
+ "GetValueMode() should return VALUE_MODE_VALUE!");
+
+ // The element has a content attribute value different from it's value when
+ // it's in the value mode value.
+ nsAutoString resetVal;
+ GetDefaultValue(resetVal);
+
+ // SetValueInternal is going to sanitize the value.
+ return SetValueInternal(resetVal, nsTextEditorState::eSetValue_Internal);
+}
+
+void
+HTMLInputElement::SetDirectionIfAuto(bool aAuto, bool aNotify)
+{
+ if (aAuto) {
+ SetHasDirAuto();
+ if (IsSingleLineTextControl(true)) {
+ nsAutoString value;
+ GetValue(value);
+ SetDirectionalityFromValue(this, value, aNotify);
+ }
+ } else {
+ ClearHasDirAuto();
+ }
+}
+
+NS_IMETHODIMP
+HTMLInputElement::Reset()
+{
+ // We should be able to reset all dirty flags regardless of the type.
+ SetCheckedChanged(false);
+ SetValueChanged(false);
+ mLastValueChangeWasInteractive = false;
+
+ switch (GetValueMode()) {
+ case VALUE_MODE_VALUE:
+ return SetDefaultValueAsValue();
+ case VALUE_MODE_DEFAULT_ON:
+ DoSetChecked(DefaultChecked(), true, false);
+ return NS_OK;
+ case VALUE_MODE_FILENAME:
+ ClearFiles(false);
+ return NS_OK;
+ case VALUE_MODE_DEFAULT:
+ default:
+ return NS_OK;
+ }
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission)
+{
+ // Disabled elements don't submit
+ // For type=reset, and type=button, we just never submit, period.
+ // For type=image and type=button, we only submit if we were the button
+ // pressed
+ // For type=radio and type=checkbox, we only submit if checked=true
+ if (IsDisabled() || mType == NS_FORM_INPUT_RESET ||
+ mType == NS_FORM_INPUT_BUTTON ||
+ ((mType == NS_FORM_INPUT_SUBMIT || mType == NS_FORM_INPUT_IMAGE) &&
+ aFormSubmission->GetOriginatingElement() != this) ||
+ ((mType == NS_FORM_INPUT_RADIO || mType == NS_FORM_INPUT_CHECKBOX) &&
+ !mChecked)) {
+ return NS_OK;
+ }
+
+ // Get the name
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+
+ // Submit .x, .y for input type=image
+ if (mType == NS_FORM_INPUT_IMAGE) {
+ // Get a property set by the frame to find out where it was clicked.
+ nsIntPoint* lastClickedPoint =
+ static_cast<nsIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
+ int32_t x, y;
+ if (lastClickedPoint) {
+ // Convert the values to strings for submission
+ x = lastClickedPoint->x;
+ y = lastClickedPoint->y;
+ } else {
+ x = y = 0;
+ }
+
+ nsAutoString xVal, yVal;
+ xVal.AppendInt(x);
+ yVal.AppendInt(y);
+
+ if (!name.IsEmpty()) {
+ aFormSubmission->AddNameValuePair(name + NS_LITERAL_STRING(".x"), xVal);
+ aFormSubmission->AddNameValuePair(name + NS_LITERAL_STRING(".y"), yVal);
+ } else {
+ // If the Image Element has no name, simply return x and y
+ // to Nav and IE compatibility.
+ aFormSubmission->AddNameValuePair(NS_LITERAL_STRING("x"), xVal);
+ aFormSubmission->AddNameValuePair(NS_LITERAL_STRING("y"), yVal);
+ }
+
+ return NS_OK;
+ }
+
+ //
+ // Submit name=value
+ //
+
+ // If name not there, don't submit
+ if (name.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // Get the value
+ nsAutoString value;
+ nsresult rv = GetValue(value);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (mType == NS_FORM_INPUT_SUBMIT && value.IsEmpty() &&
+ !HasAttr(kNameSpaceID_None, nsGkAtoms::value)) {
+ // Get our default value, which is the same as our default label
+ nsXPIDLString defaultValue;
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "Submit", defaultValue);
+ value = defaultValue;
+ }
+
+ //
+ // Submit file if its input type=file and this encoding method accepts files
+ //
+ if (mType == NS_FORM_INPUT_FILE) {
+ // Submit files
+
+ const nsTArray<OwningFileOrDirectory>& files =
+ GetFilesOrDirectoriesInternal();
+
+ if (files.IsEmpty()) {
+ aFormSubmission->AddNameBlobOrNullPair(name, nullptr);
+ return NS_OK;
+ }
+
+ for (uint32_t i = 0; i < files.Length(); ++i) {
+ if (files[i].IsFile()) {
+ aFormSubmission->AddNameBlobOrNullPair(name, files[i].GetAsFile());
+ } else {
+ MOZ_ASSERT(files[i].IsDirectory());
+ aFormSubmission->AddNameDirectoryPair(name, files[i].GetAsDirectory());
+ }
+ }
+
+ return NS_OK;
+ }
+
+ if (mType == NS_FORM_INPUT_HIDDEN && name.EqualsLiteral("_charset_")) {
+ nsCString charset;
+ aFormSubmission->GetCharset(charset);
+ return aFormSubmission->AddNameValuePair(name,
+ NS_ConvertASCIItoUTF16(charset));
+ }
+ if (IsSingleLineTextControl(true) &&
+ name.EqualsLiteral("isindex") &&
+ aFormSubmission->SupportsIsindexSubmission()) {
+ return aFormSubmission->AddIsindex(value);
+ }
+ return aFormSubmission->AddNameValuePair(name, value);
+}
+
+
+NS_IMETHODIMP
+HTMLInputElement::SaveState()
+{
+ RefPtr<HTMLInputElementState> inputState;
+ switch (GetValueMode()) {
+ case VALUE_MODE_DEFAULT_ON:
+ if (mCheckedChanged) {
+ inputState = new HTMLInputElementState();
+ inputState->SetChecked(mChecked);
+ }
+ break;
+ case VALUE_MODE_FILENAME:
+ if (!mFilesOrDirectories.IsEmpty()) {
+ inputState = new HTMLInputElementState();
+ inputState->SetFilesOrDirectories(mFilesOrDirectories);
+ }
+ break;
+ case VALUE_MODE_VALUE:
+ case VALUE_MODE_DEFAULT:
+ // VALUE_MODE_DEFAULT shouldn't have their value saved except 'hidden',
+ // mType shouldn't be NS_FORM_INPUT_PASSWORD and value should have changed.
+ if ((GetValueMode() == VALUE_MODE_DEFAULT &&
+ mType != NS_FORM_INPUT_HIDDEN) ||
+ mType == NS_FORM_INPUT_PASSWORD || !mValueChanged) {
+ break;
+ }
+
+ inputState = new HTMLInputElementState();
+ nsAutoString value;
+ nsresult rv = GetValue(value);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!IsSingleLineTextControl(false)) {
+ rv = nsLinebreakConverter::ConvertStringLineBreaks(
+ value,
+ nsLinebreakConverter::eLinebreakPlatform,
+ nsLinebreakConverter::eLinebreakContent);
+
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Converting linebreaks failed!");
+ return rv;
+ }
+ }
+
+ inputState->SetValue(value);
+ break;
+ }
+
+ if (inputState) {
+ nsPresState* state = GetPrimaryPresState();
+ if (state) {
+ state->SetStateProperty(inputState);
+ }
+ }
+
+ if (mDisabledChanged) {
+ nsPresState* state = GetPrimaryPresState();
+ if (state) {
+ // We do not want to save the real disabled state but the disabled
+ // attribute.
+ state->SetDisabled(HasAttr(kNameSpaceID_None, nsGkAtoms::disabled));
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+HTMLInputElement::DoneCreatingElement()
+{
+ mDoneCreating = true;
+
+ //
+ // Restore state as needed. Note that disabled state applies to all control
+ // types.
+ //
+ bool restoredCheckedState =
+ !mInhibitRestoration && NS_SUCCEEDED(GenerateStateKey()) && RestoreFormControlState();
+
+ //
+ // If restore does not occur, we initialize .checked using the CHECKED
+ // property.
+ //
+ if (!restoredCheckedState && mShouldInitChecked) {
+ DoSetChecked(DefaultChecked(), false, true);
+ DoSetCheckedChanged(false, false);
+ }
+
+ // Sanitize the value.
+ if (GetValueMode() == VALUE_MODE_VALUE) {
+ nsAutoString aValue;
+ GetValue(aValue);
+ // TODO: What should we do if SetValueInternal fails? (The allocation
+ // may potentially be big, but most likely we've failed to allocate
+ // before the type change.)
+ SetValueInternal(aValue, nsTextEditorState::eSetValue_Internal);
+ }
+
+ mShouldInitChecked = false;
+}
+
+EventStates
+HTMLInputElement::IntrinsicState() const
+{
+ // If you add states here, and they're type-dependent, you need to add them
+ // to the type case in AfterSetAttr.
+
+ EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState();
+ if (mType == NS_FORM_INPUT_CHECKBOX || mType == NS_FORM_INPUT_RADIO) {
+ // Check current checked state (:checked)
+ if (mChecked) {
+ state |= NS_EVENT_STATE_CHECKED;
+ }
+
+ // Check current indeterminate state (:indeterminate)
+ if (mType == NS_FORM_INPUT_CHECKBOX && mIndeterminate) {
+ state |= NS_EVENT_STATE_INDETERMINATE;
+ }
+
+ if (mType == NS_FORM_INPUT_RADIO) {
+ nsCOMPtr<nsIDOMHTMLInputElement> selected = GetSelectedRadioButton();
+ bool indeterminate = !selected && !mChecked;
+
+ if (indeterminate) {
+ state |= NS_EVENT_STATE_INDETERMINATE;
+ }
+ }
+
+ // Check whether we are the default checked element (:default)
+ if (DefaultChecked()) {
+ state |= NS_EVENT_STATE_DEFAULT;
+ }
+ } else if (mType == NS_FORM_INPUT_IMAGE) {
+ state |= nsImageLoadingContent::ImageState();
+ }
+
+ if (DoesRequiredApply() && HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
+ state |= NS_EVENT_STATE_REQUIRED;
+ } else {
+ state |= NS_EVENT_STATE_OPTIONAL;
+ }
+
+ if (IsCandidateForConstraintValidation()) {
+ if (IsValid()) {
+ state |= NS_EVENT_STATE_VALID;
+ } else {
+ state |= NS_EVENT_STATE_INVALID;
+
+ if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
+ (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
+ (mCanShowInvalidUI && ShouldShowValidityUI()))) {
+ state |= NS_EVENT_STATE_MOZ_UI_INVALID;
+ }
+ }
+
+ // :-moz-ui-valid applies if all of the following conditions are true:
+ // 1. The element is not focused, or had either :-moz-ui-valid or
+ // :-moz-ui-invalid applying before it was focused ;
+ // 2. The element is either valid or isn't allowed to have
+ // :-moz-ui-invalid applying ;
+ // 3. The element has no form owner or its form owner doesn't have the
+ // novalidate attribute set ;
+ // 4. The element has already been modified or the user tried to submit the
+ // form owner while invalid.
+ if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
+ (mCanShowValidUI && ShouldShowValidityUI() &&
+ (IsValid() || (!state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
+ !mCanShowInvalidUI)))) {
+ state |= NS_EVENT_STATE_MOZ_UI_VALID;
+ }
+
+ // :in-range and :out-of-range only apply if the element currently has a range
+ if (mHasRange) {
+ state |= (GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW) ||
+ GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW))
+ ? NS_EVENT_STATE_OUTOFRANGE
+ : NS_EVENT_STATE_INRANGE;
+ }
+ }
+
+ if (PlaceholderApplies() &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder) &&
+ IsValueEmpty()) {
+ state |= NS_EVENT_STATE_PLACEHOLDERSHOWN;
+ }
+
+ if (mForm && !mForm->GetValidity() && IsSubmitControl()) {
+ state |= NS_EVENT_STATE_MOZ_SUBMITINVALID;
+ }
+
+ return state;
+}
+
+void
+HTMLInputElement::AddStates(EventStates aStates)
+{
+ if (mType == NS_FORM_INPUT_TEXT) {
+ EventStates focusStates(aStates & (NS_EVENT_STATE_FOCUS |
+ NS_EVENT_STATE_FOCUSRING));
+ if (!focusStates.IsEmpty()) {
+ HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
+ if (ownerNumberControl) {
+ ownerNumberControl->AddStates(focusStates);
+ } else {
+ HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
+ if (ownerDateTimeControl) {
+ ownerDateTimeControl->AddStates(focusStates);
+ }
+ }
+ }
+ }
+ nsGenericHTMLFormElementWithState::AddStates(aStates);
+}
+
+void
+HTMLInputElement::RemoveStates(EventStates aStates)
+{
+ if (mType == NS_FORM_INPUT_TEXT) {
+ EventStates focusStates(aStates & (NS_EVENT_STATE_FOCUS |
+ NS_EVENT_STATE_FOCUSRING));
+ if (!focusStates.IsEmpty()) {
+ HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
+ if (ownerNumberControl) {
+ ownerNumberControl->RemoveStates(focusStates);
+ } else {
+ HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
+ if (ownerDateTimeControl) {
+ ownerDateTimeControl->RemoveStates(focusStates);
+ }
+ }
+ }
+ }
+ nsGenericHTMLFormElementWithState::RemoveStates(aStates);
+}
+
+bool
+HTMLInputElement::RestoreState(nsPresState* aState)
+{
+ bool restoredCheckedState = false;
+
+ nsCOMPtr<HTMLInputElementState> inputState
+ (do_QueryInterface(aState->GetStateProperty()));
+
+ if (inputState) {
+ switch (GetValueMode()) {
+ case VALUE_MODE_DEFAULT_ON:
+ if (inputState->IsCheckedSet()) {
+ restoredCheckedState = true;
+ DoSetChecked(inputState->GetChecked(), true, true);
+ }
+ break;
+ case VALUE_MODE_FILENAME:
+ {
+ nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
+ if (window) {
+ nsTArray<OwningFileOrDirectory> array;
+ inputState->GetFilesOrDirectories(window, array);
+
+ SetFilesOrDirectories(array, true);
+ }
+ }
+ break;
+ case VALUE_MODE_VALUE:
+ case VALUE_MODE_DEFAULT:
+ if (GetValueMode() == VALUE_MODE_DEFAULT &&
+ mType != NS_FORM_INPUT_HIDDEN) {
+ break;
+ }
+
+ // TODO: What should we do if SetValueInternal fails? (The allocation
+ // may potentially be big, but most likely we've failed to allocate
+ // before the type change.)
+ SetValueInternal(inputState->GetValue(),
+ nsTextEditorState::eSetValue_Notify);
+ break;
+ }
+ }
+
+ if (aState->IsDisabledSet()) {
+ SetDisabled(aState->GetDisabled());
+ }
+
+ return restoredCheckedState;
+}
+
+bool
+HTMLInputElement::AllowDrop()
+{
+ // Allow drop on anything other than file inputs.
+
+ return mType != NS_FORM_INPUT_FILE;
+}
+
+/*
+ * Radio group stuff
+ */
+
+void
+HTMLInputElement::AddedToRadioGroup()
+{
+ // If the element is neither in a form nor a document, there is no group so we
+ // should just stop here.
+ if (!mForm && !IsInUncomposedDoc()) {
+ return;
+ }
+
+ // Make sure not to notify if we're still being created
+ bool notify = mDoneCreating;
+
+ //
+ // If the input element is checked, and we add it to the group, it will
+ // deselect whatever is currently selected in that group
+ //
+ if (mChecked) {
+ //
+ // If it is checked, call "RadioSetChecked" to perform the selection/
+ // deselection ritual. This has the side effect of repainting the
+ // radio button, but as adding a checked radio button into the group
+ // should not be that common an occurrence, I think we can live with
+ // that.
+ //
+ RadioSetChecked(notify);
+ }
+
+ //
+ // For integrity purposes, we have to ensure that "checkedChanged" is
+ // the same for this new element as for all the others in the group
+ //
+ bool checkedChanged = mCheckedChanged;
+
+ nsCOMPtr<nsIRadioVisitor> visitor =
+ new nsRadioGetCheckedChangedVisitor(&checkedChanged, this);
+ VisitGroup(visitor, notify);
+
+ SetCheckedChangedInternal(checkedChanged);
+
+ //
+ // Add the radio to the radio group container.
+ //
+ nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
+ if (container) {
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+ container->AddToRadioGroup(name, static_cast<nsIFormControl*>(this));
+
+ // We initialize the validity of the element to the validity of the group
+ // because we assume UpdateValueMissingState() will be called after.
+ SetValidityState(VALIDITY_STATE_VALUE_MISSING,
+ container->GetValueMissingState(name));
+ }
+}
+
+void
+HTMLInputElement::WillRemoveFromRadioGroup()
+{
+ nsIRadioGroupContainer* container = GetRadioGroupContainer();
+ if (!container) {
+ return;
+ }
+
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+
+ // If this button was checked, we need to notify the group that there is no
+ // longer a selected radio button
+ if (mChecked) {
+ container->SetCurrentRadioButton(name, nullptr);
+
+ nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
+ VisitGroup(visitor, true);
+ }
+
+ // Remove this radio from its group in the container.
+ // We need to call UpdateValueMissingValidityStateForRadio before to make sure
+ // the group validity is updated (with this element being ignored).
+ UpdateValueMissingValidityStateForRadio(true);
+ container->RemoveFromRadioGroup(name, static_cast<nsIFormControl*>(this));
+}
+
+bool
+HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t* aTabIndex)
+{
+ if (nsGenericHTMLFormElementWithState::IsHTMLFocusable(aWithMouse, aIsFocusable,
+ aTabIndex))
+ {
+ return true;
+ }
+
+ if (IsDisabled()) {
+ *aIsFocusable = false;
+ return true;
+ }
+
+ if (IsSingleLineTextControl(false) ||
+ mType == NS_FORM_INPUT_RANGE) {
+ *aIsFocusable = true;
+ return false;
+ }
+
+#ifdef XP_MACOSX
+ const bool defaultFocusable = !aWithMouse || nsFocusManager::sMouseFocusesFormControl;
+#else
+ const bool defaultFocusable = true;
+#endif
+
+ if (mType == NS_FORM_INPUT_FILE ||
+ mType == NS_FORM_INPUT_NUMBER ||
+ mType == NS_FORM_INPUT_TIME) {
+ if (aTabIndex) {
+ // We only want our native anonymous child to be tabable to, not ourself.
+ *aTabIndex = -1;
+ }
+ if (mType == NS_FORM_INPUT_NUMBER ||
+ mType == NS_FORM_INPUT_TIME) {
+ *aIsFocusable = true;
+ } else {
+ *aIsFocusable = defaultFocusable;
+ }
+ return true;
+ }
+
+ if (mType == NS_FORM_INPUT_HIDDEN) {
+ if (aTabIndex) {
+ *aTabIndex = -1;
+ }
+ *aIsFocusable = false;
+ return false;
+ }
+
+ if (!aTabIndex) {
+ // The other controls are all focusable
+ *aIsFocusable = defaultFocusable;
+ return false;
+ }
+
+ if (mType != NS_FORM_INPUT_RADIO) {
+ *aIsFocusable = defaultFocusable;
+ return false;
+ }
+
+ if (mChecked) {
+ // Selected radio buttons are tabbable
+ *aIsFocusable = defaultFocusable;
+ return false;
+ }
+
+ // Current radio button is not selected.
+ // But make it tabbable if nothing in group is selected.
+ nsIRadioGroupContainer* container = GetRadioGroupContainer();
+ if (!container) {
+ *aIsFocusable = defaultFocusable;
+ return false;
+ }
+
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+
+ if (container->GetCurrentRadioButton(name)) {
+ *aTabIndex = -1;
+ }
+ *aIsFocusable = defaultFocusable;
+ return false;
+}
+
+nsresult
+HTMLInputElement::VisitGroup(nsIRadioVisitor* aVisitor, bool aFlushContent)
+{
+ nsIRadioGroupContainer* container = GetRadioGroupContainer();
+ if (container) {
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+ return container->WalkRadioGroup(name, aVisitor, aFlushContent);
+ }
+
+ aVisitor->Visit(this);
+ return NS_OK;
+}
+
+HTMLInputElement::ValueModeType
+HTMLInputElement::GetValueMode() const
+{
+ switch (mType)
+ {
+ case NS_FORM_INPUT_HIDDEN:
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_BUTTON:
+ case NS_FORM_INPUT_RESET:
+ case NS_FORM_INPUT_IMAGE:
+ return VALUE_MODE_DEFAULT;
+ case NS_FORM_INPUT_CHECKBOX:
+ case NS_FORM_INPUT_RADIO:
+ return VALUE_MODE_DEFAULT_ON;
+ case NS_FORM_INPUT_FILE:
+ return VALUE_MODE_FILENAME;
+#ifdef DEBUG
+ case NS_FORM_INPUT_TEXT:
+ case NS_FORM_INPUT_PASSWORD:
+ case NS_FORM_INPUT_SEARCH:
+ case NS_FORM_INPUT_TEL:
+ case NS_FORM_INPUT_EMAIL:
+ case NS_FORM_INPUT_URL:
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_RANGE:
+ case NS_FORM_INPUT_DATE:
+ case NS_FORM_INPUT_TIME:
+ case NS_FORM_INPUT_COLOR:
+ case NS_FORM_INPUT_MONTH:
+ case NS_FORM_INPUT_WEEK:
+ case NS_FORM_INPUT_DATETIME_LOCAL:
+ return VALUE_MODE_VALUE;
+ default:
+ NS_NOTYETIMPLEMENTED("Unexpected input type in GetValueMode()");
+ return VALUE_MODE_VALUE;
+#else // DEBUG
+ default:
+ return VALUE_MODE_VALUE;
+#endif // DEBUG
+ }
+}
+
+bool
+HTMLInputElement::IsMutable() const
+{
+ return !IsDisabled() &&
+ !(DoesReadOnlyApply() &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::readonly));
+}
+
+bool
+HTMLInputElement::DoesReadOnlyApply() const
+{
+ switch (mType)
+ {
+ case NS_FORM_INPUT_HIDDEN:
+ case NS_FORM_INPUT_BUTTON:
+ case NS_FORM_INPUT_IMAGE:
+ case NS_FORM_INPUT_RESET:
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_RADIO:
+ case NS_FORM_INPUT_FILE:
+ case NS_FORM_INPUT_CHECKBOX:
+ case NS_FORM_INPUT_RANGE:
+ case NS_FORM_INPUT_COLOR:
+ return false;
+#ifdef DEBUG
+ case NS_FORM_INPUT_TEXT:
+ case NS_FORM_INPUT_PASSWORD:
+ case NS_FORM_INPUT_SEARCH:
+ case NS_FORM_INPUT_TEL:
+ case NS_FORM_INPUT_EMAIL:
+ case NS_FORM_INPUT_URL:
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_DATE:
+ case NS_FORM_INPUT_TIME:
+ case NS_FORM_INPUT_MONTH:
+ case NS_FORM_INPUT_WEEK:
+ case NS_FORM_INPUT_DATETIME_LOCAL:
+ return true;
+ default:
+ NS_NOTYETIMPLEMENTED("Unexpected input type in DoesReadOnlyApply()");
+ return true;
+#else // DEBUG
+ default:
+ return true;
+#endif // DEBUG
+ }
+}
+
+bool
+HTMLInputElement::DoesRequiredApply() const
+{
+ switch (mType)
+ {
+ case NS_FORM_INPUT_HIDDEN:
+ case NS_FORM_INPUT_BUTTON:
+ case NS_FORM_INPUT_IMAGE:
+ case NS_FORM_INPUT_RESET:
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_RANGE:
+ case NS_FORM_INPUT_COLOR:
+ return false;
+#ifdef DEBUG
+ case NS_FORM_INPUT_RADIO:
+ case NS_FORM_INPUT_CHECKBOX:
+ case NS_FORM_INPUT_FILE:
+ case NS_FORM_INPUT_TEXT:
+ case NS_FORM_INPUT_PASSWORD:
+ case NS_FORM_INPUT_SEARCH:
+ case NS_FORM_INPUT_TEL:
+ case NS_FORM_INPUT_EMAIL:
+ case NS_FORM_INPUT_URL:
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_DATE:
+ case NS_FORM_INPUT_TIME:
+ case NS_FORM_INPUT_MONTH:
+ case NS_FORM_INPUT_WEEK:
+ case NS_FORM_INPUT_DATETIME_LOCAL:
+ return true;
+ default:
+ NS_NOTYETIMPLEMENTED("Unexpected input type in DoesRequiredApply()");
+ return true;
+#else // DEBUG
+ default:
+ return true;
+#endif // DEBUG
+ }
+}
+
+bool
+HTMLInputElement::PlaceholderApplies() const
+{
+ if (IsDateTimeInputType(mType)) {
+ return false;
+ }
+
+ return IsSingleLineTextControl(false);
+}
+
+bool
+HTMLInputElement::DoesPatternApply() const
+{
+ // TODO: temporary until bug 773205 is fixed.
+ if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
+ return false;
+ }
+
+ return IsSingleLineTextControl(false);
+}
+
+bool
+HTMLInputElement::DoesMinMaxApply() const
+{
+ switch (mType)
+ {
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_DATE:
+ case NS_FORM_INPUT_TIME:
+ case NS_FORM_INPUT_RANGE:
+ case NS_FORM_INPUT_MONTH:
+ case NS_FORM_INPUT_WEEK:
+ case NS_FORM_INPUT_DATETIME_LOCAL:
+ return true;
+#ifdef DEBUG
+ case NS_FORM_INPUT_RESET:
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_IMAGE:
+ case NS_FORM_INPUT_BUTTON:
+ case NS_FORM_INPUT_HIDDEN:
+ case NS_FORM_INPUT_RADIO:
+ case NS_FORM_INPUT_CHECKBOX:
+ case NS_FORM_INPUT_FILE:
+ case NS_FORM_INPUT_TEXT:
+ case NS_FORM_INPUT_PASSWORD:
+ case NS_FORM_INPUT_SEARCH:
+ case NS_FORM_INPUT_TEL:
+ case NS_FORM_INPUT_EMAIL:
+ case NS_FORM_INPUT_URL:
+ case NS_FORM_INPUT_COLOR:
+ return false;
+ default:
+ NS_NOTYETIMPLEMENTED("Unexpected input type in DoesRequiredApply()");
+ return false;
+#else // DEBUG
+ default:
+ return false;
+#endif // DEBUG
+ }
+}
+
+bool
+HTMLInputElement::DoesAutocompleteApply() const
+{
+ switch (mType)
+ {
+ case NS_FORM_INPUT_HIDDEN:
+ case NS_FORM_INPUT_TEXT:
+ case NS_FORM_INPUT_SEARCH:
+ case NS_FORM_INPUT_URL:
+ case NS_FORM_INPUT_TEL:
+ case NS_FORM_INPUT_EMAIL:
+ case NS_FORM_INPUT_PASSWORD:
+ case NS_FORM_INPUT_DATE:
+ case NS_FORM_INPUT_TIME:
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_RANGE:
+ case NS_FORM_INPUT_COLOR:
+ case NS_FORM_INPUT_MONTH:
+ case NS_FORM_INPUT_WEEK:
+ case NS_FORM_INPUT_DATETIME_LOCAL:
+ return true;
+#ifdef DEBUG
+ case NS_FORM_INPUT_RESET:
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_IMAGE:
+ case NS_FORM_INPUT_BUTTON:
+ case NS_FORM_INPUT_RADIO:
+ case NS_FORM_INPUT_CHECKBOX:
+ case NS_FORM_INPUT_FILE:
+ return false;
+ default:
+ NS_NOTYETIMPLEMENTED("Unexpected input type in DoesAutocompleteApply()");
+ return false;
+#else // DEBUG
+ default:
+ return false;
+#endif // DEBUG
+ }
+}
+
+Decimal
+HTMLInputElement::GetStep() const
+{
+ MOZ_ASSERT(DoesStepApply(), "GetStep() can only be called if @step applies");
+
+ if (!HasAttr(kNameSpaceID_None, nsGkAtoms::step)) {
+ return GetDefaultStep() * GetStepScaleFactor();
+ }
+
+ nsAutoString stepStr;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::step, stepStr);
+
+ if (stepStr.LowerCaseEqualsLiteral("any")) {
+ // The element can't suffer from step mismatch if there is no step.
+ return kStepAny;
+ }
+
+ Decimal step = StringToDecimal(stepStr);
+ if (!step.isFinite() || step <= Decimal(0)) {
+ step = GetDefaultStep();
+ }
+
+ // For input type=date, we round the step value to have a rounded day.
+ if (mType == NS_FORM_INPUT_DATE || mType == NS_FORM_INPUT_MONTH ||
+ mType == NS_FORM_INPUT_WEEK) {
+ step = std::max(step.round(), Decimal(1));
+ }
+
+ return step * GetStepScaleFactor();
+}
+
+// nsIConstraintValidation
+
+NS_IMETHODIMP
+HTMLInputElement::SetCustomValidity(const nsAString& aError)
+{
+ nsIConstraintValidation::SetCustomValidity(aError);
+
+ UpdateState(true);
+
+ return NS_OK;
+}
+
+bool
+HTMLInputElement::IsTooLong()
+{
+ if (!mValueChanged ||
+ !mLastValueChangeWasInteractive ||
+ !MinOrMaxLengthApplies() ||
+ !HasAttr(kNameSpaceID_None, nsGkAtoms::maxlength)) {
+ return false;
+ }
+
+ int32_t maxLength = MaxLength();
+
+ // Maxlength of -1 means parsing error.
+ if (maxLength == -1) {
+ return false;
+ }
+
+ int32_t textLength = -1;
+ GetTextLength(&textLength);
+
+ return textLength > maxLength;
+}
+
+bool
+HTMLInputElement::IsTooShort()
+{
+ if (!mValueChanged ||
+ !mLastValueChangeWasInteractive ||
+ !MinOrMaxLengthApplies() ||
+ !HasAttr(kNameSpaceID_None, nsGkAtoms::minlength)) {
+ return false;
+ }
+
+ int32_t minLength = MinLength();
+
+ // Minlength of -1 means parsing error.
+ if (minLength == -1) {
+ return false;
+ }
+
+ int32_t textLength = -1;
+ GetTextLength(&textLength);
+
+ return textLength && textLength < minLength;
+}
+
+bool
+HTMLInputElement::IsValueMissing() const
+{
+ // Should use UpdateValueMissingValidityStateForRadio() for type radio.
+ MOZ_ASSERT(mType != NS_FORM_INPUT_RADIO);
+
+ if (!HasAttr(kNameSpaceID_None, nsGkAtoms::required) ||
+ !DoesRequiredApply()) {
+ return false;
+ }
+
+ if (!IsMutable()) {
+ return false;
+ }
+
+ switch (GetValueMode()) {
+ case VALUE_MODE_VALUE:
+ return IsValueEmpty();
+
+ case VALUE_MODE_FILENAME:
+ return GetFilesOrDirectoriesInternal().IsEmpty();
+
+ case VALUE_MODE_DEFAULT_ON:
+ // This should not be used for type radio.
+ // See the MOZ_ASSERT at the beginning of the method.
+ return !mChecked;
+
+ case VALUE_MODE_DEFAULT:
+ default:
+ return false;
+ }
+}
+
+bool
+HTMLInputElement::HasTypeMismatch() const
+{
+ if (mType != NS_FORM_INPUT_EMAIL && mType != NS_FORM_INPUT_URL) {
+ return false;
+ }
+
+ nsAutoString value;
+ NS_ENSURE_SUCCESS(GetValueInternal(value), false);
+
+ if (value.IsEmpty()) {
+ return false;
+ }
+
+ if (mType == NS_FORM_INPUT_EMAIL) {
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)
+ ? !IsValidEmailAddressList(value) : !IsValidEmailAddress(value);
+ } else if (mType == NS_FORM_INPUT_URL) {
+ /**
+ * TODO:
+ * The URL is not checked as the HTML5 specifications want it to be because
+ * there is no code to check for a valid URI/IRI according to 3986 and 3987
+ * RFC's at the moment, see bug 561586.
+ *
+ * RFC 3987 (IRI) implementation: bug 42899
+ *
+ * HTML5 specifications:
+ * http://dev.w3.org/html5/spec/infrastructure.html#valid-url
+ */
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService();
+ nsCOMPtr<nsIURI> uri;
+
+ return !NS_SUCCEEDED(ioService->NewURI(NS_ConvertUTF16toUTF8(value), nullptr,
+ nullptr, getter_AddRefs(uri)));
+ }
+
+ return false;
+}
+
+bool
+HTMLInputElement::HasPatternMismatch() const
+{
+ if (!DoesPatternApply() ||
+ !HasAttr(kNameSpaceID_None, nsGkAtoms::pattern)) {
+ return false;
+ }
+
+ nsAutoString pattern;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::pattern, pattern);
+
+ nsAutoString value;
+ NS_ENSURE_SUCCESS(GetValueInternal(value), false);
+
+ if (value.IsEmpty()) {
+ return false;
+ }
+
+ nsIDocument* doc = OwnerDoc();
+
+ return !nsContentUtils::IsPatternMatching(value, pattern, doc);
+}
+
+bool
+HTMLInputElement::IsRangeOverflow() const
+{
+ // TODO: this is temporary until bug 888331 is fixed.
+ if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
+ return false;
+ }
+
+ Decimal maximum = GetMaximum();
+ if (maximum.isNaN()) {
+ return false;
+ }
+
+ Decimal value = GetValueAsDecimal();
+ if (value.isNaN()) {
+ return false;
+ }
+
+ return value > maximum;
+}
+
+bool
+HTMLInputElement::IsRangeUnderflow() const
+{
+ // TODO: this is temporary until bug 888331 is fixed.
+ if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
+ return false;
+ }
+
+ Decimal minimum = GetMinimum();
+ if (minimum.isNaN()) {
+ return false;
+ }
+
+ Decimal value = GetValueAsDecimal();
+ if (value.isNaN()) {
+ return false;
+ }
+
+ return value < minimum;
+}
+
+bool
+HTMLInputElement::HasStepMismatch(bool aUseZeroIfValueNaN) const
+{
+ if (!DoesStepApply()) {
+ return false;
+ }
+
+ Decimal value = GetValueAsDecimal();
+ if (value.isNaN()) {
+ if (aUseZeroIfValueNaN) {
+ value = Decimal(0);
+ } else {
+ // The element can't suffer from step mismatch if it's value isn't a number.
+ return false;
+ }
+ }
+
+ Decimal step = GetStep();
+ if (step == kStepAny) {
+ return false;
+ }
+
+ // Value has to be an integral multiple of step.
+ return NS_floorModulo(value - GetStepBase(), step) != Decimal(0);
+}
+
+/**
+ * Takes aEmail and attempts to convert everything after the first "@"
+ * character (if anything) to punycode before returning the complete result via
+ * the aEncodedEmail out-param. The aIndexOfAt out-param is set to the index of
+ * the "@" character.
+ *
+ * If no "@" is found in aEmail, aEncodedEmail is simply set to aEmail and
+ * the aIndexOfAt out-param is set to kNotFound.
+ *
+ * Returns true in all cases unless an attempt to punycode encode fails. If
+ * false is returned, aEncodedEmail has not been set.
+ *
+ * This function exists because ConvertUTF8toACE() splits on ".", meaning that
+ * for 'user.name@sld.tld' it would treat "name@sld" as a label. We want to
+ * encode the domain part only.
+ */
+static bool PunycodeEncodeEmailAddress(const nsAString& aEmail,
+ nsAutoCString& aEncodedEmail,
+ uint32_t* aIndexOfAt)
+{
+ nsAutoCString value = NS_ConvertUTF16toUTF8(aEmail);
+ *aIndexOfAt = (uint32_t)value.FindChar('@');
+
+ if (*aIndexOfAt == (uint32_t)kNotFound ||
+ *aIndexOfAt == value.Length() - 1) {
+ aEncodedEmail = value;
+ return true;
+ }
+
+ nsCOMPtr<nsIIDNService> idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID);
+ if (!idnSrv) {
+ NS_ERROR("nsIIDNService isn't present!");
+ return false;
+ }
+
+ uint32_t indexOfDomain = *aIndexOfAt + 1;
+
+ const nsDependentCSubstring domain = Substring(value, indexOfDomain);
+ bool ace;
+ if (NS_SUCCEEDED(idnSrv->IsACE(domain, &ace)) && !ace) {
+ nsAutoCString domainACE;
+ if (NS_FAILED(idnSrv->ConvertUTF8toACE(domain, domainACE))) {
+ return false;
+ }
+ value.Replace(indexOfDomain, domain.Length(), domainACE);
+ }
+
+ aEncodedEmail = value;
+ return true;
+}
+
+bool
+HTMLInputElement::HasBadInput() const
+{
+ if (mType == NS_FORM_INPUT_NUMBER) {
+ nsAutoString value;
+ GetValueInternal(value);
+ if (!value.IsEmpty()) {
+ // The input can't be bad, otherwise it would have been sanitized to the
+ // empty string.
+ NS_ASSERTION(!GetValueAsDecimal().isNaN(), "Should have sanitized");
+ return false;
+ }
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame &&
+ !numberControlFrame->AnonTextControlIsEmpty()) {
+ // The input the user entered failed to parse as a number.
+ return true;
+ }
+ return false;
+ }
+ if (mType == NS_FORM_INPUT_EMAIL) {
+ // With regards to suffering from bad input the spec says that only the
+ // punycode conversion works, so we don't care whether the email address is
+ // valid or not here. (If the email address is invalid then we will be
+ // suffering from a type mismatch.)
+ nsAutoString value;
+ nsAutoCString unused;
+ uint32_t unused2;
+ NS_ENSURE_SUCCESS(GetValueInternal(value), false);
+ HTMLSplitOnSpacesTokenizer tokenizer(value, ',');
+ while (tokenizer.hasMoreTokens()) {
+ if (!PunycodeEncodeEmailAddress(tokenizer.nextToken(), unused, &unused2)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return false;
+}
+
+void
+HTMLInputElement::UpdateTooLongValidityState()
+{
+ SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong());
+}
+
+void
+HTMLInputElement::UpdateTooShortValidityState()
+{
+ SetValidityState(VALIDITY_STATE_TOO_SHORT, IsTooShort());
+}
+
+void
+HTMLInputElement::UpdateValueMissingValidityStateForRadio(bool aIgnoreSelf)
+{
+ bool notify = mDoneCreating;
+ nsCOMPtr<nsIDOMHTMLInputElement> selection = GetSelectedRadioButton();
+
+ aIgnoreSelf = aIgnoreSelf || !IsMutable();
+
+ // If there is no selection, that might mean the radio is not in a group.
+ // In that case, we can look for the checked state of the radio.
+ bool selected = selection || (!aIgnoreSelf && mChecked);
+ bool required = !aIgnoreSelf && HasAttr(kNameSpaceID_None, nsGkAtoms::required);
+ bool valueMissing = false;
+
+ nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
+
+ if (!container) {
+ SetValidityState(VALIDITY_STATE_VALUE_MISSING,
+ IsMutable() && required && !selected);
+ return;
+ }
+
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+
+ // If the current radio is required and not ignored, we can assume the entire
+ // group is required.
+ if (!required) {
+ required = (aIgnoreSelf && HasAttr(kNameSpaceID_None, nsGkAtoms::required))
+ ? container->GetRequiredRadioCount(name) - 1
+ : container->GetRequiredRadioCount(name);
+ }
+
+ valueMissing = required && !selected;
+
+ if (container->GetValueMissingState(name) != valueMissing) {
+ container->SetValueMissingState(name, valueMissing);
+
+ SetValidityState(VALIDITY_STATE_VALUE_MISSING, valueMissing);
+
+ // nsRadioSetValueMissingState will call ContentStateChanged while visiting.
+ nsAutoScriptBlocker scriptBlocker;
+ nsCOMPtr<nsIRadioVisitor> visitor =
+ new nsRadioSetValueMissingState(this, valueMissing, notify);
+ VisitGroup(visitor, notify);
+ }
+}
+
+void
+HTMLInputElement::UpdateValueMissingValidityState()
+{
+ if (mType == NS_FORM_INPUT_RADIO) {
+ UpdateValueMissingValidityStateForRadio(false);
+ return;
+ }
+
+ SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
+}
+
+void
+HTMLInputElement::UpdateTypeMismatchValidityState()
+{
+ SetValidityState(VALIDITY_STATE_TYPE_MISMATCH, HasTypeMismatch());
+}
+
+void
+HTMLInputElement::UpdatePatternMismatchValidityState()
+{
+ SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH, HasPatternMismatch());
+}
+
+void
+HTMLInputElement::UpdateRangeOverflowValidityState()
+{
+ SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW, IsRangeOverflow());
+}
+
+void
+HTMLInputElement::UpdateRangeUnderflowValidityState()
+{
+ SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW, IsRangeUnderflow());
+}
+
+void
+HTMLInputElement::UpdateStepMismatchValidityState()
+{
+ SetValidityState(VALIDITY_STATE_STEP_MISMATCH, HasStepMismatch());
+}
+
+void
+HTMLInputElement::UpdateBadInputValidityState()
+{
+ SetValidityState(VALIDITY_STATE_BAD_INPUT, HasBadInput());
+}
+
+void
+HTMLInputElement::UpdateAllValidityStates(bool aNotify)
+{
+ bool validBefore = IsValid();
+ UpdateTooLongValidityState();
+ UpdateTooShortValidityState();
+ UpdateValueMissingValidityState();
+ UpdateTypeMismatchValidityState();
+ UpdatePatternMismatchValidityState();
+ UpdateRangeOverflowValidityState();
+ UpdateRangeUnderflowValidityState();
+ UpdateStepMismatchValidityState();
+ UpdateBadInputValidityState();
+
+ if (validBefore != IsValid()) {
+ UpdateState(aNotify);
+ }
+}
+
+void
+HTMLInputElement::UpdateBarredFromConstraintValidation()
+{
+ SetBarredFromConstraintValidation(mType == NS_FORM_INPUT_HIDDEN ||
+ mType == NS_FORM_INPUT_BUTTON ||
+ mType == NS_FORM_INPUT_RESET ||
+ HasAttr(kNameSpaceID_None, nsGkAtoms::readonly) ||
+ IsDisabled());
+}
+
+void
+HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
+ ErrorResult& aRv)
+{
+ aRv = GetValidationMessage(aValidationMessage);
+}
+
+nsresult
+HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
+ ValidityStateType aType)
+{
+ nsresult rv = NS_OK;
+
+ switch (aType)
+ {
+ case VALIDITY_STATE_TOO_LONG:
+ {
+ nsXPIDLString message;
+ int32_t maxLength = MaxLength();
+ int32_t textLength = -1;
+ nsAutoString strMaxLength;
+ nsAutoString strTextLength;
+
+ GetTextLength(&textLength);
+
+ strMaxLength.AppendInt(maxLength);
+ strTextLength.AppendInt(textLength);
+
+ const char16_t* params[] = { strMaxLength.get(), strTextLength.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationTextTooLong",
+ params, message);
+ aValidationMessage = message;
+ break;
+ }
+ case VALIDITY_STATE_TOO_SHORT:
+ {
+ nsXPIDLString message;
+ int32_t minLength = MinLength();
+ int32_t textLength = -1;
+ nsAutoString strMinLength;
+ nsAutoString strTextLength;
+
+ GetTextLength(&textLength);
+
+ strMinLength.AppendInt(minLength);
+ strTextLength.AppendInt(textLength);
+
+ const char16_t* params[] = { strMinLength.get(), strTextLength.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationTextTooShort",
+ params, message);
+ aValidationMessage = message;
+ break;
+ }
+ case VALIDITY_STATE_VALUE_MISSING:
+ {
+ nsXPIDLString message;
+ nsAutoCString key;
+ switch (mType)
+ {
+ case NS_FORM_INPUT_FILE:
+ key.AssignLiteral("FormValidationFileMissing");
+ break;
+ case NS_FORM_INPUT_CHECKBOX:
+ key.AssignLiteral("FormValidationCheckboxMissing");
+ break;
+ case NS_FORM_INPUT_RADIO:
+ key.AssignLiteral("FormValidationRadioMissing");
+ break;
+ case NS_FORM_INPUT_NUMBER:
+ key.AssignLiteral("FormValidationBadInputNumber");
+ break;
+ default:
+ key.AssignLiteral("FormValidationValueMissing");
+ }
+ rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ key.get(), message);
+ aValidationMessage = message;
+ break;
+ }
+ case VALIDITY_STATE_TYPE_MISMATCH:
+ {
+ nsXPIDLString message;
+ nsAutoCString key;
+ if (mType == NS_FORM_INPUT_EMAIL) {
+ key.AssignLiteral("FormValidationInvalidEmail");
+ } else if (mType == NS_FORM_INPUT_URL) {
+ key.AssignLiteral("FormValidationInvalidURL");
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+ rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ key.get(), message);
+ aValidationMessage = message;
+ break;
+ }
+ case VALIDITY_STATE_PATTERN_MISMATCH:
+ {
+ nsXPIDLString message;
+ nsAutoString title;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::title, title);
+ if (title.IsEmpty()) {
+ rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationPatternMismatch",
+ message);
+ } else {
+ if (title.Length() > nsIConstraintValidation::sContentSpecifiedMaxLengthMessage) {
+ title.Truncate(nsIConstraintValidation::sContentSpecifiedMaxLengthMessage);
+ }
+ const char16_t* params[] = { title.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationPatternMismatchWithTitle",
+ params, message);
+ }
+ aValidationMessage = message;
+ break;
+ }
+ case VALIDITY_STATE_RANGE_OVERFLOW:
+ {
+ static const char kNumberOverTemplate[] = "FormValidationNumberRangeOverflow";
+ static const char kDateOverTemplate[] = "FormValidationDateRangeOverflow";
+ static const char kTimeOverTemplate[] = "FormValidationTimeRangeOverflow";
+
+ const char* msgTemplate;
+ nsXPIDLString message;
+
+ nsAutoString maxStr;
+ if (mType == NS_FORM_INPUT_NUMBER ||
+ mType == NS_FORM_INPUT_RANGE) {
+ msgTemplate = kNumberOverTemplate;
+
+ //We want to show the value as parsed when it's a number
+ Decimal maximum = GetMaximum();
+ MOZ_ASSERT(!maximum.isNaN());
+
+ char buf[32];
+ DebugOnly<bool> ok = maximum.toString(buf, ArrayLength(buf));
+ maxStr.AssignASCII(buf);
+ MOZ_ASSERT(ok, "buf not big enough");
+ } else if (IsDateTimeInputType(mType)) {
+ msgTemplate = mType == NS_FORM_INPUT_TIME ? kTimeOverTemplate : kDateOverTemplate;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
+ } else {
+ msgTemplate = kNumberOverTemplate;
+ NS_NOTREACHED("Unexpected input type");
+ }
+
+ const char16_t* params[] = { maxStr.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ msgTemplate,
+ params, message);
+ aValidationMessage = message;
+ break;
+ }
+ case VALIDITY_STATE_RANGE_UNDERFLOW:
+ {
+ static const char kNumberUnderTemplate[] = "FormValidationNumberRangeUnderflow";
+ static const char kDateUnderTemplate[] = "FormValidationDateRangeUnderflow";
+ static const char kTimeUnderTemplate[] = "FormValidationTimeRangeUnderflow";
+
+ const char* msgTemplate;
+ nsXPIDLString message;
+
+ nsAutoString minStr;
+ if (mType == NS_FORM_INPUT_NUMBER ||
+ mType == NS_FORM_INPUT_RANGE) {
+ msgTemplate = kNumberUnderTemplate;
+
+ Decimal minimum = GetMinimum();
+ MOZ_ASSERT(!minimum.isNaN());
+
+ char buf[32];
+ DebugOnly<bool> ok = minimum.toString(buf, ArrayLength(buf));
+ minStr.AssignASCII(buf);
+ MOZ_ASSERT(ok, "buf not big enough");
+ } else if (IsDateTimeInputType(mType)) {
+ msgTemplate = mType == NS_FORM_INPUT_TIME ? kTimeUnderTemplate : kDateUnderTemplate;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
+ } else {
+ msgTemplate = kNumberUnderTemplate;
+ NS_NOTREACHED("Unexpected input type");
+ }
+
+ const char16_t* params[] = { minStr.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ msgTemplate,
+ params, message);
+ aValidationMessage = message;
+ break;
+ }
+ case VALIDITY_STATE_STEP_MISMATCH:
+ {
+ nsXPIDLString message;
+
+ Decimal value = GetValueAsDecimal();
+ MOZ_ASSERT(!value.isNaN());
+
+ Decimal step = GetStep();
+ MOZ_ASSERT(step != kStepAny && step > Decimal(0));
+
+ Decimal stepBase = GetStepBase();
+
+ Decimal valueLow = value - NS_floorModulo(value - stepBase, step);
+ Decimal valueHigh = value + step - NS_floorModulo(value - stepBase, step);
+
+ Decimal maximum = GetMaximum();
+
+ if (maximum.isNaN() || valueHigh <= maximum) {
+ nsAutoString valueLowStr, valueHighStr;
+ ConvertNumberToString(valueLow, valueLowStr);
+ ConvertNumberToString(valueHigh, valueHighStr);
+
+ if (valueLowStr.Equals(valueHighStr)) {
+ const char16_t* params[] = { valueLowStr.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationStepMismatchOneValue",
+ params, message);
+ } else {
+ const char16_t* params[] = { valueLowStr.get(), valueHighStr.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationStepMismatch",
+ params, message);
+ }
+ } else {
+ nsAutoString valueLowStr;
+ ConvertNumberToString(valueLow, valueLowStr);
+
+ const char16_t* params[] = { valueLowStr.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationStepMismatchOneValue",
+ params, message);
+ }
+
+ aValidationMessage = message;
+ break;
+ }
+ case VALIDITY_STATE_BAD_INPUT:
+ {
+ nsXPIDLString message;
+ nsAutoCString key;
+ if (mType == NS_FORM_INPUT_NUMBER) {
+ key.AssignLiteral("FormValidationBadInputNumber");
+ } else if (mType == NS_FORM_INPUT_EMAIL) {
+ key.AssignLiteral("FormValidationInvalidEmail");
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+ rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ key.get(), message);
+ aValidationMessage = message;
+ break;
+ }
+ default:
+ rv = nsIConstraintValidation::GetValidationMessage(aValidationMessage, aType);
+ }
+
+ return rv;
+}
+
+//static
+bool
+HTMLInputElement::IsValidEmailAddressList(const nsAString& aValue)
+{
+ HTMLSplitOnSpacesTokenizer tokenizer(aValue, ',');
+
+ while (tokenizer.hasMoreTokens()) {
+ if (!IsValidEmailAddress(tokenizer.nextToken())) {
+ return false;
+ }
+ }
+
+ return !tokenizer.separatorAfterCurrentToken();
+}
+
+//static
+bool
+HTMLInputElement::IsValidEmailAddress(const nsAString& aValue)
+{
+ // Email addresses can't be empty and can't end with a '.' or '-'.
+ if (aValue.IsEmpty() || aValue.Last() == '.' || aValue.Last() == '-') {
+ return false;
+ }
+
+ uint32_t atPos;
+ nsAutoCString value;
+ if (!PunycodeEncodeEmailAddress(aValue, value, &atPos) ||
+ atPos == (uint32_t)kNotFound || atPos == 0 || atPos == value.Length() - 1) {
+ // Could not encode, or "@" was not found, or it was at the start or end
+ // of the input - in all cases, not a valid email address.
+ return false;
+ }
+
+ uint32_t length = value.Length();
+ uint32_t i = 0;
+
+ // Parsing the username.
+ for (; i < atPos; ++i) {
+ char16_t c = value[i];
+
+ // The username characters have to be in this list to be valid.
+ if (!(nsCRT::IsAsciiAlpha(c) || nsCRT::IsAsciiDigit(c) ||
+ c == '.' || c == '!' || c == '#' || c == '$' || c == '%' ||
+ c == '&' || c == '\''|| c == '*' || c == '+' || c == '-' ||
+ c == '/' || c == '=' || c == '?' || c == '^' || c == '_' ||
+ c == '`' || c == '{' || c == '|' || c == '}' || c == '~' )) {
+ return false;
+ }
+ }
+
+ // Skip the '@'.
+ ++i;
+
+ // The domain name can't begin with a dot or a dash.
+ if (value[i] == '.' || value[i] == '-') {
+ return false;
+ }
+
+ // Parsing the domain name.
+ for (; i < length; ++i) {
+ char16_t c = value[i];
+
+ if (c == '.') {
+ // A dot can't follow a dot or a dash.
+ if (value[i-1] == '.' || value[i-1] == '-') {
+ return false;
+ }
+ } else if (c == '-'){
+ // A dash can't follow a dot.
+ if (value[i-1] == '.') {
+ return false;
+ }
+ } else if (!(nsCRT::IsAsciiAlpha(c) || nsCRT::IsAsciiDigit(c) ||
+ c == '-')) {
+ // The domain characters have to be in this list to be valid.
+ return false;
+ }
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP_(bool)
+HTMLInputElement::IsSingleLineTextControl() const
+{
+ return IsSingleLineTextControl(false);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLInputElement::IsTextArea() const
+{
+ return false;
+}
+
+NS_IMETHODIMP_(bool)
+HTMLInputElement::IsPlainTextControl() const
+{
+ // need to check our HTML attribute and/or CSS.
+ return true;
+}
+
+NS_IMETHODIMP_(bool)
+HTMLInputElement::IsPasswordTextControl() const
+{
+ return mType == NS_FORM_INPUT_PASSWORD;
+}
+
+NS_IMETHODIMP_(int32_t)
+HTMLInputElement::GetCols()
+{
+ // Else we know (assume) it is an input with size attr
+ const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::size);
+ if (attr && attr->Type() == nsAttrValue::eInteger) {
+ int32_t cols = attr->GetIntegerValue();
+ if (cols > 0) {
+ return cols;
+ }
+ }
+
+ return DEFAULT_COLS;
+}
+
+NS_IMETHODIMP_(int32_t)
+HTMLInputElement::GetWrapCols()
+{
+ return 0; // only textarea's can have wrap cols
+}
+
+NS_IMETHODIMP_(int32_t)
+HTMLInputElement::GetRows()
+{
+ return DEFAULT_ROWS;
+}
+
+NS_IMETHODIMP_(void)
+HTMLInputElement::GetDefaultValueFromContent(nsAString& aValue)
+{
+ nsTextEditorState *state = GetEditorState();
+ if (state) {
+ GetDefaultValue(aValue);
+ // This is called by the frame to show the value.
+ // We have to sanitize it when needed.
+ if (mDoneCreating) {
+ SanitizeValue(aValue);
+ }
+ }
+}
+
+NS_IMETHODIMP_(bool)
+HTMLInputElement::ValueChanged() const
+{
+ return mValueChanged;
+}
+
+NS_IMETHODIMP_(void)
+HTMLInputElement::GetTextEditorValue(nsAString& aValue,
+ bool aIgnoreWrap) const
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ state->GetValue(aValue, aIgnoreWrap);
+ }
+}
+
+NS_IMETHODIMP_(void)
+HTMLInputElement::InitializeKeyboardEventListeners()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ state->InitializeKeyboardEventListeners();
+ }
+}
+
+NS_IMETHODIMP_(void)
+HTMLInputElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange)
+{
+ mLastValueChangeWasInteractive = aWasInteractiveUserChange;
+
+ UpdateAllValidityStates(aNotify);
+
+ if (HasDirAuto()) {
+ SetDirectionIfAuto(true, aNotify);
+ }
+
+ // :placeholder-shown pseudo-class may change when the value changes.
+ // However, we don't want to waste cycles if the state doesn't apply.
+ if (PlaceholderApplies() &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
+ UpdateState(aNotify);
+ }
+}
+
+NS_IMETHODIMP_(bool)
+HTMLInputElement::HasCachedSelection()
+{
+ bool isCached = false;
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ isCached = state->IsSelectionCached() &&
+ state->HasNeverInitializedBefore() &&
+ !state->GetSelectionProperties().IsDefault();
+ if (isCached) {
+ state->WillInitEagerly();
+ }
+ }
+ return isCached;
+}
+
+void
+HTMLInputElement::FieldSetDisabledChanged(bool aNotify)
+{
+ UpdateValueMissingValidityState();
+ UpdateBarredFromConstraintValidation();
+
+ nsGenericHTMLFormElementWithState::FieldSetDisabledChanged(aNotify);
+}
+
+void
+HTMLInputElement::SetFilePickerFiltersFromAccept(nsIFilePicker* filePicker)
+{
+ // We always add |filterAll|
+ filePicker->AppendFilters(nsIFilePicker::filterAll);
+
+ NS_ASSERTION(HasAttr(kNameSpaceID_None, nsGkAtoms::accept),
+ "You should not call SetFilePickerFiltersFromAccept if the"
+ " element has no accept attribute!");
+
+ // Services to retrieve image/*, audio/*, video/* filters
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::services::GetStringBundleService();
+ if (!stringService) {
+ return;
+ }
+ nsCOMPtr<nsIStringBundle> filterBundle;
+ if (NS_FAILED(stringService->CreateBundle("chrome://global/content/filepicker.properties",
+ getter_AddRefs(filterBundle)))) {
+ return;
+ }
+
+ // Service to retrieve mime type information for mime types filters
+ nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
+ if (!mimeService) {
+ return;
+ }
+
+ nsAutoString accept;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::accept, accept);
+
+ HTMLSplitOnSpacesTokenizer tokenizer(accept, ',');
+
+ nsTArray<nsFilePickerFilter> filters;
+ nsString allExtensionsList;
+
+ bool allMimeTypeFiltersAreValid = true;
+ bool atLeastOneFileExtensionFilter = false;
+
+ // Retrieve all filters
+ while (tokenizer.hasMoreTokens()) {
+ const nsDependentSubstring& token = tokenizer.nextToken();
+
+ if (token.IsEmpty()) {
+ continue;
+ }
+
+ int32_t filterMask = 0;
+ nsString filterName;
+ nsString extensionListStr;
+
+ // First, check for image/audio/video filters...
+ if (token.EqualsLiteral("image/*")) {
+ filterMask = nsIFilePicker::filterImages;
+ filterBundle->GetStringFromName(u"imageFilter",
+ getter_Copies(extensionListStr));
+ } else if (token.EqualsLiteral("audio/*")) {
+ filterMask = nsIFilePicker::filterAudio;
+ filterBundle->GetStringFromName(u"audioFilter",
+ getter_Copies(extensionListStr));
+ } else if (token.EqualsLiteral("video/*")) {
+ filterMask = nsIFilePicker::filterVideo;
+ filterBundle->GetStringFromName(u"videoFilter",
+ getter_Copies(extensionListStr));
+ } else if (token.First() == '.') {
+ if (token.Contains(';') || token.Contains('*')) {
+ // Ignore this filter as it contains reserved characters
+ continue;
+ }
+ extensionListStr = NS_LITERAL_STRING("*") + token;
+ filterName = extensionListStr;
+ atLeastOneFileExtensionFilter = true;
+ } else {
+ //... if no image/audio/video filter is found, check mime types filters
+ nsCOMPtr<nsIMIMEInfo> mimeInfo;
+ if (NS_FAILED(mimeService->GetFromTypeAndExtension(
+ NS_ConvertUTF16toUTF8(token),
+ EmptyCString(), // No extension
+ getter_AddRefs(mimeInfo))) ||
+ !mimeInfo) {
+ allMimeTypeFiltersAreValid = false;
+ continue;
+ }
+
+ // Get a name for the filter: first try the description, then the mime type
+ // name if there is no description
+ mimeInfo->GetDescription(filterName);
+ if (filterName.IsEmpty()) {
+ nsCString mimeTypeName;
+ mimeInfo->GetType(mimeTypeName);
+ CopyUTF8toUTF16(mimeTypeName, filterName);
+ }
+
+ // Get extension list
+ nsCOMPtr<nsIUTF8StringEnumerator> extensions;
+ mimeInfo->GetFileExtensions(getter_AddRefs(extensions));
+
+ bool hasMore;
+ while (NS_SUCCEEDED(extensions->HasMore(&hasMore)) && hasMore) {
+ nsCString extension;
+ if (NS_FAILED(extensions->GetNext(extension))) {
+ continue;
+ }
+ if (!extensionListStr.IsEmpty()) {
+ extensionListStr.AppendLiteral("; ");
+ }
+ extensionListStr += NS_LITERAL_STRING("*.") +
+ NS_ConvertUTF8toUTF16(extension);
+ }
+ }
+
+ if (!filterMask && (extensionListStr.IsEmpty() || filterName.IsEmpty())) {
+ // No valid filter found
+ allMimeTypeFiltersAreValid = false;
+ continue;
+ }
+
+ // If we arrived here, that means we have a valid filter: let's create it
+ // and add it to our list, if no similar filter is already present
+ nsFilePickerFilter filter;
+ if (filterMask) {
+ filter = nsFilePickerFilter(filterMask);
+ } else {
+ filter = nsFilePickerFilter(filterName, extensionListStr);
+ }
+
+ if (!filters.Contains(filter)) {
+ if (!allExtensionsList.IsEmpty()) {
+ allExtensionsList.AppendLiteral("; ");
+ }
+ allExtensionsList += extensionListStr;
+ filters.AppendElement(filter);
+ }
+ }
+
+ // Remove similar filters
+ // Iterate over a copy, as we might modify the original filters list
+ nsTArray<nsFilePickerFilter> filtersCopy;
+ filtersCopy = filters;
+ for (uint32_t i = 0; i < filtersCopy.Length(); ++i) {
+ const nsFilePickerFilter& filterToCheck = filtersCopy[i];
+ if (filterToCheck.mFilterMask) {
+ continue;
+ }
+ for (uint32_t j = 0; j < filtersCopy.Length(); ++j) {
+ if (i == j) {
+ continue;
+ }
+ // Check if this filter's extension list is a substring of the other one.
+ // e.g. if filters are "*.jpeg" and "*.jpeg; *.jpg" the first one should
+ // be removed.
+ // Add an extra "; " to be sure the check will work and avoid cases like
+ // "*.xls" being a subtring of "*.xslx" while those are two differents
+ // filters and none should be removed.
+ if (FindInReadable(filterToCheck.mFilter + NS_LITERAL_STRING(";"),
+ filtersCopy[j].mFilter + NS_LITERAL_STRING(";"))) {
+ // We already have a similar, less restrictive filter (i.e.
+ // filterToCheck extensionList is just a subset of another filter
+ // extension list): remove this one
+ filters.RemoveElement(filterToCheck);
+ }
+ }
+ }
+
+ // Add "All Supported Types" filter
+ if (filters.Length() > 1) {
+ nsXPIDLString title;
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "AllSupportedTypes", title);
+ filePicker->AppendFilter(title, allExtensionsList);
+ }
+
+ // Add each filter
+ for (uint32_t i = 0; i < filters.Length(); ++i) {
+ const nsFilePickerFilter& filter = filters[i];
+ if (filter.mFilterMask) {
+ filePicker->AppendFilters(filter.mFilterMask);
+ } else {
+ filePicker->AppendFilter(filter.mTitle, filter.mFilter);
+ }
+ }
+
+ if (filters.Length() >= 1 &&
+ (allMimeTypeFiltersAreValid || atLeastOneFileExtensionFilter)) {
+ // |filterAll| will always use index=0 so we need to set index=1 as the
+ // current filter.
+ filePicker->SetFilterIndex(1);
+ }
+}
+
+Decimal
+HTMLInputElement::GetStepScaleFactor() const
+{
+ MOZ_ASSERT(DoesStepApply());
+
+ switch (mType) {
+ case NS_FORM_INPUT_DATE:
+ return kStepScaleFactorDate;
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_RANGE:
+ return kStepScaleFactorNumberRange;
+ case NS_FORM_INPUT_TIME:
+ return kStepScaleFactorTime;
+ case NS_FORM_INPUT_MONTH:
+ return kStepScaleFactorMonth;
+ case NS_FORM_INPUT_WEEK:
+ return kStepScaleFactorWeek;
+ default:
+ MOZ_ASSERT(false, "Unrecognized input type");
+ return Decimal::nan();
+ }
+}
+
+Decimal
+HTMLInputElement::GetDefaultStep() const
+{
+ MOZ_ASSERT(DoesStepApply());
+
+ switch (mType) {
+ case NS_FORM_INPUT_DATE:
+ case NS_FORM_INPUT_MONTH:
+ case NS_FORM_INPUT_WEEK:
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_RANGE:
+ return kDefaultStep;
+ case NS_FORM_INPUT_TIME:
+ return kDefaultStepTime;
+ default:
+ MOZ_ASSERT(false, "Unrecognized input type");
+ return Decimal::nan();
+ }
+}
+
+void
+HTMLInputElement::UpdateValidityUIBits(bool aIsFocused)
+{
+ if (aIsFocused) {
+ // If the invalid UI is shown, we should show it while focusing (and
+ // update). Otherwise, we should not.
+ mCanShowInvalidUI = !IsValid() && ShouldShowValidityUI();
+
+ // If neither invalid UI nor valid UI is shown, we shouldn't show the valid
+ // UI while typing.
+ mCanShowValidUI = ShouldShowValidityUI();
+ } else {
+ mCanShowInvalidUI = true;
+ mCanShowValidUI = true;
+ }
+}
+
+void
+HTMLInputElement::UpdateHasRange()
+{
+ /*
+ * There is a range if min/max applies for the type and if the element
+ * currently have a valid min or max.
+ */
+
+ mHasRange = false;
+
+ // TODO: this is temporary until bug 888331 is fixed.
+ if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
+ return;
+ }
+
+ Decimal minimum = GetMinimum();
+ if (!minimum.isNaN()) {
+ mHasRange = true;
+ return;
+ }
+
+ Decimal maximum = GetMaximum();
+ if (!maximum.isNaN()) {
+ mHasRange = true;
+ return;
+ }
+}
+
+void
+HTMLInputElement::PickerClosed()
+{
+ mPickerRunning = false;
+}
+
+JSObject*
+HTMLInputElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLInputElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+HTMLInputElement::ClearGetFilesHelpers()
+{
+ if (mGetFilesRecursiveHelper) {
+ mGetFilesRecursiveHelper->Unlink();
+ mGetFilesRecursiveHelper = nullptr;
+ }
+
+ if (mGetFilesNonRecursiveHelper) {
+ mGetFilesNonRecursiveHelper->Unlink();
+ mGetFilesNonRecursiveHelper = nullptr;
+ }
+}
+
+GetFilesHelper*
+HTMLInputElement::GetOrCreateGetFilesHelper(bool aRecursiveFlag,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
+ MOZ_ASSERT(global);
+ if (!global) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ if (aRecursiveFlag) {
+ if (!mGetFilesRecursiveHelper) {
+ mGetFilesRecursiveHelper =
+ GetFilesHelper::Create(global,
+ GetFilesOrDirectoriesInternal(),
+ aRecursiveFlag, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ return mGetFilesRecursiveHelper;
+ }
+
+ if (!mGetFilesNonRecursiveHelper) {
+ mGetFilesNonRecursiveHelper =
+ GetFilesHelper::Create(global,
+ GetFilesOrDirectoriesInternal(),
+ aRecursiveFlag, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ return mGetFilesNonRecursiveHelper;
+}
+
+void
+HTMLInputElement::UpdateEntries(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
+{
+ MOZ_ASSERT(mEntries.IsEmpty());
+
+ nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
+ MOZ_ASSERT(global);
+
+ RefPtr<FileSystem> fs = FileSystem::Create(global);
+ if (NS_WARN_IF(!fs)) {
+ return;
+ }
+
+ Sequence<RefPtr<FileSystemEntry>> entries;
+ for (uint32_t i = 0; i < aFilesOrDirectories.Length(); ++i) {
+ RefPtr<FileSystemEntry> entry =
+ FileSystemEntry::Create(global, aFilesOrDirectories[i], fs);
+ MOZ_ASSERT(entry);
+
+ if (!entries.AppendElement(entry, fallible)) {
+ return;
+ }
+ }
+
+ // The root fileSystem is a DirectoryEntry object that contains only the
+ // dropped fileEntry and directoryEntry objects.
+ fs->CreateRoot(entries);
+
+ mEntries.SwapElements(entries);
+}
+
+void
+HTMLInputElement::GetWebkitEntries(nsTArray<RefPtr<FileSystemEntry>>& aSequence)
+{
+ Telemetry::Accumulate(Telemetry::BLINK_FILESYSTEM_USED, true);
+ aSequence.AppendElements(mEntries);
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#undef NS_ORIGINAL_CHECKED_VALUE
diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h
new file mode 100644
index 000000000..e5d670e08
--- /dev/null
+++ b/dom/html/HTMLInputElement.h
@@ -0,0 +1,1691 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_HTMLInputElement_h
+#define mozilla_dom_HTMLInputElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsImageLoadingContent.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsITextControlElement.h"
+#include "nsITimer.h"
+#include "nsIPhonetic.h"
+#include "nsIDOMNSEditableElement.h"
+#include "nsCOMPtr.h"
+#include "nsIConstraintValidation.h"
+#include "mozilla/dom/HTMLFormElement.h" // for HasEverTriedInvalidSubmit()
+#include "mozilla/dom/HTMLInputElementBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "nsIFilePicker.h"
+#include "nsIContentPrefService2.h"
+#include "mozilla/Decimal.h"
+#include "nsContentUtils.h"
+#include "nsTextEditorState.h"
+
+class nsIRadioGroupContainer;
+class nsIRadioVisitor;
+
+namespace mozilla {
+
+class EventChainPostVisitor;
+class EventChainPreVisitor;
+
+namespace dom {
+
+class AfterSetFilesOrDirectoriesRunnable;
+class Date;
+class DispatchChangeEventCallback;
+class File;
+class FileList;
+class FileSystemEntry;
+class GetFilesHelper;
+
+/**
+ * A class we use to create a singleton object that is used to keep track of
+ * the last directory from which the user has picked files (via
+ * <input type=file>) on a per-domain basis. The implementation uses
+ * nsIContentPrefService2/NS_CONTENT_PREF_SERVICE_CONTRACTID to store the last
+ * directory per-domain, and to ensure that whether the directories are
+ * persistently saved (saved across sessions) or not honors whether or not the
+ * page is being viewed in private browsing.
+ */
+class UploadLastDir final : public nsIObserver, public nsSupportsWeakReference
+{
+ ~UploadLastDir() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ /**
+ * Fetch the last used directory for this location from the content
+ * pref service, and display the file picker opened in that directory.
+ *
+ * @param aDoc current document
+ * @param aFilePicker the file picker to open
+ * @param aFpCallback the callback object to be run when the file is shown.
+ */
+ nsresult FetchDirectoryAndDisplayPicker(nsIDocument* aDoc,
+ nsIFilePicker* aFilePicker,
+ nsIFilePickerShownCallback* aFpCallback);
+
+ /**
+ * Store the last used directory for this location using the
+ * content pref service, if it is available
+ * @param aURI URI of the current page
+ * @param aDir Parent directory of the file(s)/directory chosen by the user
+ */
+ nsresult StoreLastUsedDirectory(nsIDocument* aDoc, nsIFile* aDir);
+
+ class ContentPrefCallback final : public nsIContentPrefCallback2
+ {
+ virtual ~ContentPrefCallback()
+ { }
+
+ public:
+ ContentPrefCallback(nsIFilePicker* aFilePicker, nsIFilePickerShownCallback* aFpCallback)
+ : mFilePicker(aFilePicker)
+ , mFpCallback(aFpCallback)
+ { }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPREFCALLBACK2
+
+ nsCOMPtr<nsIFilePicker> mFilePicker;
+ nsCOMPtr<nsIFilePickerShownCallback> mFpCallback;
+ nsCOMPtr<nsIContentPref> mResult;
+ };
+};
+
+class HTMLInputElement final : public nsGenericHTMLFormElementWithState,
+ public nsImageLoadingContent,
+ public nsIDOMHTMLInputElement,
+ public nsITextControlElement,
+ public nsIPhonetic,
+ public nsIDOMNSEditableElement,
+ public nsIConstraintValidation
+{
+ friend class AfterSetFilesOrDirectoriesCallback;
+ friend class DispatchChangeEventCallback;
+
+public:
+ using nsIConstraintValidation::GetValidationMessage;
+ using nsIConstraintValidation::CheckValidity;
+ using nsIConstraintValidation::ReportValidity;
+ using nsIConstraintValidation::WillValidate;
+ using nsIConstraintValidation::Validity;
+ using nsGenericHTMLFormElementWithState::GetForm;
+
+ enum class FromClone { no, yes };
+
+ HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ mozilla::dom::FromParser aFromParser,
+ FromClone aFromClone = FromClone::no);
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLInputElement, input)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual int32_t TabIndexDefault() override;
+ using nsGenericHTMLElement::Focus;
+ virtual void Blur(ErrorResult& aError) override;
+ virtual void Focus(ErrorResult& aError) override;
+
+ // nsINode
+#if !defined(ANDROID) && !defined(XP_MACOSX)
+ virtual bool IsNodeApzAwareInternal() const override;
+#endif
+
+ // Element
+ virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
+
+ // EventTarget
+ virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;
+
+ // nsIDOMHTMLInputElement
+ NS_DECL_NSIDOMHTMLINPUTELEMENT
+
+ // nsIPhonetic
+ NS_DECL_NSIPHONETIC
+
+ // nsIDOMNSEditableElement
+ NS_IMETHOD GetEditor(nsIEditor** aEditor) override
+ {
+ return nsGenericHTMLElement::GetEditor(aEditor);
+ }
+
+ NS_IMETHOD SetUserInput(const nsAString& aInput) override;
+
+ // Overriden nsIFormControl methods
+ NS_IMETHOD_(uint32_t) GetType() const override { return mType; }
+ NS_IMETHOD Reset() override;
+ NS_IMETHOD SubmitNamesValues(HTMLFormSubmission* aFormSubmission) override;
+ NS_IMETHOD SaveState() override;
+ virtual bool RestoreState(nsPresState* aState) override;
+ virtual bool AllowDrop() override;
+ virtual bool IsDisabledForEvents(EventMessage aMessage) override;
+
+ virtual void FieldSetDisabledChanged(bool aNotify) override;
+
+ // nsIContent
+ virtual bool IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable, int32_t *aTabIndex) override;
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+
+ virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) override;
+ virtual nsresult PostHandleEvent(
+ EventChainPostVisitor& aVisitor) override;
+ void PostHandleEventForRangeThumb(EventChainPostVisitor& aVisitor);
+ void StartRangeThumbDrag(WidgetGUIEvent* aEvent);
+ void FinishRangeThumbDrag(WidgetGUIEvent* aEvent = nullptr);
+ void CancelRangeThumbDrag(bool aIsForUserEvent = true);
+ void SetValueOfRangeForUserEvent(Decimal aValue);
+
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+
+ virtual void DoneCreatingElement() override;
+
+ virtual EventStates IntrinsicState() const override;
+
+ // Element
+private:
+ virtual void AddStates(EventStates aStates) override;
+ virtual void RemoveStates(EventStates aStates) override;
+
+public:
+
+ // nsITextControlElement
+ NS_IMETHOD SetValueChanged(bool aValueChanged) override;
+ NS_IMETHOD_(bool) IsSingleLineTextControl() const override;
+ NS_IMETHOD_(bool) IsTextArea() const override;
+ NS_IMETHOD_(bool) IsPlainTextControl() const override;
+ NS_IMETHOD_(bool) IsPasswordTextControl() const override;
+ NS_IMETHOD_(int32_t) GetCols() override;
+ NS_IMETHOD_(int32_t) GetWrapCols() override;
+ NS_IMETHOD_(int32_t) GetRows() override;
+ NS_IMETHOD_(void) GetDefaultValueFromContent(nsAString& aValue) override;
+ NS_IMETHOD_(bool) ValueChanged() const override;
+ NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, bool aIgnoreWrap) const override;
+ NS_IMETHOD_(nsIEditor*) GetTextEditor() override;
+ NS_IMETHOD_(nsISelectionController*) GetSelectionController() override;
+ NS_IMETHOD_(nsFrameSelection*) GetConstFrameSelection() override;
+ NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override;
+ NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override;
+ NS_IMETHOD CreateEditor() override;
+ NS_IMETHOD_(nsIContent*) GetRootEditorNode() override;
+ NS_IMETHOD_(Element*) CreatePlaceholderNode() override;
+ NS_IMETHOD_(Element*) GetPlaceholderNode() override;
+ NS_IMETHOD_(void) UpdatePlaceholderVisibility(bool aNotify) override;
+ NS_IMETHOD_(bool) GetPlaceholderVisibility() override;
+ NS_IMETHOD_(void) InitializeKeyboardEventListeners() override;
+ NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) override;
+ NS_IMETHOD_(bool) HasCachedSelection() override;
+
+ void GetDisplayFileName(nsAString& aFileName) const;
+
+ const nsTArray<OwningFileOrDirectory>& GetFilesOrDirectoriesInternal() const
+ {
+ return mFilesOrDirectories;
+ }
+
+ void SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories,
+ bool aSetValueChanged);
+ void SetFiles(nsIDOMFileList* aFiles, bool aSetValueChanged);
+
+ // This method is used for test only. Onces the data is set, a 'change' event
+ // is dispatched.
+ void MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirectory>& aSequence);
+
+ // Called when a nsIFilePicker or a nsIColorPicker terminate.
+ void PickerClosed();
+
+ void SetCheckedChangedInternal(bool aCheckedChanged);
+ bool GetCheckedChanged() const {
+ return mCheckedChanged;
+ }
+ void AddedToRadioGroup();
+ void WillRemoveFromRadioGroup();
+
+ /**
+ * Helper function returning the currently selected button in the radio group.
+ * Returning null if the element is not a button or if there is no selectied
+ * button in the group.
+ *
+ * @return the selected button (or null).
+ */
+ already_AddRefed<nsIDOMHTMLInputElement> GetSelectedRadioButton() const;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLInputElement,
+ nsGenericHTMLFormElementWithState)
+
+ static UploadLastDir* gUploadLastDir;
+ // create and destroy the static UploadLastDir object for remembering
+ // which directory was last used on a site-by-site basis
+ static void InitUploadLastDir();
+ static void DestroyUploadLastDir();
+
+ //If the valueAsDate attribute should be enabled in webIDL
+ static bool ValueAsDateEnabled(JSContext* cx, JSObject* obj);
+
+ void MaybeLoadImage();
+
+ void SetSelectionProperties(const nsTextEditorState::SelectionProperties& aProps)
+ {
+ MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER);
+ mSelectionCached = true;
+ mSelectionProperties = aProps;
+ }
+ bool IsSelectionCached() const
+ {
+ MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER);
+ return mSelectionCached;
+ }
+ void ClearSelectionCached()
+ {
+ MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER);
+ mSelectionCached = false;
+ }
+ nsTextEditorState::SelectionProperties& GetSelectionProperties()
+ {
+ MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER);
+ return mSelectionProperties;
+ }
+
+ // nsIConstraintValidation
+ bool IsTooLong();
+ bool IsTooShort();
+ bool IsValueMissing() const;
+ bool HasTypeMismatch() const;
+ bool HasPatternMismatch() const;
+ bool IsRangeOverflow() const;
+ bool IsRangeUnderflow() const;
+ bool HasStepMismatch(bool aUseZeroIfValueNaN = false) const;
+ bool HasBadInput() const;
+ void UpdateTooLongValidityState();
+ void UpdateTooShortValidityState();
+ void UpdateValueMissingValidityState();
+ void UpdateTypeMismatchValidityState();
+ void UpdatePatternMismatchValidityState();
+ void UpdateRangeOverflowValidityState();
+ void UpdateRangeUnderflowValidityState();
+ void UpdateStepMismatchValidityState();
+ void UpdateBadInputValidityState();
+ void UpdateAllValidityStates(bool aNotify);
+ void UpdateBarredFromConstraintValidation();
+ nsresult GetValidationMessage(nsAString& aValidationMessage,
+ ValidityStateType aType) override;
+ /**
+ * Update the value missing validity state for radio elements when they have
+ * a group.
+ *
+ * @param aIgnoreSelf Whether the required attribute and the checked state
+ * of the current radio should be ignored.
+ * @note This method shouldn't be called if the radio element hasn't a group.
+ */
+ void UpdateValueMissingValidityStateForRadio(bool aIgnoreSelf);
+
+ /**
+ * Set filters to the filePicker according to the accept attribute value.
+ *
+ * See:
+ * http://dev.w3.org/html5/spec/forms.html#attr-input-accept
+ *
+ * @note You should not call this function if the element has no @accept.
+ * @note "All Files" filter is always set, no matter if there is a valid
+ * filter specified or not.
+ * @note If more than one valid filter is found, the "All Supported Types"
+ * filter is added, which is the concatenation of all valid filters.
+ * @note Duplicate filters and similar filters (i.e. filters whose file
+ * extensions already exist in another filter) are ignored.
+ * @note "All Files" filter will be selected by default if unknown mime types
+ * have been specified and no file extension filter has been specified.
+ * Otherwise, specified filter or "All Supported Types" filter will be
+ * selected by default.
+ * The logic behind is that having unknown mime type means we might restrict
+ * user's input too much, as some filters will be missing.
+ * However, if author has also specified some file extension filters, it's
+ * likely those are fallback for the unusual mime type we haven't been able
+ * to resolve; so it's better to select author specified filters in that case.
+ */
+ void SetFilePickerFiltersFromAccept(nsIFilePicker* filePicker);
+
+ /**
+ * The form might need to request an update of the UI bits
+ * (BF_CAN_SHOW_INVALID_UI and BF_CAN_SHOW_VALID_UI) when an invalid form
+ * submission is tried.
+ *
+ * @param aIsFocused Whether the element is currently focused.
+ *
+ * @note The caller is responsible to call ContentStatesChanged.
+ */
+ void UpdateValidityUIBits(bool aIsFocused);
+
+ /**
+ * Fires change event if mFocusedValue and current value held are unequal.
+ */
+ void FireChangeEventIfNeeded();
+
+ /**
+ * Returns the input element's value as a Decimal.
+ * Returns NaN if the current element's value is not a floating point number.
+ *
+ * @return the input element's value as a Decimal.
+ */
+ Decimal GetValueAsDecimal() const;
+
+ /**
+ * Returns the input's "minimum" (as defined by the HTML5 spec) as a double.
+ * Note this takes account of any default minimum that the type may have.
+ * Returns NaN if the min attribute isn't a valid floating point number and
+ * the input's type does not have a default minimum.
+ *
+ * NOTE: Only call this if you know DoesMinMaxApply() returns true.
+ */
+ Decimal GetMinimum() const;
+
+ /**
+ * Returns the input's "maximum" (as defined by the HTML5 spec) as a double.
+ * Note this takes account of any default maximum that the type may have.
+ * Returns NaN if the max attribute isn't a valid floating point number and
+ * the input's type does not have a default maximum.
+ *
+ * NOTE:Only call this if you know DoesMinMaxApply() returns true.
+ */
+ Decimal GetMaximum() const;
+
+ // WebIDL
+
+ // XPCOM GetAccept() is OK
+ void SetAccept(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::accept, aValue, aRv);
+ }
+
+ // XPCOM GetAlt() is OK
+ void SetAlt(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::alt, aValue, aRv);
+ }
+
+ // XPCOM GetAutocomplete() is OK
+ void SetAutocomplete(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::autocomplete, aValue, aRv);
+ }
+
+ void GetAutocompleteInfo(Nullable<AutocompleteInfo>& aInfo);
+
+ bool Autofocus() const
+ {
+ return GetBoolAttr(nsGkAtoms::autofocus);
+ }
+
+ void SetAutofocus(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::autofocus, aValue, aRv);
+ }
+
+ bool DefaultChecked() const
+ {
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::checked);
+ }
+
+ void SetDefaultChecked(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::checked, aValue, aRv);
+ }
+
+ bool Checked() const
+ {
+ return mChecked;
+ }
+ // XPCOM SetChecked() is OK
+
+ bool Disabled() const
+ {
+ return GetBoolAttr(nsGkAtoms::disabled);
+ }
+
+ void SetDisabled(bool aValue,ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::disabled, aValue, aRv);
+ }
+
+ // XPCOM GetForm() is OK
+
+ FileList* GetFiles();
+
+ // XPCOM GetFormAction() is OK
+ void SetFormAction(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::formaction, aValue, aRv);
+ }
+
+ // XPCOM GetFormEnctype() is OK
+ void SetFormEnctype(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::formenctype, aValue, aRv);
+ }
+
+ // XPCOM GetFormMethod() is OK
+ void SetFormMethod(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::formmethod, aValue, aRv);
+ }
+
+ bool FormNoValidate() const
+ {
+ return GetBoolAttr(nsGkAtoms::formnovalidate);
+ }
+
+ void SetFormNoValidate(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::formnovalidate, aValue, aRv);
+ }
+
+ // XPCOM GetFormTarget() is OK
+ void SetFormTarget(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::formtarget, aValue, aRv);
+ }
+
+ uint32_t Height();
+
+ void SetHeight(uint32_t aValue, ErrorResult& aRv)
+ {
+ SetUnsignedIntAttr(nsGkAtoms::height, aValue, 0, aRv);
+ }
+
+ bool Indeterminate() const
+ {
+ return mIndeterminate;
+ }
+ // XPCOM SetIndeterminate() is OK
+
+ // XPCOM GetInputMode() is OK
+ void SetInputMode(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::inputmode, aValue, aRv);
+ }
+
+ nsGenericHTMLElement* GetList() const;
+
+ // XPCOM GetMax() is OK
+ void SetMax(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::max, aValue, aRv);
+ }
+
+ int32_t MaxLength() const
+ {
+ return GetIntAttr(nsGkAtoms::maxlength, -1);
+ }
+
+ void SetMaxLength(int32_t aValue, ErrorResult& aRv)
+ {
+ int32_t minLength = MinLength();
+ if (aValue < 0 || (minLength >= 0 && aValue < minLength)) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ SetHTMLIntAttr(nsGkAtoms::maxlength, aValue, aRv);
+ }
+
+ int32_t MinLength() const
+ {
+ return GetIntAttr(nsGkAtoms::minlength, -1);
+ }
+
+ void SetMinLength(int32_t aValue, ErrorResult& aRv)
+ {
+ int32_t maxLength = MaxLength();
+ if (aValue < 0 || (maxLength >= 0 && aValue > maxLength)) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ SetHTMLIntAttr(nsGkAtoms::minlength, aValue, aRv);
+ }
+
+ // XPCOM GetMin() is OK
+ void SetMin(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::min, aValue, aRv);
+ }
+
+ bool Multiple() const
+ {
+ return GetBoolAttr(nsGkAtoms::multiple);
+ }
+
+ void SetMultiple(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::multiple, aValue, aRv);
+ }
+
+ // XPCOM GetName() is OK
+ void SetName(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::name, aValue, aRv);
+ }
+
+ // XPCOM GetPattern() is OK
+ void SetPattern(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::pattern, aValue, aRv);
+ }
+
+ // XPCOM GetPlaceholder() is OK
+ void SetPlaceholder(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::placeholder, aValue, aRv);
+ }
+
+ bool ReadOnly() const
+ {
+ return GetBoolAttr(nsGkAtoms::readonly);
+ }
+
+ void SetReadOnly(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::readonly, aValue, aRv);
+ }
+
+ bool Required() const
+ {
+ return GetBoolAttr(nsGkAtoms::required);
+ }
+
+ void SetRequired(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::required, aValue, aRv);
+ }
+
+ uint32_t Size() const
+ {
+ return GetUnsignedIntAttr(nsGkAtoms::size, DEFAULT_COLS);
+ }
+
+ void SetSize(uint32_t aValue, ErrorResult& aRv)
+ {
+ if (aValue == 0) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ SetUnsignedIntAttr(nsGkAtoms::size, aValue, DEFAULT_COLS, aRv);
+ }
+
+ // XPCOM GetSrc() is OK
+ void SetSrc(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::src, aValue, aRv);
+ }
+
+ // XPCOM GetStep() is OK
+ void SetStep(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::step, aValue, aRv);
+ }
+
+ // XPCOM GetType() is OK
+ void SetType(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::type, aValue, aRv);
+ }
+
+ // XPCOM GetDefaultValue() is OK
+ void SetDefaultValue(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::value, aValue, aRv);
+ }
+
+ void GetValue(nsAString& aValue, ErrorResult& aRv);
+ void SetValue(const nsAString& aValue, ErrorResult& aRv);
+
+ Nullable<Date> GetValueAsDate(ErrorResult& aRv);
+
+ void SetValueAsDate(Nullable<Date>, ErrorResult& aRv);
+
+ double ValueAsNumber() const
+ {
+ return DoesValueAsNumberApply() ? GetValueAsDecimal().toDouble()
+ : UnspecifiedNaN<double>();
+ }
+
+ void SetValueAsNumber(double aValue, ErrorResult& aRv);
+
+ uint32_t Width();
+
+ void SetWidth(uint32_t aValue, ErrorResult& aRv)
+ {
+ SetUnsignedIntAttr(nsGkAtoms::width, aValue, 0, aRv);
+ }
+
+ void StepUp(int32_t aN, ErrorResult& aRv)
+ {
+ aRv = ApplyStep(aN);
+ }
+
+ void StepDown(int32_t aN, ErrorResult& aRv)
+ {
+ aRv = ApplyStep(-aN);
+ }
+
+ /**
+ * Returns the current step value.
+ * Returns kStepAny if the current step is "any" string.
+ *
+ * @return the current step value.
+ */
+ Decimal GetStep() const;
+
+ void GetValidationMessage(nsAString& aValidationMessage, ErrorResult& aRv);
+
+ // XPCOM GetCustomVisibility() is OK
+
+ // XPCOM Select() is OK
+
+ Nullable<int32_t> GetSelectionStart(ErrorResult& aRv);
+ void SetSelectionStart(const Nullable<int32_t>& aValue, ErrorResult& aRv);
+
+ Nullable<int32_t> GetSelectionEnd(ErrorResult& aRv);
+ void SetSelectionEnd(const Nullable<int32_t>& aValue, ErrorResult& aRv);
+
+ void GetSelectionDirection(nsAString& aValue, ErrorResult& aRv);
+ void SetSelectionDirection(const nsAString& aValue, ErrorResult& aRv);
+
+ void SetSelectionRange(int32_t aStart, int32_t aEnd,
+ const Optional< nsAString >& direction,
+ ErrorResult& aRv);
+
+ void SetRangeText(const nsAString& aReplacement, ErrorResult& aRv);
+
+ void SetRangeText(const nsAString& aReplacement, uint32_t aStart,
+ uint32_t aEnd, const SelectionMode& aSelectMode,
+ ErrorResult& aRv, int32_t aSelectionStart = -1,
+ int32_t aSelectionEnd = -1);
+
+ bool Allowdirs() const
+ {
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::allowdirs);
+ }
+
+ void SetAllowdirs(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::allowdirs, aValue, aRv);
+ }
+
+ bool WebkitDirectoryAttr() const
+ {
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory);
+ }
+
+ void SetWebkitDirectoryAttr(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::webkitdirectory, aValue, aRv);
+ }
+
+ void GetWebkitEntries(nsTArray<RefPtr<FileSystemEntry>>& aSequence);
+
+ bool IsFilesAndDirectoriesSupported() const;
+
+ already_AddRefed<Promise> GetFilesAndDirectories(ErrorResult& aRv);
+
+ already_AddRefed<Promise> GetFiles(bool aRecursiveFlag, ErrorResult& aRv);
+
+ void ChooseDirectory(ErrorResult& aRv);
+
+ // XPCOM GetAlign() is OK
+ void SetAlign(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::align, aValue, aRv);
+ }
+
+ // XPCOM GetUseMap() is OK
+ void SetUseMap(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::usemap, aValue, aRv);
+ }
+
+ nsIControllers* GetControllers(ErrorResult& aRv);
+
+ int32_t GetTextLength(ErrorResult& aRv);
+
+ void MozGetFileNameArray(nsTArray<nsString>& aFileNames, ErrorResult& aRv);
+
+ void MozSetFileNameArray(const Sequence< nsString >& aFileNames, ErrorResult& aRv);
+ void MozSetFileArray(const Sequence<OwningNonNull<File>>& aFiles);
+ void MozSetDirectory(const nsAString& aDirectoryPath, ErrorResult& aRv);
+
+ /*
+ * The following functions are called from datetime picker to let input box
+ * know the current state of the picker or to update the input box on changes.
+ */
+ void GetDateTimeInputBoxValue(DateTimeValue& aValue);
+ void UpdateDateTimeInputBox(const DateTimeValue& aValue);
+ void SetDateTimePickerState(bool aOpen);
+
+ /*
+ * The following functions are called from datetime input box XBL to control
+ * and update the picker.
+ */
+ void OpenDateTimePicker(const DateTimeValue& aInitialValue);
+ void UpdateDateTimePicker(const DateTimeValue& aValue);
+ void CloseDateTimePicker();
+
+ HTMLInputElement* GetOwnerNumberControl();
+ HTMLInputElement* GetOwnerDateTimeControl();
+
+ void StartNumberControlSpinnerSpin();
+ enum SpinnerStopState {
+ eAllowDispatchingEvents,
+ eDisallowDispatchingEvents
+ };
+ void StopNumberControlSpinnerSpin(SpinnerStopState aState =
+ eAllowDispatchingEvents);
+ void StepNumberControlForUserEvent(int32_t aDirection);
+
+ /**
+ * The callback function used by the nsRepeatService that we use to spin the
+ * spinner for <input type=number>.
+ */
+ static void HandleNumberControlSpin(void* aData);
+
+ bool NumberSpinnerUpButtonIsDepressed() const
+ {
+ return mNumberControlSpinnerIsSpinning && mNumberControlSpinnerSpinsUp;
+ }
+
+ bool NumberSpinnerDownButtonIsDepressed() const
+ {
+ return mNumberControlSpinnerIsSpinning && !mNumberControlSpinnerSpinsUp;
+ }
+
+ bool MozIsTextField(bool aExcludePassword);
+
+ nsIEditor* GetEditor();
+
+ void SetUserInput(const nsAString& aInput,
+ nsIPrincipal& aSubjectPrincipal);
+
+ // XPCOM GetPhonetic() is OK
+
+ /**
+ * If aValue contains a valid floating-point number in the format specified
+ * by the HTML 5 spec:
+ *
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#floating-point-numbers
+ *
+ * then this function will return the number parsed as a Decimal, otherwise
+ * it will return a Decimal for which Decimal::isFinite() will return false.
+ */
+ static Decimal StringToDecimal(const nsAString& aValue);
+
+ void UpdateEntries(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories);
+
+protected:
+ virtual ~HTMLInputElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // Pull IsSingleLineTextControl into our scope, otherwise it'd be hidden
+ // by the nsITextControlElement version.
+ using nsGenericHTMLFormElementWithState::IsSingleLineTextControl;
+
+ /**
+ * The ValueModeType specifies how the value IDL attribute should behave.
+ *
+ * See: http://dev.w3.org/html5/spec/forms.html#dom-input-value
+ */
+ enum ValueModeType
+ {
+ // On getting, returns the value.
+ // On setting, sets value.
+ VALUE_MODE_VALUE,
+ // On getting, returns the value if present or the empty string.
+ // On setting, sets the value.
+ VALUE_MODE_DEFAULT,
+ // On getting, returns the value if present or "on".
+ // On setting, sets the value.
+ VALUE_MODE_DEFAULT_ON,
+ // On getting, returns "C:\fakepath\" followed by the file name of the
+ // first file of the selected files if any.
+ // On setting the empty string, empties the selected files list, otherwise
+ // throw the INVALID_STATE_ERR exception.
+ VALUE_MODE_FILENAME
+ };
+
+ /**
+ * This helper method returns true if aValue is a valid email address.
+ * This is following the HTML5 specification:
+ * http://dev.w3.org/html5/spec/forms.html#valid-e-mail-address
+ *
+ * @param aValue the email address to check.
+ * @result whether the given string is a valid email address.
+ */
+ static bool IsValidEmailAddress(const nsAString& aValue);
+
+ /**
+ * This helper method returns true if aValue is a valid email address list.
+ * Email address list is a list of email address separated by comas (,) which
+ * can be surrounded by space charecters.
+ * This is following the HTML5 specification:
+ * http://dev.w3.org/html5/spec/forms.html#valid-e-mail-address-list
+ *
+ * @param aValue the email address list to check.
+ * @result whether the given string is a valid email address list.
+ */
+ static bool IsValidEmailAddressList(const nsAString& aValue);
+
+ /**
+ * This helper method convert a sub-string that contains only digits to a
+ * number (unsigned int given that it can't contain a minus sign).
+ * This method will return whether the sub-string is correctly formatted
+ * (ie. contains only digit) and it can be successfuly parsed to generate a
+ * number).
+ * If the method returns true, |aResult| will contained the parsed number.
+ *
+ * @param aValue the string on which the sub-string will be extracted and parsed.
+ * @param aStart the beginning of the sub-string in aValue.
+ * @param aLen the length of the sub-string.
+ * @param aResult the parsed number.
+ * @return whether the sub-string has been parsed successfully.
+ */
+ static bool DigitSubStringToNumber(const nsAString& aValue, uint32_t aStart,
+ uint32_t aLen, uint32_t* aResult);
+
+ // Helper method
+
+ /**
+ * Setting the value.
+ *
+ * @param aValue String to set.
+ * @param aFlags See nsTextEditorState::SetValueFlags.
+ */
+ nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags);
+
+ nsresult GetValueInternal(nsAString& aValue) const;
+
+ /**
+ * Returns whether the current value is the empty string.
+ *
+ * @return whether the current value is the empty string.
+ */
+ bool IsValueEmpty() const;
+
+ void ClearFiles(bool aSetValueChanged);
+
+ void SetIndeterminateInternal(bool aValue,
+ bool aShouldInvalidate);
+
+ nsresult GetSelectionRange(int32_t* aSelectionStart, int32_t* aSelectionEnd);
+
+ /**
+ * Called when an attribute is about to be changed
+ */
+ virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify) override;
+ /**
+ * Called when an attribute has just been changed
+ */
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+
+ /**
+ * Dispatch a select event. Returns true if the event was not cancelled.
+ */
+ bool DispatchSelectEvent(nsPresContext* aPresContext);
+
+ void SelectAll(nsPresContext* aPresContext);
+ bool IsImage() const
+ {
+ return AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::image, eIgnoreCase);
+ }
+
+ /**
+ * Visit the group of radio buttons this radio belongs to
+ * @param aVisitor the visitor to visit with
+ */
+ nsresult VisitGroup(nsIRadioVisitor* aVisitor, bool aFlushContent);
+
+ /**
+ * Do all the work that |SetChecked| does (radio button handling, etc.), but
+ * take an |aNotify| parameter.
+ */
+ void DoSetChecked(bool aValue, bool aNotify, bool aSetValueChanged);
+
+ /**
+ * Do all the work that |SetCheckedChanged| does (radio button handling,
+ * etc.), but take an |aNotify| parameter that lets it avoid flushing content
+ * when it can.
+ */
+ void DoSetCheckedChanged(bool aCheckedChanged, bool aNotify);
+
+ /**
+ * Actually set checked and notify the frame of the change.
+ * @param aValue the value of checked to set
+ */
+ void SetCheckedInternal(bool aValue, bool aNotify);
+
+ void RadioSetChecked(bool aNotify);
+ void SetCheckedChanged(bool aCheckedChanged);
+
+ /**
+ * MaybeSubmitForm looks for a submit input or a single text control
+ * and submits the form if either is present.
+ */
+ nsresult MaybeSubmitForm(nsPresContext* aPresContext);
+
+ /**
+ * Update mFileList with the currently selected file.
+ */
+ void UpdateFileList();
+
+ /**
+ * Called after calling one of the SetFilesOrDirectories() functions.
+ * This method can explore the directory recursively if needed.
+ */
+ void AfterSetFilesOrDirectories(bool aSetValueChanged);
+
+ /**
+ * Recursively explore the directory and populate mFileOrDirectories correctly
+ * for webkitdirectory.
+ */
+ void ExploreDirectoryRecursively(bool aSetValuechanged);
+
+ /**
+ * Determine whether the editor needs to be initialized explicitly for
+ * a particular event.
+ */
+ bool NeedToInitializeEditorForEvent(EventChainPreVisitor& aVisitor) const;
+
+ /**
+ * Get the value mode of the element, depending of the type.
+ */
+ ValueModeType GetValueMode() const;
+
+ /**
+ * Get the mutable state of the element.
+ * When the element isn't mutable (immutable), the value or checkedness
+ * should not be changed by the user.
+ *
+ * See: http://dev.w3.org/html5/spec/forms.html#concept-input-mutable
+ */
+ bool IsMutable() const;
+
+ /**
+ * Returns if the readonly attribute applies for the current type.
+ */
+ bool DoesReadOnlyApply() const;
+
+ /**
+ * Returns if the required attribute applies for the current type.
+ */
+ bool DoesRequiredApply() const;
+
+ /**
+ * Returns if the pattern attribute applies for the current type.
+ */
+ bool DoesPatternApply() const;
+
+ /**
+ * Returns if the min and max attributes apply for the current type.
+ */
+ bool DoesMinMaxApply() const;
+
+ /**
+ * Returns if the step attribute apply for the current type.
+ */
+ bool DoesStepApply() const
+ {
+ // TODO: this is temporary until bug 888331 is fixed.
+ return DoesMinMaxApply() && mType != NS_FORM_INPUT_DATETIME_LOCAL;
+ }
+
+ /**
+ * Returns if stepDown and stepUp methods apply for the current type.
+ */
+ bool DoStepDownStepUpApply() const { return DoesStepApply(); }
+
+ /**
+ * Returns if valueAsNumber attribute applies for the current type.
+ */
+ bool DoesValueAsNumberApply() const
+ {
+ // TODO: this is temporary until bug 888331 is fixed.
+ return DoesMinMaxApply() && mType != NS_FORM_INPUT_DATETIME_LOCAL;
+ }
+
+ /**
+ * Returns if autocomplete attribute applies for the current type.
+ */
+ bool DoesAutocompleteApply() const;
+
+ /**
+ * Returns if the minlength or maxlength attributes apply for the current type.
+ */
+ bool MinOrMaxLengthApplies() const { return IsSingleLineTextControl(false, mType); }
+
+ void FreeData();
+ nsTextEditorState *GetEditorState() const;
+
+ /**
+ * Manages the internal data storage across type changes.
+ */
+ void HandleTypeChange(uint8_t aNewType);
+
+ /**
+ * Sanitize the value of the element depending of its current type.
+ * See: http://www.whatwg.org/specs/web-apps/current-work/#value-sanitization-algorithm
+ */
+ void SanitizeValue(nsAString& aValue);
+
+ /**
+ * Returns whether the placeholder attribute applies for the current type.
+ */
+ bool PlaceholderApplies() const;
+
+ /**
+ * Set the current default value to the value of the input element.
+ * @note You should not call this method if GetValueMode() doesn't return
+ * VALUE_MODE_VALUE.
+ */
+ nsresult SetDefaultValueAsValue();
+
+ virtual void SetDirectionIfAuto(bool aAuto, bool aNotify);
+
+ /**
+ * Return if an element should have a specific validity UI
+ * (with :-moz-ui-invalid and :-moz-ui-valid pseudo-classes).
+ *
+ * @return Whether the element should have a validity UI.
+ */
+ bool ShouldShowValidityUI() const {
+ /**
+ * Always show the validity UI if the form has already tried to be submitted
+ * but was invalid.
+ *
+ * Otherwise, show the validity UI if the element's value has been changed.
+ */
+ if (mForm && mForm->HasEverTriedInvalidSubmit()) {
+ return true;
+ }
+
+ switch (GetValueMode()) {
+ case VALUE_MODE_DEFAULT:
+ return true;
+ case VALUE_MODE_DEFAULT_ON:
+ return GetCheckedChanged();
+ case VALUE_MODE_VALUE:
+ case VALUE_MODE_FILENAME:
+ return mValueChanged;
+ default:
+ NS_NOTREACHED("We should not be there: there are no other modes.");
+ return false;
+ }
+ }
+
+ /**
+ * Returns the radio group container if the element has one, null otherwise.
+ * The radio group container will be the form owner if there is one.
+ * The current document otherwise.
+ * @return the radio group container if the element has one, null otherwise.
+ */
+ nsIRadioGroupContainer* GetRadioGroupContainer() const;
+
+ /**
+ * Convert a string to a Decimal number in a type specific way,
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#concept-input-value-string-number
+ * ie parse a date string to a timestamp if type=date,
+ * or parse a number string to its value if type=number.
+ * @param aValue the string to be parsed.
+ * @param aResultValue the number as a Decimal.
+ * @result whether the parsing was successful.
+ */
+ bool ConvertStringToNumber(nsAString& aValue, Decimal& aResultValue) const;
+
+ /**
+ * Convert a Decimal to a string in a type specific way, ie convert a timestamp
+ * to a date string if type=date or append the number string representing the
+ * value if type=number.
+ *
+ * @param aValue the Decimal to be converted
+ * @param aResultString [out] the string representing the Decimal
+ * @return whether the function succeded, it will fail if the current input's
+ * type is not supported or the number can't be converted to a string
+ * as expected by the type.
+ */
+ bool ConvertNumberToString(Decimal aValue, nsAString& aResultString) const;
+
+ /**
+ * Parse a color string of the form #XXXXXX where X should be hexa characters
+ * @param the string to be parsed.
+ * @return whether the string is a valid simple color.
+ * Note : this function does not consider the empty string as valid.
+ */
+ bool IsValidSimpleColor(const nsAString& aValue) const;
+
+ /**
+ * Parse a week string of the form yyyy-Www
+ * @param the string to be parsed.
+ * @return whether the string is a valid week.
+ * Note : this function does not consider the empty string as valid.
+ */
+ bool IsValidWeek(const nsAString& aValue) const;
+
+ /**
+ * Parse a month string of the form yyyy-mm
+ * @param the string to be parsed.
+ * @return whether the string is a valid month.
+ * Note : this function does not consider the empty string as valid.
+ */
+ bool IsValidMonth(const nsAString& aValue) const;
+
+ /**
+ * Parse a date string of the form yyyy-mm-dd
+ * @param the string to be parsed.
+ * @return whether the string is a valid date.
+ * Note : this function does not consider the empty string as valid.
+ */
+ bool IsValidDate(const nsAString& aValue) const;
+
+ /**
+ * Parse a datetime-local string of the form yyyy-mm-ddThh:mm[:ss.s] or
+ * yyyy-mm-dd hh:mm[:ss.s], where fractions of seconds can be 1 to 3 digits.
+ *
+ * @param the string to be parsed.
+ * @return whether the string is a valid datetime-local string.
+ * Note : this function does not consider the empty string as valid.
+ */
+ bool IsValidDateTimeLocal(const nsAString& aValue) const;
+
+ /**
+ * Parse a year string of the form yyyy
+ *
+ * @param the string to be parsed.
+ *
+ * @return the year in aYear.
+ * @return whether the parsing was successful.
+ */
+ bool ParseYear(const nsAString& aValue, uint32_t* aYear) const;
+
+ /**
+ * Parse a month string of the form yyyy-mm
+ *
+ * @param the string to be parsed.
+ * @return the year and month in aYear and aMonth.
+ * @return whether the parsing was successful.
+ */
+ bool ParseMonth(const nsAString& aValue,
+ uint32_t* aYear,
+ uint32_t* aMonth) const;
+
+ /**
+ * Parse a week string of the form yyyy-Www
+ *
+ * @param the string to be parsed.
+ * @return the year and week in aYear and aWeek.
+ * @return whether the parsing was successful.
+ */
+ bool ParseWeek(const nsAString& aValue,
+ uint32_t* aYear,
+ uint32_t* aWeek) const;
+ /**
+ * Parse a date string of the form yyyy-mm-dd
+ *
+ * @param the string to be parsed.
+ * @return the date in aYear, aMonth, aDay.
+ * @return whether the parsing was successful.
+ */
+ bool ParseDate(const nsAString& aValue,
+ uint32_t* aYear,
+ uint32_t* aMonth,
+ uint32_t* aDay) const;
+
+ /**
+ * Parse a datetime-local string of the form yyyy-mm-ddThh:mm[:ss.s] or
+ * yyyy-mm-dd hh:mm[:ss.s], where fractions of seconds can be 1 to 3 digits.
+ *
+ * @param the string to be parsed.
+ * @return the date in aYear, aMonth, aDay and time expressed in milliseconds
+ * in aTime.
+ * @return whether the parsing was successful.
+ */
+ bool ParseDateTimeLocal(const nsAString& aValue,
+ uint32_t* aYear,
+ uint32_t* aMonth,
+ uint32_t* aDay,
+ uint32_t* aTime) const;
+
+ /**
+ * Normalize the datetime-local string following the HTML specifications:
+ * https://html.spec.whatwg.org/multipage/infrastructure.html#valid-normalised-local-date-and-time-string
+ */
+ void NormalizeDateTimeLocal(nsAString& aValue) const;
+ /**
+ * This methods returns the number of days since epoch for a given year and
+ * week.
+ */
+ double DaysSinceEpochFromWeek(uint32_t aYear, uint32_t aWeek) const;
+
+ /**
+ * This methods returns the number of days in a given month, for a given year.
+ */
+ uint32_t NumberOfDaysInMonth(uint32_t aMonth, uint32_t aYear) const;
+
+ /**
+ * This methods returns the number of months between January 1970 and the
+ * given year and month.
+ */
+ int32_t MonthsSinceJan1970(uint32_t aYear, uint32_t aMonth) const;
+
+ /**
+ * This methods returns the day of the week given a date. If @isoWeek is true,
+ * 7=Sunday, otherwise, 0=Sunday.
+ */
+ uint32_t DayOfWeek(uint32_t aYear, uint32_t aMonth, uint32_t aDay,
+ bool isoWeek) const;
+
+ /**
+ * This methods returns the maximum number of week in a given year, the
+ * result is either 52 or 53.
+ */
+ uint32_t MaximumWeekInYear(uint32_t aYear) const;
+
+ /**
+ * This methods returns true if it's a leap year.
+ */
+ bool IsLeapYear(uint32_t aYear) const;
+
+ /**
+ * Returns whether aValue is a valid time as described by HTML specifications:
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-time-string
+ *
+ * @param aValue the string to be tested.
+ * @return Whether the string is a valid time per HTML specifications.
+ */
+ bool IsValidTime(const nsAString& aValue) const;
+
+ /**
+ * Returns the time expressed in milliseconds of |aValue| being parsed as a
+ * time following the HTML specifications:
+ * http://www.whatwg.org/specs/web-apps/current-work/#parse-a-time-string
+ *
+ * Note: |aResult| can be null.
+ *
+ * @param aValue the string to be parsed.
+ * @param aResult the time expressed in milliseconds representing the time [out]
+ * @return Whether the parsing was successful.
+ */
+ static bool ParseTime(const nsAString& aValue, uint32_t* aResult);
+
+ /**
+ * Sets the value of the element to the string representation of the Decimal.
+ *
+ * @param aValue The Decimal that will be used to set the value.
+ */
+ void SetValue(Decimal aValue);
+
+ /**
+ * Update the HAS_RANGE bit field value.
+ */
+ void UpdateHasRange();
+
+ /**
+ * Get the step scale value for the current type.
+ * See:
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-input-element-attributes.html#concept-input-step-scale
+ */
+ Decimal GetStepScaleFactor() const;
+
+ /**
+ * Return the base used to compute if a value matches step.
+ * Basically, it's the min attribute if present and a default value otherwise.
+ *
+ * @return The step base.
+ */
+ Decimal GetStepBase() const;
+
+ /**
+ * Returns the default step for the current type.
+ * @return the default step for the current type.
+ */
+ Decimal GetDefaultStep() const;
+
+ enum StepCallerType {
+ CALLED_FOR_USER_EVENT,
+ CALLED_FOR_SCRIPT
+ };
+
+ /**
+ * Sets the aValue outparam to the value that this input would take if
+ * someone tries to step aStep steps and this input's value would change as
+ * a result. Leaves aValue untouched if this inputs value would not change
+ * (e.g. already at max, and asking for the next step up).
+ *
+ * Negative aStep means step down, positive means step up.
+ *
+ * Returns NS_OK or else the error values that should be thrown if this call
+ * was initiated by a stepUp()/stepDown() call from script under conditions
+ * that such a call should throw.
+ */
+ nsresult GetValueIfStepped(int32_t aStepCount,
+ StepCallerType aCallerType,
+ Decimal* aNextStep);
+
+ /**
+ * Apply a step change from stepUp or stepDown by multiplying aStep by the
+ * current step value.
+ *
+ * @param aStep The value used to be multiplied against the step value.
+ */
+ nsresult ApplyStep(int32_t aStep);
+
+ /**
+ * Returns if the current type is an experimental mobile type.
+ */
+ static bool IsExperimentalMobileType(uint8_t aType);
+
+ /*
+ * Returns if the current type is one of the date/time input types: date,
+ * time and month. TODO: week and datetime-local.
+ */
+ static bool IsDateTimeInputType(uint8_t aType);
+
+ /**
+ * Flushes the layout frame tree to make sure we have up-to-date frames.
+ */
+ void FlushFrames();
+
+ /**
+ * Returns true if the element should prevent dispatching another DOMActivate.
+ * This is used in situations where the anonymous subtree should already have
+ * sent a DOMActivate and prevents firing more than once.
+ */
+ bool ShouldPreventDOMActivateDispatch(EventTarget* aOriginalTarget);
+
+ /**
+ * Some input type (color and file) let user choose a value using a picker:
+ * this function checks if it is needed, and if so, open the corresponding
+ * picker (color picker or file picker).
+ */
+ nsresult MaybeInitPickers(EventChainPostVisitor& aVisitor);
+
+ enum FilePickerType {
+ FILE_PICKER_FILE,
+ FILE_PICKER_DIRECTORY
+ };
+ nsresult InitFilePicker(FilePickerType aType);
+ nsresult InitColorPicker();
+ nsresult InitDatePicker();
+
+ /**
+ * Use this function before trying to open a picker.
+ * It checks if the page is allowed to open a new pop-up.
+ * If it returns true, you should not create the picker.
+ *
+ * @return true if popup should be blocked, false otherwise
+ */
+ bool IsPopupBlocked() const;
+
+ GetFilesHelper* GetOrCreateGetFilesHelper(bool aRecursiveFlag,
+ ErrorResult& aRv);
+
+ void ClearGetFilesHelpers();
+
+ /**
+ * nsINode::SetMayBeApzAware() will be invoked in this function if necessary
+ * to prevent default action of APZC so that we can increase/decrease the
+ * value of this InputElement when mouse wheel event comes without scrolling
+ * the page.
+ *
+ * SetMayBeApzAware() will set flag MayBeApzAware which is checked by apzc to
+ * decide whether to add this element into its dispatch-to-content region.
+ */
+ void UpdateApzAwareFlag();
+
+ nsCOMPtr<nsIControllers> mControllers;
+
+ /*
+ * In mInputData, the mState field is used if IsSingleLineTextControl returns
+ * true and mValue is used otherwise. We have to be careful when handling it
+ * on a type change.
+ *
+ * Accessing the mState member should be done using the GetEditorState function,
+ * which returns null if the state is not present.
+ */
+ union InputData {
+ /**
+ * The current value of the input if it has been changed from the default
+ */
+ char16_t* mValue;
+ /**
+ * The state of the text editor associated with the text/password input
+ */
+ nsTextEditorState* mState;
+ } mInputData;
+
+ /**
+ * The value of the input if it is a file input. This is the list of files or
+ * directories DOM objects used when uploading a file. It is vital that this
+ * is kept separate from mValue so that it won't be possible to 'leak' the
+ * value from a text-input to a file-input. Additionally, the logic for this
+ * value is kept as simple as possible to avoid accidental errors where the
+ * wrong filename is used. Therefor the list of filenames is always owned by
+ * this member, never by the frame. Whenever the frame wants to change the
+ * filename it has to call SetFilesOrDirectories to update this member.
+ */
+ nsTArray<OwningFileOrDirectory> mFilesOrDirectories;
+
+ RefPtr<GetFilesHelper> mGetFilesRecursiveHelper;
+ RefPtr<GetFilesHelper> mGetFilesNonRecursiveHelper;
+
+ /**
+ * Hack for bug 1086684: Stash the .value when we're a file picker.
+ */
+ nsString mFirstFilePath;
+
+ RefPtr<FileList> mFileList;
+ Sequence<RefPtr<FileSystemEntry>> mEntries;
+
+ nsString mStaticDocFileList;
+
+ /**
+ * The value of the input element when first initialized and it is updated
+ * when the element is either changed through a script, focused or dispatches
+ * a change event. This is to ensure correct future change event firing.
+ * NB: This is ONLY applicable where the element is a text control. ie,
+ * where type= "text", "email", "search", "tel", "url" or "password".
+ */
+ nsString mFocusedValue;
+
+ /**
+ * If mIsDraggingRange is true, this is the value that the input had before
+ * the drag started. Used to reset the input to its old value if the drag is
+ * canceled.
+ */
+ Decimal mRangeThumbDragStartValue;
+
+ /**
+ * Current value in the input box, in DateTimeValue dictionary format, see
+ * HTMLInputElement.webidl for details.
+ */
+ nsAutoPtr<DateTimeValue> mDateTimeInputBoxValue;
+
+ /**
+ * The selection properties cache for number controls. This is needed because
+ * the number controls don't recycle their text field, so the normal cache in
+ * nsTextEditorState cannot do its job.
+ */
+ nsTextEditorState::SelectionProperties mSelectionProperties;
+
+ // Step scale factor values, for input types that have one.
+ static const Decimal kStepScaleFactorDate;
+ static const Decimal kStepScaleFactorNumberRange;
+ static const Decimal kStepScaleFactorTime;
+ static const Decimal kStepScaleFactorMonth;
+ static const Decimal kStepScaleFactorWeek;
+
+ // Default step base value when a type do not have specific one.
+ static const Decimal kDefaultStepBase;
+ // Default step base value when type=week does not not have a specific one,
+ // which is −259200000, the start of week 1970-W01.
+ static const Decimal kDefaultStepBaseWeek;
+
+ // Default step used when there is no specified step.
+ static const Decimal kDefaultStep;
+ static const Decimal kDefaultStepTime;
+
+ // Float value returned by GetStep() when the step attribute is set to 'any'.
+ static const Decimal kStepAny;
+
+ // Minimum year limited by HTML standard, year >= 1.
+ static const double kMinimumYear;
+ // Maximum year limited by ECMAScript date object range, year <= 275760.
+ static const double kMaximumYear;
+ // Maximum valid week is 275760-W37.
+ static const double kMaximumWeekInMaximumYear;
+ // Maximum valid day is 275760-09-13.
+ static const double kMaximumDayInMaximumYear;
+ // Maximum valid month is 275760-09.
+ static const double kMaximumMonthInMaximumYear;
+ // Long years in a ISO calendar have 53 weeks in them.
+ static const double kMaximumWeekInYear;
+ // Milliseconds in a day.
+ static const double kMsPerDay;
+
+
+ /**
+ * The type of this input (<input type=...>) as an integer.
+ * @see nsIFormControl.h (specifically NS_FORM_INPUT_*)
+ */
+ uint8_t mType;
+ nsContentUtils::AutocompleteAttrState mAutocompleteAttrState;
+ bool mDisabledChanged : 1;
+ bool mValueChanged : 1;
+ bool mLastValueChangeWasInteractive : 1;
+ bool mCheckedChanged : 1;
+ bool mChecked : 1;
+ bool mHandlingSelectEvent : 1;
+ bool mShouldInitChecked : 1;
+ bool mDoneCreating : 1;
+ bool mInInternalActivate : 1;
+ bool mCheckedIsToggled : 1;
+ bool mIndeterminate : 1;
+ bool mInhibitRestoration : 1;
+ bool mCanShowValidUI : 1;
+ bool mCanShowInvalidUI : 1;
+ bool mHasRange : 1;
+ bool mIsDraggingRange : 1;
+ bool mNumberControlSpinnerIsSpinning : 1;
+ bool mNumberControlSpinnerSpinsUp : 1;
+ bool mPickerRunning : 1;
+ bool mSelectionCached : 1;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+
+ /**
+ * Returns true if this input's type will fire a DOM "change" event when it
+ * loses focus if its value has changed since it gained focus.
+ */
+ bool MayFireChangeOnBlur() const {
+ return MayFireChangeOnBlur(mType);
+ }
+
+ /**
+ * Returns true if selection methods can be called on element
+ */
+ bool SupportsTextSelection() const {
+ return mType == NS_FORM_INPUT_TEXT || mType == NS_FORM_INPUT_SEARCH ||
+ mType == NS_FORM_INPUT_URL || mType == NS_FORM_INPUT_TEL ||
+ mType == NS_FORM_INPUT_PASSWORD;
+ }
+
+ static bool MayFireChangeOnBlur(uint8_t aType) {
+ return IsSingleLineTextControl(false, aType) ||
+ aType == NS_FORM_INPUT_RANGE ||
+ aType == NS_FORM_INPUT_NUMBER ||
+ aType == NS_FORM_INPUT_TIME;
+ }
+
+ struct nsFilePickerFilter {
+ nsFilePickerFilter()
+ : mFilterMask(0) {}
+
+ explicit nsFilePickerFilter(int32_t aFilterMask)
+ : mFilterMask(aFilterMask) {}
+
+ nsFilePickerFilter(const nsString& aTitle,
+ const nsString& aFilter)
+ : mFilterMask(0), mTitle(aTitle), mFilter(aFilter) {}
+
+ nsFilePickerFilter(const nsFilePickerFilter& other) {
+ mFilterMask = other.mFilterMask;
+ mTitle = other.mTitle;
+ mFilter = other.mFilter;
+ }
+
+ bool operator== (const nsFilePickerFilter& other) const {
+ if ((mFilter == other.mFilter) && (mFilterMask == other.mFilterMask)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // Filter mask, using values defined in nsIFilePicker
+ int32_t mFilterMask;
+ // If mFilterMask is defined, mTitle and mFilter are useless and should be
+ // ignored
+ nsString mTitle;
+ nsString mFilter;
+ };
+
+ class nsFilePickerShownCallback
+ : public nsIFilePickerShownCallback
+ {
+ virtual ~nsFilePickerShownCallback()
+ { }
+
+ public:
+ nsFilePickerShownCallback(HTMLInputElement* aInput,
+ nsIFilePicker* aFilePicker);
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Done(int16_t aResult) override;
+
+ private:
+ nsCOMPtr<nsIFilePicker> mFilePicker;
+ RefPtr<HTMLInputElement> mInput;
+ };
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/html/HTMLLIElement.cpp b/dom/html/HTMLLIElement.cpp
new file mode 100644
index 000000000..304105399
--- /dev/null
+++ b/dom/html/HTMLLIElement.cpp
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLLIElement.h"
+#include "mozilla/dom/HTMLLIElementBinding.h"
+
+#include "nsAttrValueInlines.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsMappedAttributes.h"
+#include "nsRuleData.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(LI)
+
+namespace mozilla {
+namespace dom {
+
+HTMLLIElement::~HTMLLIElement()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLLIElement, nsGenericHTMLElement,
+ nsIDOMHTMLLIElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLLIElement)
+
+NS_IMPL_STRING_ATTR(HTMLLIElement, Type, type)
+NS_IMPL_INT_ATTR(HTMLLIElement, Value, value)
+
+// values that are handled case-insensitively
+static const nsAttrValue::EnumTable kUnorderedListItemTypeTable[] = {
+ { "disc", NS_STYLE_LIST_STYLE_DISC },
+ { "circle", NS_STYLE_LIST_STYLE_CIRCLE },
+ { "round", NS_STYLE_LIST_STYLE_CIRCLE },
+ { "square", NS_STYLE_LIST_STYLE_SQUARE },
+ { nullptr, 0 }
+};
+
+// values that are handled case-sensitively
+static const nsAttrValue::EnumTable kOrderedListItemTypeTable[] = {
+ { "A", NS_STYLE_LIST_STYLE_UPPER_ALPHA },
+ { "a", NS_STYLE_LIST_STYLE_LOWER_ALPHA },
+ { "I", NS_STYLE_LIST_STYLE_UPPER_ROMAN },
+ { "i", NS_STYLE_LIST_STYLE_LOWER_ROMAN },
+ { "1", NS_STYLE_LIST_STYLE_DECIMAL },
+ { nullptr, 0 }
+};
+
+bool
+HTMLLIElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::type) {
+ return aResult.ParseEnumValue(aValue, kOrderedListItemTypeTable,
+ true) ||
+ aResult.ParseEnumValue(aValue, kUnorderedListItemTypeTable, false);
+ }
+ if (aAttribute == nsGkAtoms::value) {
+ return aResult.ParseIntValue(aValue);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLLIElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(List)) {
+ nsCSSValue* listStyleType = aData->ValueForListStyleType();
+ if (listStyleType->GetUnit() == eCSSUnit_Null) {
+ // type: enum
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::type);
+ if (value && value->Type() == nsAttrValue::eEnum)
+ listStyleType->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
+ }
+ }
+
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLLIElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::type },
+ { nullptr },
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+nsMapRuleToAttributesFunc
+HTMLLIElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+JSObject*
+HTMLLIElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLLIElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLLIElement.h b/dom/html/HTMLLIElement.h
new file mode 100644
index 000000000..3f5692009
--- /dev/null
+++ b/dom/html/HTMLLIElement.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_HTMLLIElement_h
+#define mozilla_dom_HTMLLIElement_h
+
+#include "mozilla/Attributes.h"
+
+#include "nsIDOMHTMLLIElement.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLLIElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLLIElement
+{
+public:
+ explicit HTMLLIElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLLIElement
+ NS_DECL_NSIDOMHTMLLIELEMENT
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ // WebIDL API
+ void GetType(DOMString& aType)
+ {
+ GetHTMLAttr(nsGkAtoms::type, aType);
+ }
+ void SetType(const nsAString& aType, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::type, aType, rv);
+ }
+ int32_t Value() const
+ {
+ return GetIntAttr(nsGkAtoms::value, 0);
+ }
+ void SetValue(int32_t aValue, mozilla::ErrorResult& rv)
+ {
+ SetHTMLIntAttr(nsGkAtoms::value, aValue, rv);
+ }
+
+protected:
+ virtual ~HTMLLIElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLLIElement_h
diff --git a/dom/html/HTMLLabelElement.cpp b/dom/html/HTMLLabelElement.cpp
new file mode 100644
index 000000000..c1d22b0a6
--- /dev/null
+++ b/dom/html/HTMLLabelElement.cpp
@@ -0,0 +1,304 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Implementation of HTML <label> elements.
+ */
+#include "HTMLLabelElement.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/dom/HTMLLabelElementBinding.h"
+#include "nsFocusManager.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsQueryObject.h"
+
+// construction, destruction
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Label)
+
+namespace mozilla {
+namespace dom {
+
+HTMLLabelElement::~HTMLLabelElement()
+{
+}
+
+JSObject*
+HTMLLabelElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLLabelElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+// nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLLabelElement, nsGenericHTMLElement,
+ nsIDOMHTMLLabelElement)
+
+// nsIDOMHTMLLabelElement
+
+NS_IMPL_ELEMENT_CLONE(HTMLLabelElement)
+
+NS_IMETHODIMP
+HTMLLabelElement::GetForm(nsIDOMHTMLFormElement** aForm)
+{
+ RefPtr<nsIDOMHTMLFormElement> form = GetForm();
+ form.forget(aForm);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLLabelElement::GetControl(nsIDOMHTMLElement** aElement)
+{
+ nsCOMPtr<nsIDOMHTMLElement> element = do_QueryObject(GetLabeledElement());
+ element.forget(aElement);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLLabelElement::SetHtmlFor(const nsAString& aHtmlFor)
+{
+ ErrorResult rv;
+ SetHtmlFor(aHtmlFor, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLLabelElement::GetHtmlFor(nsAString& aHtmlFor)
+{
+ nsString htmlFor;
+ GetHtmlFor(htmlFor);
+ aHtmlFor = htmlFor;
+ return NS_OK;
+}
+
+HTMLFormElement*
+HTMLLabelElement::GetForm() const
+{
+ nsGenericHTMLElement* control = GetControl();
+ if (!control) {
+ return nullptr;
+ }
+
+ // Not all labeled things have a form association. Stick to the ones that do.
+ nsCOMPtr<nsIFormControl> formControl = do_QueryObject(control);
+ if (!formControl) {
+ return nullptr;
+ }
+
+ return static_cast<HTMLFormElement*>(formControl->GetFormElement());
+}
+
+void
+HTMLLabelElement::Focus(ErrorResult& aError)
+{
+ // retarget the focus method at the for content
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ nsCOMPtr<nsIDOMElement> elem = do_QueryObject(GetLabeledElement());
+ if (elem)
+ fm->SetFocus(elem, 0);
+ }
+}
+
+static bool
+InInteractiveHTMLContent(nsIContent* aContent, nsIContent* aStop)
+{
+ nsIContent* content = aContent;
+ while (content && content != aStop) {
+ if (content->IsElement() &&
+ content->AsElement()->IsInteractiveHTMLContent(true)) {
+ return true;
+ }
+ content = content->GetParent();
+ }
+ return false;
+}
+
+nsresult
+HTMLLabelElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
+ if (mHandlingEvent ||
+ (!(mouseEvent && mouseEvent->IsLeftClickEvent()) &&
+ aVisitor.mEvent->mMessage != eMouseDown) ||
+ aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault ||
+ !aVisitor.mPresContext ||
+ // Don't handle the event if it's already been handled by another label
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIContent> target = do_QueryInterface(aVisitor.mEvent->mTarget);
+ if (InInteractiveHTMLContent(target, this)) {
+ return NS_OK;
+ }
+
+ // Strong ref because event dispatch is going to happen.
+ RefPtr<Element> content = GetLabeledElement();
+
+ if (content) {
+ mHandlingEvent = true;
+ switch (aVisitor.mEvent->mMessage) {
+ case eMouseDown:
+ if (mouseEvent->button == WidgetMouseEvent::eLeftButton) {
+ // We reset the mouse-down point on every event because there is
+ // no guarantee we will reach the eMouseClick code below.
+ LayoutDeviceIntPoint* curPoint =
+ new LayoutDeviceIntPoint(mouseEvent->mRefPoint);
+ SetProperty(nsGkAtoms::labelMouseDownPtProperty,
+ static_cast<void*>(curPoint),
+ nsINode::DeleteProperty<LayoutDeviceIntPoint>);
+ }
+ break;
+
+ case eMouseClick:
+ if (mouseEvent->IsLeftClickEvent()) {
+ LayoutDeviceIntPoint* mouseDownPoint =
+ static_cast<LayoutDeviceIntPoint*>(
+ GetProperty(nsGkAtoms::labelMouseDownPtProperty));
+
+ bool dragSelect = false;
+ if (mouseDownPoint) {
+ LayoutDeviceIntPoint dragDistance = *mouseDownPoint;
+ DeleteProperty(nsGkAtoms::labelMouseDownPtProperty);
+
+ dragDistance -= mouseEvent->mRefPoint;
+ const int CLICK_DISTANCE = 2;
+ dragSelect = dragDistance.x > CLICK_DISTANCE ||
+ dragDistance.x < -CLICK_DISTANCE ||
+ dragDistance.y > CLICK_DISTANCE ||
+ dragDistance.y < -CLICK_DISTANCE;
+ }
+ // Don't click the for-content if we did drag-select text or if we
+ // have a kbd modifier (which adjusts a selection).
+ if (dragSelect || mouseEvent->IsShift() || mouseEvent->IsControl() ||
+ mouseEvent->IsAlt() || mouseEvent->IsMeta()) {
+ break;
+ }
+ // Only set focus on the first click of multiple clicks to prevent
+ // to prevent immediate de-focus.
+ if (mouseEvent->mClickCount <= 1) {
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ // Use FLAG_BYMOVEFOCUS here so that the label is scrolled to.
+ // Also, within HTMLInputElement::PostHandleEvent, inputs will
+ // be selected only when focused via a key or when the navigation
+ // flag is used and we want to select the text on label clicks as
+ // well.
+ // If the label has been clicked by the user, we also want to
+ // pass FLAG_BYMOUSE so that we get correct focus ring behavior,
+ // but we don't want to pass FLAG_BYMOUSE if this click event was
+ // caused by the user pressing an accesskey.
+ nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(content);
+ bool byMouse = (mouseEvent->inputSource != nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD);
+ bool byTouch = (mouseEvent->inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH);
+ fm->SetFocus(elem, nsIFocusManager::FLAG_BYMOVEFOCUS |
+ (byMouse ? nsIFocusManager::FLAG_BYMOUSE : 0) |
+ (byTouch ? nsIFocusManager::FLAG_BYTOUCH : 0));
+ }
+ }
+ // Dispatch a new click event to |content|
+ // (For compatibility with IE, we do only left click. If
+ // we wanted to interpret the HTML spec very narrowly, we
+ // would do nothing. If we wanted to do something
+ // sensible, we might send more events through like
+ // this.) See bug 7554, bug 49897, and bug 96813.
+ nsEventStatus status = aVisitor.mEventStatus;
+ // Ok to use aVisitor.mEvent as parameter because DispatchClickEvent
+ // will actually create a new event.
+ EventFlags eventFlags;
+ eventFlags.mMultipleActionsPrevented = true;
+ DispatchClickEvent(aVisitor.mPresContext, mouseEvent,
+ content, false, &eventFlags, &status);
+ // Do we care about the status this returned? I don't think we do...
+ // Don't run another <label> off of this click
+ mouseEvent->mFlags.mMultipleActionsPrevented = true;
+ }
+ break;
+
+ default:
+ break;
+ }
+ mHandlingEvent = false;
+ }
+ return NS_OK;
+}
+
+bool
+HTMLLabelElement::PerformAccesskey(bool aKeyCausesActivation,
+ bool aIsTrustedEvent)
+{
+ if (!aKeyCausesActivation) {
+ RefPtr<Element> element = GetLabeledElement();
+ if (element) {
+ return element->PerformAccesskey(aKeyCausesActivation, aIsTrustedEvent);
+ }
+ } else {
+ nsPresContext *presContext = GetPresContext(eForUncomposedDoc);
+ if (!presContext) {
+ return false;
+ }
+
+ // Click on it if the users prefs indicate to do so.
+ WidgetMouseEvent event(aIsTrustedEvent, eMouseClick,
+ nullptr, WidgetMouseEvent::eReal);
+ event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD;
+
+ nsAutoPopupStatePusher popupStatePusher(aIsTrustedEvent ?
+ openAllowed : openAbused);
+
+ EventDispatcher::Dispatch(static_cast<nsIContent*>(this), presContext,
+ &event);
+ }
+
+ return aKeyCausesActivation;
+}
+
+nsGenericHTMLElement*
+HTMLLabelElement::GetLabeledElement() const
+{
+ nsAutoString elementId;
+
+ if (!GetAttr(kNameSpaceID_None, nsGkAtoms::_for, elementId)) {
+ // No @for, so we are a label for our first form control element.
+ // Do a depth-first traversal to look for the first form control element.
+ return GetFirstLabelableDescendant();
+ }
+
+ // We have a @for. The id has to be linked to an element in the same document
+ // and this element should be a labelable form control.
+ //XXXsmaug It is unclear how this should work in case the element is in
+ // Shadow DOM.
+ // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=26365.
+ nsIDocument* doc = GetUncomposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ Element* element = doc->GetElementById(elementId);
+ if (element && element->IsLabelable()) {
+ return static_cast<nsGenericHTMLElement*>(element);
+ }
+
+ return nullptr;
+}
+
+nsGenericHTMLElement*
+HTMLLabelElement::GetFirstLabelableDescendant() const
+{
+ for (nsIContent* cur = nsINode::GetFirstChild(); cur;
+ cur = cur->GetNextNode(this)) {
+ Element* element = cur->IsElement() ? cur->AsElement() : nullptr;
+ if (element && element->IsLabelable()) {
+ return static_cast<nsGenericHTMLElement*>(element);
+ }
+ }
+
+ return nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLLabelElement.h b/dom/html/HTMLLabelElement.h
new file mode 100644
index 000000000..c8385fc53
--- /dev/null
+++ b/dom/html/HTMLLabelElement.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/. */
+
+/**
+ * Declaration of HTML <label> elements.
+ */
+#ifndef HTMLLabelElement_h
+#define HTMLLabelElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIDOMHTMLLabelElement.h"
+
+namespace mozilla {
+class EventChainPostVisitor;
+namespace dom {
+
+class HTMLLabelElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLLabelElement
+{
+public:
+ explicit HTMLLabelElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo),
+ mHandlingEvent(false)
+ {
+ }
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLLabelElement, label)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Element
+ virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override
+ {
+ return true;
+ }
+
+ // nsIDOMHTMLLabelElement
+ NS_DECL_NSIDOMHTMLLABELELEMENT
+
+ HTMLFormElement* GetForm() const;
+ void GetHtmlFor(nsString& aHtmlFor)
+ {
+ GetHTMLAttr(nsGkAtoms::_for, aHtmlFor);
+ }
+ void SetHtmlFor(const nsAString& aHtmlFor, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::_for, aHtmlFor, aError);
+ }
+ nsGenericHTMLElement* GetControl() const
+ {
+ return GetLabeledElement();
+ }
+
+ using nsGenericHTMLElement::Focus;
+ virtual void Focus(mozilla::ErrorResult& aError) override;
+
+ virtual bool IsDisabled() const override { return false; }
+
+ // nsIContent
+ virtual nsresult PostHandleEvent(
+ EventChainPostVisitor& aVisitor) override;
+ virtual bool PerformAccesskey(bool aKeyCausesActivation,
+ bool aIsTrustedEvent) override;
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ nsGenericHTMLElement* GetLabeledElement() const;
+protected:
+ virtual ~HTMLLabelElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ nsGenericHTMLElement* GetFirstLabelableDescendant() const;
+
+ // XXX It would be nice if we could use an event flag instead.
+ bool mHandlingEvent;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* HTMLLabelElement_h */
diff --git a/dom/html/HTMLLegendElement.cpp b/dom/html/HTMLLegendElement.cpp
new file mode 100644
index 000000000..1a593d769
--- /dev/null
+++ b/dom/html/HTMLLegendElement.cpp
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/HTMLLegendElement.h"
+#include "mozilla/dom/HTMLLegendElementBinding.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "nsFocusManager.h"
+#include "nsIFrame.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Legend)
+
+namespace mozilla {
+namespace dom {
+
+
+HTMLLegendElement::~HTMLLegendElement()
+{
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLLegendElement)
+
+nsIContent*
+HTMLLegendElement::GetFieldSet() const
+{
+ nsIContent* parent = GetParent();
+
+ if (parent && parent->IsHTMLElement(nsGkAtoms::fieldset)) {
+ return parent;
+ }
+
+ return nullptr;
+}
+
+bool
+HTMLLegendElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ // this contains center, because IE4 does
+ static const nsAttrValue::EnumTable kAlignTable[] = {
+ { "left", NS_STYLE_TEXT_ALIGN_LEFT },
+ { "right", NS_STYLE_TEXT_ALIGN_RIGHT },
+ { "center", NS_STYLE_TEXT_ALIGN_CENTER },
+ { "bottom", NS_STYLE_VERTICAL_ALIGN_BOTTOM },
+ { "top", NS_STYLE_VERTICAL_ALIGN_TOP },
+ { nullptr, 0 }
+ };
+
+ if (aAttribute == nsGkAtoms::align && aNamespaceID == kNameSpaceID_None) {
+ return aResult.ParseEnumValue(aValue, kAlignTable, false);
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+nsChangeHint
+HTMLLegendElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const
+{
+ nsChangeHint retval =
+ nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
+ if (aAttribute == nsGkAtoms::align) {
+ retval |= NS_STYLE_HINT_REFLOW;
+ }
+ return retval;
+}
+
+nsresult
+HTMLLegendElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify)
+{
+ return nsGenericHTMLElement::SetAttr(aNameSpaceID, aAttribute,
+ aPrefix, aValue, aNotify);
+}
+nsresult
+HTMLLegendElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify)
+{
+ return nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttribute, aNotify);
+}
+
+nsresult
+HTMLLegendElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ return nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+}
+
+void
+HTMLLegendElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+void
+HTMLLegendElement::Focus(ErrorResult& aError)
+{
+ nsIFrame* frame = GetPrimaryFrame();
+ if (!frame) {
+ return;
+ }
+
+ int32_t tabIndex;
+ if (frame->IsFocusable(&tabIndex, false)) {
+ nsGenericHTMLElement::Focus(aError);
+ return;
+ }
+
+ // If the legend isn't focusable, focus whatever is focusable following
+ // the legend instead, bug 81481.
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return;
+ }
+
+ nsCOMPtr<nsIDOMElement> result;
+ aError = fm->MoveFocus(nullptr, this, nsIFocusManager::MOVEFOCUS_FORWARD,
+ nsIFocusManager::FLAG_NOPARENTFRAME,
+ getter_AddRefs(result));
+}
+
+bool
+HTMLLegendElement::PerformAccesskey(bool aKeyCausesActivation,
+ bool aIsTrustedEvent)
+{
+ // just use the same behaviour as the focus method
+ ErrorResult rv;
+ Focus(rv);
+ return NS_SUCCEEDED(rv.StealNSResult());
+}
+
+already_AddRefed<HTMLFormElement>
+HTMLLegendElement::GetForm()
+{
+ Element* form = GetFormElement();
+ MOZ_ASSERT_IF(form, form->IsHTMLElement(nsGkAtoms::form));
+ RefPtr<HTMLFormElement> ret = static_cast<HTMLFormElement*>(form);
+ return ret.forget();
+}
+
+JSObject*
+HTMLLegendElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLLegendElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLLegendElement.h b/dom/html/HTMLLegendElement.h
new file mode 100644
index 000000000..e2d71d62d
--- /dev/null
+++ b/dom/html/HTMLLegendElement.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLLegendElement_h
+#define mozilla_dom_HTMLLegendElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "mozilla/dom/HTMLFormElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLLegendElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLLegendElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ }
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLLegendElement, legend)
+
+ using nsGenericHTMLElement::Focus;
+ virtual void Focus(ErrorResult& aError) override;
+
+ virtual bool PerformAccesskey(bool aKeyCausesActivation,
+ bool aIsTrustedEvent) override;
+
+ // nsIContent
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const 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* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify) override;
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify) override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+
+ Element* GetFormElement() const
+ {
+ nsCOMPtr<nsIFormControl> fieldsetControl = do_QueryInterface(GetFieldSet());
+
+ return fieldsetControl ? fieldsetControl->GetFormElement() : nullptr;
+ }
+
+ /**
+ * WebIDL Interface
+ */
+
+ already_AddRefed<HTMLFormElement> GetForm();
+
+ void GetAlign(DOMString& aAlign)
+ {
+ GetHTMLAttr(nsGkAtoms::align, aAlign);
+ }
+
+ void SetAlign(const nsAString& aAlign, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::align, aAlign, aError);
+ }
+
+ nsINode* GetScopeChainParent() const override
+ {
+ Element* form = GetFormElement();
+ return form ? form : nsGenericHTMLElement::GetScopeChainParent();
+ }
+
+protected:
+ virtual ~HTMLLegendElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ /**
+ * Get the fieldset content element that contains this legend.
+ * Returns null if there is no fieldset containing this legend.
+ */
+ nsIContent* GetFieldSet() const;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_HTMLLegendElement_h */
diff --git a/dom/html/HTMLLinkElement.cpp b/dom/html/HTMLLinkElement.cpp
new file mode 100644
index 000000000..8afe767bd
--- /dev/null
+++ b/dom/html/HTMLLinkElement.cpp
@@ -0,0 +1,584 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLLinkElement.h"
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/HTMLLinkElementBinding.h"
+#include "nsContentUtils.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+#include "nsDOMTokenList.h"
+#include "nsIContentInlines.h"
+#include "nsIDocument.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMStyleSheet.h"
+#include "nsINode.h"
+#include "nsIStyleSheetLinkingElement.h"
+#include "nsIURL.h"
+#include "nsPIDOMWindow.h"
+#include "nsReadableUtils.h"
+#include "nsStyleConsts.h"
+#include "nsStyleLinkElement.h"
+#include "nsUnicharUtils.h"
+
+#define LINK_ELEMENT_FLAG_BIT(n_) \
+ NODE_FLAG_BIT(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + (n_))
+
+// Link element specific bits
+enum {
+ // Indicates that a DNS Prefetch has been requested from this Link element.
+ HTML_LINK_DNS_PREFETCH_REQUESTED = LINK_ELEMENT_FLAG_BIT(0),
+
+ // Indicates that a DNS Prefetch was added to the deferral queue
+ HTML_LINK_DNS_PREFETCH_DEFERRED = LINK_ELEMENT_FLAG_BIT(1)
+};
+
+#undef LINK_ELEMENT_FLAG_BIT
+
+ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 2);
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Link)
+
+namespace mozilla {
+namespace dom {
+
+HTMLLinkElement::HTMLLinkElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ , Link(this)
+{
+}
+
+HTMLLinkElement::~HTMLLinkElement()
+{
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLLinkElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLLinkElement,
+ nsGenericHTMLElement)
+ tmp->nsStyleLinkElement::Traverse(cb);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelList)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImportLoader)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLLinkElement,
+ nsGenericHTMLElement)
+ tmp->nsStyleLinkElement::Unlink();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRelList)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mImportLoader)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(HTMLLinkElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLLinkElement, Element)
+
+
+// QueryInterface implementation for HTMLLinkElement
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLLinkElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLLinkElement,
+ nsIDOMHTMLLinkElement,
+ nsIStyleSheetLinkingElement,
+ Link)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLElement)
+
+
+NS_IMPL_ELEMENT_CLONE(HTMLLinkElement)
+
+bool
+HTMLLinkElement::Disabled()
+{
+ StyleSheet* ss = GetSheet();
+ return ss && ss->Disabled();
+}
+
+NS_IMETHODIMP
+HTMLLinkElement::GetMozDisabled(bool* aDisabled)
+{
+ *aDisabled = Disabled();
+ return NS_OK;
+}
+
+void
+HTMLLinkElement::SetDisabled(bool aDisabled)
+{
+ if (StyleSheet* ss = GetSheet()) {
+ ss->SetDisabled(aDisabled);
+ }
+}
+
+NS_IMETHODIMP
+HTMLLinkElement::SetMozDisabled(bool aDisabled)
+{
+ SetDisabled(aDisabled);
+ return NS_OK;
+}
+
+
+NS_IMPL_STRING_ATTR(HTMLLinkElement, Charset, charset)
+NS_IMPL_URI_ATTR(HTMLLinkElement, Href, href)
+NS_IMPL_STRING_ATTR(HTMLLinkElement, Hreflang, hreflang)
+NS_IMPL_STRING_ATTR(HTMLLinkElement, Media, media)
+NS_IMPL_STRING_ATTR(HTMLLinkElement, Rel, rel)
+NS_IMPL_STRING_ATTR(HTMLLinkElement, Rev, rev)
+NS_IMPL_STRING_ATTR(HTMLLinkElement, Target, target)
+NS_IMPL_STRING_ATTR(HTMLLinkElement, Type, type)
+
+void
+HTMLLinkElement::OnDNSPrefetchRequested()
+{
+ UnsetFlags(HTML_LINK_DNS_PREFETCH_DEFERRED);
+ SetFlags(HTML_LINK_DNS_PREFETCH_REQUESTED);
+}
+
+void
+HTMLLinkElement::OnDNSPrefetchDeferred()
+{
+ UnsetFlags(HTML_LINK_DNS_PREFETCH_REQUESTED);
+ SetFlags(HTML_LINK_DNS_PREFETCH_DEFERRED);
+}
+
+bool
+HTMLLinkElement::HasDeferredDNSPrefetchRequest()
+{
+ return HasFlag(HTML_LINK_DNS_PREFETCH_DEFERRED);
+}
+
+nsresult
+HTMLLinkElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ Link::ResetLinkState(false, Link::ElementHasHref());
+
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Link must be inert in ShadowRoot.
+ if (aDocument && !GetContainingShadow()) {
+ aDocument->RegisterPendingLinkUpdate(this);
+ }
+
+ if (IsInComposedDoc()) {
+ TryDNSPrefetchPreconnectOrPrefetch();
+ }
+
+ void (HTMLLinkElement::*update)() = &HTMLLinkElement::UpdateStyleSheetInternal;
+ nsContentUtils::AddScriptRunner(NewRunnableMethod(this, update));
+
+ void (HTMLLinkElement::*updateImport)() = &HTMLLinkElement::UpdateImport;
+ nsContentUtils::AddScriptRunner(NewRunnableMethod(this, updateImport));
+
+ CreateAndDispatchEvent(aDocument, NS_LITERAL_STRING("DOMLinkAdded"));
+
+ return rv;
+}
+
+void
+HTMLLinkElement::LinkAdded()
+{
+ CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkAdded"));
+}
+
+void
+HTMLLinkElement::LinkRemoved()
+{
+ CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkRemoved"));
+}
+
+void
+HTMLLinkElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ // Cancel any DNS prefetches
+ // Note: Must come before ResetLinkState. If called after, it will recreate
+ // mCachedURI based on data that is invalid - due to a call to GetHostname.
+ CancelDNSPrefetch(HTML_LINK_DNS_PREFETCH_DEFERRED,
+ HTML_LINK_DNS_PREFETCH_REQUESTED);
+ CancelPrefetch();
+
+ // If this link is ever reinserted into a document, it might
+ // be under a different xml:base, so forget the cached state now.
+ Link::ResetLinkState(false, Link::ElementHasHref());
+
+ // If this is reinserted back into the document it will not be
+ // from the parser.
+ nsCOMPtr<nsIDocument> oldDoc = GetUncomposedDoc();
+
+ // Check for a ShadowRoot because link elements are inert in a
+ // ShadowRoot.
+ ShadowRoot* oldShadowRoot = GetBindingParent() ?
+ GetBindingParent()->GetShadowRoot() : nullptr;
+
+ OwnerDoc()->UnregisterPendingLinkUpdate(this);
+
+ CreateAndDispatchEvent(oldDoc, NS_LITERAL_STRING("DOMLinkRemoved"));
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+
+ UpdateStyleSheetInternal(oldDoc, oldShadowRoot);
+ UpdateImport();
+}
+
+bool
+HTMLLinkElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::crossorigin) {
+ ParseCORSValue(aValue, aResult);
+ return true;
+ }
+
+ if (aAttribute == nsGkAtoms::sizes) {
+ aResult.ParseAtomArray(aValue);
+ return true;
+ }
+
+ if (aAttribute == nsGkAtoms::integrity) {
+ aResult.ParseStringOrAtom(aValue);
+ return true;
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLLinkElement::CreateAndDispatchEvent(nsIDocument* aDoc,
+ const nsAString& aEventName)
+{
+ if (!aDoc)
+ return;
+
+ // In the unlikely case that both rev is specified *and* rel=stylesheet,
+ // this code will cause the event to fire, on the principle that maybe the
+ // page really does want to specify that its author is a stylesheet. Since
+ // this should never actually happen and the performance hit is minimal,
+ // doing the "right" thing costs virtually nothing here, even if it doesn't
+ // make much sense.
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::_empty, &nsGkAtoms::stylesheet, nullptr};
+
+ if (!nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
+ nsGkAtoms::rev) &&
+ FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::rel,
+ strings, eIgnoreCase) != ATTR_VALUE_NO_MATCH)
+ return;
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, aEventName, true, true);
+ // Always run async in order to avoid running script when the content
+ // sink isn't expecting it.
+ asyncDispatcher->PostDOMEvent();
+}
+
+void
+HTMLLinkElement::UpdateImport()
+{
+ // 1. link node should be attached to the document.
+ nsCOMPtr<nsIDocument> doc = GetUncomposedDoc();
+ if (!doc) {
+ // We might have been just removed from the document, so
+ // let's remove ourself from the list of link nodes of
+ // the import and reset mImportLoader.
+ if (mImportLoader) {
+ mImportLoader->RemoveLinkElement(this);
+ mImportLoader = nullptr;
+ }
+ return;
+ }
+
+ // 2. rel type should be import.
+ nsAutoString rel;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::rel, rel);
+ uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(rel, NodePrincipal());
+ if (!(linkTypes & eHTMLIMPORT)) {
+ mImportLoader = nullptr;
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri = GetHrefURI();
+ if (!uri) {
+ mImportLoader = nullptr;
+ return;
+ }
+
+ if (!nsStyleLinkElement::IsImportEnabled()) {
+ // For now imports are hidden behind a pref...
+ return;
+ }
+
+ RefPtr<ImportManager> manager = doc->ImportManager();
+ MOZ_ASSERT(manager, "ImportManager should be created lazily when needed");
+
+ {
+ // The load even might fire sooner than we could set mImportLoader so
+ // we must use async event and a scriptBlocker here.
+ nsAutoScriptBlocker scriptBlocker;
+ // CORS check will happen at the start of the load.
+ mImportLoader = manager->Get(uri, this, doc);
+ }
+}
+
+nsresult
+HTMLLinkElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aName == nsGkAtoms::href || aName == nsGkAtoms::rel)) {
+ CancelDNSPrefetch(HTML_LINK_DNS_PREFETCH_DEFERRED,
+ HTML_LINK_DNS_PREFETCH_REQUESTED);
+ CancelPrefetch();
+ }
+
+ return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+nsresult
+HTMLLinkElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ // It's safe to call ResetLinkState here because our new attr value has
+ // already been set or unset. ResetLinkState needs the updated attribute
+ // value because notifying the document that content states have changed will
+ // call IntrinsicState, which will try to get updated information about the
+ // visitedness from Link.
+ if (aName == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
+ bool hasHref = aValue;
+ Link::ResetLinkState(!!aNotify, hasHref);
+ if (IsInUncomposedDoc()) {
+ CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkChanged"));
+ }
+ }
+
+ if (aValue) {
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aName == nsGkAtoms::href ||
+ aName == nsGkAtoms::rel ||
+ aName == nsGkAtoms::title ||
+ aName == nsGkAtoms::media ||
+ aName == nsGkAtoms::type)) {
+ bool dropSheet = false;
+ if (aName == nsGkAtoms::rel) {
+ nsAutoString value;
+ aValue->ToString(value);
+ uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(value,
+ NodePrincipal());
+ if (GetSheet()) {
+ dropSheet = !(linkTypes & nsStyleLinkElement::eSTYLESHEET);
+ } else if (linkTypes & eHTMLIMPORT) {
+ UpdateImport();
+ }
+ }
+
+ if (aName == nsGkAtoms::href) {
+ UpdateImport();
+ }
+
+ if ((aName == nsGkAtoms::rel || aName == nsGkAtoms::href) &&
+ IsInComposedDoc()) {
+ TryDNSPrefetchPreconnectOrPrefetch();
+ }
+
+ UpdateStyleSheetInternal(nullptr, nullptr,
+ dropSheet ||
+ (aName == nsGkAtoms::title ||
+ aName == nsGkAtoms::media ||
+ aName == nsGkAtoms::type));
+ }
+ } else {
+ // Since removing href or rel makes us no longer link to a
+ // stylesheet, force updates for those too.
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aName == nsGkAtoms::href ||
+ aName == nsGkAtoms::rel ||
+ aName == nsGkAtoms::title ||
+ aName == nsGkAtoms::media ||
+ aName == nsGkAtoms::type) {
+ UpdateStyleSheetInternal(nullptr, nullptr, true);
+ }
+ if (aName == nsGkAtoms::href ||
+ aName == nsGkAtoms::rel) {
+ UpdateImport();
+ }
+ }
+ }
+
+ return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
+ aNotify);
+}
+
+nsresult
+HTMLLinkElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ return PreHandleEventForAnchors(aVisitor);
+}
+
+nsresult
+HTMLLinkElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ return PostHandleEventForAnchors(aVisitor);
+}
+
+bool
+HTMLLinkElement::IsLink(nsIURI** aURI) const
+{
+ return IsHTMLLink(aURI);
+}
+
+void
+HTMLLinkElement::GetLinkTarget(nsAString& aTarget)
+{
+ GetAttr(kNameSpaceID_None, nsGkAtoms::target, aTarget);
+ if (aTarget.IsEmpty()) {
+ GetBaseTarget(aTarget);
+ }
+}
+
+static const DOMTokenListSupportedToken sSupportedRelValues[] = {
+ // Keep this in sync with ToLinkMask in nsStyleLinkElement.cpp.
+ // "import" must come first because it's conditional.
+ "import",
+ "prefetch",
+ "dns-prefetch",
+ "stylesheet",
+ "next",
+ "alternate",
+ "preconnect",
+ "icon",
+ "search",
+ nullptr
+};
+
+nsDOMTokenList*
+HTMLLinkElement::RelList()
+{
+ if (!mRelList) {
+ const DOMTokenListSupportedTokenArray relValues =
+ nsStyleLinkElement::IsImportEnabled() ?
+ sSupportedRelValues : &sSupportedRelValues[1];
+
+ mRelList = new nsDOMTokenList(this, nsGkAtoms::rel, relValues);
+ }
+ return mRelList;
+}
+
+already_AddRefed<nsIURI>
+HTMLLinkElement::GetHrefURI() const
+{
+ return GetHrefURIForAnchors();
+}
+
+already_AddRefed<nsIURI>
+HTMLLinkElement::GetStyleSheetURL(bool* aIsInline)
+{
+ *aIsInline = false;
+ nsAutoString href;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
+ if (href.IsEmpty()) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIURI> uri = Link::GetURI();
+ return uri.forget();
+}
+
+void
+HTMLLinkElement::GetStyleSheetInfo(nsAString& aTitle,
+ nsAString& aType,
+ nsAString& aMedia,
+ bool* aIsScoped,
+ bool* aIsAlternate)
+{
+ aTitle.Truncate();
+ aType.Truncate();
+ aMedia.Truncate();
+ *aIsScoped = false;
+ *aIsAlternate = false;
+
+ nsAutoString rel;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::rel, rel);
+ uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(rel, NodePrincipal());
+ // Is it a stylesheet link?
+ if (!(linkTypes & nsStyleLinkElement::eSTYLESHEET)) {
+ return;
+ }
+
+ nsAutoString title;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::title, title);
+ title.CompressWhitespace();
+ aTitle.Assign(title);
+
+ // If alternate, does it have title?
+ if (linkTypes & nsStyleLinkElement::eALTERNATE) {
+ if (aTitle.IsEmpty()) { // alternates must have title
+ return;
+ } else {
+ *aIsAlternate = true;
+ }
+ }
+
+ GetAttr(kNameSpaceID_None, nsGkAtoms::media, aMedia);
+ // The HTML5 spec is formulated in terms of the CSSOM spec, which specifies
+ // that media queries should be ASCII lowercased during serialization.
+ nsContentUtils::ASCIIToLower(aMedia);
+
+ nsAutoString mimeType;
+ nsAutoString notUsed;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::type, aType);
+ nsContentUtils::SplitMimeType(aType, mimeType, notUsed);
+ if (!mimeType.IsEmpty() && !mimeType.LowerCaseEqualsLiteral("text/css")) {
+ return;
+ }
+
+ // If we get here we assume that we're loading a css file, so set the
+ // type to 'text/css'
+ aType.AssignLiteral("text/css");
+
+ return;
+}
+
+CORSMode
+HTMLLinkElement::GetCORSMode() const
+{
+ return AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin));
+}
+
+EventStates
+HTMLLinkElement::IntrinsicState() const
+{
+ return Link::LinkState() | nsGenericHTMLElement::IntrinsicState();
+}
+
+size_t
+HTMLLinkElement::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ return nsGenericHTMLElement::SizeOfExcludingThis(aMallocSizeOf) +
+ Link::SizeOfExcludingThis(aMallocSizeOf);
+}
+
+JSObject*
+HTMLLinkElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLLinkElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<nsIDocument>
+HTMLLinkElement::GetImport()
+{
+ return mImportLoader ? RefPtr<nsIDocument>(mImportLoader->GetImport()).forget() : nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLLinkElement.h b/dom/html/HTMLLinkElement.h
new file mode 100644
index 000000000..421b149e9
--- /dev/null
+++ b/dom/html/HTMLLinkElement.h
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_HTMLLinkElement_h
+#define mozilla_dom_HTMLLinkElement_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Link.h"
+#include "ImportManager.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIDOMHTMLLinkElement.h"
+#include "nsStyleLinkElement.h"
+
+namespace mozilla {
+class EventChainPostVisitor;
+class EventChainPreVisitor;
+namespace dom {
+
+class HTMLLinkElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLLinkElement,
+ public nsStyleLinkElement,
+ public Link
+{
+public:
+ explicit HTMLLinkElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // CC
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLLinkElement,
+ nsGenericHTMLElement)
+
+ // nsIDOMHTMLLinkElement
+ NS_DECL_NSIDOMHTMLLINKELEMENT
+
+ // DOM memory reporter participant
+ NS_DECL_SIZEOF_EXCLUDING_THIS
+
+ void LinkAdded();
+ void LinkRemoved();
+
+ void UpdateImport();
+
+ // nsIDOMEventTarget
+ virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) override;
+ virtual nsresult PostHandleEvent(
+ EventChainPostVisitor& aVisitor) override;
+
+ // nsINode
+ virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // nsIContent
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+ virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify) override;
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue,
+ bool aNotify) override;
+ virtual bool IsLink(nsIURI** aURI) const override;
+ virtual already_AddRefed<nsIURI> GetHrefURI() const override;
+
+ // Element
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ virtual void GetLinkTarget(nsAString& aTarget) override;
+ virtual EventStates IntrinsicState() const override;
+
+ void CreateAndDispatchEvent(nsIDocument* aDoc, const nsAString& aEventName);
+
+ virtual void OnDNSPrefetchDeferred() override;
+ virtual void OnDNSPrefetchRequested() override;
+ virtual bool HasDeferredDNSPrefetchRequest() override;
+
+ // WebIDL
+ bool Disabled();
+ void SetDisabled(bool aDisabled);
+ // XPCOM GetHref is fine.
+ void SetHref(const nsAString& aHref, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::href, aHref, aRv);
+ }
+ void GetCrossOrigin(nsAString& aResult)
+ {
+ // Null for both missing and invalid defaults is ok, since we
+ // always parse to an enum value, so we don't need an invalid
+ // default, and we _want_ the missing default to be null.
+ GetEnumAttr(nsGkAtoms::crossorigin, nullptr, aResult);
+ }
+ void SetCrossOrigin(const nsAString& aCrossOrigin, ErrorResult& aError)
+ {
+ SetOrRemoveNullableStringAttr(nsGkAtoms::crossorigin, aCrossOrigin, aError);
+ }
+ // XPCOM GetRel is fine.
+ void SetRel(const nsAString& aRel, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::rel, aRel, aRv);
+ }
+ nsDOMTokenList* RelList();
+ // XPCOM GetMedia is fine.
+ void SetMedia(const nsAString& aMedia, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::media, aMedia, aRv);
+ }
+ // XPCOM GetHreflang is fine.
+ void SetHreflang(const nsAString& aHreflang, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::hreflang, aHreflang, aRv);
+ }
+ nsDOMTokenList* Sizes()
+ {
+ return GetTokenList(nsGkAtoms::sizes);
+ }
+ // XPCOM GetType is fine.
+ void SetType(const nsAString& aType, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::type, aType, aRv);
+ }
+ // XPCOM GetCharset is fine.
+ void SetCharset(const nsAString& aCharset, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::charset, aCharset, aRv);
+ }
+ // XPCOM GetRev is fine.
+ void SetRev(const nsAString& aRev, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::rev, aRev, aRv);
+ }
+ // XPCOM GetTarget is fine.
+ void SetTarget(const nsAString& aTarget, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::target, aTarget, aRv);
+ }
+ void GetIntegrity(nsAString& aIntegrity) const
+ {
+ GetHTMLAttr(nsGkAtoms::integrity, aIntegrity);
+ }
+ void SetIntegrity(const nsAString& aIntegrity, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::integrity, aIntegrity, aRv);
+ }
+ void SetReferrerPolicy(const nsAString& aReferrer, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::referrerpolicy, aReferrer, aError);
+ }
+ void GetReferrerPolicy(nsAString& aReferrer)
+ {
+ GetEnumAttr(nsGkAtoms::referrerpolicy, EmptyCString().get(), aReferrer);
+ }
+ mozilla::net::ReferrerPolicy GetLinkReferrerPolicy() override
+ {
+ return GetReferrerPolicyAsEnum();
+ }
+
+ already_AddRefed<nsIDocument> GetImport();
+ already_AddRefed<ImportLoader> GetImportLoader()
+ {
+ return RefPtr<ImportLoader>(mImportLoader).forget();
+ }
+
+ virtual CORSMode GetCORSMode() const override;
+protected:
+ virtual ~HTMLLinkElement();
+
+ // nsStyleLinkElement
+ virtual already_AddRefed<nsIURI> GetStyleSheetURL(bool* aIsInline) override;
+ virtual void GetStyleSheetInfo(nsAString& aTitle,
+ nsAString& aType,
+ nsAString& aMedia,
+ bool* aIsScoped,
+ bool* aIsAlternate) override;
+protected:
+ RefPtr<nsDOMTokenList> mRelList;
+
+private:
+ RefPtr<ImportLoader> mImportLoader;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLLinkElement_h
diff --git a/dom/html/HTMLMapElement.cpp b/dom/html/HTMLMapElement.cpp
new file mode 100644
index 000000000..36a0f410b
--- /dev/null
+++ b/dom/html/HTMLMapElement.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/HTMLMapElement.h"
+#include "mozilla/dom/HTMLMapElementBinding.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsContentList.h"
+#include "nsCOMPtr.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Map)
+
+namespace mozilla {
+namespace dom {
+
+HTMLMapElement::HTMLMapElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+{
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMapElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMapElement,
+ nsGenericHTMLElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAreas)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_ADDREF_INHERITED(HTMLMapElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLMapElement, Element)
+
+
+// QueryInterface implementation for HTMLMapElement
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLMapElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLMapElement, nsIDOMHTMLMapElement)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLMapElement)
+
+
+nsIHTMLCollection*
+HTMLMapElement::Areas()
+{
+ if (!mAreas) {
+ // Not using NS_GetContentList because this should not be cached
+ mAreas = new nsContentList(this,
+ kNameSpaceID_XHTML,
+ nsGkAtoms::area,
+ nsGkAtoms::area,
+ false);
+ }
+
+ return mAreas;
+}
+
+NS_IMETHODIMP
+HTMLMapElement::GetAreas(nsIDOMHTMLCollection** aAreas)
+{
+ NS_ENSURE_ARG_POINTER(aAreas);
+ NS_ADDREF(*aAreas = Areas());
+ return NS_OK;
+}
+
+
+NS_IMPL_STRING_ATTR(HTMLMapElement, Name, name)
+
+
+JSObject*
+HTMLMapElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLMapElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLMapElement.h b/dom/html/HTMLMapElement.h
new file mode 100644
index 000000000..a15bd6d23
--- /dev/null
+++ b/dom/html/HTMLMapElement.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLMapElement_h
+#define mozilla_dom_HTMLMapElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIDOMHTMLMapElement.h"
+#include "nsGkAtoms.h"
+
+class nsContentList;
+
+namespace mozilla {
+namespace dom {
+
+class HTMLMapElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLMapElement
+{
+public:
+ explicit HTMLMapElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLMapElement
+ NS_DECL_NSIDOMHTMLMAPELEMENT
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(HTMLMapElement,
+ nsGenericHTMLElement)
+
+ // XPCOM GetName is fine.
+ void SetName(const nsAString& aName, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::name, aName, aError);
+ }
+ nsIHTMLCollection* Areas();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+ ~HTMLMapElement() {}
+
+ RefPtr<nsContentList> mAreas;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLMapElement_h
diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp
new file mode 100644
index 000000000..b64761270
--- /dev/null
+++ b/dom/html/HTMLMediaElement.cpp
@@ -0,0 +1,6883 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLMediaElement.h"
+#include "mozilla/dom/HTMLMediaElementBinding.h"
+#include "mozilla/dom/HTMLSourceElement.h"
+#include "mozilla/dom/ElementInlines.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/dom/MediaEncryptedEvent.h"
+
+#include "base/basictypes.h"
+#include "nsIDOMHTMLMediaElement.h"
+#include "nsIDOMHTMLSourceElement.h"
+#include "TimeRanges.h"
+#include "nsGenericHTMLElement.h"
+#include "nsAttrValueInlines.h"
+#include "nsPresContext.h"
+#include "nsIPresShell.h"
+#include "nsGkAtoms.h"
+#include "nsSize.h"
+#include "nsIFrame.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDocShell.h"
+#include "nsError.h"
+#include "nsNodeInfoManager.h"
+#include "nsNetUtil.h"
+#include "nsXPCOMStrings.h"
+#include "xpcpublic.h"
+#include "nsThreadUtils.h"
+#include "nsIThreadInternal.h"
+#include "nsContentUtils.h"
+#include "nsIRequest.h"
+#include "nsQueryObject.h"
+#include "nsIObserverService.h"
+#include "nsISupportsPrimitives.h"
+
+#include "nsIScriptSecurityManager.h"
+#include "nsIXPConnect.h"
+#include "jsapi.h"
+
+#include "nsITimer.h"
+
+#include "MediaError.h"
+#include "MediaDecoder.h"
+#include "MediaPrefs.h"
+#include "MediaResource.h"
+
+#include "nsICategoryManager.h"
+#include "nsIContentPolicy.h"
+#include "nsContentPolicyUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsICachingChannel.h"
+#include "nsLayoutUtils.h"
+#include "nsVideoFrame.h"
+#include "Layers.h"
+#include <limits>
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsMediaFragmentURIParser.h"
+#include "nsURIHashKey.h"
+#include "nsJSUtils.h"
+#include "MediaStreamGraph.h"
+#include "nsIScriptError.h"
+#include "nsHostObjectProtocolHandler.h"
+#include "mozilla/dom/MediaSource.h"
+#include "MediaMetadataManager.h"
+#include "MediaSourceDecoder.h"
+#include "MediaStreamListener.h"
+#include "DOMMediaStream.h"
+#include "AudioStreamTrack.h"
+#include "VideoStreamTrack.h"
+#include "MediaTrackList.h"
+#include "MediaStreamError.h"
+#include "VideoFrameContainer.h"
+
+#include "AudioChannelService.h"
+
+#include "mozilla/dom/power/PowerManagerService.h"
+#include "mozilla/dom/WakeLock.h"
+
+#include "mozilla/dom/AudioTrack.h"
+#include "mozilla/dom/AudioTrackList.h"
+#include "mozilla/dom/MediaErrorBinding.h"
+#include "mozilla/dom/VideoTrack.h"
+#include "mozilla/dom/VideoTrackList.h"
+#include "mozilla/dom/TextTrack.h"
+#include "nsIContentPolicy.h"
+#include "mozilla/Telemetry.h"
+#include "DecoderDoctorDiagnostics.h"
+#include "DecoderTraits.h"
+#include "MediaContentType.h"
+
+#include "ImageContainer.h"
+#include "nsRange.h"
+#include <algorithm>
+#include <cmath>
+
+static mozilla::LazyLogModule gMediaElementLog("nsMediaElement");
+static mozilla::LazyLogModule gMediaElementEventsLog("nsMediaElementEvents");
+
+#define LOG(type, msg) MOZ_LOG(gMediaElementLog, type, msg)
+#define LOG_EVENT(type, msg) MOZ_LOG(gMediaElementEventsLog, type, msg)
+
+#include "nsIContentSecurityPolicy.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/FloatingPoint.h"
+
+#include "nsIPermissionManager.h"
+#include "nsDocShell.h"
+
+#include "mozilla/EventStateManager.h"
+
+#include "mozilla/dom/HTMLVideoElement.h"
+#include "mozilla/dom/VideoPlaybackQuality.h"
+
+using namespace mozilla::layers;
+using mozilla::net::nsMediaFragmentURIParser;
+
+class MOZ_STACK_CLASS AutoNotifyAudioChannelAgent
+{
+ RefPtr<mozilla::dom::HTMLMediaElement> mElement;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
+public:
+ explicit AutoNotifyAudioChannelAgent(mozilla::dom::HTMLMediaElement* aElement
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : mElement(aElement)
+ {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ }
+
+ ~AutoNotifyAudioChannelAgent()
+ {
+ mElement->UpdateAudioChannelPlayingState();
+ }
+};
+
+namespace mozilla {
+namespace dom {
+
+// Number of milliseconds between progress events as defined by spec
+static const uint32_t PROGRESS_MS = 350;
+
+// Number of milliseconds of no data before a stall event is fired as defined by spec
+static const uint32_t STALL_MS = 3000;
+
+// Used by AudioChannel for suppresssing the volume to this ratio.
+#define FADED_VOLUME_RATIO 0.25
+
+// These constants are arbitrary
+// Minimum playbackRate for a media
+static const double MIN_PLAYBACKRATE = 0.25;
+// Maximum playbackRate for a media
+static const double MAX_PLAYBACKRATE = 5.0;
+// These are the limits beyonds which SoundTouch does not perform too well and when
+// speech is hard to understand anyway.
+// Threshold above which audio is muted
+static const double THRESHOLD_HIGH_PLAYBACKRATE_AUDIO = 4.0;
+// Threshold under which audio is muted
+static const double THRESHOLD_LOW_PLAYBACKRATE_AUDIO = 0.5;
+
+// Media error values. These need to match the ones in MediaError.webidl.
+static const unsigned short MEDIA_ERR_ABORTED = 1;
+static const unsigned short MEDIA_ERR_NETWORK = 2;
+static const unsigned short MEDIA_ERR_DECODE = 3;
+static const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
+
+// Under certain conditions there may be no-one holding references to
+// a media element from script, DOM parent, etc, but the element may still
+// fire meaningful events in the future so we can't destroy it yet:
+// 1) If the element is delaying the load event (or would be, if it were
+// in a document), then events up to loadeddata or error could be fired,
+// so we need to stay alive.
+// 2) If the element is not paused and playback has not ended, then
+// we will (or might) play, sending timeupdate and ended events and possibly
+// audio output, so we need to stay alive.
+// 3) if the element is seeking then we will fire seeking events and possibly
+// start playing afterward, so we need to stay alive.
+// 4) If autoplay could start playback in this element (if we got enough data),
+// then we need to stay alive.
+// 5) if the element is currently loading, not suspended, and its source is
+// not a MediaSource, then script might be waiting for progress events or a
+// 'stalled' or 'suspend' event, so we need to stay alive.
+// If we're already suspended then (all other conditions being met),
+// it's OK to just disappear without firing any more events,
+// since we have the freedom to remain suspended indefinitely. Note
+// that we could use this 'suspended' loophole to garbage-collect a suspended
+// element in case 4 even if it had 'autoplay' set, but we choose not to.
+// If someone throws away all references to a loading 'autoplay' element
+// sound should still eventually play.
+// 6) If the source is a MediaSource, most loading events will not fire unless
+// appendBuffer() is called on a SourceBuffer, in which case something is
+// already referencing the SourceBuffer, which keeps the associated media
+// element alive. Further, a MediaSource will never time out the resource
+// fetch, and so should not keep the media element alive if it is
+// unreferenced. A pending 'stalled' event keeps the media element alive.
+//
+// Media elements owned by inactive documents (i.e. documents not contained in any
+// document viewer) should never hold a self-reference because none of the
+// above conditions are allowed: the element will stop loading and playing
+// and never resume loading or playing unless its owner document changes to
+// an active document (which can only happen if there is an external reference
+// to the element).
+// Media elements with no owner doc should be able to hold a self-reference.
+// Something native must have created the element and may expect it to
+// stay alive to play.
+
+// It's very important that any change in state which could change the value of
+// needSelfReference in AddRemoveSelfReference be followed by a call to
+// AddRemoveSelfReference before this element could die!
+// It's especially important if needSelfReference would change to 'true',
+// since if we neglect to add a self-reference, this element might be
+// garbage collected while there are still event listeners that should
+// receive events. If we neglect to remove the self-reference then the element
+// just lives longer than it needs to.
+
+class nsMediaEvent : public Runnable
+{
+public:
+
+ explicit nsMediaEvent(HTMLMediaElement* aElement) :
+ mElement(aElement),
+ mLoadID(mElement->GetCurrentLoadID()) {}
+ ~nsMediaEvent() {}
+
+ NS_IMETHOD Run() = 0;
+
+protected:
+ bool IsCancelled() {
+ return mElement->GetCurrentLoadID() != mLoadID;
+ }
+
+ RefPtr<HTMLMediaElement> mElement;
+ uint32_t mLoadID;
+};
+
+class HTMLMediaElement::nsAsyncEventRunner : public nsMediaEvent
+{
+private:
+ nsString mName;
+
+public:
+ nsAsyncEventRunner(const nsAString& aName, HTMLMediaElement* aElement) :
+ nsMediaEvent(aElement), mName(aName)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ // Silently cancel if our load has been cancelled.
+ if (IsCancelled())
+ return NS_OK;
+
+ return mElement->DispatchEvent(mName);
+ }
+};
+
+class nsSourceErrorEventRunner : public nsMediaEvent
+{
+private:
+ nsCOMPtr<nsIContent> mSource;
+public:
+ nsSourceErrorEventRunner(HTMLMediaElement* aElement,
+ nsIContent* aSource)
+ : nsMediaEvent(aElement),
+ mSource(aSource)
+ {
+ }
+
+ NS_IMETHOD Run() override {
+ // Silently cancel if our load has been cancelled.
+ if (IsCancelled())
+ return NS_OK;
+ LOG_EVENT(LogLevel::Debug, ("%p Dispatching simple event source error", mElement.get()));
+ return nsContentUtils::DispatchTrustedEvent(mElement->OwnerDoc(),
+ mSource,
+ NS_LITERAL_STRING("error"),
+ false,
+ false);
+ }
+};
+
+/**
+ * This listener observes the first video frame to arrive with a non-empty size,
+ * and calls HTMLMediaElement::UpdateInitialMediaSize() with that size.
+ */
+class HTMLMediaElement::StreamSizeListener : public DirectMediaStreamTrackListener {
+public:
+ explicit StreamSizeListener(HTMLMediaElement* aElement) :
+ mElement(aElement),
+ mInitialSizeFound(false)
+ {}
+
+ void Forget() { mElement = nullptr; }
+
+ void ReceivedSize(gfx::IntSize aSize)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mElement) {
+ return;
+ }
+
+ RefPtr<HTMLMediaElement> deathGrip = mElement;
+ deathGrip->UpdateInitialMediaSize(aSize);
+ }
+
+ void NotifyRealtimeTrackData(MediaStreamGraph* aGraph,
+ StreamTime aTrackOffset,
+ const MediaSegment& aMedia) override
+ {
+ if (mInitialSizeFound) {
+ return;
+ }
+
+ if (aMedia.GetType() != MediaSegment::VIDEO) {
+ MOZ_ASSERT(false, "Should only lock on to a video track");
+ return;
+ }
+
+ const VideoSegment& video = static_cast<const VideoSegment&>(aMedia);
+ for (VideoSegment::ConstChunkIterator c(video); !c.IsEnded(); c.Next()) {
+ if (c->mFrame.GetIntrinsicSize() != gfx::IntSize(0,0)) {
+ mInitialSizeFound = true;
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod<gfx::IntSize>(this, &StreamSizeListener::ReceivedSize,
+ c->mFrame.GetIntrinsicSize());
+ // This is fine to dispatch straight to main thread (instead of via
+ // ...AfterStreamUpdate()) since it reflects state of the element,
+ // not the stream. Events reflecting stream or track state should be
+ // dispatched so their order is preserved.
+ NS_DispatchToMainThread(event.forget());
+ return;
+ }
+ }
+ }
+
+private:
+ // These fields may only be accessed on the main thread
+ HTMLMediaElement* mElement;
+
+ // These fields may only be accessed on the MSG's appending thread.
+ // (this is a direct listener so we get called by whoever is producing
+ // this track's data)
+ bool mInitialSizeFound;
+};
+
+/**
+ * There is a reference cycle involving this class: MediaLoadListener
+ * holds a reference to the HTMLMediaElement, which holds a reference
+ * to an nsIChannel, which holds a reference to this listener.
+ * We break the reference cycle in OnStartRequest by clearing mElement.
+ */
+class HTMLMediaElement::MediaLoadListener final : public nsIStreamListener,
+ public nsIChannelEventSink,
+ public nsIInterfaceRequestor,
+ public nsIObserver
+{
+ ~MediaLoadListener() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+public:
+ explicit MediaLoadListener(HTMLMediaElement* aElement)
+ : mElement(aElement),
+ mLoadID(aElement->GetCurrentLoadID())
+ {
+ MOZ_ASSERT(mElement, "Must pass an element to call back");
+ }
+
+private:
+ RefPtr<HTMLMediaElement> mElement;
+ nsCOMPtr<nsIStreamListener> mNextListener;
+ const uint32_t mLoadID;
+};
+
+NS_IMPL_ISUPPORTS(HTMLMediaElement::MediaLoadListener, nsIRequestObserver,
+ nsIStreamListener, nsIChannelEventSink,
+ nsIInterfaceRequestor, nsIObserver)
+
+NS_IMETHODIMP
+HTMLMediaElement::MediaLoadListener::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ nsContentUtils::UnregisterShutdownObserver(this);
+
+ // Clear mElement to break cycle so we don't leak on shutdown
+ mElement = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLMediaElement::MediaLoadListener::OnStartRequest(nsIRequest* aRequest,
+ nsISupports* aContext)
+{
+ nsContentUtils::UnregisterShutdownObserver(this);
+
+ if (!mElement) {
+ // We've been notified by the shutdown observer, and are shutting down.
+ return NS_BINDING_ABORTED;
+ }
+
+ // The element is only needed until we've had a chance to call
+ // InitializeDecoderForChannel. So make sure mElement is cleared here.
+ RefPtr<HTMLMediaElement> element;
+ element.swap(mElement);
+
+ if (mLoadID != element->GetCurrentLoadID()) {
+ // The channel has been cancelled before we had a chance to create
+ // a decoder. Abort, don't dispatch an "error" event, as the new load
+ // may not be in an error state.
+ return NS_BINDING_ABORTED;
+ }
+
+ // Don't continue to load if the request failed or has been canceled.
+ nsresult status;
+ nsresult rv = aRequest->GetStatus(&status);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (NS_FAILED(status)) {
+ if (element) {
+ // Handle media not loading error because source was a tracking URL.
+ // We make a note of this media node by including it in a dedicated
+ // array of blocked tracking nodes under its parent document.
+ if (status == NS_ERROR_TRACKING_URI) {
+ nsIDocument* ownerDoc = element->OwnerDoc();
+ if (ownerDoc) {
+ ownerDoc->AddBlockedTrackingNode(element);
+ }
+ }
+ element->NotifyLoadError();
+ }
+ return status;
+ }
+
+ nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
+ bool succeeded;
+ if (hc && NS_SUCCEEDED(hc->GetRequestSucceeded(&succeeded)) && !succeeded) {
+ element->NotifyLoadError();
+ uint32_t responseStatus = 0;
+ hc->GetResponseStatus(&responseStatus);
+ nsAutoString code;
+ code.AppendInt(responseStatus);
+ nsAutoString src;
+ element->GetCurrentSrc(src);
+ const char16_t* params[] = { code.get(), src.get() };
+ element->ReportLoadError("MediaLoadHttpError", params, ArrayLength(params));
+ return NS_BINDING_ABORTED;
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (channel &&
+ NS_SUCCEEDED(rv = element->InitializeDecoderForChannel(channel, getter_AddRefs(mNextListener))) &&
+ mNextListener) {
+ rv = mNextListener->OnStartRequest(aRequest, aContext);
+ } else {
+ // If InitializeDecoderForChannel() returned an error, fire a network error.
+ if (NS_FAILED(rv) && !mNextListener) {
+ // Load failed, attempt to load the next candidate resource. If there
+ // are none, this will trigger a MEDIA_ERR_SRC_NOT_SUPPORTED error.
+ element->NotifyLoadError();
+ }
+ // If InitializeDecoderForChannel did not return a listener (but may
+ // have otherwise succeeded), we abort the connection since we aren't
+ // interested in keeping the channel alive ourselves.
+ rv = NS_BINDING_ABORTED;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+HTMLMediaElement::MediaLoadListener::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatus)
+{
+ if (mNextListener) {
+ return mNextListener->OnStopRequest(aRequest, aContext, aStatus);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLMediaElement::MediaLoadListener::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ if (!mNextListener) {
+ NS_ERROR("Must have a chained listener; OnStartRequest should have canceled this request");
+ return NS_BINDING_ABORTED;
+ }
+ return mNextListener->OnDataAvailable(aRequest, aContext, aStream, aOffset, aCount);
+}
+
+NS_IMETHODIMP
+HTMLMediaElement::MediaLoadListener::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* cb)
+{
+ // TODO is this really correct?? See bug #579329.
+ if (mElement) {
+ mElement->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
+ }
+ nsCOMPtr<nsIChannelEventSink> sink = do_QueryInterface(mNextListener);
+ if (sink) {
+ return sink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, cb);
+ }
+ cb->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLMediaElement::MediaLoadListener::GetInterface(const nsIID& aIID,
+ void** aResult)
+{
+ return QueryInterface(aIID, aResult);
+}
+
+void HTMLMediaElement::ReportLoadError(const char* aMsg,
+ const char16_t** aParams,
+ uint32_t aParamCount)
+{
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Media"),
+ OwnerDoc(),
+ nsContentUtils::eDOM_PROPERTIES,
+ aMsg,
+ aParams,
+ aParamCount);
+}
+
+class HTMLMediaElement::ChannelLoader final {
+public:
+ NS_INLINE_DECL_REFCOUNTING(ChannelLoader);
+
+ void LoadInternal(HTMLMediaElement* aElement)
+ {
+ if (mCancelled) {
+ return;
+ }
+
+ // determine what security checks need to be performed in AsyncOpen2().
+ nsSecurityFlags securityFlags = aElement->ShouldCheckAllowOrigin()
+ ? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS :
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
+
+ if (aElement->GetCORSMode() == CORS_USE_CREDENTIALS) {
+ securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
+ }
+
+ MOZ_ASSERT(aElement->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video));
+ nsContentPolicyType contentPolicyType = aElement->IsHTMLElement(nsGkAtoms::audio)
+ ? nsIContentPolicy::TYPE_INTERNAL_AUDIO :
+ nsIContentPolicy::TYPE_INTERNAL_VIDEO;
+
+ nsCOMPtr<nsILoadGroup> loadGroup = aElement->GetDocumentLoadGroup();
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannel(getter_AddRefs(channel),
+ aElement->mLoadingSrc,
+ static_cast<Element*>(aElement),
+ securityFlags,
+ contentPolicyType,
+ loadGroup,
+ nullptr, // aCallbacks
+ nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY |
+ nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE |
+ nsIChannel::LOAD_CLASSIFY_URI |
+ nsIChannel::LOAD_CALL_CONTENT_SNIFFERS);
+
+ if (NS_FAILED(rv)) {
+ // Notify load error so the element will try next resource candidate.
+ aElement->NotifyLoadError();
+ return;
+ }
+
+ // The listener holds a strong reference to us. This creates a
+ // reference cycle, once we've set mChannel, which is manually broken
+ // in the listener's OnStartRequest method after it is finished with
+ // the element. The cycle will also be broken if we get a shutdown
+ // notification before OnStartRequest fires. Necko guarantees that
+ // OnStartRequest will eventually fire if we don't shut down first.
+ RefPtr<MediaLoadListener> loadListener = new MediaLoadListener(aElement);
+
+ channel->SetNotificationCallbacks(loadListener);
+
+ nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(channel);
+ if (hc) {
+ // Use a byte range request from the start of the resource.
+ // This enables us to detect if the stream supports byte range
+ // requests, and therefore seeking, early.
+ hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"),
+ NS_LITERAL_CSTRING("bytes=0-"),
+ false);
+ aElement->SetRequestHeaders(hc);
+ }
+
+ rv = channel->AsyncOpen2(loadListener);
+ if (NS_FAILED(rv)) {
+ // Notify load error so the element will try next resource candidate.
+ aElement->NotifyLoadError();
+ return;
+ }
+
+ // Else the channel must be open and starting to download. If it encounters
+ // a non-catastrophic failure, it will set a new task to continue loading
+ // another candidate. It's safe to set it as mChannel now.
+ mChannel = channel;
+
+ // loadListener will be unregistered either on shutdown or when
+ // OnStartRequest for the channel we just opened fires.
+ nsContentUtils::RegisterShutdownObserver(loadListener);
+ }
+
+ nsresult Load(HTMLMediaElement* aElement)
+ {
+ // Per bug 1235183 comment 8, we can't spin the event loop from stable
+ // state. Defer NS_NewChannel() to a new regular runnable.
+ return NS_DispatchToMainThread(NewRunnableMethod<HTMLMediaElement*>(
+ this, &ChannelLoader::LoadInternal, aElement));
+ }
+
+ void Cancel()
+ {
+ mCancelled = true;
+ if (mChannel) {
+ mChannel->Cancel(NS_BINDING_ABORTED);
+ mChannel = nullptr;
+ }
+ }
+
+ void Done() {
+ MOZ_ASSERT(mChannel);
+ // Decoder successfully created, the decoder now owns the MediaResource
+ // which owns the channel.
+ mChannel = nullptr;
+ }
+
+ nsresult Redirect(nsIChannel* aChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aFlags)
+ {
+ NS_ASSERTION(aChannel == mChannel, "Channels should match!");
+ mChannel = aNewChannel;
+
+ // Handle forwarding of Range header so that the intial detection
+ // of seeking support (via result code 206) works across redirects.
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
+ NS_ENSURE_STATE(http);
+
+ NS_NAMED_LITERAL_CSTRING(rangeHdr, "Range");
+
+ nsAutoCString rangeVal;
+ if (NS_SUCCEEDED(http->GetRequestHeader(rangeHdr, rangeVal))) {
+ NS_ENSURE_STATE(!rangeVal.IsEmpty());
+
+ http = do_QueryInterface(aNewChannel);
+ NS_ENSURE_STATE(http);
+
+ nsresult rv = http->SetRequestHeader(rangeHdr, rangeVal, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ ~ChannelLoader()
+ {
+ MOZ_ASSERT(!mChannel);
+ }
+ // Holds a reference to the first channel we open to the media resource.
+ // Once the decoder is created, control over the channel passes to the
+ // decoder, and we null out this reference. We must store this in case
+ // we need to cancel the channel before control of it passes to the decoder.
+ nsCOMPtr<nsIChannel> mChannel;
+
+ bool mCancelled = false;
+};
+
+class HTMLMediaElement::ErrorSink
+{
+public:
+ explicit ErrorSink(HTMLMediaElement* aOwner)
+ : mOwner(aOwner)
+ {
+ MOZ_ASSERT(mOwner);
+ }
+
+ void SetError(uint16_t aErrorCode, const nsACString& aErrorDetails)
+ {
+ // Since we have multiple paths calling into DecodeError, e.g.
+ // MediaKeys::Terminated and EMEH264Decoder::Error. We should take the 1st
+ // one only in order not to fire multiple 'error' events.
+ if (mError) {
+ return;
+ }
+
+ if (!IsValidErrorCode(aErrorCode)) {
+ NS_ASSERTION(false, "Undefined MediaError codes!");
+ return;
+ }
+
+ mError = new MediaError(mOwner, aErrorCode, aErrorDetails);
+ if (CanOwnerPlayUnsupportedTypeMedia()) {
+ mOwner->ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE);
+ OpenUnsupportedMediaForOwner();
+ } else {
+ mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("error"));
+ if (mOwner->ReadyState() == HAVE_NOTHING &&
+ aErrorCode == MEDIA_ERR_ABORTED) {
+ // https://html.spec.whatwg.org/multipage/embedded-content.html#media-data-processing-steps-list
+ // "If the media data fetching process is aborted by the user"
+ mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
+ mOwner->ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY);
+ mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
+ } else if (aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED) {
+ mOwner->ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE);
+ } else {
+ mOwner->ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
+ }
+ }
+ }
+
+ void ResetError()
+ {
+ mError = nullptr;
+ }
+
+ void NotifyPlayStarted()
+ {
+ if (CanOwnerPlayUnsupportedTypeMedia()) {
+ OpenUnsupportedMediaForOwner();
+ }
+ }
+
+ RefPtr<MediaError> mError;
+
+private:
+ bool IsValidErrorCode(const uint16_t& aErrorCode) const
+ {
+ return (aErrorCode == MEDIA_ERR_DECODE ||
+ aErrorCode == MEDIA_ERR_NETWORK ||
+ aErrorCode == MEDIA_ERR_ABORTED ||
+ aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED);
+ }
+
+ bool CanOwnerPlayUnsupportedTypeMedia() const
+ {
+#if defined(MOZ_WIDGET_ANDROID)
+ // On Fennec, we will user an external app to open unsupported media types.
+ if (!Preferences::GetBool("media.openUnsupportedTypeWithExternalApp")) {
+ return false;
+ }
+
+ if (!mError) {
+ return false;
+ }
+
+ uint16_t errorCode = mError->Code();
+ if (errorCode != MEDIA_ERR_SRC_NOT_SUPPORTED) {
+ return false;
+ }
+
+ // If media doesn't start playing, we don't need to open it.
+ if (mOwner->Paused()) {
+ return false;
+ }
+
+ return true;
+#endif
+ return false;
+ }
+
+ void OpenUnsupportedMediaForOwner() const
+ {
+ nsContentUtils::DispatchTrustedEvent(mOwner->OwnerDoc(),
+ static_cast<nsIContent*>(mOwner),
+ NS_LITERAL_STRING("OpenMediaWithExternalApp"),
+ true,
+ true);
+ }
+
+ // Media elememt's life cycle would be longer than error sink, so we use the
+ // raw pointer and this class would only be referenced by media element.
+ HTMLMediaElement* mOwner;
+};
+
+NS_IMPL_ADDREF_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
+NS_IMPL_RELEASE_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSource)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcMediaSource)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcStream)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcAttrStream)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourcePointer)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadBlockedDoc)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceLoadCandidate)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelAgent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mErrorSink->mError)
+ for (uint32_t i = 0; i < tmp->mOutputStreams.Length(); ++i) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams[i].mStream);
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlayed);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedVideoStreamTrack)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
+ if (tmp->mSrcStream) {
+ // Need to EndMediaStreamPlayback to clear mSrcStream and make sure everything
+ // gets unhooked correctly.
+ tmp->EndSrcMediaStreamPlayback();
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcAttrStream)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaSource)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcMediaSource)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourcePointer)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoadBlockedDoc)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceLoadCandidate)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelAgent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mErrorSink->mError)
+ for (uint32_t i = 0; i < tmp->mOutputStreams.Length(); ++i) {
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputStreams[i].mStream)
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLMediaElement)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLMediaElement)
+ NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
+
+// nsIDOMHTMLMediaElement
+NS_IMPL_URI_ATTR(HTMLMediaElement, Src, src)
+NS_IMPL_BOOL_ATTR(HTMLMediaElement, Controls, controls)
+NS_IMPL_BOOL_ATTR(HTMLMediaElement, Autoplay, autoplay)
+NS_IMPL_BOOL_ATTR(HTMLMediaElement, Loop, loop)
+NS_IMPL_BOOL_ATTR(HTMLMediaElement, DefaultMuted, muted)
+NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLMediaElement, Preload, preload, nullptr)
+
+NS_IMETHODIMP
+HTMLMediaElement::GetMozAudioChannelType(nsAString& aValue)
+{
+ nsString defaultValue;
+ AudioChannelService::GetDefaultAudioChannelString(defaultValue);
+
+ NS_ConvertUTF16toUTF8 str(defaultValue);
+ GetEnumAttr(nsGkAtoms::mozaudiochannel, str.get(), aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLMediaElement::SetMozAudioChannelType(const nsAString& aValue)
+{
+ return SetAttrHelper(nsGkAtoms::mozaudiochannel, aValue);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLMediaElement::IsVideo()
+{
+ return false;
+}
+
+already_AddRefed<MediaSource>
+HTMLMediaElement::GetMozMediaSourceObject() const
+{
+ RefPtr<MediaSource> source = mMediaSource;
+ return source.forget();
+}
+
+void
+HTMLMediaElement::GetMozDebugReaderData(nsAString& aString)
+{
+ if (mDecoder && !mSrcStream) {
+ mDecoder->GetMozDebugReaderData(aString);
+ }
+}
+
+void
+HTMLMediaElement::MozDumpDebugInfo()
+{
+ if (mDecoder) {
+ mDecoder->DumpDebugInfo();
+ }
+}
+
+void
+HTMLMediaElement::SetVisible(bool aVisible)
+{
+ if (!mDecoder) {
+ return;
+ }
+
+ mDecoder->SetForcedHidden(!aVisible);
+}
+
+already_AddRefed<DOMMediaStream>
+HTMLMediaElement::GetSrcObject() const
+{
+ NS_ASSERTION(!mSrcAttrStream || mSrcAttrStream->GetPlaybackStream(),
+ "MediaStream should have been set up properly");
+ RefPtr<DOMMediaStream> stream = mSrcAttrStream;
+ return stream.forget();
+}
+
+void
+HTMLMediaElement::SetSrcObject(DOMMediaStream& aValue)
+{
+ SetMozSrcObject(&aValue);
+}
+
+void
+HTMLMediaElement::SetSrcObject(DOMMediaStream* aValue)
+{
+ mSrcAttrStream = aValue;
+ UpdateAudioChannelPlayingState();
+ DoLoad();
+}
+
+// TODO: Remove prefixed versions soon (1183495)
+
+already_AddRefed<DOMMediaStream>
+HTMLMediaElement::GetMozSrcObject() const
+{
+ NS_ASSERTION(!mSrcAttrStream || mSrcAttrStream->GetPlaybackStream(),
+ "MediaStream should have been set up properly");
+ RefPtr<DOMMediaStream> stream = mSrcAttrStream;
+ return stream.forget();
+}
+
+void
+HTMLMediaElement::SetMozSrcObject(DOMMediaStream& aValue)
+{
+ SetMozSrcObject(&aValue);
+}
+
+void
+HTMLMediaElement::SetMozSrcObject(DOMMediaStream* aValue)
+{
+ mSrcAttrStream = aValue;
+ UpdateAudioChannelPlayingState();
+ DoLoad();
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetMozAutoplayEnabled(bool *aAutoplayEnabled)
+{
+ *aAutoplayEnabled = mAutoplayEnabled;
+
+ return NS_OK;
+}
+
+bool
+HTMLMediaElement::Ended()
+{
+ return (mDecoder && mDecoder->IsEnded()) ||
+ (mSrcStream && !mSrcStream->Active());
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetEnded(bool* aEnded)
+{
+ *aEnded = Ended();
+ return NS_OK;
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetCurrentSrc(nsAString & aCurrentSrc)
+{
+ nsAutoCString src;
+ GetCurrentSpec(src);
+ aCurrentSrc = NS_ConvertUTF8toUTF16(src);
+ return NS_OK;
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetNetworkState(uint16_t* aNetworkState)
+{
+ *aNetworkState = NetworkState();
+ return NS_OK;
+}
+
+nsresult
+HTMLMediaElement::OnChannelRedirect(nsIChannel* aChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aFlags)
+{
+ MOZ_ASSERT(mChannelLoader);
+ return mChannelLoader->Redirect(aChannel, aNewChannel, aFlags);
+}
+
+void HTMLMediaElement::ShutdownDecoder()
+{
+ RemoveMediaElementFromURITable();
+ NS_ASSERTION(mDecoder, "Must have decoder to shut down");
+ mWaitingForKeyListener.DisconnectIfExists();
+ mDecoder->Shutdown();
+ mDecoder = nullptr;
+}
+
+void HTMLMediaElement::AbortExistingLoads()
+{
+ // If there is no existing decoder then we don't have anything to
+ // report. This prevents reporting the initial load from an
+ // empty video element as a failed EME load.
+ if (mDecoder) {
+ ReportEMETelemetry();
+ }
+ // Abort any already-running instance of the resource selection algorithm.
+ mLoadWaitStatus = NOT_WAITING;
+
+ // Set a new load ID. This will cause events which were enqueued
+ // with a different load ID to silently be cancelled.
+ mCurrentLoadID++;
+
+ if (mChannelLoader) {
+ mChannelLoader->Cancel();
+ mChannelLoader = nullptr;
+ }
+
+ bool fireTimeUpdate = false;
+
+ // We need to remove StreamSizeListener before VideoTracks get emptied.
+ if (mMediaStreamSizeListener) {
+ mSelectedVideoStreamTrack->RemoveDirectListener(mMediaStreamSizeListener);
+ mMediaStreamSizeListener->Forget();
+ mMediaStreamSizeListener = nullptr;
+ }
+
+ // When aborting the existing loads, empty the objects in audio track list and
+ // video track list, no events (in particular, no removetrack events) are
+ // fired as part of this. Ending MediaStream sends track ended notifications,
+ // so we empty the track lists prior.
+ AudioTracks()->EmptyTracks();
+ VideoTracks()->EmptyTracks();
+
+ if (mDecoder) {
+ fireTimeUpdate = mDecoder->GetCurrentTime() != 0.0;
+ ShutdownDecoder();
+ }
+ if (mSrcStream) {
+ EndSrcMediaStreamPlayback();
+ }
+
+ RemoveMediaElementFromURITable();
+ mLoadingSrc = nullptr;
+ mMediaSource = nullptr;
+
+ if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING ||
+ mNetworkState == nsIDOMHTMLMediaElement::NETWORK_IDLE)
+ {
+ DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
+ }
+
+ mErrorSink->ResetError();
+ mCurrentPlayRangeStart = -1.0;
+ mLoadedDataFired = false;
+ mAutoplaying = true;
+ mIsLoadingFromSourceChildren = false;
+ mSuspendedAfterFirstFrame = false;
+ mAllowSuspendAfterFirstFrame = true;
+ mHaveQueuedSelectResource = false;
+ mSuspendedForPreloadNone = false;
+ mDownloadSuspendedByCache = false;
+ mMediaInfo = MediaInfo();
+ mIsEncrypted = false;
+ mPendingEncryptedInitData.mInitDatas.Clear();
+ mWaitingForKey = NOT_WAITING_FOR_KEY;
+ mSourcePointer = nullptr;
+
+ mTags = nullptr;
+
+ if (mNetworkState != nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
+ NS_ASSERTION(!mDecoder && !mSrcStream, "How did someone setup a new stream/decoder already?");
+ // ChangeNetworkState() will call UpdateAudioChannelPlayingState()
+ // indirectly which depends on mPaused. So we need to update mPaused first.
+ mPaused = true;
+ ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY);
+ ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING);
+
+ //TODO: Apply the rules for text track cue rendering Bug 865407
+ if (mTextTrackManager) {
+ mTextTrackManager->GetTextTracks()->SetCuesInactive();
+ }
+
+ if (fireTimeUpdate) {
+ // Since we destroyed the decoder above, the current playback position
+ // will now be reported as 0. The playback position was non-zero when
+ // we destroyed the decoder, so fire a timeupdate event so that the
+ // change will be reflected in the controls.
+ FireTimeUpdate(false);
+ }
+ DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
+ UpdateAudioChannelPlayingState();
+ }
+
+ // We may have changed mPaused, mAutoplaying, and other
+ // things which can affect AddRemoveSelfReference
+ AddRemoveSelfReference();
+
+ mIsRunningSelectResource = false;
+
+ if (mTextTrackManager) {
+ mTextTrackManager->NotifyReset();
+ }
+
+ mEventDeliveryPaused = false;
+ mPendingEvents.Clear();
+}
+
+void HTMLMediaElement::NoSupportedMediaSourceError(const nsACString& aErrorDetails)
+{
+ if (mDecoder) {
+ ShutdownDecoder();
+ }
+ mErrorSink->SetError(MEDIA_ERR_SRC_NOT_SUPPORTED, aErrorDetails);
+ ChangeDelayLoadStatus(false);
+ UpdateAudioChannelPlayingState();
+}
+
+typedef void (HTMLMediaElement::*SyncSectionFn)();
+
+// Runs a "synchronous section", a function that must run once the event loop
+// has reached a "stable state". See:
+// http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section
+class nsSyncSection : public nsMediaEvent
+{
+private:
+ nsCOMPtr<nsIRunnable> mRunnable;
+public:
+ nsSyncSection(HTMLMediaElement* aElement,
+ nsIRunnable* aRunnable) :
+ nsMediaEvent(aElement),
+ mRunnable(aRunnable)
+ {
+ }
+
+ NS_IMETHOD Run() override {
+ // Silently cancel if our load has been cancelled.
+ if (IsCancelled())
+ return NS_OK;
+ mRunnable->Run();
+ return NS_OK;
+ }
+};
+
+void HTMLMediaElement::RunInStableState(nsIRunnable* aRunnable)
+{
+ nsCOMPtr<nsIRunnable> event = new nsSyncSection(this, aRunnable);
+ nsContentUtils::RunInStableState(event.forget());
+}
+
+void HTMLMediaElement::QueueLoadFromSourceTask()
+{
+ ChangeDelayLoadStatus(true);
+ ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_LOADING);
+ RefPtr<Runnable> r = NewRunnableMethod(this, &HTMLMediaElement::LoadFromSourceChildren);
+ RunInStableState(r);
+}
+
+void HTMLMediaElement::QueueSelectResourceTask()
+{
+ // Don't allow multiple async select resource calls to be queued.
+ if (mHaveQueuedSelectResource)
+ return;
+ mHaveQueuedSelectResource = true;
+ ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE);
+ RefPtr<Runnable> r = NewRunnableMethod(this, &HTMLMediaElement::SelectResourceWrapper);
+ RunInStableState(r);
+}
+
+static bool HasSourceChildren(nsIContent* aElement)
+{
+ for (nsIContent* child = aElement->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->IsHTMLElement(nsGkAtoms::source))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+NS_IMETHODIMP HTMLMediaElement::Load()
+{
+ LOG(LogLevel::Debug,
+ ("%p Load() hasSrcAttrStream=%d hasSrcAttr=%d hasSourceChildren=%d "
+ "handlingInput=%d",
+ this, !!mSrcAttrStream, HasAttr(kNameSpaceID_None, nsGkAtoms::src),
+ HasSourceChildren(this), EventStateManager::IsHandlingUserInput()));
+
+ if (mIsRunningLoadMethod) {
+ return NS_OK;
+ }
+
+ mIsDoingExplicitLoad = true;
+ DoLoad();
+
+ return NS_OK;
+}
+
+void HTMLMediaElement::DoLoad()
+{
+ if (mIsRunningLoadMethod) {
+ return;
+ }
+
+ // Detect if user has interacted with element so that play will not be
+ // blocked when initiated by a script. This enables sites to capture user
+ // intent to play by calling load() in the click handler of a "catalog
+ // view" of a gallery of videos.
+ if (EventStateManager::IsHandlingUserInput()) {
+ mHasUserInteraction = true;
+ }
+
+ SetPlayedOrSeeked(false);
+ mIsRunningLoadMethod = true;
+ AbortExistingLoads();
+ SetPlaybackRate(mDefaultPlaybackRate);
+ QueueSelectResourceTask();
+ ResetState();
+ mIsRunningLoadMethod = false;
+}
+
+void HTMLMediaElement::ResetState()
+{
+ // There might be a pending MediaDecoder::PlaybackPositionChanged() which
+ // will overwrite |mMediaInfo.mVideo.mDisplay| in UpdateMediaSize() to give
+ // staled videoWidth and videoHeight. We have to call ForgetElement() here
+ // such that the staled callbacks won't reach us.
+ if (mVideoFrameContainer) {
+ mVideoFrameContainer->ForgetElement();
+ mVideoFrameContainer = nullptr;
+ }
+}
+
+void HTMLMediaElement::SelectResourceWrapper()
+{
+ SelectResource();
+ mIsRunningSelectResource = false;
+ mHaveQueuedSelectResource = false;
+ mIsDoingExplicitLoad = false;
+}
+
+void HTMLMediaElement::SelectResource()
+{
+ if (!mSrcAttrStream && !HasAttr(kNameSpaceID_None, nsGkAtoms::src) &&
+ !HasSourceChildren(this)) {
+ // The media element has neither a src attribute nor any source
+ // element children, abort the load.
+ ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY);
+ ChangeDelayLoadStatus(false);
+ return;
+ }
+
+ ChangeDelayLoadStatus(true);
+
+ ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_LOADING);
+ DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
+
+ // Delay setting mIsRunningSeletResource until after UpdatePreloadAction
+ // so that we don't lose our state change by bailing out of the preload
+ // state update
+ UpdatePreloadAction();
+ mIsRunningSelectResource = true;
+
+ // If we have a 'src' attribute, use that exclusively.
+ nsAutoString src;
+ if (mSrcAttrStream) {
+ SetupSrcMediaStreamPlayback(mSrcAttrStream);
+ } else if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NewURIFromString(src, getter_AddRefs(uri));
+ if (NS_SUCCEEDED(rv)) {
+ LOG(LogLevel::Debug, ("%p Trying load from src=%s", this, NS_ConvertUTF16toUTF8(src).get()));
+ NS_ASSERTION(!mIsLoadingFromSourceChildren,
+ "Should think we're not loading from source children by default");
+
+ RemoveMediaElementFromURITable();
+ mLoadingSrc = uri;
+ mMediaSource = mSrcMediaSource;
+ UpdatePreloadAction();
+ if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE &&
+ !IsMediaStreamURI(mLoadingSrc) && !mMediaSource) {
+ // preload:none media, suspend the load here before we make any
+ // network requests.
+ SuspendLoad();
+ return;
+ }
+
+ rv = LoadResource();
+ if (NS_SUCCEEDED(rv)) {
+ return;
+ }
+ } else {
+ const char16_t* params[] = { src.get() };
+ ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
+ }
+ NoSupportedMediaSourceError();
+ } else {
+ // Otherwise, the source elements will be used.
+ mIsLoadingFromSourceChildren = true;
+ LoadFromSourceChildren();
+ }
+}
+
+void HTMLMediaElement::NotifyLoadError()
+{
+ if (!mIsLoadingFromSourceChildren) {
+ LOG(LogLevel::Debug, ("NotifyLoadError(), no supported media error"));
+ NoSupportedMediaSourceError();
+ } else if (mSourceLoadCandidate) {
+ DispatchAsyncSourceError(mSourceLoadCandidate);
+ QueueLoadFromSourceTask();
+ } else {
+ NS_WARNING("Should know the source we were loading from!");
+ }
+}
+
+void HTMLMediaElement::NotifyMediaTrackEnabled(MediaTrack* aTrack)
+{
+ MOZ_ASSERT(aTrack);
+ if (!aTrack) {
+ return;
+ }
+#ifdef DEBUG
+ nsString id;
+ aTrack->GetId(id);
+
+ LOG(LogLevel::Debug, ("MediaElement %p %sTrack with id %s enabled",
+ this, aTrack->AsAudioTrack() ? "Audio" : "Video",
+ NS_ConvertUTF16toUTF8(id).get()));
+#endif
+
+ MOZ_ASSERT((aTrack->AsAudioTrack() && aTrack->AsAudioTrack()->Enabled()) ||
+ (aTrack->AsVideoTrack() && aTrack->AsVideoTrack()->Selected()));
+
+ if (aTrack->AsAudioTrack()) {
+ SetMutedInternal(mMuted & ~MUTED_BY_AUDIO_TRACK);
+ } else if (aTrack->AsVideoTrack()) {
+ if (!IsVideo()) {
+ MOZ_ASSERT(false);
+ return;
+ }
+ mDisableVideo = false;
+ } else {
+ MOZ_ASSERT(false, "Unknown track type");
+ }
+
+ if (mSrcStream) {
+ if (aTrack->AsVideoTrack()) {
+ MOZ_ASSERT(!mSelectedVideoStreamTrack);
+ MOZ_ASSERT(!mMediaStreamSizeListener);
+
+ mSelectedVideoStreamTrack = aTrack->AsVideoTrack()->GetVideoStreamTrack();
+ VideoFrameContainer* container = GetVideoFrameContainer();
+ if (mSrcStreamIsPlaying && container) {
+ mSelectedVideoStreamTrack->AddVideoOutput(container);
+ }
+ HTMLVideoElement* self = static_cast<HTMLVideoElement*>(this);
+ if (self->VideoWidth() <= 1 && self->VideoHeight() <= 1) {
+ // MediaInfo uses dummy values of 1 for width and height to
+ // mark video as valid. We need a new stream size listener
+ // if size is 0x0 or 1x1.
+ mMediaStreamSizeListener = new StreamSizeListener(this);
+ mSelectedVideoStreamTrack->AddDirectListener(mMediaStreamSizeListener);
+ }
+ }
+
+ if (mReadyState == HAVE_NOTHING) {
+ // No MediaStreamTracks are captured until we have metadata.
+ return;
+ }
+ for (OutputMediaStream& ms : mOutputStreams) {
+ if (aTrack->AsVideoTrack() && ms.mCapturingAudioOnly) {
+ // If the output stream is for audio only we ignore video tracks.
+ continue;
+ }
+ AddCaptureMediaTrackToOutputStream(aTrack, ms);
+ }
+ }
+}
+
+void HTMLMediaElement::NotifyMediaTrackDisabled(MediaTrack* aTrack)
+{
+ MOZ_ASSERT(aTrack);
+ if (!aTrack) {
+ return;
+ }
+#ifdef DEBUG
+ nsString id;
+ aTrack->GetId(id);
+
+ LOG(LogLevel::Debug, ("MediaElement %p %sTrack with id %s disabled",
+ this, aTrack->AsAudioTrack() ? "Audio" : "Video",
+ NS_ConvertUTF16toUTF8(id).get()));
+#endif
+
+ MOZ_ASSERT((!aTrack->AsAudioTrack() || !aTrack->AsAudioTrack()->Enabled()) &&
+ (!aTrack->AsVideoTrack() || !aTrack->AsVideoTrack()->Selected()));
+
+ if (aTrack->AsAudioTrack()) {
+ bool shouldMute = true;
+ for (uint32_t i = 0; i < AudioTracks()->Length(); ++i) {
+ if ((*AudioTracks())[i]->Enabled()) {
+ shouldMute = false;
+ break;
+ }
+ }
+ if (shouldMute) {
+ SetMutedInternal(mMuted | MUTED_BY_AUDIO_TRACK);
+ }
+ } else if (aTrack->AsVideoTrack()) {
+ if (mSrcStream) {
+ MOZ_ASSERT(mSelectedVideoStreamTrack);
+ if (mSelectedVideoStreamTrack && mMediaStreamSizeListener) {
+ mSelectedVideoStreamTrack->RemoveDirectListener(mMediaStreamSizeListener);
+ mMediaStreamSizeListener->Forget();
+ mMediaStreamSizeListener = nullptr;
+ }
+ VideoFrameContainer* container = GetVideoFrameContainer();
+ if (mSrcStreamIsPlaying && container) {
+ mSelectedVideoStreamTrack->RemoveVideoOutput(container);
+ }
+ mSelectedVideoStreamTrack = nullptr;
+ }
+ }
+
+ if (mReadyState == HAVE_NOTHING) {
+ // No MediaStreamTracks are captured until we have metadata, and code
+ // below doesn't do anything for captured decoders.
+ return;
+ }
+
+ for (OutputMediaStream& ms : mOutputStreams) {
+ if (ms.mCapturingDecoder) {
+ MOZ_ASSERT(!ms.mCapturingMediaStream);
+ continue;
+ }
+ MOZ_ASSERT(ms.mCapturingMediaStream);
+ for (int32_t i = ms.mTrackPorts.Length() - 1; i >= 0; --i) {
+ if (ms.mTrackPorts[i].first() == aTrack->GetId()) {
+ // The source of this track just ended. Force-notify that it ended.
+ // If we bounce it to the MediaStreamGraph it might not be picked up,
+ // for instance if the MediaInputPort was destroyed in the same
+ // iteration as it was added.
+ MediaStreamTrack* outputTrack = ms.mStream->FindOwnedDOMTrack(
+ ms.mTrackPorts[i].second()->GetDestination(),
+ ms.mTrackPorts[i].second()->GetDestinationTrackId());
+ MOZ_ASSERT(outputTrack);
+ if (outputTrack) {
+ NS_DispatchToMainThread(
+ NewRunnableMethod(outputTrack, &MediaStreamTrack::OverrideEnded));
+ }
+
+ ms.mTrackPorts[i].second()->Destroy();
+ ms.mTrackPorts.RemoveElementAt(i);
+ break;
+ }
+ }
+#ifdef DEBUG
+ for (auto pair : ms.mTrackPorts) {
+ MOZ_ASSERT(pair.first() != aTrack->GetId(),
+ "The same MediaTrack was forwarded to the output stream more than once. This shouldn't happen.");
+ }
+#endif
+ }
+}
+
+void HTMLMediaElement::NotifyMediaStreamTracksAvailable(DOMMediaStream* aStream)
+{
+ if (!mSrcStream || mSrcStream != aStream) {
+ return;
+ }
+
+ LOG(LogLevel::Debug, ("MediaElement %p MediaStream tracks available", this));
+
+ mSrcStreamTracksAvailable = true;
+
+ bool videoHasChanged = IsVideo() && HasVideo() != !VideoTracks()->IsEmpty();
+
+ if (videoHasChanged) {
+ // We are a video element and HasVideo() changed so update the screen
+ // wakelock
+ NotifyOwnerDocumentActivityChanged();
+ }
+
+ mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
+}
+
+void
+HTMLMediaElement::NotifyOutputTrackStopped(DOMMediaStream* aOwningStream,
+ TrackID aDestinationTrackID)
+{
+ for (OutputMediaStream& ms : mOutputStreams) {
+ if (!ms.mCapturingMediaStream) {
+ continue;
+ }
+
+ if (ms.mStream != aOwningStream) {
+ continue;
+ }
+
+ for (int32_t i = ms.mTrackPorts.Length() - 1; i >= 0; --i) {
+ MediaInputPort* port = ms.mTrackPorts[i].second();
+ if (port->GetDestinationTrackId() != aDestinationTrackID) {
+ continue;
+ }
+
+ port->Destroy();
+ ms.mTrackPorts.RemoveElementAt(i);
+ return;
+ }
+ }
+
+ // An output track ended but its port is already gone.
+ // It was probably cleared by the removal of the source MediaTrack.
+}
+
+void HTMLMediaElement::LoadFromSourceChildren()
+{
+ NS_ASSERTION(mDelayingLoadEvent,
+ "Should delay load event (if in document) during load");
+ NS_ASSERTION(mIsLoadingFromSourceChildren,
+ "Must remember we're loading from source children");
+
+ nsIDocument* parentDoc = OwnerDoc()->GetParentDocument();
+ if (parentDoc) {
+ parentDoc->FlushPendingNotifications(Flush_Layout);
+ }
+
+ while (true) {
+ nsIContent* child = GetNextSource();
+ if (!child) {
+ // Exhausted candidates, wait for more candidates to be appended to
+ // the media element.
+ mLoadWaitStatus = WAITING_FOR_SOURCE;
+ ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE);
+ ChangeDelayLoadStatus(false);
+ ReportLoadError("MediaLoadExhaustedCandidates");
+ return;
+ }
+
+ // Must have src attribute.
+ nsAutoString src;
+ if (!child->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
+ ReportLoadError("MediaLoadSourceMissingSrc");
+ DispatchAsyncSourceError(child);
+ continue;
+ }
+
+ // If we have a type attribute, it must be a supported type.
+ nsAutoString type;
+ if (child->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type)) {
+ DecoderDoctorDiagnostics diagnostics;
+ CanPlayStatus canPlay = GetCanPlay(type, &diagnostics);
+ diagnostics.StoreFormatDiagnostics(
+ OwnerDoc(), type, canPlay != CANPLAY_NO, __func__);
+ if (canPlay == CANPLAY_NO) {
+ DispatchAsyncSourceError(child);
+ const char16_t* params[] = { type.get(), src.get() };
+ ReportLoadError("MediaLoadUnsupportedTypeAttribute", params, ArrayLength(params));
+ continue;
+ }
+ }
+ nsAutoString media;
+ HTMLSourceElement *childSrc = HTMLSourceElement::FromContent(child);
+ MOZ_ASSERT(childSrc, "Expect child to be HTMLSourceElement");
+ if (childSrc && !childSrc->MatchesCurrentMedia()) {
+ DispatchAsyncSourceError(child);
+ const char16_t* params[] = { media.get(), src.get() };
+ ReportLoadError("MediaLoadSourceMediaNotMatched", params, ArrayLength(params));
+ continue;
+ }
+ LOG(LogLevel::Debug, ("%p Trying load from <source>=%s type=%s media=%s", this,
+ NS_ConvertUTF16toUTF8(src).get(), NS_ConvertUTF16toUTF8(type).get(),
+ NS_ConvertUTF16toUTF8(media).get()));
+
+ nsCOMPtr<nsIURI> uri;
+ NewURIFromString(src, getter_AddRefs(uri));
+ if (!uri) {
+ DispatchAsyncSourceError(child);
+ const char16_t* params[] = { src.get() };
+ ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
+ continue;
+ }
+
+ RemoveMediaElementFromURITable();
+ mLoadingSrc = uri;
+ mMediaSource = childSrc->GetSrcMediaSource();
+ NS_ASSERTION(mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING,
+ "Network state should be loading");
+
+ if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE &&
+ !IsMediaStreamURI(mLoadingSrc) && !mMediaSource) {
+ // preload:none media, suspend the load here before we make any
+ // network requests.
+ SuspendLoad();
+ return;
+ }
+
+ if (NS_SUCCEEDED(LoadResource())) {
+ return;
+ }
+
+ // If we fail to load, loop back and try loading the next resource.
+ DispatchAsyncSourceError(child);
+ }
+ NS_NOTREACHED("Execution should not reach here!");
+}
+
+void HTMLMediaElement::SuspendLoad()
+{
+ mSuspendedForPreloadNone = true;
+ ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
+ ChangeDelayLoadStatus(false);
+}
+
+void HTMLMediaElement::ResumeLoad(PreloadAction aAction)
+{
+ NS_ASSERTION(mSuspendedForPreloadNone,
+ "Must be halted for preload:none to resume from preload:none suspended load.");
+ mSuspendedForPreloadNone = false;
+ mPreloadAction = aAction;
+ ChangeDelayLoadStatus(true);
+ ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_LOADING);
+ if (!mIsLoadingFromSourceChildren) {
+ // We were loading from the element's src attribute.
+ if (NS_FAILED(LoadResource())) {
+ NoSupportedMediaSourceError();
+ }
+ } else {
+ // We were loading from a child <source> element. Try to resume the
+ // load of that child, and if that fails, try the next child.
+ if (NS_FAILED(LoadResource())) {
+ LoadFromSourceChildren();
+ }
+ }
+}
+
+static bool IsAutoplayEnabled()
+{
+ return Preferences::GetBool("media.autoplay.enabled");
+}
+
+void HTMLMediaElement::UpdatePreloadAction()
+{
+ PreloadAction nextAction = PRELOAD_UNDEFINED;
+ // If autoplay is set, or we're playing, we should always preload data,
+ // as we'll need it to play.
+ if ((IsAutoplayEnabled() && HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay)) ||
+ !mPaused)
+ {
+ nextAction = HTMLMediaElement::PRELOAD_ENOUGH;
+ } else {
+ // Find the appropriate preload action by looking at the attribute.
+ const nsAttrValue* val = mAttrsAndChildren.GetAttr(nsGkAtoms::preload,
+ kNameSpaceID_None);
+ // MSE doesn't work if preload is none, so it ignores the pref when src is
+ // from MSE.
+ uint32_t preloadDefault = mMediaSource ?
+ HTMLMediaElement::PRELOAD_ATTR_METADATA :
+ Preferences::GetInt("media.preload.default",
+ HTMLMediaElement::PRELOAD_ATTR_METADATA);
+ uint32_t preloadAuto =
+ Preferences::GetInt("media.preload.auto",
+ HTMLMediaElement::PRELOAD_ENOUGH);
+ if (!val) {
+ // Attribute is not set. Use the preload action specified by the
+ // media.preload.default pref, or just preload metadata if not present.
+ nextAction = static_cast<PreloadAction>(preloadDefault);
+ } else if (val->Type() == nsAttrValue::eEnum) {
+ PreloadAttrValue attr = static_cast<PreloadAttrValue>(val->GetEnumValue());
+ if (attr == HTMLMediaElement::PRELOAD_ATTR_EMPTY ||
+ attr == HTMLMediaElement::PRELOAD_ATTR_AUTO)
+ {
+ nextAction = static_cast<PreloadAction>(preloadAuto);
+ } else if (attr == HTMLMediaElement::PRELOAD_ATTR_METADATA) {
+ nextAction = HTMLMediaElement::PRELOAD_METADATA;
+ } else if (attr == HTMLMediaElement::PRELOAD_ATTR_NONE) {
+ nextAction = HTMLMediaElement::PRELOAD_NONE;
+ }
+ } else {
+ // Use the suggested "missing value default" of "metadata", or the value
+ // specified by the media.preload.default, if present.
+ nextAction = static_cast<PreloadAction>(preloadDefault);
+ }
+ }
+
+ if (nextAction == HTMLMediaElement::PRELOAD_NONE && mIsDoingExplicitLoad) {
+ nextAction = HTMLMediaElement::PRELOAD_METADATA;
+ }
+
+ mPreloadAction = nextAction;
+
+ if (nextAction == HTMLMediaElement::PRELOAD_ENOUGH) {
+ if (mSuspendedForPreloadNone) {
+ // Our load was previouly suspended due to the media having preload
+ // value "none". The preload value has changed to preload:auto, so
+ // resume the load.
+ ResumeLoad(PRELOAD_ENOUGH);
+ } else {
+ // Preload as much of the video as we can, i.e. don't suspend after
+ // the first frame.
+ StopSuspendingAfterFirstFrame();
+ }
+
+ } else if (nextAction == HTMLMediaElement::PRELOAD_METADATA) {
+ // Ensure that the video can be suspended after first frame.
+ mAllowSuspendAfterFirstFrame = true;
+ if (mSuspendedForPreloadNone) {
+ // Our load was previouly suspended due to the media having preload
+ // value "none". The preload value has changed to preload:metadata, so
+ // resume the load. We'll pause the load again after we've read the
+ // metadata.
+ ResumeLoad(PRELOAD_METADATA);
+ }
+ }
+}
+
+nsresult HTMLMediaElement::LoadResource()
+{
+ NS_ASSERTION(mDelayingLoadEvent,
+ "Should delay load event (if in document) during load");
+
+ if (mChannelLoader) {
+ mChannelLoader->Cancel();
+ mChannelLoader = nullptr;
+ }
+
+ // Check if media is allowed for the docshell.
+ nsCOMPtr<nsIDocShell> docShell = OwnerDoc()->GetDocShell();
+ if (docShell && !docShell->GetAllowMedia()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Set the media element's CORS mode only when loading a resource
+ mCORSMode = AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin));
+
+ HTMLMediaElement* other = LookupMediaElementURITable(mLoadingSrc);
+ if (other && other->mDecoder) {
+ // Clone it.
+ nsresult rv = InitializeDecoderAsClone(other->mDecoder);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ }
+
+ if (IsMediaStreamURI(mLoadingSrc)) {
+ RefPtr<DOMMediaStream> stream;
+ nsresult rv = NS_GetStreamForMediaStreamURI(mLoadingSrc, getter_AddRefs(stream));
+ if (NS_FAILED(rv)) {
+ nsAutoString spec;
+ GetCurrentSrc(spec);
+ const char16_t* params[] = { spec.get() };
+ ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
+ return rv;
+ }
+ SetupSrcMediaStreamPlayback(stream);
+ return NS_OK;
+ }
+
+ if (mMediaSource) {
+ RefPtr<MediaSourceDecoder> decoder = new MediaSourceDecoder(this);
+ if (!mMediaSource->Attach(decoder)) {
+ // TODO: Handle failure: run "If the media data cannot be fetched at
+ // all, due to network errors, causing the user agent to give up
+ // trying to fetch the resource" section of resource fetch algorithm.
+ decoder->Shutdown();
+ return NS_ERROR_FAILURE;
+ }
+ ChangeDelayLoadStatus(false);
+ RefPtr<MediaResource> resource =
+ MediaSourceDecoder::CreateResource(mMediaSource->GetPrincipal());
+ return FinishDecoderSetup(decoder, resource, nullptr);
+ }
+
+ RefPtr<ChannelLoader> loader = new ChannelLoader;
+ nsresult rv = loader->Load(this);
+ if (NS_SUCCEEDED(rv)) {
+ mChannelLoader = loader.forget();
+ }
+ return rv;
+}
+
+nsresult HTMLMediaElement::LoadWithChannel(nsIChannel* aChannel,
+ nsIStreamListener** aListener)
+{
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ *aListener = nullptr;
+
+ // Make sure we don't reenter during synchronous abort events.
+ if (mIsRunningLoadMethod)
+ return NS_OK;
+ mIsRunningLoadMethod = true;
+ AbortExistingLoads();
+ mIsRunningLoadMethod = false;
+
+ nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(mLoadingSrc));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ChangeDelayLoadStatus(true);
+ rv = InitializeDecoderForChannel(aChannel, aListener);
+ if (NS_FAILED(rv)) {
+ ChangeDelayLoadStatus(false);
+ return rv;
+ }
+
+ SetPlaybackRate(mDefaultPlaybackRate);
+ DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetReadyState(uint16_t* aReadyState)
+{
+ *aReadyState = ReadyState();
+
+ return NS_OK;
+}
+
+bool
+HTMLMediaElement::Seeking() const
+{
+ return mDecoder && mDecoder->IsSeeking();
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetSeeking(bool* aSeeking)
+{
+ *aSeeking = Seeking();
+ return NS_OK;
+}
+
+double
+HTMLMediaElement::CurrentTime() const
+{
+ if (MediaStream* stream = GetSrcMediaStream()) {
+ if (mSrcStreamPausedCurrentTime >= 0) {
+ return mSrcStreamPausedCurrentTime;
+ }
+ return stream->StreamTimeToSeconds(stream->GetCurrentTime());
+ }
+
+ if (mDefaultPlaybackStartPosition == 0.0 && mDecoder) {
+ return mDecoder->GetCurrentTime();
+ }
+
+ return mDefaultPlaybackStartPosition;
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetCurrentTime(double* aCurrentTime)
+{
+ *aCurrentTime = CurrentTime();
+ return NS_OK;
+}
+
+void
+HTMLMediaElement::FastSeek(double aTime, ErrorResult& aRv)
+{
+ LOG(LogLevel::Debug, ("Reporting telemetry VIDEO_FASTSEEK_USED"));
+ Telemetry::Accumulate(Telemetry::VIDEO_FASTSEEK_USED, 1);
+ RefPtr<Promise> tobeDropped = Seek(aTime, SeekTarget::PrevSyncPoint, aRv);
+}
+
+already_AddRefed<Promise>
+HTMLMediaElement::SeekToNextFrame(ErrorResult& aRv)
+{
+ return Seek(CurrentTime(), SeekTarget::NextFrame, aRv);
+}
+
+void
+HTMLMediaElement::SetCurrentTime(double aCurrentTime, ErrorResult& aRv)
+{
+ RefPtr<Promise> tobeDropped = Seek(aCurrentTime, SeekTarget::Accurate, aRv);
+}
+
+/**
+ * Check if aValue is inside a range of aRanges, and if so sets aIsInRanges
+ * to true and put the range index in aIntervalIndex. If aValue is not
+ * inside a range, aIsInRanges is set to false, and aIntervalIndex
+ * is set to the index of the range which ends immediately before aValue
+ * (and can be -1 if aValue is before aRanges.Start(0)). Returns NS_OK
+ * on success, and NS_ERROR_FAILURE on failure.
+ */
+static nsresult
+IsInRanges(dom::TimeRanges& aRanges,
+ double aValue,
+ bool& aIsInRanges,
+ int32_t& aIntervalIndex)
+{
+ aIsInRanges = false;
+ uint32_t length;
+ nsresult rv = aRanges.GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t i = 0; i < length; i++) {
+ double start, end;
+ rv = aRanges.Start(i, &start);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (start > aValue) {
+ aIntervalIndex = i - 1;
+ return NS_OK;
+ }
+ rv = aRanges.End(i, &end);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aValue <= end) {
+ aIntervalIndex = i;
+ aIsInRanges = true;
+ return NS_OK;
+ }
+ }
+ aIntervalIndex = length - 1;
+ return NS_OK;
+}
+
+already_AddRefed<Promise>
+HTMLMediaElement::Seek(double aTime,
+ SeekTarget::Type aSeekType,
+ ErrorResult& aRv)
+{
+ // aTime should be non-NaN.
+ MOZ_ASSERT(!mozilla::IsNaN(aTime));
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(OwnerDoc()->GetInnerWindow());
+
+ if (!global) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // Detect if user has interacted with element by seeking so that
+ // play will not be blocked when initiated by a script.
+ if (EventStateManager::IsHandlingUserInput()) {
+ mHasUserInteraction = true;
+ }
+
+ StopSuspendingAfterFirstFrame();
+
+ if (mSrcStream) {
+ // do nothing since media streams have an empty Seekable range.
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return promise.forget();
+ }
+
+ if (mPlayed && mCurrentPlayRangeStart != -1.0) {
+ double rangeEndTime = CurrentTime();
+ LOG(LogLevel::Debug, ("%p Adding \'played\' a range : [%f, %f]", this, mCurrentPlayRangeStart, rangeEndTime));
+ // Multiple seek without playing, or seek while playing.
+ if (mCurrentPlayRangeStart != rangeEndTime) {
+ mPlayed->Add(mCurrentPlayRangeStart, rangeEndTime);
+ }
+ // Reset the current played range start time. We'll re-set it once
+ // the seek completes.
+ mCurrentPlayRangeStart = -1.0;
+ }
+
+ if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
+ mDefaultPlaybackStartPosition = aTime;
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return promise.forget();
+ }
+
+ if (!mDecoder) {
+ // mDecoder must always be set in order to reach this point.
+ NS_ASSERTION(mDecoder, "SetCurrentTime failed: no decoder");
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return promise.forget();
+ }
+
+ // Clamp the seek target to inside the seekable ranges.
+ RefPtr<dom::TimeRanges> seekable = new dom::TimeRanges(ToSupports(OwnerDoc()));
+ media::TimeIntervals seekableIntervals = mDecoder->GetSeekable();
+ if (seekableIntervals.IsInvalid()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); // This will reject the promise.
+ return promise.forget();
+ }
+ seekableIntervals.ToTimeRanges(seekable);
+ uint32_t length = 0;
+ seekable->GetLength(&length);
+ if (!length) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return promise.forget();
+ }
+
+ // If the position we want to seek to is not in a seekable range, we seek
+ // to the closest position in the seekable ranges instead. If two positions
+ // are equally close, we seek to the closest position from the currentTime.
+ // See seeking spec, point 7 :
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#seeking
+ int32_t range = 0;
+ bool isInRange = false;
+ if (NS_FAILED(IsInRanges(*seekable, aTime, isInRange, range))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); // This will reject the promise.
+ return promise.forget();
+ }
+ if (!isInRange) {
+ if (range != -1) {
+ // |range + 1| can't be negative, because the only possible negative value
+ // for |range| is -1.
+ if (uint32_t(range + 1) < length) {
+ double leftBound, rightBound;
+ if (NS_FAILED(seekable->End(range, &leftBound))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return promise.forget();
+ }
+ if (NS_FAILED(seekable->Start(range + 1, &rightBound))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return promise.forget();
+ }
+ double distanceLeft = Abs(leftBound - aTime);
+ double distanceRight = Abs(rightBound - aTime);
+ if (distanceLeft == distanceRight) {
+ double currentTime = CurrentTime();
+ distanceLeft = Abs(leftBound - currentTime);
+ distanceRight = Abs(rightBound - currentTime);
+ }
+ aTime = (distanceLeft < distanceRight) ? leftBound : rightBound;
+ } else {
+ // Seek target is after the end last range in seekable data.
+ // Clamp the seek target to the end of the last seekable range.
+ if (NS_FAILED(seekable->End(length - 1, &aTime))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return promise.forget();
+ }
+ }
+ } else {
+ // aTime is before the first range in |seekable|, the closest point we can
+ // seek to is the start of the first range.
+ seekable->Start(0, &aTime);
+ }
+ }
+
+ // TODO: The spec requires us to update the current time to reflect the
+ // actual seek target before beginning the synchronous section, but
+ // that requires changing all MediaDecoderReaders to support telling
+ // us the fastSeek target, and it's currently not possible to get
+ // this information as we don't yet control the demuxer for all
+ // MediaDecoderReaders.
+
+ mPlayingBeforeSeek = IsPotentiallyPlaying();
+ // Set the Variable if the Seekstarted while active playing
+ if (mPlayingThroughTheAudioChannel) {
+ mPlayingThroughTheAudioChannelBeforeSeek = true;
+ }
+
+ // The media backend is responsible for dispatching the timeupdate
+ // event if it changes the playback position as a result of the seek.
+ LOG(LogLevel::Debug, ("%p SetCurrentTime(%f) starting seek", this, aTime));
+ nsresult rv = mDecoder->Seek(aTime, aSeekType, promise);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+
+ // We changed whether we're seeking so we need to AddRemoveSelfReference.
+ AddRemoveSelfReference();
+
+ return promise.forget();
+}
+
+NS_IMETHODIMP HTMLMediaElement::SetCurrentTime(double aCurrentTime)
+{
+ // Detect for a NaN and invalid values.
+ if (mozilla::IsNaN(aCurrentTime)) {
+ LOG(LogLevel::Debug, ("%p SetCurrentTime(%f) failed: bad time", this, aCurrentTime));
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult rv;
+ SetCurrentTime(aCurrentTime, rv);
+ return rv.StealNSResult();
+}
+
+double
+HTMLMediaElement::Duration() const
+{
+ if (mSrcStream) {
+ return std::numeric_limits<double>::infinity();
+ }
+
+ if (mDecoder) {
+ return mDecoder->GetDuration();
+ }
+
+ return std::numeric_limits<double>::quiet_NaN();
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetDuration(double* aDuration)
+{
+ *aDuration = Duration();
+ return NS_OK;
+}
+
+already_AddRefed<TimeRanges>
+HTMLMediaElement::Seekable() const
+{
+ RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc()));
+ if (mDecoder) {
+ mDecoder->GetSeekable().ToTimeRanges(ranges);
+ }
+ return ranges.forget();
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetSeekable(nsIDOMTimeRanges** aSeekable)
+{
+ RefPtr<TimeRanges> ranges = Seekable();
+ ranges.forget(aSeekable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetPaused(bool* aPaused)
+{
+ *aPaused = Paused();
+
+ return NS_OK;
+}
+
+already_AddRefed<TimeRanges>
+HTMLMediaElement::Played()
+{
+ RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc()));
+
+ uint32_t timeRangeCount = 0;
+ if (mPlayed) {
+ mPlayed->GetLength(&timeRangeCount);
+ }
+ for (uint32_t i = 0; i < timeRangeCount; i++) {
+ double begin;
+ double end;
+ mPlayed->Start(i, &begin);
+ mPlayed->End(i, &end);
+ ranges->Add(begin, end);
+ }
+
+ if (mCurrentPlayRangeStart != -1.0) {
+ double now = CurrentTime();
+ if (mCurrentPlayRangeStart != now) {
+ ranges->Add(mCurrentPlayRangeStart, now);
+ }
+ }
+
+ ranges->Normalize();
+ return ranges.forget();
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetPlayed(nsIDOMTimeRanges** aPlayed)
+{
+ RefPtr<TimeRanges> ranges = Played();
+ ranges.forget(aPlayed);
+ return NS_OK;
+}
+
+void
+HTMLMediaElement::Pause(ErrorResult& aRv)
+{
+ if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
+ LOG(LogLevel::Debug, ("Loading due to Pause()"));
+ DoLoad();
+ } else if (mDecoder) {
+ mDecoder->Pause();
+ }
+
+ bool oldPaused = mPaused;
+ mPaused = true;
+ mAutoplaying = false;
+ // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
+ AddRemoveSelfReference();
+ UpdateSrcMediaStreamPlaying();
+ UpdateAudioChannelPlayingState();
+
+ if (!oldPaused) {
+ FireTimeUpdate(false);
+ DispatchAsyncEvent(NS_LITERAL_STRING("pause"));
+ }
+}
+
+NS_IMETHODIMP HTMLMediaElement::Pause()
+{
+ ErrorResult rv;
+ Pause(rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetVolume(double* aVolume)
+{
+ *aVolume = Volume();
+ return NS_OK;
+}
+
+void
+HTMLMediaElement::SetVolume(double aVolume, ErrorResult& aRv)
+{
+ if (aVolume < 0.0 || aVolume > 1.0) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ if (aVolume == mVolume)
+ return;
+
+ mVolume = aVolume;
+
+ // Here we want just to update the volume.
+ SetVolumeInternal();
+
+ DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
+}
+
+NS_IMETHODIMP HTMLMediaElement::SetVolume(double aVolume)
+{
+ ErrorResult rv;
+ SetVolume(aVolume, rv);
+ return rv.StealNSResult();
+}
+
+void
+HTMLMediaElement::MozGetMetadata(JSContext* cx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ if (mReadyState < nsIDOMHTMLMediaElement::HAVE_METADATA) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ JS::Rooted<JSObject*> tags(cx, JS_NewPlainObject(cx));
+ if (!tags) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ if (mTags) {
+ for (auto iter = mTags->ConstIter(); !iter.Done(); iter.Next()) {
+ nsString wideValue = NS_ConvertUTF8toUTF16(iter.UserData());
+ JS::Rooted<JSString*> string(cx,
+ JS_NewUCStringCopyZ(cx, wideValue.Data()));
+ if (!string || !JS_DefineProperty(cx, tags, iter.Key().Data(), string,
+ JSPROP_ENUMERATE)) {
+ NS_WARNING("couldn't create metadata object!");
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ }
+ }
+
+ aRetval.set(tags);
+}
+
+NS_IMETHODIMP
+HTMLMediaElement::MozGetMetadata(JSContext* cx, JS::MutableHandle<JS::Value> aValue)
+{
+ ErrorResult rv;
+ JS::Rooted<JSObject*> obj(cx);
+ MozGetMetadata(cx, &obj, rv);
+ if (!rv.Failed()) {
+ MOZ_ASSERT(obj);
+ aValue.setObject(*obj);
+ }
+
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetMuted(bool* aMuted)
+{
+ *aMuted = Muted();
+ return NS_OK;
+}
+
+void HTMLMediaElement::SetMutedInternal(uint32_t aMuted)
+{
+ uint32_t oldMuted = mMuted;
+ mMuted = aMuted;
+
+ if (!!aMuted == !!oldMuted) {
+ return;
+ }
+
+ SetVolumeInternal();
+}
+
+void HTMLMediaElement::SetVolumeInternal()
+{
+ float effectiveVolume = ComputedVolume();
+
+ if (mDecoder) {
+ mDecoder->SetVolume(effectiveVolume);
+ } else if (MediaStream* stream = GetSrcMediaStream()) {
+ if (mSrcStreamIsPlaying) {
+ stream->SetAudioOutputVolume(this, effectiveVolume);
+ }
+ }
+
+ NotifyAudioPlaybackChanged(
+ AudioChannelService::AudibleChangedReasons::eVolumeChanged);
+}
+
+NS_IMETHODIMP HTMLMediaElement::SetMuted(bool aMuted)
+{
+ if (aMuted == Muted()) {
+ return NS_OK;
+ }
+
+ if (aMuted) {
+ SetMutedInternal(mMuted | MUTED_BY_CONTENT);
+ } else {
+ SetMutedInternal(mMuted & ~MUTED_BY_CONTENT);
+ }
+
+ DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
+ return NS_OK;
+}
+
+class HTMLMediaElement::StreamCaptureTrackSource :
+ public MediaStreamTrackSource,
+ public MediaStreamTrackSource::Sink
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(StreamCaptureTrackSource,
+ MediaStreamTrackSource)
+
+ StreamCaptureTrackSource(HTMLMediaElement* aElement,
+ MediaStreamTrackSource* aCapturedTrackSource,
+ DOMMediaStream* aOwningStream,
+ TrackID aDestinationTrackID)
+ : MediaStreamTrackSource(aCapturedTrackSource->GetPrincipal(),
+ nsString())
+ , mElement(aElement)
+ , mCapturedTrackSource(aCapturedTrackSource)
+ , mOwningStream(aOwningStream)
+ , mDestinationTrackID(aDestinationTrackID)
+ {
+ MOZ_ASSERT(mElement);
+ MOZ_ASSERT(mCapturedTrackSource);
+ MOZ_ASSERT(mOwningStream);
+ MOZ_ASSERT(IsTrackIDExplicit(mDestinationTrackID));
+ }
+
+ void Destroy() override
+ {
+ if (mCapturedTrackSource) {
+ mCapturedTrackSource->UnregisterSink(this);
+ mCapturedTrackSource = nullptr;
+ }
+ }
+
+ MediaSourceEnum GetMediaSource() const override
+ {
+ return MediaSourceEnum::Other;
+ }
+
+ CORSMode GetCORSMode() const override
+ {
+ if (!mCapturedTrackSource) {
+ // This could happen during shutdown.
+ return CORS_NONE;
+ }
+
+ return mCapturedTrackSource->GetCORSMode();
+ }
+
+ void Stop() override
+ {
+ if (mElement && mElement->mSrcStream) {
+ // Only notify if we're still playing the source stream. GC might have
+ // cleared it before the track sources.
+ mElement->NotifyOutputTrackStopped(mOwningStream, mDestinationTrackID);
+ }
+ mElement = nullptr;
+ mOwningStream = nullptr;
+
+ Destroy();
+ }
+
+ void PrincipalChanged() override
+ {
+ if (!mCapturedTrackSource) {
+ // This could happen during shutdown.
+ return;
+ }
+
+ mPrincipal = mCapturedTrackSource->GetPrincipal();
+ MediaStreamTrackSource::PrincipalChanged();
+ }
+
+private:
+ virtual ~StreamCaptureTrackSource() {}
+
+ RefPtr<HTMLMediaElement> mElement;
+ RefPtr<MediaStreamTrackSource> mCapturedTrackSource;
+ RefPtr<DOMMediaStream> mOwningStream;
+ TrackID mDestinationTrackID;
+};
+
+NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::StreamCaptureTrackSource,
+ MediaStreamTrackSource)
+NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::StreamCaptureTrackSource,
+ MediaStreamTrackSource)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::StreamCaptureTrackSource)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::StreamCaptureTrackSource,
+ MediaStreamTrackSource,
+ mElement,
+ mCapturedTrackSource,
+ mOwningStream)
+
+class HTMLMediaElement::DecoderCaptureTrackSource :
+ public MediaStreamTrackSource,
+ public DecoderPrincipalChangeObserver
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DecoderCaptureTrackSource,
+ MediaStreamTrackSource)
+
+ explicit DecoderCaptureTrackSource(HTMLMediaElement* aElement)
+ : MediaStreamTrackSource(nsCOMPtr<nsIPrincipal>(aElement->GetCurrentPrincipal()).get(),
+ nsString())
+ , mElement(aElement)
+ {
+ MOZ_ASSERT(mElement);
+ mElement->AddDecoderPrincipalChangeObserver(this);
+ }
+
+ void Destroy() override
+ {
+ if (mElement) {
+ DebugOnly<bool> res = mElement->RemoveDecoderPrincipalChangeObserver(this);
+ NS_ASSERTION(res, "Removing decoder principal changed observer failed. "
+ "Had it already been removed?");
+ mElement = nullptr;
+ }
+ }
+
+ MediaSourceEnum GetMediaSource() const override
+ {
+ return MediaSourceEnum::Other;
+ }
+
+ CORSMode GetCORSMode() const override
+ {
+ if (!mElement) {
+ MOZ_ASSERT(false, "Should always have an element if in use");
+ return CORS_NONE;
+ }
+
+ return mElement->GetCORSMode();
+ }
+
+ void Stop() override
+ {
+ // We don't notify the source that a track was stopped since it will keep
+ // producing tracks until the element ends. The decoder also needs the
+ // tracks it created to be live at the source since the decoder's clock is
+ // based on MediaStreams during capture.
+ }
+
+ void NotifyDecoderPrincipalChanged() override
+ {
+ nsCOMPtr<nsIPrincipal> newPrincipal = mElement->GetCurrentPrincipal();
+ if (nsContentUtils::CombineResourcePrincipals(&mPrincipal, newPrincipal)) {
+ PrincipalChanged();
+ }
+ }
+
+protected:
+ virtual ~DecoderCaptureTrackSource()
+ {
+ }
+
+ RefPtr<HTMLMediaElement> mElement;
+};
+
+NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::DecoderCaptureTrackSource,
+ MediaStreamTrackSource)
+NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::DecoderCaptureTrackSource,
+ MediaStreamTrackSource)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::DecoderCaptureTrackSource)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::DecoderCaptureTrackSource,
+ MediaStreamTrackSource,
+ mElement)
+
+class HTMLMediaElement::CaptureStreamTrackSourceGetter :
+ public MediaStreamTrackSourceGetter
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CaptureStreamTrackSourceGetter,
+ MediaStreamTrackSourceGetter)
+
+ explicit CaptureStreamTrackSourceGetter(HTMLMediaElement* aElement)
+ : mElement(aElement) {}
+
+ already_AddRefed<dom::MediaStreamTrackSource>
+ GetMediaStreamTrackSource(TrackID aInputTrackID) override
+ {
+ if (mElement && mElement->mSrcStream) {
+ NS_ERROR("Captured media element playing a stream adds tracks explicitly on main thread.");
+ return nullptr;
+ }
+
+ // We can return a new source each time here, even for different streams,
+ // since the sources don't keep any internal state and all of them call
+ // through to the same HTMLMediaElement.
+ // If this changes (after implementing Stop()?) we'll have to ensure we
+ // return the same source for all requests to the same TrackID, and only
+ // have one getter.
+ return do_AddRef(new DecoderCaptureTrackSource(mElement));
+ }
+
+protected:
+ virtual ~CaptureStreamTrackSourceGetter() {}
+
+ RefPtr<HTMLMediaElement> mElement;
+};
+
+NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::CaptureStreamTrackSourceGetter,
+ MediaStreamTrackSourceGetter)
+NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::CaptureStreamTrackSourceGetter,
+ MediaStreamTrackSourceGetter)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::CaptureStreamTrackSourceGetter)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSourceGetter)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::CaptureStreamTrackSourceGetter,
+ MediaStreamTrackSourceGetter,
+ mElement)
+
+void
+HTMLMediaElement::SetCapturedOutputStreamsEnabled(bool aEnabled) {
+ for (OutputMediaStream& ms : mOutputStreams) {
+ if (ms.mCapturingDecoder) {
+ MOZ_ASSERT(!ms.mCapturingMediaStream);
+ continue;
+ }
+ for (auto pair : ms.mTrackPorts) {
+ MediaStream* outputSource = ms.mStream->GetInputStream();
+ if (!outputSource) {
+ NS_ERROR("No output source stream");
+ return;
+ }
+
+ TrackID id = pair.second()->GetDestinationTrackId();
+ outputSource->SetTrackEnabled(id, aEnabled ? DisabledTrackMode::ENABLED
+ : DisabledTrackMode::SILENCE_FREEZE);
+
+ LOG(LogLevel::Debug,
+ ("%s track %d for captured MediaStream %p",
+ aEnabled ? "Enabled" : "Disabled", id, ms.mStream.get()));
+ }
+ }
+}
+
+void
+HTMLMediaElement::AddCaptureMediaTrackToOutputStream(MediaTrack* aTrack,
+ OutputMediaStream& aOutputStream,
+ bool aAsyncAddtrack)
+{
+ if (aOutputStream.mCapturingDecoder) {
+ MOZ_ASSERT(!aOutputStream.mCapturingMediaStream);
+ return;
+ }
+ aOutputStream.mCapturingMediaStream = true;
+
+ if (aOutputStream.mStream == mSrcStream) {
+ // Cycle detected. This can happen since tracks are added async.
+ // We avoid forwarding it to the output here or we'd get into an infloop.
+ return;
+ }
+
+ MediaStream* outputSource = aOutputStream.mStream->GetInputStream();
+ if (!outputSource) {
+ NS_ERROR("No output source stream");
+ return;
+ }
+
+ ProcessedMediaStream* processedOutputSource =
+ outputSource->AsProcessedStream();
+ if (!processedOutputSource) {
+ NS_ERROR("Input stream not a ProcessedMediaStream");
+ return;
+ }
+
+ if (!aTrack) {
+ MOZ_ASSERT(false, "Bad MediaTrack");
+ return;
+ }
+
+ MediaStreamTrack* inputTrack = mSrcStream->GetTrackById(aTrack->GetId());
+ MOZ_ASSERT(inputTrack);
+ if (!inputTrack) {
+ NS_ERROR("Input track not found in source stream");
+ return;
+ }
+
+#if DEBUG
+ for (auto pair : aOutputStream.mTrackPorts) {
+ MOZ_ASSERT(pair.first() != aTrack->GetId(),
+ "Captured track already captured to output stream");
+ }
+#endif
+
+ TrackID destinationTrackID = aOutputStream.mNextAvailableTrackID++;
+ RefPtr<MediaStreamTrackSource> source =
+ new StreamCaptureTrackSource(this,
+ &inputTrack->GetSource(),
+ aOutputStream.mStream,
+ destinationTrackID);
+
+ MediaSegment::Type type = inputTrack->AsAudioStreamTrack()
+ ? MediaSegment::AUDIO
+ : MediaSegment::VIDEO;
+
+ RefPtr<MediaStreamTrack> track =
+ aOutputStream.mStream->CreateDOMTrack(destinationTrackID, type, source);
+
+ if (aAsyncAddtrack) {
+ NS_DispatchToMainThread(
+ NewRunnableMethod<StorensRefPtrPassByPtr<MediaStreamTrack>>(
+ aOutputStream.mStream, &DOMMediaStream::AddTrackInternal, track));
+ } else {
+ aOutputStream.mStream->AddTrackInternal(track);
+ }
+
+ // Track is muted initially, so we don't leak data if it's added while paused
+ // and an MSG iteration passes before the mute comes into effect.
+ processedOutputSource->SetTrackEnabled(destinationTrackID,
+ DisabledTrackMode::SILENCE_FREEZE);
+ RefPtr<MediaInputPort> port =
+ inputTrack->ForwardTrackContentsTo(processedOutputSource,
+ destinationTrackID);
+
+ Pair<nsString, RefPtr<MediaInputPort>> p(aTrack->GetId(), port);
+ aOutputStream.mTrackPorts.AppendElement(Move(p));
+
+ if (mSrcStreamIsPlaying) {
+ processedOutputSource->SetTrackEnabled(destinationTrackID,
+ DisabledTrackMode::ENABLED);
+ }
+
+ LOG(LogLevel::Debug,
+ ("Created %s track %p with id %d from track %p through MediaInputPort %p",
+ inputTrack->AsAudioStreamTrack() ? "audio" : "video",
+ track.get(), destinationTrackID, inputTrack, port.get()));
+}
+
+already_AddRefed<DOMMediaStream>
+HTMLMediaElement::CaptureStreamInternal(bool aFinishWhenEnded,
+ bool aCaptureAudio,
+ MediaStreamGraph* aGraph)
+{
+ MOZ_RELEASE_ASSERT(aGraph);
+
+ MarkAsContentSource(CallerAPI::CAPTURE_STREAM);
+
+ nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
+ if (!window) {
+ return nullptr;
+ }
+ if (ContainsRestrictedContent()) {
+ return nullptr;
+ }
+
+ if (!mOutputStreams.IsEmpty() &&
+ aGraph != mOutputStreams[0].mStream->GetInputStream()->Graph()) {
+ return nullptr;
+ }
+
+ OutputMediaStream* out = mOutputStreams.AppendElement();
+ MediaStreamTrackSourceGetter* getter = new CaptureStreamTrackSourceGetter(this);
+ out->mStream = DOMMediaStream::CreateTrackUnionStreamAsInput(window, aGraph, getter);
+ out->mStream->SetInactiveOnFinish();
+ out->mFinishWhenEnded = aFinishWhenEnded;
+ out->mCapturingAudioOnly = aCaptureAudio;
+
+ if (aCaptureAudio) {
+ if (mSrcStream) {
+ // We don't support applying volume and mute to the captured stream, when
+ // capturing a MediaStream.
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("Media"),
+ OwnerDoc(),
+ nsContentUtils::eDOM_PROPERTIES,
+ "MediaElementAudioCaptureOfMediaStreamError");
+ return nullptr;
+ }
+
+ // mAudioCaptured tells the user that the audio played by this media element
+ // is being routed to the captureStreams *instead* of being played to
+ // speakers.
+ mAudioCaptured = true;
+ }
+
+ if (mDecoder) {
+ out->mCapturingDecoder = true;
+ mDecoder->AddOutputStream(out->mStream->GetInputStream()->AsProcessedStream(),
+ aFinishWhenEnded);
+ } else if (mSrcStream) {
+ out->mCapturingMediaStream = true;
+ }
+
+ if (mReadyState == HAVE_NOTHING) {
+ // Do not expose the tracks until we have metadata.
+ RefPtr<DOMMediaStream> result = out->mStream;
+ return result.forget();
+ }
+
+ if (mDecoder) {
+ if (HasAudio()) {
+ TrackID audioTrackId = mMediaInfo.mAudio.mTrackId;
+ RefPtr<MediaStreamTrackSource> trackSource =
+ getter->GetMediaStreamTrackSource(audioTrackId);
+ RefPtr<MediaStreamTrack> track =
+ out->mStream->CreateDOMTrack(audioTrackId, MediaSegment::AUDIO,
+ trackSource);
+ out->mStream->AddTrackInternal(track);
+ LOG(LogLevel::Debug,
+ ("Created audio track %d for captured decoder", audioTrackId));
+ }
+ if (IsVideo() && HasVideo() && !out->mCapturingAudioOnly) {
+ TrackID videoTrackId = mMediaInfo.mVideo.mTrackId;
+ RefPtr<MediaStreamTrackSource> trackSource =
+ getter->GetMediaStreamTrackSource(videoTrackId);
+ RefPtr<MediaStreamTrack> track =
+ out->mStream->CreateDOMTrack(videoTrackId, MediaSegment::VIDEO,
+ trackSource);
+ out->mStream->AddTrackInternal(track);
+ LOG(LogLevel::Debug,
+ ("Created video track %d for captured decoder", videoTrackId));
+ }
+ }
+
+ if (mSrcStream) {
+ for (size_t i = 0; i < AudioTracks()->Length(); ++i) {
+ AudioTrack* t = (*AudioTracks())[i];
+ if (t->Enabled()) {
+ AddCaptureMediaTrackToOutputStream(t, *out, false);
+ }
+ }
+ if (IsVideo() && !out->mCapturingAudioOnly) {
+ // Only add video tracks if we're a video element and the output stream
+ // wants video.
+ for (size_t i = 0; i < VideoTracks()->Length(); ++i) {
+ VideoTrack* t = (*VideoTracks())[i];
+ if (t->Selected()) {
+ AddCaptureMediaTrackToOutputStream(t, *out, false);
+ }
+ }
+ }
+ }
+ RefPtr<DOMMediaStream> result = out->mStream;
+ return result.forget();
+}
+
+already_AddRefed<DOMMediaStream>
+HTMLMediaElement::CaptureAudio(ErrorResult& aRv,
+ MediaStreamGraph* aGraph)
+{
+ MOZ_RELEASE_ASSERT(aGraph);
+
+ RefPtr<DOMMediaStream> stream =
+ CaptureStreamInternal(false, true, aGraph);
+ if (!stream) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return stream.forget();
+}
+
+already_AddRefed<DOMMediaStream>
+HTMLMediaElement::MozCaptureStream(ErrorResult& aRv)
+{
+ MediaStreamGraph::GraphDriverType graphDriverType =
+ HasAudio() ? MediaStreamGraph::AUDIO_THREAD_DRIVER
+ : MediaStreamGraph::SYSTEM_THREAD_DRIVER;
+ MediaStreamGraph* graph =
+ MediaStreamGraph::GetInstance(graphDriverType, mAudioChannel);
+
+ RefPtr<DOMMediaStream> stream =
+ CaptureStreamInternal(false, false, graph);
+ if (!stream) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return stream.forget();
+}
+
+already_AddRefed<DOMMediaStream>
+HTMLMediaElement::MozCaptureStreamUntilEnded(ErrorResult& aRv)
+{
+ MediaStreamGraph::GraphDriverType graphDriverType =
+ HasAudio() ? MediaStreamGraph::AUDIO_THREAD_DRIVER
+ : MediaStreamGraph::SYSTEM_THREAD_DRIVER;
+ MediaStreamGraph* graph =
+ MediaStreamGraph::GetInstance(graphDriverType, mAudioChannel);
+
+ RefPtr<DOMMediaStream> stream =
+ CaptureStreamInternal(true, false, graph);
+ if (!stream) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return stream.forget();
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetMozAudioCaptured(bool* aCaptured)
+{
+ *aCaptured = MozAudioCaptured();
+ return NS_OK;
+}
+
+class MediaElementSetForURI : public nsURIHashKey {
+public:
+ explicit MediaElementSetForURI(const nsIURI* aKey) : nsURIHashKey(aKey) {}
+ MediaElementSetForURI(const MediaElementSetForURI& toCopy)
+ : nsURIHashKey(toCopy), mElements(toCopy.mElements) {}
+ nsTArray<HTMLMediaElement*> mElements;
+};
+
+typedef nsTHashtable<MediaElementSetForURI> MediaElementURITable;
+// Elements in this table must have non-null mDecoder and mLoadingSrc, and those
+// can't change while the element is in the table. The table is keyed by
+// the element's mLoadingSrc. Each entry has a list of all elements with the
+// same mLoadingSrc.
+static MediaElementURITable* gElementTable;
+
+#ifdef DEBUG
+static bool
+URISafeEquals(nsIURI* a1, nsIURI* a2)
+{
+ if (!a1 || !a2) {
+ // Consider two empty URIs *not* equal!
+ return false;
+ }
+ bool equal = false;
+ nsresult rv = a1->Equals(a2, &equal);
+ return NS_SUCCEEDED(rv) && equal;
+}
+// Returns the number of times aElement appears in the media element table
+// for aURI. If this returns other than 0 or 1, there's a bug somewhere!
+static unsigned
+MediaElementTableCount(HTMLMediaElement* aElement, nsIURI* aURI)
+{
+ if (!gElementTable || !aElement) {
+ return 0;
+ }
+ uint32_t uriCount = 0;
+ uint32_t otherCount = 0;
+ for (auto it = gElementTable->ConstIter(); !it.Done(); it.Next()) {
+ MediaElementSetForURI* entry = it.Get();
+ uint32_t count = 0;
+ for (const auto& elem : entry->mElements) {
+ if (elem == aElement) {
+ count++;
+ }
+ }
+ if (URISafeEquals(aURI, entry->GetKey())) {
+ uriCount = count;
+ } else {
+ otherCount += count;
+ }
+ }
+ NS_ASSERTION(otherCount == 0, "Should not have entries for unknown URIs");
+ return uriCount;
+}
+#endif
+
+void
+HTMLMediaElement::AddMediaElementToURITable()
+{
+ NS_ASSERTION(mDecoder && mDecoder->GetResource(), "Call this only with decoder Load called");
+ NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
+ "Should not have entry for element in element table before addition");
+ if (!gElementTable) {
+ gElementTable = new MediaElementURITable();
+ }
+ MediaElementSetForURI* entry = gElementTable->PutEntry(mLoadingSrc);
+ entry->mElements.AppendElement(this);
+ NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 1,
+ "Should have a single entry for element in element table after addition");
+}
+
+void
+HTMLMediaElement::RemoveMediaElementFromURITable()
+{
+ if (!mDecoder || !mLoadingSrc || !gElementTable) {
+ return;
+ }
+ MediaElementSetForURI* entry = gElementTable->GetEntry(mLoadingSrc);
+ if (!entry) {
+ return;
+ }
+ entry->mElements.RemoveElement(this);
+ if (entry->mElements.IsEmpty()) {
+ gElementTable->RemoveEntry(entry);
+ if (gElementTable->Count() == 0) {
+ delete gElementTable;
+ gElementTable = nullptr;
+ }
+ }
+ NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
+ "After remove, should no longer have an entry in element table");
+}
+
+HTMLMediaElement*
+HTMLMediaElement::LookupMediaElementURITable(nsIURI* aURI)
+{
+ if (!gElementTable) {
+ return nullptr;
+ }
+ MediaElementSetForURI* entry = gElementTable->GetEntry(aURI);
+ if (!entry) {
+ return nullptr;
+ }
+ for (uint32_t i = 0; i < entry->mElements.Length(); ++i) {
+ HTMLMediaElement* elem = entry->mElements[i];
+ bool equal;
+ // Look for elements that have the same principal and CORS mode.
+ // Ditto for anything else that could cause us to send different headers.
+ if (NS_SUCCEEDED(elem->NodePrincipal()->Equals(NodePrincipal(), &equal)) && equal &&
+ elem->mCORSMode == mCORSMode) {
+ NS_ASSERTION(elem->mDecoder && elem->mDecoder->GetResource(), "Decoder gone");
+ MediaResource* resource = elem->mDecoder->GetResource();
+ if (resource->CanClone()) {
+ return elem;
+ }
+ }
+ }
+ return nullptr;
+}
+
+class HTMLMediaElement::ShutdownObserver : public nsIObserver {
+ enum class Phase : int8_t {
+ Init,
+ Subscribed,
+ Unsubscribed
+ };
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Observe(nsISupports*, const char* aTopic, const char16_t*) override {
+ MOZ_DIAGNOSTIC_ASSERT(mPhase == Phase::Subscribed);
+ MOZ_DIAGNOSTIC_ASSERT(mWeak);
+ if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ mWeak->NotifyShutdownEvent();
+ }
+ return NS_OK;
+ }
+ void Subscribe(HTMLMediaElement* aPtr) {
+ MOZ_DIAGNOSTIC_ASSERT(mPhase == Phase::Init);
+ MOZ_DIAGNOSTIC_ASSERT(!mWeak);
+ mWeak = aPtr;
+ nsContentUtils::RegisterShutdownObserver(this);
+ mPhase = Phase::Subscribed;
+ }
+ void Unsubscribe() {
+ MOZ_DIAGNOSTIC_ASSERT(mPhase == Phase::Subscribed);
+ MOZ_DIAGNOSTIC_ASSERT(mWeak);
+ mWeak = nullptr;
+ nsContentUtils::UnregisterShutdownObserver(this);
+ mPhase = Phase::Unsubscribed;
+ }
+ void AddRefMediaElement() {
+ mWeak->AddRef();
+ }
+ void ReleaseMediaElement() {
+ mWeak->Release();
+ }
+private:
+ virtual ~ShutdownObserver() {
+ MOZ_DIAGNOSTIC_ASSERT(mPhase == Phase::Unsubscribed);
+ MOZ_DIAGNOSTIC_ASSERT(!mWeak);
+ }
+ // Guaranteed to be valid by HTMLMediaElement.
+ HTMLMediaElement* mWeak = nullptr;
+ Phase mPhase = Phase::Init;
+};
+
+NS_IMPL_ISUPPORTS(HTMLMediaElement::ShutdownObserver, nsIObserver)
+
+HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo),
+ mWatchManager(this, AbstractThread::MainThread()),
+ mSrcStreamTracksAvailable(false),
+ mSrcStreamPausedCurrentTime(-1),
+ mShutdownObserver(new ShutdownObserver),
+ mCurrentLoadID(0),
+ mNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY),
+ mReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING, "HTMLMediaElement::mReadyState"),
+ mLoadWaitStatus(NOT_WAITING),
+ mVolume(1.0),
+ mPreloadAction(PRELOAD_UNDEFINED),
+ mLastCurrentTime(0.0),
+ mFragmentStart(-1.0),
+ mFragmentEnd(-1.0),
+ mDefaultPlaybackRate(1.0),
+ mPlaybackRate(1.0),
+ mPreservesPitch(true),
+ mPlayed(new TimeRanges(ToSupports(OwnerDoc()))),
+ mCurrentPlayRangeStart(-1.0),
+ mBegun(false),
+ mLoadedDataFired(false),
+ mAutoplaying(true),
+ mAutoplayEnabled(true),
+ mPaused(true),
+ mMuted(0),
+ mAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED),
+ mStatsShowing(false),
+ mAllowCasting(false),
+ mIsCasting(false),
+ mAudioCaptured(false),
+ mAudioCapturedByWindow(false),
+ mPlayingBeforeSeek(false),
+ mPlayingThroughTheAudioChannelBeforeSeek(false),
+ mPausedForInactiveDocumentOrChannel(false),
+ mEventDeliveryPaused(false),
+ mIsRunningLoadMethod(false),
+ mIsDoingExplicitLoad(false),
+ mIsLoadingFromSourceChildren(false),
+ mDelayingLoadEvent(false),
+ mIsRunningSelectResource(false),
+ mHaveQueuedSelectResource(false),
+ mSuspendedAfterFirstFrame(false),
+ mAllowSuspendAfterFirstFrame(true),
+ mHasPlayedOrSeeked(false),
+ mHasSelfReference(false),
+ mShuttingDown(false),
+ mSuspendedForPreloadNone(false),
+ mSrcStreamIsPlaying(false),
+ mMediaSecurityVerified(false),
+ mCORSMode(CORS_NONE),
+ mIsEncrypted(false),
+ mWaitingForKey(NOT_WAITING_FOR_KEY),
+ mDownloadSuspendedByCache(false, "HTMLMediaElement::mDownloadSuspendedByCache"),
+ mAudioChannelVolume(1.0),
+ mPlayingThroughTheAudioChannel(false),
+ mDisableVideo(false),
+ mHasUserInteraction(false),
+ mFirstFrameLoaded(false),
+ mDefaultPlaybackStartPosition(0.0),
+ mIsAudioTrackAudible(false),
+ mAudible(IsAudible()),
+ mVisibilityState(Visibility::APPROXIMATELY_NONVISIBLE),
+ mErrorSink(new ErrorSink(this))
+{
+ ErrorResult rv;
+
+ double defaultVolume = Preferences::GetFloat("media.default_volume", 1.0);
+ SetVolume(defaultVolume, rv);
+
+ mAudioChannel = AudioChannelService::GetDefaultAudioChannel();
+
+ mPaused.SetOuter(this);
+
+ RegisterActivityObserver();
+ NotifyOwnerDocumentActivityChanged();
+
+ MOZ_ASSERT(NS_IsMainThread());
+ mWatchManager.Watch(mDownloadSuspendedByCache, &HTMLMediaElement::UpdateReadyStateInternal);
+ // Paradoxically, there is a self-edge whereby UpdateReadyStateInternal refuses
+ // to run until mReadyState reaches at least HAVE_METADATA by some other means.
+ mWatchManager.Watch(mReadyState, &HTMLMediaElement::UpdateReadyStateInternal);
+
+ mShutdownObserver->Subscribe(this);
+ MaybeCreateAudioChannelAgent();
+}
+
+HTMLMediaElement::~HTMLMediaElement()
+{
+ NS_ASSERTION(!mHasSelfReference,
+ "How can we be destroyed if we're still holding a self reference?");
+
+ mShutdownObserver->Unsubscribe();
+
+ if (mVideoFrameContainer) {
+ mVideoFrameContainer->ForgetElement();
+ }
+ UnregisterActivityObserver();
+ if (mDecoder) {
+ ShutdownDecoder();
+ }
+ if (mProgressTimer) {
+ StopProgress();
+ }
+ if (mVideoDecodeSuspendTimer) {
+ mVideoDecodeSuspendTimer->Cancel();
+ mVideoDecodeSuspendTimer = nullptr;
+ }
+ if (mSrcStream) {
+ EndSrcMediaStreamPlayback();
+ }
+
+ if (mCaptureStreamPort) {
+ mCaptureStreamPort->Destroy();
+ mCaptureStreamPort = nullptr;
+ }
+
+ NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
+ "Destroyed media element should no longer be in element table");
+
+ if (mChannelLoader) {
+ mChannelLoader->Cancel();
+ }
+
+ WakeLockRelease();
+ mAudioChannelAgent = nullptr;
+}
+
+void HTMLMediaElement::StopSuspendingAfterFirstFrame()
+{
+ mAllowSuspendAfterFirstFrame = false;
+ if (!mSuspendedAfterFirstFrame)
+ return;
+ mSuspendedAfterFirstFrame = false;
+ if (mDecoder) {
+ mDecoder->Resume();
+ }
+}
+
+void HTMLMediaElement::SetPlayedOrSeeked(bool aValue)
+{
+ if (aValue == mHasPlayedOrSeeked) {
+ return;
+ }
+
+ mHasPlayedOrSeeked = aValue;
+
+ // Force a reflow so that the poster frame hides or shows immediately.
+ nsIFrame* frame = GetPrimaryFrame();
+ if (!frame) {
+ return;
+ }
+ frame->PresContext()->PresShell()->FrameNeedsReflow(frame,
+ nsIPresShell::eTreeChange,
+ NS_FRAME_IS_DIRTY);
+}
+
+void
+HTMLMediaElement::NotifyXPCOMShutdown()
+{
+ ShutdownDecoder();
+}
+
+void
+HTMLMediaElement::Play(ErrorResult& aRv)
+{
+ if (!IsAllowedToPlay()) {
+ MaybeDoLoad();
+ return;
+ }
+
+ nsresult rv = PlayInternal();
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+
+ OpenUnsupportedMediaWithExternalAppIfNeeded();
+}
+
+nsresult
+HTMLMediaElement::PlayInternal()
+{
+ // Play was not blocked so assume user interacted with the element.
+ mHasUserInteraction = true;
+
+ StopSuspendingAfterFirstFrame();
+ SetPlayedOrSeeked(true);
+
+ MaybeDoLoad();
+ if (mSuspendedForPreloadNone) {
+ ResumeLoad(PRELOAD_ENOUGH);
+ }
+
+ // Even if we just did Load() or ResumeLoad(), we could already have a decoder
+ // here if we managed to clone an existing decoder.
+ if (mDecoder) {
+ if (mDecoder->IsEnded()) {
+ SetCurrentTime(0);
+ }
+ if (!mPausedForInactiveDocumentOrChannel) {
+ nsresult rv = mDecoder->Play();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ if (mCurrentPlayRangeStart == -1.0) {
+ mCurrentPlayRangeStart = CurrentTime();
+ }
+
+ bool oldPaused = mPaused;
+ mPaused = false;
+ mAutoplaying = false;
+ SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
+
+ // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
+ // and our preload status.
+ AddRemoveSelfReference();
+ UpdatePreloadAction();
+ UpdateSrcMediaStreamPlaying();
+ UpdateAudioChannelPlayingState();
+
+ // We should check audio channel playing state before dispatching any events,
+ // because we don't want to dispatch events for blocked media. For blocked
+ // media, the event would be pending until media is resumed.
+ // TODO: If the playback has ended, then the user agent must set
+ // seek to the effective start.
+ if (oldPaused) {
+ DispatchAsyncEvent(NS_LITERAL_STRING("play"));
+ switch (mReadyState) {
+ case nsIDOMHTMLMediaElement::HAVE_NOTHING:
+ DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
+ break;
+ case nsIDOMHTMLMediaElement::HAVE_METADATA:
+ case nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA:
+ FireTimeUpdate(false);
+ DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
+ break;
+ case nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA:
+ case nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA:
+ FireTimeUpdate(false);
+ DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+HTMLMediaElement::MaybeDoLoad()
+{
+ if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
+ DoLoad();
+ }
+}
+
+NS_IMETHODIMP HTMLMediaElement::Play()
+{
+ if (!IsAllowedToPlay()) {
+ MaybeDoLoad();
+ return NS_OK;
+ }
+
+ nsresult rv = PlayInternal();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ OpenUnsupportedMediaWithExternalAppIfNeeded();
+ return NS_OK;
+}
+
+HTMLMediaElement::WakeLockBoolWrapper&
+HTMLMediaElement::WakeLockBoolWrapper::operator=(bool val)
+{
+ if (mValue == val) {
+ return *this;
+ }
+
+ mValue = val;
+ UpdateWakeLock();
+ return *this;
+}
+
+HTMLMediaElement::WakeLockBoolWrapper::~WakeLockBoolWrapper()
+{
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+}
+
+void
+HTMLMediaElement::WakeLockBoolWrapper::SetCanPlay(bool aCanPlay)
+{
+ mCanPlay = aCanPlay;
+ UpdateWakeLock();
+}
+
+void
+HTMLMediaElement::WakeLockBoolWrapper::UpdateWakeLock()
+{
+ if (!mOuter) {
+ return;
+ }
+
+ bool playing = (!mValue && mCanPlay);
+
+ if (playing) {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ mOuter->WakeLockCreate();
+ } else if (!mTimer) {
+ // Don't release the wake lock immediately; instead, release it after a
+ // grace period.
+ int timeout = Preferences::GetInt("media.wakelock_timeout", 2000);
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (mTimer) {
+ mTimer->InitWithFuncCallback(TimerCallback, this, timeout,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ }
+}
+
+void
+HTMLMediaElement::WakeLockBoolWrapper::TimerCallback(nsITimer* aTimer,
+ void* aClosure)
+{
+ WakeLockBoolWrapper* wakeLock = static_cast<WakeLockBoolWrapper*>(aClosure);
+ wakeLock->mOuter->WakeLockRelease();
+ wakeLock->mTimer = nullptr;
+}
+
+void
+HTMLMediaElement::WakeLockCreate()
+{
+ if (!mWakeLock) {
+ RefPtr<power::PowerManagerService> pmService =
+ power::PowerManagerService::GetInstance();
+ NS_ENSURE_TRUE_VOID(pmService);
+
+ ErrorResult rv;
+ mWakeLock = pmService->NewWakeLock(NS_LITERAL_STRING("cpu"),
+ OwnerDoc()->GetInnerWindow(),
+ rv);
+ }
+}
+
+void
+HTMLMediaElement::WakeLockRelease()
+{
+ if (mWakeLock) {
+ ErrorResult rv;
+ mWakeLock->Unlock(rv);
+ rv.SuppressException();
+ mWakeLock = nullptr;
+ }
+}
+
+HTMLMediaElement::OutputMediaStream::OutputMediaStream()
+ : mFinishWhenEnded(false)
+ , mCapturingAudioOnly(false)
+ , mCapturingDecoder(false)
+ , mCapturingMediaStream(false)
+ , mNextAvailableTrackID(1) {}
+
+HTMLMediaElement::OutputMediaStream::~OutputMediaStream()
+{
+ for (auto pair : mTrackPorts) {
+ pair.second()->Destroy();
+ }
+}
+
+bool HTMLMediaElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ // Mappings from 'preload' attribute strings to an enumeration.
+ static const nsAttrValue::EnumTable kPreloadTable[] = {
+ { "", HTMLMediaElement::PRELOAD_ATTR_EMPTY },
+ { "none", HTMLMediaElement::PRELOAD_ATTR_NONE },
+ { "metadata", HTMLMediaElement::PRELOAD_ATTR_METADATA },
+ { "auto", HTMLMediaElement::PRELOAD_ATTR_AUTO },
+ { nullptr, 0 }
+ };
+
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (ParseImageAttribute(aAttribute, aValue, aResult)) {
+ return true;
+ }
+ if (aAttribute == nsGkAtoms::crossorigin) {
+ ParseCORSValue(aValue, aResult);
+ return true;
+ }
+ if (aAttribute == nsGkAtoms::preload) {
+ return aResult.ParseEnumValue(aValue, kPreloadTable, false);
+ }
+
+ if (aAttribute == nsGkAtoms::mozaudiochannel) {
+ const nsAttrValue::EnumTable* table =
+ AudioChannelService::GetAudioChannelTable();
+ MOZ_ASSERT(table);
+
+ bool parsed = aResult.ParseEnumValue(aValue, table, false, &table[0]);
+ if (!parsed) {
+ return false;
+ }
+
+ AudioChannel audioChannel = static_cast<AudioChannel>(aResult.GetEnumValue());
+
+ if (audioChannel == mAudioChannel ||
+ !CheckAudioChannelPermissions(aValue)) {
+ return true;
+ }
+
+ // We cannot change the AudioChannel of a decoder.
+ if (mDecoder) {
+ return true;
+ }
+
+ mAudioChannel = audioChannel;
+
+ if (mSrcStream) {
+ RefPtr<MediaStream> stream = GetSrcMediaStream();
+ if (stream) {
+ stream->SetAudioChannelType(mAudioChannel);
+ }
+ }
+
+ return true;
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+bool HTMLMediaElement::CheckAudioChannelPermissions(const nsAString& aString)
+{
+ // Only normal channel doesn't need permission.
+ if (aString.EqualsASCII("normal")) {
+ return true;
+ }
+
+ // Maybe this audio channel is equal to the default value from the pref.
+ nsString audioChannel;
+ AudioChannelService::GetDefaultAudioChannelString(audioChannel);
+ if (audioChannel.Equals(aString)) {
+ return true;
+ }
+
+ nsCOMPtr<nsIPermissionManager> permissionManager =
+ services::GetPermissionManager();
+ if (!permissionManager) {
+ return false;
+ }
+
+ uint32_t perm = nsIPermissionManager::UNKNOWN_ACTION;
+ permissionManager->TestExactPermissionFromPrincipal(NodePrincipal(),
+ nsCString(NS_LITERAL_CSTRING("audio-channel-") + NS_ConvertUTF16toUTF8(aString)).get(), &perm);
+ if (perm != nsIPermissionManager::ALLOW_ACTION) {
+ return false;
+ }
+
+ return true;
+}
+
+void HTMLMediaElement::DoneCreatingElement()
+{
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::muted)) {
+ mMuted |= MUTED_BY_CONTENT;
+ }
+}
+
+bool HTMLMediaElement::IsHTMLFocusable(bool aWithMouse,
+ bool* aIsFocusable,
+ int32_t* aTabIndex)
+{
+ if (nsGenericHTMLElement::IsHTMLFocusable(aWithMouse, aIsFocusable, aTabIndex)) {
+ return true;
+ }
+
+ *aIsFocusable = true;
+ return false;
+}
+
+int32_t HTMLMediaElement::TabIndexDefault()
+{
+ return 0;
+}
+
+nsresult HTMLMediaElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify)
+{
+ nsresult rv =
+ nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
+ aNotify);
+ if (NS_FAILED(rv))
+ return rv;
+ if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::src) {
+ DoLoad();
+ }
+ if (aNotify && aNameSpaceID == kNameSpaceID_None) {
+ if (aName == nsGkAtoms::autoplay) {
+ StopSuspendingAfterFirstFrame();
+ CheckAutoplayDataReady();
+ // This attribute can affect AddRemoveSelfReference
+ AddRemoveSelfReference();
+ UpdatePreloadAction();
+ } else if (aName == nsGkAtoms::preload) {
+ UpdatePreloadAction();
+ }
+ }
+
+ return rv;
+}
+
+nsresult HTMLMediaElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttr,
+ bool aNotify)
+{
+ nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttr, aNotify);
+ if (NS_FAILED(rv))
+ return rv;
+ if (aNotify && aNameSpaceID == kNameSpaceID_None) {
+ if (aAttr == nsGkAtoms::autoplay) {
+ // This attribute can affect AddRemoveSelfReference
+ AddRemoveSelfReference();
+ UpdatePreloadAction();
+ } else if (aAttr == nsGkAtoms::preload) {
+ UpdatePreloadAction();
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+HTMLMediaElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::src) {
+ mSrcMediaSource = nullptr;
+ if (aValue) {
+ nsString srcStr = aValue->GetStringValue();
+ nsCOMPtr<nsIURI> uri;
+ NewURIFromString(srcStr, getter_AddRefs(uri));
+ if (uri && IsMediaSourceURI(uri)) {
+ nsresult rv =
+ NS_GetSourceForMediaSourceURI(uri, getter_AddRefs(mSrcMediaSource));
+ if (NS_FAILED(rv)) {
+ nsAutoString spec;
+ GetCurrentSrc(spec);
+ const char16_t* params[] = { spec.get() };
+ ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
+ }
+ }
+ }
+ }
+
+ return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+nsresult HTMLMediaElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument,
+ aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+
+ mUnboundFromTree = false;
+
+ if (aDocument) {
+ mAutoplayEnabled =
+ IsAutoplayEnabled() && (!aDocument || !aDocument->IsStaticDocument()) &&
+ !IsEditable();
+ // The preload action depends on the value of the autoplay attribute.
+ // It's value may have changed, so update it.
+ UpdatePreloadAction();
+ }
+
+ if (mDecoder) {
+ // When the MediaElement is binding to tree, the dormant status is
+ // aligned to document's hidden status.
+ mDecoder->NotifyOwnerActivityChanged(!IsHidden());
+ }
+
+ return rv;
+}
+
+/* static */
+void HTMLMediaElement::VideoDecodeSuspendTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ auto element = static_cast<HTMLMediaElement*>(aClosure);
+ element->mVideoDecodeSuspendTime.Start();
+ element->mVideoDecodeSuspendTimer = nullptr;
+}
+
+void HTMLMediaElement::HiddenVideoStart()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mHiddenPlayTime.Start();
+ if (mVideoDecodeSuspendTimer) {
+ // Already started, just keep it running.
+ return;
+ }
+ mVideoDecodeSuspendTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mVideoDecodeSuspendTimer->InitWithNamedFuncCallback(
+ VideoDecodeSuspendTimerCallback, this,
+ MediaPrefs::MDSMSuspendBackgroundVideoDelay(), nsITimer::TYPE_ONE_SHOT,
+ "HTMLMediaElement::VideoDecodeSuspendTimerCallback");
+}
+
+void HTMLMediaElement::HiddenVideoStop()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mHiddenPlayTime.Pause();
+ mVideoDecodeSuspendTime.Pause();
+ if (!mVideoDecodeSuspendTimer) {
+ return;
+ }
+ mVideoDecodeSuspendTimer->Cancel();
+ mVideoDecodeSuspendTimer = nullptr;
+}
+
+void
+HTMLMediaElement::ReportEMETelemetry()
+{
+ // Report telemetry for EME videos when a page is unloaded.
+ NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+ if (mIsEncrypted && Preferences::GetBool("media.eme.enabled")) {
+ Telemetry::Accumulate(Telemetry::VIDEO_EME_PLAY_SUCCESS, mLoadedDataFired);
+ LOG(LogLevel::Debug, ("%p VIDEO_EME_PLAY_SUCCESS = %s",
+ this, mLoadedDataFired ? "true" : "false"));
+ }
+}
+
+void
+HTMLMediaElement::ReportTelemetry()
+{
+ // Report telemetry for videos when a page is unloaded. We
+ // want to know data on what state the video is at when
+ // the user has exited.
+ enum UnloadedState {
+ ENDED = 0,
+ PAUSED = 1,
+ STALLED = 2,
+ SEEKING = 3,
+ OTHER = 4
+ };
+
+ UnloadedState state = OTHER;
+ if (Seeking()) {
+ state = SEEKING;
+ }
+ else if (Ended()) {
+ state = ENDED;
+ }
+ else if (Paused()) {
+ state = PAUSED;
+ }
+ else {
+ // For buffering we check if the current playback position is at the end
+ // of a buffered range, within a margin of error. We also consider to be
+ // buffering if the last frame status was buffering and the ready state is
+ // HAVE_CURRENT_DATA to account for times where we are in a buffering state
+ // regardless of what actual data we have buffered.
+ bool stalled = false;
+ RefPtr<TimeRanges> ranges = Buffered();
+ const double errorMargin = 0.05;
+ double t = CurrentTime();
+ TimeRanges::index_type index = ranges->Find(t, errorMargin);
+ ErrorResult ignore;
+ stalled = index != TimeRanges::NoIndex &&
+ (ranges->End(index, ignore) - t) < errorMargin;
+ stalled |= mDecoder && NextFrameStatus() == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING &&
+ mReadyState == HTMLMediaElement::HAVE_CURRENT_DATA;
+ if (stalled) {
+ state = STALLED;
+ }
+ }
+
+ Telemetry::Accumulate(Telemetry::VIDEO_UNLOAD_STATE, state);
+ LOG(LogLevel::Debug, ("%p VIDEO_UNLOAD_STATE = %d", this, state));
+
+ FrameStatisticsData data;
+
+ if (HTMLVideoElement* vid = HTMLVideoElement::FromContentOrNull(this)) {
+ FrameStatistics* stats = vid->GetFrameStatistics();
+ if (stats) {
+ data = stats->GetFrameStatisticsData();
+ if (data.mParsedFrames) {
+ MOZ_ASSERT(data.mDroppedFrames <= data.mParsedFrames);
+ // Dropped frames <= total frames, so 'percentage' cannot be higher than
+ // 100 and therefore can fit in a uint32_t (that Telemetry takes).
+ uint32_t percentage = 100 * data.mDroppedFrames / data.mParsedFrames;
+ LOG(LogLevel::Debug,
+ ("Reporting telemetry DROPPED_FRAMES_IN_VIDEO_PLAYBACK"));
+ Telemetry::Accumulate(Telemetry::VIDEO_DROPPED_FRAMES_PROPORTION,
+ percentage);
+ }
+ }
+ }
+
+ if (mMediaInfo.HasVideo() &&
+ mMediaInfo.mVideo.mImage.height > 0) {
+ // We have a valid video.
+ double playTime = mPlayTime.Total();
+ double hiddenPlayTime = mHiddenPlayTime.Total();
+ double videoDecodeSuspendTime = mVideoDecodeSuspendTime.Total();
+
+ Telemetry::Accumulate(Telemetry::VIDEO_PLAY_TIME_MS, SECONDS_TO_MS(playTime));
+ LOG(LogLevel::Debug, ("%p VIDEO_PLAY_TIME_MS = %f", this, playTime));
+
+ Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_MS, SECONDS_TO_MS(hiddenPlayTime));
+ LOG(LogLevel::Debug, ("%p VIDEO_HIDDEN_PLAY_TIME_MS = %f", this, hiddenPlayTime));
+
+ if (playTime > 0.0) {
+ // We have actually played something -> Report some valid-video telemetry.
+
+ // Keyed by audio+video or video alone, and by a resolution range.
+ nsCString key(mMediaInfo.HasAudio() ? "AV," : "V,");
+ static const struct { int32_t mH; const char* mRes; } sResolutions[] = {
+ { 240, "0<h<=240" },
+ { 480, "240<h<=480" },
+ { 576, "480<h<=576" },
+ { 720, "576<h<=720" },
+ { 1080, "720<h<=1080" },
+ { 2160, "1080<h<=2160" }
+ };
+ const char* resolution = "h>2160";
+ int32_t height = mMediaInfo.mVideo.mImage.height;
+ for (const auto& res : sResolutions) {
+ if (height <= res.mH) {
+ resolution = res.mRes;
+ break;
+ }
+ }
+ key.AppendASCII(resolution);
+
+ uint32_t hiddenPercentage = uint32_t(hiddenPlayTime / playTime * 100.0 + 0.5);
+ Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE,
+ key,
+ hiddenPercentage);
+ // Also accumulate all percentages in an "All" key.
+ Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE,
+ NS_LITERAL_CSTRING("All"),
+ hiddenPercentage);
+ LOG(LogLevel::Debug, ("%p VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE = %u, keys: '%s' and 'All'",
+ this, hiddenPercentage, key.get()));
+
+ uint32_t videoDecodeSuspendPercentage =
+ uint32_t(videoDecodeSuspendTime / playTime * 100.0 + 0.5);
+ Telemetry::Accumulate(Telemetry::VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE,
+ key,
+ videoDecodeSuspendPercentage);
+ Telemetry::Accumulate(Telemetry::VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE,
+ NS_LITERAL_CSTRING("All"),
+ videoDecodeSuspendPercentage);
+ LOG(LogLevel::Debug, ("%p VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE = %u, keys: '%s' and 'All'",
+ this, videoDecodeSuspendPercentage, key.get()));
+
+ if (data.mInterKeyframeCount != 0) {
+ uint32_t average_ms =
+ uint32_t(std::min<uint64_t>(double(data.mInterKeyframeSum_us)
+ / double(data.mInterKeyframeCount)
+ / 1000.0
+ + 0.5,
+ UINT32_MAX));
+ Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_AVERAGE_MS,
+ key,
+ average_ms);
+ Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_AVERAGE_MS,
+ NS_LITERAL_CSTRING("All"),
+ average_ms);
+ LOG(LogLevel::Debug, ("%p VIDEO_INTER_KEYFRAME_AVERAGE_MS = %u, keys: '%s' and 'All'",
+ this, average_ms, key.get()));
+
+ uint32_t max_ms =
+ uint32_t(std::min<uint64_t>((data.mInterKeyFrameMax_us + 500) / 1000,
+ UINT32_MAX));
+ Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS,
+ key,
+ max_ms);
+ Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS,
+ NS_LITERAL_CSTRING("All"),
+ max_ms);
+ LOG(LogLevel::Debug, ("%p VIDEO_INTER_KEYFRAME_MAX_MS = %u, keys: '%s' and 'All'",
+ this, max_ms, key.get()));
+ } else {
+ // Here, we have played *some* of the video, but didn't get more than 1
+ // keyframe. Report '0' if we have played for longer than the video-
+ // decode-suspend delay (showing recovery would be difficult).
+ uint32_t suspendDelay_ms = MediaPrefs::MDSMSuspendBackgroundVideoDelay();
+ if (uint32_t(playTime * 1000.0) > suspendDelay_ms) {
+ Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS,
+ key,
+ 0);
+ Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS,
+ NS_LITERAL_CSTRING("All"),
+ 0);
+ LOG(LogLevel::Debug, ("%p VIDEO_INTER_KEYFRAME_MAX_MS = 0 (only 1 keyframe), keys: '%s' and 'All'",
+ this, key.get()));
+ }
+ }
+ }
+ }
+}
+
+void HTMLMediaElement::UnbindFromTree(bool aDeep,
+ bool aNullParent)
+{
+ mUnboundFromTree = true;
+
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+
+ if (mDecoder) {
+ MOZ_ASSERT(IsHidden());
+ mDecoder->NotifyOwnerActivityChanged(false);
+ }
+
+ RefPtr<HTMLMediaElement> self(this);
+ nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction([self] () {
+ if (self->mUnboundFromTree &&
+ self->mNetworkState != nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
+ self->Pause();
+ }
+ });
+ RunInStableState(task);
+}
+
+/* static */
+CanPlayStatus
+HTMLMediaElement::GetCanPlay(const nsAString& aType,
+ DecoderDoctorDiagnostics* aDiagnostics)
+{
+ MediaContentType contentType{aType};
+ return DecoderTraits::CanHandleContentType(contentType, aDiagnostics);
+}
+
+NS_IMETHODIMP
+HTMLMediaElement::CanPlayType(const nsAString& aType, nsAString& aResult)
+{
+ DecoderDoctorDiagnostics diagnostics;
+ CanPlayStatus canPlay = GetCanPlay(aType, &diagnostics);
+ diagnostics.StoreFormatDiagnostics(
+ OwnerDoc(), aType, canPlay != CANPLAY_NO, __func__);
+ switch (canPlay) {
+ case CANPLAY_NO:
+ aResult.Truncate();
+ break;
+ case CANPLAY_YES:
+ aResult.AssignLiteral("probably");
+ break;
+ default:
+ case CANPLAY_MAYBE:
+ aResult.AssignLiteral("maybe");
+ break;
+ }
+
+ LOG(LogLevel::Debug, ("%p CanPlayType(%s) = \"%s\"", this,
+ NS_ConvertUTF16toUTF8(aType).get(),
+ NS_ConvertUTF16toUTF8(aResult).get()));
+
+ return NS_OK;
+}
+
+nsresult HTMLMediaElement::InitializeDecoderAsClone(MediaDecoder* aOriginal)
+{
+ NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set");
+ NS_ASSERTION(mDecoder == nullptr, "Shouldn't have a decoder");
+
+ MediaResource* originalResource = aOriginal->GetResource();
+ if (!originalResource)
+ return NS_ERROR_FAILURE;
+ RefPtr<MediaDecoder> decoder = aOriginal->Clone(this);
+ if (!decoder)
+ return NS_ERROR_FAILURE;
+
+ LOG(LogLevel::Debug, ("%p Cloned decoder %p from %p", this, decoder.get(), aOriginal));
+
+ RefPtr<MediaResource> resource =
+ originalResource->CloneData(decoder->GetResourceCallback());
+
+ if (!resource) {
+ decoder->Shutdown();
+ LOG(LogLevel::Debug, ("%p Failed to cloned stream for decoder %p", this, decoder.get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ return FinishDecoderSetup(decoder, resource, nullptr);
+}
+
+nsresult HTMLMediaElement::InitializeDecoderForChannel(nsIChannel* aChannel,
+ nsIStreamListener** aListener)
+{
+ NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set");
+ NS_ASSERTION(mDecoder == nullptr, "Shouldn't have a decoder");
+
+ nsAutoCString mimeType;
+
+ aChannel->GetContentType(mimeType);
+ NS_ASSERTION(!mimeType.IsEmpty(), "We should have the Content-Type.");
+
+ DecoderDoctorDiagnostics diagnostics;
+ RefPtr<MediaDecoder> decoder =
+ DecoderTraits::CreateDecoder(mimeType, this, &diagnostics);
+ diagnostics.StoreFormatDiagnostics(OwnerDoc(),
+ NS_ConvertASCIItoUTF16(mimeType),
+ decoder != nullptr,
+ __func__);
+ if (!decoder) {
+ nsAutoString src;
+ GetCurrentSrc(src);
+ NS_ConvertUTF8toUTF16 mimeUTF16(mimeType);
+ const char16_t* params[] = { mimeUTF16.get(), src.get() };
+ ReportLoadError("MediaLoadUnsupportedMimeType", params, ArrayLength(params));
+ return NS_ERROR_FAILURE;
+ }
+
+ LOG(LogLevel::Debug, ("%p Created decoder %p for type %s", this, decoder.get(), mimeType.get()));
+
+ RefPtr<MediaResource> resource =
+ MediaResource::Create(decoder->GetResourceCallback(), aChannel);
+
+ if (!resource) {
+ decoder->Shutdown();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (mChannelLoader) {
+ mChannelLoader->Done();
+ mChannelLoader = nullptr;
+ }
+
+ return FinishDecoderSetup(decoder, resource, aListener);
+}
+
+nsresult HTMLMediaElement::FinishDecoderSetup(MediaDecoder* aDecoder,
+ MediaResource* aStream,
+ nsIStreamListener** aListener)
+{
+ ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_LOADING);
+
+ // Force a same-origin check before allowing events for this media resource.
+ mMediaSecurityVerified = false;
+
+ // Set mDecoder now so if methods like GetCurrentSrc get called between
+ // here and Load(), they work.
+ SetDecoder(aDecoder);
+
+ // Tell the decoder about its MediaResource now so things like principals are
+ // available immediately.
+ mDecoder->SetResource(aStream);
+ mDecoder->SetAudioChannel(mAudioChannel);
+ mDecoder->SetVolume(mMuted ? 0.0 : mVolume);
+ mDecoder->SetPreservesPitch(mPreservesPitch);
+ mDecoder->SetPlaybackRate(mPlaybackRate);
+ if (mPreloadAction == HTMLMediaElement::PRELOAD_METADATA) {
+ mDecoder->SetMinimizePrerollUntilPlaybackStarts();
+ }
+
+ // Update decoder principal before we start decoding, since it
+ // can affect how we feed data to MediaStreams
+ NotifyDecoderPrincipalChanged();
+
+ nsresult rv = aDecoder->Load(aListener);
+ if (NS_FAILED(rv)) {
+ ShutdownDecoder();
+ LOG(LogLevel::Debug, ("%p Failed to load for decoder %p", this, aDecoder));
+ return rv;
+ }
+
+ for (OutputMediaStream& ms : mOutputStreams) {
+ if (ms.mCapturingMediaStream) {
+ MOZ_ASSERT(!ms.mCapturingDecoder);
+ continue;
+ }
+
+ ms.mCapturingDecoder = true;
+ aDecoder->AddOutputStream(ms.mStream->GetInputStream()->AsProcessedStream(),
+ ms.mFinishWhenEnded);
+ }
+
+ if (mMediaKeys) {
+ if (mMediaKeys->GetCDMProxy()) {
+ mDecoder->SetCDMProxy(mMediaKeys->GetCDMProxy());
+ } else {
+ // CDM must have crashed.
+ ShutdownDecoder();
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ MediaEventSource<void>* waitingForKeyProducer = mDecoder->WaitingForKeyEvent();
+ // Not every decoder will produce waitingForKey events, only add ones that can
+ if (waitingForKeyProducer) {
+ mWaitingForKeyListener = waitingForKeyProducer->Connect(
+ AbstractThread::MainThread(), this, &HTMLMediaElement::CannotDecryptWaitingForKey);
+ }
+
+ if (mChannelLoader) {
+ mChannelLoader->Done();
+ mChannelLoader = nullptr;
+ }
+
+ AddMediaElementToURITable();
+
+ // We may want to suspend the new stream now.
+ // This will also do an AddRemoveSelfReference.
+ NotifyOwnerDocumentActivityChanged();
+ UpdateAudioChannelPlayingState();
+
+ if (!mPaused) {
+ SetPlayedOrSeeked(true);
+ if (!mPausedForInactiveDocumentOrChannel) {
+ rv = mDecoder->Play();
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ ShutdownDecoder();
+ }
+
+ NS_ASSERTION(NS_SUCCEEDED(rv) == (MediaElementTableCount(this, mLoadingSrc) == 1),
+ "Media element should have single table entry if decode initialized");
+
+ return rv;
+}
+
+class HTMLMediaElement::StreamListener : public MediaStreamListener,
+ public WatchTarget
+{
+public:
+ explicit StreamListener(HTMLMediaElement* aElement, const char* aName) :
+ WatchTarget(aName),
+ mElement(aElement),
+ mHaveCurrentData(false),
+ mBlocked(false),
+ mFinished(false),
+ mMutex(aName),
+ mPendingNotifyOutput(false)
+ {}
+ void Forget()
+ {
+ mElement = nullptr;
+ NotifyWatchers();
+ }
+
+ // Main thread
+
+ MediaDecoderOwner::NextFrameStatus NextFrameStatus()
+ {
+ if (!mElement || !mHaveCurrentData || mFinished) {
+ return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
+ }
+ return mBlocked
+ ? MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING
+ : MediaDecoderOwner::NEXT_FRAME_AVAILABLE;
+ }
+
+ void DoNotifyBlocked()
+ {
+ mBlocked = true;
+ NotifyWatchers();
+ }
+ void DoNotifyUnblocked()
+ {
+ mBlocked = false;
+ NotifyWatchers();
+ }
+ void DoNotifyOutput()
+ {
+ {
+ MutexAutoLock lock(mMutex);
+ mPendingNotifyOutput = false;
+ }
+ if (mElement && mHaveCurrentData) {
+ RefPtr<HTMLMediaElement> kungFuDeathGrip = mElement;
+ kungFuDeathGrip->FireTimeUpdate(true);
+ }
+ }
+ void DoNotifyHaveCurrentData()
+ {
+ mHaveCurrentData = true;
+ if (mElement) {
+ RefPtr<HTMLMediaElement> kungFuDeathGrip = mElement;
+ kungFuDeathGrip->FirstFrameLoaded();
+ }
+ NotifyWatchers();
+ DoNotifyOutput();
+ }
+
+ // These notifications run on the media graph thread so we need to
+ // dispatch events to the main thread.
+ virtual void NotifyBlockingChanged(MediaStreamGraph* aGraph, Blocking aBlocked) override
+ {
+ nsCOMPtr<nsIRunnable> event;
+ if (aBlocked == BLOCKED) {
+ event = NewRunnableMethod(this, &StreamListener::DoNotifyBlocked);
+ } else {
+ event = NewRunnableMethod(this, &StreamListener::DoNotifyUnblocked);
+ }
+ aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+ }
+ virtual void NotifyHasCurrentData(MediaStreamGraph* aGraph) override
+ {
+ MutexAutoLock lock(mMutex);
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &StreamListener::DoNotifyHaveCurrentData);
+ aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+ }
+ virtual void NotifyOutput(MediaStreamGraph* aGraph,
+ GraphTime aCurrentTime) override
+ {
+ MutexAutoLock lock(mMutex);
+ if (mPendingNotifyOutput)
+ return;
+ mPendingNotifyOutput = true;
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &StreamListener::DoNotifyOutput);
+ aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
+ }
+
+private:
+ // These fields may only be accessed on the main thread
+ HTMLMediaElement* mElement;
+ bool mHaveCurrentData;
+ bool mBlocked;
+ bool mFinished;
+
+ // mMutex protects the fields below; they can be accessed on any thread
+ Mutex mMutex;
+ bool mPendingNotifyOutput;
+};
+
+class HTMLMediaElement::MediaStreamTracksAvailableCallback:
+ public OnTracksAvailableCallback
+{
+public:
+ explicit MediaStreamTracksAvailableCallback(HTMLMediaElement* aElement):
+ OnTracksAvailableCallback(), mElement(aElement)
+ {}
+ virtual void NotifyTracksAvailable(DOMMediaStream* aStream)
+ {
+ NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+
+ if (!mElement) {
+ return;
+ }
+ mElement->NotifyMediaStreamTracksAvailable(aStream);
+ }
+
+private:
+ WeakPtr<HTMLMediaElement> mElement;
+};
+
+class HTMLMediaElement::MediaStreamTrackListener :
+ public DOMMediaStream::TrackListener
+{
+public:
+ explicit MediaStreamTrackListener(HTMLMediaElement* aElement):
+ mElement(aElement) {}
+
+ void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override
+ {
+ mElement->NotifyMediaStreamTrackAdded(aTrack);
+ }
+
+ void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override
+ {
+ mElement->NotifyMediaStreamTrackRemoved(aTrack);
+ }
+
+ void NotifyActive() override
+ {
+ LOG(LogLevel::Debug, ("%p, mSrcStream %p became active",
+ mElement, mElement->mSrcStream.get()));
+ mElement->CheckAutoplayDataReady();
+ }
+
+ void NotifyInactive() override
+ {
+ LOG(LogLevel::Debug, ("%p, mSrcStream %p became inactive",
+ mElement, mElement->mSrcStream.get()));
+ MOZ_ASSERT(!mElement->mSrcStream->Active());
+ if (mElement->mMediaStreamListener) {
+ mElement->mMediaStreamListener->Forget();
+ }
+ mElement->PlaybackEnded();
+ }
+
+protected:
+ HTMLMediaElement* const mElement;
+};
+
+void HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags)
+{
+ if (!mSrcStream) {
+ return;
+ }
+ // We might be in cycle collection with mSrcStream->GetPlaybackStream() already
+ // returning null due to unlinking.
+
+ MediaStream* stream = GetSrcMediaStream();
+ bool shouldPlay = !(aFlags & REMOVING_SRC_STREAM) && !mPaused &&
+ !mPausedForInactiveDocumentOrChannel && stream;
+ if (shouldPlay == mSrcStreamIsPlaying) {
+ return;
+ }
+ mSrcStreamIsPlaying = shouldPlay;
+
+ LOG(LogLevel::Debug, ("MediaElement %p %s playback of DOMMediaStream %p",
+ this, shouldPlay ? "Setting up" : "Removing",
+ mSrcStream.get()));
+
+ if (shouldPlay) {
+ mSrcStreamPausedCurrentTime = -1;
+
+ mMediaStreamListener = new StreamListener(this,
+ "HTMLMediaElement::mMediaStreamListener");
+ stream->AddListener(mMediaStreamListener);
+
+ mWatchManager.Watch(*mMediaStreamListener,
+ &HTMLMediaElement::UpdateReadyStateInternal);
+
+ stream->AddAudioOutput(this);
+ SetVolumeInternal();
+
+ VideoFrameContainer* container = GetVideoFrameContainer();
+ if (mSelectedVideoStreamTrack && container) {
+ mSelectedVideoStreamTrack->AddVideoOutput(container);
+ }
+
+ SetCapturedOutputStreamsEnabled(true); // Unmute
+ } else {
+ if (stream) {
+ mSrcStreamPausedCurrentTime = CurrentTime();
+
+ stream->RemoveListener(mMediaStreamListener);
+
+ stream->RemoveAudioOutput(this);
+ VideoFrameContainer* container = GetVideoFrameContainer();
+ if (mSelectedVideoStreamTrack && container) {
+ mSelectedVideoStreamTrack->RemoveVideoOutput(container);
+ }
+
+ SetCapturedOutputStreamsEnabled(false); // Mute
+ }
+ // If stream is null, then DOMMediaStream::Destroy must have been
+ // called and that will remove all listeners/outputs.
+
+ mWatchManager.Unwatch(*mMediaStreamListener,
+ &HTMLMediaElement::UpdateReadyStateInternal);
+
+ mMediaStreamListener->Forget();
+ mMediaStreamListener = nullptr;
+ }
+
+ // If the input is a media stream, we don't check its data and always regard
+ // it as audible when it's playing.
+ SetAudibleState(shouldPlay);
+}
+
+void HTMLMediaElement::SetupSrcMediaStreamPlayback(DOMMediaStream* aStream)
+{
+ NS_ASSERTION(!mSrcStream && !mMediaStreamListener && !mMediaStreamSizeListener,
+ "Should have been ended already");
+
+ mSrcStream = aStream;
+
+ nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
+ if (!window) {
+ return;
+ }
+
+ RefPtr<MediaStream> stream = GetSrcMediaStream();
+ if (stream) {
+ stream->SetAudioChannelType(mAudioChannel);
+ }
+
+ UpdateSrcMediaStreamPlaying();
+
+ // If we pause this media element, track changes in the underlying stream
+ // will continue to fire events at this element and alter its track list.
+ // That's simpler than delaying the events, but probably confusing...
+ nsTArray<RefPtr<MediaStreamTrack>> tracks;
+ mSrcStream->GetTracks(tracks);
+ for (const RefPtr<MediaStreamTrack>& track : tracks) {
+ NotifyMediaStreamTrackAdded(track);
+ }
+
+ mSrcStream->OnTracksAvailable(new MediaStreamTracksAvailableCallback(this));
+ mMediaStreamTrackListener = new MediaStreamTrackListener(this);
+ mSrcStream->RegisterTrackListener(mMediaStreamTrackListener);
+
+ mSrcStream->AddPrincipalChangeObserver(this);
+ mSrcStreamVideoPrincipal = mSrcStream->GetVideoPrincipal();
+
+ ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
+ ChangeDelayLoadStatus(false);
+ CheckAutoplayDataReady();
+
+ // FirstFrameLoaded() will be called when the stream has current data.
+}
+
+void HTMLMediaElement::EndSrcMediaStreamPlayback()
+{
+ MOZ_ASSERT(mSrcStream);
+
+ UpdateSrcMediaStreamPlaying(REMOVING_SRC_STREAM);
+
+ if (mMediaStreamSizeListener) {
+ MOZ_ASSERT(mSelectedVideoStreamTrack);
+ if (mSelectedVideoStreamTrack) {
+ mSelectedVideoStreamTrack->RemoveDirectListener(mMediaStreamSizeListener);
+ }
+ mMediaStreamSizeListener->Forget();
+ }
+ mSelectedVideoStreamTrack = nullptr;
+ mMediaStreamSizeListener = nullptr;
+
+ mSrcStream->UnregisterTrackListener(mMediaStreamTrackListener);
+ mMediaStreamTrackListener = nullptr;
+ mSrcStreamTracksAvailable = false;
+
+ mSrcStream->RemovePrincipalChangeObserver(this);
+ mSrcStreamVideoPrincipal = nullptr;
+
+ for (OutputMediaStream& ms : mOutputStreams) {
+ for (auto pair : ms.mTrackPorts) {
+ pair.second()->Destroy();
+ }
+ ms.mTrackPorts.Clear();
+ }
+
+ mSrcStream = nullptr;
+}
+
+static already_AddRefed<AudioTrack>
+CreateAudioTrack(AudioStreamTrack* aStreamTrack)
+{
+ nsAutoString id;
+ nsAutoString label;
+ aStreamTrack->GetId(id);
+ aStreamTrack->GetLabel(label);
+
+ return MediaTrackList::CreateAudioTrack(id, NS_LITERAL_STRING("main"),
+ label, EmptyString(), true);
+}
+
+static already_AddRefed<VideoTrack>
+CreateVideoTrack(VideoStreamTrack* aStreamTrack)
+{
+ nsAutoString id;
+ nsAutoString label;
+ aStreamTrack->GetId(id);
+ aStreamTrack->GetLabel(label);
+
+ return MediaTrackList::CreateVideoTrack(id, NS_LITERAL_STRING("main"),
+ label, EmptyString(),
+ aStreamTrack);
+}
+
+void
+HTMLMediaElement::NotifyMediaStreamTrackAdded(const RefPtr<MediaStreamTrack>& aTrack)
+{
+ MOZ_ASSERT(aTrack);
+
+ if (aTrack->Ended()) {
+ return;
+ }
+
+#ifdef DEBUG
+ nsString id;
+ aTrack->GetId(id);
+
+ LOG(LogLevel::Debug, ("%p, Adding %sTrack with id %s",
+ this, aTrack->AsAudioStreamTrack() ? "Audio" : "Video",
+ NS_ConvertUTF16toUTF8(id).get()));
+#endif
+
+ if (AudioStreamTrack* t = aTrack->AsAudioStreamTrack()) {
+ RefPtr<AudioTrack> audioTrack = CreateAudioTrack(t);
+ AudioTracks()->AddTrack(audioTrack);
+ } else if (VideoStreamTrack* t = aTrack->AsVideoStreamTrack()) {
+ // TODO: Fix this per the spec on bug 1273443.
+ if (!IsVideo()) {
+ return;
+ }
+ RefPtr<VideoTrack> videoTrack = CreateVideoTrack(t);
+ VideoTracks()->AddTrack(videoTrack);
+ // New MediaStreamTrack added, set the new added video track as selected
+ // video track when there is no selected track.
+ if (VideoTracks()->SelectedIndex() == -1) {
+ MOZ_ASSERT(!mSelectedVideoStreamTrack);
+ videoTrack->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS);
+ }
+ }
+
+ mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
+}
+
+void
+HTMLMediaElement::NotifyMediaStreamTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack)
+{
+ MOZ_ASSERT(aTrack);
+
+ nsAutoString id;
+ aTrack->GetId(id);
+
+ LOG(LogLevel::Debug, ("%p, Removing %sTrack with id %s",
+ this, aTrack->AsAudioStreamTrack() ? "Audio" : "Video",
+ NS_ConvertUTF16toUTF8(id).get()));
+
+ if (MediaTrack* t = AudioTracks()->GetTrackById(id)) {
+ AudioTracks()->RemoveTrack(t);
+ } else if (MediaTrack* t = VideoTracks()->GetTrackById(id)) {
+ VideoTracks()->RemoveTrack(t);
+ } else {
+ NS_ASSERTION(aTrack->AsVideoStreamTrack() && !IsVideo(),
+ "MediaStreamTrack ended but did not exist in track lists. "
+ "This is only allowed if a video element ends and we are an "
+ "audio element.");
+ return;
+ }
+}
+
+
+void HTMLMediaElement::ProcessMediaFragmentURI()
+{
+ nsMediaFragmentURIParser parser(mLoadingSrc);
+
+ if (mDecoder && parser.HasEndTime()) {
+ mFragmentEnd = parser.GetEndTime();
+ }
+
+ if (parser.HasStartTime()) {
+ SetCurrentTime(parser.GetStartTime());
+ mFragmentStart = parser.GetStartTime();
+ }
+}
+
+void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
+ nsAutoPtr<const MetadataTags> aTags)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If the element is gaining or losing an audio track, we need to notify
+ // the audio channel agent so that the correct audio-playback events will
+ // get dispatched.
+ AutoNotifyAudioChannelAgent autoNotify(this);
+
+ SetMediaInfo(*aInfo);
+
+ mIsEncrypted = aInfo->IsEncrypted() || mPendingEncryptedInitData.IsEncrypted();
+ mTags = aTags.forget();
+ mLoadedDataFired = false;
+ ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
+
+ DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
+ if (IsVideo() && HasVideo()) {
+ DispatchAsyncEvent(NS_LITERAL_STRING("resize"));
+ }
+ NS_ASSERTION(!HasVideo() ||
+ (mMediaInfo.mVideo.mDisplay.width > 0 &&
+ mMediaInfo.mVideo.mDisplay.height > 0),
+ "Video resolution must be known on 'loadedmetadata'");
+ DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
+ if (mDecoder && mDecoder->IsTransportSeekable() && mDecoder->IsMediaSeekable()) {
+ ProcessMediaFragmentURI();
+ mDecoder->SetFragmentEndTime(mFragmentEnd);
+ }
+ if (mIsEncrypted) {
+ // We only support playback of encrypted content via MSE by default.
+ if (!mMediaSource && Preferences::GetBool("media.eme.mse-only", true)) {
+ DecodeError(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ "Encrypted content not supported outside of MSE"));
+ return;
+ }
+
+ // Dispatch a distinct 'encrypted' event for each initData we have.
+ for (const auto& initData : mPendingEncryptedInitData.mInitDatas) {
+ DispatchEncrypted(initData.mInitData, initData.mType);
+ }
+ mPendingEncryptedInitData.mInitDatas.Clear();
+ }
+
+ mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
+
+ if (IsVideo() && aInfo->HasVideo()) {
+ // We are a video element playing video so update the screen wakelock
+ NotifyOwnerDocumentActivityChanged();
+ }
+
+ if (mDefaultPlaybackStartPosition != 0.0) {
+ SetCurrentTime(mDefaultPlaybackStartPosition);
+ mDefaultPlaybackStartPosition = 0.0;
+ }
+
+ if (!mSrcStream) {
+ return;
+ }
+ for (OutputMediaStream& ms : mOutputStreams) {
+ for (size_t i = 0; i < AudioTracks()->Length(); ++i) {
+ AudioTrack* t = (*AudioTracks())[i];
+ if (t->Enabled()) {
+ AddCaptureMediaTrackToOutputStream(t, ms);
+ }
+ }
+ if (IsVideo() && !ms.mCapturingAudioOnly) {
+ // Only add video tracks if we're a video element and the output stream
+ // wants video.
+ for (size_t i = 0; i < VideoTracks()->Length(); ++i) {
+ VideoTrack* t = (*VideoTracks())[i];
+ if (t->Selected()) {
+ AddCaptureMediaTrackToOutputStream(t, ms);
+ }
+ }
+ }
+ }
+}
+
+void HTMLMediaElement::FirstFrameLoaded()
+{
+ LOG(LogLevel::Debug, ("%p, FirstFrameLoaded() mFirstFrameLoaded=%d mWaitingForKey=%d",
+ this, mFirstFrameLoaded, mWaitingForKey));
+
+ NS_ASSERTION(!mSuspendedAfterFirstFrame, "Should not have already suspended");
+
+ if (!mFirstFrameLoaded) {
+ mFirstFrameLoaded = true;
+ UpdateReadyStateInternal();
+ }
+
+ ChangeDelayLoadStatus(false);
+
+ if (mDecoder && mAllowSuspendAfterFirstFrame && mPaused &&
+ !HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) &&
+ mPreloadAction == HTMLMediaElement::PRELOAD_METADATA) {
+ mSuspendedAfterFirstFrame = true;
+ mDecoder->Suspend();
+ }
+}
+
+void HTMLMediaElement::NetworkError()
+{
+ if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
+ NoSupportedMediaSourceError();
+ } else {
+ Error(MEDIA_ERR_NETWORK);
+ }
+}
+
+void HTMLMediaElement::DecodeError(const MediaResult& aError)
+{
+ nsAutoString src;
+ GetCurrentSrc(src);
+ const char16_t* params[] = { src.get() };
+ ReportLoadError("MediaLoadDecodeError", params, ArrayLength(params));
+
+ AudioTracks()->EmptyTracks();
+ VideoTracks()->EmptyTracks();
+ if (mIsLoadingFromSourceChildren) {
+ mErrorSink->ResetError();
+ if (mSourceLoadCandidate) {
+ DispatchAsyncSourceError(mSourceLoadCandidate);
+ QueueLoadFromSourceTask();
+ } else {
+ NS_WARNING("Should know the source we were loading from!");
+ }
+ } else if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
+ NoSupportedMediaSourceError(aError.Description());
+ } else {
+ Error(MEDIA_ERR_DECODE, aError.Description());
+ }
+}
+
+bool HTMLMediaElement::HasError() const
+{
+ return GetError();
+}
+
+void HTMLMediaElement::LoadAborted()
+{
+ Error(MEDIA_ERR_ABORTED);
+}
+
+void HTMLMediaElement::Error(uint16_t aErrorCode,
+ const nsACString& aErrorDetails)
+{
+ mErrorSink->SetError(aErrorCode, aErrorDetails);
+ ChangeDelayLoadStatus(false);
+ UpdateAudioChannelPlayingState();
+}
+
+void HTMLMediaElement::PlaybackEnded()
+{
+ // We changed state which can affect AddRemoveSelfReference
+ AddRemoveSelfReference();
+
+ NS_ASSERTION(!mDecoder || mDecoder->IsEnded(),
+ "Decoder fired ended, but not in ended state");
+
+ // Discard all output streams that have finished now.
+ for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) {
+ if (mOutputStreams[i].mFinishWhenEnded) {
+ LOG(LogLevel::Debug, ("Playback ended. Removing output stream %p",
+ mOutputStreams[i].mStream.get()));
+ mOutputStreams.RemoveElementAt(i);
+ }
+ }
+
+ if (mSrcStream || (mDecoder && mDecoder->IsInfinite())) {
+ LOG(LogLevel::Debug, ("%p, got duration by reaching the end of the resource", this));
+ DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
+ }
+
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::loop)) {
+ SetCurrentTime(0);
+ return;
+ }
+
+ Pause();
+
+ if (mSrcStream) {
+ // A MediaStream that goes from inactive to active shall be eligible for
+ // autoplay again according to the mediacapture-main spec.
+ mAutoplaying = true;
+ }
+
+ FireTimeUpdate(false);
+ DispatchAsyncEvent(NS_LITERAL_STRING("ended"));
+}
+
+void HTMLMediaElement::SeekStarted()
+{
+ DispatchAsyncEvent(NS_LITERAL_STRING("seeking"));
+}
+
+void HTMLMediaElement::SeekCompleted()
+{
+ mPlayingBeforeSeek = false;
+ SetPlayedOrSeeked(true);
+ if (mTextTrackManager) {
+ mTextTrackManager->DidSeek();
+ }
+ FireTimeUpdate(false);
+ DispatchAsyncEvent(NS_LITERAL_STRING("seeked"));
+ // We changed whether we're seeking so we need to AddRemoveSelfReference
+ AddRemoveSelfReference();
+ if (mCurrentPlayRangeStart == -1.0) {
+ mCurrentPlayRangeStart = CurrentTime();
+ }
+ // Unset the variable on seekend
+ mPlayingThroughTheAudioChannelBeforeSeek = false;
+}
+
+void HTMLMediaElement::NotifySuspendedByCache(bool aIsSuspended)
+{
+ mDownloadSuspendedByCache = aIsSuspended;
+}
+
+void HTMLMediaElement::DownloadSuspended()
+{
+ if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING) {
+ DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
+ }
+ if (mBegun) {
+ ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
+ }
+}
+
+void HTMLMediaElement::DownloadResumed(bool aForceNetworkLoading)
+{
+ if (mBegun || aForceNetworkLoading) {
+ ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_LOADING);
+ }
+}
+
+void HTMLMediaElement::CheckProgress(bool aHaveNewProgress)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING);
+
+ TimeStamp now = TimeStamp::NowLoRes();
+
+ if (aHaveNewProgress) {
+ mDataTime = now;
+ }
+
+ // If this is the first progress, or PROGRESS_MS has passed since the last
+ // progress event fired and more data has arrived since then, fire a
+ // progress event.
+ NS_ASSERTION((mProgressTime.IsNull() && !aHaveNewProgress) ||
+ !mDataTime.IsNull(),
+ "null TimeStamp mDataTime should not be used in comparison");
+ if (mProgressTime.IsNull() ? aHaveNewProgress
+ : (now - mProgressTime >= TimeDuration::FromMilliseconds(PROGRESS_MS) &&
+ mDataTime > mProgressTime)) {
+ DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
+ // Resolution() ensures that future data will have now > mProgressTime,
+ // and so will trigger another event. mDataTime is not reset because it
+ // is still required to detect stalled; it is similarly offset by
+ // resolution to indicate the new data has not yet arrived.
+ mProgressTime = now - TimeDuration::Resolution();
+ if (mDataTime > mProgressTime) {
+ mDataTime = mProgressTime;
+ }
+ if (!mProgressTimer) {
+ NS_ASSERTION(aHaveNewProgress,
+ "timer dispatched when there was no timer");
+ // Were stalled. Restart timer.
+ StartProgressTimer();
+ if (!mLoadedDataFired) {
+ ChangeDelayLoadStatus(true);
+ }
+ }
+ // Download statistics may have been updated, force a recheck of the readyState.
+ UpdateReadyStateInternal();
+ }
+
+ if (now - mDataTime >= TimeDuration::FromMilliseconds(STALL_MS)) {
+ DispatchAsyncEvent(NS_LITERAL_STRING("stalled"));
+
+ if (mMediaSource) {
+ ChangeDelayLoadStatus(false);
+ }
+
+ NS_ASSERTION(mProgressTimer, "detected stalled without timer");
+ // Stop timer events, which prevents repeated stalled events until there
+ // is more progress.
+ StopProgress();
+ }
+
+ AddRemoveSelfReference();
+}
+
+/* static */
+void HTMLMediaElement::ProgressTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ auto decoder = static_cast<HTMLMediaElement*>(aClosure);
+ decoder->CheckProgress(false);
+}
+
+void HTMLMediaElement::StartProgressTimer()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING);
+ NS_ASSERTION(!mProgressTimer, "Already started progress timer.");
+
+ mProgressTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mProgressTimer->InitWithNamedFuncCallback(
+ ProgressTimerCallback, this, PROGRESS_MS, nsITimer::TYPE_REPEATING_SLACK,
+ "HTMLMediaElement::ProgressTimerCallback");
+}
+
+void HTMLMediaElement::StartProgress()
+{
+ // Record the time now for detecting stalled.
+ mDataTime = TimeStamp::NowLoRes();
+ // Reset mProgressTime so that mDataTime is not indicating bytes received
+ // after the last progress event.
+ mProgressTime = TimeStamp();
+ StartProgressTimer();
+}
+
+void HTMLMediaElement::StopProgress()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mProgressTimer) {
+ return;
+ }
+
+ mProgressTimer->Cancel();
+ mProgressTimer = nullptr;
+}
+
+void HTMLMediaElement::DownloadProgressed()
+{
+ if (mNetworkState != nsIDOMHTMLMediaElement::NETWORK_LOADING) {
+ return;
+ }
+ CheckProgress(true);
+}
+
+bool HTMLMediaElement::ShouldCheckAllowOrigin()
+{
+ return mCORSMode != CORS_NONE;
+}
+
+bool HTMLMediaElement::IsCORSSameOrigin()
+{
+ bool subsumes;
+ RefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
+ return
+ (NS_SUCCEEDED(NodePrincipal()->Subsumes(principal, &subsumes)) && subsumes) ||
+ ShouldCheckAllowOrigin();
+}
+
+void
+HTMLMediaElement::UpdateReadyStateInternal()
+{
+ if (!mDecoder && !mSrcStream) {
+ // Not initialized - bail out.
+ LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
+ "Not initialized", this));
+ return;
+ }
+
+ if (mDecoder && mReadyState < nsIDOMHTMLMediaElement::HAVE_METADATA) {
+ // aNextFrame might have a next frame because the decoder can advance
+ // on its own thread before MetadataLoaded gets a chance to run.
+ // The arrival of more data can't change us out of this readyState.
+ LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
+ "Decoder ready state < HAVE_METADATA", this));
+ return;
+ }
+
+ if (mSrcStream && mReadyState < nsIDOMHTMLMediaElement::HAVE_METADATA) {
+ if (!mSrcStreamTracksAvailable) {
+ LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
+ "MediaStreamTracks not available yet", this));
+ return;
+ }
+
+ bool hasAudioTracks = !AudioTracks()->IsEmpty();
+ bool hasVideoTracks = !VideoTracks()->IsEmpty();
+ if (!hasAudioTracks && !hasVideoTracks) {
+ LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
+ "Stream with no tracks", this));
+ return;
+ }
+
+ if (IsVideo() && hasVideoTracks && !HasVideo()) {
+ LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
+ "Stream waiting for video", this));
+ return;
+ }
+
+ LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() Stream has "
+ "metadata; audioTracks=%d, videoTracks=%d, "
+ "hasVideoFrame=%d", this, AudioTracks()->Length(),
+ VideoTracks()->Length(), HasVideo()));
+
+ // We are playing a stream that has video and a video frame is now set.
+ // This means we have all metadata needed to change ready state.
+ MediaInfo mediaInfo = mMediaInfo;
+ if (hasAudioTracks) {
+ mediaInfo.EnableAudio();
+ }
+ if (hasVideoTracks) {
+ mediaInfo.EnableVideo();
+ }
+ MetadataLoaded(&mediaInfo, nsAutoPtr<const MetadataTags>(nullptr));
+ }
+
+ enum NextFrameStatus nextFrameStatus = NextFrameStatus();
+ if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE ||
+ (nextFrameStatus == NEXT_FRAME_UNAVAILABLE_BUFFERING &&
+ mWaitingForKey == WAITING_FOR_KEY)) {
+ if (mWaitingForKey != NOT_WAITING_FOR_KEY) {
+ // http://w3c.github.io/encrypted-media/#wait-for-key
+ // Continuing 7.3.4 Queue a "waitingforkey" Event
+ // 4. Queue a task to fire a simple event named waitingforkey
+ // at the media element.
+ if (mWaitingForKey == WAITING_FOR_KEY) {
+ mWaitingForKey = WAITING_FOR_KEY_DISPATCHED;
+ DispatchAsyncEvent(NS_LITERAL_STRING("waitingforkey"));
+ }
+ // 5. Set the readyState of media element to HAVE_METADATA.
+ // NOTE: We'll change to HAVE_CURRENT_DATA or HAVE_METADATA
+ // depending on whether we've loaded the first frame or not
+ // below.
+ // 6. Suspend playback.
+ // Note: Playback will already be stalled, as the next frame is
+ // unavailable.
+ } else if (mDecoder) {
+ nextFrameStatus = mDecoder->NextFrameBufferedStatus();
+ }
+ }
+
+ if (nextFrameStatus == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING) {
+ LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
+ "NEXT_FRAME_UNAVAILABLE_SEEKING; Forcing HAVE_METADATA", this));
+ ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
+ return;
+ }
+
+ if (IsVideo() && HasVideo() && !IsPlaybackEnded() &&
+ GetImageContainer() && !GetImageContainer()->HasCurrentImage()) {
+ // Don't advance if we are playing video, but don't have a video frame.
+ // Also, if video became available after advancing to HAVE_CURRENT_DATA
+ // while we are still playing, we need to revert to HAVE_METADATA until
+ // a video frame is available.
+ LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
+ "Playing video but no video frame; Forcing HAVE_METADATA", this));
+ ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
+ return;
+ }
+
+ if (!mFirstFrameLoaded) {
+ // We haven't yet loaded the first frame, making us unable to determine
+ // if we have enough valid data at the present stage.
+ return;
+ }
+
+ if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE_BUFFERING) {
+ // Force HAVE_CURRENT_DATA when buffering.
+ ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
+ return;
+ }
+
+ if (mDownloadSuspendedByCache && mDecoder && !mDecoder->IsEnded()) {
+ // The decoder has signaled that the download has been suspended by the
+ // media cache. So move readyState into HAVE_ENOUGH_DATA, in case there's
+ // script waiting for a "canplaythrough" event; without this forced
+ // transition, we will never fire the "canplaythrough" event if the
+ // media cache is too small, and scripts are bound to fail. Don't force
+ // this transition if the decoder is in ended state; the readyState
+ // should remain at HAVE_CURRENT_DATA in this case.
+ // Note that this state transition includes the case where we finished
+ // downloaded the whole data stream.
+ LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
+ "Decoder download suspended by cache", this));
+ ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
+ return;
+ }
+
+ if (mDecoder && !mDecoder->IsEnded() &&
+ !mDecoder->GetResource()->IsExpectingMoreData()) {
+ LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
+ "Decoder fetched all data for media resource", this));
+ ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
+ return;
+ }
+
+ if (nextFrameStatus != MediaDecoderOwner::NEXT_FRAME_AVAILABLE) {
+ LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
+ "Next frame not available", this));
+ ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
+ return;
+ }
+
+ if (mSrcStream) {
+ LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
+ "Stream HAVE_ENOUGH_DATA", this));
+ ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
+ return;
+ }
+
+ // Now see if we should set HAVE_ENOUGH_DATA.
+ // If it's something we don't know the size of, then we can't
+ // make a real estimate, so we go straight to HAVE_ENOUGH_DATA once
+ // we've downloaded enough data that our download rate is considered
+ // reliable. We have to move to HAVE_ENOUGH_DATA at some point or
+ // autoplay elements for live streams will never play. Otherwise we
+ // move to HAVE_ENOUGH_DATA if we can play through the entire media
+ // without stopping to buffer.
+ if (mWaitingForKey == NOT_WAITING_FOR_KEY && mDecoder->CanPlayThrough()) {
+ LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
+ "Decoder can play through", this));
+ ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
+ return;
+ }
+ LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
+ "Default; Decoder has future data", this));
+ ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA);
+}
+
+static const char* const gReadyStateToString[] = {
+ "HAVE_NOTHING",
+ "HAVE_METADATA",
+ "HAVE_CURRENT_DATA",
+ "HAVE_FUTURE_DATA",
+ "HAVE_ENOUGH_DATA"
+};
+
+void HTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
+{
+ nsMediaReadyState oldState = mReadyState;
+ mReadyState = aState;
+
+ if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY ||
+ oldState == mReadyState) {
+ return;
+ }
+
+ LOG(LogLevel::Debug, ("%p Ready state changed to %s", this, gReadyStateToString[aState]));
+
+ UpdateAudioChannelPlayingState();
+
+ // Handle raising of "waiting" event during seek (see 4.8.10.9)
+ // or
+ // 4.8.12.7 Ready states:
+ // "If the previous ready state was HAVE_FUTURE_DATA or more, and the new
+ // ready state is HAVE_CURRENT_DATA or less
+ // If the media element was potentially playing before its readyState
+ // attribute changed to a value lower than HAVE_FUTURE_DATA, and the element
+ // has not ended playback, and playback has not stopped due to errors,
+ // paused for user interaction, or paused for in-band content, the user agent
+ // must queue a task to fire a simple event named timeupdate at the element,
+ // and queue a task to fire a simple event named waiting at the element."
+ if (mPlayingBeforeSeek &&
+ mReadyState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
+ DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
+ } else if (oldState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
+ mReadyState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
+ !Paused() && !Ended() && !mErrorSink->mError) {
+ FireTimeUpdate(false);
+ DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
+ }
+
+ if (oldState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
+ mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
+ !mLoadedDataFired) {
+ DispatchAsyncEvent(NS_LITERAL_STRING("loadeddata"));
+ mLoadedDataFired = true;
+ }
+
+ if (oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
+ mReadyState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
+ DispatchAsyncEvent(NS_LITERAL_STRING("canplay"));
+ if (!mPaused) {
+ mWaitingForKey = NOT_WAITING_FOR_KEY;
+ DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
+ }
+ }
+
+ CheckAutoplayDataReady();
+
+ if (oldState < nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA &&
+ mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) {
+ DispatchAsyncEvent(NS_LITERAL_STRING("canplaythrough"));
+ }
+}
+
+static const char* const gNetworkStateToString[] = {
+ "EMPTY",
+ "IDLE",
+ "LOADING",
+ "NO_SOURCE"
+ };
+
+void HTMLMediaElement::ChangeNetworkState(nsMediaNetworkState aState)
+{
+ if (mNetworkState == aState) {
+ return;
+ }
+
+ nsMediaNetworkState oldState = mNetworkState;
+ mNetworkState = aState;
+ LOG(LogLevel::Debug, ("%p Network state changed to %s", this, gNetworkStateToString[aState]));
+
+ // TODO: |mBegun| reflects the download status. We should be able to remove
+ // it and check |mNetworkState| only.
+
+ if (oldState == nsIDOMHTMLMediaElement::NETWORK_LOADING) {
+ // Reset |mBegun| since we're not downloading anymore.
+ mBegun = false;
+ // Stop progress notification when exiting NETWORK_LOADING.
+ StopProgress();
+ }
+
+ if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING) {
+ // Download is begun.
+ mBegun = true;
+ // Start progress notification when entering NETWORK_LOADING.
+ StartProgress();
+ } else if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_IDLE &&
+ !mErrorSink->mError) {
+ // Fire 'suspend' event when entering NETWORK_IDLE and no error presented.
+ DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
+ }
+
+ // Changing mNetworkState affects AddRemoveSelfReference().
+ AddRemoveSelfReference();
+}
+
+bool HTMLMediaElement::CanActivateAutoplay()
+{
+ // For stream inputs, we activate autoplay on HAVE_NOTHING because
+ // this element itself might be blocking the stream from making progress by
+ // being paused. We only check that it has data by checking its active state.
+ // We also activate autoplay when playing a media source since the data
+ // download is controlled by the script and there is no way to evaluate
+ // MediaDecoder::CanPlayThrough().
+
+ if (!HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) || !mAutoplayEnabled) {
+ return false;
+ }
+
+ if (!mAutoplaying) {
+ return false;
+ }
+
+ if (IsEditable()) {
+ return false;
+ }
+
+ if (!mPaused) {
+ return false;
+ }
+
+ if (mPausedForInactiveDocumentOrChannel) {
+ return false;
+ }
+
+ if (!IsAllowedToPlayByAudioChannel()) {
+ return false;
+ }
+
+ bool hasData =
+ (mDecoder && mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) ||
+ (mSrcStream && mSrcStream->Active()) ||
+ mMediaSource;
+
+ return hasData;
+}
+
+void HTMLMediaElement::CheckAutoplayDataReady()
+{
+ if (!CanActivateAutoplay()) {
+ return;
+ }
+
+ mPaused = false;
+ // We changed mPaused which can affect AddRemoveSelfReference
+ AddRemoveSelfReference();
+ UpdateSrcMediaStreamPlaying();
+ UpdateAudioChannelPlayingState();
+
+ if (mDecoder) {
+ SetPlayedOrSeeked(true);
+ if (mCurrentPlayRangeStart == -1.0) {
+ mCurrentPlayRangeStart = CurrentTime();
+ }
+ mDecoder->Play();
+ } else if (mSrcStream) {
+ SetPlayedOrSeeked(true);
+ }
+
+ // For blocked media, the event would be pending until it is resumed.
+ DispatchAsyncEvent(NS_LITERAL_STRING("play"));
+
+ DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
+}
+
+bool HTMLMediaElement::IsActive() const
+{
+ nsIDocument* ownerDoc = OwnerDoc();
+ return ownerDoc && ownerDoc->IsActive() && ownerDoc->IsVisible();
+}
+
+bool HTMLMediaElement::IsHidden() const
+{
+ nsIDocument* ownerDoc;
+ return mUnboundFromTree || !(ownerDoc = OwnerDoc()) || ownerDoc->Hidden();
+}
+
+VideoFrameContainer* HTMLMediaElement::GetVideoFrameContainer()
+{
+ if (mShuttingDown) {
+ return nullptr;
+ }
+
+ if (mVideoFrameContainer)
+ return mVideoFrameContainer;
+
+ // Only video frames need an image container.
+ if (!IsVideo()) {
+ return nullptr;
+ }
+
+ mVideoFrameContainer =
+ new VideoFrameContainer(this, LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS));
+
+ return mVideoFrameContainer;
+}
+
+void
+HTMLMediaElement::PrincipalChanged(DOMMediaStream* aStream)
+{
+ LOG(LogLevel::Info, ("HTMLMediaElement %p Stream principal changed.", this));
+ nsContentUtils::CombineResourcePrincipals(&mSrcStreamVideoPrincipal,
+ aStream->GetVideoPrincipal());
+
+ LOG(LogLevel::Debug, ("HTMLMediaElement %p Stream video principal changed to "
+ "%p. Waiting for it to reach VideoFrameContainer before "
+ "setting.", this, aStream->GetVideoPrincipal()));
+ if (mVideoFrameContainer) {
+ UpdateSrcStreamVideoPrincipal(mVideoFrameContainer->GetLastPrincipalHandle());
+ }
+}
+
+void
+HTMLMediaElement::UpdateSrcStreamVideoPrincipal(const PrincipalHandle& aPrincipalHandle)
+{
+ nsTArray<RefPtr<VideoStreamTrack>> videoTracks;
+ mSrcStream->GetVideoTracks(videoTracks);
+
+ PrincipalHandle handle(aPrincipalHandle);
+ bool matchesTrackPrincipal = false;
+ for (const RefPtr<VideoStreamTrack>& track : videoTracks) {
+ if (PrincipalHandleMatches(handle,
+ track->GetPrincipal()) &&
+ !track->Ended()) {
+ // When the PrincipalHandle for the VideoFrameContainer changes to that of
+ // a track in mSrcStream we know that a removed track was displayed but
+ // is no longer so.
+ matchesTrackPrincipal = true;
+ LOG(LogLevel::Debug, ("HTMLMediaElement %p VideoFrameContainer's "
+ "PrincipalHandle matches track %p. That's all we "
+ "need.", this, track.get()));
+ break;
+ }
+ }
+
+ if (matchesTrackPrincipal) {
+ mSrcStreamVideoPrincipal = mSrcStream->GetVideoPrincipal();
+ }
+}
+
+void
+HTMLMediaElement::PrincipalHandleChangedForVideoFrameContainer(VideoFrameContainer* aContainer,
+ const PrincipalHandle& aNewPrincipalHandle)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mSrcStream) {
+ return;
+ }
+
+ LOG(LogLevel::Debug, ("HTMLMediaElement %p PrincipalHandle changed in "
+ "VideoFrameContainer.",
+ this));
+
+ UpdateSrcStreamVideoPrincipal(aNewPrincipalHandle);
+}
+
+nsresult HTMLMediaElement::DispatchEvent(const nsAString& aName)
+{
+ LOG_EVENT(LogLevel::Debug, ("%p Dispatching event %s", this,
+ NS_ConvertUTF16toUTF8(aName).get()));
+
+ // Save events that occur while in the bfcache. These will be dispatched
+ // if the page comes out of the bfcache.
+ if (mEventDeliveryPaused) {
+ mPendingEvents.AppendElement(aName);
+ return NS_OK;
+ }
+
+ return nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+ static_cast<nsIContent*>(this),
+ aName,
+ false,
+ false);
+}
+
+nsresult HTMLMediaElement::DispatchAsyncEvent(const nsAString& aName)
+{
+ LOG_EVENT(LogLevel::Debug, ("%p Queuing event %s", this,
+ NS_ConvertUTF16toUTF8(aName).get()));
+
+ // Save events that occur while in the bfcache. These will be dispatched
+ // if the page comes out of the bfcache.
+ if (mEventDeliveryPaused) {
+ mPendingEvents.AppendElement(aName);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIRunnable> event = new nsAsyncEventRunner(aName, this);
+ NS_DispatchToMainThread(event);
+
+ if ((aName.EqualsLiteral("play") || aName.EqualsLiteral("playing"))) {
+ mPlayTime.Start();
+ if (IsHidden()) {
+ HiddenVideoStart();
+ }
+ } else if (aName.EqualsLiteral("waiting")) {
+ mPlayTime.Pause();
+ HiddenVideoStop();
+ } else if (aName.EqualsLiteral("pause")) {
+ mPlayTime.Pause();
+ HiddenVideoStop();
+ }
+
+ return NS_OK;
+}
+
+nsresult HTMLMediaElement::DispatchPendingMediaEvents()
+{
+ NS_ASSERTION(!mEventDeliveryPaused,
+ "Must not be in bfcache when dispatching pending media events");
+
+ uint32_t count = mPendingEvents.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ DispatchAsyncEvent(mPendingEvents[i]);
+ }
+ mPendingEvents.Clear();
+
+ return NS_OK;
+}
+
+bool HTMLMediaElement::IsPotentiallyPlaying() const
+{
+ // TODO:
+ // playback has not stopped due to errors,
+ // and the element has not paused for user interaction
+ return
+ !mPaused &&
+ (mReadyState == nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA ||
+ mReadyState == nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) &&
+ !IsPlaybackEnded();
+}
+
+bool HTMLMediaElement::IsPlaybackEnded() const
+{
+ // TODO:
+ // the current playback position is equal to the effective end of the media resource.
+ // See bug 449157.
+ return mReadyState >= nsIDOMHTMLMediaElement::HAVE_METADATA &&
+ mDecoder && mDecoder->IsEnded();
+}
+
+already_AddRefed<nsIPrincipal> HTMLMediaElement::GetCurrentPrincipal()
+{
+ if (mDecoder) {
+ return mDecoder->GetCurrentPrincipal();
+ }
+ if (mSrcStream) {
+ nsCOMPtr<nsIPrincipal> principal = mSrcStream->GetPrincipal();
+ return principal.forget();
+ }
+ return nullptr;
+}
+
+already_AddRefed<nsIPrincipal> HTMLMediaElement::GetCurrentVideoPrincipal()
+{
+ if (mDecoder) {
+ return mDecoder->GetCurrentPrincipal();
+ }
+ if (mSrcStream) {
+ nsCOMPtr<nsIPrincipal> principal = mSrcStreamVideoPrincipal;
+ return principal.forget();
+ }
+ return nullptr;
+}
+
+void HTMLMediaElement::NotifyDecoderPrincipalChanged()
+{
+ RefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
+
+ mDecoder->UpdateSameOriginStatus(!principal || IsCORSSameOrigin());
+
+ for (DecoderPrincipalChangeObserver* observer :
+ mDecoderPrincipalChangeObservers) {
+ observer->NotifyDecoderPrincipalChanged();
+ }
+}
+
+void HTMLMediaElement::AddDecoderPrincipalChangeObserver(DecoderPrincipalChangeObserver* aObserver)
+{
+ mDecoderPrincipalChangeObservers.AppendElement(aObserver);
+}
+
+bool HTMLMediaElement::RemoveDecoderPrincipalChangeObserver(DecoderPrincipalChangeObserver* aObserver)
+{
+ return mDecoderPrincipalChangeObservers.RemoveElement(aObserver);
+}
+
+void HTMLMediaElement::UpdateMediaSize(const nsIntSize& aSize)
+{
+ if (IsVideo() && mReadyState != HAVE_NOTHING &&
+ mMediaInfo.mVideo.mDisplay != aSize) {
+ DispatchAsyncEvent(NS_LITERAL_STRING("resize"));
+ }
+
+ mMediaInfo.mVideo.mDisplay = aSize;
+ mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
+}
+
+void HTMLMediaElement::UpdateInitialMediaSize(const nsIntSize& aSize)
+{
+ if (!mMediaInfo.HasVideo()) {
+ UpdateMediaSize(aSize);
+ }
+
+ if (!mMediaStreamSizeListener) {
+ return;
+ }
+
+ if (!mSelectedVideoStreamTrack) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ mSelectedVideoStreamTrack->RemoveDirectListener(mMediaStreamSizeListener);
+ mMediaStreamSizeListener->Forget();
+ mMediaStreamSizeListener = nullptr;
+}
+
+void HTMLMediaElement::SuspendOrResumeElement(bool aPauseElement, bool aSuspendEvents)
+{
+ LOG(LogLevel::Debug, ("%p SuspendOrResumeElement(pause=%d, suspendEvents=%d) hidden=%d",
+ this, aPauseElement, aSuspendEvents, OwnerDoc()->Hidden()));
+
+ if (aPauseElement != mPausedForInactiveDocumentOrChannel) {
+ mPausedForInactiveDocumentOrChannel = aPauseElement;
+ UpdateSrcMediaStreamPlaying();
+ UpdateAudioChannelPlayingState();
+ if (aPauseElement) {
+ ReportTelemetry();
+ ReportEMETelemetry();
+
+ // For EME content, force destruction of the CDM client (and CDM
+ // instance if this is the last client for that CDM instance) and
+ // the CDM's decoder. This ensures the CDM gets reliable and prompt
+ // shutdown notifications, as it may have book-keeping it needs
+ // to do on shutdown.
+ if (mMediaKeys) {
+ mMediaKeys->Shutdown();
+ mMediaKeys = nullptr;
+ if (mDecoder) {
+ ShutdownDecoder();
+ }
+ }
+ if (mDecoder) {
+ mDecoder->Pause();
+ mDecoder->Suspend();
+ }
+ mEventDeliveryPaused = aSuspendEvents;
+ } else {
+ MOZ_ASSERT(!mMediaKeys);
+ if (mDecoder) {
+ mDecoder->Resume();
+ if (!mPaused && !mDecoder->IsEnded()) {
+ mDecoder->Play();
+ }
+ }
+ if (mEventDeliveryPaused) {
+ mEventDeliveryPaused = false;
+ DispatchPendingMediaEvents();
+ }
+ }
+ }
+}
+
+bool HTMLMediaElement::IsBeingDestroyed()
+{
+ nsIDocument* ownerDoc = OwnerDoc();
+ nsIDocShell* docShell = ownerDoc ? ownerDoc->GetDocShell() : nullptr;
+ bool isBeingDestroyed = false;
+ if (docShell) {
+ docShell->IsBeingDestroyed(&isBeingDestroyed);
+ }
+ return isBeingDestroyed;
+}
+
+void HTMLMediaElement::NotifyOwnerDocumentActivityChanged()
+{
+ bool visible = !IsHidden();
+ if (visible) {
+ // Visible -> Just pause hidden play time (no-op if already paused).
+ HiddenVideoStop();
+ } else if (mPlayTime.IsStarted()) {
+ // Not visible, play time is running -> Start hidden play time if needed.
+ HiddenVideoStart();
+ }
+
+ if (mDecoder && !IsBeingDestroyed()) {
+ mDecoder->NotifyOwnerActivityChanged(visible);
+ }
+
+ bool pauseElement = ShouldElementBePaused();
+ SuspendOrResumeElement(pauseElement, !IsActive());
+
+ AddRemoveSelfReference();
+}
+
+void HTMLMediaElement::AddRemoveSelfReference()
+{
+ // XXX we could release earlier here in many situations if we examined
+ // which event listeners are attached. Right now we assume there is a
+ // potential listener for every event. We would also have to keep the
+ // element alive if it was playing and producing audio output --- right now
+ // that's covered by the !mPaused check.
+ nsIDocument* ownerDoc = OwnerDoc();
+
+ // See the comment at the top of this file for the explanation of this
+ // boolean expression.
+ bool needSelfReference = !mShuttingDown &&
+ ownerDoc->IsActive() &&
+ (mDelayingLoadEvent ||
+ (!mPaused && mDecoder && !mDecoder->IsEnded()) ||
+ (!mPaused && mSrcStream && !mSrcStream->IsFinished()) ||
+ (mDecoder && mDecoder->IsSeeking()) ||
+ CanActivateAutoplay() ||
+ (mMediaSource ? mProgressTimer :
+ mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING));
+
+ if (needSelfReference != mHasSelfReference) {
+ mHasSelfReference = needSelfReference;
+ if (needSelfReference) {
+ // The shutdown observer will hold a strong reference to us. This
+ // will do to keep us alive. We need to know about shutdown so that
+ // we can release our self-reference.
+ mShutdownObserver->AddRefMediaElement();
+ } else {
+ // Dispatch Release asynchronously so that we don't destroy this object
+ // inside a call stack of method calls on this object
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &HTMLMediaElement::DoRemoveSelfReference);
+ NS_DispatchToMainThread(event);
+ }
+ }
+
+ UpdateAudioChannelPlayingState();
+}
+
+void HTMLMediaElement::DoRemoveSelfReference()
+{
+ mShutdownObserver->ReleaseMediaElement();
+}
+
+void HTMLMediaElement::NotifyShutdownEvent()
+{
+ mShuttingDown = true;
+ ResetState();
+ AddRemoveSelfReference();
+}
+
+bool
+HTMLMediaElement::IsNodeOfType(uint32_t aFlags) const
+{
+ return !(aFlags & ~(eCONTENT | eMEDIA));
+}
+
+void HTMLMediaElement::DispatchAsyncSourceError(nsIContent* aSourceElement)
+{
+ LOG_EVENT(LogLevel::Debug, ("%p Queuing simple source error event", this));
+
+ nsCOMPtr<nsIRunnable> event = new nsSourceErrorEventRunner(this, aSourceElement);
+ NS_DispatchToMainThread(event);
+}
+
+void HTMLMediaElement::NotifyAddedSource()
+{
+ // If a source element is inserted as a child of a media element
+ // that has no src attribute and whose networkState has the value
+ // NETWORK_EMPTY, the user agent must invoke the media element's
+ // resource selection algorithm.
+ if (!HasAttr(kNameSpaceID_None, nsGkAtoms::src) &&
+ mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY)
+ {
+ QueueSelectResourceTask();
+ }
+
+ // A load was paused in the resource selection algorithm, waiting for
+ // a new source child to be added, resume the resource selection algorithm.
+ if (mLoadWaitStatus == WAITING_FOR_SOURCE) {
+ // Rest the flag so we don't queue multiple LoadFromSourceTask() when
+ // multiple <source> are attached in an event loop.
+ mLoadWaitStatus = NOT_WAITING;
+ QueueLoadFromSourceTask();
+ }
+}
+
+nsIContent* HTMLMediaElement::GetNextSource()
+{
+ nsCOMPtr<nsIDOMNode> thisDomNode = do_QueryObject(this);
+
+ mSourceLoadCandidate = nullptr;
+
+ nsresult rv = NS_OK;
+ if (!mSourcePointer) {
+ // First time this has been run, create a selection to cover children.
+ mSourcePointer = new nsRange(this);
+ // If this media element is removed from the DOM, don't gravitate the
+ // range up to its ancestor, leave it attached to the media element.
+ mSourcePointer->SetEnableGravitationOnElementRemoval(false);
+
+ rv = mSourcePointer->SelectNodeContents(thisDomNode);
+ if (NS_FAILED(rv)) return nullptr;
+
+ rv = mSourcePointer->Collapse(true);
+ if (NS_FAILED(rv)) return nullptr;
+ }
+
+ while (true) {
+#ifdef DEBUG
+ nsCOMPtr<nsIDOMNode> startContainer;
+ rv = mSourcePointer->GetStartContainer(getter_AddRefs(startContainer));
+ if (NS_FAILED(rv)) return nullptr;
+ NS_ASSERTION(startContainer == thisDomNode,
+ "Should only iterate over direct children");
+#endif
+
+ int32_t startOffset = 0;
+ rv = mSourcePointer->GetStartOffset(&startOffset);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ if (uint32_t(startOffset) == GetChildCount())
+ return nullptr; // No more children.
+
+ // Advance the range to the next child.
+ rv = mSourcePointer->SetStart(thisDomNode, startOffset + 1);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsIContent* child = GetChildAt(startOffset);
+
+ // If child is a <source> element, it is the next candidate.
+ if (child && child->IsHTMLElement(nsGkAtoms::source)) {
+ mSourceLoadCandidate = child;
+ return child;
+ }
+ }
+ NS_NOTREACHED("Execution should not reach here!");
+ return nullptr;
+}
+
+void HTMLMediaElement::ChangeDelayLoadStatus(bool aDelay)
+{
+ if (mDelayingLoadEvent == aDelay)
+ return;
+
+ mDelayingLoadEvent = aDelay;
+
+ LOG(LogLevel::Debug, ("%p ChangeDelayLoadStatus(%d) doc=0x%p", this, aDelay, mLoadBlockedDoc.get()));
+ if (mDecoder) {
+ mDecoder->SetLoadInBackground(!aDelay);
+ }
+ if (aDelay) {
+ mLoadBlockedDoc = OwnerDoc();
+ mLoadBlockedDoc->BlockOnload();
+ } else {
+ // mLoadBlockedDoc might be null due to GC unlinking
+ if (mLoadBlockedDoc) {
+ mLoadBlockedDoc->UnblockOnload(false);
+ mLoadBlockedDoc = nullptr;
+ }
+ }
+
+ // We changed mDelayingLoadEvent which can affect AddRemoveSelfReference
+ AddRemoveSelfReference();
+}
+
+already_AddRefed<nsILoadGroup> HTMLMediaElement::GetDocumentLoadGroup()
+{
+ if (!OwnerDoc()->IsActive()) {
+ NS_WARNING("Load group requested for media element in inactive document.");
+ }
+ return OwnerDoc()->GetDocumentLoadGroup();
+}
+
+nsresult
+HTMLMediaElement::CopyInnerTo(Element* aDest)
+{
+ nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aDest->OwnerDoc()->IsStaticDocument()) {
+ HTMLMediaElement* dest = static_cast<HTMLMediaElement*>(aDest);
+ dest->SetMediaInfo(mMediaInfo);
+ }
+ return rv;
+}
+
+already_AddRefed<TimeRanges>
+HTMLMediaElement::Buffered() const
+{
+ RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc()));
+ if (mDecoder) {
+ media::TimeIntervals buffered = mDecoder->GetBuffered();
+ if (!buffered.IsInvalid()) {
+ buffered.ToTimeRanges(ranges);
+ }
+ }
+ return ranges.forget();
+}
+
+nsresult HTMLMediaElement::GetBuffered(nsIDOMTimeRanges** aBuffered)
+{
+ RefPtr<TimeRanges> ranges = Buffered();
+ ranges.forget(aBuffered);
+ return NS_OK;
+}
+
+void HTMLMediaElement::SetRequestHeaders(nsIHttpChannel* aChannel)
+{
+ // Send Accept header for video and audio types only (Bug 489071)
+ SetAcceptHeader(aChannel);
+
+ // Media elements are likely candidates for HTTP Pipeline head of line
+ // blocking problems, so disable pipelines.
+ nsLoadFlags loadflags;
+ aChannel->GetLoadFlags(&loadflags);
+ loadflags |= nsIRequest::INHIBIT_PIPELINE;
+ aChannel->SetLoadFlags(loadflags);
+
+ // Apache doesn't send Content-Length when gzip transfer encoding is used,
+ // which prevents us from estimating the video length (if explicit Content-Duration
+ // and a length spec in the container are not present either) and from seeking.
+ // So, disable the standard "Accept-Encoding: gzip,deflate" that we usually send.
+ // See bug 614760.
+ aChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept-Encoding"),
+ EmptyCString(), false);
+
+ // Set the Referer header
+ aChannel->SetReferrerWithPolicy(OwnerDoc()->GetDocumentURI(),
+ OwnerDoc()->GetReferrerPolicy());
+}
+
+void HTMLMediaElement::FireTimeUpdate(bool aPeriodic)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+
+ TimeStamp now = TimeStamp::Now();
+ double time = CurrentTime();
+
+ // Fire a timeupdate event if this is not a periodic update (i.e. it's a
+ // timeupdate event mandated by the spec), or if it's a periodic update
+ // and TIMEUPDATE_MS has passed since the last timeupdate event fired and
+ // the time has changed.
+ if (!aPeriodic ||
+ (mLastCurrentTime != time &&
+ (mTimeUpdateTime.IsNull() ||
+ now - mTimeUpdateTime >= TimeDuration::FromMilliseconds(TIMEUPDATE_MS)))) {
+ DispatchAsyncEvent(NS_LITERAL_STRING("timeupdate"));
+ mTimeUpdateTime = now;
+ mLastCurrentTime = time;
+ }
+ if (mFragmentEnd >= 0.0 && time >= mFragmentEnd) {
+ Pause();
+ mFragmentEnd = -1.0;
+ mFragmentStart = -1.0;
+ mDecoder->SetFragmentEndTime(mFragmentEnd);
+ }
+
+ // Update the cues displaying on the video.
+ // Here mTextTrackManager can be null if the cycle collector has unlinked
+ // us before our parent. In that case UnbindFromTree will call us
+ // when our parent is unlinked.
+ if (mTextTrackManager) {
+ mTextTrackManager->TimeMarchesOn();
+ }
+}
+
+MediaStream* HTMLMediaElement::GetSrcMediaStream() const
+{
+ if (!mSrcStream) {
+ return nullptr;
+ }
+ return mSrcStream->GetPlaybackStream();
+}
+
+MediaError*
+HTMLMediaElement::GetError() const
+{
+ return mErrorSink->mError;
+}
+
+void
+HTMLMediaElement::OpenUnsupportedMediaWithExternalAppIfNeeded() const
+{
+ // Error sink would check the error state and other conditions to decide
+ // whether we can open unsupported type media with an external app.
+ mErrorSink->NotifyPlayStarted();
+}
+
+void HTMLMediaElement::GetCurrentSpec(nsCString& aString)
+{
+ if (mLoadingSrc) {
+ mLoadingSrc->GetSpec(aString);
+ } else {
+ aString.Truncate();
+ }
+}
+
+double
+HTMLMediaElement::MozFragmentEnd()
+{
+ double duration = Duration();
+
+ // If there is no end fragment, or the fragment end is greater than the
+ // duration, return the duration.
+ return (mFragmentEnd < 0.0 || mFragmentEnd > duration) ? duration : mFragmentEnd;
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetMozFragmentEnd(double* aTime)
+{
+ *aTime = MozFragmentEnd();
+ return NS_OK;
+}
+
+static double ClampPlaybackRate(double aPlaybackRate)
+{
+ if (aPlaybackRate == 0.0) {
+ return aPlaybackRate;
+ }
+ if (Abs(aPlaybackRate) < MIN_PLAYBACKRATE) {
+ return aPlaybackRate < 0 ? -MIN_PLAYBACKRATE : MIN_PLAYBACKRATE;
+ }
+ if (Abs(aPlaybackRate) > MAX_PLAYBACKRATE) {
+ return aPlaybackRate < 0 ? -MAX_PLAYBACKRATE : MAX_PLAYBACKRATE;
+ }
+ return aPlaybackRate;
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetDefaultPlaybackRate(double* aDefaultPlaybackRate)
+{
+ *aDefaultPlaybackRate = DefaultPlaybackRate();
+ return NS_OK;
+}
+
+void
+HTMLMediaElement::SetDefaultPlaybackRate(double aDefaultPlaybackRate, ErrorResult& aRv)
+{
+ if (aDefaultPlaybackRate < 0) {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return;
+ }
+
+ mDefaultPlaybackRate = ClampPlaybackRate(aDefaultPlaybackRate);
+ DispatchAsyncEvent(NS_LITERAL_STRING("ratechange"));
+}
+
+NS_IMETHODIMP HTMLMediaElement::SetDefaultPlaybackRate(double aDefaultPlaybackRate)
+{
+ ErrorResult rv;
+ SetDefaultPlaybackRate(aDefaultPlaybackRate, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetPlaybackRate(double* aPlaybackRate)
+{
+ *aPlaybackRate = PlaybackRate();
+ return NS_OK;
+}
+
+void
+HTMLMediaElement::SetPlaybackRate(double aPlaybackRate, ErrorResult& aRv)
+{
+ // Changing the playback rate of a media that has more than two channels is
+ // not supported.
+ if (aPlaybackRate < 0) {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return;
+ }
+
+ mPlaybackRate = ClampPlaybackRate(aPlaybackRate);
+
+ if (mPlaybackRate != 0.0 &&
+ (mPlaybackRate < 0 || mPlaybackRate > THRESHOLD_HIGH_PLAYBACKRATE_AUDIO ||
+ mPlaybackRate < THRESHOLD_LOW_PLAYBACKRATE_AUDIO)) {
+ SetMutedInternal(mMuted | MUTED_BY_INVALID_PLAYBACK_RATE);
+ } else {
+ SetMutedInternal(mMuted & ~MUTED_BY_INVALID_PLAYBACK_RATE);
+ }
+
+ if (mDecoder) {
+ mDecoder->SetPlaybackRate(mPlaybackRate);
+ }
+ DispatchAsyncEvent(NS_LITERAL_STRING("ratechange"));
+}
+
+NS_IMETHODIMP HTMLMediaElement::SetPlaybackRate(double aPlaybackRate)
+{
+ ErrorResult rv;
+ SetPlaybackRate(aPlaybackRate, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP HTMLMediaElement::GetMozPreservesPitch(bool* aPreservesPitch)
+{
+ *aPreservesPitch = MozPreservesPitch();
+ return NS_OK;
+}
+
+NS_IMETHODIMP HTMLMediaElement::SetMozPreservesPitch(bool aPreservesPitch)
+{
+ mPreservesPitch = aPreservesPitch;
+ if (mDecoder) {
+ mDecoder->SetPreservesPitch(mPreservesPitch);
+ }
+ return NS_OK;
+}
+
+ImageContainer* HTMLMediaElement::GetImageContainer()
+{
+ VideoFrameContainer* container = GetVideoFrameContainer();
+ return container ? container->GetImageContainer() : nullptr;
+}
+
+bool
+HTMLMediaElement::MaybeCreateAudioChannelAgent()
+{
+ if (mAudioChannelAgent) {
+ return true;
+ }
+
+ mAudioChannelAgent = new AudioChannelAgent();
+ nsresult rv = mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetInnerWindow(),
+ static_cast<int32_t>(mAudioChannel),
+ this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mAudioChannelAgent = nullptr;
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("HTMLMediaElement, Fail to initialize the audio channel agent,"
+ " this = %p\n", this));
+ return false;
+ }
+
+ return true;
+}
+
+bool
+HTMLMediaElement::IsPlayingThroughTheAudioChannel() const
+{
+ // If we have an error, we are not playing.
+ if (mErrorSink->mError) {
+ return false;
+ }
+
+ // It might be resumed from remote, we should keep the audio channel agent.
+ if (IsSuspendedByAudioChannel()) {
+ return true;
+ }
+
+ // Are we paused
+ if (mPaused) {
+ return false;
+ }
+
+ // We should consider any bfcached page or inactive document as non-playing.
+ if (!IsActive()) {
+ return false;
+ }
+
+ // A loop always is playing
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::loop)) {
+ return true;
+ }
+
+ // If we are actually playing...
+ if (IsCurrentlyPlaying()) {
+ return true;
+ }
+
+ // If we are seeking, we consider it as playing
+ if (mPlayingThroughTheAudioChannelBeforeSeek) {
+ return true;
+ }
+
+ // If we are playing an external stream.
+ if (mSrcAttrStream) {
+ return true;
+ }
+
+ return false;
+}
+
+void
+HTMLMediaElement::UpdateAudioChannelPlayingState(bool aForcePlaying)
+{
+ bool playingThroughTheAudioChannel =
+ aForcePlaying || IsPlayingThroughTheAudioChannel();
+
+ if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
+ if (!MaybeCreateAudioChannelAgent()) {
+ return;
+ }
+
+ mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
+ NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
+ }
+}
+
+void
+HTMLMediaElement::NotifyAudioChannelAgent(bool aPlaying)
+{
+ if (aPlaying) {
+ // The reason we don't call NotifyStartedPlaying after the media element
+ // really becomes audible is because there is another case needs to block
+ // element as early as we can, we would hear sound leaking if we block it
+ // too late. In that case (block autoplay in non-visited-tab), we need to
+ // create a connection before decoding, because we don't want user hearing
+ // any sound.
+ AudioPlaybackConfig config;
+ nsresult rv = mAudioChannelAgent->NotifyStartedPlaying(&config,
+ IsAudible());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ WindowVolumeChanged(config.mVolume, config.mMuted);
+ WindowSuspendChanged(config.mSuspend);
+ } else {
+ mAudioChannelAgent->NotifyStoppedPlaying();
+ }
+}
+
+NS_IMETHODIMP
+HTMLMediaElement::WindowVolumeChanged(float aVolume, bool aMuted)
+{
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("HTMLMediaElement, WindowVolumeChanged, this = %p, "
+ "aVolume = %f, aMuted = %d\n", this, aVolume, aMuted));
+
+ if (mAudioChannelVolume != aVolume) {
+ mAudioChannelVolume = aVolume;
+ SetVolumeInternal();
+ }
+
+ if (aMuted && !ComputedMuted()) {
+ SetMutedInternal(mMuted | MUTED_BY_AUDIO_CHANNEL);
+ } else if (!aMuted && ComputedMuted()) {
+ SetMutedInternal(mMuted & ~MUTED_BY_AUDIO_CHANNEL);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLMediaElement::WindowSuspendChanged(SuspendTypes aSuspend)
+{
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("HTMLMediaElement, WindowSuspendChanged, this = %p, "
+ "aSuspend = %d\n", this, aSuspend));
+
+ switch (aSuspend) {
+ case nsISuspendedTypes::NONE_SUSPENDED:
+ ResumeFromAudioChannel();
+ break;
+ case nsISuspendedTypes::SUSPENDED_PAUSE:
+ case nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE:
+ PauseByAudioChannel(aSuspend);
+ break;
+ case nsISuspendedTypes::SUSPENDED_BLOCK:
+ BlockByAudioChannel();
+ break;
+ case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE:
+ SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
+ Pause();
+ break;
+ default:
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("HTMLMediaElement, WindowSuspendChanged, this = %p, "
+ "Error : unknown suspended type!\n", this));
+ }
+
+ return NS_OK;
+}
+
+void
+HTMLMediaElement::ResumeFromAudioChannel()
+{
+ if (!IsSuspendedByAudioChannel()) {
+ return;
+ }
+
+ switch (mAudioChannelSuspended) {
+ case nsISuspendedTypes::SUSPENDED_PAUSE:
+ case nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE:
+ ResumeFromAudioChannelPaused(mAudioChannelSuspended);
+ break;
+ case nsISuspendedTypes::SUSPENDED_BLOCK:
+ ResumeFromAudioChannelBlocked();
+ break;
+ default:
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("HTMLMediaElement, ResumeFromAudioChannel, this = %p, "
+ "Error : resume without suspended!\n", this));
+ }
+}
+
+void
+HTMLMediaElement::ResumeFromAudioChannelPaused(SuspendTypes aSuspend)
+{
+ MOZ_ASSERT(mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_PAUSE ||
+ mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE);
+
+ SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
+ nsresult rv = Play();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptend"));
+}
+
+void
+HTMLMediaElement::ResumeFromAudioChannelBlocked()
+{
+ MOZ_ASSERT(mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_BLOCK);
+
+ SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
+ nsresult rv = Play();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+}
+
+void
+HTMLMediaElement::PauseByAudioChannel(SuspendTypes aSuspend)
+{
+ if (IsSuspendedByAudioChannel()) {
+ return;
+ }
+
+ SetAudioChannelSuspended(aSuspend);
+ Pause();
+ DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptbegin"));
+}
+
+void
+HTMLMediaElement::BlockByAudioChannel()
+{
+ if (IsSuspendedByAudioChannel()) {
+ return;
+ }
+
+ SetAudioChannelSuspended(nsISuspendedTypes::SUSPENDED_BLOCK);
+}
+
+void
+HTMLMediaElement::SetAudioChannelSuspended(SuspendTypes aSuspend)
+{
+ if (mAudioChannelSuspended == aSuspend) {
+ return;
+ }
+
+ MaybeNotifyMediaResumed(aSuspend);
+ mAudioChannelSuspended = aSuspend;
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("HTMLMediaElement, SetAudioChannelSuspended, this = %p, "
+ "aSuspend = %d\n", this, aSuspend));
+
+ NotifyAudioPlaybackChanged(
+ AudioChannelService::AudibleChangedReasons::ePauseStateChanged);
+}
+
+bool
+HTMLMediaElement::IsSuspendedByAudioChannel() const
+{
+ return (mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_PAUSE ||
+ mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE ||
+ mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_BLOCK);
+}
+
+bool
+HTMLMediaElement::IsAllowedToPlay()
+{
+ // Prevent media element from being auto-started by a script when
+ // media.autoplay.enabled=false
+ if (!mHasUserInteraction &&
+ !IsAutoplayEnabled() &&
+ !EventStateManager::IsHandlingUserInput()) {
+#if defined(MOZ_WIDGET_ANDROID)
+ nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+ static_cast<nsIContent*>(this),
+ NS_LITERAL_STRING("MozAutoplayMediaBlocked"),
+ false,
+ false);
+#endif
+ return false;
+ }
+
+ return IsAllowedToPlayByAudioChannel();
+}
+
+bool
+HTMLMediaElement::IsAllowedToPlayByAudioChannel()
+{
+ // The media element has already been paused or blocked, so it can't start
+ // playback again by script or user's intend until resuming by audio channel.
+ if (mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_PAUSE ||
+ mAudioChannelSuspended == nsISuspendedTypes::SUSPENDED_BLOCK) {
+ return false;
+ }
+
+ // If the tab hasn't been activated yet, the media element in that tab can't
+ // be playback now until the tab goes to foreground first time or user clicks
+ // the unblocking tab icon.
+ if (MaybeCreateAudioChannelAgent() && !IsTabActivated()) {
+ // Even we haven't start playing yet, we still need to notify the audio
+ // channe system because we need to receive the resume notification later.
+ UpdateAudioChannelPlayingState(true /* force to start */);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+HTMLMediaElement::IsTabActivated() const
+{
+ MOZ_ASSERT(mAudioChannelAgent);
+ return !mAudioChannelAgent->ShouldBlockMedia();
+}
+
+static const char* VisibilityString(Visibility aVisibility) {
+ switch(aVisibility) {
+ case Visibility::UNTRACKED: {
+ return "UNTRACKED";
+ }
+ case Visibility::APPROXIMATELY_NONVISIBLE: {
+ return "APPROXIMATELY_NONVISIBLE";
+ }
+ case Visibility::APPROXIMATELY_VISIBLE: {
+ return "APPROXIMATELY_VISIBLE";
+ }
+ }
+
+ return "NAN";
+}
+
+void
+HTMLMediaElement::OnVisibilityChange(Visibility aNewVisibility)
+{
+ LOG(LogLevel::Debug, ("OnVisibilityChange(): %s\n",
+ VisibilityString(aNewVisibility)));
+
+ mVisibilityState = aNewVisibility;
+
+ if (!mDecoder) {
+ return;
+ }
+
+ switch (aNewVisibility) {
+ case Visibility::UNTRACKED: {
+ MOZ_ASSERT_UNREACHABLE("Shouldn't notify for untracked visibility");
+ break;
+ }
+ case Visibility::APPROXIMATELY_NONVISIBLE: {
+ if (mPlayTime.IsStarted()) {
+ // Not visible, play time is running -> Start hidden play time if needed.
+ HiddenVideoStart();
+ }
+
+ mDecoder->NotifyOwnerActivityChanged(false);
+ break;
+ }
+ case Visibility::APPROXIMATELY_VISIBLE: {
+ // Visible -> Just pause hidden play time (no-op if already paused).
+ HiddenVideoStop();
+
+ mDecoder->NotifyOwnerActivityChanged(true);
+ break;
+ }
+ }
+
+}
+
+MediaKeys*
+HTMLMediaElement::GetMediaKeys() const
+{
+ return mMediaKeys;
+}
+
+bool
+HTMLMediaElement::ContainsRestrictedContent()
+{
+ return GetMediaKeys() != nullptr;
+}
+
+already_AddRefed<Promise>
+HTMLMediaElement::SetMediaKeys(mozilla::dom::MediaKeys* aMediaKeys,
+ ErrorResult& aRv)
+{
+ LOG(LogLevel::Debug, ("%p SetMediaKeys(%p) mMediaKeys=%p mDecoder=%p",
+ this, aMediaKeys, mMediaKeys.get(), mDecoder.get()));
+
+ if (MozAudioCaptured()) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(OwnerDoc()->GetInnerWindow());
+ if (!global) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ RefPtr<DetailedPromise> promise = DetailedPromise::Create(global, aRv,
+ NS_LITERAL_CSTRING("HTMLMediaElement.setMediaKeys"));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // 1. If mediaKeys and the mediaKeys attribute are the same object,
+ // return a resolved promise.
+ if (mMediaKeys == aMediaKeys) {
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+
+ // Note: Our attaching code is synchronous, so we can skip the following steps.
+
+ // 2. If this object's attaching media keys value is true, return a
+ // promise rejected with a new DOMException whose name is InvalidStateError.
+ // 3. Let this object's attaching media keys value be true.
+ // 4. Let promise be a new promise.
+ // 5. Run the following steps in parallel:
+
+ // 5.1 If mediaKeys is not null, CDM instance represented by mediaKeys is
+ // already in use by another media element, and the user agent is unable
+ // to use it with this element, let this object's attaching media keys
+ // value be false and reject promise with a new DOMException whose name
+ // is QuotaExceededError.
+ if (aMediaKeys && aMediaKeys->IsBoundToMediaElement()) {
+ promise->MaybeReject(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR,
+ NS_LITERAL_CSTRING("MediaKeys object is already bound to another HTMLMediaElement"));
+ return promise.forget();
+ }
+
+ // 5.2 If the mediaKeys attribute is not null, run the following steps:
+ if (mMediaKeys) {
+ // 5.2.1 If the user agent or CDM do not support removing the association,
+ // let this object's attaching media keys value be false and reject promise
+ // with a new DOMException whose name is NotSupportedError.
+
+ // 5.2.2 If the association cannot currently be removed, let this object's
+ // attaching media keys value be false and reject promise with a new
+ // DOMException whose name is InvalidStateError.
+ if (mDecoder) {
+ // We don't support swapping out the MediaKeys once we've started to
+ // setup the playback pipeline. Note this also means we don't need to worry
+ // about handling disassociating the MediaKeys from the MediaDecoder.
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Can't change MediaKeys on HTMLMediaElement after load has started"));
+ return promise.forget();
+ }
+
+ // 5.2.3 Stop using the CDM instance represented by the mediaKeys attribute
+ // to decrypt media data and remove the association with the media element.
+ mMediaKeys->Unbind();
+ mMediaKeys = nullptr;
+
+ // 5.2.4 If the preceding step failed, let this object's attaching media
+ // keys value be false and reject promise with a new DOMException whose
+ // name is the appropriate error name.
+ }
+
+ // 5.3. If mediaKeys is not null, run the following steps:
+ if (aMediaKeys) {
+ if (!aMediaKeys->GetCDMProxy()) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("CDM crashed before binding MediaKeys object to HTMLMediaElement"));
+ return promise.forget();
+ }
+
+ // 5.3.1 Associate the CDM instance represented by mediaKeys with the
+ // media element for decrypting media data.
+ if (NS_FAILED(aMediaKeys->Bind(this))) {
+ // 5.3.2 If the preceding step failed, run the following steps:
+ // 5.3.2.1 Set the mediaKeys attribute to null.
+ mMediaKeys = nullptr;
+ // 5.3.2.2 Let this object's attaching media keys value be false.
+ // 5.3.2.3 Reject promise with a new DOMException whose name is
+ // the appropriate error name.
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Failed to bind MediaKeys object to HTMLMediaElement"));
+ return promise.forget();
+ }
+ // 5.3.3 Queue a task to run the "Attempt to Resume Playback If Necessary"
+ // algorithm on the media element.
+ // Note: Setting the CDMProxy on the MediaDecoder will unblock playback.
+ if (mDecoder) {
+ mDecoder->SetCDMProxy(aMediaKeys->GetCDMProxy());
+ }
+ }
+
+ // 5.4 Set the mediaKeys attribute to mediaKeys.
+ mMediaKeys = aMediaKeys;
+
+ // 5.5 Let this object's attaching media keys value be false.
+
+ // 5.6 Resolve promise.
+ promise->MaybeResolveWithUndefined();
+
+ // 6. Return promise.
+ return promise.forget();
+}
+
+EventHandlerNonNull*
+HTMLMediaElement::GetOnencrypted()
+{
+ return EventTarget::GetEventHandler(nsGkAtoms::onencrypted, EmptyString());
+}
+
+void
+HTMLMediaElement::SetOnencrypted(EventHandlerNonNull* aCallback)
+{
+ EventTarget::SetEventHandler(nsGkAtoms::onencrypted, EmptyString(), aCallback);
+}
+
+EventHandlerNonNull*
+HTMLMediaElement::GetOnwaitingforkey()
+{
+ return EventTarget::GetEventHandler(nsGkAtoms::onwaitingforkey, EmptyString());
+}
+
+void
+HTMLMediaElement::SetOnwaitingforkey(EventHandlerNonNull* aCallback)
+{
+ EventTarget::SetEventHandler(nsGkAtoms::onwaitingforkey, EmptyString(), aCallback);
+}
+
+void
+HTMLMediaElement::DispatchEncrypted(const nsTArray<uint8_t>& aInitData,
+ const nsAString& aInitDataType)
+{
+ LOG(LogLevel::Debug,
+ ("%p DispatchEncrypted initDataType='%s'",
+ this, NS_ConvertUTF16toUTF8(aInitDataType).get()));
+
+ if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
+ // Ready state not HAVE_METADATA (yet), don't dispatch encrypted now.
+ // Queueing for later dispatch in MetadataLoaded.
+ mPendingEncryptedInitData.AddInitData(aInitDataType, aInitData);
+ return;
+ }
+
+ RefPtr<MediaEncryptedEvent> event;
+ if (IsCORSSameOrigin()) {
+ event = MediaEncryptedEvent::Constructor(this, aInitDataType, aInitData);
+ } else {
+ event = MediaEncryptedEvent::Constructor(this);
+ }
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, event);
+ asyncDispatcher->PostDOMEvent();
+}
+
+bool
+HTMLMediaElement::IsEventAttributeName(nsIAtom* aName)
+{
+ return aName == nsGkAtoms::onencrypted ||
+ nsGenericHTMLElement::IsEventAttributeName(aName);
+}
+
+already_AddRefed<nsIPrincipal>
+HTMLMediaElement::GetTopLevelPrincipal()
+{
+ RefPtr<nsIPrincipal> principal;
+ nsCOMPtr<nsPIDOMWindowInner> window = OwnerDoc()->GetInnerWindow();
+ if (!window) {
+ return nullptr;
+ }
+ // XXXkhuey better hope we always have an outer ...
+ nsCOMPtr<nsPIDOMWindowOuter> top = window->GetOuterWindow()->GetTop();
+ if (!top) {
+ return nullptr;
+ }
+ nsIDocument* doc = top->GetExtantDoc();
+ if (!doc) {
+ return nullptr;
+ }
+ principal = doc->NodePrincipal();
+ return principal.forget();
+}
+
+void
+HTMLMediaElement::CannotDecryptWaitingForKey()
+{
+ LOG(LogLevel::Debug, ("%p, CannotDecryptWaitingForKey()", this));
+
+ // http://w3c.github.io/encrypted-media/#wait-for-key
+ // 7.3.4 Queue a "waitingforkey" Event
+ // 1. Let the media element be the specified HTMLMediaElement object.
+ // 2. If the media element's waiting for key value is true, abort these steps.
+ if (mWaitingForKey == NOT_WAITING_FOR_KEY) {
+ // 3. Set the media element's waiting for key value to true.
+ // Note: algorithm continues in UpdateReadyStateInternal() when all decoded
+ // data enqueued in the MDSM is consumed.
+ mWaitingForKey = WAITING_FOR_KEY;
+ UpdateReadyStateInternal();
+ }
+}
+
+NS_IMETHODIMP HTMLMediaElement::WindowAudioCaptureChanged(bool aCapture)
+{
+ MOZ_ASSERT(mAudioChannelAgent);
+
+ if (mAudioCapturedByWindow != aCapture) {
+ mAudioCapturedByWindow = aCapture;
+ AudioCaptureStreamChangeIfNeeded();
+ }
+ return NS_OK;
+}
+
+AudioTrackList*
+HTMLMediaElement::AudioTracks()
+{
+ if (!mAudioTrackList) {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(OwnerDoc()->GetParentObject());
+ mAudioTrackList = new AudioTrackList(window, this);
+ }
+ return mAudioTrackList;
+}
+
+VideoTrackList*
+HTMLMediaElement::VideoTracks()
+{
+ if (!mVideoTrackList) {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(OwnerDoc()->GetParentObject());
+ mVideoTrackList = new VideoTrackList(window, this);
+ }
+ return mVideoTrackList;
+}
+
+TextTrackList*
+HTMLMediaElement::GetTextTracks()
+{
+ return GetOrCreateTextTrackManager()->GetTextTracks();
+}
+
+already_AddRefed<TextTrack>
+HTMLMediaElement::AddTextTrack(TextTrackKind aKind,
+ const nsAString& aLabel,
+ const nsAString& aLanguage)
+{
+ return
+ GetOrCreateTextTrackManager()->AddTextTrack(aKind, aLabel, aLanguage,
+ TextTrackMode::Hidden,
+ TextTrackReadyState::Loaded,
+ TextTrackSource::AddTextTrack);
+}
+
+void
+HTMLMediaElement::PopulatePendingTextTrackList()
+{
+ if (mTextTrackManager) {
+ mTextTrackManager->PopulatePendingList();
+ }
+}
+
+TextTrackManager*
+HTMLMediaElement::GetOrCreateTextTrackManager()
+{
+ if (!mTextTrackManager) {
+ mTextTrackManager = new TextTrackManager(this);
+ mTextTrackManager->AddListeners();
+ }
+ return mTextTrackManager;
+}
+
+void
+HTMLMediaElement::SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv)
+{
+ nsString channel;
+ channel.AssignASCII(AudioChannelValues::strings[uint32_t(aValue)].value,
+ AudioChannelValues::strings[uint32_t(aValue)].length);
+ SetHTMLAttr(nsGkAtoms::mozaudiochannel, channel, aRv);
+}
+
+MediaDecoderOwner::NextFrameStatus
+HTMLMediaElement::NextFrameStatus()
+{
+ if (mDecoder) {
+ return mDecoder->NextFrameStatus();
+ } else if (mMediaStreamListener) {
+ return mMediaStreamListener->NextFrameStatus();
+ }
+ return NEXT_FRAME_UNINITIALIZED;
+}
+
+float
+HTMLMediaElement::ComputedVolume() const
+{
+ return mMuted ? 0.0f : float(mVolume * mAudioChannelVolume);
+}
+
+bool
+HTMLMediaElement::ComputedMuted() const
+{
+ return (mMuted & MUTED_BY_AUDIO_CHANNEL);
+}
+
+nsSuspendedTypes
+HTMLMediaElement::ComputedSuspended() const
+{
+ return mAudioChannelSuspended;
+}
+
+bool
+HTMLMediaElement::IsCurrentlyPlaying() const
+{
+ // We have playable data, but we still need to check whether data is "real"
+ // current data.
+ return mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
+ !IsPlaybackEnded();
+}
+
+void
+HTMLMediaElement::SetAudibleState(bool aAudible)
+{
+ if (mIsAudioTrackAudible != aAudible) {
+ mIsAudioTrackAudible = aAudible;
+ NotifyAudioPlaybackChanged(
+ AudioChannelService::AudibleChangedReasons::eDataAudibleChanged);
+ }
+}
+
+void
+HTMLMediaElement::NotifyAudioPlaybackChanged(AudibleChangedReasons aReason)
+{
+ if (mAudible == IsAudible()) {
+ return;
+ }
+
+ mAudible = IsAudible();
+
+ if (mAudioChannelAgent && mAudioChannelAgent->IsPlayingStarted()) {
+ mAudioChannelAgent->NotifyStartedAudible(mAudible, aReason);
+ }
+}
+
+AudibleState
+HTMLMediaElement::IsAudible() const
+{
+ // Muted or the volume should not be ~0
+ if (Muted() || (std::fabs(Volume()) <= 1e-7)) {
+ return AudioChannelService::AudibleState::eNotAudible;
+ }
+
+ // No audio track.
+ if (!HasAudio()) {
+ return AudioChannelService::AudibleState::eNotAudible;
+ }
+
+ // Might be audible but not yet.
+ if (HasAudio() && !mIsAudioTrackAudible) {
+ return AudioChannelService::AudibleState::eMaybeAudible;
+ }
+
+ // Media is suspended.
+ if (mAudioChannelSuspended != nsISuspendedTypes::NONE_SUSPENDED) {
+ return AudioChannelService::AudibleState::eNotAudible;
+ }
+
+ return AudioChannelService::AudibleState::eAudible;
+}
+
+void
+HTMLMediaElement::MaybeNotifyMediaResumed(SuspendTypes aSuspend)
+{
+ // In fennec, we should send the notification when media is resumed from the
+ // pause-disposable which was called by media control.
+ if (mAudioChannelSuspended != nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE &&
+ aSuspend != nsISuspendedTypes::NONE_SUSPENDED) {
+ return;
+ }
+
+ MOZ_ASSERT(mAudioChannelAgent);
+ uint64_t windowID = mAudioChannelAgent->WindowID();
+ NS_DispatchToMainThread(NS_NewRunnableFunction([windowID]() -> void {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (NS_WARN_IF(!observerService)) {
+ return;
+ }
+
+ nsCOMPtr<nsISupportsPRUint64> wrapper =
+ do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
+ if (NS_WARN_IF(!wrapper)) {
+ return;
+ }
+
+ wrapper->SetData(windowID);
+ observerService->NotifyObservers(wrapper,
+ "media-playback-resumed",
+ u"active");
+ }));
+}
+
+bool
+HTMLMediaElement::ShouldElementBePaused()
+{
+ // Bfcached page or inactive document.
+ if (!IsActive()) {
+ return true;
+ }
+
+ return false;
+}
+
+void
+HTMLMediaElement::SetMediaInfo(const MediaInfo& aInfo)
+{
+ const bool oldHasAudio = mMediaInfo.HasAudio();
+ mMediaInfo = aInfo;
+ if (aInfo.HasAudio() != oldHasAudio) {
+ NotifyAudioPlaybackChanged(
+ AudioChannelService::AudibleChangedReasons::eDataAudibleChanged);
+ }
+ AudioCaptureStreamChangeIfNeeded();
+}
+
+void
+HTMLMediaElement::AudioCaptureStreamChangeIfNeeded()
+{
+ // No need to capture a silence media element.
+ if (!HasAudio()) {
+ return;
+ }
+
+ if (MaybeCreateAudioChannelAgent() &&
+ !mAudioChannelAgent->IsPlayingStarted()) {
+ return;
+ }
+
+ if (mAudioCapturedByWindow && !mCaptureStreamPort) {
+ nsCOMPtr<nsPIDOMWindowInner> window = OwnerDoc()->GetInnerWindow();
+ if (!OwnerDoc()->GetInnerWindow()) {
+ return;
+ }
+
+ uint64_t id = window->WindowID();
+ MediaStreamGraph* msg =
+ MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
+ mAudioChannel);
+
+ if (GetSrcMediaStream()) {
+ mCaptureStreamPort = msg->ConnectToCaptureStream(id, GetSrcMediaStream());
+ } else {
+ RefPtr<DOMMediaStream> stream =
+ CaptureStreamInternal(false, false, msg);
+ mCaptureStreamPort = msg->ConnectToCaptureStream(id, stream->GetPlaybackStream());
+ }
+ } else if (!mAudioCapturedByWindow && mCaptureStreamPort) {
+ if (mDecoder) {
+ ProcessedMediaStream* ps =
+ mCaptureStreamPort->GetSource()->AsProcessedStream();
+ MOZ_ASSERT(ps);
+
+ for (uint32_t i = 0; i < mOutputStreams.Length(); i++) {
+ if (mOutputStreams[i].mStream->GetPlaybackStream() == ps) {
+ mOutputStreams.RemoveElementAt(i);
+ break;
+ }
+ }
+ mDecoder->RemoveOutputStream(ps);
+ }
+ mCaptureStreamPort->Destroy();
+ mCaptureStreamPort = nullptr;
+ }
+}
+
+void
+HTMLMediaElement::NotifyCueDisplayStatesChanged()
+{
+ if (!mTextTrackManager) {
+ return;
+ }
+
+ mTextTrackManager->DispatchUpdateCueDisplay();
+}
+
+void
+HTMLMediaElement::MarkAsContentSource(CallerAPI aAPI)
+{
+ const bool isVisible = mVisibilityState != Visibility::APPROXIMATELY_NONVISIBLE;
+
+ if (isVisible) {
+ // 0 = ALL_VISIBLE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 0);
+ } else {
+ // 1 = ALL_INVISIBLE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 1);
+
+ if (IsInUncomposedDoc()) {
+ // 0 = ALL_IN_TREE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 0);
+ } else {
+ // 1 = ALL_NOT_IN_TREE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 1);
+ }
+ }
+
+ switch (aAPI) {
+ case CallerAPI::DRAW_IMAGE: {
+ if (isVisible) {
+ // 2 = drawImage_VISIBLE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 2);
+ } else {
+ // 3 = drawImage_INVISIBLE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 3);
+
+ if (IsInUncomposedDoc()) {
+ // 2 = drawImage_IN_TREE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 2);
+ } else {
+ // 3 = drawImage_NOT_IN_TREE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 3);
+ }
+ }
+ break;
+ }
+ case CallerAPI::CREATE_PATTERN: {
+ if (isVisible) {
+ // 4 = createPattern_VISIBLE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 4);
+ } else {
+ // 5 = createPattern_INVISIBLE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 5);
+
+ if (IsInUncomposedDoc()) {
+ // 4 = createPattern_IN_TREE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 4);
+ } else {
+ // 5 = createPattern_NOT_IN_TREE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 5);
+ }
+ }
+ break;
+ }
+ case CallerAPI::CREATE_IMAGEBITMAP: {
+ if (isVisible) {
+ // 6 = createImageBitmap_VISIBLE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 6);
+ } else {
+ // 7 = createImageBitmap_INVISIBLE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 7);
+
+ if (IsInUncomposedDoc()) {
+ // 6 = createImageBitmap_IN_TREE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 6);
+ } else {
+ // 7 = createImageBitmap_NOT_IN_TREE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 7);
+ }
+ }
+ break;
+ }
+ case CallerAPI::CAPTURE_STREAM: {
+ if (isVisible) {
+ // 8 = captureStream_VISIBLE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 8);
+ } else {
+ // 9 = captureStream_INVISIBLE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 9);
+
+ if (IsInUncomposedDoc()) {
+ // 8 = captureStream_IN_TREE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 8);
+ } else {
+ // 9 = captureStream_NOT_IN_TREE
+ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 9);
+ }
+ }
+ break;
+ }
+ }
+
+ LOG(LogLevel::Debug,
+ ("%p Log VIDEO_AS_CONTENT_SOURCE: visibility = %u, API: '%d' and 'All'",
+ this, isVisible, aAPI));
+
+ if (!isVisible) {
+ LOG(LogLevel::Debug,
+ ("%p Log VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT: inTree = %u, API: '%d' and 'All'",
+ this, IsInUncomposedDoc(), aAPI));
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLMediaElement.h b/dom/html/HTMLMediaElement.h
new file mode 100644
index 000000000..d40e9df46
--- /dev/null
+++ b/dom/html/HTMLMediaElement.h
@@ -0,0 +1,1756 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_HTMLMediaElement_h
+#define mozilla_dom_HTMLMediaElement_h
+
+#include "nsAutoPtr.h"
+#include "nsIDOMHTMLMediaElement.h"
+#include "nsGenericHTMLElement.h"
+#include "MediaDecoderOwner.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIObserver.h"
+#include "mozilla/CORSMode.h"
+#include "DecoderTraits.h"
+#include "nsIAudioChannelAgent.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/TextTrackManager.h"
+#include "mozilla/WeakPtr.h"
+#include "MediaDecoder.h"
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/StateWatching.h"
+#include "nsGkAtoms.h"
+#include "PrincipalChangeObserver.h"
+
+// X.h on Linux #defines CurrentTime as 0L, so we have to #undef it here.
+#ifdef CurrentTime
+#undef CurrentTime
+#endif
+
+#include "mozilla/dom/HTMLMediaElementBinding.h"
+
+// Define to output information on decoding and painting framerate
+/* #define DEBUG_FRAME_RATE 1 */
+
+typedef uint16_t nsMediaNetworkState;
+typedef uint16_t nsMediaReadyState;
+typedef uint32_t SuspendTypes;
+typedef uint32_t AudibleChangedReasons;
+typedef uint8_t AudibleState;
+
+namespace mozilla {
+class DecoderDoctorDiagnostics;
+class DOMMediaStream;
+class ErrorResult;
+class MediaResource;
+class MediaDecoder;
+class VideoFrameContainer;
+namespace dom {
+class AudioChannelAgent;
+class MediaKeys;
+class TextTrack;
+class TimeRanges;
+class WakeLock;
+class MediaTrack;
+class MediaStreamTrack;
+class VideoStreamTrack;
+} // namespace dom
+} // namespace mozilla
+
+class AutoNotifyAudioChannelAgent;
+class nsIChannel;
+class nsIHttpChannel;
+class nsILoadGroup;
+class nsIRunnable;
+class nsITimer;
+class nsRange;
+
+namespace mozilla {
+namespace dom {
+
+// Number of milliseconds between timeupdate events as defined by spec
+#define TIMEUPDATE_MS 250
+
+class MediaError;
+class MediaSource;
+class TextTrackList;
+class AudioTrackList;
+class VideoTrackList;
+
+class HTMLMediaElement : public nsGenericHTMLElement,
+ public nsIDOMHTMLMediaElement,
+ public MediaDecoderOwner,
+ public nsIAudioChannelAgentCallback,
+ public PrincipalChangeObserver<DOMMediaStream>,
+ public SupportsWeakPtr<HTMLMediaElement>
+{
+ friend AutoNotifyAudioChannelAgent;
+
+public:
+ typedef mozilla::TimeStamp TimeStamp;
+ typedef mozilla::layers::ImageContainer ImageContainer;
+ typedef mozilla::VideoFrameContainer VideoFrameContainer;
+ typedef mozilla::MediaStream MediaStream;
+ typedef mozilla::MediaResource MediaResource;
+ typedef mozilla::MediaDecoderOwner MediaDecoderOwner;
+ typedef mozilla::MetadataTags MetadataTags;
+
+ MOZ_DECLARE_WEAKREFERENCE_TYPENAME(HTMLMediaElement)
+
+ CORSMode GetCORSMode() {
+ return mCORSMode;
+ }
+
+ explicit HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ /**
+ * This is used when the browser is constructing a video element to play
+ * a channel that we've already started loading. The src attribute and
+ * <source> children are ignored.
+ * @param aChannel the channel to use
+ * @param aListener returns a stream listener that should receive
+ * notifications for the stream
+ */
+ nsresult LoadWithChannel(nsIChannel *aChannel, nsIStreamListener **aListener);
+
+ // nsIDOMHTMLMediaElement
+ NS_DECL_NSIDOMHTMLMEDIAELEMENT
+
+ NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLMediaElement,
+ nsGenericHTMLElement)
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ // SetAttr override. C++ is stupid, so have to override both
+ // overloaded methods.
+ 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;
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttr,
+ bool aNotify) override;
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue,
+ 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;
+ virtual void DoneCreatingElement() override;
+
+ virtual bool IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable,
+ int32_t *aTabIndex) override;
+ virtual int32_t TabIndexDefault() override;
+
+ /**
+ * Call this to reevaluate whether we should start/stop due to our owner
+ * document being active, inactive, visible or hidden.
+ */
+ virtual void NotifyOwnerDocumentActivityChanged();
+
+ // Called by the video decoder object, on the main thread,
+ // when it has read the metadata containing video dimensions,
+ // etc.
+ virtual void MetadataLoaded(const MediaInfo* aInfo,
+ nsAutoPtr<const MetadataTags> aTags) final override;
+
+ // Called by the decoder object, on the main thread,
+ // when it has read the first frame of the video or audio.
+ virtual void FirstFrameLoaded() final override;
+
+ // Called by the video decoder object, on the main thread,
+ // when the resource has a network error during loading.
+ virtual void NetworkError() final override;
+
+ // Called by the video decoder object, on the main thread, when the
+ // resource has a decode error during metadata loading or decoding.
+ virtual void DecodeError(const MediaResult& aError) final override;
+
+ // Return true if error attribute is not null.
+ virtual bool HasError() const final override;
+
+ // Called by the video decoder object, on the main thread, when the
+ // resource load has been cancelled.
+ virtual void LoadAborted() final override;
+
+ // Called by the video decoder object, on the main thread,
+ // when the video playback has ended.
+ virtual void PlaybackEnded() final override;
+
+ // Called by the video decoder object, on the main thread,
+ // when the resource has started seeking.
+ virtual void SeekStarted() final override;
+
+ // Called by the video decoder object, on the main thread,
+ // when the resource has completed seeking.
+ virtual void SeekCompleted() final override;
+
+ // Called by the media stream, on the main thread, when the download
+ // has been suspended by the cache or because the element itself
+ // asked the decoder to suspend the download.
+ virtual void DownloadSuspended() final override;
+
+ // Called by the media stream, on the main thread, when the download
+ // has been resumed by the cache or because the element itself
+ // asked the decoder to resumed the download.
+ // If aForceNetworkLoading is True, ignore the fact that the download has
+ // previously finished. We are downloading the middle of the media after
+ // having downloaded the end, we need to notify the element a download in
+ // ongoing.
+ virtual void DownloadResumed(bool aForceNetworkLoading = false) final override;
+
+ // Called to indicate the download is progressing.
+ virtual void DownloadProgressed() final override;
+
+ // Called by the media decoder to indicate whether the media cache has
+ // suspended the channel.
+ virtual void NotifySuspendedByCache(bool aIsSuspended) final override;
+
+ virtual bool IsActive() const final override;
+
+ virtual bool IsHidden() const final override;
+
+ // Called by the media decoder and the video frame to get the
+ // ImageContainer containing the video data.
+ virtual VideoFrameContainer* GetVideoFrameContainer() final override;
+ layers::ImageContainer* GetImageContainer();
+
+ // From PrincipalChangeObserver<DOMMediaStream>.
+ void PrincipalChanged(DOMMediaStream* aStream) override;
+
+ void UpdateSrcStreamVideoPrincipal(const PrincipalHandle& aPrincipalHandle);
+
+ // Called after the MediaStream we're playing rendered a frame to aContainer
+ // with a different principalHandle than the previous frame.
+ void PrincipalHandleChangedForVideoFrameContainer(VideoFrameContainer* aContainer,
+ const PrincipalHandle& aNewPrincipalHandle);
+
+ // Dispatch events
+ virtual nsresult DispatchAsyncEvent(const nsAString& aName) final override;
+
+ // Triggers a recomputation of readyState.
+ void UpdateReadyState() override { UpdateReadyStateInternal(); }
+
+ // Dispatch events that were raised while in the bfcache
+ nsresult DispatchPendingMediaEvents();
+
+ // Return true if we can activate autoplay assuming enough data has arrived.
+ bool CanActivateAutoplay();
+
+ // Notify that state has changed that might cause an autoplay element to
+ // start playing.
+ // If the element is 'autoplay' and is ready to play back (not paused,
+ // autoplay pref enabled, etc), it should start playing back.
+ void CheckAutoplayDataReady();
+
+ // Check if the media element had crossorigin set when loading started
+ bool ShouldCheckAllowOrigin();
+
+ // Returns true if the currently loaded resource is CORS same-origin with
+ // respect to the document.
+ bool IsCORSSameOrigin();
+
+ // Is the media element potentially playing as defined by the HTML 5 specification.
+ // http://www.whatwg.org/specs/web-apps/current-work/#potentially-playing
+ bool IsPotentiallyPlaying() const;
+
+ // Has playback ended as defined by the HTML 5 specification.
+ // http://www.whatwg.org/specs/web-apps/current-work/#ended
+ bool IsPlaybackEnded() const;
+
+ // principal of the currently playing resource. Anything accessing the contents
+ // of this element must have a principal that subsumes this principal.
+ // Returns null if nothing is playing.
+ already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
+
+ // Principal of the currently playing video resource. Anything accessing the
+ // image container of this element must have a principal that subsumes this
+ // principal. If there are no live video tracks but content has been rendered
+ // to the image container, we return the last video principal we had. Should
+ // the image container be empty with no live video tracks, we return nullptr.
+ already_AddRefed<nsIPrincipal> GetCurrentVideoPrincipal();
+
+ // called to notify that the principal of the decoder's media resource has changed.
+ void NotifyDecoderPrincipalChanged() final override;
+
+ // An interface for observing principal changes on the media elements
+ // MediaDecoder. This will also be notified if the active CORSMode changes.
+ class DecoderPrincipalChangeObserver
+ {
+ public:
+ virtual void NotifyDecoderPrincipalChanged() = 0;
+ };
+
+ /**
+ * Add a DecoderPrincipalChangeObserver to this media element.
+ *
+ * Ownership of the DecoderPrincipalChangeObserver remains with the caller,
+ * and it's the caller's responsibility to remove the observer before it dies.
+ */
+ void AddDecoderPrincipalChangeObserver(DecoderPrincipalChangeObserver* aObserver);
+
+ /**
+ * Remove an added DecoderPrincipalChangeObserver from this media element.
+ *
+ * Returns true if it was successfully removed.
+ */
+ bool RemoveDecoderPrincipalChangeObserver(DecoderPrincipalChangeObserver* aObserver);
+
+ class StreamCaptureTrackSource;
+ class DecoderCaptureTrackSource;
+ class CaptureStreamTrackSourceGetter;
+
+ // Update the visual size of the media. Called from the decoder on the
+ // main thread when/if the size changes.
+ void UpdateMediaSize(const nsIntSize& aSize);
+ // Like UpdateMediaSize, but only updates the size if no size has yet
+ // been set.
+ void UpdateInitialMediaSize(const nsIntSize& aSize);
+
+ // Returns the CanPlayStatus indicating if we can handle the
+ // full MIME type including the optional codecs parameter.
+ static CanPlayStatus GetCanPlay(const nsAString& aType,
+ DecoderDoctorDiagnostics* aDiagnostics);
+
+ /**
+ * Called when a child source element is added to this media element. This
+ * may queue a task to run the select resource algorithm if appropriate.
+ */
+ void NotifyAddedSource();
+
+ /**
+ * Called when there's been an error fetching the resource. This decides
+ * whether it's appropriate to fire an error event.
+ */
+ void NotifyLoadError();
+
+ /**
+ * Called by one of our associated MediaTrackLists (audio/video) when an
+ * AudioTrack is enabled or a VideoTrack is selected.
+ */
+ void NotifyMediaTrackEnabled(MediaTrack* aTrack);
+
+ /**
+ * Called by one of our associated MediaTrackLists (audio/video) when an
+ * AudioTrack is disabled or a VideoTrack is unselected.
+ */
+ void NotifyMediaTrackDisabled(MediaTrack* aTrack);
+
+ /**
+ * Called when tracks become available to the source media stream.
+ */
+ void NotifyMediaStreamTracksAvailable(DOMMediaStream* aStream);
+
+ /**
+ * Called when a captured MediaStreamTrack is stopped so we can clean up its
+ * MediaInputPort.
+ */
+ void NotifyOutputTrackStopped(DOMMediaStream* aOwningStream,
+ TrackID aDestinationTrackID);
+
+ virtual bool IsNodeOfType(uint32_t aFlags) const override;
+
+ /**
+ * Returns the current load ID. Asynchronous events store the ID that was
+ * current when they were enqueued, and if it has changed when they come to
+ * fire, they consider themselves cancelled, and don't fire.
+ */
+ uint32_t GetCurrentLoadID() { return mCurrentLoadID; }
+
+ /**
+ * Returns the load group for this media element's owner document.
+ * XXX XBL2 issue.
+ */
+ already_AddRefed<nsILoadGroup> GetDocumentLoadGroup();
+
+ /**
+ * Returns true if the media has played or completed a seek.
+ * Used by video frame to determine whether to paint the poster.
+ */
+ bool GetPlayedOrSeeked() const { return mHasPlayedOrSeeked; }
+
+ nsresult CopyInnerTo(Element* aDest);
+
+ /**
+ * Sets the Accept header on the HTTP channel to the required
+ * video or audio MIME types.
+ */
+ virtual nsresult SetAcceptHeader(nsIHttpChannel* aChannel) = 0;
+
+ /**
+ * Sets the required request headers on the HTTP channel for
+ * video or audio requests.
+ */
+ void SetRequestHeaders(nsIHttpChannel* aChannel);
+
+ /**
+ * Asynchronously awaits a stable state, whereupon aRunnable runs on the main
+ * thread. This adds an event which run aRunnable to the appshell's list of
+ * sections synchronous the next time control returns to the event loop.
+ */
+ void RunInStableState(nsIRunnable* aRunnable);
+
+ /**
+ * Fires a timeupdate event. If aPeriodic is true, the event will only
+ * be fired if we've not fired a timeupdate event (for any reason) in the
+ * last 250ms, as required by the spec when the current time is periodically
+ * increasing during playback.
+ */
+ virtual void FireTimeUpdate(bool aPeriodic) final override;
+
+ /**
+ * This will return null if mSrcStream is null, or if mSrcStream is not
+ * null but its GetPlaybackStream() returns null --- which can happen during
+ * cycle collection unlinking!
+ */
+ MediaStream* GetSrcMediaStream() const;
+
+ // WebIDL
+
+ MediaError* GetError() const;
+
+ // XPCOM GetSrc() is OK
+ void SetSrc(const nsAString& aSrc, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::src, aSrc, aRv);
+ }
+
+ // XPCOM GetCurrentSrc() is OK
+
+ void GetCrossOrigin(nsAString& aResult)
+ {
+ // Null for both missing and invalid defaults is ok, since we
+ // always parse to an enum value, so we don't need an invalid
+ // default, and we _want_ the missing default to be null.
+ GetEnumAttr(nsGkAtoms::crossorigin, nullptr, aResult);
+ }
+ void SetCrossOrigin(const nsAString& aCrossOrigin, ErrorResult& aError)
+ {
+ SetOrRemoveNullableStringAttr(nsGkAtoms::crossorigin, aCrossOrigin, aError);
+ }
+
+ uint16_t NetworkState() const
+ {
+ return mNetworkState;
+ }
+
+ void NotifyXPCOMShutdown() final override;
+
+ // Called by media decoder when the audible state changed or when input is
+ // a media stream.
+ virtual void SetAudibleState(bool aAudible) final override;
+
+ // Notify agent when the MediaElement changes its audible state.
+ void NotifyAudioPlaybackChanged(AudibleChangedReasons aReason);
+
+ // XPCOM GetPreload() is OK
+ void SetPreload(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::preload, aValue, aRv);
+ }
+
+ already_AddRefed<TimeRanges> Buffered() const;
+
+ // XPCOM Load() is OK
+
+ // XPCOM CanPlayType() is OK
+
+ uint16_t ReadyState() const
+ {
+ return mReadyState;
+ }
+
+ bool Seeking() const;
+
+ double CurrentTime() const;
+
+ void SetCurrentTime(double aCurrentTime, ErrorResult& aRv);
+
+ void FastSeek(double aTime, ErrorResult& aRv);
+
+ already_AddRefed<Promise> SeekToNextFrame(ErrorResult& aRv);
+
+ double Duration() const;
+
+ bool HasAudio() const
+ {
+ return mMediaInfo.HasAudio();
+ }
+
+ bool HasVideo() const
+ {
+ return mMediaInfo.HasVideo();
+ }
+
+ bool IsEncrypted() const
+ {
+ return mIsEncrypted;
+ }
+
+ bool Paused() const
+ {
+ return mPaused;
+ }
+
+ double DefaultPlaybackRate() const
+ {
+ return mDefaultPlaybackRate;
+ }
+
+ void SetDefaultPlaybackRate(double aDefaultPlaybackRate, ErrorResult& aRv);
+
+ double PlaybackRate() const
+ {
+ return mPlaybackRate;
+ }
+
+ void SetPlaybackRate(double aPlaybackRate, ErrorResult& aRv);
+
+ already_AddRefed<TimeRanges> Played();
+
+ already_AddRefed<TimeRanges> Seekable() const;
+
+ bool Ended();
+
+ bool Autoplay() const
+ {
+ return GetBoolAttr(nsGkAtoms::autoplay);
+ }
+
+ void SetAutoplay(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::autoplay, aValue, aRv);
+ }
+
+ bool Loop() const
+ {
+ return GetBoolAttr(nsGkAtoms::loop);
+ }
+
+ void SetLoop(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::loop, aValue, aRv);
+ }
+
+ void Play(ErrorResult& aRv);
+
+ void Pause(ErrorResult& aRv);
+
+ bool Controls() const
+ {
+ return GetBoolAttr(nsGkAtoms::controls);
+ }
+
+ void SetControls(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::controls, aValue, aRv);
+ }
+
+ double Volume() const
+ {
+ return mVolume;
+ }
+
+ void SetVolume(double aVolume, ErrorResult& aRv);
+
+ bool Muted() const
+ {
+ return mMuted & MUTED_BY_CONTENT;
+ }
+
+ // XPCOM SetMuted() is OK
+
+ bool DefaultMuted() const
+ {
+ return GetBoolAttr(nsGkAtoms::muted);
+ }
+
+ void SetDefaultMuted(bool aMuted, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::muted, aMuted, aRv);
+ }
+
+ bool MozAllowCasting() const
+ {
+ return mAllowCasting;
+ }
+
+ void SetMozAllowCasting(bool aShow)
+ {
+ mAllowCasting = aShow;
+ }
+
+ bool MozIsCasting() const
+ {
+ return mIsCasting;
+ }
+
+ void SetMozIsCasting(bool aShow)
+ {
+ mIsCasting = aShow;
+ }
+
+ already_AddRefed<MediaSource> GetMozMediaSourceObject() const;
+ // Returns a string describing the state of the media player internal
+ // data. Used for debugging purposes.
+ void GetMozDebugReaderData(nsAString& aString);
+
+ void MozDumpDebugInfo();
+
+ void SetVisible(bool aVisible);
+
+ already_AddRefed<DOMMediaStream> GetSrcObject() const;
+ void SetSrcObject(DOMMediaStream& aValue);
+ void SetSrcObject(DOMMediaStream* aValue);
+
+ // TODO: remove prefixed versions soon (1183495).
+ already_AddRefed<DOMMediaStream> GetMozSrcObject() const;
+ void SetMozSrcObject(DOMMediaStream& aValue);
+ void SetMozSrcObject(DOMMediaStream* aValue);
+
+ bool MozPreservesPitch() const
+ {
+ return mPreservesPitch;
+ }
+
+ // XPCOM MozPreservesPitch() is OK
+
+ MediaKeys* GetMediaKeys() const;
+
+ already_AddRefed<Promise> SetMediaKeys(MediaKeys* mediaKeys,
+ ErrorResult& aRv);
+
+ mozilla::dom::EventHandlerNonNull* GetOnencrypted();
+ void SetOnencrypted(mozilla::dom::EventHandlerNonNull* aCallback);
+
+ mozilla::dom::EventHandlerNonNull* GetOnwaitingforkey();
+ void SetOnwaitingforkey(mozilla::dom::EventHandlerNonNull* aCallback);
+
+ void DispatchEncrypted(const nsTArray<uint8_t>& aInitData,
+ const nsAString& aInitDataType) override;
+
+ bool IsEventAttributeName(nsIAtom* aName) override;
+
+ // Returns the principal of the "top level" document; the origin displayed
+ // in the URL bar of the browser window.
+ already_AddRefed<nsIPrincipal> GetTopLevelPrincipal();
+
+ bool ContainsRestrictedContent();
+
+ void CannotDecryptWaitingForKey();
+
+ bool MozAutoplayEnabled() const
+ {
+ return mAutoplayEnabled;
+ }
+
+ already_AddRefed<DOMMediaStream> CaptureAudio(ErrorResult& aRv,
+ MediaStreamGraph* aGraph);
+
+ already_AddRefed<DOMMediaStream> MozCaptureStream(ErrorResult& aRv);
+
+ already_AddRefed<DOMMediaStream> MozCaptureStreamUntilEnded(ErrorResult& aRv);
+
+ bool MozAudioCaptured() const
+ {
+ return mAudioCaptured;
+ }
+
+ void MozGetMetadata(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
+ ErrorResult& aRv);
+
+ double MozFragmentEnd();
+
+ AudioChannel MozAudioChannelType() const
+ {
+ return mAudioChannel;
+ }
+
+ void SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv);
+
+ AudioTrackList* AudioTracks();
+
+ VideoTrackList* VideoTracks();
+
+ TextTrackList* GetTextTracks();
+
+ already_AddRefed<TextTrack> AddTextTrack(TextTrackKind aKind,
+ const nsAString& aLabel,
+ const nsAString& aLanguage);
+
+ void AddTextTrack(TextTrack* aTextTrack) {
+ GetOrCreateTextTrackManager()->AddTextTrack(aTextTrack);
+ }
+
+ void RemoveTextTrack(TextTrack* aTextTrack, bool aPendingListOnly = false) {
+ if (mTextTrackManager) {
+ mTextTrackManager->RemoveTextTrack(aTextTrack, aPendingListOnly);
+ }
+ }
+
+ void NotifyCueAdded(TextTrackCue& aCue) {
+ if (mTextTrackManager) {
+ mTextTrackManager->NotifyCueAdded(aCue);
+ }
+ }
+ void NotifyCueRemoved(TextTrackCue& aCue) {
+ if (mTextTrackManager) {
+ mTextTrackManager->NotifyCueRemoved(aCue);
+ }
+ }
+ void NotifyCueUpdated(TextTrackCue *aCue) {
+ if (mTextTrackManager) {
+ mTextTrackManager->NotifyCueUpdated(aCue);
+ }
+ }
+
+ void NotifyCueDisplayStatesChanged();
+
+ bool GetHasUserInteraction()
+ {
+ return mHasUserInteraction;
+ }
+
+ // A method to check whether we are currently playing.
+ bool IsCurrentlyPlaying() const;
+
+ // Returns true if the media element is being destroyed. Used in
+ // dormancy checks to prevent dormant processing for an element
+ // that will soon be gone.
+ bool IsBeingDestroyed();
+
+ IMPL_EVENT_HANDLER(mozinterruptbegin)
+ IMPL_EVENT_HANDLER(mozinterruptend)
+
+ // These are used for testing only
+ float ComputedVolume() const;
+ bool ComputedMuted() const;
+ nsSuspendedTypes ComputedSuspended() const;
+
+ void SetMediaInfo(const MediaInfo& aInfo);
+
+ // Telemetry: to record the usage of a {visible / invisible} video element as
+ // the source of {drawImage(), createPattern(), createImageBitmap() and
+ // captureStream()} APIs.
+ enum class CallerAPI {
+ DRAW_IMAGE,
+ CREATE_PATTERN,
+ CREATE_IMAGEBITMAP,
+ CAPTURE_STREAM,
+ };
+ void MarkAsContentSource(CallerAPI aAPI);
+
+protected:
+ virtual ~HTMLMediaElement();
+
+ class ChannelLoader;
+ class ErrorSink;
+ class MediaLoadListener;
+ class MediaStreamTracksAvailableCallback;
+ class MediaStreamTrackListener;
+ class StreamListener;
+ class StreamSizeListener;
+ class ShutdownObserver;
+
+ MediaDecoderOwner::NextFrameStatus NextFrameStatus();
+
+ void SetDecoder(MediaDecoder* aDecoder) {
+ MOZ_ASSERT(aDecoder); // Use ShutdownDecoder() to clear.
+ if (mDecoder) {
+ ShutdownDecoder();
+ }
+ mDecoder = aDecoder;
+ }
+
+ class WakeLockBoolWrapper {
+ public:
+ explicit WakeLockBoolWrapper(bool val = false)
+ : mValue(val), mCanPlay(true), mOuter(nullptr) {}
+
+ ~WakeLockBoolWrapper();
+
+ void SetOuter(HTMLMediaElement* outer) { mOuter = outer; }
+ void SetCanPlay(bool aCanPlay);
+
+ MOZ_IMPLICIT operator bool() const { return mValue; }
+
+ WakeLockBoolWrapper& operator=(bool val);
+
+ bool operator !() const { return !mValue; }
+
+ static void TimerCallback(nsITimer* aTimer, void* aClosure);
+
+ private:
+ void UpdateWakeLock();
+
+ bool mValue;
+ bool mCanPlay;
+ HTMLMediaElement* mOuter;
+ nsCOMPtr<nsITimer> mTimer;
+ };
+
+ // Holds references to the DOM wrappers for the MediaStreams that we're
+ // writing to.
+ struct OutputMediaStream {
+ OutputMediaStream();
+ ~OutputMediaStream();
+
+ RefPtr<DOMMediaStream> mStream;
+ bool mFinishWhenEnded;
+ bool mCapturingAudioOnly;
+ bool mCapturingDecoder;
+ bool mCapturingMediaStream;
+
+ // The following members are keeping state for a captured MediaStream.
+ TrackID mNextAvailableTrackID;
+ nsTArray<Pair<nsString, RefPtr<MediaInputPort>>> mTrackPorts;
+ };
+
+ nsresult PlayInternal();
+
+ /** Use this method to change the mReadyState member, so required
+ * events can be fired.
+ */
+ void ChangeReadyState(nsMediaReadyState aState);
+
+ /**
+ * Use this method to change the mNetworkState member, so required
+ * actions will be taken during the transition.
+ */
+ void ChangeNetworkState(nsMediaNetworkState aState);
+
+ /**
+ * These two methods are called by the WakeLockBoolWrapper when the wakelock
+ * has to be created or released.
+ */
+ virtual void WakeLockCreate();
+ virtual void WakeLockRelease();
+ RefPtr<WakeLock> mWakeLock;
+
+ /**
+ * Logs a warning message to the web console to report various failures.
+ * aMsg is the localized message identifier, aParams is the parameters to
+ * be substituted into the localized message, and aParamCount is the number
+ * of parameters in aParams.
+ */
+ void ReportLoadError(const char* aMsg,
+ const char16_t** aParams = nullptr,
+ uint32_t aParamCount = 0);
+
+ /**
+ * Changes mHasPlayedOrSeeked to aValue. If mHasPlayedOrSeeked changes
+ * we'll force a reflow so that the video frame gets reflowed to reflect
+ * the poster hiding or showing immediately.
+ */
+ void SetPlayedOrSeeked(bool aValue);
+
+ /**
+ * Initialize the media element for playback of aStream
+ */
+ void SetupSrcMediaStreamPlayback(DOMMediaStream* aStream);
+ /**
+ * Stop playback on mSrcStream.
+ */
+ void EndSrcMediaStreamPlayback();
+ /**
+ * Ensure we're playing mSrcStream if and only if we're not paused.
+ */
+ enum { REMOVING_SRC_STREAM = 0x1 };
+ void UpdateSrcMediaStreamPlaying(uint32_t aFlags = 0);
+
+ /**
+ * Called by our DOMMediaStream::TrackListener when a new MediaStreamTrack has
+ * been added to the playback stream of |mSrcStream|.
+ */
+ void NotifyMediaStreamTrackAdded(const RefPtr<MediaStreamTrack>& aTrack);
+
+ /**
+ * Called by our DOMMediaStream::TrackListener when a MediaStreamTrack in
+ * |mSrcStream|'s playback stream has ended.
+ */
+ void NotifyMediaStreamTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack);
+
+ /**
+ * Enables or disables all tracks forwarded from mSrcStream to all
+ * OutputMediaStreams. We do this for muting the tracks when pausing,
+ * and unmuting when playing the media element again.
+ *
+ * If mSrcStream is unset, this does nothing.
+ */
+ void SetCapturedOutputStreamsEnabled(bool aEnabled);
+
+ /**
+ * Create a new MediaStreamTrack for aTrack and add it to the DOMMediaStream
+ * in aOutputStream. This automatically sets the output track to enabled or
+ * disabled depending on our current playing state.
+ */
+ void AddCaptureMediaTrackToOutputStream(MediaTrack* aTrack,
+ OutputMediaStream& aOutputStream,
+ bool aAsyncAddtrack = true);
+
+ /**
+ * Returns an DOMMediaStream containing the played contents of this
+ * element. When aFinishWhenEnded is true, when this element ends playback
+ * we will finish the stream and not play any more into it.
+ * When aFinishWhenEnded is false, ending playback does not finish the stream.
+ * The stream will never finish.
+ *
+ * When aCaptureAudio is true, we stop playout of audio and instead route it
+ * to the DOMMediaStream. Volume and mute state will be applied to the audio
+ * reaching the stream. No video tracks will be captured in this case.
+ */
+ already_AddRefed<DOMMediaStream> CaptureStreamInternal(bool aFinishWhenEnded,
+ bool aCaptureAudio,
+ MediaStreamGraph* aGraph);
+
+ /**
+ * Initialize a decoder as a clone of an existing decoder in another
+ * element.
+ * mLoadingSrc must already be set.
+ */
+ nsresult InitializeDecoderAsClone(MediaDecoder* aOriginal);
+
+ /**
+ * Initialize a decoder to load the given channel. The decoder's stream
+ * listener is returned via aListener.
+ * mLoadingSrc must already be set.
+ */
+ nsresult InitializeDecoderForChannel(nsIChannel *aChannel,
+ nsIStreamListener **aListener);
+
+ /**
+ * Finish setting up the decoder after Load() has been called on it.
+ * Called by InitializeDecoderForChannel/InitializeDecoderAsClone.
+ */
+ nsresult FinishDecoderSetup(MediaDecoder* aDecoder,
+ MediaResource* aStream,
+ nsIStreamListener **aListener);
+
+ /**
+ * Call this after setting up mLoadingSrc and mDecoder.
+ */
+ void AddMediaElementToURITable();
+ /**
+ * Call this before modifying mLoadingSrc.
+ */
+ void RemoveMediaElementFromURITable();
+ /**
+ * Call this to find a media element with the same NodePrincipal and mLoadingSrc
+ * set to aURI, and with a decoder on which Load() has been called.
+ */
+ HTMLMediaElement* LookupMediaElementURITable(nsIURI* aURI);
+
+ /**
+ * Shutdown and clear mDecoder and maintain associated invariants.
+ */
+ void ShutdownDecoder();
+ /**
+ * Execute the initial steps of the load algorithm that ensure existing
+ * loads are aborted, the element is emptied, and a new load ID is
+ * created.
+ */
+ void AbortExistingLoads();
+
+ /**
+ * Called when all potential resources are exhausted. Changes network
+ * state to NETWORK_NO_SOURCE, and sends error event with code
+ * MEDIA_ERR_SRC_NOT_SUPPORTED.
+ */
+ void NoSupportedMediaSourceError(const nsACString& aErrorDetails = nsCString());
+
+ /**
+ * Attempts to load resources from the <source> children. This is a
+ * substep of the resource selection algorithm. Do not call this directly,
+ * call QueueLoadFromSourceTask() instead.
+ */
+ void LoadFromSourceChildren();
+
+ /**
+ * Asynchronously awaits a stable state, and then causes
+ * LoadFromSourceChildren() to be called on the main threads' event loop.
+ */
+ void QueueLoadFromSourceTask();
+
+ /**
+ * Runs the media resource selection algorithm.
+ */
+ void SelectResource();
+
+ /**
+ * A wrapper function that allows us to cleanly reset flags after a call
+ * to SelectResource()
+ */
+ void SelectResourceWrapper();
+
+ /**
+ * Asynchronously awaits a stable state, and then causes SelectResource()
+ * to be run on the main thread's event loop.
+ */
+ void QueueSelectResourceTask();
+
+ /**
+ * When loading a new source on an existing media element, make sure to reset
+ * everything that is accessible using the media element API.
+ */
+ void ResetState();
+
+ /**
+ * The resource-fetch algorithm step of the load algorithm.
+ */
+ nsresult LoadResource();
+
+ /**
+ * Selects the next <source> child from which to load a resource. Called
+ * during the resource selection algorithm. Stores the return value in
+ * mSourceLoadCandidate before returning.
+ */
+ nsIContent* GetNextSource();
+
+ /**
+ * Changes mDelayingLoadEvent, and will call BlockOnLoad()/UnblockOnLoad()
+ * on the owning document, so it can delay the load event firing.
+ */
+ void ChangeDelayLoadStatus(bool aDelay);
+
+ /**
+ * If we suspended downloading after the first frame, unsuspend now.
+ */
+ void StopSuspendingAfterFirstFrame();
+
+ /**
+ * Called when our channel is redirected to another channel.
+ * Updates our mChannel reference to aNewChannel.
+ */
+ nsresult OnChannelRedirect(nsIChannel *aChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags);
+
+ /**
+ * Call this to reevaluate whether we should be holding a self-reference.
+ */
+ void AddRemoveSelfReference();
+
+ /**
+ * Called asynchronously to release a self-reference to this element.
+ */
+ void DoRemoveSelfReference();
+
+ /**
+ * Called when "xpcom-shutdown" event is received.
+ */
+ void NotifyShutdownEvent();
+
+ /**
+ * Possible values of the 'preload' attribute.
+ */
+ enum PreloadAttrValue : uint8_t {
+ PRELOAD_ATTR_EMPTY, // set to ""
+ PRELOAD_ATTR_NONE, // set to "none"
+ PRELOAD_ATTR_METADATA, // set to "metadata"
+ PRELOAD_ATTR_AUTO // set to "auto"
+ };
+
+ /**
+ * The preloading action to perform. These dictate how we react to the
+ * preload attribute. See mPreloadAction.
+ */
+ enum PreloadAction {
+ PRELOAD_UNDEFINED = 0, // not determined - used only for initialization
+ PRELOAD_NONE = 1, // do not preload
+ PRELOAD_METADATA = 2, // preload only the metadata (and first frame)
+ PRELOAD_ENOUGH = 3 // preload enough data to allow uninterrupted
+ // playback
+ };
+
+ /**
+ * The guts of Load(). Load() acts as a wrapper around this which sets
+ * mIsDoingExplicitLoad to true so that when script calls 'load()'
+ * preload-none will be automatically upgraded to preload-metadata.
+ */
+ void DoLoad();
+
+ /**
+ * Suspends the load of mLoadingSrc, so that it can be resumed later
+ * by ResumeLoad(). This is called when we have a media with a 'preload'
+ * attribute value of 'none', during the resource selection algorithm.
+ */
+ void SuspendLoad();
+
+ /**
+ * Resumes a previously suspended load (suspended by SuspendLoad(uri)).
+ * Will continue running the resource selection algorithm.
+ * Sets mPreloadAction to aAction.
+ */
+ void ResumeLoad(PreloadAction aAction);
+
+ /**
+ * Handle a change to the preload attribute. Should be called whenever the
+ * value (or presence) of the preload attribute changes. The change in
+ * attribute value may cause a change in the mPreloadAction of this
+ * element. If there is a change then this method will initiate any
+ * behaviour that is necessary to implement the action.
+ */
+ void UpdatePreloadAction();
+
+ /**
+ * Fire progress events if needed according to the time and byte constraints
+ * outlined in the specification. aHaveNewProgress is true if progress has
+ * just been detected. Otherwise the method is called as a result of the
+ * progress timer.
+ */
+ void CheckProgress(bool aHaveNewProgress);
+ static void ProgressTimerCallback(nsITimer* aTimer, void* aClosure);
+ /**
+ * Start timer to update download progress.
+ */
+ void StartProgressTimer();
+ /**
+ * Start sending progress and/or stalled events.
+ */
+ void StartProgress();
+ /**
+ * Stop progress information timer and events.
+ */
+ void StopProgress();
+
+ /**
+ * Dispatches an error event to a child source element.
+ */
+ void DispatchAsyncSourceError(nsIContent* aSourceElement);
+
+ /**
+ * Resets the media element for an error condition as per aErrorCode.
+ * aErrorCode must be one of nsIDOMHTMLMediaError codes.
+ */
+ void Error(uint16_t aErrorCode, const nsACString& aErrorDetails = nsCString());
+
+ /**
+ * Returns the URL spec of the currentSrc.
+ **/
+ void GetCurrentSpec(nsCString& aString);
+
+ /**
+ * Process any media fragment entries in the URI
+ */
+ void ProcessMediaFragmentURI();
+
+ /**
+ * Mute or unmute the audio and change the value that the |muted| map.
+ */
+ void SetMutedInternal(uint32_t aMuted);
+ /**
+ * Update the volume of the output audio stream to match the element's
+ * current mMuted/mVolume/mAudioChannelFaded state.
+ */
+ void SetVolumeInternal();
+
+ /**
+ * Suspend (if aPauseForInactiveDocument) or resume element playback and
+ * resource download. If aSuspendEvents is true, event delivery is
+ * suspended (and events queued) until the element is resumed.
+ */
+ void SuspendOrResumeElement(bool aPauseElement, bool aSuspendEvents);
+
+ // Get the HTMLMediaElement object if the decoder is being used from an
+ // HTML media element, and null otherwise.
+ virtual HTMLMediaElement* GetMediaElement() final override
+ {
+ return this;
+ }
+
+ // Return true if decoding should be paused
+ virtual bool GetPaused() final override
+ {
+ bool isPaused = false;
+ GetPaused(&isPaused);
+ return isPaused;
+ }
+
+ /**
+ * Video has been playing while hidden and, if feature was enabled, would
+ * trigger suspending decoder.
+ * Used to track hidden-video-decode-suspend telemetry.
+ */
+ static void VideoDecodeSuspendTimerCallback(nsITimer* aTimer, void* aClosure);
+ /**
+ * Video is now both: playing and hidden.
+ * Used to track hidden-video telemetry.
+ */
+ void HiddenVideoStart();
+ /**
+ * Video is not playing anymore and/or has become visible.
+ * Used to track hidden-video telemetry.
+ */
+ void HiddenVideoStop();
+
+ void ReportEMETelemetry();
+
+ void ReportTelemetry();
+
+ // Check the permissions for audiochannel.
+ bool CheckAudioChannelPermissions(const nsAString& aType);
+
+ // Seeks to aTime seconds. aSeekType can be Exact to seek to exactly the
+ // seek target, or PrevSyncPoint if a quicker but less precise seek is
+ // desired, and we'll seek to the sync point (keyframe and/or start of the
+ // next block of audio samples) preceeding seek target.
+ already_AddRefed<Promise> Seek(double aTime, SeekTarget::Type aSeekType, ErrorResult& aRv);
+
+ // A method to check if we are playing through the AudioChannel.
+ bool IsPlayingThroughTheAudioChannel() const;
+
+ // Update the audio channel playing state
+ void UpdateAudioChannelPlayingState(bool aForcePlaying = false);
+
+ // Adds to the element's list of pending text tracks each text track
+ // in the element's list of text tracks whose text track mode is not disabled
+ // and whose text track readiness state is loading.
+ void PopulatePendingTextTrackList();
+
+ // Gets a reference to the MediaElement's TextTrackManager. If the
+ // MediaElement doesn't yet have one then it will create it.
+ TextTrackManager* GetOrCreateTextTrackManager();
+
+ // Recomputes ready state and fires events as necessary based on current state.
+ void UpdateReadyStateInternal();
+
+ // Notifies the audio channel agent when the element starts or stops playing.
+ void NotifyAudioChannelAgent(bool aPlaying);
+
+ // True if we create the audio channel agent successfully or we already have
+ // one. The agent is used to communicate with the AudioChannelService. eg.
+ // notify we are playing/audible and receive muted/unmuted/suspend/resume
+ // commands from AudioChannelService.
+ bool MaybeCreateAudioChannelAgent();
+
+ // Determine if the element should be paused because of suspend conditions.
+ bool ShouldElementBePaused();
+
+ // Create or destroy the captured stream depend on mAudioCapturedByWindow.
+ void AudioCaptureStreamChangeIfNeeded();
+
+ /**
+ * We have different kinds of suspended cases,
+ * - SUSPENDED_PAUSE
+ * It's used when we temporary lost platform audio focus. MediaElement can
+ * only be resumed when we gain the audio focus again.
+ *
+ * - SUSPENDED_PAUSE_DISPOSABLE
+ * It's used when user press the pause botton on the remote media-control.
+ * MediaElement can be resumed by reomte media-control or via play().
+ *
+ * - SUSPENDED_BLOCK
+ * It's used to reduce the power comsuption, we won't play the auto-play
+ * audio/video in the page we have never visited before. MediaElement would
+ * be resumed when the page is active. See bug647429 for more details.
+ *
+ * - SUSPENDED_STOP_DISPOSABLE
+ * When we permanently lost platform audio focus, we shuold stop playing
+ * and stop the audio channel agent. MediaElement can only be restarted by
+ * play().
+ */
+ void PauseByAudioChannel(SuspendTypes aSuspend);
+ void BlockByAudioChannel();
+
+ void ResumeFromAudioChannel();
+ void ResumeFromAudioChannelPaused(SuspendTypes aSuspend);
+ void ResumeFromAudioChannelBlocked();
+
+ bool IsSuspendedByAudioChannel() const;
+ void SetAudioChannelSuspended(SuspendTypes aSuspend);
+
+ // A method to check whether the media element is allowed to start playback.
+ bool IsAllowedToPlay();
+ bool IsAllowedToPlayByAudioChannel();
+
+ // If the network state is empty and then we would trigger DoLoad().
+ void MaybeDoLoad();
+
+ // True if the tab which media element belongs to has been to foreground at
+ // least once or activated by manually clicking the unblocking tab icon.
+ bool IsTabActivated() const;
+
+ AudibleState IsAudible() const;
+
+ // It's used for fennec only, send the notification when the user resumes the
+ // media which was paused by media control.
+ void MaybeNotifyMediaResumed(SuspendTypes aSuspend);
+
+ class nsAsyncEventRunner;
+ using nsGenericHTMLElement::DispatchEvent;
+ // For nsAsyncEventRunner.
+ nsresult DispatchEvent(const nsAString& aName);
+
+ // Open unsupported types media with the external app when the media element
+ // triggers play() after loaded fail. eg. preload the data before start play.
+ void OpenUnsupportedMediaWithExternalAppIfNeeded() const;
+
+ // The current decoder. Load() has been called on this decoder.
+ // At most one of mDecoder and mSrcStream can be non-null.
+ RefPtr<MediaDecoder> mDecoder;
+
+ // Observers listening to changes to the mDecoder principal.
+ // Used by streams captured from this element.
+ nsTArray<DecoderPrincipalChangeObserver*> mDecoderPrincipalChangeObservers;
+
+ // State-watching manager.
+ WatchManager<HTMLMediaElement> mWatchManager;
+
+ // A reference to the VideoFrameContainer which contains the current frame
+ // of video to display.
+ RefPtr<VideoFrameContainer> mVideoFrameContainer;
+
+ // Holds a reference to the DOM wrapper for the MediaStream that has been
+ // set in the src attribute.
+ RefPtr<DOMMediaStream> mSrcAttrStream;
+
+ // Holds a reference to the DOM wrapper for the MediaStream that we're
+ // actually playing.
+ // At most one of mDecoder and mSrcStream can be non-null.
+ RefPtr<DOMMediaStream> mSrcStream;
+
+ // True once mSrcStream's initial set of tracks are known.
+ bool mSrcStreamTracksAvailable;
+
+ // If non-negative, the time we should return for currentTime while playing
+ // mSrcStream.
+ double mSrcStreamPausedCurrentTime;
+
+ // Holds a reference to the stream connecting this stream to the capture sink.
+ RefPtr<MediaInputPort> mCaptureStreamPort;
+
+ // Holds references to the DOM wrappers for the MediaStreams that we're
+ // writing to.
+ nsTArray<OutputMediaStream> mOutputStreams;
+
+ // Holds a reference to the MediaStreamListener attached to mSrcStream's
+ // playback stream.
+ RefPtr<StreamListener> mMediaStreamListener;
+ // Holds a reference to the size-getting MediaStreamListener attached to
+ // mSrcStream.
+ RefPtr<StreamSizeListener> mMediaStreamSizeListener;
+ // The selected video stream track which contained mMediaStreamSizeListener.
+ RefPtr<VideoStreamTrack> mSelectedVideoStreamTrack;
+
+ const RefPtr<ShutdownObserver> mShutdownObserver;
+
+ // Holds a reference to the MediaSource, if any, referenced by the src
+ // attribute on the media element.
+ RefPtr<MediaSource> mSrcMediaSource;
+
+ // Holds a reference to the MediaSource supplying data for playback. This
+ // may either match mSrcMediaSource or come from Source element children.
+ // This is set when and only when mLoadingSrc corresponds to an object url
+ // that resolved to a MediaSource.
+ RefPtr<MediaSource> mMediaSource;
+
+ RefPtr<ChannelLoader> mChannelLoader;
+
+ // The current media load ID. This is incremented every time we start a
+ // new load. Async events note the ID when they're first sent, and only fire
+ // if the ID is unchanged when they come to fire.
+ uint32_t mCurrentLoadID;
+
+ // Points to the child source elements, used to iterate through the children
+ // when selecting a resource to load.
+ RefPtr<nsRange> mSourcePointer;
+
+ // Points to the document whose load we're blocking. This is the document
+ // we're bound to when loading starts.
+ nsCOMPtr<nsIDocument> mLoadBlockedDoc;
+
+ // Contains names of events that have been raised while in the bfcache.
+ // These events get re-dispatched when the bfcache is exited.
+ nsTArray<nsString> mPendingEvents;
+
+ // Media loading flags. See:
+ // http://www.whatwg.org/specs/web-apps/current-work/#video)
+ nsMediaNetworkState mNetworkState;
+ Watchable<nsMediaReadyState> mReadyState;
+
+ enum LoadAlgorithmState {
+ // No load algorithm instance is waiting for a source to be added to the
+ // media in order to continue loading.
+ NOT_WAITING,
+ // We've run the load algorithm, and we tried all source children of the
+ // media element, and failed to load any successfully. We're waiting for
+ // another source element to be added to the media element, and will try
+ // to load any such element when its added.
+ WAITING_FOR_SOURCE
+ };
+
+ // Denotes the waiting state of a load algorithm instance. When the load
+ // algorithm is waiting for a source element child to be added, this is set
+ // to WAITING_FOR_SOURCE, otherwise it's NOT_WAITING.
+ LoadAlgorithmState mLoadWaitStatus;
+
+ // Current audio volume
+ double mVolume;
+
+ nsAutoPtr<const MetadataTags> mTags;
+
+ // URI of the resource we're attempting to load. This stores the value we
+ // return in the currentSrc attribute. Use GetCurrentSrc() to access the
+ // currentSrc attribute.
+ // This is always the original URL we're trying to load --- before
+ // redirects etc.
+ nsCOMPtr<nsIURI> mLoadingSrc;
+
+ // Stores the current preload action for this element. Initially set to
+ // PRELOAD_UNDEFINED, its value is changed by calling
+ // UpdatePreloadAction().
+ PreloadAction mPreloadAction;
+
+ // Time that the last timeupdate event was fired. Read/Write from the
+ // main thread only.
+ TimeStamp mTimeUpdateTime;
+
+ // Time that the last progress event was fired. Read/Write from the
+ // main thread only.
+ TimeStamp mProgressTime;
+
+ // Time that data was last read from the media resource. Used for
+ // computing if the download has stalled and to rate limit progress events
+ // when data is arriving slower than PROGRESS_MS.
+ // Read/Write from the main thread only.
+ TimeStamp mDataTime;
+
+ // Media 'currentTime' value when the last timeupdate event occurred.
+ // Read/Write from the main thread only.
+ double mLastCurrentTime;
+
+ // Logical start time of the media resource in seconds as obtained
+ // from any media fragments. A negative value indicates that no
+ // fragment time has been set. Read/Write from the main thread only.
+ double mFragmentStart;
+
+ // Logical end time of the media resource in seconds as obtained
+ // from any media fragments. A negative value indicates that no
+ // fragment time has been set. Read/Write from the main thread only.
+ double mFragmentEnd;
+
+ // The defaultPlaybackRate attribute gives the desired speed at which the
+ // media resource is to play, as a multiple of its intrinsic speed.
+ double mDefaultPlaybackRate;
+
+ // The playbackRate attribute gives the speed at which the media resource
+ // plays, as a multiple of its intrinsic speed. If it is not equal to the
+ // defaultPlaybackRate, then the implication is that the user is using a
+ // feature such as fast forward or slow motion playback.
+ double mPlaybackRate;
+
+ // True if pitch correction is applied when playbackRate is set to a
+ // non-intrinsic value.
+ bool mPreservesPitch;
+
+ // Reference to the source element last returned by GetNextSource().
+ // This is the child source element which we're trying to load from.
+ nsCOMPtr<nsIContent> mSourceLoadCandidate;
+
+ // Range of time played.
+ RefPtr<TimeRanges> mPlayed;
+
+ // Timer used for updating progress events.
+ nsCOMPtr<nsITimer> mProgressTimer;
+
+ // Timer used to simulate video-suspend.
+ nsCOMPtr<nsITimer> mVideoDecodeSuspendTimer;
+
+ // Encrypted Media Extension media keys.
+ RefPtr<MediaKeys> mMediaKeys;
+
+ // Stores the time at the start of the current 'played' range.
+ double mCurrentPlayRangeStart;
+
+ // If true then we have begun downloading the media content.
+ // Set to false when completed, or not yet started.
+ bool mBegun;
+
+ // True if loadeddata has been fired.
+ bool mLoadedDataFired;
+
+ // Indicates whether current playback is a result of user action
+ // (ie. calling of the Play method), or automatic playback due to
+ // the 'autoplay' attribute being set. A true value indicates the
+ // latter case.
+ // The 'autoplay' HTML attribute indicates that the video should
+ // start playing when loaded. The 'autoplay' attribute of the object
+ // is a mirror of the HTML attribute. These are different from this
+ // 'mAutoplaying' flag, which indicates whether the current playback
+ // is a result of the autoplay attribute.
+ bool mAutoplaying;
+
+ // Indicates whether |autoplay| will actually autoplay based on the pref
+ // media.autoplay.enabled
+ bool mAutoplayEnabled;
+
+ // Playback of the video is paused either due to calling the
+ // 'Pause' method, or playback not yet having started.
+ WakeLockBoolWrapper mPaused;
+
+ enum MutedReasons {
+ MUTED_BY_CONTENT = 0x01,
+ MUTED_BY_INVALID_PLAYBACK_RATE = 0x02,
+ MUTED_BY_AUDIO_CHANNEL = 0x04,
+ MUTED_BY_AUDIO_TRACK = 0x08
+ };
+
+ uint32_t mMuted;
+ SuspendTypes mAudioChannelSuspended;
+
+ // True if the media statistics are currently being shown by the builtin
+ // video controls
+ bool mStatsShowing;
+
+ // The following two fields are here for the private storage of the builtin
+ // video controls, and control 'casting' of the video to external devices
+ // (TVs, projectors etc.)
+ // True if casting is currently allowed
+ bool mAllowCasting;
+ // True if currently casting this video
+ bool mIsCasting;
+
+ // True if the sound is being captured.
+ bool mAudioCaptured;
+
+ // True if the sound is being captured by the window.
+ bool mAudioCapturedByWindow;
+
+ // If TRUE then the media element was actively playing before the currently
+ // in progress seeking. If FALSE then the media element is either not seeking
+ // or was not actively playing before the current seek. Used to decide whether
+ // to raise the 'waiting' event as per 4.7.1.8 in HTML 5 specification.
+ bool mPlayingBeforeSeek;
+
+ // if TRUE then the seek started while content was in active playing state
+ // if FALSE then the seek started while the content was not playing.
+ bool mPlayingThroughTheAudioChannelBeforeSeek;
+
+ // True iff this element is paused because the document is inactive or has
+ // been suspended by the audio channel service.
+ bool mPausedForInactiveDocumentOrChannel;
+
+ // True iff event delivery is suspended (mPausedForInactiveDocumentOrChannel must also be true).
+ bool mEventDeliveryPaused;
+
+ // True if we're running the "load()" method.
+ bool mIsRunningLoadMethod;
+
+ // True if we're running or waiting to run queued tasks due to an explicit
+ // call to "load()".
+ bool mIsDoingExplicitLoad;
+
+ // True if we're loading the resource from the child source elements.
+ bool mIsLoadingFromSourceChildren;
+
+ // True if we're delaying the "load" event. They are delayed until either
+ // an error occurs, or the first frame is loaded.
+ bool mDelayingLoadEvent;
+
+ // True when we've got a task queued to call SelectResource(),
+ // or while we're running SelectResource().
+ bool mIsRunningSelectResource;
+
+ // True when we already have select resource call queued
+ bool mHaveQueuedSelectResource;
+
+ // True if we suspended the decoder because we were paused,
+ // preloading metadata is enabled, autoplay was not enabled, and we loaded
+ // the first frame.
+ bool mSuspendedAfterFirstFrame;
+
+ // True if we are allowed to suspend the decoder because we were paused,
+ // preloading metdata was enabled, autoplay was not enabled, and we loaded
+ // the first frame.
+ bool mAllowSuspendAfterFirstFrame;
+
+ // True if we've played or completed a seek. We use this to determine
+ // when the poster frame should be shown.
+ bool mHasPlayedOrSeeked;
+
+ // True if we've added a reference to ourselves to keep the element
+ // alive while no-one is referencing it but the element may still fire
+ // events of its own accord.
+ bool mHasSelfReference;
+
+ // True if we've received a notification that the engine is shutting
+ // down.
+ bool mShuttingDown;
+
+ // True if we've suspended a load in the resource selection algorithm
+ // due to loading a preload:none media. When true, the resource we'll
+ // load when the user initiates either playback or an explicit load is
+ // stored in mPreloadURI.
+ bool mSuspendedForPreloadNone;
+
+ // True if we've connected mSrcStream to the media element output.
+ bool mSrcStreamIsPlaying;
+
+ // True if a same-origin check has been done for the media element and resource.
+ bool mMediaSecurityVerified;
+
+ // The CORS mode when loading the media element
+ CORSMode mCORSMode;
+
+ // Info about the played media.
+ MediaInfo mMediaInfo;
+
+ // True if the media has encryption information.
+ bool mIsEncrypted;
+
+ enum WaitingForKeyState {
+ NOT_WAITING_FOR_KEY = 0,
+ WAITING_FOR_KEY = 1,
+ WAITING_FOR_KEY_DISPATCHED = 2
+ };
+
+ // True when the CDM cannot decrypt the current block due to lacking a key.
+ // Note: the "waitingforkey" event is not dispatched until all decoded data
+ // has been rendered.
+ WaitingForKeyState mWaitingForKey;
+
+ // Listens for waitingForKey events from the owned decoder.
+ MediaEventListener mWaitingForKeyListener;
+
+ // Init Data that needs to be sent in 'encrypted' events in MetadataLoaded().
+ EncryptionInfo mPendingEncryptedInitData;
+
+ // True if the media's channel's download has been suspended.
+ Watchable<bool> mDownloadSuspendedByCache;
+
+ // Audio Channel.
+ AudioChannel mAudioChannel;
+
+ // The audio channel volume
+ float mAudioChannelVolume;
+
+ // Is this media element playing?
+ bool mPlayingThroughTheAudioChannel;
+
+ // Disable the video playback by track selection. This flag might not be
+ // enough if we ever expand the ability of supporting multi-tracks video
+ // playback.
+ bool mDisableVideo;
+
+ // An agent used to join audio channel service and its life cycle would equal
+ // to media element.
+ RefPtr<AudioChannelAgent> mAudioChannelAgent;
+
+ RefPtr<TextTrackManager> mTextTrackManager;
+
+ RefPtr<AudioTrackList> mAudioTrackList;
+
+ RefPtr<VideoTrackList> mVideoTrackList;
+
+ nsAutoPtr<MediaStreamTrackListener> mMediaStreamTrackListener;
+
+ // The principal guarding mVideoFrameContainer access when playing a
+ // MediaStream.
+ nsCOMPtr<nsIPrincipal> mSrcStreamVideoPrincipal;
+
+ // True if UnbindFromTree() is called on the element.
+ // Note this flag is false when the element is in a phase after creation and
+ // before attaching to the DOM tree.
+ bool mUnboundFromTree = false;
+
+public:
+ // Helper class to measure times for MSE telemetry stats
+ class TimeDurationAccumulator
+ {
+ public:
+ TimeDurationAccumulator()
+ : mCount(0)
+ {}
+ void Start()
+ {
+ if (IsStarted()) {
+ return;
+ }
+ mStartTime = TimeStamp::Now();
+ }
+ void Pause()
+ {
+ if (!IsStarted()) {
+ return;
+ }
+ mSum += (TimeStamp::Now() - mStartTime);
+ mCount++;
+ mStartTime = TimeStamp();
+ }
+ bool IsStarted() const
+ {
+ return !mStartTime.IsNull();
+ }
+ double Total() const
+ {
+ if (!IsStarted()) {
+ return mSum.ToSeconds();
+ }
+ // Add current running time until now, but keep it running.
+ return (mSum + (TimeStamp::Now() - mStartTime)).ToSeconds();
+ }
+ uint32_t Count() const
+ {
+ if (!IsStarted()) {
+ return mCount;
+ }
+ // Count current run in this report, without increasing the stored count.
+ return mCount + 1;
+ }
+ private:
+ TimeStamp mStartTime;
+ TimeDuration mSum;
+ uint32_t mCount;
+ };
+private:
+ // Total time a video has spent playing.
+ TimeDurationAccumulator mPlayTime;
+
+ // Total time a video has spent playing while hidden.
+ TimeDurationAccumulator mHiddenPlayTime;
+
+ // Total time a video has (or would have) spent in video-decode-suspend mode.
+ TimeDurationAccumulator mVideoDecodeSuspendTime;
+
+ // Indicates if user has interacted with the element.
+ // Used to block autoplay when disabled.
+ bool mHasUserInteraction;
+
+ // True if the first frame has been successfully loaded.
+ bool mFirstFrameLoaded;
+
+ // Media elements also have a default playback start position, which must
+ // initially be set to zero seconds. This time is used to allow the element to
+ // be seeked even before the media is loaded.
+ double mDefaultPlaybackStartPosition;
+
+ // True if the audio track is not silent.
+ bool mIsAudioTrackAudible;
+
+ // Indicate whether media element is audible for users.
+ AudibleState mAudible;
+
+ Visibility mVisibilityState;
+
+ UniquePtr<ErrorSink> mErrorSink;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLMediaElement_h
diff --git a/dom/html/HTMLMenuElement.cpp b/dom/html/HTMLMenuElement.cpp
new file mode 100644
index 000000000..098590f1b
--- /dev/null
+++ b/dom/html/HTMLMenuElement.cpp
@@ -0,0 +1,268 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLMenuElement.h"
+
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/dom/HTMLMenuElementBinding.h"
+#include "mozilla/dom/HTMLMenuItemElement.h"
+#include "nsIMenuBuilder.h"
+#include "nsAttrValueInlines.h"
+#include "nsContentUtils.h"
+#include "nsIURI.h"
+
+#define HTMLMENUBUILDER_CONTRACTID "@mozilla.org/content/html-menu-builder;1"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Menu)
+
+namespace mozilla {
+namespace dom {
+
+enum MenuType : uint8_t
+{
+ MENU_TYPE_CONTEXT = 1,
+ MENU_TYPE_TOOLBAR,
+ MENU_TYPE_LIST
+};
+
+static const nsAttrValue::EnumTable kMenuTypeTable[] = {
+ { "context", MENU_TYPE_CONTEXT },
+ { "toolbar", MENU_TYPE_TOOLBAR },
+ { "list", MENU_TYPE_LIST },
+ { nullptr, 0 }
+};
+
+static const nsAttrValue::EnumTable* kMenuDefaultType =
+ &kMenuTypeTable[2];
+
+enum SeparatorType
+{
+ ST_TRUE_INIT = -1,
+ ST_FALSE = 0,
+ ST_TRUE = 1
+};
+
+
+
+HTMLMenuElement::HTMLMenuElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo), mType(MENU_TYPE_LIST)
+{
+}
+
+HTMLMenuElement::~HTMLMenuElement()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLMenuElement, nsGenericHTMLElement,
+ nsIDOMHTMLMenuElement, nsIHTMLMenu)
+
+NS_IMPL_ELEMENT_CLONE(HTMLMenuElement)
+
+NS_IMPL_BOOL_ATTR(HTMLMenuElement, Compact, compact)
+NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLMenuElement, Type, type,
+ kMenuDefaultType->tag)
+NS_IMPL_STRING_ATTR(HTMLMenuElement, Label, label)
+
+
+NS_IMETHODIMP
+HTMLMenuElement::SendShowEvent()
+{
+ NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_DOM_SECURITY_ERR);
+
+ nsCOMPtr<nsIDocument> document = GetComposedDoc();
+ if (!document) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WidgetEvent event(true, eShow);
+ event.mFlags.mBubbles = false;
+ event.mFlags.mCancelable = false;
+
+ nsCOMPtr<nsIPresShell> shell = document->GetShell();
+ if (!shell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsPresContext> presContext = shell->GetPresContext();
+ nsEventStatus status = nsEventStatus_eIgnore;
+ EventDispatcher::Dispatch(static_cast<nsIContent*>(this), presContext,
+ &event, nullptr, &status);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLMenuElement::CreateBuilder(nsIMenuBuilder** _retval)
+{
+ NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_DOM_SECURITY_ERR);
+
+ nsCOMPtr<nsIMenuBuilder> builder = CreateBuilder();
+ builder.swap(*_retval);
+ return NS_OK;
+}
+
+already_AddRefed<nsIMenuBuilder>
+HTMLMenuElement::CreateBuilder()
+{
+ if (mType != MENU_TYPE_CONTEXT) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIMenuBuilder> builder = do_CreateInstance(HTMLMENUBUILDER_CONTRACTID);
+ NS_WARNING_ASSERTION(builder, "No builder available");
+ return builder.forget();
+}
+
+NS_IMETHODIMP
+HTMLMenuElement::Build(nsIMenuBuilder* aBuilder)
+{
+ NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_DOM_SECURITY_ERR);
+
+ if (!aBuilder) {
+ return NS_OK;
+ }
+
+ BuildSubmenu(EmptyString(), this, aBuilder);
+
+ return NS_OK;
+}
+
+
+bool
+HTMLMenuElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::type) {
+ bool success = aResult.ParseEnumValue(aValue, kMenuTypeTable,
+ false);
+ if (success) {
+ mType = aResult.GetEnumValue();
+ } else {
+ mType = kMenuDefaultType->value;
+ }
+
+ return success;
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLMenuElement::BuildSubmenu(const nsAString& aLabel,
+ nsIContent* aContent,
+ nsIMenuBuilder* aBuilder)
+{
+ aBuilder->OpenContainer(aLabel);
+
+ int8_t separator = ST_TRUE_INIT;
+ TraverseContent(aContent, aBuilder, separator);
+
+ if (separator == ST_TRUE) {
+ aBuilder->UndoAddSeparator();
+ }
+
+ aBuilder->CloseContainer();
+}
+
+// static
+bool
+HTMLMenuElement::CanLoadIcon(nsIContent* aContent, const nsAString& aIcon)
+{
+ if (aIcon.IsEmpty()) {
+ return false;
+ }
+
+ nsIDocument* doc = aContent->OwnerDoc();
+
+ nsCOMPtr<nsIURI> baseURI = aContent->GetBaseURI();
+ nsCOMPtr<nsIURI> uri;
+ nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), aIcon, doc,
+ baseURI);
+
+ if (!uri) {
+ return false;
+ }
+
+ return nsContentUtils::CanLoadImage(uri, aContent, doc,
+ aContent->NodePrincipal());
+}
+
+void
+HTMLMenuElement::TraverseContent(nsIContent* aContent,
+ nsIMenuBuilder* aBuilder,
+ int8_t& aSeparator)
+{
+ nsCOMPtr<nsIContent> child;
+ for (child = aContent->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ nsGenericHTMLElement* element = nsGenericHTMLElement::FromContent(child);
+ if (!element) {
+ continue;
+ }
+
+ if (child->IsHTMLElement(nsGkAtoms::menuitem)) {
+ HTMLMenuItemElement* menuitem = HTMLMenuItemElement::FromContent(child);
+
+ if (menuitem->IsHidden()) {
+ continue;
+ }
+
+ nsAutoString label;
+ menuitem->GetLabel(label);
+ if (label.IsEmpty()) {
+ continue;
+ }
+
+ nsAutoString icon;
+ menuitem->GetIcon(icon);
+
+ aBuilder->AddItemFor(menuitem, CanLoadIcon(child, icon));
+
+ aSeparator = ST_FALSE;
+ } else if (child->IsHTMLElement(nsGkAtoms::hr)) {
+ aBuilder->AddSeparator();
+ } else if (child->IsHTMLElement(nsGkAtoms::menu) && !element->IsHidden()) {
+ if (child->HasAttr(kNameSpaceID_None, nsGkAtoms::label)) {
+ nsAutoString label;
+ child->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label);
+
+ BuildSubmenu(label, child, aBuilder);
+
+ aSeparator = ST_FALSE;
+ } else {
+ AddSeparator(aBuilder, aSeparator);
+
+ TraverseContent(child, aBuilder, aSeparator);
+
+ AddSeparator(aBuilder, aSeparator);
+ }
+ }
+ }
+}
+
+inline void
+HTMLMenuElement::AddSeparator(nsIMenuBuilder* aBuilder, int8_t& aSeparator)
+{
+ if (aSeparator) {
+ return;
+ }
+
+ aBuilder->AddSeparator();
+ aSeparator = ST_TRUE;
+}
+
+JSObject*
+HTMLMenuElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLMenuElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLMenuElement.h b/dom/html/HTMLMenuElement.h
new file mode 100644
index 000000000..c374e7e60
--- /dev/null
+++ b/dom/html/HTMLMenuElement.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLMenuElement_h
+#define mozilla_dom_HTMLMenuElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsIDOMHTMLMenuElement.h"
+#include "nsIHTMLMenu.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLMenuElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLMenuElement,
+ public nsIHTMLMenu
+{
+public:
+ explicit HTMLMenuElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLMenuElement, menu)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLMenuElement
+ NS_DECL_NSIDOMHTMLMENUELEMENT
+
+ // nsIHTMLMenu
+ NS_DECL_NSIHTMLMENU
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ uint8_t GetType() const { return mType; }
+
+ // WebIDL
+
+ // The XPCOM GetType is OK for us
+ void SetType(const nsAString& aType, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::type, aType, aError);
+ }
+
+ // The XPCOM GetLabel is OK for us
+ void SetLabel(const nsAString& aLabel, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::label, aLabel, aError);
+ }
+
+ bool Compact() const
+ {
+ return GetBoolAttr(nsGkAtoms::compact);
+ }
+ void SetCompact(bool aCompact, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::compact, aCompact, aError);
+ }
+
+ // The XPCOM SendShowEvent is OK for us
+
+ already_AddRefed<nsIMenuBuilder> CreateBuilder();
+
+ // The XPCOM Build is OK for us
+
+protected:
+ virtual ~HTMLMenuElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+
+protected:
+ static bool CanLoadIcon(nsIContent* aContent, const nsAString& aIcon);
+
+ void BuildSubmenu(const nsAString& aLabel,
+ nsIContent* aContent,
+ nsIMenuBuilder* aBuilder);
+
+ void TraverseContent(nsIContent* aContent,
+ nsIMenuBuilder* aBuilder,
+ int8_t& aSeparator);
+
+ void AddSeparator(nsIMenuBuilder* aBuilder, int8_t& aSeparator);
+
+ uint8_t mType;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLMenuElement_h
diff --git a/dom/html/HTMLMenuItemElement.cpp b/dom/html/HTMLMenuItemElement.cpp
new file mode 100644
index 000000000..ca8bb4feb
--- /dev/null
+++ b/dom/html/HTMLMenuItemElement.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/HTMLMenuItemElement.h"
+
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/dom/HTMLMenuItemElementBinding.h"
+#include "nsAttrValueInlines.h"
+#include "nsContentUtils.h"
+
+
+NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(MenuItem)
+
+namespace mozilla {
+namespace dom {
+
+// First bits are needed for the menuitem type.
+#define NS_CHECKED_IS_TOGGLED (1 << 2)
+#define NS_ORIGINAL_CHECKED_VALUE (1 << 3)
+#define NS_MENUITEM_TYPE(bits) ((bits) & ~( \
+ NS_CHECKED_IS_TOGGLED | NS_ORIGINAL_CHECKED_VALUE))
+
+enum CmdType : uint8_t
+{
+ CMD_TYPE_MENUITEM = 1,
+ CMD_TYPE_CHECKBOX,
+ CMD_TYPE_RADIO
+};
+
+static const nsAttrValue::EnumTable kMenuItemTypeTable[] = {
+ { "menuitem", CMD_TYPE_MENUITEM },
+ { "checkbox", CMD_TYPE_CHECKBOX },
+ { "radio", CMD_TYPE_RADIO },
+ { nullptr, 0 }
+};
+
+static const nsAttrValue::EnumTable* kMenuItemDefaultType =
+ &kMenuItemTypeTable[0];
+
+// A base class inherited by all radio visitors.
+class Visitor
+{
+public:
+ Visitor() { }
+ virtual ~Visitor() { }
+
+ /**
+ * Visit a node in the tree. This is meant to be called on all radios in a
+ * group, sequentially. If the method returns false then the iteration is
+ * stopped.
+ */
+ virtual bool Visit(HTMLMenuItemElement* aMenuItem) = 0;
+};
+
+// Find the selected radio, see GetSelectedRadio().
+class GetCheckedVisitor : public Visitor
+{
+public:
+ explicit GetCheckedVisitor(HTMLMenuItemElement** aResult)
+ : mResult(aResult)
+ { }
+ virtual bool Visit(HTMLMenuItemElement* aMenuItem)
+ {
+ if (aMenuItem->IsChecked()) {
+ *mResult = aMenuItem;
+ return false;
+ }
+ return true;
+ }
+protected:
+ HTMLMenuItemElement** mResult;
+};
+
+// Deselect all radios except the one passed to the constructor.
+class ClearCheckedVisitor : public Visitor
+{
+public:
+ explicit ClearCheckedVisitor(HTMLMenuItemElement* aExcludeMenuItem)
+ : mExcludeMenuItem(aExcludeMenuItem)
+ { }
+ virtual bool Visit(HTMLMenuItemElement* aMenuItem)
+ {
+ if (aMenuItem != mExcludeMenuItem && aMenuItem->IsChecked()) {
+ aMenuItem->ClearChecked();
+ }
+ return true;
+ }
+protected:
+ HTMLMenuItemElement* mExcludeMenuItem;
+};
+
+// Get current value of the checked dirty flag. The same value is stored on all
+// radios in the group, so we need to check only the first one.
+class GetCheckedDirtyVisitor : public Visitor
+{
+public:
+ GetCheckedDirtyVisitor(bool* aCheckedDirty,
+ HTMLMenuItemElement* aExcludeMenuItem)
+ : mCheckedDirty(aCheckedDirty),
+ mExcludeMenuItem(aExcludeMenuItem)
+ { }
+ virtual bool Visit(HTMLMenuItemElement* aMenuItem)
+ {
+ if (aMenuItem == mExcludeMenuItem) {
+ return true;
+ }
+ *mCheckedDirty = aMenuItem->IsCheckedDirty();
+ return false;
+ }
+protected:
+ bool* mCheckedDirty;
+ HTMLMenuItemElement* mExcludeMenuItem;
+};
+
+// Set checked dirty to true on all radios in the group.
+class SetCheckedDirtyVisitor : public Visitor
+{
+public:
+ SetCheckedDirtyVisitor()
+ { }
+ virtual bool Visit(HTMLMenuItemElement* aMenuItem)
+ {
+ aMenuItem->SetCheckedDirty();
+ return true;
+ }
+};
+
+// A helper visitor that is used to combine two operations (visitors) to avoid
+// iterating over radios twice.
+class CombinedVisitor : public Visitor
+{
+public:
+ CombinedVisitor(Visitor* aVisitor1, Visitor* aVisitor2)
+ : mVisitor1(aVisitor1), mVisitor2(aVisitor2),
+ mContinue1(true), mContinue2(true)
+ { }
+ virtual bool Visit(HTMLMenuItemElement* aMenuItem)
+ {
+ if (mContinue1) {
+ mContinue1 = mVisitor1->Visit(aMenuItem);
+ }
+ if (mContinue2) {
+ mContinue2 = mVisitor2->Visit(aMenuItem);
+ }
+ return mContinue1 || mContinue2;
+ }
+protected:
+ Visitor* mVisitor1;
+ Visitor* mVisitor2;
+ bool mContinue1;
+ bool mContinue2;
+};
+
+
+HTMLMenuItemElement::HTMLMenuItemElement(
+ already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo, FromParser aFromParser)
+ : nsGenericHTMLElement(aNodeInfo),
+ mType(kMenuItemDefaultType->value),
+ mParserCreating(false),
+ mShouldInitChecked(false),
+ mCheckedDirty(false),
+ mChecked(false)
+{
+ mParserCreating = aFromParser;
+}
+
+HTMLMenuItemElement::~HTMLMenuItemElement()
+{
+}
+
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLMenuItemElement, nsGenericHTMLElement,
+ nsIDOMHTMLMenuItemElement)
+
+//NS_IMPL_ELEMENT_CLONE(HTMLMenuItemElement)
+nsresult
+HTMLMenuItemElement::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const
+{
+ *aResult = nullptr;
+ already_AddRefed<mozilla::dom::NodeInfo> ni = RefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget();
+ RefPtr<HTMLMenuItemElement> it =
+ new HTMLMenuItemElement(ni, NOT_FROM_PARSER);
+ nsresult rv = const_cast<HTMLMenuItemElement*>(this)->CopyInnerTo(it);
+ if (NS_SUCCEEDED(rv)) {
+ switch (mType) {
+ case CMD_TYPE_CHECKBOX:
+ case CMD_TYPE_RADIO:
+ if (mCheckedDirty) {
+ // We no longer have our original checked state. Set our
+ // checked state on the clone.
+ it->mCheckedDirty = true;
+ it->mChecked = mChecked;
+ }
+ break;
+ }
+
+ it.forget(aResult);
+ }
+
+ return rv;
+}
+
+
+NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLMenuItemElement, Type, type,
+ kMenuItemDefaultType->tag)
+// GetText returns a whitespace compressed .textContent value.
+NS_IMPL_STRING_ATTR_WITH_FALLBACK(HTMLMenuItemElement, Label, label, GetText)
+NS_IMPL_URI_ATTR(HTMLMenuItemElement, Icon, icon)
+NS_IMPL_BOOL_ATTR(HTMLMenuItemElement, Disabled, disabled)
+NS_IMPL_BOOL_ATTR(HTMLMenuItemElement, DefaultChecked, checked)
+//NS_IMPL_BOOL_ATTR(HTMLMenuItemElement, Checked, checked)
+NS_IMPL_STRING_ATTR(HTMLMenuItemElement, Radiogroup, radiogroup)
+
+NS_IMETHODIMP
+HTMLMenuItemElement::GetChecked(bool* aChecked)
+{
+ *aChecked = mChecked;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLMenuItemElement::SetChecked(bool aChecked)
+{
+ bool checkedChanged = mChecked != aChecked;
+
+ mChecked = aChecked;
+
+ if (mType == CMD_TYPE_RADIO) {
+ if (checkedChanged) {
+ if (mCheckedDirty) {
+ ClearCheckedVisitor visitor(this);
+ WalkRadioGroup(&visitor);
+ } else {
+ ClearCheckedVisitor visitor1(this);
+ SetCheckedDirtyVisitor visitor2;
+ CombinedVisitor visitor(&visitor1, &visitor2);
+ WalkRadioGroup(&visitor);
+ }
+ } else if (!mCheckedDirty) {
+ SetCheckedDirtyVisitor visitor;
+ WalkRadioGroup(&visitor);
+ }
+ } else {
+ mCheckedDirty = true;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLMenuItemElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ if (aVisitor.mEvent->mMessage == eMouseClick) {
+
+ bool originalCheckedValue = false;
+ switch (mType) {
+ case CMD_TYPE_CHECKBOX:
+ originalCheckedValue = mChecked;
+ SetChecked(!originalCheckedValue);
+ aVisitor.mItemFlags |= NS_CHECKED_IS_TOGGLED;
+ break;
+ case CMD_TYPE_RADIO:
+ nsCOMPtr<nsIDOMHTMLMenuItemElement> selectedRadio = GetSelectedRadio();
+ aVisitor.mItemData = selectedRadio;
+
+ originalCheckedValue = mChecked;
+ if (!originalCheckedValue) {
+ SetChecked(true);
+ aVisitor.mItemFlags |= NS_CHECKED_IS_TOGGLED;
+ }
+ break;
+ }
+
+ if (originalCheckedValue) {
+ aVisitor.mItemFlags |= NS_ORIGINAL_CHECKED_VALUE;
+ }
+
+ // We must cache type because mType may change during JS event.
+ aVisitor.mItemFlags |= mType;
+ }
+
+ return nsGenericHTMLElement::PreHandleEvent(aVisitor);
+}
+
+nsresult
+HTMLMenuItemElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ // Check to see if the event was cancelled.
+ if (aVisitor.mEvent->mMessage == eMouseClick &&
+ aVisitor.mItemFlags & NS_CHECKED_IS_TOGGLED &&
+ aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault) {
+ bool originalCheckedValue =
+ !!(aVisitor.mItemFlags & NS_ORIGINAL_CHECKED_VALUE);
+ uint8_t oldType = NS_MENUITEM_TYPE(aVisitor.mItemFlags);
+
+ nsCOMPtr<nsIDOMHTMLMenuItemElement> selectedRadio =
+ do_QueryInterface(aVisitor.mItemData);
+ if (selectedRadio) {
+ selectedRadio->SetChecked(true);
+ if (mType != CMD_TYPE_RADIO) {
+ SetChecked(false);
+ }
+ } else if (oldType == CMD_TYPE_CHECKBOX) {
+ SetChecked(originalCheckedValue);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLMenuItemElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+
+ if (NS_SUCCEEDED(rv) && aDocument && mType == CMD_TYPE_RADIO) {
+ AddedToRadioGroup();
+ }
+
+ return rv;
+}
+
+bool
+HTMLMenuItemElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::type) {
+ bool success = aResult.ParseEnumValue(aValue, kMenuItemTypeTable,
+ false);
+ if (success) {
+ mType = aResult.GetEnumValue();
+ } else {
+ mType = kMenuItemDefaultType->value;
+ }
+
+ return success;
+ }
+
+ if (aAttribute == nsGkAtoms::radiogroup) {
+ aResult.ParseAtom(aValue);
+ return true;
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLMenuItemElement::DoneCreatingElement()
+{
+ mParserCreating = false;
+
+ if (mShouldInitChecked) {
+ InitChecked();
+ mShouldInitChecked = false;
+ }
+}
+
+void
+HTMLMenuItemElement::GetText(nsAString& aText)
+{
+ nsAutoString text;
+ nsContentUtils::GetNodeTextContent(this, false, text);
+
+ text.CompressWhitespace(true, true);
+ aText = text;
+}
+
+nsresult
+HTMLMenuItemElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if ((aName == nsGkAtoms::radiogroup || aName == nsGkAtoms::type) &&
+ mType == CMD_TYPE_RADIO &&
+ !mParserCreating) {
+ if (IsInUncomposedDoc() && GetParent()) {
+ AddedToRadioGroup();
+ }
+ }
+
+ // Checked must be set no matter what type of menuitem it is, since
+ // GetChecked() must reflect the new value
+ if (aName == nsGkAtoms::checked &&
+ !mCheckedDirty) {
+ if (mParserCreating) {
+ mShouldInitChecked = true;
+ } else {
+ InitChecked();
+ }
+ }
+ }
+
+ return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
+ aNotify);
+}
+
+void
+HTMLMenuItemElement::WalkRadioGroup(Visitor* aVisitor)
+{
+ nsIContent* parent = GetParent();
+ if (!parent) {
+ aVisitor->Visit(this);
+ return;
+ }
+
+ BorrowedAttrInfo info1(GetAttrInfo(kNameSpaceID_None,
+ nsGkAtoms::radiogroup));
+ bool info1Empty = !info1.mValue || info1.mValue->IsEmptyString();
+
+ for (nsIContent* cur = parent->GetFirstChild();
+ cur;
+ cur = cur->GetNextSibling()) {
+ HTMLMenuItemElement* menuitem = HTMLMenuItemElement::FromContent(cur);
+
+ if (!menuitem || menuitem->GetType() != CMD_TYPE_RADIO) {
+ continue;
+ }
+
+ BorrowedAttrInfo info2(menuitem->GetAttrInfo(kNameSpaceID_None,
+ nsGkAtoms::radiogroup));
+ bool info2Empty = !info2.mValue || info2.mValue->IsEmptyString();
+
+ if (info1Empty != info2Empty ||
+ (info1.mValue && info2.mValue && !info1.mValue->Equals(*info2.mValue))) {
+ continue;
+ }
+
+ if (!aVisitor->Visit(menuitem)) {
+ break;
+ }
+ }
+}
+
+HTMLMenuItemElement*
+HTMLMenuItemElement::GetSelectedRadio()
+{
+ HTMLMenuItemElement* result = nullptr;
+
+ GetCheckedVisitor visitor(&result);
+ WalkRadioGroup(&visitor);
+
+ return result;
+}
+
+void
+HTMLMenuItemElement::AddedToRadioGroup()
+{
+ bool checkedDirty = mCheckedDirty;
+ if (mChecked) {
+ ClearCheckedVisitor visitor1(this);
+ GetCheckedDirtyVisitor visitor2(&checkedDirty, this);
+ CombinedVisitor visitor(&visitor1, &visitor2);
+ WalkRadioGroup(&visitor);
+ } else {
+ GetCheckedDirtyVisitor visitor(&checkedDirty, this);
+ WalkRadioGroup(&visitor);
+ }
+ mCheckedDirty = checkedDirty;
+}
+
+void
+HTMLMenuItemElement::InitChecked()
+{
+ bool defaultChecked;
+ GetDefaultChecked(&defaultChecked);
+ mChecked = defaultChecked;
+ if (mType == CMD_TYPE_RADIO) {
+ ClearCheckedVisitor visitor(this);
+ WalkRadioGroup(&visitor);
+ }
+}
+
+JSObject*
+HTMLMenuItemElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLMenuItemElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#undef NS_ORIGINAL_CHECKED_VALUE
diff --git a/dom/html/HTMLMenuItemElement.h b/dom/html/HTMLMenuItemElement.h
new file mode 100644
index 000000000..3c19b1170
--- /dev/null
+++ b/dom/html/HTMLMenuItemElement.h
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLMenuItemElement_h
+#define mozilla_dom_HTMLMenuItemElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsIDOMHTMLMenuItemElement.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+
+class EventChainPreVisitor;
+
+namespace dom {
+
+class Visitor;
+
+class HTMLMenuItemElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLMenuItemElement
+{
+public:
+ using mozilla::dom::Element::GetText;
+
+ HTMLMenuItemElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ mozilla::dom::FromParser aFromParser);
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLMenuItemElement, menuitem)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLMenuItemElement
+ NS_DECL_NSIDOMHTMLMENUITEMELEMENT
+
+ virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) override;
+ virtual nsresult PostHandleEvent(
+ EventChainPostVisitor& aVisitor) override;
+
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+
+ virtual void DoneCreatingElement() override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ uint8_t GetType() const { return mType; }
+
+ /**
+ * Syntax sugar to make it easier to check for checked and checked dirty
+ */
+ bool IsChecked() const { return mChecked; }
+ bool IsCheckedDirty() const { return mCheckedDirty; }
+
+ void GetText(nsAString& aText);
+
+ // WebIDL
+
+ // The XPCOM GetType is OK for us
+ void SetType(const nsAString& aType, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::type, aType, aError);
+ }
+
+ // The XPCOM GetLabel is OK for us
+ void SetLabel(const nsAString& aLabel, ErrorResult& aError)
+ {
+ SetAttrHelper(nsGkAtoms::label, aLabel);
+ }
+
+ // The XPCOM GetIcon is OK for us
+ void SetIcon(const nsAString& aIcon, ErrorResult& aError)
+ {
+ SetAttrHelper(nsGkAtoms::icon, aIcon);
+ }
+
+ bool Disabled() const
+ {
+ return GetBoolAttr(nsGkAtoms::disabled);
+ }
+ void SetDisabled(bool aDisabled, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::disabled, aDisabled, aError);
+ }
+
+ bool Checked() const
+ {
+ return mChecked;
+ }
+ void SetChecked(bool aChecked, ErrorResult& aError)
+ {
+ aError = SetChecked(aChecked);
+ }
+
+ // The XPCOM GetRadiogroup is OK for us
+ void SetRadiogroup(const nsAString& aRadiogroup, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::radiogroup, aRadiogroup, aError);
+ }
+
+ bool DefaultChecked() const
+ {
+ return GetBoolAttr(nsGkAtoms::checked);
+ }
+ void SetDefaultChecked(bool aDefault, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::checked, aDefault, aError);
+ }
+
+protected:
+ virtual ~HTMLMenuItemElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+
+protected:
+ virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+
+ void WalkRadioGroup(Visitor* aVisitor);
+
+ HTMLMenuItemElement* GetSelectedRadio();
+
+ void AddedToRadioGroup();
+
+ void InitChecked();
+
+ friend class ClearCheckedVisitor;
+ friend class SetCheckedDirtyVisitor;
+
+ void ClearChecked() { mChecked = false; }
+ void SetCheckedDirty() { mCheckedDirty = true; }
+
+private:
+ uint8_t mType : 2;
+ bool mParserCreating : 1;
+ bool mShouldInitChecked : 1;
+ bool mCheckedDirty : 1;
+ bool mChecked : 1;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLMenuItemElement_h
diff --git a/dom/html/HTMLMetaElement.cpp b/dom/html/HTMLMetaElement.cpp
new file mode 100644
index 000000000..47effa2bc
--- /dev/null
+++ b/dom/html/HTMLMetaElement.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 "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/dom/HTMLMetaElement.h"
+#include "mozilla/dom/HTMLMetaElementBinding.h"
+#include "mozilla/dom/nsCSPService.h"
+#include "nsContentUtils.h"
+#include "nsStyleConsts.h"
+#include "nsIContentSecurityPolicy.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Meta)
+
+namespace mozilla {
+namespace dom {
+
+HTMLMetaElement::HTMLMetaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+{
+}
+
+HTMLMetaElement::~HTMLMetaElement()
+{
+}
+
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLMetaElement, nsGenericHTMLElement,
+ nsIDOMHTMLMetaElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLMetaElement)
+
+
+NS_IMPL_STRING_ATTR(HTMLMetaElement, Content, content)
+NS_IMPL_STRING_ATTR(HTMLMetaElement, HttpEquiv, httpEquiv)
+NS_IMPL_STRING_ATTR(HTMLMetaElement, Name, name)
+NS_IMPL_STRING_ATTR(HTMLMetaElement, Scheme, scheme)
+
+nsresult
+HTMLMetaElement::SetMetaReferrer(nsIDocument* aDocument)
+{
+ if (!aDocument ||
+ !AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, nsGkAtoms::referrer, eIgnoreCase)) {
+ return NS_OK;
+ }
+ nsAutoString content;
+ nsresult rv = GetContent(content);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ Element* headElt = aDocument->GetHeadElement();
+ if (headElt && nsContentUtils::ContentIsDescendantOf(this, headElt)) {
+ content = nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(content);
+ aDocument->SetHeaderData(nsGkAtoms::referrer, content);
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLMetaElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None) {
+ nsIDocument *document = GetUncomposedDoc();
+ if (aName == nsGkAtoms::content) {
+ if (document && AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+ nsGkAtoms::viewport, eIgnoreCase)) {
+ nsAutoString content;
+ nsresult rv = GetContent(content);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsContentUtils::ProcessViewportInfo(document, content);
+ }
+ CreateAndDispatchEvent(document, NS_LITERAL_STRING("DOMMetaChanged"));
+ }
+ // Update referrer policy when it got changed from JS
+ nsresult rv = SetMetaReferrer(document);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
+ aNotify);
+}
+
+nsresult
+HTMLMetaElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aDocument &&
+ AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, nsGkAtoms::viewport, eIgnoreCase)) {
+ nsAutoString content;
+ rv = GetContent(content);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsContentUtils::ProcessViewportInfo(aDocument, content);
+ }
+
+ if (CSPService::sCSPEnabled && aDocument &&
+ AttrValueIs(kNameSpaceID_None, nsGkAtoms::httpEquiv, nsGkAtoms::headerCSP, eIgnoreCase)) {
+
+ // only accept <meta http-equiv="Content-Security-Policy" content=""> if it appears
+ // in the <head> element.
+ Element* headElt = aDocument->GetHeadElement();
+ if (headElt && nsContentUtils::ContentIsDescendantOf(this, headElt)) {
+
+ nsAutoString content;
+ rv = GetContent(content);
+ NS_ENSURE_SUCCESS(rv, rv);
+ content = nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(content);
+
+ nsIPrincipal* principal = aDocument->NodePrincipal();
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aDocument);
+ principal->EnsureCSP(domDoc, getter_AddRefs(csp));
+ if (csp) {
+ // Multiple CSPs (delivered through either header of meta tag) need to be
+ // joined together, see:
+ // https://w3c.github.io/webappsec/specs/content-security-policy/#delivery-html-meta-element
+ rv = csp->AppendPolicy(content,
+ false, // csp via meta tag can not be report only
+ true); // delivered through the meta tag
+ NS_ENSURE_SUCCESS(rv, rv);
+ aDocument->ApplySettingsFromCSP(false);
+ }
+ }
+ }
+
+ // Referrer Policy spec requires a <meta name="referrer" tag to be in the
+ // <head> element.
+ rv = SetMetaReferrer(aDocument);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ CreateAndDispatchEvent(aDocument, NS_LITERAL_STRING("DOMMetaAdded"));
+ return rv;
+}
+
+void
+HTMLMetaElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ nsCOMPtr<nsIDocument> oldDoc = GetUncomposedDoc();
+ CreateAndDispatchEvent(oldDoc, NS_LITERAL_STRING("DOMMetaRemoved"));
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+void
+HTMLMetaElement::CreateAndDispatchEvent(nsIDocument* aDoc,
+ const nsAString& aEventName)
+{
+ if (!aDoc)
+ return;
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, aEventName, true, true);
+ asyncDispatcher->RunDOMEventWhenSafe();
+}
+
+JSObject*
+HTMLMetaElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLMetaElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLMetaElement.h b/dom/html/HTMLMetaElement.h
new file mode 100644
index 000000000..d3f05826d
--- /dev/null
+++ b/dom/html/HTMLMetaElement.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 mozilla_dom_HTMLMetaElement_h
+#define mozilla_dom_HTMLMetaElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIDOMHTMLMetaElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLMetaElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLMetaElement
+{
+public:
+ explicit HTMLMetaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLMetaElement
+ NS_DECL_NSIDOMHTMLMETAELEMENT
+
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+
+ void CreateAndDispatchEvent(nsIDocument* aDoc, const nsAString& aEventName);
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ // XPCOM GetName is fine.
+ void SetName(const nsAString& aName, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::name, aName, aRv);
+ }
+ // XPCOM GetHttpEquiv is fine.
+ void SetHttpEquiv(const nsAString& aHttpEquiv, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::httpEquiv, aHttpEquiv, aRv);
+ }
+ // XPCOM GetContent is fine.
+ void SetContent(const nsAString& aContent, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::content, aContent, aRv);
+ }
+ // XPCOM GetScheme is fine.
+ void SetScheme(const nsAString& aScheme, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::scheme, aScheme, aRv);
+ }
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+ virtual ~HTMLMetaElement();
+
+private:
+ nsresult SetMetaReferrer(nsIDocument* aDocument);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLMetaElement_h
diff --git a/dom/html/HTMLMeterElement.cpp b/dom/html/HTMLMeterElement.cpp
new file mode 100644
index 000000000..dd0e21470
--- /dev/null
+++ b/dom/html/HTMLMeterElement.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 "HTMLMeterElement.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/HTMLMeterElementBinding.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Meter)
+
+namespace mozilla {
+namespace dom {
+
+const double HTMLMeterElement::kDefaultValue = 0.0;
+const double HTMLMeterElement::kDefaultMin = 0.0;
+const double HTMLMeterElement::kDefaultMax = 1.0;
+
+
+HTMLMeterElement::HTMLMeterElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+{
+}
+
+HTMLMeterElement::~HTMLMeterElement()
+{
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLMeterElement)
+
+EventStates
+HTMLMeterElement::IntrinsicState() const
+{
+ EventStates state = nsGenericHTMLElement::IntrinsicState();
+
+ state |= GetOptimumState();
+
+ return state;
+}
+
+bool
+HTMLMeterElement::ParseAttribute(int32_t aNamespaceID, nsIAtom* aAttribute,
+ const nsAString& aValue, nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::value || aAttribute == nsGkAtoms::max ||
+ aAttribute == nsGkAtoms::min || aAttribute == nsGkAtoms::low ||
+ aAttribute == nsGkAtoms::high || aAttribute == nsGkAtoms::optimum) {
+ return aResult.ParseDoubleValue(aValue);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute,
+ aValue, aResult);
+}
+
+/*
+ * Value getters :
+ * const getters used by XPCOM methods and by IntrinsicState
+ */
+
+double
+HTMLMeterElement::Min() const
+{
+ /**
+ * If the attribute min is defined, the minimum is this value.
+ * Otherwise, the minimum is the default value.
+ */
+ const nsAttrValue* attrMin = mAttrsAndChildren.GetAttr(nsGkAtoms::min);
+ if (attrMin && attrMin->Type() == nsAttrValue::eDoubleValue) {
+ return attrMin->GetDoubleValue();
+ }
+ return kDefaultMin;
+}
+
+double
+HTMLMeterElement::Max() const
+{
+ /**
+ * If the attribute max is defined, the maximum is this value.
+ * Otherwise, the maximum is the default value.
+ * If the maximum value is less than the minimum value,
+ * the maximum value is the same as the minimum value.
+ */
+ double max;
+
+ const nsAttrValue* attrMax = mAttrsAndChildren.GetAttr(nsGkAtoms::max);
+ if (attrMax && attrMax->Type() == nsAttrValue::eDoubleValue) {
+ max = attrMax->GetDoubleValue();
+ } else {
+ max = kDefaultMax;
+ }
+
+ return std::max(max, Min());
+}
+
+double
+HTMLMeterElement::Value() const
+{
+ /**
+ * If the attribute value is defined, the actual value is this value.
+ * Otherwise, the actual value is the default value.
+ * If the actual value is less than the minimum value,
+ * the actual value is the same as the minimum value.
+ * If the actual value is greater than the maximum value,
+ * the actual value is the same as the maximum value.
+ */
+ double value;
+
+ const nsAttrValue* attrValue = mAttrsAndChildren.GetAttr(nsGkAtoms::value);
+ if (attrValue && attrValue->Type() == nsAttrValue::eDoubleValue) {
+ value = attrValue->GetDoubleValue();
+ } else {
+ value = kDefaultValue;
+ }
+
+ double min = Min();
+
+ if (value <= min) {
+ return min;
+ }
+
+ return std::min(value, Max());
+}
+
+double
+HTMLMeterElement::Low() const
+{
+ /**
+ * If the low value is defined, the low value is this value.
+ * Otherwise, the low value is the minimum value.
+ * If the low value is less than the minimum value,
+ * the low value is the same as the minimum value.
+ * If the low value is greater than the maximum value,
+ * the low value is the same as the maximum value.
+ */
+
+ double min = Min();
+
+ const nsAttrValue* attrLow = mAttrsAndChildren.GetAttr(nsGkAtoms::low);
+ if (!attrLow || attrLow->Type() != nsAttrValue::eDoubleValue) {
+ return min;
+ }
+
+ double low = attrLow->GetDoubleValue();
+
+ if (low <= min) {
+ return min;
+ }
+
+ return std::min(low, Max());
+}
+
+double
+HTMLMeterElement::High() const
+{
+ /**
+ * If the high value is defined, the high value is this value.
+ * Otherwise, the high value is the maximum value.
+ * If the high value is less than the low value,
+ * the high value is the same as the low value.
+ * If the high value is greater than the maximum value,
+ * the high value is the same as the maximum value.
+ */
+
+ double max = Max();
+
+ const nsAttrValue* attrHigh = mAttrsAndChildren.GetAttr(nsGkAtoms::high);
+ if (!attrHigh || attrHigh->Type() != nsAttrValue::eDoubleValue) {
+ return max;
+ }
+
+ double high = attrHigh->GetDoubleValue();
+
+ if (high >= max) {
+ return max;
+ }
+
+ return std::max(high, Low());
+}
+
+double
+HTMLMeterElement::Optimum() const
+{
+ /**
+ * If the optimum value is defined, the optimum value is this value.
+ * Otherwise, the optimum value is the midpoint between
+ * the minimum value and the maximum value :
+ * min + (max - min)/2 = (min + max)/2
+ * If the optimum value is less than the minimum value,
+ * the optimum value is the same as the minimum value.
+ * If the optimum value is greater than the maximum value,
+ * the optimum value is the same as the maximum value.
+ */
+
+ double max = Max();
+
+ double min = Min();
+
+ const nsAttrValue* attrOptimum =
+ mAttrsAndChildren.GetAttr(nsGkAtoms::optimum);
+ if (!attrOptimum || attrOptimum->Type() != nsAttrValue::eDoubleValue) {
+ return (min + max) / 2.0;
+ }
+
+ double optimum = attrOptimum->GetDoubleValue();
+
+ if (optimum <= min) {
+ return min;
+ }
+
+ return std::min(optimum, max);
+}
+
+EventStates
+HTMLMeterElement::GetOptimumState() const
+{
+ /*
+ * If the optimum value is in [minimum, low[,
+ * return if the value is in optimal, suboptimal or sub-suboptimal region
+ *
+ * If the optimum value is in [low, high],
+ * return if the value is in optimal or suboptimal region
+ *
+ * If the optimum value is in ]high, maximum],
+ * return if the value is in optimal, suboptimal or sub-suboptimal region
+ */
+ double value = Value();
+ double low = Low();
+ double high = High();
+ double optimum = Optimum();
+
+ if (optimum < low) {
+ if (value < low) {
+ return NS_EVENT_STATE_OPTIMUM;
+ }
+ if (value <= high) {
+ return NS_EVENT_STATE_SUB_OPTIMUM;
+ }
+ return NS_EVENT_STATE_SUB_SUB_OPTIMUM;
+ }
+ if (optimum > high) {
+ if (value > high) {
+ return NS_EVENT_STATE_OPTIMUM;
+ }
+ if (value >= low) {
+ return NS_EVENT_STATE_SUB_OPTIMUM;
+ }
+ return NS_EVENT_STATE_SUB_SUB_OPTIMUM;
+ }
+ // optimum in [low, high]
+ if (value >= low && value <= high) {
+ return NS_EVENT_STATE_OPTIMUM;
+ }
+ return NS_EVENT_STATE_SUB_OPTIMUM;
+}
+
+JSObject*
+HTMLMeterElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLMeterElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLMeterElement.h b/dom/html/HTMLMeterElement.h
new file mode 100644
index 000000000..2e870bfa3
--- /dev/null
+++ b/dom/html/HTMLMeterElement.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_HTMLMeterElement_h
+#define mozilla_dom_HTMLMeterElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "nsAlgorithm.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace dom {
+
+class HTMLMeterElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLMeterElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ virtual EventStates IntrinsicState() const override;
+
+ nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+
+ bool ParseAttribute(int32_t aNamespaceID, nsIAtom* aAttribute,
+ const nsAString& aValue, nsAttrValue& aResult) override;
+
+ // WebIDL
+
+ /* @return the value */
+ double Value() const;
+ void SetValue(double aValue, ErrorResult& aRv)
+ {
+ SetDoubleAttr(nsGkAtoms::value, aValue, aRv);
+ }
+
+ /* @return the minimum value */
+ double Min() const;
+ void SetMin(double aValue, ErrorResult& aRv)
+ {
+ SetDoubleAttr(nsGkAtoms::min, aValue, aRv);
+ }
+
+ /* @return the maximum value */
+ double Max() const;
+ void SetMax(double aValue, ErrorResult& aRv)
+ {
+ SetDoubleAttr(nsGkAtoms::max, aValue, aRv);
+ }
+
+ /* @return the low value */
+ double Low() const;
+ void SetLow(double aValue, ErrorResult& aRv)
+ {
+ SetDoubleAttr(nsGkAtoms::low, aValue, aRv);
+ }
+
+ /* @return the high value */
+ double High() const;
+ void SetHigh(double aValue, ErrorResult& aRv)
+ {
+ SetDoubleAttr(nsGkAtoms::high, aValue, aRv);
+ }
+
+ /* @return the optimum value */
+ double Optimum() const;
+ void SetOptimum(double aValue, ErrorResult& aRv)
+ {
+ SetDoubleAttr(nsGkAtoms::optimum, aValue, aRv);
+ }
+
+protected:
+ virtual ~HTMLMeterElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+
+ static const double kDefaultValue;
+ static const double kDefaultMin;
+ static const double kDefaultMax;
+
+ /**
+ * Returns the optimum state of the element.
+ * NS_EVENT_STATE_OPTIMUM if the actual value is in the optimum region.
+ * NS_EVENT_STATE_SUB_OPTIMUM if the actual value is in the sub-optimal region.
+ * NS_EVENT_STATE_SUB_SUB_OPTIMUM if the actual value is in the sub-sub-optimal region.
+ *
+ * @return the optimum state of the element.
+ */
+ EventStates GetOptimumState() const;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLMeterElement_h
diff --git a/dom/html/HTMLModElement.cpp b/dom/html/HTMLModElement.cpp
new file mode 100644
index 000000000..074fa5d5b
--- /dev/null
+++ b/dom/html/HTMLModElement.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/HTMLModElement.h"
+#include "mozilla/dom/HTMLModElementBinding.h"
+#include "nsStyleConsts.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Mod)
+
+namespace mozilla {
+namespace dom {
+
+HTMLModElement::HTMLModElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+{
+}
+
+HTMLModElement::~HTMLModElement()
+{
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLModElement)
+
+JSObject*
+HTMLModElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLModElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLModElement.h b/dom/html/HTMLModElement.h
new file mode 100644
index 000000000..e3afe6ebc
--- /dev/null
+++ b/dom/html/HTMLModElement.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLModElement_h
+#define mozilla_dom_HTMLModElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLModElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLModElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ void GetCite(nsString& aCite)
+ {
+ GetHTMLURIAttr(nsGkAtoms::cite, aCite);
+ }
+ void SetCite(const nsAString& aCite, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::cite, aCite, aRv);
+ }
+ void GetDateTime(DOMString& aDateTime)
+ {
+ GetHTMLAttr(nsGkAtoms::datetime, aDateTime);
+ }
+ void SetDateTime(const nsAString& aDateTime, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::datetime, aDateTime, aRv);
+ }
+
+protected:
+ virtual ~HTMLModElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLModElement_h
diff --git a/dom/html/HTMLObjectElement.cpp b/dom/html/HTMLObjectElement.cpp
new file mode 100644
index 000000000..65f407889
--- /dev/null
+++ b/dom/html/HTMLObjectElement.cpp
@@ -0,0 +1,601 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/EventStates.h"
+#include "mozilla/dom/HTMLFormSubmission.h"
+#include "mozilla/dom/HTMLObjectElement.h"
+#include "mozilla/dom/HTMLObjectElementBinding.h"
+#include "mozilla/dom/ElementInlines.h"
+#include "nsAttrValueInlines.h"
+#include "nsGkAtoms.h"
+#include "nsError.h"
+#include "nsIDocument.h"
+#include "nsIPluginDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIObjectFrame.h"
+#include "nsNPAPIPluginInstance.h"
+#include "nsIWidget.h"
+#include "nsContentUtils.h"
+#ifdef XP_MACOSX
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/dom/Event.h"
+#include "nsFocusManager.h"
+#endif
+
+namespace mozilla {
+namespace dom {
+
+HTMLObjectElement::HTMLObjectElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser)
+ : nsGenericHTMLFormElement(aNodeInfo),
+ mIsDoneAddingChildren(!aFromParser)
+{
+ RegisterActivityObserver();
+ SetIsNetworkCreated(aFromParser == FROM_PARSER_NETWORK);
+
+ // <object> is always barred from constraint validation.
+ SetBarredFromConstraintValidation(true);
+
+ // By default we're in the loading state
+ AddStatesSilently(NS_EVENT_STATE_LOADING);
+}
+
+HTMLObjectElement::~HTMLObjectElement()
+{
+#ifdef XP_MACOSX
+ OnFocusBlurPlugin(this, false);
+#endif
+ UnregisterActivityObserver();
+ DestroyImageLoadingContent();
+}
+
+bool
+HTMLObjectElement::IsInteractiveHTMLContent(bool aIgnoreTabindex) const
+{
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::usemap) ||
+ nsGenericHTMLFormElement::IsInteractiveHTMLContent(aIgnoreTabindex);
+}
+
+void
+HTMLObjectElement::AsyncEventRunning(AsyncEventDispatcher* aEvent)
+{
+ nsImageLoadingContent::AsyncEventRunning(aEvent);
+}
+
+bool
+HTMLObjectElement::IsDoneAddingChildren()
+{
+ return mIsDoneAddingChildren;
+}
+
+void
+HTMLObjectElement::DoneAddingChildren(bool aHaveNotified)
+{
+ mIsDoneAddingChildren = true;
+
+ // If we're already in a document, we need to trigger the load
+ // Otherwise, BindToTree takes care of that.
+ if (IsInComposedDoc()) {
+ StartObjectLoad(aHaveNotified);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLObjectElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLObjectElement,
+ nsGenericHTMLFormElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
+ nsObjectLoadingContent::Traverse(tmp, cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLObjectElement,
+ nsGenericHTMLFormElement)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(HTMLObjectElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLObjectElement, Element)
+
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLObjectElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLObjectElement,
+ nsIDOMHTMLObjectElement,
+ imgINotificationObserver,
+ nsIRequestObserver,
+ nsIStreamListener,
+ nsIFrameLoaderOwner,
+ nsIObjectLoadingContent,
+ nsIImageLoadingContent,
+ imgIOnloadBlocker,
+ nsIChannelEventSink,
+ nsIConstraintValidation)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLObjectElement)
+
+// nsIConstraintValidation
+NS_IMPL_NSICONSTRAINTVALIDATION(HTMLObjectElement)
+
+#ifdef XP_MACOSX
+
+static nsIWidget* GetWidget(Element* aElement)
+{
+ return nsContentUtils::WidgetForDocument(aElement->OwnerDoc());
+}
+
+Element* HTMLObjectElement::sLastFocused = nullptr; // Weak
+
+class PluginFocusSetter : public Runnable
+{
+public:
+ PluginFocusSetter(nsIWidget* aWidget, Element* aElement)
+ : mWidget(aWidget), mElement(aElement)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ if (mElement) {
+ HTMLObjectElement::sLastFocused = mElement;
+ bool value = true;
+ mWidget->SetPluginFocused(value);
+ } else if (!HTMLObjectElement::sLastFocused) {
+ bool value = false;
+ mWidget->SetPluginFocused(value);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIWidget> mWidget;
+ nsCOMPtr<Element> mElement;
+};
+
+void
+HTMLObjectElement::OnFocusBlurPlugin(Element* aElement, bool aFocus)
+{
+ // In general we don't want to call nsIWidget::SetPluginFocused() for any
+ // Element that doesn't have a plugin running. But if SetPluginFocused(true)
+ // was just called for aElement while it had a plugin running, we want to
+ // make sure nsIWidget::SetPluginFocused(false) gets called for it now, even
+ // if aFocus is true.
+ if (aFocus) {
+ nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(aElement);
+ bool hasRunningPlugin = false;
+ if (olc) {
+ // nsIObjectLoadingContent::GetHasRunningPlugin() fails when
+ // nsContentUtils::IsCallerChrome() returns false (which it can do even
+ // when we're processing a trusted focus event). We work around this by
+ // calling nsObjectLoadingContent::HasRunningPlugin() directly.
+ hasRunningPlugin =
+ static_cast<nsObjectLoadingContent*>(olc.get())->HasRunningPlugin();
+ }
+ if (!hasRunningPlugin) {
+ aFocus = false;
+ }
+ }
+
+ if (aFocus || aElement == sLastFocused) {
+ if (!aFocus) {
+ sLastFocused = nullptr;
+ }
+ nsIWidget* widget = GetWidget(aElement);
+ if (widget) {
+ nsContentUtils::AddScriptRunner(
+ new PluginFocusSetter(widget, aFocus ? aElement : nullptr));
+ }
+ }
+}
+
+void
+HTMLObjectElement::HandlePluginCrashed(Element* aElement)
+{
+ OnFocusBlurPlugin(aElement, false);
+}
+
+void
+HTMLObjectElement::HandlePluginInstantiated(Element* aElement)
+{
+ // If aElement is already focused when a plugin is instantiated, we need
+ // to initiate a call to nsIWidget::SetPluginFocused(true). Otherwise
+ // keyboard input won't work in a click-to-play plugin until aElement
+ // loses focus and regains it.
+ nsIContent* focusedContent = nullptr;
+ nsFocusManager *fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ focusedContent = fm->GetFocusedContent();
+ }
+ if (SameCOMIdentity(focusedContent, aElement)) {
+ OnFocusBlurPlugin(aElement, true);
+ }
+}
+
+void
+HTMLObjectElement::HandleFocusBlurPlugin(Element* aElement,
+ WidgetEvent* aEvent)
+{
+ if (!aEvent->IsTrusted()) {
+ return;
+ }
+ switch (aEvent->mMessage) {
+ case eFocus: {
+ OnFocusBlurPlugin(aElement, true);
+ break;
+ }
+ case eBlur: {
+ OnFocusBlurPlugin(aElement, false);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+NS_IMETHODIMP
+HTMLObjectElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ HandleFocusBlurPlugin(this, aVisitor.mEvent);
+ return NS_OK;
+}
+
+#endif // #ifdef XP_MACOSX
+
+NS_IMETHODIMP
+HTMLObjectElement::GetForm(nsIDOMHTMLFormElement **aForm)
+{
+ return nsGenericHTMLFormElement::GetForm(aForm);
+}
+
+nsresult
+HTMLObjectElement::BindToTree(nsIDocument *aDocument,
+ nsIContent *aParent,
+ nsIContent *aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLFormElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsObjectLoadingContent::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't kick off load from being bound to a plugin document - the plugin
+ // document will call nsObjectLoadingContent::InitializeFromChannel() for the
+ // initial load.
+ nsCOMPtr<nsIPluginDocument> pluginDoc = do_QueryInterface(aDocument);
+
+ // If we already have all the children, start the load.
+ if (mIsDoneAddingChildren && !pluginDoc) {
+ void (HTMLObjectElement::*start)() = &HTMLObjectElement::StartObjectLoad;
+ nsContentUtils::AddScriptRunner(NewRunnableMethod(this, start));
+ }
+
+ return NS_OK;
+}
+
+void
+HTMLObjectElement::UnbindFromTree(bool aDeep,
+ bool aNullParent)
+{
+#ifdef XP_MACOSX
+ // When a page is reloaded (when an nsIDocument's content is removed), the
+ // focused element isn't necessarily sent an eBlur event. See
+ // nsFocusManager::ContentRemoved(). This means that a widget may think it
+ // still contains a focused plugin when it doesn't -- which in turn can
+ // disable text input in the browser window. See bug 1137229.
+ OnFocusBlurPlugin(this, false);
+#endif
+ nsObjectLoadingContent::UnbindFromTree(aDeep, aNullParent);
+ nsGenericHTMLFormElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+
+
+nsresult
+HTMLObjectElement::SetAttr(int32_t aNameSpaceID, nsIAtom *aName,
+ nsIAtom *aPrefix, const nsAString &aValue,
+ bool aNotify)
+{
+ nsresult rv = nsGenericHTMLFormElement::SetAttr(aNameSpaceID, aName, aPrefix,
+ aValue, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if aNotify is false, we are coming from the parser or some such place;
+ // we'll get bound after all the attributes have been set, so we'll do the
+ // object load from BindToTree/DoneAddingChildren.
+ // Skip the LoadObject call in that case.
+ // We also don't want to start loading the object when we're not yet in
+ // a document, just in case that the caller wants to set additional
+ // attributes before inserting the node into the document.
+ if (aNotify && IsInComposedDoc() && mIsDoneAddingChildren &&
+ aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::data) {
+ return LoadObject(aNotify, true);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLObjectElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify)
+{
+ nsresult rv = nsGenericHTMLFormElement::UnsetAttr(aNameSpaceID,
+ aAttribute, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // See comment in SetAttr
+ if (aNotify && IsInComposedDoc() && mIsDoneAddingChildren &&
+ aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::data) {
+ return LoadObject(aNotify, true);
+ }
+
+ return NS_OK;
+}
+
+bool
+HTMLObjectElement::IsFocusableForTabIndex()
+{
+ nsIDocument* doc = GetComposedDoc();
+ if (!doc || doc->HasFlag(NODE_IS_EDITABLE)) {
+ return false;
+ }
+
+ return IsEditableRoot() ||
+ (Type() == eType_Document &&
+ nsContentUtils::IsSubDocumentTabbable(this));
+}
+
+bool
+HTMLObjectElement::IsHTMLFocusable(bool aWithMouse,
+ bool *aIsFocusable, int32_t *aTabIndex)
+{
+ // TODO: this should probably be managed directly by IsHTMLFocusable.
+ // See bug 597242.
+ nsIDocument *doc = GetComposedDoc();
+ if (!doc || doc->HasFlag(NODE_IS_EDITABLE)) {
+ if (aTabIndex) {
+ GetTabIndex(aTabIndex);
+ }
+
+ *aIsFocusable = false;
+
+ return false;
+ }
+
+ // This method doesn't call nsGenericHTMLFormElement intentionally.
+ // TODO: It should probably be changed when bug 597242 will be fixed.
+ if (Type() == eType_Plugin || IsEditableRoot() ||
+ (Type() == eType_Document && nsContentUtils::IsSubDocumentTabbable(this))) {
+ // Has plugin content: let the plugin decide what to do in terms of
+ // internal focus from mouse clicks
+ if (aTabIndex) {
+ GetTabIndex(aTabIndex);
+ }
+
+ *aIsFocusable = true;
+
+ return false;
+ }
+
+ // TODO: this should probably be managed directly by IsHTMLFocusable.
+ // See bug 597242.
+ const nsAttrValue* attrVal = mAttrsAndChildren.GetAttr(nsGkAtoms::tabindex);
+
+ *aIsFocusable = attrVal && attrVal->Type() == nsAttrValue::eInteger;
+
+ if (aTabIndex && *aIsFocusable) {
+ *aTabIndex = attrVal->GetIntegerValue();
+ }
+
+ return false;
+}
+
+nsIContent::IMEState
+HTMLObjectElement::GetDesiredIMEState()
+{
+ if (Type() == eType_Plugin) {
+ return IMEState(IMEState::PLUGIN);
+ }
+
+ return nsGenericHTMLFormElement::GetDesiredIMEState();
+}
+
+NS_IMETHODIMP
+HTMLObjectElement::Reset()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLObjectElement::SubmitNamesValues(HTMLFormSubmission *aFormSubmission)
+{
+ nsAutoString name;
+ if (!GetAttr(kNameSpaceID_None, nsGkAtoms::name, name)) {
+ // No name, don't submit.
+
+ return NS_OK;
+ }
+
+ nsIFrame* frame = GetPrimaryFrame();
+
+ nsIObjectFrame *objFrame = do_QueryFrame(frame);
+ if (!objFrame) {
+ // No frame, nothing to submit.
+
+ return NS_OK;
+ }
+
+ RefPtr<nsNPAPIPluginInstance> pi;
+ objFrame->GetPluginInstance(getter_AddRefs(pi));
+ if (!pi)
+ return NS_OK;
+
+ nsAutoString value;
+ nsresult rv = pi->GetFormValue(value);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aFormSubmission->AddNameValuePair(name, value);
+}
+
+NS_IMPL_STRING_ATTR(HTMLObjectElement, Align, align)
+NS_IMPL_STRING_ATTR(HTMLObjectElement, Archive, archive)
+NS_IMPL_STRING_ATTR(HTMLObjectElement, Border, border)
+NS_IMPL_STRING_ATTR(HTMLObjectElement, Code, code)
+NS_IMPL_URI_ATTR(HTMLObjectElement, CodeBase, codebase)
+NS_IMPL_STRING_ATTR(HTMLObjectElement, CodeType, codetype)
+NS_IMPL_URI_ATTR_WITH_BASE(HTMLObjectElement, Data, data, codebase)
+NS_IMPL_BOOL_ATTR(HTMLObjectElement, Declare, declare)
+NS_IMPL_STRING_ATTR(HTMLObjectElement, Height, height)
+NS_IMPL_INT_ATTR(HTMLObjectElement, Hspace, hspace)
+NS_IMPL_STRING_ATTR(HTMLObjectElement, Name, name)
+NS_IMPL_STRING_ATTR(HTMLObjectElement, Standby, standby)
+NS_IMPL_STRING_ATTR(HTMLObjectElement, Type, type)
+NS_IMPL_STRING_ATTR(HTMLObjectElement, UseMap, usemap)
+NS_IMPL_INT_ATTR(HTMLObjectElement, Vspace, vspace)
+NS_IMPL_STRING_ATTR(HTMLObjectElement, Width, width)
+
+int32_t
+HTMLObjectElement::TabIndexDefault()
+{
+ return IsFocusableForTabIndex() ? 0 : -1;
+}
+
+NS_IMETHODIMP
+HTMLObjectElement::GetContentDocument(nsIDOMDocument **aContentDocument)
+{
+ NS_ENSURE_ARG_POINTER(aContentDocument);
+
+ nsCOMPtr<nsIDOMDocument> domDoc =
+ do_QueryInterface(GetContentDocument(*nsContentUtils::SubjectPrincipal()));
+ domDoc.forget(aContentDocument);
+ return NS_OK;
+}
+
+nsPIDOMWindowOuter*
+HTMLObjectElement::GetContentWindow(nsIPrincipal& aSubjectPrincipal)
+{
+ nsIDocument* doc = GetContentDocument(aSubjectPrincipal);
+ if (doc) {
+ return doc->GetWindow();
+ }
+
+ return nullptr;
+}
+
+bool
+HTMLObjectElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom *aAttribute,
+ const nsAString &aValue,
+ nsAttrValue &aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::align) {
+ return ParseAlignValue(aValue, aResult);
+ }
+ if (ParseImageAttribute(aAttribute, aValue, aResult)) {
+ return true;
+ }
+ }
+
+ return nsGenericHTMLFormElement::ParseAttribute(aNamespaceID, aAttribute,
+ aValue, aResult);
+}
+
+void
+HTMLObjectElement::MapAttributesIntoRule(const nsMappedAttributes *aAttributes,
+ nsRuleData *aData)
+{
+ nsGenericHTMLFormElement::MapImageAlignAttributeInto(aAttributes, aData);
+ nsGenericHTMLFormElement::MapImageBorderAttributeInto(aAttributes, aData);
+ nsGenericHTMLFormElement::MapImageMarginAttributeInto(aAttributes, aData);
+ nsGenericHTMLFormElement::MapImageSizeAttributesInto(aAttributes, aData);
+ nsGenericHTMLFormElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLObjectElement::IsAttributeMapped(const nsIAtom *aAttribute) const
+{
+ static const MappedAttributeEntry* const map[] = {
+ sCommonAttributeMap,
+ sImageMarginSizeAttributeMap,
+ sImageBorderAttributeMap,
+ sImageAlignAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+
+nsMapRuleToAttributesFunc
+HTMLObjectElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+void
+HTMLObjectElement::StartObjectLoad(bool aNotify)
+{
+ // BindToTree can call us asynchronously, and we may be removed from the tree
+ // in the interim
+ if (!IsInComposedDoc() || !OwnerDoc()->IsActive()) {
+ return;
+ }
+
+ LoadObject(aNotify);
+ SetIsNetworkCreated(false);
+}
+
+EventStates
+HTMLObjectElement::IntrinsicState() const
+{
+ return nsGenericHTMLFormElement::IntrinsicState() | ObjectState();
+}
+
+uint32_t
+HTMLObjectElement::GetCapabilities() const
+{
+ return nsObjectLoadingContent::GetCapabilities() | eSupportClassID;
+}
+
+void
+HTMLObjectElement::DestroyContent()
+{
+ nsObjectLoadingContent::DestroyContent();
+ nsGenericHTMLFormElement::DestroyContent();
+}
+
+nsresult
+HTMLObjectElement::CopyInnerTo(Element* aDest)
+{
+ nsresult rv = nsGenericHTMLFormElement::CopyInnerTo(aDest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aDest->OwnerDoc()->IsStaticDocument()) {
+ CreateStaticClone(static_cast<HTMLObjectElement*>(aDest));
+ }
+
+ return rv;
+}
+
+JSObject*
+HTMLObjectElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ JS::Rooted<JSObject*> obj(aCx,
+ HTMLObjectElementBinding::Wrap(aCx, this, aGivenProto));
+ if (!obj) {
+ return nullptr;
+ }
+ SetupProtoChain(aCx, obj);
+ return obj;
+}
+
+} // namespace dom
+} // namespace mozilla
+
+NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Object)
diff --git a/dom/html/HTMLObjectElement.h b/dom/html/HTMLObjectElement.h
new file mode 100644
index 000000000..4041b78a3
--- /dev/null
+++ b/dom/html/HTMLObjectElement.h
@@ -0,0 +1,280 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_HTMLObjectElement_h
+#define mozilla_dom_HTMLObjectElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsObjectLoadingContent.h"
+#include "nsIDOMHTMLObjectElement.h"
+#include "nsIConstraintValidation.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLFormSubmission;
+
+class HTMLObjectElement final : public nsGenericHTMLFormElement
+ , public nsObjectLoadingContent
+ , public nsIDOMHTMLObjectElement
+ , public nsIConstraintValidation
+{
+public:
+ explicit HTMLObjectElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser = NOT_FROM_PARSER);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLObjectElement, object)
+ virtual int32_t TabIndexDefault() override;
+
+#ifdef XP_MACOSX
+ // nsIDOMEventTarget
+ NS_IMETHOD PostHandleEvent(EventChainPostVisitor& aVisitor) override;
+ // Helper methods
+ static void OnFocusBlurPlugin(Element* aElement, bool aFocus);
+ static void HandleFocusBlurPlugin(Element* aElement, WidgetEvent* aEvent);
+ static void HandlePluginCrashed(Element* aElement);
+ static void HandlePluginInstantiated(Element* aElement);
+ // Weak pointer. Null if last action was blur.
+ static Element* sLastFocused;
+#endif
+
+ // Element
+ virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
+
+ // EventTarget
+ virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;
+
+ // nsIDOMHTMLObjectElement
+ NS_DECL_NSIDOMHTMLOBJECTELEMENT
+
+ virtual nsresult BindToTree(nsIDocument *aDocument, nsIContent *aParent,
+ nsIContent *aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+ virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom *aName,
+ nsIAtom *aPrefix, const nsAString &aValue,
+ bool aNotify) override;
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify) override;
+
+ virtual bool IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable, int32_t *aTabIndex) override;
+ virtual IMEState GetDesiredIMEState() override;
+
+ // Overriden nsIFormControl methods
+ NS_IMETHOD_(uint32_t) GetType() const override
+ {
+ return NS_FORM_OBJECT;
+ }
+
+ NS_IMETHOD Reset() override;
+ NS_IMETHOD SubmitNamesValues(HTMLFormSubmission *aFormSubmission) override;
+
+ virtual bool IsDisabled() const override { return false; }
+
+ virtual void DoneAddingChildren(bool aHaveNotified) override;
+ virtual bool IsDoneAddingChildren() override;
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom *aAttribute,
+ const nsAString &aValue,
+ nsAttrValue &aResult) override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom *aAttribute) const override;
+ virtual EventStates IntrinsicState() const override;
+ virtual void DestroyContent() override;
+
+ // nsObjectLoadingContent
+ virtual uint32_t GetCapabilities() const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ nsresult CopyInnerTo(Element* aDest);
+
+ void StartObjectLoad() { StartObjectLoad(true); }
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLObjectElement,
+ nsGenericHTMLFormElement)
+
+ // Web IDL binding methods
+ // XPCOM GetData is ok; note that it's a URI attribute with a weird base URI
+ void SetData(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::data, aValue, aRv);
+ }
+ void GetType(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::type, aValue);
+ }
+ void SetType(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::type, aValue, aRv);
+ }
+ bool TypeMustMatch()
+ {
+ return GetBoolAttr(nsGkAtoms::typemustmatch);
+ }
+ void SetTypeMustMatch(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::typemustmatch, aValue, aRv);
+ }
+ void GetName(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::name, aValue);
+ }
+ void SetName(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::name, aValue, aRv);
+ }
+ void GetUseMap(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::usemap, aValue);
+ }
+ void SetUseMap(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::usemap, aValue, aRv);
+ }
+ using nsGenericHTMLFormElement::GetForm;
+ void GetWidth(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::width, aValue);
+ }
+ void SetWidth(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::width, aValue, aRv);
+ }
+ void GetHeight(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::height, aValue);
+ }
+ void SetHeight(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::height, aValue, aRv);
+ }
+ using nsObjectLoadingContent::GetContentDocument;
+
+ nsPIDOMWindowOuter*
+ GetContentWindow(nsIPrincipal& aSubjectPrincipal);
+
+ using nsIConstraintValidation::CheckValidity;
+ using nsIConstraintValidation::ReportValidity;
+ using nsIConstraintValidation::GetValidationMessage;
+ void GetAlign(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::align, aValue);
+ }
+ void SetAlign(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::align, aValue, aRv);
+ }
+ void GetArchive(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::archive, aValue);
+ }
+ void SetArchive(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::archive, aValue, aRv);
+ }
+ // XPCOM GetCode is ok
+ void SetCode(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::code, aValue, aRv);
+ }
+ bool Declare()
+ {
+ return GetBoolAttr(nsGkAtoms::declare);
+ }
+ void SetDeclare(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::declare, aValue, aRv);
+ }
+ uint32_t Hspace()
+ {
+ return GetUnsignedIntAttr(nsGkAtoms::hspace, 0);
+ }
+ void SetHspace(uint32_t aValue, ErrorResult& aRv)
+ {
+ SetUnsignedIntAttr(nsGkAtoms::hspace, aValue, 0, aRv);
+ }
+ void GetStandby(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::standby, aValue);
+ }
+ void SetStandby(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::standby, aValue, aRv);
+ }
+ uint32_t Vspace()
+ {
+ return GetUnsignedIntAttr(nsGkAtoms::vspace, 0);
+ }
+ void SetVspace(uint32_t aValue, ErrorResult& aRv)
+ {
+ SetUnsignedIntAttr(nsGkAtoms::vspace, aValue, 0, aRv);
+ }
+ // XPCOM GetCodebase is ok; note that it's a URI attribute
+ void SetCodeBase(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::codebase, aValue, aRv);
+ }
+ void GetCodeType(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::codetype, aValue);
+ }
+ void SetCodeType(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::codetype, aValue, aRv);
+ }
+ void GetBorder(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::border, aValue);
+ }
+ void SetBorder(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::border, aValue, aRv);
+ }
+
+ nsIDocument*
+ GetSVGDocument(nsIPrincipal& aSubjectPrincipal)
+ {
+ return GetContentDocument(aSubjectPrincipal);
+ }
+
+private:
+ /**
+ * Calls LoadObject with the correct arguments to start the plugin load.
+ */
+ void StartObjectLoad(bool aNotify);
+
+ /**
+ * Returns if the element is currently focusable regardless of it's tabindex
+ * value. This is used to know the default tabindex value.
+ */
+ bool IsFocusableForTabIndex();
+
+ nsContentPolicyType GetContentPolicyType() const override
+ {
+ return nsIContentPolicy::TYPE_INTERNAL_OBJECT;
+ }
+
+ virtual ~HTMLObjectElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+
+ bool mIsDoneAddingChildren;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLObjectElement_h
diff --git a/dom/html/HTMLOptGroupElement.cpp b/dom/html/HTMLOptGroupElement.cpp
new file mode 100644
index 000000000..8a044fbf3
--- /dev/null
+++ b/dom/html/HTMLOptGroupElement.cpp
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/HTMLOptGroupElement.h"
+#include "mozilla/dom/HTMLOptGroupElementBinding.h"
+#include "mozilla/dom/HTMLSelectElement.h" // SafeOptionListMutation
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsIFrame.h"
+#include "nsIFormControlFrame.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(OptGroup)
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * The implementation of &lt;optgroup&gt;
+ */
+
+
+
+HTMLOptGroupElement::HTMLOptGroupElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+{
+ // We start off enabled
+ AddStatesSilently(NS_EVENT_STATE_ENABLED);
+}
+
+HTMLOptGroupElement::~HTMLOptGroupElement()
+{
+}
+
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLOptGroupElement, nsGenericHTMLElement,
+ nsIDOMHTMLOptGroupElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLOptGroupElement)
+
+
+NS_IMPL_BOOL_ATTR(HTMLOptGroupElement, Disabled, disabled)
+NS_IMPL_STRING_ATTR(HTMLOptGroupElement, Label, label)
+
+
+nsresult
+HTMLOptGroupElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ aVisitor.mCanHandle = false;
+ // Do not process any DOM events if the element is disabled
+ // XXXsmaug This is not the right thing to do. But what is?
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) {
+ return NS_OK;
+ }
+
+ nsIFrame* frame = GetPrimaryFrame();
+ if (frame) {
+ const nsStyleUserInterface* uiStyle = frame->StyleUserInterface();
+ if (uiStyle->mUserInput == StyleUserInput::None ||
+ uiStyle->mUserInput == StyleUserInput::Disabled) {
+ return NS_OK;
+ }
+ }
+
+ return nsGenericHTMLElement::PreHandleEvent(aVisitor);
+}
+
+Element*
+HTMLOptGroupElement::GetSelect()
+{
+ Element* parent = nsINode::GetParentElement();
+ if (!parent || !parent->IsHTMLElement(nsGkAtoms::select)) {
+ return nullptr;
+ }
+ return parent;
+}
+
+nsresult
+HTMLOptGroupElement::InsertChildAt(nsIContent* aKid,
+ uint32_t aIndex,
+ bool aNotify)
+{
+ SafeOptionListMutation safeMutation(GetSelect(), this, aKid, aIndex, aNotify);
+ nsresult rv = nsGenericHTMLElement::InsertChildAt(aKid, aIndex, aNotify);
+ if (NS_FAILED(rv)) {
+ safeMutation.MutationFailed();
+ }
+ return rv;
+}
+
+void
+HTMLOptGroupElement::RemoveChildAt(uint32_t aIndex, bool aNotify)
+{
+ SafeOptionListMutation safeMutation(GetSelect(), this, nullptr, aIndex,
+ aNotify);
+ nsGenericHTMLElement::RemoveChildAt(aIndex, aNotify);
+}
+
+nsresult
+HTMLOptGroupElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::disabled) {
+ // All our children <option> have their :disabled state depending on our
+ // disabled attribute. We should make sure their state is updated.
+ for (nsIContent* child = nsINode::GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsHTMLElement(nsGkAtoms::option)) {
+ // No need to call |IsElement()| because it's an HTML element.
+ child->AsElement()->UpdateState(true);
+ }
+ }
+ }
+
+ return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
+ aNotify);
+}
+
+EventStates
+HTMLOptGroupElement::IntrinsicState() const
+{
+ EventStates state = nsGenericHTMLElement::IntrinsicState();
+
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) {
+ state |= NS_EVENT_STATE_DISABLED;
+ state &= ~NS_EVENT_STATE_ENABLED;
+ } else {
+ state &= ~NS_EVENT_STATE_DISABLED;
+ state |= NS_EVENT_STATE_ENABLED;
+ }
+
+ return state;
+}
+
+JSObject*
+HTMLOptGroupElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLOptGroupElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLOptGroupElement.h b/dom/html/HTMLOptGroupElement.h
new file mode 100644
index 000000000..d53a2e32b
--- /dev/null
+++ b/dom/html/HTMLOptGroupElement.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_HTMLOptGroupElement_h
+#define mozilla_dom_HTMLOptGroupElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsIDOMHTMLOptGroupElement.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+class EventChainPreVisitor;
+namespace dom {
+
+class HTMLOptGroupElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLOptGroupElement
+{
+public:
+ explicit HTMLOptGroupElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLOptGroupElement, optgroup)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLOptGroupElement
+ NS_DECL_NSIDOMHTMLOPTGROUPELEMENT
+
+ // nsINode
+ virtual nsresult InsertChildAt(nsIContent* aKid, uint32_t aIndex,
+ bool aNotify) override;
+ virtual void RemoveChildAt(uint32_t aIndex, bool aNotify) override;
+
+ // nsIContent
+ virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) override;
+
+ virtual EventStates IntrinsicState() const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+
+ virtual nsIDOMNode* AsDOMNode() override { return this; }
+
+ virtual bool IsDisabled() const override {
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::disabled);
+ }
+
+ bool Disabled() const
+ {
+ return GetBoolAttr(nsGkAtoms::disabled);
+ }
+ void SetDisabled(bool aValue, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::disabled, aValue, aError);
+ }
+
+ // The XPCOM GetLabel is OK for us
+ void SetLabel(const nsAString& aLabel, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::label, aLabel, aError);
+ }
+
+protected:
+ virtual ~HTMLOptGroupElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+
+ /**
+ * Get the select content element that contains this option
+ * @param aSelectElement the select element [OUT]
+ */
+ Element* GetSelect();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_HTMLOptGroupElement_h */
diff --git a/dom/html/HTMLOptionElement.cpp b/dom/html/HTMLOptionElement.cpp
new file mode 100644
index 000000000..7b1e3ca2d
--- /dev/null
+++ b/dom/html/HTMLOptionElement.cpp
@@ -0,0 +1,458 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLOptionElement.h"
+#include "mozilla/dom/HTMLOptionElementBinding.h"
+#include "mozilla/dom/HTMLSelectElement.h"
+#include "nsIDOMHTMLOptGroupElement.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsIFormControl.h"
+#include "nsIForm.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMHTMLCollection.h"
+#include "nsISelectControlFrame.h"
+
+// Notify/query select frame for selected state
+#include "nsIFormControlFrame.h"
+#include "nsIDocument.h"
+#include "nsIDOMHTMLSelectElement.h"
+#include "nsNodeInfoManager.h"
+#include "nsCOMPtr.h"
+#include "mozilla/EventStates.h"
+#include "nsContentCreatorFunctions.h"
+#include "mozAutoDocUpdate.h"
+#include "nsTextNode.h"
+
+/**
+ * Implementation of &lt;option&gt;
+ */
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Option)
+
+namespace mozilla {
+namespace dom {
+
+HTMLOptionElement::HTMLOptionElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo),
+ mSelectedChanged(false),
+ mIsSelected(false),
+ mIsInSetDefaultSelected(false)
+{
+ // We start off enabled
+ AddStatesSilently(NS_EVENT_STATE_ENABLED);
+}
+
+HTMLOptionElement::~HTMLOptionElement()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLOptionElement, nsGenericHTMLElement,
+ nsIDOMHTMLOptionElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLOptionElement)
+
+
+NS_IMETHODIMP
+HTMLOptionElement::GetForm(nsIDOMHTMLFormElement** aForm)
+{
+ NS_IF_ADDREF(*aForm = GetForm());
+ return NS_OK;
+}
+
+mozilla::dom::HTMLFormElement*
+HTMLOptionElement::GetForm()
+{
+ HTMLSelectElement* selectControl = GetSelect();
+ return selectControl ? selectControl->GetForm() : nullptr;
+}
+
+void
+HTMLOptionElement::SetSelectedInternal(bool aValue, bool aNotify)
+{
+ mSelectedChanged = true;
+ mIsSelected = aValue;
+
+ // When mIsInSetDefaultSelected is true, the state change will be handled by
+ // SetAttr/UnsetAttr.
+ if (!mIsInSetDefaultSelected) {
+ UpdateState(aNotify);
+ }
+}
+
+NS_IMETHODIMP
+HTMLOptionElement::GetSelected(bool* aValue)
+{
+ NS_ENSURE_ARG_POINTER(aValue);
+ *aValue = Selected();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLOptionElement::SetSelected(bool aValue)
+{
+ // Note: The select content obj maintains all the PresState
+ // so defer to it to get the answer
+ HTMLSelectElement* selectInt = GetSelect();
+ if (selectInt) {
+ int32_t index = Index();
+ uint32_t mask = HTMLSelectElement::SET_DISABLED | HTMLSelectElement::NOTIFY;
+ if (aValue) {
+ mask |= HTMLSelectElement::IS_SELECTED;
+ }
+
+ // This should end up calling SetSelectedInternal
+ selectInt->SetOptionsSelectedByIndex(index, index, mask);
+ } else {
+ SetSelectedInternal(aValue, true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_BOOL_ATTR(HTMLOptionElement, DefaultSelected, selected)
+// GetText returns a whitespace compressed .textContent value.
+NS_IMPL_STRING_ATTR_WITH_FALLBACK(HTMLOptionElement, Label, label, GetText)
+NS_IMPL_STRING_ATTR_WITH_FALLBACK(HTMLOptionElement, Value, value, GetText)
+NS_IMPL_BOOL_ATTR(HTMLOptionElement, Disabled, disabled)
+
+NS_IMETHODIMP
+HTMLOptionElement::GetIndex(int32_t* aIndex)
+{
+ *aIndex = Index();
+ return NS_OK;
+}
+
+int32_t
+HTMLOptionElement::Index()
+{
+ static int32_t defaultIndex = 0;
+
+ // Only select elements can contain a list of options.
+ HTMLSelectElement* selectElement = GetSelect();
+ if (!selectElement) {
+ return defaultIndex;
+ }
+
+ HTMLOptionsCollection* options = selectElement->GetOptions();
+ if (!options) {
+ return defaultIndex;
+ }
+
+ int32_t index = defaultIndex;
+ MOZ_ALWAYS_SUCCEEDS(options->GetOptionIndex(this, 0, true, &index));
+ return index;
+}
+
+bool
+HTMLOptionElement::Selected() const
+{
+ // If we haven't been explictly selected or deselected, use our default value
+ if (!mSelectedChanged) {
+ return DefaultSelected();
+ }
+
+ return mIsSelected;
+}
+
+bool
+HTMLOptionElement::DefaultSelected() const
+{
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::selected);
+}
+
+nsChangeHint
+HTMLOptionElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const
+{
+ nsChangeHint retval =
+ nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
+
+ if (aAttribute == nsGkAtoms::label ||
+ aAttribute == nsGkAtoms::text) {
+ retval |= NS_STYLE_HINT_REFLOW;
+ }
+ return retval;
+}
+
+nsresult
+HTMLOptionElement::BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify)
+{
+ nsresult rv = nsGenericHTMLElement::BeforeSetAttr(aNamespaceID, aName,
+ aValue, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aNamespaceID != kNameSpaceID_None || aName != nsGkAtoms::selected ||
+ mSelectedChanged) {
+ return NS_OK;
+ }
+
+ bool defaultSelected = aValue;
+ // First make sure we actually set our mIsSelected state to reflect our new
+ // defaultSelected state. If that turns out to be wrong,
+ // SetOptionsSelectedByIndex will fix it up. But otherwise we can end up in a
+ // situation where mIsSelected is still false, but mSelectedChanged becomes
+ // true (later in this method, when we compare mIsSelected to
+ // defaultSelected), and then we start returning false for Selected() even
+ // though we're actually selected.
+ mIsSelected = defaultSelected;
+
+ // We just changed out selected state (since we look at the "selected"
+ // attribute when mSelectedChanged is false). Let's tell our select about
+ // it.
+ HTMLSelectElement* selectInt = GetSelect();
+ if (!selectInt) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!mSelectedChanged, "Shouldn't be here");
+
+ bool inSetDefaultSelected = mIsInSetDefaultSelected;
+ mIsInSetDefaultSelected = true;
+
+ int32_t index = Index();
+ uint32_t mask = HTMLSelectElement::SET_DISABLED;
+ if (defaultSelected) {
+ mask |= HTMLSelectElement::IS_SELECTED;
+ }
+
+ if (aNotify) {
+ mask |= HTMLSelectElement::NOTIFY;
+ }
+
+ // This can end up calling SetSelectedInternal if our selected state needs to
+ // change, which we will allow to take effect so that parts of
+ // SetOptionsSelectedByIndex that might depend on it working don't get
+ // confused.
+ selectInt->SetOptionsSelectedByIndex(index, index, mask);
+
+ // Now reset our members; when we finish the attr set we'll end up with the
+ // rigt selected state.
+ mIsInSetDefaultSelected = inSetDefaultSelected;
+ // mIsSelected might have been changed by SetOptionsSelectedByIndex. Possibly
+ // more than once; make sure our mSelectedChanged state is set correctly.
+ mSelectedChanged = mIsSelected != defaultSelected;
+
+ return NS_OK;
+}
+
+nsresult
+HTMLOptionElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None &&
+ aName == nsGkAtoms::value && Selected()) {
+ // Since this option is selected, changing value
+ // may have changed missing validity state of the
+ // Select element
+ HTMLSelectElement* select = GetSelect();
+ if (select) {
+ select->UpdateValueMissingValidityState();
+ }
+ }
+
+ return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+NS_IMETHODIMP
+HTMLOptionElement::GetText(nsAString& aText)
+{
+ nsAutoString text;
+
+ nsIContent* child = nsINode::GetFirstChild();
+ while (child) {
+ if (child->NodeType() == nsIDOMNode::TEXT_NODE ||
+ child->NodeType() == nsIDOMNode::CDATA_SECTION_NODE) {
+ child->AppendTextTo(text);
+ }
+ if (child->IsHTMLElement(nsGkAtoms::script) ||
+ child->IsSVGElement(nsGkAtoms::script)) {
+ child = child->GetNextNonChildNode(this);
+ } else {
+ child = child->GetNextNode(this);
+ }
+ }
+
+ // XXX No CompressWhitespace for nsAString. Sad.
+ text.CompressWhitespace(true, true);
+ aText = text;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLOptionElement::SetText(const nsAString& aText)
+{
+ return nsContentUtils::SetNodeTextContent(this, aText, true);
+}
+
+nsresult
+HTMLOptionElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Our new parent might change :disabled/:enabled state.
+ UpdateState(false);
+
+ return NS_OK;
+}
+
+void
+HTMLOptionElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+
+ // Our previous parent could have been involved in :disabled/:enabled state.
+ UpdateState(false);
+}
+
+EventStates
+HTMLOptionElement::IntrinsicState() const
+{
+ EventStates state = nsGenericHTMLElement::IntrinsicState();
+ if (Selected()) {
+ state |= NS_EVENT_STATE_CHECKED;
+ }
+ if (DefaultSelected()) {
+ state |= NS_EVENT_STATE_DEFAULT;
+ }
+
+ // An <option> is disabled if it has @disabled set or if it's <optgroup> has
+ // @disabled set.
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) {
+ state |= NS_EVENT_STATE_DISABLED;
+ state &= ~NS_EVENT_STATE_ENABLED;
+ } else {
+ nsIContent* parent = GetParent();
+ if (parent && parent->IsHTMLElement(nsGkAtoms::optgroup) &&
+ parent->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) {
+ state |= NS_EVENT_STATE_DISABLED;
+ state &= ~NS_EVENT_STATE_ENABLED;
+ } else {
+ state &= ~NS_EVENT_STATE_DISABLED;
+ state |= NS_EVENT_STATE_ENABLED;
+ }
+ }
+
+ return state;
+}
+
+// Get the select content element that contains this option
+HTMLSelectElement*
+HTMLOptionElement::GetSelect()
+{
+ nsIContent* parent = GetParent();
+ if (!parent) {
+ return nullptr;
+ }
+
+ HTMLSelectElement* select = HTMLSelectElement::FromContent(parent);
+ if (select) {
+ return select;
+ }
+
+ if (!parent->IsHTMLElement(nsGkAtoms::optgroup)) {
+ return nullptr;
+ }
+
+ return HTMLSelectElement::FromContentOrNull(parent->GetParent());
+}
+
+already_AddRefed<HTMLOptionElement>
+HTMLOptionElement::Option(const GlobalObject& aGlobal,
+ const Optional<nsAString>& aText,
+ const Optional<nsAString>& aValue,
+ const Optional<bool>& aDefaultSelected,
+ const Optional<bool>& aSelected, ErrorResult& aError)
+{
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal.GetAsSupports());
+ nsIDocument* doc;
+ if (!win || !(doc = win->GetExtantDoc())) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ already_AddRefed<mozilla::dom::NodeInfo> nodeInfo =
+ doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::option, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+
+ RefPtr<HTMLOptionElement> option = new HTMLOptionElement(nodeInfo);
+
+ if (aText.WasPassed()) {
+ // Create a new text node and append it to the option
+ RefPtr<nsTextNode> textContent =
+ new nsTextNode(option->NodeInfo()->NodeInfoManager());
+
+ textContent->SetText(aText.Value(), false);
+
+ aError = option->AppendChildTo(textContent, false);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ if (aValue.WasPassed()) {
+ // Set the value attribute for this element. We're calling SetAttr
+ // directly because we want to pass aNotify == false.
+ aError = option->SetAttr(kNameSpaceID_None, nsGkAtoms::value,
+ aValue.Value(), false);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ if (aDefaultSelected.WasPassed()) {
+ if (aDefaultSelected.Value()) {
+ // We're calling SetAttr directly because we want to pass
+ // aNotify == false.
+ aError = option->SetAttr(kNameSpaceID_None, nsGkAtoms::selected,
+ EmptyString(), false);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ }
+
+ if (aSelected.WasPassed()) {
+ option->SetSelected(aSelected.Value(), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ }
+ }
+ }
+ }
+
+ return option.forget();
+}
+
+nsresult
+HTMLOptionElement::CopyInnerTo(Element* aDest)
+{
+ nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aDest->OwnerDoc()->IsStaticDocument()) {
+ static_cast<HTMLOptionElement*>(aDest)->SetSelected(Selected());
+ }
+ return NS_OK;
+}
+
+JSObject*
+HTMLOptionElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLOptionElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLOptionElement.h b/dom/html/HTMLOptionElement.h
new file mode 100644
index 000000000..e220b84df
--- /dev/null
+++ b/dom/html/HTMLOptionElement.h
@@ -0,0 +1,141 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_HTMLOptionElement_h__
+#define mozilla_dom_HTMLOptionElement_h__
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIDOMHTMLOptionElement.h"
+#include "mozilla/dom/HTMLFormElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLSelectElement;
+
+class HTMLOptionElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLOptionElement
+{
+public:
+ explicit HTMLOptionElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ static already_AddRefed<HTMLOptionElement>
+ Option(const GlobalObject& aGlobal,
+ const Optional<nsAString>& aText,
+ const Optional<nsAString>& aValue,
+ const Optional<bool>& aDefaultSelected,
+ const Optional<bool>& aSelected, ErrorResult& aError);
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLOptionElement, option)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLOptionElement
+ using mozilla::dom::Element::SetText;
+ using mozilla::dom::Element::GetText;
+ NS_DECL_NSIDOMHTMLOPTIONELEMENT
+
+ bool Selected() const;
+ bool DefaultSelected() const;
+
+ virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const override;
+
+ virtual nsresult BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify) override;
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+
+ void SetSelectedInternal(bool aValue, bool aNotify);
+
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+
+ // nsIContent
+ virtual EventStates IntrinsicState() const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+
+ nsresult CopyInnerTo(mozilla::dom::Element* aDest);
+
+ virtual bool IsDisabled() const override {
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::disabled);
+ }
+
+ bool Disabled() const
+ {
+ return GetBoolAttr(nsGkAtoms::disabled);
+ }
+
+ void SetDisabled(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::disabled, aValue, aRv);
+ }
+
+ HTMLFormElement* GetForm();
+
+ // The XPCOM GetLabel is OK for us
+ void SetLabel(const nsAString& aLabel, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::label, aLabel, aError);
+ }
+
+ // The XPCOM DefaultSelected is OK for us
+ void SetDefaultSelected(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::selected, aValue, aRv);
+ }
+
+ // The XPCOM Selected is OK for us
+ void SetSelected(bool aValue, ErrorResult& aRv)
+ {
+ aRv = SetSelected(aValue);
+ }
+
+ // The XPCOM GetValue is OK for us
+ void SetValue(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::value, aValue, aRv);
+ }
+
+ // The XPCOM GetText is OK for us
+ void SetText(const nsAString& aValue, ErrorResult& aRv)
+ {
+ aRv = SetText(aValue);
+ }
+
+ int32_t Index();
+
+protected:
+ virtual ~HTMLOptionElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ /**
+ * Get the select content element that contains this option, this
+ * intentionally does not return nsresult, all we care about is if
+ * there's a select associated with this option or not.
+ */
+ HTMLSelectElement* GetSelect();
+
+ bool mSelectedChanged;
+ bool mIsSelected;
+
+ // True only while we're under the SetOptionsSelectedByIndex call when our
+ // "selected" attribute is changing and mSelectedChanged is false.
+ bool mIsInSetDefaultSelected;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLOptionElement_h__
diff --git a/dom/html/HTMLOptionsCollection.cpp b/dom/html/HTMLOptionsCollection.cpp
new file mode 100644
index 000000000..294493c0c
--- /dev/null
+++ b/dom/html/HTMLOptionsCollection.cpp
@@ -0,0 +1,375 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/HTMLOptionsCollection.h"
+
+#include "HTMLOptGroupElement.h"
+#include "mozAutoDocUpdate.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLFormSubmission.h"
+#include "mozilla/dom/HTMLOptionElement.h"
+#include "mozilla/dom/HTMLOptionsCollectionBinding.h"
+#include "mozilla/dom/HTMLSelectElement.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsError.h"
+#include "nsGkAtoms.h"
+#include "nsIComboboxControlFrame.h"
+#include "nsIDocument.h"
+#include "nsIDOMHTMLOptGroupElement.h"
+#include "nsIFormControlFrame.h"
+#include "nsIForm.h"
+#include "nsIFormProcessor.h"
+#include "nsIListControlFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsMappedAttributes.h"
+#include "nsRuleData.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStyleConsts.h"
+#include "jsfriendapi.h"
+
+namespace mozilla {
+namespace dom {
+
+HTMLOptionsCollection::HTMLOptionsCollection(HTMLSelectElement* aSelect)
+{
+ // Do not maintain a reference counted reference. When
+ // the select goes away, it will let us know.
+ mSelect = aSelect;
+}
+
+HTMLOptionsCollection::~HTMLOptionsCollection()
+{
+ DropReference();
+}
+
+void
+HTMLOptionsCollection::DropReference()
+{
+ // Drop our (non ref-counted) reference
+ mSelect = nullptr;
+}
+
+nsresult
+HTMLOptionsCollection::GetOptionIndex(Element* aOption,
+ int32_t aStartIndex,
+ bool aForward,
+ int32_t* aIndex)
+{
+ // NOTE: aIndex shouldn't be set if the returned value isn't NS_OK.
+
+ int32_t index;
+
+ // Make the common case fast
+ if (aStartIndex == 0 && aForward) {
+ index = mElements.IndexOf(aOption);
+ if (index == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aIndex = index;
+ return NS_OK;
+ }
+
+ int32_t high = mElements.Length();
+ int32_t step = aForward ? 1 : -1;
+
+ for (index = aStartIndex; index < high && index > -1; index += step) {
+ if (mElements[index] == aOption) {
+ *aIndex = index;
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HTMLOptionsCollection, mElements)
+
+// nsISupports
+
+// QueryInterface implementation for HTMLOptionsCollection
+NS_INTERFACE_TABLE_HEAD(HTMLOptionsCollection)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE(HTMLOptionsCollection,
+ nsIHTMLCollection,
+ nsIDOMHTMLOptionsCollection,
+ nsIDOMHTMLCollection)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(HTMLOptionsCollection)
+NS_INTERFACE_MAP_END
+
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLOptionsCollection)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLOptionsCollection)
+
+
+JSObject*
+HTMLOptionsCollection::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLOptionsCollectionBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMETHODIMP
+HTMLOptionsCollection::GetLength(uint32_t* aLength)
+{
+ *aLength = mElements.Length();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLOptionsCollection::SetLength(uint32_t aLength)
+{
+ if (!mSelect) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return mSelect->SetLength(aLength);
+}
+
+NS_IMETHODIMP
+HTMLOptionsCollection::SetOption(uint32_t aIndex,
+ nsIDOMHTMLOptionElement* aOption)
+{
+ if (!mSelect) {
+ return NS_OK;
+ }
+
+ // if the new option is null, just remove this option. Note that it's safe
+ // to pass a too-large aIndex in here.
+ if (!aOption) {
+ mSelect->Remove(aIndex);
+
+ // We're done.
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+
+ uint32_t index = uint32_t(aIndex);
+
+ // Now we're going to be setting an option in our collection
+ if (index > mElements.Length()) {
+ // Fill our array with blank options up to (but not including, since we're
+ // about to change it) aIndex, for compat with other browsers.
+ rv = SetLength(index);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_ASSERTION(index <= mElements.Length(), "SetLength lied");
+
+ nsCOMPtr<nsIDOMNode> ret;
+ if (index == mElements.Length()) {
+ nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aOption);
+ rv = mSelect->AppendChild(node, getter_AddRefs(ret));
+ } else {
+ // Find the option they're talking about and replace it
+ // hold a strong reference to follow COM rules.
+ RefPtr<HTMLOptionElement> refChild = ItemAsOption(index);
+ NS_ENSURE_TRUE(refChild, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsINode> parent = refChild->GetParent();
+ if (parent) {
+ nsCOMPtr<nsINode> node = do_QueryInterface(aOption);
+ ErrorResult res;
+ parent->ReplaceChild(*node, *refChild, res);
+ rv = res.StealNSResult();
+ }
+ }
+
+ return rv;
+}
+
+int32_t
+HTMLOptionsCollection::GetSelectedIndex(ErrorResult& aError)
+{
+ if (!mSelect) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return 0;
+ }
+
+ int32_t selectedIndex;
+ aError = mSelect->GetSelectedIndex(&selectedIndex);
+ return selectedIndex;
+}
+
+NS_IMETHODIMP
+HTMLOptionsCollection::GetSelectedIndex(int32_t* aSelectedIndex)
+{
+ ErrorResult rv;
+ *aSelectedIndex = GetSelectedIndex(rv);
+ return rv.StealNSResult();
+}
+
+void
+HTMLOptionsCollection::SetSelectedIndex(int32_t aSelectedIndex,
+ ErrorResult& aError)
+{
+ if (!mSelect) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ aError = mSelect->SetSelectedIndex(aSelectedIndex);
+}
+
+NS_IMETHODIMP
+HTMLOptionsCollection::SetSelectedIndex(int32_t aSelectedIndex)
+{
+ ErrorResult rv;
+ SetSelectedIndex(aSelectedIndex, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLOptionsCollection::Item(uint32_t aIndex, nsIDOMNode** aReturn)
+{
+ nsISupports* item = GetElementAt(aIndex);
+ if (!item) {
+ *aReturn = nullptr;
+
+ return NS_OK;
+ }
+
+ return CallQueryInterface(item, aReturn);
+}
+
+Element*
+HTMLOptionsCollection::GetElementAt(uint32_t aIndex)
+{
+ return ItemAsOption(aIndex);
+}
+
+HTMLOptionElement*
+HTMLOptionsCollection::NamedGetter(const nsAString& aName, bool& aFound)
+{
+ uint32_t count = mElements.Length();
+ for (uint32_t i = 0; i < count; i++) {
+ HTMLOptionElement* content = mElements.ElementAt(i);
+ if (content &&
+ (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, aName,
+ eCaseMatters) ||
+ content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id, aName,
+ eCaseMatters))) {
+ aFound = true;
+ return content;
+ }
+ }
+
+ aFound = false;
+ return nullptr;
+}
+
+nsINode*
+HTMLOptionsCollection::GetParentObject()
+{
+ return mSelect;
+}
+
+NS_IMETHODIMP
+HTMLOptionsCollection::NamedItem(const nsAString& aName,
+ nsIDOMNode** aReturn)
+{
+ NS_IF_ADDREF(*aReturn = GetNamedItem(aName));
+
+ return NS_OK;
+}
+
+void
+HTMLOptionsCollection::GetSupportedNames(nsTArray<nsString>& aNames)
+{
+ AutoTArray<nsIAtom*, 8> atoms;
+ for (uint32_t i = 0; i < mElements.Length(); ++i) {
+ HTMLOptionElement* content = mElements.ElementAt(i);
+ if (content) {
+ // Note: HasName means the names is exposed on the document,
+ // which is false for options, so we don't check it here.
+ const nsAttrValue* val = content->GetParsedAttr(nsGkAtoms::name);
+ if (val && val->Type() == nsAttrValue::eAtom) {
+ nsIAtom* name = val->GetAtomValue();
+ if (!atoms.Contains(name)) {
+ atoms.AppendElement(name);
+ }
+ }
+ if (content->HasID()) {
+ nsIAtom* id = content->GetID();
+ if (!atoms.Contains(id)) {
+ atoms.AppendElement(id);
+ }
+ }
+ }
+ }
+
+ uint32_t atomsLen = atoms.Length();
+ nsString* names = aNames.AppendElements(atomsLen);
+ for (uint32_t i = 0; i < atomsLen; ++i) {
+ atoms[i]->ToString(names[i]);
+ }
+}
+
+NS_IMETHODIMP
+HTMLOptionsCollection::GetSelect(nsIDOMHTMLSelectElement** aReturn)
+{
+ NS_IF_ADDREF(*aReturn = mSelect);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLOptionsCollection::Add(nsIDOMHTMLOptionElement* aOption,
+ nsIVariant* aBefore)
+{
+ if (!aOption) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!mSelect) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsCOMPtr<nsIDOMHTMLElement> elem = do_QueryInterface(aOption);
+ return mSelect->Add(elem, aBefore);
+}
+
+void
+HTMLOptionsCollection::Add(const HTMLOptionOrOptGroupElement& aElement,
+ const Nullable<HTMLElementOrLong>& aBefore,
+ ErrorResult& aError)
+{
+ if (!mSelect) {
+ aError.Throw(NS_ERROR_NOT_INITIALIZED);
+ return;
+ }
+
+ mSelect->Add(aElement, aBefore, aError);
+}
+
+void
+HTMLOptionsCollection::Remove(int32_t aIndex, ErrorResult& aError)
+{
+ if (!mSelect) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ uint32_t len = 0;
+ mSelect->GetLength(&len);
+ if (aIndex < 0 || (uint32_t)aIndex >= len)
+ aIndex = 0;
+
+ aError = mSelect->Remove(aIndex);
+}
+
+NS_IMETHODIMP
+HTMLOptionsCollection::Remove(int32_t aIndex)
+{
+ ErrorResult rv;
+ Remove(aIndex, rv);
+ return rv.StealNSResult();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLOptionsCollection.h b/dom/html/HTMLOptionsCollection.h
new file mode 100644
index 000000000..21123b3d2
--- /dev/null
+++ b/dom/html/HTMLOptionsCollection.h
@@ -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/. */
+#ifndef mozilla_dom_HTMLOptionsCollection_h
+#define mozilla_dom_HTMLOptionsCollection_h
+
+#include "mozilla/Attributes.h"
+#include "nsIHTMLCollection.h"
+#include "nsIDOMHTMLOptionsCollection.h"
+#include "nsWrapperCache.h"
+
+#include "mozilla/dom/HTMLOptionElement.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCOMPtr.h"
+#include "nsError.h"
+#include "nsGenericHTMLElement.h"
+#include "nsTArray.h"
+
+class nsIDOMHTMLOptionElement;
+
+namespace mozilla {
+namespace dom {
+
+class HTMLElementOrLong;
+class HTMLOptionElementOrHTMLOptGroupElement;
+class HTMLSelectElement;
+
+/**
+ * The collection of options in the select (what you get back when you do
+ * select.options in DOM)
+ */
+class HTMLOptionsCollection final : public nsIHTMLCollection
+ , public nsIDOMHTMLOptionsCollection
+ , public nsWrapperCache
+{
+ typedef HTMLOptionElementOrHTMLOptGroupElement HTMLOptionOrOptGroupElement;
+public:
+ explicit HTMLOptionsCollection(HTMLSelectElement* aSelect);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ // nsWrapperCache
+ using nsWrapperCache::GetWrapperPreserveColor;
+ using nsWrapperCache::GetWrapper;
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+protected:
+ virtual ~HTMLOptionsCollection();
+
+ virtual JSObject* GetWrapperPreserveColorInternal() override
+ {
+ return nsWrapperCache::GetWrapperPreserveColor();
+ }
+public:
+
+ // nsIDOMHTMLOptionsCollection interface
+ NS_DECL_NSIDOMHTMLOPTIONSCOLLECTION
+
+ // nsIDOMHTMLCollection interface, all its methods are defined in
+ // nsIDOMHTMLOptionsCollection
+
+ virtual Element* GetElementAt(uint32_t aIndex) override;
+ virtual nsINode* GetParentObject() override;
+
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(HTMLOptionsCollection,
+ nsIHTMLCollection)
+
+ // Helpers for HTMLSelectElement
+ /**
+ * Insert an option
+ * @param aOption the option to insert
+ * @param aIndex the index to insert at
+ */
+ void InsertOptionAt(mozilla::dom::HTMLOptionElement* aOption, uint32_t aIndex)
+ {
+ mElements.InsertElementAt(aIndex, aOption);
+ }
+
+ /**
+ * Remove an option
+ * @param aIndex the index of the option to remove
+ */
+ void RemoveOptionAt(uint32_t aIndex)
+ {
+ mElements.RemoveElementAt(aIndex);
+ }
+
+ /**
+ * Get the option at the index
+ * @param aIndex the index
+ * @param aReturn the option returned [OUT]
+ */
+ mozilla::dom::HTMLOptionElement* ItemAsOption(uint32_t aIndex)
+ {
+ return mElements.SafeElementAt(aIndex, nullptr);
+ }
+
+ /**
+ * Clears out all options
+ */
+ void Clear()
+ {
+ mElements.Clear();
+ }
+
+ /**
+ * Append an option to end of array
+ */
+ void AppendOption(mozilla::dom::HTMLOptionElement* aOption)
+ {
+ mElements.AppendElement(aOption);
+ }
+
+ /**
+ * Drop the reference to the select. Called during select destruction.
+ */
+ void DropReference();
+
+ /**
+ * Finds the index of a given option element.
+ * If the option isn't part of the collection, return NS_ERROR_FAILURE
+ * without setting aIndex.
+ *
+ * @param aOption the option to get the index of
+ * @param aStartIndex the index to start looking at
+ * @param aForward TRUE to look forward, FALSE to look backward
+ * @return the option index
+ */
+ nsresult GetOptionIndex(Element* aOption,
+ int32_t aStartIndex, bool aForward,
+ int32_t* aIndex);
+
+ HTMLOptionElement* GetNamedItem(const nsAString& aName)
+ {
+ bool dummy;
+ return NamedGetter(aName, dummy);
+ }
+ HTMLOptionElement* NamedGetter(const nsAString& aName, bool& aFound);
+ virtual Element*
+ GetFirstNamedElement(const nsAString& aName, bool& aFound) override
+ {
+ return NamedGetter(aName, aFound);
+ }
+
+ void Add(const HTMLOptionOrOptGroupElement& aElement,
+ const Nullable<HTMLElementOrLong>& aBefore,
+ ErrorResult& aError);
+ void Remove(int32_t aIndex, ErrorResult& aError);
+ int32_t GetSelectedIndex(ErrorResult& aError);
+ void SetSelectedIndex(int32_t aSelectedIndex, ErrorResult& aError);
+ void IndexedSetter(uint32_t aIndex, nsIDOMHTMLOptionElement* aOption,
+ ErrorResult& aError)
+ {
+ aError = SetOption(aIndex, aOption);
+ }
+ virtual void GetSupportedNames(nsTArray<nsString>& aNames) override;
+
+private:
+ /** The list of options (holds strong references). This is infallible, so
+ * various members such as InsertOptionAt are also infallible. */
+ nsTArray<RefPtr<mozilla::dom::HTMLOptionElement> > mElements;
+ /** The select element that contains this array */
+ HTMLSelectElement* mSelect;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLOptionsCollection_h
diff --git a/dom/html/HTMLOutputElement.cpp b/dom/html/HTMLOutputElement.cpp
new file mode 100644
index 000000000..17608c94f
--- /dev/null
+++ b/dom/html/HTMLOutputElement.cpp
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/HTMLOutputElement.h"
+
+#include "mozAutoDocUpdate.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/HTMLFormElement.h"
+#include "mozilla/dom/HTMLFormSubmission.h"
+#include "mozilla/dom/HTMLOutputElementBinding.h"
+#include "nsContentUtils.h"
+#include "nsDOMTokenList.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Output)
+
+namespace mozilla {
+namespace dom {
+
+HTMLOutputElement::HTMLOutputElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser)
+ : nsGenericHTMLFormElement(aNodeInfo)
+ , mValueModeFlag(eModeDefault)
+ , mIsDoneAddingChildren(!aFromParser)
+{
+ AddMutationObserver(this);
+
+ // We start out valid and ui-valid (since we have no form).
+ AddStatesSilently(NS_EVENT_STATE_VALID | NS_EVENT_STATE_MOZ_UI_VALID);
+}
+
+HTMLOutputElement::~HTMLOutputElement()
+{
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLOutputElement)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLOutputElement,
+ nsGenericHTMLFormElement)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mTokenList)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLOutputElement,
+ nsGenericHTMLFormElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTokenList)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_ADDREF_INHERITED(HTMLOutputElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLOutputElement, Element)
+
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLOutputElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLOutputElement,
+ nsIMutationObserver,
+ nsIConstraintValidation)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLOutputElement)
+
+void
+HTMLOutputElement::SetCustomValidity(const nsAString& aError)
+{
+ nsIConstraintValidation::SetCustomValidity(aError);
+
+ UpdateState(true);
+}
+
+NS_IMETHODIMP
+HTMLOutputElement::Reset()
+{
+ mValueModeFlag = eModeDefault;
+ return nsContentUtils::SetNodeTextContent(this, mDefaultValue, true);
+}
+
+NS_IMETHODIMP
+HTMLOutputElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission)
+{
+ // The output element is not submittable.
+ return NS_OK;
+}
+
+bool
+HTMLOutputElement::ParseAttribute(int32_t aNamespaceID, nsIAtom* aAttribute,
+ const nsAString& aValue, nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::_for) {
+ aResult.ParseAtomArray(aValue);
+ return true;
+ }
+ }
+
+ return nsGenericHTMLFormElement::ParseAttribute(aNamespaceID, aAttribute,
+ aValue, aResult);
+}
+
+void
+HTMLOutputElement::DoneAddingChildren(bool aHaveNotified)
+{
+ mIsDoneAddingChildren = true;
+}
+
+EventStates
+HTMLOutputElement::IntrinsicState() const
+{
+ EventStates states = nsGenericHTMLFormElement::IntrinsicState();
+
+ // We don't have to call IsCandidateForConstraintValidation()
+ // because <output> can't be barred from constraint validation.
+ if (IsValid()) {
+ states |= NS_EVENT_STATE_VALID;
+ if (!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) {
+ states |= NS_EVENT_STATE_MOZ_UI_VALID;
+ }
+ } else {
+ states |= NS_EVENT_STATE_INVALID;
+ if (!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) {
+ states |= NS_EVENT_STATE_MOZ_UI_INVALID;
+ }
+ }
+
+ return states;
+}
+
+nsresult
+HTMLOutputElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLFormElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Unfortunately, we can actually end up having to change our state
+ // as a result of being bound to a tree even from the parser: we
+ // might end up a in a novalidate form, and unlike other form
+ // controls that on its own is enough to make change ui-valid state.
+ // So just go ahead and update our state now.
+ UpdateState(false);
+
+ return rv;
+}
+
+void
+HTMLOutputElement::GetValue(nsAString& aValue)
+{
+ nsContentUtils::GetNodeTextContent(this, true, aValue);
+}
+
+void
+HTMLOutputElement::SetValue(const nsAString& aValue, ErrorResult& aRv)
+{
+ mValueModeFlag = eModeValue;
+ aRv = nsContentUtils::SetNodeTextContent(this, aValue, true);
+}
+
+void
+HTMLOutputElement::SetDefaultValue(const nsAString& aDefaultValue, ErrorResult& aRv)
+{
+ mDefaultValue = aDefaultValue;
+ if (mValueModeFlag == eModeDefault) {
+ aRv = nsContentUtils::SetNodeTextContent(this, mDefaultValue, true);
+ }
+}
+
+nsDOMTokenList*
+HTMLOutputElement::HtmlFor()
+{
+ if (!mTokenList) {
+ mTokenList = new nsDOMTokenList(this, nsGkAtoms::_for);
+ }
+ return mTokenList;
+}
+
+void HTMLOutputElement::DescendantsChanged()
+{
+ if (mIsDoneAddingChildren && mValueModeFlag == eModeDefault) {
+ nsContentUtils::GetNodeTextContent(this, true, mDefaultValue);
+ }
+}
+
+// nsIMutationObserver
+
+void HTMLOutputElement::CharacterDataChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+ DescendantsChanged();
+}
+
+void HTMLOutputElement::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer)
+{
+ DescendantsChanged();
+}
+
+void HTMLOutputElement::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ DescendantsChanged();
+}
+
+void HTMLOutputElement::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ DescendantsChanged();
+}
+
+JSObject*
+HTMLOutputElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLOutputElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLOutputElement.h b/dom/html/HTMLOutputElement.h
new file mode 100644
index 000000000..588262480
--- /dev/null
+++ b/dom/html/HTMLOutputElement.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_HTMLOutputElement_h
+#define mozilla_dom_HTMLOutputElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsStubMutationObserver.h"
+#include "nsIConstraintValidation.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLFormSubmission;
+
+class HTMLOutputElement final : public nsGenericHTMLFormElement,
+ public nsStubMutationObserver,
+ public nsIConstraintValidation
+{
+public:
+ using nsIConstraintValidation::GetValidationMessage;
+
+ explicit HTMLOutputElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser = NOT_FROM_PARSER);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIFormControl
+ NS_IMETHOD_(uint32_t) GetType() const override { return NS_FORM_OUTPUT; }
+ NS_IMETHOD Reset() override;
+ NS_IMETHOD SubmitNamesValues(HTMLFormSubmission* aFormSubmission) override;
+
+ virtual bool IsDisabled() const override { return false; }
+
+ nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+
+ bool ParseAttribute(int32_t aNamespaceID, nsIAtom* aAttribute,
+ const nsAString& aValue, nsAttrValue& aResult) override;
+
+ virtual void DoneAddingChildren(bool aHaveNotified) override;
+
+ EventStates IntrinsicState() const override;
+
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+
+ // This function is called when a callback function from nsIMutationObserver
+ // has to be used to update the defaultValue attribute.
+ void DescendantsChanged();
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLOutputElement,
+ nsGenericHTMLFormElement)
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL
+ nsDOMTokenList* HtmlFor();
+ // nsGenericHTMLFormElement::GetForm is fine.
+ void GetName(nsAString& aName)
+ {
+ GetHTMLAttr(nsGkAtoms::name, aName);
+ }
+
+ void SetName(const nsAString& aName, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::name, aName, aRv);
+ }
+
+ void GetType(nsAString& aType)
+ {
+ aType.AssignLiteral("output");
+ }
+
+ void GetDefaultValue(nsAString& aDefaultValue)
+ {
+ aDefaultValue = mDefaultValue;
+ }
+
+ void SetDefaultValue(const nsAString& aDefaultValue, ErrorResult& aRv);
+
+ void GetValue(nsAString& aValue);
+ void SetValue(const nsAString& aValue, ErrorResult& aRv);
+
+ // nsIConstraintValidation::WillValidate is fine.
+ // nsIConstraintValidation::Validity() is fine.
+ // nsIConstraintValidation::GetValidationMessage() is fine.
+ // nsIConstraintValidation::CheckValidity() is fine.
+ void SetCustomValidity(const nsAString& aError);
+
+protected:
+ virtual ~HTMLOutputElement();
+
+ enum ValueModeFlag {
+ eModeDefault,
+ eModeValue
+ };
+
+ ValueModeFlag mValueModeFlag;
+ bool mIsDoneAddingChildren;
+ nsString mDefaultValue;
+ RefPtr<nsDOMTokenList> mTokenList;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLOutputElement_h
diff --git a/dom/html/HTMLParagraphElement.cpp b/dom/html/HTMLParagraphElement.cpp
new file mode 100644
index 000000000..1157dee43
--- /dev/null
+++ b/dom/html/HTMLParagraphElement.cpp
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/HTMLParagraphElement.h"
+#include "mozilla/dom/HTMLParagraphElementBinding.h"
+
+#include "nsStyleConsts.h"
+#include "nsMappedAttributes.h"
+#include "nsRuleData.h"
+
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Paragraph)
+
+namespace mozilla {
+namespace dom {
+
+HTMLParagraphElement::~HTMLParagraphElement()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLParagraphElement, nsGenericHTMLElement,
+ nsIDOMHTMLParagraphElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLParagraphElement)
+
+NS_IMPL_STRING_ATTR(HTMLParagraphElement, Align, align)
+
+bool
+HTMLParagraphElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aAttribute == nsGkAtoms::align && aNamespaceID == kNameSpaceID_None) {
+ return ParseDivAlignValue(aValue, aResult);
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLParagraphElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ nsGenericHTMLElement::MapDivAlignAttributeInto(aAttributes, aData);
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLParagraphElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry* const map[] = {
+ sDivAlignAttributeMap,
+ sCommonAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+
+nsMapRuleToAttributesFunc
+HTMLParagraphElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+JSObject*
+HTMLParagraphElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLParagraphElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLParagraphElement.h b/dom/html/HTMLParagraphElement.h
new file mode 100644
index 000000000..c1e231ab7
--- /dev/null
+++ b/dom/html/HTMLParagraphElement.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLParagraphElement_h
+#define mozilla_dom_HTMLParagraphElement_h
+
+#include "mozilla/Attributes.h"
+
+#include "nsIDOMHTMLParagraphElement.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLParagraphElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLParagraphElement
+{
+public:
+ explicit HTMLParagraphElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLParagraphElement
+ NS_DECL_NSIDOMHTMLPARAGRAPHELEMENT
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ // WebIDL API
+ // The XPCOM GetAlign is fine for our purposes
+ void SetAlign(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::align, aValue, rv);
+ }
+
+protected:
+ virtual ~HTMLParagraphElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLParagraphElement_h
diff --git a/dom/html/HTMLPictureElement.cpp b/dom/html/HTMLPictureElement.cpp
new file mode 100644
index 000000000..eab405ed8
--- /dev/null
+++ b/dom/html/HTMLPictureElement.cpp
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/HTMLPictureElement.h"
+#include "mozilla/dom/HTMLPictureElementBinding.h"
+#include "mozilla/dom/HTMLImageElement.h"
+
+// Expand NS_IMPL_NS_NEW_HTML_ELEMENT(Picture) to add pref check.
+nsGenericHTMLElement*
+NS_NewHTMLPictureElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ mozilla::dom::FromParser aFromParser)
+{
+ return new mozilla::dom::HTMLPictureElement(aNodeInfo);
+}
+
+namespace mozilla {
+namespace dom {
+
+HTMLPictureElement::HTMLPictureElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+{
+}
+
+HTMLPictureElement::~HTMLPictureElement()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLPictureElement, nsGenericHTMLElement,
+ nsIDOMHTMLPictureElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLPictureElement)
+
+void
+HTMLPictureElement::RemoveChildAt(uint32_t aIndex, bool aNotify)
+{
+ nsCOMPtr<nsIContent> child = GetChildAt(aIndex);
+
+ if (child && child->IsHTMLElement(nsGkAtoms::img)) {
+ HTMLImageElement* img = HTMLImageElement::FromContent(child);
+ if (img) {
+ img->PictureSourceRemoved(child->AsContent());
+ }
+ } else if (child && child->IsHTMLElement(nsGkAtoms::source)) {
+ // Find all img siblings after this <source> to notify them of its demise
+ nsCOMPtr<nsIContent> nextSibling = child->GetNextSibling();
+ if (nextSibling && nextSibling->GetParentNode() == this) {
+ do {
+ HTMLImageElement* img = HTMLImageElement::FromContent(nextSibling);
+ if (img) {
+ img->PictureSourceRemoved(child->AsContent());
+ }
+ } while ( (nextSibling = nextSibling->GetNextSibling()) );
+ }
+ }
+
+ nsGenericHTMLElement::RemoveChildAt(aIndex, aNotify);
+}
+
+nsresult
+HTMLPictureElement::InsertChildAt(nsIContent* aKid, uint32_t aIndex, bool aNotify)
+{
+ nsresult rv = nsGenericHTMLElement::InsertChildAt(aKid, aIndex, aNotify);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(aKid, rv);
+
+ if (aKid->IsHTMLElement(nsGkAtoms::img)) {
+ HTMLImageElement* img = HTMLImageElement::FromContent(aKid);
+ if (img) {
+ img->PictureSourceAdded(aKid->AsContent());
+ }
+ } else if (aKid->IsHTMLElement(nsGkAtoms::source)) {
+ // Find all img siblings after this <source> to notify them of its insertion
+ nsCOMPtr<nsIContent> nextSibling = aKid->GetNextSibling();
+ if (nextSibling && nextSibling->GetParentNode() == this) {
+ do {
+ HTMLImageElement* img = HTMLImageElement::FromContent(nextSibling);
+ if (img) {
+ img->PictureSourceAdded(aKid->AsContent());
+ }
+ } while ( (nextSibling = nextSibling->GetNextSibling()) );
+ }
+ }
+
+ return rv;
+}
+
+JSObject*
+HTMLPictureElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLPictureElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLPictureElement.h b/dom/html/HTMLPictureElement.h
new file mode 100644
index 000000000..28714e02a
--- /dev/null
+++ b/dom/html/HTMLPictureElement.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLPictureElement_h
+#define mozilla_dom_HTMLPictureElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsIDOMHTMLPictureElement.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLPictureElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLPictureElement
+{
+public:
+ explicit HTMLPictureElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLPictureElement
+ NS_DECL_NSIDOMHTMLPICTUREELEMENT
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+ virtual void RemoveChildAt(uint32_t aIndex, bool aNotify) override;
+ virtual nsresult InsertChildAt(nsIContent* aKid, uint32_t aIndex, bool aNotify) override;
+
+protected:
+ virtual ~HTMLPictureElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLPictureElement_h
diff --git a/dom/html/HTMLPreElement.cpp b/dom/html/HTMLPreElement.cpp
new file mode 100644
index 000000000..36a1bc657
--- /dev/null
+++ b/dom/html/HTMLPreElement.cpp
@@ -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/. */
+
+#include "mozilla/dom/HTMLPreElement.h"
+#include "mozilla/dom/HTMLPreElementBinding.h"
+
+#include "nsAttrValueInlines.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsMappedAttributes.h"
+#include "nsRuleData.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Pre)
+
+namespace mozilla {
+namespace dom {
+
+HTMLPreElement::~HTMLPreElement()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLPreElement, nsGenericHTMLElement,
+ nsIDOMHTMLPreElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLPreElement)
+
+NS_IMPL_INT_ATTR(HTMLPreElement, Width, width)
+
+bool
+HTMLPreElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::width) {
+ return aResult.ParseIntValue(aValue);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLPreElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Text)) {
+ nsCSSValue* whiteSpace = aData->ValueForWhiteSpace();
+ if (whiteSpace->GetUnit() == eCSSUnit_Null) {
+ // wrap: empty
+ if (aAttributes->GetAttr(nsGkAtoms::wrap))
+ whiteSpace->SetIntValue(NS_STYLE_WHITESPACE_PRE_WRAP, eCSSUnit_Enumerated);
+ }
+ }
+
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLPreElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ if (!mNodeInfo->Equals(nsGkAtoms::pre)) {
+ return nsGenericHTMLElement::IsAttributeMapped(aAttribute);
+ }
+
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::wrap },
+ { nullptr },
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+nsMapRuleToAttributesFunc
+HTMLPreElement::GetAttributeMappingFunction() const
+{
+ if (!mNodeInfo->Equals(nsGkAtoms::pre)) {
+ return nsGenericHTMLElement::GetAttributeMappingFunction();
+ }
+
+ return &MapAttributesIntoRule;
+}
+
+JSObject*
+HTMLPreElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLPreElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLPreElement.h b/dom/html/HTMLPreElement.h
new file mode 100644
index 000000000..bd86dfe0d
--- /dev/null
+++ b/dom/html/HTMLPreElement.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLPreElement_h
+#define mozilla_dom_HTMLPreElement_h
+
+#include "mozilla/Attributes.h"
+
+#include "nsIDOMHTMLPreElement.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLPreElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLPreElement
+{
+public:
+ explicit HTMLPreElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLPreElement
+ NS_IMETHOD GetWidth(int32_t* aWidth) override;
+ NS_IMETHOD SetWidth(int32_t aWidth) override;
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ // WebIDL API
+ int32_t Width() const
+ {
+ return GetIntAttr(nsGkAtoms::width, 0);
+ }
+ void SetWidth(int32_t aWidth, mozilla::ErrorResult& rv)
+ {
+ rv = SetIntAttr(nsGkAtoms::width, aWidth);
+ }
+
+protected:
+ virtual ~HTMLPreElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLPreElement_h
diff --git a/dom/html/HTMLProgressElement.cpp b/dom/html/HTMLProgressElement.cpp
new file mode 100644
index 000000000..fc1926ee5
--- /dev/null
+++ b/dom/html/HTMLProgressElement.cpp
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/EventStates.h"
+#include "mozilla/dom/HTMLProgressElement.h"
+#include "mozilla/dom/HTMLProgressElementBinding.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Progress)
+
+namespace mozilla {
+namespace dom {
+
+const double HTMLProgressElement::kIndeterminatePosition = -1.0;
+const double HTMLProgressElement::kDefaultValue = 0.0;
+const double HTMLProgressElement::kDefaultMax = 1.0;
+
+
+HTMLProgressElement::HTMLProgressElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+{
+ // We start out indeterminate
+ AddStatesSilently(NS_EVENT_STATE_INDETERMINATE);
+}
+
+HTMLProgressElement::~HTMLProgressElement()
+{
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLProgressElement)
+
+
+EventStates
+HTMLProgressElement::IntrinsicState() const
+{
+ EventStates state = nsGenericHTMLElement::IntrinsicState();
+
+ if (IsIndeterminate()) {
+ state |= NS_EVENT_STATE_INDETERMINATE;
+ }
+
+ return state;
+}
+
+bool
+HTMLProgressElement::ParseAttribute(int32_t aNamespaceID, nsIAtom* aAttribute,
+ const nsAString& aValue, nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::value || aAttribute == nsGkAtoms::max) {
+ return aResult.ParseDoubleValue(aValue);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute,
+ aValue, aResult);
+}
+
+double
+HTMLProgressElement::Value() const
+{
+ const nsAttrValue* attrValue = mAttrsAndChildren.GetAttr(nsGkAtoms::value);
+ if (!attrValue || attrValue->Type() != nsAttrValue::eDoubleValue ||
+ attrValue->GetDoubleValue() < 0.0) {
+ return kDefaultValue;
+ }
+
+ return std::min(attrValue->GetDoubleValue(), Max());
+}
+
+double
+HTMLProgressElement::Max() const
+{
+ const nsAttrValue* attrMax = mAttrsAndChildren.GetAttr(nsGkAtoms::max);
+ if (!attrMax || attrMax->Type() != nsAttrValue::eDoubleValue ||
+ attrMax->GetDoubleValue() <= 0.0) {
+ return kDefaultMax;
+ }
+
+ return attrMax->GetDoubleValue();
+}
+
+double
+HTMLProgressElement::Position() const
+{
+ if (IsIndeterminate()) {
+ return kIndeterminatePosition;
+ }
+
+ return Value() / Max();
+}
+
+bool
+HTMLProgressElement::IsIndeterminate() const
+{
+ const nsAttrValue* attrValue = mAttrsAndChildren.GetAttr(nsGkAtoms::value);
+ return !attrValue || attrValue->Type() != nsAttrValue::eDoubleValue;
+}
+
+JSObject*
+HTMLProgressElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLProgressElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLProgressElement.h b/dom/html/HTMLProgressElement.h
new file mode 100644
index 000000000..ec10b48ab
--- /dev/null
+++ b/dom/html/HTMLProgressElement.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLProgressElement_h
+#define mozilla_dom_HTMLProgressElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace dom {
+
+class HTMLProgressElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLProgressElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ EventStates IntrinsicState() const override;
+
+ nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+
+ bool ParseAttribute(int32_t aNamespaceID, nsIAtom* aAttribute,
+ const nsAString& aValue, nsAttrValue& aResult) override;
+
+ // WebIDL
+ double Value() const;
+ void SetValue(double aValue, ErrorResult& aRv)
+ {
+ SetDoubleAttr(nsGkAtoms::value, aValue, aRv);
+ }
+ double Max() const;
+ void SetMax(double aValue, ErrorResult& aRv)
+ {
+ SetDoubleAttr(nsGkAtoms::max, aValue, aRv);
+ }
+ double Position() const;
+
+protected:
+ virtual ~HTMLProgressElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+ /**
+ * Returns whethem the progress element is in the indeterminate state.
+ * A progress element is in the indeterminate state if its value is ommited
+ * or is not a floating point number..
+ *
+ * @return whether the progress element is in the indeterminate state.
+ */
+ bool IsIndeterminate() const;
+
+ static const double kIndeterminatePosition;
+ static const double kDefaultValue;
+ static const double kDefaultMax;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLProgressElement_h
diff --git a/dom/html/HTMLScriptElement.cpp b/dom/html/HTMLScriptElement.cpp
new file mode 100644
index 000000000..94d09c12c
--- /dev/null
+++ b/dom/html/HTMLScriptElement.cpp
@@ -0,0 +1,316 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsIDocument.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsContentUtils.h"
+#include "nsUnicharUtils.h" // for nsCaseInsensitiveStringComparator()
+#include "nsIScriptContext.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIXPConnect.h"
+#include "nsServiceManagerUtils.h"
+#include "nsError.h"
+#include "nsIArray.h"
+#include "nsTArray.h"
+#include "nsDOMJSUtils.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/dom/HTMLScriptElement.h"
+#include "mozilla/dom/HTMLScriptElementBinding.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Script)
+
+namespace mozilla {
+namespace dom {
+
+JSObject*
+HTMLScriptElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLScriptElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+HTMLScriptElement::HTMLScriptElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser)
+ : nsGenericHTMLElement(aNodeInfo)
+ , nsScriptElement(aFromParser)
+{
+ AddMutationObserver(this);
+}
+
+HTMLScriptElement::~HTMLScriptElement()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLScriptElement, nsGenericHTMLElement,
+ nsIDOMHTMLScriptElement,
+ nsIScriptLoaderObserver,
+ nsIScriptElement,
+ nsIMutationObserver)
+
+nsresult
+HTMLScriptElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (GetComposedDoc()) {
+ MaybeProcessScript();
+ }
+
+ return NS_OK;
+}
+
+bool
+HTMLScriptElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::crossorigin) {
+ ParseCORSValue(aValue, aResult);
+ return true;
+ }
+
+ if (aAttribute == nsGkAtoms::integrity) {
+ aResult.ParseStringOrAtom(aValue);
+ return true;
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+nsresult
+HTMLScriptElement::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const
+{
+ *aResult = nullptr;
+
+ already_AddRefed<mozilla::dom::NodeInfo> ni = RefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget();
+ HTMLScriptElement* it = new HTMLScriptElement(ni, NOT_FROM_PARSER);
+
+ nsCOMPtr<nsINode> kungFuDeathGrip = it;
+ nsresult rv = const_cast<HTMLScriptElement*>(this)->CopyInnerTo(it);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The clone should be marked evaluated if we are.
+ it->mAlreadyStarted = mAlreadyStarted;
+ it->mLineNumber = mLineNumber;
+ it->mMalformed = mMalformed;
+
+ kungFuDeathGrip.swap(*aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLScriptElement::GetText(nsAString& aValue)
+{
+ if (!nsContentUtils::GetNodeTextContent(this, false, aValue, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLScriptElement::SetText(const nsAString& aValue)
+{
+ ErrorResult rv;
+ SetText(aValue, rv);
+ return rv.StealNSResult();
+}
+
+void
+HTMLScriptElement::SetText(const nsAString& aValue, ErrorResult& rv)
+{
+ rv = nsContentUtils::SetNodeTextContent(this, aValue, true);
+}
+
+
+NS_IMPL_STRING_ATTR(HTMLScriptElement, Charset, charset)
+NS_IMPL_BOOL_ATTR(HTMLScriptElement, Defer, defer)
+// If this ever gets changed to return "" if the attr value is "" (see
+// https://github.com/whatwg/html/issues/1739 for why it might not get changed),
+// it may be worth it to use GetSrc instead of GetAttr and manual
+// NewURIWithDocumentCharset in FreezeUriAsyncDefer.
+NS_IMPL_URI_ATTR(HTMLScriptElement, Src, src)
+NS_IMPL_STRING_ATTR(HTMLScriptElement, Type, type)
+NS_IMPL_STRING_ATTR(HTMLScriptElement, HtmlFor, _for)
+NS_IMPL_STRING_ATTR(HTMLScriptElement, Event, event)
+
+void
+HTMLScriptElement::SetCharset(const nsAString& aCharset, ErrorResult& rv)
+{
+ SetHTMLAttr(nsGkAtoms::charset, aCharset, rv);
+}
+
+void
+HTMLScriptElement::SetDefer(bool aDefer, ErrorResult& rv)
+{
+ SetHTMLBoolAttr(nsGkAtoms::defer, aDefer, rv);
+}
+
+bool
+HTMLScriptElement::Defer()
+{
+ return GetBoolAttr(nsGkAtoms::defer);
+}
+
+void
+HTMLScriptElement::SetSrc(const nsAString& aSrc, ErrorResult& rv)
+{
+ rv = SetAttrHelper(nsGkAtoms::src, aSrc);
+}
+
+void
+HTMLScriptElement::SetType(const nsAString& aType, ErrorResult& rv)
+{
+ SetHTMLAttr(nsGkAtoms::type, aType, rv);
+}
+
+void
+HTMLScriptElement::SetHtmlFor(const nsAString& aHtmlFor, ErrorResult& rv)
+{
+ SetHTMLAttr(nsGkAtoms::_for, aHtmlFor, rv);
+}
+
+void
+HTMLScriptElement::SetEvent(const nsAString& aEvent, ErrorResult& rv)
+{
+ SetHTMLAttr(nsGkAtoms::event, aEvent, rv);
+}
+
+nsresult
+HTMLScriptElement::GetAsync(bool* aValue)
+{
+ *aValue = Async();
+ return NS_OK;
+}
+
+bool
+HTMLScriptElement::Async()
+{
+ return mForceAsync || GetBoolAttr(nsGkAtoms::async);
+}
+
+nsresult
+HTMLScriptElement::SetAsync(bool aValue)
+{
+ ErrorResult rv;
+ SetAsync(aValue, rv);
+ return rv.StealNSResult();
+}
+
+void
+HTMLScriptElement::SetAsync(bool aValue, ErrorResult& rv)
+{
+ mForceAsync = false;
+ SetHTMLBoolAttr(nsGkAtoms::async, aValue, rv);
+}
+
+nsresult
+HTMLScriptElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (nsGkAtoms::async == aName && kNameSpaceID_None == aNamespaceID) {
+ mForceAsync = false;
+ }
+ return nsGenericHTMLElement::AfterSetAttr(aNamespaceID, aName, aValue,
+ aNotify);
+}
+
+NS_IMETHODIMP
+HTMLScriptElement::GetInnerHTML(nsAString& aInnerHTML)
+{
+ if (!nsContentUtils::GetNodeTextContent(this, false, aInnerHTML, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+void
+HTMLScriptElement::SetInnerHTML(const nsAString& aInnerHTML,
+ ErrorResult& aError)
+{
+ aError = nsContentUtils::SetNodeTextContent(this, aInnerHTML, true);
+}
+
+// variation of this code in nsSVGScriptElement - check if changes
+// need to be transfered when modifying
+
+bool
+HTMLScriptElement::GetScriptType(nsAString& type)
+{
+ return GetAttr(kNameSpaceID_None, nsGkAtoms::type, type);
+}
+
+void
+HTMLScriptElement::GetScriptText(nsAString& text)
+{
+ GetText(text);
+}
+
+void
+HTMLScriptElement::GetScriptCharset(nsAString& charset)
+{
+ GetCharset(charset);
+}
+
+void
+HTMLScriptElement::FreezeUriAsyncDefer()
+{
+ if (mFrozen) {
+ return;
+ }
+
+ // variation of this code in nsSVGScriptElement - check if changes
+ // need to be transfered when modifying. Note that we don't use GetSrc here
+ // because it will return the base URL when the attr value is "".
+ nsAutoString src;
+ if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
+ // Empty src should be treated as invalid URL.
+ if (!src.IsEmpty()) {
+ nsCOMPtr<nsIURI> baseURI = GetBaseURI();
+ nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(mUri),
+ src, OwnerDoc(), baseURI);
+ }
+
+ // At this point mUri will be null for invalid URLs.
+ mExternal = true;
+
+ bool defer, async;
+ GetAsync(&async);
+ GetDefer(&defer);
+
+ mDefer = !async && defer;
+ mAsync = async;
+ }
+
+ mFrozen = true;
+}
+
+CORSMode
+HTMLScriptElement::GetCORSMode() const
+{
+ return AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin));
+}
+
+bool
+HTMLScriptElement::HasScriptContent()
+{
+ return (mFrozen ? mExternal : HasAttr(kNameSpaceID_None, nsGkAtoms::src)) ||
+ nsContentUtils::HasNonEmptyTextContent(this);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLScriptElement.h b/dom/html/HTMLScriptElement.h
new file mode 100644
index 000000000..00628bd6d
--- /dev/null
+++ b/dom/html/HTMLScriptElement.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_HTMLScriptElement_h
+#define mozilla_dom_HTMLScriptElement_h
+
+#include "nsIDOMHTMLScriptElement.h"
+#include "nsScriptElement.h"
+#include "nsGenericHTMLElement.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLScriptElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLScriptElement,
+ public nsScriptElement
+{
+public:
+ using Element::GetText;
+ using Element::SetText;
+
+ HTMLScriptElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD GetInnerHTML(nsAString& aInnerHTML) override;
+ using nsGenericHTMLElement::SetInnerHTML;
+ virtual void SetInnerHTML(const nsAString& aInnerHTML,
+ mozilla::ErrorResult& aError) override;
+
+ // nsIDOMHTMLScriptElement
+ NS_DECL_NSIDOMHTMLSCRIPTELEMENT
+
+ // nsIScriptElement
+ virtual bool GetScriptType(nsAString& type) override;
+ virtual void GetScriptText(nsAString& text) override;
+ virtual void GetScriptCharset(nsAString& charset) override;
+ virtual void FreezeUriAsyncDefer() override;
+ virtual CORSMode GetCORSMode() const override;
+
+ // nsIContent
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ // Element
+ virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+
+ // WebIDL
+ void SetText(const nsAString& aValue, ErrorResult& rv);
+ void SetCharset(const nsAString& aCharset, ErrorResult& rv);
+ void SetDefer(bool aDefer, ErrorResult& rv);
+ bool Defer();
+ void SetSrc(const nsAString& aSrc, ErrorResult& rv);
+ void SetType(const nsAString& aType, ErrorResult& rv);
+ void SetHtmlFor(const nsAString& aHtmlFor, ErrorResult& rv);
+ void SetEvent(const nsAString& aEvent, ErrorResult& rv);
+ void GetCrossOrigin(nsAString& aResult)
+ {
+ // Null for both missing and invalid defaults is ok, since we
+ // always parse to an enum value, so we don't need an invalid
+ // default, and we _want_ the missing default to be null.
+ GetEnumAttr(nsGkAtoms::crossorigin, nullptr, aResult);
+ }
+ void SetCrossOrigin(const nsAString& aCrossOrigin, ErrorResult& aError)
+ {
+ SetOrRemoveNullableStringAttr(nsGkAtoms::crossorigin, aCrossOrigin, aError);
+ }
+ void GetIntegrity(nsAString& aIntegrity)
+ {
+ GetHTMLAttr(nsGkAtoms::integrity, aIntegrity);
+ }
+ void SetIntegrity(const nsAString& aIntegrity, ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::integrity, aIntegrity, rv);
+ }
+ bool Async();
+ void SetAsync(bool aValue, ErrorResult& rv);
+
+protected:
+ virtual ~HTMLScriptElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+ // nsScriptElement
+ virtual bool HasScriptContent() override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLScriptElement_h
diff --git a/dom/html/HTMLSelectElement.cpp b/dom/html/HTMLSelectElement.cpp
new file mode 100644
index 000000000..24ddabb65
--- /dev/null
+++ b/dom/html/HTMLSelectElement.cpp
@@ -0,0 +1,1917 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLSelectElement.h"
+
+#include "mozAutoDocUpdate.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLFormSubmission.h"
+#include "mozilla/dom/HTMLOptGroupElement.h"
+#include "mozilla/dom/HTMLOptionElement.h"
+#include "mozilla/dom/HTMLSelectElementBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentList.h"
+#include "nsError.h"
+#include "nsGkAtoms.h"
+#include "nsIComboboxControlFrame.h"
+#include "nsIDocument.h"
+#include "nsIFormControlFrame.h"
+#include "nsIForm.h"
+#include "nsIFormProcessor.h"
+#include "nsIFrame.h"
+#include "nsIListControlFrame.h"
+#include "nsISelectControlFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsMappedAttributes.h"
+#include "nsPresState.h"
+#include "nsRuleData.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStyleConsts.h"
+#include "nsTextNode.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Select)
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS(SelectState, SelectState)
+
+//----------------------------------------------------------------------
+//
+// SafeOptionListMutation
+//
+
+SafeOptionListMutation::SafeOptionListMutation(nsIContent* aSelect,
+ nsIContent* aParent,
+ nsIContent* aKid,
+ uint32_t aIndex,
+ bool aNotify)
+ : mSelect(HTMLSelectElement::FromContentOrNull(aSelect))
+ , mTopLevelMutation(false)
+ , mNeedsRebuild(false)
+{
+ if (mSelect) {
+ mTopLevelMutation = !mSelect->mMutating;
+ if (mTopLevelMutation) {
+ mSelect->mMutating = true;
+ } else {
+ // This is very unfortunate, but to handle mutation events properly,
+ // option list must be up-to-date before inserting or removing options.
+ // Fortunately this is called only if mutation event listener
+ // adds or removes options.
+ mSelect->RebuildOptionsArray(aNotify);
+ }
+ nsresult rv;
+ if (aKid) {
+ rv = mSelect->WillAddOptions(aKid, aParent, aIndex, aNotify);
+ } else {
+ rv = mSelect->WillRemoveOptions(aParent, aIndex, aNotify);
+ }
+ mNeedsRebuild = NS_FAILED(rv);
+ }
+}
+
+SafeOptionListMutation::~SafeOptionListMutation()
+{
+ if (mSelect) {
+ if (mNeedsRebuild || (mTopLevelMutation && mGuard.Mutated(1))) {
+ mSelect->RebuildOptionsArray(true);
+ }
+ if (mTopLevelMutation) {
+ mSelect->mMutating = false;
+ }
+#ifdef DEBUG
+ mSelect->VerifyOptionsArray();
+#endif
+ }
+}
+
+//----------------------------------------------------------------------
+//
+// HTMLSelectElement
+//
+
+// construction, destruction
+
+
+HTMLSelectElement::HTMLSelectElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser)
+ : nsGenericHTMLFormElementWithState(aNodeInfo),
+ mOptions(new HTMLOptionsCollection(this)),
+ mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown),
+ mIsDoneAddingChildren(!aFromParser),
+ mDisabledChanged(false),
+ mMutating(false),
+ mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT)),
+ mSelectionHasChanged(false),
+ mDefaultSelectionSet(false),
+ mCanShowInvalidUI(true),
+ mCanShowValidUI(true),
+ mNonOptionChildren(0),
+ mOptGroupCount(0),
+ mSelectedIndex(-1)
+{
+ SetHasWeirdParserInsertionMode();
+
+ // DoneAddingChildren() will be called later if it's from the parser,
+ // otherwise it is
+
+ // Set up our default state: enabled, optional, and valid.
+ AddStatesSilently(NS_EVENT_STATE_ENABLED |
+ NS_EVENT_STATE_OPTIONAL |
+ NS_EVENT_STATE_VALID);
+}
+
+HTMLSelectElement::~HTMLSelectElement()
+{
+ mOptions->DropReference();
+}
+
+// ISupports
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLSelectElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLSelectElement,
+ nsGenericHTMLFormElementWithState)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOptions)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedOptions)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLSelectElement,
+ nsGenericHTMLFormElementWithState)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedOptions)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(HTMLSelectElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLSelectElement, Element)
+
+// QueryInterface implementation for HTMLSelectElement
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLSelectElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLSelectElement,
+ nsIDOMHTMLSelectElement,
+ nsIConstraintValidation)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElementWithState)
+
+
+// nsIDOMHTMLSelectElement
+
+
+NS_IMPL_ELEMENT_CLONE(HTMLSelectElement)
+
+// nsIConstraintValidation
+NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(HTMLSelectElement)
+
+NS_IMETHODIMP
+HTMLSelectElement::SetCustomValidity(const nsAString& aError)
+{
+ nsIConstraintValidation::SetCustomValidity(aError);
+
+ UpdateState(true);
+
+ return NS_OK;
+}
+
+void
+HTMLSelectElement::GetAutocomplete(DOMString& aValue)
+{
+ const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
+
+ mAutocompleteAttrState =
+ nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue,
+ mAutocompleteAttrState);
+}
+
+NS_IMETHODIMP
+HTMLSelectElement::GetForm(nsIDOMHTMLFormElement** aForm)
+{
+ return nsGenericHTMLFormElementWithState::GetForm(aForm);
+}
+
+nsresult
+HTMLSelectElement::InsertChildAt(nsIContent* aKid,
+ uint32_t aIndex,
+ bool aNotify)
+{
+ SafeOptionListMutation safeMutation(this, this, aKid, aIndex, aNotify);
+ nsresult rv = nsGenericHTMLFormElementWithState::InsertChildAt(aKid, aIndex,
+ aNotify);
+ if (NS_FAILED(rv)) {
+ safeMutation.MutationFailed();
+ }
+ return rv;
+}
+
+void
+HTMLSelectElement::RemoveChildAt(uint32_t aIndex, bool aNotify)
+{
+ SafeOptionListMutation safeMutation(this, this, nullptr, aIndex, aNotify);
+ nsGenericHTMLFormElementWithState::RemoveChildAt(aIndex, aNotify);
+}
+
+
+
+void
+HTMLSelectElement::InsertOptionsIntoList(nsIContent* aOptions,
+ int32_t aListIndex,
+ int32_t aDepth,
+ bool aNotify)
+{
+ MOZ_ASSERT(aDepth == 0 || aDepth == 1);
+ int32_t insertIndex = aListIndex;
+
+ HTMLOptionElement* optElement = HTMLOptionElement::FromContent(aOptions);
+ if (optElement) {
+ mOptions->InsertOptionAt(optElement, insertIndex);
+ insertIndex++;
+ } else if (aDepth == 0) {
+ // If it's at the top level, then we just found out there are non-options
+ // at the top level, which will throw off the insert count
+ mNonOptionChildren++;
+
+ // Deal with optgroups
+ if (aOptions->IsHTMLElement(nsGkAtoms::optgroup)) {
+ mOptGroupCount++;
+
+ for (nsIContent* child = aOptions->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ optElement = HTMLOptionElement::FromContent(child);
+ if (optElement) {
+ mOptions->InsertOptionAt(optElement, insertIndex);
+ insertIndex++;
+ }
+ }
+ }
+ } // else ignore even if optgroup; we want to ignore nested optgroups.
+
+ // Deal with the selected list
+ if (insertIndex - aListIndex) {
+ // Fix the currently selected index
+ if (aListIndex <= mSelectedIndex) {
+ mSelectedIndex += (insertIndex - aListIndex);
+ SetSelectionChanged(true, aNotify);
+ }
+
+ // Get the frame stuff for notification. No need to flush here
+ // since if there's no frame for the select yet the select will
+ // get into the right state once it's created.
+ nsISelectControlFrame* selectFrame = nullptr;
+ nsWeakFrame weakSelectFrame;
+ bool didGetFrame = false;
+
+ // Actually select the options if the added options warrant it
+ for (int32_t i = aListIndex; i < insertIndex; i++) {
+ // Notify the frame that the option is added
+ if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) {
+ selectFrame = GetSelectFrame();
+ weakSelectFrame = do_QueryFrame(selectFrame);
+ didGetFrame = true;
+ }
+
+ if (selectFrame) {
+ selectFrame->AddOption(i);
+ }
+
+ RefPtr<HTMLOptionElement> option = Item(i);
+ if (option && option->Selected()) {
+ // Clear all other options
+ if (!HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
+ uint32_t mask = IS_SELECTED | CLEAR_ALL | SET_DISABLED | NOTIFY;
+ SetOptionsSelectedByIndex(i, i, mask);
+ }
+
+ // This is sort of a hack ... we need to notify that the option was
+ // set and change selectedIndex even though we didn't really change
+ // its value.
+ OnOptionSelected(selectFrame, i, true, false, false);
+ }
+ }
+
+ CheckSelectSomething(aNotify);
+ }
+}
+
+nsresult
+HTMLSelectElement::RemoveOptionsFromList(nsIContent* aOptions,
+ int32_t aListIndex,
+ int32_t aDepth,
+ bool aNotify)
+{
+ MOZ_ASSERT(aDepth == 0 || aDepth == 1);
+ int32_t numRemoved = 0;
+
+ HTMLOptionElement* optElement = HTMLOptionElement::FromContent(aOptions);
+ if (optElement) {
+ if (mOptions->ItemAsOption(aListIndex) != optElement) {
+ NS_ERROR("wrong option at index");
+ return NS_ERROR_UNEXPECTED;
+ }
+ mOptions->RemoveOptionAt(aListIndex);
+ numRemoved++;
+ } else if (aDepth == 0) {
+ // Yay, one less artifact at the top level.
+ mNonOptionChildren--;
+
+ // Recurse down deeper for options
+ if (mOptGroupCount && aOptions->IsHTMLElement(nsGkAtoms::optgroup)) {
+ mOptGroupCount--;
+
+ for (nsIContent* child = aOptions->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ optElement = HTMLOptionElement::FromContent(child);
+ if (optElement) {
+ if (mOptions->ItemAsOption(aListIndex) != optElement) {
+ NS_ERROR("wrong option at index");
+ return NS_ERROR_UNEXPECTED;
+ }
+ mOptions->RemoveOptionAt(aListIndex);
+ numRemoved++;
+ }
+ }
+ }
+ } // else don't check for an optgroup; we want to ignore nested optgroups
+
+ if (numRemoved) {
+ // Tell the widget we removed the options
+ nsISelectControlFrame* selectFrame = GetSelectFrame();
+ if (selectFrame) {
+ nsAutoScriptBlocker scriptBlocker;
+ for (int32_t i = aListIndex; i < aListIndex + numRemoved; ++i) {
+ selectFrame->RemoveOption(i);
+ }
+ }
+
+ // Fix the selected index
+ if (aListIndex <= mSelectedIndex) {
+ if (mSelectedIndex < (aListIndex+numRemoved)) {
+ // aListIndex <= mSelectedIndex < aListIndex+numRemoved
+ // Find a new selected index if it was one of the ones removed.
+ FindSelectedIndex(aListIndex, aNotify);
+ } else {
+ // Shift the selected index if something in front of it was removed
+ // aListIndex+numRemoved <= mSelectedIndex
+ mSelectedIndex -= numRemoved;
+ SetSelectionChanged(true, aNotify);
+ }
+ }
+
+ // Select something in case we removed the selected option on a
+ // single select
+ if (!CheckSelectSomething(aNotify) && mSelectedIndex == -1) {
+ // Update the validity state in case of we've just removed the last
+ // option.
+ UpdateValueMissingValidityState();
+
+ UpdateState(aNotify);
+ }
+ }
+
+ return NS_OK;
+}
+
+// XXXldb Doing the processing before the content nodes have been added
+// to the document (as the name of this function seems to require, and
+// as the callers do), is highly unusual. Passing around unparented
+// content to other parts of the app can make those things think the
+// options are the root content node.
+NS_IMETHODIMP
+HTMLSelectElement::WillAddOptions(nsIContent* aOptions,
+ nsIContent* aParent,
+ int32_t aContentIndex,
+ bool aNotify)
+{
+ if (this != aParent && this != aParent->GetParent()) {
+ return NS_OK;
+ }
+ int32_t level = aParent == this ? 0 : 1;
+
+ // Get the index where the options will be inserted
+ int32_t ind = -1;
+ if (!mNonOptionChildren) {
+ // If there are no artifacts, aContentIndex == ind
+ ind = aContentIndex;
+ } else {
+ // If there are artifacts, we have to get the index of the option the
+ // hard way
+ int32_t children = aParent->GetChildCount();
+
+ if (aContentIndex >= children) {
+ // If the content insert is after the end of the parent, then we want to get
+ // the next index *after* the parent and insert there.
+ ind = GetOptionIndexAfter(aParent);
+ } else {
+ // If the content insert is somewhere in the middle of the container, then
+ // we want to get the option currently at the index and insert in front of
+ // that.
+ nsIContent* currentKid = aParent->GetChildAt(aContentIndex);
+ NS_ASSERTION(currentKid, "Child not found!");
+ if (currentKid) {
+ ind = GetOptionIndexAt(currentKid);
+ } else {
+ ind = -1;
+ }
+ }
+ }
+
+ InsertOptionsIntoList(aOptions, ind, level, aNotify);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLSelectElement::WillRemoveOptions(nsIContent* aParent,
+ int32_t aContentIndex,
+ bool aNotify)
+{
+ if (this != aParent && this != aParent->GetParent()) {
+ return NS_OK;
+ }
+ int32_t level = this == aParent ? 0 : 1;
+
+ // Get the index where the options will be removed
+ nsIContent* currentKid = aParent->GetChildAt(aContentIndex);
+ if (currentKid) {
+ int32_t ind;
+ if (!mNonOptionChildren) {
+ // If there are no artifacts, aContentIndex == ind
+ ind = aContentIndex;
+ } else {
+ // If there are artifacts, we have to get the index of the option the
+ // hard way
+ ind = GetFirstOptionIndex(currentKid);
+ }
+ if (ind != -1) {
+ nsresult rv = RemoveOptionsFromList(currentKid, ind, level, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+int32_t
+HTMLSelectElement::GetOptionIndexAt(nsIContent* aOptions)
+{
+ // Search this node and below.
+ // If not found, find the first one *after* this node.
+ int32_t retval = GetFirstOptionIndex(aOptions);
+ if (retval == -1) {
+ retval = GetOptionIndexAfter(aOptions);
+ }
+
+ return retval;
+}
+
+int32_t
+HTMLSelectElement::GetOptionIndexAfter(nsIContent* aOptions)
+{
+ // - If this is the select, the next option is the last.
+ // - If not, search all the options after aOptions and up to the last option
+ // in the parent.
+ // - If it's not there, search for the first option after the parent.
+ if (aOptions == this) {
+ return Length();
+ }
+
+ int32_t retval = -1;
+
+ nsCOMPtr<nsIContent> parent = aOptions->GetParent();
+
+ if (parent) {
+ int32_t index = parent->IndexOf(aOptions);
+ int32_t count = parent->GetChildCount();
+
+ retval = GetFirstChildOptionIndex(parent, index+1, count);
+
+ if (retval == -1) {
+ retval = GetOptionIndexAfter(parent);
+ }
+ }
+
+ return retval;
+}
+
+int32_t
+HTMLSelectElement::GetFirstOptionIndex(nsIContent* aOptions)
+{
+ int32_t listIndex = -1;
+ HTMLOptionElement* optElement = HTMLOptionElement::FromContent(aOptions);
+ if (optElement) {
+ GetOptionIndex(optElement, 0, true, &listIndex);
+ return listIndex;
+ }
+
+ listIndex = GetFirstChildOptionIndex(aOptions, 0, aOptions->GetChildCount());
+
+ return listIndex;
+}
+
+int32_t
+HTMLSelectElement::GetFirstChildOptionIndex(nsIContent* aOptions,
+ int32_t aStartIndex,
+ int32_t aEndIndex)
+{
+ int32_t retval = -1;
+
+ for (int32_t i = aStartIndex; i < aEndIndex; ++i) {
+ retval = GetFirstOptionIndex(aOptions->GetChildAt(i));
+ if (retval != -1) {
+ break;
+ }
+ }
+
+ return retval;
+}
+
+nsISelectControlFrame*
+HTMLSelectElement::GetSelectFrame()
+{
+ nsIFormControlFrame* form_control_frame = GetFormControlFrame(false);
+
+ nsISelectControlFrame* select_frame = nullptr;
+
+ if (form_control_frame) {
+ select_frame = do_QueryFrame(form_control_frame);
+ }
+
+ return select_frame;
+}
+
+void
+HTMLSelectElement::Add(const HTMLOptionElementOrHTMLOptGroupElement& aElement,
+ const Nullable<HTMLElementOrLong>& aBefore,
+ ErrorResult& aRv)
+{
+ nsGenericHTMLElement& element =
+ aElement.IsHTMLOptionElement() ?
+ static_cast<nsGenericHTMLElement&>(aElement.GetAsHTMLOptionElement()) :
+ static_cast<nsGenericHTMLElement&>(aElement.GetAsHTMLOptGroupElement());
+
+ if (aBefore.IsNull()) {
+ Add(element, static_cast<nsGenericHTMLElement*>(nullptr), aRv);
+ } else if (aBefore.Value().IsHTMLElement()) {
+ Add(element, &aBefore.Value().GetAsHTMLElement(), aRv);
+ } else {
+ Add(element, aBefore.Value().GetAsLong(), aRv);
+ }
+}
+
+void
+HTMLSelectElement::Add(nsGenericHTMLElement& aElement,
+ nsGenericHTMLElement* aBefore,
+ ErrorResult& aError)
+{
+ if (!aBefore) {
+ Element::AppendChild(aElement, aError);
+ return;
+ }
+
+ // Just in case we're not the parent, get the parent of the reference
+ // element
+ nsCOMPtr<nsINode> parent = aBefore->Element::GetParentNode();
+ if (!parent || !nsContentUtils::ContentIsDescendantOf(parent, this)) {
+ // NOT_FOUND_ERR: Raised if before is not a descendant of the SELECT
+ // element.
+ aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+ return;
+ }
+
+ // If the before parameter is not null, we are equivalent to the
+ // insertBefore method on the parent of before.
+ nsCOMPtr<nsINode> refNode = aBefore;
+ parent->InsertBefore(aElement, refNode, aError);
+}
+
+NS_IMETHODIMP
+HTMLSelectElement::Add(nsIDOMHTMLElement* aElement,
+ nsIVariant* aBefore)
+{
+ uint16_t dataType;
+ nsresult rv = aBefore->GetDataType(&dataType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIContent> element = do_QueryInterface(aElement);
+ nsGenericHTMLElement* htmlElement =
+ nsGenericHTMLElement::FromContentOrNull(element);
+ if (!htmlElement) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ // aBefore is omitted, undefined or null
+ if (dataType == nsIDataType::VTYPE_EMPTY ||
+ dataType == nsIDataType::VTYPE_VOID) {
+ ErrorResult error;
+ Add(*htmlElement, (nsGenericHTMLElement*)nullptr, error);
+ return error.StealNSResult();
+ }
+
+ nsCOMPtr<nsISupports> supports;
+
+ // whether aBefore is nsIDOMHTMLElement...
+ if (NS_SUCCEEDED(aBefore->GetAsISupports(getter_AddRefs(supports)))) {
+ nsCOMPtr<nsIContent> beforeElement = do_QueryInterface(supports);
+ nsGenericHTMLElement* beforeHTMLElement =
+ nsGenericHTMLElement::FromContentOrNull(beforeElement);
+
+ NS_ENSURE_TRUE(beforeHTMLElement, NS_ERROR_DOM_SYNTAX_ERR);
+
+ ErrorResult error;
+ Add(*htmlElement, beforeHTMLElement, error);
+ return error.StealNSResult();
+ }
+
+ // otherwise, whether aBefore is long
+ int32_t index;
+ NS_ENSURE_SUCCESS(aBefore->GetAsInt32(&index), NS_ERROR_DOM_SYNTAX_ERR);
+
+ ErrorResult error;
+ Add(*htmlElement, index, error);
+ return error.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLSelectElement::Remove(int32_t aIndex)
+{
+ nsCOMPtr<nsINode> option = Item(static_cast<uint32_t>(aIndex));
+ if (!option) {
+ return NS_OK;
+ }
+
+ option->Remove();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLSelectElement::GetOptions(nsIDOMHTMLOptionsCollection** aValue)
+{
+ NS_IF_ADDREF(*aValue = GetOptions());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLSelectElement::GetType(nsAString& aType)
+{
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
+ aType.AssignLiteral("select-multiple");
+ }
+ else {
+ aType.AssignLiteral("select-one");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLSelectElement::GetLength(uint32_t* aLength)
+{
+ return mOptions->GetLength(aLength);
+}
+
+#define MAX_DYNAMIC_SELECT_LENGTH 10000
+
+NS_IMETHODIMP
+HTMLSelectElement::SetLength(uint32_t aLength)
+{
+ ErrorResult rv;
+ SetLength(aLength, rv);
+ return rv.StealNSResult();
+}
+
+void
+HTMLSelectElement::SetLength(uint32_t aLength, ErrorResult& aRv)
+{
+ uint32_t curlen = Length();
+
+ if (curlen > aLength) { // Remove extra options
+ for (uint32_t i = curlen; i > aLength; --i) {
+ MOZ_ALWAYS_SUCCEEDS(Remove(i - 1));
+ }
+ } else if (aLength > curlen) {
+ if (aLength > MAX_DYNAMIC_SELECT_LENGTH) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+
+ nsContentUtils::NameChanged(mNodeInfo, nsGkAtoms::option,
+ getter_AddRefs(nodeInfo));
+
+ nsCOMPtr<nsINode> node = NS_NewHTMLOptionElement(nodeInfo.forget());
+
+ RefPtr<nsTextNode> text = new nsTextNode(mNodeInfo->NodeInfoManager());
+
+ aRv = node->AppendChildTo(text, false);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ for (uint32_t i = curlen; i < aLength; i++) {
+ nsINode::AppendChild(*node, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (i + 1 < aLength) {
+ node = node->CloneNode(true, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ MOZ_ASSERT(node);
+ }
+ }
+ }
+}
+
+/* static */
+bool
+HTMLSelectElement::MatchSelectedOptions(nsIContent* aContent,
+ int32_t /* unused */,
+ nsIAtom* /* unused */,
+ void* /* unused*/)
+{
+ HTMLOptionElement* option = HTMLOptionElement::FromContent(aContent);
+ return option && option->Selected();
+}
+
+nsIHTMLCollection*
+HTMLSelectElement::SelectedOptions()
+{
+ if (!mSelectedOptions) {
+ mSelectedOptions = new nsContentList(this, MatchSelectedOptions, nullptr,
+ nullptr, /* deep */ true);
+ }
+ return mSelectedOptions;
+}
+
+NS_IMETHODIMP
+HTMLSelectElement::GetSelectedOptions(nsIDOMHTMLCollection** aSelectedOptions)
+{
+ NS_ADDREF(*aSelectedOptions = SelectedOptions());
+ return NS_OK;
+}
+
+//NS_IMPL_INT_ATTR(HTMLSelectElement, SelectedIndex, selectedindex)
+
+NS_IMETHODIMP
+HTMLSelectElement::GetSelectedIndex(int32_t* aValue)
+{
+ *aValue = SelectedIndex();
+
+ return NS_OK;
+}
+
+nsresult
+HTMLSelectElement::SetSelectedIndexInternal(int32_t aIndex, bool aNotify)
+{
+ int32_t oldSelectedIndex = mSelectedIndex;
+ uint32_t mask = IS_SELECTED | CLEAR_ALL | SET_DISABLED;
+ if (aNotify) {
+ mask |= NOTIFY;
+ }
+
+ SetOptionsSelectedByIndex(aIndex, aIndex, mask);
+
+ nsresult rv = NS_OK;
+ nsISelectControlFrame* selectFrame = GetSelectFrame();
+ if (selectFrame) {
+ rv = selectFrame->OnSetSelectedIndex(oldSelectedIndex, mSelectedIndex);
+ }
+
+ SetSelectionChanged(true, aNotify);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+HTMLSelectElement::SetSelectedIndex(int32_t aIndex)
+{
+ return SetSelectedIndexInternal(aIndex, true);
+}
+
+NS_IMETHODIMP
+HTMLSelectElement::GetOptionIndex(nsIDOMHTMLOptionElement* aOption,
+ int32_t aStartIndex, bool aForward,
+ int32_t* aIndex)
+{
+ nsCOMPtr<nsINode> option = do_QueryInterface(aOption);
+ return mOptions->GetOptionIndex(option->AsElement(), aStartIndex, aForward, aIndex);
+}
+
+bool
+HTMLSelectElement::IsOptionSelectedByIndex(int32_t aIndex)
+{
+ HTMLOptionElement* option = Item(static_cast<uint32_t>(aIndex));
+ return option && option->Selected();
+}
+
+void
+HTMLSelectElement::OnOptionSelected(nsISelectControlFrame* aSelectFrame,
+ int32_t aIndex,
+ bool aSelected,
+ bool aChangeOptionState,
+ bool aNotify)
+{
+ // Set the selected index
+ if (aSelected && (aIndex < mSelectedIndex || mSelectedIndex < 0)) {
+ mSelectedIndex = aIndex;
+ SetSelectionChanged(true, aNotify);
+ } else if (!aSelected && aIndex == mSelectedIndex) {
+ FindSelectedIndex(aIndex + 1, aNotify);
+ }
+
+ if (aChangeOptionState) {
+ // Tell the option to get its bad self selected
+ RefPtr<HTMLOptionElement> option = Item(static_cast<uint32_t>(aIndex));
+ if (option) {
+ option->SetSelectedInternal(aSelected, aNotify);
+ }
+ }
+
+ // Let the frame know too
+ if (aSelectFrame) {
+ aSelectFrame->OnOptionSelected(aIndex, aSelected);
+ }
+
+ UpdateSelectedOptions();
+ UpdateValueMissingValidityState();
+ UpdateState(aNotify);
+}
+
+void
+HTMLSelectElement::FindSelectedIndex(int32_t aStartIndex, bool aNotify)
+{
+ mSelectedIndex = -1;
+ SetSelectionChanged(true, aNotify);
+ uint32_t len = Length();
+ for (int32_t i = aStartIndex; i < int32_t(len); i++) {
+ if (IsOptionSelectedByIndex(i)) {
+ mSelectedIndex = i;
+ SetSelectionChanged(true, aNotify);
+ break;
+ }
+ }
+}
+
+// XXX Consider splitting this into two functions for ease of reading:
+// SelectOptionsByIndex(startIndex, endIndex, clearAll, checkDisabled)
+// startIndex, endIndex - the range of options to turn on
+// (-1, -1) will clear all indices no matter what.
+// clearAll - will clear all other options unless checkDisabled is on
+// and all the options attempted to be set are disabled
+// (note that if it is not multiple, and an option is selected,
+// everything else will be cleared regardless).
+// checkDisabled - if this is TRUE, and an option is disabled, it will not be
+// changed regardless of whether it is selected or not.
+// Generally the UI passes TRUE and JS passes FALSE.
+// (setDisabled currently is the opposite)
+// DeselectOptionsByIndex(startIndex, endIndex, checkDisabled)
+// startIndex, endIndex - the range of options to turn on
+// (-1, -1) will clear all indices no matter what.
+// checkDisabled - if this is TRUE, and an option is disabled, it will not be
+// changed regardless of whether it is selected or not.
+// Generally the UI passes TRUE and JS passes FALSE.
+// (setDisabled currently is the opposite)
+//
+// XXXbz the above comment is pretty confusing. Maybe we should actually
+// document the args to this function too, in addition to documenting what
+// things might end up looking like? In particular, pay attention to the
+// setDisabled vs checkDisabled business.
+bool
+HTMLSelectElement::SetOptionsSelectedByIndex(int32_t aStartIndex,
+ int32_t aEndIndex,
+ uint32_t aOptionsMask)
+{
+#if 0
+ printf("SetOption(%d-%d, %c, ClearAll=%c)\n", aStartIndex, aEndIndex,
+ (aOptionsMask & IS_SELECTED ? 'Y' : 'N'),
+ (aOptionsMask & CLEAR_ALL ? 'Y' : 'N'));
+#endif
+ // Don't bother if the select is disabled
+ if (!(aOptionsMask & SET_DISABLED) && IsDisabled()) {
+ return false;
+ }
+
+ // Don't bother if there are no options
+ uint32_t numItems = Length();
+ if (numItems == 0) {
+ return false;
+ }
+
+ // First, find out whether multiple items can be selected
+ bool isMultiple = Multiple();
+
+ // These variables tell us whether any options were selected
+ // or deselected.
+ bool optionsSelected = false;
+ bool optionsDeselected = false;
+
+ nsISelectControlFrame* selectFrame = nullptr;
+ bool didGetFrame = false;
+ nsWeakFrame weakSelectFrame;
+
+ if (aOptionsMask & IS_SELECTED) {
+ // Setting selectedIndex to an out-of-bounds index means -1. (HTML5)
+ if (aStartIndex < 0 || AssertedCast<uint32_t>(aStartIndex) >= numItems ||
+ aEndIndex < 0 || AssertedCast<uint32_t>(aEndIndex) >= numItems) {
+ aStartIndex = -1;
+ aEndIndex = -1;
+ }
+
+ // Only select the first value if it's not multiple
+ if (!isMultiple) {
+ aEndIndex = aStartIndex;
+ }
+
+ // This variable tells whether or not all of the options we attempted to
+ // select are disabled. If ClearAll is passed in as true, and we do not
+ // select anything because the options are disabled, we will not clear the
+ // other options. (This is to make the UI work the way one might expect.)
+ bool allDisabled = !(aOptionsMask & SET_DISABLED);
+
+ //
+ // Save a little time when clearing other options
+ //
+ int32_t previousSelectedIndex = mSelectedIndex;
+
+ //
+ // Select the requested indices
+ //
+ // If index is -1, everything will be deselected (bug 28143)
+ if (aStartIndex != -1) {
+ MOZ_ASSERT(aStartIndex >= 0);
+ MOZ_ASSERT(aEndIndex >= 0);
+ // Loop through the options and select them (if they are not disabled and
+ // if they are not already selected).
+ for (uint32_t optIndex = AssertedCast<uint32_t>(aStartIndex);
+ optIndex <= AssertedCast<uint32_t>(aEndIndex);
+ optIndex++) {
+ RefPtr<HTMLOptionElement> option = Item(optIndex);
+
+ // Ignore disabled options.
+ if (!(aOptionsMask & SET_DISABLED)) {
+ if (option && IsOptionDisabled(option)) {
+ continue;
+ }
+ allDisabled = false;
+ }
+
+ // If the index is already selected, ignore it.
+ if (option && !option->Selected()) {
+ // To notify the frame if anything gets changed. No need
+ // to flush here, if there's no frame yet we don't need to
+ // force it to be created just to notify it about a change
+ // in the select.
+ selectFrame = GetSelectFrame();
+ weakSelectFrame = do_QueryFrame(selectFrame);
+ didGetFrame = true;
+
+ OnOptionSelected(selectFrame, optIndex, true, true,
+ aOptionsMask & NOTIFY);
+ optionsSelected = true;
+ }
+ }
+ }
+
+ // Next remove all other options if single select or all is clear
+ // If index is -1, everything will be deselected (bug 28143)
+ if (((!isMultiple && optionsSelected)
+ || ((aOptionsMask & CLEAR_ALL) && !allDisabled)
+ || aStartIndex == -1)
+ && previousSelectedIndex != -1) {
+ for (uint32_t optIndex = AssertedCast<uint32_t>(previousSelectedIndex);
+ optIndex < numItems;
+ optIndex++) {
+ if (static_cast<int32_t>(optIndex) < aStartIndex ||
+ static_cast<int32_t>(optIndex) > aEndIndex) {
+ HTMLOptionElement* option = Item(optIndex);
+ // If the index is already selected, ignore it.
+ if (option && option->Selected()) {
+ if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) {
+ // To notify the frame if anything gets changed, don't
+ // flush, if the frame doesn't exist we don't need to
+ // create it just to tell it about this change.
+ selectFrame = GetSelectFrame();
+ weakSelectFrame = do_QueryFrame(selectFrame);
+
+ didGetFrame = true;
+ }
+
+ OnOptionSelected(selectFrame, optIndex, false, true,
+ aOptionsMask & NOTIFY);
+ optionsDeselected = true;
+
+ // Only need to deselect one option if not multiple
+ if (!isMultiple) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ } else {
+ // If we're deselecting, loop through all selected items and deselect
+ // any that are in the specified range.
+ for (int32_t optIndex = aStartIndex; optIndex <= aEndIndex; optIndex++) {
+ HTMLOptionElement* option = Item(optIndex);
+ if (!(aOptionsMask & SET_DISABLED) && IsOptionDisabled(option)) {
+ continue;
+ }
+
+ // If the index is already selected, ignore it.
+ if (option && option->Selected()) {
+ if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) {
+ // To notify the frame if anything gets changed, don't
+ // flush, if the frame doesn't exist we don't need to
+ // create it just to tell it about this change.
+ selectFrame = GetSelectFrame();
+ weakSelectFrame = do_QueryFrame(selectFrame);
+
+ didGetFrame = true;
+ }
+
+ OnOptionSelected(selectFrame, optIndex, false, true,
+ aOptionsMask & NOTIFY);
+ optionsDeselected = true;
+ }
+ }
+ }
+
+ // Make sure something is selected unless we were set to -1 (none)
+ if (optionsDeselected && aStartIndex != -1) {
+ optionsSelected =
+ CheckSelectSomething(aOptionsMask & NOTIFY) || optionsSelected;
+ }
+
+ // Let the caller know whether anything was changed
+ return optionsSelected || optionsDeselected;
+}
+
+NS_IMETHODIMP
+HTMLSelectElement::IsOptionDisabled(int32_t aIndex, bool* aIsDisabled)
+{
+ *aIsDisabled = false;
+ RefPtr<HTMLOptionElement> option = Item(aIndex);
+ NS_ENSURE_TRUE(option, NS_ERROR_FAILURE);
+
+ *aIsDisabled = IsOptionDisabled(option);
+ return NS_OK;
+}
+
+bool
+HTMLSelectElement::IsOptionDisabled(HTMLOptionElement* aOption)
+{
+ MOZ_ASSERT(aOption);
+ if (aOption->Disabled()) {
+ return true;
+ }
+
+ // Check for disabled optgroups
+ // If there are no artifacts, there are no optgroups
+ if (mNonOptionChildren) {
+ for (nsCOMPtr<Element> node = static_cast<nsINode*>(aOption)->GetParentElement();
+ node;
+ node = node->GetParentElement()) {
+ // If we reached the select element, we're done
+ if (node->IsHTMLElement(nsGkAtoms::select)) {
+ return false;
+ }
+
+ RefPtr<HTMLOptGroupElement> optGroupElement =
+ HTMLOptGroupElement::FromContent(node);
+
+ if (!optGroupElement) {
+ // If you put something else between you and the optgroup, you're a
+ // moron and you deserve not to have optgroup disabling work.
+ return false;
+ }
+
+ if (optGroupElement->Disabled()) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+HTMLSelectElement::GetValue(nsAString& aValue)
+{
+ DOMString value;
+ GetValue(value);
+ value.ToString(aValue);
+ return NS_OK;
+}
+
+void
+HTMLSelectElement::GetValue(DOMString& aValue)
+{
+ int32_t selectedIndex = SelectedIndex();
+ if (selectedIndex < 0) {
+ return;
+ }
+
+ RefPtr<HTMLOptionElement> option =
+ Item(static_cast<uint32_t>(selectedIndex));
+
+ if (!option) {
+ return;
+ }
+
+ DebugOnly<nsresult> rv = option->GetValue(aValue);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+NS_IMETHODIMP
+HTMLSelectElement::SetValue(const nsAString& aValue)
+{
+ uint32_t length = Length();
+
+ for (uint32_t i = 0; i < length; i++) {
+ RefPtr<HTMLOptionElement> option = Item(i);
+ if (!option) {
+ continue;
+ }
+
+ nsAutoString optionVal;
+ option->GetValue(optionVal);
+ if (optionVal.Equals(aValue)) {
+ SetSelectedIndexInternal(int32_t(i), true);
+ return NS_OK;
+ }
+ }
+ // No matching option was found.
+ SetSelectedIndexInternal(-1, true);
+ return NS_OK;
+}
+
+
+NS_IMPL_BOOL_ATTR(HTMLSelectElement, Autofocus, autofocus)
+NS_IMPL_BOOL_ATTR(HTMLSelectElement, Disabled, disabled)
+NS_IMPL_BOOL_ATTR(HTMLSelectElement, Multiple, multiple)
+NS_IMPL_STRING_ATTR(HTMLSelectElement, Name, name)
+NS_IMPL_BOOL_ATTR(HTMLSelectElement, Required, required)
+NS_IMPL_UINT_ATTR(HTMLSelectElement, Size, size)
+
+int32_t
+HTMLSelectElement::TabIndexDefault()
+{
+ return 0;
+}
+
+bool
+HTMLSelectElement::IsHTMLFocusable(bool aWithMouse,
+ bool* aIsFocusable, int32_t* aTabIndex)
+{
+ if (nsGenericHTMLFormElementWithState::IsHTMLFocusable(aWithMouse, aIsFocusable,
+ aTabIndex))
+ {
+ return true;
+ }
+
+ *aIsFocusable = !IsDisabled();
+
+ return false;
+}
+
+NS_IMETHODIMP
+HTMLSelectElement::Item(uint32_t aIndex, nsIDOMNode** aReturn)
+{
+ return mOptions->Item(aIndex, aReturn);
+}
+
+NS_IMETHODIMP
+HTMLSelectElement::NamedItem(const nsAString& aName, nsIDOMNode** aReturn)
+{
+ return mOptions->NamedItem(aName, aReturn);
+}
+
+bool
+HTMLSelectElement::CheckSelectSomething(bool aNotify)
+{
+ if (mIsDoneAddingChildren) {
+ if (mSelectedIndex < 0 && IsCombobox()) {
+ return SelectSomething(aNotify);
+ }
+ }
+ return false;
+}
+
+bool
+HTMLSelectElement::SelectSomething(bool aNotify)
+{
+ // If we're not done building the select, don't play with this yet.
+ if (!mIsDoneAddingChildren) {
+ return false;
+ }
+
+ uint32_t count;
+ GetLength(&count);
+ for (uint32_t i = 0; i < count; i++) {
+ bool disabled;
+ nsresult rv = IsOptionDisabled(i, &disabled);
+
+ if (NS_FAILED(rv) || !disabled) {
+ rv = SetSelectedIndexInternal(i, aNotify);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ UpdateValueMissingValidityState();
+ UpdateState(aNotify);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult
+HTMLSelectElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLFormElementWithState::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If there is a disabled fieldset in the parent chain, the element is now
+ // barred from constraint validation.
+ // XXXbz is this still needed now that fieldset changes always call
+ // FieldSetDisabledChanged?
+ UpdateBarredFromConstraintValidation();
+
+ // And now make sure our state is up to date
+ UpdateState(false);
+
+ return rv;
+}
+
+void
+HTMLSelectElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ nsGenericHTMLFormElementWithState::UnbindFromTree(aDeep, aNullParent);
+
+ // We might be no longer disabled because our parent chain changed.
+ // XXXbz is this still needed now that fieldset changes always call
+ // FieldSetDisabledChanged?
+ UpdateBarredFromConstraintValidation();
+
+ // And now make sure our state is up to date
+ UpdateState(false);
+}
+
+nsresult
+HTMLSelectElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify)
+{
+ if (aNotify && aName == nsGkAtoms::disabled &&
+ aNameSpaceID == kNameSpaceID_None) {
+ mDisabledChanged = true;
+ }
+
+ return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+nsresult
+HTMLSelectElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aName == nsGkAtoms::disabled) {
+ UpdateBarredFromConstraintValidation();
+ } else if (aName == nsGkAtoms::required) {
+ UpdateValueMissingValidityState();
+ } else if (aName == nsGkAtoms::autocomplete) {
+ // Clear the cached @autocomplete attribute state
+ mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
+ }
+
+ UpdateState(aNotify);
+ }
+
+ return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+nsresult
+HTMLSelectElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify)
+{
+ if (aNotify && aNameSpaceID == kNameSpaceID_None &&
+ aAttribute == nsGkAtoms::multiple) {
+ // We're changing from being a multi-select to a single-select.
+ // Make sure we only have one option selected before we do that.
+ // Note that this needs to come before we really unset the attr,
+ // since SetOptionsSelectedByIndex does some bail-out type
+ // optimization for cases when the select is not multiple that
+ // would lead to only a single option getting deselected.
+ if (mSelectedIndex >= 0) {
+ SetSelectedIndexInternal(mSelectedIndex, aNotify);
+ }
+ }
+
+ nsresult rv = nsGenericHTMLFormElementWithState::UnsetAttr(aNameSpaceID, aAttribute,
+ aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aNotify && aNameSpaceID == kNameSpaceID_None &&
+ aAttribute == nsGkAtoms::multiple) {
+ // We might have become a combobox; make sure _something_ gets
+ // selected in that case
+ CheckSelectSomething(aNotify);
+ }
+
+ return rv;
+}
+
+void
+HTMLSelectElement::DoneAddingChildren(bool aHaveNotified)
+{
+ mIsDoneAddingChildren = true;
+
+ nsISelectControlFrame* selectFrame = GetSelectFrame();
+
+ // If we foolishly tried to restore before we were done adding
+ // content, restore the rest of the options proper-like
+ if (mRestoreState) {
+ RestoreStateTo(mRestoreState);
+ mRestoreState = nullptr;
+ }
+
+ // Notify the frame
+ if (selectFrame) {
+ selectFrame->DoneAddingChildren(true);
+ }
+
+ if (!mInhibitStateRestoration) {
+ nsresult rv = GenerateStateKey();
+ if (NS_SUCCEEDED(rv)) {
+ RestoreFormControlState();
+ }
+ }
+
+ // Now that we're done, select something (if it's a single select something
+ // must be selected)
+ if (!CheckSelectSomething(false)) {
+ // If an option has @selected set, it will be selected during parsing but
+ // with an empty value. We have to make sure the select element updates it's
+ // validity state to take this into account.
+ UpdateValueMissingValidityState();
+
+ // And now make sure we update our content state too
+ UpdateState(aHaveNotified);
+ }
+
+ mDefaultSelectionSet = true;
+}
+
+bool
+HTMLSelectElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (kNameSpaceID_None == aNamespaceID) {
+ if (aAttribute == nsGkAtoms::size) {
+ return aResult.ParsePositiveIntValue(aValue);
+ } else if (aAttribute == nsGkAtoms::autocomplete) {
+ aResult.ParseAtomArray(aValue);
+ return true;
+ }
+ }
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLSelectElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ nsGenericHTMLFormElementWithState::MapImageAlignAttributeInto(aAttributes, aData);
+ nsGenericHTMLFormElementWithState::MapCommonAttributesInto(aAttributes, aData);
+}
+
+nsChangeHint
+HTMLSelectElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const
+{
+ nsChangeHint retval =
+ nsGenericHTMLFormElementWithState::GetAttributeChangeHint(aAttribute, aModType);
+ if (aAttribute == nsGkAtoms::multiple ||
+ aAttribute == nsGkAtoms::size) {
+ retval |= nsChangeHint_ReconstructFrame;
+ }
+ return retval;
+}
+
+NS_IMETHODIMP_(bool)
+HTMLSelectElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry* const map[] = {
+ sCommonAttributeMap,
+ sImageAlignAttributeMap
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+nsMapRuleToAttributesFunc
+HTMLSelectElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+bool
+HTMLSelectElement::IsDisabledForEvents(EventMessage aMessage)
+{
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
+ nsIFrame* formFrame = nullptr;
+ if (formControlFrame) {
+ formFrame = do_QueryFrame(formControlFrame);
+ }
+ return IsElementDisabledForEvents(aMessage, formFrame);
+}
+
+nsresult
+HTMLSelectElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ aVisitor.mCanHandle = false;
+ if (IsDisabledForEvents(aVisitor.mEvent->mMessage)) {
+ return NS_OK;
+ }
+
+ return nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor);
+}
+
+nsresult
+HTMLSelectElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ if (aVisitor.mEvent->mMessage == eFocus) {
+ // If the invalid UI is shown, we should show it while focused and
+ // update the invalid/valid UI.
+ mCanShowInvalidUI = !IsValid() && ShouldShowValidityUI();
+
+ // If neither invalid UI nor valid UI is shown, we shouldn't show the valid
+ // UI while focused.
+ mCanShowValidUI = ShouldShowValidityUI();
+
+ // We don't have to update NS_EVENT_STATE_MOZ_UI_INVALID nor
+ // NS_EVENT_STATE_MOZ_UI_VALID given that the states should not change.
+ } else if (aVisitor.mEvent->mMessage == eBlur) {
+ mCanShowInvalidUI = true;
+ mCanShowValidUI = true;
+
+ UpdateState(true);
+ }
+
+ return nsGenericHTMLFormElementWithState::PostHandleEvent(aVisitor);
+}
+
+EventStates
+HTMLSelectElement::IntrinsicState() const
+{
+ EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState();
+
+ if (IsCandidateForConstraintValidation()) {
+ if (IsValid()) {
+ state |= NS_EVENT_STATE_VALID;
+ } else {
+ state |= NS_EVENT_STATE_INVALID;
+
+ if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
+ (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
+ (mCanShowInvalidUI && ShouldShowValidityUI()))) {
+ state |= NS_EVENT_STATE_MOZ_UI_INVALID;
+ }
+ }
+
+ // :-moz-ui-valid applies if all the following are true:
+ // 1. The element is not focused, or had either :-moz-ui-valid or
+ // :-moz-ui-invalid applying before it was focused ;
+ // 2. The element is either valid or isn't allowed to have
+ // :-moz-ui-invalid applying ;
+ // 3. The element has no form owner or its form owner doesn't have the
+ // novalidate attribute set ;
+ // 4. The element has already been modified or the user tried to submit the
+ // form owner while invalid.
+ if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
+ (mCanShowValidUI && ShouldShowValidityUI() &&
+ (IsValid() || (state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
+ !mCanShowInvalidUI)))) {
+ state |= NS_EVENT_STATE_MOZ_UI_VALID;
+ }
+ }
+
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
+ state |= NS_EVENT_STATE_REQUIRED;
+ } else {
+ state |= NS_EVENT_STATE_OPTIONAL;
+ }
+
+ return state;
+}
+
+// nsIFormControl
+
+NS_IMETHODIMP
+HTMLSelectElement::SaveState()
+{
+ RefPtr<SelectState> state = new SelectState();
+
+ uint32_t len = Length();
+
+ for (uint32_t optIndex = 0; optIndex < len; optIndex++) {
+ HTMLOptionElement* option = Item(optIndex);
+ if (option && option->Selected()) {
+ nsAutoString value;
+ option->GetValue(value);
+ state->PutOption(optIndex, value);
+ }
+ }
+
+ nsPresState* presState = GetPrimaryPresState();
+ if (presState) {
+ presState->SetStateProperty(state);
+
+ if (mDisabledChanged) {
+ // We do not want to save the real disabled state but the disabled
+ // attribute.
+ presState->SetDisabled(HasAttr(kNameSpaceID_None, nsGkAtoms::disabled));
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+HTMLSelectElement::RestoreState(nsPresState* aState)
+{
+ // Get the presentation state object to retrieve our stuff out of.
+ nsCOMPtr<SelectState> state(
+ do_QueryInterface(aState->GetStateProperty()));
+
+ if (state) {
+ RestoreStateTo(state);
+
+ // Don't flush, if the frame doesn't exist yet it doesn't care if
+ // we're reset or not.
+ DispatchContentReset();
+ }
+
+ if (aState->IsDisabledSet()) {
+ SetDisabled(aState->GetDisabled());
+ }
+
+ return false;
+}
+
+void
+HTMLSelectElement::RestoreStateTo(SelectState* aNewSelected)
+{
+ if (!mIsDoneAddingChildren) {
+ mRestoreState = aNewSelected;
+ return;
+ }
+
+ uint32_t len = Length();
+ uint32_t mask = IS_SELECTED | CLEAR_ALL | SET_DISABLED | NOTIFY;
+
+ // First clear all
+ SetOptionsSelectedByIndex(-1, -1, mask);
+
+ // Next set the proper ones
+ for (uint32_t i = 0; i < len; i++) {
+ HTMLOptionElement* option = Item(i);
+ if (option) {
+ nsAutoString value;
+ nsresult rv = option->GetValue(value);
+ if (NS_SUCCEEDED(rv) && aNewSelected->ContainsOption(i, value)) {
+ SetOptionsSelectedByIndex(i, i, IS_SELECTED | SET_DISABLED | NOTIFY);
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+HTMLSelectElement::Reset()
+{
+ uint32_t numSelected = 0;
+
+ //
+ // Cycle through the options array and reset the options
+ //
+ uint32_t numOptions = Length();
+
+ for (uint32_t i = 0; i < numOptions; i++) {
+ RefPtr<HTMLOptionElement> option = Item(i);
+ if (option) {
+ //
+ // Reset the option to its default value
+ //
+
+ uint32_t mask = SET_DISABLED | NOTIFY;
+ if (option->DefaultSelected()) {
+ mask |= IS_SELECTED;
+ numSelected++;
+ }
+
+ SetOptionsSelectedByIndex(i, i, mask);
+ }
+ }
+
+ //
+ // If nothing was selected and it's not multiple, select something
+ //
+ if (numSelected == 0 && IsCombobox()) {
+ SelectSomething(true);
+ }
+
+ SetSelectionChanged(false, true);
+
+ //
+ // Let the frame know we were reset
+ //
+ // Don't flush, if there's no frame yet it won't care about us being
+ // reset even if we forced it to be created now.
+ //
+ DispatchContentReset();
+
+ return NS_OK;
+}
+
+static NS_DEFINE_CID(kFormProcessorCID, NS_FORMPROCESSOR_CID);
+
+NS_IMETHODIMP
+HTMLSelectElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission)
+{
+ // Disabled elements don't submit
+ if (IsDisabled()) {
+ return NS_OK;
+ }
+
+ //
+ // Get the name (if no name, no submit)
+ //
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+ if (name.IsEmpty()) {
+ return NS_OK;
+ }
+
+ //
+ // Submit
+ //
+ uint32_t len = Length();
+
+ nsAutoString mozType;
+ nsCOMPtr<nsIFormProcessor> keyGenProcessor;
+ if (GetAttr(kNameSpaceID_None, nsGkAtoms::moztype, mozType) &&
+ mozType.EqualsLiteral("-mozilla-keygen")) {
+ keyGenProcessor = do_GetService(kFormProcessorCID);
+ }
+
+ for (uint32_t optIndex = 0; optIndex < len; optIndex++) {
+ HTMLOptionElement* option = Item(optIndex);
+
+ // Don't send disabled options
+ if (!option || IsOptionDisabled(option)) {
+ continue;
+ }
+
+ if (!option->Selected()) {
+ continue;
+ }
+
+ nsString value;
+ MOZ_ALWAYS_SUCCEEDS(option->GetValue(value));
+
+ if (keyGenProcessor) {
+ nsString tmp(value);
+ if (NS_SUCCEEDED(keyGenProcessor->ProcessValue(this, name, tmp))) {
+ value = tmp;
+ }
+ }
+
+ aFormSubmission->AddNameValuePair(name, value);
+ }
+
+ return NS_OK;
+}
+
+void
+HTMLSelectElement::DispatchContentReset()
+{
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
+ if (formControlFrame) {
+ // Only dispatch content reset notification if this is a list control
+ // frame or combo box control frame.
+ if (IsCombobox()) {
+ nsIComboboxControlFrame* comboFrame = do_QueryFrame(formControlFrame);
+ if (comboFrame) {
+ comboFrame->OnContentReset();
+ }
+ } else {
+ nsIListControlFrame* listFrame = do_QueryFrame(formControlFrame);
+ if (listFrame) {
+ listFrame->OnContentReset();
+ }
+ }
+ }
+}
+
+static void
+AddOptions(nsIContent* aRoot, HTMLOptionsCollection* aArray)
+{
+ for (nsIContent* child = aRoot->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ HTMLOptionElement* opt = HTMLOptionElement::FromContent(child);
+ if (opt) {
+ aArray->AppendOption(opt);
+ } else if (child->IsHTMLElement(nsGkAtoms::optgroup)) {
+ for (nsIContent* grandchild = child->GetFirstChild();
+ grandchild;
+ grandchild = grandchild->GetNextSibling()) {
+ opt = HTMLOptionElement::FromContent(grandchild);
+ if (opt) {
+ aArray->AppendOption(opt);
+ }
+ }
+ }
+ }
+}
+
+void
+HTMLSelectElement::RebuildOptionsArray(bool aNotify)
+{
+ mOptions->Clear();
+ AddOptions(this, mOptions);
+ FindSelectedIndex(0, aNotify);
+}
+
+bool
+HTMLSelectElement::IsValueMissing()
+{
+ if (!Required()) {
+ return false;
+ }
+
+ uint32_t length = Length();
+
+ for (uint32_t i = 0; i < length; ++i) {
+ RefPtr<HTMLOptionElement> option = Item(i);
+ if (!option->Selected()) {
+ continue;
+ }
+
+ if (IsOptionDisabled(option)) {
+ continue;
+ }
+
+ nsAutoString value;
+ MOZ_ALWAYS_SUCCEEDS(option->GetValue(value));
+ if (!value.IsEmpty()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void
+HTMLSelectElement::UpdateValueMissingValidityState()
+{
+ SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
+}
+
+nsresult
+HTMLSelectElement::GetValidationMessage(nsAString& aValidationMessage,
+ ValidityStateType aType)
+{
+ switch (aType) {
+ case VALIDITY_STATE_VALUE_MISSING: {
+ nsXPIDLString message;
+ nsresult rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationSelectMissing",
+ message);
+ aValidationMessage = message;
+ return rv;
+ }
+ default: {
+ return nsIConstraintValidation::GetValidationMessage(aValidationMessage, aType);
+ }
+ }
+}
+
+#ifdef DEBUG
+
+void
+HTMLSelectElement::VerifyOptionsArray()
+{
+ int32_t index = 0;
+ for (nsIContent* child = nsINode::GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ HTMLOptionElement* opt = HTMLOptionElement::FromContent(child);
+ if (opt) {
+ NS_ASSERTION(opt == mOptions->ItemAsOption(index++),
+ "Options collection broken");
+ } else if (child->IsHTMLElement(nsGkAtoms::optgroup)) {
+ for (nsIContent* grandchild = child->GetFirstChild();
+ grandchild;
+ grandchild = grandchild->GetNextSibling()) {
+ opt = HTMLOptionElement::FromContent(grandchild);
+ if (opt) {
+ NS_ASSERTION(opt == mOptions->ItemAsOption(index++),
+ "Options collection broken");
+ }
+ }
+ }
+ }
+}
+
+#endif
+
+void
+HTMLSelectElement::UpdateBarredFromConstraintValidation()
+{
+ SetBarredFromConstraintValidation(IsDisabled());
+}
+
+void
+HTMLSelectElement::FieldSetDisabledChanged(bool aNotify)
+{
+ UpdateBarredFromConstraintValidation();
+
+ nsGenericHTMLFormElementWithState::FieldSetDisabledChanged(aNotify);
+}
+
+void
+HTMLSelectElement::SetSelectionChanged(bool aValue, bool aNotify)
+{
+ if (!mDefaultSelectionSet) {
+ return;
+ }
+
+ UpdateSelectedOptions();
+
+ bool previousSelectionChangedValue = mSelectionHasChanged;
+ mSelectionHasChanged = aValue;
+
+ if (mSelectionHasChanged != previousSelectionChangedValue) {
+ UpdateState(aNotify);
+ }
+}
+
+void
+HTMLSelectElement::UpdateSelectedOptions()
+{
+ if (mSelectedOptions) {
+ mSelectedOptions->SetDirty();
+ }
+}
+
+bool
+HTMLSelectElement::OpenInParentProcess()
+{
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
+ nsIComboboxControlFrame* comboFrame = do_QueryFrame(formControlFrame);
+ if (comboFrame) {
+ return comboFrame->IsOpenInParentProcess();
+ }
+
+ return false;
+}
+
+void
+HTMLSelectElement::SetOpenInParentProcess(bool aVal)
+{
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
+ nsIComboboxControlFrame* comboFrame = do_QueryFrame(formControlFrame);
+ if (comboFrame) {
+ comboFrame->SetOpenInParentProcess(aVal);
+ }
+}
+
+JSObject*
+HTMLSelectElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLSelectElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLSelectElement.h b/dom/html/HTMLSelectElement.h
new file mode 100644
index 000000000..d7e4350b4
--- /dev/null
+++ b/dom/html/HTMLSelectElement.h
@@ -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/. */
+#ifndef mozilla_dom_HTMLSelectElement_h
+#define mozilla_dom_HTMLSelectElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIDOMHTMLSelectElement.h"
+#include "nsIConstraintValidation.h"
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/HTMLOptionsCollection.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCheapSets.h"
+#include "nsCOMPtr.h"
+#include "nsError.h"
+#include "mozilla/dom/HTMLFormElement.h"
+#include "nsContentUtils.h"
+
+class nsContentList;
+class nsIDOMHTMLOptionElement;
+class nsIHTMLCollection;
+class nsISelectControlFrame;
+class nsPresState;
+
+namespace mozilla {
+
+class EventChainPostVisitor;
+class EventChainPreVisitor;
+
+namespace dom {
+
+class HTMLFormSubmission;
+class HTMLSelectElement;
+
+#define NS_SELECT_STATE_IID \
+{ /* 4db54c7c-d159-455f-9d8e-f60ee466dbf3 */ \
+ 0x4db54c7c, \
+ 0xd159, \
+ 0x455f, \
+ {0x9d, 0x8e, 0xf6, 0x0e, 0xe4, 0x66, 0xdb, 0xf3} \
+}
+
+/**
+ * The restore state used by select
+ */
+class SelectState : public nsISupports
+{
+public:
+ SelectState()
+ {
+ }
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_SELECT_STATE_IID)
+ NS_DECL_ISUPPORTS
+
+ void PutOption(int32_t aIndex, const nsAString& aValue)
+ {
+ // If the option is empty, store the index. If not, store the value.
+ if (aValue.IsEmpty()) {
+ mIndices.Put(aIndex);
+ } else {
+ mValues.Put(aValue);
+ }
+ }
+
+ bool ContainsOption(int32_t aIndex, const nsAString& aValue)
+ {
+ return mValues.Contains(aValue) || mIndices.Contains(aIndex);
+ }
+
+private:
+ virtual ~SelectState()
+ {
+ }
+
+ nsCheapSet<nsStringHashKey> mValues;
+ nsCheapSet<nsUint32HashKey> mIndices;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(SelectState, NS_SELECT_STATE_IID)
+
+class MOZ_STACK_CLASS SafeOptionListMutation
+{
+public:
+ /**
+ * @param aSelect The select element which option list is being mutated.
+ * Can be null.
+ * @param aParent The content object which is being mutated.
+ * @param aKid If not null, a new child element is being inserted to
+ * aParent. Otherwise a child element will be removed.
+ * @param aIndex The index of the content object in the parent.
+ */
+ SafeOptionListMutation(nsIContent* aSelect, nsIContent* aParent,
+ nsIContent* aKid, uint32_t aIndex, bool aNotify);
+ ~SafeOptionListMutation();
+ void MutationFailed() { mNeedsRebuild = true; }
+private:
+ static void* operator new(size_t) CPP_THROW_NEW { return 0; }
+ static void operator delete(void*, size_t) {}
+ /** The select element which option list is being mutated. */
+ RefPtr<HTMLSelectElement> mSelect;
+ /** true if the current mutation is the first one in the stack. */
+ bool mTopLevelMutation;
+ /** true if it is known that the option list must be recreated. */
+ bool mNeedsRebuild;
+ /** Option list must be recreated if more than one mutation is detected. */
+ nsMutationGuard mGuard;
+};
+
+
+/**
+ * Implementation of &lt;select&gt;
+ */
+class HTMLSelectElement final : public nsGenericHTMLFormElementWithState,
+ public nsIDOMHTMLSelectElement,
+ public nsIConstraintValidation
+{
+public:
+ /**
+ * IS_SELECTED whether to set the option(s) to true or false
+ *
+ * CLEAR_ALL whether to clear all other options (for example, if you
+ * are normal-clicking on the current option)
+ *
+ * SET_DISABLED whether it is permissible to set disabled options
+ * (for JavaScript)
+ *
+ * NOTIFY whether to notify frames and such
+ */
+ enum OptionType {
+ IS_SELECTED = 1 << 0,
+ CLEAR_ALL = 1 << 1,
+ SET_DISABLED = 1 << 2,
+ NOTIFY = 1 << 3
+ };
+
+ using nsIConstraintValidation::GetValidationMessage;
+
+ explicit HTMLSelectElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser = NOT_FROM_PARSER);
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLSelectElement, select)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual int32_t TabIndexDefault() override;
+
+ // Element
+ virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override
+ {
+ return true;
+ }
+
+ // nsIDOMHTMLSelectElement
+ NS_DECL_NSIDOMHTMLSELECTELEMENT
+
+ // WebIdl HTMLSelectElement
+ bool Autofocus() const
+ {
+ return GetBoolAttr(nsGkAtoms::autofocus);
+ }
+ void SetAutofocus(bool aVal, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::autofocus, aVal, aRv);
+ }
+ void GetAutocomplete(DOMString& aValue);
+ void SetAutocomplete(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::autocomplete, aValue, aRv);
+ }
+ bool Disabled() const
+ {
+ return GetBoolAttr(nsGkAtoms::disabled);
+ }
+ void SetDisabled(bool aVal, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::disabled, aVal, aRv);
+ }
+ HTMLFormElement* GetForm() const
+ {
+ return nsGenericHTMLFormElementWithState::GetForm();
+ }
+ bool Multiple() const
+ {
+ return GetBoolAttr(nsGkAtoms::multiple);
+ }
+ void SetMultiple(bool aVal, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::multiple, aVal, aRv);
+ }
+ // Uses XPCOM GetName.
+ void SetName(const nsAString& aName, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::name, aName, aRv);
+ }
+ bool Required() const
+ {
+ return GetBoolAttr(nsGkAtoms::required);
+ }
+ void SetRequired(bool aVal, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::required, aVal, aRv);
+ }
+ uint32_t Size() const
+ {
+ return GetUnsignedIntAttr(nsGkAtoms::size, 0);
+ }
+ void SetSize(uint32_t aSize, ErrorResult& aRv)
+ {
+ SetUnsignedIntAttr(nsGkAtoms::size, aSize, 0, aRv);
+ }
+
+ // Uses XPCOM GetType.
+
+ HTMLOptionsCollection* Options() const
+ {
+ return mOptions;
+ }
+ uint32_t Length() const
+ {
+ return mOptions->Length();
+ }
+ void SetLength(uint32_t aLength, ErrorResult& aRv);
+ Element* IndexedGetter(uint32_t aIdx, bool& aFound) const
+ {
+ return mOptions->IndexedGetter(aIdx, aFound);
+ }
+ HTMLOptionElement* Item(uint32_t aIdx) const
+ {
+ return mOptions->ItemAsOption(aIdx);
+ }
+ HTMLOptionElement* NamedItem(const nsAString& aName) const
+ {
+ return mOptions->GetNamedItem(aName);
+ }
+ void Add(const HTMLOptionElementOrHTMLOptGroupElement& aElement,
+ const Nullable<HTMLElementOrLong>& aBefore,
+ ErrorResult& aRv);
+ // Uses XPCOM Remove.
+ void IndexedSetter(uint32_t aIndex, HTMLOptionElement* aOption,
+ ErrorResult& aRv)
+ {
+ mOptions->IndexedSetter(aIndex, aOption, aRv);
+ }
+
+ static bool MatchSelectedOptions(nsIContent* aContent, int32_t, nsIAtom*,
+ void*);
+
+ nsIHTMLCollection* SelectedOptions();
+
+ int32_t SelectedIndex() const
+ {
+ return mSelectedIndex;
+ }
+ void SetSelectedIndex(int32_t aIdx, ErrorResult& aRv)
+ {
+ aRv = SetSelectedIndexInternal(aIdx, true);
+ }
+ void GetValue(DOMString& aValue);
+ // Uses XPCOM SetValue.
+
+ // nsIConstraintValidation::WillValidate is fine.
+ // nsIConstraintValidation::Validity() is fine.
+ // nsIConstraintValidation::GetValidationMessage() is fine.
+ // nsIConstraintValidation::CheckValidity() is fine.
+ using nsIConstraintValidation::CheckValidity;
+ using nsIConstraintValidation::ReportValidity;
+ // nsIConstraintValidation::SetCustomValidity() is fine.
+
+ using nsINode::Remove;
+
+
+ // nsINode
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // nsIContent
+ virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) override;
+ virtual nsresult PostHandleEvent(
+ EventChainPostVisitor& aVisitor) override;
+
+ virtual bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t* aTabIndex) override;
+ virtual nsresult InsertChildAt(nsIContent* aKid, uint32_t aIndex,
+ bool aNotify) override;
+ virtual void RemoveChildAt(uint32_t aIndex, bool aNotify) override;
+
+ // Overriden nsIFormControl methods
+ NS_IMETHOD_(uint32_t) GetType() const override { return NS_FORM_SELECT; }
+ NS_IMETHOD Reset() override;
+ NS_IMETHOD SubmitNamesValues(HTMLFormSubmission* aFormSubmission) override;
+ NS_IMETHOD SaveState() override;
+ virtual bool RestoreState(nsPresState* aState) override;
+ virtual bool IsDisabledForEvents(EventMessage aMessage) override;
+
+ virtual void FieldSetDisabledChanged(bool aNotify) override;
+
+ EventStates IntrinsicState() const override;
+
+ /**
+ * To be called when stuff is added under a child of the select--but *before*
+ * they are actually added.
+ *
+ * @param aOptions the content that was added (usually just an option, but
+ * could be an optgroup node with many child options)
+ * @param aParent the parent the options were added to (could be an optgroup)
+ * @param aContentIndex the index where the options are being added within the
+ * parent (if the parent is an optgroup, the index within the optgroup)
+ */
+ NS_IMETHOD WillAddOptions(nsIContent* aOptions,
+ nsIContent* aParent,
+ int32_t aContentIndex,
+ bool aNotify);
+
+ /**
+ * To be called when stuff is removed under a child of the select--but
+ * *before* they are actually removed.
+ *
+ * @param aParent the parent the option(s) are being removed from
+ * @param aContentIndex the index of the option(s) within the parent (if the
+ * parent is an optgroup, the index within the optgroup)
+ */
+ NS_IMETHOD WillRemoveOptions(nsIContent* aParent,
+ int32_t aContentIndex,
+ bool aNotify);
+
+ /**
+ * Checks whether an option is disabled (even if it's part of an optgroup)
+ *
+ * @param aIndex the index of the option to check
+ * @return whether the option is disabled
+ */
+ NS_IMETHOD IsOptionDisabled(int32_t aIndex,
+ bool* aIsDisabled);
+ bool IsOptionDisabled(HTMLOptionElement* aOption);
+
+ /**
+ * Sets multiple options (or just sets startIndex if select is single)
+ * and handles notifications and cleanup and everything under the sun.
+ * When this method exits, the select will be in a consistent state. i.e.
+ * if you set the last option to false, it will select an option anyway.
+ *
+ * @param aStartIndex the first index to set
+ * @param aEndIndex the last index to set (set same as first index for one
+ * option)
+ * @param aOptionsMask determines whether to set, clear all or disable
+ * options and whether frames are to be notified of such.
+ * @return whether any options were actually changed
+ */
+ bool SetOptionsSelectedByIndex(int32_t aStartIndex,
+ int32_t aEndIndex,
+ uint32_t aOptionsMask);
+
+ /**
+ * Finds the index of a given option element
+ *
+ * @param aOption the option to get the index of
+ * @param aStartIndex the index to start looking at
+ * @param aForward TRUE to look forward, FALSE to look backward
+ * @return the option index
+ */
+ NS_IMETHOD GetOptionIndex(nsIDOMHTMLOptionElement* aOption,
+ int32_t aStartIndex,
+ bool aForward,
+ int32_t* aIndex);
+
+ /**
+ * Called when an attribute is about to be changed
+ */
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep, bool aNullParent) override;
+ virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify) override;
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify) override;
+
+ virtual void DoneAddingChildren(bool aHaveNotified) override;
+ virtual bool IsDoneAddingChildren() override {
+ return mIsDoneAddingChildren;
+ }
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLSelectElement,
+ nsGenericHTMLFormElementWithState)
+
+ HTMLOptionsCollection* GetOptions()
+ {
+ return mOptions;
+ }
+
+ // nsIConstraintValidation
+ nsresult GetValidationMessage(nsAString& aValidationMessage,
+ ValidityStateType aType) override;
+
+ void UpdateValueMissingValidityState();
+ /**
+ * Insert aElement before the node given by aBefore
+ */
+ void Add(nsGenericHTMLElement& aElement, nsGenericHTMLElement* aBefore,
+ ErrorResult& aError);
+ void Add(nsGenericHTMLElement& aElement, int32_t aIndex, ErrorResult& aError)
+ {
+ // If item index is out of range, insert to last.
+ // (since beforeElement becomes null, it is inserted to last)
+ nsIContent* beforeContent = mOptions->GetElementAt(aIndex);
+ return Add(aElement, nsGenericHTMLElement::FromContentOrNull(beforeContent),
+ aError);
+ }
+
+ /**
+ * Is this a combobox?
+ */
+ bool IsCombobox() const
+ {
+ return !Multiple() && Size() <= 1;
+ }
+
+ bool OpenInParentProcess();
+ void SetOpenInParentProcess(bool aVal);
+
+protected:
+ virtual ~HTMLSelectElement();
+
+ friend class SafeOptionListMutation;
+
+ // Helper Methods
+ /**
+ * Check whether the option specified by the index is selected
+ * @param aIndex the index
+ * @return whether the option at the index is selected
+ */
+ bool IsOptionSelectedByIndex(int32_t aIndex);
+ /**
+ * Starting with (and including) aStartIndex, find the first selected index
+ * and set mSelectedIndex to it.
+ * @param aStartIndex the index to start with
+ */
+ void FindSelectedIndex(int32_t aStartIndex, bool aNotify);
+ /**
+ * Select some option if possible (generally the first non-disabled option).
+ * @return true if something was selected, false otherwise
+ */
+ bool SelectSomething(bool aNotify);
+ /**
+ * Call SelectSomething(), but only if nothing is selected
+ * @see SelectSomething()
+ * @return true if something was selected, false otherwise
+ */
+ bool CheckSelectSomething(bool aNotify);
+ /**
+ * Called to trigger notifications of frames and fixing selected index
+ *
+ * @param aSelectFrame the frame for this content (could be null)
+ * @param aIndex the index that was selected or deselected
+ * @param aSelected whether the index was selected or deselected
+ * @param aChangeOptionState if false, don't do anything to the
+ * HTMLOptionElement at aIndex. If true, change
+ * its selected state to aSelected.
+ * @param aNotify whether to notify the style system and such
+ */
+ void OnOptionSelected(nsISelectControlFrame* aSelectFrame,
+ int32_t aIndex,
+ bool aSelected,
+ bool aChangeOptionState,
+ bool aNotify);
+ /**
+ * Restore state to a particular state string (representing the options)
+ * @param aNewSelected the state string to restore to
+ */
+ void RestoreStateTo(SelectState* aNewSelected);
+
+ // Adding options
+ /**
+ * Insert option(s) into the options[] array and perform notifications
+ * @param aOptions the option or optgroup being added
+ * @param aListIndex the index to start adding options into the list at
+ * @param aDepth the depth of aOptions (1=direct child of select ...)
+ */
+ void InsertOptionsIntoList(nsIContent* aOptions,
+ int32_t aListIndex,
+ int32_t aDepth,
+ bool aNotify);
+ /**
+ * Remove option(s) from the options[] array
+ * @param aOptions the option or optgroup being added
+ * @param aListIndex the index to start removing options from the list at
+ * @param aDepth the depth of aOptions (1=direct child of select ...)
+ */
+ nsresult RemoveOptionsFromList(nsIContent* aOptions,
+ int32_t aListIndex,
+ int32_t aDepth,
+ bool aNotify);
+
+ // nsIConstraintValidation
+ void UpdateBarredFromConstraintValidation();
+ bool IsValueMissing();
+
+ /**
+ * Get the index of the first option at, under or following the content in
+ * the select, or length of options[] if none are found
+ * @param aOptions the content
+ * @return the index of the first option
+ */
+ int32_t GetOptionIndexAt(nsIContent* aOptions);
+ /**
+ * Get the next option following the content in question (not at or under)
+ * (this could include siblings of the current content or siblings of the
+ * parent or children of siblings of the parent).
+ * @param aOptions the content
+ * @return the index of the next option after the content
+ */
+ int32_t GetOptionIndexAfter(nsIContent* aOptions);
+ /**
+ * Get the first option index at or under the content in question.
+ * @param aOptions the content
+ * @return the index of the first option at or under the content
+ */
+ int32_t GetFirstOptionIndex(nsIContent* aOptions);
+ /**
+ * Get the first option index under the content in question, within the
+ * range specified.
+ * @param aOptions the content
+ * @param aStartIndex the first child to look at
+ * @param aEndIndex the child *after* the last child to look at
+ * @return the index of the first option at or under the content
+ */
+ int32_t GetFirstChildOptionIndex(nsIContent* aOptions,
+ int32_t aStartIndex,
+ int32_t aEndIndex);
+
+ /**
+ * Get the frame as an nsISelectControlFrame (MAY RETURN nullptr)
+ * @return the select frame, or null
+ */
+ nsISelectControlFrame* GetSelectFrame();
+
+ /**
+ * Helper method for dispatching ContentReset notifications to list
+ * and combo box frames.
+ */
+ void DispatchContentReset();
+
+ /**
+ * Rebuilds the options array from scratch as a fallback in error cases.
+ */
+ void RebuildOptionsArray(bool aNotify);
+
+#ifdef DEBUG
+ void VerifyOptionsArray();
+#endif
+
+ nsresult SetSelectedIndexInternal(int32_t aIndex, bool aNotify);
+
+ void SetSelectionChanged(bool aValue, bool aNotify);
+
+ /**
+ * Marks the selectedOptions list as dirty, so that it'll populate itself
+ * again.
+ */
+ void UpdateSelectedOptions();
+
+ /**
+ * Return whether an element should have a validity UI.
+ * (with :-moz-ui-invalid and :-moz-ui-valid pseudo-classes).
+ *
+ * @return Whether the element should have a validity UI.
+ */
+ bool ShouldShowValidityUI() const {
+ /**
+ * Always show the validity UI if the form has already tried to be submitted
+ * but was invalid.
+ *
+ * Otherwise, show the validity UI if the selection has been changed.
+ */
+ if (mForm && mForm->HasEverTriedInvalidSubmit()) {
+ return true;
+ }
+
+ return mSelectionHasChanged;
+ }
+
+ /** The options[] array */
+ RefPtr<HTMLOptionsCollection> mOptions;
+ nsContentUtils::AutocompleteAttrState mAutocompleteAttrState;
+ /** false if the parser is in the middle of adding children. */
+ bool mIsDoneAddingChildren;
+ /** true if our disabled state has changed from the default **/
+ bool mDisabledChanged;
+ /** true if child nodes are being added or removed.
+ * Used by SafeOptionListMutation.
+ */
+ bool mMutating;
+ /**
+ * True if DoneAddingChildren will get called but shouldn't restore state.
+ */
+ bool mInhibitStateRestoration;
+ /**
+ * True if the selection has changed since the element's creation.
+ */
+ bool mSelectionHasChanged;
+ /**
+ * True if the default selected option has been set.
+ */
+ bool mDefaultSelectionSet;
+ /**
+ * True if :-moz-ui-invalid can be shown.
+ */
+ bool mCanShowInvalidUI;
+ /**
+ * True if :-moz-ui-valid can be shown.
+ */
+ bool mCanShowValidUI;
+
+ /** The number of non-options as children of the select */
+ uint32_t mNonOptionChildren;
+ /** The number of optgroups anywhere under the select */
+ uint32_t mOptGroupCount;
+ /**
+ * The current selected index for selectedIndex (will be the first selected
+ * index if multiple are selected)
+ */
+ int32_t mSelectedIndex;
+ /**
+ * The temporary restore state in case we try to restore before parser is
+ * done adding options
+ */
+ nsCOMPtr<SelectState> mRestoreState;
+
+ /**
+ * The live list of selected options.
+ */
+ RefPtr<nsContentList> mSelectedOptions;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLSelectElement_h
diff --git a/dom/html/HTMLShadowElement.cpp b/dom/html/HTMLShadowElement.cpp
new file mode 100644
index 000000000..8824c2875
--- /dev/null
+++ b/dom/html/HTMLShadowElement.cpp
@@ -0,0 +1,373 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/ShadowRoot.h"
+
+#include "ChildIterator.h"
+#include "nsContentUtils.h"
+#include "nsDocument.h"
+#include "mozilla/dom/HTMLShadowElement.h"
+#include "mozilla/dom/HTMLUnknownElement.h"
+#include "mozilla/dom/HTMLShadowElementBinding.h"
+
+// Expand NS_IMPL_NS_NEW_HTML_ELEMENT(Shadow) to add check for web components
+// being enabled.
+nsGenericHTMLElement*
+NS_NewHTMLShadowElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ mozilla::dom::FromParser aFromParser)
+{
+ // When this check is removed, remove the nsDocument.h and
+ // HTMLUnknownElement.h includes. Also remove nsINode::IsHTMLShadowElement.
+ //
+ // We have to jump through some hoops to be able to produce both NodeInfo* and
+ // already_AddRefed<NodeInfo>& for our callees.
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo);
+ if (!nsDocument::IsWebComponentsEnabled(nodeInfo)) {
+ already_AddRefed<mozilla::dom::NodeInfo> nodeInfoArg(nodeInfo.forget());
+ return new mozilla::dom::HTMLUnknownElement(nodeInfoArg);
+ }
+
+ already_AddRefed<mozilla::dom::NodeInfo> nodeInfoArg(nodeInfo.forget());
+ return new mozilla::dom::HTMLShadowElement(nodeInfoArg);
+}
+
+using namespace mozilla::dom;
+
+HTMLShadowElement::HTMLShadowElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo), mIsInsertionPoint(false)
+{
+}
+
+HTMLShadowElement::~HTMLShadowElement()
+{
+ if (mProjectedShadow) {
+ mProjectedShadow->RemoveMutationObserver(this);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLShadowElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLShadowElement,
+ nsGenericHTMLElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProjectedShadow)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLShadowElement,
+ nsGenericHTMLElement)
+ if (tmp->mProjectedShadow) {
+ tmp->mProjectedShadow->RemoveMutationObserver(tmp);
+ tmp->mProjectedShadow = nullptr;
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(HTMLShadowElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLShadowElement, Element)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLShadowElement)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLShadowElement)
+
+JSObject*
+HTMLShadowElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLShadowElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+HTMLShadowElement::SetProjectedShadow(ShadowRoot* aProjectedShadow)
+{
+ if (mProjectedShadow) {
+ mProjectedShadow->RemoveMutationObserver(this);
+
+ // The currently projected ShadowRoot is going away,
+ // thus the destination insertion points need to be updated.
+ ExplicitChildIterator childIterator(mProjectedShadow);
+ for (nsIContent* content = childIterator.GetNextChild();
+ content;
+ content = childIterator.GetNextChild()) {
+ ShadowRoot::RemoveDestInsertionPoint(this, content->DestInsertionPoints());
+ }
+ }
+
+ mProjectedShadow = aProjectedShadow;
+ if (mProjectedShadow) {
+ // A new ShadowRoot is being projected, thus its explcit
+ // children will be distributed to this shadow insertion point.
+ ExplicitChildIterator childIterator(mProjectedShadow);
+ for (nsIContent* content = childIterator.GetNextChild();
+ content;
+ content = childIterator.GetNextChild()) {
+ content->DestInsertionPoints().AppendElement(this);
+ }
+
+ // Watch for mutations on the projected shadow because
+ // it affects the nodes that are distributed to this shadow
+ // insertion point.
+ mProjectedShadow->AddMutationObserver(this);
+ }
+}
+
+static bool
+IsInFallbackContent(nsIContent* aContent)
+{
+ nsINode* parentNode = aContent->GetParentNode();
+ while (parentNode) {
+ if (parentNode->IsHTMLElement(nsGkAtoms::content)) {
+ return true;
+ }
+ parentNode = parentNode->GetParentNode();
+ }
+
+ return false;
+}
+
+nsresult
+HTMLShadowElement::BindToTree(nsIDocument* aDocument,
+ nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ RefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow();
+
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ShadowRoot* containingShadow = GetContainingShadow();
+ if (containingShadow && !oldContainingShadow) {
+ // Keep track of all descendant <shadow> elements in tree order so
+ // that when the current shadow insertion point is removed, the next
+ // one can be found quickly.
+ TreeOrderComparator comparator;
+ containingShadow->ShadowDescendants().InsertElementSorted(this, comparator);
+
+ if (containingShadow->ShadowDescendants()[0] != this) {
+ // Only the first <shadow> (in tree order) of a ShadowRoot can be an insertion point.
+ return NS_OK;
+ }
+
+ if (IsInFallbackContent(this)) {
+ // If the first shadow element in tree order is invalid (in fallback content),
+ // the containing ShadowRoot will not have a shadow insertion point.
+ containingShadow->SetShadowElement(nullptr);
+ } else {
+ mIsInsertionPoint = true;
+ containingShadow->SetShadowElement(this);
+ }
+
+ containingShadow->SetInsertionPointChanged();
+ }
+
+ if (mIsInsertionPoint && containingShadow) {
+ // Propagate BindToTree calls to projected shadow root children.
+ ShadowRoot* projectedShadow = containingShadow->GetOlderShadowRoot();
+ if (projectedShadow) {
+ projectedShadow->SetIsComposedDocParticipant(IsInComposedDoc());
+
+ for (nsIContent* child = projectedShadow->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ rv = child->BindToTree(nullptr, projectedShadow,
+ projectedShadow->GetBindingParent(),
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+HTMLShadowElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ RefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow();
+
+ if (mIsInsertionPoint && oldContainingShadow) {
+ // Propagate UnbindFromTree call to previous projected shadow
+ // root children.
+ ShadowRoot* projectedShadow = oldContainingShadow->GetOlderShadowRoot();
+ if (projectedShadow) {
+ for (nsIContent* child = projectedShadow->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ child->UnbindFromTree(true, false);
+ }
+
+ projectedShadow->SetIsComposedDocParticipant(false);
+ }
+ }
+
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+
+ if (oldContainingShadow && !GetContainingShadow() && mIsInsertionPoint) {
+ nsTArray<HTMLShadowElement*>& shadowDescendants =
+ oldContainingShadow->ShadowDescendants();
+ shadowDescendants.RemoveElement(this);
+ oldContainingShadow->SetShadowElement(nullptr);
+
+ // Find the next shadow insertion point.
+ if (shadowDescendants.Length() > 0 &&
+ !IsInFallbackContent(shadowDescendants[0])) {
+ oldContainingShadow->SetShadowElement(shadowDescendants[0]);
+ }
+
+ oldContainingShadow->SetInsertionPointChanged();
+
+ mIsInsertionPoint = false;
+ }
+}
+
+void
+HTMLShadowElement::DistributeSingleNode(nsIContent* aContent)
+{
+ if (aContent->DestInsertionPoints().Contains(this)) {
+ // Node has already been distrbuted this this node,
+ // we are done.
+ return;
+ }
+
+ aContent->DestInsertionPoints().AppendElement(this);
+
+ // Handle the case where the shadow element is a child of
+ // a node with a ShadowRoot. The nodes that have been distributed to
+ // this shadow insertion point will need to be reprojected into the
+ // insertion points of the parent's ShadowRoot.
+ ShadowRoot* parentShadowRoot = GetParent()->GetShadowRoot();
+ if (parentShadowRoot) {
+ parentShadowRoot->DistributeSingleNode(aContent);
+ return;
+ }
+
+ // Handle the case where the parent of this shadow element is a ShadowRoot
+ // that is projected into a shadow insertion point in the younger ShadowRoot.
+ ShadowRoot* containingShadow = GetContainingShadow();
+ ShadowRoot* youngerShadow = containingShadow->GetYoungerShadowRoot();
+ if (youngerShadow && GetParent() == containingShadow) {
+ HTMLShadowElement* youngerShadowElement = youngerShadow->GetShadowElement();
+ if (youngerShadowElement) {
+ youngerShadowElement->DistributeSingleNode(aContent);
+ }
+ }
+}
+
+void
+HTMLShadowElement::RemoveDistributedNode(nsIContent* aContent)
+{
+ ShadowRoot::RemoveDestInsertionPoint(this, aContent->DestInsertionPoints());
+
+ // Handle the case where the shadow element is a child of
+ // a node with a ShadowRoot. The nodes that have been distributed to
+ // this shadow insertion point will need to be removed from the
+ // insertion points of the parent's ShadowRoot.
+ ShadowRoot* parentShadowRoot = GetParent()->GetShadowRoot();
+ if (parentShadowRoot) {
+ parentShadowRoot->RemoveDistributedNode(aContent);
+ return;
+ }
+
+ // Handle the case where the parent of this shadow element is a ShadowRoot
+ // that is projected into a shadow insertion point in the younger ShadowRoot.
+ ShadowRoot* containingShadow = GetContainingShadow();
+ ShadowRoot* youngerShadow = containingShadow->GetYoungerShadowRoot();
+ if (youngerShadow && GetParent() == containingShadow) {
+ HTMLShadowElement* youngerShadowElement = youngerShadow->GetShadowElement();
+ if (youngerShadowElement) {
+ youngerShadowElement->RemoveDistributedNode(aContent);
+ }
+ }
+}
+
+void
+HTMLShadowElement::DistributeAllNodes()
+{
+ // All the explicit children of the projected ShadowRoot are distributed
+ // into this shadow insertion point so update the destination insertion
+ // points.
+ ShadowRoot* containingShadow = GetContainingShadow();
+ ShadowRoot* olderShadow = containingShadow->GetOlderShadowRoot();
+ if (olderShadow) {
+ ExplicitChildIterator childIterator(olderShadow);
+ for (nsIContent* content = childIterator.GetNextChild();
+ content;
+ content = childIterator.GetNextChild()) {
+ ShadowRoot::RemoveDestInsertionPoint(this, content->DestInsertionPoints());
+ content->DestInsertionPoints().AppendElement(this);
+ }
+ }
+
+ // Handle the case where the shadow element is a child of
+ // a node with a ShadowRoot. The nodes that have been distributed to
+ // this shadow insertion point will need to be reprojected into the
+ // insertion points of the parent's ShadowRoot.
+ ShadowRoot* parentShadowRoot = GetParent()->GetShadowRoot();
+ if (parentShadowRoot) {
+ parentShadowRoot->DistributeAllNodes();
+ return;
+ }
+
+ // Handle the case where the parent of this shadow element is a ShadowRoot
+ // that is projected into a shadow insertion point in the younger ShadowRoot.
+ ShadowRoot* youngerShadow = containingShadow->GetYoungerShadowRoot();
+ if (youngerShadow && GetParent() == containingShadow) {
+ HTMLShadowElement* youngerShadowElement = youngerShadow->GetShadowElement();
+ if (youngerShadowElement) {
+ youngerShadowElement->DistributeAllNodes();
+ }
+ }
+}
+
+void
+HTMLShadowElement::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer)
+{
+ // Watch for content appended to the projected shadow (the ShadowRoot that
+ // will be rendered in place of this shadow insertion point) because the
+ // nodes may need to be distributed into other insertion points.
+ nsIContent* currentChild = aFirstNewContent;
+ while (currentChild) {
+ if (ShadowRoot::IsPooledNode(currentChild, aContainer, mProjectedShadow)) {
+ DistributeSingleNode(currentChild);
+ }
+ currentChild = currentChild->GetNextSibling();
+ }
+}
+
+void
+HTMLShadowElement::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ // Watch for content appended to the projected shadow (the ShadowRoot that
+ // will be rendered in place of this shadow insertion point) because the
+ // nodes may need to be distributed into other insertion points.
+ if (!ShadowRoot::IsPooledNode(aChild, aContainer, mProjectedShadow)) {
+ return;
+ }
+
+ DistributeSingleNode(aChild);
+}
+
+void
+HTMLShadowElement::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ // Watch for content removed from the projected shadow (the ShadowRoot that
+ // will be rendered in place of this shadow insertion point) because the
+ // nodes may need to be removed from other insertion points.
+ if (!ShadowRoot::IsPooledNode(aChild, aContainer, mProjectedShadow)) {
+ return;
+ }
+
+ RemoveDistributedNode(aChild);
+}
+
diff --git a/dom/html/HTMLShadowElement.h b/dom/html/HTMLShadowElement.h
new file mode 100644
index 000000000..95083b802
--- /dev/null
+++ b/dom/html/HTMLShadowElement.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLShadowElement_h__
+#define mozilla_dom_HTMLShadowElement_h__
+
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLShadowElement final : public nsGenericHTMLElement,
+ public nsStubMutationObserver
+{
+public:
+ explicit HTMLShadowElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLShadowElement,
+ nsGenericHTMLElement)
+
+ static HTMLShadowElement* FromContent(nsIContent* aContent)
+ {
+ if (aContent->IsHTMLShadowElement()) {
+ return static_cast<HTMLShadowElement*>(aContent);
+ }
+
+ return nullptr;
+ }
+
+ virtual bool IsHTMLShadowElement() const override { return true; }
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+
+ bool IsInsertionPoint() { return mIsInsertionPoint; }
+
+ /**
+ * Sets the ShadowRoot that will be rendered in place of
+ * this shadow insertion point.
+ */
+ void SetProjectedShadow(ShadowRoot* aProjectedShadow);
+
+ /**
+ * Distributes a single explicit child of the projected ShadowRoot
+ * to relevant insertion points.
+ */
+ void DistributeSingleNode(nsIContent* aContent);
+
+ /**
+ * Removes a single explicit child of the projected ShadowRoot
+ * from relevant insertion points.
+ */
+ void RemoveDistributedNode(nsIContent* aContent);
+
+ /**
+ * Distributes all the explicit children of the projected ShadowRoot
+ * to the shadow insertion point in the younger ShadowRoot and
+ * the content insertion point of the parent node's ShadowRoot.
+ */
+ void DistributeAllNodes();
+
+ // WebIDL methods.
+ ShadowRoot* GetOlderShadowRoot() { return mProjectedShadow; }
+
+protected:
+ virtual ~HTMLShadowElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // The ShadowRoot that will be rendered in place of this shadow insertion point.
+ RefPtr<ShadowRoot> mProjectedShadow;
+
+ bool mIsInsertionPoint;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLShadowElement_h__
+
diff --git a/dom/html/HTMLSharedElement.cpp b/dom/html/HTMLSharedElement.cpp
new file mode 100644
index 000000000..bcb84d29b
--- /dev/null
+++ b/dom/html/HTMLSharedElement.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/HTMLSharedElement.h"
+#include "mozilla/dom/HTMLBaseElementBinding.h"
+#include "mozilla/dom/HTMLDirectoryElementBinding.h"
+#include "mozilla/dom/HTMLHeadElementBinding.h"
+#include "mozilla/dom/HTMLHtmlElementBinding.h"
+#include "mozilla/dom/HTMLParamElementBinding.h"
+#include "mozilla/dom/HTMLQuoteElementBinding.h"
+
+#include "nsAttrValueInlines.h"
+#include "nsStyleConsts.h"
+#include "nsRuleData.h"
+#include "nsMappedAttributes.h"
+#include "nsContentUtils.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIURI.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Shared)
+
+namespace mozilla {
+namespace dom {
+
+extern nsAttrValue::EnumTable kListTypeTable[];
+
+HTMLSharedElement::~HTMLSharedElement()
+{
+}
+
+NS_IMPL_ADDREF_INHERITED(HTMLSharedElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLSharedElement, Element)
+
+// QueryInterface implementation for HTMLSharedElement
+NS_INTERFACE_MAP_BEGIN(HTMLSharedElement)
+ NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLBaseElement, base)
+ NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLDirectoryElement, dir)
+ NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLQuoteElement, q)
+ NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLQuoteElement, blockquote)
+ NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLHeadElement, head)
+ NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLHtmlElement, html)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
+
+
+NS_IMPL_ELEMENT_CLONE(HTMLSharedElement)
+
+// nsIDOMHTMLQuoteElement
+NS_IMPL_URI_ATTR(HTMLSharedElement, Cite, cite)
+
+// nsIDOMHTMLHeadElement
+// Empty
+
+// nsIDOMHTMLHtmlElement
+NS_IMPL_STRING_ATTR(HTMLSharedElement, Version, version)
+
+// nsIDOMHTMLBaseElement
+NS_IMPL_STRING_ATTR(HTMLSharedElement, Target, target)
+
+NS_IMETHODIMP
+HTMLSharedElement::GetHref(nsAString& aValue)
+{
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::base),
+ "This should only get called for <base> elements");
+ nsAutoString href;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
+
+ nsCOMPtr<nsIURI> uri;
+ nsIDocument* doc = OwnerDoc();
+ nsContentUtils::NewURIWithDocumentCharset(
+ getter_AddRefs(uri), href, doc, doc->GetFallbackBaseURI());
+
+ if (!uri) {
+ aValue = href;
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ uri->GetSpec(spec);
+ CopyUTF8toUTF16(spec, aValue);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLSharedElement::SetHref(const nsAString& aValue)
+{
+ return SetAttrHelper(nsGkAtoms::href, aValue);
+}
+
+
+bool
+HTMLSharedElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None &&
+ mNodeInfo->Equals(nsGkAtoms::dir)) {
+ if (aAttribute == nsGkAtoms::type) {
+ return aResult.ParseEnumValue(aValue, mozilla::dom::kListTypeTable, false);
+ }
+ if (aAttribute == nsGkAtoms::start) {
+ return aResult.ParseIntWithBounds(aValue, 1);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+static void
+DirectoryMapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(List)) {
+ nsCSSValue* listStyleType = aData->ValueForListStyleType();
+ if (listStyleType->GetUnit() == eCSSUnit_Null) {
+ // type: enum
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::type);
+ if (value) {
+ if (value->Type() == nsAttrValue::eEnum) {
+ listStyleType->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
+ } else {
+ listStyleType->SetIntValue(NS_STYLE_LIST_STYLE_DISC, eCSSUnit_Enumerated);
+ }
+ }
+ }
+ }
+
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLSharedElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ if (mNodeInfo->Equals(nsGkAtoms::dir)) {
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::type },
+ // { &nsGkAtoms::compact }, // XXX
+ { nullptr}
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+ }
+
+ return nsGenericHTMLElement::IsAttributeMapped(aAttribute);
+}
+
+static void
+SetBaseURIUsingFirstBaseWithHref(nsIDocument* aDocument, nsIContent* aMustMatch)
+{
+ NS_PRECONDITION(aDocument, "Need a document!");
+
+ for (nsIContent* child = aDocument->GetFirstChild(); child;
+ child = child->GetNextNode()) {
+ if (child->IsHTMLElement(nsGkAtoms::base) &&
+ child->HasAttr(kNameSpaceID_None, nsGkAtoms::href)) {
+ if (aMustMatch && child != aMustMatch) {
+ return;
+ }
+
+ // Resolve the <base> element's href relative to our document's
+ // fallback base URI.
+ nsAutoString href;
+ child->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
+
+ nsCOMPtr<nsIURI> newBaseURI;
+ nsContentUtils::NewURIWithDocumentCharset(
+ getter_AddRefs(newBaseURI), href, aDocument,
+ aDocument->GetFallbackBaseURI());
+
+ // Check if CSP allows this base-uri
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ nsresult rv = aDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Getting CSP Failed");
+ // For all the different error cases we assign a nullptr to
+ // newBaseURI, so we basically call aDocument->SetBaseURI(nullptr);
+ if (NS_FAILED(rv)) {
+ newBaseURI = nullptr;
+ }
+ if (csp && newBaseURI) {
+ // base-uri is only enforced if explicitly defined in the
+ // policy - do *not* consult default-src, see:
+ // http://www.w3.org/TR/CSP2/#directive-default-src
+ bool cspPermitsBaseURI = true;
+ rv = csp->Permits(newBaseURI, nsIContentSecurityPolicy::BASE_URI_DIRECTIVE,
+ true, &cspPermitsBaseURI);
+ if (NS_FAILED(rv) || !cspPermitsBaseURI) {
+ newBaseURI = nullptr;
+ }
+ }
+ aDocument->SetBaseURI(newBaseURI);
+ aDocument->SetChromeXHRDocBaseURI(nullptr);
+ return;
+ }
+ }
+
+ aDocument->SetBaseURI(nullptr);
+}
+
+static void
+SetBaseTargetUsingFirstBaseWithTarget(nsIDocument* aDocument,
+ nsIContent* aMustMatch)
+{
+ NS_PRECONDITION(aDocument, "Need a document!");
+
+ for (nsIContent* child = aDocument->GetFirstChild(); child;
+ child = child->GetNextNode()) {
+ if (child->IsHTMLElement(nsGkAtoms::base) &&
+ child->HasAttr(kNameSpaceID_None, nsGkAtoms::target)) {
+ if (aMustMatch && child != aMustMatch) {
+ return;
+ }
+
+ nsString target;
+ child->GetAttr(kNameSpaceID_None, nsGkAtoms::target, target);
+ aDocument->SetBaseTarget(target);
+ return;
+ }
+ }
+
+ aDocument->SetBaseTarget(EmptyString());
+}
+
+nsresult
+HTMLSharedElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify)
+{
+ nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
+ aValue, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the href attribute of a <base> tag is changing, we may need to update
+ // the document's base URI, which will cause all the links on the page to be
+ // re-resolved given the new base. If the target attribute is changing, we
+ // similarly need to change the base target.
+ if (mNodeInfo->Equals(nsGkAtoms::base) &&
+ aNameSpaceID == kNameSpaceID_None &&
+ IsInUncomposedDoc()) {
+ if (aName == nsGkAtoms::href) {
+ SetBaseURIUsingFirstBaseWithHref(GetUncomposedDoc(), this);
+ } else if (aName == nsGkAtoms::target) {
+ SetBaseTargetUsingFirstBaseWithTarget(GetUncomposedDoc(), this);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLSharedElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ bool aNotify)
+{
+ nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aName, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we're the first <base> with an href and our href attribute is being
+ // unset, then we're no longer the first <base> with an href, and we need to
+ // find the new one. Similar for target.
+ if (mNodeInfo->Equals(nsGkAtoms::base) &&
+ aNameSpaceID == kNameSpaceID_None &&
+ IsInUncomposedDoc()) {
+ if (aName == nsGkAtoms::href) {
+ SetBaseURIUsingFirstBaseWithHref(GetUncomposedDoc(), nullptr);
+ } else if (aName == nsGkAtoms::target) {
+ SetBaseTargetUsingFirstBaseWithTarget(GetUncomposedDoc(), nullptr);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLSharedElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The document stores a pointer to its base URI and base target, which we may
+ // need to update here.
+ if (mNodeInfo->Equals(nsGkAtoms::base) &&
+ aDocument) {
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::href)) {
+ SetBaseURIUsingFirstBaseWithHref(aDocument, this);
+ }
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::target)) {
+ SetBaseTargetUsingFirstBaseWithTarget(aDocument, this);
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+HTMLSharedElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ nsIDocument* doc = GetUncomposedDoc();
+
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+
+ // If we're removing a <base> from a document, we may need to update the
+ // document's base URI and base target
+ if (doc && mNodeInfo->Equals(nsGkAtoms::base)) {
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::href)) {
+ SetBaseURIUsingFirstBaseWithHref(doc, nullptr);
+ }
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::target)) {
+ SetBaseTargetUsingFirstBaseWithTarget(doc, nullptr);
+ }
+ }
+}
+
+nsMapRuleToAttributesFunc
+HTMLSharedElement::GetAttributeMappingFunction() const
+{
+ if (mNodeInfo->Equals(nsGkAtoms::dir)) {
+ return &DirectoryMapAttributesIntoRule;
+ }
+
+ return nsGenericHTMLElement::GetAttributeMappingFunction();
+}
+
+JSObject*
+HTMLSharedElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ if (mNodeInfo->Equals(nsGkAtoms::param)) {
+ return HTMLParamElementBinding::Wrap(aCx, this, aGivenProto);
+ }
+ if (mNodeInfo->Equals(nsGkAtoms::base)) {
+ return HTMLBaseElementBinding::Wrap(aCx, this, aGivenProto);
+ }
+ if (mNodeInfo->Equals(nsGkAtoms::dir)) {
+ return HTMLDirectoryElementBinding::Wrap(aCx, this, aGivenProto);
+ }
+ if (mNodeInfo->Equals(nsGkAtoms::q) ||
+ mNodeInfo->Equals(nsGkAtoms::blockquote)) {
+ return HTMLQuoteElementBinding::Wrap(aCx, this, aGivenProto);
+ }
+ if (mNodeInfo->Equals(nsGkAtoms::head)) {
+ return HTMLHeadElementBinding::Wrap(aCx, this, aGivenProto);
+ }
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::html));
+ return HTMLHtmlElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLSharedElement.h b/dom/html/HTMLSharedElement.h
new file mode 100644
index 000000000..205750cf6
--- /dev/null
+++ b/dom/html/HTMLSharedElement.h
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLSharedElement_h
+#define mozilla_dom_HTMLSharedElement_h
+
+#include "nsIDOMHTMLBaseElement.h"
+#include "nsIDOMHTMLDirectoryElement.h"
+#include "nsIDOMHTMLQuoteElement.h"
+#include "nsIDOMHTMLHeadElement.h"
+#include "nsIDOMHTMLHtmlElement.h"
+#include "nsGenericHTMLElement.h"
+
+#include "nsGkAtoms.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Assertions.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLSharedElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLBaseElement,
+ public nsIDOMHTMLDirectoryElement,
+ public nsIDOMHTMLQuoteElement,
+ public nsIDOMHTMLHeadElement,
+ public nsIDOMHTMLHtmlElement
+{
+public:
+ explicit HTMLSharedElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ if (mNodeInfo->Equals(nsGkAtoms::head) ||
+ mNodeInfo->Equals(nsGkAtoms::html)) {
+ SetHasWeirdParserInsertionMode();
+ }
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLBaseElement
+ NS_DECL_NSIDOMHTMLBASEELEMENT
+
+ // nsIDOMHTMLQuoteElement
+ NS_DECL_NSIDOMHTMLQUOTEELEMENT
+
+ // nsIDOMHTMLHeadElement
+ NS_DECL_NSIDOMHTMLHEADELEMENT
+
+ // nsIDOMHTMLHtmlElement
+ NS_DECL_NSIDOMHTMLHTMLELEMENT
+
+ // nsIContent
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) 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* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify) override;
+
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ 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;
+
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ // WebIDL API
+ // HTMLParamElement
+ void GetName(DOMString& aValue)
+ {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::param));
+ GetHTMLAttr(nsGkAtoms::name, aValue);
+ }
+ void SetName(const nsAString& aValue, ErrorResult& aResult)
+ {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::param));
+ SetHTMLAttr(nsGkAtoms::name, aValue, aResult);
+ }
+ void GetValue(DOMString& aValue)
+ {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::param));
+ GetHTMLAttr(nsGkAtoms::value, aValue);
+ }
+ void SetValue(const nsAString& aValue, ErrorResult& aResult)
+ {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::param));
+ SetHTMLAttr(nsGkAtoms::value, aValue, aResult);
+ }
+ void GetType(DOMString& aValue)
+ {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::param));
+ GetHTMLAttr(nsGkAtoms::type, aValue);
+ }
+ void SetType(const nsAString& aValue, ErrorResult& aResult)
+ {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::param));
+ SetHTMLAttr(nsGkAtoms::type, aValue, aResult);
+ }
+ void GetValueType(DOMString& aValue)
+ {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::param));
+ GetHTMLAttr(nsGkAtoms::valuetype, aValue);
+ }
+ void SetValueType(const nsAString& aValue, ErrorResult& aResult)
+ {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::param));
+ SetHTMLAttr(nsGkAtoms::valuetype, aValue, aResult);
+ }
+
+ // HTMLBaseElement
+ void GetTarget(DOMString& aValue)
+ {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::base));
+ GetHTMLAttr(nsGkAtoms::target, aValue);
+ }
+ void SetTarget(const nsAString& aValue, ErrorResult& aResult)
+ {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::base));
+ SetHTMLAttr(nsGkAtoms::target, aValue, aResult);
+ }
+ // The XPCOM GetHref is fine for us
+ void SetHref(const nsAString& aValue, ErrorResult& aResult)
+ {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::base));
+ SetHTMLAttr(nsGkAtoms::href, aValue, aResult);
+ }
+
+ // HTMLDirectoryElement
+ bool Compact() const
+ {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::dir));
+ return GetBoolAttr(nsGkAtoms::compact);
+ }
+ void SetCompact(bool aCompact, ErrorResult& aResult)
+ {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::dir));
+ SetHTMLBoolAttr(nsGkAtoms::compact, aCompact, aResult);
+ }
+
+ // HTMLQuoteElement
+ // The XPCOM GetCite works fine for us
+ void SetCite(const nsAString& aValue, ErrorResult& aResult)
+ {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::q) ||
+ mNodeInfo->Equals(nsGkAtoms::blockquote));
+ SetHTMLAttr(nsGkAtoms::cite, aValue, aResult);
+ }
+
+ // HTMLHtmlElement
+ void GetVersion(DOMString& aValue)
+ {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::html));
+ GetHTMLAttr(nsGkAtoms::version, aValue);
+ }
+ void SetVersion(const nsAString& aValue, ErrorResult& aResult)
+ {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::html));
+ SetHTMLAttr(nsGkAtoms::version, aValue, aResult);
+ }
+
+protected:
+ virtual ~HTMLSharedElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLSharedElement_h
diff --git a/dom/html/HTMLSharedListElement.cpp b/dom/html/HTMLSharedListElement.cpp
new file mode 100644
index 000000000..a76a7513c
--- /dev/null
+++ b/dom/html/HTMLSharedListElement.cpp
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/HTMLSharedListElement.h"
+#include "mozilla/dom/HTMLDListElementBinding.h"
+#include "mozilla/dom/HTMLOListElementBinding.h"
+#include "mozilla/dom/HTMLUListElementBinding.h"
+
+#include "nsGenericHTMLElement.h"
+#include "nsAttrValueInlines.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsMappedAttributes.h"
+#include "nsRuleData.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(SharedList)
+
+namespace mozilla {
+namespace dom {
+
+HTMLSharedListElement::~HTMLSharedListElement()
+{
+}
+
+NS_IMPL_ADDREF_INHERITED(HTMLSharedListElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLSharedListElement, Element)
+
+// QueryInterface implementation for nsHTMLSharedListElement
+NS_INTERFACE_MAP_BEGIN(HTMLSharedListElement)
+ NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLOListElement, ol)
+ NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLUListElement, ul)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
+
+
+NS_IMPL_ELEMENT_CLONE(HTMLSharedListElement)
+
+
+NS_IMPL_BOOL_ATTR(HTMLSharedListElement, Compact, compact)
+NS_IMPL_INT_ATTR_DEFAULT_VALUE(HTMLSharedListElement, Start, start, 1)
+NS_IMPL_STRING_ATTR(HTMLSharedListElement, Type, type)
+NS_IMPL_BOOL_ATTR(HTMLSharedListElement, Reversed, reversed)
+
+// Shared with nsHTMLSharedElement.cpp
+nsAttrValue::EnumTable kListTypeTable[] = {
+ { "none", NS_STYLE_LIST_STYLE_NONE },
+ { "disc", NS_STYLE_LIST_STYLE_DISC },
+ { "circle", NS_STYLE_LIST_STYLE_CIRCLE },
+ { "round", NS_STYLE_LIST_STYLE_CIRCLE },
+ { "square", NS_STYLE_LIST_STYLE_SQUARE },
+ { "decimal", NS_STYLE_LIST_STYLE_DECIMAL },
+ { "lower-roman", NS_STYLE_LIST_STYLE_LOWER_ROMAN },
+ { "upper-roman", NS_STYLE_LIST_STYLE_UPPER_ROMAN },
+ { "lower-alpha", NS_STYLE_LIST_STYLE_LOWER_ALPHA },
+ { "upper-alpha", NS_STYLE_LIST_STYLE_UPPER_ALPHA },
+ { nullptr, 0 }
+};
+
+static const nsAttrValue::EnumTable kOldListTypeTable[] = {
+ { "1", NS_STYLE_LIST_STYLE_DECIMAL },
+ { "A", NS_STYLE_LIST_STYLE_UPPER_ALPHA },
+ { "a", NS_STYLE_LIST_STYLE_LOWER_ALPHA },
+ { "I", NS_STYLE_LIST_STYLE_UPPER_ROMAN },
+ { "i", NS_STYLE_LIST_STYLE_LOWER_ROMAN },
+ { nullptr, 0 }
+};
+
+bool
+HTMLSharedListElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (mNodeInfo->Equals(nsGkAtoms::ol) ||
+ mNodeInfo->Equals(nsGkAtoms::ul)) {
+ if (aAttribute == nsGkAtoms::type) {
+ return aResult.ParseEnumValue(aValue, kListTypeTable, false) ||
+ aResult.ParseEnumValue(aValue, kOldListTypeTable, true);
+ }
+ if (aAttribute == nsGkAtoms::start) {
+ return aResult.ParseIntValue(aValue);
+ }
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLSharedListElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(List)) {
+ nsCSSValue* listStyleType = aData->ValueForListStyleType();
+ if (listStyleType->GetUnit() == eCSSUnit_Null) {
+ // type: enum
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::type);
+ if (value && value->Type() == nsAttrValue::eEnum) {
+ listStyleType->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
+ }
+ }
+ }
+
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLSharedListElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ if (mNodeInfo->Equals(nsGkAtoms::ol) ||
+ mNodeInfo->Equals(nsGkAtoms::ul)) {
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::type },
+ { nullptr }
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+ }
+
+ return nsGenericHTMLElement::IsAttributeMapped(aAttribute);
+}
+
+nsMapRuleToAttributesFunc
+HTMLSharedListElement::GetAttributeMappingFunction() const
+{
+ if (mNodeInfo->Equals(nsGkAtoms::ol) ||
+ mNodeInfo->Equals(nsGkAtoms::ul)) {
+ return &MapAttributesIntoRule;
+ }
+
+ return nsGenericHTMLElement::GetAttributeMappingFunction();
+}
+
+JSObject*
+HTMLSharedListElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ if (mNodeInfo->Equals(nsGkAtoms::ol)) {
+ return HTMLOListElementBinding::Wrap(aCx, this, aGivenProto);
+ }
+ if (mNodeInfo->Equals(nsGkAtoms::dl)) {
+ return HTMLDListElementBinding::Wrap(aCx, this, aGivenProto);
+ }
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::ul));
+ return HTMLUListElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLSharedListElement.h b/dom/html/HTMLSharedListElement.h
new file mode 100644
index 000000000..2f733aa2e
--- /dev/null
+++ b/dom/html/HTMLSharedListElement.h
@@ -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/. */
+
+#ifndef mozilla_dom_HTMLSharedListElement_h
+#define mozilla_dom_HTMLSharedListElement_h
+
+#include "mozilla/Attributes.h"
+
+#include "nsIDOMHTMLOListElement.h"
+#include "nsIDOMHTMLUListElement.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLSharedListElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLOListElement,
+ public nsIDOMHTMLUListElement
+{
+public:
+ explicit HTMLSharedListElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLOListElement
+ NS_DECL_NSIDOMHTMLOLISTELEMENT
+
+ // nsIDOMHTMLUListElement
+ // fully declared by NS_DECL_NSIDOMHTMLOLISTELEMENT
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ bool Reversed() const
+ {
+ return GetBoolAttr(nsGkAtoms::reversed);
+ }
+ void SetReversed(bool aReversed, mozilla::ErrorResult& rv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::reversed, aReversed, rv);
+ }
+ int32_t Start() const
+ {
+ return GetIntAttr(nsGkAtoms::start, 1);
+ }
+ void SetStart(int32_t aStart, mozilla::ErrorResult& rv)
+ {
+ SetHTMLIntAttr(nsGkAtoms::start, aStart, rv);
+ }
+ void GetType(DOMString& aType)
+ {
+ GetHTMLAttr(nsGkAtoms::type, aType);
+ }
+ void SetType(const nsAString& aType, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::type, aType, rv);
+ }
+ bool Compact() const
+ {
+ return GetBoolAttr(nsGkAtoms::compact);
+ }
+ void SetCompact(bool aCompact, mozilla::ErrorResult& rv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::compact, aCompact, rv);
+ }
+
+protected:
+ virtual ~HTMLSharedListElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLSharedListElement_h
diff --git a/dom/html/HTMLSharedObjectElement.cpp b/dom/html/HTMLSharedObjectElement.cpp
new file mode 100644
index 000000000..889fd5aef
--- /dev/null
+++ b/dom/html/HTMLSharedObjectElement.cpp
@@ -0,0 +1,419 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/EventStates.h"
+#include "mozilla/dom/HTMLSharedObjectElement.h"
+#include "mozilla/dom/HTMLEmbedElementBinding.h"
+#include "mozilla/dom/HTMLAppletElementBinding.h"
+#include "mozilla/dom/ElementInlines.h"
+
+#include "nsIDocument.h"
+#include "nsIPluginDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsThreadUtils.h"
+#include "nsIScriptError.h"
+#include "nsIWidget.h"
+#include "nsContentUtils.h"
+#ifdef XP_MACOSX
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/dom/Event.h"
+#endif
+#include "mozilla/dom/HTMLObjectElement.h"
+
+
+NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(SharedObject)
+
+namespace mozilla {
+namespace dom {
+
+HTMLSharedObjectElement::HTMLSharedObjectElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser)
+ : nsGenericHTMLElement(aNodeInfo),
+ mIsDoneAddingChildren(mNodeInfo->Equals(nsGkAtoms::embed) || !aFromParser)
+{
+ RegisterActivityObserver();
+ SetIsNetworkCreated(aFromParser == FROM_PARSER_NETWORK);
+
+ // By default we're in the loading state
+ AddStatesSilently(NS_EVENT_STATE_LOADING);
+}
+
+HTMLSharedObjectElement::~HTMLSharedObjectElement()
+{
+#ifdef XP_MACOSX
+ HTMLObjectElement::OnFocusBlurPlugin(this, false);
+#endif
+ UnregisterActivityObserver();
+ DestroyImageLoadingContent();
+}
+
+bool
+HTMLSharedObjectElement::IsDoneAddingChildren()
+{
+ return mIsDoneAddingChildren;
+}
+
+void
+HTMLSharedObjectElement::DoneAddingChildren(bool aHaveNotified)
+{
+ if (!mIsDoneAddingChildren) {
+ mIsDoneAddingChildren = true;
+
+ // If we're already in a document, we need to trigger the load
+ // Otherwise, BindToTree takes care of that.
+ if (IsInComposedDoc()) {
+ StartObjectLoad(aHaveNotified, false);
+ }
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLSharedObjectElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLSharedObjectElement,
+ nsGenericHTMLElement)
+ nsObjectLoadingContent::Traverse(tmp, cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_ADDREF_INHERITED(HTMLSharedObjectElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLSharedObjectElement, Element)
+
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLSharedObjectElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLSharedObjectElement,
+ nsIRequestObserver,
+ nsIStreamListener,
+ nsIFrameLoaderOwner,
+ nsIObjectLoadingContent,
+ imgINotificationObserver,
+ nsIImageLoadingContent,
+ imgIOnloadBlocker,
+ nsIChannelEventSink)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE
+ NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLAppletElement, applet)
+ NS_INTERFACE_MAP_ENTRY_IF_TAG(nsIDOMHTMLEmbedElement, embed)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLSharedObjectElement)
+
+#ifdef XP_MACOSX
+
+NS_IMETHODIMP
+HTMLSharedObjectElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ HTMLObjectElement::HandleFocusBlurPlugin(this, aVisitor.mEvent);
+ return NS_OK;
+}
+
+#endif // #ifdef XP_MACOSX
+
+void
+HTMLSharedObjectElement::AsyncEventRunning(AsyncEventDispatcher* aEvent)
+{
+ nsImageLoadingContent::AsyncEventRunning(aEvent);
+}
+
+nsresult
+HTMLSharedObjectElement::BindToTree(nsIDocument *aDocument,
+ nsIContent *aParent,
+ nsIContent *aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsObjectLoadingContent::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't kick off load from being bound to a plugin document - the plugin
+ // document will call nsObjectLoadingContent::InitializeFromChannel() for the
+ // initial load.
+ nsCOMPtr<nsIPluginDocument> pluginDoc = do_QueryInterface(aDocument);
+
+ // If we already have all the children, start the load.
+ if (mIsDoneAddingChildren && !pluginDoc) {
+ void (HTMLSharedObjectElement::*start)() =
+ &HTMLSharedObjectElement::StartObjectLoad;
+ nsContentUtils::AddScriptRunner(NewRunnableMethod(this, start));
+ }
+
+ return NS_OK;
+}
+
+void
+HTMLSharedObjectElement::UnbindFromTree(bool aDeep,
+ bool aNullParent)
+{
+#ifdef XP_MACOSX
+ // When a page is reloaded (when an nsIDocument's content is removed), the
+ // focused element isn't necessarily sent an eBlur event. See
+ // nsFocusManager::ContentRemoved(). This means that a widget may think it
+ // still contains a focused plugin when it doesn't -- which in turn can
+ // disable text input in the browser window. See bug 1137229.
+ HTMLObjectElement::OnFocusBlurPlugin(this, false);
+#endif
+ nsObjectLoadingContent::UnbindFromTree(aDeep, aNullParent);
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+
+nsresult
+HTMLSharedObjectElement::SetAttr(int32_t aNameSpaceID, nsIAtom *aName,
+ nsIAtom *aPrefix, const nsAString &aValue,
+ bool aNotify)
+{
+ nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
+ aValue, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if aNotify is false, we are coming from the parser or some such place;
+ // we'll get bound after all the attributes have been set, so we'll do the
+ // object load from BindToTree/DoneAddingChildren.
+ // Skip the LoadObject call in that case.
+ // We also don't want to start loading the object when we're not yet in
+ // a document, just in case that the caller wants to set additional
+ // attributes before inserting the node into the document.
+ if (aNotify && IsInComposedDoc() && mIsDoneAddingChildren &&
+ aNameSpaceID == kNameSpaceID_None && aName == URIAttrName()
+ && !BlockEmbedContentLoading()) {
+ return LoadObject(aNotify, true);
+ }
+
+ return NS_OK;
+}
+
+bool
+HTMLSharedObjectElement::IsHTMLFocusable(bool aWithMouse,
+ bool *aIsFocusable,
+ int32_t *aTabIndex)
+{
+ if (mNodeInfo->Equals(nsGkAtoms::embed) || Type() == eType_Plugin) {
+ // Has plugin content: let the plugin decide what to do in terms of
+ // internal focus from mouse clicks
+ if (aTabIndex) {
+ GetTabIndex(aTabIndex);
+ }
+
+ *aIsFocusable = true;
+
+ // Let the plugin decide, so override.
+ return true;
+ }
+
+ return nsGenericHTMLElement::IsHTMLFocusable(aWithMouse, aIsFocusable, aTabIndex);
+}
+
+nsIContent::IMEState
+HTMLSharedObjectElement::GetDesiredIMEState()
+{
+ if (Type() == eType_Plugin) {
+ return IMEState(IMEState::PLUGIN);
+ }
+
+ return nsGenericHTMLElement::GetDesiredIMEState();
+}
+
+NS_IMPL_STRING_ATTR(HTMLSharedObjectElement, Align, align)
+NS_IMPL_STRING_ATTR(HTMLSharedObjectElement, Alt, alt)
+NS_IMPL_STRING_ATTR(HTMLSharedObjectElement, Archive, archive)
+NS_IMPL_STRING_ATTR(HTMLSharedObjectElement, Code, code)
+NS_IMPL_URI_ATTR(HTMLSharedObjectElement, CodeBase, codebase)
+NS_IMPL_STRING_ATTR(HTMLSharedObjectElement, Height, height)
+NS_IMPL_INT_ATTR(HTMLSharedObjectElement, Hspace, hspace)
+NS_IMPL_STRING_ATTR(HTMLSharedObjectElement, Name, name)
+NS_IMPL_URI_ATTR_WITH_BASE(HTMLSharedObjectElement, Object, object, codebase)
+NS_IMPL_URI_ATTR(HTMLSharedObjectElement, Src, src)
+NS_IMPL_STRING_ATTR(HTMLSharedObjectElement, Type, type)
+NS_IMPL_INT_ATTR(HTMLSharedObjectElement, Vspace, vspace)
+NS_IMPL_STRING_ATTR(HTMLSharedObjectElement, Width, width)
+
+int32_t
+HTMLSharedObjectElement::TabIndexDefault()
+{
+ return -1;
+}
+
+bool
+HTMLSharedObjectElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom *aAttribute,
+ const nsAString &aValue,
+ nsAttrValue &aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::align) {
+ return ParseAlignValue(aValue, aResult);
+ }
+ if (ParseImageAttribute(aAttribute, aValue, aResult)) {
+ return true;
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+static void
+MapAttributesIntoRuleBase(const nsMappedAttributes *aAttributes,
+ nsRuleData *aData)
+{
+ nsGenericHTMLElement::MapImageBorderAttributeInto(aAttributes, aData);
+ nsGenericHTMLElement::MapImageMarginAttributeInto(aAttributes, aData);
+ nsGenericHTMLElement::MapImageSizeAttributesInto(aAttributes, aData);
+ nsGenericHTMLElement::MapImageAlignAttributeInto(aAttributes, aData);
+}
+
+static void
+MapAttributesIntoRuleExceptHidden(const nsMappedAttributes *aAttributes,
+ nsRuleData *aData)
+{
+ MapAttributesIntoRuleBase(aAttributes, aData);
+ nsGenericHTMLElement::MapCommonAttributesIntoExceptHidden(aAttributes, aData);
+}
+
+void
+HTMLSharedObjectElement::MapAttributesIntoRule(const nsMappedAttributes *aAttributes,
+ nsRuleData *aData)
+{
+ MapAttributesIntoRuleBase(aAttributes, aData);
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLSharedObjectElement::IsAttributeMapped(const nsIAtom *aAttribute) const
+{
+ static const MappedAttributeEntry* const map[] = {
+ sCommonAttributeMap,
+ sImageMarginSizeAttributeMap,
+ sImageBorderAttributeMap,
+ sImageAlignAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+
+nsMapRuleToAttributesFunc
+HTMLSharedObjectElement::GetAttributeMappingFunction() const
+{
+ if (mNodeInfo->Equals(nsGkAtoms::embed)) {
+ return &MapAttributesIntoRuleExceptHidden;
+ }
+
+ return &MapAttributesIntoRule;
+}
+
+void
+HTMLSharedObjectElement::StartObjectLoad(bool aNotify, bool aForceLoad)
+{
+ // BindToTree can call us asynchronously, and we may be removed from the tree
+ // in the interim
+ if (!IsInComposedDoc() || !OwnerDoc()->IsActive() ||
+ BlockEmbedContentLoading()) {
+ return;
+ }
+
+ LoadObject(aNotify, aForceLoad);
+ SetIsNetworkCreated(false);
+}
+
+EventStates
+HTMLSharedObjectElement::IntrinsicState() const
+{
+ return nsGenericHTMLElement::IntrinsicState() | ObjectState();
+}
+
+uint32_t
+HTMLSharedObjectElement::GetCapabilities() const
+{
+ uint32_t capabilities = eSupportPlugins | eAllowPluginSkipChannel;
+ if (mNodeInfo->Equals(nsGkAtoms::embed)) {
+ capabilities |= eSupportImages | eSupportDocuments;
+ }
+
+ return capabilities;
+}
+
+void
+HTMLSharedObjectElement::DestroyContent()
+{
+ nsObjectLoadingContent::DestroyContent();
+ nsGenericHTMLElement::DestroyContent();
+}
+
+nsresult
+HTMLSharedObjectElement::CopyInnerTo(Element* aDest)
+{
+ nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aDest->OwnerDoc()->IsStaticDocument()) {
+ CreateStaticClone(static_cast<HTMLSharedObjectElement*>(aDest));
+ }
+
+ return rv;
+}
+
+JSObject*
+HTMLSharedObjectElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ JSObject* obj;
+ if (mNodeInfo->Equals(nsGkAtoms::applet)) {
+ obj = HTMLAppletElementBinding::Wrap(aCx, this, aGivenProto);
+ } else {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::embed));
+ obj = HTMLEmbedElementBinding::Wrap(aCx, this, aGivenProto);
+ }
+ if (!obj) {
+ return nullptr;
+ }
+ JS::Rooted<JSObject*> rootedObj(aCx, obj);
+ SetupProtoChain(aCx, rootedObj);
+ return rootedObj;
+}
+
+nsContentPolicyType
+HTMLSharedObjectElement::GetContentPolicyType() const
+{
+ if (mNodeInfo->Equals(nsGkAtoms::applet)) {
+ // We use TYPE_INTERNAL_OBJECT for applet too, since it is not exposed
+ // through RequestContext yet.
+ return nsIContentPolicy::TYPE_INTERNAL_OBJECT;
+ } else {
+ MOZ_ASSERT(mNodeInfo->Equals(nsGkAtoms::embed));
+ return nsIContentPolicy::TYPE_INTERNAL_EMBED;
+ }
+}
+
+bool
+HTMLSharedObjectElement::BlockEmbedContentLoading()
+{
+ // Only check on embed elements
+ if (!IsHTMLElement(nsGkAtoms::embed)) {
+ return false;
+ }
+ // Traverse up the node tree to see if we have any ancestors that may block us
+ // from loading
+ for (nsIContent* parent = GetParent(); parent; parent = parent->GetParent()) {
+ if (parent->IsAnyOfHTMLElements(nsGkAtoms::video, nsGkAtoms::audio)) {
+ return true;
+ }
+ // If we have an ancestor that is an object with a source, it'll have an
+ // associated displayed type. If that type is not null, don't load content
+ // for the embed.
+ if (HTMLObjectElement* object = HTMLObjectElement::FromContent(parent)) {
+ uint32_t type = object->DisplayedType();
+ if (type != eType_Null) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLSharedObjectElement.h b/dom/html/HTMLSharedObjectElement.h
new file mode 100644
index 000000000..c70ba8ebd
--- /dev/null
+++ b/dom/html/HTMLSharedObjectElement.h
@@ -0,0 +1,243 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_HTMLSharedObjectElement_h
+#define mozilla_dom_HTMLSharedObjectElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsObjectLoadingContent.h"
+#include "nsGkAtoms.h"
+#include "nsError.h"
+#include "nsIDOMHTMLAppletElement.h"
+#include "nsIDOMHTMLEmbedElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLSharedObjectElement final : public nsGenericHTMLElement
+ , public nsObjectLoadingContent
+ , public nsIDOMHTMLAppletElement
+ , public nsIDOMHTMLEmbedElement
+{
+public:
+ explicit HTMLSharedObjectElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ mozilla::dom::FromParser aFromParser = mozilla::dom::NOT_FROM_PARSER);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual int32_t TabIndexDefault() override;
+
+#ifdef XP_MACOSX
+ // nsIDOMEventTarget
+ NS_IMETHOD PostHandleEvent(EventChainPostVisitor& aVisitor) override;
+#endif
+
+ // nsIDOMHTMLAppletElement
+ NS_DECL_NSIDOMHTMLAPPLETELEMENT
+
+ // Can't use macro for nsIDOMHTMLEmbedElement because it has conflicts with
+ // NS_DECL_NSIDOMHTMLAPPLETELEMENT.
+
+ // EventTarget
+ virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;
+
+ // nsIDOMHTMLEmbedElement
+ NS_IMETHOD GetSrc(nsAString &aSrc) override;
+ NS_IMETHOD SetSrc(const nsAString &aSrc) override;
+ NS_IMETHOD GetType(nsAString &aType) override;
+ NS_IMETHOD SetType(const nsAString &aType) override;
+
+ virtual nsresult BindToTree(nsIDocument *aDocument, nsIContent *aParent,
+ nsIContent *aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+ virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom *aName,
+ nsIAtom *aPrefix, const nsAString &aValue,
+ bool aNotify) override;
+
+ virtual bool IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable, int32_t *aTabIndex) override;
+ virtual IMEState GetDesiredIMEState() override;
+
+ virtual void DoneAddingChildren(bool aHaveNotified) override;
+ virtual bool IsDoneAddingChildren() override;
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom *aAttribute,
+ const nsAString &aValue,
+ nsAttrValue &aResult) override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom *aAttribute) const override;
+ virtual EventStates IntrinsicState() const override;
+ virtual void DestroyContent() override;
+
+ // nsObjectLoadingContent
+ virtual uint32_t GetCapabilities() const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ nsresult CopyInnerTo(Element* aDest);
+
+ void StartObjectLoad() { StartObjectLoad(true, false); }
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(HTMLSharedObjectElement,
+ nsGenericHTMLElement)
+
+ // WebIDL API for <applet>
+ void GetAlign(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::align, aValue);
+ }
+ void SetAlign(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::align, aValue, aRv);
+ }
+ void GetAlt(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::alt, aValue);
+ }
+ void SetAlt(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::alt, aValue, aRv);
+ }
+ void GetArchive(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::archive, aValue);
+ }
+ void SetArchive(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::archive, aValue, aRv);
+ }
+ void GetCode(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::code, aValue);
+ }
+ void SetCode(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::code, aValue, aRv);
+ }
+ // XPCOM GetCodebase is ok; note that it's a URI attribute
+ void SetCodeBase(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::codebase, aValue, aRv);
+ }
+ void GetHeight(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::height, aValue);
+ }
+ void SetHeight(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::height, aValue, aRv);
+ }
+ uint32_t Hspace()
+ {
+ return GetUnsignedIntAttr(nsGkAtoms::hspace, 0);
+ }
+ void SetHspace(uint32_t aValue, ErrorResult& aRv)
+ {
+ SetUnsignedIntAttr(nsGkAtoms::hspace, aValue, 0, aRv);
+ }
+ void GetName(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::name, aValue);
+ }
+ void SetName(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::name, aValue, aRv);
+ }
+ // XPCOM GetObject is ok; note that it's a URI attribute with a weird base URI
+ void SetObject(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::object, aValue, aRv);
+ }
+ uint32_t Vspace()
+ {
+ return GetUnsignedIntAttr(nsGkAtoms::vspace, 0);
+ }
+ void SetVspace(uint32_t aValue, ErrorResult& aRv)
+ {
+ SetUnsignedIntAttr(nsGkAtoms::vspace, aValue, 0, aRv);
+ }
+ void GetWidth(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::width, aValue);
+ }
+ void SetWidth(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::width, aValue, aRv);
+ }
+
+ // WebIDL <embed> api
+ // XPCOM GetSrc is ok; note that it's a URI attribute
+ void SetSrc(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::src, aValue, aRv);
+ }
+ void GetType(DOMString& aValue)
+ {
+ GetHTMLAttr(nsGkAtoms::type, aValue);
+ }
+ void SetType(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::type, aValue, aRv);
+ }
+ // width covered by <applet>
+ // height covered by <applet>
+ // align covered by <applet>
+ // name covered by <applet>
+ nsIDocument*
+ GetSVGDocument(nsIPrincipal& aSubjectPrincipal)
+ {
+ return GetContentDocument(aSubjectPrincipal);
+ }
+
+ /**
+ * Calls LoadObject with the correct arguments to start the plugin load.
+ */
+ void StartObjectLoad(bool aNotify, bool aForceLoad);
+private:
+ virtual ~HTMLSharedObjectElement();
+
+ nsIAtom *URIAttrName() const
+ {
+ return mNodeInfo->Equals(nsGkAtoms::applet) ?
+ nsGkAtoms::code :
+ nsGkAtoms::src;
+ }
+
+ nsContentPolicyType GetContentPolicyType() const override;
+
+ // mIsDoneAddingChildren is only really used for <applet>. This boolean is
+ // always true for <embed>, per the documentation in nsIContent.h.
+ bool mIsDoneAddingChildren;
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+
+ /**
+ * Decides whether we should load embed node content.
+ *
+ * If this is an embed node there are cases in which we should not try to load
+ * the content:
+ *
+ * - If the embed node is the child of a media element
+ * - If the embed node is the child of an object node that already has
+ * content being loaded.
+ *
+ * In these cases, this function will return false, which will cause
+ * us to skip calling LoadObject.
+ */
+ bool BlockEmbedContentLoading();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLSharedObjectElement_h
diff --git a/dom/html/HTMLSourceElement.cpp b/dom/html/HTMLSourceElement.cpp
new file mode 100644
index 000000000..73d9c9d8c
--- /dev/null
+++ b/dom/html/HTMLSourceElement.cpp
@@ -0,0 +1,176 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/HTMLSourceElement.h"
+#include "mozilla/dom/HTMLSourceElementBinding.h"
+
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/ResponsiveImageSelector.h"
+#include "mozilla/dom/MediaSource.h"
+
+#include "nsGkAtoms.h"
+
+#include "nsIMediaList.h"
+#include "nsCSSParser.h"
+#include "nsHostObjectProtocolHandler.h"
+
+#include "mozilla/Preferences.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Source)
+
+namespace mozilla {
+namespace dom {
+
+HTMLSourceElement::HTMLSourceElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+{
+}
+
+HTMLSourceElement::~HTMLSourceElement()
+{
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLSourceElement, nsGenericHTMLElement,
+ mSrcMediaSource)
+
+NS_IMPL_ADDREF_INHERITED(HTMLSourceElement, nsGenericHTMLElement)
+NS_IMPL_RELEASE_INHERITED(HTMLSourceElement, nsGenericHTMLElement)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLSourceElement)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLSourceElement)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLSourceElement)
+
+NS_IMPL_URI_ATTR(HTMLSourceElement, Src, src)
+NS_IMPL_STRING_ATTR(HTMLSourceElement, Type, type)
+NS_IMPL_STRING_ATTR(HTMLSourceElement, Srcset, srcset)
+NS_IMPL_STRING_ATTR(HTMLSourceElement, Sizes, sizes)
+NS_IMPL_STRING_ATTR(HTMLSourceElement, Media, media)
+
+bool
+HTMLSourceElement::MatchesCurrentMedia()
+{
+ if (mMediaList) {
+ nsIPresShell* presShell = OwnerDoc()->GetShell();
+ nsPresContext* pctx = presShell ? presShell->GetPresContext() : nullptr;
+ return pctx && mMediaList->Matches(pctx, nullptr);
+ }
+
+ // No media specified
+ return true;
+}
+
+/* static */ bool
+HTMLSourceElement::WouldMatchMediaForDocument(const nsAString& aMedia,
+ const nsIDocument *aDocument)
+{
+ if (aMedia.IsEmpty()) {
+ return true;
+ }
+
+ nsIPresShell* presShell = aDocument->GetShell();
+ nsPresContext* pctx = presShell ? presShell->GetPresContext() : nullptr;
+
+ nsCSSParser cssParser;
+ RefPtr<nsMediaList> mediaList = new nsMediaList();
+ cssParser.ParseMediaList(aMedia, nullptr, 0, mediaList, false);
+
+ return pctx && mediaList->Matches(pctx, nullptr);
+}
+
+void
+HTMLSourceElement::UpdateMediaList(const nsAttrValue* aValue)
+{
+ mMediaList = nullptr;
+ nsString mediaStr;
+ if (!aValue || (mediaStr = aValue->GetStringValue()).IsEmpty()) {
+ return;
+ }
+
+ nsCSSParser cssParser;
+ mMediaList = new nsMediaList();
+ cssParser.ParseMediaList(mediaStr, nullptr, 0, mMediaList, false);
+}
+
+nsresult
+HTMLSourceElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ // If we are associated with a <picture> with a valid <img>, notify it of
+ // responsive parameter changes
+ Element *parent = nsINode::GetParentElement();
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aName == nsGkAtoms::srcset ||
+ aName == nsGkAtoms::sizes ||
+ aName == nsGkAtoms::media ||
+ aName == nsGkAtoms::type) &&
+ parent && parent->IsHTMLElement(nsGkAtoms::picture)) {
+ nsString strVal = aValue ? aValue->GetStringValue() : EmptyString();
+ // Find all img siblings after this <source> and notify them of the change
+ nsCOMPtr<nsIContent> sibling = AsContent();
+ while ( (sibling = sibling->GetNextSibling()) ) {
+ if (sibling->IsHTMLElement(nsGkAtoms::img)) {
+ HTMLImageElement *img = static_cast<HTMLImageElement*>(sibling.get());
+ if (aName == nsGkAtoms::srcset) {
+ img->PictureSourceSrcsetChanged(AsContent(), strVal, aNotify);
+ } else if (aName == nsGkAtoms::sizes) {
+ img->PictureSourceSizesChanged(AsContent(), strVal, aNotify);
+ } else if (aName == nsGkAtoms::media) {
+ UpdateMediaList(aValue);
+ img->PictureSourceMediaOrTypeChanged(AsContent(), aNotify);
+ } else if (aName == nsGkAtoms::type) {
+ img->PictureSourceMediaOrTypeChanged(AsContent(), aNotify);
+ }
+ }
+ }
+
+ } else if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::media) {
+ UpdateMediaList(aValue);
+ } else if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::src) {
+ mSrcMediaSource = nullptr;
+ if (aValue) {
+ nsString srcStr = aValue->GetStringValue();
+ nsCOMPtr<nsIURI> uri;
+ NewURIFromString(srcStr, getter_AddRefs(uri));
+ if (uri && IsMediaSourceURI(uri)) {
+ NS_GetSourceForMediaSourceURI(uri, getter_AddRefs(mSrcMediaSource));
+ }
+ }
+ }
+
+ return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+nsresult
+HTMLSourceElement::BindToTree(nsIDocument *aDocument,
+ nsIContent *aParent,
+ nsIContent *aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument,
+ aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aParent && aParent->IsNodeOfType(nsINode::eMEDIA)) {
+ HTMLMediaElement* media = static_cast<HTMLMediaElement*>(aParent);
+ media->NotifyAddedSource();
+ }
+
+ return NS_OK;
+}
+
+JSObject*
+HTMLSourceElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLSourceElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLSourceElement.h b/dom/html/HTMLSourceElement.h
new file mode 100644
index 000000000..595e21015
--- /dev/null
+++ b/dom/html/HTMLSourceElement.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_HTMLSourceElement_h
+#define mozilla_dom_HTMLSourceElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsIDOMHTMLSourceElement.h"
+#include "nsGenericHTMLElement.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+
+class nsMediaList;
+class nsAttrValue;
+
+namespace mozilla {
+namespace dom {
+
+class HTMLSourceElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLSourceElement
+{
+public:
+ explicit HTMLSourceElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLSourceElement,
+ nsGenericHTMLElement)
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLSourceElement, source)
+
+ // nsIDOMHTMLSourceElement
+ NS_DECL_NSIDOMHTMLSOURCEELEMENT
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+
+ // Override BindToTree() so that we can trigger a load when we add a
+ // child source element.
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+
+ // If this element's media attr matches for its owner document. Returns true
+ // if no media attr was set.
+ bool MatchesCurrentMedia();
+
+ // True if a source tag would match the given media attribute for the
+ // specified document. Used by the preloader to determine valid <source> tags
+ // prior to DOM creation.
+ static bool WouldMatchMediaForDocument(const nsAString& aMediaStr,
+ const nsIDocument *aDocument);
+
+ // Return the MediaSource object if any associated with the src attribute
+ // when it was set.
+ MediaSource* GetSrcMediaSource() { return mSrcMediaSource; };
+
+ // WebIDL
+ void GetSrc(nsString& aSrc)
+ {
+ GetURIAttr(nsGkAtoms::src, nullptr, aSrc);
+ }
+ void SetSrc(const nsAString& aSrc, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::src, aSrc, rv);
+ }
+
+ void GetType(DOMString& aType)
+ {
+ GetHTMLAttr(nsGkAtoms::type, aType);
+ }
+ void SetType(const nsAString& aType, ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::type, aType, rv);
+ }
+
+ void GetSrcset(DOMString& aSrcset)
+ {
+ GetHTMLAttr(nsGkAtoms::srcset, aSrcset);
+ }
+ void SetSrcset(const nsAString& aSrcset, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::srcset, aSrcset, rv);
+ }
+
+ void GetSizes(DOMString& aSizes)
+ {
+ GetHTMLAttr(nsGkAtoms::sizes, aSizes);
+ }
+ void SetSizes(const nsAString& aSizes, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::sizes, aSizes, rv);
+ }
+
+ void GetMedia(DOMString& aMedia)
+ {
+ GetHTMLAttr(nsGkAtoms::media, aMedia);
+ }
+ void SetMedia(const nsAString& aMedia, mozilla::ErrorResult& rv)
+ {
+ SetHTMLAttr(nsGkAtoms::media, aMedia, rv);
+ }
+
+protected:
+ virtual ~HTMLSourceElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue,
+ bool aNotify) override;
+
+private:
+ RefPtr<nsMediaList> mMediaList;
+ RefPtr<MediaSource> mSrcMediaSource;
+
+ // Generates a new nsMediaList using the given input
+ void UpdateMediaList(const nsAttrValue* aValue);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLSourceElement_h
diff --git a/dom/html/HTMLSpanElement.cpp b/dom/html/HTMLSpanElement.cpp
new file mode 100644
index 000000000..d1888f8bd
--- /dev/null
+++ b/dom/html/HTMLSpanElement.cpp
@@ -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/. */
+
+#include "mozilla/dom/HTMLSpanElement.h"
+#include "mozilla/dom/HTMLSpanElementBinding.h"
+
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsIAtom.h"
+#include "nsRuleData.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Span)
+
+namespace mozilla {
+namespace dom {
+
+HTMLSpanElement::~HTMLSpanElement()
+{
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLSpanElement)
+
+JSObject*
+HTMLSpanElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLSpanElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLSpanElement.h b/dom/html/HTMLSpanElement.h
new file mode 100644
index 000000000..e76bc8781
--- /dev/null
+++ b/dom/html/HTMLSpanElement.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLSpanElement_h
+#define mozilla_dom_HTMLSpanElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsIAtom.h"
+#include "nsRuleData.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLSpanElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLSpanElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ }
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+protected:
+ virtual ~HTMLSpanElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLSpanElement_h
diff --git a/dom/html/HTMLStyleElement.cpp b/dom/html/HTMLStyleElement.cpp
new file mode 100644
index 000000000..329dda648
--- /dev/null
+++ b/dom/html/HTMLStyleElement.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/HTMLStyleElement.h"
+#include "mozilla/dom/HTMLStyleElementBinding.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsIDOMStyleSheet.h"
+#include "nsIDocument.h"
+#include "nsUnicharUtils.h"
+#include "nsThreadUtils.h"
+#include "nsContentUtils.h"
+#include "nsStubMutationObserver.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Style)
+
+namespace mozilla {
+namespace dom {
+
+HTMLStyleElement::HTMLStyleElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+{
+ AddMutationObserver(this);
+}
+
+HTMLStyleElement::~HTMLStyleElement()
+{
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLStyleElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLStyleElement,
+ nsGenericHTMLElement)
+ tmp->nsStyleLinkElement::Traverse(cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLStyleElement,
+ nsGenericHTMLElement)
+ tmp->nsStyleLinkElement::Unlink();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(HTMLStyleElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLStyleElement, Element)
+
+
+// QueryInterface implementation for HTMLStyleElement
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLStyleElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLStyleElement,
+ nsIDOMHTMLStyleElement,
+ nsIStyleSheetLinkingElement,
+ nsIMutationObserver)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLStyleElement)
+
+
+NS_IMETHODIMP
+HTMLStyleElement::GetMozDisabled(bool* aDisabled)
+{
+ NS_ENSURE_ARG_POINTER(aDisabled);
+
+ *aDisabled = Disabled();
+ return NS_OK;
+}
+
+bool
+HTMLStyleElement::Disabled()
+{
+ StyleSheet* ss = GetSheet();
+ return ss && ss->Disabled();
+}
+
+NS_IMETHODIMP
+HTMLStyleElement::SetMozDisabled(bool aDisabled)
+{
+ SetDisabled(aDisabled);
+ return NS_OK;
+}
+
+void
+HTMLStyleElement::SetDisabled(bool aDisabled)
+{
+ if (StyleSheet* ss = GetSheet()) {
+ ss->SetDisabled(aDisabled);
+ }
+}
+
+NS_IMPL_STRING_ATTR(HTMLStyleElement, Media, media)
+NS_IMPL_BOOL_ATTR(HTMLStyleElement, Scoped, scoped)
+NS_IMPL_STRING_ATTR(HTMLStyleElement, Type, type)
+
+void
+HTMLStyleElement::CharacterDataChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+ ContentChanged(aContent);
+}
+
+void
+HTMLStyleElement::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer)
+{
+ ContentChanged(aContainer);
+}
+
+void
+HTMLStyleElement::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ ContentChanged(aChild);
+}
+
+void
+HTMLStyleElement::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ ContentChanged(aChild);
+}
+
+void
+HTMLStyleElement::ContentChanged(nsIContent* aContent)
+{
+ if (nsContentUtils::IsInSameAnonymousTree(this, aContent)) {
+ UpdateStyleSheetInternal(nullptr, nullptr);
+ }
+}
+
+nsresult
+HTMLStyleElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ void (HTMLStyleElement::*update)() = &HTMLStyleElement::UpdateStyleSheetInternal;
+ nsContentUtils::AddScriptRunner(NewRunnableMethod(this, update));
+
+ return rv;
+}
+
+void
+HTMLStyleElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ nsCOMPtr<nsIDocument> oldDoc = GetUncomposedDoc();
+ ShadowRoot* oldShadow = GetContainingShadow();
+
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+
+ if (oldShadow && GetContainingShadow()) {
+ // The style is in a shadow tree and is still in the
+ // shadow tree. Thus the sheets in the shadow DOM
+ // do not need to be updated.
+ return;
+ }
+
+ UpdateStyleSheetInternal(oldDoc, oldShadow);
+}
+
+nsresult
+HTMLStyleElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aName == nsGkAtoms::title ||
+ aName == nsGkAtoms::media ||
+ aName == nsGkAtoms::type) {
+ UpdateStyleSheetInternal(nullptr, nullptr, true);
+ } else if (aName == nsGkAtoms::scoped) {
+ bool isScoped = aValue;
+ UpdateStyleSheetScopedness(isScoped);
+ }
+ }
+
+ return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
+ aNotify);
+}
+
+NS_IMETHODIMP
+HTMLStyleElement::GetInnerHTML(nsAString& aInnerHTML)
+{
+ if (!nsContentUtils::GetNodeTextContent(this, false, aInnerHTML, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+void
+HTMLStyleElement::SetInnerHTML(const nsAString& aInnerHTML,
+ ErrorResult& aError)
+{
+ SetEnableUpdates(false);
+
+ aError = nsContentUtils::SetNodeTextContent(this, aInnerHTML, true);
+
+ SetEnableUpdates(true);
+
+ UpdateStyleSheetInternal(nullptr, nullptr);
+}
+
+already_AddRefed<nsIURI>
+HTMLStyleElement::GetStyleSheetURL(bool* aIsInline)
+{
+ *aIsInline = true;
+ return nullptr;
+}
+
+void
+HTMLStyleElement::GetStyleSheetInfo(nsAString& aTitle,
+ nsAString& aType,
+ nsAString& aMedia,
+ bool* aIsScoped,
+ bool* aIsAlternate)
+{
+ aTitle.Truncate();
+ aType.Truncate();
+ aMedia.Truncate();
+ *aIsAlternate = false;
+
+ nsAutoString title;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::title, title);
+ title.CompressWhitespace();
+ aTitle.Assign(title);
+
+ GetAttr(kNameSpaceID_None, nsGkAtoms::media, aMedia);
+ // The HTML5 spec is formulated in terms of the CSSOM spec, which specifies
+ // that media queries should be ASCII lowercased during serialization.
+ nsContentUtils::ASCIIToLower(aMedia);
+
+ GetAttr(kNameSpaceID_None, nsGkAtoms::type, aType);
+
+ *aIsScoped = HasAttr(kNameSpaceID_None, nsGkAtoms::scoped);
+
+ nsAutoString mimeType;
+ nsAutoString notUsed;
+ nsContentUtils::SplitMimeType(aType, mimeType, notUsed);
+ if (!mimeType.IsEmpty() && !mimeType.LowerCaseEqualsLiteral("text/css")) {
+ return;
+ }
+
+ // If we get here we assume that we're loading a css file, so set the
+ // type to 'text/css'
+ aType.AssignLiteral("text/css");
+}
+
+JSObject*
+HTMLStyleElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLStyleElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
+
diff --git a/dom/html/HTMLStyleElement.h b/dom/html/HTMLStyleElement.h
new file mode 100644
index 000000000..6b2a12b1f
--- /dev/null
+++ b/dom/html/HTMLStyleElement.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_HTMLStyleElement_h
+#define mozilla_dom_HTMLStyleElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsIDOMHTMLStyleElement.h"
+#include "nsGenericHTMLElement.h"
+#include "nsStyleLinkElement.h"
+#include "nsStubMutationObserver.h"
+
+class nsIDocument;
+
+namespace mozilla {
+namespace dom {
+
+class HTMLStyleElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLStyleElement,
+ public nsStyleLinkElement,
+ public nsStubMutationObserver
+{
+public:
+ explicit HTMLStyleElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // CC
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLStyleElement,
+ nsGenericHTMLElement)
+
+ NS_IMETHOD GetInnerHTML(nsAString& aInnerHTML) override;
+ using nsGenericHTMLElement::SetInnerHTML;
+ virtual void SetInnerHTML(const nsAString& aInnerHTML,
+ mozilla::ErrorResult& aError) override;
+
+ // nsIDOMHTMLStyleElement
+ NS_DECL_NSIDOMHTMLSTYLEELEMENT
+
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue,
+ bool aNotify) override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ bool Disabled();
+ void SetDisabled(bool aDisabled);
+ void SetMedia(const nsAString& aMedia, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::media, aMedia, aError);
+ }
+ void SetType(const nsAString& aType, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::type, aType, aError);
+ }
+ bool Scoped()
+ {
+ return GetBoolAttr(nsGkAtoms::scoped);
+ }
+ void SetScoped(bool aScoped, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::scoped, aScoped, aError);
+ }
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+ virtual ~HTMLStyleElement();
+
+ already_AddRefed<nsIURI> GetStyleSheetURL(bool* aIsInline) override;
+ void GetStyleSheetInfo(nsAString& aTitle,
+ nsAString& aType,
+ nsAString& aMedia,
+ bool* aIsScoped,
+ bool* aIsAlternate) override;
+ /**
+ * Common method to call from the various mutation observer methods.
+ * aContent is a content node that's either the one that changed or its
+ * parent; we should only respond to the change if aContent is non-anonymous.
+ */
+ void ContentChanged(nsIContent* aContent);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
+
diff --git a/dom/html/HTMLSummaryElement.cpp b/dom/html/HTMLSummaryElement.cpp
new file mode 100644
index 000000000..ee3c07b20
--- /dev/null
+++ b/dom/html/HTMLSummaryElement.cpp
@@ -0,0 +1,165 @@
+/* -*- 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 "mozilla/dom/HTMLSummaryElement.h"
+
+#include "mozilla/dom/HTMLDetailsElement.h"
+#include "mozilla/dom/HTMLElementBinding.h"
+#include "mozilla/dom/HTMLUnknownElement.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEvents.h"
+#include "nsFocusManager.h"
+
+// Expand NS_IMPL_NS_NEW_HTML_ELEMENT(Summary) to add pref check.
+nsGenericHTMLElement*
+NS_NewHTMLSummaryElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ mozilla::dom::FromParser aFromParser)
+{
+ if (!mozilla::dom::HTMLDetailsElement::IsDetailsEnabled()) {
+ return new mozilla::dom::HTMLUnknownElement(aNodeInfo);
+ }
+
+ return new mozilla::dom::HTMLSummaryElement(aNodeInfo);
+}
+
+namespace mozilla {
+namespace dom {
+
+HTMLSummaryElement::~HTMLSummaryElement()
+{
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLSummaryElement)
+
+nsresult
+HTMLSummaryElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ nsresult rv = NS_OK;
+ if (!aVisitor.mPresContext) {
+ return rv;
+ }
+
+ if (aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault) {
+ return rv;
+ }
+
+ if (!IsMainSummary()) {
+ return rv;
+ }
+
+ WidgetEvent* const event = aVisitor.mEvent;
+
+ if (event->HasMouseEventMessage()) {
+ WidgetMouseEvent* mouseEvent = event->AsMouseEvent();
+
+ if (mouseEvent->IsLeftClickEvent()) {
+ RefPtr<HTMLDetailsElement> details = GetDetails();
+ MOZ_ASSERT(details,
+ "Expected to find details since this is the main summary!");
+
+ // When dispatching a synthesized mouse click event to a details element
+ // with 'display: none', both Chrome and Safari do not toggle the 'open'
+ // attribute. We had tried to be compatible with this behavior, but found
+ // more inconsistency in test cases in bug 1245424. So we stop doing that.
+ details->ToggleOpen();
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ return NS_OK;
+ }
+ } // event->HasMouseEventMessage()
+
+ if (event->HasKeyEventMessage()) {
+ WidgetKeyboardEvent* keyboardEvent = event->AsKeyboardEvent();
+ bool dispatchClick = false;
+
+ switch (event->mMessage) {
+ case eKeyPress:
+ if (keyboardEvent->mCharCode == ' ') {
+ // Consume 'space' key to prevent scrolling the page down.
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+
+ dispatchClick = keyboardEvent->mKeyCode == NS_VK_RETURN;
+ break;
+
+ case eKeyUp:
+ dispatchClick = keyboardEvent->mKeyCode == NS_VK_SPACE;
+ break;
+
+ default:
+ break;
+ }
+
+ if (dispatchClick) {
+ rv = DispatchSimulatedClick(this, event->mFlags.mIsTrusted,
+ aVisitor.mPresContext);
+ if (NS_SUCCEEDED(rv)) {
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ } // event->HasKeyEventMessage()
+
+ return rv;
+}
+
+bool
+HTMLSummaryElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+ int32_t* aTabIndex)
+{
+ bool disallowOverridingFocusability =
+ nsGenericHTMLElement::IsHTMLFocusable(aWithMouse, aIsFocusable, aTabIndex);
+
+ if (disallowOverridingFocusability || !IsMainSummary()) {
+ return disallowOverridingFocusability;
+ }
+
+#ifdef XP_MACOSX
+ // The parent does not have strong opinion about the focusability of this main
+ // summary element, but we'd like to override it when mouse clicking on Mac OS
+ // like other form elements.
+ *aIsFocusable = !aWithMouse || nsFocusManager::sMouseFocusesFormControl;
+#else
+ // The main summary element is focusable on other platforms.
+ *aIsFocusable = true;
+#endif
+
+ // Give a chance to allow the subclass to override aIsFocusable.
+ return false;
+}
+
+int32_t
+HTMLSummaryElement::TabIndexDefault()
+{
+ // Make the main summary be able to navigate via tab, and be focusable.
+ // See nsGenericHTMLElement::IsHTMLFocusable().
+ return IsMainSummary() ? 0 : nsGenericHTMLElement::TabIndexDefault();
+}
+
+bool
+HTMLSummaryElement::IsMainSummary() const
+{
+ HTMLDetailsElement* details = GetDetails();
+ if (!details) {
+ return false;
+ }
+
+ return details->GetFirstSummary() == this || IsRootOfNativeAnonymousSubtree();
+}
+
+HTMLDetailsElement*
+HTMLSummaryElement::GetDetails() const
+{
+ return HTMLDetailsElement::FromContentOrNull(GetParent());
+}
+
+JSObject*
+HTMLSummaryElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLSummaryElement.h b/dom/html/HTMLSummaryElement.h
new file mode 100644
index 000000000..587383bc6
--- /dev/null
+++ b/dom/html/HTMLSummaryElement.h
@@ -0,0 +1,59 @@
+/* -*- 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_HTMLSummaryElement_h
+#define mozilla_dom_HTMLSummaryElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+namespace dom {
+class HTMLDetailsElement;
+
+// HTMLSummaryElement implements the <summary> tag, which is used as a summary
+// or legend of the <details> tag. Please see the spec for more information.
+// https://html.spec.whatwg.org/multipage/forms.html#the-details-element
+//
+class HTMLSummaryElement final : public nsGenericHTMLElement
+{
+public:
+ using NodeInfo = mozilla::dom::NodeInfo;
+
+ explicit HTMLSummaryElement(already_AddRefed<NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ }
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLSummaryElement, summary)
+
+ nsresult Clone(NodeInfo* aNodeInfo, nsINode** aResult) const override;
+
+ nsresult PostHandleEvent(EventChainPostVisitor& aVisitor) override;
+
+ bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+ int32_t* aTabIndex) override;
+
+ int32_t TabIndexDefault() override;
+
+ // Return true if this is the first summary element child of a details or the
+ // default summary element generated by DetailsFrame.
+ bool IsMainSummary() const;
+
+ // Return the details element which contains this summary. Otherwise return
+ // nullptr if there is no such details element.
+ HTMLDetailsElement* GetDetails() const;
+
+protected:
+ virtual ~HTMLSummaryElement();
+
+ JSObject* WrapNode(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_HTMLSummaryElement_h */
diff --git a/dom/html/HTMLTableCaptionElement.cpp b/dom/html/HTMLTableCaptionElement.cpp
new file mode 100644
index 000000000..fd9328de8
--- /dev/null
+++ b/dom/html/HTMLTableCaptionElement.cpp
@@ -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/. */
+
+#include "mozilla/dom/HTMLTableCaptionElement.h"
+#include "nsAttrValueInlines.h"
+#include "nsMappedAttributes.h"
+#include "nsRuleData.h"
+#include "mozilla/dom/HTMLTableCaptionElementBinding.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(TableCaption)
+
+namespace mozilla {
+namespace dom {
+
+HTMLTableCaptionElement::~HTMLTableCaptionElement()
+{
+}
+
+JSObject*
+HTMLTableCaptionElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLTableCaptionElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLTableCaptionElement)
+
+static const nsAttrValue::EnumTable kCaptionAlignTable[] = {
+ { "left", NS_STYLE_CAPTION_SIDE_LEFT },
+ { "right", NS_STYLE_CAPTION_SIDE_RIGHT },
+ { "top", NS_STYLE_CAPTION_SIDE_TOP },
+ { "bottom", NS_STYLE_CAPTION_SIDE_BOTTOM },
+ { nullptr, 0 }
+};
+
+bool
+HTMLTableCaptionElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aAttribute == nsGkAtoms::align && aNamespaceID == kNameSpaceID_None) {
+ return aResult.ParseEnumValue(aValue, kCaptionAlignTable, false);
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLTableCaptionElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(TableBorder)) {
+ nsCSSValue* captionSide = aData->ValueForCaptionSide();
+ if (captionSide->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align);
+ if (value && value->Type() == nsAttrValue::eEnum)
+ captionSide->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
+ }
+ }
+
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLTableCaptionElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::align },
+ { nullptr }
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+nsMapRuleToAttributesFunc
+HTMLTableCaptionElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLTableCaptionElement.h b/dom/html/HTMLTableCaptionElement.h
new file mode 100644
index 000000000..1c84118d7
--- /dev/null
+++ b/dom/html/HTMLTableCaptionElement.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef mozilla_dom_HTMLTableCaptionElement_h
+#define mozilla_dom_HTMLTableCaptionElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLTableCaptionElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLTableCaptionElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ SetHasWeirdParserInsertionMode();
+ }
+
+ void GetAlign(DOMString& aAlign)
+ {
+ GetHTMLAttr(nsGkAtoms::align, aAlign);
+ }
+ void SetAlign(const nsAString& aAlign, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::align, aAlign, aError);
+ }
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+protected:
+ virtual ~HTMLTableCaptionElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_HTMLTableCaptionElement_h */
diff --git a/dom/html/HTMLTableCellElement.cpp b/dom/html/HTMLTableCellElement.cpp
new file mode 100644
index 000000000..d00d60400
--- /dev/null
+++ b/dom/html/HTMLTableCellElement.cpp
@@ -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/. */
+
+#include "mozilla/dom/HTMLTableCellElement.h"
+#include "mozilla/dom/HTMLTableElement.h"
+#include "mozilla/dom/HTMLTableRowElement.h"
+#include "nsMappedAttributes.h"
+#include "nsAttrValueInlines.h"
+#include "nsRuleData.h"
+#include "nsRuleWalker.h"
+#include "celldata.h"
+#include "mozilla/dom/HTMLTableCellElementBinding.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(TableCell)
+
+namespace mozilla {
+namespace dom {
+
+HTMLTableCellElement::~HTMLTableCellElement()
+{
+}
+
+JSObject*
+HTMLTableCellElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLTableCellElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLTableCellElement, nsGenericHTMLElement,
+ nsIDOMHTMLTableCellElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLTableCellElement)
+
+
+// protected method
+HTMLTableRowElement*
+HTMLTableCellElement::GetRow() const
+{
+ return HTMLTableRowElement::FromContentOrNull(GetParent());
+}
+
+// protected method
+HTMLTableElement*
+HTMLTableCellElement::GetTable() const
+{
+ nsIContent *parent = GetParent();
+ if (!parent) {
+ return nullptr;
+ }
+
+ // parent should be a row.
+ nsIContent* section = parent->GetParent();
+ if (!section) {
+ return nullptr;
+ }
+
+ if (section->IsHTMLElement(nsGkAtoms::table)) {
+ // XHTML, without a row group.
+ return static_cast<HTMLTableElement*>(section);
+ }
+
+ // We have a row group.
+ nsIContent* result = section->GetParent();
+ if (result && result->IsHTMLElement(nsGkAtoms::table)) {
+ return static_cast<HTMLTableElement*>(result);
+ }
+
+ return nullptr;
+}
+
+int32_t
+HTMLTableCellElement::CellIndex() const
+{
+ HTMLTableRowElement* row = GetRow();
+ if (!row) {
+ return -1;
+ }
+
+ nsIHTMLCollection* cells = row->Cells();
+ if (!cells) {
+ return -1;
+ }
+
+ uint32_t numCells = cells->Length();
+ for (uint32_t i = 0; i < numCells; i++) {
+ if (cells->Item(i) == this) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::GetCellIndex(int32_t* aCellIndex)
+{
+ *aCellIndex = CellIndex();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::WalkContentStyleRules(nsRuleWalker* aRuleWalker)
+{
+ nsresult rv = nsGenericHTMLElement::WalkContentStyleRules(aRuleWalker);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (HTMLTableElement* table = GetTable()) {
+ nsMappedAttributes* tableInheritedAttributes =
+ table->GetAttributesMappedForCell();
+ if (tableInheritedAttributes) {
+ aRuleWalker->Forward(tableInheritedAttributes);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::SetAbbr(const nsAString& aAbbr)
+{
+ ErrorResult rv;
+ SetAbbr(aAbbr, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::GetAbbr(nsAString& aAbbr)
+{
+ DOMString abbr;
+ GetAbbr(abbr);
+ abbr.ToString(aAbbr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::SetAxis(const nsAString& aAxis)
+{
+ ErrorResult rv;
+ SetAxis(aAxis, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::GetAxis(nsAString& aAxis)
+{
+ DOMString axis;
+ GetAxis(axis);
+ axis.ToString(aAxis);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::SetAlign(const nsAString& aAlign)
+{
+ ErrorResult rv;
+ SetAlign(aAlign, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::GetAlign(nsAString& aAlign)
+{
+ DOMString align;
+ GetAlign(align);
+ align.ToString(aAlign);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::SetVAlign(const nsAString& aVAlign)
+{
+ ErrorResult rv;
+ SetVAlign(aVAlign, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::GetVAlign(nsAString& aVAlign)
+{
+ DOMString vAlign;
+ GetVAlign(vAlign);
+ vAlign.ToString(aVAlign);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::SetCh(const nsAString& aCh)
+{
+ ErrorResult rv;
+ SetCh(aCh, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::GetCh(nsAString& aCh)
+{
+ DOMString ch;
+ GetCh(ch);
+ ch.ToString(aCh);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::SetChOff(const nsAString& aChOff)
+{
+ ErrorResult rv;
+ SetChOff(aChOff, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::GetChOff(nsAString& aChOff)
+{
+ DOMString chOff;
+ GetChOff(chOff);
+ chOff.ToString(aChOff);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::SetBgColor(const nsAString& aBgColor)
+{
+ ErrorResult rv;
+ SetBgColor(aBgColor, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::GetBgColor(nsAString& aBgColor)
+{
+ DOMString bgColor;
+ GetBgColor(bgColor);
+ bgColor.ToString(aBgColor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::SetHeight(const nsAString& aHeight)
+{
+ ErrorResult rv;
+ SetHeight(aHeight, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::GetHeight(nsAString& aHeight)
+{
+ DOMString height;
+ GetHeight(height);
+ height.ToString(aHeight);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::SetWidth(const nsAString& aWidth)
+{
+ ErrorResult rv;
+ SetWidth(aWidth, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::GetWidth(nsAString& aWidth)
+{
+ DOMString width;
+ GetWidth(width);
+ width.ToString(aWidth);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::SetNoWrap(bool aNoWrap)
+{
+ ErrorResult rv;
+ SetNoWrap(aNoWrap, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::GetNoWrap(bool* aNoWrap)
+{
+ *aNoWrap = NoWrap();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::SetScope(const nsAString& aScope)
+{
+ ErrorResult rv;
+ SetScope(aScope, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::GetScope(nsAString& aScope)
+{
+ DOMString scope;
+ GetScope(scope);
+ scope.ToString(aScope);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::SetHeaders(const nsAString& aHeaders)
+{
+ ErrorResult rv;
+ SetHeaders(aHeaders, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::GetHeaders(nsAString& aHeaders)
+{
+ DOMString headers;
+ GetHeaders(headers);
+ headers.ToString(aHeaders);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::SetColSpan(int32_t aColSpan)
+{
+ ErrorResult rv;
+ SetColSpan(aColSpan, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::GetColSpan(int32_t* aColSpan)
+{
+ *aColSpan = ColSpan();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::SetRowSpan(int32_t aRowSpan)
+{
+ ErrorResult rv;
+ SetRowSpan(aRowSpan, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLTableCellElement::GetRowSpan(int32_t* aRowSpan)
+{
+ *aRowSpan = RowSpan();
+ return NS_OK;
+}
+
+void
+HTMLTableCellElement::GetAlign(DOMString& aValue)
+{
+ if (!GetAttr(kNameSpaceID_None, nsGkAtoms::align, aValue)) {
+ // There's no align attribute, ask the row for the alignment.
+ HTMLTableRowElement* row = GetRow();
+ if (row) {
+ row->GetAlign(aValue);
+ }
+ }
+}
+
+static const nsAttrValue::EnumTable kCellScopeTable[] = {
+ { "row", NS_STYLE_CELL_SCOPE_ROW },
+ { "col", NS_STYLE_CELL_SCOPE_COL },
+ { "rowgroup", NS_STYLE_CELL_SCOPE_ROWGROUP },
+ { "colgroup", NS_STYLE_CELL_SCOPE_COLGROUP },
+ { nullptr, 0 }
+};
+
+void
+HTMLTableCellElement::GetScope(DOMString& aScope)
+{
+ GetEnumAttr(nsGkAtoms::scope, nullptr, aScope);
+}
+
+bool
+HTMLTableCellElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ /* ignore these attributes, stored simply as strings
+ abbr, axis, ch, headers
+ */
+ if (aAttribute == nsGkAtoms::charoff) {
+ /* attributes that resolve to integers with a min of 0 */
+ return aResult.ParseIntWithBounds(aValue, 0);
+ }
+ if (aAttribute == nsGkAtoms::colspan) {
+ bool res = aResult.ParseIntWithBounds(aValue, -1);
+ if (res) {
+ int32_t val = aResult.GetIntegerValue();
+ // reset large colspan values as IE and opera do
+ if (val > MAX_COLSPAN || val <= 0) {
+ aResult.SetTo(1, &aValue);
+ }
+ }
+ return res;
+ }
+ if (aAttribute == nsGkAtoms::rowspan) {
+ bool res = aResult.ParseIntWithBounds(aValue, -1, MAX_ROWSPAN);
+ if (res) {
+ int32_t val = aResult.GetIntegerValue();
+ // quirks mode does not honor the special html 4 value of 0
+ if (val < 0 || (0 == val && InNavQuirksMode(OwnerDoc()))) {
+ aResult.SetTo(1, &aValue);
+ }
+ }
+ return res;
+ }
+ if (aAttribute == nsGkAtoms::height) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::width) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::align) {
+ return ParseTableCellHAlignValue(aValue, aResult);
+ }
+ if (aAttribute == nsGkAtoms::bgcolor) {
+ return aResult.ParseColor(aValue);
+ }
+ if (aAttribute == nsGkAtoms::scope) {
+ return aResult.ParseEnumValue(aValue, kCellScopeTable, false);
+ }
+ if (aAttribute == nsGkAtoms::valign) {
+ return ParseTableVAlignValue(aValue, aResult);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseBackgroundAttribute(aNamespaceID,
+ aAttribute, aValue,
+ aResult) ||
+ nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLTableCellElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Position)) {
+ // width: value
+ nsCSSValue* width = aData->ValueForWidth();
+ if (width->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::width);
+ if (value && value->Type() == nsAttrValue::eInteger) {
+ if (value->GetIntegerValue() > 0)
+ width->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ // else 0 implies auto for compatibility.
+ }
+ else if (value && value->Type() == nsAttrValue::ePercent) {
+ if (value->GetPercentValue() > 0.0f)
+ width->SetPercentValue(value->GetPercentValue());
+ // else 0 implies auto for compatibility
+ }
+ }
+
+ // height: value
+ nsCSSValue* height = aData->ValueForHeight();
+ if (height->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::height);
+ if (value && value->Type() == nsAttrValue::eInteger) {
+ if (value->GetIntegerValue() > 0)
+ height->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ // else 0 implies auto for compatibility.
+ }
+ else if (value && value->Type() == nsAttrValue::ePercent) {
+ if (value->GetPercentValue() > 0.0f)
+ height->SetPercentValue(value->GetPercentValue());
+ // else 0 implies auto for compatibility
+ }
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Text)) {
+ nsCSSValue* textAlign = aData->ValueForTextAlign();
+ if (textAlign->GetUnit() == eCSSUnit_Null) {
+ // align: enum
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align);
+ if (value && value->Type() == nsAttrValue::eEnum)
+ textAlign->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
+ }
+
+ nsCSSValue* whiteSpace = aData->ValueForWhiteSpace();
+ if (whiteSpace->GetUnit() == eCSSUnit_Null) {
+ // nowrap: enum
+ if (aAttributes->GetAttr(nsGkAtoms::nowrap)) {
+ // See if our width is not a nonzero integer width.
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::width);
+ nsCompatibility mode = aData->mPresContext->CompatibilityMode();
+ if (!value || value->Type() != nsAttrValue::eInteger ||
+ value->GetIntegerValue() == 0 ||
+ eCompatibility_NavQuirks != mode) {
+ whiteSpace->SetIntValue(NS_STYLE_WHITESPACE_NOWRAP, eCSSUnit_Enumerated);
+ }
+ }
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Display)) {
+ nsCSSValue* verticalAlign = aData->ValueForVerticalAlign();
+ if (verticalAlign->GetUnit() == eCSSUnit_Null) {
+ // valign: enum
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::valign);
+ if (value && value->Type() == nsAttrValue::eEnum)
+ verticalAlign->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
+ }
+ }
+
+ nsGenericHTMLElement::MapBackgroundAttributesInto(aAttributes, aData);
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLTableCellElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::align },
+ { &nsGkAtoms::valign },
+ { &nsGkAtoms::nowrap },
+#if 0
+ // XXXldb If these are implemented, they might need to move to
+ // GetAttributeChangeHint (depending on how, and preferably not).
+ { &nsGkAtoms::abbr },
+ { &nsGkAtoms::axis },
+ { &nsGkAtoms::headers },
+ { &nsGkAtoms::scope },
+#endif
+ { &nsGkAtoms::width },
+ { &nsGkAtoms::height },
+ { nullptr }
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap,
+ sBackgroundAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+nsMapRuleToAttributesFunc
+HTMLTableCellElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLTableCellElement.h b/dom/html/HTMLTableCellElement.h
new file mode 100644
index 000000000..916333510
--- /dev/null
+++ b/dom/html/HTMLTableCellElement.h
@@ -0,0 +1,171 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef mozilla_dom_HTMLTableCellElement_h
+#define mozilla_dom_HTMLTableCellElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIDOMHTMLTableCellElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLTableElement;
+
+class HTMLTableCellElement final : public nsGenericHTMLElement,
+ public nsIDOMHTMLTableCellElement
+{
+public:
+ explicit HTMLTableCellElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ SetHasWeirdParserInsertionMode();
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMHTMLTableCellElement
+ NS_DECL_NSIDOMHTMLTABLECELLELEMENT
+
+ uint32_t ColSpan() const
+ {
+ return GetIntAttr(nsGkAtoms::colspan, 1);
+ }
+ void SetColSpan(uint32_t aColSpan, ErrorResult& aError)
+ {
+ SetHTMLIntAttr(nsGkAtoms::colspan, aColSpan, aError);
+ }
+ uint32_t RowSpan() const
+ {
+ return GetIntAttr(nsGkAtoms::rowspan, 1);
+ }
+ void SetRowSpan(uint32_t aRowSpan, ErrorResult& aError)
+ {
+ SetHTMLIntAttr(nsGkAtoms::rowspan, aRowSpan, aError);
+ }
+ //already_AddRefed<nsDOMTokenList> Headers() const;
+ void GetHeaders(DOMString& aHeaders)
+ {
+ GetHTMLAttr(nsGkAtoms::headers, aHeaders);
+ }
+ void SetHeaders(const nsAString& aHeaders, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::headers, aHeaders, aError);
+ }
+ int32_t CellIndex() const;
+
+ void GetAbbr(DOMString& aAbbr)
+ {
+ GetHTMLAttr(nsGkAtoms::abbr, aAbbr);
+ }
+ void SetAbbr(const nsAString& aAbbr, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::abbr, aAbbr, aError);
+ }
+ void GetScope(DOMString& aScope);
+ void SetScope(const nsAString& aScope, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::scope, aScope, aError);
+ }
+ void GetAlign(DOMString& aAlign);
+ void SetAlign(const nsAString& aAlign, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::align, aAlign, aError);
+ }
+ void GetAxis(DOMString& aAxis)
+ {
+ GetHTMLAttr(nsGkAtoms::axis, aAxis);
+ }
+ void SetAxis(const nsAString& aAxis, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::axis, aAxis, aError);
+ }
+ void GetHeight(DOMString& aHeight)
+ {
+ GetHTMLAttr(nsGkAtoms::height, aHeight);
+ }
+ void SetHeight(const nsAString& aHeight, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::height, aHeight, aError);
+ }
+ void GetWidth(DOMString& aWidth)
+ {
+ GetHTMLAttr(nsGkAtoms::width, aWidth);
+ }
+ void SetWidth(const nsAString& aWidth, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::width, aWidth, aError);
+ }
+ void GetCh(DOMString& aCh)
+ {
+ GetHTMLAttr(nsGkAtoms::_char, aCh);
+ }
+ void SetCh(const nsAString& aCh, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::_char, aCh, aError);
+ }
+ void GetChOff(DOMString& aChOff)
+ {
+ GetHTMLAttr(nsGkAtoms::charoff, aChOff);
+ }
+ void SetChOff(const nsAString& aChOff, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::charoff, aChOff, aError);
+ }
+ bool NoWrap()
+ {
+ return GetBoolAttr(nsGkAtoms::nowrap);
+ }
+ void SetNoWrap(bool aNoWrap, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::nowrap, aNoWrap, aError);
+ }
+ void GetVAlign(DOMString& aVAlign)
+ {
+ GetHTMLAttr(nsGkAtoms::valign, aVAlign);
+ }
+ void SetVAlign(const nsAString& aVAlign, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::valign, aVAlign, aError);
+ }
+ void GetBgColor(DOMString& aBgColor)
+ {
+ GetHTMLAttr(nsGkAtoms::bgcolor, aBgColor);
+ }
+ void SetBgColor(const nsAString& aBgColor, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::bgcolor, aBgColor, aError);
+ }
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ NS_IMETHOD WalkContentStyleRules(nsRuleWalker* aRuleWalker) override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+protected:
+ virtual ~HTMLTableCellElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ HTMLTableElement* GetTable() const;
+
+ HTMLTableRowElement* GetRow() const;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_HTMLTableCellElement_h */
diff --git a/dom/html/HTMLTableColElement.cpp b/dom/html/HTMLTableColElement.cpp
new file mode 100644
index 000000000..484070bf0
--- /dev/null
+++ b/dom/html/HTMLTableColElement.cpp
@@ -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/. */
+
+#include "mozilla/dom/HTMLTableColElement.h"
+#include "nsMappedAttributes.h"
+#include "nsAttrValueInlines.h"
+#include "nsRuleData.h"
+#include "mozilla/dom/HTMLTableColElementBinding.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(TableCol)
+
+namespace mozilla {
+namespace dom {
+
+// use the same protection as ancient code did
+// http://lxr.mozilla.org/classic/source/lib/layout/laytable.c#46
+#define MAX_COLSPAN 1000
+
+HTMLTableColElement::~HTMLTableColElement()
+{
+}
+
+JSObject*
+HTMLTableColElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLTableColElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLTableColElement)
+
+bool
+HTMLTableColElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ /* ignore these attributes, stored simply as strings ch */
+ if (aAttribute == nsGkAtoms::charoff) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::span) {
+ /* protection from unrealistic large colspan values */
+ aResult.ParseIntWithFallback(aValue, 1, MAX_COLSPAN);
+ return true;
+ }
+ if (aAttribute == nsGkAtoms::width) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::align) {
+ return ParseTableCellHAlignValue(aValue, aResult);
+ }
+ if (aAttribute == nsGkAtoms::valign) {
+ return ParseTableVAlignValue(aValue, aResult);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLTableColElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Table)) {
+ nsCSSValue *span = aData->ValueForSpan();
+ if (span->GetUnit() == eCSSUnit_Null) {
+ // span: int
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::span);
+ if (value && value->Type() == nsAttrValue::eInteger) {
+ int32_t val = value->GetIntegerValue();
+ // Note: Do NOT use this code for table cells! The value "0"
+ // means something special for colspan and rowspan, but for <col
+ // span> and <colgroup span> it's just disallowed.
+ if (val > 0) {
+ span->SetIntValue(value->GetIntegerValue(), eCSSUnit_Integer);
+ }
+ }
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Position)) {
+ nsCSSValue* width = aData->ValueForWidth();
+ if (width->GetUnit() == eCSSUnit_Null) {
+ // width
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::width);
+ if (value) {
+ switch (value->Type()) {
+ case nsAttrValue::ePercent: {
+ width->SetPercentValue(value->GetPercentValue());
+ break;
+ }
+ case nsAttrValue::eInteger: {
+ width->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Text)) {
+ nsCSSValue* textAlign = aData->ValueForTextAlign();
+ if (textAlign->GetUnit() == eCSSUnit_Null) {
+ // align: enum
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align);
+ if (value && value->Type() == nsAttrValue::eEnum)
+ textAlign->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Display)) {
+ nsCSSValue* verticalAlign = aData->ValueForVerticalAlign();
+ if (verticalAlign->GetUnit() == eCSSUnit_Null) {
+ // valign: enum
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::valign);
+ if (value && value->Type() == nsAttrValue::eEnum)
+ verticalAlign->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
+ }
+ }
+
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLTableColElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::width },
+ { &nsGkAtoms::align },
+ { &nsGkAtoms::valign },
+ { &nsGkAtoms::span },
+ { nullptr }
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+
+nsMapRuleToAttributesFunc
+HTMLTableColElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLTableColElement.h b/dom/html/HTMLTableColElement.h
new file mode 100644
index 000000000..8bdc7634e
--- /dev/null
+++ b/dom/html/HTMLTableColElement.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef mozilla_dom_HTMLTableColElement_h
+#define mozilla_dom_HTMLTableColElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLTableColElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLTableColElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ SetHasWeirdParserInsertionMode();
+ }
+
+ uint32_t Span() const
+ {
+ return GetIntAttr(nsGkAtoms::span, 1);
+ }
+ void SetSpan(uint32_t aSpan, ErrorResult& aError)
+ {
+ uint32_t span = aSpan ? aSpan : 1;
+ SetUnsignedIntAttr(nsGkAtoms::span, span, 1, aError);
+ }
+
+ void GetAlign(DOMString& aAlign)
+ {
+ GetHTMLAttr(nsGkAtoms::align, aAlign);
+ }
+ void SetAlign(const nsAString& aAlign, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::align, aAlign, aError);
+ }
+ void GetCh(DOMString& aCh)
+ {
+ GetHTMLAttr(nsGkAtoms::_char, aCh);
+ }
+ void SetCh(const nsAString& aCh, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::_char, aCh, aError);
+ }
+ void GetChOff(DOMString& aChOff)
+ {
+ GetHTMLAttr(nsGkAtoms::charoff, aChOff);
+ }
+ void SetChOff(const nsAString& aChOff, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::charoff, aChOff, aError);
+ }
+ void GetVAlign(DOMString& aVAlign)
+ {
+ GetHTMLAttr(nsGkAtoms::valign, aVAlign);
+ }
+ void SetVAlign(const nsAString& aVAlign, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::valign, aVAlign, aError);
+ }
+ void GetWidth(DOMString& aWidth)
+ {
+ GetHTMLAttr(nsGkAtoms::width, aWidth);
+ }
+ void SetWidth(const nsAString& aWidth, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::width, aWidth, aError);
+ }
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+protected:
+ virtual ~HTMLTableColElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_HTMLTableColElement_h */
diff --git a/dom/html/HTMLTableElement.cpp b/dom/html/HTMLTableElement.cpp
new file mode 100644
index 000000000..ec1b7cecb
--- /dev/null
+++ b/dom/html/HTMLTableElement.cpp
@@ -0,0 +1,1015 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLTableElement.h"
+#include "nsAttrValueInlines.h"
+#include "nsRuleData.h"
+#include "nsHTMLStyleSheet.h"
+#include "nsMappedAttributes.h"
+#include "mozilla/dom/HTMLCollectionBinding.h"
+#include "mozilla/dom/HTMLTableElementBinding.h"
+#include "nsContentUtils.h"
+#include "jsfriendapi.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Table)
+
+namespace mozilla {
+namespace dom {
+
+/* ------------------------------ TableRowsCollection -------------------------------- */
+/**
+ * This class provides a late-bound collection of rows in a table.
+ * mParent is NOT ref-counted to avoid circular references
+ */
+class TableRowsCollection final : public nsIHTMLCollection,
+ public nsWrapperCache
+{
+public:
+ explicit TableRowsCollection(HTMLTableElement* aParent);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIDOMHTMLCOLLECTION
+
+ virtual Element* GetElementAt(uint32_t aIndex) override;
+ virtual nsINode* GetParentObject() override
+ {
+ return mParent;
+ }
+
+ virtual Element*
+ GetFirstNamedElement(const nsAString& aName, bool& aFound) override;
+ virtual void GetSupportedNames(nsTArray<nsString>& aNames) override;
+
+ NS_IMETHOD ParentDestroyed();
+
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TableRowsCollection)
+
+ // nsWrapperCache
+ using nsWrapperCache::GetWrapperPreserveColor;
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+protected:
+ virtual ~TableRowsCollection();
+
+ virtual JSObject* GetWrapperPreserveColorInternal() override
+ {
+ return nsWrapperCache::GetWrapperPreserveColor();
+ }
+
+ // Those rows that are not in table sections
+ HTMLTableElement* mParent;
+};
+
+
+TableRowsCollection::TableRowsCollection(HTMLTableElement *aParent)
+ : mParent(aParent)
+{
+}
+
+TableRowsCollection::~TableRowsCollection()
+{
+ // we do NOT have a ref-counted reference to mParent, so do NOT
+ // release it! this is to avoid circular references. The
+ // instantiator who provided mParent is responsible for managing our
+ // reference for us.
+}
+
+JSObject*
+TableRowsCollection::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLCollectionBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(TableRowsCollection)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TableRowsCollection)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TableRowsCollection)
+
+NS_INTERFACE_TABLE_HEAD(TableRowsCollection)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE(TableRowsCollection, nsIHTMLCollection,
+ nsIDOMHTMLCollection)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(TableRowsCollection)
+NS_INTERFACE_MAP_END
+
+// Macro that can be used to avoid copy/pasting code to iterate over the
+// rowgroups. _code should be the code to execute for each rowgroup. The
+// rowgroup's rows will be in the nsIDOMHTMLCollection* named "rows".
+// _trCode should be the code to execute for each tr row. Note that
+// this may be null at any time. This macro assumes an nsresult named
+// |rv| is in scope.
+ #define DO_FOR_EACH_BY_ORDER(_code, _trCode) \
+ do { \
+ if (mParent) { \
+ HTMLTableSectionElement* rowGroup; \
+ nsIHTMLCollection* rows; \
+ /* THead */ \
+ for (nsIContent* _node = mParent->nsINode::GetFirstChild(); \
+ _node; _node = _node->GetNextSibling()) { \
+ if (_node->IsHTMLElement(nsGkAtoms::thead)) { \
+ rowGroup = static_cast<HTMLTableSectionElement*>(_node);\
+ rows = rowGroup->Rows(); \
+ do { /* gives scoping */ \
+ _code \
+ } while (0); \
+ } \
+ } \
+ /* TBodies */ \
+ for (nsIContent* _node = mParent->nsINode::GetFirstChild(); \
+ _node; _node = _node->GetNextSibling()) { \
+ if (_node->IsHTMLElement(nsGkAtoms::tr)) { \
+ do { \
+ _trCode \
+ } while (0); \
+ } else if (_node->IsHTMLElement(nsGkAtoms::tbody)) { \
+ rowGroup = static_cast<HTMLTableSectionElement*>(_node); \
+ rows = rowGroup->Rows(); \
+ do { /* gives scoping */ \
+ _code \
+ } while (0); \
+ } \
+ } \
+ /* TFoot */ \
+ for (nsIContent* _node = mParent->nsINode::GetFirstChild(); \
+ _node; _node = _node->GetNextSibling()) { \
+ if (_node->IsHTMLElement(nsGkAtoms::tfoot)) { \
+ rowGroup = static_cast<HTMLTableSectionElement*>(_node);\
+ rows = rowGroup->Rows(); \
+ do { /* gives scoping */ \
+ _code \
+ } while (0); \
+ } \
+ } \
+ } \
+ } while (0)
+
+static uint32_t
+CountRowsInRowGroup(nsIDOMHTMLCollection* rows)
+{
+ uint32_t length = 0;
+
+ if (rows) {
+ rows->GetLength(&length);
+ }
+
+ return length;
+}
+
+// we re-count every call. A better implementation would be to set
+// ourselves up as an observer of contentAppended, contentInserted,
+// and contentDeleted
+NS_IMETHODIMP
+TableRowsCollection::GetLength(uint32_t* aLength)
+{
+ *aLength=0;
+
+ DO_FOR_EACH_BY_ORDER({
+ *aLength += CountRowsInRowGroup(rows);
+ }, {
+ (*aLength)++;
+ });
+
+ return NS_OK;
+}
+
+// Returns the item at index aIndex if available. If null is returned,
+// then aCount will be set to the number of rows in this row collection.
+// Otherwise, the value of aCount is undefined.
+static Element*
+GetItemOrCountInRowGroup(nsIDOMHTMLCollection* rows,
+ uint32_t aIndex, uint32_t* aCount)
+{
+ *aCount = 0;
+
+ if (rows) {
+ rows->GetLength(aCount);
+ if (aIndex < *aCount) {
+ nsIHTMLCollection* list = static_cast<nsIHTMLCollection*>(rows);
+ return list->GetElementAt(aIndex);
+ }
+ }
+
+ return nullptr;
+}
+
+Element*
+TableRowsCollection::GetElementAt(uint32_t aIndex)
+{
+ DO_FOR_EACH_BY_ORDER({
+ uint32_t count;
+ Element* node = GetItemOrCountInRowGroup(rows, aIndex, &count);
+ if (node) {
+ return node;
+ }
+
+ NS_ASSERTION(count <= aIndex, "GetItemOrCountInRowGroup screwed up");
+ aIndex -= count;
+ },{
+ if (aIndex == 0) {
+ return _node->AsElement();
+ }
+ aIndex--;
+ });
+
+ return nullptr;
+}
+
+NS_IMETHODIMP
+TableRowsCollection::Item(uint32_t aIndex, nsIDOMNode** aReturn)
+{
+ nsISupports* node = GetElementAt(aIndex);
+ if (!node) {
+ *aReturn = nullptr;
+
+ return NS_OK;
+ }
+
+ return CallQueryInterface(node, aReturn);
+}
+
+Element*
+TableRowsCollection::GetFirstNamedElement(const nsAString& aName, bool& aFound)
+{
+ aFound = false;
+ nsCOMPtr<nsIAtom> nameAtom = NS_Atomize(aName);
+ NS_ENSURE_TRUE(nameAtom, nullptr);
+ DO_FOR_EACH_BY_ORDER({
+ Element* item = rows->NamedGetter(aName, aFound);
+ if (aFound) {
+ return item;
+ }
+ }, {
+ if (_node->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+ nameAtom, eCaseMatters) ||
+ _node->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id,
+ nameAtom, eCaseMatters)) {
+ aFound = true;
+ return _node->AsElement();
+ }
+ });
+
+ return nullptr;
+}
+
+void
+TableRowsCollection::GetSupportedNames(nsTArray<nsString>& aNames)
+{
+ DO_FOR_EACH_BY_ORDER({
+ nsTArray<nsString> names;
+ nsCOMPtr<nsIHTMLCollection> coll = do_QueryInterface(rows);
+ if (coll) {
+ coll->GetSupportedNames(names);
+ for (uint32_t i = 0; i < names.Length(); ++i) {
+ if (!aNames.Contains(names[i])) {
+ aNames.AppendElement(names[i]);
+ }
+ }
+ }
+ }, {
+ if (_node->HasID()) {
+ nsIAtom* idAtom = _node->GetID();
+ MOZ_ASSERT(idAtom != nsGkAtoms::_empty,
+ "Empty ids don't get atomized");
+ nsDependentAtomString idStr(idAtom);
+ if (!aNames.Contains(idStr)) {
+ aNames.AppendElement(idStr);
+ }
+ }
+
+ nsGenericHTMLElement* el = nsGenericHTMLElement::FromContent(_node);
+ if (el) {
+ const nsAttrValue* val = el->GetParsedAttr(nsGkAtoms::name);
+ if (val && val->Type() == nsAttrValue::eAtom) {
+ nsIAtom* nameAtom = val->GetAtomValue();
+ MOZ_ASSERT(nameAtom != nsGkAtoms::_empty,
+ "Empty names don't get atomized");
+ nsDependentAtomString nameStr(nameAtom);
+ if (!aNames.Contains(nameStr)) {
+ aNames.AppendElement(nameStr);
+ }
+ }
+ }
+ });
+}
+
+
+NS_IMETHODIMP
+TableRowsCollection::NamedItem(const nsAString& aName,
+ nsIDOMNode** aReturn)
+{
+ bool found;
+ nsISupports* node = GetFirstNamedElement(aName, found);
+ if (!node) {
+ *aReturn = nullptr;
+
+ return NS_OK;
+ }
+
+ return CallQueryInterface(node, aReturn);
+}
+
+NS_IMETHODIMP
+TableRowsCollection::ParentDestroyed()
+{
+ // see comment in destructor, do NOT release mParent!
+ mParent = nullptr;
+
+ return NS_OK;
+}
+
+/* --------------------------- HTMLTableElement ---------------------------- */
+
+HTMLTableElement::HTMLTableElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo),
+ mTableInheritedAttributes(TABLE_ATTRS_DIRTY)
+{
+ SetHasWeirdParserInsertionMode();
+}
+
+HTMLTableElement::~HTMLTableElement()
+{
+ if (mRows) {
+ mRows->ParentDestroyed();
+ }
+ ReleaseInheritedAttributes();
+}
+
+JSObject*
+HTMLTableElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLTableElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLTableElement)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLTableElement, nsGenericHTMLElement)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mTBodies)
+ if (tmp->mRows) {
+ tmp->mRows->ParentDestroyed();
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRows)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLTableElement,
+ nsGenericHTMLElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTBodies)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRows)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_ADDREF_INHERITED(HTMLTableElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLTableElement, Element)
+
+// QueryInterface implementation for HTMLTableElement
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLTableElement)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
+
+
+NS_IMPL_ELEMENT_CLONE(HTMLTableElement)
+
+
+// the DOM spec says border, cellpadding, cellSpacing are all "wstring"
+// in fact, they are integers or they are meaningless. so we store them
+// here as ints.
+
+nsIHTMLCollection*
+HTMLTableElement::Rows()
+{
+ if (!mRows) {
+ mRows = new TableRowsCollection(this);
+ }
+
+ return mRows;
+}
+
+nsIHTMLCollection*
+HTMLTableElement::TBodies()
+{
+ if (!mTBodies) {
+ // Not using NS_GetContentList because this should not be cached
+ mTBodies = new nsContentList(this,
+ kNameSpaceID_XHTML,
+ nsGkAtoms::tbody,
+ nsGkAtoms::tbody,
+ false);
+ }
+
+ return mTBodies;
+}
+
+already_AddRefed<nsGenericHTMLElement>
+HTMLTableElement::CreateTHead()
+{
+ RefPtr<nsGenericHTMLElement> head = GetTHead();
+ if (!head) {
+ // Create a new head rowgroup.
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nsContentUtils::NameChanged(mNodeInfo, nsGkAtoms::thead,
+ getter_AddRefs(nodeInfo));
+
+ head = NS_NewHTMLTableSectionElement(nodeInfo.forget());
+ if (!head) {
+ return nullptr;
+ }
+
+ ErrorResult rv;
+ nsCOMPtr<nsINode> refNode = nsINode::GetFirstChild();
+ nsINode::InsertBefore(*head, refNode, rv);
+ }
+ return head.forget();
+}
+
+void
+HTMLTableElement::DeleteTHead()
+{
+ HTMLTableSectionElement* tHead = GetTHead();
+ if (tHead) {
+ mozilla::ErrorResult rv;
+ nsINode::RemoveChild(*tHead, rv);
+ MOZ_ASSERT(!rv.Failed());
+ }
+}
+
+already_AddRefed<nsGenericHTMLElement>
+HTMLTableElement::CreateTFoot()
+{
+ RefPtr<nsGenericHTMLElement> foot = GetTFoot();
+ if (!foot) {
+ // create a new foot rowgroup
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nsContentUtils::NameChanged(mNodeInfo, nsGkAtoms::tfoot,
+ getter_AddRefs(nodeInfo));
+
+ foot = NS_NewHTMLTableSectionElement(nodeInfo.forget());
+ if (!foot) {
+ return nullptr;
+ }
+ AppendChildTo(foot, true);
+ }
+
+ return foot.forget();
+}
+
+void
+HTMLTableElement::DeleteTFoot()
+{
+ HTMLTableSectionElement* tFoot = GetTFoot();
+ if (tFoot) {
+ mozilla::ErrorResult rv;
+ nsINode::RemoveChild(*tFoot, rv);
+ MOZ_ASSERT(!rv.Failed());
+ }
+}
+
+already_AddRefed<nsGenericHTMLElement>
+HTMLTableElement::CreateCaption()
+{
+ RefPtr<nsGenericHTMLElement> caption = GetCaption();
+ if (!caption) {
+ // Create a new caption.
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nsContentUtils::NameChanged(mNodeInfo, nsGkAtoms::caption,
+ getter_AddRefs(nodeInfo));
+
+ caption = NS_NewHTMLTableCaptionElement(nodeInfo.forget());
+ if (!caption) {
+ return nullptr;
+ }
+
+ AppendChildTo(caption, true);
+ }
+ return caption.forget();
+}
+
+void
+HTMLTableElement::DeleteCaption()
+{
+ HTMLTableCaptionElement* caption = GetCaption();
+ if (caption) {
+ mozilla::ErrorResult rv;
+ nsINode::RemoveChild(*caption, rv);
+ MOZ_ASSERT(!rv.Failed());
+ }
+}
+
+already_AddRefed<nsGenericHTMLElement>
+HTMLTableElement::CreateTBody()
+{
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo =
+ OwnerDoc()->NodeInfoManager()->GetNodeInfo(nsGkAtoms::tbody, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+ MOZ_ASSERT(nodeInfo);
+
+ RefPtr<nsGenericHTMLElement> newBody =
+ NS_NewHTMLTableSectionElement(nodeInfo.forget());
+ MOZ_ASSERT(newBody);
+
+ nsCOMPtr<nsIContent> referenceNode = nullptr;
+ for (nsIContent* child = nsINode::GetLastChild();
+ child;
+ child = child->GetPreviousSibling()) {
+ if (child->IsHTMLElement(nsGkAtoms::tbody)) {
+ referenceNode = child->GetNextSibling();
+ break;
+ }
+ }
+
+ ErrorResult rv;
+ nsINode::InsertBefore(*newBody, referenceNode, rv);
+
+ return newBody.forget();
+}
+
+already_AddRefed<nsGenericHTMLElement>
+HTMLTableElement::InsertRow(int32_t aIndex, ErrorResult& aError)
+{
+ /* get the ref row at aIndex
+ if there is one,
+ get its parent
+ insert the new row just before the ref row
+ else
+ get the first row group
+ insert the new row as its first child
+ */
+ if (aIndex < -1) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return nullptr;
+ }
+
+ nsIHTMLCollection* rows = Rows();
+ uint32_t rowCount = rows->Length();
+ if ((uint32_t)aIndex > rowCount && aIndex != -1) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return nullptr;
+ }
+
+ // use local variable refIndex so we can remember original aIndex
+ uint32_t refIndex = (uint32_t)aIndex;
+
+ RefPtr<nsGenericHTMLElement> newRow;
+ if (rowCount > 0) {
+ if (refIndex == rowCount || aIndex == -1) {
+ // we set refIndex to the last row so we can get the last row's
+ // parent we then do an AppendChild below if (rowCount<aIndex)
+
+ refIndex = rowCount - 1;
+ }
+
+ RefPtr<Element> refRow = rows->Item(refIndex);
+ nsCOMPtr<nsINode> parent = refRow->GetParentNode();
+
+ // create the row
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nsContentUtils::NameChanged(mNodeInfo, nsGkAtoms::tr,
+ getter_AddRefs(nodeInfo));
+
+ newRow = NS_NewHTMLTableRowElement(nodeInfo.forget());
+
+ if (newRow) {
+ // If aIndex is -1 or equal to the number of rows, the new row
+ // is appended.
+ if (aIndex == -1 || uint32_t(aIndex) == rowCount) {
+ parent->AppendChild(*newRow, aError);
+ } else {
+ // insert the new row before the reference row we found above
+ parent->InsertBefore(*newRow, refRow, aError);
+ }
+
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ }
+ } else {
+ // the row count was 0, so
+ // find the last row group and insert there as first child
+ nsCOMPtr<nsIContent> rowGroup;
+ for (nsIContent* child = nsINode::GetLastChild();
+ child;
+ child = child->GetPreviousSibling()) {
+ if (child->IsHTMLElement(nsGkAtoms::tbody)) {
+ rowGroup = child;
+ break;
+ }
+ }
+
+ if (!rowGroup) { // need to create a TBODY
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nsContentUtils::NameChanged(mNodeInfo, nsGkAtoms::tbody,
+ getter_AddRefs(nodeInfo));
+
+ rowGroup = NS_NewHTMLTableSectionElement(nodeInfo.forget());
+ if (rowGroup) {
+ aError = AppendChildTo(rowGroup, true);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ }
+ }
+
+ if (rowGroup) {
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nsContentUtils::NameChanged(mNodeInfo, nsGkAtoms::tr,
+ getter_AddRefs(nodeInfo));
+
+ newRow = NS_NewHTMLTableRowElement(nodeInfo.forget());
+ if (newRow) {
+ HTMLTableSectionElement* section =
+ static_cast<HTMLTableSectionElement*>(rowGroup.get());
+ nsIHTMLCollection* rows = section->Rows();
+ nsCOMPtr<nsINode> refNode = rows->Item(0);
+ rowGroup->InsertBefore(*newRow, refNode, aError);
+ }
+ }
+ }
+
+ return newRow.forget();
+}
+
+void
+HTMLTableElement::DeleteRow(int32_t aIndex, ErrorResult& aError)
+{
+ if (aIndex < -1) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ nsIHTMLCollection* rows = Rows();
+ uint32_t refIndex;
+ if (aIndex == -1) {
+ refIndex = rows->Length();
+ if (refIndex == 0) {
+ return;
+ }
+
+ --refIndex;
+ } else {
+ refIndex = (uint32_t)aIndex;
+ }
+
+ nsCOMPtr<nsIContent> row = rows->Item(refIndex);
+ if (!row) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ row->RemoveFromParent();
+}
+
+bool
+HTMLTableElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ /* ignore summary, just a string */
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::cellspacing ||
+ aAttribute == nsGkAtoms::cellpadding ||
+ aAttribute == nsGkAtoms::border) {
+ return aResult.ParseNonNegativeIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::height) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::width) {
+ if (aResult.ParseSpecialIntValue(aValue)) {
+ // treat 0 width as auto
+ nsAttrValue::ValueType type = aResult.Type();
+ return !((type == nsAttrValue::eInteger &&
+ aResult.GetIntegerValue() == 0) ||
+ (type == nsAttrValue::ePercent &&
+ aResult.GetPercentValue() == 0.0f));
+ }
+ return false;
+ }
+
+ if (aAttribute == nsGkAtoms::align) {
+ return ParseTableHAlignValue(aValue, aResult);
+ }
+ if (aAttribute == nsGkAtoms::bgcolor ||
+ aAttribute == nsGkAtoms::bordercolor) {
+ return aResult.ParseColor(aValue);
+ }
+ if (aAttribute == nsGkAtoms::hspace ||
+ aAttribute == nsGkAtoms::vspace) {
+ return aResult.ParseIntWithBounds(aValue, 0);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseBackgroundAttribute(aNamespaceID,
+ aAttribute, aValue,
+ aResult) ||
+ nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+
+
+void
+HTMLTableElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ // XXX Bug 211636: This function is used by a single style rule
+ // that's used to match two different type of elements -- tables, and
+ // table cells. (nsHTMLTableCellElement overrides
+ // WalkContentStyleRules so that this happens.) This violates the
+ // nsIStyleRule contract, since it's the same style rule object doing
+ // the mapping in two different ways. It's also incorrect since it's
+ // testing the display type of the style context rather than checking
+ // which *element* it's matching (style rules should not stop matching
+ // when the display type is changed).
+
+ nsPresContext* presContext = aData->mPresContext;
+ nsCompatibility mode = presContext->CompatibilityMode();
+
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(TableBorder)) {
+ // cellspacing
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::cellspacing);
+ nsCSSValue* borderSpacing = aData->ValueForBorderSpacing();
+ if (value && value->Type() == nsAttrValue::eInteger &&
+ borderSpacing->GetUnit() == eCSSUnit_Null) {
+ borderSpacing->
+ SetFloatValue(float(value->GetIntegerValue()), eCSSUnit_Pixel);
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Margin)) {
+ // align; Check for enumerated type (it may be another type if
+ // illegal)
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align);
+
+ if (value && value->Type() == nsAttrValue::eEnum) {
+ if (value->GetEnumValue() == NS_STYLE_TEXT_ALIGN_CENTER ||
+ value->GetEnumValue() == NS_STYLE_TEXT_ALIGN_MOZ_CENTER) {
+ nsCSSValue* marginLeft = aData->ValueForMarginLeft();
+ if (marginLeft->GetUnit() == eCSSUnit_Null)
+ marginLeft->SetAutoValue();
+ nsCSSValue* marginRight = aData->ValueForMarginRight();
+ if (marginRight->GetUnit() == eCSSUnit_Null)
+ marginRight->SetAutoValue();
+ }
+ }
+
+ // hspace is mapped into left and right margin,
+ // vspace is mapped into top and bottom margins
+ // - *** Quirks Mode only ***
+ if (eCompatibility_NavQuirks == mode) {
+ value = aAttributes->GetAttr(nsGkAtoms::hspace);
+
+ if (value && value->Type() == nsAttrValue::eInteger) {
+ nsCSSValue* marginLeft = aData->ValueForMarginLeft();
+ if (marginLeft->GetUnit() == eCSSUnit_Null)
+ marginLeft->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ nsCSSValue* marginRight = aData->ValueForMarginRight();
+ if (marginRight->GetUnit() == eCSSUnit_Null)
+ marginRight->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ }
+
+ value = aAttributes->GetAttr(nsGkAtoms::vspace);
+
+ if (value && value->Type() == nsAttrValue::eInteger) {
+ nsCSSValue* marginTop = aData->ValueForMarginTop();
+ if (marginTop->GetUnit() == eCSSUnit_Null)
+ marginTop->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ nsCSSValue* marginBottom = aData->ValueForMarginBottom();
+ if (marginBottom->GetUnit() == eCSSUnit_Null)
+ marginBottom->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ }
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Position)) {
+ // width: value
+ nsCSSValue* width = aData->ValueForWidth();
+ if (width->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::width);
+ if (value && value->Type() == nsAttrValue::eInteger)
+ width->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ else if (value && value->Type() == nsAttrValue::ePercent)
+ width->SetPercentValue(value->GetPercentValue());
+ }
+
+ // height: value
+ nsCSSValue* height = aData->ValueForHeight();
+ if (height->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::height);
+ if (value && value->Type() == nsAttrValue::eInteger)
+ height->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ else if (value && value->Type() == nsAttrValue::ePercent)
+ height->SetPercentValue(value->GetPercentValue());
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Border)) {
+ // bordercolor
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::bordercolor);
+ nscolor color;
+ if (value && presContext->UseDocumentColors() &&
+ value->GetColorValue(color)) {
+ nsCSSValue* borderLeftColor = aData->ValueForBorderLeftColor();
+ if (borderLeftColor->GetUnit() == eCSSUnit_Null)
+ borderLeftColor->SetColorValue(color);
+ nsCSSValue* borderRightColor = aData->ValueForBorderRightColor();
+ if (borderRightColor->GetUnit() == eCSSUnit_Null)
+ borderRightColor->SetColorValue(color);
+ nsCSSValue* borderTopColor = aData->ValueForBorderTopColor();
+ if (borderTopColor->GetUnit() == eCSSUnit_Null)
+ borderTopColor->SetColorValue(color);
+ nsCSSValue* borderBottomColor = aData->ValueForBorderBottomColor();
+ if (borderBottomColor->GetUnit() == eCSSUnit_Null)
+ borderBottomColor->SetColorValue(color);
+ }
+
+ // border
+ const nsAttrValue* borderValue = aAttributes->GetAttr(nsGkAtoms::border);
+ if (borderValue) {
+ // border = 1 pixel default
+ int32_t borderThickness = 1;
+
+ if (borderValue->Type() == nsAttrValue::eInteger)
+ borderThickness = borderValue->GetIntegerValue();
+
+ // by default, set all border sides to the specified width
+ nsCSSValue* borderLeftWidth = aData->ValueForBorderLeftWidth();
+ if (borderLeftWidth->GetUnit() == eCSSUnit_Null)
+ borderLeftWidth->SetFloatValue((float)borderThickness, eCSSUnit_Pixel);
+ nsCSSValue* borderRightWidth = aData->ValueForBorderRightWidth();
+ if (borderRightWidth->GetUnit() == eCSSUnit_Null)
+ borderRightWidth->SetFloatValue((float)borderThickness, eCSSUnit_Pixel);
+ nsCSSValue* borderTopWidth = aData->ValueForBorderTopWidth();
+ if (borderTopWidth->GetUnit() == eCSSUnit_Null)
+ borderTopWidth->SetFloatValue((float)borderThickness, eCSSUnit_Pixel);
+ nsCSSValue* borderBottomWidth = aData->ValueForBorderBottomWidth();
+ if (borderBottomWidth->GetUnit() == eCSSUnit_Null)
+ borderBottomWidth->SetFloatValue((float)borderThickness, eCSSUnit_Pixel);
+ }
+ }
+ nsGenericHTMLElement::MapBackgroundAttributesInto(aAttributes, aData);
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLTableElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::cellpadding },
+ { &nsGkAtoms::cellspacing },
+ { &nsGkAtoms::border },
+ { &nsGkAtoms::width },
+ { &nsGkAtoms::height },
+ { &nsGkAtoms::hspace },
+ { &nsGkAtoms::vspace },
+
+ { &nsGkAtoms::bordercolor },
+
+ { &nsGkAtoms::align },
+ { nullptr }
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap,
+ sBackgroundAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+nsMapRuleToAttributesFunc
+HTMLTableElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+static void
+MapInheritedTableAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Padding)) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::cellpadding);
+ if (value && value->Type() == nsAttrValue::eInteger) {
+ // We have cellpadding. This will override our padding values if we
+ // don't have any set.
+ nsCSSValue padVal(float(value->GetIntegerValue()), eCSSUnit_Pixel);
+
+ nsCSSValue* paddingLeft = aData->ValueForPaddingLeft();
+ if (paddingLeft->GetUnit() == eCSSUnit_Null) {
+ *paddingLeft = padVal;
+ }
+
+ nsCSSValue* paddingRight = aData->ValueForPaddingRight();
+ if (paddingRight->GetUnit() == eCSSUnit_Null) {
+ *paddingRight = padVal;
+ }
+
+ nsCSSValue* paddingTop = aData->ValueForPaddingTop();
+ if (paddingTop->GetUnit() == eCSSUnit_Null) {
+ *paddingTop = padVal;
+ }
+
+ nsCSSValue* paddingBottom = aData->ValueForPaddingBottom();
+ if (paddingBottom->GetUnit() == eCSSUnit_Null) {
+ *paddingBottom = padVal;
+ }
+ }
+ }
+}
+
+nsMappedAttributes*
+HTMLTableElement::GetAttributesMappedForCell()
+{
+ if (mTableInheritedAttributes) {
+ if (mTableInheritedAttributes == TABLE_ATTRS_DIRTY)
+ BuildInheritedAttributes();
+ if (mTableInheritedAttributes != TABLE_ATTRS_DIRTY)
+ return mTableInheritedAttributes;
+ }
+ return nullptr;
+}
+
+void
+HTMLTableElement::BuildInheritedAttributes()
+{
+ NS_ASSERTION(mTableInheritedAttributes == TABLE_ATTRS_DIRTY,
+ "potential leak, plus waste of work");
+ nsIDocument *document = GetComposedDoc();
+ nsHTMLStyleSheet* sheet = document ?
+ document->GetAttributeStyleSheet() : nullptr;
+ RefPtr<nsMappedAttributes> newAttrs;
+ if (sheet) {
+ const nsAttrValue* value = mAttrsAndChildren.GetAttr(nsGkAtoms::cellpadding);
+ if (value) {
+ RefPtr<nsMappedAttributes> modifiableMapped = new
+ nsMappedAttributes(sheet, MapInheritedTableAttributesIntoRule);
+
+ if (modifiableMapped) {
+ nsAttrValue val(*value);
+ modifiableMapped->SetAndTakeAttr(nsGkAtoms::cellpadding, val);
+ }
+ newAttrs = sheet->UniqueMappedAttributes(modifiableMapped);
+ NS_ASSERTION(newAttrs, "out of memory, but handling gracefully");
+
+ if (newAttrs != modifiableMapped) {
+ // Reset the stylesheet of modifiableMapped so that it doesn't
+ // spend time trying to remove itself from the hash. There is no
+ // risk that modifiableMapped is in the hash since we created
+ // it ourselves and it didn't come from the stylesheet (in which
+ // case it would not have been modifiable).
+ modifiableMapped->DropStyleSheetReference();
+ }
+ }
+ mTableInheritedAttributes = newAttrs;
+ NS_IF_ADDREF(mTableInheritedAttributes);
+ }
+}
+
+void
+HTMLTableElement::ReleaseInheritedAttributes()
+{
+ if (mTableInheritedAttributes &&
+ mTableInheritedAttributes != TABLE_ATTRS_DIRTY)
+ NS_RELEASE(mTableInheritedAttributes);
+ mTableInheritedAttributes = TABLE_ATTRS_DIRTY;
+}
+
+nsresult
+HTMLTableElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ ReleaseInheritedAttributes();
+ return nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+}
+
+void
+HTMLTableElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ ReleaseInheritedAttributes();
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+nsresult
+HTMLTableElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify)
+{
+ if (aName == nsGkAtoms::cellpadding && aNameSpaceID == kNameSpaceID_None) {
+ ReleaseInheritedAttributes();
+ }
+ return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue,
+ aNotify);
+}
+
+nsresult
+HTMLTableElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue,
+ bool aNotify)
+{
+ if (aName == nsGkAtoms::cellpadding && aNameSpaceID == kNameSpaceID_None) {
+ BuildInheritedAttributes();
+ }
+ return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
+ aNotify);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLTableElement.h b/dom/html/HTMLTableElement.h
new file mode 100644
index 000000000..4e172964b
--- /dev/null
+++ b/dom/html/HTMLTableElement.h
@@ -0,0 +1,237 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_HTMLTableElement_h
+#define mozilla_dom_HTMLTableElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "mozilla/dom/HTMLTableCaptionElement.h"
+#include "mozilla/dom/HTMLTableSectionElement.h"
+
+namespace mozilla {
+namespace dom {
+
+#define TABLE_ATTRS_DIRTY ((nsMappedAttributes*)0x1)
+
+class TableRowsCollection;
+
+class HTMLTableElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLTableElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLTableElement, table)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ HTMLTableCaptionElement* GetCaption() const
+ {
+ return static_cast<HTMLTableCaptionElement*>(GetChild(nsGkAtoms::caption));
+ }
+ void SetCaption(HTMLTableCaptionElement* aCaption, ErrorResult& aError)
+ {
+ DeleteCaption();
+ if (aCaption) {
+ nsINode::AppendChild(*aCaption, aError);
+ }
+ }
+
+ void DeleteTFoot();
+
+ already_AddRefed<nsGenericHTMLElement> CreateCaption();
+
+ void DeleteCaption();
+
+ HTMLTableSectionElement* GetTHead() const
+ {
+ return static_cast<HTMLTableSectionElement*>(GetChild(nsGkAtoms::thead));
+ }
+ void SetTHead(HTMLTableSectionElement* aTHead, ErrorResult& aError)
+ {
+ if (aTHead && !aTHead->IsHTMLElement(nsGkAtoms::thead)) {
+ aError.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return;
+ }
+
+ DeleteTHead();
+ if (aTHead) {
+ nsCOMPtr<nsINode> refNode = nsINode::GetFirstChild();
+ nsINode::InsertBefore(*aTHead, refNode, aError);
+ }
+ }
+ already_AddRefed<nsGenericHTMLElement> CreateTHead();
+
+ void DeleteTHead();
+
+ HTMLTableSectionElement* GetTFoot() const
+ {
+ return static_cast<HTMLTableSectionElement*>(GetChild(nsGkAtoms::tfoot));
+ }
+ void SetTFoot(HTMLTableSectionElement* aTFoot, ErrorResult& aError)
+ {
+ if (aTFoot && !aTFoot->IsHTMLElement(nsGkAtoms::tfoot)) {
+ aError.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return;
+ }
+
+ DeleteTFoot();
+ if (aTFoot) {
+ nsINode::AppendChild(*aTFoot, aError);
+ }
+ }
+ already_AddRefed<nsGenericHTMLElement> CreateTFoot();
+
+ nsIHTMLCollection* TBodies();
+
+ already_AddRefed<nsGenericHTMLElement> CreateTBody();
+
+ nsIHTMLCollection* Rows();
+
+ already_AddRefed<nsGenericHTMLElement> InsertRow(int32_t aIndex,
+ ErrorResult& aError);
+ void DeleteRow(int32_t aIndex, ErrorResult& aError);
+
+ void GetAlign(DOMString& aAlign)
+ {
+ GetHTMLAttr(nsGkAtoms::align, aAlign);
+ }
+ void SetAlign(const nsAString& aAlign, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::align, aAlign, aError);
+ }
+ void GetBorder(DOMString& aBorder)
+ {
+ GetHTMLAttr(nsGkAtoms::border, aBorder);
+ }
+ void SetBorder(const nsAString& aBorder, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::border, aBorder, aError);
+ }
+ void GetFrame(DOMString& aFrame)
+ {
+ GetHTMLAttr(nsGkAtoms::frame, aFrame);
+ }
+ void SetFrame(const nsAString& aFrame, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::frame, aFrame, aError);
+ }
+ void GetRules(DOMString& aRules)
+ {
+ GetHTMLAttr(nsGkAtoms::rules, aRules);
+ }
+ void SetRules(const nsAString& aRules, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::rules, aRules, aError);
+ }
+ void GetSummary(nsString& aSummary)
+ {
+ GetHTMLAttr(nsGkAtoms::summary, aSummary);
+ }
+ void GetSummary(DOMString& aSummary)
+ {
+ GetHTMLAttr(nsGkAtoms::summary, aSummary);
+ }
+ void SetSummary(const nsAString& aSummary, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::summary, aSummary, aError);
+ }
+ void GetWidth(DOMString& aWidth)
+ {
+ GetHTMLAttr(nsGkAtoms::width, aWidth);
+ }
+ void SetWidth(const nsAString& aWidth, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::width, aWidth, aError);
+ }
+ void GetBgColor(DOMString& aBgColor)
+ {
+ GetHTMLAttr(nsGkAtoms::bgcolor, aBgColor);
+ }
+ void SetBgColor(const nsAString& aBgColor, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::bgcolor, aBgColor, aError);
+ }
+ void GetCellPadding(DOMString& aCellPadding)
+ {
+ GetHTMLAttr(nsGkAtoms::cellpadding, aCellPadding);
+ }
+ void SetCellPadding(const nsAString& aCellPadding, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::cellpadding, aCellPadding, aError);
+ }
+ void GetCellSpacing(DOMString& aCellSpacing)
+ {
+ GetHTMLAttr(nsGkAtoms::cellspacing, aCellSpacing);
+ }
+ void SetCellSpacing(const nsAString& aCellSpacing, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::cellspacing, aCellSpacing, aError);
+ }
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+ /**
+ * Called when an attribute is about to be changed
+ */
+ virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify) override;
+ /**
+ * Called when an attribute has just been changed
+ */
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLTableElement,
+ nsGenericHTMLElement)
+ nsMappedAttributes* GetAttributesMappedForCell();
+
+protected:
+ virtual ~HTMLTableElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ nsIContent* GetChild(nsIAtom *aTag) const
+ {
+ for (nsIContent* cur = nsINode::GetFirstChild(); cur;
+ cur = cur->GetNextSibling()) {
+ if (cur->IsHTMLElement(aTag)) {
+ return cur;
+ }
+ }
+ return nullptr;
+ }
+
+ RefPtr<nsContentList> mTBodies;
+ RefPtr<TableRowsCollection> mRows;
+ // Sentinel value of TABLE_ATTRS_DIRTY indicates that this is dirty and needs
+ // to be recalculated.
+ nsMappedAttributes *mTableInheritedAttributes;
+ void BuildInheritedAttributes();
+ void ReleaseInheritedAttributes();
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_HTMLTableElement_h */
diff --git a/dom/html/HTMLTableRowElement.cpp b/dom/html/HTMLTableRowElement.cpp
new file mode 100644
index 000000000..2dec9c883
--- /dev/null
+++ b/dom/html/HTMLTableRowElement.cpp
@@ -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/. */
+
+#include "mozilla/dom/HTMLTableRowElement.h"
+#include "mozilla/dom/HTMLTableElement.h"
+#include "nsMappedAttributes.h"
+#include "nsAttrValueInlines.h"
+#include "nsRuleData.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/HTMLTableRowElementBinding.h"
+#include "nsContentList.h"
+#include "nsContentUtils.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(TableRow)
+
+namespace mozilla {
+namespace dom {
+
+HTMLTableRowElement::~HTMLTableRowElement()
+{
+}
+
+JSObject*
+HTMLTableRowElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLTableRowElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLTableRowElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLTableRowElement,
+ nsGenericHTMLElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCells)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_ADDREF_INHERITED(HTMLTableRowElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLTableRowElement, Element)
+
+// QueryInterface implementation for HTMLTableRowElement
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLTableRowElement)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
+
+
+NS_IMPL_ELEMENT_CLONE(HTMLTableRowElement)
+
+
+// protected method
+HTMLTableSectionElement*
+HTMLTableRowElement::GetSection() const
+{
+ nsIContent* parent = GetParent();
+ if (parent &&
+ parent->IsAnyOfHTMLElements(nsGkAtoms::thead,
+ nsGkAtoms::tbody,
+ nsGkAtoms::tfoot)) {
+ return static_cast<HTMLTableSectionElement*>(parent);
+ }
+ return nullptr;
+}
+
+// protected method
+HTMLTableElement*
+HTMLTableRowElement::GetTable() const
+{
+ nsIContent* parent = GetParent();
+ if (!parent) {
+ return nullptr;
+ }
+
+ // We may not be in a section
+ HTMLTableElement* table = HTMLTableElement::FromContent(parent);
+ if (table) {
+ return table;
+ }
+
+ return HTMLTableElement::FromContentOrNull(parent->GetParent());
+}
+
+int32_t
+HTMLTableRowElement::RowIndex() const
+{
+ HTMLTableElement* table = GetTable();
+ if (!table) {
+ return -1;
+ }
+
+ nsIHTMLCollection* rows = table->Rows();
+
+ uint32_t numRows = rows->Length();
+
+ for (uint32_t i = 0; i < numRows; i++) {
+ if (rows->GetElementAt(i) == this) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+int32_t
+HTMLTableRowElement::SectionRowIndex() const
+{
+ HTMLTableSectionElement* section = GetSection();
+ if (!section) {
+ return -1;
+ }
+
+ nsCOMPtr<nsIHTMLCollection> coll = section->Rows();
+ uint32_t numRows = coll->Length();
+ for (uint32_t i = 0; i < numRows; i++) {
+ if (coll->GetElementAt(i) == this) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+static bool
+IsCell(nsIContent *aContent, int32_t aNamespaceID,
+ nsIAtom* aAtom, void *aData)
+{
+ return aContent->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th);
+}
+
+nsIHTMLCollection*
+HTMLTableRowElement::Cells()
+{
+ if (!mCells) {
+ mCells = new nsContentList(this,
+ IsCell,
+ nullptr, // destroy func
+ nullptr, // closure data
+ false,
+ nullptr,
+ kNameSpaceID_XHTML,
+ false);
+ }
+
+ return mCells;
+}
+
+already_AddRefed<nsGenericHTMLElement>
+HTMLTableRowElement::InsertCell(int32_t aIndex,
+ ErrorResult& aError)
+{
+ if (aIndex < -1) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return nullptr;
+ }
+
+ // Make sure mCells is initialized.
+ nsIHTMLCollection* cells = Cells();
+
+ NS_ASSERTION(mCells, "How did that happen?");
+
+ nsCOMPtr<nsINode> nextSibling;
+ // -1 means append, so should use null nextSibling
+ if (aIndex != -1) {
+ nextSibling = cells->Item(aIndex);
+ // Check whether we're inserting past end of list. We want to avoid doing
+ // this unless we really have to, since this has to walk all our kids. If
+ // we have a nextSibling, we're clearly not past end of list.
+ if (!nextSibling) {
+ uint32_t cellCount = cells->Length();
+ if (aIndex > int32_t(cellCount)) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return nullptr;
+ }
+ }
+ }
+
+ // create the cell
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nsContentUtils::NameChanged(mNodeInfo, nsGkAtoms::td,
+ getter_AddRefs(nodeInfo));
+
+ RefPtr<nsGenericHTMLElement> cell =
+ NS_NewHTMLTableCellElement(nodeInfo.forget());
+ if (!cell) {
+ aError.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ nsINode::InsertBefore(*cell, nextSibling, aError);
+
+ return cell.forget();
+}
+
+void
+HTMLTableRowElement::DeleteCell(int32_t aValue, ErrorResult& aError)
+{
+ if (aValue < -1) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ nsIHTMLCollection* cells = Cells();
+
+ uint32_t refIndex;
+ if (aValue == -1) {
+ refIndex = cells->Length();
+ if (refIndex == 0) {
+ return;
+ }
+
+ --refIndex;
+ }
+ else {
+ refIndex = (uint32_t)aValue;
+ }
+
+ nsCOMPtr<nsINode> cell = cells->Item(refIndex);
+ if (!cell) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ nsINode::RemoveChild(*cell, aError);
+}
+
+bool
+HTMLTableRowElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ /*
+ * ignore these attributes, stored simply as strings
+ *
+ * ch
+ */
+
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::charoff) {
+ return aResult.ParseIntWithBounds(aValue, 0);
+ }
+ if (aAttribute == nsGkAtoms::height) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::width) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::align) {
+ return ParseTableCellHAlignValue(aValue, aResult);
+ }
+ if (aAttribute == nsGkAtoms::bgcolor) {
+ return aResult.ParseColor(aValue);
+ }
+ if (aAttribute == nsGkAtoms::valign) {
+ return ParseTableVAlignValue(aValue, aResult);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseBackgroundAttribute(aNamespaceID,
+ aAttribute, aValue,
+ aResult) ||
+ nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLTableRowElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Position)) {
+ // height: value
+ nsCSSValue* height = aData->ValueForHeight();
+ if (height->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::height);
+ if (value && value->Type() == nsAttrValue::eInteger)
+ height->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ else if (value && value->Type() == nsAttrValue::ePercent)
+ height->SetPercentValue(value->GetPercentValue());
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Text)) {
+ nsCSSValue* textAlign = aData->ValueForTextAlign();
+ if (textAlign->GetUnit() == eCSSUnit_Null) {
+ // align: enum
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align);
+ if (value && value->Type() == nsAttrValue::eEnum)
+ textAlign->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Display)) {
+ nsCSSValue* verticalAlign = aData->ValueForVerticalAlign();
+ if (verticalAlign->GetUnit() == eCSSUnit_Null) {
+ // valign: enum
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::valign);
+ if (value && value->Type() == nsAttrValue::eEnum)
+ verticalAlign->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
+ }
+ }
+
+ nsGenericHTMLElement::MapBackgroundAttributesInto(aAttributes, aData);
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLTableRowElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::align },
+ { &nsGkAtoms::valign },
+ { &nsGkAtoms::height },
+ { nullptr }
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap,
+ sBackgroundAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+nsMapRuleToAttributesFunc
+HTMLTableRowElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLTableRowElement.h b/dom/html/HTMLTableRowElement.h
new file mode 100644
index 000000000..56310e389
--- /dev/null
+++ b/dom/html/HTMLTableRowElement.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef mozilla_dom_HTMLTableRowElement_h
+#define mozilla_dom_HTMLTableRowElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+
+class nsContentList;
+
+namespace mozilla {
+namespace dom {
+
+class HTMLTableSectionElement;
+
+class HTMLTableRowElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLTableRowElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ SetHasWeirdParserInsertionMode();
+ }
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLTableRowElement, tr)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ int32_t RowIndex() const;
+ int32_t SectionRowIndex() const;
+ nsIHTMLCollection* Cells();
+ already_AddRefed<nsGenericHTMLElement>
+ InsertCell(int32_t aIndex, ErrorResult& aError);
+ void DeleteCell(int32_t aValue, ErrorResult& aError);
+
+ void GetAlign(DOMString& aAlign)
+ {
+ GetHTMLAttr(nsGkAtoms::align, aAlign);
+ }
+ void SetAlign(const nsAString& aAlign, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::align, aAlign, aError);
+ }
+ void GetCh(DOMString& aCh)
+ {
+ GetHTMLAttr(nsGkAtoms::_char, aCh);
+ }
+ void SetCh(const nsAString& aCh, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::_char, aCh, aError);
+ }
+ void GetChOff(DOMString& aChOff)
+ {
+ GetHTMLAttr(nsGkAtoms::charoff, aChOff);
+ }
+ void SetChOff(const nsAString& aChOff, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::charoff, aChOff, aError);
+ }
+ void GetVAlign(DOMString& aVAlign)
+ {
+ GetHTMLAttr(nsGkAtoms::valign, aVAlign);
+ }
+ void SetVAlign(const nsAString& aVAlign, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::valign, aVAlign, aError);
+ }
+ void GetBgColor(DOMString& aBgColor)
+ {
+ GetHTMLAttr(nsGkAtoms::bgcolor, aBgColor);
+ }
+ void SetBgColor(const nsAString& aBgColor, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::bgcolor, aBgColor, aError);
+ }
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(HTMLTableRowElement,
+ nsGenericHTMLElement)
+
+protected:
+ virtual ~HTMLTableRowElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ HTMLTableSectionElement* GetSection() const;
+ HTMLTableElement* GetTable() const;
+ RefPtr<nsContentList> mCells;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_HTMLTableRowElement_h */
diff --git a/dom/html/HTMLTableSectionElement.cpp b/dom/html/HTMLTableSectionElement.cpp
new file mode 100644
index 000000000..c7b0665dd
--- /dev/null
+++ b/dom/html/HTMLTableSectionElement.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 "mozilla/dom/HTMLTableSectionElement.h"
+#include "nsMappedAttributes.h"
+#include "nsAttrValueInlines.h"
+#include "nsRuleData.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/HTMLTableSectionElementBinding.h"
+#include "nsContentUtils.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(TableSection)
+
+namespace mozilla {
+namespace dom {
+
+// you will see the phrases "rowgroup" and "section" used interchangably
+
+HTMLTableSectionElement::~HTMLTableSectionElement()
+{
+}
+
+JSObject*
+HTMLTableSectionElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLTableSectionElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLTableSectionElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLTableSectionElement,
+ nsGenericHTMLElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRows)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_ADDREF_INHERITED(HTMLTableSectionElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLTableSectionElement, Element)
+
+// QueryInterface implementation for HTMLTableSectionElement
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLTableSectionElement)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
+
+
+NS_IMPL_ELEMENT_CLONE(HTMLTableSectionElement)
+
+nsIHTMLCollection*
+HTMLTableSectionElement::Rows()
+{
+ if (!mRows) {
+ mRows = new nsContentList(this,
+ mNodeInfo->NamespaceID(),
+ nsGkAtoms::tr,
+ nsGkAtoms::tr,
+ false);
+ }
+
+ return mRows;
+}
+
+already_AddRefed<nsGenericHTMLElement>
+HTMLTableSectionElement::InsertRow(int32_t aIndex, ErrorResult& aError)
+{
+ if (aIndex < -1) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return nullptr;
+ }
+
+ nsIHTMLCollection* rows = Rows();
+
+ uint32_t rowCount = rows->Length();
+ if (aIndex > (int32_t)rowCount) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return nullptr;
+ }
+
+ bool doInsert = (aIndex < int32_t(rowCount)) && (aIndex != -1);
+
+ // create the row
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nsContentUtils::NameChanged(mNodeInfo, nsGkAtoms::tr,
+ getter_AddRefs(nodeInfo));
+
+ RefPtr<nsGenericHTMLElement> rowContent =
+ NS_NewHTMLTableRowElement(nodeInfo.forget());
+ if (!rowContent) {
+ aError.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ if (doInsert) {
+ nsCOMPtr<nsINode> refNode = rows->Item(aIndex);
+ nsINode::InsertBefore(*rowContent, refNode, aError);
+ } else {
+ nsINode::AppendChild(*rowContent, aError);
+ }
+ return rowContent.forget();
+}
+
+void
+HTMLTableSectionElement::DeleteRow(int32_t aValue, ErrorResult& aError)
+{
+ if (aValue < -1) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ nsIHTMLCollection* rows = Rows();
+
+ uint32_t refIndex;
+ if (aValue == -1) {
+ refIndex = rows->Length();
+ if (refIndex == 0) {
+ return;
+ }
+
+ --refIndex;
+ }
+ else {
+ refIndex = (uint32_t)aValue;
+ }
+
+ nsINode* row = rows->Item(refIndex);
+ if (!row) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ nsINode::RemoveChild(*row, aError);
+}
+
+bool
+HTMLTableSectionElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ /* ignore these attributes, stored simply as strings
+ ch
+ */
+ if (aAttribute == nsGkAtoms::charoff) {
+ return aResult.ParseIntWithBounds(aValue, 0);
+ }
+ if (aAttribute == nsGkAtoms::height) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::align) {
+ return ParseTableCellHAlignValue(aValue, aResult);
+ }
+ if (aAttribute == nsGkAtoms::bgcolor) {
+ return aResult.ParseColor(aValue);
+ }
+ if (aAttribute == nsGkAtoms::valign) {
+ return ParseTableVAlignValue(aValue, aResult);
+ }
+ }
+
+ return nsGenericHTMLElement::ParseBackgroundAttribute(aNamespaceID,
+ aAttribute, aValue,
+ aResult) ||
+ nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLTableSectionElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Position)) {
+ // height: value
+ nsCSSValue* height = aData->ValueForHeight();
+ if (height->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::height);
+ if (value && value->Type() == nsAttrValue::eInteger)
+ height->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Text)) {
+ nsCSSValue* textAlign = aData->ValueForTextAlign();
+ if (textAlign->GetUnit() == eCSSUnit_Null) {
+ // align: enum
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align);
+ if (value && value->Type() == nsAttrValue::eEnum)
+ textAlign->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Display)) {
+ nsCSSValue* verticalAlign = aData->ValueForVerticalAlign();
+ if (verticalAlign->GetUnit() == eCSSUnit_Null) {
+ // valign: enum
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::valign);
+ if (value && value->Type() == nsAttrValue::eEnum)
+ verticalAlign->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
+ }
+ }
+
+ nsGenericHTMLElement::MapBackgroundAttributesInto(aAttributes, aData);
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLTableSectionElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::align },
+ { &nsGkAtoms::valign },
+ { &nsGkAtoms::height },
+ { nullptr }
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap,
+ sBackgroundAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+
+nsMapRuleToAttributesFunc
+HTMLTableSectionElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLTableSectionElement.h b/dom/html/HTMLTableSectionElement.h
new file mode 100644
index 000000000..cabde0042
--- /dev/null
+++ b/dom/html/HTMLTableSectionElement.h
@@ -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/. */
+#ifndef mozilla_dom_HTMLTableSectionElement_h
+#define mozilla_dom_HTMLTableSectionElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsContentList.h" // For ctor.
+
+namespace mozilla {
+namespace dom {
+
+class HTMLTableSectionElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLTableSectionElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ SetHasWeirdParserInsertionMode();
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsIHTMLCollection* Rows();
+ already_AddRefed<nsGenericHTMLElement>
+ InsertRow(int32_t aIndex, ErrorResult& aError);
+ void DeleteRow(int32_t aValue, ErrorResult& aError);
+
+ void GetAlign(DOMString& aAlign)
+ {
+ GetHTMLAttr(nsGkAtoms::align, aAlign);
+ }
+ void SetAlign(const nsAString& aAlign, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::align, aAlign, aError);
+ }
+ void GetCh(DOMString& aCh)
+ {
+ GetHTMLAttr(nsGkAtoms::_char, aCh);
+ }
+ void SetCh(const nsAString& aCh, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::_char, aCh, aError);
+ }
+ void GetChOff(DOMString& aChOff)
+ {
+ GetHTMLAttr(nsGkAtoms::charoff, aChOff);
+ }
+ void SetChOff(const nsAString& aChOff, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::charoff, aChOff, aError);
+ }
+ void GetVAlign(DOMString& aVAlign)
+ {
+ GetHTMLAttr(nsGkAtoms::valign, aVAlign);
+ }
+ void SetVAlign(const nsAString& aVAlign, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::valign, aVAlign, aError);
+ }
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(HTMLTableSectionElement,
+ nsGenericHTMLElement)
+protected:
+ virtual ~HTMLTableSectionElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ RefPtr<nsContentList> mRows;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_HTMLTableSectionElement_h */
diff --git a/dom/html/HTMLTemplateElement.cpp b/dom/html/HTMLTemplateElement.cpp
new file mode 100644
index 000000000..27fb12870
--- /dev/null
+++ b/dom/html/HTMLTemplateElement.cpp
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/HTMLTemplateElement.h"
+#include "mozilla/dom/HTMLTemplateElementBinding.h"
+
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsIAtom.h"
+#include "nsRuleData.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Template)
+
+namespace mozilla {
+namespace dom {
+
+HTMLTemplateElement::HTMLTemplateElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+{
+ SetHasWeirdParserInsertionMode();
+
+ nsIDocument* contentsOwner = OwnerDoc()->GetTemplateContentsOwner();
+ if (!contentsOwner) {
+ MOZ_CRASH("There should always be a template contents owner.");
+ }
+
+ mContent = contentsOwner->CreateDocumentFragment();
+ mContent->SetHost(this);
+}
+
+HTMLTemplateElement::~HTMLTemplateElement()
+{
+ if (mContent) {
+ mContent->SetHost(nullptr);
+ }
+}
+
+NS_IMPL_ADDREF_INHERITED(HTMLTemplateElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLTemplateElement, Element)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLTemplateElement)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLTemplateElement,
+ nsGenericHTMLElement)
+ if (tmp->mContent) {
+ tmp->mContent->SetHost(nullptr);
+ tmp->mContent = nullptr;
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLTemplateElement,
+ nsGenericHTMLElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+// QueryInterface implementation for HTMLTemplateElement
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLTemplateElement)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLTemplateElement)
+
+JSObject*
+HTMLTemplateElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLTemplateElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
+
diff --git a/dom/html/HTMLTemplateElement.h b/dom/html/HTMLTemplateElement.h
new file mode 100644
index 000000000..53c05c09a
--- /dev/null
+++ b/dom/html/HTMLTemplateElement.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_HTMLTemplateElement_h
+#define mozilla_dom_HTMLTemplateElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsGenericHTMLElement.h"
+#include "mozilla/dom/DocumentFragment.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLTemplateElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLTemplateElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLTemplateElement,
+ nsGenericHTMLElement)
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ DocumentFragment* Content()
+ {
+ return mContent;
+ }
+
+protected:
+ virtual ~HTMLTemplateElement();
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ RefPtr<DocumentFragment> mContent;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLTemplateElement_h
+
diff --git a/dom/html/HTMLTextAreaElement.cpp b/dom/html/HTMLTextAreaElement.cpp
new file mode 100644
index 000000000..42bc02435
--- /dev/null
+++ b/dom/html/HTMLTextAreaElement.cpp
@@ -0,0 +1,1676 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLTextAreaElement.h"
+
+#include "mozAutoDocUpdate.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/HTMLFormSubmission.h"
+#include "mozilla/dom/HTMLTextAreaElementBinding.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/MouseEvents.h"
+#include "nsAttrValueInlines.h"
+#include "nsContentCID.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsError.h"
+#include "nsFocusManager.h"
+#include "nsIComponentManager.h"
+#include "nsIConstraintValidation.h"
+#include "nsIControllers.h"
+#include "nsIDocument.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "nsIFormControlFrame.h"
+#include "nsIFormControl.h"
+#include "nsIForm.h"
+#include "nsIFrame.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITextControlFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsLinebreakConverter.h"
+#include "nsMappedAttributes.h"
+#include "nsPIDOMWindow.h"
+#include "nsPresContext.h"
+#include "nsPresState.h"
+#include "nsReadableUtils.h"
+#include "nsRuleData.h"
+#include "nsStyleConsts.h"
+#include "nsTextEditorState.h"
+#include "nsIController.h"
+
+static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID);
+
+#define NS_NO_CONTENT_DISPATCH (1 << 0)
+
+NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(TextArea)
+
+namespace mozilla {
+namespace dom {
+
+HTMLTextAreaElement::HTMLTextAreaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser)
+ : nsGenericHTMLFormElementWithState(aNodeInfo),
+ mValueChanged(false),
+ mLastValueChangeWasInteractive(false),
+ mHandlingSelect(false),
+ mDoneAddingChildren(!aFromParser),
+ mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT)),
+ mDisabledChanged(false),
+ mCanShowInvalidUI(true),
+ mCanShowValidUI(true),
+ mState(this)
+{
+ AddMutationObserver(this);
+
+ // Set up our default state. By default we're enabled (since we're
+ // a control type that can be disabled but not actually disabled
+ // right now), optional, and valid. We are NOT readwrite by default
+ // until someone calls UpdateEditableState on us, apparently! Also
+ // by default we don't have to show validity UI and so forth.
+ AddStatesSilently(NS_EVENT_STATE_ENABLED |
+ NS_EVENT_STATE_OPTIONAL |
+ NS_EVENT_STATE_VALID);
+}
+
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLTextAreaElement,
+ nsGenericHTMLFormElementWithState,
+ mValidity,
+ mControllers,
+ mState)
+
+NS_IMPL_ADDREF_INHERITED(HTMLTextAreaElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLTextAreaElement, Element)
+
+
+// QueryInterface implementation for HTMLTextAreaElement
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLTextAreaElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLTextAreaElement,
+ nsIDOMHTMLTextAreaElement,
+ nsITextControlElement,
+ nsIDOMNSEditableElement,
+ nsIMutationObserver,
+ nsIConstraintValidation)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElementWithState)
+
+
+// nsIDOMHTMLTextAreaElement
+
+nsresult
+HTMLTextAreaElement::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const
+{
+ *aResult = nullptr;
+ already_AddRefed<mozilla::dom::NodeInfo> ni =
+ RefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget();
+ RefPtr<HTMLTextAreaElement> it = new HTMLTextAreaElement(ni);
+
+ nsresult rv = const_cast<HTMLTextAreaElement*>(this)->CopyInnerTo(it);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mValueChanged) {
+ // Set our value on the clone.
+ nsAutoString value;
+ GetValueInternal(value, true);
+
+ // SetValueInternal handles setting mValueChanged for us
+ rv = it->SetValueInternal(value, nsTextEditorState::eSetValue_Notify);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ it->mLastValueChangeWasInteractive = mLastValueChangeWasInteractive;
+ it.forget(aResult);
+ return NS_OK;
+}
+
+// nsIConstraintValidation
+NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(HTMLTextAreaElement)
+
+
+NS_IMETHODIMP
+HTMLTextAreaElement::GetForm(nsIDOMHTMLFormElement** aForm)
+{
+ return nsGenericHTMLFormElementWithState::GetForm(aForm);
+}
+
+
+// nsIContent
+
+NS_IMETHODIMP
+HTMLTextAreaElement::Select()
+{
+ // XXX Bug? We have to give the input focus before contents can be
+ // selected
+
+ FocusTristate state = FocusState();
+ if (state == eUnfocusable) {
+ return NS_OK;
+ }
+
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+
+ RefPtr<nsPresContext> presContext = GetPresContext(eForComposedDoc);
+ if (state == eInactiveWindow) {
+ if (fm)
+ fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL);
+ SelectAll(presContext);
+ return NS_OK;
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetGUIEvent event(true, eFormSelect, nullptr);
+ // XXXbz HTMLInputElement guards against this reentering; shouldn't we?
+ EventDispatcher::Dispatch(static_cast<nsIContent*>(this), presContext,
+ &event, nullptr, &status);
+
+ // If the DOM event was not canceled (e.g. by a JS event handler
+ // returning false)
+ if (status == nsEventStatus_eIgnore) {
+ if (fm) {
+ fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL);
+
+ // ensure that the element is actually focused
+ nsCOMPtr<nsIDOMElement> focusedElement;
+ fm->GetFocusedElement(getter_AddRefs(focusedElement));
+ if (SameCOMIdentity(static_cast<nsIDOMNode*>(this), focusedElement)) {
+ // Now Select all the text!
+ SelectAll(presContext);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::SelectAll(nsPresContext* aPresContext)
+{
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+
+ if (formControlFrame) {
+ formControlFrame->SetFormProperty(nsGkAtoms::select, EmptyString());
+ }
+
+ return NS_OK;
+}
+
+bool
+HTMLTextAreaElement::IsHTMLFocusable(bool aWithMouse,
+ bool *aIsFocusable, int32_t *aTabIndex)
+{
+ if (nsGenericHTMLFormElementWithState::IsHTMLFocusable(aWithMouse, aIsFocusable,
+ aTabIndex))
+ {
+ return true;
+ }
+
+ // disabled textareas are not focusable
+ *aIsFocusable = !IsDisabled();
+ return false;
+}
+
+NS_IMPL_BOOL_ATTR(HTMLTextAreaElement, Autofocus, autofocus)
+NS_IMPL_UINT_ATTR_NON_ZERO_DEFAULT_VALUE(HTMLTextAreaElement, Cols, cols, DEFAULT_COLS)
+NS_IMPL_BOOL_ATTR(HTMLTextAreaElement, Disabled, disabled)
+NS_IMPL_NON_NEGATIVE_INT_ATTR(HTMLTextAreaElement, MaxLength, maxlength)
+NS_IMPL_NON_NEGATIVE_INT_ATTR(HTMLTextAreaElement, MinLength, minlength)
+NS_IMPL_STRING_ATTR(HTMLTextAreaElement, Name, name)
+NS_IMPL_BOOL_ATTR(HTMLTextAreaElement, ReadOnly, readonly)
+NS_IMPL_BOOL_ATTR(HTMLTextAreaElement, Required, required)
+NS_IMPL_UINT_ATTR_NON_ZERO_DEFAULT_VALUE(HTMLTextAreaElement, Rows, rows, DEFAULT_ROWS_TEXTAREA)
+NS_IMPL_STRING_ATTR(HTMLTextAreaElement, Wrap, wrap)
+NS_IMPL_STRING_ATTR(HTMLTextAreaElement, Placeholder, placeholder)
+
+int32_t
+HTMLTextAreaElement::TabIndexDefault()
+{
+ return 0;
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::GetType(nsAString& aType)
+{
+ aType.AssignLiteral("textarea");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::GetValue(nsAString& aValue)
+{
+ GetValueInternal(aValue, true);
+ return NS_OK;
+}
+
+void
+HTMLTextAreaElement::GetValueInternal(nsAString& aValue, bool aIgnoreWrap) const
+{
+ mState.GetValue(aValue, aIgnoreWrap);
+}
+
+NS_IMETHODIMP_(nsIEditor*)
+HTMLTextAreaElement::GetTextEditor()
+{
+ return GetEditor();
+}
+
+NS_IMETHODIMP_(nsISelectionController*)
+HTMLTextAreaElement::GetSelectionController()
+{
+ return mState.GetSelectionController();
+}
+
+NS_IMETHODIMP_(nsFrameSelection*)
+HTMLTextAreaElement::GetConstFrameSelection()
+{
+ return mState.GetConstFrameSelection();
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::BindToFrame(nsTextControlFrame* aFrame)
+{
+ return mState.BindToFrame(aFrame);
+}
+
+NS_IMETHODIMP_(void)
+HTMLTextAreaElement::UnbindFromFrame(nsTextControlFrame* aFrame)
+{
+ if (aFrame) {
+ mState.UnbindFromFrame(aFrame);
+ }
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::CreateEditor()
+{
+ return mState.PrepareEditor();
+}
+
+NS_IMETHODIMP_(nsIContent*)
+HTMLTextAreaElement::GetRootEditorNode()
+{
+ return mState.GetRootNode();
+}
+
+NS_IMETHODIMP_(Element*)
+HTMLTextAreaElement::CreatePlaceholderNode()
+{
+ NS_ENSURE_SUCCESS(mState.CreatePlaceholderNode(), nullptr);
+ return mState.GetPlaceholderNode();
+}
+
+NS_IMETHODIMP_(Element*)
+HTMLTextAreaElement::GetPlaceholderNode()
+{
+ return mState.GetPlaceholderNode();
+}
+
+NS_IMETHODIMP_(void)
+HTMLTextAreaElement::UpdatePlaceholderVisibility(bool aNotify)
+{
+ mState.UpdatePlaceholderVisibility(aNotify);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLTextAreaElement::GetPlaceholderVisibility()
+{
+ return mState.GetPlaceholderVisibility();
+}
+
+nsresult
+HTMLTextAreaElement::SetValueInternal(const nsAString& aValue,
+ uint32_t aFlags)
+{
+ // Need to set the value changed flag here, so that
+ // nsTextControlFrame::UpdateValueDisplay retrieves the correct value
+ // if needed.
+ SetValueChanged(true);
+ aFlags |= nsTextEditorState::eSetValue_Notify;
+ if (!mState.SetValue(aValue, aFlags)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::SetValue(const nsAString& aValue)
+{
+ // If the value has been set by a script, we basically want to keep the
+ // current change event state. If the element is ready to fire a change
+ // event, we should keep it that way. Otherwise, we should make sure the
+ // element will not fire any event because of the script interaction.
+ //
+ // NOTE: this is currently quite expensive work (too much string
+ // manipulation). We should probably optimize that.
+ nsAutoString currentValue;
+ GetValueInternal(currentValue, true);
+
+ nsresult rv =
+ SetValueInternal(aValue, nsTextEditorState::eSetValue_ByContent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mFocusedValue.Equals(currentValue)) {
+ GetValueInternal(mFocusedValue, true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::SetUserInput(const nsAString& aValue)
+{
+ if (!nsContentUtils::IsCallerChrome()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ return SetValueInternal(aValue, nsTextEditorState::eSetValue_BySetUserInput);
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::SetValueChanged(bool aValueChanged)
+{
+ bool previousValue = mValueChanged;
+
+ mValueChanged = aValueChanged;
+ if (!aValueChanged && !mState.IsEmpty()) {
+ mState.EmptyValue();
+ }
+
+ if (mValueChanged != previousValue) {
+ UpdateState(true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::GetDefaultValue(nsAString& aDefaultValue)
+{
+ if (!nsContentUtils::GetNodeTextContent(this, false, aDefaultValue, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::SetDefaultValue(const nsAString& aDefaultValue)
+{
+ ErrorResult error;
+ SetDefaultValue(aDefaultValue, error);
+ return error.StealNSResult();
+}
+
+void
+HTMLTextAreaElement::SetDefaultValue(const nsAString& aDefaultValue, ErrorResult& aError)
+{
+ nsresult rv = nsContentUtils::SetNodeTextContent(this, aDefaultValue, true);
+ if (NS_SUCCEEDED(rv) && !mValueChanged) {
+ Reset();
+ }
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ }
+}
+
+bool
+HTMLTextAreaElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::maxlength ||
+ aAttribute == nsGkAtoms::minlength) {
+ return aResult.ParseNonNegativeIntValue(aValue);
+ } else if (aAttribute == nsGkAtoms::cols) {
+ aResult.ParseIntWithFallback(aValue, DEFAULT_COLS);
+ return true;
+ } else if (aAttribute == nsGkAtoms::rows) {
+ aResult.ParseIntWithFallback(aValue, DEFAULT_ROWS_TEXTAREA);
+ return true;
+ }
+ }
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLTextAreaElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Text)) {
+ // wrap=off
+ nsCSSValue* whiteSpace = aData->ValueForWhiteSpace();
+ if (whiteSpace->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::wrap);
+ if (value && value->Type() == nsAttrValue::eString &&
+ value->Equals(nsGkAtoms::OFF, eIgnoreCase)) {
+ whiteSpace->SetIntValue(NS_STYLE_WHITESPACE_PRE, eCSSUnit_Enumerated);
+ }
+ }
+ }
+
+ nsGenericHTMLFormElementWithState::MapDivAlignAttributeInto(aAttributes, aData);
+ nsGenericHTMLFormElementWithState::MapCommonAttributesInto(aAttributes, aData);
+}
+
+nsChangeHint
+HTMLTextAreaElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const
+{
+ nsChangeHint retval =
+ nsGenericHTMLFormElementWithState::GetAttributeChangeHint(aAttribute, aModType);
+ if (aAttribute == nsGkAtoms::rows ||
+ aAttribute == nsGkAtoms::cols) {
+ retval |= NS_STYLE_HINT_REFLOW;
+ } else if (aAttribute == nsGkAtoms::wrap) {
+ retval |= nsChangeHint_ReconstructFrame;
+ } else if (aAttribute == nsGkAtoms::placeholder) {
+ retval |= nsChangeHint_ReconstructFrame;
+ }
+ return retval;
+}
+
+NS_IMETHODIMP_(bool)
+HTMLTextAreaElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::wrap },
+ { nullptr }
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sDivAlignAttributeMap,
+ sCommonAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+nsMapRuleToAttributesFunc
+HTMLTextAreaElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+bool
+HTMLTextAreaElement::IsDisabledForEvents(EventMessage aMessage)
+{
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
+ nsIFrame* formFrame = do_QueryFrame(formControlFrame);
+ return IsElementDisabledForEvents(aMessage, formFrame);
+}
+
+nsresult
+HTMLTextAreaElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ aVisitor.mCanHandle = false;
+ if (IsDisabledForEvents(aVisitor.mEvent->mMessage)) {
+ return NS_OK;
+ }
+
+ // Don't dispatch a second select event if we are already handling
+ // one.
+ if (aVisitor.mEvent->mMessage == eFormSelect) {
+ if (mHandlingSelect) {
+ return NS_OK;
+ }
+ mHandlingSelect = true;
+ }
+
+ // If noContentDispatch is true we will not allow content to handle
+ // this event. But to allow middle mouse button paste to work we must allow
+ // middle clicks to go to text fields anyway.
+ if (aVisitor.mEvent->mFlags.mNoContentDispatch) {
+ aVisitor.mItemFlags |= NS_NO_CONTENT_DISPATCH;
+ }
+ if (aVisitor.mEvent->mMessage == eMouseClick &&
+ aVisitor.mEvent->AsMouseEvent()->button ==
+ WidgetMouseEvent::eMiddleButton) {
+ aVisitor.mEvent->mFlags.mNoContentDispatch = false;
+ }
+
+ // Fire onchange (if necessary), before we do the blur, bug 370521.
+ if (aVisitor.mEvent->mMessage == eBlur) {
+ FireChangeEventIfNeeded();
+ }
+
+ return nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor);
+}
+
+void
+HTMLTextAreaElement::FireChangeEventIfNeeded()
+{
+ nsString value;
+ GetValueInternal(value, true);
+
+ if (mFocusedValue.Equals(value)) {
+ return;
+ }
+
+ // Dispatch the change event.
+ mFocusedValue = value;
+ nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+ static_cast<nsIContent*>(this),
+ NS_LITERAL_STRING("change"), true,
+ false);
+}
+
+nsresult
+HTMLTextAreaElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ if (aVisitor.mEvent->mMessage == eFormSelect) {
+ mHandlingSelect = false;
+ }
+
+ if (aVisitor.mEvent->mMessage == eFocus ||
+ aVisitor.mEvent->mMessage == eBlur) {
+ if (aVisitor.mEvent->mMessage == eFocus) {
+ // If the invalid UI is shown, we should show it while focusing (and
+ // update). Otherwise, we should not.
+ GetValueInternal(mFocusedValue, true);
+ mCanShowInvalidUI = !IsValid() && ShouldShowValidityUI();
+
+ // If neither invalid UI nor valid UI is shown, we shouldn't show the valid
+ // UI while typing.
+ mCanShowValidUI = ShouldShowValidityUI();
+ } else { // eBlur
+ mCanShowInvalidUI = true;
+ mCanShowValidUI = true;
+ }
+
+ UpdateState(true);
+ }
+
+ // Reset the flag for other content besides this text field
+ aVisitor.mEvent->mFlags.mNoContentDispatch =
+ ((aVisitor.mItemFlags & NS_NO_CONTENT_DISPATCH) != 0);
+
+ return NS_OK;
+}
+
+void
+HTMLTextAreaElement::DoneAddingChildren(bool aHaveNotified)
+{
+ if (!mValueChanged) {
+ if (!mDoneAddingChildren) {
+ // Reset now that we're done adding children if the content sink tried to
+ // sneak some text in without calling AppendChildTo.
+ Reset();
+ }
+
+ if (!mInhibitStateRestoration) {
+ nsresult rv = GenerateStateKey();
+ if (NS_SUCCEEDED(rv)) {
+ RestoreFormControlState();
+ }
+ }
+ }
+
+ mDoneAddingChildren = true;
+}
+
+bool
+HTMLTextAreaElement::IsDoneAddingChildren()
+{
+ return mDoneAddingChildren;
+}
+
+// Controllers Methods
+
+nsIControllers*
+HTMLTextAreaElement::GetControllers(ErrorResult& aError)
+{
+ if (!mControllers)
+ {
+ nsresult rv;
+ mControllers = do_CreateInstance(kXULControllersCID, &rv);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIController> controller = do_CreateInstance("@mozilla.org/editor/editorcontroller;1", &rv);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+
+ mControllers->AppendController(controller);
+
+ controller = do_CreateInstance("@mozilla.org/editor/editingcontroller;1", &rv);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+
+ mControllers->AppendController(controller);
+ }
+
+ return mControllers;
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::GetControllers(nsIControllers** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ ErrorResult error;
+ *aResult = GetControllers(error);
+ NS_IF_ADDREF(*aResult);
+
+ return error.StealNSResult();
+}
+
+uint32_t
+HTMLTextAreaElement::GetTextLength()
+{
+ nsAutoString val;
+ GetValue(val);
+ return val.Length();
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::GetTextLength(int32_t *aTextLength)
+{
+ NS_ENSURE_ARG_POINTER(aTextLength);
+ *aTextLength = GetTextLength();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::GetSelectionStart(int32_t *aSelectionStart)
+{
+ NS_ENSURE_ARG_POINTER(aSelectionStart);
+
+ ErrorResult error;
+ Nullable<uint32_t> selStart(GetSelectionStart(error));
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ *aSelectionStart = int32_t(selStart.Value());
+ return error.StealNSResult();
+}
+
+Nullable<uint32_t>
+HTMLTextAreaElement::GetSelectionStart(ErrorResult& aError)
+{
+ int32_t selStart, selEnd;
+ nsresult rv = GetSelectionRange(&selStart, &selEnd);
+
+ if (NS_FAILED(rv) && mState.IsSelectionCached()) {
+ return Nullable<uint32_t>(mState.GetSelectionProperties().GetStart());
+ }
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ }
+ return Nullable<uint32_t>(selStart);
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::SetSelectionStart(int32_t aSelectionStart)
+{
+ ErrorResult error;
+ Nullable<uint32_t> selStart(aSelectionStart);
+ SetSelectionStart(selStart, error);
+ return error.StealNSResult();
+}
+
+void
+HTMLTextAreaElement::SetSelectionStart(const Nullable<uint32_t>& aSelectionStart,
+ ErrorResult& aError)
+{
+ int32_t selStart = 0;
+ if (!aSelectionStart.IsNull()) {
+ selStart = aSelectionStart.Value();
+ }
+
+ if (mState.IsSelectionCached()) {
+ mState.GetSelectionProperties().SetStart(selStart);
+ return;
+ }
+
+ nsAutoString direction;
+ nsresult rv = GetSelectionDirection(direction);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return;
+ }
+ int32_t start, end;
+ rv = GetSelectionRange(&start, &end);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return;
+ }
+ start = selStart;
+ if (end < start) {
+ end = start;
+ }
+ rv = SetSelectionRange(start, end, direction);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ }
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::GetSelectionEnd(int32_t *aSelectionEnd)
+{
+ NS_ENSURE_ARG_POINTER(aSelectionEnd);
+
+ ErrorResult error;
+ Nullable<uint32_t> selEnd(GetSelectionEnd(error));
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ *aSelectionEnd = int32_t(selEnd.Value());
+ return NS_OK;
+}
+
+Nullable<uint32_t>
+HTMLTextAreaElement::GetSelectionEnd(ErrorResult& aError)
+{
+ int32_t selStart, selEnd;
+ nsresult rv = GetSelectionRange(&selStart, &selEnd);
+
+ if (NS_FAILED(rv) && mState.IsSelectionCached()) {
+ return Nullable<uint32_t>(mState.GetSelectionProperties().GetEnd());
+ }
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ }
+ return Nullable<uint32_t>(selEnd);
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::SetSelectionEnd(int32_t aSelectionEnd)
+{
+ ErrorResult error;
+ Nullable<uint32_t> selEnd(aSelectionEnd);
+ SetSelectionEnd(selEnd, error);
+ return error.StealNSResult();
+}
+
+void
+HTMLTextAreaElement::SetSelectionEnd(const Nullable<uint32_t>& aSelectionEnd,
+ ErrorResult& aError)
+{
+ int32_t selEnd = 0;
+ if (!aSelectionEnd.IsNull()) {
+ selEnd = aSelectionEnd.Value();
+ }
+
+ if (mState.IsSelectionCached()) {
+ mState.GetSelectionProperties().SetEnd(selEnd);
+ return;
+ }
+
+ nsAutoString direction;
+ nsresult rv = GetSelectionDirection(direction);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return;
+ }
+ int32_t start, end;
+ rv = GetSelectionRange(&start, &end);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return;
+ }
+ end = selEnd;
+ if (start > end) {
+ start = end;
+ }
+ rv = SetSelectionRange(start, end, direction);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ }
+}
+
+nsresult
+HTMLTextAreaElement::GetSelectionRange(int32_t* aSelectionStart,
+ int32_t* aSelectionEnd)
+{
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+ nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
+ if (textControlFrame) {
+ return textControlFrame->GetSelectionRange(aSelectionStart, aSelectionEnd);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+static void
+DirectionToName(nsITextControlFrame::SelectionDirection dir, nsAString& aDirection)
+{
+ if (dir == nsITextControlFrame::eNone) {
+ aDirection.AssignLiteral("none");
+ } else if (dir == nsITextControlFrame::eForward) {
+ aDirection.AssignLiteral("forward");
+ } else if (dir == nsITextControlFrame::eBackward) {
+ aDirection.AssignLiteral("backward");
+ } else {
+ NS_NOTREACHED("Invalid SelectionDirection value");
+ }
+}
+
+nsresult
+HTMLTextAreaElement::GetSelectionDirection(nsAString& aDirection)
+{
+ ErrorResult error;
+ GetSelectionDirection(aDirection, error);
+ return error.StealNSResult();
+}
+
+void
+HTMLTextAreaElement::GetSelectionDirection(nsAString& aDirection, ErrorResult& aError)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+ nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
+ if (textControlFrame) {
+ nsITextControlFrame::SelectionDirection dir;
+ rv = textControlFrame->GetSelectionRange(nullptr, nullptr, &dir);
+ if (NS_SUCCEEDED(rv)) {
+ DirectionToName(dir, aDirection);
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ if (mState.IsSelectionCached()) {
+ DirectionToName(mState.GetSelectionProperties().GetDirection(), aDirection);
+ return;
+ }
+ aError.Throw(rv);
+ }
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::SetSelectionDirection(const nsAString& aDirection)
+{
+ ErrorResult error;
+ SetSelectionDirection(aDirection, error);
+ return error.StealNSResult();
+}
+
+void
+HTMLTextAreaElement::SetSelectionDirection(const nsAString& aDirection,
+ ErrorResult& aError)
+{
+ if (mState.IsSelectionCached()) {
+ nsITextControlFrame::SelectionDirection dir = nsITextControlFrame::eNone;
+ if (aDirection.EqualsLiteral("forward")) {
+ dir = nsITextControlFrame::eForward;
+ } else if (aDirection.EqualsLiteral("backward")) {
+ dir = nsITextControlFrame::eBackward;
+ }
+ mState.GetSelectionProperties().SetDirection(dir);
+ return;
+ }
+
+ int32_t start, end;
+ nsresult rv = GetSelectionRange(&start, &end);
+ if (NS_SUCCEEDED(rv)) {
+ rv = SetSelectionRange(start, end, aDirection);
+ }
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ }
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::SetSelectionRange(int32_t aSelectionStart,
+ int32_t aSelectionEnd,
+ const nsAString& aDirection)
+{
+ ErrorResult error;
+ Optional<nsAString> dir;
+ dir = &aDirection;
+ SetSelectionRange(aSelectionStart, aSelectionEnd, dir, error);
+ return error.StealNSResult();
+}
+
+void
+HTMLTextAreaElement::SetSelectionRange(uint32_t aSelectionStart,
+ uint32_t aSelectionEnd,
+ const Optional<nsAString>& aDirection,
+ ErrorResult& aError)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+ nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
+ if (textControlFrame) {
+ // Default to forward, even if not specified.
+ // Note that we don't currently support directionless selections, so
+ // "none" is treated like "forward".
+ nsITextControlFrame::SelectionDirection dir = nsITextControlFrame::eForward;
+ if (aDirection.WasPassed() && aDirection.Value().EqualsLiteral("backward")) {
+ dir = nsITextControlFrame::eBackward;
+ }
+
+ rv = textControlFrame->SetSelectionRange(aSelectionStart, aSelectionEnd, dir);
+ if (NS_SUCCEEDED(rv)) {
+ rv = textControlFrame->ScrollSelectionIntoView();
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, NS_LITERAL_STRING("select"),
+ true, false);
+ asyncDispatcher->PostDOMEvent();
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ }
+}
+
+void
+HTMLTextAreaElement::SetRangeText(const nsAString& aReplacement,
+ ErrorResult& aRv)
+{
+ int32_t start, end;
+ aRv = GetSelectionRange(&start, &end);
+ if (aRv.Failed()) {
+ if (mState.IsSelectionCached()) {
+ start = mState.GetSelectionProperties().GetStart();
+ end = mState.GetSelectionProperties().GetEnd();
+ aRv = NS_OK;
+ }
+ }
+
+ SetRangeText(aReplacement, start, end, mozilla::dom::SelectionMode::Preserve,
+ aRv, start, end);
+}
+
+void
+HTMLTextAreaElement::SetRangeText(const nsAString& aReplacement,
+ uint32_t aStart, uint32_t aEnd,
+ const SelectionMode& aSelectMode,
+ ErrorResult& aRv, int32_t aSelectionStart,
+ int32_t aSelectionEnd)
+{
+ if (aStart > aEnd) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ nsAutoString value;
+ GetValueInternal(value, false);
+ uint32_t inputValueLength = value.Length();
+
+ if (aStart > inputValueLength) {
+ aStart = inputValueLength;
+ }
+
+ if (aEnd > inputValueLength) {
+ aEnd = inputValueLength;
+ }
+
+ if (aSelectionStart == -1 && aSelectionEnd == -1) {
+ aRv = GetSelectionRange(&aSelectionStart, &aSelectionEnd);
+ if (aRv.Failed()) {
+ if (mState.IsSelectionCached()) {
+ aSelectionStart = mState.GetSelectionProperties().GetStart();
+ aSelectionEnd = mState.GetSelectionProperties().GetEnd();
+ aRv = NS_OK;
+ }
+ }
+ }
+
+ if (aStart <= aEnd) {
+ value.Replace(aStart, aEnd - aStart, aReplacement);
+ nsresult rv =
+ SetValueInternal(value, nsTextEditorState::eSetValue_ByContent);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+ }
+
+ uint32_t newEnd = aStart + aReplacement.Length();
+ int32_t delta = aReplacement.Length() - (aEnd - aStart);
+
+ switch (aSelectMode) {
+ case mozilla::dom::SelectionMode::Select:
+ {
+ aSelectionStart = aStart;
+ aSelectionEnd = newEnd;
+ }
+ break;
+ case mozilla::dom::SelectionMode::Start:
+ {
+ aSelectionStart = aSelectionEnd = aStart;
+ }
+ break;
+ case mozilla::dom::SelectionMode::End:
+ {
+ aSelectionStart = aSelectionEnd = newEnd;
+ }
+ break;
+ case mozilla::dom::SelectionMode::Preserve:
+ {
+ if ((uint32_t)aSelectionStart > aEnd) {
+ aSelectionStart += delta;
+ } else if ((uint32_t)aSelectionStart > aStart) {
+ aSelectionStart = aStart;
+ }
+
+ if ((uint32_t)aSelectionEnd > aEnd) {
+ aSelectionEnd += delta;
+ } else if ((uint32_t)aSelectionEnd > aStart) {
+ aSelectionEnd = newEnd;
+ }
+ }
+ break;
+ default:
+ MOZ_CRASH("Unknown mode!");
+ }
+
+ Optional<nsAString> direction;
+ SetSelectionRange(aSelectionStart, aSelectionEnd, direction, aRv);
+}
+
+nsresult
+HTMLTextAreaElement::Reset()
+{
+ nsresult rv;
+
+ // To get the initial spellchecking, reset value to
+ // empty string before setting the default value.
+ rv = SetValue(EmptyString());
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoString resetVal;
+ GetDefaultValue(resetVal);
+ rv = SetValue(resetVal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetValueChanged(false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission)
+{
+ // Disabled elements don't submit
+ if (IsDisabled()) {
+ return NS_OK;
+ }
+
+ //
+ // Get the name (if no name, no submit)
+ //
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+ if (name.IsEmpty()) {
+ return NS_OK;
+ }
+
+ //
+ // Get the value
+ //
+ nsAutoString value;
+ GetValueInternal(value, false);
+
+ //
+ // Submit
+ //
+ return aFormSubmission->AddNameValuePair(name, value);
+}
+
+NS_IMETHODIMP
+HTMLTextAreaElement::SaveState()
+{
+ nsresult rv = NS_OK;
+
+ // Only save if value != defaultValue (bug 62713)
+ nsPresState *state = nullptr;
+ if (mValueChanged) {
+ state = GetPrimaryPresState();
+ if (state) {
+ nsAutoString value;
+ GetValueInternal(value, true);
+
+ rv = nsLinebreakConverter::ConvertStringLineBreaks(
+ value,
+ nsLinebreakConverter::eLinebreakPlatform,
+ nsLinebreakConverter::eLinebreakContent);
+
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Converting linebreaks failed!");
+ return rv;
+ }
+
+ nsCOMPtr<nsISupportsString> pState =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
+ if (!pState) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ pState->SetData(value);
+ state->SetStateProperty(pState);
+ }
+ }
+
+ if (mDisabledChanged) {
+ if (!state) {
+ state = GetPrimaryPresState();
+ rv = NS_OK;
+ }
+ if (state) {
+ // We do not want to save the real disabled state but the disabled
+ // attribute.
+ state->SetDisabled(HasAttr(kNameSpaceID_None, nsGkAtoms::disabled));
+ }
+ }
+ return rv;
+}
+
+bool
+HTMLTextAreaElement::RestoreState(nsPresState* aState)
+{
+ nsCOMPtr<nsISupportsString> state
+ (do_QueryInterface(aState->GetStateProperty()));
+
+ if (state) {
+ nsAutoString data;
+ state->GetData(data);
+ nsresult rv = SetValue(data);
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ if (aState->IsDisabledSet()) {
+ SetDisabled(aState->GetDisabled());
+ }
+
+ return false;
+}
+
+EventStates
+HTMLTextAreaElement::IntrinsicState() const
+{
+ EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState();
+
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
+ state |= NS_EVENT_STATE_REQUIRED;
+ } else {
+ state |= NS_EVENT_STATE_OPTIONAL;
+ }
+
+ if (IsCandidateForConstraintValidation()) {
+ if (IsValid()) {
+ state |= NS_EVENT_STATE_VALID;
+ } else {
+ state |= NS_EVENT_STATE_INVALID;
+ // :-moz-ui-invalid always apply if the element suffers from a custom
+ // error and never applies if novalidate is set on the form owner.
+ if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
+ (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
+ (mCanShowInvalidUI && ShouldShowValidityUI()))) {
+ state |= NS_EVENT_STATE_MOZ_UI_INVALID;
+ }
+ }
+
+ // :-moz-ui-valid applies if all the following are true:
+ // 1. The element is not focused, or had either :-moz-ui-valid or
+ // :-moz-ui-invalid applying before it was focused ;
+ // 2. The element is either valid or isn't allowed to have
+ // :-moz-ui-invalid applying ;
+ // 3. The element has no form owner or its form owner doesn't have the
+ // novalidate attribute set ;
+ // 4. The element has already been modified or the user tried to submit the
+ // form owner while invalid.
+ if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
+ (mCanShowValidUI && ShouldShowValidityUI() &&
+ (IsValid() || (state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
+ !mCanShowInvalidUI)))) {
+ state |= NS_EVENT_STATE_MOZ_UI_VALID;
+ }
+ }
+
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder) &&
+ IsValueEmpty()) {
+ state |= NS_EVENT_STATE_PLACEHOLDERSHOWN;
+ }
+
+ return state;
+}
+
+nsresult
+HTMLTextAreaElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLFormElementWithState::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If there is a disabled fieldset in the parent chain, the element is now
+ // barred from constraint validation and can't suffer from value missing.
+ UpdateValueMissingValidityState();
+ UpdateBarredFromConstraintValidation();
+
+ // And now make sure our state is up to date
+ UpdateState(false);
+
+ return rv;
+}
+
+void
+HTMLTextAreaElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ nsGenericHTMLFormElementWithState::UnbindFromTree(aDeep, aNullParent);
+
+ // We might be no longer disabled because of parent chain changed.
+ UpdateValueMissingValidityState();
+ UpdateBarredFromConstraintValidation();
+
+ // And now make sure our state is up to date
+ UpdateState(false);
+}
+
+nsresult
+HTMLTextAreaElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify)
+{
+ if (aNotify && aName == nsGkAtoms::disabled &&
+ aNameSpaceID == kNameSpaceID_None) {
+ mDisabledChanged = true;
+ }
+
+ return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+void
+HTMLTextAreaElement::CharacterDataChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+ ContentChanged(aContent);
+}
+
+void
+HTMLTextAreaElement::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t /* unused */)
+{
+ ContentChanged(aFirstNewContent);
+}
+
+void
+HTMLTextAreaElement::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t /* unused */)
+{
+ ContentChanged(aChild);
+}
+
+void
+HTMLTextAreaElement::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ ContentChanged(aChild);
+}
+
+void
+HTMLTextAreaElement::ContentChanged(nsIContent* aContent)
+{
+ if (!mValueChanged && mDoneAddingChildren &&
+ nsContentUtils::IsInSameAnonymousTree(this, aContent)) {
+ // Hard to say what the reset can trigger, so be safe pending
+ // further auditing.
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ Reset();
+ }
+}
+
+nsresult
+HTMLTextAreaElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
+ aName == nsGkAtoms::readonly) {
+ UpdateValueMissingValidityState();
+
+ // This *has* to be called *after* validity has changed.
+ if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
+ UpdateBarredFromConstraintValidation();
+ }
+ } else if (aName == nsGkAtoms::maxlength) {
+ UpdateTooLongValidityState();
+ } else if (aName == nsGkAtoms::minlength) {
+ UpdateTooShortValidityState();
+ }
+
+ UpdateState(aNotify);
+ }
+
+ return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName, aValue,
+ aNotify);
+ }
+
+nsresult
+HTMLTextAreaElement::CopyInnerTo(Element* aDest)
+{
+ nsresult rv = nsGenericHTMLFormElementWithState::CopyInnerTo(aDest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aDest->OwnerDoc()->IsStaticDocument()) {
+ nsAutoString value;
+ GetValueInternal(value, true);
+ return static_cast<HTMLTextAreaElement*>(aDest)->SetValue(value);
+ }
+ return NS_OK;
+}
+
+bool
+HTMLTextAreaElement::IsMutable() const
+{
+ return (!HasAttr(kNameSpaceID_None, nsGkAtoms::readonly) && !IsDisabled());
+}
+
+bool
+HTMLTextAreaElement::IsValueEmpty() const
+{
+ nsAutoString value;
+ GetValueInternal(value, true);
+
+ return value.IsEmpty();
+}
+
+// nsIConstraintValidation
+
+NS_IMETHODIMP
+HTMLTextAreaElement::SetCustomValidity(const nsAString& aError)
+{
+ nsIConstraintValidation::SetCustomValidity(aError);
+
+ UpdateState(true);
+
+ return NS_OK;
+}
+
+bool
+HTMLTextAreaElement::IsTooLong()
+{
+ if (!mValueChanged ||
+ !mLastValueChangeWasInteractive ||
+ !HasAttr(kNameSpaceID_None, nsGkAtoms::maxlength)) {
+ return false;
+ }
+
+ int32_t maxLength = -1;
+ GetMaxLength(&maxLength);
+
+ // Maxlength of -1 means parsing error.
+ if (maxLength == -1) {
+ return false;
+ }
+
+ int32_t textLength = -1;
+ GetTextLength(&textLength);
+
+ return textLength > maxLength;
+}
+
+bool
+HTMLTextAreaElement::IsTooShort()
+{
+ if (!mValueChanged ||
+ !mLastValueChangeWasInteractive ||
+ !HasAttr(kNameSpaceID_None, nsGkAtoms::minlength)) {
+ return false;
+ }
+
+ int32_t minLength = -1;
+ GetMinLength(&minLength);
+
+ // Minlength of -1 means parsing error.
+ if (minLength == -1) {
+ return false;
+ }
+
+ int32_t textLength = -1;
+ GetTextLength(&textLength);
+
+ return textLength && textLength < minLength;
+}
+
+bool
+HTMLTextAreaElement::IsValueMissing() const
+{
+ if (!HasAttr(kNameSpaceID_None, nsGkAtoms::required) || !IsMutable()) {
+ return false;
+ }
+
+ return IsValueEmpty();
+}
+
+void
+HTMLTextAreaElement::UpdateTooLongValidityState()
+{
+ SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong());
+}
+
+void
+HTMLTextAreaElement::UpdateTooShortValidityState()
+{
+ SetValidityState(VALIDITY_STATE_TOO_SHORT, IsTooShort());
+}
+
+void
+HTMLTextAreaElement::UpdateValueMissingValidityState()
+{
+ SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
+}
+
+void
+HTMLTextAreaElement::UpdateBarredFromConstraintValidation()
+{
+ SetBarredFromConstraintValidation(HasAttr(kNameSpaceID_None,
+ nsGkAtoms::readonly) ||
+ IsDisabled());
+}
+
+nsresult
+HTMLTextAreaElement::GetValidationMessage(nsAString& aValidationMessage,
+ ValidityStateType aType)
+{
+ nsresult rv = NS_OK;
+
+ switch (aType)
+ {
+ case VALIDITY_STATE_TOO_LONG:
+ {
+ nsXPIDLString message;
+ int32_t maxLength = -1;
+ int32_t textLength = -1;
+ nsAutoString strMaxLength;
+ nsAutoString strTextLength;
+
+ GetMaxLength(&maxLength);
+ GetTextLength(&textLength);
+
+ strMaxLength.AppendInt(maxLength);
+ strTextLength.AppendInt(textLength);
+
+ const char16_t* params[] = { strMaxLength.get(), strTextLength.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationTextTooLong",
+ params, message);
+ aValidationMessage = message;
+ }
+ break;
+ case VALIDITY_STATE_TOO_SHORT:
+ {
+ nsXPIDLString message;
+ int32_t minLength = -1;
+ int32_t textLength = -1;
+ nsAutoString strMinLength;
+ nsAutoString strTextLength;
+
+ GetMinLength(&minLength);
+ GetTextLength(&textLength);
+
+ strMinLength.AppendInt(minLength);
+ strTextLength.AppendInt(textLength);
+
+ const char16_t* params[] = { strMinLength.get(), strTextLength.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationTextTooShort",
+ params, message);
+ aValidationMessage = message;
+ }
+ break;
+ case VALIDITY_STATE_VALUE_MISSING:
+ {
+ nsXPIDLString message;
+ rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationValueMissing",
+ message);
+ aValidationMessage = message;
+ }
+ break;
+ default:
+ rv = nsIConstraintValidation::GetValidationMessage(aValidationMessage, aType);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP_(bool)
+HTMLTextAreaElement::IsSingleLineTextControl() const
+{
+ return false;
+}
+
+NS_IMETHODIMP_(bool)
+HTMLTextAreaElement::IsTextArea() const
+{
+ return true;
+}
+
+NS_IMETHODIMP_(bool)
+HTMLTextAreaElement::IsPlainTextControl() const
+{
+ // need to check our HTML attribute and/or CSS.
+ return true;
+}
+
+NS_IMETHODIMP_(bool)
+HTMLTextAreaElement::IsPasswordTextControl() const
+{
+ return false;
+}
+
+NS_IMETHODIMP_(int32_t)
+HTMLTextAreaElement::GetCols()
+{
+ return Cols();
+}
+
+NS_IMETHODIMP_(int32_t)
+HTMLTextAreaElement::GetWrapCols()
+{
+ // wrap=off means -1 for wrap width no matter what cols is
+ nsHTMLTextWrap wrapProp;
+ nsITextControlElement::GetWrapPropertyEnum(this, wrapProp);
+ if (wrapProp == nsITextControlElement::eHTMLTextWrap_Off) {
+ // do not wrap when wrap=off
+ return 0;
+ }
+
+ // Otherwise we just wrap at the given number of columns
+ return GetCols();
+}
+
+
+NS_IMETHODIMP_(int32_t)
+HTMLTextAreaElement::GetRows()
+{
+ const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::rows);
+ if (attr && attr->Type() == nsAttrValue::eInteger) {
+ int32_t rows = attr->GetIntegerValue();
+ return (rows <= 0) ? DEFAULT_ROWS_TEXTAREA : rows;
+ }
+
+ return DEFAULT_ROWS_TEXTAREA;
+}
+
+NS_IMETHODIMP_(void)
+HTMLTextAreaElement::GetDefaultValueFromContent(nsAString& aValue)
+{
+ GetDefaultValue(aValue);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLTextAreaElement::ValueChanged() const
+{
+ return mValueChanged;
+}
+
+NS_IMETHODIMP_(void)
+HTMLTextAreaElement::GetTextEditorValue(nsAString& aValue,
+ bool aIgnoreWrap) const
+{
+ mState.GetValue(aValue, aIgnoreWrap);
+}
+
+NS_IMETHODIMP_(void)
+HTMLTextAreaElement::InitializeKeyboardEventListeners()
+{
+ mState.InitializeKeyboardEventListeners();
+}
+
+NS_IMETHODIMP_(void)
+HTMLTextAreaElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange)
+{
+ mLastValueChangeWasInteractive = aWasInteractiveUserChange;
+
+ // Update the validity state
+ bool validBefore = IsValid();
+ UpdateTooLongValidityState();
+ UpdateTooShortValidityState();
+ UpdateValueMissingValidityState();
+
+ if (validBefore != IsValid() ||
+ HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
+ UpdateState(aNotify);
+ }
+}
+
+NS_IMETHODIMP_(bool)
+HTMLTextAreaElement::HasCachedSelection()
+{
+ return mState.IsSelectionCached();
+}
+
+void
+HTMLTextAreaElement::FieldSetDisabledChanged(bool aNotify)
+{
+ UpdateValueMissingValidityState();
+ UpdateBarredFromConstraintValidation();
+
+ nsGenericHTMLFormElementWithState::FieldSetDisabledChanged(aNotify);
+}
+
+JSObject*
+HTMLTextAreaElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLTextAreaElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLTextAreaElement.h b/dom/html/HTMLTextAreaElement.h
new file mode 100644
index 000000000..039082818
--- /dev/null
+++ b/dom/html/HTMLTextAreaElement.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 mozilla_dom_HTMLTextAreaElement_h
+#define mozilla_dom_HTMLTextAreaElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsIDOMHTMLTextAreaElement.h"
+#include "nsITextControlElement.h"
+#include "nsIControllers.h"
+#include "nsIDOMNSEditableElement.h"
+#include "nsCOMPtr.h"
+#include "nsGenericHTMLElement.h"
+#include "nsStubMutationObserver.h"
+#include "nsIConstraintValidation.h"
+#include "mozilla/dom/HTMLFormElement.h"
+#include "mozilla/dom/HTMLInputElementBinding.h"
+#include "nsGkAtoms.h"
+
+#include "nsTextEditorState.h"
+
+class nsIControllers;
+class nsIDocument;
+class nsPresContext;
+class nsPresState;
+
+namespace mozilla {
+
+class EventChainPostVisitor;
+class EventChainPreVisitor;
+class EventStates;
+
+namespace dom {
+
+class HTMLFormSubmission;
+
+class HTMLTextAreaElement final : public nsGenericHTMLFormElementWithState,
+ public nsIDOMHTMLTextAreaElement,
+ public nsITextControlElement,
+ public nsIDOMNSEditableElement,
+ public nsStubMutationObserver,
+ public nsIConstraintValidation
+{
+public:
+ using nsIConstraintValidation::GetValidationMessage;
+
+ explicit HTMLTextAreaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser = NOT_FROM_PARSER);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual int32_t TabIndexDefault() override;
+
+ // Element
+ virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override
+ {
+ return true;
+ }
+
+ // nsIDOMHTMLTextAreaElement
+ NS_DECL_NSIDOMHTMLTEXTAREAELEMENT
+
+ // nsIDOMNSEditableElement
+ NS_IMETHOD GetEditor(nsIEditor** aEditor) override
+ {
+ return nsGenericHTMLElement::GetEditor(aEditor);
+ }
+ NS_IMETHOD SetUserInput(const nsAString& aInput) override;
+
+ // nsIFormControl
+ NS_IMETHOD_(uint32_t) GetType() const override { return NS_FORM_TEXTAREA; }
+ NS_IMETHOD Reset() override;
+ NS_IMETHOD SubmitNamesValues(HTMLFormSubmission* aFormSubmission) override;
+ NS_IMETHOD SaveState() override;
+ virtual bool RestoreState(nsPresState* aState) override;
+ virtual bool IsDisabledForEvents(EventMessage aMessage) override;
+
+ virtual void FieldSetDisabledChanged(bool aNotify) override;
+
+ virtual EventStates IntrinsicState() const override;
+
+ // nsITextControlElemet
+ NS_IMETHOD SetValueChanged(bool aValueChanged) override;
+ NS_IMETHOD_(bool) IsSingleLineTextControl() const override;
+ NS_IMETHOD_(bool) IsTextArea() const override;
+ NS_IMETHOD_(bool) IsPlainTextControl() const override;
+ NS_IMETHOD_(bool) IsPasswordTextControl() const override;
+ NS_IMETHOD_(int32_t) GetCols() override;
+ NS_IMETHOD_(int32_t) GetWrapCols() override;
+ NS_IMETHOD_(int32_t) GetRows() override;
+ NS_IMETHOD_(void) GetDefaultValueFromContent(nsAString& aValue) override;
+ NS_IMETHOD_(bool) ValueChanged() const override;
+ NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, bool aIgnoreWrap) const override;
+ NS_IMETHOD_(nsIEditor*) GetTextEditor() override;
+ NS_IMETHOD_(nsISelectionController*) GetSelectionController() override;
+ NS_IMETHOD_(nsFrameSelection*) GetConstFrameSelection() override;
+ NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override;
+ NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override;
+ NS_IMETHOD CreateEditor() override;
+ NS_IMETHOD_(nsIContent*) GetRootEditorNode() override;
+ NS_IMETHOD_(Element*) CreatePlaceholderNode() override;
+ NS_IMETHOD_(Element*) GetPlaceholderNode() override;
+ NS_IMETHOD_(void) UpdatePlaceholderVisibility(bool aNotify) override;
+ NS_IMETHOD_(bool) GetPlaceholderVisibility() override;
+ NS_IMETHOD_(void) InitializeKeyboardEventListeners() override;
+ NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) override;
+ NS_IMETHOD_(bool) HasCachedSelection() override;
+
+ // nsIContent
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+ virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+
+ virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) override;
+ virtual nsresult PostHandleEvent(
+ EventChainPostVisitor& aVisitor) override;
+
+ virtual bool IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable, int32_t *aTabIndex) override;
+
+ virtual void DoneAddingChildren(bool aHaveNotified) override;
+ virtual bool IsDoneAddingChildren() override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ nsresult CopyInnerTo(Element* aDest);
+
+ /**
+ * Called when an attribute is about to be changed
+ */
+ virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify) override;
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLTextAreaElement,
+ nsGenericHTMLFormElementWithState)
+
+ // nsIConstraintValidation
+ bool IsTooLong();
+ bool IsTooShort();
+ bool IsValueMissing() const;
+ void UpdateTooLongValidityState();
+ void UpdateTooShortValidityState();
+ void UpdateValueMissingValidityState();
+ void UpdateBarredFromConstraintValidation();
+ nsresult GetValidationMessage(nsAString& aValidationMessage,
+ ValidityStateType aType) override;
+
+ // Web IDL binding methods
+ bool Autofocus()
+ {
+ return GetBoolAttr(nsGkAtoms::autofocus);
+ }
+ void SetAutofocus(bool aAutoFocus, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::autofocus, aAutoFocus, aError);
+ }
+ uint32_t Cols()
+ {
+ return GetIntAttr(nsGkAtoms::cols, DEFAULT_COLS);
+ }
+ void SetCols(uint32_t aCols, ErrorResult& aError)
+ {
+ uint32_t cols = aCols ? aCols : DEFAULT_COLS;
+ SetUnsignedIntAttr(nsGkAtoms::cols, cols, DEFAULT_COLS, aError);
+ }
+ bool Disabled()
+ {
+ return GetBoolAttr(nsGkAtoms::disabled);
+ }
+ void SetDisabled(bool aDisabled, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::disabled, aDisabled, aError);
+ }
+ // nsGenericHTMLFormElementWithState::GetForm is fine
+ using nsGenericHTMLFormElementWithState::GetForm;
+ int32_t MaxLength()
+ {
+ return GetIntAttr(nsGkAtoms::maxlength, -1);
+ }
+ void SetMaxLength(int32_t aMaxLength, ErrorResult& aError)
+ {
+ int32_t minLength = MinLength();
+ if (aMaxLength < 0 || (minLength >= 0 && aMaxLength < minLength)) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ } else {
+ SetHTMLIntAttr(nsGkAtoms::maxlength, aMaxLength, aError);
+ }
+ }
+ int32_t MinLength()
+ {
+ return GetIntAttr(nsGkAtoms::minlength, -1);
+ }
+ void SetMinLength(int32_t aMinLength, ErrorResult& aError)
+ {
+ int32_t maxLength = MaxLength();
+ if (aMinLength < 0 || (maxLength >= 0 && aMinLength > maxLength)) {
+ aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ } else {
+ SetHTMLIntAttr(nsGkAtoms::minlength, aMinLength, aError);
+ }
+ }
+ // XPCOM GetName is fine
+ void SetName(const nsAString& aName, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::name, aName, aError);
+ }
+ // XPCOM GetPlaceholder is fine
+ void SetPlaceholder(const nsAString& aPlaceholder, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::placeholder, aPlaceholder, aError);
+ }
+ bool ReadOnly()
+ {
+ return GetBoolAttr(nsGkAtoms::readonly);
+ }
+ void SetReadOnly(bool aReadOnly, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::readonly, aReadOnly, aError);
+ }
+ bool Required()
+ {
+ return GetBoolAttr(nsGkAtoms::required);
+ }
+
+ void SetRangeText(const nsAString& aReplacement, ErrorResult& aRv);
+
+ void SetRangeText(const nsAString& aReplacement, uint32_t aStart,
+ uint32_t aEnd, const SelectionMode& aSelectMode,
+ ErrorResult& aRv, int32_t aSelectionStart = -1,
+ int32_t aSelectionEnd = -1);
+
+ void SetRequired(bool aRequired, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::required, aRequired, aError);
+ }
+ uint32_t Rows()
+ {
+ return GetIntAttr(nsGkAtoms::rows, DEFAULT_ROWS_TEXTAREA);
+ }
+ void SetRows(uint32_t aRows, ErrorResult& aError)
+ {
+ uint32_t rows = aRows ? aRows : DEFAULT_ROWS_TEXTAREA;
+ SetUnsignedIntAttr(nsGkAtoms::rows, rows, DEFAULT_ROWS_TEXTAREA, aError);
+ }
+ // XPCOM GetWrap is fine
+ void SetWrap(const nsAString& aWrap, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::wrap, aWrap, aError);
+ }
+ // XPCOM GetType is fine
+ // XPCOM GetDefaultValue is fine
+ void SetDefaultValue(const nsAString& aDefaultValue, ErrorResult& aError);
+ // XPCOM GetValue/SetValue are fine
+ uint32_t GetTextLength();
+ // nsIConstraintValidation::WillValidate is fine.
+ // nsIConstraintValidation::Validity() is fine.
+ // nsIConstraintValidation::GetValidationMessage() is fine.
+ // nsIConstraintValidation::CheckValidity() is fine.
+ using nsIConstraintValidation::CheckValidity;
+ using nsIConstraintValidation::ReportValidity;
+ // nsIConstraintValidation::SetCustomValidity() is fine.
+ // XPCOM Select is fine
+ Nullable<uint32_t> GetSelectionStart(ErrorResult& aError);
+ void SetSelectionStart(const Nullable<uint32_t>& aSelectionStart, ErrorResult& aError);
+ Nullable<uint32_t> GetSelectionEnd(ErrorResult& aError);
+ void SetSelectionEnd(const Nullable<uint32_t>& aSelectionEnd, ErrorResult& aError);
+ void GetSelectionDirection(nsAString& aDirection, ErrorResult& aError);
+ void SetSelectionDirection(const nsAString& aDirection, ErrorResult& aError);
+ void SetSelectionRange(uint32_t aSelectionStart, uint32_t aSelectionEnd, const Optional<nsAString>& aDirecton, ErrorResult& aError);
+ nsIControllers* GetControllers(ErrorResult& aError);
+ nsIEditor* GetEditor()
+ {
+ return mState.GetEditor();
+ }
+
+protected:
+ virtual ~HTMLTextAreaElement() {}
+
+ // get rid of the compiler warning
+ using nsGenericHTMLFormElementWithState::IsSingleLineTextControl;
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ nsCOMPtr<nsIControllers> mControllers;
+ /** Whether or not the value has changed since its default value was given. */
+ bool mValueChanged;
+ /** Whether or not the last change to the value was made interactively by the user. */
+ bool mLastValueChangeWasInteractive;
+ /** Whether or not we are already handling select event. */
+ bool mHandlingSelect;
+ /** Whether or not we are done adding children (always true if not
+ created by a parser */
+ bool mDoneAddingChildren;
+ /** Whether state restoration should be inhibited in DoneAddingChildren. */
+ bool mInhibitStateRestoration;
+ /** Whether our disabled state has changed from the default **/
+ bool mDisabledChanged;
+ /** Whether we should make :-moz-ui-invalid apply on the element. **/
+ bool mCanShowInvalidUI;
+ /** Whether we should make :-moz-ui-valid apply on the element. **/
+ bool mCanShowValidUI;
+
+ void FireChangeEventIfNeeded();
+
+ nsString mFocusedValue;
+
+ /** The state of the text editor (selection controller and the editor) **/
+ nsTextEditorState mState;
+
+ NS_IMETHOD SelectAll(nsPresContext* aPresContext);
+ /**
+ * Get the value, whether it is from the content or the frame.
+ * @param aValue the value [out]
+ * @param aIgnoreWrap whether to ignore the wrap attribute when getting the
+ * value. If this is true, linebreaks will not be inserted even if
+ * wrap=hard.
+ */
+ void GetValueInternal(nsAString& aValue, bool aIgnoreWrap) const;
+
+ /**
+ * Setting the value.
+ *
+ * @param aValue String to set.
+ * @param aFlags See nsTextEditorState::SetValueFlags.
+ */
+ nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags);
+
+ nsresult GetSelectionRange(int32_t* aSelectionStart, int32_t* aSelectionEnd);
+
+ /**
+ * Common method to call from the various mutation observer methods.
+ * aContent is a content node that's either the one that changed or its
+ * parent; we should only respond to the change if aContent is non-anonymous.
+ */
+ void ContentChanged(nsIContent* aContent);
+
+ virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom *aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+
+ /**
+ * Return if an element should have a specific validity UI
+ * (with :-moz-ui-invalid and :-moz-ui-valid pseudo-classes).
+ *
+ * @return Whether the element should have a validity UI.
+ */
+ bool ShouldShowValidityUI() const {
+ /**
+ * Always show the validity UI if the form has already tried to be submitted
+ * but was invalid.
+ *
+ * Otherwise, show the validity UI if the element's value has been changed.
+ */
+
+ if (mForm && mForm->HasEverTriedInvalidSubmit()) {
+ return true;
+ }
+
+ return mValueChanged;
+ }
+
+ /**
+ * Get the mutable state of the element.
+ */
+ bool IsMutable() const;
+
+ /**
+ * Returns whether the current value is the empty string.
+ *
+ * @return whether the current value is the empty string.
+ */
+ bool IsValueEmpty() const;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
+
diff --git a/dom/html/HTMLTimeElement.cpp b/dom/html/HTMLTimeElement.cpp
new file mode 100644
index 000000000..cf803eced
--- /dev/null
+++ b/dom/html/HTMLTimeElement.cpp
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HTMLTimeElement.h"
+#include "mozilla/dom/HTMLTimeElementBinding.h"
+#include "nsGenericHTMLElement.h"
+#include "nsVariant.h"
+#include "nsGkAtoms.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Time)
+
+namespace mozilla {
+namespace dom {
+
+HTMLTimeElement::HTMLTimeElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+{
+}
+
+HTMLTimeElement::~HTMLTimeElement()
+{
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLTimeElement)
+
+JSObject*
+HTMLTimeElement::WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLTimeElementBinding::Wrap(cx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLTimeElement.h b/dom/html/HTMLTimeElement.h
new file mode 100644
index 000000000..2755e2c92
--- /dev/null
+++ b/dom/html/HTMLTimeElement.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_HTMLTimeElement_h
+#define mozilla_dom_HTMLTimeElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLTimeElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLTimeElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+ virtual ~HTMLTimeElement();
+
+ // HTMLTimeElement WebIDL
+ void GetDateTime(DOMString& aDateTime)
+ {
+ GetHTMLAttr(nsGkAtoms::datetime, aDateTime);
+ }
+
+ void SetDateTime(const nsAString& aDateTime, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::datetime, aDateTime, aError);
+ }
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+
+protected:
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLTimeElement_h
diff --git a/dom/html/HTMLTitleElement.cpp b/dom/html/HTMLTitleElement.cpp
new file mode 100644
index 000000000..b005d104a
--- /dev/null
+++ b/dom/html/HTMLTitleElement.cpp
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/HTMLTitleElement.h"
+
+#include "mozilla/dom/HTMLTitleElementBinding.h"
+#include "mozilla/ErrorResult.h"
+#include "nsStyleConsts.h"
+#include "nsIDocument.h"
+#include "nsContentUtils.h"
+
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Title)
+
+namespace mozilla {
+namespace dom {
+
+HTMLTitleElement::HTMLTitleElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+{
+ AddMutationObserver(this);
+}
+
+HTMLTitleElement::~HTMLTitleElement()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLTitleElement, nsGenericHTMLElement,
+ nsIMutationObserver)
+
+NS_IMPL_ELEMENT_CLONE(HTMLTitleElement)
+
+JSObject*
+HTMLTitleElement::WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLTitleElementBinding::Wrap(cx, this, aGivenProto);
+}
+
+void
+HTMLTitleElement::GetText(DOMString& aText, ErrorResult& aError)
+{
+ if (!nsContentUtils::GetNodeTextContent(this, false, aText, fallible)) {
+ aError = NS_ERROR_OUT_OF_MEMORY;
+ }
+}
+
+void
+HTMLTitleElement::SetText(const nsAString& aText, ErrorResult& aError)
+{
+ aError = nsContentUtils::SetNodeTextContent(this, aText, true);
+}
+
+void
+HTMLTitleElement::CharacterDataChanged(nsIDocument *aDocument,
+ nsIContent *aContent,
+ CharacterDataChangeInfo *aInfo)
+{
+ SendTitleChangeEvent(false);
+}
+
+void
+HTMLTitleElement::ContentAppended(nsIDocument *aDocument,
+ nsIContent *aContainer,
+ nsIContent *aFirstNewContent,
+ int32_t aNewIndexInContainer)
+{
+ SendTitleChangeEvent(false);
+}
+
+void
+HTMLTitleElement::ContentInserted(nsIDocument *aDocument,
+ nsIContent *aContainer,
+ nsIContent *aChild,
+ int32_t aIndexInContainer)
+{
+ SendTitleChangeEvent(false);
+}
+
+void
+HTMLTitleElement::ContentRemoved(nsIDocument *aDocument,
+ nsIContent *aContainer,
+ nsIContent *aChild,
+ int32_t aIndexInContainer,
+ nsIContent *aPreviousSibling)
+{
+ SendTitleChangeEvent(false);
+}
+
+nsresult
+HTMLTitleElement::BindToTree(nsIDocument *aDocument,
+ nsIContent *aParent,
+ nsIContent *aBindingParent,
+ bool aCompileEventHandlers)
+{
+ // Let this fall through.
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SendTitleChangeEvent(true);
+
+ return NS_OK;
+}
+
+void
+HTMLTitleElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ SendTitleChangeEvent(false);
+
+ // Let this fall through.
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+void
+HTMLTitleElement::DoneAddingChildren(bool aHaveNotified)
+{
+ if (!aHaveNotified) {
+ SendTitleChangeEvent(false);
+ }
+}
+
+void
+HTMLTitleElement::SendTitleChangeEvent(bool aBound)
+{
+ nsIDocument* doc = GetUncomposedDoc();
+ if (doc) {
+ doc->NotifyPossibleTitleChange(aBound);
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLTitleElement.h b/dom/html/HTMLTitleElement.h
new file mode 100644
index 000000000..430ae0c1a
--- /dev/null
+++ b/dom/html/HTMLTitleElement.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_HTMLTITLEElement_h_
+#define mozilla_dom_HTMLTITLEElement_h_
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+#include "nsStubMutationObserver.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class HTMLTitleElement final : public nsGenericHTMLElement,
+ public nsStubMutationObserver
+{
+public:
+ using Element::GetText;
+ using Element::SetText;
+
+ explicit HTMLTitleElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ //HTMLTitleElement
+ void GetText(DOMString& aText, ErrorResult& aError);
+ void SetText(const nsAString& aText, ErrorResult& aError);
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ virtual nsresult BindToTree(nsIDocument *aDocument, nsIContent *aParent,
+ nsIContent *aBindingParent,
+ bool aCompileEventHandlers) override;
+
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+
+ virtual void DoneAddingChildren(bool aHaveNotified) override;
+
+protected:
+ virtual ~HTMLTitleElement();
+
+ virtual JSObject* WrapNode(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
+ override final;
+
+private:
+ void SendTitleChangeEvent(bool aBound);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLTitleElement_h_
diff --git a/dom/html/HTMLTrackElement.cpp b/dom/html/HTMLTrackElement.cpp
new file mode 100644
index 000000000..758018f8d
--- /dev/null
+++ b/dom/html/HTMLTrackElement.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 "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/HTMLTrackElement.h"
+#include "mozilla/dom/HTMLTrackElementBinding.h"
+#include "mozilla/dom/HTMLUnknownElement.h"
+#include "nsIContentPolicy.h"
+#include "mozilla/LoadInfo.h"
+#include "WebVTTListener.h"
+#include "nsAttrValueInlines.h"
+#include "nsCOMPtr.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsICachingChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIContentPolicy.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIDocument.h"
+#include "nsIDOMEventTarget.h"
+#include "nsIDOMHTMLMediaElement.h"
+#include "nsIHttpChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsILoadGroup.h"
+#include "nsIObserver.h"
+#include "nsIStreamListener.h"
+#include "nsISupportsImpl.h"
+#include "nsISupportsPrimitives.h"
+#include "nsMappedAttributes.h"
+#include "nsNetUtil.h"
+#include "nsRuleData.h"
+#include "nsStyleConsts.h"
+#include "nsThreadUtils.h"
+#include "nsVideoFrame.h"
+
+static mozilla::LazyLogModule gTrackElementLog("nsTrackElement");
+#define LOG(type, msg) MOZ_LOG(gTrackElementLog, type, msg)
+
+// Replace the usual NS_IMPL_NS_NEW_HTML_ELEMENT(Track) so
+// we can return an UnknownElement instead when pref'd off.
+nsGenericHTMLElement*
+NS_NewHTMLTrackElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ mozilla::dom::FromParser aFromParser)
+{
+ return new mozilla::dom::HTMLTrackElement(aNodeInfo);
+}
+
+namespace mozilla {
+namespace dom {
+
+// Map html attribute string values to TextTrackKind enums.
+static constexpr nsAttrValue::EnumTable kKindTable[] = {
+ { "subtitles", static_cast<int16_t>(TextTrackKind::Subtitles) },
+ { "captions", static_cast<int16_t>(TextTrackKind::Captions) },
+ { "descriptions", static_cast<int16_t>(TextTrackKind::Descriptions) },
+ { "chapters", static_cast<int16_t>(TextTrackKind::Chapters) },
+ { "metadata", static_cast<int16_t>(TextTrackKind::Metadata) },
+ { nullptr, 0 }
+};
+
+// Invalid values are treated as "metadata" in ParseAttribute, but if no value
+// at all is specified, it's treated as "subtitles" in GetKind
+static constexpr const nsAttrValue::EnumTable* kKindTableInvalidValueDefault = &kKindTable[4];
+
+class WindowDestroyObserver final : public nsIObserver
+{
+ NS_DECL_ISUPPORTS
+
+public:
+ explicit WindowDestroyObserver(HTMLTrackElement* aElement, uint64_t aWinID)
+ : mTrackElement(aElement)
+ , mInnerID(aWinID)
+ {
+ RegisterWindowDestroyObserver();
+ }
+ void RegisterWindowDestroyObserver()
+ {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(this, "inner-window-destroyed", false);
+ }
+ }
+ void UnRegisterWindowDestroyObserver()
+ {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "inner-window-destroyed");
+ }
+ mTrackElement = nullptr;
+ }
+ NS_IMETHODIMP Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (strcmp(aTopic, "inner-window-destroyed") == 0) {
+ nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+ NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+ uint64_t innerID;
+ nsresult rv = wrapper->GetData(&innerID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (innerID == mInnerID) {
+ if (mTrackElement) {
+ mTrackElement->NotifyShutdown();
+ }
+ UnRegisterWindowDestroyObserver();
+ }
+ }
+ return NS_OK;
+ }
+
+private:
+ ~WindowDestroyObserver() {};
+ HTMLTrackElement* mTrackElement;
+ uint64_t mInnerID;
+};
+NS_IMPL_ISUPPORTS(WindowDestroyObserver, nsIObserver);
+
+/** HTMLTrackElement */
+HTMLTrackElement::HTMLTrackElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ , mLoadResourceDispatched(false)
+ , mWindowDestroyObserver(nullptr)
+{
+ nsISupports* parentObject = OwnerDoc()->GetParentObject();
+ NS_ENSURE_TRUE_VOID(parentObject);
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject);
+ if (window) {
+ mWindowDestroyObserver = new WindowDestroyObserver(this, window->WindowID());
+ }
+}
+
+HTMLTrackElement::~HTMLTrackElement()
+{
+ if (mWindowDestroyObserver) {
+ mWindowDestroyObserver->UnRegisterWindowDestroyObserver();
+ }
+ NotifyShutdown();
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLTrackElement)
+
+NS_IMPL_ADDREF_INHERITED(HTMLTrackElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLTrackElement, Element)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLTrackElement, nsGenericHTMLElement,
+ mTrack, mMediaParent, mListener)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLTrackElement)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
+
+void
+HTMLTrackElement::GetKind(DOMString& aKind) const
+{
+ GetEnumAttr(nsGkAtoms::kind, kKindTable[0].tag, aKind);
+}
+
+void
+HTMLTrackElement::OnChannelRedirect(nsIChannel* aChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aFlags)
+{
+ NS_ASSERTION(aChannel == mChannel, "Channels should match!");
+ mChannel = aNewChannel;
+}
+
+JSObject*
+HTMLTrackElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLTrackElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+TextTrack*
+HTMLTrackElement::GetTrack()
+{
+ if (!mTrack) {
+ CreateTextTrack();
+ }
+
+ return mTrack;
+}
+
+void
+HTMLTrackElement::CreateTextTrack()
+{
+ nsString label, srcLang;
+ GetSrclang(srcLang);
+ GetLabel(label);
+
+ TextTrackKind kind;
+ if (const nsAttrValue* value = GetParsedAttr(nsGkAtoms::kind)) {
+ kind = static_cast<TextTrackKind>(value->GetEnumValue());
+ } else {
+ kind = TextTrackKind::Subtitles;
+ }
+
+ nsISupports* parentObject =
+ OwnerDoc()->GetParentObject();
+
+ NS_ENSURE_TRUE_VOID(parentObject);
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject);
+ mTrack = new TextTrack(window, kind, label, srcLang,
+ TextTrackMode::Disabled,
+ TextTrackReadyState::NotLoaded,
+ TextTrackSource::Track);
+ mTrack->SetTrackElement(this);
+
+ if (mMediaParent) {
+ mMediaParent->AddTextTrack(mTrack);
+ }
+}
+
+bool
+HTMLTrackElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::kind) {
+ // Case-insensitive lookup, with the first element as the default.
+ return aResult.ParseEnumValue(aValue, kKindTable, false,
+ kKindTableInvalidValueDefault);
+ }
+
+ // Otherwise call the generic implementation.
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID,
+ aAttribute,
+ aValue,
+ aResult);
+}
+
+void
+HTMLTrackElement::SetSrc(const nsAString& aSrc, ErrorResult& aError)
+{
+ SetHTMLAttr(nsGkAtoms::src, aSrc, aError);
+ uint16_t oldReadyState = ReadyState();
+ SetReadyState(TextTrackReadyState::NotLoaded);
+ if (!mMediaParent) {
+ return;
+ }
+ if (mTrack && (oldReadyState != TextTrackReadyState::NotLoaded)) {
+ // Remove all the cues in MediaElement.
+ mMediaParent->RemoveTextTrack(mTrack);
+ // Recreate mTrack.
+ CreateTextTrack();
+ }
+ // Stop WebVTTListener.
+ mListener = nullptr;
+ if (mChannel) {
+ mChannel->Cancel(NS_BINDING_ABORTED);
+ mChannel = nullptr;
+ }
+
+ DispatchLoadResource();
+}
+
+void
+HTMLTrackElement::DispatchLoadResource()
+{
+ if (!mLoadResourceDispatched) {
+ RefPtr<Runnable> r = NewRunnableMethod(this, &HTMLTrackElement::LoadResource);
+ nsContentUtils::RunInStableState(r.forget());
+ mLoadResourceDispatched = true;
+ }
+}
+
+void
+HTMLTrackElement::LoadResource()
+{
+ mLoadResourceDispatched = false;
+
+ // Find our 'src' url
+ nsAutoString src;
+ if (!GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NewURIFromString(src, getter_AddRefs(uri));
+ NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
+ LOG(LogLevel::Info, ("%p Trying to load from src=%s", this,
+ NS_ConvertUTF16toUTF8(src).get()));
+
+ if (mChannel) {
+ mChannel->Cancel(NS_BINDING_ABORTED);
+ mChannel = nullptr;
+ }
+
+ // According to https://www.w3.org/TR/html5/embedded-content-0.html#sourcing-out-of-band-text-tracks
+ //
+ // "8: If the track element's parent is a media element then let CORS mode
+ // be the state of the parent media element's crossorigin content attribute.
+ // Otherwise, let CORS mode be No CORS."
+ //
+ CORSMode corsMode = mMediaParent ? mMediaParent->GetCORSMode() : CORS_NONE;
+
+ // Determine the security flag based on corsMode.
+ nsSecurityFlags secFlags;
+ if (CORS_NONE == corsMode) {
+ // Same-origin is required for track element.
+ secFlags = nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS;
+ } else {
+ secFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
+ if (CORS_ANONYMOUS == corsMode) {
+ secFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
+ } else if (CORS_USE_CREDENTIALS == corsMode) {
+ secFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
+ } else {
+ NS_WARNING("Unknown CORS mode.");
+ secFlags = nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS;
+ }
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsILoadGroup> loadGroup = OwnerDoc()->GetDocumentLoadGroup();
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ static_cast<Element*>(this),
+ secFlags,
+ nsIContentPolicy::TYPE_INTERNAL_TRACK,
+ loadGroup,
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI);
+
+ NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
+
+ mListener = new WebVTTListener(this);
+ rv = mListener->LoadResource();
+ NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
+ channel->SetNotificationCallbacks(mListener);
+
+ LOG(LogLevel::Debug, ("opening webvtt channel"));
+ rv = channel->AsyncOpen2(mListener);
+
+ if (NS_FAILED(rv)) {
+ SetReadyState(TextTrackReadyState::FailedToLoad);
+ return;
+ }
+
+ mChannel = channel;
+}
+
+nsresult
+HTMLTrackElement::BindToTree(nsIDocument* aDocument,
+ nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument,
+ aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(LogLevel::Debug, ("Track Element bound to tree."));
+ if (!aParent || !aParent->IsNodeOfType(nsINode::eMEDIA)) {
+ return NS_OK;
+ }
+
+ // Store our parent so we can look up its frame for display.
+ if (!mMediaParent) {
+ mMediaParent = static_cast<HTMLMediaElement*>(aParent);
+
+ // TODO: separate notification for 'alternate' tracks?
+ mMediaParent->NotifyAddedSource();
+ LOG(LogLevel::Debug, ("Track element sent notification to parent."));
+
+ // We may already have a TextTrack at this point if GetTrack() has already
+ // been called. This happens, for instance, if script tries to get the
+ // TextTrack before its mTrackElement has been bound to the DOM tree.
+ if (!mTrack) {
+ CreateTextTrack();
+ }
+ DispatchLoadResource();
+ }
+
+ return NS_OK;
+}
+
+void
+HTMLTrackElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ if (mMediaParent && aNullParent) {
+ // mTrack can be null if HTMLTrackElement::LoadResource has never been
+ // called.
+ if (mTrack) {
+ mMediaParent->RemoveTextTrack(mTrack);
+ }
+ mMediaParent = nullptr;
+ }
+
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+uint16_t
+HTMLTrackElement::ReadyState() const
+{
+ if (!mTrack) {
+ return TextTrackReadyState::NotLoaded;
+ }
+
+ return mTrack->ReadyState();
+}
+
+void
+HTMLTrackElement::SetReadyState(uint16_t aReadyState)
+{
+ if (ReadyState() == aReadyState) {
+ return;
+ }
+
+ if (mTrack) {
+ switch (aReadyState) {
+ case TextTrackReadyState::Loaded:
+ DispatchTrackRunnable(NS_LITERAL_STRING("load"));
+ break;
+ case TextTrackReadyState::FailedToLoad:
+ DispatchTrackRunnable(NS_LITERAL_STRING("error"));
+ break;
+ }
+ mTrack->SetReadyState(aReadyState);
+ }
+}
+
+void
+HTMLTrackElement::DispatchTrackRunnable(const nsString& aEventName)
+{
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod
+ <const nsString>(this,
+ &HTMLTrackElement::DispatchTrustedEvent,
+ aEventName);
+ NS_DispatchToMainThread(runnable);
+}
+
+void
+HTMLTrackElement::DispatchTrustedEvent(const nsAString& aName)
+{
+ nsIDocument* doc = OwnerDoc();
+ if (!doc) {
+ return;
+ }
+ nsContentUtils::DispatchTrustedEvent(doc, static_cast<nsIContent*>(this),
+ aName, false, false);
+}
+
+void
+HTMLTrackElement::DropChannel()
+{
+ mChannel = nullptr;
+}
+
+void
+HTMLTrackElement::NotifyShutdown()
+{
+ if (mChannel) {
+ mChannel->Cancel(NS_BINDING_ABORTED);
+ }
+ mChannel = nullptr;
+ mListener = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLTrackElement.h b/dom/html/HTMLTrackElement.h
new file mode 100644
index 000000000..566f1e0d3
--- /dev/null
+++ b/dom/html/HTMLTrackElement.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 mozilla_dom_HTMLTrackElement_h
+#define mozilla_dom_HTMLTrackElement_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/TextTrack.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIDOMEventTarget.h"
+#include "nsIHttpChannel.h"
+
+class nsIContent;
+class nsIDocument;
+
+namespace mozilla {
+namespace dom {
+
+class WebVTTListener;
+class WindowDestroyObserver;
+
+class HTMLTrackElement final : public nsGenericHTMLElement
+{
+public:
+ explicit HTMLTrackElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLTrackElement,
+ nsGenericHTMLElement)
+
+ // HTMLTrackElement WebIDL
+ void GetKind(DOMString& aKind) const;
+ void SetKind(const nsAString& aKind, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::kind, aKind, aError);
+ }
+
+ void GetSrc(DOMString& aSrc) const
+ {
+ GetHTMLURIAttr(nsGkAtoms::src, aSrc);
+ }
+
+ void SetSrc(const nsAString& aSrc, ErrorResult& aError);
+
+ void GetSrclang(DOMString& aSrclang) const
+ {
+ GetHTMLAttr(nsGkAtoms::srclang, aSrclang);
+ }
+ void GetSrclang(nsAString& aSrclang) const
+ {
+ GetHTMLAttr(nsGkAtoms::srclang, aSrclang);
+ }
+ void SetSrclang(const nsAString& aSrclang, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::srclang, aSrclang, aError);
+ }
+
+ void GetLabel(DOMString& aLabel) const
+ {
+ GetHTMLAttr(nsGkAtoms::label, aLabel);
+ }
+ void GetLabel(nsAString& aLabel) const
+ {
+ GetHTMLAttr(nsGkAtoms::label, aLabel);
+ }
+ void SetLabel(const nsAString& aLabel, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::label, aLabel, aError);
+ }
+
+ bool Default() const
+ {
+ return GetBoolAttr(nsGkAtoms::_default);
+ }
+ void SetDefault(bool aDefault, ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::_default, aDefault, aError);
+ }
+
+ uint16_t ReadyState() const;
+ void SetReadyState(uint16_t aReadyState);
+
+ TextTrack* GetTrack();
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+
+ // Override ParseAttribute() to convert kind strings to enum values.
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+
+ // Override BindToTree() so that we can trigger a load when we become
+ // the child of a media element.
+ virtual nsresult BindToTree(nsIDocument* aDocument,
+ nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep, bool aNullParent) override;
+
+ void DispatchTrackRunnable(const nsString& aEventName);
+ void DispatchTrustedEvent(const nsAString& aName);
+
+ void DropChannel();
+
+ void NotifyShutdown();
+
+protected:
+ virtual ~HTMLTrackElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+ void OnChannelRedirect(nsIChannel* aChannel, nsIChannel* aNewChannel,
+ uint32_t aFlags);
+ // Open a new channel to the HTMLTrackElement's src attribute and call
+ // mListener's LoadResource().
+ void LoadResource();
+
+ friend class TextTrackCue;
+ friend class WebVTTListener;
+
+ RefPtr<TextTrack> mTrack;
+ nsCOMPtr<nsIChannel> mChannel;
+ RefPtr<HTMLMediaElement> mMediaParent;
+ RefPtr<WebVTTListener> mListener;
+
+ void CreateTextTrack();
+
+private:
+ void DispatchLoadResource();
+ bool mLoadResourceDispatched;
+
+ RefPtr<WindowDestroyObserver> mWindowDestroyObserver;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLTrackElement_h
diff --git a/dom/html/HTMLUnknownElement.cpp b/dom/html/HTMLUnknownElement.cpp
new file mode 100644
index 000000000..1e01d2ce2
--- /dev/null
+++ b/dom/html/HTMLUnknownElement.cpp
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDocument.h"
+#include "mozilla/dom/HTMLUnknownElement.h"
+#include "mozilla/dom/HTMLElementBinding.h"
+#include "jsapi.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Unknown)
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLUnknownElement, nsGenericHTMLElement,
+ HTMLUnknownElement)
+
+JSObject*
+HTMLUnknownElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLUnknownElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLUnknownElement)
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLUnknownElement.h b/dom/html/HTMLUnknownElement.h
new file mode 100644
index 000000000..c77fba919
--- /dev/null
+++ b/dom/html/HTMLUnknownElement.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 mozilla_dom_HTMLUnknownElement_h
+#define mozilla_dom_HTMLUnknownElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+namespace dom {
+
+#define NS_HTMLUNKNOWNELEMENT_IID \
+{ 0xc09e665b, 0x3876, 0x40dd, \
+ { 0x85, 0x28, 0x44, 0xc2, 0x3f, 0xd4, 0x58, 0xf2 } }
+
+class HTMLUnknownElement final : public nsGenericHTMLElement
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTMLUNKNOWNELEMENT_IID)
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ explicit HTMLUnknownElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ {
+ if (NodeInfo()->Equals(nsGkAtoms::bdi)) {
+ SetHasDirAuto();
+ }
+ }
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+protected:
+ virtual ~HTMLUnknownElement() {}
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HTMLUnknownElement, NS_HTMLUNKNOWNELEMENT_IID)
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_HTMLUnknownElement_h */
diff --git a/dom/html/HTMLVideoElement.cpp b/dom/html/HTMLVideoElement.cpp
new file mode 100644
index 000000000..bddec24c9
--- /dev/null
+++ b/dom/html/HTMLVideoElement.cpp
@@ -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/. */
+
+#include "nsIDOMHTMLSourceElement.h"
+#include "mozilla/dom/HTMLVideoElement.h"
+#include "mozilla/dom/HTMLVideoElementBinding.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+#include "nsSize.h"
+#include "nsError.h"
+#include "nsNodeInfoManager.h"
+#include "plbase64.h"
+#include "nsXPCOMStrings.h"
+#include "prlock.h"
+#include "nsThreadUtils.h"
+#include "ImageContainer.h"
+#include "VideoFrameContainer.h"
+
+#include "nsIScriptSecurityManager.h"
+#include "nsIXPConnect.h"
+
+#include "nsITimer.h"
+
+#include "MediaError.h"
+#include "MediaDecoder.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/WakeLock.h"
+#include "mozilla/dom/power/PowerManagerService.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/VideoPlaybackQuality.h"
+
+#include <algorithm>
+#include <limits>
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Video)
+
+namespace mozilla {
+namespace dom {
+
+static bool sVideoStatsEnabled;
+
+NS_IMPL_ELEMENT_CLONE(HTMLVideoElement)
+
+HTMLVideoElement::HTMLVideoElement(already_AddRefed<NodeInfo>& aNodeInfo)
+ : HTMLMediaElement(aNodeInfo)
+ , mUseScreenWakeLock(true)
+{
+}
+
+HTMLVideoElement::~HTMLVideoElement()
+{
+}
+
+nsresult HTMLVideoElement::GetVideoSize(nsIntSize* size)
+{
+ if (!mMediaInfo.HasVideo()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mDisableVideo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ switch (mMediaInfo.mVideo.mRotation) {
+ case VideoInfo::Rotation::kDegree_90:
+ case VideoInfo::Rotation::kDegree_270: {
+ size->width = mMediaInfo.mVideo.mDisplay.height;
+ size->height = mMediaInfo.mVideo.mDisplay.width;
+ break;
+ }
+ case VideoInfo::Rotation::kDegree_0:
+ case VideoInfo::Rotation::kDegree_180:
+ default: {
+ size->height = mMediaInfo.mVideo.mDisplay.height;
+ size->width = mMediaInfo.mVideo.mDisplay.width;
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+bool
+HTMLVideoElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+
+ return HTMLMediaElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLVideoElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ nsGenericHTMLElement::MapImageSizeAttributesInto(aAttributes, aData);
+ nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLVideoElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::width },
+ { &nsGkAtoms::height },
+ { nullptr }
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+nsMapRuleToAttributesFunc
+HTMLVideoElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+nsresult HTMLVideoElement::SetAcceptHeader(nsIHttpChannel* aChannel)
+{
+ nsAutoCString value(
+ "video/webm,"
+ "video/ogg,"
+ "video/*;q=0.9,"
+ "application/ogg;q=0.7,"
+ "audio/*;q=0.6,*/*;q=0.5");
+
+ return aChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
+ value,
+ false);
+}
+
+bool
+HTMLVideoElement::IsInteractiveHTMLContent(bool aIgnoreTabindex) const
+{
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::controls) ||
+ HTMLMediaElement::IsInteractiveHTMLContent(aIgnoreTabindex);
+}
+
+uint32_t HTMLVideoElement::MozParsedFrames() const
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
+ if (!sVideoStatsEnabled) {
+ return 0;
+ }
+ return mDecoder ? mDecoder->GetFrameStatistics().GetParsedFrames() : 0;
+}
+
+uint32_t HTMLVideoElement::MozDecodedFrames() const
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
+ if (!sVideoStatsEnabled) {
+ return 0;
+ }
+ return mDecoder ? mDecoder->GetFrameStatistics().GetDecodedFrames() : 0;
+}
+
+uint32_t HTMLVideoElement::MozPresentedFrames() const
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
+ if (!sVideoStatsEnabled) {
+ return 0;
+ }
+ return mDecoder ? mDecoder->GetFrameStatistics().GetPresentedFrames() : 0;
+}
+
+uint32_t HTMLVideoElement::MozPaintedFrames()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
+ if (!sVideoStatsEnabled) {
+ return 0;
+ }
+ layers::ImageContainer* container = GetImageContainer();
+ return container ? container->GetPaintCount() : 0;
+}
+
+double HTMLVideoElement::MozFrameDelay()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
+ VideoFrameContainer* container = GetVideoFrameContainer();
+ // Hide negative delays. Frame timing tweaks in the compositor (e.g.
+ // adding a bias value to prevent multiple dropped/duped frames when
+ // frame times are aligned with composition times) may produce apparent
+ // negative delay, but we shouldn't report that.
+ return container ? std::max(0.0, container->GetFrameDelay()) : 0.0;
+}
+
+bool HTMLVideoElement::MozHasAudio() const
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
+ return HasAudio();
+}
+
+bool HTMLVideoElement::MozUseScreenWakeLock() const
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
+ return mUseScreenWakeLock;
+}
+
+void HTMLVideoElement::SetMozUseScreenWakeLock(bool aValue)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
+ mUseScreenWakeLock = aValue;
+ UpdateScreenWakeLock();
+}
+
+JSObject*
+HTMLVideoElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLVideoElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+HTMLVideoElement::NotifyOwnerDocumentActivityChanged()
+{
+ HTMLMediaElement::NotifyOwnerDocumentActivityChanged();
+ UpdateScreenWakeLock();
+}
+
+FrameStatistics*
+HTMLVideoElement::GetFrameStatistics()
+{
+ return mDecoder ? &(mDecoder->GetFrameStatistics()) : nullptr;
+}
+
+already_AddRefed<VideoPlaybackQuality>
+HTMLVideoElement::GetVideoPlaybackQuality()
+{
+ DOMHighResTimeStamp creationTime = 0;
+ uint32_t totalFrames = 0;
+ uint32_t droppedFrames = 0;
+ uint32_t corruptedFrames = 0;
+
+ if (sVideoStatsEnabled) {
+ if (nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow()) {
+ Performance* perf = window->GetPerformance();
+ if (perf) {
+ creationTime = perf->Now();
+ }
+ }
+
+ if (mDecoder) {
+ FrameStatisticsData stats =
+ mDecoder->GetFrameStatistics().GetFrameStatisticsData();
+ if (sizeof(totalFrames) >= sizeof(stats.mParsedFrames)) {
+ totalFrames = stats.mPresentedFrames + stats.mDroppedFrames;
+ droppedFrames = stats.mDroppedFrames;
+ } else {
+ uint64_t total = stats.mPresentedFrames + stats.mDroppedFrames;
+ const auto maxNumber = std::numeric_limits<uint32_t>::max();
+ if (total <= maxNumber) {
+ totalFrames = uint32_t(total);
+ droppedFrames = uint32_t(stats.mDroppedFrames);
+ } else {
+ // Too big number(s) -> Resize everything to fit in 32 bits.
+ double ratio = double(maxNumber) / double(total);
+ totalFrames = maxNumber; // === total * ratio
+ droppedFrames = uint32_t(double(stats.mDroppedFrames) * ratio);
+ }
+ }
+ corruptedFrames = 0;
+ }
+ }
+
+ RefPtr<VideoPlaybackQuality> playbackQuality =
+ new VideoPlaybackQuality(this, creationTime, totalFrames, droppedFrames,
+ corruptedFrames);
+ return playbackQuality.forget();
+}
+
+void
+HTMLVideoElement::WakeLockCreate()
+{
+ HTMLMediaElement::WakeLockCreate();
+ UpdateScreenWakeLock();
+}
+
+void
+HTMLVideoElement::WakeLockRelease()
+{
+ UpdateScreenWakeLock();
+ HTMLMediaElement::WakeLockRelease();
+}
+
+void
+HTMLVideoElement::UpdateScreenWakeLock()
+{
+ bool hidden = OwnerDoc()->Hidden();
+
+ if (mScreenWakeLock && (mPaused || hidden || !mUseScreenWakeLock)) {
+ ErrorResult rv;
+ mScreenWakeLock->Unlock(rv);
+ rv.SuppressException();
+ mScreenWakeLock = nullptr;
+ return;
+ }
+
+ if (!mScreenWakeLock && !mPaused && !hidden &&
+ mUseScreenWakeLock && HasVideo()) {
+ RefPtr<power::PowerManagerService> pmService =
+ power::PowerManagerService::GetInstance();
+ NS_ENSURE_TRUE_VOID(pmService);
+
+ ErrorResult rv;
+ mScreenWakeLock = pmService->NewWakeLock(NS_LITERAL_STRING("screen"),
+ OwnerDoc()->GetInnerWindow(),
+ rv);
+ }
+}
+
+void
+HTMLVideoElement::Init()
+{
+ Preferences::AddBoolVarCache(&sVideoStatsEnabled, "media.video_stats.enabled");
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/HTMLVideoElement.h b/dom/html/HTMLVideoElement.h
new file mode 100644
index 000000000..72e629a94
--- /dev/null
+++ b/dom/html/HTMLVideoElement.h
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_HTMLVideoElement_h
+#define mozilla_dom_HTMLVideoElement_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class WakeLock;
+class VideoPlaybackQuality;
+
+class HTMLVideoElement final : public HTMLMediaElement
+{
+public:
+ typedef mozilla::dom::NodeInfo NodeInfo;
+
+ explicit HTMLVideoElement(already_AddRefed<NodeInfo>& aNodeInfo);
+
+ NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLVideoElement, video)
+
+ using HTMLMediaElement::GetPaused;
+
+ NS_IMETHOD_(bool) IsVideo() override {
+ return true;
+ }
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+
+ static void Init();
+
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+
+ virtual nsresult Clone(NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ // Set size with the current video frame's height and width.
+ // If there is no video frame, returns NS_ERROR_FAILURE.
+ nsresult GetVideoSize(nsIntSize* size);
+
+ virtual nsresult SetAcceptHeader(nsIHttpChannel* aChannel) override;
+
+ // Element
+ virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
+
+ // WebIDL
+
+ uint32_t Width() const
+ {
+ return GetIntAttr(nsGkAtoms::width, 0);
+ }
+
+ void SetWidth(uint32_t aValue, ErrorResult& aRv)
+ {
+ SetUnsignedIntAttr(nsGkAtoms::width, aValue, 0, aRv);
+ }
+
+ uint32_t Height() const
+ {
+ return GetIntAttr(nsGkAtoms::height, 0);
+ }
+
+ void SetHeight(uint32_t aValue, ErrorResult& aRv)
+ {
+ SetUnsignedIntAttr(nsGkAtoms::height, aValue, 0, aRv);
+ }
+
+ uint32_t VideoWidth() const
+ {
+ if (mMediaInfo.HasVideo()) {
+ if (mMediaInfo.mVideo.mRotation == VideoInfo::Rotation::kDegree_90 ||
+ mMediaInfo.mVideo.mRotation == VideoInfo::Rotation::kDegree_270) {
+ return mMediaInfo.mVideo.mDisplay.height;
+ }
+ return mMediaInfo.mVideo.mDisplay.width;
+ }
+ return 0;
+ }
+
+ uint32_t VideoHeight() const
+ {
+ if (mMediaInfo.HasVideo()) {
+ if (mMediaInfo.mVideo.mRotation == VideoInfo::Rotation::kDegree_90 ||
+ mMediaInfo.mVideo.mRotation == VideoInfo::Rotation::kDegree_270) {
+ return mMediaInfo.mVideo.mDisplay.width;
+ }
+ return mMediaInfo.mVideo.mDisplay.height;
+ }
+ return 0;
+ }
+
+ VideoInfo::Rotation RotationDegrees() const
+ {
+ return mMediaInfo.mVideo.mRotation;
+ }
+
+ void GetPoster(nsAString& aValue)
+ {
+ GetURIAttr(nsGkAtoms::poster, nullptr, aValue);
+ }
+ void SetPoster(const nsAString& aValue, ErrorResult& aRv)
+ {
+ SetHTMLAttr(nsGkAtoms::poster, aValue, aRv);
+ }
+
+ uint32_t MozParsedFrames() const;
+
+ uint32_t MozDecodedFrames() const;
+
+ uint32_t MozPresentedFrames() const;
+
+ uint32_t MozPaintedFrames();
+
+ double MozFrameDelay();
+
+ bool MozHasAudio() const;
+
+ bool MozUseScreenWakeLock() const;
+
+ void SetMozUseScreenWakeLock(bool aValue);
+
+ void NotifyOwnerDocumentActivityChanged() override;
+
+ // Gives access to the decoder's frame statistics, if present.
+ FrameStatistics* GetFrameStatistics();
+
+ already_AddRefed<VideoPlaybackQuality> GetVideoPlaybackQuality();
+
+protected:
+ virtual ~HTMLVideoElement();
+
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual void WakeLockCreate() override;
+ virtual void WakeLockRelease() override;
+ void UpdateScreenWakeLock();
+
+ bool mUseScreenWakeLock;
+ RefPtr<WakeLock> mScreenWakeLock;
+
+private:
+ static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLVideoElement_h
diff --git a/dom/html/ImageDocument.cpp b/dom/html/ImageDocument.cpp
new file mode 100644
index 000000000..200bb5d46
--- /dev/null
+++ b/dom/html/ImageDocument.cpp
@@ -0,0 +1,851 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ImageDocument.h"
+#include "mozilla/dom/ImageDocumentBinding.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "nsRect.h"
+#include "nsIImageLoadingContent.h"
+#include "nsGenericHTMLElement.h"
+#include "nsDocShell.h"
+#include "nsIDocumentInlines.h"
+#include "nsDOMTokenList.h"
+#include "nsIDOMHTMLImageElement.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsIDOMEventListener.h"
+#include "nsIFrame.h"
+#include "nsGkAtoms.h"
+#include "imgIRequest.h"
+#include "imgILoader.h"
+#include "imgIContainer.h"
+#include "imgINotificationObserver.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsStyleContext.h"
+#include "nsIChannel.h"
+#include "nsIContentPolicy.h"
+#include "nsContentPolicyUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsError.h"
+#include "nsURILoader.h"
+#include "nsIDocShell.h"
+#include "nsIContentViewer.h"
+#include "nsThreadUtils.h"
+#include "nsIScrollableFrame.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Preferences.h"
+#include <algorithm>
+
+#define AUTOMATIC_IMAGE_RESIZING_PREF "browser.enable_automatic_image_resizing"
+#define CLICK_IMAGE_RESIZING_PREF "browser.enable_click_image_resizing"
+//XXX A hack needed for Firefox's site specific zoom.
+#define SITE_SPECIFIC_ZOOM "browser.zoom.siteSpecific"
+
+namespace mozilla {
+namespace dom {
+
+class ImageListener : public MediaDocumentStreamListener
+{
+public:
+ NS_DECL_NSIREQUESTOBSERVER
+
+ explicit ImageListener(ImageDocument* aDocument);
+ virtual ~ImageListener();
+};
+
+ImageListener::ImageListener(ImageDocument* aDocument)
+ : MediaDocumentStreamListener(aDocument)
+{
+}
+
+ImageListener::~ImageListener()
+{
+}
+
+NS_IMETHODIMP
+ImageListener::OnStartRequest(nsIRequest* request, nsISupports *ctxt)
+{
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
+
+ ImageDocument *imgDoc = static_cast<ImageDocument*>(mDocument.get());
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (!channel) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow = imgDoc->GetWindow();
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_UNEXPECTED);
+
+ // Do a ShouldProcess check to see whether to keep loading the image.
+ nsCOMPtr<nsIURI> channelURI;
+ channel->GetURI(getter_AddRefs(channelURI));
+
+ nsAutoCString mimeType;
+ channel->GetContentType(mimeType);
+
+ nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
+ nsCOMPtr<nsIPrincipal> channelPrincipal;
+ if (secMan) {
+ secMan->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal));
+ }
+
+ int16_t decision = nsIContentPolicy::ACCEPT;
+ nsresult rv = NS_CheckContentProcessPolicy(nsIContentPolicy::TYPE_INTERNAL_IMAGE,
+ channelURI,
+ channelPrincipal,
+ domWindow->GetFrameElementInternal(),
+ mimeType,
+ nullptr,
+ &decision,
+ nsContentUtils::GetContentPolicy(),
+ secMan);
+
+ if (NS_FAILED(rv) || NS_CP_REJECTED(decision)) {
+ request->Cancel(NS_ERROR_CONTENT_BLOCKED);
+ return NS_OK;
+ }
+
+ if (!imgDoc->mObservingImageLoader) {
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(imgDoc->mImageContent);
+ NS_ENSURE_TRUE(imageLoader, NS_ERROR_UNEXPECTED);
+
+ imageLoader->AddObserver(imgDoc);
+ imgDoc->mObservingImageLoader = true;
+ imageLoader->LoadImageWithChannel(channel, getter_AddRefs(mNextStream));
+ }
+
+ return MediaDocumentStreamListener::OnStartRequest(request, ctxt);
+}
+
+NS_IMETHODIMP
+ImageListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aCtxt, nsresult aStatus)
+{
+ ImageDocument* imgDoc = static_cast<ImageDocument*>(mDocument.get());
+ nsContentUtils::DispatchChromeEvent(imgDoc, static_cast<nsIDocument*>(imgDoc),
+ NS_LITERAL_STRING("ImageContentLoaded"),
+ true, true);
+ return MediaDocumentStreamListener::OnStopRequest(aRequest, aCtxt, aStatus);
+}
+
+ImageDocument::ImageDocument()
+ : MediaDocument(),
+ mOriginalZoomLevel(1.0)
+{
+ // NOTE! nsDocument::operator new() zeroes out all members, so don't
+ // bother initializing members to 0.
+}
+
+ImageDocument::~ImageDocument()
+{
+}
+
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ImageDocument, MediaDocument,
+ mImageContent)
+
+NS_IMPL_ADDREF_INHERITED(ImageDocument, MediaDocument)
+NS_IMPL_RELEASE_INHERITED(ImageDocument, MediaDocument)
+
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(ImageDocument)
+ NS_INTERFACE_TABLE_INHERITED(ImageDocument, nsIImageDocument,
+ imgINotificationObserver, nsIDOMEventListener)
+NS_INTERFACE_TABLE_TAIL_INHERITING(MediaDocument)
+
+
+nsresult
+ImageDocument::Init()
+{
+ nsresult rv = MediaDocument::Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mResizeImageByDefault = Preferences::GetBool(AUTOMATIC_IMAGE_RESIZING_PREF);
+ mClickResizingEnabled = Preferences::GetBool(CLICK_IMAGE_RESIZING_PREF);
+ mShouldResize = mResizeImageByDefault;
+ mFirstResize = true;
+
+ return NS_OK;
+}
+
+JSObject*
+ImageDocument::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ImageDocumentBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsresult
+ImageDocument::StartDocumentLoad(const char* aCommand,
+ nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener** aDocListener,
+ bool aReset,
+ nsIContentSink* aSink)
+{
+ nsresult rv =
+ MediaDocument::StartDocumentLoad(aCommand, aChannel, aLoadGroup, aContainer,
+ aDocListener, aReset, aSink);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mOriginalZoomLevel =
+ Preferences::GetBool(SITE_SPECIFIC_ZOOM, false) ? 1.0 : GetZoomLevel();
+
+ NS_ASSERTION(aDocListener, "null aDocListener");
+ *aDocListener = new ImageListener(this);
+ NS_ADDREF(*aDocListener);
+
+ return NS_OK;
+}
+
+void
+ImageDocument::Destroy()
+{
+ if (mImageContent) {
+ // Remove our event listener from the image content.
+ nsCOMPtr<EventTarget> target = do_QueryInterface(mImageContent);
+ target->RemoveEventListener(NS_LITERAL_STRING("load"), this, false);
+ target->RemoveEventListener(NS_LITERAL_STRING("click"), this, false);
+
+ // Break reference cycle with mImageContent, if we have one
+ if (mObservingImageLoader) {
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mImageContent);
+ if (imageLoader) {
+ imageLoader->RemoveObserver(this);
+ }
+ }
+
+ mImageContent = nullptr;
+ }
+
+ MediaDocument::Destroy();
+}
+
+void
+ImageDocument::SetScriptGlobalObject(nsIScriptGlobalObject* aScriptGlobalObject)
+{
+ // If the script global object is changing, we need to unhook our event
+ // listeners on the window.
+ nsCOMPtr<EventTarget> target;
+ if (mScriptGlobalObject &&
+ aScriptGlobalObject != mScriptGlobalObject) {
+ target = do_QueryInterface(mScriptGlobalObject);
+ target->RemoveEventListener(NS_LITERAL_STRING("resize"), this, false);
+ target->RemoveEventListener(NS_LITERAL_STRING("keypress"), this,
+ false);
+ }
+
+ // Set the script global object on the superclass before doing
+ // anything that might require it....
+ MediaDocument::SetScriptGlobalObject(aScriptGlobalObject);
+
+ if (aScriptGlobalObject) {
+ if (!GetRootElement()) {
+ // Create synthetic document
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ CreateSyntheticDocument();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create synthetic document");
+
+ target = do_QueryInterface(mImageContent);
+ target->AddEventListener(NS_LITERAL_STRING("load"), this, false);
+ target->AddEventListener(NS_LITERAL_STRING("click"), this, false);
+ }
+
+ target = do_QueryInterface(aScriptGlobalObject);
+ target->AddEventListener(NS_LITERAL_STRING("resize"), this, false);
+ target->AddEventListener(NS_LITERAL_STRING("keypress"), this, false);
+
+ if (GetReadyStateEnum() != nsIDocument::READYSTATE_COMPLETE) {
+ LinkStylesheet(NS_LITERAL_STRING("resource://gre/res/ImageDocument.css"));
+ if (!nsContentUtils::IsChildOfSameType(this)) {
+ LinkStylesheet(NS_LITERAL_STRING("resource://gre/res/TopLevelImageDocument.css"));
+ LinkStylesheet(NS_LITERAL_STRING("chrome://global/skin/media/TopLevelImageDocument.css"));
+ }
+ }
+ BecomeInteractive();
+ }
+}
+
+void
+ImageDocument::OnPageShow(bool aPersisted,
+ EventTarget* aDispatchStartTarget)
+{
+ if (aPersisted) {
+ mOriginalZoomLevel =
+ Preferences::GetBool(SITE_SPECIFIC_ZOOM, false) ? 1.0 : GetZoomLevel();
+ }
+ RefPtr<ImageDocument> kungFuDeathGrip(this);
+ UpdateSizeFromLayout();
+
+ MediaDocument::OnPageShow(aPersisted, aDispatchStartTarget);
+}
+
+NS_IMETHODIMP
+ImageDocument::GetImageIsOverflowing(bool* aImageIsOverflowing)
+{
+ *aImageIsOverflowing = ImageIsOverflowing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ImageDocument::GetImageIsResized(bool* aImageIsResized)
+{
+ *aImageIsResized = ImageIsResized();
+ return NS_OK;
+}
+
+already_AddRefed<imgIRequest>
+ImageDocument::GetImageRequest(ErrorResult& aRv)
+{
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mImageContent);
+ nsCOMPtr<imgIRequest> imageRequest;
+ if (imageLoader) {
+ aRv = imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(imageRequest));
+ }
+ return imageRequest.forget();
+}
+
+NS_IMETHODIMP
+ImageDocument::GetImageRequest(imgIRequest** aImageRequest)
+{
+ ErrorResult rv;
+ *aImageRequest = GetImageRequest(rv).take();
+ return rv.StealNSResult();
+}
+
+void
+ImageDocument::ShrinkToFit()
+{
+ if (!mImageContent) {
+ return;
+ }
+ if (GetZoomLevel() != mOriginalZoomLevel && mImageIsResized &&
+ !nsContentUtils::IsChildOfSameType(this)) {
+ // If we're zoomed, so that we don't maintain the invariant that
+ // mImageIsResized if and only if its displayed width/height fit in
+ // mVisibleWidth/mVisibleHeight, then we may need to switch to/from the
+ // overflowingVertical class here, because our viewport size may have
+ // changed and we don't plan to adjust the image size to compensate. Since
+ // mImageIsResized it has a "height" attribute set, and we can just get the
+ // displayed image height by getting .height on the HTMLImageElement.
+ //
+ // Hold strong ref, because Height() can run script.
+ RefPtr<HTMLImageElement> img = HTMLImageElement::FromContent(mImageContent);
+ uint32_t imageHeight = img->Height();
+ nsDOMTokenList* classList = img->ClassList();
+ ErrorResult ignored;
+ if (imageHeight > mVisibleHeight) {
+ classList->Add(NS_LITERAL_STRING("overflowingVertical"), ignored);
+ } else {
+ classList->Remove(NS_LITERAL_STRING("overflowingVertical"), ignored);
+ }
+ ignored.SuppressException();
+ return;
+ }
+
+ // Keep image content alive while changing the attributes.
+ nsCOMPtr<Element> imageContent = mImageContent;
+ nsCOMPtr<nsIDOMHTMLImageElement> image = do_QueryInterface(imageContent);
+ image->SetWidth(std::max(1, NSToCoordFloor(GetRatio() * mImageWidth)));
+ image->SetHeight(std::max(1, NSToCoordFloor(GetRatio() * mImageHeight)));
+
+ // The view might have been scrolled when zooming in, scroll back to the
+ // origin now that we're showing a shrunk-to-window version.
+ ScrollImageTo(0, 0, false);
+
+ if (!mImageContent) {
+ // ScrollImageTo flush destroyed our content.
+ return;
+ }
+
+ SetModeClass(eShrinkToFit);
+
+ mImageIsResized = true;
+
+ UpdateTitleAndCharset();
+}
+
+NS_IMETHODIMP
+ImageDocument::DOMShrinkToFit()
+{
+ ShrinkToFit();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ImageDocument::DOMRestoreImageTo(int32_t aX, int32_t aY)
+{
+ RestoreImageTo(aX, aY);
+ return NS_OK;
+}
+
+void
+ImageDocument::ScrollImageTo(int32_t aX, int32_t aY, bool restoreImage)
+{
+ float ratio = GetRatio();
+
+ if (restoreImage) {
+ RestoreImage();
+ FlushPendingNotifications(Flush_Layout);
+ }
+
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ if (!shell) {
+ return;
+ }
+
+ nsIScrollableFrame* sf = shell->GetRootScrollFrameAsScrollable();
+ if (!sf) {
+ return;
+ }
+
+ nsRect portRect = sf->GetScrollPortRect();
+ sf->ScrollTo(nsPoint(nsPresContext::CSSPixelsToAppUnits(aX/ratio) - portRect.width/2,
+ nsPresContext::CSSPixelsToAppUnits(aY/ratio) - portRect.height/2),
+ nsIScrollableFrame::INSTANT);
+}
+
+void
+ImageDocument::RestoreImage()
+{
+ if (!mImageContent) {
+ return;
+ }
+ // Keep image content alive while changing the attributes.
+ nsCOMPtr<Element> imageContent = mImageContent;
+ imageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::width, true);
+ imageContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::height, true);
+
+ if (ImageIsOverflowing()) {
+ if (!mImageIsOverflowingVertically) {
+ SetModeClass(eOverflowingHorizontalOnly);
+ } else {
+ SetModeClass(eOverflowingVertical);
+ }
+ }
+ else {
+ SetModeClass(eNone);
+ }
+
+ mImageIsResized = false;
+
+ UpdateTitleAndCharset();
+}
+
+NS_IMETHODIMP
+ImageDocument::DOMRestoreImage()
+{
+ RestoreImage();
+ return NS_OK;
+}
+
+void
+ImageDocument::ToggleImageSize()
+{
+ mShouldResize = true;
+ if (mImageIsResized) {
+ mShouldResize = false;
+ ResetZoomLevel();
+ RestoreImage();
+ }
+ else if (ImageIsOverflowing()) {
+ ResetZoomLevel();
+ ShrinkToFit();
+ }
+}
+
+NS_IMETHODIMP
+ImageDocument::DOMToggleImageSize()
+{
+ ToggleImageSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ImageDocument::Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData)
+{
+ if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ return OnSizeAvailable(aRequest, image);
+ }
+
+ // Run this using a script runner because HAS_TRANSPARENCY notifications can
+ // come during painting and this will trigger invalidation.
+ if (aType == imgINotificationObserver::HAS_TRANSPARENCY) {
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod(this, &ImageDocument::OnHasTransparency);
+ nsContentUtils::AddScriptRunner(runnable);
+ }
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ uint32_t reqStatus;
+ aRequest->GetImageStatus(&reqStatus);
+ nsresult status =
+ reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
+ return OnLoadComplete(aRequest, status);
+ }
+
+ return NS_OK;
+}
+
+void
+ImageDocument::OnHasTransparency()
+{
+ if (!mImageContent || nsContentUtils::IsChildOfSameType(this)) {
+ return;
+ }
+
+ nsDOMTokenList* classList = mImageContent->ClassList();
+ mozilla::ErrorResult rv;
+ classList->Add(NS_LITERAL_STRING("transparent"), rv);
+}
+
+void
+ImageDocument::SetModeClass(eModeClasses mode)
+{
+ nsDOMTokenList* classList = mImageContent->ClassList();
+ ErrorResult rv;
+
+ if (mode == eShrinkToFit) {
+ classList->Add(NS_LITERAL_STRING("shrinkToFit"), rv);
+ } else {
+ classList->Remove(NS_LITERAL_STRING("shrinkToFit"), rv);
+ }
+
+ if (mode == eOverflowingVertical) {
+ classList->Add(NS_LITERAL_STRING("overflowingVertical"), rv);
+ } else {
+ classList->Remove(NS_LITERAL_STRING("overflowingVertical"), rv);
+ }
+
+ if (mode == eOverflowingHorizontalOnly) {
+ classList->Add(NS_LITERAL_STRING("overflowingHorizontalOnly"), rv);
+ } else {
+ classList->Remove(NS_LITERAL_STRING("overflowingHorizontalOnly"), rv);
+ }
+
+ rv.SuppressException();
+}
+
+nsresult
+ImageDocument::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage)
+{
+ // Styles have not yet been applied, so we don't know the final size. For now,
+ // default to the image's intrinsic size.
+ aImage->GetWidth(&mImageWidth);
+ aImage->GetHeight(&mImageHeight);
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod(this, &ImageDocument::DefaultCheckOverflowing);
+ nsContentUtils::AddScriptRunner(runnable);
+ UpdateTitleAndCharset();
+
+ return NS_OK;
+}
+
+nsresult
+ImageDocument::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus)
+{
+ UpdateTitleAndCharset();
+
+ // mImageContent can be null if the document is already destroyed
+ if (NS_FAILED(aStatus) && mStringBundle && mImageContent) {
+ nsAutoCString src;
+ mDocumentURI->GetSpec(src);
+ NS_ConvertUTF8toUTF16 srcString(src);
+ const char16_t* formatString[] = { srcString.get() };
+ nsXPIDLString errorMsg;
+ NS_NAMED_LITERAL_STRING(str, "InvalidImage");
+ mStringBundle->FormatStringFromName(str.get(), formatString, 1,
+ getter_Copies(errorMsg));
+
+ mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::alt, errorMsg, false);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ImageDocument::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ if (eventType.EqualsLiteral("resize")) {
+ CheckOverflowing(false);
+ }
+ else if (eventType.EqualsLiteral("click") && mClickResizingEnabled) {
+ ResetZoomLevel();
+ mShouldResize = true;
+ if (mImageIsResized) {
+ int32_t x = 0, y = 0;
+ nsCOMPtr<nsIDOMMouseEvent> event(do_QueryInterface(aEvent));
+ if (event) {
+ event->GetClientX(&x);
+ event->GetClientY(&y);
+ int32_t left = 0, top = 0;
+ nsCOMPtr<nsIDOMHTMLElement> htmlElement =
+ do_QueryInterface(mImageContent);
+ htmlElement->GetOffsetLeft(&left);
+ htmlElement->GetOffsetTop(&top);
+ x -= left;
+ y -= top;
+ }
+ mShouldResize = false;
+ RestoreImageTo(x, y);
+ }
+ else if (ImageIsOverflowing()) {
+ ShrinkToFit();
+ }
+ } else if (eventType.EqualsLiteral("load")) {
+ UpdateSizeFromLayout();
+ }
+
+ return NS_OK;
+}
+
+void
+ImageDocument::UpdateSizeFromLayout()
+{
+ // Pull an updated size from the content frame to account for any size
+ // change due to CSS properties like |image-orientation|.
+ if (!mImageContent) {
+ return;
+ }
+
+ // Need strong ref, because GetPrimaryFrame can run script.
+ nsCOMPtr<Element> imageContent = mImageContent;
+ nsIFrame* contentFrame = imageContent->GetPrimaryFrame(Flush_Frames);
+ if (!contentFrame) {
+ return;
+ }
+
+ nsIntSize oldSize(mImageWidth, mImageHeight);
+ IntrinsicSize newSize = contentFrame->GetIntrinsicSize();
+
+ if (newSize.width.GetUnit() == eStyleUnit_Coord) {
+ mImageWidth = nsPresContext::AppUnitsToFloatCSSPixels(newSize.width.GetCoordValue());
+ }
+ if (newSize.height.GetUnit() == eStyleUnit_Coord) {
+ mImageHeight = nsPresContext::AppUnitsToFloatCSSPixels(newSize.height.GetCoordValue());
+ }
+
+ // Ensure that our information about overflow is up-to-date if needed.
+ if (mImageWidth != oldSize.width || mImageHeight != oldSize.height) {
+ CheckOverflowing(false);
+ }
+}
+
+nsresult
+ImageDocument::CreateSyntheticDocument()
+{
+ // Synthesize an html document that refers to the image
+ nsresult rv = MediaDocument::CreateSyntheticDocument();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add the image element
+ Element* body = GetBodyElement();
+ if (!body) {
+ NS_WARNING("no body on image document!");
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::img, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+
+ mImageContent = NS_NewHTMLImageElement(nodeInfo.forget());
+ if (!mImageContent) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mImageContent);
+ NS_ENSURE_TRUE(imageLoader, NS_ERROR_UNEXPECTED);
+
+ nsAutoCString src;
+ mDocumentURI->GetSpec(src);
+
+ NS_ConvertUTF8toUTF16 srcString(src);
+ // Make sure not to start the image load from here...
+ imageLoader->SetLoadingEnabled(false);
+ mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::src, srcString, false);
+ mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::alt, srcString, false);
+
+ body->AppendChildTo(mImageContent, false);
+ imageLoader->SetLoadingEnabled(true);
+
+ return NS_OK;
+}
+
+nsresult
+ImageDocument::CheckOverflowing(bool changeState)
+{
+ /* Create a scope so that the style context gets destroyed before we might
+ * call RebuildStyleData. Also, holding onto pointers to the
+ * presentation through style resolution is potentially dangerous.
+ */
+ {
+ nsIPresShell *shell = GetShell();
+ if (!shell) {
+ return NS_OK;
+ }
+
+ nsPresContext *context = shell->GetPresContext();
+ nsRect visibleArea = context->GetVisibleArea();
+
+ mVisibleWidth = nsPresContext::AppUnitsToFloatCSSPixels(visibleArea.width);
+ mVisibleHeight = nsPresContext::AppUnitsToFloatCSSPixels(visibleArea.height);
+ }
+
+ bool imageWasOverflowing = ImageIsOverflowing();
+ bool imageWasOverflowingVertically = mImageIsOverflowingVertically;
+ mImageIsOverflowingHorizontally = mImageWidth > mVisibleWidth;
+ mImageIsOverflowingVertically = mImageHeight > mVisibleHeight;
+ bool windowBecameBigEnough = imageWasOverflowing && !ImageIsOverflowing();
+ bool verticalOverflowChanged =
+ mImageIsOverflowingVertically != imageWasOverflowingVertically;
+
+ if (changeState || mShouldResize || mFirstResize ||
+ windowBecameBigEnough || verticalOverflowChanged) {
+ if (ImageIsOverflowing() && (changeState || mShouldResize)) {
+ ShrinkToFit();
+ }
+ else if (mImageIsResized || mFirstResize || windowBecameBigEnough) {
+ RestoreImage();
+ } else if (!mImageIsResized && verticalOverflowChanged) {
+ if (mImageIsOverflowingVertically) {
+ SetModeClass(eOverflowingVertical);
+ } else {
+ SetModeClass(eOverflowingHorizontalOnly);
+ }
+ }
+ }
+ mFirstResize = false;
+
+ return NS_OK;
+}
+
+void
+ImageDocument::UpdateTitleAndCharset()
+{
+ nsAutoCString typeStr;
+ nsCOMPtr<imgIRequest> imageRequest;
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mImageContent);
+ if (imageLoader) {
+ imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(imageRequest));
+ }
+
+ if (imageRequest) {
+ nsXPIDLCString mimeType;
+ imageRequest->GetMimeType(getter_Copies(mimeType));
+ ToUpperCase(mimeType);
+ nsXPIDLCString::const_iterator start, end;
+ mimeType.BeginReading(start);
+ mimeType.EndReading(end);
+ nsXPIDLCString::const_iterator iter = end;
+ if (FindInReadable(NS_LITERAL_CSTRING("IMAGE/"), start, iter) &&
+ iter != end) {
+ // strip out "X-" if any
+ if (*iter == 'X') {
+ ++iter;
+ if (iter != end && *iter == '-') {
+ ++iter;
+ if (iter == end) {
+ // looks like "IMAGE/X-" is the type?? Bail out of here.
+ mimeType.BeginReading(iter);
+ }
+ } else {
+ --iter;
+ }
+ }
+ typeStr = Substring(iter, end);
+ } else {
+ typeStr = mimeType;
+ }
+ }
+
+ nsXPIDLString status;
+ if (mImageIsResized) {
+ nsAutoString ratioStr;
+ ratioStr.AppendInt(NSToCoordFloor(GetRatio() * 100));
+
+ const char16_t* formatString[1] = { ratioStr.get() };
+ mStringBundle->FormatStringFromName(u"ScaledImage",
+ formatString, 1,
+ getter_Copies(status));
+ }
+
+ static const char* const formatNames[4] =
+ {
+ "ImageTitleWithNeitherDimensionsNorFile",
+ "ImageTitleWithoutDimensions",
+ "ImageTitleWithDimensions2",
+ "ImageTitleWithDimensions2AndFile",
+ };
+
+ MediaDocument::UpdateTitleAndCharset(typeStr, mChannel, formatNames,
+ mImageWidth, mImageHeight, status);
+}
+
+void
+ImageDocument::ResetZoomLevel()
+{
+ nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+ if (docShell) {
+ if (nsContentUtils::IsChildOfSameType(this)) {
+ return;
+ }
+
+ nsCOMPtr<nsIContentViewer> cv;
+ docShell->GetContentViewer(getter_AddRefs(cv));
+ if (cv) {
+ cv->SetFullZoom(mOriginalZoomLevel);
+ }
+ }
+}
+
+float
+ImageDocument::GetZoomLevel()
+{
+ float zoomLevel = mOriginalZoomLevel;
+ nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+ if (docShell) {
+ nsCOMPtr<nsIContentViewer> cv;
+ docShell->GetContentViewer(getter_AddRefs(cv));
+ if (cv) {
+ cv->GetFullZoom(&zoomLevel);
+ }
+ }
+ return zoomLevel;
+}
+
+} // namespace dom
+} // namespace mozilla
+
+nsresult
+NS_NewImageDocument(nsIDocument** aResult)
+{
+ mozilla::dom::ImageDocument* doc = new mozilla::dom::ImageDocument();
+ NS_ADDREF(doc);
+
+ nsresult rv = doc->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(doc);
+ }
+
+ *aResult = doc;
+
+ return rv;
+}
diff --git a/dom/html/ImageDocument.h b/dom/html/ImageDocument.h
new file mode 100644
index 000000000..fdf2a00a8
--- /dev/null
+++ b/dom/html/ImageDocument.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_ImageDocument_h
+#define mozilla_dom_ImageDocument_h
+
+#include "mozilla/Attributes.h"
+#include "imgINotificationObserver.h"
+#include "MediaDocument.h"
+#include "nsIDOMEventListener.h"
+#include "nsIImageDocument.h"
+
+namespace mozilla {
+namespace dom {
+
+class ImageDocument final : public MediaDocument,
+ public nsIImageDocument,
+ public imgINotificationObserver,
+ public nsIDOMEventListener
+{
+public:
+ ImageDocument();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual nsresult Init() override;
+
+ virtual nsresult StartDocumentLoad(const char* aCommand,
+ nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener** aDocListener,
+ bool aReset = true,
+ nsIContentSink* aSink = nullptr) override;
+
+ virtual void SetScriptGlobalObject(nsIScriptGlobalObject* aScriptGlobalObject) override;
+ virtual void Destroy() override;
+ virtual void OnPageShow(bool aPersisted,
+ EventTarget* aDispatchStartTarget) override;
+
+ NS_DECL_NSIIMAGEDOCUMENT
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+
+ // nsIDOMEventListener
+ NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override;
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ImageDocument, MediaDocument)
+
+ friend class ImageListener;
+
+ void DefaultCheckOverflowing() { CheckOverflowing(mResizeImageByDefault); }
+
+ // WebIDL API
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+ override;
+
+ bool ImageIsOverflowing() const
+ {
+ return mImageIsOverflowingHorizontally || mImageIsOverflowingVertically;
+ }
+ bool ImageIsResized() const
+ {
+ return mImageIsResized;
+ }
+ already_AddRefed<imgIRequest> GetImageRequest(ErrorResult& aRv);
+ void ShrinkToFit();
+ void RestoreImage();
+ void RestoreImageTo(int32_t aX, int32_t aY)
+ {
+ ScrollImageTo(aX, aY, true);
+ }
+ void ToggleImageSize();
+
+protected:
+ virtual ~ImageDocument();
+
+ virtual nsresult CreateSyntheticDocument() override;
+
+ nsresult CheckOverflowing(bool changeState);
+
+ void UpdateTitleAndCharset();
+
+ void ScrollImageTo(int32_t aX, int32_t aY, bool restoreImage);
+
+ float GetRatio() {
+ return std::min(mVisibleWidth / mImageWidth,
+ mVisibleHeight / mImageHeight);
+ }
+
+ void ResetZoomLevel();
+ float GetZoomLevel();
+
+ void UpdateSizeFromLayout();
+
+ enum eModeClasses {
+ eNone,
+ eShrinkToFit,
+ eOverflowingVertical, // And maybe horizontal too.
+ eOverflowingHorizontalOnly
+ };
+ void SetModeClass(eModeClasses mode);
+
+ nsresult OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage);
+ nsresult OnLoadComplete(imgIRequest* aRequest, nsresult aStatus);
+ void OnHasTransparency();
+
+ nsCOMPtr<Element> mImageContent;
+
+ float mVisibleWidth;
+ float mVisibleHeight;
+ int32_t mImageWidth;
+ int32_t mImageHeight;
+
+ bool mResizeImageByDefault;
+ bool mClickResizingEnabled;
+ bool mImageIsOverflowingHorizontally;
+ bool mImageIsOverflowingVertically;
+ // mImageIsResized is true if the image is currently resized
+ bool mImageIsResized;
+ // mShouldResize is true if the image should be resized when it doesn't fit
+ // mImageIsResized cannot be true when this is false, but mImageIsResized
+ // can be false when this is true
+ bool mShouldResize;
+ bool mFirstResize;
+ // mObservingImageLoader is true while the observer is set.
+ bool mObservingImageLoader;
+
+ float mOriginalZoomLevel;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_ImageDocument_h */
diff --git a/dom/html/MediaDocument.cpp b/dom/html/MediaDocument.cpp
new file mode 100644
index 000000000..d74d72111
--- /dev/null
+++ b/dom/html/MediaDocument.cpp
@@ -0,0 +1,440 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaDocument.h"
+#include "nsGkAtoms.h"
+#include "nsRect.h"
+#include "nsPresContext.h"
+#include "nsIPresShell.h"
+#include "nsIScrollable.h"
+#include "nsViewManager.h"
+#include "nsITextToSubURI.h"
+#include "nsIURL.h"
+#include "nsIContentViewer.h"
+#include "nsIDocShell.h"
+#include "nsCharsetSource.h" // kCharsetFrom* macro definition
+#include "nsNodeInfoManager.h"
+#include "nsContentUtils.h"
+#include "nsDocElementCreatedNotificationRunner.h"
+#include "mozilla/Services.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIPrincipal.h"
+#include "nsIMultiPartChannel.h"
+
+namespace mozilla {
+namespace dom {
+
+MediaDocumentStreamListener::MediaDocumentStreamListener(MediaDocument *aDocument)
+{
+ mDocument = aDocument;
+}
+
+MediaDocumentStreamListener::~MediaDocumentStreamListener()
+{
+}
+
+
+NS_IMPL_ISUPPORTS(MediaDocumentStreamListener,
+ nsIRequestObserver,
+ nsIStreamListener)
+
+
+void
+MediaDocumentStreamListener::SetStreamListener(nsIStreamListener *aListener)
+{
+ mNextStream = aListener;
+}
+
+NS_IMETHODIMP
+MediaDocumentStreamListener::OnStartRequest(nsIRequest* request, nsISupports *ctxt)
+{
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
+
+ mDocument->StartLayout();
+
+ if (mNextStream) {
+ return mNextStream->OnStartRequest(request, ctxt);
+ }
+
+ return NS_ERROR_PARSED_DATA_CACHED;
+}
+
+NS_IMETHODIMP
+MediaDocumentStreamListener::OnStopRequest(nsIRequest* request,
+ nsISupports *ctxt,
+ nsresult status)
+{
+ nsresult rv = NS_OK;
+ if (mNextStream) {
+ rv = mNextStream->OnStopRequest(request, ctxt, status);
+ }
+
+ // Don't release mDocument here if we're in the middle of a multipart response.
+ bool lastPart = true;
+ nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(request));
+ if (mpchan) {
+ mpchan->GetIsLastPart(&lastPart);
+ }
+
+ if (lastPart) {
+ mDocument = nullptr;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+MediaDocumentStreamListener::OnDataAvailable(nsIRequest* request,
+ nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset,
+ uint32_t count)
+{
+ if (mNextStream) {
+ return mNextStream->OnDataAvailable(request, ctxt, inStr, sourceOffset, count);
+ }
+
+ return NS_OK;
+}
+
+// default format names for MediaDocument.
+const char* const MediaDocument::sFormatNames[4] =
+{
+ "MediaTitleWithNoInfo", // eWithNoInfo
+ "MediaTitleWithFile", // eWithFile
+ "", // eWithDim
+ "" // eWithDimAndFile
+};
+
+MediaDocument::MediaDocument()
+ : nsHTMLDocument(),
+ mDocumentElementInserted(false)
+{
+}
+MediaDocument::~MediaDocument()
+{
+}
+
+nsresult
+MediaDocument::Init()
+{
+ nsresult rv = nsHTMLDocument::Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create a bundle for the localization
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::services::GetStringBundleService();
+ if (stringService) {
+ stringService->CreateBundle(NSMEDIADOCUMENT_PROPERTIES_URI,
+ getter_AddRefs(mStringBundle));
+ }
+
+ mIsSyntheticDocument = true;
+
+ return NS_OK;
+}
+
+nsresult
+MediaDocument::StartDocumentLoad(const char* aCommand,
+ nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener** aDocListener,
+ bool aReset,
+ nsIContentSink* aSink)
+{
+ nsresult rv = nsDocument::StartDocumentLoad(aCommand, aChannel, aLoadGroup,
+ aContainer, aDocListener, aReset,
+ aSink);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // We try to set the charset of the current document to that of the
+ // 'genuine' (as opposed to an intervening 'chrome') parent document
+ // that may be in a different window/tab. Even if we fail here,
+ // we just return NS_OK because another attempt is made in
+ // |UpdateTitleAndCharset| and the worst thing possible is a mangled
+ // filename in the titlebar and the file picker.
+
+ // Note that we
+ // exclude UTF-8 as 'invalid' because UTF-8 is likely to be the charset
+ // of a chrome document that has nothing to do with the actual content
+ // whose charset we want to know. Even if "the actual content" is indeed
+ // in UTF-8, we don't lose anything because the default empty value is
+ // considered synonymous with UTF-8.
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aContainer));
+
+ // not being able to set the charset is not critical.
+ NS_ENSURE_TRUE(docShell, NS_OK);
+
+ nsAutoCString charset;
+ int32_t source;
+ nsCOMPtr<nsIPrincipal> principal;
+ // opening in a new tab
+ docShell->GetParentCharset(charset, &source, getter_AddRefs(principal));
+
+ if (!charset.IsEmpty() &&
+ !charset.EqualsLiteral("UTF-8") &&
+ NodePrincipal()->Equals(principal)) {
+ SetDocumentCharacterSetSource(source);
+ SetDocumentCharacterSet(charset);
+ }
+
+ return NS_OK;
+}
+
+void
+MediaDocument::BecomeInteractive()
+{
+ // Even though our readyState code isn't really reliable, here we pretend
+ // that it is and conclude that we are restoring from the b/f cache if
+ // GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE.
+ if (GetReadyStateEnum() != nsIDocument::READYSTATE_COMPLETE) {
+ MOZ_ASSERT(GetReadyStateEnum() == nsIDocument::READYSTATE_LOADING,
+ "Bad readyState");
+ SetReadyStateInternal(nsIDocument::READYSTATE_INTERACTIVE);
+ }
+}
+
+nsresult
+MediaDocument::CreateSyntheticDocument()
+{
+ // Synthesize an empty html document
+ nsresult rv;
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::html, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+
+ RefPtr<nsGenericHTMLElement> root = NS_NewHTMLHtmlElement(nodeInfo.forget());
+ NS_ENSURE_TRUE(root, NS_ERROR_OUT_OF_MEMORY);
+
+ NS_ASSERTION(GetChildCount() == 0, "Shouldn't have any kids");
+ rv = AppendChildTo(root, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::head, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+
+ // Create a <head> so our title has somewhere to live
+ RefPtr<nsGenericHTMLElement> head = NS_NewHTMLHeadElement(nodeInfo.forget());
+ NS_ENSURE_TRUE(head, NS_ERROR_OUT_OF_MEMORY);
+
+ nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::meta, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+
+ RefPtr<nsGenericHTMLElement> metaContent = NS_NewHTMLMetaElement(nodeInfo.forget());
+ NS_ENSURE_TRUE(metaContent, NS_ERROR_OUT_OF_MEMORY);
+ metaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::name,
+ NS_LITERAL_STRING("viewport"),
+ true);
+
+ metaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::content,
+ NS_LITERAL_STRING("width=device-width; height=device-height;"),
+ true);
+ head->AppendChildTo(metaContent, false);
+
+ root->AppendChildTo(head, false);
+
+ nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::body, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+
+ RefPtr<nsGenericHTMLElement> body = NS_NewHTMLBodyElement(nodeInfo.forget());
+ NS_ENSURE_TRUE(body, NS_ERROR_OUT_OF_MEMORY);
+
+ root->AppendChildTo(body, false);
+
+ return NS_OK;
+}
+
+nsresult
+MediaDocument::StartLayout()
+{
+ mMayStartLayout = true;
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ // Don't mess with the presshell if someone has already handled
+ // its initial reflow.
+ if (shell && !shell->DidInitialize()) {
+ nsRect visibleArea = shell->GetPresContext()->GetVisibleArea();
+ nsresult rv = shell->Initialize(visibleArea.width, visibleArea.height);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+void
+MediaDocument::GetFileName(nsAString& aResult, nsIChannel* aChannel)
+{
+ aResult.Truncate();
+
+ if (aChannel) {
+ aChannel->GetContentDispositionFilename(aResult);
+ if (!aResult.IsEmpty())
+ return;
+ }
+
+ nsCOMPtr<nsIURL> url = do_QueryInterface(mDocumentURI);
+ if (!url)
+ return;
+
+ nsAutoCString fileName;
+ url->GetFileName(fileName);
+ if (fileName.IsEmpty())
+ return;
+
+ nsAutoCString docCharset;
+ // Now that the charset is set in |StartDocumentLoad| to the charset of
+ // the document viewer instead of a bogus value ("ISO-8859-1" set in
+ // |nsDocument|'s ctor), the priority is given to the current charset.
+ // This is necessary to deal with a media document being opened in a new
+ // window or a new tab, in which case |originCharset| of |nsIURI| is not
+ // reliable.
+ if (mCharacterSetSource != kCharsetUninitialized) {
+ docCharset = mCharacterSet;
+ } else {
+ // resort to |originCharset|
+ url->GetOriginCharset(docCharset);
+ SetDocumentCharacterSet(docCharset);
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsITextToSubURI> textToSubURI =
+ do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ // UnEscapeURIForUI always succeeds
+ textToSubURI->UnEscapeURIForUI(docCharset, fileName, aResult);
+ } else {
+ CopyUTF8toUTF16(fileName, aResult);
+ }
+}
+
+nsresult
+MediaDocument::LinkStylesheet(const nsAString& aStylesheet)
+{
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::link, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+
+ RefPtr<nsGenericHTMLElement> link = NS_NewHTMLLinkElement(nodeInfo.forget());
+ NS_ENSURE_TRUE(link, NS_ERROR_OUT_OF_MEMORY);
+
+ link->SetAttr(kNameSpaceID_None, nsGkAtoms::rel,
+ NS_LITERAL_STRING("stylesheet"), true);
+
+ link->SetAttr(kNameSpaceID_None, nsGkAtoms::href, aStylesheet, true);
+
+ Element* head = GetHeadElement();
+ return head->AppendChildTo(link, false);
+}
+
+nsresult
+MediaDocument::LinkScript(const nsAString& aScript)
+{
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::script, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+
+ RefPtr<nsGenericHTMLElement> script = NS_NewHTMLScriptElement(nodeInfo.forget());
+ NS_ENSURE_TRUE(script, NS_ERROR_OUT_OF_MEMORY);
+
+ script->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
+ NS_LITERAL_STRING("text/javascript;version=1.8"), true);
+
+ script->SetAttr(kNameSpaceID_None, nsGkAtoms::src, aScript, true);
+
+ Element* head = GetHeadElement();
+ return head->AppendChildTo(script, false);
+}
+
+void
+MediaDocument::UpdateTitleAndCharset(const nsACString& aTypeStr,
+ nsIChannel* aChannel,
+ const char* const* aFormatNames,
+ int32_t aWidth, int32_t aHeight,
+ const nsAString& aStatus)
+{
+ nsXPIDLString fileStr;
+ GetFileName(fileStr, aChannel);
+
+ NS_ConvertASCIItoUTF16 typeStr(aTypeStr);
+ nsXPIDLString title;
+
+ if (mStringBundle) {
+ // if we got a valid size (not all media have a size)
+ if (aWidth != 0 && aHeight != 0) {
+ nsAutoString widthStr;
+ nsAutoString heightStr;
+ widthStr.AppendInt(aWidth);
+ heightStr.AppendInt(aHeight);
+ // If we got a filename, display it
+ if (!fileStr.IsEmpty()) {
+ const char16_t *formatStrings[4] = {fileStr.get(), typeStr.get(),
+ widthStr.get(), heightStr.get()};
+ NS_ConvertASCIItoUTF16 fmtName(aFormatNames[eWithDimAndFile]);
+ mStringBundle->FormatStringFromName(fmtName.get(), formatStrings, 4,
+ getter_Copies(title));
+ }
+ else {
+ const char16_t *formatStrings[3] = {typeStr.get(), widthStr.get(),
+ heightStr.get()};
+ NS_ConvertASCIItoUTF16 fmtName(aFormatNames[eWithDim]);
+ mStringBundle->FormatStringFromName(fmtName.get(), formatStrings, 3,
+ getter_Copies(title));
+ }
+ }
+ else {
+ // If we got a filename, display it
+ if (!fileStr.IsEmpty()) {
+ const char16_t *formatStrings[2] = {fileStr.get(), typeStr.get()};
+ NS_ConvertASCIItoUTF16 fmtName(aFormatNames[eWithFile]);
+ mStringBundle->FormatStringFromName(fmtName.get(), formatStrings, 2,
+ getter_Copies(title));
+ }
+ else {
+ const char16_t *formatStrings[1] = {typeStr.get()};
+ NS_ConvertASCIItoUTF16 fmtName(aFormatNames[eWithNoInfo]);
+ mStringBundle->FormatStringFromName(fmtName.get(), formatStrings, 1,
+ getter_Copies(title));
+ }
+ }
+ }
+
+ // set it on the document
+ if (aStatus.IsEmpty()) {
+ SetTitle(title);
+ }
+ else {
+ nsXPIDLString titleWithStatus;
+ const nsPromiseFlatString& status = PromiseFlatString(aStatus);
+ const char16_t *formatStrings[2] = {title.get(), status.get()};
+ NS_NAMED_LITERAL_STRING(fmtName, "TitleWithStatus");
+ mStringBundle->FormatStringFromName(fmtName.get(), formatStrings, 2,
+ getter_Copies(titleWithStatus));
+ SetTitle(titleWithStatus);
+ }
+}
+
+void
+MediaDocument::SetScriptGlobalObject(nsIScriptGlobalObject* aGlobalObject)
+{
+ nsHTMLDocument::SetScriptGlobalObject(aGlobalObject);
+ if (!mDocumentElementInserted && aGlobalObject) {
+ mDocumentElementInserted = true;
+ nsContentUtils::AddScriptRunner(
+ new nsDocElementCreatedNotificationRunner(this));
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/MediaDocument.h b/dom/html/MediaDocument.h
new file mode 100644
index 000000000..7d1be6265
--- /dev/null
+++ b/dom/html/MediaDocument.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MediaDocument_h
+#define mozilla_dom_MediaDocument_h
+
+#include "mozilla/Attributes.h"
+#include "nsHTMLDocument.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIStringBundle.h"
+
+#define NSMEDIADOCUMENT_PROPERTIES_URI "chrome://global/locale/layout/MediaDocument.properties"
+
+namespace mozilla {
+namespace dom {
+
+class MediaDocument : public nsHTMLDocument
+{
+public:
+ MediaDocument();
+ virtual ~MediaDocument();
+
+ virtual nsresult Init() override;
+
+ virtual nsresult StartDocumentLoad(const char* aCommand,
+ nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener** aDocListener,
+ bool aReset = true,
+ nsIContentSink* aSink = nullptr) override;
+
+ virtual void SetScriptGlobalObject(nsIScriptGlobalObject* aGlobalObject) override;
+
+ virtual bool WillIgnoreCharsetOverride() override
+ {
+ return true;
+ }
+
+protected:
+ void BecomeInteractive();
+
+ virtual nsresult CreateSyntheticDocument();
+
+ friend class MediaDocumentStreamListener;
+ nsresult StartLayout();
+
+ void GetFileName(nsAString& aResult, nsIChannel* aChannel);
+
+ nsresult LinkStylesheet(const nsAString& aStylesheet);
+ nsresult LinkScript(const nsAString& aScript);
+
+ // |aFormatNames[]| needs to have four elements in the following order:
+ // a format name with neither dimension nor file, a format name with
+ // filename but w/o dimension, a format name with dimension but w/o filename,
+ // a format name with both of them. For instance, it can have
+ // "ImageTitleWithNeitherDimensionsNorFile", "ImageTitleWithoutDimensions",
+ // "ImageTitleWithDimesions2", "ImageTitleWithDimensions2AndFile".
+ //
+ // Also see MediaDocument.properties if you want to define format names
+ // for a new subclass. aWidth and aHeight are pixels for |ImageDocument|,
+ // but could be in other units for other 'media', in which case you have to
+ // define format names accordingly.
+ void UpdateTitleAndCharset(const nsACString& aTypeStr,
+ nsIChannel* aChannel,
+ const char* const* aFormatNames = sFormatNames,
+ int32_t aWidth = 0,
+ int32_t aHeight = 0,
+ const nsAString& aStatus = EmptyString());
+
+ nsCOMPtr<nsIStringBundle> mStringBundle;
+ static const char* const sFormatNames[4];
+
+private:
+ enum {eWithNoInfo, eWithFile, eWithDim, eWithDimAndFile};
+ bool mDocumentElementInserted;
+};
+
+
+class MediaDocumentStreamListener: public nsIStreamListener
+{
+protected:
+ virtual ~MediaDocumentStreamListener();
+
+public:
+ explicit MediaDocumentStreamListener(MediaDocument* aDocument);
+ void SetStreamListener(nsIStreamListener *aListener);
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIREQUESTOBSERVER
+
+ NS_DECL_NSISTREAMLISTENER
+
+ RefPtr<MediaDocument> mDocument;
+ nsCOMPtr<nsIStreamListener> mNextStream;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_MediaDocument_h */
diff --git a/dom/html/MediaError.cpp b/dom/html/MediaError.cpp
new file mode 100644
index 000000000..83b9ffc5f
--- /dev/null
+++ b/dom/html/MediaError.cpp
@@ -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/. */
+
+#include "mozilla/dom/MediaError.h"
+#include "nsDOMClassInfoID.h"
+#include "mozilla/dom/MediaErrorBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaError, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaError)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaError)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaError)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+MediaError::MediaError(HTMLMediaElement* aParent, uint16_t aCode,
+ const nsACString& aMessage)
+ : mParent(aParent)
+ , mCode(aCode)
+ , mMessage(aMessage)
+{
+}
+
+void
+MediaError::GetMessage(nsAString& aResult) const
+{
+ CopyUTF8toUTF16(mMessage, aResult);
+}
+
+JSObject*
+MediaError::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MediaErrorBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/MediaError.h b/dom/html/MediaError.h
new file mode 100644
index 000000000..27be1050e
--- /dev/null
+++ b/dom/html/MediaError.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MediaError_h
+#define mozilla_dom_MediaError_h
+
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "nsWrapperCache.h"
+#include "nsISupports.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace dom {
+
+class MediaError final : public nsISupports,
+ public nsWrapperCache
+{
+ ~MediaError() {}
+
+public:
+ MediaError(HTMLMediaElement* aParent, uint16_t aCode,
+ const nsACString& aMessage = nsCString());
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaError)
+
+ HTMLMediaElement* GetParentObject() const
+ {
+ return mParent;
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ uint16_t Code() const
+ {
+ return mCode;
+ }
+
+ void GetMessage(nsAString& aResult) const;
+
+private:
+ RefPtr<HTMLMediaElement> mParent;
+
+ // Error code
+ const uint16_t mCode;
+ // Error details;
+ const nsCString mMessage;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MediaError_h
diff --git a/dom/html/PluginDocument.cpp b/dom/html/PluginDocument.cpp
new file mode 100644
index 000000000..1c923ecc6
--- /dev/null
+++ b/dom/html/PluginDocument.cpp
@@ -0,0 +1,302 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaDocument.h"
+#include "nsIPluginDocument.h"
+#include "nsGkAtoms.h"
+#include "nsIPresShell.h"
+#include "nsIObjectFrame.h"
+#include "nsNPAPIPluginInstance.h"
+#include "nsIDocumentInlines.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentPolicyUtils.h"
+#include "nsIPropertyBag2.h"
+#include "mozilla/dom/Element.h"
+#include "nsObjectLoadingContent.h"
+#include "GeckoProfiler.h"
+
+namespace mozilla {
+namespace dom {
+
+class PluginDocument final : public MediaDocument
+ , public nsIPluginDocument
+{
+public:
+ PluginDocument();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIPLUGINDOCUMENT
+
+ virtual nsresult StartDocumentLoad(const char* aCommand,
+ nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener** aDocListener,
+ bool aReset = true,
+ nsIContentSink* aSink = nullptr) override;
+
+ virtual void SetScriptGlobalObject(nsIScriptGlobalObject* aScriptGlobalObject) override;
+ virtual bool CanSavePresentation(nsIRequest *aNewRequest) override;
+
+ const nsCString& GetType() const { return mMimeType; }
+ Element* GetPluginContent() { return mPluginContent; }
+
+ void StartLayout() { MediaDocument::StartLayout(); }
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PluginDocument, MediaDocument)
+protected:
+ virtual ~PluginDocument();
+
+ nsresult CreateSyntheticPluginDocument();
+
+ nsCOMPtr<Element> mPluginContent;
+ RefPtr<MediaDocumentStreamListener> mStreamListener;
+ nsCString mMimeType;
+};
+
+class PluginStreamListener : public MediaDocumentStreamListener
+{
+public:
+ explicit PluginStreamListener(PluginDocument* aDoc)
+ : MediaDocumentStreamListener(aDoc)
+ , mPluginDoc(aDoc)
+ {}
+ NS_IMETHOD OnStartRequest(nsIRequest* request, nsISupports *ctxt);
+private:
+ RefPtr<PluginDocument> mPluginDoc;
+};
+
+
+NS_IMETHODIMP
+PluginStreamListener::OnStartRequest(nsIRequest* request, nsISupports *ctxt)
+{
+ PROFILER_LABEL("PluginStreamListener", "OnStartRequest",
+ js::ProfileEntry::Category::NETWORK);
+
+ nsCOMPtr<nsIContent> embed = mPluginDoc->GetPluginContent();
+ nsCOMPtr<nsIObjectLoadingContent> objlc = do_QueryInterface(embed);
+ nsCOMPtr<nsIStreamListener> objListener = do_QueryInterface(objlc);
+
+ if (!objListener) {
+ NS_NOTREACHED("PluginStreamListener without appropriate content node");
+ return NS_BINDING_ABORTED;
+ }
+
+ SetStreamListener(objListener);
+
+ // Sets up the ObjectLoadingContent tag as if it is waiting for a
+ // channel, so it can proceed with a load normally once it gets OnStartRequest
+ nsresult rv = objlc->InitializeFromChannel(request);
+ if (NS_FAILED(rv)) {
+ NS_NOTREACHED("InitializeFromChannel failed");
+ return rv;
+ }
+
+ // Note that because we're now hooked up to a plugin listener, this will
+ // likely spawn a plugin, which may re-enter.
+ return MediaDocumentStreamListener::OnStartRequest(request, ctxt);
+}
+
+ // NOTE! nsDocument::operator new() zeroes out all members, so don't
+ // bother initializing members to 0.
+
+PluginDocument::PluginDocument()
+{}
+
+PluginDocument::~PluginDocument()
+{}
+
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(PluginDocument, MediaDocument,
+ mPluginContent)
+
+NS_IMPL_ADDREF_INHERITED(PluginDocument, MediaDocument)
+NS_IMPL_RELEASE_INHERITED(PluginDocument, MediaDocument)
+
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(PluginDocument)
+ NS_INTERFACE_TABLE_INHERITED(PluginDocument, nsIPluginDocument)
+NS_INTERFACE_TABLE_TAIL_INHERITING(MediaDocument)
+
+void
+PluginDocument::SetScriptGlobalObject(nsIScriptGlobalObject* aScriptGlobalObject)
+{
+ // Set the script global object on the superclass before doing
+ // anything that might require it....
+ MediaDocument::SetScriptGlobalObject(aScriptGlobalObject);
+
+ if (aScriptGlobalObject) {
+ if (!mPluginContent) {
+ // Create synthetic document
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ CreateSyntheticPluginDocument();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create synthetic document");
+ }
+ BecomeInteractive();
+ } else {
+ mStreamListener = nullptr;
+ }
+}
+
+
+bool
+PluginDocument::CanSavePresentation(nsIRequest *aNewRequest)
+{
+ // Full-page plugins cannot be cached, currently, because we don't have
+ // the stream listener data to feed to the plugin instance.
+ return false;
+}
+
+
+nsresult
+PluginDocument::StartDocumentLoad(const char* aCommand,
+ nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener** aDocListener,
+ bool aReset,
+ nsIContentSink* aSink)
+{
+ // do not allow message panes to host full-page plugins
+ // returning an error causes helper apps to take over
+ nsCOMPtr<nsIDocShellTreeItem> dsti (do_QueryInterface(aContainer));
+ if (dsti) {
+ bool isMsgPane = false;
+ dsti->NameEquals(NS_LITERAL_STRING("messagepane"), &isMsgPane);
+ if (isMsgPane) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ nsresult rv =
+ MediaDocument::StartDocumentLoad(aCommand, aChannel, aLoadGroup, aContainer,
+ aDocListener, aReset, aSink);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = aChannel->GetContentType(mMimeType);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ MediaDocument::UpdateTitleAndCharset(mMimeType, aChannel);
+
+ mStreamListener = new PluginStreamListener(this);
+ NS_ASSERTION(aDocListener, "null aDocListener");
+ NS_ADDREF(*aDocListener = mStreamListener);
+
+ return rv;
+}
+
+nsresult
+PluginDocument::CreateSyntheticPluginDocument()
+{
+ NS_ASSERTION(!GetShell() || !GetShell()->DidInitialize(),
+ "Creating synthetic plugin document content too late");
+
+ // make our generic document
+ nsresult rv = MediaDocument::CreateSyntheticDocument();
+ NS_ENSURE_SUCCESS(rv, rv);
+ // then attach our plugin
+
+ Element* body = GetBodyElement();
+ if (!body) {
+ NS_WARNING("no body on plugin document!");
+ return NS_ERROR_FAILURE;
+ }
+
+ // remove margins from body
+ NS_NAMED_LITERAL_STRING(zero, "0");
+ body->SetAttr(kNameSpaceID_None, nsGkAtoms::marginwidth, zero, false);
+ body->SetAttr(kNameSpaceID_None, nsGkAtoms::marginheight, zero, false);
+
+
+ // make plugin content
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::embed, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+ rv = NS_NewHTMLElement(getter_AddRefs(mPluginContent), nodeInfo.forget(),
+ NOT_FROM_PARSER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // make it a named element
+ mPluginContent->SetAttr(kNameSpaceID_None, nsGkAtoms::name,
+ NS_LITERAL_STRING("plugin"), false);
+
+ // fill viewport and auto-resize
+ NS_NAMED_LITERAL_STRING(percent100, "100%");
+ mPluginContent->SetAttr(kNameSpaceID_None, nsGkAtoms::width, percent100,
+ false);
+ mPluginContent->SetAttr(kNameSpaceID_None, nsGkAtoms::height, percent100,
+ false);
+
+ // set URL
+ nsAutoCString src;
+ mDocumentURI->GetSpec(src);
+ mPluginContent->SetAttr(kNameSpaceID_None, nsGkAtoms::src,
+ NS_ConvertUTF8toUTF16(src), false);
+
+ // set mime type
+ mPluginContent->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
+ NS_ConvertUTF8toUTF16(mMimeType), false);
+
+ // nsHTML(Shared)ObjectElement does not kick off a load on BindToTree if it is
+ // to a PluginDocument
+ body->AppendChildTo(mPluginContent, false);
+
+ return NS_OK;
+
+
+}
+
+NS_IMETHODIMP
+PluginDocument::Print()
+{
+ NS_ENSURE_TRUE(mPluginContent, NS_ERROR_FAILURE);
+
+ nsIObjectFrame* objectFrame =
+ do_QueryFrame(mPluginContent->GetPrimaryFrame());
+ if (objectFrame) {
+ RefPtr<nsNPAPIPluginInstance> pi;
+ objectFrame->GetPluginInstance(getter_AddRefs(pi));
+ if (pi) {
+ NPPrint npprint;
+ npprint.mode = NP_FULL;
+ npprint.print.fullPrint.pluginPrinted = false;
+ npprint.print.fullPrint.printOne = false;
+ npprint.print.fullPrint.platformPrint = nullptr;
+
+ pi->Print(&npprint);
+ }
+ }
+
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
+
+nsresult
+NS_NewPluginDocument(nsIDocument** aResult)
+{
+ mozilla::dom::PluginDocument* doc = new mozilla::dom::PluginDocument();
+
+ NS_ADDREF(doc);
+ nsresult rv = doc->Init();
+
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(doc);
+ }
+
+ *aResult = doc;
+
+ return rv;
+}
diff --git a/dom/html/RadioNodeList.cpp b/dom/html/RadioNodeList.cpp
new file mode 100644
index 000000000..79780e11c
--- /dev/null
+++ b/dom/html/RadioNodeList.cpp
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/RadioNodeList.h"
+
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/RadioNodeListBinding.h"
+#include "js/TypeDecls.h"
+
+#include "HTMLInputElement.h"
+
+namespace mozilla {
+namespace dom {
+
+/* virtual */ JSObject*
+RadioNodeList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return RadioNodeListBinding::Wrap(aCx, this, aGivenProto);
+}
+
+HTMLInputElement*
+GetAsRadio(nsIContent* node)
+{
+ HTMLInputElement* el = HTMLInputElement::FromContent(node);
+ if (el && el->GetType() == NS_FORM_INPUT_RADIO) {
+ return el;
+ }
+ return nullptr;
+}
+
+void
+RadioNodeList::GetValue(nsString& retval)
+{
+ for (uint32_t i = 0; i < Length(); i++) {
+ HTMLInputElement* maybeRadio = GetAsRadio(Item(i));
+ if (maybeRadio && maybeRadio->Checked()) {
+ maybeRadio->GetValue(retval);
+ return;
+ }
+ }
+ retval.Truncate();
+}
+
+void
+RadioNodeList::SetValue(const nsAString& value)
+{
+ for (uint32_t i = 0; i < Length(); i++) {
+
+ HTMLInputElement* maybeRadio = GetAsRadio(Item(i));
+ if (!maybeRadio) {
+ continue;
+ }
+
+ nsString curval = nsString();
+ maybeRadio->GetValue(curval);
+ if (curval.Equals(value)) {
+ maybeRadio->SetChecked(true);
+ return;
+ }
+
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(RadioNodeList, nsSimpleContentList, RadioNodeList)
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/RadioNodeList.h b/dom/html/RadioNodeList.h
new file mode 100644
index 000000000..8ff487feb
--- /dev/null
+++ b/dom/html/RadioNodeList.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_RadioNodeList_h
+#define mozilla_dom_RadioNodeList_h
+
+#include "nsContentList.h"
+#include "nsCOMPtr.h"
+#include "HTMLFormElement.h"
+
+#define MOZILLA_DOM_RADIONODELIST_IMPLEMENTATION_IID \
+ { 0xbba7f3e8, 0xf3b5, 0x42e5, \
+ { 0x82, 0x08, 0xa6, 0x8b, 0xe0, 0xbc, 0x22, 0x19 } }
+
+namespace mozilla {
+namespace dom {
+
+class RadioNodeList : public nsSimpleContentList
+{
+public:
+ explicit RadioNodeList(HTMLFormElement* aForm) : nsSimpleContentList(aForm) { }
+
+ virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+ void GetValue(nsString& retval);
+ void SetValue(const nsAString& value);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOM_RADIONODELIST_IMPLEMENTATION_IID)
+private:
+ ~RadioNodeList() { }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(RadioNodeList, MOZILLA_DOM_RADIONODELIST_IMPLEMENTATION_IID)
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_RadioNodeList_h
diff --git a/dom/html/TextTrackManager.cpp b/dom/html/TextTrackManager.cpp
new file mode 100644
index 000000000..8110dab29
--- /dev/null
+++ b/dom/html/TextTrackManager.cpp
@@ -0,0 +1,848 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/TextTrackManager.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/HTMLTrackElement.h"
+#include "mozilla/dom/HTMLVideoElement.h"
+#include "mozilla/dom/TextTrack.h"
+#include "mozilla/dom/TextTrackCue.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Telemetry.h"
+#include "nsComponentManagerUtils.h"
+#include "nsVariant.h"
+#include "nsVideoFrame.h"
+#include "nsIFrame.h"
+#include "nsTArrayHelpers.h"
+#include "nsIWebVTTParserWrapper.h"
+
+
+static mozilla::LazyLogModule gTextTrackLog("TextTrackManager");
+#define WEBVTT_LOG(...) MOZ_LOG(gTextTrackLog, LogLevel::Debug, (__VA_ARGS__))
+#define WEBVTT_LOGV(...) MOZ_LOG(gTextTrackLog, LogLevel::Verbose, (__VA_ARGS__))
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS(TextTrackManager::ShutdownObserverProxy, nsIObserver);
+
+CompareTextTracks::CompareTextTracks(HTMLMediaElement* aMediaElement)
+{
+ mMediaElement = aMediaElement;
+}
+
+int32_t
+CompareTextTracks::TrackChildPosition(TextTrack* aTextTrack) const {
+ MOZ_DIAGNOSTIC_ASSERT(aTextTrack);
+ HTMLTrackElement* trackElement = aTextTrack->GetTrackElement();
+ if (!trackElement) {
+ return -1;
+ }
+ return mMediaElement->IndexOf(trackElement);
+}
+
+bool
+CompareTextTracks::Equals(TextTrack* aOne, TextTrack* aTwo) const {
+ // Two tracks can never be equal. If they have corresponding TrackElements
+ // they would need to occupy the same tree position (impossible) and in the
+ // case of tracks coming from AddTextTrack source we put the newest at the
+ // last position, so they won't be equal as well.
+ return false;
+}
+
+bool
+CompareTextTracks::LessThan(TextTrack* aOne, TextTrack* aTwo) const
+{
+ // Protect against nullptr TextTrack objects; treat them as
+ // sorting toward the end.
+ if (!aOne) {
+ return false;
+ }
+ if (!aTwo) {
+ return true;
+ }
+ TextTrackSource sourceOne = aOne->GetTextTrackSource();
+ TextTrackSource sourceTwo = aTwo->GetTextTrackSource();
+ if (sourceOne != sourceTwo) {
+ return sourceOne == TextTrackSource::Track ||
+ (sourceOne == AddTextTrack && sourceTwo == MediaResourceSpecific);
+ }
+ switch (sourceOne) {
+ case Track: {
+ int32_t positionOne = TrackChildPosition(aOne);
+ int32_t positionTwo = TrackChildPosition(aTwo);
+ // If either position one or positiontwo are -1 then something has gone
+ // wrong. In this case we should just put them at the back of the list.
+ return positionOne != -1 && positionTwo != -1 &&
+ positionOne < positionTwo;
+ }
+ case AddTextTrack:
+ // For AddTextTrack sources the tracks will already be in the correct relative
+ // order in the source array. Assume we're called in iteration order and can
+ // therefore always report aOne < aTwo to maintain the original temporal ordering.
+ return true;
+ case MediaResourceSpecific:
+ // No rules for Media Resource Specific tracks yet.
+ break;
+ }
+ return true;
+}
+
+NS_IMPL_CYCLE_COLLECTION(TextTrackManager, mMediaElement, mTextTracks,
+ mPendingTextTracks, mNewCues,
+ mLastActiveCues)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextTrackManager)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TextTrackManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TextTrackManager)
+
+StaticRefPtr<nsIWebVTTParserWrapper> TextTrackManager::sParserWrapper;
+
+TextTrackManager::TextTrackManager(HTMLMediaElement *aMediaElement)
+ : mMediaElement(aMediaElement)
+ , mHasSeeked(false)
+ , mLastTimeMarchesOnCalled(0.0)
+ , mTimeMarchesOnDispatched(false)
+ , mUpdateCueDisplayDispatched(false)
+ , performedTrackSelection(false)
+ , mCueTelemetryReported(false)
+ , mShutdown(false)
+{
+ nsISupports* parentObject =
+ mMediaElement->OwnerDoc()->GetParentObject();
+
+ NS_ENSURE_TRUE_VOID(parentObject);
+ WEBVTT_LOG("%p Create TextTrackManager",this);
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject);
+ mNewCues = new TextTrackCueList(window);
+ mLastActiveCues = new TextTrackCueList(window);
+ mTextTracks = new TextTrackList(window, this);
+ mPendingTextTracks = new TextTrackList(window, this);
+
+ if (!sParserWrapper) {
+ nsCOMPtr<nsIWebVTTParserWrapper> parserWrapper =
+ do_CreateInstance(NS_WEBVTTPARSERWRAPPER_CONTRACTID);
+ sParserWrapper = parserWrapper;
+ ClearOnShutdown(&sParserWrapper);
+ }
+ mShutdownProxy = new ShutdownObserverProxy(this);
+}
+
+TextTrackManager::~TextTrackManager()
+{
+ WEBVTT_LOG("%p ~TextTrackManager",this);
+ nsContentUtils::UnregisterShutdownObserver(mShutdownProxy);
+}
+
+TextTrackList*
+TextTrackManager::GetTextTracks() const
+{
+ return mTextTracks;
+}
+
+already_AddRefed<TextTrack>
+TextTrackManager::AddTextTrack(TextTrackKind aKind, const nsAString& aLabel,
+ const nsAString& aLanguage,
+ TextTrackMode aMode,
+ TextTrackReadyState aReadyState,
+ TextTrackSource aTextTrackSource)
+{
+ if (!mMediaElement || !mTextTracks) {
+ return nullptr;
+ }
+ WEBVTT_LOG("%p AddTextTrack",this);
+ WEBVTT_LOGV("AddTextTrack kind %d Label %s Language %s",aKind,
+ NS_ConvertUTF16toUTF8(aLabel).get(), NS_ConvertUTF16toUTF8(aLanguage).get());
+ RefPtr<TextTrack> track =
+ mTextTracks->AddTextTrack(aKind, aLabel, aLanguage, aMode, aReadyState,
+ aTextTrackSource, CompareTextTracks(mMediaElement));
+ AddCues(track);
+ ReportTelemetryForTrack(track);
+
+ if (aTextTrackSource == TextTrackSource::Track) {
+ RefPtr<nsIRunnable> task =
+ NewRunnableMethod(this, &TextTrackManager::HonorUserPreferencesForTrackSelection);
+ nsContentUtils::RunInStableState(task.forget());
+ }
+
+ return track.forget();
+}
+
+void
+TextTrackManager::AddTextTrack(TextTrack* aTextTrack)
+{
+ if (!mMediaElement || !mTextTracks) {
+ return;
+ }
+ WEBVTT_LOG("%p AddTextTrack TextTrack %p",this, aTextTrack);
+ mTextTracks->AddTextTrack(aTextTrack, CompareTextTracks(mMediaElement));
+ AddCues(aTextTrack);
+ ReportTelemetryForTrack(aTextTrack);
+
+ if (aTextTrack->GetTextTrackSource() == TextTrackSource::Track) {
+ RefPtr<nsIRunnable> task =
+ NewRunnableMethod(this, &TextTrackManager::HonorUserPreferencesForTrackSelection);
+ nsContentUtils::RunInStableState(task.forget());
+ }
+}
+
+void
+TextTrackManager::AddCues(TextTrack* aTextTrack)
+{
+ if (!mNewCues) {
+ WEBVTT_LOG("AddCues mNewCues is null");
+ return;
+ }
+
+ TextTrackCueList* cueList = aTextTrack->GetCues();
+ if (cueList) {
+ bool dummy;
+ WEBVTT_LOGV("AddCues cueList->Length() %d",cueList->Length());
+ for (uint32_t i = 0; i < cueList->Length(); ++i) {
+ mNewCues->AddCue(*cueList->IndexedGetter(i, dummy));
+ }
+ DispatchTimeMarchesOn();
+ }
+}
+
+void
+TextTrackManager::RemoveTextTrack(TextTrack* aTextTrack, bool aPendingListOnly)
+{
+ if (!mPendingTextTracks || !mTextTracks) {
+ return;
+ }
+
+ WEBVTT_LOG("%p RemoveTextTrack TextTrack %p", this, aTextTrack);
+ mPendingTextTracks->RemoveTextTrack(aTextTrack);
+ if (aPendingListOnly) {
+ return;
+ }
+
+ mTextTracks->RemoveTextTrack(aTextTrack);
+ // Remove the cues in mNewCues belong to aTextTrack.
+ TextTrackCueList* removeCueList = aTextTrack->GetCues();
+ if (removeCueList) {
+ WEBVTT_LOGV("RemoveTextTrack removeCueList->Length() %d", removeCueList->Length());
+ for (uint32_t i = 0; i < removeCueList->Length(); ++i) {
+ mNewCues->RemoveCue(*((*removeCueList)[i]));
+ }
+ DispatchTimeMarchesOn();
+ }
+}
+
+void
+TextTrackManager::DidSeek()
+{
+ WEBVTT_LOG("%p DidSeek",this);
+ if (mTextTracks) {
+ mTextTracks->DidSeek();
+ }
+ if (mMediaElement) {
+ mLastTimeMarchesOnCalled = mMediaElement->CurrentTime();
+ WEBVTT_LOGV("DidSeek set mLastTimeMarchesOnCalled %lf",mLastTimeMarchesOnCalled);
+ }
+ mHasSeeked = true;
+}
+
+void
+TextTrackManager::UpdateCueDisplay()
+{
+ WEBVTT_LOG("UpdateCueDisplay");
+ mUpdateCueDisplayDispatched = false;
+
+ if (!mMediaElement || !mTextTracks) {
+ return;
+ }
+
+ nsIFrame* frame = mMediaElement->GetPrimaryFrame();
+ nsVideoFrame* videoFrame = do_QueryFrame(frame);
+ if (!videoFrame) {
+ return;
+ }
+
+ nsCOMPtr<nsIContent> overlay = videoFrame->GetCaptionOverlay();
+ nsCOMPtr<nsIContent> controls = videoFrame->GetVideoControls();
+ if (!overlay) {
+ return;
+ }
+
+ nsTArray<RefPtr<TextTrackCue> > activeCues;
+ mTextTracks->GetShowingCues(activeCues);
+
+ if (activeCues.Length() > 0) {
+ WEBVTT_LOG("UpdateCueDisplay ProcessCues");
+ WEBVTT_LOGV("UpdateCueDisplay activeCues.Length() %d",activeCues.Length());
+ RefPtr<nsVariantCC> jsCues = new nsVariantCC();
+
+ jsCues->SetAsArray(nsIDataType::VTYPE_INTERFACE,
+ &NS_GET_IID(nsIDOMEventTarget),
+ activeCues.Length(),
+ static_cast<void*>(activeCues.Elements()));
+ nsPIDOMWindowInner* window = mMediaElement->OwnerDoc()->GetInnerWindow();
+ if (window) {
+ sParserWrapper->ProcessCues(window, jsCues, overlay, controls);
+ }
+ } else if (overlay->Length() > 0) {
+ WEBVTT_LOG("UpdateCueDisplay EmptyString");
+ nsContentUtils::SetNodeTextContent(overlay, EmptyString(), true);
+ }
+}
+
+void
+TextTrackManager::NotifyCueAdded(TextTrackCue& aCue)
+{
+ WEBVTT_LOG("NotifyCueAdded");
+ if (mNewCues) {
+ mNewCues->AddCue(aCue);
+ }
+ DispatchTimeMarchesOn();
+ ReportTelemetryForCue();
+}
+
+void
+TextTrackManager::NotifyCueRemoved(TextTrackCue& aCue)
+{
+ WEBVTT_LOG("NotifyCueRemoved");
+ if (mNewCues) {
+ mNewCues->RemoveCue(aCue);
+ }
+ DispatchTimeMarchesOn();
+ if (aCue.GetActive()) {
+ // We remove an active cue, need to update the display.
+ DispatchUpdateCueDisplay();
+ }
+}
+
+void
+TextTrackManager::PopulatePendingList()
+{
+ if (!mTextTracks || !mPendingTextTracks || !mMediaElement) {
+ return;
+ }
+ uint32_t len = mTextTracks->Length();
+ bool dummy;
+ for (uint32_t index = 0; index < len; ++index) {
+ TextTrack* ttrack = mTextTracks->IndexedGetter(index, dummy);
+ if (ttrack && ttrack->Mode() != TextTrackMode::Disabled &&
+ ttrack->ReadyState() == TextTrackReadyState::Loading) {
+ mPendingTextTracks->AddTextTrack(ttrack,
+ CompareTextTracks(mMediaElement));
+ }
+ }
+}
+
+void
+TextTrackManager::AddListeners()
+{
+ if (mMediaElement) {
+ mMediaElement->AddEventListener(NS_LITERAL_STRING("resizevideocontrols"),
+ this, false, false);
+ mMediaElement->AddEventListener(NS_LITERAL_STRING("seeked"),
+ this, false, false);
+ mMediaElement->AddEventListener(NS_LITERAL_STRING("controlbarchange"),
+ this, false, true);
+ }
+}
+
+void
+TextTrackManager::HonorUserPreferencesForTrackSelection()
+{
+ if (performedTrackSelection || !mTextTracks) {
+ return;
+ }
+ WEBVTT_LOG("HonorUserPreferencesForTrackSelection");
+ TextTrackKind ttKinds[] = { TextTrackKind::Captions,
+ TextTrackKind::Subtitles };
+
+ // Steps 1 - 3: Perform automatic track selection for different TextTrack
+ // Kinds.
+ PerformTrackSelection(ttKinds, ArrayLength(ttKinds));
+ PerformTrackSelection(TextTrackKind::Descriptions);
+ PerformTrackSelection(TextTrackKind::Chapters);
+
+ // Step 4: Set all TextTracks with a kind of metadata that are disabled
+ // to hidden.
+ for (uint32_t i = 0; i < mTextTracks->Length(); i++) {
+ TextTrack* track = (*mTextTracks)[i];
+ if (track->Kind() == TextTrackKind::Metadata && TrackIsDefault(track) &&
+ track->Mode() == TextTrackMode::Disabled) {
+ track->SetMode(TextTrackMode::Hidden);
+ }
+ }
+
+ performedTrackSelection = true;
+}
+
+bool
+TextTrackManager::TrackIsDefault(TextTrack* aTextTrack)
+{
+ HTMLTrackElement* trackElement = aTextTrack->GetTrackElement();
+ if (!trackElement) {
+ return false;
+ }
+ return trackElement->Default();
+}
+
+void
+TextTrackManager::PerformTrackSelection(TextTrackKind aTextTrackKind)
+{
+ TextTrackKind ttKinds[] = { aTextTrackKind };
+ PerformTrackSelection(ttKinds, ArrayLength(ttKinds));
+}
+
+void
+TextTrackManager::PerformTrackSelection(TextTrackKind aTextTrackKinds[],
+ uint32_t size)
+{
+ nsTArray<TextTrack*> candidates;
+ GetTextTracksOfKinds(aTextTrackKinds, size, candidates);
+
+ // Step 3: If any TextTracks in candidates are showing then abort these steps.
+ for (uint32_t i = 0; i < candidates.Length(); i++) {
+ if (candidates[i]->Mode() == TextTrackMode::Showing) {
+ WEBVTT_LOGV("PerformTrackSelection Showing return kind %d",candidates[i]->Kind());
+ return;
+ }
+ }
+
+ // Step 4: Honor user preferences for track selection, otherwise, set the
+ // first TextTrack in candidates with a default attribute to showing.
+ // TODO: Bug 981691 - Honor user preferences for text track selection.
+ for (uint32_t i = 0; i < candidates.Length(); i++) {
+ if (TrackIsDefault(candidates[i]) &&
+ candidates[i]->Mode() == TextTrackMode::Disabled) {
+ candidates[i]->SetMode(TextTrackMode::Showing);
+ WEBVTT_LOGV("PerformTrackSelection set Showing kind %d", candidates[i]->Kind());
+ return;
+ }
+ }
+}
+
+void
+TextTrackManager::GetTextTracksOfKinds(TextTrackKind aTextTrackKinds[],
+ uint32_t size,
+ nsTArray<TextTrack*>& aTextTracks)
+{
+ for (uint32_t i = 0; i < size; i++) {
+ GetTextTracksOfKind(aTextTrackKinds[i], aTextTracks);
+ }
+}
+
+void
+TextTrackManager::GetTextTracksOfKind(TextTrackKind aTextTrackKind,
+ nsTArray<TextTrack*>& aTextTracks)
+{
+ if (!mTextTracks) {
+ return;
+ }
+ for (uint32_t i = 0; i < mTextTracks->Length(); i++) {
+ TextTrack* textTrack = (*mTextTracks)[i];
+ if (textTrack->Kind() == aTextTrackKind) {
+ aTextTracks.AppendElement(textTrack);
+ }
+ }
+}
+
+NS_IMETHODIMP
+TextTrackManager::HandleEvent(nsIDOMEvent* aEvent)
+{
+ if (!mTextTracks) {
+ return NS_OK;
+ }
+
+ nsAutoString type;
+ aEvent->GetType(type);
+ if (type.EqualsLiteral("resizevideocontrols") ||
+ type.EqualsLiteral("seeked")) {
+ for (uint32_t i = 0; i< mTextTracks->Length(); i++) {
+ ((*mTextTracks)[i])->SetCuesDirty();
+ }
+ }
+
+ if (type.EqualsLiteral("controlbarchange")) {
+ UpdateCueDisplay();
+ }
+
+ return NS_OK;
+}
+
+
+class SimpleTextTrackEvent : public Runnable
+{
+public:
+ friend class CompareSimpleTextTrackEvents;
+ SimpleTextTrackEvent(const nsAString& aEventName, double aTime,
+ TextTrack* aTrack, TextTrackCue* aCue)
+ : mName(aEventName),
+ mTime(aTime),
+ mTrack(aTrack),
+ mCue(aCue)
+ {}
+
+ NS_IMETHOD Run() {
+ WEBVTT_LOGV("SimpleTextTrackEvent cue %p mName %s mTime %lf",
+ mCue.get(), NS_ConvertUTF16toUTF8(mName).get(), mTime);
+ mCue->DispatchTrustedEvent(mName);
+ return NS_OK;
+ }
+
+private:
+ nsString mName;
+ double mTime;
+ TextTrack* mTrack;
+ RefPtr<TextTrackCue> mCue;
+};
+
+class CompareSimpleTextTrackEvents {
+private:
+ int32_t TrackChildPosition(SimpleTextTrackEvent* aEvent) const
+ {
+ if (aEvent->mTrack) {
+ HTMLTrackElement* trackElement = aEvent->mTrack->GetTrackElement();
+ if (trackElement) {
+ return mMediaElement->IndexOf(trackElement);
+ }
+ }
+ return -1;
+ }
+ HTMLMediaElement* mMediaElement;
+public:
+ explicit CompareSimpleTextTrackEvents(HTMLMediaElement* aMediaElement)
+ {
+ mMediaElement = aMediaElement;
+ }
+
+ bool Equals(SimpleTextTrackEvent* aOne, SimpleTextTrackEvent* aTwo) const
+ {
+ return false;
+ }
+
+ bool LessThan(SimpleTextTrackEvent* aOne, SimpleTextTrackEvent* aTwo) const
+ {
+ if (aOne->mTime < aTwo->mTime) {
+ return true;
+ } else if (aOne->mTime > aTwo->mTime) {
+ return false;
+ }
+
+ int32_t positionOne = TrackChildPosition(aOne);
+ int32_t positionTwo = TrackChildPosition(aTwo);
+ if (positionOne < positionTwo) {
+ return true;
+ } else if (positionOne > positionTwo) {
+ return false;
+ }
+
+ if (aOne->mName.EqualsLiteral("enter") ||
+ aTwo->mName.EqualsLiteral("exit")) {
+ return true;
+ }
+ return false;
+ }
+};
+
+class TextTrackListInternal
+{
+public:
+ void AddTextTrack(TextTrack* aTextTrack,
+ const CompareTextTracks& aCompareTT)
+ {
+ if (!mTextTracks.Contains(aTextTrack)) {
+ mTextTracks.InsertElementSorted(aTextTrack, aCompareTT);
+ }
+ }
+ uint32_t Length() const
+ {
+ return mTextTracks.Length();
+ }
+ TextTrack* operator[](uint32_t aIndex)
+ {
+ return mTextTracks.SafeElementAt(aIndex, nullptr);
+ }
+private:
+ nsTArray<RefPtr<TextTrack>> mTextTracks;
+};
+
+void
+TextTrackManager::DispatchUpdateCueDisplay()
+{
+ if (!mUpdateCueDisplayDispatched && !mShutdown &&
+ (mMediaElement->GetHasUserInteraction() || mMediaElement->IsCurrentlyPlaying())) {
+ WEBVTT_LOG("DispatchUpdateCueDisplay");
+ NS_DispatchToMainThread(NewRunnableMethod(this, &TextTrackManager::UpdateCueDisplay));
+ mUpdateCueDisplayDispatched = true;
+ }
+}
+
+void
+TextTrackManager::DispatchTimeMarchesOn()
+{
+ // Run the algorithm if no previous instance is still running, otherwise
+ // enqueue the current playback position and whether only that changed
+ // through its usual monotonic increase during normal playback; current
+ // executing call upon completion will check queue for further 'work'.
+ if (!mTimeMarchesOnDispatched && !mShutdown &&
+ (mMediaElement->GetHasUserInteraction() || mMediaElement->IsCurrentlyPlaying())) {
+ WEBVTT_LOG("DispatchTimeMarchesOn");
+ NS_DispatchToMainThread(NewRunnableMethod(this, &TextTrackManager::TimeMarchesOn));
+ mTimeMarchesOnDispatched = true;
+ }
+}
+
+// https://html.spec.whatwg.org/multipage/embedded-content.html#time-marches-on
+void
+TextTrackManager::TimeMarchesOn()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ WEBVTT_LOG("TimeMarchesOn");
+ mTimeMarchesOnDispatched = false;
+
+ // Early return if we don't have any TextTracks or shutting down.
+ if (!mTextTracks || mTextTracks->Length() == 0 || mShutdown) {
+ return;
+ }
+
+ nsISupports* parentObject =
+ mMediaElement->OwnerDoc()->GetParentObject();
+ if (NS_WARN_IF(!parentObject)) {
+ return;
+ }
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject);
+
+ if (mMediaElement &&
+ (!(mMediaElement->GetPlayedOrSeeked()) || mMediaElement->Seeking())) {
+ WEBVTT_LOG("TimeMarchesOn seeking or post return");
+ return;
+ }
+
+ // Step 3.
+ double currentPlaybackTime = mMediaElement->CurrentTime();
+ bool hasNormalPlayback = !mHasSeeked;
+ mHasSeeked = false;
+ WEBVTT_LOG("TimeMarchesOn mLastTimeMarchesOnCalled %lf currentPlaybackTime %lf hasNormalPlayback %d"
+ , mLastTimeMarchesOnCalled, currentPlaybackTime, hasNormalPlayback);
+
+ // Step 1, 2.
+ RefPtr<TextTrackCueList> currentCues =
+ new TextTrackCueList(window);
+ RefPtr<TextTrackCueList> otherCues =
+ new TextTrackCueList(window);
+ bool dummy;
+ for (uint32_t index = 0; index < mTextTracks->Length(); ++index) {
+ TextTrack* ttrack = mTextTracks->IndexedGetter(index, dummy);
+ if (ttrack && dummy) {
+ // TODO: call GetCueListByTimeInterval on mNewCues?
+ ttrack->UpdateActiveCueList();
+ TextTrackCueList* activeCueList = ttrack->GetActiveCues();
+ if (activeCueList) {
+ for (uint32_t i = 0; i < activeCueList->Length(); ++i) {
+ currentCues->AddCue(*((*activeCueList)[i]));
+ }
+ }
+ }
+ }
+ WEBVTT_LOGV("TimeMarchesOn currentCues %d", currentCues->Length());
+ // Populate otherCues with 'non-active" cues.
+ if (hasNormalPlayback) {
+ if (currentPlaybackTime < mLastTimeMarchesOnCalled) {
+ // TODO: Add log and find the root cause why the
+ // playback position goes backward.
+ mLastTimeMarchesOnCalled = currentPlaybackTime;
+ }
+ media::Interval<double> interval(mLastTimeMarchesOnCalled,
+ currentPlaybackTime);
+ otherCues = mNewCues->GetCueListByTimeInterval(interval);;
+ } else {
+ // Seek case. Put the mLastActiveCues into otherCues.
+ otherCues = mLastActiveCues;
+ }
+ for (uint32_t i = 0; i < currentCues->Length(); ++i) {
+ TextTrackCue* cue = (*currentCues)[i];
+ otherCues->RemoveCue(*cue);
+ }
+ WEBVTT_LOGV("TimeMarchesOn otherCues %d", otherCues->Length());
+ // Step 4.
+ RefPtr<TextTrackCueList> missedCues = new TextTrackCueList(window);
+ if (hasNormalPlayback) {
+ for (uint32_t i = 0; i < otherCues->Length(); ++i) {
+ TextTrackCue* cue = (*otherCues)[i];
+ if (cue->StartTime() >= mLastTimeMarchesOnCalled &&
+ cue->EndTime() <= currentPlaybackTime) {
+ missedCues->AddCue(*cue);
+ }
+ }
+ }
+ WEBVTT_LOGV("TimeMarchesOn missedCues %d", missedCues->Length());
+ // Step 5. Empty now.
+ // TODO: Step 6: fire timeupdate?
+
+ // Step 7. Abort steps if condition 1, 2, 3 are satisfied.
+ // 1. All of the cues in current cues have their active flag set.
+ // 2. None of the cues in other cues have their active flag set.
+ // 3. Missed cues is empty.
+ bool c1 = true;
+ for (uint32_t i = 0; i < currentCues->Length(); ++i) {
+ if (!(*currentCues)[i]->GetActive()) {
+ c1 = false;
+ break;
+ }
+ }
+ bool c2 = true;
+ for (uint32_t i = 0; i < otherCues->Length(); ++i) {
+ if ((*otherCues)[i]->GetActive()) {
+ c2 = false;
+ break;
+ }
+ }
+ bool c3 = (missedCues->Length() == 0);
+ if (c1 && c2 && c3) {
+ mLastTimeMarchesOnCalled = currentPlaybackTime;
+ WEBVTT_LOG("TimeMarchesOn step 7 return, mLastTimeMarchesOnCalled %lf", mLastTimeMarchesOnCalled);
+ return;
+ }
+
+ // Step 8. Respect PauseOnExit flag if not seek.
+ if (hasNormalPlayback) {
+ for (uint32_t i = 0; i < otherCues->Length(); ++i) {
+ TextTrackCue* cue = (*otherCues)[i];
+ if (cue && cue->PauseOnExit() && cue->GetActive()) {
+ WEBVTT_LOG("TimeMarchesOn pause the MediaElement");
+ mMediaElement->Pause();
+ break;
+ }
+ }
+ for (uint32_t i = 0; i < missedCues->Length(); ++i) {
+ TextTrackCue* cue = (*missedCues)[i];
+ if (cue && cue->PauseOnExit()) {
+ WEBVTT_LOG("TimeMarchesOn pause the MediaElement");
+ mMediaElement->Pause();
+ break;
+ }
+ }
+ }
+
+ // Step 15.
+ // Sort text tracks in the same order as the text tracks appear
+ // in the media element's list of text tracks, and remove
+ // duplicates.
+ TextTrackListInternal affectedTracks;
+ // Step 13, 14.
+ nsTArray<RefPtr<SimpleTextTrackEvent>> eventList;
+ // Step 9, 10.
+ // For each text track cue in missed cues, prepare an event named
+ // enter for the TextTrackCue object with the cue start time.
+ for (uint32_t i = 0; i < missedCues->Length(); ++i) {
+ TextTrackCue* cue = (*missedCues)[i];
+ if (cue) {
+ SimpleTextTrackEvent* event =
+ new SimpleTextTrackEvent(NS_LITERAL_STRING("enter"),
+ cue->StartTime(), cue->GetTrack(),
+ cue);
+ eventList.InsertElementSorted(event,
+ CompareSimpleTextTrackEvents(mMediaElement));
+ affectedTracks.AddTextTrack(cue->GetTrack(), CompareTextTracks(mMediaElement));
+ }
+ }
+
+ // Step 11, 17.
+ for (uint32_t i = 0; i < otherCues->Length(); ++i) {
+ TextTrackCue* cue = (*otherCues)[i];
+ if (cue->GetActive() || missedCues->IsCueExist(cue)) {
+ double time = cue->StartTime() > cue->EndTime() ? cue->StartTime()
+ : cue->EndTime();
+ SimpleTextTrackEvent* event =
+ new SimpleTextTrackEvent(NS_LITERAL_STRING("exit"), time,
+ cue->GetTrack(), cue);
+ eventList.InsertElementSorted(event,
+ CompareSimpleTextTrackEvents(mMediaElement));
+ affectedTracks.AddTextTrack(cue->GetTrack(), CompareTextTracks(mMediaElement));
+ }
+ cue->SetActive(false);
+ }
+
+ // Step 12, 17.
+ for (uint32_t i = 0; i < currentCues->Length(); ++i) {
+ TextTrackCue* cue = (*currentCues)[i];
+ if (!cue->GetActive()) {
+ SimpleTextTrackEvent* event =
+ new SimpleTextTrackEvent(NS_LITERAL_STRING("enter"),
+ cue->StartTime(), cue->GetTrack(),
+ cue);
+ eventList.InsertElementSorted(event,
+ CompareSimpleTextTrackEvents(mMediaElement));
+ affectedTracks.AddTextTrack(cue->GetTrack(), CompareTextTracks(mMediaElement));
+ }
+ cue->SetActive(true);
+ }
+
+ // Fire the eventList
+ for (uint32_t i = 0; i < eventList.Length(); ++i) {
+ NS_DispatchToMainThread(eventList[i].forget());
+ }
+
+ // Step 16.
+ for (uint32_t i = 0; i < affectedTracks.Length(); ++i) {
+ TextTrack* ttrack = affectedTracks[i];
+ if (ttrack) {
+ ttrack->DispatchAsyncTrustedEvent(NS_LITERAL_STRING("cuechange"));
+ HTMLTrackElement* trackElement = ttrack->GetTrackElement();
+ if (trackElement) {
+ trackElement->DispatchTrackRunnable(NS_LITERAL_STRING("cuechange"));
+ }
+ }
+ }
+
+ mLastTimeMarchesOnCalled = currentPlaybackTime;
+ mLastActiveCues = currentCues;
+
+ // Step 18.
+ UpdateCueDisplay();
+}
+
+void
+TextTrackManager::NotifyCueUpdated(TextTrackCue *aCue)
+{
+ // TODO: Add/Reorder the cue to mNewCues if we have some optimization?
+ WEBVTT_LOG("NotifyCueUpdated");
+ DispatchTimeMarchesOn();
+}
+
+void
+TextTrackManager::NotifyReset()
+{
+ WEBVTT_LOG("NotifyReset");
+ mLastTimeMarchesOnCalled = 0.0;
+}
+
+void
+TextTrackManager::ReportTelemetryForTrack(TextTrack* aTextTrack) const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTextTrack);
+ MOZ_ASSERT(mTextTracks->Length() > 0);
+
+ TextTrackKind kind = aTextTrack->Kind();
+ Telemetry::Accumulate(Telemetry::WEBVTT_TRACK_KINDS, uint32_t(kind));
+}
+
+void
+TextTrackManager::ReportTelemetryForCue()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mNewCues->IsEmpty() || !mLastActiveCues->IsEmpty());
+
+ if (!mCueTelemetryReported) {
+ Telemetry::Accumulate(Telemetry::WEBVTT_USED_VTT_CUES, 1);
+ mCueTelemetryReported = true;
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/TextTrackManager.h b/dom/html/TextTrackManager.h
new file mode 100644
index 000000000..d20707346
--- /dev/null
+++ b/dom/html/TextTrackManager.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_TextTrackManager_h
+#define mozilla_dom_TextTrackManager_h
+
+#include "mozilla/dom/TextTrack.h"
+#include "mozilla/dom/TextTrackList.h"
+#include "mozilla/dom/TextTrackCueList.h"
+#include "mozilla/StaticPtr.h"
+#include "nsContentUtils.h"
+
+class nsIWebVTTParserWrapper;
+
+namespace mozilla {
+namespace dom {
+
+class HTMLMediaElement;
+
+class CompareTextTracks {
+private:
+ HTMLMediaElement* mMediaElement;
+ int32_t TrackChildPosition(TextTrack* aTrack) const;
+public:
+ explicit CompareTextTracks(HTMLMediaElement* aMediaElement);
+ bool Equals(TextTrack* aOne, TextTrack* aTwo) const;
+ bool LessThan(TextTrack* aOne, TextTrack* aTwo) const;
+};
+
+class TextTrack;
+class TextTrackCue;
+
+class TextTrackManager final : public nsIDOMEventListener
+{
+ ~TextTrackManager();
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(TextTrackManager)
+
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ explicit TextTrackManager(HTMLMediaElement* aMediaElement);
+
+ TextTrackList* GetTextTracks() const;
+ already_AddRefed<TextTrack> AddTextTrack(TextTrackKind aKind,
+ const nsAString& aLabel,
+ const nsAString& aLanguage,
+ TextTrackMode aMode,
+ TextTrackReadyState aReadyState,
+ TextTrackSource aTextTrackSource);
+ void AddTextTrack(TextTrack* aTextTrack);
+ void RemoveTextTrack(TextTrack* aTextTrack, bool aPendingListOnly);
+ void DidSeek();
+
+ void NotifyCueAdded(TextTrackCue& aCue);
+ void AddCues(TextTrack* aTextTrack);
+ void NotifyCueRemoved(TextTrackCue& aCue);
+ /**
+ * Overview of WebVTT cuetext and anonymous content setup.
+ *
+ * WebVTT nodes are the parsed version of WebVTT cuetext. WebVTT cuetext is
+ * the portion of a WebVTT cue that specifies what the caption will actually
+ * show up as on screen.
+ *
+ * WebVTT cuetext can contain markup that loosely relates to HTML markup. It
+ * can contain tags like <b>, <u>, <i>, <c>, <v>, <ruby>, <rt>, <lang>,
+ * including timestamp tags.
+ *
+ * When the caption is ready to be displayed the WebVTT nodes are converted
+ * over to anonymous DOM content. <i>, <u>, <b>, <ruby>, and <rt> all become
+ * HTMLElements of their corresponding HTML markup tags. <c> and <v> are
+ * converted to <span> tags. Timestamp tags are converted to XML processing
+ * instructions. Additionally, all cuetext tags support specifying of classes.
+ * This takes the form of <foo.class.subclass>. These classes are then parsed
+ * and set as the anonymous content's class attribute.
+ *
+ * Rules on constructing DOM objects from WebVTT nodes can be found here
+ * http://dev.w3.org/html5/webvtt/#webvtt-cue-text-dom-construction-rules.
+ * Current rules are taken from revision on April 15, 2013.
+ */
+
+ void PopulatePendingList();
+
+ void AddListeners();
+
+ // The HTMLMediaElement that this TextTrackManager manages the TextTracks of.
+ RefPtr<HTMLMediaElement> mMediaElement;
+
+ void DispatchTimeMarchesOn();
+ void TimeMarchesOn();
+ void DispatchUpdateCueDisplay();
+
+ void NotifyShutdown()
+ {
+ mShutdown = true;
+ }
+
+ void NotifyCueUpdated(TextTrackCue *aCue);
+
+ void NotifyReset();
+
+private:
+ /**
+ * Converts the TextTrackCue's cuetext into a tree of DOM objects
+ * and attaches it to a div on its owning TrackElement's
+ * MediaElement's caption overlay.
+ */
+ void UpdateCueDisplay();
+
+ // List of the TextTrackManager's owning HTMLMediaElement's TextTracks.
+ RefPtr<TextTrackList> mTextTracks;
+ // List of text track objects awaiting loading.
+ RefPtr<TextTrackList> mPendingTextTracks;
+ // List of newly introduced Text Track cues.
+
+ // Contain all cues for a MediaElement. Not sorted.
+ RefPtr<TextTrackCueList> mNewCues;
+ // The active cues for the last TimeMarchesOn iteration.
+ RefPtr<TextTrackCueList> mLastActiveCues;
+
+ // True if the media player playback changed due to seeking prior to and
+ // during running the "Time Marches On" algorithm.
+ bool mHasSeeked;
+ // Playback position at the time of last "Time Marches On" call
+ double mLastTimeMarchesOnCalled;
+
+ bool mTimeMarchesOnDispatched;
+ bool mUpdateCueDisplayDispatched;
+
+ static StaticRefPtr<nsIWebVTTParserWrapper> sParserWrapper;
+
+ bool performedTrackSelection;
+
+ // Runs the algorithm for performing automatic track selection.
+ void HonorUserPreferencesForTrackSelection();
+ // Performs track selection for a single TextTrackKind.
+ void PerformTrackSelection(TextTrackKind aTextTrackKind);
+ //Performs track selection for a set of TextTrackKinds, for example,
+ // 'subtitles' and 'captions' should be selected together.
+ void PerformTrackSelection(TextTrackKind aTextTrackKinds[], uint32_t size);
+ void GetTextTracksOfKinds(TextTrackKind aTextTrackKinds[], uint32_t size,
+ nsTArray<TextTrack*>& aTextTracks);
+ void GetTextTracksOfKind(TextTrackKind aTextTrackKind,
+ nsTArray<TextTrack*>& aTextTracks);
+ bool TrackIsDefault(TextTrack* aTextTrack);
+
+ void ReportTelemetryForTrack(TextTrack* aTextTrack) const;
+ void ReportTelemetryForCue();
+
+ // If there is at least one cue has been added to the cue list once, we would
+ // report the usage of cue to Telemetry.
+ bool mCueTelemetryReported;
+
+ class ShutdownObserverProxy final : public nsIObserver
+ {
+ NS_DECL_ISUPPORTS
+
+ public:
+ explicit ShutdownObserverProxy(TextTrackManager* aManager)
+ : mManager(aManager)
+ {
+ nsContentUtils::RegisterShutdownObserver(this);
+ }
+
+ NS_IMETHODIMP Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ nsContentUtils::UnregisterShutdownObserver(this);
+ mManager->NotifyShutdown();
+ }
+ return NS_OK;
+ }
+
+ private:
+ ~ShutdownObserverProxy() {};
+ TextTrackManager* mManager;
+ };
+
+ RefPtr<ShutdownObserverProxy> mShutdownProxy;
+ bool mShutdown;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TextTrackManager_h
diff --git a/dom/html/TimeRanges.cpp b/dom/html/TimeRanges.cpp
new file mode 100644
index 000000000..debb81a2f
--- /dev/null
+++ b/dom/html/TimeRanges.cpp
@@ -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/. */
+
+#include "mozilla/dom/TimeRanges.h"
+#include "mozilla/dom/TimeRangesBinding.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "nsError.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TimeRanges, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TimeRanges)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TimeRanges)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TimeRanges)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIDOMTimeRanges)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TimeRanges::TimeRanges()
+ : mParent(nullptr)
+{
+}
+
+TimeRanges::TimeRanges(nsISupports* aParent)
+ : mParent(aParent)
+{
+}
+
+TimeRanges::~TimeRanges()
+{
+}
+
+NS_IMETHODIMP
+TimeRanges::GetLength(uint32_t* aLength)
+{
+ *aLength = Length();
+ return NS_OK;
+}
+
+double
+TimeRanges::Start(uint32_t aIndex, ErrorResult& aRv)
+{
+ if (aIndex >= mRanges.Length()) {
+ aRv = NS_ERROR_DOM_INDEX_SIZE_ERR;
+ return 0;
+ }
+
+ return mRanges[aIndex].mStart;
+}
+
+NS_IMETHODIMP
+TimeRanges::Start(uint32_t aIndex, double* aTime)
+{
+ ErrorResult rv;
+ *aTime = Start(aIndex, rv);
+ return rv.StealNSResult();
+}
+
+double
+TimeRanges::End(uint32_t aIndex, ErrorResult& aRv)
+{
+ if (aIndex >= mRanges.Length()) {
+ aRv = NS_ERROR_DOM_INDEX_SIZE_ERR;
+ return 0;
+ }
+
+ return mRanges[aIndex].mEnd;
+}
+
+NS_IMETHODIMP
+TimeRanges::End(uint32_t aIndex, double* aTime)
+{
+ ErrorResult rv;
+ *aTime = End(aIndex, rv);
+ return rv.StealNSResult();
+}
+
+void
+TimeRanges::Add(double aStart, double aEnd)
+{
+ if (aStart > aEnd) {
+ NS_WARNING("Can't add a range if the end is older that the start.");
+ return;
+ }
+ mRanges.AppendElement(TimeRange(aStart,aEnd));
+}
+
+double
+TimeRanges::GetStartTime()
+{
+ if (mRanges.IsEmpty()) {
+ return -1.0;
+ }
+ return mRanges[0].mStart;
+}
+
+double
+TimeRanges::GetEndTime()
+{
+ if (mRanges.IsEmpty()) {
+ return -1.0;
+ }
+ return mRanges[mRanges.Length() - 1].mEnd;
+}
+
+void
+TimeRanges::Normalize(double aTolerance)
+{
+ if (mRanges.Length() >= 2) {
+ AutoTArray<TimeRange,4> normalized;
+
+ mRanges.Sort(CompareTimeRanges());
+
+ // This merges the intervals.
+ TimeRange current(mRanges[0]);
+ for (uint32_t i = 1; i < mRanges.Length(); i++) {
+ if (current.mStart <= mRanges[i].mStart &&
+ current.mEnd >= mRanges[i].mEnd) {
+ continue;
+ }
+ if (current.mEnd + aTolerance >= mRanges[i].mStart) {
+ current.mEnd = mRanges[i].mEnd;
+ } else {
+ normalized.AppendElement(current);
+ current = mRanges[i];
+ }
+ }
+
+ normalized.AppendElement(current);
+
+ mRanges = normalized;
+ }
+}
+
+void
+TimeRanges::Union(const TimeRanges* aOtherRanges, double aTolerance)
+{
+ mRanges.AppendElements(aOtherRanges->mRanges);
+ Normalize(aTolerance);
+}
+
+void
+TimeRanges::Intersection(const TimeRanges* aOtherRanges)
+{
+ AutoTArray<TimeRange,4> intersection;
+
+ const nsTArray<TimeRange>& otherRanges = aOtherRanges->mRanges;
+ for (index_type i = 0, j = 0; i < mRanges.Length() && j < otherRanges.Length();) {
+ double start = std::max(mRanges[i].mStart, otherRanges[j].mStart);
+ double end = std::min(mRanges[i].mEnd, otherRanges[j].mEnd);
+ if (start < end) {
+ intersection.AppendElement(TimeRange(start, end));
+ }
+ if (mRanges[i].mEnd < otherRanges[j].mEnd) {
+ i += 1;
+ } else {
+ j += 1;
+ }
+ }
+
+ mRanges = intersection;
+}
+
+TimeRanges::index_type
+TimeRanges::Find(double aTime, double aTolerance /* = 0 */)
+{
+ for (index_type i = 0; i < mRanges.Length(); ++i) {
+ if (aTime < mRanges[i].mEnd && (aTime + aTolerance) >= mRanges[i].mStart) {
+ return i;
+ }
+ }
+ return NoIndex;
+}
+
+JSObject*
+TimeRanges::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return TimeRangesBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsISupports*
+TimeRanges::GetParentObject() const
+{
+ return mParent;
+}
+
+void
+TimeRanges::Shift(double aOffset)
+{
+ for (index_type i = 0; i < mRanges.Length(); ++i) {
+ mRanges[i].mStart += aOffset;
+ mRanges[i].mEnd += aOffset;
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/html/TimeRanges.h b/dom/html/TimeRanges.h
new file mode 100644
index 000000000..1811346fa
--- /dev/null
+++ b/dom/html/TimeRanges.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_TimeRanges_h_
+#define mozilla_dom_TimeRanges_h_
+
+#include "nsCOMPtr.h"
+#include "nsIDOMTimeRanges.h"
+#include "nsISupports.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+#include "mozilla/ErrorResult.h"
+
+namespace mozilla {
+namespace dom {
+
+class TimeRanges;
+
+} // namespace dom
+
+namespace dom {
+
+// Implements media TimeRanges:
+// http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#timeranges
+class TimeRanges final : public nsIDOMTimeRanges,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TimeRanges)
+ NS_DECL_NSIDOMTIMERANGES
+
+ TimeRanges();
+ explicit TimeRanges(nsISupports* aParent);
+
+ void Add(double aStart, double aEnd);
+
+ // Returns the start time of the first range, or -1 if no ranges exist.
+ double GetStartTime();
+
+ // Returns the end time of the last range, or -1 if no ranges exist.
+ double GetEndTime();
+
+ // See http://www.whatwg.org/html/#normalized-timeranges-object
+ void Normalize(double aTolerance = 0.0);
+
+ // Mutate this TimeRange to be the union of this and aOtherRanges.
+ void Union(const TimeRanges* aOtherRanges, double aTolerance);
+
+ // Mutate this TimeRange to be the intersection of this and aOtherRanges.
+ void Intersection(const TimeRanges* aOtherRanges);
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ nsISupports* GetParentObject() const;
+
+ uint32_t Length() const
+ {
+ return mRanges.Length();
+ }
+
+ virtual double Start(uint32_t aIndex, ErrorResult& aRv);
+
+ virtual double End(uint32_t aIndex, ErrorResult& aRv);
+
+ // Shift all values by aOffset seconds.
+ void Shift(double aOffset);
+
+private:
+ ~TimeRanges();
+
+ // Comparator which orders TimeRanges by start time. Used by Normalize().
+ struct TimeRange
+ {
+ TimeRange(double aStart, double aEnd)
+ : mStart(aStart),
+ mEnd(aEnd) {}
+ double mStart;
+ double mEnd;
+ };
+
+ struct CompareTimeRanges
+ {
+ bool Equals(const TimeRange& aTr1, const TimeRange& aTr2) const {
+ return aTr1.mStart == aTr2.mStart && aTr1.mEnd == aTr2.mEnd;
+ }
+
+ bool LessThan(const TimeRange& aTr1, const TimeRange& aTr2) const {
+ return aTr1.mStart < aTr2.mStart;
+ }
+ };
+
+ AutoTArray<TimeRange,4> mRanges;
+
+ nsCOMPtr<nsISupports> mParent;
+
+public:
+ typedef nsTArray<TimeRange>::index_type index_type;
+ static const index_type NoIndex = index_type(-1);
+
+ index_type Find(double aTime, double aTolerance = 0);
+
+ bool Contains(double aStart, double aEnd) {
+ index_type target = Find(aStart);
+ if (target == NoIndex) {
+ return false;
+ }
+
+ return mRanges[target].mEnd >= aEnd;
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TimeRanges_h_
diff --git a/dom/html/ValidityState.cpp b/dom/html/ValidityState.cpp
new file mode 100644
index 000000000..b23eb6daa
--- /dev/null
+++ b/dom/html/ValidityState.cpp
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ValidityState.h"
+#include "mozilla/dom/ValidityStateBinding.h"
+
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ValidityState, mConstraintValidation)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ValidityState)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ValidityState)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ValidityState)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIDOMValidityState)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+ValidityState::ValidityState(nsIConstraintValidation* aConstraintValidation)
+ : mConstraintValidation(aConstraintValidation)
+{
+}
+
+NS_IMETHODIMP
+ValidityState::GetValueMissing(bool* aValueMissing)
+{
+ *aValueMissing = ValueMissing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ValidityState::GetTypeMismatch(bool* aTypeMismatch)
+{
+ *aTypeMismatch = TypeMismatch();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ValidityState::GetPatternMismatch(bool* aPatternMismatch)
+{
+ *aPatternMismatch = PatternMismatch();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ValidityState::GetTooLong(bool* aTooLong)
+{
+ *aTooLong = TooLong();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ValidityState::GetTooShort(bool* aTooShort)
+{
+ *aTooShort = TooShort();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ValidityState::GetRangeUnderflow(bool* aRangeUnderflow)
+{
+ *aRangeUnderflow = RangeUnderflow();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ValidityState::GetRangeOverflow(bool* aRangeOverflow)
+{
+ *aRangeOverflow = RangeOverflow();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ValidityState::GetStepMismatch(bool* aStepMismatch)
+{
+ *aStepMismatch = StepMismatch();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ValidityState::GetBadInput(bool* aBadInput)
+{
+ *aBadInput = BadInput();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ValidityState::GetCustomError(bool* aCustomError)
+{
+ *aCustomError = CustomError();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ValidityState::GetValid(bool* aValid)
+{
+ *aValid = Valid();
+ return NS_OK;
+}
+
+JSObject*
+ValidityState::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ValidityStateBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
+
diff --git a/dom/html/ValidityState.h b/dom/html/ValidityState.h
new file mode 100644
index 000000000..4dbb94aad
--- /dev/null
+++ b/dom/html/ValidityState.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_ValidityState_h
+#define mozilla_dom_ValidityState_h
+
+#include "nsIDOMValidityState.h"
+#include "nsIConstraintValidation.h"
+#include "nsWrapperCache.h"
+#include "js/TypeDecls.h"
+
+namespace mozilla {
+namespace dom {
+
+class ValidityState final : public nsIDOMValidityState,
+ public nsWrapperCache
+{
+ ~ValidityState() {}
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ValidityState)
+ NS_DECL_NSIDOMVALIDITYSTATE
+
+ friend class ::nsIConstraintValidation;
+
+ nsIConstraintValidation* GetParentObject() const {
+ return mConstraintValidation;
+ }
+
+ virtual JSObject* WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // Web IDL methods
+ bool ValueMissing() const
+ {
+ return GetValidityState(nsIConstraintValidation::VALIDITY_STATE_VALUE_MISSING);
+ }
+ bool TypeMismatch() const
+ {
+ return GetValidityState(nsIConstraintValidation::VALIDITY_STATE_TYPE_MISMATCH);
+ }
+ bool PatternMismatch() const
+ {
+ return GetValidityState(nsIConstraintValidation::VALIDITY_STATE_PATTERN_MISMATCH);
+ }
+ bool TooLong() const
+ {
+ return GetValidityState(nsIConstraintValidation::VALIDITY_STATE_TOO_LONG);
+ }
+ bool TooShort() const
+ {
+ return GetValidityState(nsIConstraintValidation::VALIDITY_STATE_TOO_SHORT);
+ }
+ bool RangeUnderflow() const
+ {
+ return GetValidityState(nsIConstraintValidation::VALIDITY_STATE_RANGE_UNDERFLOW);
+ }
+ bool RangeOverflow() const
+ {
+ return GetValidityState(nsIConstraintValidation::VALIDITY_STATE_RANGE_OVERFLOW);
+ }
+ bool StepMismatch() const
+ {
+ return GetValidityState(nsIConstraintValidation::VALIDITY_STATE_STEP_MISMATCH);
+ }
+ bool BadInput() const
+ {
+ return GetValidityState(nsIConstraintValidation::VALIDITY_STATE_BAD_INPUT);
+ }
+ bool CustomError() const
+ {
+ return GetValidityState(nsIConstraintValidation::VALIDITY_STATE_CUSTOM_ERROR);
+ }
+ bool Valid() const
+ {
+ return !mConstraintValidation || mConstraintValidation->IsValid();
+ }
+
+protected:
+ explicit ValidityState(nsIConstraintValidation* aConstraintValidation);
+
+ /**
+ * Helper function to get a validity state from constraint validation instance.
+ */
+ inline bool GetValidityState(nsIConstraintValidation::ValidityStateType aState) const
+ {
+ return mConstraintValidation &&
+ mConstraintValidation->GetValidityState(aState);
+ }
+
+ nsCOMPtr<nsIConstraintValidation> mConstraintValidation;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ValidityState_h
+
diff --git a/dom/html/VideoDocument.cpp b/dom/html/VideoDocument.cpp
new file mode 100644
index 000000000..1bd898564
--- /dev/null
+++ b/dom/html/VideoDocument.cpp
@@ -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/. */
+
+#include "MediaDocument.h"
+#include "nsGkAtoms.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentCreatorFunctions.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "nsIDocumentInlines.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/Element.h"
+
+namespace mozilla {
+namespace dom {
+
+class VideoDocument final : public MediaDocument
+{
+public:
+ virtual nsresult StartDocumentLoad(const char* aCommand,
+ nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener** aDocListener,
+ bool aReset = true,
+ nsIContentSink* aSink = nullptr);
+ virtual void SetScriptGlobalObject(nsIScriptGlobalObject* aScriptGlobalObject);
+
+protected:
+
+ // Sets document <title> to reflect the file name and description.
+ void UpdateTitle(nsIChannel* aChannel);
+
+ nsresult CreateSyntheticVideoDocument(nsIChannel* aChannel,
+ nsIStreamListener** aListener);
+
+ RefPtr<MediaDocumentStreamListener> mStreamListener;
+};
+
+nsresult
+VideoDocument::StartDocumentLoad(const char* aCommand,
+ nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener** aDocListener,
+ bool aReset,
+ nsIContentSink* aSink)
+{
+ nsresult rv =
+ MediaDocument::StartDocumentLoad(aCommand, aChannel, aLoadGroup, aContainer,
+ aDocListener, aReset, aSink);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStreamListener = new MediaDocumentStreamListener(this);
+
+ // Create synthetic document
+ rv = CreateSyntheticVideoDocument(aChannel,
+ getter_AddRefs(mStreamListener->mNextStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aDocListener = mStreamListener);
+ return rv;
+}
+
+void
+VideoDocument::SetScriptGlobalObject(nsIScriptGlobalObject* aScriptGlobalObject)
+{
+ // Set the script global object on the superclass before doing
+ // anything that might require it....
+ MediaDocument::SetScriptGlobalObject(aScriptGlobalObject);
+
+ if (aScriptGlobalObject) {
+ if (!nsContentUtils::IsChildOfSameType(this) &&
+ GetReadyStateEnum() != nsIDocument::READYSTATE_COMPLETE) {
+ LinkStylesheet(NS_LITERAL_STRING("resource://gre/res/TopLevelVideoDocument.css"));
+ LinkStylesheet(NS_LITERAL_STRING("chrome://global/skin/media/TopLevelVideoDocument.css"));
+ LinkScript(NS_LITERAL_STRING("chrome://global/content/TopLevelVideoDocument.js"));
+ }
+ BecomeInteractive();
+ }
+}
+
+nsresult
+VideoDocument::CreateSyntheticVideoDocument(nsIChannel* aChannel,
+ nsIStreamListener** aListener)
+{
+ // make our generic document
+ nsresult rv = MediaDocument::CreateSyntheticDocument();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Element* body = GetBodyElement();
+ if (!body) {
+ NS_WARNING("no body on video document!");
+ return NS_ERROR_FAILURE;
+ }
+
+ // make content
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::video, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+
+ RefPtr<HTMLMediaElement> element =
+ static_cast<HTMLMediaElement*>(NS_NewHTMLVideoElement(nodeInfo.forget(),
+ NOT_FROM_PARSER));
+ if (!element)
+ return NS_ERROR_OUT_OF_MEMORY;
+ element->SetAutoplay(true);
+ element->SetControls(true);
+ element->LoadWithChannel(aChannel, aListener);
+ UpdateTitle(aChannel);
+
+ if (nsContentUtils::IsChildOfSameType(this)) {
+ // Video documents that aren't toplevel should fill their frames and
+ // not have margins
+ element->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
+ NS_LITERAL_STRING("position:absolute; top:0; left:0; width:100%; height:100%"),
+ true);
+ }
+
+ return body->AppendChildTo(element, false);
+}
+
+void
+VideoDocument::UpdateTitle(nsIChannel* aChannel)
+{
+ if (!aChannel)
+ return;
+
+ nsAutoString fileName;
+ GetFileName(fileName, aChannel);
+ SetTitle(fileName);
+}
+
+} // namespace dom
+} // namespace mozilla
+
+nsresult
+NS_NewVideoDocument(nsIDocument** aResult)
+{
+ mozilla::dom::VideoDocument* doc = new mozilla::dom::VideoDocument();
+
+ NS_ADDREF(doc);
+ nsresult rv = doc->Init();
+
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(doc);
+ }
+
+ *aResult = doc;
+
+ return rv;
+}
diff --git a/dom/html/crashtests/1032654.html b/dom/html/crashtests/1032654.html
new file mode 100644
index 000000000..3067e1077
--- /dev/null
+++ b/dom/html/crashtests/1032654.html
@@ -0,0 +1 @@
+<template>&#x000d;#
diff --git a/dom/html/crashtests/1141260.html b/dom/html/crashtests/1141260.html
new file mode 100644
index 000000000..c5ced3a44
--- /dev/null
+++ b/dom/html/crashtests/1141260.html
@@ -0,0 +1,4 @@
+<img src="foo" srcset="bar">
+<script>
+ document.querySelector("img").removeAttribute('src');
+</script>
diff --git a/dom/html/crashtests/1228876.html b/dom/html/crashtests/1228876.html
new file mode 100644
index 000000000..b7beb645c
--- /dev/null
+++ b/dom/html/crashtests/1228876.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var a = document.createElement("select");
+ var f = document.createElement("optgroup");
+ var g = document.createElement("optgroup");
+ a.appendChild(f);
+ g.appendChild(document.createElement("option"));
+ f.appendChild(g);
+ a.appendChild(document.createElement("option"));
+ document.body.appendChild(a);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/html/crashtests/1230110.html b/dom/html/crashtests/1230110.html
new file mode 100644
index 000000000..9802dd775
--- /dev/null
+++ b/dom/html/crashtests/1230110.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+// This test case should not leak.
+function leak()
+{
+ var img = document.createElement("img");
+ var iframe = document.createElement("iframe");
+ img.appendChild(iframe);
+ document.body.appendChild(img);
+
+ document.addEventListener('Foo', function(){}, false);
+}
+</script>
+</head>
+<body onload="leak();"></body>
+</html>
diff --git a/dom/html/crashtests/1237633.html b/dom/html/crashtests/1237633.html
new file mode 100644
index 000000000..c235f0315
--- /dev/null
+++ b/dom/html/crashtests/1237633.html
@@ -0,0 +1 @@
+<img srcset="data:,a 2400w" sizes="(min-width: 1px) calc(300px - 100vw)">
diff --git a/dom/html/crashtests/1281972-1.html b/dom/html/crashtests/1281972-1.html
new file mode 100644
index 000000000..6fa5bb250
--- /dev/null
+++ b/dom/html/crashtests/1281972-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<body onload="window[0].document.forms[0].submit();">
+<iframe src="data:text/html;charset=UTF-8,<form accept-charset=HZ-GB-2312>"></iframe>
+</body>
diff --git a/dom/html/crashtests/1282894.html b/dom/html/crashtests/1282894.html
new file mode 100644
index 000000000..456a4541f
--- /dev/null
+++ b/dom/html/crashtests/1282894.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom() {
+ var table = document.createElement("table");
+ var cap = document.createElement("caption");
+ cap.appendChild(table)
+ table.caption = cap;
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/html/crashtests/1290904.html b/dom/html/crashtests/1290904.html
new file mode 100644
index 000000000..5ea23ba3b
--- /dev/null
+++ b/dom/html/crashtests/1290904.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <fieldset id="outer">
+ <fieldset id="inner">
+ </fieldset>
+ </fieldset>
+ </body>
+</html>
+<script>
+function appendTextareaToFieldset(fieldset) {
+ var textarea = document.createElement("textarea");
+ textarea.setAttribute("required", "");
+ fieldset.appendChild(textarea);
+}
+
+var innerFieldset = document.getElementById('inner');
+var outerFieldset = document.getElementById('outer');
+
+var fieldset = document.createElement('fieldset');
+appendTextareaToFieldset(fieldset);
+appendTextareaToFieldset(fieldset);
+appendTextareaToFieldset(fieldset);
+appendTextareaToFieldset(fieldset);
+
+// Adding a fieldset to a nested fieldset.
+innerFieldset.appendChild(fieldset);
+appendTextareaToFieldset(fieldset);
+appendTextareaToFieldset(fieldset);
+// This triggers mInvalidElementsCount checks in outer fieldset.
+appendTextareaToFieldset(outerFieldset);
+
+// Removing a fieldset from a nested fieldset.
+innerFieldset.removeChild(fieldset);
+// This triggers mInvalidElementsCount checks in outer fieldset.
+appendTextareaToFieldset(outerFieldset);
+</script>
diff --git a/dom/html/crashtests/1386905.html b/dom/html/crashtests/1386905.html
new file mode 100644
index 000000000..6ecc59e23
--- /dev/null
+++ b/dom/html/crashtests/1386905.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+document.documentElement.getBoundingClientRect()
+document.documentElement.innerHTML = "<input placeholder=e type=number readonly>"
+document.designMode = "on"
+document.execCommand("inserttext", false, "")
+document.designMode = "off"
+document.documentElement.style.display = 'none'
+</script>
+</head>
+</html>
diff --git a/dom/html/crashtests/257818-1.html b/dom/html/crashtests/257818-1.html
new file mode 100644
index 000000000..162390efa
--- /dev/null
+++ b/dom/html/crashtests/257818-1.html
@@ -0,0 +1,82 @@
+<html><head>
+<script type="text/javascript">
+function cE (v) {
+ return document.createElement(v)
+}
+function cTN (v) {
+ return document.createTextNode(v)
+}
+
+function OSXBarIcon(elt) {
+ this.element = elt;
+ this.labelNode = this.element.firstChild;
+ this.labelNodeParent = this.element;
+ this.labelNodeParent.removeChild(this.labelNode);
+
+ this.contents = [];
+ var kids = this.element.childNodes;
+ for(var i=0; i<kids.length; i++) this.contents[this.contents.length] = this.element.removeChild(kids[i]);
+ this.popupSubmenu = new OSXBarSubmenu(this);
+}
+
+function OSXBarSubmenu(icon) {
+ this.parentIcon = icon;
+ this.create();
+ this.addContent();
+}
+OSXBarSubmenu.prototype = {
+ create : function() {
+ var p = this.popupNode = document.createElement("div");
+ var b = document.getElementsByTagName("body").item(0);
+ if(b) b.appendChild(p);
+ this.popupNode.style.display = "none";
+ // Uncomment next line to fix the problem
+// var v = document.body.offsetWidth;
+ }
+};
+OSXBarSubmenu.prototype.addContent = function() {
+
+ // add popup label:
+ var label = document.createElement("div");
+ label.appendChild(document.createTextNode(this.parentIcon.label));
+ this.popupNode.appendChild(label);
+
+ // add <li> children to the popup:
+ var contents = this.parentIcon.contents;
+ for(var i=0; i<contents.length; i++) {
+ this.popupNode.appendChild(contents[i]);
+
+ }
+};
+
+</script>
+
+<script type="text/javascript">
+function createControlPanel() {
+ var bar = document.getElementById("navigation");
+ var item = cE("li");
+ item.appendChild(cTN("aaa"));
+ var textfield = cE("input");
+ textfield.value = 0;
+ item.appendChild(textfield);
+ bar.insertBefore(item, bar.firstChild);
+}
+
+window.addEventListener("load", createControlPanel, false);
+</script>
+
+<script type="text/javascript">
+function ssload() {
+ new OSXBarIcon(document.getElementById("navigation").childNodes[0]);
+}
+window.addEventListener("load",ssload,false);
+
+
+</script>
+</head>
+
+<body>
+<ul id="navigation"></ul>
+</body></html>
+
+
diff --git a/dom/html/crashtests/285166-1.html b/dom/html/crashtests/285166-1.html
new file mode 100644
index 000000000..8a73d7d74
--- /dev/null
+++ b/dom/html/crashtests/285166-1.html
@@ -0,0 +1,3 @@
+<script>
+document.createElement("#text");
+</script>
diff --git a/dom/html/crashtests/294235-1.html b/dom/html/crashtests/294235-1.html
new file mode 100644
index 000000000..00eed38f4
--- /dev/null
+++ b/dom/html/crashtests/294235-1.html
@@ -0,0 +1,14 @@
+<html>
+ <head>
+ <title>bug 294235</title>
+ <script>
+ function countdown(){
+ count2.innerHTML="foo";
+ }
+ document.links.length;
+ </script>
+ </head>
+ <body>
+ <strong><div id="count2"><script>countdown();</script><noscript>bar</noscript></div></strong>
+ </body>
+</html>
diff --git a/dom/html/crashtests/307616-1.html b/dom/html/crashtests/307616-1.html
new file mode 100644
index 000000000..8f28ddd6e
--- /dev/null
+++ b/dom/html/crashtests/307616-1.html
@@ -0,0 +1,8 @@
+<html>
+<head><title>Testcase for assertion</title></head>
+<body>
+
+<input type="image" src="./nosuch.gif" alt="Search">
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/html/crashtests/324918-1.xhtml b/dom/html/crashtests/324918-1.xhtml
new file mode 100644
index 000000000..921714cff
--- /dev/null
+++ b/dom/html/crashtests/324918-1.xhtml
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+
+function init()
+{
+ var sel = document.getElementById("sel");
+ var div = document.getElementById("div");
+ var newo = document.createElementNS("http://www.w3.org/1999/xhtml", "option");
+
+ sel.appendChild(div);
+ div.appendChild(newo);
+ sel.removeChild(div);
+}
+
+</script>
+</head>
+<body onload="init();">
+
+<div id="div"><option></option></div>
+
+<select id="sel"></select>
+
+</body>
+</html>
diff --git a/dom/html/crashtests/338649-1.xhtml b/dom/html/crashtests/338649-1.xhtml
new file mode 100644
index 000000000..b0bf3186f
--- /dev/null
+++ b/dom/html/crashtests/338649-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>ASSERTION: Options collection broken</title>
+</head>
+
+<body>
+
+<div>
+
+<select>
+ <option id="n29">B
+ <optgroup label="middle" id="n20">
+ <option>N</option>
+ </optgroup>
+ </option>
+ <option>M</option>
+</select>
+
+</div>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/html/crashtests/339501-1.xhtml b/dom/html/crashtests/339501-1.xhtml
new file mode 100644
index 000000000..1a231ee64
--- /dev/null
+++ b/dom/html/crashtests/339501-1.xhtml
@@ -0,0 +1,33 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script>
+
+function boom()
+{
+ document.addEventListener("DOMNodeRemoved", foo, false);
+ remove(document.getElementById("A"));
+ document.removeEventListener("DOMNodeRemoved", foo, false);
+
+ function foo()
+ {
+ document.removeEventListener("DOMNodeRemoved", foo, false);
+ remove(document.getElementById("B"));
+ }
+}
+
+
+window.addEventListener("load", boom, false);
+
+function remove(q1) { q1.parentNode.removeChild(q1); }
+
+</script>
+
+</head>
+
+<body>
+
+<select><option id="A">A</option><option id="B">B</option></select>
+
+</body>
+</html>
diff --git a/dom/html/crashtests/339501-2.xhtml b/dom/html/crashtests/339501-2.xhtml
new file mode 100644
index 000000000..6a1835fb7
--- /dev/null
+++ b/dom/html/crashtests/339501-2.xhtml
@@ -0,0 +1,33 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script>
+
+function boom()
+{
+ document.addEventListener("DOMNodeRemoved", foo, false);
+ remove(document.getElementById("B"));
+ document.removeEventListener("DOMNodeRemoved", foo, false);
+
+ function foo()
+ {
+ document.removeEventListener("DOMNodeRemoved", foo, false);
+ remove(document.getElementById("A"));
+ }
+}
+
+
+window.addEventListener("load", boom, false);
+
+function remove(q1) { q1.parentNode.removeChild(q1); }
+
+</script>
+
+</head>
+
+<body>
+
+<select><option id="A">A</option><option id="B">B</option></select>
+
+</body>
+</html>
diff --git a/dom/html/crashtests/378993-1.xhtml b/dom/html/crashtests/378993-1.xhtml
new file mode 100644
index 000000000..99cbb01da
--- /dev/null
+++ b/dom/html/crashtests/378993-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+<script>
+document.createElement('AREA');
+</script>
+</body>
+</html>
diff --git a/dom/html/crashtests/382568-1-inner.xhtml b/dom/html/crashtests/382568-1-inner.xhtml
new file mode 100644
index 000000000..a3685f4c6
--- /dev/null
+++ b/dom/html/crashtests/382568-1-inner.xhtml
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!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"> <head>
+<title>Test</title>
+<script type="text/javascript"><![CDATA[
+
+function onAttrModified(evt) {
+// window.alert("Mutation event fired within the frame code.");
+// evt.target.focus();
+// evt.target.blur();
+ evt.target.style.background = 'green';
+ bounce(evt.target);
+// evt.target.normalize();
+// bounce(evt.target.parentNode);
+}
+function die(n) {
+ p = n.parentNode;
+ p.removeChild(n);
+}
+
+function bounce(n) {
+ p = n.parentNode;
+ p.removeChild(n);
+ p.appendChild(n);
+}
+
+
+function test_AttrModified() {
+ var x = document.getElementById("x");
+ x.addEventListener("DOMAttrModified", onAttrModified, false);
+ bounce(x);
+}
+
+function test() {
+ setTimeout(test_AttrModified, 3000);
+}
+]]></script>
+</head>
+
+<body onload="test()">
+<h1>TestCase for unsafe mutable events from textarea</h1>
+<p>Please wait for 3 seconds after document was loaded,
+if your browser is vulnerable, it may stop responding
+to keyboard and mouse event
+and most likely it will eventually crash (may take a
+while for debug builds).</p>
+<p>
+<textarea id="x"></textarea>
+</p>
+</body> </html>
diff --git a/dom/html/crashtests/382568-1.html b/dom/html/crashtests/382568-1.html
new file mode 100644
index 000000000..c10c71fd1
--- /dev/null
+++ b/dom/html/crashtests/382568-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 3500);
+</script>
+<body>
+<iframe src="382568-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/dom/html/crashtests/383137.xhtml b/dom/html/crashtests/383137.xhtml
new file mode 100644
index 000000000..87d853c03
--- /dev/null
+++ b/dom/html/crashtests/383137.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<script id="script" xmlns="http://www.w3.org/1999/xhtml">//<![CDATA[
+function doe(){
+document.documentElement.setAttribute('style', '');
+}
+
+setTimeout(doe,100);
+//]]></script>
+
+<style style="overflow: scroll; display: block;">
+style::before {content: counter(section); }
+</style>
+</html> \ No newline at end of file
diff --git a/dom/html/crashtests/388183-1.html b/dom/html/crashtests/388183-1.html
new file mode 100644
index 000000000..fcca410e9
--- /dev/null
+++ b/dom/html/crashtests/388183-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html contenteditable="true">
+<head>
+</head>
+<body>
+<div contenteditable="true"></div>
+</body>
+</html>
diff --git a/dom/html/crashtests/395340-1.html b/dom/html/crashtests/395340-1.html
new file mode 100644
index 000000000..ddbccfe96
--- /dev/null
+++ b/dom/html/crashtests/395340-1.html
@@ -0,0 +1,28 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var div1 = document.createElement("div");
+ div1.style.counterIncrement = "chicken";
+ div1.contentEditable = "true";
+
+ var div2 = document.createElement("div");
+ div2.style.counterIncrement = "chicken";
+
+ document.body.style.content = "'A'";
+
+ div1.appendChild(div2);
+ document.body.appendChild(div1);
+ div1.removeChild(div2);
+}
+
+</script>
+
+</head>
+
+<body onload="boom();">
+
+</body>
+</html>
diff --git a/dom/html/crashtests/399694-1.html b/dom/html/crashtests/399694-1.html
new file mode 100644
index 000000000..e6db2342b
--- /dev/null
+++ b/dom/html/crashtests/399694-1.html
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+function boom()
+{
+ var legend = document.createElementNS("http://www.w3.org/1999/xhtml", "legend")
+ var input = document.getElementById("input");
+ input.appendChild(legend);
+ legend.focus();
+
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload="setTimeout(boom, 3);" contenteditable="true">
+
+<input contenteditable="false"><input id="input"><q><div></div></q>
+
+</body>
+</html>
diff --git a/dom/html/crashtests/407053.html b/dom/html/crashtests/407053.html
new file mode 100644
index 000000000..b80a23172
--- /dev/null
+++ b/dom/html/crashtests/407053.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.execCommand('copy', false, '');">
+<div contenteditable="true"></div>
+</body>
+</html>
diff --git a/dom/html/crashtests/423371-1.html b/dom/html/crashtests/423371-1.html
new file mode 100644
index 000000000..0069cf95d
--- /dev/null
+++ b/dom/html/crashtests/423371-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div style="position:fixed; top:100px;" id="d">hello</div>
+<script>
+document.write(document.getElementById("d").offsetTop);
+</script>
+</body>
+</html>
diff --git a/dom/html/crashtests/448564.html b/dom/html/crashtests/448564.html
new file mode 100644
index 000000000..f31830afa
--- /dev/null
+++ b/dom/html/crashtests/448564.html
@@ -0,0 +1,7 @@
+<s>
+<form>a</form>
+<iframe></iframe>
+<script src=a></script>
+<form></form>
+<table>
+<optgroup>
diff --git a/dom/html/crashtests/451123-1.html b/dom/html/crashtests/451123-1.html
new file mode 100644
index 000000000..abf23c1e7
--- /dev/null
+++ b/dom/html/crashtests/451123-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+<body>
+<input type="file" type="file">
+</body>
+</html>
diff --git a/dom/html/crashtests/453406-1.html b/dom/html/crashtests/453406-1.html
new file mode 100644
index 000000000..4017c97ca
--- /dev/null
+++ b/dom/html/crashtests/453406-1.html
@@ -0,0 +1,34 @@
+<html>
+<head>
+<script type="text/javascript">
+function boom()
+{
+ var r = document.documentElement;
+ while (r.firstChild)
+ r.removeChild(r.firstChild);
+
+ var b = document.createElement("BODY");
+ var s = document.createElement("SCRIPT");
+ var f1 = document.createElement("FORM");
+ var i = document.createElement("INPUT");
+ var br = document.createElement("BR");
+ var f2 = document.createElement("FORM");
+ var span = document.createElement("SPAN");
+ f1.appendChild(i);
+ f1.appendChild(br);
+ s.appendChild(f1);
+ b.appendChild(s);
+ f2.appendChild(span);
+ b.appendChild(f2)
+ document.documentElement.appendChild(b);
+ b.contentEditable = "true";
+ document.execCommand("indent", false, null);
+ b.contentEditable = "false";
+ span.appendChild(i);
+ i.parentNode.removeChild(i);
+}
+</script>
+</head>
+<body onload="boom();">
+</body>
+</html>
diff --git a/dom/html/crashtests/464197-1.html b/dom/html/crashtests/464197-1.html
new file mode 100644
index 000000000..785686023
--- /dev/null
+++ b/dom/html/crashtests/464197-1.html
@@ -0,0 +1,23 @@
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+var i = 0;
+
+function boom()
+{
+ var s = document.createElement("span");
+ s.innerHTML = "<audio src='javascript:5'><\/audio>";
+ s.innerHTML = "";
+
+ if (++i < 10)
+ setTimeout(boom, 0);
+ else
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="boom();">
+</body>
+</html>
diff --git a/dom/html/crashtests/465466-1.xhtml b/dom/html/crashtests/465466-1.xhtml
new file mode 100644
index 000000000..4e1258702
--- /dev/null
+++ b/dom/html/crashtests/465466-1.xhtml
@@ -0,0 +1,23 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="u">
+<content><foopy><children xmlns="http://www.mozilla.org/xbl"/></foopy></content>
+</binding></bindings>
+
+
+<script type="text/javascript">
+
+function boom()
+{
+ var f = document.getElementById("f");
+ var anon = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(f))[0];
+ document.body.removeChild(f);
+ anon.appendChild(document.createElement("label"));
+}
+
+</script>
+</head>
+
+<body onload="boom();"><form id="f" style="-moz-binding: url(#u);"><label></label></form></body>
+</html>
diff --git a/dom/html/crashtests/468562-1.html b/dom/html/crashtests/468562-1.html
new file mode 100644
index 000000000..81123fe2e
--- /dev/null
+++ b/dom/html/crashtests/468562-1.html
@@ -0,0 +1,6 @@
+<html>
+<head></head>
+<body>
+<table><script>document.write("Q");</script><link>
+</body>
+</html>
diff --git a/dom/html/crashtests/468562-2.html b/dom/html/crashtests/468562-2.html
new file mode 100644
index 000000000..e0db5cd97
--- /dev/null
+++ b/dom/html/crashtests/468562-2.html
@@ -0,0 +1,6 @@
+<html>
+<head></head>
+<body>
+<table><script>document.write("Q");</script><link></table>
+</body>
+</html>
diff --git a/dom/html/crashtests/494225.html b/dom/html/crashtests/494225.html
new file mode 100644
index 000000000..a78051d2a
--- /dev/null
+++ b/dom/html/crashtests/494225.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html><head><script>
+ var x = "f: '" + document.fgColor +
+ "' b: '" + document.bgColor +
+ "' l: '" + document.linkColor +
+ "' a: '" + document.alinkColor +
+ "' v: '" + document.vlinkColor + "'";
+</script></head><body
+onload="document.body.appendChild(document.createTextNode(x))"
+></body></html>
diff --git a/dom/html/crashtests/495543.svg b/dom/html/crashtests/495543.svg
new file mode 100644
index 000000000..b795796ad
--- /dev/null
+++ b/dom/html/crashtests/495543.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<script type="text/javascript">
+
+function boom()
+{
+ var htmlBody = document.createElementNS("http://www.w3.org/1999/xhtml", "body");
+ document.documentElement.appendChild(htmlBody);
+ htmlBody.setAttribute('vlink', "transparent");
+ document.height;
+ document.vlinkColor;
+}
+
+window.addEventListener("load", boom, false);
+</script>
+
+</svg>
diff --git a/dom/html/crashtests/495546-1.html b/dom/html/crashtests/495546-1.html
new file mode 100644
index 000000000..0547b9867
--- /dev/null
+++ b/dom/html/crashtests/495546-1.html
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var video = document.createElement("video");
+ var source = document.createElement("source");
+ source.setAttributeNS(null, "src", "http://127.0.0.1/");
+ video.appendChild(source);
+ document.body.appendChild(video);
+ setTimeout(function() { document.body.removeChild(video); }, 20);
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/html/crashtests/504183-1.html b/dom/html/crashtests/504183-1.html
new file mode 100644
index 000000000..e44db4152
--- /dev/null
+++ b/dom/html/crashtests/504183-1.html
@@ -0,0 +1,12 @@
+<html class="reftest-wait">
+<input id="a" type="file">
+<script>
+function doe() {
+var elem = document.getElementById('a');
+elem.style.display = "none";
+elem.focus();
+document.documentElement.className = "";
+}
+setTimeout(doe, 0);
+</script>
+</html>
diff --git a/dom/html/crashtests/515829-1.html b/dom/html/crashtests/515829-1.html
new file mode 100644
index 000000000..e2fc655c0
--- /dev/null
+++ b/dom/html/crashtests/515829-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body onload="document.getElementById('x').innerHTML = '<button></button>';">
+<form><div id="x"><button></button></div><button></button></form>
+</body>
+</html>
diff --git a/dom/html/crashtests/515829-2.html b/dom/html/crashtests/515829-2.html
new file mode 100644
index 000000000..6de6d5986
--- /dev/null
+++ b/dom/html/crashtests/515829-2.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body onload="document.getElementById('x').innerHTML = '<button></button>';">
+<form><div id="x"><button></button></div><button></button><input type="image"></form>
+</body>
+</html>
diff --git a/dom/html/crashtests/564461.xhtml b/dom/html/crashtests/564461.xhtml
new file mode 100644
index 000000000..9a7bfeb24
--- /dev/null
+++ b/dom/html/crashtests/564461.xhtml
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="editable"><content><div contenteditable="true" xmlns="http://www.w3.org/1999/xhtml"></div></content></binding>
+</bindings>
+
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ while (document.documentElement.firstChild) document.documentElement.removeChild(document.documentElement.firstChild);
+
+ document.addEventListener("DOMSubtreeModified", function(){}, false);
+ document.addEventListener("DOMNodeInsertedIntoDocument", function(){}, false);
+ document.documentElement.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "body"));
+ document.body.style.MozBinding = 'url(#editable)';
+}
+
+]]>
+</script></head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/html/crashtests/570566-1.html b/dom/html/crashtests/570566-1.html
new file mode 100644
index 000000000..70ec003a6
--- /dev/null
+++ b/dom/html/crashtests/570566-1.html
@@ -0,0 +1,2 @@
+<body onload="document.getElementById('i').removeAttribute('type')"><input id=i type=image></body>
+
diff --git a/dom/html/crashtests/571428-1.html b/dom/html/crashtests/571428-1.html
new file mode 100644
index 000000000..ae2b960ca
--- /dev/null
+++ b/dom/html/crashtests/571428-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function boom()
+{
+ var i = document.getElementById("i");
+ i.setAttribute("type", "button");
+ i.removeAttribute("type");
+}
+</script>
+</head>
+<body onload="boom();"><input id="i"></body>
+</html>
diff --git a/dom/html/crashtests/580507-1.xhtml b/dom/html/crashtests/580507-1.xhtml
new file mode 100644
index 000000000..eff3fb255
--- /dev/null
+++ b/dom/html/crashtests/580507-1.xhtml
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var input = document.getElementById("i");
+ input.setAttribute('type', "image");
+ input.removeAttribute('type');
+ input.placeholder = "Y";
+}
+
+</script>
+</head>
+
+<body onload="boom();"><input id="i" /></body>
+</html>
diff --git a/dom/html/crashtests/590387.html b/dom/html/crashtests/590387.html
new file mode 100644
index 000000000..50cd05ac9
--- /dev/null
+++ b/dom/html/crashtests/590387.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+document.createElement("div").innerHTML = "<output form=x>";
+</script>
+</head>
+</html>
diff --git a/dom/html/crashtests/596785-1.html b/dom/html/crashtests/596785-1.html
new file mode 100644
index 000000000..5d830628b
--- /dev/null
+++ b/dom/html/crashtests/596785-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <body onload="document.getElementById('i').type = 'image';
+ document.documentElement.className = '';">
+ <form>
+ <input id='i' required>
+ </form>
+ </body>
+</html>
diff --git a/dom/html/crashtests/596785-2.html b/dom/html/crashtests/596785-2.html
new file mode 100644
index 000000000..18cfe2788
--- /dev/null
+++ b/dom/html/crashtests/596785-2.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <body onload="document.getElementById('i').type = 'text';
+ document.documentElement.className = '';">
+ <form>
+ <input id='i' type='image' required>
+ </form>
+ </body>
+</html>
diff --git a/dom/html/crashtests/601422.html b/dom/html/crashtests/601422.html
new file mode 100644
index 000000000..d3468a306
--- /dev/null
+++ b/dom/html/crashtests/601422.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var frame = document.getElementById("f");
+ var frameDoc = frame.contentDocument;
+ document.body.removeChild(frame);
+ try { frameDoc.shrinkToFit(); }catch(e){}
+ try { frameDoc.restoreImageTo(1,1); }catch(e){}
+ try { frameDoc.restoreImage(); }catch(e){}
+ try { frameDoc.toggleImageSize(); }catch(e){}
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<iframe id="f" src="data:image/gif;base64,R0lGODlhAQABAID/AP///wAAACwAAAAAAQABAAACAkQBADs="></iframe>
+</body>
+</html>
diff --git a/dom/html/crashtests/602117.html b/dom/html/crashtests/602117.html
new file mode 100644
index 000000000..0d1818b2a
--- /dev/null
+++ b/dom/html/crashtests/602117.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+ <script>
+ var isindex = document.createElement("isindex");
+ isindex.setAttribute("form", "f");
+ isindex.form;
+ </script>
+</html>
diff --git a/dom/html/crashtests/604807.html b/dom/html/crashtests/604807.html
new file mode 100644
index 000000000..a519a778f
--- /dev/null
+++ b/dom/html/crashtests/604807.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script>
+try {
+ var selectElem = document.createElementNS("http://www.w3.org/1999/xhtml", "select");
+ selectElem.QueryInterface(Components.interfaces.nsISelectElement);
+ selectElem.getOptionIndex(null, 0, true);
+} catch (e) {
+}
+</script>
diff --git a/dom/html/crashtests/605264.html b/dom/html/crashtests/605264.html
new file mode 100644
index 000000000..3c1db3578
--- /dev/null
+++ b/dom/html/crashtests/605264.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script>
+
+var v = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
+v.QueryInterface(Components.interfaces.nsIObserver);
+v.observe(null, null, null);
+
+</script>
diff --git a/dom/html/crashtests/606430-1.html b/dom/html/crashtests/606430-1.html
new file mode 100644
index 000000000..c347e3c9f
--- /dev/null
+++ b/dom/html/crashtests/606430-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var r = document.documentElement;
+ var i = document.getElementById("i");
+
+ document.removeChild(r);
+ document.appendChild(r);
+ w("dump('A\\n')");
+ document.removeChild(r);
+ w("dump('B\\n')");
+ document.appendChild(r);
+
+ function w(s)
+ {
+ var ns = document.createElement("script");
+ var nt = document.createTextNode(s);
+ ns.appendChild(nt);
+ i.appendChild(ns);
+ }
+}
+
+</script>
+</head>
+
+<body onload="boom();"><input id="i"></body>
+</html>
diff --git a/dom/html/crashtests/613027.html b/dom/html/crashtests/613027.html
new file mode 100644
index 000000000..a866860f1
--- /dev/null
+++ b/dom/html/crashtests/613027.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var a = document.createElementNS("http://www.w3.org/1999/xhtml", "fieldset");
+ var b = document.createElementNS("http://www.w3.org/1999/xhtml", "legend");
+ var c = document.createElementNS("http://www.w3.org/1999/xhtml", "input");
+
+ a.appendChild(b);
+ a.appendChild(c);
+ a.removeChild(b);
+ c.expandoQ = a;
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/html/crashtests/614279.html b/dom/html/crashtests/614279.html
new file mode 100644
index 000000000..ff1c50516
--- /dev/null
+++ b/dom/html/crashtests/614279.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var a = document.createElementNS("http://www.w3.org/1999/xhtml", "datalist");
+ var b = document.createElementNS("http://www.w3.org/1999/xhtml", "option");
+
+ a.appendChild(b);
+ b.expando = a.options;
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/html/crashtests/614988-1.html b/dom/html/crashtests/614988-1.html
new file mode 100644
index 000000000..931f83ac7
--- /dev/null
+++ b/dom/html/crashtests/614988-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<head><script>window.addEventListener("load", function() { var t = document.getElementById("t"); t.appendChild(t.previousSibling); }, false);</script></head>
+<body><fieldset><legend></legend><textarea id="t"></textarea></fieldset></body>
+</html>
diff --git a/dom/html/crashtests/616401.html b/dom/html/crashtests/616401.html
new file mode 100644
index 000000000..eb5a0bcf4
--- /dev/null
+++ b/dom/html/crashtests/616401.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<script>
+var c = document.createElement("canvas");
+c.getContext("experimental-webgl", {
+ get a() { throw 7; },
+ get b() { throw 8; }
+});
+</script>
diff --git a/dom/html/crashtests/620078-1.html b/dom/html/crashtests/620078-1.html
new file mode 100644
index 000000000..39462706c
--- /dev/null
+++ b/dom/html/crashtests/620078-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var frame = document.getElementById("f");
+ var frameRoot = frame.contentDocument.documentElement;
+ document.body.removeChild(frame);
+ var i = document.createElementNS("http://www.w3.org/1999/xhtml", "input");
+ i.setAttribute("autofocus", "true");
+ frameRoot.appendChild(i);
+}
+
+</script>
+</head>
+
+<body onload="boom();"><iframe id="f" src="data:text/html,"></iframe></body>
+</html>
diff --git a/dom/html/crashtests/620078-2.html b/dom/html/crashtests/620078-2.html
new file mode 100644
index 000000000..1be23fa1f
--- /dev/null
+++ b/dom/html/crashtests/620078-2.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+ <body onload='document.cloneNode(1)'>
+ <button autofocus></button>
+ </body>
+</html>
diff --git a/dom/html/crashtests/631421.html b/dom/html/crashtests/631421.html
new file mode 100644
index 000000000..e4a7b9192
--- /dev/null
+++ b/dom/html/crashtests/631421.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+"use strict";
+
+var f2;
+
+function newIframe()
+{
+ var f = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
+ f.setAttributeNS(null, "src", "631421.png");
+ document.body.appendChild(f);
+ return f;
+}
+
+function b1()
+{
+ void newIframe();
+ f2 = newIframe();
+ setTimeout(b2, 0);
+}
+
+function b2()
+{
+ document.body.removeChild(f2);
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="b1();"></body>
+</html>
diff --git a/dom/html/crashtests/631421.png b/dom/html/crashtests/631421.png
new file mode 100644
index 000000000..ef350c467
--- /dev/null
+++ b/dom/html/crashtests/631421.png
Binary files differ
diff --git a/dom/html/crashtests/673853.html b/dom/html/crashtests/673853.html
new file mode 100644
index 000000000..1325fa9ed
--- /dev/null
+++ b/dom/html/crashtests/673853.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var otherDoc = document.implementation.createDocument('', '', null);
+ var input = otherDoc.createElementNS("http://www.w3.org/1999/xhtml", "input");
+ var form = otherDoc.createElementNS("http://www.w3.org/1999/xhtml", "form");
+ input.setAttributeNS(null, "form", "x");
+ form.setAttributeNS(null, "id", "x");
+ input.appendChild(form);
+ otherDoc.appendChild(input);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/html/crashtests/680922-1.xul b/dom/html/crashtests/680922-1.xul
new file mode 100644
index 000000000..2fd1784f6
--- /dev/null
+++ b/dom/html/crashtests/680922-1.xul
@@ -0,0 +1,9 @@
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+<box id="e">
+<box id="b" name="e" style="-moz-binding:url(680922-binding.xml#xbl);">
+</box>
+</box>
+
+</window>
diff --git a/dom/html/crashtests/680922-binding.xml b/dom/html/crashtests/680922-binding.xml
new file mode 100644
index 000000000..86cb0932b
--- /dev/null
+++ b/dom/html/crashtests/680922-binding.xml
@@ -0,0 +1,7 @@
+<bindings xmlns="http://www.mozilla.org/xbl" xmlns:html="http://www.w3.org/1999/xhtml">
+<binding id="xbl" inheritstyle="false">
+<content>
+<html:form id="g" observes="b"/>
+</content>
+</binding>
+</bindings>
diff --git a/dom/html/crashtests/682058.xhtml b/dom/html/crashtests/682058.xhtml
new file mode 100644
index 000000000..95e7da98f
--- /dev/null
+++ b/dom/html/crashtests/682058.xhtml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <body onload="test()">
+ <script>
+ function test() {
+ document.body.innerHTML = "Foobar";
+ }
+ document.addEventListener("DOMNodeRemoved", function() {});
+ </script>
+ </body>
+</html>
diff --git a/dom/html/crashtests/682460.html b/dom/html/crashtests/682460.html
new file mode 100644
index 000000000..91306be71
--- /dev/null
+++ b/dom/html/crashtests/682460.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var f = function() {
+ document.documentElement.offsetHeight;
+ };
+ window.addEventListener("DOMSubtreeModified", f, true);
+
+ document.getElementsByTagName("table")[0].setAttribute("cellpadding", "2");
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<table><tr><td></td></tr></table>
+</body>
+</html>
diff --git a/dom/html/crashtests/68912-1.html b/dom/html/crashtests/68912-1.html
new file mode 100644
index 000000000..bdd2ab461
--- /dev/null
+++ b/dom/html/crashtests/68912-1.html
@@ -0,0 +1,24 @@
+<html>
+<head>
+<title>Crash TR.cells = null</title>
+<script language="javascript">
+function crashme()
+{
+ var elm = document.createElement('tr');
+
+ elm.cells = null;
+}
+
+</script>
+</head>
+<body onload="crashme()">
+
+<p>
+This test case creates a TR element then tries to assign to the cells property
+</p>
+<p>
+Crash
+</p>
+
+</body>
+</html>
diff --git a/dom/html/crashtests/738744.xhtml b/dom/html/crashtests/738744.xhtml
new file mode 100644
index 000000000..7f91d0149
--- /dev/null
+++ b/dom/html/crashtests/738744.xhtml
@@ -0,0 +1,4 @@
+<input xmlns="http://www.w3.org/1999/xhtml" form="f">
+ <form id="f" />
+ <input form="f" />
+</input>
diff --git a/dom/html/crashtests/741218.json b/dom/html/crashtests/741218.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/dom/html/crashtests/741218.json
@@ -0,0 +1 @@
+{}
diff --git a/dom/html/crashtests/741218.json^headers^ b/dom/html/crashtests/741218.json^headers^
new file mode 100644
index 000000000..7b5e82d4b
--- /dev/null
+++ b/dom/html/crashtests/741218.json^headers^
@@ -0,0 +1 @@
+Content-Type: application/json
diff --git a/dom/html/crashtests/741250.xhtml b/dom/html/crashtests/741250.xhtml
new file mode 100644
index 000000000..e18a9409f
--- /dev/null
+++ b/dom/html/crashtests/741250.xhtml
@@ -0,0 +1,9 @@
+<input form="f" id="x" xmlns="http://www.w3.org/1999/xhtml">
+<form id="f"></form>
+<script>
+window.addEventListener("load", function() {
+ var x = document.getElementById("x");
+ x.appendChild(x.cloneNode(true));
+}, false);
+</script>
+</input>
diff --git a/dom/html/crashtests/795221-1.html b/dom/html/crashtests/795221-1.html
new file mode 100644
index 000000000..70e400bed
--- /dev/null
+++ b/dom/html/crashtests/795221-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<style>
+ div { }
+</style>
+<script>
+ document.styleSheets[0].cssRules[0].style.foo = document;
+</script>
diff --git a/dom/html/crashtests/795221-2.html b/dom/html/crashtests/795221-2.html
new file mode 100644
index 000000000..fc2aa7d50
--- /dev/null
+++ b/dom/html/crashtests/795221-2.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<style>
+ @media all {
+ div { }
+ }
+</style>
+<script>
+ document.styleSheets[0].cssRules[0].cssRules[0].style.foo = document;
+</script>
diff --git a/dom/html/crashtests/795221-3.html b/dom/html/crashtests/795221-3.html
new file mode 100644
index 000000000..93348af0b
--- /dev/null
+++ b/dom/html/crashtests/795221-3.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<body>
+<script>
+ // Create the stylesheet via script, so that the parser's preloading doesn't
+ // make the CSS loader hold on to the sheet we're _not_ trying to form a
+ // cycle through, thus accidentally avoiding the cycle
+ var link = document.createElement("link");
+ link.setAttribute("rel", "stylesheet");
+ link.setAttribute("href", "data:text/css,div { }");
+ link.onload = function () {
+ document.styleSheets[0].cssRules[0].style.foo = document;
+ }
+ document.body.appendChild(link);
+</script>
diff --git a/dom/html/crashtests/795221-4.html b/dom/html/crashtests/795221-4.html
new file mode 100644
index 000000000..476dd3467
--- /dev/null
+++ b/dom/html/crashtests/795221-4.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<svg>
+ <style>
+ div { }
+ </style>
+</svg>
+<script>
+ document.styleSheets[0].cssRules[0].style.foo = document;
+</script>
diff --git a/dom/html/crashtests/795221-5.xml b/dom/html/crashtests/795221-5.xml
new file mode 100644
index 000000000..286dcff0d
--- /dev/null
+++ b/dom/html/crashtests/795221-5.xml
@@ -0,0 +1,6 @@
+<?xml-stylesheet href="data:text/css,div {}"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <script>
+ document.styleSheets[0].cssRules[0].style.foo = document;
+ </script>
+</html>
diff --git a/dom/html/crashtests/798802-1.html b/dom/html/crashtests/798802-1.html
new file mode 100644
index 000000000..92ab50fd8
--- /dev/null
+++ b/dom/html/crashtests/798802-1.html
@@ -0,0 +1,18 @@
+<html>
+ <head>
+ <script>
+ onload = function() {
+ var canvas2d = document.createElement('canvas')
+ canvas2d.setAttribute('width', 0)
+ document.body.appendChild(canvas2d)
+ var ctx2d = canvas2d.getContext('2d')
+ ctx2d.fillStyle = 'black'
+ var gl = document.createElement('canvas').getContext('experimental-webgl')
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas2d)
+ ctx2d.fillRect(0, 0, 1, 1)
+ }
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/dom/html/crashtests/811226.html b/dom/html/crashtests/811226.html
new file mode 100644
index 000000000..990ef9af5
--- /dev/null
+++ b/dom/html/crashtests/811226.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.getElementById('s').onerror;">
+<span id="s" onerror="0;"></span>
+</body>
+</html>
diff --git a/dom/html/crashtests/819745.html b/dom/html/crashtests/819745.html
new file mode 100644
index 000000000..389e6b8c1
--- /dev/null
+++ b/dom/html/crashtests/819745.html
@@ -0,0 +1,5 @@
+<!doctype="html">
+<body>
+<script>
+ document.body.onerror = null;
+</script>
diff --git a/dom/html/crashtests/828180.html b/dom/html/crashtests/828180.html
new file mode 100644
index 000000000..055c8a34b
--- /dev/null
+++ b/dom/html/crashtests/828180.html
@@ -0,0 +1,5 @@
+<script>
+var table = document.createElement("table");
+table.tHead = null;
+table.tFoot = null;
+</script>
diff --git a/dom/html/crashtests/828472.html b/dom/html/crashtests/828472.html
new file mode 100644
index 000000000..59ce4d280
--- /dev/null
+++ b/dom/html/crashtests/828472.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<input type='date' value='2013-01-01'>
+</body>
+</html>
diff --git a/dom/html/crashtests/837033.html b/dom/html/crashtests/837033.html
new file mode 100644
index 000000000..772d24014
--- /dev/null
+++ b/dom/html/crashtests/837033.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.createElement('button').validity.x = null;"></body>
+</html>
diff --git a/dom/html/crashtests/838256-1.html b/dom/html/crashtests/838256-1.html
new file mode 100644
index 000000000..e1cdc588e
--- /dev/null
+++ b/dom/html/crashtests/838256-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<style>
+
+::-moz-range-track {
+ display: none ! important;
+}
+
+::-moz-range-thumb {
+ display: none ! important;
+}
+
+</style>
+</head>
+<body>
+<input type="range">
+</body>
+</html>
diff --git a/dom/html/crashtests/862084.html b/dom/html/crashtests/862084.html
new file mode 100644
index 000000000..d6d04f74d
--- /dev/null
+++ b/dom/html/crashtests/862084.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<select></select>
+<script>
+var select = document.getElementsByTagName("select");
+select.item(0);
+select[0];
+select.namedItem("x")
+select["x"]
+</script>
diff --git a/dom/html/crashtests/865147.html b/dom/html/crashtests/865147.html
new file mode 100644
index 000000000..841cf8b6d
--- /dev/null
+++ b/dom/html/crashtests/865147.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<select></select>
+<script>
+var select = document.getElementsByTagName("select")[0];
+var newOpt = document.createElement("option");
+select.add(newOpt, newOpt);
+</script>
diff --git a/dom/html/crashtests/877910.html b/dom/html/crashtests/877910.html
new file mode 100644
index 000000000..d454c4478
--- /dev/null
+++ b/dom/html/crashtests/877910.html
@@ -0,0 +1 @@
+<select><option id="foo"><script>document.querySelector("select").namedItem("foo")</script>
diff --git a/dom/html/crashtests/903106.html b/dom/html/crashtests/903106.html
new file mode 100644
index 000000000..fceaf4761
--- /dev/null
+++ b/dom/html/crashtests/903106.html
@@ -0,0 +1,3 @@
+<script>
+ document.createElement("tr").sectionRowIndex;
+</script>
diff --git a/dom/html/crashtests/916322-1.html b/dom/html/crashtests/916322-1.html
new file mode 100644
index 000000000..279a8e59f
--- /dev/null
+++ b/dom/html/crashtests/916322-1.html
@@ -0,0 +1,10 @@
+<canvas height=16 id=gl>
+<script>
+// Without the fix, this would trigger an assertion in the debug build
+document.addEventListener("DOMContentLoaded", DifferentSizes, false);
+function DifferentSizes() {
+ gl.getContext("2d");
+ gl.removeAttribute('height');
+ gl.toBlob(function() { }, false)
+}
+</script>
diff --git a/dom/html/crashtests/916322-2.html b/dom/html/crashtests/916322-2.html
new file mode 100644
index 000000000..29e390119
--- /dev/null
+++ b/dom/html/crashtests/916322-2.html
@@ -0,0 +1,10 @@
+<canvas height=200 id=gl>
+<script>
+// Without the fix, this would trigger an assertion in the debug build
+document.addEventListener("DOMContentLoaded", DifferentSizes, false);
+function DifferentSizes() {
+ gl.getContext("2d");
+ gl.removeAttribute('height');
+ gl.toBlob(function() { }, false)
+}
+</script>
diff --git a/dom/html/crashtests/crashtests.list b/dom/html/crashtests/crashtests.list
new file mode 100644
index 000000000..e55a0a350
--- /dev/null
+++ b/dom/html/crashtests/crashtests.list
@@ -0,0 +1,81 @@
+load 68912-1.html
+load 257818-1.html
+load 285166-1.html
+load 294235-1.html
+load 307616-1.html
+load 324918-1.xhtml
+load 338649-1.xhtml
+load 339501-1.xhtml
+load 339501-2.xhtml
+load 378993-1.xhtml
+load 382568-1.html
+load 383137.xhtml
+load 388183-1.html
+load 395340-1.html
+load 399694-1.html
+load 407053.html
+load 423371-1.html
+load 448564.html
+load 451123-1.html
+load 453406-1.html
+load 464197-1.html
+load 465466-1.xhtml
+load 468562-1.html
+load 468562-2.html
+load 494225.html
+load 495543.svg
+load 495546-1.html
+load 504183-1.html
+load 515829-1.html
+load 515829-2.html
+load 564461.xhtml
+load 570566-1.html
+load 571428-1.html
+load 580507-1.xhtml
+load 590387.html
+load 596785-1.html
+load 596785-2.html
+load 601422.html
+load 602117.html
+load 604807.html
+load 605264.html
+load 606430-1.html
+load 613027.html
+load 614279.html
+load 614988-1.html
+load 620078-1.html
+load 620078-2.html
+load 631421.html
+load 673853.html
+load 680922-1.xul
+load 682058.xhtml
+load 682460.html
+load 738744.xhtml
+load 741218.json
+load 741250.xhtml
+load 795221-1.html
+load 795221-2.html
+load 795221-3.html
+load 795221-4.html
+load 795221-5.xml
+load 811226.html
+load 819745.html
+load 828180.html
+pref(dom.experimental_forms,true) load 828472.html
+load 837033.html
+load 838256-1.html
+load 862084.html
+load 865147.html
+load 877910.html
+load 903106.html
+load 916322-1.html
+load 916322-2.html
+load 1032654.html
+load 1141260.html
+load 1228876.html
+load 1230110.html
+load 1237633.html
+load 1281972-1.html
+load 1282894.html
+load 1290904.html
+load 1386905.html
diff --git a/dom/html/htmlMenuBuilder.js b/dom/html/htmlMenuBuilder.js
new file mode 100644
index 000000000..863fc4d74
--- /dev/null
+++ b/dom/html/htmlMenuBuilder.js
@@ -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/. */
+
+// This component is used to build the menus for the HTML contextmenu attribute.
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+// A global value that is used to identify each menu item. It is
+// incremented with each one that is found.
+var gGeneratedId = 1;
+
+function HTMLMenuBuilder() {
+ this.currentNode = null;
+ this.root = null;
+ this.items = {};
+ this.nestedStack = [];
+};
+
+// Building is done in two steps:
+// The first generates a hierarchical JS object that contains the menu structure.
+// This object is returned by toJSONString.
+//
+// The second step can take this structure and generate a XUL menu hierarchy or
+// other UI from this object. The default UI is done in PageMenu.jsm.
+//
+// When a multi-process browser is used, the first step is performed by the child
+// process and the second step is performed by the parent process.
+
+HTMLMenuBuilder.prototype =
+{
+ classID: Components.ID("{51c65f5d-0de5-4edc-9058-60e50cef77f8}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIMenuBuilder]),
+
+ currentNode: null,
+ root: null,
+ items: {},
+ nestedStack: [],
+
+ toJSONString: function() {
+ return JSON.stringify(this.root);
+ },
+
+ openContainer: function(aLabel) {
+ if (!this.currentNode) {
+ this.root = {
+ type: "menu",
+ children: []
+ };
+ this.currentNode = this.root;
+ }
+ else {
+ let parent = this.currentNode;
+ this.currentNode = {
+ type: "menu",
+ label: aLabel,
+ children: []
+ };
+ parent.children.push(this.currentNode);
+ this.nestedStack.push(parent);
+ }
+ },
+
+ addItemFor: function(aElement, aCanLoadIcon) {
+ if (!("children" in this.currentNode)) {
+ return;
+ }
+
+ let item = {
+ type: "menuitem",
+ label: aElement.label
+ };
+
+ let elementType = aElement.type;
+ if (elementType == "checkbox" || elementType == "radio") {
+ item.checkbox = true;
+
+ if (aElement.checked) {
+ item.checked = true;
+ }
+ }
+
+ let icon = aElement.icon;
+ if (icon.length > 0 && aCanLoadIcon) {
+ item.icon = icon;
+ }
+
+ if (aElement.disabled) {
+ item.disabled = true;
+ }
+
+ item.id = gGeneratedId++;
+ this.currentNode.children.push(item);
+
+ this.items[item.id] = aElement;
+ },
+
+ addSeparator: function() {
+ if (!("children" in this.currentNode)) {
+ return;
+ }
+
+ this.currentNode.children.push({ type: "separator"});
+ },
+
+ undoAddSeparator: function() {
+ if (!("children" in this.currentNode)) {
+ return;
+ }
+
+ let children = this.currentNode.children;
+ if (children.length && children[children.length - 1].type == "separator") {
+ children.pop();
+ }
+ },
+
+ closeContainer: function() {
+ this.currentNode = this.nestedStack.length ? this.nestedStack.pop() : this.root;
+ },
+
+ click: function(id) {
+ let item = this.items[id];
+ if (item) {
+ item.click();
+ }
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HTMLMenuBuilder]);
diff --git a/dom/html/htmlMenuBuilder.manifest b/dom/html/htmlMenuBuilder.manifest
new file mode 100644
index 000000000..b245f8fe2
--- /dev/null
+++ b/dom/html/htmlMenuBuilder.manifest
@@ -0,0 +1,3 @@
+component {51c65f5d-0de5-4edc-9058-60e50cef77f8} htmlMenuBuilder.js
+contract @mozilla.org/content/html-menu-builder;1 {51c65f5d-0de5-4edc-9058-60e50cef77f8}
+
diff --git a/dom/html/moz.build b/dom/html/moz.build
new file mode 100644
index 000000000..c9879d1e6
--- /dev/null
+++ b/dom/html/moz.build
@@ -0,0 +1,247 @@
+# -*- 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 += [
+ 'test/forms/mochitest.ini',
+ 'test/imports/mochitest.ini',
+ 'test/mochitest.ini',
+]
+
+MOCHITEST_CHROME_MANIFESTS += [
+ 'test/chrome.ini',
+ 'test/forms/chrome.ini',
+]
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
+XPIDL_SOURCES += [
+ 'nsIDateTimeInputArea.idl',
+ 'nsIFormSubmitObserver.idl',
+ 'nsIHTMLMenu.idl',
+ 'nsIImageDocument.idl',
+ 'nsIMenuBuilder.idl',
+ 'nsIPhonetic.idl',
+]
+
+XPIDL_MODULE = 'content_html'
+
+EXPORTS += [
+ 'nsGenericHTMLElement.h',
+ 'nsHTMLDNSPrefetch.h',
+ 'nsIConstraintValidation.h',
+ 'nsIForm.h',
+ 'nsIFormControl.h',
+ 'nsIFormProcessor.h',
+ 'nsIHTMLCollection.h',
+ 'nsIHTMLDocument.h',
+ 'nsIRadioGroupContainer.h',
+ 'nsIRadioVisitor.h',
+ 'nsITextControlElement.h',
+ 'nsTextEditorState.h',
+]
+
+EXPORTS.mozilla.dom += [
+ 'HTMLAllCollection.h',
+ 'HTMLAnchorElement.h',
+ 'HTMLAreaElement.h',
+ 'HTMLAudioElement.h',
+ 'HTMLBodyElement.h',
+ 'HTMLBRElement.h',
+ 'HTMLButtonElement.h',
+ 'HTMLCanvasElement.h',
+ 'HTMLContentElement.h',
+ 'HTMLDataElement.h',
+ 'HTMLDataListElement.h',
+ 'HTMLDetailsElement.h',
+ 'HTMLDivElement.h',
+ 'HTMLFieldSetElement.h',
+ 'HTMLFontElement.h',
+ 'HTMLFormControlsCollection.h',
+ 'HTMLFormElement.h',
+ 'HTMLFormSubmission.h',
+ 'HTMLFrameElement.h',
+ 'HTMLFrameSetElement.h',
+ 'HTMLHeadingElement.h',
+ 'HTMLHRElement.h',
+ 'HTMLIFrameElement.h',
+ 'HTMLImageElement.h',
+ 'HTMLInputElement.h',
+ 'HTMLLabelElement.h',
+ 'HTMLLegendElement.h',
+ 'HTMLLIElement.h',
+ 'HTMLLinkElement.h',
+ 'HTMLMapElement.h',
+ 'HTMLMediaElement.h',
+ 'HTMLMenuElement.h',
+ 'HTMLMenuItemElement.h',
+ 'HTMLMetaElement.h',
+ 'HTMLMeterElement.h',
+ 'HTMLModElement.h',
+ 'HTMLObjectElement.h',
+ 'HTMLOptGroupElement.h',
+ 'HTMLOptionElement.h',
+ 'HTMLOptionsCollection.h',
+ 'HTMLOutputElement.h',
+ 'HTMLParagraphElement.h',
+ 'HTMLPictureElement.h',
+ 'HTMLPreElement.h',
+ 'HTMLProgressElement.h',
+ 'HTMLScriptElement.h',
+ 'HTMLSelectElement.h',
+ 'HTMLShadowElement.h',
+ 'HTMLSharedElement.h',
+ 'HTMLSharedListElement.h',
+ 'HTMLSharedObjectElement.h',
+ 'HTMLSourceElement.h',
+ 'HTMLSpanElement.h',
+ 'HTMLStyleElement.h',
+ 'HTMLSummaryElement.h',
+ 'HTMLTableCaptionElement.h',
+ 'HTMLTableCellElement.h',
+ 'HTMLTableColElement.h',
+ 'HTMLTableElement.h',
+ 'HTMLTableRowElement.h',
+ 'HTMLTableSectionElement.h',
+ 'HTMLTemplateElement.h',
+ 'HTMLTextAreaElement.h',
+ 'HTMLTimeElement.h',
+ 'HTMLTitleElement.h',
+ 'HTMLTrackElement.h',
+ 'HTMLUnknownElement.h',
+ 'HTMLVideoElement.h',
+ 'ImageDocument.h',
+ 'MediaError.h',
+ 'nsBrowserElement.h',
+ 'RadioNodeList.h',
+ 'TextTrackManager.h',
+ 'TimeRanges.h',
+ 'ValidityState.h',
+]
+
+UNIFIED_SOURCES += [
+ 'HTMLAllCollection.cpp',
+ 'HTMLAnchorElement.cpp',
+ 'HTMLAreaElement.cpp',
+ 'HTMLAudioElement.cpp',
+ 'HTMLBodyElement.cpp',
+ 'HTMLBRElement.cpp',
+ 'HTMLButtonElement.cpp',
+ 'HTMLCanvasElement.cpp',
+ 'HTMLContentElement.cpp',
+ 'HTMLDataElement.cpp',
+ 'HTMLDataListElement.cpp',
+ 'HTMLDetailsElement.cpp',
+ 'HTMLDivElement.cpp',
+ 'HTMLElement.cpp',
+ 'HTMLFieldSetElement.cpp',
+ 'HTMLFontElement.cpp',
+ 'HTMLFormControlsCollection.cpp',
+ 'HTMLFormElement.cpp',
+ 'HTMLFormSubmission.cpp',
+ 'HTMLFrameElement.cpp',
+ 'HTMLFrameSetElement.cpp',
+ 'HTMLHeadingElement.cpp',
+ 'HTMLHRElement.cpp',
+ 'HTMLIFrameElement.cpp',
+ 'HTMLImageElement.cpp',
+ 'HTMLInputElement.cpp',
+ 'HTMLLabelElement.cpp',
+ 'HTMLLegendElement.cpp',
+ 'HTMLLIElement.cpp',
+ 'HTMLLinkElement.cpp',
+ 'HTMLMapElement.cpp',
+ 'HTMLMediaElement.cpp',
+ 'HTMLMenuElement.cpp',
+ 'HTMLMenuItemElement.cpp',
+ 'HTMLMetaElement.cpp',
+ 'HTMLMeterElement.cpp',
+ 'HTMLModElement.cpp',
+ 'HTMLObjectElement.cpp',
+ 'HTMLOptGroupElement.cpp',
+ 'HTMLOptionElement.cpp',
+ 'HTMLOptionsCollection.cpp',
+ 'HTMLOutputElement.cpp',
+ 'HTMLParagraphElement.cpp',
+ 'HTMLPictureElement.cpp',
+ 'HTMLPreElement.cpp',
+ 'HTMLProgressElement.cpp',
+ 'HTMLScriptElement.cpp',
+ 'HTMLSelectElement.cpp',
+ 'HTMLShadowElement.cpp',
+ 'HTMLSharedElement.cpp',
+ 'HTMLSharedListElement.cpp',
+ 'HTMLSharedObjectElement.cpp',
+ 'HTMLSourceElement.cpp',
+ 'HTMLSpanElement.cpp',
+ 'HTMLStyleElement.cpp',
+ 'HTMLSummaryElement.cpp',
+ 'HTMLTableCaptionElement.cpp',
+ 'HTMLTableCellElement.cpp',
+ 'HTMLTableColElement.cpp',
+ 'HTMLTableElement.cpp',
+ 'HTMLTableRowElement.cpp',
+ 'HTMLTableSectionElement.cpp',
+ 'HTMLTemplateElement.cpp',
+ 'HTMLTextAreaElement.cpp',
+ 'HTMLTimeElement.cpp',
+ 'HTMLTitleElement.cpp',
+ 'HTMLTrackElement.cpp',
+ 'HTMLUnknownElement.cpp',
+ 'HTMLVideoElement.cpp',
+ 'ImageDocument.cpp',
+ 'MediaDocument.cpp',
+ 'MediaError.cpp',
+ 'nsBrowserElement.cpp',
+ 'nsDOMStringMap.cpp',
+ 'nsGenericHTMLElement.cpp',
+ 'nsGenericHTMLFrameElement.cpp',
+ 'nsHTMLContentSink.cpp',
+ 'nsHTMLDNSPrefetch.cpp',
+ 'nsHTMLDocument.cpp',
+ 'nsIConstraintValidation.cpp',
+ 'nsRadioVisitor.cpp',
+ 'nsTextEditorState.cpp',
+ 'RadioNodeList.cpp',
+ 'TextTrackManager.cpp',
+ 'TimeRanges.cpp',
+ 'ValidityState.cpp',
+ 'VideoDocument.cpp',
+]
+
+SOURCES += [
+ # Includes npapi.h.
+ 'PluginDocument.cpp',
+]
+
+EXTRA_COMPONENTS += [
+ 'htmlMenuBuilder.js',
+ 'htmlMenuBuilder.manifest'
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+LOCAL_INCLUDES += [
+ '/caps',
+ '/docshell/base',
+ '/dom/base',
+ '/dom/canvas',
+ '/dom/media/',
+ '/dom/xbl',
+ '/dom/xul',
+ '/editor/txmgr',
+ '/image',
+ '/layout/forms',
+ '/layout/generic',
+ '/layout/style',
+ '/layout/tables',
+ '/layout/xul',
+ '/netwerk/base',
+]
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/dom/html/nsBrowserElement.cpp b/dom/html/nsBrowserElement.cpp
new file mode 100644
index 000000000..858d1c7cb
--- /dev/null
+++ b/dom/html/nsBrowserElement.cpp
@@ -0,0 +1,727 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBrowserElement.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/dom/BrowserElementBinding.h"
+#include "mozilla/dom/BrowserElementAudioChannel.h"
+#include "mozilla/dom/DOMRequest.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ToJSValue.h"
+
+#include "AudioChannelService.h"
+
+#include "mozIApplication.h"
+#include "nsComponentManagerUtils.h"
+#include "nsFrameLoader.h"
+#include "nsIAppsService.h"
+#include "nsIDOMDOMRequest.h"
+#include "nsIDOMElement.h"
+#include "nsIMozBrowserFrame.h"
+#include "nsINode.h"
+#include "nsIPrincipal.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+bool
+nsBrowserElement::IsBrowserElementOrThrow(ErrorResult& aRv)
+{
+ if (mBrowserElementAPI) {
+ return true;
+ }
+ aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+ return false;
+}
+
+void
+nsBrowserElement::InitBrowserElementAPI()
+{
+ bool isMozBrowserOrApp;
+ nsCOMPtr<nsIFrameLoader> frameLoader = GetFrameLoader();
+ NS_ENSURE_TRUE_VOID(frameLoader);
+ nsresult rv = frameLoader->GetOwnerIsMozBrowserOrAppFrame(&isMozBrowserOrApp);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (!isMozBrowserOrApp) {
+ return;
+ }
+
+ if (!mBrowserElementAPI) {
+ mBrowserElementAPI = do_CreateInstance("@mozilla.org/dom/browser-element-api;1");
+ if (NS_WARN_IF(!mBrowserElementAPI)) {
+ return;
+ }
+ }
+ mBrowserElementAPI->SetFrameLoader(frameLoader);
+}
+
+void
+nsBrowserElement::DestroyBrowserElementFrameScripts()
+{
+ if (!mBrowserElementAPI) {
+ return;
+ }
+ mBrowserElementAPI->DestroyFrameScripts();
+}
+
+void
+nsBrowserElement::SetVisible(bool aVisible, ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ nsresult rv = mBrowserElementAPI->SetVisible(aVisible);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetVisible(ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+
+ nsCOMPtr<nsIDOMDOMRequest> req;
+ nsresult rv = mBrowserElementAPI->GetVisible(getter_AddRefs(req));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ return req.forget().downcast<DOMRequest>();
+}
+
+void
+nsBrowserElement::SetActive(bool aVisible, ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ nsresult rv = mBrowserElementAPI->SetActive(aVisible);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+}
+
+bool
+nsBrowserElement::GetActive(ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), false);
+
+ bool isActive;
+ nsresult rv = mBrowserElementAPI->GetActive(&isActive);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return false;
+ }
+
+ return isActive;
+}
+
+void
+nsBrowserElement::SendMouseEvent(const nsAString& aType,
+ uint32_t aX,
+ uint32_t aY,
+ uint32_t aButton,
+ uint32_t aClickCount,
+ uint32_t aModifiers,
+ ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ nsresult rv = mBrowserElementAPI->SendMouseEvent(aType,
+ aX,
+ aY,
+ aButton,
+ aClickCount,
+ aModifiers);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+}
+
+void
+nsBrowserElement::SendTouchEvent(const nsAString& aType,
+ const Sequence<uint32_t>& aIdentifiers,
+ const Sequence<int32_t>& aXs,
+ const Sequence<int32_t>& aYs,
+ const Sequence<uint32_t>& aRxs,
+ const Sequence<uint32_t>& aRys,
+ const Sequence<float>& aRotationAngles,
+ const Sequence<float>& aForces,
+ uint32_t aCount,
+ uint32_t aModifiers,
+ ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ if (aIdentifiers.Length() != aCount ||
+ aXs.Length() != aCount ||
+ aYs.Length() != aCount ||
+ aRxs.Length() != aCount ||
+ aRys.Length() != aCount ||
+ aRotationAngles.Length() != aCount ||
+ aForces.Length() != aCount) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return;
+ }
+
+ nsresult rv = mBrowserElementAPI->SendTouchEvent(aType,
+ aIdentifiers.Elements(),
+ aXs.Elements(),
+ aYs.Elements(),
+ aRxs.Elements(),
+ aRys.Elements(),
+ aRotationAngles.Elements(),
+ aForces.Elements(),
+ aCount,
+ aModifiers);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+}
+
+void
+nsBrowserElement::GoBack(ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ nsresult rv = mBrowserElementAPI->GoBack();
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+}
+
+void
+nsBrowserElement::GoForward(ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ nsresult rv = mBrowserElementAPI->GoForward();
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+}
+
+void
+nsBrowserElement::Reload(bool aHardReload, ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ nsresult rv = mBrowserElementAPI->Reload(aHardReload);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+}
+
+void
+nsBrowserElement::Stop(ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ nsresult rv = mBrowserElementAPI->Stop();
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::Download(const nsAString& aUrl,
+ const BrowserElementDownloadOptions& aOptions,
+ ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+
+ nsCOMPtr<nsIDOMDOMRequest> req;
+ nsCOMPtr<nsIXPConnectWrappedJS> wrappedObj = do_QueryInterface(mBrowserElementAPI);
+ MOZ_ASSERT(wrappedObj, "Failed to get wrapped JS from XPCOM component.");
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(wrappedObj->GetJSObject())) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JS::Value> options(cx);
+ aRv.MightThrowJSException();
+ if (!ToJSValue(cx, aOptions, &options)) {
+ aRv.StealExceptionFromJSContext(cx);
+ return nullptr;
+ }
+ nsresult rv = mBrowserElementAPI->Download(aUrl, options, getter_AddRefs(req));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ return req.forget().downcast<DOMRequest>();
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::PurgeHistory(ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+
+ nsCOMPtr<nsIDOMDOMRequest> req;
+ nsresult rv = mBrowserElementAPI->PurgeHistory(getter_AddRefs(req));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ return req.forget().downcast<DOMRequest>();
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetScreenshot(uint32_t aWidth,
+ uint32_t aHeight,
+ const nsAString& aMimeType,
+ ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+
+ nsCOMPtr<nsIDOMDOMRequest> req;
+ nsresult rv = mBrowserElementAPI->GetScreenshot(aWidth, aHeight, aMimeType,
+ getter_AddRefs(req));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ if (rv == NS_ERROR_INVALID_ARG) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ } else {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+ return nullptr;
+ }
+
+ return req.forget().downcast<DOMRequest>();
+}
+
+void
+nsBrowserElement::Zoom(float aZoom, ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ nsresult rv = mBrowserElementAPI->Zoom(aZoom);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetCanGoBack(ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+
+ nsCOMPtr<nsIDOMDOMRequest> req;
+ nsresult rv = mBrowserElementAPI->GetCanGoBack(getter_AddRefs(req));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ return req.forget().downcast<DOMRequest>();
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetCanGoForward(ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+
+ nsCOMPtr<nsIDOMDOMRequest> req;
+ nsresult rv = mBrowserElementAPI->GetCanGoForward(getter_AddRefs(req));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ return req.forget().downcast<DOMRequest>();
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetContentDimensions(ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+
+ nsCOMPtr<nsIDOMDOMRequest> req;
+ nsresult rv = mBrowserElementAPI->GetContentDimensions(getter_AddRefs(req));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ return req.forget().downcast<DOMRequest>();
+}
+
+void
+nsBrowserElement::FindAll(const nsAString& aSearchString,
+ BrowserFindCaseSensitivity aCaseSensitivity,
+ ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ uint32_t caseSensitivity;
+ if (aCaseSensitivity == BrowserFindCaseSensitivity::Case_insensitive) {
+ caseSensitivity = nsIBrowserElementAPI::FIND_CASE_INSENSITIVE;
+ } else {
+ caseSensitivity = nsIBrowserElementAPI::FIND_CASE_SENSITIVE;
+ }
+
+ nsresult rv = mBrowserElementAPI->FindAll(aSearchString, caseSensitivity);
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+void
+nsBrowserElement::FindNext(BrowserFindDirection aDirection,
+ ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ uint32_t direction;
+ if (aDirection == BrowserFindDirection::Backward) {
+ direction = nsIBrowserElementAPI::FIND_BACKWARD;
+ } else {
+ direction = nsIBrowserElementAPI::FIND_FORWARD;
+ }
+
+ nsresult rv = mBrowserElementAPI->FindNext(direction);
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+void
+nsBrowserElement::ClearMatch(ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ nsresult rv = mBrowserElementAPI->ClearMatch();
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+void
+nsBrowserElement::AddNextPaintListener(BrowserElementNextPaintEventCallback& aListener,
+ ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ CallbackObjectHolder<BrowserElementNextPaintEventCallback,
+ nsIBrowserElementNextPaintListener> holder(&aListener);
+ nsCOMPtr<nsIBrowserElementNextPaintListener> listener = holder.ToXPCOMCallback();
+
+ nsresult rv = mBrowserElementAPI->AddNextPaintListener(listener);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+}
+
+void
+nsBrowserElement::RemoveNextPaintListener(BrowserElementNextPaintEventCallback& aListener,
+ ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ CallbackObjectHolder<BrowserElementNextPaintEventCallback,
+ nsIBrowserElementNextPaintListener> holder(&aListener);
+ nsCOMPtr<nsIBrowserElementNextPaintListener> listener = holder.ToXPCOMCallback();
+
+ nsresult rv = mBrowserElementAPI->RemoveNextPaintListener(listener);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::SetInputMethodActive(bool aIsActive,
+ ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+
+ nsCOMPtr<nsIDOMDOMRequest> req;
+ nsresult rv = mBrowserElementAPI->SetInputMethodActive(aIsActive,
+ getter_AddRefs(req));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ if (rv == NS_ERROR_INVALID_ARG) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ } else {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+ return nullptr;
+ }
+
+ return req.forget().downcast<DOMRequest>();
+}
+
+void
+nsBrowserElement::GetAllowedAudioChannels(
+ nsTArray<RefPtr<BrowserElementAudioChannel>>& aAudioChannels,
+ ErrorResult& aRv)
+{
+ aAudioChannels.Clear();
+
+ // If empty, it means that this is the first call of this method.
+ if (mBrowserElementAudioChannels.IsEmpty()) {
+ nsCOMPtr<nsIFrameLoader> frameLoader = GetFrameLoader();
+ if (NS_WARN_IF(!frameLoader)) {
+ return;
+ }
+
+ bool isMozBrowserOrApp;
+ aRv = frameLoader->GetOwnerIsMozBrowserOrAppFrame(&isMozBrowserOrApp);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (!isMozBrowserOrApp) {
+ return;
+ }
+
+ nsCOMPtr<nsIDOMElement> frameElement;
+ aRv = frameLoader->GetOwnerElement(getter_AddRefs(frameElement));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(frameElement);
+
+ nsCOMPtr<nsIDOMDocument> doc;
+ aRv = frameElement->GetOwnerDocument(getter_AddRefs(doc));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(doc);
+
+ nsCOMPtr<mozIDOMWindowProxy> win;
+ aRv = doc->GetDefaultView(getter_AddRefs(win));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(win);
+
+ auto* window = nsPIDOMWindowOuter::From(win);
+ nsPIDOMWindowInner* innerWindow = window->GetCurrentInnerWindow();
+
+ nsCOMPtr<nsIMozBrowserFrame> mozBrowserFrame =
+ do_QueryInterface(frameElement);
+ if (NS_WARN_IF(!mozBrowserFrame)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("nsBrowserElement, GetAllowedAudioChannels, this = %p\n", this));
+
+ GenerateAllowedAudioChannels(innerWindow, frameLoader, mBrowserElementAPI,
+ mBrowserElementAudioChannels, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ aAudioChannels.AppendElements(mBrowserElementAudioChannels);
+}
+
+/* static */ void
+nsBrowserElement::GenerateAllowedAudioChannels(
+ nsPIDOMWindowInner* aWindow,
+ nsIFrameLoader* aFrameLoader,
+ nsIBrowserElementAPI* aAPI,
+ nsTArray<RefPtr<BrowserElementAudioChannel>>& aAudioChannels,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(aAudioChannels.IsEmpty());
+
+ // Normal is always allowed.
+ nsTArray<RefPtr<BrowserElementAudioChannel>> channels;
+
+ RefPtr<BrowserElementAudioChannel> ac =
+ BrowserElementAudioChannel::Create(aWindow, aFrameLoader, aAPI,
+ AudioChannel::Normal, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ channels.AppendElement(ac);
+
+ nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Since we don't have permissions anymore let only chrome windows pick a
+ // non-default channel
+ if (nsContentUtils::IsChromeDoc(doc)) {
+ const nsAttrValue::EnumTable* audioChannelTable =
+ AudioChannelService::GetAudioChannelTable();
+
+ for (uint32_t i = 0; audioChannelTable && audioChannelTable[i].tag; ++i) {
+ AudioChannel value = (AudioChannel)audioChannelTable[i].value;
+ RefPtr<BrowserElementAudioChannel> ac =
+ BrowserElementAudioChannel::Create(aWindow, aFrameLoader, aAPI,
+ value, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ channels.AppendElement(ac);
+ }
+ }
+
+ aAudioChannels.SwapElements(channels);
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetMuted(ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+
+ nsCOMPtr<nsIDOMDOMRequest> req;
+ nsresult rv = mBrowserElementAPI->GetMuted(getter_AddRefs(req));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ return req.forget().downcast<DOMRequest>();
+}
+
+void
+nsBrowserElement::Mute(ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ nsresult rv = mBrowserElementAPI->Mute();
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+}
+
+void
+nsBrowserElement::Unmute(ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ nsresult rv = mBrowserElementAPI->Unmute();
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetVolume(ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+
+ nsCOMPtr<nsIDOMDOMRequest> req;
+ nsresult rv = mBrowserElementAPI->GetVolume(getter_AddRefs(req));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ return req.forget().downcast<DOMRequest>();
+}
+
+void
+nsBrowserElement::SetVolume(float aVolume, ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+ nsresult rv = mBrowserElementAPI->SetVolume(aVolume);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::ExecuteScript(const nsAString& aScript,
+ const BrowserElementExecuteScriptOptions& aOptions,
+ ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+
+ nsCOMPtr<nsIDOMDOMRequest> req;
+ nsCOMPtr<nsIXPConnectWrappedJS> wrappedObj = do_QueryInterface(mBrowserElementAPI);
+ MOZ_ASSERT(wrappedObj, "Failed to get wrapped JS from XPCOM component.");
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(wrappedObj->GetJSObject())) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JS::Value> options(cx);
+ aRv.MightThrowJSException();
+ if (!ToJSValue(cx, aOptions, &options)) {
+ aRv.StealExceptionFromJSContext(cx);
+ return nullptr;
+ }
+
+ nsresult rv = mBrowserElementAPI->ExecuteScript(aScript, options, getter_AddRefs(req));
+
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_INVALID_ARG) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ } else {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+ return nullptr;
+ }
+
+ return req.forget().downcast<DOMRequest>();
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetWebManifest(ErrorResult& aRv)
+{
+ NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+
+ nsCOMPtr<nsIDOMDOMRequest> req;
+ nsresult rv = mBrowserElementAPI->GetWebManifest(getter_AddRefs(req));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ return req.forget().downcast<DOMRequest>();
+}
+
+
+
+} // namespace mozilla
diff --git a/dom/html/nsBrowserElement.h b/dom/html/nsBrowserElement.h
new file mode 100644
index 000000000..e0f4ca186
--- /dev/null
+++ b/dom/html/nsBrowserElement.h
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBrowserElement_h
+#define nsBrowserElement_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/BrowserElementAudioChannel.h"
+
+#include "nsCOMPtr.h"
+#include "nsIBrowserElementAPI.h"
+
+class nsFrameLoader;
+
+namespace mozilla {
+
+namespace dom {
+struct BrowserElementDownloadOptions;
+struct BrowserElementExecuteScriptOptions;
+class BrowserElementNextPaintEventCallback;
+class DOMRequest;
+enum class BrowserFindCaseSensitivity: uint32_t;
+enum class BrowserFindDirection: uint32_t;
+} // namespace dom
+
+class ErrorResult;
+
+/**
+ * A helper class for browser-element frames
+ */
+class nsBrowserElement
+{
+public:
+ nsBrowserElement() {}
+ virtual ~nsBrowserElement() {}
+
+ void SetVisible(bool aVisible, ErrorResult& aRv);
+ already_AddRefed<dom::DOMRequest> GetVisible(ErrorResult& aRv);
+ void SetActive(bool aActive, ErrorResult& aRv);
+ bool GetActive(ErrorResult& aRv);
+
+ void SendMouseEvent(const nsAString& aType,
+ uint32_t aX,
+ uint32_t aY,
+ uint32_t aButton,
+ uint32_t aClickCount,
+ uint32_t aModifiers,
+ ErrorResult& aRv);
+ void SendTouchEvent(const nsAString& aType,
+ const dom::Sequence<uint32_t>& aIdentifiers,
+ const dom::Sequence<int32_t>& aX,
+ const dom::Sequence<int32_t>& aY,
+ const dom::Sequence<uint32_t>& aRx,
+ const dom::Sequence<uint32_t>& aRy,
+ const dom::Sequence<float>& aRotationAngles,
+ const dom::Sequence<float>& aForces,
+ uint32_t aCount,
+ uint32_t aModifiers,
+ ErrorResult& aRv);
+ void GoBack(ErrorResult& aRv);
+ void GoForward(ErrorResult& aRv);
+ void Reload(bool aHardReload, ErrorResult& aRv);
+ void Stop(ErrorResult& aRv);
+
+ already_AddRefed<dom::DOMRequest>
+ Download(const nsAString& aUrl,
+ const dom::BrowserElementDownloadOptions& options,
+ ErrorResult& aRv);
+
+ already_AddRefed<dom::DOMRequest> PurgeHistory(ErrorResult& aRv);
+
+ void GetAllowedAudioChannels(
+ nsTArray<RefPtr<dom::BrowserElementAudioChannel>>& aAudioChannels,
+ ErrorResult& aRv);
+
+ void Mute(ErrorResult& aRv);
+ void Unmute(ErrorResult& aRv);
+ already_AddRefed<dom::DOMRequest> GetMuted(ErrorResult& aRv);
+
+ void SetVolume(float aVolume , ErrorResult& aRv);
+ already_AddRefed<dom::DOMRequest> GetVolume(ErrorResult& aRv);
+
+ already_AddRefed<dom::DOMRequest>
+ GetScreenshot(uint32_t aWidth,
+ uint32_t aHeight,
+ const nsAString& aMimeType,
+ ErrorResult& aRv);
+
+ void Zoom(float aZoom, ErrorResult& aRv);
+
+ already_AddRefed<dom::DOMRequest> GetCanGoBack(ErrorResult& aRv);
+ already_AddRefed<dom::DOMRequest> GetCanGoForward(ErrorResult& aRv);
+ already_AddRefed<dom::DOMRequest> GetContentDimensions(ErrorResult& aRv);
+
+ void FindAll(const nsAString& aSearchString, dom::BrowserFindCaseSensitivity aCaseSensitivity,
+ ErrorResult& aRv);
+ void FindNext(dom::BrowserFindDirection aDirection, ErrorResult& aRv);
+ void ClearMatch(ErrorResult& aRv);
+
+ void AddNextPaintListener(dom::BrowserElementNextPaintEventCallback& listener,
+ ErrorResult& aRv);
+ void RemoveNextPaintListener(dom::BrowserElementNextPaintEventCallback& listener,
+ ErrorResult& aRv);
+
+ already_AddRefed<dom::DOMRequest> SetInputMethodActive(bool isActive,
+ ErrorResult& aRv);
+
+ already_AddRefed<dom::DOMRequest> ExecuteScript(const nsAString& aScript,
+ const dom::BrowserElementExecuteScriptOptions& aOptions,
+ ErrorResult& aRv);
+
+ already_AddRefed<dom::DOMRequest> GetWebManifest(ErrorResult& aRv);
+
+ // Helper
+ static void GenerateAllowedAudioChannels(
+ nsPIDOMWindowInner* aWindow,
+ nsIFrameLoader* aFrameLoader,
+ nsIBrowserElementAPI* aAPI,
+ nsTArray<RefPtr<dom::BrowserElementAudioChannel>>& aAudioChannels,
+ ErrorResult& aRv);
+
+protected:
+ NS_IMETHOD_(already_AddRefed<nsFrameLoader>) GetFrameLoader() = 0;
+ NS_IMETHOD GetParentApplication(mozIApplication** aApplication) = 0;
+
+ void InitBrowserElementAPI();
+ void DestroyBrowserElementFrameScripts();
+ nsCOMPtr<nsIBrowserElementAPI> mBrowserElementAPI;
+ nsTArray<RefPtr<dom::BrowserElementAudioChannel>> mBrowserElementAudioChannels;
+
+private:
+ bool IsBrowserElementOrThrow(ErrorResult& aRv);
+};
+
+} // namespace mozilla
+
+#endif // nsBrowserElement_h
diff --git a/dom/html/nsDOMStringMap.cpp b/dom/html/nsDOMStringMap.cpp
new file mode 100644
index 000000000..42725bc6f
--- /dev/null
+++ b/dom/html/nsDOMStringMap.cpp
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDOMStringMap.h"
+
+#include "jsapi.h"
+#include "nsError.h"
+#include "nsGenericHTMLElement.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/DOMStringMapBinding.h"
+#include "nsIDOMMutationEvent.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMStringMap)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMStringMap)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMStringMap)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ // Check that mElement exists in case the unlink code is run more than once.
+ if (tmp->mElement) {
+ // Call back to element to null out weak reference to this object.
+ tmp->mElement->ClearDataset();
+ tmp->mElement->RemoveMutationObserver(tmp);
+ tmp->mElement = nullptr;
+ }
+ tmp->mExpandoAndGeneration.OwnerUnlinked();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsDOMStringMap)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMStringMap)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMStringMap)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMStringMap)
+
+nsDOMStringMap::nsDOMStringMap(Element* aElement)
+ : mElement(aElement),
+ mRemovingProp(false)
+{
+ mElement->AddMutationObserver(this);
+}
+
+nsDOMStringMap::~nsDOMStringMap()
+{
+ // Check if element still exists, may have been unlinked by cycle collector.
+ if (mElement) {
+ // Call back to element to null out weak reference to this object.
+ mElement->ClearDataset();
+ mElement->RemoveMutationObserver(this);
+ }
+}
+
+/* virtual */
+JSObject*
+nsDOMStringMap::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return DOMStringMapBinding::Wrap(cx, this, aGivenProto);
+}
+
+void
+nsDOMStringMap::NamedGetter(const nsAString& aProp, bool& found,
+ DOMString& aResult) const
+{
+ nsAutoString attr;
+
+ if (!DataPropToAttr(aProp, attr)) {
+ found = false;
+ return;
+ }
+
+ found = mElement->GetAttr(attr, aResult);
+}
+
+void
+nsDOMStringMap::NamedSetter(const nsAString& aProp,
+ const nsAString& aValue,
+ ErrorResult& rv)
+{
+ nsAutoString attr;
+ if (!DataPropToAttr(aProp, attr)) {
+ rv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ nsresult res = nsContentUtils::CheckQName(attr, false);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ return;
+ }
+
+ nsCOMPtr<nsIAtom> attrAtom = NS_Atomize(attr);
+ MOZ_ASSERT(attrAtom, "Should be infallible");
+
+ res = mElement->SetAttr(kNameSpaceID_None, attrAtom, aValue, true);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ }
+}
+
+void
+nsDOMStringMap::NamedDeleter(const nsAString& aProp, bool& found)
+{
+ // Currently removing property, attribute is already removed.
+ if (mRemovingProp) {
+ found = false;
+ return;
+ }
+
+ nsAutoString attr;
+ if (!DataPropToAttr(aProp, attr)) {
+ found = false;
+ return;
+ }
+
+ nsCOMPtr<nsIAtom> attrAtom = NS_Atomize(attr);
+ MOZ_ASSERT(attrAtom, "Should be infallible");
+
+ found = mElement->HasAttr(kNameSpaceID_None, attrAtom);
+
+ if (found) {
+ mRemovingProp = true;
+ mElement->UnsetAttr(kNameSpaceID_None, attrAtom, true);
+ mRemovingProp = false;
+ }
+}
+
+void
+nsDOMStringMap::GetSupportedNames(nsTArray<nsString>& aNames)
+{
+ uint32_t attrCount = mElement->GetAttrCount();
+
+ // Iterate through all the attributes and add property
+ // names corresponding to data attributes to return array.
+ for (uint32_t i = 0; i < attrCount; ++i) {
+ const nsAttrName* attrName = mElement->GetAttrNameAt(i);
+ // Skip the ones that are not in the null namespace
+ if (attrName->NamespaceID() != kNameSpaceID_None) {
+ continue;
+ }
+
+ nsAutoString prop;
+ if (!AttrToDataProp(nsDependentAtomString(attrName->LocalName()),
+ prop)) {
+ continue;
+ }
+
+ aNames.AppendElement(prop);
+ }
+}
+
+/**
+ * Converts a dataset property name to the corresponding data attribute name.
+ * (ex. aBigFish to data-a-big-fish).
+ */
+bool nsDOMStringMap::DataPropToAttr(const nsAString& aProp,
+ nsAutoString& aResult)
+{
+ // aResult is an autostring, so don't worry about setting its capacity:
+ // SetCapacity is slow even when it's a no-op and we already have enough
+ // storage there for most cases, probably.
+ aResult.AppendLiteral("data-");
+
+ // Iterate property by character to form attribute name.
+ // Return syntax error if there is a sequence of "-" followed by a character
+ // in the range "a" to "z".
+ // Replace capital characters with "-" followed by lower case character.
+ // Otherwise, simply append character to attribute name.
+ const char16_t* start = aProp.BeginReading();
+ const char16_t* end = aProp.EndReading();
+ const char16_t* cur = start;
+ for (; cur < end; ++cur) {
+ const char16_t* next = cur + 1;
+ if (char16_t('-') == *cur && next < end &&
+ char16_t('a') <= *next && *next <= char16_t('z')) {
+ // Syntax error if character following "-" is in range "a" to "z".
+ return false;
+ }
+
+ if (char16_t('A') <= *cur && *cur <= char16_t('Z')) {
+ // Append the characters in the range [start, cur)
+ aResult.Append(start, cur - start);
+ // Uncamel-case characters in the range of "A" to "Z".
+ aResult.Append(char16_t('-'));
+ aResult.Append(*cur - 'A' + 'a');
+ start = next; // We've already appended the thing at *cur
+ }
+ }
+
+ aResult.Append(start, cur - start);
+
+ return true;
+}
+
+/**
+ * Converts a data attribute name to the corresponding dataset property name.
+ * (ex. data-a-big-fish to aBigFish).
+ */
+bool nsDOMStringMap::AttrToDataProp(const nsAString& aAttr,
+ nsAutoString& aResult)
+{
+ // If the attribute name does not begin with "data-" then it can not be
+ // a data attribute.
+ if (!StringBeginsWith(aAttr, NS_LITERAL_STRING("data-"))) {
+ return false;
+ }
+
+ // Start reading attribute from first character after "data-".
+ const char16_t* cur = aAttr.BeginReading() + 5;
+ const char16_t* end = aAttr.EndReading();
+
+ // Don't try to mess with aResult's capacity: the probably-no-op SetCapacity()
+ // call is not that fast.
+
+ // Iterate through attrName by character to form property name.
+ // If there is a sequence of "-" followed by a character in the range "a" to
+ // "z" then replace with upper case letter.
+ // Otherwise append character to property name.
+ for (; cur < end; ++cur) {
+ const char16_t* next = cur + 1;
+ if (char16_t('-') == *cur && next < end &&
+ char16_t('a') <= *next && *next <= char16_t('z')) {
+ // Upper case the lower case letters that follow a "-".
+ aResult.Append(*next - 'a' + 'A');
+ // Consume character to account for "-" character.
+ ++cur;
+ } else {
+ // Simply append character if camel case is not necessary.
+ aResult.Append(*cur);
+ }
+ }
+
+ return true;
+}
+
+void
+nsDOMStringMap::AttributeChanged(nsIDocument *aDocument, Element* aElement,
+ int32_t aNameSpaceID, nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ if ((aModType == nsIDOMMutationEvent::ADDITION ||
+ aModType == nsIDOMMutationEvent::REMOVAL) &&
+ aNameSpaceID == kNameSpaceID_None &&
+ StringBeginsWith(nsDependentAtomString(aAttribute),
+ NS_LITERAL_STRING("data-"))) {
+ ++mExpandoAndGeneration.generation;
+ }
+}
diff --git a/dom/html/nsDOMStringMap.h b/dom/html/nsDOMStringMap.h
new file mode 100644
index 000000000..bf28957f7
--- /dev/null
+++ b/dom/html/nsDOMStringMap.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDOMStringMap_h
+#define nsDOMStringMap_h
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/Element.h"
+#include "jsfriendapi.h" // For js::ExpandoAndGeneration
+
+namespace mozilla {
+class ErrorResult;
+} // namespace mozilla
+
+class nsDOMStringMap : public nsStubMutationObserver,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMStringMap)
+
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+
+ nsINode* GetParentObject()
+ {
+ return mElement;
+ }
+
+ explicit nsDOMStringMap(mozilla::dom::Element* aElement);
+
+ // WebIDL API
+ virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+ void NamedGetter(const nsAString& aProp, bool& found,
+ mozilla::dom::DOMString& aResult) const;
+ void NamedSetter(const nsAString& aProp, const nsAString& aValue,
+ mozilla::ErrorResult& rv);
+ void NamedDeleter(const nsAString& aProp, bool &found);
+ void GetSupportedNames(nsTArray<nsString>& aNames);
+
+ js::ExpandoAndGeneration mExpandoAndGeneration;
+
+private:
+ virtual ~nsDOMStringMap();
+
+protected:
+ RefPtr<mozilla::dom::Element> mElement;
+ // Flag to guard against infinite recursion.
+ bool mRemovingProp;
+ static bool DataPropToAttr(const nsAString& aProp, nsAutoString& aResult);
+ static bool AttrToDataProp(const nsAString& aAttr, nsAutoString& aResult);
+};
+
+#endif
diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp
new file mode 100644
index 000000000..d75001a83
--- /dev/null
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -0,0 +1,3032 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/DeclarationBlockInlines.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Likely.h"
+
+#include "nscore.h"
+#include "nsGenericHTMLElement.h"
+#include "nsAttrValueInlines.h"
+#include "nsCOMPtr.h"
+#include "nsIAtom.h"
+#include "nsQueryObject.h"
+#include "nsIContentInlines.h"
+#include "nsIContentViewer.h"
+#include "mozilla/css/Declaration.h"
+#include "nsIDocument.h"
+#include "nsIDocumentEncoder.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIDOMAttr.h"
+#include "nsIDOMDocumentFragment.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIDOMHTMLMenuElement.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMDocument.h"
+#include "nsMappedAttributes.h"
+#include "nsHTMLStyleSheet.h"
+#include "nsIHTMLDocument.h"
+#include "nsPIDOMWindow.h"
+#include "nsIURL.h"
+#include "nsEscape.h"
+#include "nsIFrameInlines.h"
+#include "nsIScrollableFrame.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsIWidget.h"
+#include "nsRange.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsIDocShell.h"
+#include "nsNameSpaceManager.h"
+#include "nsError.h"
+#include "nsScriptLoader.h"
+#include "nsRuleData.h"
+#include "nsIPrincipal.h"
+#include "nsContainerFrame.h"
+#include "nsStyleUtil.h"
+
+#include "nsPresState.h"
+#include "nsILayoutHistoryState.h"
+
+#include "nsHTMLParts.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/DirectionalityUtils.h"
+#include "nsString.h"
+#include "nsUnicharUtils.h"
+#include "nsGkAtoms.h"
+#include "nsIDOMEvent.h"
+#include "nsDOMCSSDeclaration.h"
+#include "nsITextControlFrame.h"
+#include "nsIForm.h"
+#include "nsIFormControl.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "mozilla/dom/HTMLFormElement.h"
+#include "nsFocusManager.h"
+#include "nsAttrValueOrString.h"
+
+#include "mozilla/InternalMutationEvent.h"
+#include "nsDOMStringMap.h"
+
+#include "nsIEditor.h"
+#include "nsIEditorIMESupport.h"
+#include "nsLayoutUtils.h"
+#include "mozAutoDocUpdate.h"
+#include "nsHtml5Module.h"
+#include "nsITextControlElement.h"
+#include "mozilla/dom/Element.h"
+#include "HTMLFieldSetElement.h"
+#include "nsTextNode.h"
+#include "HTMLBRElement.h"
+#include "HTMLMenuElement.h"
+#include "nsDOMMutationObserver.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/FromParser.h"
+#include "mozilla/dom/Link.h"
+#include "mozilla/BloomFilter.h"
+
+#include "nsVariant.h"
+#include "nsDOMTokenList.h"
+#include "nsThreadUtils.h"
+#include "nsTextFragment.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/TouchEvent.h"
+#include "mozilla/ErrorResult.h"
+#include "nsHTMLDocument.h"
+#include "nsGlobalWindow.h"
+#include "mozilla/dom/HTMLBodyElement.h"
+#include "imgIContainer.h"
+#include "nsComputedDOMStyle.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "ReferrerPolicy.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/**
+ * nsAutoFocusEvent is used to dispatch a focus event when a
+ * nsGenericHTMLFormElement is binded to the tree with the autofocus attribute
+ * enabled.
+ */
+class nsAutoFocusEvent : public Runnable
+{
+public:
+ explicit nsAutoFocusEvent(nsGenericHTMLFormElement* aElement) : mElement(aElement) {}
+
+ NS_IMETHOD Run() override {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsIDocument* document = mElement->OwnerDoc();
+
+ nsPIDOMWindowOuter* window = document->GetWindow();
+ if (!window) {
+ return NS_OK;
+ }
+
+ // Trying to found the top window (equivalent to window.top).
+ if (nsCOMPtr<nsPIDOMWindowOuter> top = window->GetTop()) {
+ window = top;
+ }
+
+ if (window->GetFocusedNode()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDocument> topDoc = window->GetExtantDoc();
+ if (topDoc && topDoc->GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE) {
+ return NS_OK;
+ }
+
+ // If something is focused in the same document, ignore autofocus.
+ if (!fm->GetFocusedContent() ||
+ fm->GetFocusedContent()->OwnerDoc() != document) {
+ mozilla::ErrorResult rv;
+ mElement->Focus(rv);
+ return rv.StealNSResult();
+ }
+
+ return NS_OK;
+ }
+private:
+ // NOTE: nsGenericHTMLFormElement is saved as a nsGenericHTMLElement
+ // because AddRef/Release are ambiguous with nsGenericHTMLFormElement
+ // and Focus() is declared (and defined) in nsGenericHTMLElement class.
+ RefPtr<nsGenericHTMLElement> mElement;
+};
+
+NS_IMPL_ADDREF_INHERITED(nsGenericHTMLElement, nsGenericHTMLElementBase)
+NS_IMPL_RELEASE_INHERITED(nsGenericHTMLElement, nsGenericHTMLElementBase)
+
+NS_INTERFACE_MAP_BEGIN(nsGenericHTMLElement)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLElement)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMElement)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMNode)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElementBase)
+
+nsresult
+nsGenericHTMLElement::CopyInnerTo(Element* aDst)
+{
+ nsresult rv;
+ int32_t i, count = GetAttrCount();
+ for (i = 0; i < count; ++i) {
+ const nsAttrName *name = mAttrsAndChildren.AttrNameAt(i);
+ const nsAttrValue *value = mAttrsAndChildren.AttrAt(i);
+
+ nsAutoString valStr;
+ value->ToString(valStr);
+
+ if (name->Equals(nsGkAtoms::style, kNameSpaceID_None) &&
+ value->Type() == nsAttrValue::eCSSDeclaration) {
+ DeclarationBlock* decl = value->GetCSSDeclarationValue();
+ // We can't just set this as a string, because that will fail
+ // to reparse the string into style data until the node is
+ // inserted into the document. Clone the Rule instead.
+ RefPtr<DeclarationBlock> declClone = decl->Clone();
+
+ rv = aDst->SetInlineStyleDeclaration(declClone, &valStr, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ continue;
+ }
+
+ rv = aDst->SetAttr(name->NamespaceID(), name->LocalName(),
+ name->GetPrefix(), valStr, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGenericHTMLElement::GetDataset(nsISupports** aDataset)
+{
+ *aDataset = Dataset().take();
+ return NS_OK;
+}
+
+static const nsAttrValue::EnumTable kDirTable[] = {
+ { "ltr", eDir_LTR },
+ { "rtl", eDir_RTL },
+ { "auto", eDir_Auto },
+ { nullptr, 0 }
+};
+
+void
+nsGenericHTMLElement::GetAccessKeyLabel(nsString& aLabel)
+{
+ nsAutoString suffix;
+ GetAccessKey(suffix);
+ if (!suffix.IsEmpty()) {
+ EventStateManager::GetAccessKeyLabelPrefix(this, aLabel);
+ aLabel.Append(suffix);
+ }
+}
+
+static bool IS_TABLE_CELL(nsIAtom* frameType) {
+ return nsGkAtoms::tableCellFrame == frameType ||
+ nsGkAtoms::bcTableCellFrame == frameType;
+}
+
+static bool
+IsOffsetParent(nsIFrame* aFrame)
+{
+ nsIAtom* frameType = aFrame->GetType();
+
+ if (IS_TABLE_CELL(frameType) || frameType == nsGkAtoms::tableFrame) {
+ // Per the IDL for Element, only td, th, and table are acceptable offsetParents
+ // apart from body or positioned elements; we need to check the content type as
+ // well as the frame type so we ignore anonymous tables created by an element
+ // with display: table-cell with no actual table
+ nsIContent* content = aFrame->GetContent();
+
+ return content->IsAnyOfHTMLElements(nsGkAtoms::table,
+ nsGkAtoms::td,
+ nsGkAtoms::th);
+ }
+ return false;
+}
+
+Element*
+nsGenericHTMLElement::GetOffsetRect(CSSIntRect& aRect)
+{
+ aRect = CSSIntRect();
+
+ nsIFrame* frame = GetStyledFrame();
+ if (!frame) {
+ return nullptr;
+ }
+
+ nsIFrame* parent = frame->GetParent();
+ nsPoint origin(0, 0);
+
+ if (parent && parent->GetType() == nsGkAtoms::tableWrapperFrame &&
+ frame->GetType() == nsGkAtoms::tableFrame) {
+ origin = parent->GetPositionIgnoringScrolling();
+ parent = parent->GetParent();
+ }
+
+ nsIContent* offsetParent = nullptr;
+ Element* docElement = GetComposedDoc()->GetRootElement();
+ nsIContent* content = frame->GetContent();
+
+ if (content && (content->IsHTMLElement(nsGkAtoms::body) ||
+ content == docElement)) {
+ parent = frame;
+ }
+ else {
+ const bool isPositioned = frame->IsAbsPosContainingBlock();
+ const bool isAbsolutelyPositioned = frame->IsAbsolutelyPositioned();
+ origin += frame->GetPositionIgnoringScrolling();
+
+ for ( ; parent ; parent = parent->GetParent()) {
+ content = parent->GetContent();
+
+ // Stop at the first ancestor that is positioned.
+ if (parent->IsAbsPosContainingBlock()) {
+ offsetParent = content;
+ break;
+ }
+
+ // Add the parent's origin to our own to get to the
+ // right coordinate system.
+ const bool isOffsetParent = !isPositioned && IsOffsetParent(parent);
+ if (!isAbsolutelyPositioned && !isOffsetParent) {
+ origin += parent->GetPositionIgnoringScrolling();
+ }
+
+ if (content) {
+ // If we've hit the document element, break here.
+ if (content == docElement) {
+ break;
+ }
+
+ // Break if the ancestor frame type makes it suitable as offset parent
+ // and this element is *not* positioned or if we found the body element.
+ if (isOffsetParent || content->IsHTMLElement(nsGkAtoms::body)) {
+ offsetParent = content;
+ break;
+ }
+ }
+ }
+
+ if (isAbsolutelyPositioned && !offsetParent) {
+ // If this element is absolutely positioned, but we don't have
+ // an offset parent it means this element is an absolutely
+ // positioned child that's not nested inside another positioned
+ // element, in this case the element's frame's parent is the
+ // frame for the HTML element so we fail to find the body in the
+ // parent chain. We want the offset parent in this case to be
+ // the body, so we just get the body element from the document.
+
+ nsCOMPtr<nsIDOMHTMLDocument> html_doc(do_QueryInterface(GetComposedDoc()));
+
+ if (html_doc) {
+ offsetParent = static_cast<nsHTMLDocument*>(html_doc.get())->GetBody();
+ }
+ }
+ }
+
+ // Subtract the parent border unless it uses border-box sizing.
+ if (parent &&
+ parent->StylePosition()->mBoxSizing != StyleBoxSizing::Border) {
+ const nsStyleBorder* border = parent->StyleBorder();
+ origin.x -= border->GetComputedBorderWidth(NS_SIDE_LEFT);
+ origin.y -= border->GetComputedBorderWidth(NS_SIDE_TOP);
+ }
+
+ // XXX We should really consider subtracting out padding for
+ // content-box sizing, but we should see what IE does....
+
+ // Get the union of all rectangles in this and continuation frames.
+ // It doesn't really matter what we use as aRelativeTo here, since
+ // we only care about the size. We just have to use something non-null.
+ nsRect rcFrame = nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame);
+ rcFrame.MoveTo(origin);
+ aRect = CSSIntRect::FromAppUnitsRounded(rcFrame);
+
+ return offsetParent ? offsetParent->AsElement() : nullptr;
+}
+
+NS_IMETHODIMP
+nsGenericHTMLElement::InsertAdjacentHTML(const nsAString& aPosition,
+ const nsAString& aText)
+{
+ ErrorResult rv;
+ Element::InsertAdjacentHTML(aPosition, aText, rv);
+ return rv.StealNSResult();
+}
+
+bool
+nsGenericHTMLElement::Spellcheck()
+{
+ // Has the state has been explicitly set?
+ nsIContent* node;
+ for (node = this; node; node = node->GetParent()) {
+ if (node->IsHTMLElement()) {
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::_true, &nsGkAtoms::_false, nullptr};
+ switch (node->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::spellcheck,
+ strings, eCaseMatters)) {
+ case 0: // spellcheck = "true"
+ return true;
+ case 1: // spellcheck = "false"
+ return false;
+ }
+ }
+ }
+
+ // contenteditable/designMode are spellchecked by default
+ if (IsEditable()) {
+ return true;
+ }
+
+ // Is this a chrome element?
+ if (nsContentUtils::IsChromeDoc(OwnerDoc())) {
+ return false; // Not spellchecked by default
+ }
+
+ // Anything else that's not a form control is not spellchecked by default
+ nsCOMPtr<nsIFormControl> formControl = do_QueryObject(this);
+ if (!formControl) {
+ return false; // Not spellchecked by default
+ }
+
+ // Is this a multiline plaintext input?
+ int32_t controlType = formControl->GetType();
+ if (controlType == NS_FORM_TEXTAREA) {
+ return true; // Spellchecked by default
+ }
+
+ // Is this anything other than an input text?
+ // Other inputs are not spellchecked.
+ if (controlType != NS_FORM_INPUT_TEXT) {
+ return false; // Not spellchecked by default
+ }
+
+ // Does the user want input text spellchecked by default?
+ // NOTE: Do not reflect a pref value of 0 back to the DOM getter.
+ // The web page should not know if the user has disabled spellchecking.
+ // We'll catch this in the editor itself.
+ int32_t spellcheckLevel = Preferences::GetInt("layout.spellcheckDefault", 1);
+ return spellcheckLevel == 2; // "Spellcheck multi- and single-line"
+}
+
+bool
+nsGenericHTMLElement::InNavQuirksMode(nsIDocument* aDoc)
+{
+ return aDoc && aDoc->GetCompatibilityMode() == eCompatibility_NavQuirks;
+}
+
+void
+nsGenericHTMLElement::UpdateEditableState(bool aNotify)
+{
+ // XXX Should we do this only when in a document?
+ ContentEditableTristate value = GetContentEditableValue();
+ if (value != eInherit) {
+ DoSetEditableFlag(!!value, aNotify);
+ return;
+ }
+
+ nsStyledElement::UpdateEditableState(aNotify);
+}
+
+EventStates
+nsGenericHTMLElement::IntrinsicState() const
+{
+ EventStates state = nsGenericHTMLElementBase::IntrinsicState();
+
+ if (GetDirectionality() == eDir_RTL) {
+ state |= NS_EVENT_STATE_RTL;
+ state &= ~NS_EVENT_STATE_LTR;
+ } else { // at least for HTML, directionality is exclusively LTR or RTL
+ NS_ASSERTION(GetDirectionality() == eDir_LTR,
+ "HTML element's directionality must be either RTL or LTR");
+ state |= NS_EVENT_STATE_LTR;
+ state &= ~NS_EVENT_STATE_RTL;
+ }
+
+ return state;
+}
+
+uint32_t
+nsGenericHTMLElement::EditableInclusiveDescendantCount()
+{
+ bool isEditable = IsInUncomposedDoc() && HasFlag(NODE_IS_EDITABLE) &&
+ GetContentEditableValue() == eTrue;
+ return EditableDescendantCount() + isEditable;
+}
+
+nsresult
+nsGenericHTMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElementBase::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aDocument) {
+ RegAccessKey();
+ if (HasName()) {
+ aDocument->
+ AddToNameTable(this, GetParsedAttr(nsGkAtoms::name)->GetAtomValue());
+ }
+
+ if (HasFlag(NODE_IS_EDITABLE) && GetContentEditableValue() == eTrue) {
+ nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(aDocument);
+ if (htmlDocument) {
+ htmlDocument->ChangeContentEditableCount(this, +1);
+ }
+ }
+ }
+
+ return rv;
+}
+
+void
+nsGenericHTMLElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ if (IsInUncomposedDoc()) {
+ UnregAccessKey();
+ }
+
+ RemoveFromNameTable();
+
+ if (GetContentEditableValue() == eTrue) {
+ //XXXsmaug Fix this for Shadow DOM, bug 1066965.
+ nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(GetUncomposedDoc());
+ if (htmlDocument) {
+ htmlDocument->ChangeContentEditableCount(this, -1);
+ }
+ }
+
+ nsStyledElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+HTMLFormElement*
+nsGenericHTMLElement::FindAncestorForm(HTMLFormElement* aCurrentForm)
+{
+ NS_ASSERTION(!HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
+ IsHTMLElement(nsGkAtoms::img),
+ "FindAncestorForm should not be called if @form is set!");
+
+ // Make sure we don't end up finding a form that's anonymous from
+ // our point of view.
+ nsIContent* bindingParent = GetBindingParent();
+
+ nsIContent* content = this;
+ while (content != bindingParent && content) {
+ // If the current ancestor is a form, return it as our form
+ if (content->IsHTMLElement(nsGkAtoms::form)) {
+#ifdef DEBUG
+ if (!nsContentUtils::IsInSameAnonymousTree(this, content)) {
+ // It's possible that we started unbinding at |content| or
+ // some ancestor of it, and |content| and |this| used to all be
+ // anonymous. Check for this the hard way.
+ for (nsIContent* child = this; child != content;
+ child = child->GetParent()) {
+ NS_ASSERTION(child->GetParent()->IndexOf(child) != -1,
+ "Walked too far?");
+ }
+ }
+#endif
+ return static_cast<HTMLFormElement*>(content);
+ }
+
+ nsIContent *prevContent = content;
+ content = prevContent->GetParent();
+
+ if (!content && aCurrentForm) {
+ // We got to the root of the subtree we're in, and we're being removed
+ // from the DOM (the only time we get into this method with a non-null
+ // aCurrentForm). Check whether aCurrentForm is in the same subtree. If
+ // it is, we want to return aCurrentForm, since this case means that
+ // we're one of those inputs-in-a-table that have a hacked mForm pointer
+ // and a subtree containing both us and the form got removed from the
+ // DOM.
+ if (nsContentUtils::ContentIsDescendantOf(aCurrentForm, prevContent)) {
+ return aCurrentForm;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+bool
+nsGenericHTMLElement::CheckHandleEventForAnchorsPreconditions(
+ EventChainVisitor& aVisitor)
+{
+ NS_PRECONDITION(nsCOMPtr<Link>(do_QueryObject(this)),
+ "should be called only when |this| implements |Link|");
+
+ if (!aVisitor.mPresContext) {
+ // We need a pres context to do link stuff. Some events (e.g. mutation
+ // events) don't have one.
+ // XXX: ideally, shouldn't we be able to do what we need without one?
+ return false;
+ }
+
+ //Need to check if we hit an imagemap area and if so see if we're handling
+ //the event on that map or on a link farther up the tree. If we're on a
+ //link farther up, do nothing.
+ nsCOMPtr<nsIContent> target = aVisitor.mPresContext->EventStateManager()->
+ GetEventTargetContent(aVisitor.mEvent);
+
+ return !target || !target->IsHTMLElement(nsGkAtoms::area) ||
+ IsHTMLElement(nsGkAtoms::area);
+}
+
+nsresult
+nsGenericHTMLElement::PreHandleEventForAnchors(EventChainPreVisitor& aVisitor)
+{
+ nsresult rv = nsGenericHTMLElementBase::PreHandleEvent(aVisitor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) {
+ return NS_OK;
+ }
+
+ return PreHandleEventForLinks(aVisitor);
+}
+
+nsresult
+nsGenericHTMLElement::PostHandleEventForAnchors(EventChainPostVisitor& aVisitor)
+{
+ if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) {
+ return NS_OK;
+ }
+
+ return PostHandleEventForLinks(aVisitor);
+}
+
+bool
+nsGenericHTMLElement::IsHTMLLink(nsIURI** aURI) const
+{
+ NS_PRECONDITION(aURI, "Must provide aURI out param");
+
+ *aURI = GetHrefURIForAnchors().take();
+ // We promise out param is non-null if we return true, so base rv on it
+ return *aURI != nullptr;
+}
+
+already_AddRefed<nsIURI>
+nsGenericHTMLElement::GetHrefURIForAnchors() const
+{
+ // This is used by the three Link implementations and
+ // nsHTMLStyleElement.
+
+ // Get href= attribute (relative URI).
+
+ // We use the nsAttrValue's copy of the URI string to avoid copying.
+ nsCOMPtr<nsIURI> uri;
+ GetURIAttr(nsGkAtoms::href, nullptr, getter_AddRefs(uri));
+
+ return uri.forget();
+}
+
+nsresult
+nsGenericHTMLElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (IsEventAttributeName(aName) && aValue) {
+ MOZ_ASSERT(aValue->Type() == nsAttrValue::eString,
+ "Expected string value for script body");
+ nsresult rv = SetEventHandler(aName, aValue->GetStringValue());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else if (aNotify && aName == nsGkAtoms::spellcheck) {
+ SyncEditorsOnSubtree(this);
+ }
+ else if (aName == nsGkAtoms::dir) {
+ Directionality dir = eDir_LTR;
+ if (aValue && aValue->Type() == nsAttrValue::eEnum) {
+ SetHasValidDir();
+ Directionality dirValue = (Directionality)aValue->GetEnumValue();
+ if (dirValue == eDir_Auto) {
+ SetHasDirAuto();
+ ClearHasFixedDir();
+ } else {
+ dir = dirValue;
+ SetDirectionality(dir, aNotify);
+ ClearHasDirAuto();
+ SetHasFixedDir();
+ }
+ } else {
+ ClearHasValidDir();
+ ClearHasFixedDir();
+ if (NodeInfo()->Equals(nsGkAtoms::bdi)) {
+ SetHasDirAuto();
+ } else {
+ ClearHasDirAuto();
+ dir = RecomputeDirectionality(this, aNotify);
+ }
+ }
+ SetDirectionalityOnDescendants(this, dir, aNotify);
+ }
+ }
+
+ return nsGenericHTMLElementBase::AfterSetAttr(aNamespaceID, aName,
+ aValue, aNotify);
+}
+
+EventListenerManager*
+nsGenericHTMLElement::GetEventListenerManagerForAttr(nsIAtom* aAttrName,
+ bool* aDefer)
+{
+ // Attributes on the body and frameset tags get set on the global object
+ if ((mNodeInfo->Equals(nsGkAtoms::body) ||
+ mNodeInfo->Equals(nsGkAtoms::frameset)) &&
+ // We only forward some event attributes from body/frameset to window
+ (0
+#define EVENT(name_, id_, type_, struct_) /* nothing */
+#define FORWARDED_EVENT(name_, id_, type_, struct_) \
+ || nsGkAtoms::on##name_ == aAttrName
+#define WINDOW_EVENT FORWARDED_EVENT
+#include "mozilla/EventNameList.h" // IWYU pragma: keep
+#undef WINDOW_EVENT
+#undef FORWARDED_EVENT
+#undef EVENT
+ )
+ ) {
+ nsPIDOMWindowInner *win;
+
+ // If we have a document, and it has a window, add the event
+ // listener on the window (the inner window). If not, proceed as
+ // normal.
+ // XXXbz sXBL/XBL2 issue: should we instead use GetComposedDoc() here,
+ // override BindToTree for those classes and munge event listeners there?
+ nsIDocument *document = OwnerDoc();
+
+ *aDefer = false;
+ if ((win = document->GetInnerWindow())) {
+ nsCOMPtr<EventTarget> piTarget(do_QueryInterface(win));
+
+ return piTarget->GetOrCreateListenerManager();
+ }
+
+ return nullptr;
+ }
+
+ return nsGenericHTMLElementBase::GetEventListenerManagerForAttr(aAttrName,
+ aDefer);
+}
+
+#define EVENT(name_, id_, type_, struct_) /* nothing; handled by nsINode */
+#define FORWARDED_EVENT(name_, id_, type_, struct_) \
+EventHandlerNonNull* \
+nsGenericHTMLElement::GetOn##name_() \
+{ \
+ if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
+ /* XXXbz note to self: add tests for this! */ \
+ if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \
+ nsGlobalWindow* globalWin = nsGlobalWindow::Cast(win); \
+ return globalWin->GetOn##name_(); \
+ } \
+ return nullptr; \
+ } \
+ \
+ return nsINode::GetOn##name_(); \
+} \
+void \
+nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler) \
+{ \
+ if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
+ nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \
+ if (!win) { \
+ return; \
+ } \
+ \
+ nsGlobalWindow* globalWin = nsGlobalWindow::Cast(win); \
+ return globalWin->SetOn##name_(handler); \
+ } \
+ \
+ return nsINode::SetOn##name_(handler); \
+}
+#define ERROR_EVENT(name_, id_, type_, struct_) \
+already_AddRefed<EventHandlerNonNull> \
+nsGenericHTMLElement::GetOn##name_() \
+{ \
+ if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
+ /* XXXbz note to self: add tests for this! */ \
+ if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \
+ nsGlobalWindow* globalWin = nsGlobalWindow::Cast(win); \
+ OnErrorEventHandlerNonNull* errorHandler = globalWin->GetOn##name_(); \
+ if (errorHandler) { \
+ RefPtr<EventHandlerNonNull> handler = \
+ new EventHandlerNonNull(errorHandler); \
+ return handler.forget(); \
+ } \
+ } \
+ return nullptr; \
+ } \
+ \
+ RefPtr<EventHandlerNonNull> handler = nsINode::GetOn##name_(); \
+ return handler.forget(); \
+} \
+void \
+nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler) \
+{ \
+ if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \
+ nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \
+ if (!win) { \
+ return; \
+ } \
+ \
+ nsGlobalWindow* globalWin = nsGlobalWindow::Cast(win); \
+ RefPtr<OnErrorEventHandlerNonNull> errorHandler; \
+ if (handler) { \
+ errorHandler = new OnErrorEventHandlerNonNull(handler); \
+ } \
+ return globalWin->SetOn##name_(errorHandler); \
+ } \
+ \
+ return nsINode::SetOn##name_(handler); \
+}
+#include "mozilla/EventNameList.h" // IWYU pragma: keep
+#undef ERROR_EVENT
+#undef FORWARDED_EVENT
+#undef EVENT
+
+nsresult
+nsGenericHTMLElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify)
+{
+ bool contentEditable = aNameSpaceID == kNameSpaceID_None &&
+ aName == nsGkAtoms::contenteditable;
+ bool accessKey = aName == nsGkAtoms::accesskey &&
+ aNameSpaceID == kNameSpaceID_None;
+
+ int32_t change = 0;
+ if (contentEditable) {
+ change = GetContentEditableValue() == eTrue ? -1 : 0;
+ SetMayHaveContentEditableAttr();
+ }
+
+ if (accessKey) {
+ UnregAccessKey();
+ }
+
+ nsresult rv = nsStyledElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
+ aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (contentEditable) {
+ if (aValue.IsEmpty() || aValue.LowerCaseEqualsLiteral("true")) {
+ change += 1;
+ }
+
+ ChangeEditableState(change);
+ }
+
+ if (accessKey && !aValue.IsEmpty()) {
+ SetFlags(NODE_HAS_ACCESSKEY);
+ RegAccessKey();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsGenericHTMLElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify)
+{
+ bool contentEditable = false;
+ int32_t contentEditableChange = 0;
+
+ // Check for event handlers
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::name) {
+ // Have to do this before clearing flag. See RemoveFromNameTable
+ RemoveFromNameTable();
+ ClearHasName();
+ }
+ else if (aAttribute == nsGkAtoms::contenteditable) {
+ contentEditable = true;
+ contentEditableChange = GetContentEditableValue() == eTrue ? -1 : 0;
+ }
+ else if (aAttribute == nsGkAtoms::accesskey) {
+ // Have to unregister before clearing flag. See UnregAccessKey
+ UnregAccessKey();
+ UnsetFlags(NODE_HAS_ACCESSKEY);
+ }
+ else if (IsEventAttributeName(aAttribute)) {
+ if (EventListenerManager* manager = GetExistingListenerManager()) {
+ manager->RemoveEventHandler(aAttribute, EmptyString());
+ }
+ }
+ }
+
+ nsresult rv = nsGenericHTMLElementBase::UnsetAttr(aNameSpaceID, aAttribute,
+ aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (contentEditable) {
+ ChangeEditableState(contentEditableChange);
+ }
+
+ return NS_OK;
+}
+
+void
+nsGenericHTMLElement::GetBaseTarget(nsAString& aBaseTarget) const
+{
+ OwnerDoc()->GetBaseTarget(aBaseTarget);
+}
+
+//----------------------------------------------------------------------
+
+bool
+nsGenericHTMLElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::dir) {
+ return aResult.ParseEnumValue(aValue, kDirTable, false);
+ }
+
+ if (aAttribute == nsGkAtoms::tabindex) {
+ return aResult.ParseIntValue(aValue);
+ }
+
+ if (aAttribute == nsGkAtoms::referrerpolicy) {
+ return ParseReferrerAttribute(aValue, aResult);
+ }
+
+ if (aAttribute == nsGkAtoms::name) {
+ // Store name as an atom. name="" means that the element has no name,
+ // not that it has an emptystring as the name.
+ RemoveFromNameTable();
+ if (aValue.IsEmpty()) {
+ ClearHasName();
+ return false;
+ }
+
+ aResult.ParseAtom(aValue);
+
+ if (CanHaveName(NodeInfo()->NameAtom())) {
+ SetHasName();
+ AddToNameTable(aResult.GetAtomValue());
+ }
+
+ return true;
+ }
+
+ if (aAttribute == nsGkAtoms::contenteditable) {
+ aResult.ParseAtom(aValue);
+ return true;
+ }
+
+ if (aAttribute == nsGkAtoms::rel) {
+ aResult.ParseAtomArray(aValue);
+ return true;
+ }
+ }
+
+ return nsGenericHTMLElementBase::ParseAttribute(aNamespaceID, aAttribute,
+ aValue, aResult);
+}
+
+bool
+nsGenericHTMLElement::ParseBackgroundAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None &&
+ aAttribute == nsGkAtoms::background &&
+ !aValue.IsEmpty()) {
+ // Resolve url to an absolute url
+ nsIDocument* doc = OwnerDoc();
+ nsCOMPtr<nsIURI> baseURI = GetBaseURI();
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = nsContentUtils::NewURIWithDocumentCharset(
+ getter_AddRefs(uri), aValue, doc, baseURI);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsString value(aValue);
+ RefPtr<nsStringBuffer> buffer = nsCSSValue::BufferFromString(value);
+ if (MOZ_UNLIKELY(!buffer)) {
+ return false;
+ }
+
+ mozilla::css::URLValue *url =
+ new mozilla::css::URLValue(uri, buffer, baseURI, doc->GetDocumentURI(),
+ NodePrincipal());
+ aResult.SetTo(url, &aValue);
+ return true;
+ }
+
+ return false;
+}
+
+bool
+nsGenericHTMLElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry* const map[] = {
+ sCommonAttributeMap
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+nsMapRuleToAttributesFunc
+nsGenericHTMLElement::GetAttributeMappingFunction() const
+{
+ return &MapCommonAttributesInto;
+}
+
+nsIFormControlFrame*
+nsGenericHTMLElement::GetFormControlFrame(bool aFlushFrames)
+{
+ if (aFlushFrames && IsInComposedDoc()) {
+ // Cause a flush of the frames, so we get up-to-date frame information
+ GetComposedDoc()->FlushPendingNotifications(Flush_Frames);
+ }
+ nsIFrame* frame = GetPrimaryFrame();
+ if (frame) {
+ nsIFormControlFrame* form_frame = do_QueryFrame(frame);
+ if (form_frame) {
+ return form_frame;
+ }
+
+ // If we have generated content, the primary frame will be a
+ // wrapper frame.. out real frame will be in its child list.
+ for (frame = frame->PrincipalChildList().FirstChild();
+ frame;
+ frame = frame->GetNextSibling()) {
+ form_frame = do_QueryFrame(frame);
+ if (form_frame) {
+ return form_frame;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+nsPresContext*
+nsGenericHTMLElement::GetPresContext(PresContextFor aFor)
+{
+ // Get the document
+ nsIDocument* doc = (aFor == eForComposedDoc) ?
+ GetComposedDoc() : GetUncomposedDoc();
+ if (doc) {
+ // Get presentation shell.
+ nsIPresShell *presShell = doc->GetShell();
+ if (presShell) {
+ return presShell->GetPresContext();
+ }
+ }
+
+ return nullptr;
+}
+
+static const nsAttrValue::EnumTable kDivAlignTable[] = {
+ { "left", NS_STYLE_TEXT_ALIGN_MOZ_LEFT },
+ { "right", NS_STYLE_TEXT_ALIGN_MOZ_RIGHT },
+ { "center", NS_STYLE_TEXT_ALIGN_MOZ_CENTER },
+ { "middle", NS_STYLE_TEXT_ALIGN_MOZ_CENTER },
+ { "justify", NS_STYLE_TEXT_ALIGN_JUSTIFY },
+ { nullptr, 0 }
+};
+
+static const nsAttrValue::EnumTable kFrameborderTable[] = {
+ { "yes", NS_STYLE_FRAME_YES },
+ { "no", NS_STYLE_FRAME_NO },
+ { "1", NS_STYLE_FRAME_1 },
+ { "0", NS_STYLE_FRAME_0 },
+ { nullptr, 0 }
+};
+
+static const nsAttrValue::EnumTable kScrollingTable[] = {
+ { "yes", NS_STYLE_FRAME_YES },
+ { "no", NS_STYLE_FRAME_NO },
+ { "on", NS_STYLE_FRAME_ON },
+ { "off", NS_STYLE_FRAME_OFF },
+ { "scroll", NS_STYLE_FRAME_SCROLL },
+ { "noscroll", NS_STYLE_FRAME_NOSCROLL },
+ { "auto", NS_STYLE_FRAME_AUTO },
+ { nullptr, 0 }
+};
+
+static const nsAttrValue::EnumTable kTableVAlignTable[] = {
+ { "top", NS_STYLE_VERTICAL_ALIGN_TOP },
+ { "middle", NS_STYLE_VERTICAL_ALIGN_MIDDLE },
+ { "bottom", NS_STYLE_VERTICAL_ALIGN_BOTTOM },
+ { "baseline",NS_STYLE_VERTICAL_ALIGN_BASELINE },
+ { nullptr, 0 }
+};
+
+bool
+nsGenericHTMLElement::ParseAlignValue(const nsAString& aString,
+ nsAttrValue& aResult)
+{
+ static const nsAttrValue::EnumTable kAlignTable[] = {
+ { "left", NS_STYLE_TEXT_ALIGN_LEFT },
+ { "right", NS_STYLE_TEXT_ALIGN_RIGHT },
+
+ { "top", NS_STYLE_VERTICAL_ALIGN_TOP },
+ { "middle", NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE },
+ { "bottom", NS_STYLE_VERTICAL_ALIGN_BASELINE },
+
+ { "center", NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE },
+ { "baseline", NS_STYLE_VERTICAL_ALIGN_BASELINE },
+
+ { "texttop", NS_STYLE_VERTICAL_ALIGN_TEXT_TOP },
+ { "absmiddle", NS_STYLE_VERTICAL_ALIGN_MIDDLE },
+ { "abscenter", NS_STYLE_VERTICAL_ALIGN_MIDDLE },
+ { "absbottom", NS_STYLE_VERTICAL_ALIGN_BOTTOM },
+ { nullptr, 0 }
+ };
+
+ return aResult.ParseEnumValue(aString, kAlignTable, false);
+}
+
+//----------------------------------------
+
+static const nsAttrValue::EnumTable kTableHAlignTable[] = {
+ { "left", NS_STYLE_TEXT_ALIGN_LEFT },
+ { "right", NS_STYLE_TEXT_ALIGN_RIGHT },
+ { "center", NS_STYLE_TEXT_ALIGN_CENTER },
+ { "char", NS_STYLE_TEXT_ALIGN_CHAR },
+ { "justify",NS_STYLE_TEXT_ALIGN_JUSTIFY },
+ { nullptr, 0 }
+};
+
+bool
+nsGenericHTMLElement::ParseTableHAlignValue(const nsAString& aString,
+ nsAttrValue& aResult)
+{
+ return aResult.ParseEnumValue(aString, kTableHAlignTable, false);
+}
+
+//----------------------------------------
+
+// This table is used for td, th, tr, col, thead, tbody and tfoot.
+static const nsAttrValue::EnumTable kTableCellHAlignTable[] = {
+ { "left", NS_STYLE_TEXT_ALIGN_MOZ_LEFT },
+ { "right", NS_STYLE_TEXT_ALIGN_MOZ_RIGHT },
+ { "center", NS_STYLE_TEXT_ALIGN_MOZ_CENTER },
+ { "char", NS_STYLE_TEXT_ALIGN_CHAR },
+ { "justify",NS_STYLE_TEXT_ALIGN_JUSTIFY },
+ { "middle", NS_STYLE_TEXT_ALIGN_MOZ_CENTER },
+ { "absmiddle", NS_STYLE_TEXT_ALIGN_CENTER },
+ { nullptr, 0 }
+};
+
+bool
+nsGenericHTMLElement::ParseTableCellHAlignValue(const nsAString& aString,
+ nsAttrValue& aResult)
+{
+ return aResult.ParseEnumValue(aString, kTableCellHAlignTable, false);
+}
+
+//----------------------------------------
+
+bool
+nsGenericHTMLElement::ParseTableVAlignValue(const nsAString& aString,
+ nsAttrValue& aResult)
+{
+ return aResult.ParseEnumValue(aString, kTableVAlignTable, false);
+}
+
+bool
+nsGenericHTMLElement::ParseDivAlignValue(const nsAString& aString,
+ nsAttrValue& aResult)
+{
+ return aResult.ParseEnumValue(aString, kDivAlignTable, false);
+}
+
+bool
+nsGenericHTMLElement::ParseImageAttribute(nsIAtom* aAttribute,
+ const nsAString& aString,
+ nsAttrValue& aResult)
+{
+ if ((aAttribute == nsGkAtoms::width) ||
+ (aAttribute == nsGkAtoms::height)) {
+ return aResult.ParseSpecialIntValue(aString);
+ }
+ if ((aAttribute == nsGkAtoms::hspace) ||
+ (aAttribute == nsGkAtoms::vspace) ||
+ (aAttribute == nsGkAtoms::border)) {
+ return aResult.ParseIntWithBounds(aString, 0);
+ }
+ return false;
+}
+
+bool
+nsGenericHTMLElement::ParseReferrerAttribute(const nsAString& aString,
+ nsAttrValue& aResult)
+{
+ static const nsAttrValue::EnumTable kReferrerTable[] = {
+ { net::kRPS_No_Referrer, static_cast<int16_t>(net::RP_No_Referrer) },
+ { net::kRPS_Origin, static_cast<int16_t>(net::RP_Origin) },
+ { net::kRPS_Origin_When_Cross_Origin, static_cast<int16_t>(net::RP_Origin_When_Crossorigin) },
+ { net::kRPS_No_Referrer_When_Downgrade, static_cast<int16_t>(net::RP_No_Referrer_When_Downgrade) },
+ { net::kRPS_Unsafe_URL, static_cast<int16_t>(net::RP_Unsafe_URL) },
+ { net::kRPS_Strict_Origin, static_cast<int16_t>(net::RP_Strict_Origin) },
+ { net::kRPS_Same_Origin, static_cast<int16_t>(net::RP_Same_Origin) },
+ { net::kRPS_Strict_Origin_When_Cross_Origin, static_cast<int16_t>(net::RP_Strict_Origin_When_Cross_Origin) },
+ { nullptr, 0 }
+ };
+ return aResult.ParseEnumValue(aString, kReferrerTable, false);
+}
+
+bool
+nsGenericHTMLElement::ParseFrameborderValue(const nsAString& aString,
+ nsAttrValue& aResult)
+{
+ return aResult.ParseEnumValue(aString, kFrameborderTable, false);
+}
+
+bool
+nsGenericHTMLElement::ParseScrollingValue(const nsAString& aString,
+ nsAttrValue& aResult)
+{
+ return aResult.ParseEnumValue(aString, kScrollingTable, false);
+}
+
+static inline void
+MapLangAttributeInto(const nsMappedAttributes* aAttributes, nsRuleData* aData)
+{
+ if (!(aData->mSIDs & (NS_STYLE_INHERIT_BIT(Font) |
+ NS_STYLE_INHERIT_BIT(Text)))) {
+ return;
+ }
+
+ const nsAttrValue* langValue = aAttributes->GetAttr(nsGkAtoms::lang);
+ if (!langValue || langValue->Type() != nsAttrValue::eString) {
+ return;
+ }
+
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Font)) {
+ nsCSSValue* lang = aData->ValueForLang();
+ if (lang->GetUnit() == eCSSUnit_Null) {
+ lang->SetStringValue(langValue->GetStringValue(), eCSSUnit_Ident);
+ }
+ }
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Text)) {
+ nsCSSValue* emphasisPos = aData->ValueForTextEmphasisPosition();
+ if (emphasisPos->GetUnit() == eCSSUnit_Null) {
+ const nsAString& lang = langValue->GetStringValue();
+ if (nsStyleUtil::MatchesLanguagePrefix(lang, u"zh")) {
+ emphasisPos->SetIntValue(NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT_ZH,
+ eCSSUnit_Enumerated);
+ } else if (nsStyleUtil::MatchesLanguagePrefix(lang, u"ja") ||
+ nsStyleUtil::MatchesLanguagePrefix(lang, u"mn")) {
+ // This branch is currently no part of the spec.
+ // See bug 1040668 comment 69 and comment 75.
+ emphasisPos->SetIntValue(NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT,
+ eCSSUnit_Enumerated);
+ }
+ }
+ }
+}
+
+/**
+ * Handle attributes common to all html elements
+ */
+void
+nsGenericHTMLElement::MapCommonAttributesIntoExceptHidden(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(UserInterface)) {
+ nsCSSValue* userModify = aData->ValueForUserModify();
+ if (userModify->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value =
+ aAttributes->GetAttr(nsGkAtoms::contenteditable);
+ if (value) {
+ if (value->Equals(nsGkAtoms::_empty, eCaseMatters) ||
+ value->Equals(nsGkAtoms::_true, eIgnoreCase)) {
+ userModify->SetIntValue(StyleUserModify::ReadWrite,
+ eCSSUnit_Enumerated);
+ }
+ else if (value->Equals(nsGkAtoms::_false, eIgnoreCase)) {
+ userModify->SetIntValue(StyleUserModify::ReadOnly,
+ eCSSUnit_Enumerated);
+ }
+ }
+ }
+ }
+
+ MapLangAttributeInto(aAttributes, aData);
+}
+
+void
+nsGenericHTMLElement::MapCommonAttributesInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ MapCommonAttributesIntoExceptHidden(aAttributes, aData);
+
+ if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Display)) {
+ nsCSSValue* display = aData->ValueForDisplay();
+ if (display->GetUnit() == eCSSUnit_Null) {
+ if (aAttributes->IndexOfAttr(nsGkAtoms::hidden) >= 0) {
+ display->SetIntValue(StyleDisplay::None, eCSSUnit_Enumerated);
+ }
+ }
+ }
+}
+
+/* static */ const nsGenericHTMLElement::MappedAttributeEntry
+nsGenericHTMLElement::sCommonAttributeMap[] = {
+ { &nsGkAtoms::contenteditable },
+ { &nsGkAtoms::lang },
+ { &nsGkAtoms::hidden },
+ { nullptr }
+};
+
+/* static */ const Element::MappedAttributeEntry
+nsGenericHTMLElement::sImageMarginSizeAttributeMap[] = {
+ { &nsGkAtoms::width },
+ { &nsGkAtoms::height },
+ { &nsGkAtoms::hspace },
+ { &nsGkAtoms::vspace },
+ { nullptr }
+};
+
+/* static */ const Element::MappedAttributeEntry
+nsGenericHTMLElement::sImageAlignAttributeMap[] = {
+ { &nsGkAtoms::align },
+ { nullptr }
+};
+
+/* static */ const Element::MappedAttributeEntry
+nsGenericHTMLElement::sDivAlignAttributeMap[] = {
+ { &nsGkAtoms::align },
+ { nullptr }
+};
+
+/* static */ const Element::MappedAttributeEntry
+nsGenericHTMLElement::sImageBorderAttributeMap[] = {
+ { &nsGkAtoms::border },
+ { nullptr }
+};
+
+/* static */ const Element::MappedAttributeEntry
+nsGenericHTMLElement::sBackgroundAttributeMap[] = {
+ { &nsGkAtoms::background },
+ { &nsGkAtoms::bgcolor },
+ { nullptr }
+};
+
+/* static */ const Element::MappedAttributeEntry
+nsGenericHTMLElement::sBackgroundColorAttributeMap[] = {
+ { &nsGkAtoms::bgcolor },
+ { nullptr }
+};
+
+void
+nsGenericHTMLElement::MapImageAlignAttributeInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aRuleData)
+{
+ if (aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(Display)) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align);
+ if (value && value->Type() == nsAttrValue::eEnum) {
+ int32_t align = value->GetEnumValue();
+ nsCSSValue* cssFloat = aRuleData->ValueForFloat();
+ if (cssFloat->GetUnit() == eCSSUnit_Null) {
+ if (align == NS_STYLE_TEXT_ALIGN_LEFT) {
+ cssFloat->SetIntValue(StyleFloat::Left, eCSSUnit_Enumerated);
+ } else if (align == NS_STYLE_TEXT_ALIGN_RIGHT) {
+ cssFloat->SetIntValue(StyleFloat::Right, eCSSUnit_Enumerated);
+ }
+ }
+ nsCSSValue* verticalAlign = aRuleData->ValueForVerticalAlign();
+ if (verticalAlign->GetUnit() == eCSSUnit_Null) {
+ switch (align) {
+ case NS_STYLE_TEXT_ALIGN_LEFT:
+ case NS_STYLE_TEXT_ALIGN_RIGHT:
+ break;
+ default:
+ verticalAlign->SetIntValue(align, eCSSUnit_Enumerated);
+ break;
+ }
+ }
+ }
+ }
+}
+
+void
+nsGenericHTMLElement::MapDivAlignAttributeInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aRuleData)
+{
+ if (aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(Text)) {
+ nsCSSValue* textAlign = aRuleData->ValueForTextAlign();
+ if (textAlign->GetUnit() == eCSSUnit_Null) {
+ // align: enum
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align);
+ if (value && value->Type() == nsAttrValue::eEnum)
+ textAlign->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated);
+ }
+ }
+}
+
+
+void
+nsGenericHTMLElement::MapImageMarginAttributeInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (!(aData->mSIDs & NS_STYLE_INHERIT_BIT(Margin)))
+ return;
+
+ const nsAttrValue* value;
+
+ // hspace: value
+ value = aAttributes->GetAttr(nsGkAtoms::hspace);
+ if (value) {
+ nsCSSValue hval;
+ if (value->Type() == nsAttrValue::eInteger)
+ hval.SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ else if (value->Type() == nsAttrValue::ePercent)
+ hval.SetPercentValue(value->GetPercentValue());
+
+ if (hval.GetUnit() != eCSSUnit_Null) {
+ nsCSSValue* left = aData->ValueForMarginLeft();
+ if (left->GetUnit() == eCSSUnit_Null)
+ *left = hval;
+ nsCSSValue* right = aData->ValueForMarginRight();
+ if (right->GetUnit() == eCSSUnit_Null)
+ *right = hval;
+ }
+ }
+
+ // vspace: value
+ value = aAttributes->GetAttr(nsGkAtoms::vspace);
+ if (value) {
+ nsCSSValue vval;
+ if (value->Type() == nsAttrValue::eInteger)
+ vval.SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ else if (value->Type() == nsAttrValue::ePercent)
+ vval.SetPercentValue(value->GetPercentValue());
+
+ if (vval.GetUnit() != eCSSUnit_Null) {
+ nsCSSValue* top = aData->ValueForMarginTop();
+ if (top->GetUnit() == eCSSUnit_Null)
+ *top = vval;
+ nsCSSValue* bottom = aData->ValueForMarginBottom();
+ if (bottom->GetUnit() == eCSSUnit_Null)
+ *bottom = vval;
+ }
+ }
+}
+
+void
+nsGenericHTMLElement::MapImageSizeAttributesInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (!(aData->mSIDs & NS_STYLE_INHERIT_BIT(Position)))
+ return;
+
+ // width: value
+ nsCSSValue* width = aData->ValueForWidth();
+ if (width->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::width);
+ if (value && value->Type() == nsAttrValue::eInteger)
+ width->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ else if (value && value->Type() == nsAttrValue::ePercent)
+ width->SetPercentValue(value->GetPercentValue());
+ }
+
+ // height: value
+ nsCSSValue* height = aData->ValueForHeight();
+ if (height->GetUnit() == eCSSUnit_Null) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::height);
+ if (value && value->Type() == nsAttrValue::eInteger)
+ height->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel);
+ else if (value && value->Type() == nsAttrValue::ePercent)
+ height->SetPercentValue(value->GetPercentValue());
+ }
+}
+
+void
+nsGenericHTMLElement::MapImageBorderAttributeInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (!(aData->mSIDs & NS_STYLE_INHERIT_BIT(Border)))
+ return;
+
+ // border: pixels
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::border);
+ if (!value)
+ return;
+
+ nscoord val = 0;
+ if (value->Type() == nsAttrValue::eInteger)
+ val = value->GetIntegerValue();
+
+ nsCSSValue* borderLeftWidth = aData->ValueForBorderLeftWidth();
+ if (borderLeftWidth->GetUnit() == eCSSUnit_Null)
+ borderLeftWidth->SetFloatValue((float)val, eCSSUnit_Pixel);
+ nsCSSValue* borderTopWidth = aData->ValueForBorderTopWidth();
+ if (borderTopWidth->GetUnit() == eCSSUnit_Null)
+ borderTopWidth->SetFloatValue((float)val, eCSSUnit_Pixel);
+ nsCSSValue* borderRightWidth = aData->ValueForBorderRightWidth();
+ if (borderRightWidth->GetUnit() == eCSSUnit_Null)
+ borderRightWidth->SetFloatValue((float)val, eCSSUnit_Pixel);
+ nsCSSValue* borderBottomWidth = aData->ValueForBorderBottomWidth();
+ if (borderBottomWidth->GetUnit() == eCSSUnit_Null)
+ borderBottomWidth->SetFloatValue((float)val, eCSSUnit_Pixel);
+
+ nsCSSValue* borderLeftStyle = aData->ValueForBorderLeftStyle();
+ if (borderLeftStyle->GetUnit() == eCSSUnit_Null)
+ borderLeftStyle->SetIntValue(NS_STYLE_BORDER_STYLE_SOLID, eCSSUnit_Enumerated);
+ nsCSSValue* borderTopStyle = aData->ValueForBorderTopStyle();
+ if (borderTopStyle->GetUnit() == eCSSUnit_Null)
+ borderTopStyle->SetIntValue(NS_STYLE_BORDER_STYLE_SOLID, eCSSUnit_Enumerated);
+ nsCSSValue* borderRightStyle = aData->ValueForBorderRightStyle();
+ if (borderRightStyle->GetUnit() == eCSSUnit_Null)
+ borderRightStyle->SetIntValue(NS_STYLE_BORDER_STYLE_SOLID, eCSSUnit_Enumerated);
+ nsCSSValue* borderBottomStyle = aData->ValueForBorderBottomStyle();
+ if (borderBottomStyle->GetUnit() == eCSSUnit_Null)
+ borderBottomStyle->SetIntValue(NS_STYLE_BORDER_STYLE_SOLID, eCSSUnit_Enumerated);
+
+ nsCSSValue* borderLeftColor = aData->ValueForBorderLeftColor();
+ if (borderLeftColor->GetUnit() == eCSSUnit_Null)
+ borderLeftColor->SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
+ nsCSSValue* borderTopColor = aData->ValueForBorderTopColor();
+ if (borderTopColor->GetUnit() == eCSSUnit_Null)
+ borderTopColor->SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
+ nsCSSValue* borderRightColor = aData->ValueForBorderRightColor();
+ if (borderRightColor->GetUnit() == eCSSUnit_Null)
+ borderRightColor->SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
+ nsCSSValue* borderBottomColor = aData->ValueForBorderBottomColor();
+ if (borderBottomColor->GetUnit() == eCSSUnit_Null)
+ borderBottomColor->SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
+}
+
+void
+nsGenericHTMLElement::MapBackgroundInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (!(aData->mSIDs & NS_STYLE_INHERIT_BIT(Background)))
+ return;
+
+ nsPresContext* presContext = aData->mPresContext;
+ nsCSSValue* backImage = aData->ValueForBackgroundImage();
+ if (backImage->GetUnit() == eCSSUnit_Null &&
+ presContext->UseDocumentColors()) {
+ // background
+ nsAttrValue* value =
+ const_cast<nsAttrValue*>(aAttributes->GetAttr(nsGkAtoms::background));
+ // If the value is an image, or it is a URL and we attempted a load,
+ // put it in the style tree.
+ if (value) {
+ if (value->Type() == nsAttrValue::eURL) {
+ value->LoadImage(presContext->Document());
+ }
+ if (value->Type() == nsAttrValue::eImage) {
+ nsCSSValueList* list = backImage->SetListValue();
+ list->mValue.SetImageValue(value->GetImageValue());
+ }
+ }
+ }
+}
+
+void
+nsGenericHTMLElement::MapBGColorInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ if (!(aData->mSIDs & NS_STYLE_INHERIT_BIT(Background)))
+ return;
+
+ nsCSSValue* backColor = aData->ValueForBackgroundColor();
+ if (backColor->GetUnit() == eCSSUnit_Null &&
+ aData->mPresContext->UseDocumentColors()) {
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::bgcolor);
+ nscolor color;
+ if (value && value->GetColorValue(color)) {
+ backColor->SetColorValue(color);
+ }
+ }
+}
+
+void
+nsGenericHTMLElement::MapBackgroundAttributesInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ MapBackgroundInto(aAttributes, aData);
+ MapBGColorInto(aAttributes, aData);
+}
+
+//----------------------------------------------------------------------
+
+nsresult
+nsGenericHTMLElement::SetAttrHelper(nsIAtom* aAttr, const nsAString& aValue)
+{
+ return SetAttr(kNameSpaceID_None, aAttr, aValue, true);
+}
+
+int32_t
+nsGenericHTMLElement::GetIntAttr(nsIAtom* aAttr, int32_t aDefault) const
+{
+ const nsAttrValue* attrVal = mAttrsAndChildren.GetAttr(aAttr);
+ if (attrVal && attrVal->Type() == nsAttrValue::eInteger) {
+ return attrVal->GetIntegerValue();
+ }
+ return aDefault;
+}
+
+nsresult
+nsGenericHTMLElement::SetIntAttr(nsIAtom* aAttr, int32_t aValue)
+{
+ nsAutoString value;
+ value.AppendInt(aValue);
+
+ return SetAttr(kNameSpaceID_None, aAttr, value, true);
+}
+
+uint32_t
+nsGenericHTMLElement::GetUnsignedIntAttr(nsIAtom* aAttr,
+ uint32_t aDefault) const
+{
+ const nsAttrValue* attrVal = mAttrsAndChildren.GetAttr(aAttr);
+ if (!attrVal || attrVal->Type() != nsAttrValue::eInteger) {
+ return aDefault;
+ }
+
+ return attrVal->GetIntegerValue();
+}
+
+void
+nsGenericHTMLElement::GetURIAttr(nsIAtom* aAttr, nsIAtom* aBaseAttr,
+ nsAString& aResult) const
+{
+ nsCOMPtr<nsIURI> uri;
+ bool hadAttr = GetURIAttr(aAttr, aBaseAttr, getter_AddRefs(uri));
+ if (!hadAttr) {
+ aResult.Truncate();
+ return;
+ }
+
+ if (!uri) {
+ // Just return the attr value
+ GetAttr(kNameSpaceID_None, aAttr, aResult);
+ return;
+ }
+
+ nsAutoCString spec;
+ uri->GetSpec(spec);
+ CopyUTF8toUTF16(spec, aResult);
+}
+
+bool
+nsGenericHTMLElement::GetURIAttr(nsIAtom* aAttr, nsIAtom* aBaseAttr, nsIURI** aURI) const
+{
+ *aURI = nullptr;
+
+ const nsAttrValue* attr = mAttrsAndChildren.GetAttr(aAttr);
+ if (!attr) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> baseURI = GetBaseURI();
+
+ if (aBaseAttr) {
+ nsAutoString baseAttrValue;
+ if (GetAttr(kNameSpaceID_None, aBaseAttr, baseAttrValue)) {
+ nsCOMPtr<nsIURI> baseAttrURI;
+ nsresult rv =
+ nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(baseAttrURI),
+ baseAttrValue, OwnerDoc(),
+ baseURI);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+ baseURI.swap(baseAttrURI);
+ }
+ }
+
+ // Don't care about return value. If it fails, we still want to
+ // return true, and *aURI will be null.
+ nsContentUtils::NewURIWithDocumentCharset(aURI,
+ attr->GetStringValue(),
+ OwnerDoc(), baseURI);
+ return true;
+}
+
+/* static */ bool
+nsGenericHTMLElement::IsScrollGrabAllowed(JSContext*, JSObject*)
+{
+ // Only allow scroll grabbing in chrome
+ nsIPrincipal* prin = nsContentUtils::SubjectPrincipal();
+ return nsContentUtils::IsSystemPrincipal(prin);
+}
+
+HTMLMenuElement*
+nsGenericHTMLElement::GetContextMenu() const
+{
+ nsAutoString value;
+ GetHTMLAttr(nsGkAtoms::contextmenu, value);
+ if (!value.IsEmpty()) {
+ //XXXsmaug How should this work in Shadow DOM?
+ nsIDocument* doc = GetUncomposedDoc();
+ if (doc) {
+ return HTMLMenuElement::FromContentOrNull(doc->GetElementById(value));
+ }
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsGenericHTMLElement::GetContextMenu(nsIDOMHTMLMenuElement** aContextMenu)
+{
+ NS_IF_ADDREF(*aContextMenu = GetContextMenu());
+ return NS_OK;
+}
+
+bool
+nsGenericHTMLElement::IsLabelable() const
+{
+ return IsAnyOfHTMLElements(nsGkAtoms::progress, nsGkAtoms::meter);
+}
+
+bool
+nsGenericHTMLElement::IsInteractiveHTMLContent(bool aIgnoreTabindex) const
+{
+ return IsAnyOfHTMLElements(nsGkAtoms::details, nsGkAtoms::embed,
+ nsGkAtoms::keygen) ||
+ (!aIgnoreTabindex && HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex));
+}
+
+// static
+bool
+nsGenericHTMLElement::TouchEventsEnabled(JSContext* aCx, JSObject* aGlobal)
+{
+ return TouchEvent::PrefEnabled(aCx, aGlobal);
+}
+
+//----------------------------------------------------------------------
+
+nsGenericHTMLFormElement::nsGenericHTMLFormElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ , mForm(nullptr)
+ , mFieldSet(nullptr)
+{
+ // We should add the NS_EVENT_STATE_ENABLED bit here as needed, but
+ // that depends on our type, which is not initialized yet. So we
+ // have to do this in subclasses.
+}
+
+nsGenericHTMLFormElement::~nsGenericHTMLFormElement()
+{
+ if (mFieldSet) {
+ mFieldSet->RemoveElement(this);
+ }
+
+ // Check that this element doesn't know anything about its form at this point.
+ NS_ASSERTION(!mForm, "mForm should be null at this point!");
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsGenericHTMLFormElement,
+ nsGenericHTMLElement,
+ nsIFormControl)
+
+nsINode*
+nsGenericHTMLFormElement::GetScopeChainParent() const
+{
+ return mForm ? mForm : nsGenericHTMLElement::GetScopeChainParent();
+}
+
+bool
+nsGenericHTMLFormElement::IsNodeOfType(uint32_t aFlags) const
+{
+ return !(aFlags & ~(eCONTENT | eHTML_FORM_CONTROL));
+}
+
+void
+nsGenericHTMLFormElement::SaveSubtreeState()
+{
+ SaveState();
+
+ nsGenericHTMLElement::SaveSubtreeState();
+}
+
+void
+nsGenericHTMLFormElement::SetForm(nsIDOMHTMLFormElement* aForm)
+{
+ NS_PRECONDITION(aForm, "Don't pass null here");
+ NS_ASSERTION(!mForm,
+ "We don't support switching from one non-null form to another.");
+
+ // keep a *weak* ref to the form here
+ mForm = static_cast<HTMLFormElement*>(aForm);
+}
+
+void
+nsGenericHTMLFormElement::ClearForm(bool aRemoveFromForm)
+{
+ NS_ASSERTION((mForm != nullptr) == HasFlag(ADDED_TO_FORM),
+ "Form control should have had flag set correctly");
+
+ if (!mForm) {
+ return;
+ }
+
+ if (aRemoveFromForm) {
+ nsAutoString nameVal, idVal;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, nameVal);
+ GetAttr(kNameSpaceID_None, nsGkAtoms::id, idVal);
+
+ mForm->RemoveElement(this, true);
+
+ if (!nameVal.IsEmpty()) {
+ mForm->RemoveElementFromTable(this, nameVal,
+ HTMLFormElement::ElementRemoved);
+ }
+
+ if (!idVal.IsEmpty()) {
+ mForm->RemoveElementFromTable(this, idVal,
+ HTMLFormElement::ElementRemoved);
+ }
+ }
+
+ UnsetFlags(ADDED_TO_FORM);
+ mForm = nullptr;
+}
+
+Element*
+nsGenericHTMLFormElement::GetFormElement()
+{
+ return mForm;
+}
+
+HTMLFieldSetElement*
+nsGenericHTMLFormElement::GetFieldSet()
+{
+ return mFieldSet;
+}
+
+nsresult
+nsGenericHTMLFormElement::GetForm(nsIDOMHTMLFormElement** aForm)
+{
+ NS_ENSURE_ARG_POINTER(aForm);
+ NS_IF_ADDREF(*aForm = mForm);
+ return NS_OK;
+}
+
+nsIContent::IMEState
+nsGenericHTMLFormElement::GetDesiredIMEState()
+{
+ nsIEditor* editor = GetEditorInternal();
+ if (!editor)
+ return nsGenericHTMLElement::GetDesiredIMEState();
+ nsCOMPtr<nsIEditorIMESupport> imeEditor = do_QueryInterface(editor);
+ if (!imeEditor)
+ return nsGenericHTMLElement::GetDesiredIMEState();
+ IMEState state;
+ nsresult rv = imeEditor->GetPreferredIMEState(&state);
+ if (NS_FAILED(rv))
+ return nsGenericHTMLElement::GetDesiredIMEState();
+ return state;
+}
+
+nsresult
+nsGenericHTMLFormElement::BindToTree(nsIDocument* aDocument,
+ nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // An autofocus event has to be launched if the autofocus attribute is
+ // specified and the element accept the autofocus attribute. In addition,
+ // the document should not be already loaded and the "browser.autofocus"
+ // preference should be 'true'.
+ if (IsAutofocusable() && HasAttr(kNameSpaceID_None, nsGkAtoms::autofocus) &&
+ Preferences::GetBool("browser.autofocus", true)) {
+ nsCOMPtr<nsIRunnable> event = new nsAutoFocusEvent(this);
+ rv = NS_DispatchToCurrentThread(event);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If @form is set, the element *has* to be in a document, otherwise it
+ // wouldn't be possible to find an element with the corresponding id.
+ // If @form isn't set, the element *has* to have a parent, otherwise it
+ // wouldn't be possible to find a form ancestor.
+ // We should not call UpdateFormOwner if none of these conditions are
+ // fulfilled.
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::form) ? !!GetUncomposedDoc()
+ : !!aParent) {
+ UpdateFormOwner(true, nullptr);
+ }
+
+ // Set parent fieldset which should be used for the disabled state.
+ UpdateFieldSet(false);
+
+ return NS_OK;
+}
+
+void
+nsGenericHTMLFormElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ // Save state before doing anything
+ SaveState();
+
+ if (mForm) {
+ // Might need to unset mForm
+ if (aNullParent) {
+ // No more parent means no more form
+ ClearForm(true);
+ } else {
+ // Recheck whether we should still have an mForm.
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::form) ||
+ !FindAncestorForm(mForm)) {
+ ClearForm(true);
+ } else {
+ UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
+ }
+ }
+
+ if (!mForm) {
+ // Our novalidate state might have changed
+ UpdateState(false);
+ }
+ }
+
+ // We have to remove the form id observer if there was one.
+ // We will re-add one later if needed (during bind to tree).
+ if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
+ nsGkAtoms::form)) {
+ RemoveFormIdObserver();
+ }
+
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+
+ // The element might not have a fieldset anymore.
+ UpdateFieldSet(false);
+}
+
+nsresult
+nsGenericHTMLFormElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None) {
+ nsAutoString tmp;
+
+ // remove the control from the hashtable as needed
+
+ if (mForm && (aName == nsGkAtoms::name || aName == nsGkAtoms::id)) {
+ GetAttr(kNameSpaceID_None, aName, tmp);
+
+ if (!tmp.IsEmpty()) {
+ mForm->RemoveElementFromTable(this, tmp,
+ HTMLFormElement::AttributeUpdated);
+ }
+ }
+
+ if (mForm && aName == nsGkAtoms::type) {
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, tmp);
+
+ if (!tmp.IsEmpty()) {
+ mForm->RemoveElementFromTable(this, tmp,
+ HTMLFormElement::AttributeUpdated);
+ }
+
+ GetAttr(kNameSpaceID_None, nsGkAtoms::id, tmp);
+
+ if (!tmp.IsEmpty()) {
+ mForm->RemoveElementFromTable(this, tmp,
+ HTMLFormElement::AttributeUpdated);
+ }
+
+ mForm->RemoveElement(this, false);
+
+ // Removing the element from the form can make it not be the default
+ // control anymore. Go ahead and notify on that change, though we might
+ // end up readding and becoming the default control again in
+ // AfterSetAttr.
+ // FIXME: Bug 656197
+ UpdateState(aNotify);
+ }
+
+ if (aName == nsGkAtoms::form) {
+ // If @form isn't set or set to the empty string, there were no observer
+ // so we don't have to remove it.
+ if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
+ nsGkAtoms::form)) {
+ // The current form id observer is no longer needed.
+ // A new one may be added in AfterSetAttr.
+ RemoveFormIdObserver();
+ }
+ }
+ }
+
+ return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+nsresult
+nsGenericHTMLFormElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None) {
+ // add the control to the hashtable as needed
+
+ if (mForm && (aName == nsGkAtoms::name || aName == nsGkAtoms::id) &&
+ aValue && !aValue->IsEmptyString()) {
+ MOZ_ASSERT(aValue->Type() == nsAttrValue::eAtom,
+ "Expected atom value for name/id");
+ mForm->AddElementToTable(this,
+ nsDependentAtomString(aValue->GetAtomValue()));
+ }
+
+ if (mForm && aName == nsGkAtoms::type) {
+ nsAutoString tmp;
+
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, tmp);
+
+ if (!tmp.IsEmpty()) {
+ mForm->AddElementToTable(this, tmp);
+ }
+
+ GetAttr(kNameSpaceID_None, nsGkAtoms::id, tmp);
+
+ if (!tmp.IsEmpty()) {
+ mForm->AddElementToTable(this, tmp);
+ }
+
+ mForm->AddElement(this, false, aNotify);
+
+ // Adding the element to the form can make it be the default control .
+ // Go ahead and notify on that change.
+ // Note: no need to notify on CanBeDisabled(), since type attr
+ // changes can't affect that.
+ UpdateState(aNotify);
+ }
+
+ if (aName == nsGkAtoms::form) {
+ // We need a new form id observer.
+ //XXXsmaug How should this work in Shadow DOM?
+ nsIDocument* doc = GetUncomposedDoc();
+ if (doc) {
+ Element* formIdElement = nullptr;
+ if (aValue && !aValue->IsEmptyString()) {
+ formIdElement = AddFormIdObserver();
+ }
+
+ // Because we have a new @form value (or no more @form), we have to
+ // update our form owner.
+ UpdateFormOwner(false, formIdElement);
+ }
+ }
+ }
+
+ return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+nsresult
+nsGenericHTMLFormElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ if (aVisitor.mEvent->IsTrusted()) {
+ switch (aVisitor.mEvent->mMessage) {
+ case eFocus: {
+ // Check to see if focus has bubbled up from a form control's
+ // child textfield or button. If that's the case, don't focus
+ // this parent file control -- leave focus on the child.
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+ if (formControlFrame &&
+ aVisitor.mEvent->mOriginalTarget == static_cast<nsINode*>(this))
+ formControlFrame->SetFocus(true, true);
+ break;
+ }
+ case eBlur: {
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+ if (formControlFrame)
+ formControlFrame->SetFocus(false, false);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ return nsGenericHTMLElement::PreHandleEvent(aVisitor);
+}
+
+/* virtual */
+bool
+nsGenericHTMLFormElement::IsDisabled() const
+{
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::disabled) ||
+ (mFieldSet && mFieldSet->IsDisabled());
+}
+
+void
+nsGenericHTMLFormElement::ForgetFieldSet(nsIContent* aFieldset)
+{
+ if (mFieldSet == aFieldset) {
+ mFieldSet = nullptr;
+ }
+}
+
+bool
+nsGenericHTMLFormElement::CanBeDisabled() const
+{
+ int32_t type = GetType();
+ // It's easier to test the types that _cannot_ be disabled
+ return
+ type != NS_FORM_OBJECT &&
+ type != NS_FORM_OUTPUT;
+}
+
+bool
+nsGenericHTMLFormElement::IsHTMLFocusable(bool aWithMouse,
+ bool* aIsFocusable,
+ int32_t* aTabIndex)
+{
+ if (nsGenericHTMLElement::IsHTMLFocusable(aWithMouse, aIsFocusable, aTabIndex)) {
+ return true;
+ }
+
+#ifdef XP_MACOSX
+ *aIsFocusable =
+ (!aWithMouse || nsFocusManager::sMouseFocusesFormControl) && *aIsFocusable;
+#endif
+ return false;
+}
+
+EventStates
+nsGenericHTMLFormElement::IntrinsicState() const
+{
+ // If you add attribute-dependent states here, you need to add them them to
+ // AfterSetAttr too. And add them to AfterSetAttr for all subclasses that
+ // implement IntrinsicState() and are affected by that attribute.
+ EventStates state = nsGenericHTMLElement::IntrinsicState();
+
+ if (CanBeDisabled()) {
+ // :enabled/:disabled
+ if (IsDisabled()) {
+ state |= NS_EVENT_STATE_DISABLED;
+ state &= ~NS_EVENT_STATE_ENABLED;
+ } else {
+ state &= ~NS_EVENT_STATE_DISABLED;
+ state |= NS_EVENT_STATE_ENABLED;
+ }
+ }
+
+ if (mForm && mForm->IsDefaultSubmitElement(this)) {
+ NS_ASSERTION(IsSubmitControl(),
+ "Default submit element that isn't a submit control.");
+ // We are the default submit element (:default)
+ state |= NS_EVENT_STATE_DEFAULT;
+ }
+
+ // Make the text controls read-write
+ if (!state.HasState(NS_EVENT_STATE_MOZ_READWRITE) &&
+ IsTextOrNumberControl(/*aExcludePassword*/ false)) {
+ bool roState = GetBoolAttr(nsGkAtoms::readonly);
+
+ if (!roState) {
+ state |= NS_EVENT_STATE_MOZ_READWRITE;
+ state &= ~NS_EVENT_STATE_MOZ_READONLY;
+ }
+ }
+
+ return state;
+}
+
+nsGenericHTMLFormElement::FocusTristate
+nsGenericHTMLFormElement::FocusState()
+{
+ // We can't be focused if we aren't in a (composed) document
+ nsIDocument* doc = GetComposedDoc();
+ if (!doc)
+ return eUnfocusable;
+
+ // first see if we are disabled or not. If disabled then do nothing.
+ if (IsDisabled()) {
+ return eUnfocusable;
+ }
+
+ // If the window is not active, do not allow the focus to bring the
+ // window to the front. We update the focus controller, but do
+ // nothing else.
+ if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
+ nsCOMPtr<nsPIDOMWindowOuter> rootWindow = win->GetPrivateRoot();
+
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ if (fm && rootWindow) {
+ nsCOMPtr<mozIDOMWindowProxy> activeWindow;
+ fm->GetActiveWindow(getter_AddRefs(activeWindow));
+ if (activeWindow == rootWindow) {
+ return eActiveWindow;
+ }
+ }
+ }
+
+ return eInactiveWindow;
+}
+
+Element*
+nsGenericHTMLFormElement::AddFormIdObserver()
+{
+ NS_ASSERTION(GetUncomposedDoc(), "When adding a form id observer, "
+ "we should be in a document!");
+
+ nsAutoString formId;
+ nsIDocument* doc = OwnerDoc();
+ GetAttr(kNameSpaceID_None, nsGkAtoms::form, formId);
+ NS_ASSERTION(!formId.IsEmpty(),
+ "@form value should not be the empty string!");
+ nsCOMPtr<nsIAtom> atom = NS_Atomize(formId);
+
+ return doc->AddIDTargetObserver(atom, FormIdUpdated, this, false);
+}
+
+void
+nsGenericHTMLFormElement::RemoveFormIdObserver()
+{
+ /**
+ * We are using OwnerDoc() because we don't really care about having the
+ * element actually being in the tree. If it is not and @form value changes,
+ * this method will be called for nothing but removing an observer which does
+ * not exist doesn't cost so much (no entry in the hash table) so having a
+ * boolean for GetUncomposedDoc()/GetOwnerDoc() would make everything look
+ * more complex for nothing.
+ */
+
+ nsIDocument* doc = OwnerDoc();
+
+ // At this point, we may not have a document anymore. In that case, we can't
+ // remove the observer. The document did that for us.
+ if (!doc) {
+ return;
+ }
+
+ nsAutoString formId;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::form, formId);
+ NS_ASSERTION(!formId.IsEmpty(),
+ "@form value should not be the empty string!");
+ nsCOMPtr<nsIAtom> atom = NS_Atomize(formId);
+
+ doc->RemoveIDTargetObserver(atom, FormIdUpdated, this, false);
+}
+
+
+/* static */
+bool
+nsGenericHTMLFormElement::FormIdUpdated(Element* aOldElement,
+ Element* aNewElement,
+ void* aData)
+{
+ nsGenericHTMLFormElement* element =
+ static_cast<nsGenericHTMLFormElement*>(aData);
+
+ NS_ASSERTION(element->IsHTMLElement(), "aData should be an HTML element");
+
+ element->UpdateFormOwner(false, aNewElement);
+
+ return true;
+}
+
+bool
+nsGenericHTMLFormElement::IsElementDisabledForEvents(EventMessage aMessage,
+ nsIFrame* aFrame)
+{
+ switch (aMessage) {
+ case eMouseMove:
+ case eMouseOver:
+ case eMouseOut:
+ case eMouseEnter:
+ case eMouseLeave:
+ case ePointerMove:
+ case ePointerOver:
+ case ePointerOut:
+ case ePointerEnter:
+ case ePointerLeave:
+ case eWheel:
+ case eLegacyMouseLineOrPageScroll:
+ case eLegacyMousePixelScroll:
+ return false;
+ default:
+ break;
+ }
+
+ bool disabled = IsDisabled();
+ if (!disabled && aFrame) {
+ const nsStyleUserInterface* uiStyle = aFrame->StyleUserInterface();
+ disabled = uiStyle->mUserInput == StyleUserInput::None ||
+ uiStyle->mUserInput == StyleUserInput::Disabled;
+
+ }
+ return disabled;
+}
+
+void
+nsGenericHTMLFormElement::UpdateFormOwner(bool aBindToTree,
+ Element* aFormIdElement)
+{
+ NS_PRECONDITION(!aBindToTree || !aFormIdElement,
+ "aFormIdElement shouldn't be set if aBindToTree is true!");
+
+ bool needStateUpdate = false;
+ if (!aBindToTree) {
+ needStateUpdate = mForm && mForm->IsDefaultSubmitElement(this);
+ ClearForm(true);
+ }
+
+ HTMLFormElement *oldForm = mForm;
+
+ if (!mForm) {
+ // If @form is set, we have to use that to find the form.
+ nsAutoString formId;
+ if (GetAttr(kNameSpaceID_None, nsGkAtoms::form, formId)) {
+ if (!formId.IsEmpty()) {
+ Element* element = nullptr;
+
+ if (aBindToTree) {
+ element = AddFormIdObserver();
+ } else {
+ element = aFormIdElement;
+ }
+
+ NS_ASSERTION(GetUncomposedDoc(), "The element should be in a document "
+ "when UpdateFormOwner is called!");
+ NS_ASSERTION(!GetUncomposedDoc() ||
+ element == GetUncomposedDoc()->GetElementById(formId),
+ "element should be equals to the current element "
+ "associated with the id in @form!");
+
+ if (element && element->IsHTMLElement(nsGkAtoms::form)) {
+ mForm = static_cast<HTMLFormElement*>(element);
+ }
+ }
+ } else {
+ // We now have a parent, so we may have picked up an ancestor form. Search
+ // for it. Note that if mForm is already set we don't want to do this,
+ // because that means someone (probably the content sink) has already set
+ // it to the right value. Also note that even if being bound here didn't
+ // change our parent, we still need to search, since our parent chain
+ // probably changed _somewhere_.
+ mForm = FindAncestorForm();
+ }
+ }
+
+ if (mForm && !HasFlag(ADDED_TO_FORM)) {
+ // Now we need to add ourselves to the form
+ nsAutoString nameVal, idVal;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, nameVal);
+ GetAttr(kNameSpaceID_None, nsGkAtoms::id, idVal);
+
+ SetFlags(ADDED_TO_FORM);
+
+ // Notify only if we just found this mForm.
+ mForm->AddElement(this, true, oldForm == nullptr);
+
+ if (!nameVal.IsEmpty()) {
+ mForm->AddElementToTable(this, nameVal);
+ }
+
+ if (!idVal.IsEmpty()) {
+ mForm->AddElementToTable(this, idVal);
+ }
+ }
+
+ if (mForm != oldForm || needStateUpdate) {
+ UpdateState(true);
+ }
+}
+
+void
+nsGenericHTMLFormElement::UpdateFieldSet(bool aNotify)
+{
+ nsIContent* parent = nullptr;
+ nsIContent* prev = nullptr;
+
+ for (parent = GetParent(); parent;
+ prev = parent, parent = parent->GetParent()) {
+ HTMLFieldSetElement* fieldset =
+ HTMLFieldSetElement::FromContent(parent);
+ if (fieldset &&
+ (!prev || fieldset->GetFirstLegend() != prev)) {
+ if (mFieldSet == fieldset) {
+ // We already have the right fieldset;
+ return;
+ }
+
+ if (mFieldSet) {
+ mFieldSet->RemoveElement(this);
+ }
+ mFieldSet = fieldset;
+ fieldset->AddElement(this);
+
+ // The disabled state may have changed
+ FieldSetDisabledChanged(aNotify);
+ return;
+ }
+ }
+
+ // No fieldset found.
+ if (mFieldSet) {
+ mFieldSet->RemoveElement(this);
+ mFieldSet = nullptr;
+ // The disabled state may have changed
+ FieldSetDisabledChanged(aNotify);
+ }
+}
+
+void
+nsGenericHTMLFormElement::FieldSetDisabledChanged(bool aNotify)
+{
+ UpdateState(aNotify);
+}
+
+bool
+nsGenericHTMLFormElement::IsLabelable() const
+{
+ // TODO: keygen should be in that list, see bug 101019.
+ uint32_t type = GetType();
+ return (type & NS_FORM_INPUT_ELEMENT && type != NS_FORM_INPUT_HIDDEN) ||
+ type & NS_FORM_BUTTON_ELEMENT ||
+ // type == NS_FORM_KEYGEN ||
+ type == NS_FORM_OUTPUT ||
+ type == NS_FORM_SELECT ||
+ type == NS_FORM_TEXTAREA;
+}
+
+//----------------------------------------------------------------------
+
+void
+nsGenericHTMLElement::Click()
+{
+ if (HandlingClick())
+ return;
+
+ // Strong in case the event kills it
+ nsCOMPtr<nsIDocument> doc = GetComposedDoc();
+
+ nsCOMPtr<nsIPresShell> shell;
+ RefPtr<nsPresContext> context;
+ if (doc) {
+ shell = doc->GetShell();
+ if (shell) {
+ context = shell->GetPresContext();
+ }
+ }
+
+ SetHandlingClick();
+
+ // Click() is never called from native code, but it may be
+ // called from chrome JS. Mark this event trusted if Click()
+ // is called from chrome code.
+ WidgetMouseEvent event(nsContentUtils::IsCallerChrome(),
+ eMouseClick, nullptr, WidgetMouseEvent::eReal);
+ event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
+
+ EventDispatcher::Dispatch(static_cast<nsIContent*>(this), context, &event);
+
+ ClearHandlingClick();
+}
+
+bool
+nsGenericHTMLElement::IsHTMLFocusable(bool aWithMouse,
+ bool *aIsFocusable,
+ int32_t *aTabIndex)
+{
+ nsIDocument* doc = GetComposedDoc();
+ if (!doc || doc->HasFlag(NODE_IS_EDITABLE)) {
+ // In designMode documents we only allow focusing the document.
+ if (aTabIndex) {
+ *aTabIndex = -1;
+ }
+
+ *aIsFocusable = false;
+
+ return true;
+ }
+
+ int32_t tabIndex = TabIndex();
+ bool disabled = false;
+ bool disallowOverridingFocusability = true;
+
+ if (IsEditableRoot()) {
+ // Editable roots should always be focusable.
+ disallowOverridingFocusability = true;
+
+ // Ignore the disabled attribute in editable contentEditable/designMode
+ // roots.
+ if (!HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) {
+ // The default value for tabindex should be 0 for editable
+ // contentEditable roots.
+ tabIndex = 0;
+ }
+ }
+ else {
+ disallowOverridingFocusability = false;
+
+ // Just check for disabled attribute on form controls
+ disabled = IsDisabled();
+ if (disabled) {
+ tabIndex = -1;
+ }
+ }
+
+ if (aTabIndex) {
+ *aTabIndex = tabIndex;
+ }
+
+ // If a tabindex is specified at all, or the default tabindex is 0, we're focusable
+ *aIsFocusable =
+ (tabIndex >= 0 || (!disabled && HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)));
+
+ return disallowOverridingFocusability;
+}
+
+void
+nsGenericHTMLElement::RegUnRegAccessKey(bool aDoReg)
+{
+ // first check to see if we have an access key
+ nsAutoString accessKey;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey);
+ if (accessKey.IsEmpty()) {
+ return;
+ }
+
+ // We have an access key, so get the ESM from the pres context.
+ nsPresContext* presContext = GetPresContext(eForUncomposedDoc);
+
+ if (presContext) {
+ EventStateManager* esm = presContext->EventStateManager();
+
+ // Register or unregister as appropriate.
+ if (aDoReg) {
+ esm->RegisterAccessKey(this, (uint32_t)accessKey.First());
+ } else {
+ esm->UnregisterAccessKey(this, (uint32_t)accessKey.First());
+ }
+ }
+}
+
+bool
+nsGenericHTMLElement::PerformAccesskey(bool aKeyCausesActivation,
+ bool aIsTrustedEvent)
+{
+ nsPresContext* presContext = GetPresContext(eForUncomposedDoc);
+ if (!presContext) {
+ return false;
+ }
+
+ // It's hard to say what HTML4 wants us to do in all cases.
+ bool focused = true;
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ fm->SetFocus(this, nsIFocusManager::FLAG_BYKEY);
+
+ // Return true if the element became the current focus within its window.
+ nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow();
+ focused = (window && window->GetFocusedNode());
+ }
+
+ if (aKeyCausesActivation) {
+ // Click on it if the users prefs indicate to do so.
+ nsAutoPopupStatePusher popupStatePusher(aIsTrustedEvent ?
+ openAllowed : openAbused);
+ DispatchSimulatedClick(this, aIsTrustedEvent, presContext);
+ }
+
+ return focused;
+}
+
+nsresult
+nsGenericHTMLElement::DispatchSimulatedClick(nsGenericHTMLElement* aElement,
+ bool aIsTrusted,
+ nsPresContext* aPresContext)
+{
+ WidgetMouseEvent event(aIsTrusted, eMouseClick, nullptr,
+ WidgetMouseEvent::eReal);
+ event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD;
+ return EventDispatcher::Dispatch(ToSupports(aElement), aPresContext, &event);
+}
+
+nsresult
+nsGenericHTMLElement::GetEditor(nsIEditor** aEditor)
+{
+ *aEditor = nullptr;
+
+ // See also HTMLTextFieldAccessible::GetEditor.
+ if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ NS_IF_ADDREF(*aEditor = GetEditorInternal());
+ return NS_OK;
+}
+
+already_AddRefed<nsIEditor>
+nsGenericHTMLElement::GetAssociatedEditor()
+{
+ // If contenteditable is ever implemented, it might need to do something different here?
+
+ nsCOMPtr<nsIEditor> editor = GetEditorInternal();
+ return editor.forget();
+}
+
+bool
+nsGenericHTMLElement::IsCurrentBodyElement()
+{
+ // TODO Bug 698498: Should this handle the case where GetBody returns a
+ // frameset?
+ if (!IsHTMLElement(nsGkAtoms::body)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDOMHTMLDocument> htmlDocument =
+ do_QueryInterface(GetUncomposedDoc());
+ if (!htmlDocument) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDOMHTMLElement> htmlElement;
+ htmlDocument->GetBody(getter_AddRefs(htmlElement));
+ return htmlElement == static_cast<HTMLBodyElement*>(this);
+}
+
+// static
+void
+nsGenericHTMLElement::SyncEditorsOnSubtree(nsIContent* content)
+{
+ /* Sync this node */
+ nsGenericHTMLElement* element = FromContent(content);
+ if (element) {
+ nsCOMPtr<nsIEditor> editor = element->GetAssociatedEditor();
+ if (editor) {
+ editor->SyncRealTimeSpell();
+ }
+ }
+
+ /* Sync all children */
+ for (nsIContent* child = content->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ SyncEditorsOnSubtree(child);
+ }
+}
+
+void
+nsGenericHTMLElement::RecompileScriptEventListeners()
+{
+ int32_t i, count = mAttrsAndChildren.AttrCount();
+ for (i = 0; i < count; ++i) {
+ const nsAttrName *name = mAttrsAndChildren.AttrNameAt(i);
+
+ // Eventlistenener-attributes are always in the null namespace
+ if (!name->IsAtom()) {
+ continue;
+ }
+
+ nsIAtom *attr = name->Atom();
+ if (!IsEventAttributeName(attr)) {
+ continue;
+ }
+
+ nsAutoString value;
+ GetAttr(kNameSpaceID_None, attr, value);
+ SetEventHandler(attr, value, true);
+ }
+}
+
+bool
+nsGenericHTMLElement::IsEditableRoot() const
+{
+ nsIDocument *document = GetComposedDoc();
+ if (!document) {
+ return false;
+ }
+
+ if (document->HasFlag(NODE_IS_EDITABLE)) {
+ return false;
+ }
+
+ if (GetContentEditableValue() != eTrue) {
+ return false;
+ }
+
+ nsIContent *parent = GetParent();
+
+ return !parent || !parent->HasFlag(NODE_IS_EDITABLE);
+}
+
+static void
+MakeContentDescendantsEditable(nsIContent *aContent, nsIDocument *aDocument)
+{
+ // If aContent is not an element, we just need to update its
+ // internal editable state and don't need to notify anyone about
+ // that. For elements, we need to send a ContentStateChanged
+ // notification.
+ if (!aContent->IsElement()) {
+ aContent->UpdateEditableState(false);
+ return;
+ }
+
+ Element *element = aContent->AsElement();
+
+ element->UpdateEditableState(true);
+
+ for (nsIContent *child = aContent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (!child->HasAttr(kNameSpaceID_None, nsGkAtoms::contenteditable)) {
+ MakeContentDescendantsEditable(child, aDocument);
+ }
+ }
+}
+
+void
+nsGenericHTMLElement::ChangeEditableState(int32_t aChange)
+{
+ //XXXsmaug Fix this for Shadow DOM, bug 1066965.
+ nsIDocument* document = GetUncomposedDoc();
+ if (!document) {
+ return;
+ }
+
+ if (aChange != 0) {
+ nsCOMPtr<nsIHTMLDocument> htmlDocument =
+ do_QueryInterface(document);
+ if (htmlDocument) {
+ htmlDocument->ChangeContentEditableCount(this, aChange);
+ }
+
+ nsIContent* parent = GetParent();
+ while (parent) {
+ parent->ChangeEditableDescendantCount(aChange);
+ parent = parent->GetParent();
+ }
+ }
+
+ if (document->HasFlag(NODE_IS_EDITABLE)) {
+ document = nullptr;
+ }
+
+ // MakeContentDescendantsEditable is going to call ContentStateChanged for
+ // this element and all descendants if editable state has changed.
+ // We might as well wrap it all in one script blocker.
+ nsAutoScriptBlocker scriptBlocker;
+ MakeContentDescendantsEditable(this, document);
+}
+
+
+//----------------------------------------------------------------------
+
+nsGenericHTMLFormElementWithState::nsGenericHTMLFormElementWithState(
+ already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo
+ )
+ : nsGenericHTMLFormElement(aNodeInfo)
+{
+ mStateKey.SetIsVoid(true);
+}
+
+nsresult
+nsGenericHTMLFormElementWithState::GenerateStateKey()
+{
+ // Keep the key if already computed
+ if (!mStateKey.IsVoid()) {
+ return NS_OK;
+ }
+
+ nsIDocument* doc = GetUncomposedDoc();
+ if (!doc) {
+ return NS_OK;
+ }
+
+ // Generate the state key
+ nsresult rv = nsContentUtils::GenerateStateKey(this, doc, mStateKey);
+
+ if (NS_FAILED(rv)) {
+ mStateKey.SetIsVoid(true);
+ return rv;
+ }
+
+ // If the state key is blank, this is anonymous content or for whatever
+ // reason we are not supposed to save/restore state: keep it as such.
+ if (!mStateKey.IsEmpty()) {
+ // Add something unique to content so layout doesn't muck us up.
+ mStateKey += "-C";
+ }
+ return NS_OK;
+}
+
+nsPresState*
+nsGenericHTMLFormElementWithState::GetPrimaryPresState()
+{
+ if (mStateKey.IsEmpty()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsILayoutHistoryState> history = GetLayoutHistory(false);
+
+ if (!history) {
+ return nullptr;
+ }
+
+ // Get the pres state for this key, if it doesn't exist, create one.
+ nsPresState* result = history->GetState(mStateKey);
+ if (!result) {
+ result = new nsPresState();
+ history->AddState(mStateKey, result);
+ }
+
+ return result;
+}
+
+already_AddRefed<nsILayoutHistoryState>
+nsGenericHTMLFormElementWithState::GetLayoutHistory(bool aRead)
+{
+ nsCOMPtr<nsIDocument> doc = GetUncomposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ //
+ // Get the history
+ //
+ nsCOMPtr<nsILayoutHistoryState> history = doc->GetLayoutHistoryState();
+ if (!history) {
+ return nullptr;
+ }
+
+ if (aRead && !history->HasStates()) {
+ return nullptr;
+ }
+
+ return history.forget();
+}
+
+bool
+nsGenericHTMLFormElementWithState::RestoreFormControlState()
+{
+ if (mStateKey.IsEmpty()) {
+ return false;
+ }
+
+ nsCOMPtr<nsILayoutHistoryState> history =
+ GetLayoutHistory(true);
+ if (!history) {
+ return false;
+ }
+
+ nsPresState *state;
+ // Get the pres state for this key
+ state = history->GetState(mStateKey);
+ if (state) {
+ bool result = RestoreState(state);
+ history->RemoveState(mStateKey);
+ return result;
+ }
+
+ return false;
+}
+
+void
+nsGenericHTMLFormElementWithState::NodeInfoChanged()
+{
+ mStateKey.SetIsVoid(true);
+}
+
+nsSize
+nsGenericHTMLElement::GetWidthHeightForImage(RefPtr<imgRequestProxy>& aImageRequest)
+{
+ nsSize size(0,0);
+
+ nsIFrame* frame = GetPrimaryFrame(Flush_Layout);
+
+ if (frame) {
+ size = frame->GetContentRect().Size();
+
+ size.width = nsPresContext::AppUnitsToIntCSSPixels(size.width);
+ size.height = nsPresContext::AppUnitsToIntCSSPixels(size.height);
+ } else {
+ const nsAttrValue* value;
+ nsCOMPtr<imgIContainer> image;
+ if (aImageRequest) {
+ aImageRequest->GetImage(getter_AddRefs(image));
+ }
+
+ if ((value = GetParsedAttr(nsGkAtoms::width)) &&
+ value->Type() == nsAttrValue::eInteger) {
+ size.width = value->GetIntegerValue();
+ } else if (image) {
+ image->GetWidth(&size.width);
+ }
+
+ if ((value = GetParsedAttr(nsGkAtoms::height)) &&
+ value->Type() == nsAttrValue::eInteger) {
+ size.height = value->GetIntegerValue();
+ } else if (image) {
+ image->GetHeight(&size.height);
+ }
+ }
+
+ NS_ASSERTION(size.width >= 0, "negative width");
+ NS_ASSERTION(size.height >= 0, "negative height");
+ return size;
+}
+
+bool
+nsGenericHTMLElement::IsEventAttributeName(nsIAtom *aName)
+{
+ return nsContentUtils::IsEventAttributeName(aName, EventNameType_HTML);
+}
+
+/**
+ * Construct a URI from a string, as an element.src attribute
+ * would be set to. Helper for the media elements.
+ */
+nsresult
+nsGenericHTMLElement::NewURIFromString(const nsAString& aURISpec,
+ nsIURI** aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ *aURI = nullptr;
+
+ nsCOMPtr<nsIDocument> doc = OwnerDoc();
+
+ nsCOMPtr<nsIURI> baseURI = GetBaseURI();
+ nsresult rv = nsContentUtils::NewURIWithDocumentCharset(aURI, aURISpec,
+ doc, baseURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool equal;
+ if (aURISpec.IsEmpty() &&
+ doc->GetDocumentURI() &&
+ NS_SUCCEEDED(doc->GetDocumentURI()->Equals(*aURI, &equal)) &&
+ equal) {
+ // Assume an element can't point to a fragment of its embedding
+ // document. Fail here instead of returning the recursive URI
+ // and waiting for the subsequent load to fail.
+ NS_RELEASE(*aURI);
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ return NS_OK;
+}
+
+static bool
+IsOrHasAncestorWithDisplayNone(Element* aElement, nsIPresShell* aPresShell)
+{
+ nsTArray<Element*> elementsToCheck;
+ for (Element* e = aElement; e; e = e->GetParentElement()) {
+ if (e->GetPrimaryFrame()) {
+ // e definitely isn't display:none and doesn't have a display:none
+ // ancestor.
+ break;
+ }
+ elementsToCheck.AppendElement(e);
+ }
+
+ if (elementsToCheck.IsEmpty()) {
+ return false;
+ }
+
+ StyleSetHandle styleSet = aPresShell->StyleSet();
+ RefPtr<nsStyleContext> sc;
+ for (int32_t i = elementsToCheck.Length() - 1; i >= 0; --i) {
+ if (sc) {
+ sc = styleSet->ResolveStyleFor(elementsToCheck[i], sc);
+ } else {
+ sc = nsComputedDOMStyle::GetStyleContextForElementNoFlush(elementsToCheck[i],
+ nullptr, aPresShell);
+ }
+ if (sc->StyleDisplay()->mDisplay == StyleDisplay::None) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+nsGenericHTMLElement::GetInnerText(mozilla::dom::DOMString& aValue,
+ mozilla::ErrorResult& aError)
+{
+ if (!GetPrimaryFrame(Flush_Layout)) {
+ nsIPresShell* presShell = nsComputedDOMStyle::GetPresShellForContent(this);
+ if (!presShell || IsOrHasAncestorWithDisplayNone(this, presShell)) {
+ GetTextContentInternal(aValue, aError);
+ return;
+ }
+ }
+
+ nsRange::GetInnerTextNoFlush(aValue, aError, this, 0, this, GetChildCount());
+}
+
+void
+nsGenericHTMLElement::SetInnerText(const nsAString& aValue)
+{
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(OwnerDoc(), nullptr);
+ FireNodeRemovedForChildren();
+
+ // Might as well stick a batch around this since we're performing several
+ // mutations.
+ mozAutoDocUpdate updateBatch(GetComposedDoc(),
+ UPDATE_CONTENT_MODEL, true);
+ nsAutoMutationBatch mb;
+
+ uint32_t childCount = GetChildCount();
+
+ mb.Init(this, true, false);
+ for (uint32_t i = 0; i < childCount; ++i) {
+ RemoveChildAt(0, true);
+ }
+ mb.RemovalDone();
+
+ nsString str;
+ const char16_t* s = aValue.BeginReading();
+ const char16_t* end = aValue.EndReading();
+ while (true) {
+ if (s != end && *s == '\r' && s + 1 != end && s[1] == '\n') {
+ // a \r\n pair should only generate one <br>, so just skip the \r
+ ++s;
+ }
+ if (s == end || *s == '\r' || *s == '\n') {
+ if (!str.IsEmpty()) {
+ RefPtr<nsTextNode> textContent =
+ new nsTextNode(NodeInfo()->NodeInfoManager());
+ textContent->SetText(str, true);
+ AppendChildTo(textContent, true);
+ }
+ if (s == end) {
+ break;
+ }
+ str.Truncate();
+ already_AddRefed<mozilla::dom::NodeInfo> ni =
+ NodeInfo()->NodeInfoManager()->GetNodeInfo(nsGkAtoms::br,
+ nullptr, kNameSpaceID_XHTML, nsIDOMNode::ELEMENT_NODE);
+ RefPtr<HTMLBRElement> br = new HTMLBRElement(ni);
+ AppendChildTo(br, true);
+ } else {
+ str.Append(*s);
+ }
+ ++s;
+ }
+
+ mb.NodesAdded();
+}
diff --git a/dom/html/nsGenericHTMLElement.h b/dom/html/nsGenericHTMLElement.h
new file mode 100644
index 000000000..3cca41c3d
--- /dev/null
+++ b/dom/html/nsGenericHTMLElement.h
@@ -0,0 +1,1724 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsGenericHTMLElement_h___
+#define nsGenericHTMLElement_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "nsMappedAttributeElement.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsNameSpaceManager.h" // for kNameSpaceID_None
+#include "nsIFormControl.h"
+#include "nsGkAtoms.h"
+#include "nsContentCreatorFunctions.h"
+#include "mozilla/ErrorResult.h"
+#include "nsIDOMHTMLMenuElement.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/ValidityState.h"
+#include "mozilla/dom/ElementInlines.h"
+
+class nsDOMTokenList;
+class nsIDOMHTMLMenuElement;
+class nsIEditor;
+class nsIFormControlFrame;
+class nsIFrame;
+class nsILayoutHistoryState;
+class nsIURI;
+class nsPresState;
+struct nsSize;
+
+namespace mozilla {
+class EventChainPostVisitor;
+class EventChainPreVisitor;
+class EventChainVisitor;
+class EventListenerManager;
+class EventStates;
+namespace dom {
+class HTMLFormElement;
+class HTMLMenuElement;
+} // namespace dom
+} // namespace mozilla
+
+typedef nsMappedAttributeElement nsGenericHTMLElementBase;
+
+/**
+ * A common superclass for HTML elements
+ */
+class nsGenericHTMLElement : public nsGenericHTMLElementBase,
+ public nsIDOMHTMLElement
+{
+public:
+ using Element::SetTabIndex;
+ using Element::Focus;
+ explicit nsGenericHTMLElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElementBase(aNodeInfo)
+ {
+ NS_ASSERTION(mNodeInfo->NamespaceID() == kNameSpaceID_XHTML,
+ "Unexpected namespace");
+ AddStatesSilently(NS_EVENT_STATE_LTR);
+ SetFlags(NODE_HAS_DIRECTION_LTR);
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMPL_FROMCONTENT(nsGenericHTMLElement, kNameSpaceID_XHTML)
+
+ // From Element
+ nsresult CopyInnerTo(mozilla::dom::Element* aDest);
+
+ void GetTitle(mozilla::dom::DOMString& aTitle)
+ {
+ GetHTMLAttr(nsGkAtoms::title, aTitle);
+ }
+ NS_IMETHOD SetTitle(const nsAString& aTitle) override
+ {
+ SetHTMLAttr(nsGkAtoms::title, aTitle);
+ return NS_OK;
+ }
+ void GetLang(mozilla::dom::DOMString& aLang)
+ {
+ GetHTMLAttr(nsGkAtoms::lang, aLang);
+ }
+ NS_IMETHOD SetLang(const nsAString& aLang) override
+ {
+ SetHTMLAttr(nsGkAtoms::lang, aLang);
+ return NS_OK;
+ }
+ void GetDir(mozilla::dom::DOMString& aDir)
+ {
+ GetHTMLEnumAttr(nsGkAtoms::dir, aDir);
+ }
+ void SetDir(const nsAString& aDir, mozilla::ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::dir, aDir, aError);
+ }
+ bool Hidden() const
+ {
+ return GetBoolAttr(nsGkAtoms::hidden);
+ }
+ void SetHidden(bool aHidden, mozilla::ErrorResult& aError)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::hidden, aHidden, aError);
+ }
+ virtual void Click();
+ void GetAccessKey(nsString& aAccessKey)
+ {
+ GetHTMLAttr(nsGkAtoms::accesskey, aAccessKey);
+ }
+ void SetAccessKey(const nsAString& aAccessKey, mozilla::ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::accesskey, aAccessKey, aError);
+ }
+ void GetAccessKeyLabel(nsString& aAccessKeyLabel);
+ virtual bool Draggable() const
+ {
+ return AttrValueIs(kNameSpaceID_None, nsGkAtoms::draggable,
+ nsGkAtoms::_true, eIgnoreCase);
+ }
+ void SetDraggable(bool aDraggable, mozilla::ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::draggable,
+ aDraggable ? NS_LITERAL_STRING("true")
+ : NS_LITERAL_STRING("false"),
+ aError);
+ }
+ void GetContentEditable(nsString& aContentEditable)
+ {
+ ContentEditableTristate value = GetContentEditableValue();
+ if (value == eTrue) {
+ aContentEditable.AssignLiteral("true");
+ } else if (value == eFalse) {
+ aContentEditable.AssignLiteral("false");
+ } else {
+ aContentEditable.AssignLiteral("inherit");
+ }
+ }
+ void SetContentEditable(const nsAString& aContentEditable,
+ mozilla::ErrorResult& aError)
+ {
+ if (aContentEditable.LowerCaseEqualsLiteral("inherit")) {
+ UnsetHTMLAttr(nsGkAtoms::contenteditable, aError);
+ } else if (aContentEditable.LowerCaseEqualsLiteral("true")) {
+ SetHTMLAttr(nsGkAtoms::contenteditable, NS_LITERAL_STRING("true"), aError);
+ } else if (aContentEditable.LowerCaseEqualsLiteral("false")) {
+ SetHTMLAttr(nsGkAtoms::contenteditable, NS_LITERAL_STRING("false"), aError);
+ } else {
+ aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ }
+ }
+ bool IsContentEditable()
+ {
+ for (nsIContent* node = this; node; node = node->GetParent()) {
+ nsGenericHTMLElement* element = FromContent(node);
+ if (element) {
+ ContentEditableTristate value = element->GetContentEditableValue();
+ if (value != eInherit) {
+ return value == eTrue;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the count of descendants (inclusive of this node) in
+ * the uncomposed document that are explicitly set as editable.
+ */
+ uint32_t EditableInclusiveDescendantCount();
+
+ mozilla::dom::HTMLMenuElement* GetContextMenu() const;
+ bool Spellcheck();
+ void SetSpellcheck(bool aSpellcheck, mozilla::ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::spellcheck,
+ aSpellcheck ? NS_LITERAL_STRING("true")
+ : NS_LITERAL_STRING("false"),
+ aError);
+ }
+ bool Scrollgrab() const
+ {
+ return HasFlag(ELEMENT_HAS_SCROLLGRAB);
+ }
+ void SetScrollgrab(bool aValue)
+ {
+ if (aValue) {
+ SetFlags(ELEMENT_HAS_SCROLLGRAB);
+ } else {
+ UnsetFlags(ELEMENT_HAS_SCROLLGRAB);
+ }
+ }
+
+ void GetInnerText(mozilla::dom::DOMString& aValue, mozilla::ErrorResult& aError);
+ void SetInnerText(const nsAString& aValue);
+
+ /**
+ * Determine whether an attribute is an event (onclick, etc.)
+ * @param aName the attribute
+ * @return whether the name is an event handler name
+ */
+ virtual bool IsEventAttributeName(nsIAtom* aName) override;
+
+#define EVENT(name_, id_, type_, struct_) /* nothing; handled by nsINode */
+// The using nsINode::Get/SetOn* are to avoid warnings about shadowing the XPCOM
+// getter and setter on nsINode.
+#define FORWARDED_EVENT(name_, id_, type_, struct_) \
+ using nsINode::GetOn##name_; \
+ using nsINode::SetOn##name_; \
+ mozilla::dom::EventHandlerNonNull* GetOn##name_(); \
+ void SetOn##name_(mozilla::dom::EventHandlerNonNull* handler);
+#define ERROR_EVENT(name_, id_, type_, struct_) \
+ using nsINode::GetOn##name_; \
+ using nsINode::SetOn##name_; \
+ already_AddRefed<mozilla::dom::EventHandlerNonNull> GetOn##name_(); \
+ void SetOn##name_(mozilla::dom::EventHandlerNonNull* handler);
+#include "mozilla/EventNameList.h" // IWYU pragma: keep
+#undef ERROR_EVENT
+#undef FORWARDED_EVENT
+#undef EVENT
+ mozilla::dom::Element* GetOffsetParent()
+ {
+ mozilla::CSSIntRect rcFrame;
+ return GetOffsetRect(rcFrame);
+ }
+ int32_t OffsetTop()
+ {
+ mozilla::CSSIntRect rcFrame;
+ GetOffsetRect(rcFrame);
+
+ return rcFrame.y;
+ }
+ int32_t OffsetLeft()
+ {
+ mozilla::CSSIntRect rcFrame;
+ GetOffsetRect(rcFrame);
+
+ return rcFrame.x;
+ }
+ int32_t OffsetWidth()
+ {
+ mozilla::CSSIntRect rcFrame;
+ GetOffsetRect(rcFrame);
+
+ return rcFrame.width;
+ }
+ int32_t OffsetHeight()
+ {
+ mozilla::CSSIntRect rcFrame;
+ GetOffsetRect(rcFrame);
+
+ return rcFrame.height;
+ }
+
+ // These methods are already implemented in nsIContent but we want something
+ // faster for HTMLElements ignoring the namespace checking.
+ // This is safe because we already know that we are in the HTML namespace.
+ inline bool IsHTMLElement() const
+ {
+ return true;
+ }
+
+ inline bool IsHTMLElement(nsIAtom* aTag) const
+ {
+ return mNodeInfo->Equals(aTag);
+ }
+
+ template<typename First, typename... Args>
+ inline bool IsAnyOfHTMLElements(First aFirst, Args... aArgs) const
+ {
+ return IsNodeInternal(aFirst, aArgs...);
+ }
+
+protected:
+ virtual ~nsGenericHTMLElement() {}
+
+public:
+ /**
+ * Get width and height, using given image request if attributes are unset.
+ * Pass a reference to the image request, since the method may change the
+ * value and we want to use the updated value.
+ */
+ nsSize GetWidthHeightForImage(RefPtr<imgRequestProxy>& aImageRequest);
+
+ // XPIDL methods
+ NS_FORWARD_NSIDOMNODE_TO_NSINODE
+
+ NS_FORWARD_NSIDOMELEMENT_TO_GENERIC
+
+ NS_IMETHOD GetTitle(nsAString& aTitle) final override {
+ mozilla::dom::DOMString title;
+ GetTitle(title);
+ title.ToString(aTitle);
+ return NS_OK;
+ }
+ NS_IMETHOD GetLang(nsAString& aLang) final override {
+ mozilla::dom::DOMString lang;
+ GetLang(lang);
+ lang.ToString(aLang);
+ return NS_OK;
+ }
+ NS_IMETHOD GetDir(nsAString& aDir) final override {
+ mozilla::dom::DOMString dir;
+ GetDir(dir);
+ dir.ToString(aDir);
+ return NS_OK;
+ }
+ NS_IMETHOD SetDir(const nsAString& aDir) final override {
+ mozilla::ErrorResult rv;
+ SetDir(aDir, rv);
+ return rv.StealNSResult();
+ }
+ NS_IMETHOD GetDOMClassName(nsAString& aClassName) final {
+ GetHTMLAttr(nsGkAtoms::_class, aClassName);
+ return NS_OK;
+ }
+ NS_IMETHOD SetDOMClassName(const nsAString& aClassName) final {
+ SetClassName(aClassName);
+ return NS_OK;
+ }
+ NS_IMETHOD GetDataset(nsISupports** aDataset) final override;
+ NS_IMETHOD GetHidden(bool* aHidden) final override {
+ *aHidden = Hidden();
+ return NS_OK;
+ }
+ NS_IMETHOD SetHidden(bool aHidden) final override {
+ mozilla::ErrorResult rv;
+ SetHidden(aHidden, rv);
+ return rv.StealNSResult();
+ }
+ NS_IMETHOD DOMBlur() final override {
+ mozilla::ErrorResult rv;
+ Blur(rv);
+ return rv.StealNSResult();
+ }
+ NS_IMETHOD GetAccessKey(nsAString& aAccessKey) final override {
+ nsString accessKey;
+ GetAccessKey(accessKey);
+ aAccessKey.Assign(accessKey);
+ return NS_OK;
+ }
+ NS_IMETHOD SetAccessKey(const nsAString& aAccessKey) final override {
+ mozilla::ErrorResult rv;
+ SetAccessKey(aAccessKey, rv);
+ return rv.StealNSResult();
+ }
+ NS_IMETHOD GetAccessKeyLabel(nsAString& aAccessKeyLabel)
+ final override {
+ nsString accessKeyLabel;
+ GetAccessKeyLabel(accessKeyLabel);
+ aAccessKeyLabel.Assign(accessKeyLabel);
+ return NS_OK;
+ }
+ NS_IMETHOD SetDraggable(bool aDraggable) final override {
+ mozilla::ErrorResult rv;
+ SetDraggable(aDraggable, rv);
+ return rv.StealNSResult();
+ }
+ NS_IMETHOD GetContentEditable(nsAString& aContentEditable)
+ final override {
+ nsString contentEditable;
+ GetContentEditable(contentEditable);
+ aContentEditable.Assign(contentEditable);
+ return NS_OK;
+ }
+ NS_IMETHOD SetContentEditable(const nsAString& aContentEditable)
+ final override {
+ mozilla::ErrorResult rv;
+ SetContentEditable(aContentEditable, rv);
+ return rv.StealNSResult();
+ }
+ NS_IMETHOD GetIsContentEditable(bool* aIsContentEditable)
+ final override {
+ *aIsContentEditable = IsContentEditable();
+ return NS_OK;
+ }
+ NS_IMETHOD GetContextMenu(nsIDOMHTMLMenuElement** aContextMenu)
+ final override;
+ NS_IMETHOD GetSpellcheck(bool* aSpellcheck) final override {
+ *aSpellcheck = Spellcheck();
+ return NS_OK;
+ }
+ NS_IMETHOD SetSpellcheck(bool aSpellcheck) final override {
+ mozilla::ErrorResult rv;
+ SetSpellcheck(aSpellcheck, rv);
+ return rv.StealNSResult();
+ }
+ NS_IMETHOD GetOuterHTML(nsAString& aOuterHTML) final override {
+ mozilla::dom::Element::GetOuterHTML(aOuterHTML);
+ return NS_OK;
+ }
+ NS_IMETHOD SetOuterHTML(const nsAString& aOuterHTML) final override {
+ mozilla::ErrorResult rv;
+ mozilla::dom::Element::SetOuterHTML(aOuterHTML, rv);
+ return rv.StealNSResult();
+ }
+ NS_IMETHOD InsertAdjacentHTML(const nsAString& position,
+ const nsAString& text) final override;
+ NS_IMETHOD ScrollIntoView(bool top, uint8_t _argc) final override {
+ if (!_argc) {
+ top = true;
+ }
+ mozilla::dom::Element::ScrollIntoView(top);
+ return NS_OK;
+ }
+ NS_IMETHOD GetOffsetParent(nsIDOMElement** aOffsetParent)
+ final override {
+ mozilla::dom::Element* offsetParent = GetOffsetParent();
+ if (!offsetParent) {
+ *aOffsetParent = nullptr;
+ return NS_OK;
+ }
+ return CallQueryInterface(offsetParent, aOffsetParent);
+ }
+ NS_IMETHOD GetOffsetTop(int32_t* aOffsetTop) final override {
+ *aOffsetTop = OffsetTop();
+ return NS_OK;
+ }
+ NS_IMETHOD GetOffsetLeft(int32_t* aOffsetLeft) final override {
+ *aOffsetLeft = OffsetLeft();
+ return NS_OK;
+ }
+ NS_IMETHOD GetOffsetWidth(int32_t* aOffsetWidth) final override {
+ *aOffsetWidth = OffsetWidth();
+ return NS_OK;
+ }
+ NS_IMETHOD GetOffsetHeight(int32_t* aOffsetHeight) final override {
+ *aOffsetHeight = OffsetHeight();
+ return NS_OK;
+ }
+ NS_IMETHOD DOMClick() final override {
+ Click();
+ return NS_OK;
+ }
+ NS_IMETHOD GetTabIndex(int32_t* aTabIndex) final override {
+ *aTabIndex = TabIndex();
+ return NS_OK;
+ }
+ NS_IMETHOD SetTabIndex(int32_t aTabIndex) final override {
+ mozilla::ErrorResult rv;
+ SetTabIndex(aTabIndex, rv);
+ return rv.StealNSResult();
+ }
+ NS_IMETHOD Focus() final override {
+ mozilla::ErrorResult rv;
+ Focus(rv);
+ return rv.StealNSResult();
+ }
+ NS_IMETHOD GetDraggable(bool* aDraggable) final override {
+ *aDraggable = Draggable();
+ return NS_OK;
+ }
+ NS_IMETHOD GetInnerHTML(nsAString& aInnerHTML) override {
+ return mozilla::dom::Element::GetInnerHTML(aInnerHTML);
+ }
+ using mozilla::dom::Element::SetInnerHTML;
+ NS_IMETHOD SetInnerHTML(const nsAString& aInnerHTML) final override {
+ mozilla::ErrorResult rv;
+ SetInnerHTML(aInnerHTML, rv);
+ return rv.StealNSResult();
+ }
+
+ using nsGenericHTMLElementBase::GetOwnerDocument;
+
+ virtual nsIDOMNode* AsDOMNode() override { return this; }
+
+public:
+ // 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;
+
+ MOZ_ALWAYS_INLINE // Avoid a crashy hook from Avast 10 Beta
+ 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;
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ bool aNotify) override;
+ virtual bool IsFocusableInternal(int32_t *aTabIndex, bool aWithMouse) override
+ {
+ bool isFocusable = false;
+ IsHTMLFocusable(aWithMouse, &isFocusable, aTabIndex);
+ return isFocusable;
+ }
+ /**
+ * Returns true if a subclass is not allowed to override the value returned
+ * in aIsFocusable.
+ */
+ virtual bool IsHTMLFocusable(bool aWithMouse,
+ bool *aIsFocusable,
+ int32_t *aTabIndex);
+ virtual bool PerformAccesskey(bool aKeyCausesActivation,
+ bool aIsTrustedEvent) override;
+
+ /**
+ * Check if an event for an anchor can be handled
+ * @return true if the event can be handled, false otherwise
+ */
+ bool CheckHandleEventForAnchorsPreconditions(
+ mozilla::EventChainVisitor& aVisitor);
+ nsresult PreHandleEventForAnchors(mozilla::EventChainPreVisitor& aVisitor);
+ nsresult PostHandleEventForAnchors(mozilla::EventChainPostVisitor& aVisitor);
+ bool IsHTMLLink(nsIURI** aURI) const;
+
+ // HTML element methods
+ void Compact() { mAttrsAndChildren.Compact(); }
+
+ virtual void UpdateEditableState(bool aNotify) override;
+
+ virtual mozilla::EventStates IntrinsicState() const override;
+
+ // Helper for setting our editable flag and notifying
+ void DoSetEditableFlag(bool aEditable, bool aNotify) {
+ SetEditableFlag(aEditable);
+ UpdateState(aNotify);
+ }
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+
+ bool ParseBackgroundAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult);
+
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
+
+ /**
+ * Get the base target for any links within this piece
+ * of content. Generally, this is the document's base target,
+ * but certain content carries a local base for backward
+ * compatibility.
+ *
+ * @param aBaseTarget the base target [OUT]
+ */
+ void GetBaseTarget(nsAString& aBaseTarget) const;
+
+ /**
+ * Get the primary form control frame for this element. Same as
+ * GetPrimaryFrame(), except it QI's to nsIFormControlFrame.
+ *
+ * @param aFlush whether to flush out frames so that they're up to date.
+ * @return the primary frame as nsIFormControlFrame
+ */
+ nsIFormControlFrame* GetFormControlFrame(bool aFlushFrames);
+
+ //----------------------------------------
+
+ /**
+ * Parse an alignment attribute (top/middle/bottom/baseline)
+ *
+ * @param aString the string to parse
+ * @param aResult the resulting HTMLValue
+ * @return whether the value was parsed
+ */
+ static bool ParseAlignValue(const nsAString& aString,
+ nsAttrValue& aResult);
+
+ /**
+ * Parse a div align string to value (left/right/center/middle/justify)
+ *
+ * @param aString the string to parse
+ * @param aResult the resulting HTMLValue
+ * @return whether the value was parsed
+ */
+ static bool ParseDivAlignValue(const nsAString& aString,
+ nsAttrValue& aResult);
+
+ /**
+ * Convert a table halign string to value (left/right/center/char/justify)
+ *
+ * @param aString the string to parse
+ * @param aResult the resulting HTMLValue
+ * @return whether the value was parsed
+ */
+ static bool ParseTableHAlignValue(const nsAString& aString,
+ nsAttrValue& aResult);
+
+ /**
+ * Convert a table cell halign string to value
+ *
+ * @param aString the string to parse
+ * @param aResult the resulting HTMLValue
+ * @return whether the value was parsed
+ */
+ static bool ParseTableCellHAlignValue(const nsAString& aString,
+ nsAttrValue& aResult);
+
+ /**
+ * Convert a table valign string to value (left/right/center/char/justify/
+ * abscenter/absmiddle/middle)
+ *
+ * @param aString the string to parse
+ * @param aResult the resulting HTMLValue
+ * @return whether the value was parsed
+ */
+ static bool ParseTableVAlignValue(const nsAString& aString,
+ nsAttrValue& aResult);
+
+ /**
+ * Convert an image attribute to value (width, height, hspace, vspace, border)
+ *
+ * @param aAttribute the attribute to parse
+ * @param aString the string to parse
+ * @param aResult the resulting HTMLValue
+ * @return whether the value was parsed
+ */
+ static bool ParseImageAttribute(nsIAtom* aAttribute,
+ const nsAString& aString,
+ nsAttrValue& aResult);
+
+ static bool ParseReferrerAttribute(const nsAString& aString,
+ nsAttrValue& aResult);
+
+ /**
+ * Convert a frameborder string to value (yes/no/1/0)
+ *
+ * @param aString the string to parse
+ * @param aResult the resulting HTMLValue
+ * @return whether the value was parsed
+ */
+ static bool ParseFrameborderValue(const nsAString& aString,
+ nsAttrValue& aResult);
+
+ /**
+ * Convert a scrolling string to value (yes/no/on/off/scroll/noscroll/auto)
+ *
+ * @param aString the string to parse
+ * @param aResult the resulting HTMLValue
+ * @return whether the value was parsed
+ */
+ static bool ParseScrollingValue(const nsAString& aString,
+ nsAttrValue& aResult);
+
+ /*
+ * Attribute Mapping Helpers
+ */
+
+ /**
+ * A style attribute mapping function for the most common attributes, to be
+ * called by subclasses' attribute mapping functions. Currently handles
+ * dir, lang and hidden, could handle others.
+ *
+ * @param aAttributes the list of attributes to map
+ * @param aData the returned rule data [INOUT]
+ * @see GetAttributeMappingFunction
+ */
+ static void MapCommonAttributesInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aRuleData);
+ /**
+ * Same as MapCommonAttributesInto except that it does not handle hidden.
+ *
+ * @param aAttributes the list of attributes to map
+ * @param aData the returned rule data [INOUT]
+ * @see GetAttributeMappingFunction
+ */
+ static void MapCommonAttributesIntoExceptHidden(const nsMappedAttributes* aAttributes,
+ nsRuleData* aRuleData);
+
+ static const MappedAttributeEntry sCommonAttributeMap[];
+ static const MappedAttributeEntry sImageMarginSizeAttributeMap[];
+ static const MappedAttributeEntry sImageBorderAttributeMap[];
+ static const MappedAttributeEntry sImageAlignAttributeMap[];
+ static const MappedAttributeEntry sDivAlignAttributeMap[];
+ static const MappedAttributeEntry sBackgroundAttributeMap[];
+ static const MappedAttributeEntry sBackgroundColorAttributeMap[];
+
+ /**
+ * Helper to map the align attribute into a style struct.
+ *
+ * @param aAttributes the list of attributes to map
+ * @param aData the returned rule data [INOUT]
+ * @see GetAttributeMappingFunction
+ */
+ static void MapImageAlignAttributeInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+
+ /**
+ * Helper to map the align attribute into a style struct for things
+ * like <div>, <h1>, etc.
+ *
+ * @param aAttributes the list of attributes to map
+ * @param aData the returned rule data [INOUT]
+ * @see GetAttributeMappingFunction
+ */
+ static void MapDivAlignAttributeInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+
+ /**
+ * Helper to map the image border attribute into a style struct.
+ *
+ * @param aAttributes the list of attributes to map
+ * @param aData the returned rule data [INOUT]
+ * @see GetAttributeMappingFunction
+ */
+ static void MapImageBorderAttributeInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+ /**
+ * Helper to map the image margin attribute into a style struct.
+ *
+ * @param aAttributes the list of attributes to map
+ * @param aData the returned rule data [INOUT]
+ * @see GetAttributeMappingFunction
+ */
+ static void MapImageMarginAttributeInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+ /**
+ * Helper to map the image position attribute into a style struct.
+ *
+ * @param aAttributes the list of attributes to map
+ * @param aData the returned rule data [INOUT]
+ * @see GetAttributeMappingFunction
+ */
+ static void MapImageSizeAttributesInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+ /**
+ * Helper to map the background attribute
+ * into a style struct.
+ *
+ * @param aAttributes the list of attributes to map
+ * @param aData the returned rule data [INOUT]
+ * @see GetAttributeMappingFunction
+ */
+ static void MapBackgroundInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+ /**
+ * Helper to map the bgcolor attribute
+ * into a style struct.
+ *
+ * @param aAttributes the list of attributes to map
+ * @param aData the returned rule data [INOUT]
+ * @see GetAttributeMappingFunction
+ */
+ static void MapBGColorInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+ /**
+ * Helper to map the background attributes (currently background and bgcolor)
+ * into a style struct.
+ *
+ * @param aAttributes the list of attributes to map
+ * @param aData the returned rule data [INOUT]
+ * @see GetAttributeMappingFunction
+ */
+ static void MapBackgroundAttributesInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+ /**
+ * Helper to map the scrolling attribute on FRAME and IFRAME
+ * into a style struct.
+ *
+ * @param aAttributes the list of attributes to map
+ * @param aData the returned rule data [INOUT]
+ * @see GetAttributeMappingFunction
+ */
+ static void MapScrollingAttributeInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+ /**
+ * Get the presentation context for this content node.
+ * @return the presentation context
+ */
+ enum PresContextFor
+ {
+ eForComposedDoc,
+ eForUncomposedDoc
+ };
+ nsPresContext* GetPresContext(PresContextFor aFor);
+
+ // Form Helper Routines
+ /**
+ * Find an ancestor of this content node which is a form (could be null)
+ * @param aCurrentForm the current form for this node. If this is
+ * non-null, and no ancestor form is found, and the current form is in
+ * a connected subtree with the node, the current form will be
+ * returned. This is needed to handle cases when HTML elements have a
+ * current form that they're not descendants of.
+ * @note This method should not be called if the element has a form attribute.
+ */
+ mozilla::dom::HTMLFormElement*
+ FindAncestorForm(mozilla::dom::HTMLFormElement* aCurrentForm = nullptr);
+
+ virtual void RecompileScriptEventListeners() override;
+
+ /**
+ * See if the document being tested has nav-quirks mode enabled.
+ * @param doc the document
+ */
+ static bool InNavQuirksMode(nsIDocument* aDoc);
+
+ /**
+ * Locate an nsIEditor rooted at this content node, if there is one.
+ */
+ nsresult GetEditor(nsIEditor** aEditor);
+
+ /**
+ * Helper method for NS_IMPL_URI_ATTR macro.
+ * Gets the absolute URI value of an attribute, by resolving any relative
+ * URIs in the attribute against the baseuri of the element. If the attribute
+ * isn't a relative URI the value of the attribute is returned as is. Only
+ * works for attributes in null namespace.
+ *
+ * @param aAttr name of attribute.
+ * @param aBaseAttr name of base attribute.
+ * @param aResult result value [out]
+ */
+ void GetURIAttr(nsIAtom* aAttr, nsIAtom* aBaseAttr, nsAString& aResult) const;
+
+ /**
+ * Gets the absolute URI values of an attribute, by resolving any relative
+ * URIs in the attribute against the baseuri of the element. If a substring
+ * isn't a relative URI, the substring is returned as is. Only works for
+ * attributes in null namespace.
+ */
+ bool GetURIAttr(nsIAtom* aAttr, nsIAtom* aBaseAttr, nsIURI** aURI) const;
+
+ /**
+ * Returns the current disabled state of the element.
+ */
+ virtual bool IsDisabled() const {
+ return false;
+ }
+
+ bool IsHidden() const
+ {
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::hidden);
+ }
+
+ virtual bool IsLabelable() const override;
+ virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
+
+ static bool TouchEventsEnabled(JSContext* /* unused */, JSObject* /* unused */);
+
+ static inline bool
+ CanHaveName(nsIAtom* aTag)
+ {
+ return aTag == nsGkAtoms::img ||
+ aTag == nsGkAtoms::form ||
+ aTag == nsGkAtoms::applet ||
+ aTag == nsGkAtoms::embed ||
+ aTag == nsGkAtoms::object;
+ }
+ static inline bool
+ ShouldExposeNameAsHTMLDocumentProperty(Element* aElement)
+ {
+ return aElement->IsHTMLElement() &&
+ CanHaveName(aElement->NodeInfo()->NameAtom());
+ }
+ static inline bool
+ ShouldExposeIdAsHTMLDocumentProperty(Element* aElement)
+ {
+ if (aElement->IsAnyOfHTMLElements(nsGkAtoms::applet,
+ nsGkAtoms::embed,
+ nsGkAtoms::object)) {
+ return true;
+ }
+
+ // Per spec, <img> is exposed by id only if it also has a nonempty
+ // name (which doesn't have to match the id or anything).
+ // HasName() is true precisely when name is nonempty.
+ return aElement->IsHTMLElement(nsGkAtoms::img) && aElement->HasName();
+ }
+
+ static bool
+ IsScrollGrabAllowed(JSContext*, JSObject*);
+
+protected:
+ /**
+ * Add/remove this element to the documents name cache
+ */
+ void AddToNameTable(nsIAtom* aName) {
+ NS_ASSERTION(HasName(), "Node doesn't have name?");
+ nsIDocument* doc = GetUncomposedDoc();
+ if (doc && !IsInAnonymousSubtree()) {
+ doc->AddToNameTable(this, aName);
+ }
+ }
+ void RemoveFromNameTable() {
+ if (HasName()) {
+ nsIDocument* doc = GetUncomposedDoc();
+ if (doc) {
+ doc->RemoveFromNameTable(this, GetParsedAttr(nsGkAtoms::name)->
+ GetAtomValue());
+ }
+ }
+ }
+
+ /**
+ * Register or unregister an access key to this element based on the
+ * accesskey attribute.
+ */
+ void RegAccessKey()
+ {
+ if (HasFlag(NODE_HAS_ACCESSKEY)) {
+ RegUnRegAccessKey(true);
+ }
+ }
+
+ void UnregAccessKey()
+ {
+ if (HasFlag(NODE_HAS_ACCESSKEY)) {
+ RegUnRegAccessKey(false);
+ }
+ }
+
+private:
+ void RegUnRegAccessKey(bool aDoReg);
+
+protected:
+ virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+
+ virtual mozilla::EventListenerManager*
+ GetEventListenerManagerForAttr(nsIAtom* aAttrName,
+ bool* aDefer) override;
+
+ /**
+ * Dispatch a simulated mouse click by keyboard to the given element.
+ */
+ nsresult DispatchSimulatedClick(nsGenericHTMLElement* aElement,
+ bool aIsTrusted,
+ nsPresContext* aPresContext);
+
+ /**
+ * Create a URI for the given aURISpec string.
+ * Returns INVALID_STATE_ERR and nulls *aURI if aURISpec is empty
+ * and the document's URI matches the element's base URI.
+ */
+ nsresult NewURIFromString(const nsAString& aURISpec, nsIURI** aURI);
+
+ void GetHTMLAttr(nsIAtom* aName, nsAString& aResult) const
+ {
+ GetAttr(kNameSpaceID_None, aName, aResult);
+ }
+ void GetHTMLAttr(nsIAtom* aName, mozilla::dom::DOMString& aResult) const
+ {
+ GetAttr(kNameSpaceID_None, aName, aResult);
+ }
+ void GetHTMLEnumAttr(nsIAtom* aName, nsAString& aResult) const
+ {
+ GetEnumAttr(aName, nullptr, aResult);
+ }
+ void GetHTMLURIAttr(nsIAtom* aName, nsAString& aResult) const
+ {
+ GetURIAttr(aName, nullptr, aResult);
+ }
+
+ void SetHTMLAttr(nsIAtom* aName, const nsAString& aValue)
+ {
+ SetAttr(kNameSpaceID_None, aName, aValue, true);
+ }
+ void SetHTMLAttr(nsIAtom* aName, const nsAString& aValue, mozilla::ErrorResult& aError)
+ {
+ mozilla::dom::Element::SetAttr(aName, aValue, aError);
+ }
+ void UnsetHTMLAttr(nsIAtom* aName, mozilla::ErrorResult& aError)
+ {
+ mozilla::dom::Element::UnsetAttr(aName, aError);
+ }
+ void SetHTMLBoolAttr(nsIAtom* aName, bool aValue, mozilla::ErrorResult& aError)
+ {
+ if (aValue) {
+ SetHTMLAttr(aName, EmptyString(), aError);
+ } else {
+ UnsetHTMLAttr(aName, aError);
+ }
+ }
+ template<typename T>
+ void SetHTMLIntAttr(nsIAtom* aName, T aValue, mozilla::ErrorResult& aError)
+ {
+ nsAutoString value;
+ value.AppendInt(aValue);
+
+ SetHTMLAttr(aName, value, aError);
+ }
+
+ /**
+ * Helper method for NS_IMPL_STRING_ATTR macro.
+ * Sets the value of an attribute, returns specified default value if the
+ * attribute isn't set. Only works for attributes in null namespace.
+ *
+ * @param aAttr name of attribute.
+ * @param aDefault default-value to return if attribute isn't set.
+ * @param aResult result value [out]
+ */
+ nsresult SetAttrHelper(nsIAtom* aAttr, const nsAString& aValue);
+
+ /**
+ * Helper method for NS_IMPL_INT_ATTR macro.
+ * Gets the integer-value of an attribute, returns specified default value
+ * if the attribute isn't set or isn't set to an integer. Only works for
+ * attributes in null namespace.
+ *
+ * @param aAttr name of attribute.
+ * @param aDefault default-value to return if attribute isn't set.
+ */
+ int32_t GetIntAttr(nsIAtom* aAttr, int32_t aDefault) const;
+
+ /**
+ * Helper method for NS_IMPL_INT_ATTR macro.
+ * Sets value of attribute to specified integer. Only works for attributes
+ * in null namespace.
+ *
+ * @param aAttr name of attribute.
+ * @param aValue Integer value of attribute.
+ */
+ nsresult SetIntAttr(nsIAtom* aAttr, int32_t aValue);
+
+ /**
+ * Helper method for NS_IMPL_UINT_ATTR macro.
+ * Gets the unsigned integer-value of an attribute, returns specified default
+ * value if the attribute isn't set or isn't set to an integer. Only works for
+ * attributes in null namespace.
+ *
+ * @param aAttr name of attribute.
+ * @param aDefault default-value to return if attribute isn't set.
+ */
+ uint32_t GetUnsignedIntAttr(nsIAtom* aAttr, uint32_t aDefault) const;
+
+ /**
+ * Helper method for NS_IMPL_UINT_ATTR macro.
+ * Sets value of attribute to specified unsigned integer. Only works for
+ * attributes in null namespace.
+ *
+ * @param aAttr name of attribute.
+ * @param aValue Integer value of attribute.
+ * @param aDefault Default value (in case value is out of range). If the spec
+ * doesn't provide one, should be 1 if the value is limited to
+ * nonzero values, and 0 otherwise.
+ */
+ void SetUnsignedIntAttr(nsIAtom* aName, uint32_t aValue, uint32_t aDefault,
+ mozilla::ErrorResult& aError)
+ {
+ nsAutoString value;
+ if (aValue > INT32_MAX) {
+ value.AppendInt(aDefault);
+ } else {
+ value.AppendInt(aValue);
+ }
+
+ SetHTMLAttr(aName, value, aError);
+ }
+
+ /**
+ * Sets value of attribute to specified double. Only works for attributes
+ * in null namespace.
+ *
+ * @param aAttr name of attribute.
+ * @param aValue Double value of attribute.
+ */
+ void SetDoubleAttr(nsIAtom* aAttr, double aValue, mozilla::ErrorResult& aRv)
+ {
+ nsAutoString value;
+ value.AppendFloat(aValue);
+
+ SetHTMLAttr(aAttr, value, aRv);
+ }
+
+ /**
+ * Locates the nsIEditor associated with this node. In general this is
+ * equivalent to GetEditorInternal(), but for designmode or contenteditable,
+ * this may need to get an editor that's not actually on this element's
+ * associated TextControlFrame. This is used by the spellchecking routines
+ * to get the editor affected by changing the spellcheck attribute on this
+ * node.
+ */
+ virtual already_AddRefed<nsIEditor> GetAssociatedEditor();
+
+ /**
+ * Get the frame's offset information for offsetTop/Left/Width/Height.
+ * Returns the parent the offset is relative to.
+ * @note This method flushes pending notifications (Flush_Layout).
+ * @param aRect the offset information [OUT]
+ */
+ mozilla::dom::Element* GetOffsetRect(mozilla::CSSIntRect& aRect);
+
+ /**
+ * Returns true if this is the current document's body element
+ */
+ bool IsCurrentBodyElement();
+
+ /**
+ * Ensures all editors associated with a subtree are synced, for purposes of
+ * spellchecking.
+ */
+ static void SyncEditorsOnSubtree(nsIContent* content);
+
+ enum ContentEditableTristate {
+ eInherit = -1,
+ eFalse = 0,
+ eTrue = 1
+ };
+
+ /**
+ * Returns eTrue if the element has a contentEditable attribute and its value
+ * is "true" or an empty string. Returns eFalse if the element has a
+ * contentEditable attribute and its value is "false". Otherwise returns
+ * eInherit.
+ */
+ ContentEditableTristate GetContentEditableValue() const
+ {
+ static const nsIContent::AttrValuesArray values[] =
+ { &nsGkAtoms::_false, &nsGkAtoms::_true, &nsGkAtoms::_empty, nullptr };
+
+ if (!MayHaveContentEditableAttr())
+ return eInherit;
+
+ int32_t value = FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::contenteditable, values,
+ eIgnoreCase);
+
+ return value > 0 ? eTrue : (value == 0 ? eFalse : eInherit);
+ }
+
+ // Used by A, AREA, LINK, and STYLE.
+ already_AddRefed<nsIURI> GetHrefURIForAnchors() const;
+
+ /**
+ * Returns whether this element is an editable root. There are two types of
+ * editable roots:
+ * 1) the documentElement if the whole document is editable (for example for
+ * desginMode=on)
+ * 2) an element that is marked editable with contentEditable=true and that
+ * doesn't have a parent or whose parent is not editable.
+ * Note that this doesn't return input and textarea elements that haven't been
+ * made editable through contentEditable or designMode.
+ */
+ bool IsEditableRoot() const;
+
+private:
+ void ChangeEditableState(int32_t aChange);
+};
+
+namespace mozilla {
+namespace dom {
+class HTMLFieldSetElement;
+} // namespace dom
+} // namespace mozilla
+
+#define FORM_ELEMENT_FLAG_BIT(n_) NODE_FLAG_BIT(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + (n_))
+
+// Form element specific bits
+enum {
+ // If this flag is set on an nsGenericHTMLFormElement or an HTMLImageElement,
+ // that means that we have added ourselves to our mForm. It's possible to
+ // have a non-null mForm, but not have this flag set. That happens when the
+ // form is set via the content sink.
+ ADDED_TO_FORM = FORM_ELEMENT_FLAG_BIT(0),
+
+ // If this flag is set on an nsGenericHTMLFormElement or an HTMLImageElement,
+ // that means that its form is in the process of being unbound from the tree,
+ // and this form element hasn't re-found its form in
+ // nsGenericHTMLFormElement::UnbindFromTree yet.
+ MAYBE_ORPHAN_FORM_ELEMENT = FORM_ELEMENT_FLAG_BIT(1)
+};
+
+// NOTE: I don't think it's possible to have the above two flags set at the
+// same time, so if it becomes an issue we can probably merge them into the
+// same bit. --bz
+
+ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 2);
+
+#undef FORM_ELEMENT_FLAG_BIT
+
+/**
+ * A helper class for form elements that can contain children
+ */
+class nsGenericHTMLFormElement : public nsGenericHTMLElement,
+ public nsIFormControl
+{
+public:
+ explicit nsGenericHTMLFormElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsINode* GetScopeChainParent() const override;
+
+ virtual bool IsNodeOfType(uint32_t aFlags) const override;
+ virtual void SaveSubtreeState() override;
+
+ // nsIFormControl
+ virtual mozilla::dom::HTMLFieldSetElement* GetFieldSet() override;
+ virtual mozilla::dom::Element* GetFormElement() override;
+ mozilla::dom::HTMLFormElement* GetForm() const
+ {
+ return mForm;
+ }
+ virtual void SetForm(nsIDOMHTMLFormElement* aForm) override;
+ virtual void ClearForm(bool aRemoveFromForm) override;
+
+ nsresult GetForm(nsIDOMHTMLFormElement** aForm);
+
+ NS_IMETHOD SaveState() override
+ {
+ return NS_OK;
+ }
+
+ virtual bool RestoreState(nsPresState* aState) override
+ {
+ return false;
+ }
+ virtual bool AllowDrop() override
+ {
+ return true;
+ }
+
+ // nsIContent
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+ virtual IMEState GetDesiredIMEState() override;
+ virtual mozilla::EventStates IntrinsicState() const override;
+
+ virtual nsresult PreHandleEvent(
+ mozilla::EventChainPreVisitor& aVisitor) override;
+
+ virtual bool IsDisabled() const override;
+
+ /**
+ * This callback is called by a fieldest on all its elements whenever its
+ * disabled attribute is changed so the element knows its disabled state
+ * might have changed.
+ *
+ * @note Classes redefining this method should not do any content
+ * state updates themselves but should just make sure to call into
+ * nsGenericHTMLFormElement::FieldSetDisabledChanged.
+ */
+ virtual void FieldSetDisabledChanged(bool aNotify);
+
+ void FieldSetFirstLegendChanged(bool aNotify) {
+ UpdateFieldSet(aNotify);
+ }
+
+ /**
+ * This callback is called by a fieldset on all it's elements when it's being
+ * destroyed. When called, the elements should check that aFieldset is there
+ * first parent fieldset and null mFieldset in that case only.
+ *
+ * @param aFieldSet The fieldset being removed.
+ */
+ void ForgetFieldSet(nsIContent* aFieldset);
+
+ /**
+ * Returns if the control can be disabled.
+ */
+ bool CanBeDisabled() const;
+
+ virtual bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
+ int32_t* aTabIndex) override;
+
+ virtual bool IsLabelable() const override;
+
+protected:
+ virtual ~nsGenericHTMLFormElement();
+
+ virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify) override;
+
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+
+ /**
+ * This method will update the form owner, using @form or looking to a parent.
+ *
+ * @param aBindToTree Whether the element is being attached to the tree.
+ * @param aFormIdElement The element associated with the id in @form. If
+ * aBindToTree is false, aFormIdElement *must* contain the element associated
+ * with the id in @form. Otherwise, it *must* be null.
+ *
+ * @note Callers of UpdateFormOwner have to be sure the element is in a
+ * document (GetUncomposedDoc() != nullptr).
+ */
+ void UpdateFormOwner(bool aBindToTree, Element* aFormIdElement);
+
+ /**
+ * This method will update mFieldset and set it to the first fieldset parent.
+ */
+ void UpdateFieldSet(bool aNotify);
+
+ /**
+ * Add a form id observer which will observe when the element with the id in
+ * @form will change.
+ *
+ * @return The element associated with the current id in @form (may be null).
+ */
+ Element* AddFormIdObserver();
+
+ /**
+ * Remove the form id observer.
+ */
+ void RemoveFormIdObserver();
+
+ /**
+ * This method is a a callback for IDTargetObserver (from nsIDocument).
+ * It will be called each time the element associated with the id in @form
+ * changes.
+ */
+ static bool FormIdUpdated(Element* aOldElement, Element* aNewElement,
+ void* aData);
+
+ // Returns true if the event should not be handled from PreHandleEvent
+ bool IsElementDisabledForEvents(mozilla::EventMessage aMessage,
+ nsIFrame* aFrame);
+
+ // The focusability state of this form control. eUnfocusable means that it
+ // shouldn't be focused at all, eInactiveWindow means it's in an inactive
+ // window, eActiveWindow means it's in an active window.
+ enum FocusTristate {
+ eUnfocusable,
+ eInactiveWindow,
+ eActiveWindow
+ };
+
+ // Get our focus state. If this returns eInactiveWindow, it will set this
+ // element as the focused element for that window.
+ FocusTristate FocusState();
+
+ /** The form that contains this control */
+ mozilla::dom::HTMLFormElement* mForm;
+
+ /* This is a pointer to our closest fieldset parent if any */
+ mozilla::dom::HTMLFieldSetElement* mFieldSet;
+};
+
+class nsGenericHTMLFormElementWithState : public nsGenericHTMLFormElement
+{
+public:
+ explicit nsGenericHTMLFormElementWithState(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+
+ /**
+ * Get the presentation state for a piece of content, or create it if it does
+ * not exist. Generally used by SaveState().
+ */
+ nsPresState* GetPrimaryPresState();
+
+ /**
+ * Get the layout history object for a particular piece of content.
+ *
+ * @param aRead if true, won't return a layout history state if the
+ * layout history state is empty.
+ * @return the history state object
+ */
+ already_AddRefed<nsILayoutHistoryState>
+ GetLayoutHistory(bool aRead);
+
+ /**
+ * Restore the state for a form control. Ends up calling
+ * nsIFormControl::RestoreState().
+ *
+ * @return false if RestoreState() was not called, the return
+ * value of RestoreState() otherwise.
+ */
+ bool RestoreFormControlState();
+
+ /**
+ * Called when we have been cloned and adopted, and the information of the
+ * node has been changed.
+ */
+ virtual void NodeInfoChanged() override;
+
+protected:
+ /* Generates the state key for saving the form state in the session if not
+ computed already. The result is stored in mStateKey on success */
+ nsresult GenerateStateKey();
+
+ /* Used to store the key to that element in the session. Is void until
+ GenerateStateKey has been used */
+ nsCString mStateKey;
+};
+
+//----------------------------------------------------------------------
+
+/**
+ * This macro is similar to NS_IMPL_STRING_ATTR except that the getter method
+ * falls back to an alternative method if the content attribute isn't set.
+ */
+#define NS_IMPL_STRING_ATTR_WITH_FALLBACK(_class, _method, _atom, _fallback) \
+ NS_IMETHODIMP \
+ _class::Get##_method(nsAString& aValue) \
+ { \
+ if (!GetAttr(kNameSpaceID_None, nsGkAtoms::_atom, aValue)) { \
+ _fallback(aValue); \
+ } \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP \
+ _class::Set##_method(const nsAString& aValue) \
+ { \
+ return SetAttrHelper(nsGkAtoms::_atom, aValue); \
+ }
+
+/**
+ * A macro to implement the getter and setter for a given integer
+ * valued content property. The method uses the generic GetAttr and
+ * SetAttr methods.
+ */
+#define NS_IMPL_INT_ATTR(_class, _method, _atom) \
+ NS_IMPL_INT_ATTR_DEFAULT_VALUE(_class, _method, _atom, 0)
+
+#define NS_IMPL_INT_ATTR_DEFAULT_VALUE(_class, _method, _atom, _default) \
+ NS_IMETHODIMP \
+ _class::Get##_method(int32_t* aValue) \
+ { \
+ *aValue = GetIntAttr(nsGkAtoms::_atom, _default); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP \
+ _class::Set##_method(int32_t aValue) \
+ { \
+ return SetIntAttr(nsGkAtoms::_atom, aValue); \
+ }
+
+/**
+ * A macro to implement the getter and setter for a given unsigned integer
+ * valued content property. The method uses GetUnsignedIntAttr and
+ * SetUnsignedIntAttr methods.
+ */
+#define NS_IMPL_UINT_ATTR(_class, _method, _atom) \
+ NS_IMPL_UINT_ATTR_DEFAULT_VALUE(_class, _method, _atom, 0)
+
+#define NS_IMPL_UINT_ATTR_DEFAULT_VALUE(_class, _method, _atom, _default) \
+ NS_IMETHODIMP \
+ _class::Get##_method(uint32_t* aValue) \
+ { \
+ *aValue = GetUnsignedIntAttr(nsGkAtoms::_atom, _default); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP \
+ _class::Set##_method(uint32_t aValue) \
+ { \
+ mozilla::ErrorResult rv; \
+ SetUnsignedIntAttr(nsGkAtoms::_atom, aValue, _default, rv); \
+ return rv.StealNSResult(); \
+ }
+
+/**
+ * A macro to implement the getter and setter for a given unsigned integer
+ * valued content property. The method uses GetUnsignedIntAttr and
+ * SetUnsignedIntAttr methods. This macro is similar to NS_IMPL_UINT_ATTR except
+ * that it throws an exception if the set value is null.
+ */
+#define NS_IMPL_UINT_ATTR_NON_ZERO(_class, _method, _atom) \
+ NS_IMPL_UINT_ATTR_NON_ZERO_DEFAULT_VALUE(_class, _method, _atom, 1)
+
+#define NS_IMPL_UINT_ATTR_NON_ZERO_DEFAULT_VALUE(_class, _method, _atom, _default) \
+ NS_IMETHODIMP \
+ _class::Get##_method(uint32_t* aValue) \
+ { \
+ *aValue = GetUnsignedIntAttr(nsGkAtoms::_atom, _default); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP \
+ _class::Set##_method(uint32_t aValue) \
+ { \
+ if (aValue == 0) { \
+ return NS_ERROR_DOM_INDEX_SIZE_ERR; \
+ } \
+ mozilla::ErrorResult rv; \
+ SetUnsignedIntAttr(nsGkAtoms::_atom, aValue, _default, rv); \
+ return rv.StealNSResult(); \
+ }
+
+/**
+ * A macro to implement the getter and setter for a given content
+ * property that needs to return a URI in string form. The method
+ * uses the generic GetAttr and SetAttr methods. This macro is much
+ * like the NS_IMPL_STRING_ATTR macro, except we make sure the URI is
+ * absolute.
+ */
+#define NS_IMPL_URI_ATTR(_class, _method, _atom) \
+ NS_IMETHODIMP \
+ _class::Get##_method(nsAString& aValue) \
+ { \
+ GetURIAttr(nsGkAtoms::_atom, nullptr, aValue); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP \
+ _class::Set##_method(const nsAString& aValue) \
+ { \
+ return SetAttrHelper(nsGkAtoms::_atom, aValue); \
+ }
+
+#define NS_IMPL_URI_ATTR_WITH_BASE(_class, _method, _atom, _base_atom) \
+ NS_IMETHODIMP \
+ _class::Get##_method(nsAString& aValue) \
+ { \
+ GetURIAttr(nsGkAtoms::_atom, nsGkAtoms::_base_atom, aValue); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP \
+ _class::Set##_method(const nsAString& aValue) \
+ { \
+ return SetAttrHelper(nsGkAtoms::_atom, aValue); \
+ }
+
+/**
+ * A macro to implement getter and setter for action and form action content
+ * attributes. It's very similar to NS_IMPL_URI_ATTR excepted that if the
+ * content attribute is the empty string, the empty string is returned.
+ */
+#define NS_IMPL_ACTION_ATTR(_class, _method, _atom) \
+ NS_IMETHODIMP \
+ _class::Get##_method(nsAString& aValue) \
+ { \
+ GetAttr(kNameSpaceID_None, nsGkAtoms::_atom, aValue); \
+ if (!aValue.IsEmpty()) { \
+ GetURIAttr(nsGkAtoms::_atom, nullptr, aValue); \
+ } \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP \
+ _class::Set##_method(const nsAString& aValue) \
+ { \
+ return SetAttrHelper(nsGkAtoms::_atom, aValue); \
+ }
+
+/**
+ * A macro to implement the getter and setter for a given content
+ * property that needs to set a non-negative integer. The method
+ * uses the generic GetAttr and SetAttr methods. This macro is much
+ * like the NS_IMPL_INT_ATTR macro except we throw an exception if
+ * the set value is negative.
+ */
+#define NS_IMPL_NON_NEGATIVE_INT_ATTR(_class, _method, _atom) \
+ NS_IMPL_NON_NEGATIVE_INT_ATTR_DEFAULT_VALUE(_class, _method, _atom, -1)
+
+#define NS_IMPL_NON_NEGATIVE_INT_ATTR_DEFAULT_VALUE(_class, _method, _atom, _default) \
+ NS_IMETHODIMP \
+ _class::Get##_method(int32_t* aValue) \
+ { \
+ *aValue = GetIntAttr(nsGkAtoms::_atom, _default); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP \
+ _class::Set##_method(int32_t aValue) \
+ { \
+ if (aValue < 0) { \
+ return NS_ERROR_DOM_INDEX_SIZE_ERR; \
+ } \
+ return SetIntAttr(nsGkAtoms::_atom, aValue); \
+ }
+
+/**
+ * A macro to implement the getter and setter for a given content
+ * property that needs to set an enumerated string. The method
+ * uses a specific GetEnumAttr and the generic SetAttrHelper methods.
+ */
+#define NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(_class, _method, _atom, _default) \
+ NS_IMETHODIMP \
+ _class::Get##_method(nsAString& aValue) \
+ { \
+ GetEnumAttr(nsGkAtoms::_atom, _default, aValue); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP \
+ _class::Set##_method(const nsAString& aValue) \
+ { \
+ return SetAttrHelper(nsGkAtoms::_atom, aValue); \
+ }
+
+/**
+ * A macro to implement the getter and setter for a given content
+ * property that needs to set an enumerated string that has different
+ * default values for missing and invalid values. The method uses a
+ * specific GetEnumAttr and the generic SetAttrHelper methods.
+ */
+#define NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(_class, _method, _atom, _defaultMissing, _defaultInvalid) \
+ NS_IMETHODIMP \
+ _class::Get##_method(nsAString& aValue) \
+ { \
+ GetEnumAttr(nsGkAtoms::_atom, _defaultMissing, _defaultInvalid, aValue); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP \
+ _class::Set##_method(const nsAString& aValue) \
+ { \
+ return SetAttrHelper(nsGkAtoms::_atom, aValue); \
+ }
+
+#define NS_INTERFACE_MAP_ENTRY_IF_TAG(_interface, _tag) \
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(_interface, \
+ mNodeInfo->Equals(nsGkAtoms::_tag))
+
+
+/**
+ * A macro to declare the NS_NewHTMLXXXElement() functions.
+ */
+#define NS_DECLARE_NS_NEW_HTML_ELEMENT(_elementName) \
+namespace mozilla { \
+namespace dom { \
+class HTML##_elementName##Element; \
+} \
+} \
+nsGenericHTMLElement* \
+NS_NewHTML##_elementName##Element(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, \
+ mozilla::dom::FromParser aFromParser = mozilla::dom::NOT_FROM_PARSER);
+
+#define NS_DECLARE_NS_NEW_HTML_ELEMENT_AS_SHARED(_elementName) \
+inline nsGenericHTMLElement* \
+NS_NewHTML##_elementName##Element(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, \
+ mozilla::dom::FromParser aFromParser = mozilla::dom::NOT_FROM_PARSER) \
+{ \
+ return NS_NewHTMLSharedElement(mozilla::Move(aNodeInfo), aFromParser); \
+}
+
+/**
+ * A macro to implement the NS_NewHTMLXXXElement() functions.
+ */
+#define NS_IMPL_NS_NEW_HTML_ELEMENT(_elementName) \
+nsGenericHTMLElement* \
+NS_NewHTML##_elementName##Element(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, \
+ mozilla::dom::FromParser aFromParser) \
+{ \
+ return new mozilla::dom::HTML##_elementName##Element(aNodeInfo); \
+}
+
+#define NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(_elementName) \
+nsGenericHTMLElement* \
+NS_NewHTML##_elementName##Element(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, \
+ mozilla::dom::FromParser aFromParser) \
+{ \
+ return new mozilla::dom::HTML##_elementName##Element(aNodeInfo, \
+ aFromParser); \
+}
+
+// Here, we expand 'NS_DECLARE_NS_NEW_HTML_ELEMENT()' by hand.
+// (Calling the macro directly (with no args) produces compiler warnings.)
+nsGenericHTMLElement*
+NS_NewHTMLElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ mozilla::dom::FromParser aFromParser = mozilla::dom::NOT_FROM_PARSER);
+
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Shared)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(SharedList)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(SharedObject)
+
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Anchor)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Area)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Audio)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(BR)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Body)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Button)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Canvas)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Content)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Mod)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Data)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(DataList)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Details)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Div)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(FieldSet)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Font)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Form)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Frame)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(FrameSet)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(HR)
+NS_DECLARE_NS_NEW_HTML_ELEMENT_AS_SHARED(Head)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Heading)
+NS_DECLARE_NS_NEW_HTML_ELEMENT_AS_SHARED(Html)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(IFrame)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Image)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Input)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(LI)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Label)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Legend)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Link)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Map)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Menu)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(MenuItem)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Meta)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Meter)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Object)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(OptGroup)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Option)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Output)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Paragraph)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Picture)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Pre)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Progress)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Script)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Select)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Shadow)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Source)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Span)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Style)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Summary)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(TableCaption)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(TableCell)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(TableCol)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Table)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(TableRow)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(TableSection)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Tbody)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Template)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(TextArea)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Tfoot)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Thead)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Time)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Title)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Track)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Unknown)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Video)
+
+inline nsISupports*
+ToSupports(nsGenericHTMLElement* aHTMLElement)
+{
+ return static_cast<nsIContent*>(aHTMLElement);
+}
+
+inline nsISupports*
+ToCanonicalSupports(nsGenericHTMLElement* aHTMLElement)
+{
+ return static_cast<nsIContent*>(aHTMLElement);
+}
+
+#endif /* nsGenericHTMLElement_h___ */
diff --git a/dom/html/nsGenericHTMLFrameElement.cpp b/dom/html/nsGenericHTMLFrameElement.cpp
new file mode 100644
index 000000000..6e50a4092
--- /dev/null
+++ b/dom/html/nsGenericHTMLFrameElement.cpp
@@ -0,0 +1,690 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsGenericHTMLFrameElement.h"
+
+#include "mozilla/dom/BrowserElementAudioChannel.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/HTMLIFrameElement.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ErrorResult.h"
+#include "GeckoProfiler.h"
+#include "mozIApplication.h"
+#include "nsAttrValueInlines.h"
+#include "nsContentUtils.h"
+#include "nsIAppsService.h"
+#include "nsIDocShell.h"
+#include "nsIDOMDocument.h"
+#include "nsIFrame.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPermissionManager.h"
+#include "nsIPresShell.h"
+#include "nsIScrollable.h"
+#include "nsPresContext.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSubDocumentFrame.h"
+#include "nsXULElement.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsGenericHTMLFrameElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsGenericHTMLFrameElement,
+ nsGenericHTMLElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameLoader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpenerWindow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserElementAPI)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserElementAudioChannels)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsGenericHTMLFrameElement,
+ nsGenericHTMLElement)
+ if (tmp->mFrameLoader) {
+ tmp->mFrameLoader->Destroy();
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameLoader)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOpenerWindow)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowserElementAPI)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowserElementAudioChannels)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(nsGenericHTMLFrameElement, nsGenericHTMLElement)
+NS_IMPL_RELEASE_INHERITED(nsGenericHTMLFrameElement, nsGenericHTMLElement)
+
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsGenericHTMLFrameElement)
+ NS_INTERFACE_TABLE_INHERITED(nsGenericHTMLFrameElement,
+ nsIFrameLoaderOwner,
+ nsIDOMMozBrowserFrame,
+ nsIMozBrowserFrame)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLElement)
+NS_IMPL_BOOL_ATTR(nsGenericHTMLFrameElement, Mozbrowser, mozbrowser)
+
+int32_t
+nsGenericHTMLFrameElement::TabIndexDefault()
+{
+ return 0;
+}
+
+nsGenericHTMLFrameElement::~nsGenericHTMLFrameElement()
+{
+ if (mFrameLoader) {
+ mFrameLoader->Destroy();
+ }
+}
+
+nsresult
+nsGenericHTMLFrameElement::GetContentDocument(nsIDOMDocument** aContentDocument)
+{
+ NS_PRECONDITION(aContentDocument, "Null out param");
+ nsCOMPtr<nsIDOMDocument> document =
+ do_QueryInterface(GetContentDocument(*nsContentUtils::SubjectPrincipal()));
+ document.forget(aContentDocument);
+ return NS_OK;
+}
+
+nsIDocument*
+nsGenericHTMLFrameElement::GetContentDocument(nsIPrincipal& aSubjectPrincipal)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetContentWindow();
+ if (!win) {
+ return nullptr;
+ }
+
+ nsIDocument *doc = win->GetDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ // Return null for cross-origin contentDocument.
+ if (!aSubjectPrincipal.SubsumesConsideringDomain(doc->NodePrincipal())) {
+ return nullptr;
+ }
+ return doc;
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGenericHTMLFrameElement::GetContentWindow()
+{
+ EnsureFrameLoader();
+
+ if (!mFrameLoader) {
+ return nullptr;
+ }
+
+ bool depthTooGreat = false;
+ mFrameLoader->GetDepthTooGreat(&depthTooGreat);
+ if (depthTooGreat) {
+ // Claim to have no contentWindow
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> doc_shell;
+ mFrameLoader->GetDocShell(getter_AddRefs(doc_shell));
+ if (!doc_shell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = doc_shell->GetWindow();
+
+ if (!win) {
+ return nullptr;
+ }
+
+ NS_ASSERTION(win->IsOuterWindow(),
+ "Uh, this window should always be an outer window!");
+
+ return win.forget();
+}
+
+void
+nsGenericHTMLFrameElement::EnsureFrameLoader()
+{
+ if (!IsInComposedDoc() || mFrameLoader || mFrameLoaderCreationDisallowed) {
+ // If frame loader is there, we just keep it around, cached
+ return;
+ }
+
+ // Strangely enough, this method doesn't actually ensure that the
+ // frameloader exists. It's more of a best-effort kind of thing.
+ mFrameLoader = nsFrameLoader::Create(this,
+ nsPIDOMWindowOuter::From(mOpenerWindow),
+ mNetworkCreated);
+ if (mIsPrerendered) {
+ mFrameLoader->SetIsPrerendered();
+ }
+}
+
+nsresult
+nsGenericHTMLFrameElement::CreateRemoteFrameLoader(nsITabParent* aTabParent)
+{
+ MOZ_ASSERT(!mFrameLoader);
+ EnsureFrameLoader();
+ NS_ENSURE_STATE(mFrameLoader);
+ mFrameLoader->SetRemoteBrowser(aTabParent);
+
+ if (nsSubDocumentFrame* subdocFrame = do_QueryFrame(GetPrimaryFrame())) {
+ // The reflow for this element already happened while we were waiting
+ // for the iframe creation. Therefore the subdoc frame didn't have a
+ // frameloader when UpdatePositionAndSize was supposed to be called in
+ // ReflowFinished, and we need to do it properly now.
+ mFrameLoader->UpdatePositionAndSize(subdocFrame);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGenericHTMLFrameElement::GetFrameLoaderXPCOM(nsIFrameLoader **aFrameLoader)
+{
+ NS_IF_ADDREF(*aFrameLoader = mFrameLoader);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(already_AddRefed<nsFrameLoader>)
+nsGenericHTMLFrameElement::GetFrameLoader()
+{
+ RefPtr<nsFrameLoader> loader = mFrameLoader;
+ return loader.forget();
+}
+
+NS_IMETHODIMP
+nsGenericHTMLFrameElement::GetParentApplication(mozIApplication** aApplication)
+{
+ if (!aApplication) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aApplication = nullptr;
+
+ nsIPrincipal *principal = NodePrincipal();
+ uint32_t appId = principal->GetAppId();
+
+ nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+ if (NS_WARN_IF(!appsService)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = appsService->GetAppByLocalId(appId, aApplication);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+nsGenericHTMLFrameElement::PresetOpenerWindow(mozIDOMWindowProxy* aWindow, ErrorResult& aRv)
+{
+ MOZ_ASSERT(!mFrameLoader);
+ mOpenerWindow = nsPIDOMWindowOuter::From(aWindow);
+}
+
+void
+nsGenericHTMLFrameElement::InternalSetFrameLoader(nsIFrameLoader* aNewFrameLoader)
+{
+ mFrameLoader = static_cast<nsFrameLoader*>(aNewFrameLoader);
+}
+
+void
+nsGenericHTMLFrameElement::SwapFrameLoaders(HTMLIFrameElement& aOtherLoaderOwner,
+ ErrorResult& rv)
+{
+ if (&aOtherLoaderOwner == this) {
+ // nothing to do
+ return;
+ }
+
+ aOtherLoaderOwner.SwapFrameLoaders(this, rv);
+}
+
+void
+nsGenericHTMLFrameElement::SwapFrameLoaders(nsXULElement& aOtherLoaderOwner,
+ ErrorResult& rv)
+{
+ aOtherLoaderOwner.SwapFrameLoaders(this, rv);
+}
+
+void
+nsGenericHTMLFrameElement::SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoaderOwner,
+ mozilla::ErrorResult& rv)
+{
+ RefPtr<nsFrameLoader> loader = GetFrameLoader();
+ RefPtr<nsFrameLoader> otherLoader = aOtherLoaderOwner->GetFrameLoader();
+ if (!loader || !otherLoader) {
+ rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return;
+ }
+
+ rv = loader->SwapWithOtherLoader(otherLoader, this, aOtherLoaderOwner);
+}
+
+NS_IMETHODIMP
+nsGenericHTMLFrameElement::SetIsPrerendered()
+{
+ MOZ_ASSERT(!mFrameLoader, "Please call SetIsPrerendered before frameLoader is created");
+ mIsPrerendered = true;
+ return NS_OK;
+}
+
+nsresult
+nsGenericHTMLFrameElement::LoadSrc()
+{
+ EnsureFrameLoader();
+
+ if (!mFrameLoader) {
+ return NS_OK;
+ }
+
+ nsresult rv = mFrameLoader->LoadFrame();
+#ifdef DEBUG
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to load URL");
+ }
+#endif
+
+ return rv;
+}
+
+nsresult
+nsGenericHTMLFrameElement::BindToTree(nsIDocument* aDocument,
+ nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (IsInComposedDoc()) {
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "Missing a script blocker!");
+
+ PROFILER_LABEL("nsGenericHTMLFrameElement", "BindToTree",
+ js::ProfileEntry::Category::OTHER);
+
+ // We're in a document now. Kick off the frame load.
+ LoadSrc();
+ }
+
+ // We're now in document and scripts may move us, so clear
+ // the mNetworkCreated flag.
+ mNetworkCreated = false;
+ return rv;
+}
+
+void
+nsGenericHTMLFrameElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ if (mFrameLoader) {
+ // This iframe is being taken out of the document, destroy the
+ // iframe's frame loader (doing that will tear down the window in
+ // this iframe).
+ // XXXbz we really want to only partially destroy the frame
+ // loader... we don't want to tear down the docshell. Food for
+ // later bug.
+ mFrameLoader->Destroy();
+ mFrameLoader = nullptr;
+ }
+
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+nsresult
+nsGenericHTMLFrameElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify)
+{
+ nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
+ aValue, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::src &&
+ (!IsHTMLElement(nsGkAtoms::iframe) ||
+ !HasAttr(kNameSpaceID_None,nsGkAtoms::srcdoc))) {
+ // Don't propagate error here. The attribute was successfully set, that's
+ // what we should reflect.
+ LoadSrc();
+ } else if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::name) {
+ // Propagate "name" to the docshell to make browsing context names live,
+ // per HTML5.
+ nsIDocShell *docShell = mFrameLoader ? mFrameLoader->GetExistingDocShell()
+ : nullptr;
+ if (docShell) {
+ docShell->SetName(aValue);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsGenericHTMLFrameElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify)
+{
+ // Invoke on the superclass.
+ nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttribute, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::name) {
+ // Propagate "name" to the docshell to make browsing context names live,
+ // per HTML5.
+ nsIDocShell *docShell = mFrameLoader ? mFrameLoader->GetExistingDocShell()
+ : nullptr;
+ if (docShell) {
+ docShell->SetName(EmptyString());
+ }
+ }
+
+ return NS_OK;
+}
+
+/* static */ int32_t
+nsGenericHTMLFrameElement::MapScrollingAttribute(const nsAttrValue* aValue)
+{
+ int32_t mappedValue = nsIScrollable::Scrollbar_Auto;
+ if (aValue && aValue->Type() == nsAttrValue::eEnum) {
+ switch (aValue->GetEnumValue()) {
+ case NS_STYLE_FRAME_OFF:
+ case NS_STYLE_FRAME_NOSCROLL:
+ case NS_STYLE_FRAME_NO:
+ mappedValue = nsIScrollable::Scrollbar_Never;
+ break;
+ }
+ }
+ return mappedValue;
+}
+
+/* virtual */ nsresult
+nsGenericHTMLFrameElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue,
+ bool aNotify)
+{
+ if (aName == nsGkAtoms::scrolling && aNameSpaceID == kNameSpaceID_None) {
+ if (mFrameLoader) {
+ nsIDocShell* docshell = mFrameLoader->GetExistingDocShell();
+ nsCOMPtr<nsIScrollable> scrollable = do_QueryInterface(docshell);
+ if (scrollable) {
+ int32_t cur;
+ scrollable->GetDefaultScrollbarPreferences(nsIScrollable::ScrollOrientation_X, &cur);
+ int32_t val = MapScrollingAttribute(aValue);
+ if (cur != val) {
+ scrollable->SetDefaultScrollbarPreferences(nsIScrollable::ScrollOrientation_X, val);
+ scrollable->SetDefaultScrollbarPreferences(nsIScrollable::ScrollOrientation_Y, val);
+ RefPtr<nsPresContext> presContext;
+ docshell->GetPresContext(getter_AddRefs(presContext));
+ nsIPresShell* shell = presContext ? presContext->GetPresShell() : nullptr;
+ nsIFrame* rootScroll = shell ? shell->GetRootScrollFrame() : nullptr;
+ if (rootScroll) {
+ shell->FrameNeedsReflow(rootScroll, nsIPresShell::eStyleChange,
+ NS_FRAME_IS_DIRTY);
+ }
+ }
+ }
+ }
+ }
+
+ return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
+ aNotify);
+}
+
+void
+nsGenericHTMLFrameElement::DestroyContent()
+{
+ if (mFrameLoader) {
+ mFrameLoader->Destroy();
+ mFrameLoader = nullptr;
+ }
+
+ nsGenericHTMLElement::DestroyContent();
+}
+
+nsresult
+nsGenericHTMLFrameElement::CopyInnerTo(Element* aDest)
+{
+ nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIDocument* doc = aDest->OwnerDoc();
+ if (doc->IsStaticDocument() && mFrameLoader) {
+ nsGenericHTMLFrameElement* dest =
+ static_cast<nsGenericHTMLFrameElement*>(aDest);
+ nsFrameLoader* fl = nsFrameLoader::Create(dest, nullptr, false);
+ NS_ENSURE_STATE(fl);
+ dest->mFrameLoader = fl;
+ static_cast<nsFrameLoader*>(mFrameLoader.get())->CreateStaticClone(fl);
+ }
+
+ return rv;
+}
+
+bool
+nsGenericHTMLFrameElement::IsHTMLFocusable(bool aWithMouse,
+ bool *aIsFocusable,
+ int32_t *aTabIndex)
+{
+ if (nsGenericHTMLElement::IsHTMLFocusable(aWithMouse, aIsFocusable, aTabIndex)) {
+ return true;
+ }
+
+ *aIsFocusable = nsContentUtils::IsSubDocumentTabbable(this);
+
+ if (!*aIsFocusable && aTabIndex) {
+ *aTabIndex = -1;
+ }
+
+ return false;
+}
+
+bool
+nsGenericHTMLFrameElement::BrowserFramesEnabled()
+{
+ static bool sMozBrowserFramesEnabled = false;
+ static bool sBoolVarCacheInitialized = false;
+
+ if (!sBoolVarCacheInitialized) {
+ sBoolVarCacheInitialized = true;
+ Preferences::AddBoolVarCache(&sMozBrowserFramesEnabled,
+ "dom.mozBrowserFramesEnabled");
+ }
+
+ return sMozBrowserFramesEnabled;
+}
+
+/**
+ * Return true if this frame element really is a mozbrowser or mozapp. (It
+ * needs to have the right attributes, and its creator must have the right
+ * permissions.)
+ */
+/* [infallible] */ nsresult
+nsGenericHTMLFrameElement::GetReallyIsBrowserOrApp(bool *aOut)
+{
+ *aOut = false;
+
+ // Fail if browser frames are globally disabled.
+ if (!nsGenericHTMLFrameElement::BrowserFramesEnabled()) {
+ return NS_OK;
+ }
+
+ // Fail if this frame doesn't have the mozbrowser attribute.
+ if (!GetBoolAttr(nsGkAtoms::mozbrowser)) {
+ return NS_OK;
+ }
+
+ // Fail if the node principal isn't trusted.
+ nsIPrincipal *principal = NodePrincipal();
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ services::GetPermissionManager();
+ NS_ENSURE_TRUE(permMgr, NS_OK);
+
+ uint32_t permission = nsIPermissionManager::DENY_ACTION;
+ nsresult rv = permMgr->TestPermissionFromPrincipal(principal, "browser", &permission);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ *aOut = permission == nsIPermissionManager::ALLOW_ACTION;
+ return NS_OK;
+}
+
+/* [infallible] */ NS_IMETHODIMP
+nsGenericHTMLFrameElement::GetReallyIsApp(bool *aOut)
+{
+ nsAutoString manifestURL;
+ GetAppManifestURL(manifestURL);
+
+ *aOut = !manifestURL.IsEmpty();
+ return NS_OK;
+}
+
+namespace {
+
+bool NestedEnabled()
+{
+ static bool sMozNestedEnabled = false;
+ static bool sBoolVarCacheInitialized = false;
+
+ if (!sBoolVarCacheInitialized) {
+ sBoolVarCacheInitialized = true;
+ Preferences::AddBoolVarCache(&sMozNestedEnabled,
+ "dom.ipc.tabs.nested.enabled");
+ }
+
+ return sMozNestedEnabled;
+}
+
+} // namespace
+
+/* [infallible] */ NS_IMETHODIMP
+nsGenericHTMLFrameElement::GetIsolated(bool *aOut)
+{
+ *aOut = true;
+
+ if (!nsContentUtils::IsSystemPrincipal(NodePrincipal())) {
+ return NS_OK;
+ }
+
+ // Isolation is only disabled if the attribute is present
+ *aOut = !HasAttr(kNameSpaceID_None, nsGkAtoms::noisolation);
+ return NS_OK;
+}
+
+/*
+ * Get manifest url of app.
+ */
+void nsGenericHTMLFrameElement::GetManifestURL(nsAString& aManifestURL)
+{
+ aManifestURL.Truncate();
+
+ nsAutoString manifestURL;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::mozapp, manifestURL);
+ if (manifestURL.IsEmpty()) {
+ return;
+ }
+
+ // Check permission.
+ nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+ NS_ENSURE_TRUE_VOID(permMgr);
+ nsIPrincipal *principal = NodePrincipal();
+ const char* aPermissionType = "embed-apps";
+ uint32_t permission = nsIPermissionManager::DENY_ACTION;
+ nsresult rv = permMgr->TestPermissionFromPrincipal(principal,
+ aPermissionType,
+ &permission);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ if (permission != nsIPermissionManager::ALLOW_ACTION) {
+ return;
+ }
+
+ nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE_VOID(appsService);
+
+ nsCOMPtr<mozIApplication> app;
+ appsService->GetAppByManifestURL(manifestURL, getter_AddRefs(app));
+
+ if (!app) {
+ return;
+ }
+
+ aManifestURL.Assign(manifestURL);
+}
+
+NS_IMETHODIMP
+nsGenericHTMLFrameElement::GetAppManifestURL(nsAString& aOut)
+{
+ aOut.Truncate();
+
+ // At the moment, you can't be an app without being a browser.
+ if (!nsIMozBrowserFrame::GetReallyIsBrowserOrApp()) {
+ return NS_OK;
+ }
+
+ // Only allow content process to embed an app when nested content
+ // process is enabled.
+ if (!XRE_IsParentProcess() &&
+ !(GetBoolAttr(nsGkAtoms::Remote) && NestedEnabled())){
+ NS_WARNING("Can't embed-apps. Embed-apps is restricted to in-proc apps "
+ "or content processes with nested pref enabled, see bug 1097479");
+ return NS_OK;
+ }
+
+ nsAutoString appManifestURL;
+
+ GetManifestURL(appManifestURL);
+
+ bool isApp = !appManifestURL.IsEmpty();
+
+ if (!isApp) {
+ // No valid case to get manifest
+ return NS_OK;
+ }
+
+ if (isApp) {
+ NS_WARNING("Can not simultaneously be mozapp");
+ return NS_OK;
+ }
+
+ nsAutoString manifestURL;
+ if (isApp) {
+ manifestURL.Assign(appManifestURL);
+ }
+
+ aOut.Assign(manifestURL);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGenericHTMLFrameElement::DisallowCreateFrameLoader()
+{
+ MOZ_ASSERT(!mFrameLoader);
+ MOZ_ASSERT(!mFrameLoaderCreationDisallowed);
+ mFrameLoaderCreationDisallowed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGenericHTMLFrameElement::AllowCreateFrameLoader()
+{
+ MOZ_ASSERT(!mFrameLoader);
+ MOZ_ASSERT(mFrameLoaderCreationDisallowed);
+ mFrameLoaderCreationDisallowed = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGenericHTMLFrameElement::InitializeBrowserAPI()
+{
+ MOZ_ASSERT(mFrameLoader);
+ InitBrowserElementAPI();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGenericHTMLFrameElement::DestroyBrowserFrameScripts()
+{
+ MOZ_ASSERT(mFrameLoader);
+ DestroyBrowserElementFrameScripts();
+ return NS_OK;
+}
diff --git a/dom/html/nsGenericHTMLFrameElement.h b/dom/html/nsGenericHTMLFrameElement.h
new file mode 100644
index 000000000..d9c2df9d5
--- /dev/null
+++ b/dom/html/nsGenericHTMLFrameElement.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 nsGenericHTMLFrameElement_h
+#define nsGenericHTMLFrameElement_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/nsBrowserElement.h"
+
+#include "nsFrameLoader.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIDOMEventListener.h"
+#include "nsIFrameLoader.h"
+#include "nsIMozBrowserFrame.h"
+
+class nsXULElement;
+
+/**
+ * A helper class for frame elements
+ */
+class nsGenericHTMLFrameElement : public nsGenericHTMLElement,
+ public nsIFrameLoaderOwner,
+ public mozilla::nsBrowserElement,
+ public nsIMozBrowserFrame
+{
+public:
+ nsGenericHTMLFrameElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ mozilla::dom::FromParser aFromParser)
+ : nsGenericHTMLElement(aNodeInfo)
+ , nsBrowserElement()
+ , mNetworkCreated(aFromParser == mozilla::dom::FROM_PARSER_NETWORK)
+ , mIsPrerendered(false)
+ , mBrowserFrameListenersRegistered(false)
+ , mFrameLoaderCreationDisallowed(false)
+ {
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSIFRAMELOADEROWNER
+ NS_DECL_NSIDOMMOZBROWSERFRAME
+ NS_DECL_NSIMOZBROWSERFRAME
+
+ // nsIContent
+ virtual bool IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable, int32_t *aTabIndex) override;
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) 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* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify) override;
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify) override;
+ virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue,
+ bool aNotify) override;
+ virtual void DestroyContent() override;
+
+ nsresult CopyInnerTo(mozilla::dom::Element* aDest);
+
+ virtual int32_t TabIndexDefault() override;
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsGenericHTMLFrameElement,
+ nsGenericHTMLElement)
+
+ void SwapFrameLoaders(mozilla::dom::HTMLIFrameElement& aOtherLoaderOwner,
+ mozilla::ErrorResult& aError);
+
+ void SwapFrameLoaders(nsXULElement& aOtherLoaderOwner,
+ mozilla::ErrorResult& aError);
+
+ void SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoaderOwner,
+ mozilla::ErrorResult& rv);
+
+ void PresetOpenerWindow(mozIDOMWindowProxy* aOpenerWindow,
+ mozilla::ErrorResult& aRv);
+
+ static bool BrowserFramesEnabled();
+
+ /**
+ * Helper method to map a HTML 'scrolling' attribute value to a nsIScrollable
+ * enum value. scrolling="no" (and its synonyms) maps to
+ * nsIScrollable::Scrollbar_Never, and anything else (including nullptr) maps
+ * to nsIScrollable::Scrollbar_Auto.
+ * @param aValue the attribute value to map or nullptr
+ * @return nsIScrollable::Scrollbar_Never or nsIScrollable::Scrollbar_Auto
+ */
+ static int32_t MapScrollingAttribute(const nsAttrValue* aValue);
+
+protected:
+ virtual ~nsGenericHTMLFrameElement();
+
+ // This doesn't really ensure a frame loader in all cases, only when
+ // it makes sense.
+ void EnsureFrameLoader();
+ nsresult LoadSrc();
+ nsIDocument* GetContentDocument(nsIPrincipal& aSubjectPrincipal);
+ nsresult GetContentDocument(nsIDOMDocument** aContentDocument);
+ already_AddRefed<nsPIDOMWindowOuter> GetContentWindow();
+
+ RefPtr<nsFrameLoader> mFrameLoader;
+ nsCOMPtr<nsPIDOMWindowOuter> mOpenerWindow;
+
+ /**
+ * True when the element is created by the parser using the
+ * NS_FROM_PARSER_NETWORK flag.
+ * If the element is modified, it may lose the flag.
+ */
+ bool mNetworkCreated;
+
+ bool mIsPrerendered;
+ bool mBrowserFrameListenersRegistered;
+ bool mFrameLoaderCreationDisallowed;
+
+ // This flag is only used by <iframe>. See HTMLIFrameElement::
+ // FullscreenFlag() for details. It is placed here so that we
+ // do not bloat any struct.
+ bool mFullscreenFlag = false;
+
+private:
+ void GetManifestURL(nsAString& aOut);
+};
+
+#endif // nsGenericHTMLFrameElement_h
diff --git a/dom/html/nsHTMLContentSink.cpp b/dom/html/nsHTMLContentSink.cpp
new file mode 100644
index 000000000..3e8e019b8
--- /dev/null
+++ b/dom/html/nsHTMLContentSink.cpp
@@ -0,0 +1,1106 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 is near-OBSOLETE. It is used for about:blank only and for the
+ * HTML element factory.
+ * Don't bother adding new stuff in this file.
+ */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsContentSink.h"
+#include "nsCOMPtr.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsIHTMLContentSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsScriptLoader.h"
+#include "nsIURI.h"
+#include "nsIContentViewer.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsToken.h"
+#include "nsIAppShell.h"
+#include "nsCRT.h"
+#include "prtime.h"
+#include "mozilla/Logging.h"
+#include "nsNodeUtils.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Preferences.h"
+
+#include "nsGenericHTMLElement.h"
+
+#include "nsIDOMDocument.h"
+#include "nsIDOMDocumentType.h"
+#include "nsIScriptElement.h"
+
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.h"
+#include "nsStubDocumentObserver.h"
+#include "nsIHTMLDocument.h"
+#include "nsIDOMHTMLMapElement.h"
+#include "nsICookieService.h"
+#include "nsTArray.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIPrincipal.h"
+#include "nsTextFragment.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsNameSpaceManager.h"
+
+#include "nsIParserService.h"
+
+#include "nsIStyleSheetLinkingElement.h"
+#include "nsITimer.h"
+#include "nsError.h"
+#include "nsContentPolicyUtils.h"
+#include "nsIScriptContext.h"
+#include "nsStyleLinkElement.h"
+
+#include "nsWeakReference.h" // nsHTMLElementFactory supports weak references
+#include "nsIPrompt.h"
+#include "nsLayoutCID.h"
+#include "nsIDocShellTreeItem.h"
+
+#include "nsEscape.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentCreatorFunctions.h"
+#include "mozAutoDocUpdate.h"
+#include "nsTextNode.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+//----------------------------------------------------------------------
+
+typedef nsGenericHTMLElement*
+ (*contentCreatorCallback)(already_AddRefed<mozilla::dom::NodeInfo>&&,
+ FromParser aFromParser);
+
+nsGenericHTMLElement*
+NS_NewHTMLNOTUSEDElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ FromParser aFromParser)
+{
+ NS_NOTREACHED("The element ctor should never be called");
+ return nullptr;
+}
+
+#define HTML_TAG(_tag, _classname) NS_NewHTML##_classname##Element,
+#define HTML_HTMLELEMENT_TAG(_tag) NS_NewHTMLElement,
+#define HTML_OTHER(_tag) NS_NewHTMLNOTUSEDElement,
+static const contentCreatorCallback sContentCreatorCallbacks[] = {
+ NS_NewHTMLUnknownElement,
+#include "nsHTMLTagList.h"
+#undef HTML_TAG
+#undef HTML_HTMLELEMENT_TAG
+#undef HTML_OTHER
+ NS_NewHTMLUnknownElement
+};
+
+class SinkContext;
+class HTMLContentSink;
+
+/**
+ * This class is near-OBSOLETE. It is used for about:blank only.
+ * Don't bother adding new stuff in this file.
+ */
+class HTMLContentSink : public nsContentSink,
+ public nsIHTMLContentSink
+{
+public:
+ friend class SinkContext;
+
+ HTMLContentSink();
+
+ NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
+
+ nsresult Init(nsIDocument* aDoc, nsIURI* aURI, nsISupports* aContainer,
+ nsIChannel* aChannel);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLContentSink, nsContentSink)
+
+ // nsIContentSink
+ NS_IMETHOD WillParse(void) override;
+ NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode) override;
+ NS_IMETHOD DidBuildModel(bool aTerminated) override;
+ NS_IMETHOD WillInterrupt(void) override;
+ NS_IMETHOD WillResume(void) override;
+ NS_IMETHOD SetParser(nsParserBase* aParser) override;
+ virtual void FlushPendingNotifications(mozFlushType aType) override;
+ NS_IMETHOD SetDocumentCharset(nsACString& aCharset) override;
+ virtual nsISupports *GetTarget() override;
+ virtual bool IsScriptExecuting() override;
+
+ // nsIHTMLContentSink
+ NS_IMETHOD OpenContainer(ElementType aNodeType) override;
+ NS_IMETHOD CloseContainer(ElementType aTag) override;
+
+protected:
+ virtual ~HTMLContentSink();
+
+ nsCOMPtr<nsIHTMLDocument> mHTMLDocument;
+
+ // The maximum length of a text run
+ int32_t mMaxTextRun;
+
+ RefPtr<nsGenericHTMLElement> mRoot;
+ RefPtr<nsGenericHTMLElement> mBody;
+ RefPtr<nsGenericHTMLElement> mHead;
+
+ AutoTArray<SinkContext*, 8> mContextStack;
+ SinkContext* mCurrentContext;
+ SinkContext* mHeadContext;
+
+ // Boolean indicating whether we've seen a <head> tag that might have had
+ // attributes once already.
+ bool mHaveSeenHead;
+
+ // Boolean indicating whether we've notified insertion of our root content
+ // yet. We want to make sure to only do this once.
+ bool mNotifiedRootInsertion;
+
+ nsresult FlushTags() override;
+
+ // Routines for tags that require special handling
+ nsresult CloseHTML();
+ nsresult OpenBody();
+ nsresult CloseBody();
+
+ void CloseHeadContext();
+
+ // nsContentSink overrides
+ void UpdateChildCounts() override;
+
+ void NotifyInsert(nsIContent* aContent,
+ nsIContent* aChildContent,
+ int32_t aIndexInContainer);
+ void NotifyRootInsertion();
+};
+
+class SinkContext
+{
+public:
+ explicit SinkContext(HTMLContentSink* aSink);
+ ~SinkContext();
+
+ nsresult Begin(nsHTMLTag aNodeType, nsGenericHTMLElement* aRoot,
+ uint32_t aNumFlushed, int32_t aInsertionPoint);
+ nsresult OpenBody();
+ nsresult CloseBody();
+ nsresult End();
+
+ nsresult GrowStack();
+ nsresult FlushTags();
+
+ bool IsCurrentContainer(nsHTMLTag mType);
+
+ void DidAddContent(nsIContent* aContent);
+ void UpdateChildCounts();
+
+private:
+ // Function to check whether we've notified for the current content.
+ // What this actually does is check whether we've notified for all
+ // of the parent's kids.
+ bool HaveNotifiedForCurrentContent() const;
+
+public:
+ HTMLContentSink* mSink;
+ int32_t mNotifyLevel;
+
+ struct Node {
+ nsHTMLTag mType;
+ nsGenericHTMLElement* mContent;
+ uint32_t mNumFlushed;
+ int32_t mInsertionPoint;
+
+ nsIContent *Add(nsIContent *child);
+ };
+
+ Node* mStack;
+ int32_t mStackSize;
+ int32_t mStackPos;
+};
+
+nsresult
+NS_NewHTMLElement(Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ FromParser aFromParser, const nsAString* aIs)
+{
+ *aResult = nullptr;
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo;
+
+ nsIParserService* parserService = nsContentUtils::GetParserService();
+ if (!parserService)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsIAtom *name = nodeInfo->NameAtom();
+
+ NS_ASSERTION(nodeInfo->NamespaceEquals(kNameSpaceID_XHTML),
+ "Trying to HTML elements that don't have the XHTML namespace");
+
+ int32_t tag = parserService->HTMLCaseSensitiveAtomTagToId(name);
+
+ // Per the Custom Element specification, unknown tags that are valid custom
+ // element names should be HTMLElement instead of HTMLUnknownElement.
+ bool isCustomElementName = (tag == eHTMLTag_userdefined &&
+ nsContentUtils::IsCustomElementName(name));
+ if (isCustomElementName) {
+ NS_IF_ADDREF(*aResult = NS_NewHTMLElement(nodeInfo.forget(), aFromParser));
+ } else {
+ *aResult = CreateHTMLElement(tag, nodeInfo.forget(), aFromParser).take();
+ }
+
+ if (!*aResult) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (isCustomElementName || aIs) {
+ nsContentUtils::SetupCustomElement(*aResult, aIs);
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsGenericHTMLElement>
+CreateHTMLElement(uint32_t aNodeType,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ FromParser aFromParser)
+{
+ NS_ASSERTION(aNodeType <= NS_HTML_TAG_MAX ||
+ aNodeType == eHTMLTag_userdefined,
+ "aNodeType is out of bounds");
+
+ contentCreatorCallback cb = sContentCreatorCallbacks[aNodeType];
+
+ NS_ASSERTION(cb != NS_NewHTMLNOTUSEDElement,
+ "Don't know how to construct tag element!");
+
+ RefPtr<nsGenericHTMLElement> result = cb(Move(aNodeInfo), aFromParser);
+
+ return result.forget();
+}
+
+//----------------------------------------------------------------------
+
+SinkContext::SinkContext(HTMLContentSink* aSink)
+ : mSink(aSink),
+ mNotifyLevel(0),
+ mStack(nullptr),
+ mStackSize(0),
+ mStackPos(0)
+{
+ MOZ_COUNT_CTOR(SinkContext);
+}
+
+SinkContext::~SinkContext()
+{
+ MOZ_COUNT_DTOR(SinkContext);
+
+ if (mStack) {
+ for (int32_t i = 0; i < mStackPos; i++) {
+ NS_RELEASE(mStack[i].mContent);
+ }
+ delete [] mStack;
+ }
+}
+
+nsresult
+SinkContext::Begin(nsHTMLTag aNodeType,
+ nsGenericHTMLElement* aRoot,
+ uint32_t aNumFlushed,
+ int32_t aInsertionPoint)
+{
+ if (mStackSize < 1) {
+ nsresult rv = GrowStack();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ mStack[0].mType = aNodeType;
+ mStack[0].mContent = aRoot;
+ mStack[0].mNumFlushed = aNumFlushed;
+ mStack[0].mInsertionPoint = aInsertionPoint;
+ NS_ADDREF(aRoot);
+ mStackPos = 1;
+
+ return NS_OK;
+}
+
+bool
+SinkContext::IsCurrentContainer(nsHTMLTag aTag)
+{
+ if (aTag == mStack[mStackPos - 1].mType) {
+ return true;
+ }
+
+ return false;
+}
+
+void
+SinkContext::DidAddContent(nsIContent* aContent)
+{
+ if ((mStackPos == 2) && (mSink->mBody == mStack[1].mContent)) {
+ // We just finished adding something to the body
+ mNotifyLevel = 0;
+ }
+
+ // If we just added content to a node for which
+ // an insertion happen, we need to do an immediate
+ // notification for that insertion.
+ if (0 < mStackPos &&
+ mStack[mStackPos - 1].mInsertionPoint != -1 &&
+ mStack[mStackPos - 1].mNumFlushed <
+ mStack[mStackPos - 1].mContent->GetChildCount()) {
+ nsIContent* parent = mStack[mStackPos - 1].mContent;
+ int32_t childIndex = mStack[mStackPos - 1].mInsertionPoint - 1;
+ NS_ASSERTION(parent->GetChildAt(childIndex) == aContent,
+ "Flushing the wrong child.");
+ mSink->NotifyInsert(parent, aContent, childIndex);
+ mStack[mStackPos - 1].mNumFlushed = parent->GetChildCount();
+ } else if (mSink->IsTimeToNotify()) {
+ FlushTags();
+ }
+}
+
+nsresult
+SinkContext::OpenBody()
+{
+ if (mStackPos <= 0) {
+ NS_ERROR("container w/o parent");
+
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ if (mStackPos + 1 > mStackSize) {
+ rv = GrowStack();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo =
+ mSink->mNodeInfoManager->GetNodeInfo(nsGkAtoms::body, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+ NS_ENSURE_TRUE(nodeInfo, NS_ERROR_UNEXPECTED);
+
+ // Make the content object
+ RefPtr<nsGenericHTMLElement> body =
+ NS_NewHTMLBodyElement(nodeInfo.forget(), FROM_PARSER_NETWORK);
+ if (!body) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mStack[mStackPos].mType = eHTMLTag_body;
+ body.forget(&mStack[mStackPos].mContent);
+ mStack[mStackPos].mNumFlushed = 0;
+ mStack[mStackPos].mInsertionPoint = -1;
+ ++mStackPos;
+ mStack[mStackPos - 2].Add(mStack[mStackPos - 1].mContent);
+
+ return NS_OK;
+}
+
+bool
+SinkContext::HaveNotifiedForCurrentContent() const
+{
+ if (0 < mStackPos) {
+ nsIContent* parent = mStack[mStackPos - 1].mContent;
+ return mStack[mStackPos-1].mNumFlushed == parent->GetChildCount();
+ }
+
+ return true;
+}
+
+nsIContent *
+SinkContext::Node::Add(nsIContent *child)
+{
+ NS_ASSERTION(mContent, "No parent to insert/append into!");
+ if (mInsertionPoint != -1) {
+ NS_ASSERTION(mNumFlushed == mContent->GetChildCount(),
+ "Inserting multiple children without flushing.");
+ mContent->InsertChildAt(child, mInsertionPoint++, false);
+ } else {
+ mContent->AppendChildTo(child, false);
+ }
+ return child;
+}
+
+nsresult
+SinkContext::CloseBody()
+{
+ NS_ASSERTION(mStackPos > 0,
+ "stack out of bounds. wrong context probably!");
+
+ if (mStackPos <= 0) {
+ return NS_OK; // Fix crash - Ref. bug 45975 or 45007
+ }
+
+ --mStackPos;
+ NS_ASSERTION(mStack[mStackPos].mType == eHTMLTag_body,
+ "Tag mismatch. Closing tag on wrong context or something?");
+
+ nsGenericHTMLElement* content = mStack[mStackPos].mContent;
+
+ content->Compact();
+
+ // If we're in a state where we do append notifications as
+ // we go up the tree, and we're at the level where the next
+ // notification needs to be done, do the notification.
+ if (mNotifyLevel >= mStackPos) {
+ // Check to see if new content has been added after our last
+ // notification
+
+ if (mStack[mStackPos].mNumFlushed < content->GetChildCount()) {
+ mSink->NotifyAppend(content, mStack[mStackPos].mNumFlushed);
+ mStack[mStackPos].mNumFlushed = content->GetChildCount();
+ }
+
+ // Indicate that notification has now happened at this level
+ mNotifyLevel = mStackPos - 1;
+ }
+
+ DidAddContent(content);
+ NS_IF_RELEASE(content);
+
+ return NS_OK;
+}
+
+nsresult
+SinkContext::End()
+{
+ for (int32_t i = 0; i < mStackPos; i++) {
+ NS_RELEASE(mStack[i].mContent);
+ }
+
+ mStackPos = 0;
+
+ return NS_OK;
+}
+
+nsresult
+SinkContext::GrowStack()
+{
+ int32_t newSize = mStackSize * 2;
+ if (newSize == 0) {
+ newSize = 32;
+ }
+
+ Node* stack = new Node[newSize];
+
+ if (mStackPos != 0) {
+ memcpy(stack, mStack, sizeof(Node) * mStackPos);
+ delete [] mStack;
+ }
+
+ mStack = stack;
+ mStackSize = newSize;
+
+ return NS_OK;
+}
+
+/**
+ * NOTE!! Forked into nsXMLContentSink. Please keep in sync.
+ *
+ * Flush all elements that have been seen so far such that
+ * they are visible in the tree. Specifically, make sure
+ * that they are all added to their respective parents.
+ * Also, do notification at the top for all content that
+ * has been newly added so that the frame tree is complete.
+ */
+nsresult
+SinkContext::FlushTags()
+{
+ mSink->mDeferredFlushTags = false;
+ bool oldBeganUpdate = mSink->mBeganUpdate;
+ uint32_t oldUpdates = mSink->mUpdatesInNotification;
+
+ ++(mSink->mInNotification);
+ mSink->mUpdatesInNotification = 0;
+ {
+ // Scope so we call EndUpdate before we decrease mInNotification
+ mozAutoDocUpdate updateBatch(mSink->mDocument, UPDATE_CONTENT_MODEL,
+ true);
+ mSink->mBeganUpdate = true;
+
+ // Start from the base of the stack (growing downward) and do
+ // a notification from the node that is closest to the root of
+ // tree for any content that has been added.
+
+ // Note that we can start at stackPos == 0 here, because it's the caller's
+ // responsibility to handle flushing interactions between contexts (see
+ // HTMLContentSink::BeginContext).
+ int32_t stackPos = 0;
+ bool flushed = false;
+ uint32_t childCount;
+ nsGenericHTMLElement* content;
+
+ while (stackPos < mStackPos) {
+ content = mStack[stackPos].mContent;
+ childCount = content->GetChildCount();
+
+ if (!flushed && (mStack[stackPos].mNumFlushed < childCount)) {
+ if (mStack[stackPos].mInsertionPoint != -1) {
+ // We might have popped the child off our stack already
+ // but not notified on it yet, which is why we have to get it
+ // directly from its parent node.
+
+ int32_t childIndex = mStack[stackPos].mInsertionPoint - 1;
+ nsIContent* child = content->GetChildAt(childIndex);
+ // Child not on stack anymore; can't assert it's correct
+ NS_ASSERTION(!(mStackPos > (stackPos + 1)) ||
+ (child == mStack[stackPos + 1].mContent),
+ "Flushing the wrong child.");
+ mSink->NotifyInsert(content, child, childIndex);
+ } else {
+ mSink->NotifyAppend(content, mStack[stackPos].mNumFlushed);
+ }
+
+ flushed = true;
+ }
+
+ mStack[stackPos].mNumFlushed = childCount;
+ stackPos++;
+ }
+ mNotifyLevel = mStackPos - 1;
+ }
+ --(mSink->mInNotification);
+
+ if (mSink->mUpdatesInNotification > 1) {
+ UpdateChildCounts();
+ }
+
+ mSink->mUpdatesInNotification = oldUpdates;
+ mSink->mBeganUpdate = oldBeganUpdate;
+
+ return NS_OK;
+}
+
+/**
+ * NOTE!! Forked into nsXMLContentSink. Please keep in sync.
+ */
+void
+SinkContext::UpdateChildCounts()
+{
+ // Start from the top of the stack (growing upwards) and see if any
+ // new content has been appended. If so, we recognize that reflows
+ // have been generated for it and we should make sure that no
+ // further reflows occur. Note that we have to include stackPos == 0
+ // to properly notify on kids of <html>.
+ int32_t stackPos = mStackPos - 1;
+ while (stackPos >= 0) {
+ Node & node = mStack[stackPos];
+ node.mNumFlushed = node.mContent->GetChildCount();
+
+ stackPos--;
+ }
+
+ mNotifyLevel = mStackPos - 1;
+}
+
+nsresult
+NS_NewHTMLContentSink(nsIHTMLContentSink** aResult,
+ nsIDocument* aDoc,
+ nsIURI* aURI,
+ nsISupports* aContainer,
+ nsIChannel* aChannel)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ RefPtr<HTMLContentSink> it = new HTMLContentSink();
+
+ nsresult rv = it->Init(aDoc, aURI, aContainer, aChannel);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = it;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+HTMLContentSink::HTMLContentSink()
+{
+ // Note: operator new zeros our memory
+}
+
+HTMLContentSink::~HTMLContentSink()
+{
+ if (mNotificationTimer) {
+ mNotificationTimer->Cancel();
+ }
+
+ int32_t numContexts = mContextStack.Length();
+
+ if (mCurrentContext == mHeadContext && numContexts > 0) {
+ // Pop off the second html context if it's not done earlier
+ mContextStack.RemoveElementAt(--numContexts);
+ }
+
+ int32_t i;
+ for (i = 0; i < numContexts; i++) {
+ SinkContext* sc = mContextStack.ElementAt(i);
+ if (sc) {
+ sc->End();
+ if (sc == mCurrentContext) {
+ mCurrentContext = nullptr;
+ }
+
+ delete sc;
+ }
+ }
+
+ if (mCurrentContext == mHeadContext) {
+ mCurrentContext = nullptr;
+ }
+
+ delete mCurrentContext;
+
+ delete mHeadContext;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLContentSink)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLContentSink, nsContentSink)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mHTMLDocument)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoot)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mBody)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mHead)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLContentSink,
+ nsContentSink)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHTMLDocument)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBody)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHead)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLContentSink)
+ NS_INTERFACE_TABLE_BEGIN
+ NS_INTERFACE_TABLE_ENTRY(HTMLContentSink, nsIContentSink)
+ NS_INTERFACE_TABLE_ENTRY(HTMLContentSink, nsIHTMLContentSink)
+ NS_INTERFACE_TABLE_END
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsContentSink)
+
+NS_IMPL_ADDREF_INHERITED(HTMLContentSink, nsContentSink)
+NS_IMPL_RELEASE_INHERITED(HTMLContentSink, nsContentSink)
+
+nsresult
+HTMLContentSink::Init(nsIDocument* aDoc,
+ nsIURI* aURI,
+ nsISupports* aContainer,
+ nsIChannel* aChannel)
+{
+ NS_ENSURE_TRUE(aContainer, NS_ERROR_NULL_POINTER);
+
+ nsresult rv = nsContentSink::Init(aDoc, aURI, aContainer, aChannel);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ aDoc->AddObserver(this);
+ mIsDocumentObserver = true;
+ mHTMLDocument = do_QueryInterface(aDoc);
+
+ NS_ASSERTION(mDocShell, "oops no docshell!");
+
+ // Changed from 8192 to greatly improve page loading performance on
+ // large pages. See bugzilla bug 77540.
+ mMaxTextRun = Preferences::GetInt("content.maxtextrun", 8191);
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::html, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+
+ // Make root part
+ mRoot = NS_NewHTMLHtmlElement(nodeInfo.forget());
+ if (!mRoot) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NS_ASSERTION(mDocument->GetChildCount() == 0,
+ "Document should have no kids here!");
+ rv = mDocument->AppendChildTo(mRoot, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make head part
+ nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::head,
+ nullptr, kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+
+ mHead = NS_NewHTMLHeadElement(nodeInfo.forget());
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mRoot->AppendChildTo(mHead, false);
+
+ mCurrentContext = new SinkContext(this);
+ mCurrentContext->Begin(eHTMLTag_html, mRoot, 0, -1);
+ mContextStack.AppendElement(mCurrentContext);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLContentSink::WillParse(void)
+{
+ return WillParseImpl();
+}
+
+NS_IMETHODIMP
+HTMLContentSink::WillBuildModel(nsDTDMode aDTDMode)
+{
+ WillBuildModelImpl();
+
+ if (mHTMLDocument) {
+ nsCompatibility mode = eCompatibility_NavQuirks;
+ switch (aDTDMode) {
+ case eDTDMode_full_standards:
+ mode = eCompatibility_FullStandards;
+ break;
+ case eDTDMode_almost_standards:
+ mode = eCompatibility_AlmostStandards;
+ break;
+ default:
+ break;
+ }
+ mHTMLDocument->SetCompatibilityMode(mode);
+ }
+
+ // Notify document that the load is beginning
+ mDocument->BeginLoad();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLContentSink::DidBuildModel(bool aTerminated)
+{
+ DidBuildModelImpl(aTerminated);
+
+ // Reflow the last batch of content
+ if (mBody) {
+ mCurrentContext->FlushTags();
+ } else if (!mLayoutStarted) {
+ // We never saw the body, and layout never got started. Force
+ // layout *now*, to get an initial reflow.
+ // NOTE: only force the layout if we are NOT destroying the
+ // docshell. If we are destroying it, then starting layout will
+ // likely cause us to crash, or at best waste a lot of time as we
+ // are just going to tear it down anyway.
+ bool bDestroying = true;
+ if (mDocShell) {
+ mDocShell->IsBeingDestroyed(&bDestroying);
+ }
+
+ if (!bDestroying) {
+ StartLayout(false);
+ }
+ }
+
+ ScrollToRef();
+
+ // Make sure we no longer respond to document mutations. We've flushed all
+ // our notifications out, so there's no need to do anything else here.
+
+ // XXXbz I wonder whether we could End() our contexts here too, or something,
+ // just to make sure we no longer notify... Or is the mIsDocumentObserver
+ // thing sufficient?
+ mDocument->RemoveObserver(this);
+ mIsDocumentObserver = false;
+
+ mDocument->EndLoad();
+
+ DropParserAndPerfHint();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLContentSink::SetParser(nsParserBase* aParser)
+{
+ NS_PRECONDITION(aParser, "Should have a parser here!");
+ mParser = aParser;
+ return NS_OK;
+}
+
+nsresult
+HTMLContentSink::CloseHTML()
+{
+ if (mHeadContext) {
+ if (mCurrentContext == mHeadContext) {
+ uint32_t numContexts = mContextStack.Length();
+
+ // Pop off the second html context if it's not done earlier
+ mCurrentContext = mContextStack.ElementAt(--numContexts);
+ mContextStack.RemoveElementAt(numContexts);
+ }
+
+ mHeadContext->End();
+
+ delete mHeadContext;
+ mHeadContext = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLContentSink::OpenBody()
+{
+ CloseHeadContext(); // do this just in case if the HEAD was left open!
+
+ // if we already have a body we're done
+ if (mBody) {
+ return NS_OK;
+ }
+
+ nsresult rv = mCurrentContext->OpenBody();
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mBody = mCurrentContext->mStack[mCurrentContext->mStackPos - 1].mContent;
+
+ if (mCurrentContext->mStackPos > 1) {
+ int32_t parentIndex = mCurrentContext->mStackPos - 2;
+ nsGenericHTMLElement *parent = mCurrentContext->mStack[parentIndex].mContent;
+ int32_t numFlushed = mCurrentContext->mStack[parentIndex].mNumFlushed;
+ int32_t childCount = parent->GetChildCount();
+ NS_ASSERTION(numFlushed < childCount, "Already notified on the body?");
+
+ int32_t insertionPoint =
+ mCurrentContext->mStack[parentIndex].mInsertionPoint;
+
+ // XXX: I have yet to see a case where numFlushed is non-zero and
+ // insertionPoint is not -1, but this code will try to handle
+ // those cases too.
+
+ uint32_t oldUpdates = mUpdatesInNotification;
+ mUpdatesInNotification = 0;
+ if (insertionPoint != -1) {
+ NotifyInsert(parent, mBody, insertionPoint - 1);
+ } else {
+ NotifyAppend(parent, numFlushed);
+ }
+ mCurrentContext->mStack[parentIndex].mNumFlushed = childCount;
+ if (mUpdatesInNotification > 1) {
+ UpdateChildCounts();
+ }
+ mUpdatesInNotification = oldUpdates;
+ }
+
+ StartLayout(false);
+
+ return NS_OK;
+}
+
+nsresult
+HTMLContentSink::CloseBody()
+{
+ // Flush out anything that's left
+ mCurrentContext->FlushTags();
+ mCurrentContext->CloseBody();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLContentSink::OpenContainer(ElementType aElementType)
+{
+ nsresult rv = NS_OK;
+
+ switch (aElementType) {
+ case eBody:
+ rv = OpenBody();
+ break;
+ case eHTML:
+ if (mRoot) {
+ // If we've already hit this code once, then we're done
+ if (!mNotifiedRootInsertion) {
+ NotifyRootInsertion();
+ }
+ ProcessOfflineManifest(mRoot);
+ }
+ break;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+HTMLContentSink::CloseContainer(const ElementType aTag)
+{
+ nsresult rv = NS_OK;
+
+ switch (aTag) {
+ case eBody:
+ rv = CloseBody();
+ break;
+ case eHTML:
+ rv = CloseHTML();
+ break;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+HTMLContentSink::WillInterrupt()
+{
+ return WillInterruptImpl();
+}
+
+NS_IMETHODIMP
+HTMLContentSink::WillResume()
+{
+ return WillResumeImpl();
+}
+
+void
+HTMLContentSink::CloseHeadContext()
+{
+ if (mCurrentContext) {
+ if (!mCurrentContext->IsCurrentContainer(eHTMLTag_head))
+ return;
+
+ mCurrentContext->FlushTags();
+ }
+
+ if (!mContextStack.IsEmpty())
+ {
+ uint32_t n = mContextStack.Length() - 1;
+ mCurrentContext = mContextStack.ElementAt(n);
+ mContextStack.RemoveElementAt(n);
+ }
+}
+
+void
+HTMLContentSink::NotifyInsert(nsIContent* aContent,
+ nsIContent* aChildContent,
+ int32_t aIndexInContainer)
+{
+ if (aContent && aContent->GetUncomposedDoc() != mDocument) {
+ // aContent is not actually in our document anymore.... Just bail out of
+ // here; notifying on our document for this insert would be wrong.
+ return;
+ }
+
+ mInNotification++;
+
+ {
+ // Scope so we call EndUpdate before we decrease mInNotification
+ MOZ_AUTO_DOC_UPDATE(mDocument, UPDATE_CONTENT_MODEL, !mBeganUpdate);
+ nsNodeUtils::ContentInserted(NODE_FROM(aContent, mDocument),
+ aChildContent, aIndexInContainer);
+ mLastNotificationTime = PR_Now();
+ }
+
+ mInNotification--;
+}
+
+void
+HTMLContentSink::NotifyRootInsertion()
+{
+ NS_PRECONDITION(!mNotifiedRootInsertion, "Double-notifying on root?");
+ NS_ASSERTION(!mLayoutStarted,
+ "How did we start layout without notifying on root?");
+ // Now make sure to notify that we have now inserted our root. If
+ // there has been no initial reflow yet it'll be a no-op, but if
+ // there has been one we need this to get its frames constructed.
+ // Note that if mNotifiedRootInsertion is true we don't notify here,
+ // since that just means there are multiple <html> tags in the
+ // document; in those cases we just want to put all the attrs on one
+ // tag.
+ mNotifiedRootInsertion = true;
+ int32_t index = mDocument->IndexOf(mRoot);
+ NS_ASSERTION(index != -1, "mRoot not child of document?");
+ NotifyInsert(nullptr, mRoot, index);
+
+ // Now update the notification information in all our
+ // contexts, since we just inserted the root and notified on
+ // our whole tree
+ UpdateChildCounts();
+}
+
+void
+HTMLContentSink::UpdateChildCounts()
+{
+ uint32_t numContexts = mContextStack.Length();
+ for (uint32_t i = 0; i < numContexts; i++) {
+ SinkContext* sc = mContextStack.ElementAt(i);
+
+ sc->UpdateChildCounts();
+ }
+
+ mCurrentContext->UpdateChildCounts();
+}
+
+void
+HTMLContentSink::FlushPendingNotifications(mozFlushType aType)
+{
+ // Only flush tags if we're not doing the notification ourselves
+ // (since we aren't reentrant)
+ if (!mInNotification) {
+ // Only flush if we're still a document observer (so that our child counts
+ // should be correct).
+ if (mIsDocumentObserver) {
+ if (aType >= Flush_ContentAndNotify) {
+ FlushTags();
+ }
+ }
+ if (aType >= Flush_InterruptibleLayout) {
+ // Make sure that layout has started so that the reflow flush
+ // will actually happen.
+ StartLayout(true);
+ }
+ }
+}
+
+nsresult
+HTMLContentSink::FlushTags()
+{
+ if (!mNotifiedRootInsertion) {
+ NotifyRootInsertion();
+ return NS_OK;
+ }
+
+ return mCurrentContext ? mCurrentContext->FlushTags() : NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLContentSink::SetDocumentCharset(nsACString& aCharset)
+{
+ MOZ_ASSERT_UNREACHABLE("<meta charset> case doesn't occur with about:blank");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsISupports *
+HTMLContentSink::GetTarget()
+{
+ return mDocument;
+}
+
+bool
+HTMLContentSink::IsScriptExecuting()
+{
+ return IsScriptExecutingImpl();
+}
diff --git a/dom/html/nsHTMLDNSPrefetch.cpp b/dom/html/nsHTMLDNSPrefetch.cpp
new file mode 100644
index 000000000..2ddc8476d
--- /dev/null
+++ b/dom/html/nsHTMLDNSPrefetch.cpp
@@ -0,0 +1,474 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/NeckoChild.h"
+#include "nsURLHelper.h"
+
+#include "nsHTMLDNSPrefetch.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsIProtocolHandler.h"
+
+#include "nsIDNSListener.h"
+#include "nsIWebProgressListener.h"
+#include "nsIWebProgress.h"
+#include "nsCURILoader.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsICancelable.h"
+#include "nsGkAtoms.h"
+#include "nsIDocument.h"
+#include "nsThreadUtils.h"
+#include "nsITimer.h"
+#include "nsIObserverService.h"
+#include "mozilla/dom/Link.h"
+
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::net;
+
+static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID);
+bool sDisablePrefetchHTTPSPref;
+static bool sInitialized = false;
+static nsIDNSService *sDNSService = nullptr;
+static nsHTMLDNSPrefetch::nsDeferrals *sPrefetches = nullptr;
+static nsHTMLDNSPrefetch::nsListener *sDNSListener = nullptr;
+
+nsresult
+nsHTMLDNSPrefetch::Initialize()
+{
+ if (sInitialized) {
+ NS_WARNING("Initialize() called twice");
+ return NS_OK;
+ }
+
+ sPrefetches = new nsHTMLDNSPrefetch::nsDeferrals();
+ NS_ADDREF(sPrefetches);
+
+ sDNSListener = new nsHTMLDNSPrefetch::nsListener();
+ NS_ADDREF(sDNSListener);
+
+ sPrefetches->Activate();
+
+ Preferences::AddBoolVarCache(&sDisablePrefetchHTTPSPref,
+ "network.dns.disablePrefetchFromHTTPS");
+
+ // Default is false, so we need an explicit call to prime the cache.
+ sDisablePrefetchHTTPSPref =
+ Preferences::GetBool("network.dns.disablePrefetchFromHTTPS", true);
+
+ NS_IF_RELEASE(sDNSService);
+ nsresult rv;
+ rv = CallGetService(kDNSServiceCID, &sDNSService);
+ if (NS_FAILED(rv)) return rv;
+
+ if (IsNeckoChild())
+ NeckoChild::InitNeckoChild();
+
+ sInitialized = true;
+ return NS_OK;
+}
+
+nsresult
+nsHTMLDNSPrefetch::Shutdown()
+{
+ if (!sInitialized) {
+ NS_WARNING("Not Initialized");
+ return NS_OK;
+ }
+ sInitialized = false;
+ NS_IF_RELEASE(sDNSService);
+ NS_IF_RELEASE(sPrefetches);
+ NS_IF_RELEASE(sDNSListener);
+
+ return NS_OK;
+}
+
+bool
+nsHTMLDNSPrefetch::IsAllowed (nsIDocument *aDocument)
+{
+ // There is no need to do prefetch on non UI scenarios such as XMLHttpRequest.
+ return aDocument->IsDNSPrefetchAllowed() && aDocument->GetWindow();
+}
+
+nsresult
+nsHTMLDNSPrefetch::Prefetch(Link *aElement, uint16_t flags)
+{
+ if (!(sInitialized && sPrefetches && sDNSService && sDNSListener))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return sPrefetches->Add(flags, aElement);
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchLow(Link *aElement)
+{
+ return Prefetch(aElement, nsIDNSService::RESOLVE_PRIORITY_LOW);
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchMedium(Link *aElement)
+{
+ return Prefetch(aElement, nsIDNSService::RESOLVE_PRIORITY_MEDIUM);
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchHigh(Link *aElement)
+{
+ return Prefetch(aElement, 0);
+}
+
+nsresult
+nsHTMLDNSPrefetch::Prefetch(const nsAString &hostname, uint16_t flags)
+{
+ if (IsNeckoChild()) {
+ // We need to check IsEmpty() because net_IsValidHostName()
+ // considers empty strings to be valid hostnames
+ if (!hostname.IsEmpty() &&
+ net_IsValidHostName(NS_ConvertUTF16toUTF8(hostname))) {
+ // during shutdown gNeckoChild might be null
+ if (gNeckoChild) {
+ gNeckoChild->SendHTMLDNSPrefetch(nsAutoString(hostname), flags);
+ }
+ }
+ return NS_OK;
+ }
+
+ if (!(sInitialized && sDNSService && sPrefetches && sDNSListener))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+ return sDNSService->AsyncResolve(NS_ConvertUTF16toUTF8(hostname),
+ flags | nsIDNSService::RESOLVE_SPECULATE,
+ sDNSListener, nullptr,
+ getter_AddRefs(tmpOutstanding));
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchLow(const nsAString &hostname)
+{
+ return Prefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_LOW);
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchMedium(const nsAString &hostname)
+{
+ return Prefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_MEDIUM);
+}
+
+nsresult
+nsHTMLDNSPrefetch::PrefetchHigh(const nsAString &hostname)
+{
+ return Prefetch(hostname, 0);
+}
+
+nsresult
+nsHTMLDNSPrefetch::CancelPrefetch(Link *aElement,
+ uint16_t flags,
+ nsresult aReason)
+{
+ if (!(sInitialized && sPrefetches && sDNSService && sDNSListener))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsAutoString hostname;
+ aElement->GetHostname(hostname);
+ return CancelPrefetch(hostname, flags, aReason);
+}
+
+nsresult
+nsHTMLDNSPrefetch::CancelPrefetch(const nsAString &hostname,
+ uint16_t flags,
+ nsresult aReason)
+{
+ // Forward this request to Necko Parent if we're a child process
+ if (IsNeckoChild()) {
+ // We need to check IsEmpty() because net_IsValidHostName()
+ // considers empty strings to be valid hostnames
+ if (!hostname.IsEmpty() &&
+ net_IsValidHostName(NS_ConvertUTF16toUTF8(hostname))) {
+ // during shutdown gNeckoChild might be null
+ if (gNeckoChild) {
+ gNeckoChild->SendCancelHTMLDNSPrefetch(nsString(hostname), flags,
+ aReason);
+ }
+ }
+ return NS_OK;
+ }
+
+ if (!(sInitialized && sDNSService && sPrefetches && sDNSListener))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Forward cancellation to DNS service
+ return sDNSService->CancelAsyncResolve(NS_ConvertUTF16toUTF8(hostname),
+ flags
+ | nsIDNSService::RESOLVE_SPECULATE,
+ sDNSListener, aReason);
+}
+
+nsresult
+nsHTMLDNSPrefetch::CancelPrefetchLow(Link *aElement, nsresult aReason)
+{
+ return CancelPrefetch(aElement, nsIDNSService::RESOLVE_PRIORITY_LOW,
+ aReason);
+}
+
+nsresult
+nsHTMLDNSPrefetch::CancelPrefetchLow(const nsAString &hostname, nsresult aReason)
+{
+ return CancelPrefetch(hostname, nsIDNSService::RESOLVE_PRIORITY_LOW,
+ aReason);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsHTMLDNSPrefetch::nsListener,
+ nsIDNSListener)
+
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsListener::OnLookupComplete(nsICancelable *request,
+ nsIDNSRecord *rec,
+ nsresult status)
+{
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+nsHTMLDNSPrefetch::nsDeferrals::nsDeferrals()
+ : mHead(0),
+ mTail(0),
+ mActiveLoaderCount(0),
+ mTimerArmed(false)
+{
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+}
+
+nsHTMLDNSPrefetch::nsDeferrals::~nsDeferrals()
+{
+ if (mTimerArmed) {
+ mTimerArmed = false;
+ mTimer->Cancel();
+ }
+
+ Flush();
+}
+
+NS_IMPL_ISUPPORTS(nsHTMLDNSPrefetch::nsDeferrals,
+ nsIWebProgressListener,
+ nsISupportsWeakReference,
+ nsIObserver)
+
+void
+nsHTMLDNSPrefetch::nsDeferrals::Flush()
+{
+ while (mHead != mTail) {
+ mEntries[mTail].mElement = nullptr;
+ mTail = (mTail + 1) & sMaxDeferredMask;
+ }
+}
+
+nsresult
+nsHTMLDNSPrefetch::nsDeferrals::Add(uint16_t flags, Link *aElement)
+{
+ // The FIFO has no lock, so it can only be accessed on main thread
+ NS_ASSERTION(NS_IsMainThread(), "nsDeferrals::Add must be on main thread");
+
+ aElement->OnDNSPrefetchDeferred();
+
+ if (((mHead + 1) & sMaxDeferredMask) == mTail)
+ return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
+
+ mEntries[mHead].mFlags = flags;
+ mEntries[mHead].mElement = do_GetWeakReference(aElement);
+ mHead = (mHead + 1) & sMaxDeferredMask;
+
+ if (!mActiveLoaderCount && !mTimerArmed && mTimer) {
+ mTimerArmed = true;
+ mTimer->InitWithFuncCallback(Tick, this, 2000, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ return NS_OK;
+}
+
+void
+nsHTMLDNSPrefetch::nsDeferrals::SubmitQueue()
+{
+ NS_ASSERTION(NS_IsMainThread(), "nsDeferrals::SubmitQueue must be on main thread");
+ nsCString hostName;
+ if (!sDNSService) return;
+
+ while (mHead != mTail) {
+ nsCOMPtr<nsIContent> content = do_QueryReferent(mEntries[mTail].mElement);
+ if (content) {
+ nsCOMPtr<Link> link = do_QueryInterface(content);
+ // Only prefetch here if request was deferred and deferral not cancelled
+ if (link && link->HasDeferredDNSPrefetchRequest()) {
+ nsCOMPtr<nsIURI> hrefURI(link ? link->GetURI() : nullptr);
+ bool isLocalResource = false;
+ nsresult rv = NS_OK;
+
+ hostName.Truncate();
+ if (hrefURI) {
+ hrefURI->GetAsciiHost(hostName);
+ rv = NS_URIChainHasFlags(hrefURI,
+ nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
+ &isLocalResource);
+ }
+
+ if (!hostName.IsEmpty() && NS_SUCCEEDED(rv) && !isLocalResource) {
+ if (IsNeckoChild()) {
+ // during shutdown gNeckoChild might be null
+ if (gNeckoChild) {
+ gNeckoChild->SendHTMLDNSPrefetch(NS_ConvertUTF8toUTF16(hostName),
+ mEntries[mTail].mFlags);
+ }
+ } else {
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+
+ rv = sDNSService->AsyncResolve(hostName,
+ mEntries[mTail].mFlags
+ | nsIDNSService::RESOLVE_SPECULATE,
+ sDNSListener, nullptr,
+ getter_AddRefs(tmpOutstanding));
+ // Tell link that deferred prefetch was requested
+ if (NS_SUCCEEDED(rv))
+ link->OnDNSPrefetchRequested();
+ }
+ }
+ }
+ }
+
+ mEntries[mTail].mElement = nullptr;
+ mTail = (mTail + 1) & sMaxDeferredMask;
+ }
+
+ if (mTimerArmed) {
+ mTimerArmed = false;
+ mTimer->Cancel();
+ }
+}
+
+void
+nsHTMLDNSPrefetch::nsDeferrals::Activate()
+{
+ // Register as an observer for the document loader
+ nsCOMPtr<nsIWebProgress> progress =
+ do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
+ if (progress)
+ progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+
+ // Register as an observer for xpcom shutdown events so we can drop any element refs
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->AddObserver(this, "xpcom-shutdown", true);
+}
+
+// nsITimer related method
+
+void
+nsHTMLDNSPrefetch::nsDeferrals::Tick(nsITimer *aTimer, void *aClosure)
+{
+ nsHTMLDNSPrefetch::nsDeferrals *self = (nsHTMLDNSPrefetch::nsDeferrals *) aClosure;
+
+ NS_ASSERTION(NS_IsMainThread(), "nsDeferrals::Tick must be on main thread");
+ NS_ASSERTION(self->mTimerArmed, "Timer is not armed");
+
+ self->mTimerArmed = false;
+
+ // If the queue is not submitted here because there are outstanding pages being loaded,
+ // there is no need to rearm the timer as the queue will be submtited when those
+ // loads complete.
+ if (!self->mActiveLoaderCount)
+ self->SubmitQueue();
+}
+
+//////////// nsIWebProgressListener methods
+
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsDeferrals::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t progressStateFlags,
+ nsresult aStatus)
+{
+ // The FIFO has no lock, so it can only be accessed on main thread
+ NS_ASSERTION(NS_IsMainThread(), "nsDeferrals::OnStateChange must be on main thread");
+
+ if (progressStateFlags & STATE_IS_DOCUMENT) {
+ if (progressStateFlags & STATE_STOP) {
+
+ // Initialization may have missed a STATE_START notification, so do
+ // not go negative
+ if (mActiveLoaderCount)
+ mActiveLoaderCount--;
+
+ if (!mActiveLoaderCount)
+ SubmitQueue();
+ }
+ else if (progressStateFlags & STATE_START)
+ mActiveLoaderCount++;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsDeferrals::OnProgressChange(nsIWebProgress *aProgress,
+ nsIRequest *aRequest,
+ int32_t curSelfProgress,
+ int32_t maxSelfProgress,
+ int32_t curTotalProgress,
+ int32_t maxTotalProgress)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsDeferrals::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI *location,
+ uint32_t aFlags)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsDeferrals::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsDeferrals::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t state)
+{
+ return NS_OK;
+}
+
+//////////// nsIObserver method
+
+NS_IMETHODIMP
+nsHTMLDNSPrefetch::nsDeferrals::Observe(nsISupports *subject,
+ const char *topic,
+ const char16_t *data)
+{
+ if (!strcmp(topic, "xpcom-shutdown"))
+ Flush();
+
+ return NS_OK;
+}
diff --git a/dom/html/nsHTMLDNSPrefetch.h b/dom/html/nsHTMLDNSPrefetch.h
new file mode 100644
index 000000000..18da98f26
--- /dev/null
+++ b/dom/html/nsHTMLDNSPrefetch.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHTMLDNSPrefetch_h___
+#define nsHTMLDNSPrefetch_h___
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+#include "nsIDNSListener.h"
+#include "nsIWebProgressListener.h"
+#include "nsWeakReference.h"
+#include "nsIObserver.h"
+
+class nsIDocument;
+class nsITimer;
+namespace mozilla {
+namespace dom {
+class Link;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+namespace net {
+class NeckoParent;
+} // namespace net
+} // namespace mozilla
+
+class nsHTMLDNSPrefetch
+{
+public:
+ // The required aDocument parameter is the context requesting the prefetch - under
+ // certain circumstances (e.g. headers, or security context) associated with
+ // the context the prefetch will not be performed.
+ static bool IsAllowed(nsIDocument *aDocument);
+
+ static nsresult Initialize();
+ static nsresult Shutdown();
+
+ // Call one of the Prefetch* methods to start the lookup.
+ //
+ // The URI versions will defer DNS lookup until pageload is
+ // complete, while the string versions submit the lookup to
+ // the DNS system immediately. The URI version is somewhat lighter
+ // weight, but its request is also more likely to be dropped due to a
+ // full queue and it may only be used from the main thread.
+
+ static nsresult PrefetchHigh(mozilla::dom::Link *aElement);
+ static nsresult PrefetchMedium(mozilla::dom::Link *aElement);
+ static nsresult PrefetchLow(mozilla::dom::Link *aElement);
+ static nsresult PrefetchHigh(const nsAString &host);
+ static nsresult PrefetchMedium(const nsAString &host);
+ static nsresult PrefetchLow(const nsAString &host);
+ static nsresult CancelPrefetchLow(const nsAString &host, nsresult aReason);
+ static nsresult CancelPrefetchLow(mozilla::dom::Link *aElement,
+ nsresult aReason);
+
+private:
+ static nsresult Prefetch(const nsAString &host, uint16_t flags);
+ static nsresult Prefetch(mozilla::dom::Link *aElement, uint16_t flags);
+ static nsresult CancelPrefetch(const nsAString &hostname,
+ uint16_t flags,
+ nsresult aReason);
+ static nsresult CancelPrefetch(mozilla::dom::Link *aElement,
+ uint16_t flags,
+ nsresult aReason);
+
+public:
+ class nsListener final : public nsIDNSListener
+ {
+ // This class exists to give a safe callback no-op DNSListener
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSLISTENER
+
+ nsListener() {}
+ private:
+ ~nsListener() {}
+ };
+
+ class nsDeferrals final: public nsIWebProgressListener
+ , public nsSupportsWeakReference
+ , public nsIObserver
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIOBSERVER
+
+ nsDeferrals();
+
+ void Activate();
+ nsresult Add(uint16_t flags, mozilla::dom::Link *aElement);
+
+ private:
+ ~nsDeferrals();
+ void Flush();
+
+ void SubmitQueue();
+
+ uint16_t mHead;
+ uint16_t mTail;
+ uint32_t mActiveLoaderCount;
+
+ nsCOMPtr<nsITimer> mTimer;
+ bool mTimerArmed;
+ static void Tick(nsITimer *aTimer, void *aClosure);
+
+ static const int sMaxDeferred = 512; // keep power of 2 for masking
+ static const int sMaxDeferredMask = (sMaxDeferred - 1);
+
+ struct deferred_entry
+ {
+ uint16_t mFlags;
+ nsWeakPtr mElement;
+ } mEntries[sMaxDeferred];
+ };
+
+ friend class mozilla::net::NeckoParent;
+};
+
+#endif
diff --git a/dom/html/nsHTMLDocument.cpp b/dom/html/nsHTMLDocument.cpp
new file mode 100644
index 000000000..5e6302941
--- /dev/null
+++ b/dom/html/nsHTMLDocument.cpp
@@ -0,0 +1,3667 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsHTMLDocument.h"
+
+#include "nsIContentPolicy.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/HTMLAllCollection.h"
+#include "nsCOMPtr.h"
+#include "nsGlobalWindow.h"
+#include "nsXPIDLString.h"
+#include "nsPrintfCString.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsIHTMLContentSink.h"
+#include "nsIXMLContentSink.h"
+#include "nsHTMLParts.h"
+#include "nsHTMLStyleSheet.h"
+#include "nsGkAtoms.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsIDOMNode.h" // for Find
+#include "nsIDOMNodeList.h"
+#include "nsIDOMElement.h"
+#include "nsPIDOMWindow.h"
+#include "nsDOMString.h"
+#include "nsIStreamListener.h"
+#include "nsIURI.h"
+#include "nsIIOService.h"
+#include "nsNetUtil.h"
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsIContentViewerContainer.h"
+#include "nsIContentViewer.h"
+#include "nsDocShell.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsIWebNavigation.h"
+#include "nsIBaseWindow.h"
+#include "nsIWebShellServices.h"
+#include "nsIScriptContext.h"
+#include "nsIXPConnect.h"
+#include "nsContentList.h"
+#include "nsError.h"
+#include "nsIPrincipal.h"
+#include "nsJSPrincipals.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsAttrName.h"
+#include "nsNodeUtils.h"
+
+#include "nsNetCID.h"
+#include "nsICookieService.h"
+
+#include "nsIServiceManager.h"
+#include "nsIConsoleService.h"
+#include "nsIComponentManager.h"
+#include "nsParserCIID.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIDOMHTMLHeadElement.h"
+#include "nsNameSpaceManager.h"
+#include "nsGenericHTMLElement.h"
+#include "mozilla/css/Loader.h"
+#include "nsIHttpChannel.h"
+#include "nsIFile.h"
+#include "nsFrameSelection.h"
+#include "nsISelectionPrivate.h"//for toStringwithformat code
+
+#include "nsContentUtils.h"
+#include "nsJSUtils.h"
+#include "nsIDocumentInlines.h"
+#include "nsIDocumentEncoder.h" //for outputting selection
+#include "nsICachingChannel.h"
+#include "nsIContentViewer.h"
+#include "nsIWyciwygChannel.h"
+#include "nsIScriptElement.h"
+#include "nsIScriptError.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsIEffectiveTLDService.h"
+
+//AHMED 12-2
+#include "nsBidiUtils.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/FallbackEncoding.h"
+#include "mozilla/LoadInfo.h"
+#include "nsIEditingSession.h"
+#include "nsIEditor.h"
+#include "nsNodeInfoManager.h"
+#include "nsIPlaintextEditor.h"
+#include "nsIHTMLEditor.h"
+#include "nsIEditorStyleSheets.h"
+#include "nsIInlineSpellChecker.h"
+#include "nsRange.h"
+#include "mozAutoDocUpdate.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsHtml5Module.h"
+#include "prprf.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Preferences.h"
+#include "nsMimeTypes.h"
+#include "nsIRequest.h"
+#include "nsHtml5TreeOpExecutor.h"
+#include "nsHtml5Parser.h"
+#include "nsSandboxFlags.h"
+#include "nsIImageDocument.h"
+#include "mozilla/dom/HTMLBodyElement.h"
+#include "mozilla/dom/HTMLDocumentBinding.h"
+#include "nsCharsetSource.h"
+#include "nsIStringBundle.h"
+#include "nsDOMClassInfo.h"
+#include "nsFocusManager.h"
+#include "nsIFrame.h"
+#include "nsIContent.h"
+#include "nsLayoutStylesheetCache.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#define NS_MAX_DOCUMENT_WRITE_DEPTH 20
+
+#include "prtime.h"
+
+//#define DEBUG_charset
+
+static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID);
+
+uint32_t nsHTMLDocument::gWyciwygSessionCnt = 0;
+
+// this function will return false if the command is not recognized
+// inCommandID will be converted as necessary for internal operations
+// inParam will be converted as necessary for internal operations
+// outParam will be Empty if no parameter is needed or if returning a boolean
+// outIsBoolean will determine whether to send param as a boolean or string
+// outBooleanParam will not be set unless outIsBoolean
+static bool ConvertToMidasInternalCommand(const nsAString & inCommandID,
+ const nsAString & inParam,
+ nsACString& outCommandID,
+ nsACString& outParam,
+ bool& isBoolean,
+ bool& boolValue);
+
+static bool ConvertToMidasInternalCommand(const nsAString & inCommandID,
+ nsACString& outCommandID);
+
+// ==================================================================
+// =
+// ==================================================================
+
+nsresult
+NS_NewHTMLDocument(nsIDocument** aInstancePtrResult, bool aLoadedAsData)
+{
+ RefPtr<nsHTMLDocument> doc = new nsHTMLDocument();
+
+ nsresult rv = doc->Init();
+
+ if (NS_FAILED(rv)) {
+ *aInstancePtrResult = nullptr;
+ return rv;
+ }
+
+ doc->SetLoadedAsData(aLoadedAsData);
+ doc.forget(aInstancePtrResult);
+
+ return NS_OK;
+}
+
+ // NOTE! nsDocument::operator new() zeroes out all members, so don't
+ // bother initializing members to 0.
+
+nsHTMLDocument::nsHTMLDocument()
+ : nsDocument("text/html")
+{
+ // NOTE! nsDocument::operator new() zeroes out all members, so don't
+ // bother initializing members to 0.
+
+ mType = eHTML;
+ mDefaultElementType = kNameSpaceID_XHTML;
+ mCompatMode = eCompatibility_NavQuirks;
+}
+
+nsHTMLDocument::~nsHTMLDocument()
+{
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(nsHTMLDocument, nsDocument,
+ mAll,
+ mImages,
+ mApplets,
+ mEmbeds,
+ mLinks,
+ mAnchors,
+ mScripts,
+ mForms,
+ mFormControls,
+ mWyciwygChannel,
+ mMidasCommandManager)
+
+NS_IMPL_ADDREF_INHERITED(nsHTMLDocument, nsDocument)
+NS_IMPL_RELEASE_INHERITED(nsHTMLDocument, nsDocument)
+
+// QueryInterface implementation for nsHTMLDocument
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHTMLDocument)
+ NS_INTERFACE_TABLE_INHERITED(nsHTMLDocument, nsIHTMLDocument,
+ nsIDOMHTMLDocument)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsDocument)
+
+JSObject*
+nsHTMLDocument::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLDocumentBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsresult
+nsHTMLDocument::Init()
+{
+ nsresult rv = nsDocument::Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now reset the compatibility mode of the CSSLoader
+ // to match our compat mode.
+ CSSLoader()->SetCompatibilityMode(mCompatMode);
+
+ return NS_OK;
+}
+
+
+void
+nsHTMLDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup)
+{
+ nsDocument::Reset(aChannel, aLoadGroup);
+
+ if (aChannel) {
+ aChannel->GetLoadFlags(&mLoadFlags);
+ }
+}
+
+void
+nsHTMLDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
+ nsIPrincipal* aPrincipal)
+{
+ mLoadFlags = nsIRequest::LOAD_NORMAL;
+
+ nsDocument::ResetToURI(aURI, aLoadGroup, aPrincipal);
+
+ mImages = nullptr;
+ mApplets = nullptr;
+ mEmbeds = nullptr;
+ mLinks = nullptr;
+ mAnchors = nullptr;
+ mScripts = nullptr;
+
+ mForms = nullptr;
+
+ NS_ASSERTION(!mWyciwygChannel,
+ "nsHTMLDocument::Reset() - Wyciwyg Channel still exists!");
+
+ mWyciwygChannel = nullptr;
+
+ // Make the content type default to "text/html", we are a HTML
+ // document, after all. Once we start getting data, this may be
+ // changed.
+ SetContentTypeInternal(nsDependentCString("text/html"));
+}
+
+already_AddRefed<nsIPresShell>
+nsHTMLDocument::CreateShell(nsPresContext* aContext,
+ nsViewManager* aViewManager,
+ StyleSetHandle aStyleSet)
+{
+ return doCreateShell(aContext, aViewManager, aStyleSet);
+}
+
+void
+nsHTMLDocument::TryHintCharset(nsIContentViewer* aCv,
+ int32_t& aCharsetSource, nsACString& aCharset)
+{
+ if (aCv) {
+ int32_t requestCharsetSource;
+ nsresult rv = aCv->GetHintCharacterSetSource(&requestCharsetSource);
+
+ if(NS_SUCCEEDED(rv) && kCharsetUninitialized != requestCharsetSource) {
+ nsAutoCString requestCharset;
+ rv = aCv->GetHintCharacterSet(requestCharset);
+ aCv->SetHintCharacterSetSource((int32_t)(kCharsetUninitialized));
+
+ if(requestCharsetSource <= aCharsetSource)
+ return;
+
+ if(NS_SUCCEEDED(rv) && EncodingUtils::IsAsciiCompatible(requestCharset)) {
+ aCharsetSource = requestCharsetSource;
+ aCharset = requestCharset;
+
+ return;
+ }
+ }
+ }
+ return;
+}
+
+
+void
+nsHTMLDocument::TryUserForcedCharset(nsIContentViewer* aCv,
+ nsIDocShell* aDocShell,
+ int32_t& aCharsetSource,
+ nsACString& aCharset)
+{
+ nsresult rv = NS_OK;
+
+ if(kCharsetFromUserForced <= aCharsetSource)
+ return;
+
+ // mCharacterSet not updated yet for channel, so check aCharset, too.
+ if (WillIgnoreCharsetOverride() || !EncodingUtils::IsAsciiCompatible(aCharset)) {
+ return;
+ }
+
+ nsAutoCString forceCharsetFromDocShell;
+ if (aCv) {
+ // XXX mailnews-only
+ rv = aCv->GetForceCharacterSet(forceCharsetFromDocShell);
+ }
+
+ if(NS_SUCCEEDED(rv) &&
+ !forceCharsetFromDocShell.IsEmpty() &&
+ EncodingUtils::IsAsciiCompatible(forceCharsetFromDocShell)) {
+ aCharset = forceCharsetFromDocShell;
+ aCharsetSource = kCharsetFromUserForced;
+ return;
+ }
+
+ if (aDocShell) {
+ // This is the Character Encoding menu code path in Firefox
+ nsAutoCString charset;
+ rv = aDocShell->GetForcedCharset(charset);
+
+ if (NS_SUCCEEDED(rv) && !charset.IsEmpty()) {
+ if (!EncodingUtils::IsAsciiCompatible(charset)) {
+ return;
+ }
+ aCharset = charset;
+ aCharsetSource = kCharsetFromUserForced;
+ aDocShell->SetForcedCharset(NS_LITERAL_CSTRING(""));
+ }
+ }
+}
+
+void
+nsHTMLDocument::TryCacheCharset(nsICachingChannel* aCachingChannel,
+ int32_t& aCharsetSource,
+ nsACString& aCharset)
+{
+ nsresult rv;
+
+ if (kCharsetFromCache <= aCharsetSource) {
+ return;
+ }
+
+ nsCString cachedCharset;
+ rv = aCachingChannel->GetCacheTokenCachedCharset(cachedCharset);
+ // Check EncodingUtils::IsAsciiCompatible() even in the cache case, because the value
+ // might be stale and in the case of a stale charset that is not a rough
+ // ASCII superset, the parser has no way to recover.
+ if (NS_SUCCEEDED(rv) &&
+ !cachedCharset.IsEmpty() &&
+ EncodingUtils::IsAsciiCompatible(cachedCharset))
+ {
+ aCharset = cachedCharset;
+ aCharsetSource = kCharsetFromCache;
+ }
+}
+
+void
+nsHTMLDocument::TryParentCharset(nsIDocShell* aDocShell,
+ int32_t& aCharsetSource,
+ nsACString& aCharset)
+{
+ if (!aDocShell) {
+ return;
+ }
+ if (aCharsetSource >= kCharsetFromParentForced) {
+ return;
+ }
+
+ int32_t parentSource;
+ nsAutoCString parentCharset;
+ nsCOMPtr<nsIPrincipal> parentPrincipal;
+ aDocShell->GetParentCharset(parentCharset,
+ &parentSource,
+ getter_AddRefs(parentPrincipal));
+ if (parentCharset.IsEmpty()) {
+ return;
+ }
+ if (kCharsetFromParentForced == parentSource ||
+ kCharsetFromUserForced == parentSource) {
+ if (WillIgnoreCharsetOverride() ||
+ !EncodingUtils::IsAsciiCompatible(aCharset) || // if channel said UTF-16
+ !EncodingUtils::IsAsciiCompatible(parentCharset)) {
+ return;
+ }
+ aCharset.Assign(parentCharset);
+ aCharsetSource = kCharsetFromParentForced;
+ return;
+ }
+
+ if (aCharsetSource >= kCharsetFromParentFrame) {
+ return;
+ }
+
+ if (kCharsetFromCache <= parentSource) {
+ // Make sure that's OK
+ if (!NodePrincipal()->Equals(parentPrincipal) ||
+ !EncodingUtils::IsAsciiCompatible(parentCharset)) {
+ return;
+ }
+
+ aCharset.Assign(parentCharset);
+ aCharsetSource = kCharsetFromParentFrame;
+ }
+}
+
+void
+nsHTMLDocument::TryTLD(int32_t& aCharsetSource, nsACString& aCharset)
+{
+ if (aCharsetSource >= kCharsetFromTopLevelDomain) {
+ return;
+ }
+ if (!FallbackEncoding::sGuessFallbackFromTopLevelDomain) {
+ return;
+ }
+ if (!mDocumentURI) {
+ return;
+ }
+ nsAutoCString host;
+ mDocumentURI->GetAsciiHost(host);
+ if (host.IsEmpty()) {
+ return;
+ }
+ // First let's see if the host is DNS-absolute and ends with a dot and
+ // get rid of that one.
+ if (host.Last() == '.') {
+ host.SetLength(host.Length() - 1);
+ if (host.IsEmpty()) {
+ return;
+ }
+ }
+ // If we still have a dot, the host is weird, so let's continue only
+ // if we have something other than a dot now.
+ if (host.Last() == '.') {
+ return;
+ }
+ int32_t index = host.RFindChar('.');
+ if (index == kNotFound) {
+ // We have an intranet host, Gecko-internal URL or an IPv6 address.
+ return;
+ }
+ // Since the string didn't end with a dot and we found a dot,
+ // there is at least one character between the dot and the end of
+ // the string, so taking the substring below is safe.
+ nsAutoCString tld;
+ ToLowerCase(Substring(host, index + 1, host.Length() - (index + 1)), tld);
+ // Reject generic TLDs and country TLDs that need more research
+ if (!FallbackEncoding::IsParticipatingTopLevelDomain(tld)) {
+ return;
+ }
+ // Check if we have an IPv4 address
+ bool seenNonDigit = false;
+ for (size_t i = 0; i < tld.Length(); ++i) {
+ char c = tld.CharAt(i);
+ if (c < '0' || c > '9') {
+ seenNonDigit = true;
+ break;
+ }
+ }
+ if (!seenNonDigit) {
+ return;
+ }
+ aCharsetSource = kCharsetFromTopLevelDomain;
+ FallbackEncoding::FromTopLevelDomain(tld, aCharset);
+}
+
+void
+nsHTMLDocument::TryFallback(int32_t& aCharsetSource, nsACString& aCharset)
+{
+ if (kCharsetFromFallback <= aCharsetSource)
+ return;
+
+ aCharsetSource = kCharsetFromFallback;
+ FallbackEncoding::FromLocale(aCharset);
+}
+
+void
+nsHTMLDocument::SetDocumentCharacterSet(const nsACString& aCharSetID)
+{
+ nsDocument::SetDocumentCharacterSet(aCharSetID);
+ // Make sure to stash this charset on our channel as needed if it's a wyciwyg
+ // channel.
+ nsCOMPtr<nsIWyciwygChannel> wyciwygChannel = do_QueryInterface(mChannel);
+ if (wyciwygChannel) {
+ wyciwygChannel->SetCharsetAndSource(GetDocumentCharacterSetSource(),
+ aCharSetID);
+ }
+}
+
+nsresult
+nsHTMLDocument::StartDocumentLoad(const char* aCommand,
+ nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener **aDocListener,
+ bool aReset,
+ nsIContentSink* aSink)
+{
+ if (!aCommand) {
+ MOZ_ASSERT(false, "Command is mandatory");
+ return NS_ERROR_INVALID_POINTER;
+ }
+ if (aSink) {
+ MOZ_ASSERT(false, "Got a sink override. Should not happen for HTML doc.");
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (mType != eHTML) {
+ MOZ_ASSERT(mType == eXHTML);
+ MOZ_ASSERT(false, "Must not set HTML doc to XHTML mode before load start.");
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ nsAutoCString contentType;
+ aChannel->GetContentType(contentType);
+
+ bool view = !strcmp(aCommand, "view") ||
+ !strcmp(aCommand, "external-resource");
+ bool viewSource = !strcmp(aCommand, "view-source");
+ bool asData = !strcmp(aCommand, kLoadAsData);
+ bool import = !strcmp(aCommand, "import");
+ if (!(view || viewSource || asData || import)) {
+ MOZ_ASSERT(false, "Bad parser command");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ bool html = contentType.EqualsLiteral(TEXT_HTML);
+ bool xhtml = !html && (contentType.EqualsLiteral(APPLICATION_XHTML_XML) || contentType.EqualsLiteral(APPLICATION_WAPXHTML_XML));
+ bool plainText = !html && !xhtml && nsContentUtils::IsPlainTextType(contentType);
+ if (!(html || xhtml || plainText || viewSource)) {
+ MOZ_ASSERT(false, "Channel with bad content type.");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ bool loadAsHtml5 = true;
+
+ if (!viewSource && xhtml) {
+ // We're parsing XHTML as XML, remember that.
+ mType = eXHTML;
+ mCompatMode = eCompatibility_FullStandards;
+ loadAsHtml5 = false;
+ }
+
+ // TODO: Proper about:blank treatment is bug 543435
+ if (loadAsHtml5 && view) {
+ // mDocumentURI hasn't been set, yet, so get the URI from the channel
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetOriginalURI(getter_AddRefs(uri));
+ // Adapted from nsDocShell:
+ // GetSpec can be expensive for some URIs, so check the scheme first.
+ bool isAbout = false;
+ if (uri && NS_SUCCEEDED(uri->SchemeIs("about", &isAbout)) && isAbout) {
+ if (uri->GetSpecOrDefault().EqualsLiteral("about:blank")) {
+ loadAsHtml5 = false;
+ }
+ }
+ }
+
+ CSSLoader()->SetCompatibilityMode(mCompatMode);
+
+ nsresult rv = nsDocument::StartDocumentLoad(aCommand,
+ aChannel, aLoadGroup,
+ aContainer,
+ aDocListener, aReset);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Store the security info for future use with wyciwyg channels.
+ aChannel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
+
+ nsCOMPtr<nsIURI> uri;
+ rv = aChannel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsICachingChannel> cachingChan = do_QueryInterface(aChannel);
+
+ if (loadAsHtml5) {
+ mParser = nsHtml5Module::NewHtml5Parser();
+ if (plainText) {
+ if (viewSource) {
+ mParser->MarkAsNotScriptCreated("view-source-plain");
+ } else {
+ mParser->MarkAsNotScriptCreated("plain-text");
+ }
+ } else if (viewSource && !html) {
+ mParser->MarkAsNotScriptCreated("view-source-xml");
+ } else {
+ mParser->MarkAsNotScriptCreated(aCommand);
+ }
+ } else {
+ mParser = do_CreateInstance(kCParserCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Look for the parent document. Note that at this point we don't have our
+ // content viewer set up yet, and therefore do not have a useful
+ // mParentDocument.
+
+ // in this block of code, if we get an error result, we return it
+ // but if we get a null pointer, that's perfectly legal for parent
+ // and parentContentViewer
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aContainer));
+ nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
+ if (docShell) {
+ docShell->GetSameTypeParent(getter_AddRefs(parentAsItem));
+ }
+
+ nsCOMPtr<nsIDocShell> parent(do_QueryInterface(parentAsItem));
+ nsCOMPtr<nsIContentViewer> parentContentViewer;
+ if (parent) {
+ rv = parent->GetContentViewer(getter_AddRefs(parentContentViewer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIContentViewer> cv;
+ if (docShell) {
+ docShell->GetContentViewer(getter_AddRefs(cv));
+ }
+ if (!cv) {
+ cv = parentContentViewer.forget();
+ }
+
+ nsAutoCString urlSpec;
+ uri->GetSpec(urlSpec);
+#ifdef DEBUG_charset
+ printf("Determining charset for %s\n", urlSpec.get());
+#endif
+
+ // These are the charset source and charset for our document
+ int32_t charsetSource;
+ nsAutoCString charset;
+
+ // These are the charset source and charset for the parser. This can differ
+ // from that for the document if the channel is a wyciwyg channel.
+ int32_t parserCharsetSource;
+ nsAutoCString parserCharset;
+
+ nsCOMPtr<nsIWyciwygChannel> wyciwygChannel;
+
+ // For error reporting and referrer policy setting
+ nsHtml5TreeOpExecutor* executor = nullptr;
+ if (loadAsHtml5) {
+ executor = static_cast<nsHtml5TreeOpExecutor*> (mParser->GetContentSink());
+ if (mReferrerPolicySet) {
+ // CSP may have set the referrer policy, so a speculative parser should
+ // start with the new referrer policy.
+ executor->SetSpeculationReferrerPolicy(static_cast<ReferrerPolicy>(mReferrerPolicy));
+ }
+ }
+
+ if (!IsHTMLDocument() || !docShell) { // no docshell for text/html XHR
+ charsetSource = IsHTMLDocument() ? kCharsetFromFallback
+ : kCharsetFromDocTypeDefault;
+ charset.AssignLiteral("UTF-8");
+ TryChannelCharset(aChannel, charsetSource, charset, executor);
+ parserCharsetSource = charsetSource;
+ parserCharset = charset;
+ } else {
+ NS_ASSERTION(docShell, "Unexpected null value");
+
+ charsetSource = kCharsetUninitialized;
+ wyciwygChannel = do_QueryInterface(aChannel);
+
+ // The following will try to get the character encoding from various
+ // sources. Each Try* function will return early if the source is already
+ // at least as large as any of the sources it might look at. Some of
+ // these functions (like TryHintCharset and TryParentCharset) can set
+ // charsetSource to various values depending on where the charset they
+ // end up finding originally comes from.
+
+ // Don't actually get the charset from the channel if this is a
+ // wyciwyg channel; it'll always be UTF-16
+ if (!wyciwygChannel) {
+ // Otherwise, try the channel's charset (e.g., charset from HTTP
+ // "Content-Type" header) first. This way, we get to reject overrides in
+ // TryParentCharset and TryUserForcedCharset if the channel said UTF-16.
+ // This is to avoid socially engineered XSS by adding user-supplied
+ // content to a UTF-16 site such that the byte have a dangerous
+ // interpretation as ASCII and the user can be lured to using the
+ // charset menu.
+ TryChannelCharset(aChannel, charsetSource, charset, executor);
+ }
+
+ TryUserForcedCharset(cv, docShell, charsetSource, charset);
+
+ TryHintCharset(cv, charsetSource, charset); // XXX mailnews-only
+ TryParentCharset(docShell, charsetSource, charset);
+
+ if (cachingChan && !urlSpec.IsEmpty()) {
+ TryCacheCharset(cachingChan, charsetSource, charset);
+ }
+
+ TryTLD(charsetSource, charset);
+ TryFallback(charsetSource, charset);
+
+ if (wyciwygChannel) {
+ // We know for sure that the parser needs to be using UTF16.
+ parserCharset = "UTF-16";
+ parserCharsetSource = charsetSource < kCharsetFromChannel ?
+ kCharsetFromChannel : charsetSource;
+
+ nsAutoCString cachedCharset;
+ int32_t cachedSource;
+ rv = wyciwygChannel->GetCharsetAndSource(&cachedSource, cachedCharset);
+ if (NS_SUCCEEDED(rv)) {
+ if (cachedSource > charsetSource) {
+ charsetSource = cachedSource;
+ charset = cachedCharset;
+ }
+ } else {
+ // Don't propagate this error.
+ rv = NS_OK;
+ }
+
+ } else {
+ parserCharset = charset;
+ parserCharsetSource = charsetSource;
+ }
+ }
+
+ SetDocumentCharacterSetSource(charsetSource);
+ SetDocumentCharacterSet(charset);
+
+ if (cachingChan) {
+ NS_ASSERTION(charset == parserCharset,
+ "How did those end up different here? wyciwyg channels are "
+ "not nsICachingChannel");
+ rv = cachingChan->SetCacheTokenCachedCharset(charset);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "cannot SetMetaDataElement");
+ rv = NS_OK; // don't propagate error
+ }
+
+ // Set the parser as the stream listener for the document loader...
+ rv = NS_OK;
+ nsCOMPtr<nsIStreamListener> listener = mParser->GetStreamListener();
+ listener.forget(aDocListener);
+
+#ifdef DEBUG_charset
+ printf(" charset = %s source %d\n",
+ charset.get(), charsetSource);
+#endif
+ mParser->SetDocumentCharset(parserCharset, parserCharsetSource);
+ mParser->SetCommand(aCommand);
+
+ if (!IsHTMLDocument()) {
+ MOZ_ASSERT(!loadAsHtml5);
+ nsCOMPtr<nsIXMLContentSink> xmlsink;
+ NS_NewXMLContentSink(getter_AddRefs(xmlsink), this, uri,
+ docShell, aChannel);
+ mParser->SetContentSink(xmlsink);
+ } else {
+ if (loadAsHtml5) {
+ nsHtml5Module::Initialize(mParser, this, uri, docShell, aChannel);
+ } else {
+ // about:blank *only*
+ nsCOMPtr<nsIHTMLContentSink> htmlsink;
+ NS_NewHTMLContentSink(getter_AddRefs(htmlsink), this, uri,
+ docShell, aChannel);
+ mParser->SetContentSink(htmlsink);
+ }
+ }
+
+ if (plainText && !nsContentUtils::IsChildOfSameType(this) &&
+ Preferences::GetBool("plain_text.wrap_long_lines")) {
+ nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv) && bundleService, "The bundle service could not be loaded");
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://global/locale/browser.properties",
+ getter_AddRefs(bundle));
+ NS_ASSERTION(NS_SUCCEEDED(rv) && bundle, "chrome://global/locale/browser.properties could not be loaded");
+ nsXPIDLString title;
+ if (bundle) {
+ bundle->GetStringFromName(u"plainText.wordWrap", getter_Copies(title));
+ }
+ SetSelectedStyleSheetSet(title);
+ }
+
+ // parser the content of the URI
+ mParser->Parse(uri, nullptr, (void *)this);
+
+ return rv;
+}
+
+void
+nsHTMLDocument::StopDocumentLoad()
+{
+ BlockOnload();
+
+ // Remove the wyciwyg channel request from the document load group
+ // that we added in Open() if Open() was called on this doc.
+ RemoveWyciwygChannel();
+ NS_ASSERTION(!mWyciwygChannel, "nsHTMLDocument::StopDocumentLoad(): "
+ "nsIWyciwygChannel could not be removed!");
+
+ nsDocument::StopDocumentLoad();
+ UnblockOnload(false);
+ return;
+}
+
+void
+nsHTMLDocument::BeginLoad()
+{
+ if (IsEditingOn()) {
+ // Reset() blows away all event listeners in the document, and our
+ // editor relies heavily on those. Midas is turned on, to make it
+ // work, re-initialize it to give it a chance to add its event
+ // listeners again.
+
+ TurnEditingOff();
+ EditingStateChanged();
+ }
+ nsDocument::BeginLoad();
+}
+
+void
+nsHTMLDocument::EndLoad()
+{
+ bool turnOnEditing =
+ mParser && (HasFlag(NODE_IS_EDITABLE) || mContentEditableCount > 0);
+ // Note: nsDocument::EndLoad nulls out mParser.
+ nsDocument::EndLoad();
+ if (turnOnEditing) {
+ EditingStateChanged();
+ }
+}
+
+void
+nsHTMLDocument::SetCompatibilityMode(nsCompatibility aMode)
+{
+ NS_ASSERTION(IsHTMLDocument() || aMode == eCompatibility_FullStandards,
+ "Bad compat mode for XHTML document!");
+
+ mCompatMode = aMode;
+ CSSLoader()->SetCompatibilityMode(mCompatMode);
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ if (shell) {
+ nsPresContext *pc = shell->GetPresContext();
+ if (pc) {
+ pc->CompatibilityModeChanged();
+ }
+ }
+}
+
+//
+// nsIDOMHTMLDocument interface implementation
+//
+already_AddRefed<nsIURI>
+nsHTMLDocument::GetDomainURI()
+{
+ nsIPrincipal* principal = NodePrincipal();
+
+ nsCOMPtr<nsIURI> uri;
+ principal->GetDomain(getter_AddRefs(uri));
+ if (uri) {
+ return uri.forget();
+ }
+
+ principal->GetURI(getter_AddRefs(uri));
+ return uri.forget();
+}
+
+
+NS_IMETHODIMP
+nsHTMLDocument::GetDomain(nsAString& aDomain)
+{
+ nsCOMPtr<nsIURI> uri = GetDomainURI();
+
+ if (!uri) {
+ SetDOMStringToNull(aDomain);
+ return NS_OK;
+ }
+
+ nsAutoCString hostName;
+ nsresult rv = nsContentUtils::GetHostOrIPv6WithBrackets(uri, hostName);
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF8toUTF16(hostName, aDomain);
+ } else {
+ // If we can't get the host from the URI (e.g. about:, javascript:,
+ // etc), just return an null string.
+ SetDOMStringToNull(aDomain);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::SetDomain(const nsAString& aDomain)
+{
+ ErrorResult rv;
+ SetDomain(aDomain, rv);
+ return rv.StealNSResult();
+}
+
+void
+nsHTMLDocument::SetDomain(const nsAString& aDomain, ErrorResult& rv)
+{
+ if (mSandboxFlags & SANDBOXED_DOMAIN) {
+ // We're sandboxed; disallow setting domain
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ if (aDomain.IsEmpty()) {
+ rv.Throw(NS_ERROR_DOM_BAD_DOCUMENT_DOMAIN);
+ return;
+ }
+
+ // Create new URI
+ nsCOMPtr<nsIURI> uri = GetDomainURI();
+
+ if (!uri) {
+ rv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv2 = uri->Clone(getter_AddRefs(newURI));
+ if (NS_FAILED(rv2)) {
+ rv.Throw(rv2);
+ return;
+ }
+
+ rv2 = newURI->SetUserPass(EmptyCString());
+ if (NS_FAILED(rv2)) {
+ rv.Throw(rv2);
+ return;
+ }
+
+ // We use SetHostAndPort because we want to reset the port number if needed.
+ rv2 = newURI->SetHostAndPort(NS_ConvertUTF16toUTF8(aDomain));
+ if (NS_FAILED(rv2)) {
+ rv.Throw(rv2);
+ return;
+ }
+
+ // Check new domain - must be a superdomain of the current host
+ // For example, a page from foo.bar.com may set domain to bar.com,
+ // but not to ar.com, baz.com, or fi.foo.bar.com.
+ nsAutoCString current, domain;
+ if (NS_FAILED(uri->GetAsciiHost(current)))
+ current.Truncate();
+ if (NS_FAILED(newURI->GetAsciiHost(domain)))
+ domain.Truncate();
+
+ bool ok = current.Equals(domain);
+ if (current.Length() > domain.Length() &&
+ StringEndsWith(current, domain) &&
+ current.CharAt(current.Length() - domain.Length() - 1) == '.') {
+ // We're golden if the new domain is the current page's base domain or a
+ // subdomain of it.
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (!tldService) {
+ rv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ nsAutoCString currentBaseDomain;
+ ok = NS_SUCCEEDED(tldService->GetBaseDomain(uri, 0, currentBaseDomain));
+ NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
+ (domain.Length() >= currentBaseDomain.Length()),
+ "uh-oh! slight optimization wasn't valid somehow!");
+ ok = ok && domain.Length() >= currentBaseDomain.Length();
+ }
+ if (!ok) {
+ // Error: illegal domain
+ rv.Throw(NS_ERROR_DOM_BAD_DOCUMENT_DOMAIN);
+ return;
+ }
+
+ NS_TryToSetImmutable(newURI);
+ rv = NodePrincipal()->SetDomain(newURI);
+}
+
+nsGenericHTMLElement*
+nsHTMLDocument::GetBody()
+{
+ Element* html = GetHtmlElement();
+ if (!html) {
+ return nullptr;
+ }
+
+ for (nsIContent* child = html->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->IsHTMLElement(nsGkAtoms::body) ||
+ child->IsHTMLElement(nsGkAtoms::frameset)) {
+ return static_cast<nsGenericHTMLElement*>(child);
+ }
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::GetBody(nsIDOMHTMLElement** aBody)
+{
+ *aBody = nullptr;
+
+ nsIContent *body = GetBody();
+
+ return body ? CallQueryInterface(body, aBody) : NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::SetBody(nsIDOMHTMLElement* aBody)
+{
+ nsCOMPtr<nsIContent> newBody = do_QueryInterface(aBody);
+ MOZ_ASSERT(!newBody || newBody->IsHTMLElement(),
+ "How could we be an nsIContent but not actually HTML here?");
+ ErrorResult rv;
+ SetBody(static_cast<nsGenericHTMLElement*>(newBody.get()), rv);
+ return rv.StealNSResult();
+}
+
+void
+nsHTMLDocument::SetBody(nsGenericHTMLElement* newBody, ErrorResult& rv)
+{
+ nsCOMPtr<Element> root = GetRootElement();
+
+ // The body element must be either a body tag or a frameset tag. And we must
+ // have a html root tag, otherwise GetBody will not return the newly set
+ // body.
+ if (!newBody ||
+ !newBody->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset) ||
+ !root || !root->IsHTMLElement() ||
+ !root->IsHTMLElement(nsGkAtoms::html)) {
+ rv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return;
+ }
+
+ // Use DOM methods so that we pass through the appropriate security checks.
+ nsCOMPtr<Element> currentBody = GetBodyElement();
+ if (currentBody) {
+ root->ReplaceChild(*newBody, *currentBody, rv);
+ } else {
+ root->AppendChild(*newBody, rv);
+ }
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::GetHead(nsIDOMHTMLHeadElement** aHead)
+{
+ *aHead = nullptr;
+
+ Element* head = GetHeadElement();
+
+ return head ? CallQueryInterface(head, aHead) : NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::GetImages(nsIDOMHTMLCollection** aImages)
+{
+ NS_ADDREF(*aImages = Images());
+ return NS_OK;
+}
+
+nsIHTMLCollection*
+nsHTMLDocument::Images()
+{
+ if (!mImages) {
+ mImages = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::img, nsGkAtoms::img);
+ }
+ return mImages;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::GetApplets(nsIDOMHTMLCollection** aApplets)
+{
+ NS_ADDREF(*aApplets = Applets());
+ return NS_OK;
+}
+
+nsIHTMLCollection*
+nsHTMLDocument::Applets()
+{
+ if (!mApplets) {
+ mApplets = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::applet, nsGkAtoms::applet);
+ }
+ return mApplets;
+}
+
+bool
+nsHTMLDocument::MatchLinks(nsIContent *aContent, int32_t aNamespaceID,
+ nsIAtom* aAtom, void* aData)
+{
+ nsIDocument* doc = aContent->GetUncomposedDoc();
+
+ if (doc) {
+ NS_ASSERTION(aContent->IsInUncomposedDoc(),
+ "This method should never be called on content nodes that "
+ "are not in a document!");
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIHTMLDocument> htmldoc =
+ do_QueryInterface(aContent->GetUncomposedDoc());
+ NS_ASSERTION(htmldoc,
+ "Huh, how did this happen? This should only be used with "
+ "HTML documents!");
+ }
+#endif
+
+ mozilla::dom::NodeInfo *ni = aContent->NodeInfo();
+
+ nsIAtom *localName = ni->NameAtom();
+ if (ni->NamespaceID() == kNameSpaceID_XHTML &&
+ (localName == nsGkAtoms::a || localName == nsGkAtoms::area)) {
+ return aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::href);
+ }
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::GetLinks(nsIDOMHTMLCollection** aLinks)
+{
+ NS_ADDREF(*aLinks = Links());
+ return NS_OK;
+}
+
+nsIHTMLCollection*
+nsHTMLDocument::Links()
+{
+ if (!mLinks) {
+ mLinks = new nsContentList(this, MatchLinks, nullptr, nullptr);
+ }
+ return mLinks;
+}
+
+bool
+nsHTMLDocument::MatchAnchors(nsIContent *aContent, int32_t aNamespaceID,
+ nsIAtom* aAtom, void* aData)
+{
+ NS_ASSERTION(aContent->IsInUncomposedDoc(),
+ "This method should never be called on content nodes that "
+ "are not in a document!");
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIHTMLDocument> htmldoc =
+ do_QueryInterface(aContent->GetUncomposedDoc());
+ NS_ASSERTION(htmldoc,
+ "Huh, how did this happen? This should only be used with "
+ "HTML documents!");
+ }
+#endif
+
+ if (aContent->NodeInfo()->Equals(nsGkAtoms::a, kNameSpaceID_XHTML)) {
+ return aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::name);
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::GetAnchors(nsIDOMHTMLCollection** aAnchors)
+{
+ NS_ADDREF(*aAnchors = Anchors());
+ return NS_OK;
+}
+
+nsIHTMLCollection*
+nsHTMLDocument::Anchors()
+{
+ if (!mAnchors) {
+ mAnchors = new nsContentList(this, MatchAnchors, nullptr, nullptr);
+ }
+ return mAnchors;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::GetScripts(nsIDOMHTMLCollection** aScripts)
+{
+ NS_ADDREF(*aScripts = Scripts());
+ return NS_OK;
+}
+
+nsIHTMLCollection*
+nsHTMLDocument::Scripts()
+{
+ if (!mScripts) {
+ mScripts = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::script, nsGkAtoms::script);
+ }
+ return mScripts;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::GetCookie(nsAString& aCookie)
+{
+ ErrorResult rv;
+ GetCookie(aCookie, rv);
+ return rv.StealNSResult();
+}
+
+already_AddRefed<nsIChannel>
+nsHTMLDocument::CreateDummyChannelForCookies(nsIURI* aCodebaseURI)
+{
+ // The cookie service reads the privacy status of the channel we pass to it in
+ // order to determine which cookie database to query. In some cases we don't
+ // have a proper channel to hand it to the cookie service though. This
+ // function creates a dummy channel that is not used to load anything, for the
+ // sole purpose of handing it to the cookie service. DO NOT USE THIS CHANNEL
+ // FOR ANY OTHER PURPOSE.
+ MOZ_ASSERT(!mChannel);
+
+ // The following channel is never openend, so it does not matter what
+ // securityFlags we pass; let's follow the principle of least privilege.
+ nsCOMPtr<nsIChannel> channel;
+ NS_NewChannel(getter_AddRefs(channel), aCodebaseURI, this,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+ nsIContentPolicy::TYPE_INVALID);
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel =
+ do_QueryInterface(channel);
+ nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+ nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
+ if (!pbChannel || !loadContext) {
+ return nullptr;
+ }
+ pbChannel->SetPrivate(loadContext->UsePrivateBrowsing());
+ return channel.forget();
+}
+
+void
+nsHTMLDocument::GetCookie(nsAString& aCookie, ErrorResult& rv)
+{
+ aCookie.Truncate(); // clear current cookie in case service fails;
+ // no cookie isn't an error condition.
+
+ if (mDisableCookieAccess) {
+ return;
+ }
+
+ // If the document's sandboxed origin flag is set, access to read cookies
+ // is prohibited.
+ if (mSandboxFlags & SANDBOXED_ORIGIN) {
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ // not having a cookie service isn't an error
+ nsCOMPtr<nsICookieService> service = do_GetService(NS_COOKIESERVICE_CONTRACTID);
+ if (service) {
+ // Get a URI from the document principal. We use the original
+ // codebase in case the codebase was changed by SetDomain
+ nsCOMPtr<nsIURI> codebaseURI;
+ NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));
+
+ if (!codebaseURI) {
+ // Document's principal is not a codebase (may be system), so
+ // can't set cookies
+
+ return;
+ }
+
+ nsCOMPtr<nsIChannel> channel(mChannel);
+ if (!channel) {
+ channel = CreateDummyChannelForCookies(codebaseURI);
+ if (!channel) {
+ return;
+ }
+ }
+
+ nsXPIDLCString cookie;
+ service->GetCookieString(codebaseURI, channel, getter_Copies(cookie));
+ // CopyUTF8toUTF16 doesn't handle error
+ // because it assumes that the input is valid.
+ nsContentUtils::ConvertStringFromEncoding(NS_LITERAL_CSTRING("UTF-8"),
+ cookie, aCookie);
+ }
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::SetCookie(const nsAString& aCookie)
+{
+ ErrorResult rv;
+ SetCookie(aCookie, rv);
+ return rv.StealNSResult();
+}
+
+void
+nsHTMLDocument::SetCookie(const nsAString& aCookie, ErrorResult& rv)
+{
+ if (mDisableCookieAccess) {
+ return;
+ }
+
+ // If the document's sandboxed origin flag is set, access to write cookies
+ // is prohibited.
+ if (mSandboxFlags & SANDBOXED_ORIGIN) {
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ // not having a cookie service isn't an error
+ nsCOMPtr<nsICookieService> service = do_GetService(NS_COOKIESERVICE_CONTRACTID);
+ if (service && mDocumentURI) {
+ // The for getting the URI matches nsNavigator::GetCookieEnabled
+ nsCOMPtr<nsIURI> codebaseURI;
+ NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));
+
+ if (!codebaseURI) {
+ // Document's principal is not a codebase (may be system), so
+ // can't set cookies
+
+ return;
+ }
+
+ nsCOMPtr<nsIChannel> channel(mChannel);
+ if (!channel) {
+ channel = CreateDummyChannelForCookies(codebaseURI);
+ if (!channel) {
+ return;
+ }
+ }
+
+ NS_ConvertUTF16toUTF8 cookie(aCookie);
+ service->SetCookieString(codebaseURI, nullptr, cookie.get(), channel);
+ }
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::Open(const nsAString& aContentTypeOrUrl,
+ const nsAString& aReplaceOrName,
+ const nsAString& aFeatures,
+ JSContext* cx, uint8_t aOptionalArgCount,
+ nsISupports** aReturn)
+{
+ // When called with 3 or more arguments, document.open() calls window.open().
+ if (aOptionalArgCount > 2) {
+ ErrorResult rv;
+ *aReturn = Open(cx, aContentTypeOrUrl, aReplaceOrName, aFeatures,
+ false, rv).take();
+ return rv.StealNSResult();
+ }
+
+ nsString type;
+ if (aOptionalArgCount > 0) {
+ type = aContentTypeOrUrl;
+ } else {
+ type.AssignLiteral("text/html");
+ }
+ nsString replace;
+ if (aOptionalArgCount > 1) {
+ replace = aReplaceOrName;
+ }
+ ErrorResult rv;
+ *aReturn = Open(cx, type, replace, rv).take();
+ return rv.StealNSResult();
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsHTMLDocument::Open(JSContext* /* unused */,
+ const nsAString& aURL,
+ const nsAString& aName,
+ const nsAString& aFeatures,
+ bool aReplace,
+ ErrorResult& rv)
+{
+ NS_ASSERTION(nsContentUtils::CanCallerAccess(static_cast<nsIDOMHTMLDocument*>(this)),
+ "XOW should have caught this!");
+
+ nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow();
+ if (!window) {
+ rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return nullptr;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> outer =
+ nsPIDOMWindowOuter::GetFromCurrentInner(window);
+ if (!outer) {
+ rv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return nullptr;
+ }
+ RefPtr<nsGlobalWindow> win = nsGlobalWindow::Cast(outer);
+ nsCOMPtr<nsPIDOMWindowOuter> newWindow;
+ // XXXbz We ignore aReplace for now.
+ rv = win->OpenJS(aURL, aName, aFeatures, getter_AddRefs(newWindow));
+ return newWindow.forget();
+}
+
+already_AddRefed<nsIDocument>
+nsHTMLDocument::Open(JSContext* cx,
+ const nsAString& aType,
+ const nsAString& aReplace,
+ ErrorResult& rv)
+{
+ // Implements the "When called with two arguments (or fewer)" steps here:
+ // https://html.spec.whatwg.org/multipage/webappapis.html#opening-the-input-stream
+
+ NS_ASSERTION(nsContentUtils::CanCallerAccess(static_cast<nsIDOMHTMLDocument*>(this)),
+ "XOW should have caught this!");
+ if (!IsHTMLDocument() || mDisableDocWrite || !IsMasterDocument()) {
+ // No calling document.open() on XHTML
+ rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ nsAutoCString contentType;
+ contentType.AssignLiteral("text/html");
+
+ nsAutoString type;
+ nsContentUtils::ASCIIToLower(aType, type);
+ nsAutoCString actualType, dummy;
+ NS_ParseRequestContentType(NS_ConvertUTF16toUTF8(type), actualType, dummy);
+ if (!actualType.EqualsLiteral("text/html") &&
+ !type.EqualsLiteral("replace")) {
+ contentType.AssignLiteral("text/plain");
+ }
+
+ // If we already have a parser we ignore the document.open call.
+ if (mParser || mParserAborted) {
+ // The WHATWG spec says: "If the document has an active parser that isn't
+ // a script-created parser, and the insertion point associated with that
+ // parser's input stream is not undefined (that is, it does point to
+ // somewhere in the input stream), then the method does nothing. Abort
+ // these steps and return the Document object on which the method was
+ // invoked."
+ // Note that aborting a parser leaves the parser "active" with its
+ // insertion point "not undefined". We track this using mParserAborted,
+ // because aborting a parser nulls out mParser.
+ nsCOMPtr<nsIDocument> ret = this;
+ return ret.forget();
+ }
+
+ // No calling document.open() without a script global object
+ if (!mScriptGlobalObject) {
+ nsCOMPtr<nsIDocument> ret = this;
+ return ret.forget();
+ }
+
+ nsPIDOMWindowOuter* outer = GetWindow();
+ if (!outer || (GetInnerWindow() != outer->GetCurrentInnerWindow())) {
+ nsCOMPtr<nsIDocument> ret = this;
+ return ret.forget();
+ }
+
+ // check whether we're in the middle of unload. If so, ignore this call.
+ nsCOMPtr<nsIDocShell> shell(mDocumentContainer);
+ if (!shell) {
+ // We won't be able to create a parser anyway.
+ nsCOMPtr<nsIDocument> ret = this;
+ return ret.forget();
+ }
+
+ bool inUnload;
+ shell->GetIsInUnload(&inUnload);
+ if (inUnload) {
+ nsCOMPtr<nsIDocument> ret = this;
+ return ret.forget();
+ }
+
+ // Note: We want to use GetEntryDocument here because this document
+ // should inherit the security information of the document that's opening us,
+ // (since if it's secure, then it's presumably trusted).
+ nsCOMPtr<nsIDocument> callerDoc = GetEntryDocument();
+ if (!callerDoc) {
+ // If we're called from C++ or in some other way without an originating
+ // document we can't do a document.open w/o changing the principal of the
+ // document to something like about:blank (as that's the only sane thing to
+ // do when we don't know the origin of this call), and since we can't
+ // change the principals of a document for security reasons we'll have to
+ // refuse to go ahead with this call.
+
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ // Grab a reference to the calling documents security info (if any)
+ // and URIs as they may be lost in the call to Reset().
+ nsCOMPtr<nsISupports> securityInfo = callerDoc->GetSecurityInfo();
+ nsCOMPtr<nsIURI> uri = callerDoc->GetDocumentURI();
+ nsCOMPtr<nsIURI> baseURI = callerDoc->GetBaseURI();
+ nsCOMPtr<nsIPrincipal> callerPrincipal = callerDoc->NodePrincipal();
+ nsCOMPtr<nsIChannel> callerChannel = callerDoc->GetChannel();
+
+ // We're called from script. Make sure the script is from the same
+ // origin, not just that the caller can access the document. This is
+ // needed to keep document principals from ever changing, which is
+ // needed because of the way we use our XOW code, and is a sane
+ // thing to do anyways.
+
+ bool equals = false;
+ if (NS_FAILED(callerPrincipal->Equals(NodePrincipal(), &equals)) ||
+ !equals) {
+
+#ifdef DEBUG
+ nsCOMPtr<nsIURI> callerDocURI = callerDoc->GetDocumentURI();
+ nsCOMPtr<nsIURI> thisURI = nsIDocument::GetDocumentURI();
+ printf("nsHTMLDocument::Open callerDoc %s this %s\n",
+ callerDocURI ? callerDocURI->GetSpecOrDefault().get() : "",
+ thisURI ? thisURI->GetSpecOrDefault().get() : "");
+#endif
+
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ // Stop current loads targeted at the window this document is in.
+ if (mScriptGlobalObject) {
+ nsCOMPtr<nsIContentViewer> cv;
+ shell->GetContentViewer(getter_AddRefs(cv));
+
+ if (cv) {
+ bool okToUnload;
+ if (NS_SUCCEEDED(cv->PermitUnload(&okToUnload)) && !okToUnload) {
+ // We don't want to unload, so stop here, but don't throw an
+ // exception.
+ nsCOMPtr<nsIDocument> ret = this;
+ return ret.forget();
+ }
+ }
+
+ nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(shell));
+ webnav->Stop(nsIWebNavigation::STOP_NETWORK);
+
+ // The Stop call may have cancelled the onload blocker request or prevented
+ // it from getting added, so we need to make sure it gets added to the
+ // document again otherwise the document could have a non-zero onload block
+ // count without the onload blocker request being in the loadgroup.
+ EnsureOnloadBlocker();
+ }
+
+ // The open occurred after the document finished loading.
+ // So we reset the document and then reinitialize it.
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ callerDoc,
+ nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
+ nsIContentPolicy::TYPE_OTHER,
+ group);
+
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ if (callerChannel) {
+ nsLoadFlags callerLoadFlags;
+ rv = callerChannel->GetLoadFlags(&callerLoadFlags);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ nsLoadFlags loadFlags;
+ rv = channel->GetLoadFlags(&loadFlags);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ loadFlags |= callerLoadFlags & nsIRequest::INHIBIT_PERSISTENT_CACHING;
+
+ rv = channel->SetLoadFlags(loadFlags);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ // If the user has allowed mixed content on the rootDoc, then we should propogate it
+ // down to the new document channel.
+ bool rootHasSecureConnection = false;
+ bool allowMixedContent = false;
+ bool isDocShellRoot = false;
+ nsresult rvalue = shell->GetAllowMixedContentAndConnectionData(&rootHasSecureConnection, &allowMixedContent, &isDocShellRoot);
+ if (NS_SUCCEEDED(rvalue) && allowMixedContent && isDocShellRoot) {
+ shell->SetMixedContentChannel(channel);
+ }
+ }
+
+ // Before we reset the doc notify the globalwindow of the change,
+ // but only if we still have a window (i.e. our window object the
+ // current inner window in our outer window).
+
+ // Hold onto ourselves on the offchance that we're down to one ref
+ nsCOMPtr<nsIDocument> kungFuDeathGrip = this;
+
+ if (nsPIDOMWindowInner *window = GetInnerWindow()) {
+ // Remember the old scope in case the call to SetNewDocument changes it.
+ nsCOMPtr<nsIScriptGlobalObject> oldScope(do_QueryReferent(mScopeObject));
+
+#ifdef DEBUG
+ bool willReparent = mWillReparent;
+ mWillReparent = true;
+
+ nsDocument* templateContentsOwner =
+ static_cast<nsDocument*>(mTemplateContentsOwner.get());
+
+ if (templateContentsOwner) {
+ templateContentsOwner->mWillReparent = true;
+ }
+#endif
+
+ // Per spec, we pass false here so that a new Window is created.
+ rv = window->SetNewDocument(this, nullptr,
+ /* aForceReuseInnerWindow */ false);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+#ifdef DEBUG
+ if (templateContentsOwner) {
+ templateContentsOwner->mWillReparent = willReparent;
+ }
+
+ mWillReparent = willReparent;
+#endif
+
+ // Now make sure we're not flagged as the initial document anymore, now
+ // that we've had stuff done to us. From now on, if anyone tries to
+ // document.open() us, they get a new inner window.
+ SetIsInitialDocument(false);
+
+ nsCOMPtr<nsIScriptGlobalObject> newScope(do_QueryReferent(mScopeObject));
+ JS::Rooted<JSObject*> wrapper(cx, GetWrapper());
+ if (oldScope && newScope != oldScope && wrapper) {
+ JSAutoCompartment ac(cx, wrapper);
+ rv = mozilla::dom::ReparentWrapper(cx, wrapper);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ // Also reparent the template contents owner document
+ // because its global is set to the same as this document.
+ if (mTemplateContentsOwner) {
+ JS::Rooted<JSObject*> contentsOwnerWrapper(cx,
+ mTemplateContentsOwner->GetWrapper());
+ if (contentsOwnerWrapper) {
+ rv = mozilla::dom::ReparentWrapper(cx, contentsOwnerWrapper);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ }
+ }
+ }
+ }
+
+ mDidDocumentOpen = true;
+
+ // Call Reset(), this will now do the full reset
+ Reset(channel, group);
+ if (baseURI) {
+ mDocumentBaseURI = baseURI;
+ }
+
+ // Store the security info of the caller now that we're done
+ // resetting the document.
+ mSecurityInfo = securityInfo;
+
+ mParserAborted = false;
+ mParser = nsHtml5Module::NewHtml5Parser();
+ nsHtml5Module::Initialize(mParser, this, uri, shell, channel);
+ if (mReferrerPolicySet) {
+ // CSP may have set the referrer policy, so a speculative parser should
+ // start with the new referrer policy.
+ nsHtml5TreeOpExecutor* executor = nullptr;
+ executor = static_cast<nsHtml5TreeOpExecutor*> (mParser->GetContentSink());
+ if (executor && mReferrerPolicySet) {
+ executor->SetSpeculationReferrerPolicy(static_cast<ReferrerPolicy>(mReferrerPolicy));
+ }
+ }
+
+ // This will be propagated to the parser when someone actually calls write()
+ SetContentTypeInternal(contentType);
+
+ // Prepare the docshell and the document viewer for the impending
+ // out of band document.write()
+ shell->PrepareForNewContentModel();
+
+ // Now check whether we were opened with a "replace" argument. If
+ // so, we need to tell the docshell to not create a new history
+ // entry for this load. Otherwise, make sure that we're doing a normal load,
+ // not whatever type of load was previously done on this docshell.
+ shell->SetLoadType(aReplace.LowerCaseEqualsLiteral("replace") ?
+ LOAD_NORMAL_REPLACE : LOAD_NORMAL);
+
+ nsCOMPtr<nsIContentViewer> cv;
+ shell->GetContentViewer(getter_AddRefs(cv));
+ if (cv) {
+ cv->LoadStart(this);
+ }
+
+ // Add a wyciwyg channel request into the document load group
+ NS_ASSERTION(!mWyciwygChannel, "nsHTMLDocument::Open(): wyciwyg "
+ "channel already exists!");
+
+ // In case the editor is listening and will see the new channel
+ // being added, make sure mWriteLevel is non-zero so that the editor
+ // knows that document.open/write/close() is being called on this
+ // document.
+ ++mWriteLevel;
+
+ CreateAndAddWyciwygChannel();
+
+ --mWriteLevel;
+
+ SetReadyStateInternal(nsIDocument::READYSTATE_LOADING);
+
+ // After changing everything around, make sure that the principal on the
+ // document's compartment exactly matches NodePrincipal().
+ DebugOnly<JSObject*> wrapper = GetWrapperPreserveColor();
+ MOZ_ASSERT_IF(wrapper,
+ JS_GetCompartmentPrincipals(js::GetObjectCompartment(wrapper)) ==
+ nsJSPrincipals::get(NodePrincipal()));
+
+ return kungFuDeathGrip.forget();
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::Clear()
+{
+ // This method has been deprecated
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::Close()
+{
+ ErrorResult rv;
+ Close(rv);
+ return rv.StealNSResult();
+}
+
+void
+nsHTMLDocument::Close(ErrorResult& rv)
+{
+ if (!IsHTMLDocument()) {
+ // No calling document.close() on XHTML!
+
+ rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (!mParser || !mParser->IsScriptCreated()) {
+ return;
+ }
+
+ ++mWriteLevel;
+ rv = (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(
+ EmptyString(), nullptr, GetContentTypeInternal(), true);
+ --mWriteLevel;
+
+ // Even if that Parse() call failed, do the rest of this method
+
+ // XXX Make sure that all the document.written content is
+ // reflowed. We should remove this call once we change
+ // nsHTMLDocument::OpenCommon() so that it completely destroys the
+ // earlier document's content and frame hierarchy. Right now, it
+ // re-uses the earlier document's root content object and
+ // corresponding frame objects. These re-used frame objects think
+ // that they have already been reflowed, so they drop initial
+ // reflows. For certain cases of document.written content, like a
+ // frameset document, the dropping of the initial reflow means
+ // that we end up in document.close() without appended any reflow
+ // commands to the reflow queue and, consequently, without adding
+ // the dummy layout request to the load group. Since the dummy
+ // layout request is not added to the load group, the onload
+ // handler of the frameset fires before the frames get reflowed
+ // and loaded. That is the long explanation for why we need this
+ // one line of code here!
+ // XXXbz as far as I can tell this may not be needed anymore; all
+ // the testcases in bug 57636 pass without this line... Leaving
+ // it be for now, though. In any case, there's no reason to do
+ // this if we have no presshell, since in that case none of the
+ // above about reusing frames applies.
+ //
+ // XXXhsivonen keeping this around for bug 577508 / 253951 still :-(
+ if (GetShell()) {
+ FlushPendingNotifications(Flush_Layout);
+ }
+
+ // Removing the wyciwygChannel here is wrong when document.close() is
+ // called from within the document itself. However, legacy requires the
+ // channel to be removed here. Otherwise, the load event never fires.
+ NS_ASSERTION(mWyciwygChannel, "nsHTMLDocument::Close(): Trying to remove "
+ "nonexistent wyciwyg channel!");
+ RemoveWyciwygChannel();
+ NS_ASSERTION(!mWyciwygChannel, "nsHTMLDocument::Close(): "
+ "nsIWyciwygChannel could not be removed!");
+}
+
+void
+nsHTMLDocument::WriteCommon(JSContext *cx,
+ const Sequence<nsString>& aText,
+ bool aNewlineTerminate,
+ mozilla::ErrorResult& rv)
+{
+ // Fast path the common case
+ if (aText.Length() == 1) {
+ rv = WriteCommon(cx, aText[0], aNewlineTerminate);
+ } else {
+ // XXXbz it would be nice if we could pass all the strings to the parser
+ // without having to do all this copying and then ask it to start
+ // parsing....
+ nsString text;
+ for (uint32_t i = 0; i < aText.Length(); ++i) {
+ text.Append(aText[i]);
+ }
+ rv = WriteCommon(cx, text, aNewlineTerminate);
+ }
+}
+
+nsresult
+nsHTMLDocument::WriteCommon(JSContext *cx,
+ const nsAString& aText,
+ bool aNewlineTerminate)
+{
+ mTooDeepWriteRecursion =
+ (mWriteLevel > NS_MAX_DOCUMENT_WRITE_DEPTH || mTooDeepWriteRecursion);
+ NS_ENSURE_STATE(!mTooDeepWriteRecursion);
+
+ if (!IsHTMLDocument() || mDisableDocWrite || !IsMasterDocument()) {
+ // No calling document.write*() on XHTML!
+
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ if (mParserAborted) {
+ // Hixie says aborting the parser doesn't undefine the insertion point.
+ // However, since we null out mParser in that case, we track the
+ // theoretically defined insertion point using mParserAborted.
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+
+ void *key = GenerateParserKey();
+ if (mParser && !mParser->IsInsertionPointDefined()) {
+ if (mExternalScriptsBeingEvaluated) {
+ // Instead of implying a call to document.open(), ignore the call.
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM Events"), this,
+ nsContentUtils::eDOM_PROPERTIES,
+ "DocumentWriteIgnored",
+ nullptr, 0,
+ mDocumentURI);
+ return NS_OK;
+ }
+ mParser->Terminate();
+ NS_ASSERTION(!mParser, "mParser should have been null'd out");
+ }
+
+ if (!mParser) {
+ if (mExternalScriptsBeingEvaluated) {
+ // Instead of implying a call to document.open(), ignore the call.
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM Events"), this,
+ nsContentUtils::eDOM_PROPERTIES,
+ "DocumentWriteIgnored",
+ nullptr, 0,
+ mDocumentURI);
+ return NS_OK;
+ }
+ nsCOMPtr<nsISupports> ignored;
+ rv = Open(NS_LITERAL_STRING("text/html"), EmptyString(), EmptyString(), cx,
+ 1, getter_AddRefs(ignored));
+
+ // If Open() fails, or if it didn't create a parser (as it won't
+ // if the user chose to not discard the current document through
+ // onbeforeunload), don't write anything.
+ if (NS_FAILED(rv) || !mParser) {
+ return rv;
+ }
+ MOZ_ASSERT(!JS_IsExceptionPending(cx),
+ "Open() succeeded but JS exception is pending");
+ }
+
+ static NS_NAMED_LITERAL_STRING(new_line, "\n");
+
+ // Save the data in cache if the write isn't from within the doc
+ if (mWyciwygChannel && !key) {
+ if (!aText.IsEmpty()) {
+ mWyciwygChannel->WriteToCacheEntry(aText);
+ }
+
+ if (aNewlineTerminate) {
+ mWyciwygChannel->WriteToCacheEntry(new_line);
+ }
+ }
+
+ ++mWriteLevel;
+
+ // This could be done with less code, but for performance reasons it
+ // makes sense to have the code for two separate Parse() calls here
+ // since the concatenation of strings costs more than we like. And
+ // why pay that price when we don't need to?
+ if (aNewlineTerminate) {
+ rv = (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(
+ aText + new_line, key, GetContentTypeInternal(), false);
+ } else {
+ rv = (static_cast<nsHtml5Parser*>(mParser.get()))->Parse(
+ aText, key, GetContentTypeInternal(), false);
+ }
+
+ --mWriteLevel;
+
+ mTooDeepWriteRecursion = (mWriteLevel != 0 && mTooDeepWriteRecursion);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::Write(const nsAString& aText, JSContext *cx)
+{
+ return WriteCommon(cx, aText, false);
+}
+
+void
+nsHTMLDocument::Write(JSContext* cx, const Sequence<nsString>& aText,
+ ErrorResult& rv)
+{
+ WriteCommon(cx, aText, false, rv);
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::Writeln(const nsAString& aText, JSContext *cx)
+{
+ return WriteCommon(cx, aText, true);
+}
+
+void
+nsHTMLDocument::Writeln(JSContext* cx, const Sequence<nsString>& aText,
+ ErrorResult& rv)
+{
+ WriteCommon(cx, aText, true, rv);
+}
+
+bool
+nsHTMLDocument::MatchNameAttribute(nsIContent* aContent, int32_t aNamespaceID,
+ nsIAtom* aAtom, void* aData)
+{
+ NS_PRECONDITION(aContent, "Must have content node to work with!");
+ nsString* elementName = static_cast<nsString*>(aData);
+ return
+ aContent->GetNameSpaceID() == kNameSpaceID_XHTML &&
+ aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+ *elementName, eCaseMatters);
+}
+
+/* static */
+void*
+nsHTMLDocument::UseExistingNameString(nsINode* aRootNode, const nsString* aName)
+{
+ return const_cast<nsString*>(aName);
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::GetElementsByName(const nsAString& aElementName,
+ nsIDOMNodeList** aReturn)
+{
+ *aReturn = GetElementsByName(aElementName).take();
+ return NS_OK;
+}
+
+void
+nsHTMLDocument::AddedForm()
+{
+ ++mNumForms;
+}
+
+void
+nsHTMLDocument::RemovedForm()
+{
+ --mNumForms;
+}
+
+int32_t
+nsHTMLDocument::GetNumFormsSynchronous()
+{
+ return mNumForms;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::GetAlinkColor(nsAString& aAlinkColor)
+{
+ aAlinkColor.Truncate();
+
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->GetALink(aAlinkColor);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::SetAlinkColor(const nsAString& aAlinkColor)
+{
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->SetALink(aAlinkColor);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::GetLinkColor(nsAString& aLinkColor)
+{
+ aLinkColor.Truncate();
+
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->GetLink(aLinkColor);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::SetLinkColor(const nsAString& aLinkColor)
+{
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->SetLink(aLinkColor);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::GetVlinkColor(nsAString& aVlinkColor)
+{
+ aVlinkColor.Truncate();
+
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->GetVLink(aVlinkColor);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::SetVlinkColor(const nsAString& aVlinkColor)
+{
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->SetVLink(aVlinkColor);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::GetBgColor(nsAString& aBgColor)
+{
+ aBgColor.Truncate();
+
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->GetBgColor(aBgColor);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::SetBgColor(const nsAString& aBgColor)
+{
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->SetBgColor(aBgColor);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::GetFgColor(nsAString& aFgColor)
+{
+ aFgColor.Truncate();
+
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->GetText(aFgColor);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::SetFgColor(const nsAString& aFgColor)
+{
+ HTMLBodyElement* body = GetBodyElement();
+ if (body) {
+ body->SetText(aFgColor);
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsHTMLDocument::GetEmbeds(nsIDOMHTMLCollection** aEmbeds)
+{
+ NS_ADDREF(*aEmbeds = Embeds());
+ return NS_OK;
+}
+
+nsIHTMLCollection*
+nsHTMLDocument::Embeds()
+{
+ if (!mEmbeds) {
+ mEmbeds = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::embed, nsGkAtoms::embed);
+ }
+ return mEmbeds;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::GetSelection(nsISelection** aReturn)
+{
+ ErrorResult rv;
+ NS_IF_ADDREF(*aReturn = GetSelection(rv));
+ return rv.StealNSResult();
+}
+
+Selection*
+nsHTMLDocument::GetSelection(ErrorResult& aRv)
+{
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
+ if (!window) {
+ return nullptr;
+ }
+
+ NS_ASSERTION(window->IsInnerWindow(), "Should have inner window here!");
+ if (!window->IsCurrentInnerWindow()) {
+ return nullptr;
+ }
+
+ return nsGlobalWindow::Cast(window)->GetSelection(aRv);
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::CaptureEvents()
+{
+ WarnOnceAbout(nsIDocument::eUseOfCaptureEvents);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::ReleaseEvents()
+{
+ WarnOnceAbout(nsIDocument::eUseOfReleaseEvents);
+ return NS_OK;
+}
+
+// Mapped to document.embeds for NS4 compatibility
+NS_IMETHODIMP
+nsHTMLDocument::GetPlugins(nsIDOMHTMLCollection** aPlugins)
+{
+ *aPlugins = nullptr;
+
+ return GetEmbeds(aPlugins);
+}
+
+nsIHTMLCollection*
+nsHTMLDocument::Plugins()
+{
+ return Embeds();
+}
+
+nsISupports*
+nsHTMLDocument::ResolveName(const nsAString& aName, nsWrapperCache **aCache)
+{
+ nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aName);
+ if (!entry) {
+ *aCache = nullptr;
+ return nullptr;
+ }
+
+ nsBaseContentList *list = entry->GetNameContentList();
+ uint32_t length = list ? list->Length() : 0;
+
+ if (length > 0) {
+ if (length == 1) {
+ // Only one element in the list, return the element instead of returning
+ // the list.
+ nsIContent *node = list->Item(0);
+ *aCache = node;
+ return node;
+ }
+
+ // The list contains more than one element, return the whole list.
+ *aCache = list;
+ return list;
+ }
+
+ // No named items were found, see if there's one registerd by id for aName.
+ Element *e = entry->GetIdElement();
+
+ if (e && nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(e)) {
+ *aCache = e;
+ return e;
+ }
+
+ *aCache = nullptr;
+ return nullptr;
+}
+
+void
+nsHTMLDocument::NamedGetter(JSContext* cx, const nsAString& aName, bool& aFound,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& rv)
+{
+ nsWrapperCache* cache;
+ nsISupports* supp = ResolveName(aName, &cache);
+ if (!supp) {
+ aFound = false;
+ aRetval.set(nullptr);
+ return;
+ }
+
+ JS::Rooted<JS::Value> val(cx);
+ if (!dom::WrapObject(cx, supp, cache, nullptr, &val)) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ aFound = true;
+ aRetval.set(&val.toObject());
+}
+
+void
+nsHTMLDocument::GetSupportedNames(nsTArray<nsString>& aNames)
+{
+ for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) {
+ nsIdentifierMapEntry* entry = iter.Get();
+ if (entry->HasNameElement() ||
+ entry->HasIdElementExposedAsHTMLDocumentProperty()) {
+ aNames.AppendElement(entry->GetKey());
+ }
+ }
+}
+
+//----------------------------
+
+// forms related stuff
+
+NS_IMETHODIMP
+nsHTMLDocument::GetForms(nsIDOMHTMLCollection** aForms)
+{
+ NS_ADDREF(*aForms = nsHTMLDocument::GetForms());
+ return NS_OK;
+}
+
+nsContentList*
+nsHTMLDocument::GetForms()
+{
+ if (!mForms) {
+ mForms = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::form, nsGkAtoms::form);
+ }
+
+ return mForms;
+}
+
+static bool MatchFormControls(nsIContent* aContent, int32_t aNamespaceID,
+ nsIAtom* aAtom, void* aData)
+{
+ return aContent->IsNodeOfType(nsIContent::eHTML_FORM_CONTROL);
+}
+
+nsContentList*
+nsHTMLDocument::GetFormControls()
+{
+ if (!mFormControls) {
+ mFormControls = new nsContentList(this, MatchFormControls, nullptr, nullptr);
+ }
+
+ return mFormControls;
+}
+
+nsresult
+nsHTMLDocument::CreateAndAddWyciwygChannel(void)
+{
+ nsresult rv = NS_OK;
+ nsAutoCString url, originalSpec;
+
+ mDocumentURI->GetSpec(originalSpec);
+
+ // Generate the wyciwyg url
+ url = NS_LITERAL_CSTRING("wyciwyg://")
+ + nsPrintfCString("%d", gWyciwygSessionCnt++)
+ + NS_LITERAL_CSTRING("/")
+ + originalSpec;
+
+ nsCOMPtr<nsIURI> wcwgURI;
+ NS_NewURI(getter_AddRefs(wcwgURI), url);
+
+ // Create the nsIWyciwygChannel to store out-of-band
+ // document.write() script to cache
+ nsCOMPtr<nsIChannel> channel;
+ // Create a wyciwyg Channel
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ wcwgURI,
+ NodePrincipal(),
+ nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+ loadInfo->SetPrincipalToInherit(NodePrincipal());
+
+ mWyciwygChannel = do_QueryInterface(channel);
+
+ mWyciwygChannel->SetSecurityInfo(mSecurityInfo);
+
+ // Note: we want to treat this like a "previous document" hint so that,
+ // e.g. a <meta> tag in the document.write content can override it.
+ SetDocumentCharacterSetSource(kCharsetFromHintPrevDoc);
+ mWyciwygChannel->SetCharsetAndSource(kCharsetFromHintPrevDoc,
+ GetDocumentCharacterSet());
+
+ // Inherit load flags from the original document's channel
+ channel->SetLoadFlags(mLoadFlags);
+
+ nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
+
+ // Use the Parent document's loadgroup to trigger load notifications
+ if (loadGroup && channel) {
+ rv = channel->SetLoadGroup(loadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsLoadFlags loadFlags = 0;
+ channel->GetLoadFlags(&loadFlags);
+ loadFlags |= nsIChannel::LOAD_DOCUMENT_URI;
+ channel->SetLoadFlags(loadFlags);
+
+ channel->SetOriginalURI(wcwgURI);
+
+ rv = loadGroup->AddRequest(mWyciwygChannel, nullptr);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to add request to load group.");
+ }
+
+ return rv;
+}
+
+nsresult
+nsHTMLDocument::RemoveWyciwygChannel(void)
+{
+ nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
+
+ // note there can be a write request without a load group if
+ // this is a synchronously constructed about:blank document
+ if (loadGroup && mWyciwygChannel) {
+ mWyciwygChannel->CloseCacheEntry(NS_OK);
+ loadGroup->RemoveRequest(mWyciwygChannel, nullptr, NS_OK);
+ }
+
+ mWyciwygChannel = nullptr;
+
+ return NS_OK;
+}
+
+void *
+nsHTMLDocument::GenerateParserKey(void)
+{
+ if (!mScriptLoader) {
+ // If we don't have a script loader, then the parser probably isn't parsing
+ // anything anyway, so just return null.
+ return nullptr;
+ }
+
+ // The script loader provides us with the currently executing script element,
+ // which is guaranteed to be unique per script.
+ nsIScriptElement* script = mScriptLoader->GetCurrentParserInsertedScript();
+ if (script && mParser && mParser->IsScriptCreated()) {
+ nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
+ if (creatorParser != mParser) {
+ // Make scripts that aren't inserted by the active parser of this document
+ // participate in the context of the script that document.open()ed
+ // this document.
+ return nullptr;
+ }
+ }
+ return script;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::GetDesignMode(nsAString& aDesignMode)
+{
+ if (HasFlag(NODE_IS_EDITABLE)) {
+ aDesignMode.AssignLiteral("on");
+ }
+ else {
+ aDesignMode.AssignLiteral("off");
+ }
+ return NS_OK;
+}
+
+void
+nsHTMLDocument::MaybeEditingStateChanged()
+{
+ if (!mPendingMaybeEditingStateChanged &&
+ mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
+ if (nsContentUtils::IsSafeToRunScript()) {
+ EditingStateChanged();
+ } else if (!mInDestructor) {
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod(this, &nsHTMLDocument::MaybeEditingStateChanged));
+ }
+ }
+}
+
+void
+nsHTMLDocument::EndUpdate(nsUpdateType aUpdateType)
+{
+ const bool reset = !mPendingMaybeEditingStateChanged;
+ mPendingMaybeEditingStateChanged = true;
+ nsDocument::EndUpdate(aUpdateType);
+ if (reset) {
+ mPendingMaybeEditingStateChanged = false;
+ }
+ MaybeEditingStateChanged();
+}
+
+
+// Helper class, used below in ChangeContentEditableCount().
+class DeferredContentEditableCountChangeEvent : public Runnable
+{
+public:
+ DeferredContentEditableCountChangeEvent(nsHTMLDocument *aDoc,
+ nsIContent *aElement)
+ : mDoc(aDoc)
+ , mElement(aElement)
+ {
+ }
+
+ NS_IMETHOD Run() override {
+ if (mElement && mElement->OwnerDoc() == mDoc) {
+ mDoc->DeferredContentEditableCountChange(mElement);
+ }
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsHTMLDocument> mDoc;
+ nsCOMPtr<nsIContent> mElement;
+};
+
+nsresult
+nsHTMLDocument::ChangeContentEditableCount(nsIContent *aElement,
+ int32_t aChange)
+{
+ NS_ASSERTION(int32_t(mContentEditableCount) + aChange >= 0,
+ "Trying to decrement too much.");
+
+ mContentEditableCount += aChange;
+
+ nsContentUtils::AddScriptRunner(
+ new DeferredContentEditableCountChangeEvent(this, aElement));
+
+ return NS_OK;
+}
+
+void
+nsHTMLDocument::DeferredContentEditableCountChange(nsIContent *aElement)
+{
+ if (mParser ||
+ (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
+ return;
+ }
+
+ EditingState oldState = mEditingState;
+
+ nsresult rv = EditingStateChanged();
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (oldState == mEditingState && mEditingState == eContentEditable) {
+ // We just changed the contentEditable state of a node, we need to reset
+ // the spellchecking state of that node.
+ nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement);
+ if (node) {
+ nsPIDOMWindowOuter *window = GetWindow();
+ if (!window)
+ return;
+
+ nsIDocShell *docshell = window->GetDocShell();
+ if (!docshell)
+ return;
+
+ nsCOMPtr<nsIEditor> editor;
+ docshell->GetEditor(getter_AddRefs(editor));
+ if (editor) {
+ RefPtr<nsRange> range = new nsRange(aElement);
+ rv = range->SelectNode(node);
+ if (NS_FAILED(rv)) {
+ // The node might be detached from the document at this point,
+ // which would cause this call to fail. In this case, we can
+ // safely ignore the contenteditable count change.
+ return;
+ }
+
+ nsCOMPtr<nsIInlineSpellChecker> spellChecker;
+ rv = editor->GetInlineSpellChecker(false,
+ getter_AddRefs(spellChecker));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (spellChecker) {
+ rv = spellChecker->SpellCheckRange(range);
+ }
+ }
+ }
+ }
+}
+
+HTMLAllCollection*
+nsHTMLDocument::All()
+{
+ if (!mAll) {
+ mAll = new HTMLAllCollection(this);
+ }
+ return mAll;
+}
+
+static void
+NotifyEditableStateChange(nsINode *aNode, nsIDocument *aDocument)
+{
+ for (nsIContent* child = aNode->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->IsElement()) {
+ child->AsElement()->UpdateState(true);
+ }
+ NotifyEditableStateChange(child, aDocument);
+ }
+}
+
+void
+nsHTMLDocument::TearingDownEditor(nsIEditor *aEditor)
+{
+ if (IsEditingOn()) {
+ EditingState oldState = mEditingState;
+ mEditingState = eTearingDown;
+
+ nsCOMPtr<nsIPresShell> presShell = GetShell();
+ if (!presShell)
+ return;
+
+ nsTArray<RefPtr<StyleSheet>> agentSheets;
+ presShell->GetAgentStyleSheets(agentSheets);
+
+ auto cache = nsLayoutStylesheetCache::For(GetStyleBackendType());
+
+ agentSheets.RemoveElement(cache->ContentEditableSheet());
+ if (oldState == eDesignMode)
+ agentSheets.RemoveElement(cache->DesignModeSheet());
+
+ presShell->SetAgentStyleSheets(agentSheets);
+
+ presShell->RestyleForCSSRuleChanges();
+ }
+}
+
+nsresult
+nsHTMLDocument::TurnEditingOff()
+{
+ NS_ASSERTION(mEditingState != eOff, "Editing is already off.");
+
+ nsPIDOMWindowOuter *window = GetWindow();
+ if (!window)
+ return NS_ERROR_FAILURE;
+
+ nsIDocShell *docshell = window->GetDocShell();
+ if (!docshell)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIEditingSession> editSession;
+ nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // turn editing off
+ rv = editSession->TearDownEditorOnWindow(window);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mEditingState = eOff;
+
+ return NS_OK;
+}
+
+static bool HasPresShell(nsPIDOMWindowOuter *aWindow)
+{
+ nsIDocShell *docShell = aWindow->GetDocShell();
+ if (!docShell)
+ return false;
+ return docShell->GetPresShell() != nullptr;
+}
+
+nsresult
+nsHTMLDocument::SetEditingState(EditingState aState)
+{
+ mEditingState = aState;
+ return NS_OK;
+}
+
+nsresult
+nsHTMLDocument::EditingStateChanged()
+{
+ if (mRemovedFromDocShell) {
+ return NS_OK;
+ }
+
+ if (mEditingState == eSettingUp || mEditingState == eTearingDown) {
+ // XXX We shouldn't recurse
+ return NS_OK;
+ }
+
+ bool designMode = HasFlag(NODE_IS_EDITABLE);
+ EditingState newState = designMode ? eDesignMode :
+ (mContentEditableCount > 0 ? eContentEditable : eOff);
+ if (mEditingState == newState) {
+ // No changes in editing mode.
+ return NS_OK;
+ }
+
+ if (newState == eOff) {
+ // Editing is being turned off.
+ nsAutoScriptBlocker scriptBlocker;
+ NotifyEditableStateChange(this, this);
+ return TurnEditingOff();
+ }
+
+ // Flush out style changes on our _parent_ document, if any, so that
+ // our check for a presshell won't get stale information.
+ if (mParentDocument) {
+ mParentDocument->FlushPendingNotifications(Flush_Style);
+ }
+
+ // get editing session, make sure this is a strong reference so the
+ // window can't get deleted during the rest of this call.
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
+ if (!window)
+ return NS_ERROR_FAILURE;
+
+ nsIDocShell *docshell = window->GetDocShell();
+ if (!docshell)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIEditingSession> editSession;
+ nsresult rv = docshell->GetEditingSession(getter_AddRefs(editSession));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIEditor> existingEditor;
+ editSession->GetEditorForWindow(window, getter_AddRefs(existingEditor));
+ if (existingEditor) {
+ // We might already have an editor if it was set up for mail, let's see
+ // if this is actually the case.
+#ifdef DEBUG
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(existingEditor);
+ MOZ_ASSERT(htmlEditor, "If we have an editor, it must be an HTML editor");
+#endif
+ uint32_t flags = 0;
+ existingEditor->GetFlags(&flags);
+ if (flags & nsIPlaintextEditor::eEditorMailMask) {
+ // We already have a mail editor, then we should not attempt to create
+ // another one.
+ return NS_OK;
+ }
+ }
+
+ if (!HasPresShell(window)) {
+ // We should not make the window editable or setup its editor.
+ // It's probably style=display:none.
+ return NS_OK;
+ }
+
+ bool makeWindowEditable = mEditingState == eOff;
+ bool updateState = false;
+ bool spellRecheckAll = false;
+ bool putOffToRemoveScriptBlockerUntilModifyingEditingState = false;
+ nsCOMPtr<nsIEditor> editor;
+
+ {
+ EditingState oldState = mEditingState;
+ nsAutoEditingState push(this, eSettingUp);
+
+ nsCOMPtr<nsIPresShell> presShell = GetShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ // Before making this window editable, we need to modify UA style sheet
+ // because new style may change whether focused element will be focusable
+ // or not.
+ nsTArray<RefPtr<StyleSheet>> agentSheets;
+ rv = presShell->GetAgentStyleSheets(agentSheets);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto cache = nsLayoutStylesheetCache::For(GetStyleBackendType());
+
+ StyleSheet* contentEditableSheet = cache->ContentEditableSheet();
+
+ if (!agentSheets.Contains(contentEditableSheet)) {
+ agentSheets.AppendElement(contentEditableSheet);
+ }
+
+ // Should we update the editable state of all the nodes in the document? We
+ // need to do this when the designMode value changes, as that overrides
+ // specific states on the elements.
+ if (designMode) {
+ // designMode is being turned on (overrides contentEditable).
+ StyleSheet* designModeSheet = cache->DesignModeSheet();
+ if (!agentSheets.Contains(designModeSheet)) {
+ agentSheets.AppendElement(designModeSheet);
+ }
+
+ updateState = true;
+ spellRecheckAll = oldState == eContentEditable;
+ }
+ else if (oldState == eDesignMode) {
+ // designMode is being turned off (contentEditable is still on).
+ agentSheets.RemoveElement(cache->DesignModeSheet());
+ updateState = true;
+ }
+
+ rv = presShell->SetAgentStyleSheets(agentSheets);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ presShell->RestyleForCSSRuleChanges();
+
+ // Adjust focused element with new style but blur event shouldn't be fired
+ // until mEditingState is modified with newState.
+ nsAutoScriptBlocker scriptBlocker;
+ if (designMode) {
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsIContent* focusedContent =
+ nsFocusManager::GetFocusedDescendant(window, false,
+ getter_AddRefs(focusedWindow));
+ if (focusedContent) {
+ nsIFrame* focusedFrame = focusedContent->GetPrimaryFrame();
+ bool clearFocus = focusedFrame ? !focusedFrame->IsFocusable() :
+ !focusedContent->IsFocusable();
+ if (clearFocus) {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ fm->ClearFocus(window);
+ // If we need to dispatch blur event, we should put off after
+ // modifying mEditingState since blur event handler may change
+ // designMode state again.
+ putOffToRemoveScriptBlockerUntilModifyingEditingState = true;
+ }
+ }
+ }
+ }
+
+ if (makeWindowEditable) {
+ // Editing is being turned on (through designMode or contentEditable)
+ // Turn on editor.
+ // XXX This can cause flushing which can change the editing state, so make
+ // sure to avoid recursing.
+ rv = editSession->MakeWindowEditable(window, "html", false, false,
+ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // XXX Need to call TearDownEditorOnWindow for all failures.
+ docshell->GetEditor(getter_AddRefs(editor));
+ if (!editor)
+ return NS_ERROR_FAILURE;
+
+ // If we're entering the design mode, put the selection at the beginning of
+ // the document for compatibility reasons.
+ if (designMode && oldState == eOff) {
+ editor->BeginningOfDocument();
+ }
+
+ if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
+ nsContentUtils::AddScriptBlocker();
+ }
+ }
+
+ mEditingState = newState;
+ if (putOffToRemoveScriptBlockerUntilModifyingEditingState) {
+ nsContentUtils::RemoveScriptBlocker();
+ // If mEditingState is overwritten by another call and already disabled
+ // the editing, we shouldn't keep making window editable.
+ if (mEditingState == eOff) {
+ return NS_OK;
+ }
+ }
+
+ if (makeWindowEditable) {
+ // Set the editor to not insert br's on return when in p
+ // elements by default.
+ // XXX Do we only want to do this for designMode?
+ bool unused;
+ rv = ExecCommand(NS_LITERAL_STRING("insertBrOnReturn"), false,
+ NS_LITERAL_STRING("false"), &unused);
+
+ if (NS_FAILED(rv)) {
+ // Editor setup failed. Editing is not on after all.
+ // XXX Should we reset the editable flag on nodes?
+ editSession->TearDownEditorOnWindow(window);
+ mEditingState = eOff;
+
+ return rv;
+ }
+ }
+
+ if (updateState) {
+ nsAutoScriptBlocker scriptBlocker;
+ NotifyEditableStateChange(this, this);
+ }
+
+ // Resync the editor's spellcheck state.
+ if (spellRecheckAll) {
+ nsCOMPtr<nsISelectionController> selcon;
+ nsresult rv = editor->GetSelectionController(getter_AddRefs(selcon));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISelection> spellCheckSelection;
+ rv = selcon->GetSelection(nsISelectionController::SELECTION_SPELLCHECK,
+ getter_AddRefs(spellCheckSelection));
+ if (NS_SUCCEEDED(rv)) {
+ spellCheckSelection->RemoveAllRanges();
+ }
+ }
+ editor->SyncRealTimeSpell();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::SetDesignMode(const nsAString& aDesignMode)
+{
+ ErrorResult rv;
+ SetDesignMode(aDesignMode, nsContentUtils::GetCurrentJSContext()
+ ? Some(nsContentUtils::SubjectPrincipal())
+ : Nothing(), rv);
+ return rv.StealNSResult();
+}
+
+void
+nsHTMLDocument::SetDesignMode(const nsAString& aDesignMode,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& rv)
+{
+ SetDesignMode(aDesignMode, Some(&aSubjectPrincipal), rv);
+}
+
+void
+nsHTMLDocument::SetDesignMode(const nsAString& aDesignMode,
+ const Maybe<nsIPrincipal*>& aSubjectPrincipal,
+ ErrorResult& rv)
+{
+ if (aSubjectPrincipal.isSome() &&
+ !aSubjectPrincipal.value()->Subsumes(NodePrincipal())) {
+ rv.Throw(NS_ERROR_DOM_PROP_ACCESS_DENIED);
+ return;
+ }
+ bool editableMode = HasFlag(NODE_IS_EDITABLE);
+ if (aDesignMode.LowerCaseEqualsASCII(editableMode ? "off" : "on")) {
+ SetEditableFlag(!editableMode);
+
+ rv = EditingStateChanged();
+ }
+}
+
+nsresult
+nsHTMLDocument::GetMidasCommandManager(nsICommandManager** aCmdMgr)
+{
+ // initialize return value
+ NS_ENSURE_ARG_POINTER(aCmdMgr);
+
+ // check if we have it cached
+ if (mMidasCommandManager) {
+ NS_ADDREF(*aCmdMgr = mMidasCommandManager);
+ return NS_OK;
+ }
+
+ *aCmdMgr = nullptr;
+
+ nsPIDOMWindowOuter *window = GetWindow();
+ if (!window)
+ return NS_ERROR_FAILURE;
+
+ nsIDocShell *docshell = window->GetDocShell();
+ if (!docshell)
+ return NS_ERROR_FAILURE;
+
+ mMidasCommandManager = docshell->GetCommandManager();
+ if (!mMidasCommandManager)
+ return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*aCmdMgr = mMidasCommandManager);
+
+ return NS_OK;
+}
+
+
+struct MidasCommand {
+ const char* incomingCommandString;
+ const char* internalCommandString;
+ const char* internalParamString;
+ bool useNewParam;
+ bool convertToBoolean;
+};
+
+static const struct MidasCommand gMidasCommandTable[] = {
+ { "bold", "cmd_bold", "", true, false },
+ { "italic", "cmd_italic", "", true, false },
+ { "underline", "cmd_underline", "", true, false },
+ { "strikethrough", "cmd_strikethrough", "", true, false },
+ { "subscript", "cmd_subscript", "", true, false },
+ { "superscript", "cmd_superscript", "", true, false },
+ { "cut", "cmd_cut", "", true, false },
+ { "copy", "cmd_copy", "", true, false },
+ { "paste", "cmd_paste", "", true, false },
+ { "delete", "cmd_deleteCharBackward", "", true, false },
+ { "forwarddelete", "cmd_deleteCharForward", "", true, false },
+ { "selectall", "cmd_selectAll", "", true, false },
+ { "undo", "cmd_undo", "", true, false },
+ { "redo", "cmd_redo", "", true, false },
+ { "indent", "cmd_indent", "", true, false },
+ { "outdent", "cmd_outdent", "", true, false },
+ { "backcolor", "cmd_highlight", "", false, false },
+ { "forecolor", "cmd_fontColor", "", false, false },
+ { "hilitecolor", "cmd_highlight", "", false, false },
+ { "fontname", "cmd_fontFace", "", false, false },
+ { "fontsize", "cmd_fontSize", "", false, false },
+ { "increasefontsize", "cmd_increaseFont", "", false, false },
+ { "decreasefontsize", "cmd_decreaseFont", "", false, false },
+ { "inserthorizontalrule", "cmd_insertHR", "", true, false },
+ { "createlink", "cmd_insertLinkNoUI", "", false, false },
+ { "insertimage", "cmd_insertImageNoUI", "", false, false },
+ { "inserthtml", "cmd_insertHTML", "", false, false },
+ { "inserttext", "cmd_insertText", "", false, false },
+ { "gethtml", "cmd_getContents", "", false, false },
+ { "justifyleft", "cmd_align", "left", true, false },
+ { "justifyright", "cmd_align", "right", true, false },
+ { "justifycenter", "cmd_align", "center", true, false },
+ { "justifyfull", "cmd_align", "justify", true, false },
+ { "removeformat", "cmd_removeStyles", "", true, false },
+ { "unlink", "cmd_removeLinks", "", true, false },
+ { "insertorderedlist", "cmd_ol", "", true, false },
+ { "insertunorderedlist", "cmd_ul", "", true, false },
+ { "insertparagraph", "cmd_insertParagraph", "", true, false },
+ { "insertlinebreak", "cmd_insertLineBreak", "", true, false },
+ { "formatblock", "cmd_paragraphState", "", false, false },
+ { "heading", "cmd_paragraphState", "", false, false },
+ { "styleWithCSS", "cmd_setDocumentUseCSS", "", false, true },
+ { "contentReadOnly", "cmd_setDocumentReadOnly", "", false, true },
+ { "insertBrOnReturn", "cmd_insertBrOnReturn", "", false, true },
+ { "enableObjectResizing", "cmd_enableObjectResizing", "", false, true },
+ { "enableInlineTableEditing", "cmd_enableInlineTableEditing", "", false, true },
+#if 0
+// no editor support to remove alignments right now
+ { "justifynone", "cmd_align", "", true, false },
+
+// the following will need special review before being turned on
+ { "saveas", "cmd_saveAs", "", true, false },
+ { "print", "cmd_print", "", true, false },
+#endif
+ { nullptr, nullptr, nullptr, false, false }
+};
+
+#define MidasCommandCount ((sizeof(gMidasCommandTable) / sizeof(struct MidasCommand)) - 1)
+
+static const char* const gBlocks[] = {
+ "ADDRESS",
+ "BLOCKQUOTE",
+ "DD",
+ "DIV",
+ "DL",
+ "DT",
+ "H1",
+ "H2",
+ "H3",
+ "H4",
+ "H5",
+ "H6",
+ "P",
+ "PRE"
+};
+
+static bool
+ConvertToMidasInternalCommandInner(const nsAString& inCommandID,
+ const nsAString& inParam,
+ nsACString& outCommandID,
+ nsACString& outParam,
+ bool& outIsBoolean,
+ bool& outBooleanValue,
+ bool aIgnoreParams)
+{
+ NS_ConvertUTF16toUTF8 convertedCommandID(inCommandID);
+
+ // Hack to support old boolean commands that were backwards (see bug 301490).
+ bool invertBool = false;
+ if (convertedCommandID.LowerCaseEqualsLiteral("usecss")) {
+ convertedCommandID.AssignLiteral("styleWithCSS");
+ invertBool = true;
+ } else if (convertedCommandID.LowerCaseEqualsLiteral("readonly")) {
+ convertedCommandID.AssignLiteral("contentReadOnly");
+ invertBool = true;
+ }
+
+ uint32_t i;
+ bool found = false;
+ for (i = 0; i < MidasCommandCount; ++i) {
+ if (convertedCommandID.Equals(gMidasCommandTable[i].incomingCommandString,
+ nsCaseInsensitiveCStringComparator())) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ // reset results if the command is not found in our table
+ outCommandID.SetLength(0);
+ outParam.SetLength(0);
+ outIsBoolean = false;
+ return false;
+ }
+
+ // set outCommandID (what we use internally)
+ outCommandID.Assign(gMidasCommandTable[i].internalCommandString);
+
+ // set outParam & outIsBoolean based on flags from the table
+ outIsBoolean = gMidasCommandTable[i].convertToBoolean;
+
+ if (aIgnoreParams) {
+ // No further work to do
+ return true;
+ }
+
+ if (gMidasCommandTable[i].useNewParam) {
+ // Just have to copy it, no checking
+ outParam.Assign(gMidasCommandTable[i].internalParamString);
+ return true;
+ }
+
+ // handle checking of param passed in
+ if (outIsBoolean) {
+ // If this is a boolean value and it's not explicitly false (e.g. no value)
+ // we default to "true". For old backwards commands we invert the check (see
+ // bug 301490).
+ if (invertBool) {
+ outBooleanValue = inParam.LowerCaseEqualsLiteral("false");
+ } else {
+ outBooleanValue = !inParam.LowerCaseEqualsLiteral("false");
+ }
+ outParam.Truncate();
+
+ return true;
+ }
+
+ // String parameter -- see if we need to convert it (necessary for
+ // cmd_paragraphState and cmd_fontSize)
+ if (outCommandID.EqualsLiteral("cmd_paragraphState")) {
+ const char16_t* start = inParam.BeginReading();
+ const char16_t* end = inParam.EndReading();
+ if (start != end && *start == '<' && *(end - 1) == '>') {
+ ++start;
+ --end;
+ }
+
+ NS_ConvertUTF16toUTF8 convertedParam(Substring(start, end));
+ uint32_t j;
+ for (j = 0; j < ArrayLength(gBlocks); ++j) {
+ if (convertedParam.Equals(gBlocks[j],
+ nsCaseInsensitiveCStringComparator())) {
+ outParam.Assign(gBlocks[j]);
+ break;
+ }
+ }
+
+ if (j == ArrayLength(gBlocks)) {
+ outParam.Truncate();
+ }
+ } else if (outCommandID.EqualsLiteral("cmd_fontSize")) {
+ // Per editing spec as of April 23, 2012, we need to reject the value if
+ // it's not a valid floating-point number surrounded by optional whitespace.
+ // Otherwise, we parse it as a legacy font size. For now, we just parse as
+ // a legacy font size regardless (matching WebKit) -- bug 747879.
+ outParam.Truncate();
+ int32_t size = nsContentUtils::ParseLegacyFontSize(inParam);
+ if (size) {
+ outParam.AppendInt(size);
+ }
+ } else {
+ CopyUTF16toUTF8(inParam, outParam);
+ }
+
+ return true;
+}
+
+static bool
+ConvertToMidasInternalCommand(const nsAString & inCommandID,
+ const nsAString & inParam,
+ nsACString& outCommandID,
+ nsACString& outParam,
+ bool& outIsBoolean,
+ bool& outBooleanValue)
+{
+ return ConvertToMidasInternalCommandInner(inCommandID, inParam, outCommandID,
+ outParam, outIsBoolean,
+ outBooleanValue, false);
+}
+
+static bool
+ConvertToMidasInternalCommand(const nsAString & inCommandID,
+ nsACString& outCommandID)
+{
+ nsAutoCString dummyCString;
+ nsAutoString dummyString;
+ bool dummyBool;
+ return ConvertToMidasInternalCommandInner(inCommandID, dummyString,
+ outCommandID, dummyCString,
+ dummyBool, dummyBool, true);
+}
+
+/* TODO: don't let this call do anything if the page is not done loading */
+NS_IMETHODIMP
+nsHTMLDocument::ExecCommand(const nsAString& commandID,
+ bool doShowUI,
+ const nsAString& value,
+ bool* _retval)
+{
+ ErrorResult rv;
+ *_retval = ExecCommand(commandID, doShowUI, value, rv);
+ return rv.StealNSResult();
+}
+
+bool
+nsHTMLDocument::ExecCommand(const nsAString& commandID,
+ bool doShowUI,
+ const nsAString& value,
+ ErrorResult& rv)
+{
+ // for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
+ // this might add some ugly JS dependencies?
+
+ nsAutoCString cmdToDispatch, paramStr;
+ bool isBool, boolVal;
+ if (!ConvertToMidasInternalCommand(commandID, value,
+ cmdToDispatch, paramStr,
+ isBool, boolVal)) {
+ return false;
+ }
+
+ bool isCutCopy = (commandID.LowerCaseEqualsLiteral("cut") ||
+ commandID.LowerCaseEqualsLiteral("copy"));
+
+ // if editing is not on, bail
+ if (!isCutCopy && !IsEditingOnAfterFlush()) {
+ return false;
+ }
+
+ // if they are requesting UI from us, let's fail since we have no UI
+ if (doShowUI) {
+ return false;
+ }
+
+ // special case for cut & copy
+ // cut & copy are allowed in non editable documents
+ if (isCutCopy) {
+ if (!nsContentUtils::IsCutCopyAllowed()) {
+ // We have rejected the event due to it not being performed in an
+ // input-driven context therefore, we report the error to the console.
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM"), this,
+ nsContentUtils::eDOM_PROPERTIES,
+ "ExecCommandCutCopyDeniedNotInputDriven");
+ return false;
+ }
+
+ // For cut & copy commands, we need the behaviour from nsWindowRoot::GetControllers
+ // which is to look at the focused element, and defer to a focused textbox's controller
+ // The code past taken by other commands in ExecCommand always uses the window directly,
+ // rather than deferring to the textbox, which is desireable for most editor commands,
+ // but not 'cut' and 'copy' (as those should allow copying out of embedded editors).
+ // This behaviour is invoked if we call DoCommand directly on the docShell.
+ nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+ if (docShell) {
+ nsresult res = docShell->DoCommand(cmdToDispatch.get());
+ return NS_SUCCEEDED(res);
+ }
+ return false;
+ }
+
+ if (commandID.LowerCaseEqualsLiteral("gethtml")) {
+ rv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ bool restricted = commandID.LowerCaseEqualsLiteral("paste");
+ if (restricted && !nsContentUtils::IsCallerChrome()) {
+ return false;
+ }
+
+ // get command manager and dispatch command to our window if it's acceptable
+ nsCOMPtr<nsICommandManager> cmdMgr;
+ GetMidasCommandManager(getter_AddRefs(cmdMgr));
+ if (!cmdMgr) {
+ rv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ rv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ if ((cmdToDispatch.EqualsLiteral("cmd_fontSize") ||
+ cmdToDispatch.EqualsLiteral("cmd_insertImageNoUI") ||
+ cmdToDispatch.EqualsLiteral("cmd_insertLinkNoUI") ||
+ cmdToDispatch.EqualsLiteral("cmd_paragraphState")) &&
+ paramStr.IsEmpty()) {
+ // Invalid value, return false
+ return false;
+ }
+
+ // Return false for disabled commands (bug 760052)
+ bool enabled = false;
+ cmdMgr->IsCommandEnabled(cmdToDispatch.get(), window, &enabled);
+ if (!enabled) {
+ return false;
+ }
+
+ if (!isBool && paramStr.IsEmpty()) {
+ rv = cmdMgr->DoCommand(cmdToDispatch.get(), nullptr, window);
+ } else {
+ // we have a command that requires a parameter, create params
+ nsCOMPtr<nsICommandParams> cmdParams = do_CreateInstance(
+ NS_COMMAND_PARAMS_CONTRACTID);
+ if (!cmdParams) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return false;
+ }
+
+ if (isBool) {
+ rv = cmdParams->SetBooleanValue("state_attribute", boolVal);
+ } else if (cmdToDispatch.EqualsLiteral("cmd_fontFace")) {
+ rv = cmdParams->SetStringValue("state_attribute", value);
+ } else if (cmdToDispatch.EqualsLiteral("cmd_insertHTML") ||
+ cmdToDispatch.EqualsLiteral("cmd_insertText")) {
+ rv = cmdParams->SetStringValue("state_data", value);
+ } else {
+ rv = cmdParams->SetCStringValue("state_attribute", paramStr.get());
+ }
+ if (rv.Failed()) {
+ return false;
+ }
+ rv = cmdMgr->DoCommand(cmdToDispatch.get(), cmdParams, window);
+ }
+
+ return !rv.Failed();
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::QueryCommandEnabled(const nsAString& commandID,
+ bool* _retval)
+{
+ ErrorResult rv;
+ *_retval = QueryCommandEnabled(commandID, rv);
+ return rv.StealNSResult();
+}
+
+bool
+nsHTMLDocument::QueryCommandEnabled(const nsAString& commandID, ErrorResult& rv)
+{
+ nsAutoCString cmdToDispatch;
+ if (!ConvertToMidasInternalCommand(commandID, cmdToDispatch)) {
+ return false;
+ }
+
+ // cut & copy are always allowed
+ bool isCutCopy = commandID.LowerCaseEqualsLiteral("cut") ||
+ commandID.LowerCaseEqualsLiteral("copy");
+ if (isCutCopy) {
+ return nsContentUtils::IsCutCopyAllowed();
+ }
+
+ // Report false for restricted commands
+ bool restricted = commandID.LowerCaseEqualsLiteral("paste");
+ if (restricted && !nsContentUtils::IsCallerChrome()) {
+ return false;
+ }
+
+ // if editing is not on, bail
+ if (!IsEditingOnAfterFlush()) {
+ return false;
+ }
+
+ // get command manager and dispatch command to our window if it's acceptable
+ nsCOMPtr<nsICommandManager> cmdMgr;
+ GetMidasCommandManager(getter_AddRefs(cmdMgr));
+ if (!cmdMgr) {
+ rv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ rv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ bool retval;
+ rv = cmdMgr->IsCommandEnabled(cmdToDispatch.get(), window, &retval);
+ return retval;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::QueryCommandIndeterm(const nsAString & commandID,
+ bool *_retval)
+{
+ ErrorResult rv;
+ *_retval = QueryCommandIndeterm(commandID, rv);
+ return rv.StealNSResult();
+}
+
+bool
+nsHTMLDocument::QueryCommandIndeterm(const nsAString& commandID, ErrorResult& rv)
+{
+ nsAutoCString cmdToDispatch;
+ if (!ConvertToMidasInternalCommand(commandID, cmdToDispatch)) {
+ return false;
+ }
+
+ // if editing is not on, bail
+ if (!IsEditingOnAfterFlush()) {
+ return false;
+ }
+
+ // get command manager and dispatch command to our window if it's acceptable
+ nsCOMPtr<nsICommandManager> cmdMgr;
+ GetMidasCommandManager(getter_AddRefs(cmdMgr));
+ if (!cmdMgr) {
+ rv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ rv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ nsresult res;
+ nsCOMPtr<nsICommandParams> cmdParams = do_CreateInstance(
+ NS_COMMAND_PARAMS_CONTRACTID, &res);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ return false;
+ }
+
+ rv = cmdMgr->GetCommandState(cmdToDispatch.get(), window, cmdParams);
+ if (rv.Failed()) {
+ return false;
+ }
+
+ // If command does not have a state_mixed value, this call fails and sets
+ // retval to false. This is fine -- we want to return false in that case
+ // anyway (bug 738385), so we just don't throw regardless.
+ bool retval = false;
+ cmdParams->GetBooleanValue("state_mixed", &retval);
+ return retval;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::QueryCommandState(const nsAString & commandID, bool *_retval)
+{
+ ErrorResult rv;
+ *_retval = QueryCommandState(commandID, rv);
+ return rv.StealNSResult();
+}
+
+bool
+nsHTMLDocument::QueryCommandState(const nsAString& commandID, ErrorResult& rv)
+{
+ nsAutoCString cmdToDispatch, paramToCheck;
+ bool dummy, dummy2;
+ if (!ConvertToMidasInternalCommand(commandID, commandID,
+ cmdToDispatch, paramToCheck,
+ dummy, dummy2)) {
+ return false;
+ }
+
+ // if editing is not on, bail
+ if (!IsEditingOnAfterFlush()) {
+ return false;
+ }
+
+ // get command manager and dispatch command to our window if it's acceptable
+ nsCOMPtr<nsICommandManager> cmdMgr;
+ GetMidasCommandManager(getter_AddRefs(cmdMgr));
+ if (!cmdMgr) {
+ rv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ rv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ if (commandID.LowerCaseEqualsLiteral("usecss")) {
+ // Per spec, state is supported for styleWithCSS but not useCSS, so we just
+ // return false always.
+ return false;
+ }
+
+ nsCOMPtr<nsICommandParams> cmdParams = do_CreateInstance(
+ NS_COMMAND_PARAMS_CONTRACTID);
+ if (!cmdParams) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return false;
+ }
+
+ rv = cmdMgr->GetCommandState(cmdToDispatch.get(), window, cmdParams);
+ if (rv.Failed()) {
+ return false;
+ }
+
+ // handle alignment as a special case (possibly other commands too?)
+ // Alignment is special because the external api is individual
+ // commands but internally we use cmd_align with different
+ // parameters. When getting the state of this command, we need to
+ // return the boolean for this particular alignment rather than the
+ // string of 'which alignment is this?'
+ if (cmdToDispatch.EqualsLiteral("cmd_align")) {
+ char * actualAlignmentType = nullptr;
+ rv = cmdParams->GetCStringValue("state_attribute", &actualAlignmentType);
+ bool retval = false;
+ if (!rv.Failed() && actualAlignmentType && actualAlignmentType[0]) {
+ retval = paramToCheck.Equals(actualAlignmentType);
+ }
+ if (actualAlignmentType) {
+ free(actualAlignmentType);
+ }
+ return retval;
+ }
+
+ // If command does not have a state_all value, this call fails and sets
+ // retval to false. This is fine -- we want to return false in that case
+ // anyway (bug 738385), so we just succeed and return false regardless.
+ bool retval = false;
+ cmdParams->GetBooleanValue("state_all", &retval);
+ return retval;
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::QueryCommandSupported(const nsAString & commandID,
+ bool *_retval)
+{
+ *_retval = QueryCommandSupported(commandID);
+ return NS_OK;
+}
+
+bool
+nsHTMLDocument::QueryCommandSupported(const nsAString& commandID)
+{
+ // Gecko technically supports all the clipboard commands including
+ // cut/copy/paste, but non-privileged content will be unable to call
+ // paste, and depending on the pref "dom.allow_cut_copy", cut and copy
+ // may also be disallowed to be called from non-privileged content.
+ // For that reason, we report the support status of corresponding
+ // command accordingly.
+ if (!nsContentUtils::IsCallerChrome()) {
+ if (commandID.LowerCaseEqualsLiteral("paste")) {
+ return false;
+ }
+ if (nsContentUtils::IsCutCopyRestricted()) {
+ if (commandID.LowerCaseEqualsLiteral("cut") ||
+ commandID.LowerCaseEqualsLiteral("copy")) {
+ return false;
+ }
+ }
+ }
+
+ // commandID is supported if it can be converted to a Midas command
+ nsAutoCString cmdToDispatch;
+ return ConvertToMidasInternalCommand(commandID, cmdToDispatch);
+}
+
+NS_IMETHODIMP
+nsHTMLDocument::QueryCommandValue(const nsAString & commandID,
+ nsAString &_retval)
+{
+ ErrorResult rv;
+ QueryCommandValue(commandID, _retval, rv);
+ return rv.StealNSResult();
+}
+
+void
+nsHTMLDocument::QueryCommandValue(const nsAString& commandID,
+ nsAString& aValue,
+ ErrorResult& rv)
+{
+ aValue.Truncate();
+
+ nsAutoCString cmdToDispatch, paramStr;
+ if (!ConvertToMidasInternalCommand(commandID, cmdToDispatch)) {
+ // Return empty string
+ return;
+ }
+
+ // if editing is not on, bail
+ if (!IsEditingOnAfterFlush()) {
+ return;
+ }
+
+ // get command manager and dispatch command to our window if it's acceptable
+ nsCOMPtr<nsICommandManager> cmdMgr;
+ GetMidasCommandManager(getter_AddRefs(cmdMgr));
+ if (!cmdMgr) {
+ rv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsPIDOMWindowOuter* window = GetWindow();
+ if (!window) {
+ rv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // create params
+ nsCOMPtr<nsICommandParams> cmdParams = do_CreateInstance(
+ NS_COMMAND_PARAMS_CONTRACTID);
+ if (!cmdParams) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ // this is a special command since we are calling DoCommand rather than
+ // GetCommandState like the other commands
+ if (cmdToDispatch.EqualsLiteral("cmd_getContents")) {
+ rv = cmdParams->SetBooleanValue("selection_only", true);
+ if (rv.Failed()) {
+ return;
+ }
+ rv = cmdParams->SetCStringValue("format", "text/html");
+ if (rv.Failed()) {
+ return;
+ }
+ rv = cmdMgr->DoCommand(cmdToDispatch.get(), cmdParams, window);
+ if (rv.Failed()) {
+ return;
+ }
+ rv = cmdParams->GetStringValue("result", aValue);
+ return;
+ }
+
+ rv = cmdParams->SetCStringValue("state_attribute", paramStr.get());
+ if (rv.Failed()) {
+ return;
+ }
+
+ rv = cmdMgr->GetCommandState(cmdToDispatch.get(), window, cmdParams);
+ if (rv.Failed()) {
+ return;
+ }
+
+ // If command does not have a state_attribute value, this call fails, and
+ // aValue will wind up being the empty string. This is fine -- we want to
+ // return "" in that case anyway (bug 738385), so we just return NS_OK
+ // regardless.
+ nsXPIDLCString cStringResult;
+ cmdParams->GetCStringValue("state_attribute",
+ getter_Copies(cStringResult));
+ CopyUTF8toUTF16(cStringResult, aValue);
+}
+
+nsresult
+nsHTMLDocument::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const
+{
+ NS_ASSERTION(aNodeInfo->NodeInfoManager() == mNodeInfoManager,
+ "Can't import this document into another document!");
+
+ RefPtr<nsHTMLDocument> clone = new nsHTMLDocument();
+ nsresult rv = CloneDocHelper(clone.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // State from nsHTMLDocument
+ clone->mLoadFlags = mLoadFlags;
+
+ return CallQueryInterface(clone.get(), aResult);
+}
+
+bool
+nsHTMLDocument::IsEditingOnAfterFlush()
+{
+ nsIDocument* doc = GetParentDocument();
+ if (doc) {
+ // Make sure frames are up to date, since that can affect whether
+ // we're editable.
+ doc->FlushPendingNotifications(Flush_Frames);
+ }
+
+ return IsEditingOn();
+}
+
+void
+nsHTMLDocument::RemovedFromDocShell()
+{
+ mEditingState = eOff;
+ nsDocument::RemovedFromDocShell();
+}
+
+/* virtual */ void
+nsHTMLDocument::DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const
+{
+ nsDocument::DocAddSizeOfExcludingThis(aWindowSizes);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mImages
+ // - mApplets
+ // - mEmbeds
+ // - mLinks
+ // - mAnchors
+ // - mScripts
+ // - mForms
+ // - mFormControls
+ // - mWyciwygChannel
+ // - mMidasCommandManager
+}
+
+bool
+nsHTMLDocument::WillIgnoreCharsetOverride()
+{
+ if (mType != eHTML) {
+ MOZ_ASSERT(mType == eXHTML);
+ return true;
+ }
+ if (mCharacterSetSource == kCharsetFromByteOrderMark) {
+ return true;
+ }
+ if (!EncodingUtils::IsAsciiCompatible(mCharacterSet)) {
+ return true;
+ }
+ nsCOMPtr<nsIWyciwygChannel> wyciwyg = do_QueryInterface(mChannel);
+ if (wyciwyg) {
+ return true;
+ }
+ nsIURI* uri = GetOriginalURI();
+ if (uri) {
+ bool schemeIs = false;
+ uri->SchemeIs("about", &schemeIs);
+ if (schemeIs) {
+ return true;
+ }
+ bool isResource;
+ nsresult rv = NS_URIChainHasFlags(uri,
+ nsIProtocolHandler::URI_IS_UI_RESOURCE,
+ &isResource);
+ if (NS_FAILED(rv) || isResource) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/dom/html/nsHTMLDocument.h b/dom/html/nsHTMLDocument.h
new file mode 100644
index 000000000..2dbbf2b57
--- /dev/null
+++ b/dom/html/nsHTMLDocument.h
@@ -0,0 +1,375 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsHTMLDocument_h___
+#define nsHTMLDocument_h___
+
+#include "mozilla/Attributes.h"
+#include "nsDocument.h"
+#include "nsIHTMLDocument.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIDOMHTMLCollection.h"
+#include "nsIScriptElement.h"
+#include "nsTArray.h"
+
+#include "PLDHashTable.h"
+#include "nsIHttpChannel.h"
+#include "nsHTMLStyleSheet.h"
+
+#include "nsICommandManager.h"
+#include "mozilla/dom/HTMLSharedElement.h"
+
+class nsIEditor;
+class nsIURI;
+class nsIDocShell;
+class nsICachingChannel;
+class nsIWyciwygChannel;
+class nsILoadGroup;
+
+namespace mozilla {
+namespace dom {
+class HTMLAllCollection;
+} // namespace dom
+} // namespace mozilla
+
+class nsHTMLDocument : public nsDocument,
+ public nsIHTMLDocument,
+ public nsIDOMHTMLDocument
+{
+public:
+ using nsDocument::SetDocumentURI;
+ using nsDocument::GetPlugins;
+
+ nsHTMLDocument();
+ virtual nsresult Init() override;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsHTMLDocument, nsDocument)
+
+ // nsIDocument
+ virtual void Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) override;
+ virtual void ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
+ nsIPrincipal* aPrincipal) override;
+
+ virtual already_AddRefed<nsIPresShell> CreateShell(
+ nsPresContext* aContext,
+ nsViewManager* aViewManager,
+ mozilla::StyleSetHandle aStyleSet) override;
+
+ virtual nsresult StartDocumentLoad(const char* aCommand,
+ nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener **aDocListener,
+ bool aReset = true,
+ nsIContentSink* aSink = nullptr) override;
+ virtual void StopDocumentLoad() override;
+
+ virtual void BeginLoad() override;
+ virtual void EndLoad() override;
+
+ // nsIHTMLDocument
+ virtual void SetCompatibilityMode(nsCompatibility aMode) override;
+
+ virtual bool IsWriting() override
+ {
+ return mWriteLevel != uint32_t(0);
+ }
+
+ virtual nsContentList* GetForms() override;
+
+ virtual nsContentList* GetFormControls() override;
+
+ // nsIDOMDocument interface
+ using nsDocument::CreateElement;
+ using nsDocument::CreateElementNS;
+ NS_FORWARD_NSIDOMDOCUMENT(nsDocument::)
+
+ // And explicitly import the things from nsDocument that we just shadowed
+ using nsDocument::GetImplementation;
+ using nsDocument::GetTitle;
+ using nsDocument::SetTitle;
+ using nsDocument::GetLastStyleSheetSet;
+ using nsDocument::MozSetImageElement;
+ using nsDocument::GetMozFullScreenElement;
+
+ // nsIDOMNode interface
+ NS_FORWARD_NSIDOMNODE_TO_NSINODE
+
+ // nsIDOMHTMLDocument interface
+ NS_DECL_NSIDOMHTMLDOCUMENT
+
+ mozilla::dom::HTMLAllCollection* All();
+
+ nsISupports* ResolveName(const nsAString& aName, nsWrapperCache **aCache);
+
+ virtual void AddedForm() override;
+ virtual void RemovedForm() override;
+ virtual int32_t GetNumFormsSynchronous() override;
+ virtual void TearingDownEditor(nsIEditor *aEditor) override;
+ virtual void SetIsXHTML(bool aXHTML) override
+ {
+ mType = (aXHTML ? eXHTML : eHTML);
+ }
+ virtual void SetDocWriteDisabled(bool aDisabled) override
+ {
+ mDisableDocWrite = aDisabled;
+ }
+
+ nsresult ChangeContentEditableCount(nsIContent *aElement, int32_t aChange) override;
+ void DeferredContentEditableCountChange(nsIContent *aElement);
+
+ virtual EditingState GetEditingState() override
+ {
+ return mEditingState;
+ }
+
+ virtual void DisableCookieAccess() override
+ {
+ mDisableCookieAccess = true;
+ }
+
+ class nsAutoEditingState {
+ public:
+ nsAutoEditingState(nsHTMLDocument* aDoc, EditingState aState)
+ : mDoc(aDoc), mSavedState(aDoc->mEditingState)
+ {
+ aDoc->mEditingState = aState;
+ }
+ ~nsAutoEditingState() {
+ mDoc->mEditingState = mSavedState;
+ }
+ private:
+ nsHTMLDocument* mDoc;
+ EditingState mSavedState;
+ };
+ friend class nsAutoEditingState;
+
+ void EndUpdate(nsUpdateType aUpdateType) override;
+
+ virtual nsresult SetEditingState(EditingState aState) override;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ virtual void RemovedFromDocShell() override;
+
+ virtual mozilla::dom::Element *GetElementById(const nsAString& aElementId) override
+ {
+ return nsDocument::GetElementById(aElementId);
+ }
+
+ virtual void DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const override;
+ // DocAddSizeOfIncludingThis is inherited from nsIDocument.
+
+ virtual bool WillIgnoreCharsetOverride() override;
+
+ // WebIDL API
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+ override;
+ void SetDomain(const nsAString& aDomain, mozilla::ErrorResult& rv);
+ void GetCookie(nsAString& aCookie, mozilla::ErrorResult& rv);
+ void SetCookie(const nsAString& aCookie, mozilla::ErrorResult& rv);
+ void NamedGetter(JSContext* cx, const nsAString& aName, bool& aFound,
+ JS::MutableHandle<JSObject*> aRetval,
+ mozilla::ErrorResult& rv);
+ void GetSupportedNames(nsTArray<nsString>& aNames);
+ nsGenericHTMLElement *GetBody();
+ void SetBody(nsGenericHTMLElement* aBody, mozilla::ErrorResult& rv);
+ mozilla::dom::HTMLSharedElement *GetHead() {
+ return static_cast<mozilla::dom::HTMLSharedElement*>(GetHeadElement());
+ }
+ nsIHTMLCollection* Images();
+ nsIHTMLCollection* Embeds();
+ nsIHTMLCollection* Plugins();
+ nsIHTMLCollection* Links();
+ nsIHTMLCollection* Forms()
+ {
+ return nsHTMLDocument::GetForms();
+ }
+ nsIHTMLCollection* Scripts();
+ already_AddRefed<nsContentList> GetElementsByName(const nsAString & aName)
+ {
+ return NS_GetFuncStringNodeList(this, MatchNameAttribute, nullptr,
+ UseExistingNameString, aName);
+ }
+ already_AddRefed<nsIDocument> Open(JSContext* cx,
+ const nsAString& aType,
+ const nsAString& aReplace,
+ mozilla::ErrorResult& rv);
+ already_AddRefed<nsPIDOMWindowOuter>
+ Open(JSContext* cx,
+ const nsAString& aURL,
+ const nsAString& aName,
+ const nsAString& aFeatures,
+ bool aReplace,
+ mozilla::ErrorResult& rv);
+ void Close(mozilla::ErrorResult& rv);
+ void Write(JSContext* cx, const mozilla::dom::Sequence<nsString>& aText,
+ mozilla::ErrorResult& rv);
+ void Writeln(JSContext* cx, const mozilla::dom::Sequence<nsString>& aText,
+ mozilla::ErrorResult& rv);
+ void GetDesignMode(nsAString& aDesignMode,
+ nsIPrincipal& aSubjectPrincipal)
+ {
+ GetDesignMode(aDesignMode);
+ }
+ void SetDesignMode(const nsAString& aDesignMode,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& rv);
+ void SetDesignMode(const nsAString& aDesignMode,
+ const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
+ mozilla::ErrorResult& rv);
+ bool ExecCommand(const nsAString& aCommandID, bool aDoShowUI,
+ const nsAString& aValue, mozilla::ErrorResult& rv);
+ bool QueryCommandEnabled(const nsAString& aCommandID,
+ mozilla::ErrorResult& rv);
+ bool QueryCommandIndeterm(const nsAString& aCommandID,
+ mozilla::ErrorResult& rv);
+ bool QueryCommandState(const nsAString& aCommandID, mozilla::ErrorResult& rv);
+ bool QueryCommandSupported(const nsAString& aCommandID);
+ void QueryCommandValue(const nsAString& aCommandID, nsAString& aValue,
+ mozilla::ErrorResult& rv);
+ // The XPCOM Get/SetFgColor work OK for us, since they never throw.
+ // The XPCOM Get/SetLinkColor work OK for us, since they never throw.
+ // The XPCOM Get/SetVLinkColor work OK for us, since they never throw.
+ // The XPCOM Get/SetALinkColor work OK for us, since they never throw.
+ // The XPCOM Get/SetBgColor work OK for us, since they never throw.
+ nsIHTMLCollection* Anchors();
+ nsIHTMLCollection* Applets();
+ void Clear() const
+ {
+ // Deprecated
+ }
+ mozilla::dom::Selection* GetSelection(mozilla::ErrorResult& aRv);
+ // The XPCOM CaptureEvents works fine for us.
+ // The XPCOM ReleaseEvents works fine for us.
+ // We're picking up GetLocation from Document
+ already_AddRefed<mozilla::dom::Location> GetLocation() const
+ {
+ return nsIDocument::GetLocation();
+ }
+
+ virtual nsHTMLDocument* AsHTMLDocument() override { return this; }
+
+protected:
+ ~nsHTMLDocument();
+
+ nsresult GetBodySize(int32_t* aWidth,
+ int32_t* aHeight);
+
+ nsIContent *MatchId(nsIContent *aContent, const nsAString& aId);
+
+ static bool MatchLinks(nsIContent *aContent, int32_t aNamespaceID,
+ nsIAtom* aAtom, void* aData);
+ static bool MatchAnchors(nsIContent *aContent, int32_t aNamespaceID,
+ nsIAtom* aAtom, void* aData);
+ static bool MatchNameAttribute(nsIContent* aContent, int32_t aNamespaceID,
+ nsIAtom* aAtom, void* aData);
+ static void* UseExistingNameString(nsINode* aRootNode, const nsString* aName);
+
+ static void DocumentWriteTerminationFunc(nsISupports *aRef);
+
+ already_AddRefed<nsIURI> GetDomainURI();
+
+ nsresult WriteCommon(JSContext *cx, const nsAString& aText,
+ bool aNewlineTerminate);
+ // A version of WriteCommon used by WebIDL bindings
+ void WriteCommon(JSContext *cx,
+ const mozilla::dom::Sequence<nsString>& aText,
+ bool aNewlineTerminate,
+ mozilla::ErrorResult& rv);
+
+ nsresult CreateAndAddWyciwygChannel(void);
+ nsresult RemoveWyciwygChannel(void);
+
+ // This should *ONLY* be used in GetCookie/SetCookie.
+ already_AddRefed<nsIChannel> CreateDummyChannelForCookies(nsIURI* aCodebaseURI);
+
+ /**
+ * Like IsEditingOn(), but will flush as needed first.
+ */
+ bool IsEditingOnAfterFlush();
+
+ void *GenerateParserKey(void);
+
+ RefPtr<nsContentList> mImages;
+ RefPtr<nsContentList> mApplets;
+ RefPtr<nsContentList> mEmbeds;
+ RefPtr<nsContentList> mLinks;
+ RefPtr<nsContentList> mAnchors;
+ RefPtr<nsContentList> mScripts;
+ RefPtr<nsContentList> mForms;
+ RefPtr<nsContentList> mFormControls;
+
+ RefPtr<mozilla::dom::HTMLAllCollection> mAll;
+
+ /** # of forms in the document, synchronously set */
+ int32_t mNumForms;
+
+ static uint32_t gWyciwygSessionCnt;
+
+ static void TryHintCharset(nsIContentViewer* aContentViewer,
+ int32_t& aCharsetSource,
+ nsACString& aCharset);
+ void TryUserForcedCharset(nsIContentViewer* aCv,
+ nsIDocShell* aDocShell,
+ int32_t& aCharsetSource,
+ nsACString& aCharset);
+ static void TryCacheCharset(nsICachingChannel* aCachingChannel,
+ int32_t& aCharsetSource,
+ nsACString& aCharset);
+ void TryParentCharset(nsIDocShell* aDocShell,
+ int32_t& charsetSource, nsACString& aCharset);
+ void TryTLD(int32_t& aCharsetSource, nsACString& aCharset);
+ static void TryFallback(int32_t& aCharsetSource, nsACString& aCharset);
+
+ // Override so we can munge the charset on our wyciwyg channel as needed.
+ virtual void SetDocumentCharacterSet(const nsACString& aCharSetID) override;
+
+ // Tracks if we are currently processing any document.write calls (either
+ // implicit or explicit). Note that if a write call writes out something which
+ // would block the parser, then mWriteLevel will be incorrect until the parser
+ // finishes processing that script.
+ uint32_t mWriteLevel;
+
+ // Load flags of the document's channel
+ uint32_t mLoadFlags;
+
+ bool mTooDeepWriteRecursion;
+
+ bool mDisableDocWrite;
+
+ bool mWarnedWidthHeight;
+
+ nsCOMPtr<nsIWyciwygChannel> mWyciwygChannel;
+
+ /* Midas implementation */
+ nsresult GetMidasCommandManager(nsICommandManager** aCommandManager);
+
+ nsCOMPtr<nsICommandManager> mMidasCommandManager;
+
+ nsresult TurnEditingOff();
+ nsresult EditingStateChanged();
+ void MaybeEditingStateChanged();
+
+ uint32_t mContentEditableCount;
+ EditingState mEditingState;
+
+ // When false, the .cookies property is completely disabled
+ bool mDisableCookieAccess;
+
+ /**
+ * Temporary flag that is set in EndUpdate() to ignore
+ * MaybeEditingStateChanged() script runners from a nested scope.
+ */
+ bool mPendingMaybeEditingStateChanged;
+};
+
+#define NS_HTML_DOCUMENT_INTERFACE_TABLE_BEGIN(_class) \
+ NS_DOCUMENT_INTERFACE_TABLE_BEGIN(_class) \
+ NS_INTERFACE_TABLE_ENTRY(_class, nsIHTMLDocument) \
+ NS_INTERFACE_TABLE_ENTRY(_class, nsIDOMHTMLDocument)
+
+#endif /* nsHTMLDocument_h___ */
diff --git a/dom/html/nsIConstraintValidation.cpp b/dom/html/nsIConstraintValidation.cpp
new file mode 100644
index 000000000..00f34df67
--- /dev/null
+++ b/dom/html/nsIConstraintValidation.cpp
@@ -0,0 +1,263 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIConstraintValidation.h"
+
+#include "nsAString.h"
+#include "nsGenericHTMLElement.h"
+#include "mozilla/dom/HTMLFormElement.h"
+#include "mozilla/dom/HTMLFieldSetElement.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/ValidityState.h"
+#include "nsIFormControl.h"
+#include "nsContentUtils.h"
+
+#include "nsIFormSubmitObserver.h"
+#include "nsIObserverService.h"
+
+const uint16_t nsIConstraintValidation::sContentSpecifiedMaxLengthMessage = 256;
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsIConstraintValidation::nsIConstraintValidation()
+ : mValidityBitField(0)
+ // By default, all elements are subjects to constraint validation.
+ , mBarredFromConstraintValidation(false)
+{
+}
+
+nsIConstraintValidation::~nsIConstraintValidation()
+{
+}
+
+mozilla::dom::ValidityState*
+nsIConstraintValidation::Validity()
+{
+ if (!mValidity) {
+ mValidity = new mozilla::dom::ValidityState(this);
+ }
+
+ return mValidity;
+}
+
+nsresult
+nsIConstraintValidation::GetValidity(nsIDOMValidityState** aValidity)
+{
+ NS_ENSURE_ARG_POINTER(aValidity);
+
+ NS_ADDREF(*aValidity = Validity());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIConstraintValidation::GetValidationMessage(nsAString& aValidationMessage)
+{
+ aValidationMessage.Truncate();
+
+ if (IsCandidateForConstraintValidation() && !IsValid()) {
+ nsCOMPtr<nsIContent> content = do_QueryInterface(this);
+ NS_ASSERTION(content, "This class should be inherited by HTML elements only!");
+
+ nsAutoString authorMessage;
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::x_moz_errormessage,
+ authorMessage);
+
+ if (!authorMessage.IsEmpty()) {
+ aValidationMessage.Assign(authorMessage);
+ if (aValidationMessage.Length() > sContentSpecifiedMaxLengthMessage) {
+ aValidationMessage.Truncate(sContentSpecifiedMaxLengthMessage);
+ }
+ } else if (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR)) {
+ aValidationMessage.Assign(mCustomValidity);
+ if (aValidationMessage.Length() > sContentSpecifiedMaxLengthMessage) {
+ aValidationMessage.Truncate(sContentSpecifiedMaxLengthMessage);
+ }
+ } else if (GetValidityState(VALIDITY_STATE_TOO_LONG)) {
+ GetValidationMessage(aValidationMessage, VALIDITY_STATE_TOO_LONG);
+ } else if (GetValidityState(VALIDITY_STATE_TOO_SHORT)) {
+ GetValidationMessage(aValidationMessage, VALIDITY_STATE_TOO_SHORT);
+ } else if (GetValidityState(VALIDITY_STATE_VALUE_MISSING)) {
+ GetValidationMessage(aValidationMessage, VALIDITY_STATE_VALUE_MISSING);
+ } else if (GetValidityState(VALIDITY_STATE_TYPE_MISMATCH)) {
+ GetValidationMessage(aValidationMessage, VALIDITY_STATE_TYPE_MISMATCH);
+ } else if (GetValidityState(VALIDITY_STATE_PATTERN_MISMATCH)) {
+ GetValidationMessage(aValidationMessage, VALIDITY_STATE_PATTERN_MISMATCH);
+ } else if (GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW)) {
+ GetValidationMessage(aValidationMessage, VALIDITY_STATE_RANGE_OVERFLOW);
+ } else if (GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW)) {
+ GetValidationMessage(aValidationMessage, VALIDITY_STATE_RANGE_UNDERFLOW);
+ } else if (GetValidityState(VALIDITY_STATE_STEP_MISMATCH)) {
+ GetValidationMessage(aValidationMessage, VALIDITY_STATE_STEP_MISMATCH);
+ } else if (GetValidityState(VALIDITY_STATE_BAD_INPUT)) {
+ GetValidationMessage(aValidationMessage, VALIDITY_STATE_BAD_INPUT);
+ } else {
+ // There should not be other validity states.
+ return NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ aValidationMessage.Truncate();
+ }
+
+ return NS_OK;
+}
+
+bool
+nsIConstraintValidation::CheckValidity()
+{
+ if (!IsCandidateForConstraintValidation() || IsValid()) {
+ return true;
+ }
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(this);
+ NS_ASSERTION(content, "This class should be inherited by HTML elements only!");
+
+ nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
+ NS_LITERAL_STRING("invalid"),
+ false, true);
+ return false;
+}
+
+nsresult
+nsIConstraintValidation::CheckValidity(bool* aValidity)
+{
+ NS_ENSURE_ARG_POINTER(aValidity);
+
+ *aValidity = CheckValidity();
+
+ return NS_OK;
+}
+
+bool
+nsIConstraintValidation::ReportValidity()
+{
+ if (!IsCandidateForConstraintValidation() || IsValid()) {
+ return true;
+ }
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(this);
+ MOZ_ASSERT(content, "This class should be inherited by HTML elements only!");
+
+ bool defaultAction = true;
+ nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
+ NS_LITERAL_STRING("invalid"),
+ false, true, &defaultAction);
+ if (!defaultAction) {
+ return false;
+ }
+
+ nsCOMPtr<nsIObserverService> service =
+ mozilla::services::GetObserverService();
+ if (!service) {
+ NS_WARNING("No observer service available!");
+ return true;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> theEnum;
+ nsresult rv = service->EnumerateObservers(NS_INVALIDFORMSUBMIT_SUBJECT,
+ getter_AddRefs(theEnum));
+
+ // Return true on error here because that's what we always did
+ NS_ENSURE_SUCCESS(rv, true);
+
+ bool hasObserver = false;
+ rv = theEnum->HasMoreElements(&hasObserver);
+
+ nsCOMPtr<nsIMutableArray> invalidElements =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ invalidElements->AppendElement(content, false);
+
+ NS_ENSURE_SUCCESS(rv, true);
+ nsCOMPtr<nsISupports> inst;
+ nsCOMPtr<nsIFormSubmitObserver> observer;
+ bool more = true;
+ while (NS_SUCCEEDED(theEnum->HasMoreElements(&more)) && more) {
+ theEnum->GetNext(getter_AddRefs(inst));
+ observer = do_QueryInterface(inst);
+
+ if (observer) {
+ observer->NotifyInvalidSubmit(nullptr, invalidElements);
+ }
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::input) &&
+ nsContentUtils::IsFocusedContent(content)) {
+ HTMLInputElement* inputElement =
+ HTMLInputElement::FromContentOrNull(content);
+
+ inputElement->UpdateValidityUIBits(true);
+ }
+
+ dom::Element* element = content->AsElement();
+ element->UpdateState(true);
+ return false;
+}
+
+void
+nsIConstraintValidation::SetValidityState(ValidityStateType aState,
+ bool aValue)
+{
+ bool previousValidity = IsValid();
+
+ if (aValue) {
+ mValidityBitField |= aState;
+ } else {
+ mValidityBitField &= ~aState;
+ }
+
+ // Inform the form and fieldset elements if our validity has changed.
+ if (previousValidity != IsValid() && IsCandidateForConstraintValidation()) {
+ nsCOMPtr<nsIFormControl> formCtrl = do_QueryInterface(this);
+ NS_ASSERTION(formCtrl, "This interface should be used by form elements!");
+
+ HTMLFormElement* form =
+ static_cast<HTMLFormElement*>(formCtrl->GetFormElement());
+ if (form) {
+ form->UpdateValidity(IsValid());
+ }
+ HTMLFieldSetElement* fieldSet = formCtrl->GetFieldSet();
+ if (fieldSet) {
+ fieldSet->UpdateValidity(IsValid());
+ }
+ }
+}
+
+void
+nsIConstraintValidation::SetCustomValidity(const nsAString& aError)
+{
+ mCustomValidity.Assign(aError);
+ SetValidityState(VALIDITY_STATE_CUSTOM_ERROR, !mCustomValidity.IsEmpty());
+}
+
+void
+nsIConstraintValidation::SetBarredFromConstraintValidation(bool aBarred)
+{
+ bool previousBarred = mBarredFromConstraintValidation;
+
+ mBarredFromConstraintValidation = aBarred;
+
+ // Inform the form and fieldset elements if our status regarding constraint
+ // validation is going to change.
+ if (!IsValid() && previousBarred != mBarredFromConstraintValidation) {
+ nsCOMPtr<nsIFormControl> formCtrl = do_QueryInterface(this);
+ NS_ASSERTION(formCtrl, "This interface should be used by form elements!");
+
+ // If the element is going to be barred from constraint validation, we can
+ // inform the form and fieldset that we are now valid. Otherwise, we are now
+ // invalid.
+ HTMLFormElement* form =
+ static_cast<HTMLFormElement*>(formCtrl->GetFormElement());
+ if (form) {
+ form->UpdateValidity(aBarred);
+ }
+ HTMLFieldSetElement* fieldSet = formCtrl->GetFieldSet();
+ if (fieldSet) {
+ fieldSet->UpdateValidity(aBarred);
+ }
+ }
+}
+
diff --git a/dom/html/nsIConstraintValidation.h b/dom/html/nsIConstraintValidation.h
new file mode 100644
index 000000000..c15bf846b
--- /dev/null
+++ b/dom/html/nsIConstraintValidation.h
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIConstraintValidition_h___
+#define nsIConstraintValidition_h___
+
+#include "nsISupports.h"
+#include "nsString.h"
+
+class nsIDOMValidityState;
+
+namespace mozilla {
+namespace dom {
+class ValidityState;
+} // namespace dom
+} // namespace mozilla
+
+#define NS_ICONSTRAINTVALIDATION_IID \
+{ 0x983829da, 0x1aaf, 0x449c, \
+ { 0xa3, 0x06, 0x85, 0xd4, 0xf0, 0x31, 0x1c, 0xf6 } }
+
+/**
+ * This interface is for form elements implementing the validity constraint API.
+ * See: http://dev.w3.org/html5/spec/forms.html#the-constraint-validation-api
+ *
+ * This interface has to be implemented by all elements implementing the API
+ * and only them.
+ */
+class nsIConstraintValidation : public nsISupports
+{
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICONSTRAINTVALIDATION_IID);
+
+ friend class mozilla::dom::ValidityState;
+
+ static const uint16_t sContentSpecifiedMaxLengthMessage;
+
+ virtual ~nsIConstraintValidation();
+
+ bool IsValid() const { return mValidityBitField == 0; }
+
+ bool IsCandidateForConstraintValidation() const {
+ return !mBarredFromConstraintValidation;
+ }
+
+ NS_IMETHOD GetValidationMessage(nsAString& aValidationMessage);
+
+ enum ValidityStateType
+ {
+ VALIDITY_STATE_VALUE_MISSING = 0x1 << 0,
+ VALIDITY_STATE_TYPE_MISMATCH = 0x1 << 1,
+ VALIDITY_STATE_PATTERN_MISMATCH = 0x1 << 2,
+ VALIDITY_STATE_TOO_LONG = 0x1 << 3,
+ VALIDITY_STATE_TOO_SHORT = 0x1 << 4,
+ VALIDITY_STATE_RANGE_UNDERFLOW = 0x1 << 5,
+ VALIDITY_STATE_RANGE_OVERFLOW = 0x1 << 6,
+ VALIDITY_STATE_STEP_MISMATCH = 0x1 << 7,
+ VALIDITY_STATE_BAD_INPUT = 0x1 << 8,
+ VALIDITY_STATE_CUSTOM_ERROR = 0x1 << 9,
+ };
+
+ void SetValidityState(ValidityStateType aState,
+ bool aValue);
+
+ // Web IDL binding methods
+ bool WillValidate() const {
+ return IsCandidateForConstraintValidation();
+ }
+ mozilla::dom::ValidityState* Validity();
+ bool CheckValidity();
+ bool ReportValidity();
+
+protected:
+
+ // You can't instantiate an object from that class.
+ nsIConstraintValidation();
+
+ nsresult GetValidity(nsIDOMValidityState** aValidity);
+ nsresult CheckValidity(bool* aValidity);
+ void SetCustomValidity(const nsAString& aError);
+
+ bool GetValidityState(ValidityStateType aState) const
+ {
+ return mValidityBitField & aState;
+ }
+
+ void SetBarredFromConstraintValidation(bool aBarred);
+
+ virtual nsresult GetValidationMessage(nsAString& aValidationMessage,
+ ValidityStateType aType) {
+ return NS_OK;
+ }
+
+protected:
+ /**
+ * A pointer to the ValidityState object.
+ */
+ RefPtr<mozilla::dom::ValidityState> mValidity;
+
+private:
+
+ /**
+ * A bitfield representing the current validity state of the element.
+ * Each bit represent an error. All bits to zero means the element is valid.
+ */
+ int16_t mValidityBitField;
+
+ /**
+ * Keeps track whether the element is barred from constraint validation.
+ */
+ bool mBarredFromConstraintValidation;
+
+ /**
+ * The string representing the custom error.
+ */
+ nsString mCustomValidity;
+};
+
+/**
+ * Use these macro for class inheriting from nsIConstraintValidation to forward
+ * functions to nsIConstraintValidation.
+ */
+#define NS_FORWARD_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY \
+ NS_IMETHOD GetValidity(nsIDOMValidityState** aValidity) { \
+ return nsIConstraintValidation::GetValidity(aValidity); \
+ } \
+ NS_IMETHOD GetWillValidate(bool* aWillValidate) { \
+ *aWillValidate = WillValidate(); \
+ return NS_OK; \
+ } \
+ NS_IMETHOD GetValidationMessage(nsAString& aValidationMessage) { \
+ return nsIConstraintValidation::GetValidationMessage(aValidationMessage); \
+ } \
+ using nsIConstraintValidation::CheckValidity; \
+ NS_IMETHOD CheckValidity(bool* aValidity) { \
+ return nsIConstraintValidation::CheckValidity(aValidity); \
+ }
+
+#define NS_FORWARD_NSICONSTRAINTVALIDATION \
+ NS_FORWARD_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY \
+ NS_IMETHOD SetCustomValidity(const nsAString& aError) { \
+ nsIConstraintValidation::SetCustomValidity(aError); \
+ return NS_OK; \
+ }
+
+
+/* Use these macro when class declares functions from nsIConstraintValidation */
+#define NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(_from) \
+ NS_IMETHODIMP _from::GetValidity(nsIDOMValidityState** aValidity) { \
+ return nsIConstraintValidation::GetValidity(aValidity); \
+ } \
+ NS_IMETHODIMP _from::GetWillValidate(bool* aWillValidate) { \
+ *aWillValidate = WillValidate(); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP _from::GetValidationMessage(nsAString& aValidationMessage) { \
+ return nsIConstraintValidation::GetValidationMessage(aValidationMessage); \
+ } \
+ NS_IMETHODIMP _from::CheckValidity(bool* aValidity) { \
+ return nsIConstraintValidation::CheckValidity(aValidity); \
+ }
+
+#define NS_IMPL_NSICONSTRAINTVALIDATION(_from) \
+ NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(_from) \
+ NS_IMETHODIMP _from::SetCustomValidity(const nsAString& aError) { \
+ nsIConstraintValidation::SetCustomValidity(aError); \
+ return NS_OK; \
+ }
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIConstraintValidation,
+ NS_ICONSTRAINTVALIDATION_IID)
+
+#endif // nsIConstraintValidation_h___
+
diff --git a/dom/html/nsIDateTimeInputArea.idl b/dom/html/nsIDateTimeInputArea.idl
new file mode 100644
index 000000000..6f7cf4b1b
--- /dev/null
+++ b/dom/html/nsIDateTimeInputArea.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(465c0cc3-24cb-48ce-af1a-b18402326b05)]
+interface nsIDateTimeInputArea : nsISupports
+{
+ /**
+ * Called from DOM/Layout when input element value has changed.
+ */
+ void notifyInputElementValueChanged();
+
+ /**
+ * Called when date/time picker value has changed.
+ */
+ void setValueFromPicker(in jsval value);
+
+ /**
+ * Called from DOM/Layout to set focus on inner text box.
+ */
+ void focusInnerTextBox();
+
+ /**
+ * Called from DOM/Layout to blur inner text box.
+ */
+ void blurInnerTextBox();
+
+ /**
+ * Set the current state of the picker, true if it's opened, false otherwise.
+ */
+ void setPickerState(in boolean isOpen);
+};
diff --git a/dom/html/nsIForm.h b/dom/html/nsIForm.h
new file mode 100644
index 000000000..91bb7ac86
--- /dev/null
+++ b/dom/html/nsIForm.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsIForm_h___
+#define nsIForm_h___
+
+#include "nsISupports.h"
+#include "nsAString.h"
+
+class nsIFormControl;
+
+#define NS_FORM_METHOD_GET 0
+#define NS_FORM_METHOD_POST 1
+#define NS_FORM_ENCTYPE_URLENCODED 0
+#define NS_FORM_ENCTYPE_MULTIPART 1
+#define NS_FORM_ENCTYPE_TEXTPLAIN 2
+
+// IID for the nsIForm interface
+#define NS_IFORM_IID \
+{ 0x5e8464c8, 0x015d, 0x4cf9, \
+ { 0x92, 0xc9, 0xa6, 0xb3, 0x30, 0x8f, 0x60, 0x9d } }
+
+/**
+ * This interface provides some methods that can be used to access the
+ * guts of a form. It's being slowly phased out.
+ */
+
+class nsIForm : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFORM_IID)
+
+ /**
+ * Get the element at a specified index position in form.elements
+ *
+ * @param aIndex the index
+ * @param aElement the element at that index
+ * @return NS_OK if there was an element at that position, -1 otherwise
+ */
+ NS_IMETHOD_(nsIFormControl*) GetElementAt(int32_t aIndex) const = 0;
+
+ /**
+ * Get the number of elements in form.elements
+ *
+ * @param aCount the number of elements
+ * @return NS_OK if there was an element at that position, -1 otherwise
+ */
+ NS_IMETHOD_(uint32_t) GetElementCount() const = 0;
+
+ /**
+ * Get the index of the given control within form.elements.
+ * @param aControl the control to find the index of
+ * @param aIndex the index [OUT]
+ */
+ NS_IMETHOD_(int32_t) IndexOfControl(nsIFormControl* aControl) = 0;
+
+ /**
+ * Get the default submit element. If there's no default submit element,
+ * return null.
+ */
+ NS_IMETHOD_(nsIFormControl*) GetDefaultSubmitElement() const = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIForm, NS_IFORM_IID)
+
+#endif /* nsIForm_h___ */
diff --git a/dom/html/nsIFormControl.h b/dom/html/nsIFormControl.h
new file mode 100644
index 000000000..aaa92146c
--- /dev/null
+++ b/dom/html/nsIFormControl.h
@@ -0,0 +1,315 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsIFormControl_h___
+#define nsIFormControl_h___
+
+#include "mozilla/EventForwards.h"
+#include "nsISupports.h"
+
+class nsIDOMHTMLFormElement;
+class nsPresState;
+
+namespace mozilla {
+namespace dom {
+class Element;
+class HTMLFieldSetElement;
+class HTMLFormSubmission;
+} // namespace dom
+} // namespace mozilla
+
+enum FormControlsTypes {
+ NS_FORM_FIELDSET = 1,
+ NS_FORM_OUTPUT,
+ NS_FORM_SELECT,
+ NS_FORM_TEXTAREA,
+ NS_FORM_OBJECT,
+ eFormControlsWithoutSubTypesMax,
+ // After this, all types will have sub-types which introduce new enum lists.
+ // eFormControlsWithoutSubTypesMax let us know if the previous types values
+ // are not overlapping with sub-types/masks.
+
+ // Elements with different types, the value is used as a mask.
+ // When changing the order, adding or removing elements, be sure to update
+ // the static_assert checks accordingly.
+ NS_FORM_BUTTON_ELEMENT = 0x40, // 0b01000000
+ NS_FORM_INPUT_ELEMENT = 0x80 // 0b10000000
+};
+
+enum ButtonElementTypes : uint8_t {
+ NS_FORM_BUTTON_BUTTON = NS_FORM_BUTTON_ELEMENT + 1,
+ NS_FORM_BUTTON_RESET,
+ NS_FORM_BUTTON_SUBMIT,
+ eButtonElementTypesMax
+};
+
+enum InputElementTypes : uint8_t {
+ NS_FORM_INPUT_BUTTON = NS_FORM_INPUT_ELEMENT + 1,
+ NS_FORM_INPUT_CHECKBOX,
+ NS_FORM_INPUT_COLOR,
+ NS_FORM_INPUT_DATE,
+ NS_FORM_INPUT_EMAIL,
+ NS_FORM_INPUT_FILE,
+ NS_FORM_INPUT_HIDDEN,
+ NS_FORM_INPUT_RESET,
+ NS_FORM_INPUT_IMAGE,
+ NS_FORM_INPUT_MONTH,
+ NS_FORM_INPUT_NUMBER,
+ NS_FORM_INPUT_PASSWORD,
+ NS_FORM_INPUT_RADIO,
+ NS_FORM_INPUT_SEARCH,
+ NS_FORM_INPUT_SUBMIT,
+ NS_FORM_INPUT_TEL,
+ NS_FORM_INPUT_TEXT,
+ NS_FORM_INPUT_TIME,
+ NS_FORM_INPUT_URL,
+ NS_FORM_INPUT_RANGE,
+ NS_FORM_INPUT_WEEK,
+ NS_FORM_INPUT_DATETIME_LOCAL,
+ eInputElementTypesMax
+};
+
+static_assert(static_cast<uint32_t>(eFormControlsWithoutSubTypesMax) <
+ static_cast<uint32_t>(NS_FORM_BUTTON_ELEMENT),
+ "Too many FormControlsTypes without sub-types");
+static_assert(static_cast<uint32_t>(eButtonElementTypesMax) <
+ static_cast<uint32_t>(NS_FORM_INPUT_ELEMENT),
+ "Too many ButtonElementTypes");
+static_assert(static_cast<uint32_t>(eInputElementTypesMax) < 1<<8,
+ "Too many form control types");
+
+#define NS_IFORMCONTROL_IID \
+{ 0x4b89980c, 0x4dcd, 0x428f, \
+ { 0xb7, 0xad, 0x43, 0x5b, 0x93, 0x29, 0x79, 0xec } }
+
+/**
+ * Interface which all form controls (e.g. buttons, checkboxes, text,
+ * radio buttons, select, etc) implement in addition to their dom specific
+ * interface.
+ */
+class nsIFormControl : public nsISupports
+{
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFORMCONTROL_IID)
+
+ /**
+ * Get the fieldset for this form control.
+ * @return the fieldset
+ */
+ virtual mozilla::dom::HTMLFieldSetElement *GetFieldSet() = 0;
+
+ /**
+ * Get the form for this form control.
+ * @return the form
+ */
+ virtual mozilla::dom::Element *GetFormElement() = 0;
+
+ /**
+ * Set the form for this form control.
+ * @param aForm the form. This must not be null.
+ *
+ * @note that when setting the form the control is not added to the
+ * form. It adds itself when it gets bound to the tree thereafter,
+ * so that it can be properly sorted with the other controls in the
+ * form.
+ */
+ virtual void SetForm(nsIDOMHTMLFormElement* aForm) = 0;
+
+ /**
+ * Tell the control to forget about its form.
+ *
+ * @param aRemoveFromForm set false if you do not want this element removed
+ * from the form. (Used by nsFormControlList::Clear())
+ */
+ virtual void ClearForm(bool aRemoveFromForm) = 0;
+
+ /**
+ * Get the type of this control as an int (see NS_FORM_* above)
+ * @return the type of this control
+ */
+ NS_IMETHOD_(uint32_t) GetType() const = 0 ;
+
+ /**
+ * Reset this form control (as it should be when the user clicks the Reset
+ * button)
+ */
+ NS_IMETHOD Reset() = 0;
+
+ /**
+ * Tells the form control to submit its names and values to the form
+ * submission object
+ * @param aFormSubmission the form submission to notify of names/values/files
+ * to submit
+ */
+ NS_IMETHOD
+ SubmitNamesValues(mozilla::dom::HTMLFormSubmission* aFormSubmission) = 0;
+
+ /**
+ * Save to presentation state. The form control will determine whether it
+ * has anything to save and if so, create an entry in the layout history for
+ * its pres context.
+ */
+ NS_IMETHOD SaveState() = 0;
+
+ /**
+ * Restore from presentation state. You pass in the presentation state for
+ * this form control (generated with GenerateStateKey() + "-C") and the form
+ * control will grab its state from there.
+ *
+ * @param aState the pres state to use to restore the control
+ * @return true if the form control was a checkbox and its
+ * checked state was restored, false otherwise.
+ */
+ virtual bool RestoreState(nsPresState* aState) = 0;
+
+ virtual bool AllowDrop() = 0;
+
+ /**
+ * Returns whether this is a control which submits the form when activated by
+ * the user.
+ * @return whether this is a submit control.
+ */
+ inline bool IsSubmitControl() const;
+
+ /**
+ * Returns whether this is a text control.
+ * @param aExcludePassword to have NS_FORM_INPUT_PASSWORD returning false.
+ * @return whether this is a text control.
+ */
+ inline bool IsTextControl(bool aExcludePassword) const;
+
+ /**
+ * Returns true if this is a text control or a number control.
+ * @param aExcludePassword to have NS_FORM_INPUT_PASSWORD returning false.
+ * @return true if this is a text control or a number control.
+ */
+ inline bool IsTextOrNumberControl(bool aExcludePassword) const;
+
+ /**
+ * Returns whether this is a single line text control.
+ * @param aExcludePassword to have NS_FORM_INPUT_PASSWORD returning false.
+ * @return whether this is a single line text control.
+ */
+ inline bool IsSingleLineTextControl(bool aExcludePassword) const;
+
+ /**
+ * Returns whether this is a submittable form control.
+ * @return whether this is a submittable form control.
+ */
+ inline bool IsSubmittableControl() const;
+
+ /**
+ * Returns whether this form control can have draggable children.
+ * @return whether this form control can have draggable children.
+ */
+ inline bool AllowDraggableChildren() const;
+
+ virtual bool IsDisabledForEvents(mozilla::EventMessage aMessage)
+ {
+ return false;
+ }
+protected:
+
+ /**
+ * Returns whether mType corresponds to a single line text control type.
+ * @param aExcludePassword to have NS_FORM_INPUT_PASSWORD ignored.
+ * @param aType the type to be tested.
+ * @return whether mType corresponds to a single line text control type.
+ */
+ inline static bool IsSingleLineTextControl(bool aExcludePassword, uint32_t aType);
+
+ /**
+ * Returns whether this is a auto-focusable form control.
+ * @return whether this is a auto-focusable form control.
+ */
+ inline bool IsAutofocusable() const;
+};
+
+bool
+nsIFormControl::IsSubmitControl() const
+{
+ uint32_t type = GetType();
+ return type == NS_FORM_INPUT_SUBMIT ||
+ type == NS_FORM_INPUT_IMAGE ||
+ type == NS_FORM_BUTTON_SUBMIT;
+}
+
+bool
+nsIFormControl::IsTextControl(bool aExcludePassword) const
+{
+ uint32_t type = GetType();
+ return type == NS_FORM_TEXTAREA ||
+ IsSingleLineTextControl(aExcludePassword, type);
+}
+
+bool
+nsIFormControl::IsTextOrNumberControl(bool aExcludePassword) const
+{
+ return IsTextControl(aExcludePassword) || GetType() == NS_FORM_INPUT_NUMBER;
+}
+
+bool
+nsIFormControl::IsSingleLineTextControl(bool aExcludePassword) const
+{
+ return IsSingleLineTextControl(aExcludePassword, GetType());
+}
+
+/*static*/
+bool
+nsIFormControl::IsSingleLineTextControl(bool aExcludePassword, uint32_t aType)
+{
+ return aType == NS_FORM_INPUT_TEXT ||
+ aType == NS_FORM_INPUT_EMAIL ||
+ aType == NS_FORM_INPUT_SEARCH ||
+ aType == NS_FORM_INPUT_TEL ||
+ aType == NS_FORM_INPUT_URL ||
+ // TODO: those are temporary until bug 773205 is fixed.
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+ // On Android/B2G, date/time input appears as a normal text box.
+ aType == NS_FORM_INPUT_TIME ||
+#endif
+ aType == NS_FORM_INPUT_DATE ||
+ aType == NS_FORM_INPUT_MONTH ||
+ aType == NS_FORM_INPUT_WEEK ||
+ aType == NS_FORM_INPUT_DATETIME_LOCAL ||
+ (!aExcludePassword && aType == NS_FORM_INPUT_PASSWORD);
+}
+
+bool
+nsIFormControl::IsSubmittableControl() const
+{
+ // TODO: keygen should be in that list, see bug 101019.
+ uint32_t type = GetType();
+ return type == NS_FORM_OBJECT ||
+ type == NS_FORM_TEXTAREA ||
+ type == NS_FORM_SELECT ||
+ // type == NS_FORM_KEYGEN ||
+ type & NS_FORM_BUTTON_ELEMENT ||
+ type & NS_FORM_INPUT_ELEMENT;
+}
+
+bool
+nsIFormControl::AllowDraggableChildren() const
+{
+ uint32_t type = GetType();
+ return type == NS_FORM_OBJECT ||
+ type == NS_FORM_FIELDSET ||
+ type == NS_FORM_OUTPUT;
+}
+
+bool
+nsIFormControl::IsAutofocusable() const
+{
+ uint32_t type = GetType();
+ return type & NS_FORM_INPUT_ELEMENT ||
+ type & NS_FORM_BUTTON_ELEMENT ||
+ type == NS_FORM_TEXTAREA ||
+ type == NS_FORM_SELECT;
+}
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIFormControl, NS_IFORMCONTROL_IID)
+
+#endif /* nsIFormControl_h___ */
diff --git a/dom/html/nsIFormProcessor.h b/dom/html/nsIFormProcessor.h
new file mode 100644
index 000000000..b18305ac4
--- /dev/null
+++ b/dom/html/nsIFormProcessor.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/**
+ * MODULE NOTES:
+ * @created kmcclusk 10/19/99
+ *
+ */
+
+#ifndef nsIFormProcessor_h__
+#define nsIFormProcessor_h__
+
+#include "nsISupports.h"
+#include "nsTArrayForwardDeclare.h"
+
+class nsString;
+class nsIDOMHTMLElement;
+
+// {0ae53c0f-8ea2-4916-bedc-717443c3e185}
+#define NS_FORMPROCESSOR_CID \
+{ 0x0ae53c0f, 0x8ea2, 0x4916, { 0xbe, 0xdc, 0x71, 0x74, 0x43, 0xc3, 0xe1, 0x85 } }
+
+#define NS_FORMPROCESSOR_CONTRACTID "@mozilla.org/layout/form-processor;1"
+
+// bf8b1986-8800-424b-b1e5-7a2ca8b9e76c
+#define NS_IFORMPROCESSOR_IID \
+{ 0xbf8b1986, 0x8800, 0x424b, \
+ { 0xb1, 0xe5, 0x7a, 0x2c, 0xa8, 0xb9, 0xe7, 0x6c } }
+
+// XXX:In the future, we should examine combining this interface with nsIFormSubmitObserver.
+// nsIFormSubmitObserver could have a before, during, and after form submission methods.
+// The before and after methods would be passed a nsISupports interface. The During would
+// Have the same method signature as ProcessValue.
+
+
+class nsIFormProcessor : public nsISupports {
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFORMPROCESSOR_IID)
+
+ /* ProcessValue is called for each name value pair that is
+ * about to be submitted for both "get" and "post" form submissions.
+ *
+ * The formprocessor is registered as a service that gets called for
+ * every form submission.
+ *
+ * @param aElement element which the attribute/value pair is submitted for
+ * @param aName value of the form element name attribute about to be submitted
+ * @param aValue On entry it contains the value about to be submitted for aName.
+ * On exit it contains the value which will actually be submitted for aName.
+ *
+ */
+ virtual nsresult ProcessValue(nsIDOMHTMLElement* aElement,
+ const nsAString& aName,
+ nsAString& aValue) = 0;
+
+ /**
+ * The same as above, but with the element unpacked so that this can be
+ * called as the result of an IPC message.
+ */
+ virtual nsresult ProcessValueIPC(const nsAString& aOldValue,
+ const nsAString& aKeyType,
+ const nsAString& aChallenge,
+ const nsAString& aKeyParams,
+ nsAString& newValue) = 0;
+
+ /* Provide content for a form element. This method provides a mechanism to provide
+ * content which comes from a source other than the document (i.e. a local database)
+ *
+ * @param aFormType Type of form to get content for.
+ * @param aOptions List of nsStrings which define the contents for the form element
+ * @param aAttr Attribute to be attached to the form element. It is used to identify
+ * the form element contains non-standard content.
+ */
+
+ virtual nsresult ProvideContent(const nsAString& aFormType,
+ nsTArray<nsString>& aContent,
+ nsAString& aAttribute) = 0;
+
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIFormProcessor, NS_IFORMPROCESSOR_IID)
+
+#endif /* nsIFormProcessor_h__ */
+
diff --git a/dom/html/nsIFormSubmitObserver.idl b/dom/html/nsIFormSubmitObserver.idl
new file mode 100644
index 000000000..7fc9f6cee
--- /dev/null
+++ b/dom/html/nsIFormSubmitObserver.idl
@@ -0,0 +1,29 @@
+/* -*- 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 nsIDOMHTMLFormElement;
+interface mozIDOMWindow;
+interface nsIURI;
+interface nsIArray;
+
+[scriptable, uuid(867cb7e7-835d-408b-9788-d2834d284e03)]
+interface nsIFormSubmitObserver: nsISupports
+{
+ void notify(in nsIDOMHTMLFormElement formNode, in mozIDOMWindow window, in nsIURI actionURL, out boolean cancelSubmit);
+
+ void notifyInvalidSubmit(in nsIDOMHTMLFormElement formNode,
+ in nsIArray invalidElements);
+};
+
+%{C++
+#define NS_FORMSUBMIT_SUBJECT "formsubmit"
+#define NS_EARLYFORMSUBMIT_SUBJECT "earlyformsubmit"
+#define NS_FIRST_FORMSUBMIT_CATEGORY "firstformsubmit"
+#define NS_PASSWORDMANAGER_CATEGORY "passwordmanager"
+#define NS_INVALIDFORMSUBMIT_SUBJECT "invalidformsubmit"
+%}
diff --git a/dom/html/nsIHTMLCollection.h b/dom/html/nsIHTMLCollection.h
new file mode 100644
index 000000000..7dbfe8766
--- /dev/null
+++ b/dom/html/nsIHTMLCollection.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIHTMLCollection_h___
+#define nsIHTMLCollection_h___
+
+#include "nsIDOMHTMLCollection.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsWrapperCache.h"
+#include "js/GCAPI.h"
+#include "js/TypeDecls.h"
+
+class nsINode;
+class nsString;
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+// IID for the nsIHTMLCollection interface
+#define NS_IHTMLCOLLECTION_IID \
+{ 0x4e169191, 0x5196, 0x4e17, \
+ { 0xa4, 0x79, 0xd5, 0x35, 0x0b, 0x5b, 0x0a, 0xcd } }
+
+/**
+ * An internal interface
+ */
+class nsIHTMLCollection : public nsIDOMHTMLCollection
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IHTMLCOLLECTION_IID)
+
+ /**
+ * Get the root node for this HTML collection.
+ */
+ virtual nsINode* GetParentObject() = 0;
+
+ using nsIDOMHTMLCollection::Item;
+ using nsIDOMHTMLCollection::NamedItem;
+
+ uint32_t Length()
+ {
+ uint32_t length;
+ GetLength(&length);
+ return length;
+ }
+ virtual mozilla::dom::Element* GetElementAt(uint32_t index) = 0;
+ mozilla::dom::Element* Item(uint32_t index)
+ {
+ return GetElementAt(index);
+ }
+ mozilla::dom::Element* IndexedGetter(uint32_t index, bool& aFound)
+ {
+ mozilla::dom::Element* item = Item(index);
+ aFound = !!item;
+ return item;
+ }
+ mozilla::dom::Element* NamedItem(const nsAString& aName)
+ {
+ bool dummy;
+ return NamedGetter(aName, dummy);
+ }
+ mozilla::dom::Element* NamedGetter(const nsAString& aName, bool& aFound)
+ {
+ return GetFirstNamedElement(aName, aFound);
+ }
+ virtual mozilla::dom::Element*
+ GetFirstNamedElement(const nsAString& aName, bool& aFound) = 0;
+
+ virtual void GetSupportedNames(nsTArray<nsString>& aNames) = 0;
+
+ JSObject* GetWrapperPreserveColor()
+ {
+ return GetWrapperPreserveColorInternal();
+ }
+ JSObject* GetWrapper()
+ {
+ JSObject* obj = GetWrapperPreserveColor();
+ if (obj) {
+ JS::ExposeObjectToActiveJS(obj);
+ }
+ return obj;
+ }
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) = 0;
+protected:
+ virtual JSObject* GetWrapperPreserveColorInternal() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIHTMLCollection, NS_IHTMLCOLLECTION_IID)
+
+#endif /* nsIHTMLCollection_h___ */
diff --git a/dom/html/nsIHTMLDocument.h b/dom/html/nsIHTMLDocument.h
new file mode 100644
index 000000000..8dbee1170
--- /dev/null
+++ b/dom/html/nsIHTMLDocument.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 nsIHTMLDocument_h
+#define nsIHTMLDocument_h
+
+#include "nsISupports.h"
+#include "nsCompatibility.h"
+
+class nsIContent;
+class nsIEditor;
+class nsContentList;
+
+#define NS_IHTMLDOCUMENT_IID \
+{ 0xcf814492, 0x303c, 0x4718, \
+ { 0x9a, 0x3e, 0x39, 0xbc, 0xd5, 0x2c, 0x10, 0xdb } }
+
+/**
+ * HTML document extensions to nsIDocument.
+ */
+class nsIHTMLDocument : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IHTMLDOCUMENT_IID)
+
+ /**
+ * Set compatibility mode for this document
+ */
+ virtual void SetCompatibilityMode(nsCompatibility aMode) = 0;
+
+ /**
+ * Called when form->BindToTree() is called so that document knows
+ * immediately when a form is added
+ */
+ virtual void AddedForm() = 0;
+ /**
+ * Called when form->SetDocument() is called so that document knows
+ * immediately when a form is removed
+ */
+ virtual void RemovedForm() = 0;
+ /**
+ * Called to get a better count of forms than document.forms can provide
+ * without calling FlushPendingNotifications (bug 138892).
+ */
+ // XXXbz is this still needed now that we can flush just content,
+ // not the rest?
+ virtual int32_t GetNumFormsSynchronous() = 0;
+
+ virtual bool IsWriting() = 0;
+
+ /**
+ * Get the list of form elements in the document.
+ */
+ virtual nsContentList* GetForms() = 0;
+
+ /**
+ * Get the list of form controls in the document (all elements in
+ * the document that are of type nsIContent::eHTML_FORM_CONTROL).
+ */
+ virtual nsContentList* GetFormControls() = 0;
+
+ /**
+ * Should be called when an element's editable changes as a result of
+ * changing its contentEditable attribute/property.
+ *
+ * @param aElement the element for which the contentEditable
+ * attribute/property was changed
+ * @param aChange +1 if the contentEditable attribute/property was changed to
+ * true, -1 if it was changed to false
+ */
+ virtual nsresult ChangeContentEditableCount(nsIContent *aElement,
+ int32_t aChange) = 0;
+
+ enum EditingState {
+ eTearingDown = -2,
+ eSettingUp = -1,
+ eOff = 0,
+ eDesignMode,
+ eContentEditable
+ };
+
+ /**
+ * Returns whether the document is editable.
+ */
+ bool IsEditingOn()
+ {
+ return GetEditingState() == eDesignMode ||
+ GetEditingState() == eContentEditable;
+ }
+
+ /**
+ * Returns the editing state of the document (not editable, contentEditable or
+ * designMode).
+ */
+ virtual EditingState GetEditingState() = 0;
+
+ /**
+ * Set the editing state of the document. Don't use this if you want
+ * to enable/disable editing, call EditingStateChanged() or
+ * SetDesignMode().
+ */
+ virtual nsresult SetEditingState(EditingState aState) = 0;
+
+ /**
+ * Disables getting and setting cookies
+ */
+ virtual void DisableCookieAccess() = 0;
+
+ /**
+ * Called when this nsIHTMLDocument's editor is destroyed.
+ */
+ virtual void TearingDownEditor(nsIEditor *aEditor) = 0;
+
+ virtual void SetIsXHTML(bool aXHTML) = 0;
+
+ virtual void SetDocWriteDisabled(bool aDisabled) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIHTMLDocument, NS_IHTMLDOCUMENT_IID)
+
+#endif /* nsIHTMLDocument_h */
diff --git a/dom/html/nsIHTMLMenu.idl b/dom/html/nsIHTMLMenu.idl
new file mode 100644
index 000000000..254b4aa92
--- /dev/null
+++ b/dom/html/nsIHTMLMenu.idl
@@ -0,0 +1,42 @@
+/* -*- 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 nsIMenuBuilder;
+
+/**
+ * A private interface.
+ * All methods throw NS_ERROR_DOM_SECURITY_ERR if the caller is not chrome.
+ */
+
+[scriptable, uuid(d3d068d8-e223-4228-ba39-4d6df21ba616)]
+interface nsIHTMLMenu : nsISupports
+{
+ /**
+ * Creates and dispatches a trusted event named "show".
+ * The event is not cancelable and does not bubble.
+ * See http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus
+ */
+ void sendShowEvent();
+
+ /**
+ * Creates a native menu builder. The builder type is dependent on menu type.
+ * Currently, it returns nsXULContextMenuBuilder for context menus.
+ * Toolbar menus are not yet supported (the method returns null).
+ */
+ nsIMenuBuilder createBuilder();
+
+ /*
+ * Builds a menu by iterating over menu children.
+ * See http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#building-menus-and-toolbars
+ * The caller can use a native builder by calling createBuilder() or provide
+ * a custom builder that implements the nsIMenuBuilder interface.
+ * A custom builder can be used for example to build native context menus
+ * that are not defined using <menupopup>.
+ */
+ void build(in nsIMenuBuilder aBuilder);
+
+};
diff --git a/dom/html/nsIImageDocument.idl b/dom/html/nsIImageDocument.idl
new file mode 100644
index 000000000..845dd0d87
--- /dev/null
+++ b/dom/html/nsIImageDocument.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"
+
+/**
+ * @status UNDER_DEVELOPMENT
+ */
+
+interface imgIRequest;
+
+[scriptable, uuid(87c27f98-37dc-4b64-a8cd-92003624bcee)]
+interface nsIImageDocument : nsISupports {
+
+ /* Whether the image is overflowing visible area. */
+ readonly attribute boolean imageIsOverflowing;
+
+ /* Whether the image has been resized to fit visible area. */
+ readonly attribute boolean imageIsResized;
+
+ /* The image request being displayed in the content area */
+ readonly attribute imgIRequest imageRequest;
+
+ /* Resize the image to fit visible area. */
+ [binaryname(DOMShrinkToFit)]
+ void shrinkToFit();
+
+ /* Restore image original size. */
+ [binaryname(DOMRestoreImage)]
+ void restoreImage();
+
+ /* Restore the image, trying to keep a certain pixel in the same position.
+ * The coordinate system is that of the shrunken image.
+ */
+ [binaryname(DOMRestoreImageTo)]
+ void restoreImageTo(in long x, in long y);
+
+ /* A helper method for switching between states.
+ * The switching logic is as follows. If the image has been resized
+ * restore image original size, otherwise if the image is overflowing
+ * current visible area resize the image to fit the area.
+ */
+ [binaryname(DOMToggleImageSize)]
+ void toggleImageSize();
+};
diff --git a/dom/html/nsIMenuBuilder.idl b/dom/html/nsIMenuBuilder.idl
new file mode 100644
index 000000000..02664480f
--- /dev/null
+++ b/dom/html/nsIMenuBuilder.idl
@@ -0,0 +1,76 @@
+/* -*- 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 nsIDOMHTMLMenuItemElement;
+
+/**
+ * An interface used to construct native toolbar or context menus from <menu>
+ */
+
+[scriptable, uuid(93F4A48F-D043-4F45-97FD-9771EA1AF976)]
+interface nsIMenuBuilder : nsISupports
+{
+
+ /**
+ * Create the top level menu or a submenu. The implementation should create
+ * a new context for this menu, so all subsequent methods will add new items
+ * to this newly created menu.
+ */
+ void openContainer(in DOMString aLabel);
+
+ /**
+ * Add a new menu item. All menu item details can be obtained from
+ * the element. This method is not called for hidden elements or elements
+ * with no or empty label. The icon should be loaded only if aCanLoadIcon
+ * is true.
+ */
+ void addItemFor(in nsIDOMHTMLMenuItemElement aElement,
+ in boolean aCanLoadIcon);
+
+ /**
+ * Create a new separator.
+ */
+ void addSeparator();
+
+ /**
+ * Remove last added separator.
+ * Sometimes it's needed to remove last added separator, otherwise it's not
+ * possible to implement the postprocessing in one pass.
+ * See http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#building-menus-and-toolbars
+ */
+ void undoAddSeparator();
+
+ /**
+ * Set the context to the parent menu.
+ */
+ void closeContainer();
+
+ /**
+ * Returns a JSON string representing the menu hierarchy. For a context menu,
+ * it will be of the form:
+ * {
+ * type: "menu",
+ * children: [
+ * {
+ * type: "menuitem",
+ * label: "label",
+ * icon: "image.png"
+ * },
+ * {
+ * type: "separator",
+ * },
+ * ];
+ */
+ AString toJSONString();
+
+ /**
+ * Invoke the action of the menuitem with assigned id aGeneratedItemId.
+ *
+ * @param aGeneratedItemId the menuitem id
+ */
+ void click(in DOMString aGeneratedItemId);
+};
diff --git a/dom/html/nsIPhonetic.idl b/dom/html/nsIPhonetic.idl
new file mode 100644
index 000000000..3885ee238
--- /dev/null
+++ b/dom/html/nsIPhonetic.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"
+
+/**
+ * This interface is used to get the phonetic value of the input text.
+ * It can be used to get corresponding phonetic value for ideographic text.
+ * The interface can be retrieved by calling QI to the interface
+ * which implements the phonetic interface.
+ */
+
+[uuid(BC6EA726-AB56-46b6-A21A-AA7B76D6818F)]
+interface nsIPhonetic : nsISupports
+{
+ /**
+ * phonetic get the phonetic value of the input text
+ */
+ readonly attribute DOMString phonetic;
+};
diff --git a/dom/html/nsIRadioGroupContainer.h b/dom/html/nsIRadioGroupContainer.h
new file mode 100644
index 000000000..3d530a723
--- /dev/null
+++ b/dom/html/nsIRadioGroupContainer.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 nsIRadioGroupContainer_h___
+#define nsIRadioGroupContainer_h___
+
+#include "nsISupports.h"
+
+class nsIRadioVisitor;
+class nsIFormControl;
+
+namespace mozilla {
+namespace dom {
+class HTMLInputElement;
+} // namespace dom
+} // namespace mozilla
+
+#define NS_IRADIOGROUPCONTAINER_IID \
+{ 0x800320a0, 0x733f, 0x11e4, \
+ { 0x82, 0xf8, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
+
+/**
+ * A container that has multiple radio groups in it, defined by name.
+ */
+class nsIRadioGroupContainer : public nsISupports
+{
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IRADIOGROUPCONTAINER_IID)
+
+ /**
+ * Walk through the radio group, visiting each note with avisitor->Visit()
+ * @param aName the group name
+ * @param aVisitor the visitor to visit with
+ * @param aFlushContent whether to ensure the content model is up to date
+ * before walking.
+ */
+ NS_IMETHOD WalkRadioGroup(const nsAString& aName,
+ nsIRadioVisitor* aVisitor,
+ bool aFlushContent) = 0;
+
+ /**
+ * Set the current radio button in a group
+ * @param aName the group name
+ * @param aRadio the currently selected radio button
+ */
+ virtual void SetCurrentRadioButton(const nsAString& aName,
+ mozilla::dom::HTMLInputElement* aRadio) = 0;
+
+ /**
+ * Get the current radio button in a group
+ * @param aName the group name
+ * @return the currently selected radio button
+ */
+ virtual mozilla::dom::HTMLInputElement* GetCurrentRadioButton(const nsAString& aName) = 0;
+
+ /**
+ * Get the next/prev radio button in a group
+ * @param aName the group name
+ * @param aPrevious, true gets previous radio button, false gets next
+ * @param aFocusedRadio the currently focused radio button
+ * @param aRadio the currently selected radio button [OUT]
+ */
+ NS_IMETHOD GetNextRadioButton(const nsAString& aName,
+ const bool aPrevious,
+ mozilla::dom::HTMLInputElement* aFocusedRadio,
+ mozilla::dom::HTMLInputElement** aRadio) = 0;
+
+ /**
+ * Add radio button to radio group
+ *
+ * Note that forms do not do anything for this method since they already
+ * store radio groups on their own.
+ *
+ * @param aName radio group's name
+ * @param aRadio radio button's pointer
+ */
+ virtual void AddToRadioGroup(const nsAString& aName, nsIFormControl* aRadio) = 0;
+
+ /**
+ * Remove radio button from radio group
+ *
+ * Note that forms do not do anything for this method since they already
+ * store radio groups on their own.
+ *
+ * @param aName radio group's name
+ * @param aRadio radio button's pointer
+ */
+ virtual void RemoveFromRadioGroup(const nsAString& aName, nsIFormControl* aRadio) = 0;
+
+ virtual uint32_t GetRequiredRadioCount(const nsAString& aName) const = 0;
+ virtual void RadioRequiredWillChange(const nsAString& aName,
+ bool aRequiredAdded) = 0;
+ virtual bool GetValueMissingState(const nsAString& aName) const = 0;
+ virtual void SetValueMissingState(const nsAString& aName, bool aValue) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIRadioGroupContainer,
+ NS_IRADIOGROUPCONTAINER_IID)
+
+#endif /* nsIRadioGroupContainer_h__ */
diff --git a/dom/html/nsIRadioVisitor.h b/dom/html/nsIRadioVisitor.h
new file mode 100644
index 000000000..425a50692
--- /dev/null
+++ b/dom/html/nsIRadioVisitor.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIRadioVisitor_h___
+#define nsIRadioVisitor_h___
+
+#include "nsISupports.h"
+class nsIFormControl;
+
+// IID for the nsIRadioControl interface
+#define NS_IRADIOVISITOR_IID \
+{ 0xc6bed232, 0x1181, 0x4ab2, \
+ { 0xa1, 0xda, 0x55, 0xc2, 0x13, 0x6d, 0xea, 0x3d } }
+
+/**
+ * This interface is used for the text control frame to store its value away
+ * into the content.
+ */
+class nsIRadioVisitor : public nsISupports {
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IRADIOVISITOR_IID)
+
+ /**
+ * Visit a node in the tree. This is meant to be called on all radios in a
+ * group, sequentially. (Each radio group implementor may define
+ * sequentially in their own way, it just has to be the same every time.)
+ * Currently all radio groups are ordered in the order they appear in the
+ * document. Radio group implementors should honor the return value of the
+ * method and stop iterating if the return value is false.
+ *
+ * @param aRadio the radio button in question (must be nullptr and QI'able to
+ * nsIRadioControlElement)
+ */
+ virtual bool Visit(nsIFormControl* aRadio) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIRadioVisitor, NS_IRADIOVISITOR_IID)
+
+#endif // nsIRadioVisitor_h___
diff --git a/dom/html/nsITextControlElement.h b/dom/html/nsITextControlElement.h
new file mode 100644
index 000000000..9adbf377e
--- /dev/null
+++ b/dom/html/nsITextControlElement.h
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsITextControlElement_h___
+#define nsITextControlElement_h___
+
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+class nsIContent;
+class nsAString;
+class nsIEditor;
+class nsISelectionController;
+class nsFrameSelection;
+class nsTextControlFrame;
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+// IID for the nsITextControl interface
+#define NS_ITEXTCONTROLELEMENT_IID \
+{ 0x3df7db6d, 0xa548, 0x4e20, \
+ { 0x97, 0xfd, 0x75, 0xa3, 0x31, 0xa2, 0xf3, 0xd4 } }
+
+/**
+ * This interface is used for the text control frame to get the editor and
+ * selection controller objects, and some helper properties.
+ */
+class nsITextControlElement : public nsISupports {
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITEXTCONTROLELEMENT_IID)
+
+ /**
+ * Tell the control that value has been deliberately changed (or not).
+ */
+ NS_IMETHOD SetValueChanged(bool changed) = 0;
+
+ /**
+ * Find out whether this is a single line text control. (text or password)
+ * @return whether this is a single line text control
+ */
+ NS_IMETHOD_(bool) IsSingleLineTextControl() const = 0;
+
+ /**
+ * Find out whether this control is a textarea.
+ * @return whether this is a textarea text control
+ */
+ NS_IMETHOD_(bool) IsTextArea() const = 0;
+
+ /**
+ * Find out whether this control edits plain text. (Currently always true.)
+ * @return whether this is a plain text control
+ */
+ NS_IMETHOD_(bool) IsPlainTextControl() const = 0;
+
+ /**
+ * Find out whether this is a password control (input type=password)
+ * @return whether this is a password ontrol
+ */
+ NS_IMETHOD_(bool) IsPasswordTextControl() const = 0;
+
+ /**
+ * Get the cols attribute (if textarea) or a default
+ * @return the number of columns to use
+ */
+ NS_IMETHOD_(int32_t) GetCols() = 0;
+
+ /**
+ * Get the column index to wrap at, or -1 if we shouldn't wrap
+ */
+ NS_IMETHOD_(int32_t) GetWrapCols() = 0;
+
+ /**
+ * Get the rows attribute (if textarea) or a default
+ * @return the number of rows to use
+ */
+ NS_IMETHOD_(int32_t) GetRows() = 0;
+
+ /**
+ * Get the default value of the text control
+ */
+ NS_IMETHOD_(void) GetDefaultValueFromContent(nsAString& aValue) = 0;
+
+ /**
+ * Return true if the value of the control has been changed.
+ */
+ NS_IMETHOD_(bool) ValueChanged() const = 0;
+
+ /**
+ * Get the current value of the text editor.
+ *
+ * @param aValue the buffer to retrieve the value in
+ * @param aIgnoreWrap whether to ignore the text wrapping behavior specified
+ * for the element.
+ */
+ NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, bool aIgnoreWrap) const = 0;
+
+ /**
+ * Get the editor object associated with the text editor.
+ * The return value is null if the control does not support an editor
+ * (for example, if it is a checkbox.)
+ */
+ NS_IMETHOD_(nsIEditor*) GetTextEditor() = 0;
+
+ /**
+ * Get the selection controller object associated with the text editor.
+ * The return value is null if the control does not support an editor
+ * (for example, if it is a checkbox.)
+ */
+ NS_IMETHOD_(nsISelectionController*) GetSelectionController() = 0;
+
+ NS_IMETHOD_(nsFrameSelection*) GetConstFrameSelection() = 0;
+
+ /**
+ * Binds a frame to the text control. This is performed when a frame
+ * is created for the content node.
+ */
+ NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) = 0;
+
+ /**
+ * Unbinds a frame from the text control. This is performed when a frame
+ * belonging to a content node is destroyed.
+ */
+ NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) = 0;
+
+ /**
+ * Creates an editor for the text control. This should happen when
+ * a frame has been created for the text control element, but the created
+ * editor may outlive the frame itself.
+ */
+ NS_IMETHOD CreateEditor() = 0;
+
+ /**
+ * Get the anonymous root node for the text control.
+ */
+ NS_IMETHOD_(nsIContent*) GetRootEditorNode() = 0;
+
+ /**
+ * Create the placeholder anonymous node for the text control and returns it.
+ */
+ NS_IMETHOD_(mozilla::dom::Element*) CreatePlaceholderNode() = 0;
+
+ /**
+ * Get the placeholder anonymous node for the text control.
+ */
+ NS_IMETHOD_(mozilla::dom::Element*) GetPlaceholderNode() = 0;
+
+ /**
+ * Initialize the keyboard event listeners.
+ */
+ NS_IMETHOD_(void) InitializeKeyboardEventListeners() = 0;
+
+ /**
+ * Update the placeholder visibility based on the element's state.
+ */
+ NS_IMETHOD_(void) UpdatePlaceholderVisibility(bool aNotify) = 0;
+
+ /**
+ * Returns the current expected placeholder visibility state.
+ */
+ NS_IMETHOD_(bool) GetPlaceholderVisibility() = 0;
+
+ /**
+ * Callback called whenever the value is changed.
+ */
+ NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) = 0;
+
+ static const int32_t DEFAULT_COLS = 20;
+ static const int32_t DEFAULT_ROWS = 1;
+ static const int32_t DEFAULT_ROWS_TEXTAREA = 2;
+ static const int32_t DEFAULT_UNDO_CAP = 1000;
+
+ // wrap can be one of these three values.
+ typedef enum {
+ eHTMLTextWrap_Off = 1, // "off"
+ eHTMLTextWrap_Hard = 2, // "hard"
+ eHTMLTextWrap_Soft = 3 // the default
+ } nsHTMLTextWrap;
+
+ static bool
+ GetWrapPropertyEnum(nsIContent* aContent, nsHTMLTextWrap& aWrapProp);
+
+ /**
+ * Does the editor have a selection cache?
+ *
+ * Note that this function has the side effect of making the editor for input
+ * elements be initialized eagerly.
+ */
+ NS_IMETHOD_(bool) HasCachedSelection() = 0;
+
+ static already_AddRefed<nsITextControlElement>
+ GetTextControlElementFromEditingHost(nsIContent* aHost);
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsITextControlElement,
+ NS_ITEXTCONTROLELEMENT_IID)
+
+#endif // nsITextControlElement_h___
+
diff --git a/dom/html/nsRadioVisitor.cpp b/dom/html/nsRadioVisitor.cpp
new file mode 100644
index 000000000..a3f8a34f8
--- /dev/null
+++ b/dom/html/nsRadioVisitor.cpp
@@ -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/. */
+
+#include "nsRadioVisitor.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "nsIConstraintValidation.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsRadioVisitor, nsIRadioVisitor)
+
+bool
+nsRadioSetCheckedChangedVisitor::Visit(nsIFormControl* aRadio)
+{
+ RefPtr<HTMLInputElement> radio =
+ static_cast<HTMLInputElement*>(aRadio);
+ NS_ASSERTION(radio, "Visit() passed a null button!");
+
+ radio->SetCheckedChangedInternal(mCheckedChanged);
+ return true;
+}
+
+bool
+nsRadioGetCheckedChangedVisitor::Visit(nsIFormControl* aRadio)
+{
+ if (aRadio == mExcludeElement) {
+ return true;
+ }
+
+ RefPtr<HTMLInputElement> radio =
+ static_cast<HTMLInputElement*>(aRadio);
+ NS_ASSERTION(radio, "Visit() passed a null button!");
+
+ *mCheckedChanged = radio->GetCheckedChanged();
+ return false;
+}
+
+bool
+nsRadioSetValueMissingState::Visit(nsIFormControl* aRadio)
+{
+ if (aRadio == mExcludeElement) {
+ return true;
+ }
+
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(aRadio);
+
+ input->SetValidityState(nsIConstraintValidation::VALIDITY_STATE_VALUE_MISSING,
+ mValidity);
+
+ input->UpdateState(true);
+
+ return true;
+}
+
+bool
+nsRadioUpdateStateVisitor::Visit(nsIFormControl* aRadio)
+{
+ if (aRadio == mExcludeElement) {
+ return true;
+ }
+
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(aRadio);
+ input->UpdateState(true);
+
+ return true;
+} \ No newline at end of file
diff --git a/dom/html/nsRadioVisitor.h b/dom/html/nsRadioVisitor.h
new file mode 100644
index 000000000..b33d2e4e5
--- /dev/null
+++ b/dom/html/nsRadioVisitor.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 _nsRadioVisitor_h__
+#define _nsRadioVisitor_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIRadioVisitor.h"
+
+class nsIFormControl;
+
+/**
+ * nsRadioVisitor is the base class implementing nsIRadioVisitor and inherited
+ * by all radio visitors.
+ */
+class nsRadioVisitor : public nsIRadioVisitor
+{
+protected:
+ virtual ~nsRadioVisitor() { }
+
+public:
+ nsRadioVisitor() { }
+
+ NS_DECL_ISUPPORTS
+
+ virtual bool Visit(nsIFormControl* aRadio) override = 0;
+};
+
+/**
+ * The following declarations are radio visitors inheriting from nsRadioVisitor.
+ */
+
+/**
+ * nsRadioSetCheckedChangedVisitor is calling SetCheckedChanged with the given
+ * parameter to all radio elements in the group.
+ */
+class nsRadioSetCheckedChangedVisitor : public nsRadioVisitor
+{
+public:
+ explicit nsRadioSetCheckedChangedVisitor(bool aCheckedChanged)
+ : mCheckedChanged(aCheckedChanged)
+ { }
+
+ virtual bool Visit(nsIFormControl* aRadio) override;
+
+protected:
+ bool mCheckedChanged;
+};
+
+/**
+ * nsRadioGetCheckedChangedVisitor is getting the current checked changed value.
+ * Getting it from one radio element is the group is enough given that all
+ * elements should have the same value.
+ */
+class nsRadioGetCheckedChangedVisitor : public nsRadioVisitor
+{
+public:
+ nsRadioGetCheckedChangedVisitor(bool* aCheckedChanged,
+ nsIFormControl* aExcludeElement)
+ : mCheckedChanged(aCheckedChanged)
+ , mExcludeElement(aExcludeElement)
+ { }
+
+ virtual bool Visit(nsIFormControl* aRadio) override;
+
+protected:
+ bool* mCheckedChanged;
+ nsIFormControl* mExcludeElement;
+};
+
+/**
+ * nsRadioSetValueMissingState is calling SetValueMissingState with the given
+ * parameter to all radio elements in the group.
+ * It is also calling ContentStatesChanged if needed.
+ */
+class nsRadioSetValueMissingState : public nsRadioVisitor
+{
+public:
+ nsRadioSetValueMissingState(nsIFormControl* aExcludeElement,
+ bool aValidity, bool aNotify)
+ : mExcludeElement(aExcludeElement)
+ , mValidity(aValidity)
+ , mNotify(aNotify)
+ { }
+
+ virtual bool Visit(nsIFormControl* aRadio) override;
+
+protected:
+ nsIFormControl* mExcludeElement;
+ bool mValidity;
+ bool mNotify;
+};
+
+class nsRadioUpdateStateVisitor : public nsRadioVisitor
+{
+public:
+ explicit nsRadioUpdateStateVisitor(nsIFormControl* aExcludeElement)
+ : mExcludeElement(aExcludeElement)
+ { }
+
+ virtual bool Visit(nsIFormControl* aRadio) override;
+
+protected:
+ nsIFormControl* mExcludeElement;
+};
+
+#endif // _nsRadioVisitor_h__
+
diff --git a/dom/html/nsTextEditorState.cpp b/dom/html/nsTextEditorState.cpp
new file mode 100644
index 000000000..d70199362
--- /dev/null
+++ b/dom/html/nsTextEditorState.cpp
@@ -0,0 +1,2354 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTextEditorState.h"
+
+#include "nsCOMPtr.h"
+#include "nsIPresShell.h"
+#include "nsView.h"
+#include "nsCaret.h"
+#include "nsEditorCID.h"
+#include "nsLayoutCID.h"
+#include "nsITextControlFrame.h"
+#include "nsIPlaintextEditor.h"
+#include "nsIDOMCharacterData.h"
+#include "nsIDOMDocument.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsTextControlFrame.h"
+#include "nsIControllers.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsIDOMHTMLTextAreaElement.h"
+#include "nsITransactionManager.h"
+#include "nsIControllerContext.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIDOMEventListener.h"
+#include "nsIEditorIMESupport.h"
+#include "nsIEditorObserver.h"
+#include "nsIWidget.h"
+#include "nsIDocumentEncoder.h"
+#include "nsISelectionPrivate.h"
+#include "nsPIDOMWindow.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIEditor.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/TextEditRules.h"
+#include "mozilla/EventListenerManager.h"
+#include "nsContentUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsTextNode.h"
+#include "nsIController.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "nsNumberControlFrame.h"
+#include "nsFrameSelection.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/layers/ScrollInputMethods.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using mozilla::layers::ScrollInputMethod;
+
+static NS_DEFINE_CID(kTextEditorCID, NS_TEXTEDITOR_CID);
+
+class MOZ_STACK_CLASS ValueSetter
+{
+public:
+ explicit ValueSetter(nsIEditor* aEditor)
+ : mEditor(aEditor)
+ {
+ MOZ_ASSERT(aEditor);
+
+ // To protect against a reentrant call to SetValue, we check whether
+ // another SetValue is already happening for this editor. If it is,
+ // we must wait until we unwind to re-enable oninput events.
+ mEditor->GetSuppressDispatchingInputEvent(&mOuterTransaction);
+ }
+ ~ValueSetter()
+ {
+ mEditor->SetSuppressDispatchingInputEvent(mOuterTransaction);
+ }
+ void Init()
+ {
+ mEditor->SetSuppressDispatchingInputEvent(true);
+ }
+
+private:
+ nsCOMPtr<nsIEditor> mEditor;
+ bool mOuterTransaction;
+};
+
+class RestoreSelectionState : public Runnable {
+public:
+ RestoreSelectionState(nsTextEditorState *aState, nsTextControlFrame *aFrame)
+ : mFrame(aFrame),
+ mTextEditorState(aState)
+ {
+ }
+
+ NS_IMETHOD Run() override {
+ if (!mTextEditorState) {
+ return NS_OK;
+ }
+
+ AutoHideSelectionChanges hideSelectionChanges
+ (mFrame->GetConstFrameSelection());
+
+ if (mFrame) {
+ // SetSelectionRange leads to Selection::AddRange which flushes Layout -
+ // need to block script to avoid nested PrepareEditor calls (bug 642800).
+ nsAutoScriptBlocker scriptBlocker;
+ nsTextEditorState::SelectionProperties& properties =
+ mTextEditorState->GetSelectionProperties();
+ if (properties.IsDirty()) {
+ mFrame->SetSelectionRange(properties.GetStart(),
+ properties.GetEnd(),
+ properties.GetDirection());
+ }
+ if (!mTextEditorState->mSelectionRestoreEagerInit) {
+ mTextEditorState->HideSelectionIfBlurred();
+ }
+ mTextEditorState->mSelectionRestoreEagerInit = false;
+ }
+
+ if (mTextEditorState) {
+ mTextEditorState->FinishedRestoringSelection();
+ }
+ return NS_OK;
+ }
+
+ // Let the text editor tell us we're no longer relevant - avoids use of nsWeakFrame
+ void Revoke() {
+ mFrame = nullptr;
+ mTextEditorState = nullptr;
+ }
+
+private:
+ nsTextControlFrame* mFrame;
+ nsTextEditorState* mTextEditorState;
+};
+
+/*static*/
+bool
+nsITextControlElement::GetWrapPropertyEnum(nsIContent* aContent,
+ nsITextControlElement::nsHTMLTextWrap& aWrapProp)
+{
+ // soft is the default; "physical" defaults to soft as well because all other
+ // browsers treat it that way and there is no real reason to maintain physical
+ // and virtual as separate entities if no one else does. Only hard and off
+ // do anything different.
+ aWrapProp = eHTMLTextWrap_Soft; // the default
+
+ nsAutoString wrap;
+ if (aContent->IsHTMLElement()) {
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::HARD, &nsGkAtoms::OFF, nullptr};
+
+ switch (aContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::wrap,
+ strings, eIgnoreCase)) {
+ case 0: aWrapProp = eHTMLTextWrap_Hard; break;
+ case 1: aWrapProp = eHTMLTextWrap_Off; break;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+/*static*/
+already_AddRefed<nsITextControlElement>
+nsITextControlElement::GetTextControlElementFromEditingHost(nsIContent* aHost)
+{
+ if (!aHost) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsITextControlElement> parent =
+ do_QueryInterface(aHost->GetParent());
+
+ return parent.forget();
+}
+
+static bool
+SuppressEventHandlers(nsPresContext* aPresContext)
+{
+ bool suppressHandlers = false;
+
+ if (aPresContext)
+ {
+ // Right now we only suppress event handlers and controller manipulation
+ // when in a print preview or print context!
+
+ // In the current implementation, we only paginate when
+ // printing or in print preview.
+
+ suppressHandlers = aPresContext->IsPaginated();
+ }
+
+ return suppressHandlers;
+}
+
+class nsAnonDivObserver final : public nsStubMutationObserver
+{
+public:
+ explicit nsAnonDivObserver(nsTextEditorState* aTextEditorState)
+ : mTextEditorState(aTextEditorState) {}
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+private:
+ ~nsAnonDivObserver() {}
+ nsTextEditorState* mTextEditorState;
+};
+
+class nsTextInputSelectionImpl final : public nsSupportsWeakReference
+ , public nsISelectionController
+{
+ ~nsTextInputSelectionImpl(){}
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsTextInputSelectionImpl, nsISelectionController)
+
+ nsTextInputSelectionImpl(nsFrameSelection *aSel, nsIPresShell *aShell, nsIContent *aLimiter);
+
+ void SetScrollableFrame(nsIScrollableFrame *aScrollableFrame);
+ nsFrameSelection* GetConstFrameSelection()
+ { return mFrameSelection; }
+
+ //NSISELECTIONCONTROLLER INTERFACES
+ NS_IMETHOD SetDisplaySelection(int16_t toggle) override;
+ NS_IMETHOD GetDisplaySelection(int16_t* _retval) override;
+ NS_IMETHOD SetSelectionFlags(int16_t aInEnable) override;
+ NS_IMETHOD GetSelectionFlags(int16_t *aOutEnable) override;
+ NS_IMETHOD GetSelection(RawSelectionType aRawSelectionType,
+ nsISelection** aSelection) override;
+ NS_IMETHOD ScrollSelectionIntoView(RawSelectionType aRawSelectionType,
+ int16_t aRegion, int16_t aFlags) override;
+ NS_IMETHOD RepaintSelection(RawSelectionType aRawSelectionType) override;
+ nsresult RepaintSelection(nsPresContext* aPresContext,
+ SelectionType aSelectionType);
+ NS_IMETHOD SetCaretEnabled(bool enabled) override;
+ NS_IMETHOD SetCaretReadOnly(bool aReadOnly) override;
+ NS_IMETHOD GetCaretEnabled(bool* _retval) override;
+ NS_IMETHOD GetCaretVisible(bool* _retval) override;
+ NS_IMETHOD SetCaretVisibilityDuringSelection(bool aVisibility) override;
+ NS_IMETHOD PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend) override;
+ NS_IMETHOD CharacterMove(bool aForward, bool aExtend) override;
+ NS_IMETHOD CharacterExtendForDelete() override;
+ NS_IMETHOD CharacterExtendForBackspace() override;
+ NS_IMETHOD WordMove(bool aForward, bool aExtend) override;
+ NS_IMETHOD WordExtendForDelete(bool aForward) override;
+ NS_IMETHOD LineMove(bool aForward, bool aExtend) override;
+ NS_IMETHOD IntraLineMove(bool aForward, bool aExtend) override;
+ NS_IMETHOD PageMove(bool aForward, bool aExtend) override;
+ NS_IMETHOD CompleteScroll(bool aForward) override;
+ NS_IMETHOD CompleteMove(bool aForward, bool aExtend) override;
+ NS_IMETHOD ScrollPage(bool aForward) override;
+ NS_IMETHOD ScrollLine(bool aForward) override;
+ NS_IMETHOD ScrollCharacter(bool aRight) override;
+ NS_IMETHOD SelectAll(void) override;
+ NS_IMETHOD CheckVisibility(nsIDOMNode *node, int16_t startOffset, int16_t EndOffset, bool* _retval) override;
+ virtual nsresult CheckVisibilityContent(nsIContent* aNode, int16_t aStartOffset, int16_t aEndOffset, bool* aRetval) override;
+
+private:
+ RefPtr<nsFrameSelection> mFrameSelection;
+ nsCOMPtr<nsIContent> mLimiter;
+ nsIScrollableFrame *mScrollFrame;
+ nsWeakPtr mPresShellWeak;
+};
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTextInputSelectionImpl)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTextInputSelectionImpl)
+NS_INTERFACE_TABLE_HEAD(nsTextInputSelectionImpl)
+ NS_INTERFACE_TABLE(nsTextInputSelectionImpl,
+ nsISelectionController,
+ nsISelectionDisplay,
+ nsISupportsWeakReference)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsTextInputSelectionImpl)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(nsTextInputSelectionImpl, mFrameSelection, mLimiter)
+
+
+// BEGIN nsTextInputSelectionImpl
+
+nsTextInputSelectionImpl::nsTextInputSelectionImpl(nsFrameSelection *aSel,
+ nsIPresShell *aShell,
+ nsIContent *aLimiter)
+ : mScrollFrame(nullptr)
+{
+ if (aSel && aShell)
+ {
+ mFrameSelection = aSel;//we are the owner now!
+ mLimiter = aLimiter;
+ mFrameSelection->Init(aShell, mLimiter);
+ mPresShellWeak = do_GetWeakReference(aShell);
+ }
+}
+
+void
+nsTextInputSelectionImpl::SetScrollableFrame(nsIScrollableFrame *aScrollableFrame)
+{
+ mScrollFrame = aScrollableFrame;
+ if (!mScrollFrame && mFrameSelection) {
+ mFrameSelection->DisconnectFromPresShell();
+ mFrameSelection = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::SetDisplaySelection(int16_t aToggle)
+{
+ if (!mFrameSelection)
+ return NS_ERROR_NULL_POINTER;
+
+ mFrameSelection->SetDisplaySelection(aToggle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::GetDisplaySelection(int16_t *aToggle)
+{
+ if (!mFrameSelection)
+ return NS_ERROR_NULL_POINTER;
+
+ *aToggle = mFrameSelection->GetDisplaySelection();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::SetSelectionFlags(int16_t aToggle)
+{
+ return NS_OK;//stub this out. not used in input
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::GetSelectionFlags(int16_t *aOutEnable)
+{
+ *aOutEnable = nsISelectionDisplay::DISPLAY_TEXT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::GetSelection(RawSelectionType aRawSelectionType,
+ nsISelection** aSelection)
+{
+ if (!mFrameSelection)
+ return NS_ERROR_NULL_POINTER;
+
+ *aSelection =
+ mFrameSelection->GetSelection(ToSelectionType(aRawSelectionType));
+
+ // GetSelection() fails only when aRawSelectionType is invalid value.
+ if (!(*aSelection)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ NS_ADDREF(*aSelection);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::ScrollSelectionIntoView(
+ RawSelectionType aRawSelectionType,
+ int16_t aRegion,
+ int16_t aFlags)
+{
+ if (!mFrameSelection)
+ return NS_ERROR_FAILURE;
+
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ return frameSelection->ScrollSelectionIntoView(
+ ToSelectionType(aRawSelectionType),
+ aRegion, aFlags);
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::RepaintSelection(RawSelectionType aRawSelectionType)
+{
+ if (!mFrameSelection)
+ return NS_ERROR_FAILURE;
+
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType));
+}
+
+nsresult
+nsTextInputSelectionImpl::RepaintSelection(nsPresContext* aPresContext,
+ SelectionType aSelectionType)
+{
+ if (!mFrameSelection)
+ return NS_ERROR_FAILURE;
+
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ return frameSelection->RepaintSelection(aSelectionType);
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::SetCaretEnabled(bool enabled)
+{
+ if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak);
+ if (!shell) return NS_ERROR_FAILURE;
+
+ // tell the pres shell to enable the caret, rather than settings its visibility directly.
+ // this way the presShell's idea of caret visibility is maintained.
+ nsCOMPtr<nsISelectionController> selCon = do_QueryInterface(shell);
+ if (!selCon) return NS_ERROR_NO_INTERFACE;
+ selCon->SetCaretEnabled(enabled);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::SetCaretReadOnly(bool aReadOnly)
+{
+ if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
+ nsresult result;
+ nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak, &result);
+ if (shell)
+ {
+ RefPtr<nsCaret> caret = shell->GetCaret();
+ if (caret) {
+ nsISelection* domSel =
+ mFrameSelection->GetSelection(SelectionType::eNormal);
+ if (domSel)
+ caret->SetCaretReadOnly(aReadOnly);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::GetCaretEnabled(bool *_retval)
+{
+ return GetCaretVisible(_retval);
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::GetCaretVisible(bool *_retval)
+{
+ if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
+ nsresult result;
+ nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak, &result);
+ if (shell)
+ {
+ RefPtr<nsCaret> caret = shell->GetCaret();
+ if (caret) {
+ *_retval = caret->IsVisible();
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::SetCaretVisibilityDuringSelection(bool aVisibility)
+{
+ if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
+ nsresult result;
+ nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak, &result);
+ if (shell)
+ {
+ RefPtr<nsCaret> caret = shell->GetCaret();
+ if (caret) {
+ nsISelection* domSel =
+ mFrameSelection->GetSelection(SelectionType::eNormal);
+ if (domSel)
+ caret->SetVisibilityDuringSelection(aVisibility);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::PhysicalMove(int16_t aDirection, int16_t aAmount,
+ bool aExtend)
+{
+ if (mFrameSelection) {
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ return frameSelection->PhysicalMove(aDirection, aAmount, aExtend);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::CharacterMove(bool aForward, bool aExtend)
+{
+ if (mFrameSelection) {
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ return frameSelection->CharacterMove(aForward, aExtend);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::CharacterExtendForDelete()
+{
+ if (mFrameSelection) {
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ return frameSelection->CharacterExtendForDelete();
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::CharacterExtendForBackspace()
+{
+ if (mFrameSelection) {
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ return frameSelection->CharacterExtendForBackspace();
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::WordMove(bool aForward, bool aExtend)
+{
+ if (mFrameSelection) {
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ return frameSelection->WordMove(aForward, aExtend);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::WordExtendForDelete(bool aForward)
+{
+ if (mFrameSelection) {
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ return frameSelection->WordExtendForDelete(aForward);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::LineMove(bool aForward, bool aExtend)
+{
+ if (mFrameSelection)
+ {
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ nsresult result = frameSelection->LineMove(aForward, aExtend);
+ if (NS_FAILED(result))
+ result = CompleteMove(aForward,aExtend);
+ return result;
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::IntraLineMove(bool aForward, bool aExtend)
+{
+ if (mFrameSelection) {
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ return frameSelection->IntraLineMove(aForward, aExtend);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::PageMove(bool aForward, bool aExtend)
+{
+ // expected behavior for PageMove is to scroll AND move the caret
+ // and to remain relative position of the caret in view. see Bug 4302.
+ if (mScrollFrame)
+ {
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ frameSelection->CommonPageMove(aForward, aExtend, mScrollFrame);
+ }
+ // After ScrollSelectionIntoView(), the pending notifications might be
+ // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
+ return ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
+ nsISelectionController::SELECTION_FOCUS_REGION,
+ nsISelectionController::SCROLL_SYNCHRONOUS |
+ nsISelectionController::SCROLL_FOR_CARET_MOVE);
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::CompleteScroll(bool aForward)
+{
+ if (!mScrollFrame)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
+ (uint32_t) ScrollInputMethod::MainThreadCompleteScroll);
+
+ mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
+ nsIScrollableFrame::WHOLE,
+ nsIScrollableFrame::INSTANT);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::CompleteMove(bool aForward, bool aExtend)
+{
+ NS_ENSURE_STATE(mFrameSelection);
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+
+ // grab the parent / root DIV for this text widget
+ nsIContent* parentDIV = frameSelection->GetLimiter();
+ if (!parentDIV)
+ return NS_ERROR_UNEXPECTED;
+
+ // make the caret be either at the very beginning (0) or the very end
+ int32_t offset = 0;
+ CaretAssociationHint hint = CARET_ASSOCIATE_BEFORE;
+ if (aForward)
+ {
+ offset = parentDIV->GetChildCount();
+
+ // Prevent the caret from being placed after the last
+ // BR node in the content tree!
+
+ if (offset > 0)
+ {
+ nsIContent *child = parentDIV->GetLastChild();
+
+ if (child->IsHTMLElement(nsGkAtoms::br))
+ {
+ --offset;
+ hint = CARET_ASSOCIATE_AFTER; // for Bug 106855
+ }
+ }
+ }
+
+ frameSelection->HandleClick(parentDIV, offset, offset, aExtend,
+ false, hint);
+
+ // if we got this far, attempt to scroll no matter what the above result is
+ return CompleteScroll(aForward);
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::ScrollPage(bool aForward)
+{
+ if (!mScrollFrame)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
+ (uint32_t) ScrollInputMethod::MainThreadScrollPage);
+
+ mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
+ nsIScrollableFrame::PAGES,
+ nsIScrollableFrame::SMOOTH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::ScrollLine(bool aForward)
+{
+ if (!mScrollFrame)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
+ (uint32_t) ScrollInputMethod::MainThreadScrollLine);
+
+ mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
+ nsIScrollableFrame::LINES,
+ nsIScrollableFrame::SMOOTH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::ScrollCharacter(bool aRight)
+{
+ if (!mScrollFrame)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
+ (uint32_t) ScrollInputMethod::MainThreadScrollCharacter);
+
+ mScrollFrame->ScrollBy(nsIntPoint(aRight ? 1 : -1, 0),
+ nsIScrollableFrame::LINES,
+ nsIScrollableFrame::SMOOTH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::SelectAll()
+{
+ if (mFrameSelection) {
+ RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
+ return frameSelection->SelectAll();
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::CheckVisibility(nsIDOMNode *node, int16_t startOffset, int16_t EndOffset, bool *_retval)
+{
+ if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
+ nsresult result;
+ nsCOMPtr<nsISelectionController> shell = do_QueryReferent(mPresShellWeak, &result);
+ if (shell)
+ {
+ return shell->CheckVisibility(node,startOffset,EndOffset, _retval);
+ }
+ return NS_ERROR_FAILURE;
+
+}
+
+nsresult
+nsTextInputSelectionImpl::CheckVisibilityContent(nsIContent* aNode,
+ int16_t aStartOffset,
+ int16_t aEndOffset,
+ bool* aRetval)
+{
+ if (!mPresShellWeak) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsCOMPtr<nsISelectionController> shell = do_QueryReferent(mPresShellWeak);
+ NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
+
+ return shell->CheckVisibilityContent(aNode, aStartOffset, aEndOffset, aRetval);
+}
+
+class nsTextInputListener : public nsISelectionListener,
+ public nsIDOMEventListener,
+ public nsIEditorObserver,
+ public nsSupportsWeakReference
+{
+public:
+ /** the default constructor
+ */
+ explicit nsTextInputListener(nsITextControlElement* aTxtCtrlElement);
+
+ /** SetEditor gives an address to the editor that will be accessed
+ * @param aEditor the editor this listener calls for editing operations
+ */
+ void SetFrame(nsTextControlFrame *aFrame){mFrame = aFrame;}
+
+ void SettingValue(bool aValue) { mSettingValue = aValue; }
+ void SetValueChanged(bool aSetValueChanged) { mSetValueChanged = aSetValueChanged; }
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSISELECTIONLISTENER
+
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ NS_DECL_NSIEDITOROBSERVER
+
+protected:
+ /** the default destructor. virtual due to the possibility of derivation.
+ */
+ virtual ~nsTextInputListener();
+
+ nsresult UpdateTextInputCommands(const nsAString& commandsToUpdate,
+ nsISelection* sel = nullptr,
+ int16_t reason = 0);
+
+protected:
+
+ nsIFrame* mFrame;
+
+ nsITextControlElement* const mTxtCtrlElement;
+
+ bool mSelectionWasCollapsed;
+ /**
+ * Whether we had undo items or not the last time we got EditAction()
+ * notification (when this state changes we update undo and redo menus)
+ */
+ bool mHadUndoItems;
+ /**
+ * Whether we had redo items or not the last time we got EditAction()
+ * notification (when this state changes we update undo and redo menus)
+ */
+ bool mHadRedoItems;
+ /**
+ * Whether we're in the process of a SetValue call, and should therefore
+ * refrain from calling OnValueChanged.
+ */
+ bool mSettingValue;
+ /**
+ * Whether we are in the process of a SetValue call that doesn't want
+ * |SetValueChanged| to be called.
+ */
+ bool mSetValueChanged;
+};
+
+
+/*
+ * nsTextInputListener implementation
+ */
+
+nsTextInputListener::nsTextInputListener(nsITextControlElement* aTxtCtrlElement)
+: mFrame(nullptr)
+, mTxtCtrlElement(aTxtCtrlElement)
+, mSelectionWasCollapsed(true)
+, mHadUndoItems(false)
+, mHadRedoItems(false)
+, mSettingValue(false)
+, mSetValueChanged(true)
+{
+}
+
+nsTextInputListener::~nsTextInputListener()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsTextInputListener,
+ nsISelectionListener,
+ nsIEditorObserver,
+ nsISupportsWeakReference,
+ nsIDOMEventListener)
+
+// BEGIN nsIDOMSelectionListener
+
+NS_IMETHODIMP
+nsTextInputListener::NotifySelectionChanged(nsIDOMDocument* aDoc, nsISelection* aSel, int16_t aReason)
+{
+ bool collapsed;
+ nsWeakFrame weakFrame = mFrame;
+
+ if (!aDoc || !aSel || NS_FAILED(aSel->GetIsCollapsed(&collapsed)))
+ return NS_OK;
+
+ // Fire the select event
+ // The specs don't exactly say when we should fire the select event.
+ // IE: Whenever you add/remove a character to/from the selection. Also
+ // each time for select all. Also if you get to the end of the text
+ // field you will get new event for each keypress or a continuous
+ // stream of events if you use the mouse. IE will fire select event
+ // when the selection collapses to nothing if you are holding down
+ // the shift or mouse button.
+ // Mozilla: If we have non-empty selection we will fire a new event for each
+ // keypress (or mouseup) if the selection changed. Mozilla will also
+ // create the event each time select all is called, even if everything
+ // was previously selected, becase technically select all will first collapse
+ // and then extend. Mozilla will never create an event if the selection
+ // collapses to nothing.
+ if (!collapsed && (aReason & (nsISelectionListener::MOUSEUP_REASON |
+ nsISelectionListener::KEYPRESS_REASON |
+ nsISelectionListener::SELECTALL_REASON)))
+ {
+ nsIContent* content = mFrame->GetContent();
+ if (content)
+ {
+ nsCOMPtr<nsIDocument> doc = content->GetComposedDoc();
+ if (doc)
+ {
+ nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
+ if (presShell)
+ {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetEvent event(true, eFormSelect);
+
+ presShell->HandleEventWithTarget(&event, mFrame, content, &status);
+ }
+ }
+ }
+ }
+
+ // if the collapsed state did not change, don't fire notifications
+ if (collapsed == mSelectionWasCollapsed)
+ return NS_OK;
+
+ mSelectionWasCollapsed = collapsed;
+
+ if (!weakFrame.IsAlive() || !nsContentUtils::IsFocusedContent(mFrame->GetContent()))
+ return NS_OK;
+
+ return UpdateTextInputCommands(NS_LITERAL_STRING("select"), aSel, aReason);
+}
+
+// END nsIDOMSelectionListener
+
+static void
+DoCommandCallback(Command aCommand, void* aData)
+{
+ nsTextControlFrame *frame = static_cast<nsTextControlFrame*>(aData);
+ nsIContent *content = frame->GetContent();
+
+ nsCOMPtr<nsIControllers> controllers;
+ nsCOMPtr<nsIDOMHTMLInputElement> input = do_QueryInterface(content);
+ if (input) {
+ input->GetControllers(getter_AddRefs(controllers));
+ } else {
+ nsCOMPtr<nsIDOMHTMLTextAreaElement> textArea =
+ do_QueryInterface(content);
+
+ if (textArea) {
+ textArea->GetControllers(getter_AddRefs(controllers));
+ }
+ }
+
+ if (!controllers) {
+ NS_WARNING("Could not get controllers");
+ return;
+ }
+
+ const char* commandStr = WidgetKeyboardEvent::GetCommandStr(aCommand);
+
+ nsCOMPtr<nsIController> controller;
+ controllers->GetControllerForCommand(commandStr, getter_AddRefs(controller));
+ if (!controller) {
+ return;
+ }
+
+ bool commandEnabled;
+ nsresult rv = controller->IsCommandEnabled(commandStr, &commandEnabled);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ if (commandEnabled) {
+ controller->DoCommand(commandStr);
+ }
+}
+
+NS_IMETHODIMP
+nsTextInputListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+ bool defaultPrevented = false;
+ nsresult rv = aEvent->GetDefaultPrevented(&defaultPrevented);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (defaultPrevented) {
+ return NS_OK;
+ }
+
+ bool isTrusted = false;
+ rv = aEvent->GetIsTrusted(&isTrusted);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isTrusted) {
+ return NS_OK;
+ }
+
+ WidgetKeyboardEvent* keyEvent =
+ aEvent->WidgetEventPtr()->AsKeyboardEvent();
+ if (!keyEvent) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (keyEvent->mMessage != eKeyPress) {
+ return NS_OK;
+ }
+
+ nsIWidget::NativeKeyBindingsType nativeKeyBindingsType =
+ mTxtCtrlElement->IsTextArea() ?
+ nsIWidget::NativeKeyBindingsForMultiLineEditor :
+ nsIWidget::NativeKeyBindingsForSingleLineEditor;
+ nsIWidget* widget = keyEvent->mWidget;
+ // If the event is created by chrome script, the widget is nullptr.
+ if (!widget) {
+ widget = mFrame->GetNearestWidget();
+ NS_ENSURE_TRUE(widget, NS_OK);
+ }
+
+ if (widget->ExecuteNativeKeyBinding(nativeKeyBindingsType,
+ *keyEvent, DoCommandCallback, mFrame)) {
+ aEvent->PreventDefault();
+ }
+ return NS_OK;
+}
+
+// BEGIN nsIEditorObserver
+
+NS_IMETHODIMP
+nsTextInputListener::EditAction()
+{
+ if (!mFrame) {
+ // We've been disconnected from the nsTextEditorState object, nothing to do
+ // here.
+ return NS_OK;
+ }
+
+ nsWeakFrame weakFrame = mFrame;
+
+ nsITextControlFrame* frameBase = do_QueryFrame(mFrame);
+ nsTextControlFrame* frame = static_cast<nsTextControlFrame*> (frameBase);
+ NS_ASSERTION(frame, "Where is our frame?");
+ //
+ // Update the undo / redo menus
+ //
+ nsCOMPtr<nsIEditor> editor;
+ frame->GetEditor(getter_AddRefs(editor));
+
+ // Get the number of undo / redo items
+ int32_t numUndoItems = 0;
+ int32_t numRedoItems = 0;
+ editor->GetNumberOfUndoItems(&numUndoItems);
+ editor->GetNumberOfRedoItems(&numRedoItems);
+ if ((numUndoItems && !mHadUndoItems) || (!numUndoItems && mHadUndoItems) ||
+ (numRedoItems && !mHadRedoItems) || (!numRedoItems && mHadRedoItems)) {
+ // Modify the menu if undo or redo items are different
+ UpdateTextInputCommands(NS_LITERAL_STRING("undo"));
+
+ mHadUndoItems = numUndoItems != 0;
+ mHadRedoItems = numRedoItems != 0;
+ }
+
+ if (!weakFrame.IsAlive()) {
+ return NS_OK;
+ }
+
+ // Make sure we know we were changed (do NOT set this to false if there are
+ // no undo items; JS could change the value and we'd still need to save it)
+ if (mSetValueChanged) {
+ frame->SetValueChanged(true);
+ }
+
+ if (!mSettingValue) {
+ mTxtCtrlElement->OnValueChanged(/* aNotify = */ true,
+ /* aWasInteractiveUserChange = */ true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTextInputListener::BeforeEditAction()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTextInputListener::CancelEditAction()
+{
+ return NS_OK;
+}
+
+// END nsIEditorObserver
+
+
+nsresult
+nsTextInputListener::UpdateTextInputCommands(const nsAString& commandsToUpdate,
+ nsISelection* sel,
+ int16_t reason)
+{
+ nsIContent* content = mFrame->GetContent();
+ NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDocument> doc = content->GetComposedDoc();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ nsPIDOMWindowOuter *domWindow = doc->GetWindow();
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
+
+ return domWindow->UpdateCommands(commandsToUpdate, sel, reason);
+}
+
+// END nsTextInputListener
+
+// nsTextEditorState
+
+nsTextEditorState::nsTextEditorState(nsITextControlElement* aOwningElement)
+ : mTextCtrlElement(aOwningElement)
+ , mBoundFrame(nullptr)
+ , mEverInited(false)
+ , mEditorInitialized(false)
+ , mInitializing(false)
+ , mValueTransferInProgress(false)
+ , mSelectionCached(true)
+ , mSelectionRestoreEagerInit(false)
+ , mPlaceholderVisibility(false)
+ , mIsCommittingComposition(false)
+{
+ MOZ_COUNT_CTOR(nsTextEditorState);
+}
+
+nsTextEditorState::~nsTextEditorState()
+{
+ MOZ_COUNT_DTOR(nsTextEditorState);
+ Clear();
+}
+
+void
+nsTextEditorState::Clear()
+{
+ if (mBoundFrame) {
+ // Oops, we still have a frame!
+ // This should happen when the type of a text input control is being changed
+ // to something which is not a text control. In this case, we should pretend
+ // that a frame is being destroyed, and clean up after ourselves properly.
+ UnbindFromFrame(mBoundFrame);
+ mEditor = nullptr;
+ } else {
+ // If we have a bound frame around, UnbindFromFrame will call DestroyEditor
+ // for us.
+ DestroyEditor();
+ }
+ mTextListener = nullptr;
+}
+
+void nsTextEditorState::Unlink()
+{
+ nsTextEditorState* tmp = this;
+ tmp->Clear();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelCon)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditor)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootNode)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlaceholderDiv)
+}
+
+void nsTextEditorState::Traverse(nsCycleCollectionTraversalCallback& cb)
+{
+ nsTextEditorState* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelCon)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditor)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootNode)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaceholderDiv)
+}
+
+nsFrameSelection*
+nsTextEditorState::GetConstFrameSelection() {
+ if (mSelCon)
+ return mSelCon->GetConstFrameSelection();
+ return nullptr;
+}
+
+nsIEditor*
+nsTextEditorState::GetEditor()
+{
+ if (!mEditor) {
+ nsresult rv = PrepareEditor();
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ }
+ return mEditor;
+}
+
+nsISelectionController*
+nsTextEditorState::GetSelectionController() const
+{
+ return mSelCon;
+}
+
+// Helper class, used below in BindToFrame().
+class PrepareEditorEvent : public Runnable {
+public:
+ PrepareEditorEvent(nsTextEditorState &aState,
+ nsIContent *aOwnerContent,
+ const nsAString &aCurrentValue)
+ : mState(&aState)
+ , mOwnerContent(aOwnerContent)
+ , mCurrentValue(aCurrentValue)
+ {
+ aState.mValueTransferInProgress = true;
+ }
+
+ NS_IMETHOD Run() override {
+ NS_ENSURE_TRUE(mState, NS_ERROR_NULL_POINTER);
+
+ // Transfer the saved value to the editor if we have one
+ const nsAString *value = nullptr;
+ if (!mCurrentValue.IsEmpty()) {
+ value = &mCurrentValue;
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ mState->PrepareEditor(value);
+
+ mState->mValueTransferInProgress = false;
+
+ return NS_OK;
+ }
+
+private:
+ WeakPtr<nsTextEditorState> mState;
+ nsCOMPtr<nsIContent> mOwnerContent; // strong reference
+ nsAutoString mCurrentValue;
+};
+
+nsresult
+nsTextEditorState::BindToFrame(nsTextControlFrame* aFrame)
+{
+ NS_ASSERTION(aFrame, "The frame to bind to should be valid");
+ NS_ENSURE_ARG_POINTER(aFrame);
+
+ NS_ASSERTION(!mBoundFrame, "Cannot bind twice, need to unbind first");
+ NS_ENSURE_TRUE(!mBoundFrame, NS_ERROR_FAILURE);
+
+ // If we'll need to transfer our current value to the editor, save it before
+ // binding to the frame.
+ nsAutoString currentValue;
+ if (mEditor) {
+ GetValue(currentValue, true);
+ }
+
+ mBoundFrame = aFrame;
+
+ nsresult rv = CreateRootNode();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIContent *rootNode = GetRootNode();
+ rv = InitializeRootNode();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIPresShell *shell = mBoundFrame->PresContext()->GetPresShell();
+ NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
+
+ // Create selection
+ RefPtr<nsFrameSelection> frameSel = new nsFrameSelection();
+
+ // Create a SelectionController
+ mSelCon = new nsTextInputSelectionImpl(frameSel, shell, rootNode);
+ MOZ_ASSERT(!mTextListener, "Should not overwrite the object");
+ mTextListener = new nsTextInputListener(mTextCtrlElement);
+
+ mTextListener->SetFrame(mBoundFrame);
+ mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_ON);
+
+ // Get the caret and make it a selection listener.
+ RefPtr<nsISelection> domSelection;
+ if (NS_SUCCEEDED(mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
+ getter_AddRefs(domSelection))) &&
+ domSelection) {
+ nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(domSelection));
+ RefPtr<nsCaret> caret = shell->GetCaret();
+ nsCOMPtr<nsISelectionListener> listener;
+ if (caret) {
+ listener = do_QueryInterface(caret);
+ if (listener) {
+ selPriv->AddSelectionListener(listener);
+ }
+ }
+
+ selPriv->AddSelectionListener(static_cast<nsISelectionListener*>
+ (mTextListener));
+ }
+
+ // If an editor exists from before, prepare it for usage
+ if (mEditor) {
+ nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
+ NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
+
+ // Set the correct direction on the newly created root node
+ uint32_t flags;
+ nsresult rv = mEditor->GetFlags(&flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (flags & nsIPlaintextEditor::eEditorRightToLeft) {
+ rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("rtl"), false);
+ } else if (flags & nsIPlaintextEditor::eEditorLeftToRight) {
+ rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("ltr"), false);
+ } else {
+ // otherwise, inherit the content node's direction
+ }
+
+ nsContentUtils::AddScriptRunner(
+ new PrepareEditorEvent(*this, content, currentValue));
+ }
+
+ return NS_OK;
+}
+
+struct PreDestroyer
+{
+ void Init(nsIEditor* aEditor)
+ {
+ mNewEditor = aEditor;
+ }
+ ~PreDestroyer()
+ {
+ if (mNewEditor) {
+ mNewEditor->PreDestroy(true);
+ }
+ }
+ void Swap(nsCOMPtr<nsIEditor>& aEditor)
+ {
+ return mNewEditor.swap(aEditor);
+ }
+private:
+ nsCOMPtr<nsIEditor> mNewEditor;
+};
+
+nsresult
+nsTextEditorState::PrepareEditor(const nsAString *aValue)
+{
+ if (!mBoundFrame) {
+ // Cannot create an editor without a bound frame.
+ // Don't return a failure code, because js callers can't handle that.
+ return NS_OK;
+ }
+
+ if (mEditorInitialized) {
+ // Do not initialize the editor multiple times.
+ return NS_OK;
+ }
+
+ AutoHideSelectionChanges hideSelectionChanges(GetConstFrameSelection());
+
+ // Don't attempt to initialize recursively!
+ InitializationGuard guard(*this);
+ if (guard.IsInitializingRecursively()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // Note that we don't check mEditor here, because we might already have one
+ // around, in which case we don't create a new one, and we'll just tie the
+ // required machinery to it.
+
+ nsPresContext *presContext = mBoundFrame->PresContext();
+ nsIPresShell *shell = presContext->GetPresShell();
+
+ // Setup the editor flags
+ uint32_t editorFlags = 0;
+ if (IsPlainTextControl())
+ editorFlags |= nsIPlaintextEditor::eEditorPlaintextMask;
+ if (IsSingleLineTextControl())
+ editorFlags |= nsIPlaintextEditor::eEditorSingleLineMask;
+ if (IsPasswordTextControl())
+ editorFlags |= nsIPlaintextEditor::eEditorPasswordMask;
+
+ // All nsTextControlFrames are widgets
+ editorFlags |= nsIPlaintextEditor::eEditorWidgetMask;
+
+ // Spell check is diabled at creation time. It is enabled once
+ // the editor comes into focus.
+ editorFlags |= nsIPlaintextEditor::eEditorSkipSpellCheck;
+
+ bool shouldInitializeEditor = false;
+ nsCOMPtr<nsIEditor> newEditor; // the editor that we might create
+ nsresult rv = NS_OK;
+ PreDestroyer preDestroyer;
+ if (!mEditor) {
+ shouldInitializeEditor = true;
+
+ // Create an editor
+ newEditor = do_CreateInstance(kTextEditorCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ preDestroyer.Init(newEditor);
+
+ // Make sure we clear out the non-breaking space before we initialize the editor
+ rv = mBoundFrame->UpdateValueDisplay(true, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ if (aValue || !mEditorInitialized) {
+ // Set the correct value in the root node
+ rv = mBoundFrame->UpdateValueDisplay(true, !mEditorInitialized, aValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ newEditor = mEditor; // just pretend that we have a new editor!
+
+ // Don't lose application flags in the process.
+ uint32_t originalFlags = 0;
+ newEditor->GetFlags(&originalFlags);
+ if (originalFlags & nsIPlaintextEditor::eEditorMailMask) {
+ editorFlags |= nsIPlaintextEditor::eEditorMailMask;
+ }
+ }
+
+ // Get the current value of the textfield from the content.
+ // Note that if we've created a new editor, mEditor is null at this stage,
+ // so we will get the real value from the content.
+ nsAutoString defaultValue;
+ if (aValue) {
+ defaultValue = *aValue;
+ } else {
+ GetValue(defaultValue, true);
+ }
+
+ if (!mEditorInitialized) {
+ // Now initialize the editor.
+ //
+ // NOTE: Conversion of '\n' to <BR> happens inside the
+ // editor's Init() call.
+
+ // Get the DOM document
+ nsCOMPtr<nsIDOMDocument> domdoc = do_QueryInterface(shell->GetDocument());
+ if (!domdoc)
+ return NS_ERROR_FAILURE;
+
+ // What follows is a bit of a hack. The editor uses the public DOM APIs
+ // for its content manipulations, and it causes it to fail some security
+ // checks deep inside when initializing. So we explictly make it clear that
+ // we're native code.
+ // Note that any script that's directly trying to access our value
+ // has to be going through some scriptable object to do that and that
+ // already does the relevant security checks.
+ AutoNoJSAPI nojsapi;
+
+ rv = newEditor->Init(domdoc, GetRootNode(), mSelCon, editorFlags,
+ defaultValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Initialize the controller for the editor
+
+ if (!SuppressEventHandlers(presContext)) {
+ nsCOMPtr<nsIControllers> controllers;
+ nsCOMPtr<nsIDOMHTMLInputElement> inputElement =
+ do_QueryInterface(mTextCtrlElement);
+ if (inputElement) {
+ rv = inputElement->GetControllers(getter_AddRefs(controllers));
+ } else {
+ nsCOMPtr<nsIDOMHTMLTextAreaElement> textAreaElement =
+ do_QueryInterface(mTextCtrlElement);
+
+ if (!textAreaElement)
+ return NS_ERROR_FAILURE;
+
+ rv = textAreaElement->GetControllers(getter_AddRefs(controllers));
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (controllers) {
+ uint32_t numControllers;
+ bool found = false;
+ rv = controllers->GetControllerCount(&numControllers);
+ for (uint32_t i = 0; i < numControllers; i ++) {
+ nsCOMPtr<nsIController> controller;
+ rv = controllers->GetControllerAt(i, getter_AddRefs(controller));
+ if (NS_SUCCEEDED(rv) && controller) {
+ nsCOMPtr<nsIControllerContext> editController =
+ do_QueryInterface(controller);
+ if (editController) {
+ editController->SetCommandContext(newEditor);
+ found = true;
+ }
+ }
+ }
+ if (!found)
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+
+ if (shouldInitializeEditor) {
+ // Initialize the plaintext editor
+ nsCOMPtr<nsIPlaintextEditor> textEditor(do_QueryInterface(newEditor));
+ if (textEditor) {
+ // Set up wrapping
+ textEditor->SetWrapColumn(GetWrapCols());
+
+ // Set max text field length
+ int32_t maxLength;
+ if (GetMaxLength(&maxLength)) {
+ textEditor->SetMaxTextLength(maxLength);
+ }
+ }
+ }
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
+ if (content) {
+ rv = newEditor->GetFlags(&editorFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if the readonly attribute is set.
+ if (content->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly))
+ editorFlags |= nsIPlaintextEditor::eEditorReadonlyMask;
+
+ // Check if the disabled attribute is set.
+ // TODO: call IsDisabled() here!
+ if (content->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled))
+ editorFlags |= nsIPlaintextEditor::eEditorDisabledMask;
+
+ // Disable the selection if necessary.
+ if (editorFlags & nsIPlaintextEditor::eEditorDisabledMask)
+ mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_OFF);
+
+ newEditor->SetFlags(editorFlags);
+ }
+
+ if (shouldInitializeEditor) {
+ // Hold on to the newly created editor
+ preDestroyer.Swap(mEditor);
+ }
+
+ // If we have a default value, insert it under the div we created
+ // above, but be sure to use the editor so that '*' characters get
+ // displayed for password fields, etc. SetValue() will call the
+ // editor for us.
+
+ if (!defaultValue.IsEmpty()) {
+ rv = newEditor->SetFlags(editorFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now call SetValue() which will make the necessary editor calls to set
+ // the default value. Make sure to turn off undo before setting the default
+ // value, and turn it back on afterwards. This will make sure we can't undo
+ // past the default value.
+
+ rv = newEditor->EnableUndo(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool success = SetValue(defaultValue, eSetValue_Internal);
+ NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = newEditor->EnableUndo(true);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"Transaction Manager must have failed");
+
+ // Now restore the original editor flags.
+ rv = newEditor->SetFlags(editorFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsITransactionManager> transMgr;
+ newEditor->GetTransactionManager(getter_AddRefs(transMgr));
+ NS_ENSURE_TRUE(transMgr, NS_ERROR_FAILURE);
+
+ transMgr->SetMaxTransactionCount(nsITextControlElement::DEFAULT_UNDO_CAP);
+
+ if (IsPasswordTextControl()) {
+ // Disable undo for password textfields. Note that we want to do this at
+ // the very end of InitEditor, so the calls to EnableUndo when setting the
+ // default value don't screw us up.
+ // Since changing the control type does a reframe, we don't have to worry
+ // about dynamic type changes here.
+ newEditor->EnableUndo(false);
+ }
+
+ if (!mEditorInitialized) {
+ newEditor->PostCreate();
+ mEverInited = true;
+ mEditorInitialized = true;
+ }
+
+ if (mTextListener)
+ newEditor->AddEditorObserver(mTextListener);
+
+ // Restore our selection after being bound to a new frame
+ HTMLInputElement* number = GetParentNumberControl(mBoundFrame);
+ if (number ? number->IsSelectionCached() : mSelectionCached) {
+ if (mRestoringSelection) // paranoia
+ mRestoringSelection->Revoke();
+ mRestoringSelection = new RestoreSelectionState(this, mBoundFrame);
+ if (mRestoringSelection) {
+ nsContentUtils::AddScriptRunner(mRestoringSelection);
+ }
+ }
+
+ // The selection cache is no longer going to be valid
+ if (number) {
+ number->ClearSelectionCached();
+ } else {
+ mSelectionCached = false;
+ }
+
+ return rv;
+}
+
+void
+nsTextEditorState::FinishedRestoringSelection()
+{
+ mRestoringSelection = nullptr;
+}
+
+bool
+nsTextEditorState::IsSelectionCached() const
+{
+ if (mBoundFrame) {
+ HTMLInputElement* number = GetParentNumberControl(mBoundFrame);
+ if (number) {
+ return number->IsSelectionCached();
+ }
+ }
+ return mSelectionCached;
+}
+
+nsTextEditorState::SelectionProperties&
+nsTextEditorState::GetSelectionProperties()
+{
+ if (mBoundFrame) {
+ HTMLInputElement* number = GetParentNumberControl(mBoundFrame);
+ if (number) {
+ return number->GetSelectionProperties();
+ }
+ }
+ return mSelectionProperties;
+}
+
+void
+nsTextEditorState::SetSelectionProperties(nsTextEditorState::SelectionProperties& aProps)
+{
+ if (mBoundFrame) {
+ mBoundFrame->SetSelectionRange(aProps.GetStart(),
+ aProps.GetEnd(),
+ aProps.GetDirection());
+ } else {
+ mSelectionProperties = aProps;
+ }
+}
+
+
+HTMLInputElement*
+nsTextEditorState::GetParentNumberControl(nsFrame* aFrame) const
+{
+ MOZ_ASSERT(aFrame);
+ nsIContent* content = aFrame->GetContent();
+ MOZ_ASSERT(content);
+ nsIContent* parent = content->GetParent();
+ if (!parent) {
+ return nullptr;
+ }
+ nsIContent* parentOfParent = parent->GetParent();
+ if (!parentOfParent) {
+ return nullptr;
+ }
+ HTMLInputElement* input = HTMLInputElement::FromContent(parentOfParent);
+ if (input) {
+ // This function might be called during frame reconstruction as a result
+ // of changing the input control's type from number to something else. In
+ // that situation, the type of the control has changed, but its frame has
+ // not been reconstructed yet. So we need to check the type of the input
+ // control in addition to the type of the frame.
+ return (input->GetType() == NS_FORM_INPUT_NUMBER) ? input : nullptr;
+ }
+
+ return nullptr;
+}
+
+void
+nsTextEditorState::DestroyEditor()
+{
+ // notify the editor that we are going away
+ if (mEditorInitialized) {
+ if (mTextListener)
+ mEditor->RemoveEditorObserver(mTextListener);
+
+ mEditor->PreDestroy(true);
+ mEditorInitialized = false;
+ }
+ ClearValueCache();
+}
+
+void
+nsTextEditorState::UnbindFromFrame(nsTextControlFrame* aFrame)
+{
+ NS_ENSURE_TRUE_VOID(mBoundFrame);
+
+ // If it was, however, it should be unbounded from the same frame.
+ NS_ASSERTION(!aFrame || aFrame == mBoundFrame, "Unbinding from the wrong frame");
+ NS_ENSURE_TRUE_VOID(!aFrame || aFrame == mBoundFrame);
+
+ // If the editor is modified but nsIEditorObserver::EditAction() hasn't been
+ // called yet, we need to notify it here because editor may be destroyed
+ // before EditAction() is called if selection listener causes flushing layout.
+ bool isInEditAction = false;
+ if (mTextListener && mEditor && mEditorInitialized &&
+ NS_SUCCEEDED(mEditor->GetIsInEditAction(&isInEditAction)) &&
+ isInEditAction) {
+ mTextListener->EditAction();
+ }
+
+ // We need to start storing the value outside of the editor if we're not
+ // going to use it anymore, so retrieve it for now.
+ nsAutoString value;
+ GetValue(value, true);
+
+ if (mRestoringSelection) {
+ mRestoringSelection->Revoke();
+ mRestoringSelection = nullptr;
+ }
+
+ // Save our selection state if needed.
+ // Note that nsTextControlFrame::GetSelectionRange attempts to initialize the
+ // editor before grabbing the range, and because this is not an acceptable
+ // side effect for unbinding from a text control frame, we need to call
+ // GetSelectionRange before calling DestroyEditor, and only if
+ // mEditorInitialized indicates that we actually have an editor available.
+ int32_t start = 0, end = 0;
+ nsITextControlFrame::SelectionDirection direction =
+ nsITextControlFrame::eForward;
+ if (mEditorInitialized) {
+ HTMLInputElement* number = GetParentNumberControl(aFrame);
+ if (number) {
+ // If we are inside a number control, cache the selection on the
+ // parent control, because this text editor state will be destroyed
+ // together with the native anonymous text control.
+ SelectionProperties props;
+ mBoundFrame->GetSelectionRange(&start, &end, &direction);
+ props.SetStart(start);
+ props.SetEnd(end);
+ props.SetDirection(direction);
+ number->SetSelectionProperties(props);
+ } else {
+ mBoundFrame->GetSelectionRange(&start, &end, &direction);
+ mSelectionProperties.SetStart(start);
+ mSelectionProperties.SetEnd(end);
+ mSelectionProperties.SetDirection(direction);
+ mSelectionCached = true;
+ }
+ }
+
+ // Destroy our editor
+ DestroyEditor();
+
+ // Clean up the controller
+ if (!SuppressEventHandlers(mBoundFrame->PresContext()))
+ {
+ nsCOMPtr<nsIControllers> controllers;
+ nsCOMPtr<nsIDOMHTMLInputElement> inputElement =
+ do_QueryInterface(mTextCtrlElement);
+ if (inputElement)
+ inputElement->GetControllers(getter_AddRefs(controllers));
+ else
+ {
+ nsCOMPtr<nsIDOMHTMLTextAreaElement> textAreaElement =
+ do_QueryInterface(mTextCtrlElement);
+ if (textAreaElement) {
+ textAreaElement->GetControllers(getter_AddRefs(controllers));
+ }
+ }
+
+ if (controllers)
+ {
+ uint32_t numControllers;
+ nsresult rv = controllers->GetControllerCount(&numControllers);
+ NS_ASSERTION((NS_SUCCEEDED(rv)), "bad result in gfx text control destructor");
+ for (uint32_t i = 0; i < numControllers; i ++)
+ {
+ nsCOMPtr<nsIController> controller;
+ rv = controllers->GetControllerAt(i, getter_AddRefs(controller));
+ if (NS_SUCCEEDED(rv) && controller)
+ {
+ nsCOMPtr<nsIControllerContext> editController = do_QueryInterface(controller);
+ if (editController)
+ {
+ editController->SetCommandContext(nullptr);
+ }
+ }
+ }
+ }
+ }
+
+ if (mSelCon) {
+ if (mTextListener) {
+ RefPtr<nsISelection> domSelection;
+ if (NS_SUCCEEDED(mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
+ getter_AddRefs(domSelection))) &&
+ domSelection) {
+ nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(domSelection));
+
+ selPriv->RemoveSelectionListener(static_cast<nsISelectionListener*>
+ (mTextListener));
+ }
+ }
+
+ mSelCon->SetScrollableFrame(nullptr);
+ mSelCon = nullptr;
+ }
+
+ if (mTextListener)
+ {
+ mTextListener->SetFrame(nullptr);
+
+ nsCOMPtr<EventTarget> target = do_QueryInterface(mTextCtrlElement);
+ EventListenerManager* manager = target->GetExistingListenerManager();
+ if (manager) {
+ manager->RemoveEventListenerByType(mTextListener,
+ NS_LITERAL_STRING("keydown"),
+ TrustedEventsAtSystemGroupBubble());
+ manager->RemoveEventListenerByType(mTextListener,
+ NS_LITERAL_STRING("keypress"),
+ TrustedEventsAtSystemGroupBubble());
+ manager->RemoveEventListenerByType(mTextListener,
+ NS_LITERAL_STRING("keyup"),
+ TrustedEventsAtSystemGroupBubble());
+ }
+
+ mTextListener = nullptr;
+ }
+
+ mBoundFrame = nullptr;
+ // Clear mRootNode so that we don't unexpectedly notify below.
+ nsCOMPtr<Element> rootNode = mRootNode.forget();
+
+ // Now that we don't have a frame any more, store the value in the text buffer.
+ // The only case where we don't do this is if a value transfer is in progress.
+ if (!mValueTransferInProgress) {
+ bool success = SetValue(value, eSetValue_Internal);
+ // TODO Find something better to do if this fails...
+ NS_ENSURE_TRUE_VOID(success);
+ }
+
+ if (rootNode && mMutationObserver) {
+ rootNode->RemoveMutationObserver(mMutationObserver);
+ mMutationObserver = nullptr;
+ }
+
+ // Unbind the anonymous content from the tree.
+ // We actually hold a reference to the content nodes so that
+ // they're not actually destroyed.
+ nsContentUtils::DestroyAnonymousContent(&rootNode);
+ nsContentUtils::DestroyAnonymousContent(&mPlaceholderDiv);
+}
+
+nsresult
+nsTextEditorState::CreateRootNode()
+{
+ MOZ_ASSERT(!mRootNode);
+ MOZ_ASSERT(mBoundFrame);
+
+ nsIPresShell *shell = mBoundFrame->PresContext()->GetPresShell();
+ NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
+
+ nsIDocument *doc = shell->GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ // Now create a DIV and add it to the anonymous content child list.
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::div, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+
+ nsresult rv = NS_NewHTMLElement(getter_AddRefs(mRootNode), nodeInfo.forget(),
+ NOT_FROM_PARSER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!IsSingleLineTextControl()) {
+ mMutationObserver = new nsAnonDivObserver(this);
+ mRootNode->AddMutationObserver(mMutationObserver);
+ }
+
+ return rv;
+}
+
+nsresult
+nsTextEditorState::InitializeRootNode()
+{
+ // Make our root node editable
+ mRootNode->SetFlags(NODE_IS_EDITABLE);
+
+ // Set the necessary classes on the text control. We use class values
+ // instead of a 'style' attribute so that the style comes from a user-agent
+ // style sheet and is still applied even if author styles are disabled.
+ nsAutoString classValue;
+ classValue.AppendLiteral("anonymous-div");
+ int32_t wrapCols = GetWrapCols();
+ if (wrapCols > 0) {
+ classValue.AppendLiteral(" wrap");
+ }
+ if (!IsSingleLineTextControl()) {
+ // We can't just inherit the overflow because setting visible overflow will
+ // crash when the number of lines exceeds the height of the textarea and
+ // setting -moz-hidden-unscrollable overflow (NS_STYLE_OVERFLOW_CLIP)
+ // doesn't paint the caret for some reason.
+ const nsStyleDisplay* disp = mBoundFrame->StyleDisplay();
+ if (disp->mOverflowX != NS_STYLE_OVERFLOW_VISIBLE &&
+ disp->mOverflowX != NS_STYLE_OVERFLOW_CLIP) {
+ classValue.AppendLiteral(" inherit-overflow");
+ }
+ }
+ nsresult rv = mRootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
+ classValue, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mBoundFrame->UpdateValueDisplay(false);
+}
+
+nsresult
+nsTextEditorState::CreatePlaceholderNode()
+{
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
+ if (content) {
+ nsAutoString placeholderTxt;
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder,
+ placeholderTxt);
+ nsContentUtils::RemoveNewlines(placeholderTxt);
+ NS_ASSERTION(!placeholderTxt.IsEmpty(), "CreatePlaceholderNode() shouldn't \
+be called if @placeholder is the empty string when trimmed from line breaks");
+ }
+ }
+#endif // DEBUG
+
+ NS_ENSURE_TRUE(!mPlaceholderDiv, NS_ERROR_UNEXPECTED);
+ NS_ENSURE_ARG_POINTER(mBoundFrame);
+
+ nsIPresShell *shell = mBoundFrame->PresContext()->GetPresShell();
+ NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
+
+ nsIDocument *doc = shell->GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ nsNodeInfoManager* pNodeInfoManager = doc->NodeInfoManager();
+ NS_ENSURE_TRUE(pNodeInfoManager, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv;
+
+ // Create a DIV for the placeholder
+ // and add it to the anonymous content child list
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = pNodeInfoManager->GetNodeInfo(nsGkAtoms::div, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+
+ rv = NS_NewHTMLElement(getter_AddRefs(mPlaceholderDiv), nodeInfo.forget(),
+ NOT_FROM_PARSER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create the text node for the placeholder text before doing anything else
+ RefPtr<nsTextNode> placeholderText = new nsTextNode(pNodeInfoManager);
+
+ rv = mPlaceholderDiv->AppendChildTo(placeholderText, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // initialize the text
+ UpdatePlaceholderText(false);
+
+ return NS_OK;
+}
+
+bool
+nsTextEditorState::GetMaxLength(int32_t* aMaxLength)
+{
+ nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
+ nsGenericHTMLElement* element =
+ nsGenericHTMLElement::FromContentOrNull(content);
+ NS_ENSURE_TRUE(element, false);
+
+ const nsAttrValue* attr = element->GetParsedAttr(nsGkAtoms::maxlength);
+ if (attr && attr->Type() == nsAttrValue::eInteger) {
+ *aMaxLength = attr->GetIntegerValue();
+
+ return true;
+ }
+
+ return false;
+}
+
+void
+nsTextEditorState::GetValue(nsAString& aValue, bool aIgnoreWrap) const
+{
+ // While SetValue() is being called and requesting to commit composition to
+ // IME, GetValue() may be called for appending text or something. Then, we
+ // need to return the latest aValue of SetValue() since the value hasn't
+ // been set to the editor yet.
+ if (mIsCommittingComposition) {
+ aValue = mValueBeingSet;
+ return;
+ }
+
+ if (mEditor && mBoundFrame && (mEditorInitialized || !IsSingleLineTextControl())) {
+ bool canCache = aIgnoreWrap && !IsSingleLineTextControl();
+ if (canCache && !mCachedValue.IsEmpty()) {
+ aValue = mCachedValue;
+ return;
+ }
+
+ aValue.Truncate(); // initialize out param
+
+ uint32_t flags = (nsIDocumentEncoder::OutputLFLineBreak |
+ nsIDocumentEncoder::OutputPreformatted |
+ nsIDocumentEncoder::OutputPersistNBSP);
+
+ if (IsPlainTextControl())
+ {
+ flags |= nsIDocumentEncoder::OutputBodyOnly;
+ }
+
+ if (!aIgnoreWrap) {
+ nsITextControlElement::nsHTMLTextWrap wrapProp;
+ nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
+ if (content &&
+ nsITextControlElement::GetWrapPropertyEnum(content, wrapProp) &&
+ wrapProp == nsITextControlElement::eHTMLTextWrap_Hard) {
+ flags |= nsIDocumentEncoder::OutputWrap;
+ }
+ }
+
+ // What follows is a bit of a hack. The problem is that we could be in
+ // this method because we're being destroyed for whatever reason while
+ // script is executing. If that happens, editor will run with the
+ // privileges of the executing script, which means it may not be able to
+ // access its own DOM nodes! Let's try to deal with that by pushing a null
+ // JSContext on the JSContext stack to make it clear that we're native
+ // code. Note that any script that's directly trying to access our value
+ // has to be going through some scriptable object to do that and that
+ // already does the relevant security checks.
+ // XXXbz if we could just get the textContent of our anonymous content (eg
+ // if plaintext editor didn't create <br> nodes all over), we wouldn't need
+ // this.
+ { /* Scope for AutoNoJSAPI. */
+ AutoNoJSAPI nojsapi;
+
+ mEditor->OutputToString(NS_LITERAL_STRING("text/plain"), flags,
+ aValue);
+ }
+ if (canCache) {
+ mCachedValue = aValue;
+ } else {
+ mCachedValue.Truncate();
+ }
+ } else {
+ if (!mTextCtrlElement->ValueChanged() || !mValue) {
+ mTextCtrlElement->GetDefaultValueFromContent(aValue);
+ } else {
+ aValue = *mValue;
+ }
+ }
+}
+
+bool
+nsTextEditorState::SetValue(const nsAString& aValue, uint32_t aFlags)
+{
+ nsAutoString newValue(aValue);
+
+ // While mIsCommittingComposition is true (that means that some event
+ // handlers which are fired during committing composition are the caller of
+ // this method), GetValue() uses mValueBeingSet for its result because the
+ // first calls of this methods hasn't set the value yet. So, when it's true,
+ // we need to modify mValueBeingSet. In this case, we will back to the first
+ // call of this method, then, mValueBeingSet will be truncated when
+ // mIsCommittingComposition is set false. See below.
+ if (mIsCommittingComposition) {
+ mValueBeingSet = aValue;
+ }
+
+ // Note that if this may be called during reframe of the editor. In such
+ // case, we shouldn't commit composition. Therefore, when this is called
+ // for internal processing, we shouldn't commit the composition.
+ if (aFlags & (eSetValue_BySetUserInput | eSetValue_ByContent)) {
+ if (EditorHasComposition()) {
+ // When this is called recursively, there shouldn't be composition.
+ if (NS_WARN_IF(mIsCommittingComposition)) {
+ // Don't request to commit composition again. But if it occurs,
+ // we should skip to set the new value to the editor here. It should
+ // be set later with the updated mValueBeingSet.
+ return true;
+ }
+ if (NS_WARN_IF(!mBoundFrame)) {
+ // We're not sure if this case is possible.
+ } else {
+ // If setting value won't change current value, we shouldn't commit
+ // composition for compatibility with the other browsers.
+ nsAutoString currentValue;
+ mBoundFrame->GetText(currentValue);
+ if (newValue == currentValue) {
+ // Note that in this case, we shouldn't fire any events with setting
+ // value because event handlers may try to set value recursively but
+ // we cannot commit composition at that time due to unsafe to run
+ // script (see below).
+ return true;
+ }
+ }
+ // If there is composition, need to commit composition first because
+ // other browsers do that.
+ // NOTE: We don't need to block nested calls of this because input nor
+ // other events won't be fired by setting values and script blocker
+ // is used during setting the value to the editor. IE also allows
+ // to set the editor value on the input event which is caused by
+ // forcibly committing composition.
+ if (nsContentUtils::IsSafeToRunScript()) {
+ WeakPtr<nsTextEditorState> self(this);
+ // WARNING: During this call, compositionupdate, compositionend, input
+ // events will be fired. Therefore, everything can occur. E.g., the
+ // document may be unloaded.
+ mValueBeingSet = aValue;
+ mIsCommittingComposition = true;
+ nsCOMPtr<nsIEditorIMESupport> editorIMESupport =
+ do_QueryInterface(mEditor);
+ MOZ_RELEASE_ASSERT(editorIMESupport);
+ nsresult rv = editorIMESupport->ForceCompositionEnd();
+ if (!self.get()) {
+ return true;
+ }
+ mIsCommittingComposition = false;
+ // If this is called recursively during committing composition and
+ // some of them may be skipped above. Therefore, we need to set
+ // value to the editor with the aValue of the latest call.
+ newValue = mValueBeingSet;
+ // When mIsCommittingComposition is false, mValueBeingSet won't be
+ // used. Therefore, let's clear it.
+ mValueBeingSet.Truncate();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("nsTextEditorState failed to commit composition");
+ return true;
+ }
+ } else {
+ NS_WARNING("SetValue() is called when there is composition but "
+ "it's not safe to request to commit the composition");
+ }
+ }
+ }
+
+ if (mEditor && mBoundFrame) {
+ // The InsertText call below might flush pending notifications, which
+ // could lead into a scheduled PrepareEditor to be called. That will
+ // lead to crashes (or worse) because we'd be initializing the editor
+ // before InsertText returns. This script blocker makes sure that
+ // PrepareEditor cannot be called prematurely.
+ nsAutoScriptBlocker scriptBlocker;
+
+#ifdef DEBUG
+ if (IsSingleLineTextControl()) {
+ NS_ASSERTION(mEditorInitialized || mInitializing,
+ "We should never try to use the editor if we're not initialized unless we're being initialized");
+ }
+#endif
+
+ nsAutoString currentValue;
+ mBoundFrame->GetText(currentValue);
+
+ nsWeakFrame weakFrame(mBoundFrame);
+
+ // this is necessary to avoid infinite recursion
+ if (!currentValue.Equals(newValue))
+ {
+ ValueSetter valueSetter(mEditor);
+
+ // \r is an illegal character in the dom, but people use them,
+ // so convert windows and mac platform linebreaks to \n:
+ if (newValue.FindChar(char16_t('\r')) != -1) {
+ if (!nsContentUtils::PlatformToDOMLineBreaks(newValue, fallible)) {
+ return false;
+ }
+ }
+
+ nsCOMPtr<nsIDOMDocument> domDoc;
+ mEditor->GetDocument(getter_AddRefs(domDoc));
+ if (!domDoc) {
+ NS_WARNING("Why don't we have a document?");
+ return true;
+ }
+
+ // Time to mess with our security context... See comments in GetValue()
+ // for why this is needed. Note that we have to do this up here, because
+ // otherwise SelectAll() will fail.
+ {
+ AutoNoJSAPI nojsapi;
+
+ nsCOMPtr<nsISelection> domSel;
+ nsCOMPtr<nsISelectionPrivate> selPriv;
+ mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
+ getter_AddRefs(domSel));
+ if (domSel)
+ {
+ selPriv = do_QueryInterface(domSel);
+ if (selPriv)
+ selPriv->StartBatchChanges();
+ }
+
+ nsCOMPtr<nsISelectionController> kungFuDeathGrip = mSelCon.get();
+ uint32_t currentLength = currentValue.Length();
+ uint32_t newlength = newValue.Length();
+ if (!currentLength ||
+ !StringBeginsWith(newValue, currentValue)) {
+ // Replace the whole text.
+ currentLength = 0;
+ kungFuDeathGrip->SelectAll();
+ } else {
+ // Collapse selection to the end so that we can append data.
+ mBoundFrame->SelectAllOrCollapseToEndOfText(false);
+ }
+ const nsAString& insertValue =
+ StringTail(newValue, newlength - currentLength);
+ nsCOMPtr<nsIPlaintextEditor> plaintextEditor = do_QueryInterface(mEditor);
+ if (!plaintextEditor || !weakFrame.IsAlive()) {
+ NS_WARNING("Somehow not a plaintext editor?");
+ return true;
+ }
+
+ valueSetter.Init();
+
+ // get the flags, remove readonly and disabled, set the value,
+ // restore flags
+ uint32_t flags, savedFlags;
+ mEditor->GetFlags(&savedFlags);
+ flags = savedFlags;
+ flags &= ~(nsIPlaintextEditor::eEditorDisabledMask);
+ flags &= ~(nsIPlaintextEditor::eEditorReadonlyMask);
+ flags |= nsIPlaintextEditor::eEditorDontEchoPassword;
+ mEditor->SetFlags(flags);
+
+ mTextListener->SettingValue(true);
+ bool notifyValueChanged = !!(aFlags & eSetValue_Notify);
+ mTextListener->SetValueChanged(notifyValueChanged);
+
+ // Also don't enforce max-length here
+ int32_t savedMaxLength;
+ plaintextEditor->GetMaxTextLength(&savedMaxLength);
+ plaintextEditor->SetMaxTextLength(-1);
+
+ if (insertValue.IsEmpty()) {
+ mEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
+ } else {
+ plaintextEditor->InsertText(insertValue);
+ }
+
+ mTextListener->SetValueChanged(true);
+ mTextListener->SettingValue(false);
+
+ if (!weakFrame.IsAlive()) {
+ // If the frame was destroyed because of a flush somewhere inside
+ // InsertText, mBoundFrame here will be false. But it's also possible
+ // for the frame to go away because of another reason (such as deleting
+ // the existing selection -- see bug 574558), in which case we don't
+ // need to reset the value here.
+ if (!mBoundFrame) {
+ return SetValue(newValue, aFlags & eSetValue_Notify);
+ }
+ return true;
+ }
+
+ if (!IsSingleLineTextControl()) {
+ if (!mCachedValue.Assign(newValue, fallible)) {
+ return false;
+ }
+ }
+
+ plaintextEditor->SetMaxTextLength(savedMaxLength);
+ mEditor->SetFlags(savedFlags);
+ if (selPriv)
+ selPriv->EndBatchChanges();
+ }
+ }
+ } else {
+ if (!mValue) {
+ mValue.emplace();
+ }
+ nsString value;
+ if (!value.Assign(newValue, fallible)) {
+ return false;
+ }
+ if (!nsContentUtils::PlatformToDOMLineBreaks(value, fallible)) {
+ return false;
+ }
+ if (!mValue->Assign(value, fallible)) {
+ return false;
+ }
+
+ // Update the frame display if needed
+ if (mBoundFrame) {
+ mBoundFrame->UpdateValueDisplay(true);
+ }
+ }
+
+ // If we've reached the point where the root node has been created, we
+ // can assume that it's safe to notify.
+ ValueWasChanged(!!mRootNode);
+
+ mTextCtrlElement->OnValueChanged(/* aNotify = */ !!mRootNode,
+ /* aWasInteractiveUserChange = */ false);
+
+ return true;
+}
+
+void
+nsTextEditorState::InitializeKeyboardEventListeners()
+{
+ //register key listeners
+ nsCOMPtr<EventTarget> target = do_QueryInterface(mTextCtrlElement);
+ EventListenerManager* manager = target->GetOrCreateListenerManager();
+ if (manager) {
+ manager->AddEventListenerByType(mTextListener,
+ NS_LITERAL_STRING("keydown"),
+ TrustedEventsAtSystemGroupBubble());
+ manager->AddEventListenerByType(mTextListener,
+ NS_LITERAL_STRING("keypress"),
+ TrustedEventsAtSystemGroupBubble());
+ manager->AddEventListenerByType(mTextListener,
+ NS_LITERAL_STRING("keyup"),
+ TrustedEventsAtSystemGroupBubble());
+ }
+
+ mSelCon->SetScrollableFrame(do_QueryFrame(mBoundFrame->PrincipalChildList().FirstChild()));
+}
+
+void
+nsTextEditorState::ValueWasChanged(bool aNotify)
+{
+ UpdatePlaceholderVisibility(aNotify);
+}
+
+void
+nsTextEditorState::UpdatePlaceholderText(bool aNotify)
+{
+ NS_ASSERTION(mPlaceholderDiv, "This function should not be called if "
+ "mPlaceholderDiv isn't set");
+
+ // If we don't have a placeholder div, there's nothing to do.
+ if (!mPlaceholderDiv)
+ return;
+
+ nsAutoString placeholderValue;
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholderValue);
+ nsContentUtils::RemoveNewlines(placeholderValue);
+ NS_ASSERTION(mPlaceholderDiv->GetFirstChild(), "placeholder div has no child");
+ mPlaceholderDiv->GetFirstChild()->SetText(placeholderValue, aNotify);
+}
+
+void
+nsTextEditorState::UpdatePlaceholderVisibility(bool aNotify)
+{
+ nsAutoString value;
+ GetValue(value, true);
+
+ mPlaceholderVisibility = value.IsEmpty();
+
+ if (mPlaceholderVisibility &&
+ !Preferences::GetBool("dom.placeholder.show_on_focus", true)) {
+ nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
+ mPlaceholderVisibility = !nsContentUtils::IsFocusedContent(content);
+ }
+
+ if (mBoundFrame && aNotify) {
+ mBoundFrame->InvalidateFrame();
+ }
+}
+
+void
+nsTextEditorState::HideSelectionIfBlurred()
+{
+ MOZ_ASSERT(mSelCon, "Should have a selection controller if we have a frame!");
+ nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
+ if (!nsContentUtils::IsFocusedContent(content)) {
+ mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
+ }
+}
+
+bool
+nsTextEditorState::EditorHasComposition()
+{
+ bool isComposing = false;
+ nsCOMPtr<nsIEditorIMESupport> editorIMESupport = do_QueryInterface(mEditor);
+ return editorIMESupport &&
+ NS_SUCCEEDED(editorIMESupport->GetComposing(&isComposing)) &&
+ isComposing;
+}
+
+NS_IMPL_ISUPPORTS(nsAnonDivObserver, nsIMutationObserver)
+
+void
+nsAnonDivObserver::CharacterDataChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+ mTextEditorState->ClearValueCache();
+}
+
+void
+nsAnonDivObserver::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t /* unused */)
+{
+ mTextEditorState->ClearValueCache();
+}
+
+void
+nsAnonDivObserver::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t /* unused */)
+{
+ mTextEditorState->ClearValueCache();
+}
+
+void
+nsAnonDivObserver::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ mTextEditorState->ClearValueCache();
+}
diff --git a/dom/html/nsTextEditorState.h b/dom/html/nsTextEditorState.h
new file mode 100644
index 000000000..11494f155
--- /dev/null
+++ b/dom/html/nsTextEditorState.h
@@ -0,0 +1,364 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTextEditorState_h__
+#define nsTextEditorState_h__
+
+#include "nsString.h"
+#include "nsITextControlElement.h"
+#include "nsITextControlFrame.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/WeakPtr.h"
+
+class nsTextInputListener;
+class nsTextControlFrame;
+class nsTextInputSelectionImpl;
+class nsAnonDivObserver;
+class nsISelectionController;
+class nsFrameSelection;
+class nsIEditor;
+class nsITextControlElement;
+class nsFrame;
+
+namespace mozilla {
+namespace dom {
+class HTMLInputElement;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * nsTextEditorState is a class which is responsible for managing the state of
+ * plaintext controls. This currently includes the following HTML elements:
+ * <input type=text>
+ * <input type=password>
+ * <textarea>
+ * and also XUL controls such as <textbox> which use one of these elements behind
+ * the scenes.
+ *
+ * This class is held as a member of HTMLInputElement and nsHTMLTextAreaElement.
+ * The public functions in this class include the public APIs which content/ uses.
+ * Layout code uses the nsITextControlElement interface to invoke functions on this
+ * class.
+ *
+ * The design motivation behind this class is maintaining all of the things which
+ * collectively are considered the "state" of the text control in a single location.
+ * This state includes several things:
+ *
+ * * The control's value. This value is stored in the mValue member, and is only
+ * used when there is no frame for the control, or when the editor object has
+ * not been initialized yet.
+ *
+ * * The control's associated frame. This value is stored in the mBoundFrame member.
+ * A text control might never have an associated frame during its life cycle,
+ * or might have several different ones, but at any given moment in time there is
+ * a maximum of 1 bound frame to each text control.
+ *
+ * * The control's associated editor. This value is stored in the mEditor member.
+ * An editor is initilized for the control only when necessary (that is, when either
+ * the user is about to interact with the text control, or when some other code
+ * needs to access the editor object. Without a frame bound to the control, an
+ * editor is never initialzied. Once initialized, the editor might outlive the frame,
+ * in which case the same editor will be used if a new frame gets bound to the
+ * text control.
+ *
+ * * The anonymous content associated with the text control's frame, including the
+ * value div (the DIV element responsible for holding the value of the text control)
+ * and the placeholder div (the DIV element responsible for holding the placeholder
+ * value of the text control.) These values are stored in the mRootNode and
+ * mPlaceholderDiv members, respectively. They will be created when a
+ * frame is bound to the text control. They will be destroyed when the frame is
+ * unbound from the object. We could try and hold on to the anonymous content
+ * between different frames, but unfortunately that is not currently possible
+ * because they are not unbound from the document in time.
+ *
+ * * The frame selection controller. This value is stored in the mSelCon member.
+ * The frame selection controller is responsible for maintaining the selection state
+ * on a frame. It is created when a frame is bound to the text control element,
+ * and will be destroy when the frame is being unbound from the text control element.
+ * It is created alongside with the frame selection object which is stored in the
+ * mFrameSel member.
+ *
+ * * The editor text listener. This value is stored in the mTextListener member.
+ * Its job is to listen to selection and keyboard events, and act accordingly.
+ * It is created when an a frame is first bound to the control, and will be destroyed
+ * when the frame is unbound from the text control element.
+ *
+ * * The editor's cached value. This value is stored in the mCachedValue member.
+ * It is used to improve the performance of append operations to the text
+ * control. A mutation observer stored in the mMutationObserver has the job of
+ * invalidating this cache when the anonymous contect containing the value is
+ * changed.
+ *
+ * * The editor's cached selection properties. These vales are stored in the
+ * mSelectionProperties member, and include the selection's start, end and
+ * direction. They are only used when there is no frame available for the
+ * text field.
+ *
+ *
+ * As a general rule, nsTextEditorState objects own the value of the text control, and any
+ * attempt to retrieve or set the value must be made through those objects. Internally,
+ * the value can be represented in several different ways, based on the state the control is
+ * in.
+ *
+ * * When the control is first initialized, its value is equal to the default value of
+ * the DOM node. For <input> text controls, this default value is the value of the
+ * value attribute. For <textarea> elements, this default value is the value of the
+ * text node children of the element.
+ *
+ * * If the value has been changed through the DOM node (before the editor for the object
+ * is initialized), the value is stored as a simple string inside the mValue member of
+ * the nsTextEditorState object.
+ *
+ * * If an editor has been initialized for the control, the value is set and retrievd via
+ * the nsIPlaintextEditor interface, and is internally managed by the editor as the
+ * native anonymous content tree attached to the control's frame.
+ *
+ * * If the text editor state object is unbound from the control's frame, the value is
+ * transferred to the mValue member variable, and will be managed there until a new
+ * frame is bound to the text editor state object.
+ */
+
+class RestoreSelectionState;
+
+class nsTextEditorState : public mozilla::SupportsWeakPtr<nsTextEditorState> {
+public:
+ MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsTextEditorState)
+ explicit nsTextEditorState(nsITextControlElement* aOwningElement);
+ ~nsTextEditorState();
+
+ void Traverse(nsCycleCollectionTraversalCallback& cb);
+ void Unlink();
+
+ nsIEditor* GetEditor();
+ nsISelectionController* GetSelectionController() const;
+ nsFrameSelection* GetConstFrameSelection();
+ nsresult BindToFrame(nsTextControlFrame* aFrame);
+ void UnbindFromFrame(nsTextControlFrame* aFrame);
+ nsresult PrepareEditor(const nsAString *aValue = nullptr);
+ void InitializeKeyboardEventListeners();
+
+ enum SetValueFlags
+ {
+ // The call is for internal processing.
+ eSetValue_Internal = 0,
+ // The value is changed by a call of setUserInput() from chrome.
+ eSetValue_BySetUserInput = 1 << 0,
+ // The value is changed by changing value attribute of the element or
+ // something like setRangeText().
+ eSetValue_ByContent = 1 << 1,
+ // Whether the value change should be notified to the frame/contet nor not.
+ eSetValue_Notify = 1 << 2
+ };
+ MOZ_MUST_USE bool SetValue(const nsAString& aValue, uint32_t aFlags);
+ void GetValue(nsAString& aValue, bool aIgnoreWrap) const;
+ void EmptyValue() { if (mValue) mValue->Truncate(); }
+ bool IsEmpty() const { return mValue ? mValue->IsEmpty() : true; }
+
+ nsresult CreatePlaceholderNode();
+
+ mozilla::dom::Element* GetRootNode() {
+ return mRootNode;
+ }
+ mozilla::dom::Element* GetPlaceholderNode() {
+ return mPlaceholderDiv;
+ }
+
+ bool IsSingleLineTextControl() const {
+ return mTextCtrlElement->IsSingleLineTextControl();
+ }
+ bool IsTextArea() const {
+ return mTextCtrlElement->IsTextArea();
+ }
+ bool IsPlainTextControl() const {
+ return mTextCtrlElement->IsPlainTextControl();
+ }
+ bool IsPasswordTextControl() const {
+ return mTextCtrlElement->IsPasswordTextControl();
+ }
+ int32_t GetCols() {
+ return mTextCtrlElement->GetCols();
+ }
+ int32_t GetWrapCols() {
+ return mTextCtrlElement->GetWrapCols();
+ }
+ int32_t GetRows() {
+ return mTextCtrlElement->GetRows();
+ }
+
+ // placeholder methods
+ void UpdatePlaceholderVisibility(bool aNotify);
+ bool GetPlaceholderVisibility() {
+ return mPlaceholderVisibility;
+ }
+ void UpdatePlaceholderText(bool aNotify);
+
+ /**
+ * Get the maxlength attribute
+ * @param aMaxLength the value of the max length attr
+ * @returns false if attr not defined
+ */
+ bool GetMaxLength(int32_t* aMaxLength);
+
+ void ClearValueCache() { mCachedValue.Truncate(); }
+
+ void HideSelectionIfBlurred();
+
+ struct SelectionProperties {
+ public:
+ SelectionProperties() : mStart(0), mEnd(0),
+ mDirection(nsITextControlFrame::eForward) {}
+ bool IsDefault() const
+ {
+ return mStart == 0 && mEnd == 0 &&
+ mDirection == nsITextControlFrame::eForward;
+ }
+ int32_t GetStart() const
+ {
+ return mStart;
+ }
+ void SetStart(int32_t value)
+ {
+ mIsDirty = true;
+ mStart = value;
+ }
+ int32_t GetEnd() const
+ {
+ return mEnd;
+ }
+ void SetEnd(int32_t value)
+ {
+ mIsDirty = true;
+ mEnd = value;
+ }
+ nsITextControlFrame::SelectionDirection GetDirection() const
+ {
+ return mDirection;
+ }
+ void SetDirection(nsITextControlFrame::SelectionDirection value)
+ {
+ mIsDirty = true;
+ mDirection = value;
+ }
+ // return true only if mStart, mEnd, or mDirection have been modified
+ bool IsDirty() const
+ {
+ return mIsDirty;
+ }
+ private:
+ int32_t mStart, mEnd;
+ bool mIsDirty = false;
+ nsITextControlFrame::SelectionDirection mDirection;
+ };
+
+ bool IsSelectionCached() const;
+ SelectionProperties& GetSelectionProperties();
+ void SetSelectionProperties(SelectionProperties& aProps);
+ void WillInitEagerly() { mSelectionRestoreEagerInit = true; }
+ bool HasNeverInitializedBefore() const { return !mEverInited; }
+
+ void UpdateEditableState(bool aNotify) {
+ if (mRootNode) {
+ mRootNode->UpdateEditableState(aNotify);
+ }
+ }
+
+private:
+ friend class RestoreSelectionState;
+
+ // not copy constructible
+ nsTextEditorState(const nsTextEditorState&);
+ // not assignable
+ void operator= (const nsTextEditorState&);
+
+ nsresult CreateRootNode();
+
+ void ValueWasChanged(bool aNotify);
+
+ void DestroyEditor();
+ void Clear();
+
+ nsresult InitializeRootNode();
+
+ void FinishedRestoringSelection();
+
+ mozilla::dom::HTMLInputElement* GetParentNumberControl(nsFrame* aFrame) const;
+
+ bool EditorHasComposition();
+
+ class InitializationGuard {
+ public:
+ explicit InitializationGuard(nsTextEditorState& aState) :
+ mState(aState),
+ mGuardSet(false)
+ {
+ if (!mState.mInitializing) {
+ mGuardSet = true;
+ mState.mInitializing = true;
+ }
+ }
+ ~InitializationGuard() {
+ if (mGuardSet) {
+ mState.mInitializing = false;
+ }
+ }
+ bool IsInitializingRecursively() const {
+ return !mGuardSet;
+ }
+ private:
+ nsTextEditorState& mState;
+ bool mGuardSet;
+ };
+ friend class InitializationGuard;
+ friend class PrepareEditorEvent;
+
+ // The text control element owns this object, and ensures that this object
+ // has a smaller lifetime.
+ nsITextControlElement* const MOZ_NON_OWNING_REF mTextCtrlElement;
+ RefPtr<nsTextInputSelectionImpl> mSelCon;
+ RefPtr<RestoreSelectionState> mRestoringSelection;
+ nsCOMPtr<nsIEditor> mEditor;
+ nsCOMPtr<mozilla::dom::Element> mRootNode;
+ nsCOMPtr<mozilla::dom::Element> mPlaceholderDiv;
+ nsTextControlFrame* mBoundFrame;
+ RefPtr<nsTextInputListener> mTextListener;
+ mozilla::Maybe<nsString> mValue;
+ RefPtr<nsAnonDivObserver> mMutationObserver;
+ mutable nsString mCachedValue; // Caches non-hard-wrapped value on a multiline control.
+ // mValueBeingSet is available only while SetValue() is requesting to commit
+ // composition. I.e., this is valid only while mIsCommittingComposition is
+ // true. While active composition is being committed, GetValue() needs
+ // the latest value which is set by SetValue(). So, this is cache for that.
+ nsString mValueBeingSet;
+ SelectionProperties mSelectionProperties;
+ bool mEverInited; // Have we ever been initialized?
+ bool mEditorInitialized;
+ bool mInitializing; // Whether we're in the process of initialization
+ bool mValueTransferInProgress; // Whether a value is being transferred to the frame
+ bool mSelectionCached; // Whether mSelectionProperties is valid
+ mutable bool mSelectionRestoreEagerInit; // Whether we're eager initing because of selection restore
+ bool mPlaceholderVisibility;
+ bool mIsCommittingComposition;
+};
+
+inline void
+ImplCycleCollectionUnlink(nsTextEditorState& aField)
+{
+ aField.Unlink();
+}
+
+inline void
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+ nsTextEditorState& aField,
+ const char* aName,
+ uint32_t aFlags = 0)
+{
+ aField.Traverse(aCallback);
+}
+
+#endif
diff --git a/dom/html/reftests/41464-1-ref.html b/dom/html/reftests/41464-1-ref.html
new file mode 100644
index 000000000..3b68fca6d
--- /dev/null
+++ b/dom/html/reftests/41464-1-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<title>Dynamic manipulation of textarea.wrap</title>
+<link rel=help href=http://www.whatwg.org/html5/#dom-textarea-wrap>
+<link rel=author title=Ms2ger href=mailto:ms2ger@gmail.com>
+<textarea wrap=soft cols=20>01234567890 01234567890 01234567890</textarea>
diff --git a/dom/html/reftests/41464-1a.html b/dom/html/reftests/41464-1a.html
new file mode 100644
index 000000000..f0569347f
--- /dev/null
+++ b/dom/html/reftests/41464-1a.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<title>Dynamic manipulation of textarea.wrap</title>
+<link rel=help href=http://www.whatwg.org/html5/#dom-textarea-wrap>
+<link rel=author title=Ms2ger href=mailto:ms2ger@gmail.com>
+<textarea wrap=off cols=20>01234567890 01234567890 01234567890</textarea>
+<script>
+document.getElementsByTagName("textarea")[0].wrap = "soft";
+</script>
diff --git a/dom/html/reftests/41464-1b.html b/dom/html/reftests/41464-1b.html
new file mode 100644
index 000000000..13c42518c
--- /dev/null
+++ b/dom/html/reftests/41464-1b.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<title>Dynamic manipulation of textarea.wrap</title>
+<link rel=help href=http://www.whatwg.org/html5/#dom-textarea-wrap>
+<link rel=author title=Ms2ger href=mailto:ms2ger@gmail.com>
+<textarea wrap=off cols=20>01234567890 01234567890 01234567890</textarea>
+<script>
+document.getElementsByTagName("textarea")[0].setAttribute("wrap", "soft");
+</script>
diff --git a/dom/html/reftests/468263-1a.html b/dom/html/reftests/468263-1a.html
new file mode 100644
index 000000000..93ad7df34
--- /dev/null
+++ b/dom/html/reftests/468263-1a.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <img id="image2" src="pass.png">
+</body>
+</html>
diff --git a/dom/html/reftests/468263-1b.html b/dom/html/reftests/468263-1b.html
new file mode 100644
index 000000000..e637e3094
--- /dev/null
+++ b/dom/html/reftests/468263-1b.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <input id="image3" type="image" src="pass.png">
+</body>
+</html>
diff --git a/dom/html/reftests/468263-1c.html b/dom/html/reftests/468263-1c.html
new file mode 100644
index 000000000..14c2b2b37
--- /dev/null
+++ b/dom/html/reftests/468263-1c.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <object id="image4" type="image/png" data="pass.png"></object>
+</body>
+</html>
diff --git a/dom/html/reftests/468263-1d.html b/dom/html/reftests/468263-1d.html
new file mode 100644
index 000000000..53740e596
--- /dev/null
+++ b/dom/html/reftests/468263-1d.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <object id="image5" type="text/html" data="data:text/html,<b>Testing</b>"></object>
+</body>
+</html>
diff --git a/dom/html/reftests/468263-2-alternate-ref.html b/dom/html/reftests/468263-2-alternate-ref.html
new file mode 100644
index 000000000..538898c97
--- /dev/null
+++ b/dom/html/reftests/468263-2-alternate-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <img id="image1" src="">
+ <input id="image3" type="image">
+</body>
+</html>
diff --git a/dom/html/reftests/468263-2-ref.html b/dom/html/reftests/468263-2-ref.html
new file mode 100644
index 000000000..6e7f6cb36
--- /dev/null
+++ b/dom/html/reftests/468263-2-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <img id="image1" src="">
+ <img id="image2">
+ <input id="image3" type="image">
+ <object id="image4" type="image/png">
+ <object id="image5" type="text/html"></object>
+</body>
+</html>
diff --git a/dom/html/reftests/468263-2.html b/dom/html/reftests/468263-2.html
new file mode 100644
index 000000000..d3d447d07
--- /dev/null
+++ b/dom/html/reftests/468263-2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<body onload="document.getElementById('image1').setAttribute('src', ''); document.getElementById('image2').removeAttribute('src'); document.getElementById('image3').removeAttribute('src'); document.getElementById('image4').removeAttribute('data'); document.getElementById('image5').removeAttribute('data');">
+ <img id="image1" src="pass.png">
+ <img id="image2" src="pass.png">
+ <input id="image3" type="image" src="pass.png">
+ <object id="image4" type="image/png" data="pass.png"></object>
+ <object id="image5" type="text/html" data="data:text/html,<b>Testing</b>"></object>
+</body>
+</html>
diff --git a/dom/html/reftests/484200-1-ref.html b/dom/html/reftests/484200-1-ref.html
new file mode 100644
index 000000000..39ef12261
--- /dev/null
+++ b/dom/html/reftests/484200-1-ref.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<title>Test for Bug 484200</title>
+<style>
+p { background:lime }
+</style>
+</head>
+<body>
+<p>xxxxx</p>
+</body>
+</html>
diff --git a/dom/html/reftests/484200-1.html b/dom/html/reftests/484200-1.html
new file mode 100644
index 000000000..c2663d1ae
--- /dev/null
+++ b/dom/html/reftests/484200-1.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<title>Test for Bug 484200</title>
+<style src=style>
+p { background:lime }
+</style>
+</head>
+<body>
+<p>xxxxx</p>
+</body>
+</html>
diff --git a/dom/html/reftests/485377-ref.html b/dom/html/reftests/485377-ref.html
new file mode 100644
index 000000000..e98cbbb71
--- /dev/null
+++ b/dom/html/reftests/485377-ref.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<title>The mark element</title>
+<p>Foo <span style="background: yellow; color: black;">bar</span> baz.
diff --git a/dom/html/reftests/485377.html b/dom/html/reftests/485377.html
new file mode 100644
index 000000000..9f91f9980
--- /dev/null
+++ b/dom/html/reftests/485377.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<title>The mark element</title>
+<p>Foo <mark>bar</mark> baz.
diff --git a/dom/html/reftests/52019-1-ref.html b/dom/html/reftests/52019-1-ref.html
new file mode 100644
index 000000000..73bf05f68
--- /dev/null
+++ b/dom/html/reftests/52019-1-ref.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<title>Test for Bug 52019</title>
+</head>
+<body>
+<font>font</font>
+<font>font</font>
+<font size="+2">font</font>
+<font size="-2">font</font>
+</body>
+</html>
diff --git a/dom/html/reftests/52019-1.html b/dom/html/reftests/52019-1.html
new file mode 100644
index 000000000..09cff148f
--- /dev/null
+++ b/dom/html/reftests/52019-1.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<title>Test for Bug 52019</title>
+</head>
+<body>
+<font size="+.5">font</font>
+<font size="-.5">font</font>
+<font size="+2.5">font</font>
+<font size="-2.5">font</font>
+</body>
+</html>
diff --git a/dom/html/reftests/557840-ref.html b/dom/html/reftests/557840-ref.html
new file mode 100644
index 000000000..ea72d50f5
--- /dev/null
+++ b/dom/html/reftests/557840-ref.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<title>Canvas and hspace, vspace</title>
+<canvas style="background: black;"></canvas>
diff --git a/dom/html/reftests/557840.html b/dom/html/reftests/557840.html
new file mode 100644
index 000000000..4aed5092a
--- /dev/null
+++ b/dom/html/reftests/557840.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<title>Canvas and hspace, vspace</title>
+<canvas hspace="42" vspace="42" style="background: black;"></canvas>
diff --git a/dom/html/reftests/560059-video-dimensions-ref.html b/dom/html/reftests/560059-video-dimensions-ref.html
new file mode 100644
index 000000000..f3424de68
--- /dev/null
+++ b/dom/html/reftests/560059-video-dimensions-ref.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<title>Video dimensions</title>
+<video style="border: thin solid black;" width="300" height="150"></video>
diff --git a/dom/html/reftests/560059-video-dimensions.html b/dom/html/reftests/560059-video-dimensions.html
new file mode 100644
index 000000000..99373c999
--- /dev/null
+++ b/dom/html/reftests/560059-video-dimensions.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<title>Video dimensions</title>
+<video style="border: thin solid black;"></video>
diff --git a/dom/html/reftests/573322-no-quirks-ref.html b/dom/html/reftests/573322-no-quirks-ref.html
new file mode 100644
index 000000000..e3f993f2f
--- /dev/null
+++ b/dom/html/reftests/573322-no-quirks-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<style>
+div { background: green; border: solid blue; width: 100px; height: 100px; }
+</style>
+<table border=1 width=50%>
+<tr>
+<td><div></div>left
+</table>
+<table border=1 width=50%>
+<tr>
+<td><div></div>justify
+</table>
+<table border=1 width=50%>
+<tr>
+<td style="text-align: -moz-right;"><div></div>right
+</table>
+<table border=1 width=50%>
+<tr>
+<td style="text-align: -moz-center;"><div></div>center
+</table>
+<table border=1 width=50%>
+<tr>
+<td style="text-align: -moz-center;"><div></div>middle
+</table>
+<table border=1 width=50%>
+<tr>
+<td style="text-align: center;"><div></div>absmiddle
+</table>
diff --git a/dom/html/reftests/573322-no-quirks.html b/dom/html/reftests/573322-no-quirks.html
new file mode 100644
index 000000000..ac27609e3
--- /dev/null
+++ b/dom/html/reftests/573322-no-quirks.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<style>
+div { background: green; border: solid blue; width: 100px; height: 100px; }
+</style>
+<table border=1 width=50%>
+<tr>
+<td align=left><div></div>left
+</table>
+<table border=1 width=50%>
+<tr>
+<td align=justify><div></div>justify
+</table>
+<table border=1 width=50%>
+<tr>
+<td align=right><div></div>right
+</table>
+<table border=1 width=50%>
+<tr>
+<td align=center><div></div>center
+</table>
+<table border=1 width=50%>
+<tr>
+<td align=middle><div></div>middle
+</table>
+<table border=1 width=50%>
+<tr>
+<td align=absmiddle><div></div>absmiddle
+</table>
diff --git a/dom/html/reftests/573322-quirks-ref.html b/dom/html/reftests/573322-quirks-ref.html
new file mode 100644
index 000000000..6cc22a58e
--- /dev/null
+++ b/dom/html/reftests/573322-quirks-ref.html
@@ -0,0 +1,27 @@
+<style>
+div { background: green; border: solid blue; width: 100px; height: 100px; }
+</style>
+<table border=1 width=50%>
+<tr>
+<td><div></div>left
+</table>
+<table border=1 width=50%>
+<tr>
+<td><div></div>justify
+</table>
+<table border=1 width=50%>
+<tr>
+<td style="text-align: -moz-right;"><div></div>right
+</table>
+<table border=1 width=50%>
+<tr>
+<td style="text-align: -moz-center;"><div></div>center
+</table>
+<table border=1 width=50%>
+<tr>
+<td style="text-align: -moz-center;"><div></div>middle
+</table>
+<table border=1 width=50%>
+<tr>
+<td style="text-align: center;"><div></div>absmiddle
+</table>
diff --git a/dom/html/reftests/573322-quirks.html b/dom/html/reftests/573322-quirks.html
new file mode 100644
index 000000000..35757b185
--- /dev/null
+++ b/dom/html/reftests/573322-quirks.html
@@ -0,0 +1,27 @@
+<style>
+div { background: green; border: solid blue; width: 100px; height: 100px; }
+</style>
+<table border=1 width=50%>
+<tr>
+<td align=left><div></div>left
+</table>
+<table border=1 width=50%>
+<tr>
+<td align=justify><div></div>justify
+</table>
+<table border=1 width=50%>
+<tr>
+<td align=right><div></div>right
+</table>
+<table border=1 width=50%>
+<tr>
+<td align=center><div></div>center
+</table>
+<table border=1 width=50%>
+<tr>
+<td align=middle><div></div>middle
+</table>
+<table border=1 width=50%>
+<tr>
+<td align=absmiddle><div></div>absmiddle
+</table>
diff --git a/dom/html/reftests/596455-1a.html b/dom/html/reftests/596455-1a.html
new file mode 100644
index 000000000..60d494072
--- /dev/null
+++ b/dom/html/reftests/596455-1a.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+ <script>
+ function onLoadHandler()
+ {
+ document.getElementById('l').value = document.getElementById('i').value;
+ document.documentElement.className='';
+ }
+ </script>
+ <body onload="onLoadHandler();">
+ <input type='hidden' value='foo&#13;bar' id='i'>
+ <textarea id='l'></textarea>
+ </body>
+</html>
diff --git a/dom/html/reftests/596455-1b.html b/dom/html/reftests/596455-1b.html
new file mode 100644
index 000000000..8478786c1
--- /dev/null
+++ b/dom/html/reftests/596455-1b.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+ <script>
+ function onLoadHandler()
+ {
+ document.getElementById('l').value = document.getElementById('i').value;
+ document.documentElement.className='';
+ }
+ </script>
+ <body onload="onLoadHandler();">
+ <input value='foo&#13;bar' type='hidden' id='i'>
+ <textarea id='l'></textarea>
+ </body>
+</html>
diff --git a/dom/html/reftests/596455-2a.html b/dom/html/reftests/596455-2a.html
new file mode 100644
index 000000000..f78a36c61
--- /dev/null
+++ b/dom/html/reftests/596455-2a.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+ <script>
+ function onLoadHandler()
+ {
+ document.getElementById('l').value = document.getElementById('i').value;
+ document.documentElement.className='';
+ }
+ </script>
+ <body onload="onLoadHandler();">
+ <input style="display:none;" type='text' value='foo&#13;bar' id='i'>
+ <textarea id='l'></textarea>
+ </body>
+</html>
diff --git a/dom/html/reftests/596455-2b.html b/dom/html/reftests/596455-2b.html
new file mode 100644
index 000000000..186732790
--- /dev/null
+++ b/dom/html/reftests/596455-2b.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html class='reftest-wait'>
+ <script>
+ function onLoadHandler()
+ {
+ document.getElementById('l').value = document.getElementById('i').value;
+ document.documentElement.className='';
+ }
+ </script>
+ <body onload="onLoadHandler();">
+ <input style="display:none;" value='foo&#13;bar' type='text' id='i'>
+ <textarea id='l'></textarea>
+ </body>
+</html>
diff --git a/dom/html/reftests/596455-ref-1.html b/dom/html/reftests/596455-ref-1.html
new file mode 100644
index 000000000..10371df27
--- /dev/null
+++ b/dom/html/reftests/596455-ref-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <textarea>foo&#13;bar</textarea>
+ </body>
+</html>
diff --git a/dom/html/reftests/596455-ref-2.html b/dom/html/reftests/596455-ref-2.html
new file mode 100644
index 000000000..0dbc4dbb3
--- /dev/null
+++ b/dom/html/reftests/596455-ref-2.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <textarea>foobar</textarea>
+ </body>
+</html>
diff --git a/dom/html/reftests/610935-ref.html b/dom/html/reftests/610935-ref.html
new file mode 100644
index 000000000..7a2a41a52
--- /dev/null
+++ b/dom/html/reftests/610935-ref.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<title>Test for bug 610935</title>
+<style>
+div { width:300px; background:yellow; height:50px; }
+table { width:150%; }
+td { background:blue; }
+</style>
+<div>
+ <table cellspacing="0" cellpadding="0" border="0">
+ <tr><td>parent div float=left</td></tr>
+ </table>
+</div>
diff --git a/dom/html/reftests/610935.html b/dom/html/reftests/610935.html
new file mode 100644
index 000000000..5495ae3d5
--- /dev/null
+++ b/dom/html/reftests/610935.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>Test for bug 610935</title>
+<style>
+div { width:300px; background:yellow; height:50px; }
+td { background:blue; }
+</style>
+<div>
+ <table width="150%" cellspacing="0" cellpadding="0" border="0">
+ <tr><td>parent div float=left</td></tr>
+ </table>
+</div>
diff --git a/dom/html/reftests/649134-1.html b/dom/html/reftests/649134-1.html
new file mode 100644
index 000000000..b38e98830
--- /dev/null
+++ b/dom/html/reftests/649134-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug </title>
+<link rel="stylesheet" type="text/css" href="" />
+<!--
+ #foo {
+ /* This doesn't get evaluated */
+ color: red;
+ }
+ #ie {
+ border: 5px solid red;
+ }
+ #ie {
+ display: block;
+ }
+ #moz {
+ color: blue;
+ /* display: none; */
+ }
+-->
+
+</head>
+<body>
+
+<p id="foo">foo</p>
+<p id="ie">ie</p>
+<p id="moz">moz</p>
+
+</body>
+</html>
diff --git a/dom/html/reftests/649134-2-ref.html b/dom/html/reftests/649134-2-ref.html
new file mode 100644
index 000000000..d15fae528
--- /dev/null
+++ b/dom/html/reftests/649134-2-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug </title>
+<style>
+ #ie {
+ border: 5px solid red;
+ }
+ #ie {
+ display: block;
+ }
+ #moz {
+ color: blue;
+ /* display: none; */
+ }
+</style>
+</head>
+<body>
+
+<p id="foo">foo</p>
+<p id="ie">ie</p>
+<p id="moz">moz</p>
+
+</body>
+</html>
diff --git a/dom/html/reftests/649134-2.html b/dom/html/reftests/649134-2.html
new file mode 100644
index 000000000..4d2a5ae50
--- /dev/null
+++ b/dom/html/reftests/649134-2.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug </title>
+<link rel="stylesheet" type="text/css" href=" " />
+<!--
+ #foo {
+ /* This doesn't get evaluated */
+ color: red;
+ }
+ #ie {
+ border: 5px solid red;
+ }
+ #ie {
+ display: block;
+ }
+ #moz {
+ color: blue;
+ /* display: none; */
+ }
+-->
+
+</head>
+<body>
+
+<p id="foo">foo</p>
+<p id="ie">ie</p>
+<p id="moz">moz</p>
+
+</body>
+</html>
diff --git a/dom/html/reftests/649134-ref.html b/dom/html/reftests/649134-ref.html
new file mode 100644
index 000000000..2968464be
--- /dev/null
+++ b/dom/html/reftests/649134-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug </title>
+</head>
+<body>
+
+<p id="foo">foo</p>
+<p id="ie">ie</p>
+<p id="moz">moz</p>
+
+</body>
+</html>
diff --git a/dom/html/reftests/82711-1-ref.html b/dom/html/reftests/82711-1-ref.html
new file mode 100644
index 000000000..e0b25fc9b
--- /dev/null
+++ b/dom/html/reftests/82711-1-ref.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html>
+<body>
+<textarea rows="10" cols="25" wrap="off">
+ 0 1 2 3
+ 4 5
+
+ 6 7 8
+ 9
+
+
+ This is a long line that could wrap.
+</textarea>
+</body>
+</html>
diff --git a/dom/html/reftests/82711-1.html b/dom/html/reftests/82711-1.html
new file mode 100644
index 000000000..70a8c1b23
--- /dev/null
+++ b/dom/html/reftests/82711-1.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html>
+<body>
+<textarea rows="10" cols="25" style="white-space: pre">
+ 0 1 2 3
+ 4 5
+
+ 6 7 8
+ 9
+
+
+ This is a long line that could wrap.
+</textarea>
+</body>
+</html>
diff --git a/dom/html/reftests/82711-2-ref.html b/dom/html/reftests/82711-2-ref.html
new file mode 100644
index 000000000..963b9c714
--- /dev/null
+++ b/dom/html/reftests/82711-2-ref.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html>
+<body>
+<textarea rows="10" cols="25" wrap="off" style="white-space: normal">
+ 0 1 2 3
+ 4 5
+
+ 6 7 8
+ 9
+
+
+ This is a long line that could wrap.
+</textarea>
+</body>
+</html>
diff --git a/dom/html/reftests/82711-2.html b/dom/html/reftests/82711-2.html
new file mode 100644
index 000000000..aacd6d481
--- /dev/null
+++ b/dom/html/reftests/82711-2.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<html>
+<body>
+<textarea rows="10" cols="25" style="white-space: normal">
+0 1 2 3 4 5 6 7 8 9 This is a long line that could wrap.
+</textarea>
+</body>
+</html>
diff --git a/dom/html/reftests/autofocus/autofocus-after-body-focus-ref.html b/dom/html/reftests/autofocus/autofocus-after-body-focus-ref.html
new file mode 100644
index 000000000..3801ed754
--- /dev/null
+++ b/dom/html/reftests/autofocus/autofocus-after-body-focus-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <body onload="document.getElementsByTagName('input')[0].focus();">
+ <input onfocus="document.documentElement.removeAttribute('class');">
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/autofocus-after-body-focus.html b/dom/html/reftests/autofocus/autofocus-after-body-focus.html
new file mode 100644
index 000000000..6d43b865a
--- /dev/null
+++ b/dom/html/reftests/autofocus/autofocus-after-body-focus.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <body>
+ <script>
+ document.body.focus();
+ </script>
+ <input autofocus onfocus="document.documentElement.removeAttribute('class');">
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/autofocus-after-load-ref.html b/dom/html/reftests/autofocus/autofocus-after-load-ref.html
new file mode 100644
index 000000000..eab4f2ce2
--- /dev/null
+++ b/dom/html/reftests/autofocus/autofocus-after-load-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <body>
+ <input><textarea></textarea><select></select><button></button>
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/autofocus-after-load.html b/dom/html/reftests/autofocus/autofocus-after-load.html
new file mode 100644
index 000000000..753ef183d
--- /dev/null
+++ b/dom/html/reftests/autofocus/autofocus-after-load.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <script>
+ function loadHandler()
+ {
+ var body = document.body;
+
+ var elements = ["input", "textarea", "select", "button"];
+ for (var e of elements) {
+ var el = document.createElement(e);
+ el.autofocus = true;
+ body.appendChild(el);
+ }
+
+ setTimeout(document.documentElement.removeAttribute('class'), 0);
+ }
+ </script>
+ <body onload="loadHandler();">
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/autofocus-leaves-iframe-ref.html b/dom/html/reftests/autofocus/autofocus-leaves-iframe-ref.html
new file mode 100644
index 000000000..91cee7cbc
--- /dev/null
+++ b/dom/html/reftests/autofocus/autofocus-leaves-iframe-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <script>
+ function loadHandler()
+ {
+ frames[0].document.getElementsByTagName('input')[0].onfocus = function() {
+ document.documentElement.removeAttribute('class');
+ }
+ frames[0].document.getElementsByTagName('input')[0].focus();
+ }
+ </script>
+ <body onload="loadHandler();">
+ <iframe src="data:text/html,<input>"></iframe>
+ <input></input>
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/autofocus-leaves-iframe.html b/dom/html/reftests/autofocus/autofocus-leaves-iframe.html
new file mode 100644
index 000000000..0f950ebe2
--- /dev/null
+++ b/dom/html/reftests/autofocus/autofocus-leaves-iframe.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <script>
+ function frameLoadHandler()
+ {
+ var i = document.createElement('input');
+ i.autofocus = true;
+ document.body.appendChild(i);
+ setTimeout(document.documentElement.removeAttribute('class'), 0);
+ }
+ </script>
+ <body>
+ <iframe onload="frameLoadHandler();" src="data:text/html,<input autofocus>"></iframe>
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/button-create.html b/dom/html/reftests/autofocus/button-create.html
new file mode 100644
index 000000000..ae49d162a
--- /dev/null
+++ b/dom/html/reftests/autofocus/button-create.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <body>
+ <script>
+ var body = document.body;
+
+ var i = document.createElement('button');
+ i.autofocus = false;
+ body.appendChild(i);
+
+ i = document.createElement('button');
+ i.autofocus = true;
+ i.onfocus = function() { setTimeout(document.documentElement.removeAttribute('class'), 0); };
+ body.appendChild(i);
+
+ i = document.createElement('button');
+ i.autofocus = true;
+ i.onfocus = function() { setTimeout(document.documentElement.removeAttribute('class'), 0); };
+ body.appendChild(i);
+ </script>
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/button-load.html b/dom/html/reftests/autofocus/button-load.html
new file mode 100644
index 000000000..a9e28e2bb
--- /dev/null
+++ b/dom/html/reftests/autofocus/button-load.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <script>
+ function focusHandler()
+ {
+ setTimeout(document.documentElement.removeAttribute('class'), 0);
+ }
+ </script>
+ <body>
+ <button></button><button autofocus onfocus="focusHandler();"></button><button autofocus onfocus="focusHandler();"></button>
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/button-ref.html b/dom/html/reftests/autofocus/button-ref.html
new file mode 100644
index 000000000..878c8e268
--- /dev/null
+++ b/dom/html/reftests/autofocus/button-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <body onload="document.getElementsByTagName('button')[1].focus();">
+ <button></button><button onfocus="document.documentElement.removeAttribute('class');"></button><button></button>
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/input-create.html b/dom/html/reftests/autofocus/input-create.html
new file mode 100644
index 000000000..c6d0c2808
--- /dev/null
+++ b/dom/html/reftests/autofocus/input-create.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <body>
+ <script>
+ var body = document.body;
+
+ var i = document.createElement('input');
+ i.autofocus = false;
+ body.appendChild(i);
+
+ i = document.createElement('input');
+ i.autofocus = true;
+ i.onfocus = function() { setTimeout(document.documentElement.removeAttribute('class'), 0); };
+ body.appendChild(i);
+
+ i = document.createElement('input');
+ i.autofocus = true;
+ i.onfocus = function() { setTimeout(document.documentElement.removeAttribute('class'), 0); };
+ body.appendChild(i);
+ </script>
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/input-load.html b/dom/html/reftests/autofocus/input-load.html
new file mode 100644
index 000000000..d40b49177
--- /dev/null
+++ b/dom/html/reftests/autofocus/input-load.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <script>
+ function focusHandler()
+ {
+ document.documentElement.removeAttribute('class');
+ }
+ </script>
+ <body>
+ <input><input autofocus onfocus="focusHandler();"><input autofocus onfocus="focusHandler();">
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/input-number-ref.html b/dom/html/reftests/autofocus/input-number-ref.html
new file mode 100644
index 000000000..384915edb
--- /dev/null
+++ b/dom/html/reftests/autofocus/input-number-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <!-- In this case we're using reftest-wait to make sure the test doesn't
+ get snapshotted before it's been focused. We're not testing
+ invalidation so we don't need to listen for MozReftestInvalidate.
+ -->
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body onload="document.getElementsByTagName('input')[0].focus();">
+ <input onfocus="document.documentElement.removeAttribute('class');"
+ style="-moz-appearance: none;">
+ <!-- div to cover spin box area for type=number to type=text comparison -->
+ <div style="display:block; position:absolute; background-color:black; width:200px; height:100px; top:0px; left:100px;">
+ </body>
+</html>
+
diff --git a/dom/html/reftests/autofocus/input-number.html b/dom/html/reftests/autofocus/input-number.html
new file mode 100644
index 000000000..7816ee9bd
--- /dev/null
+++ b/dom/html/reftests/autofocus/input-number.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <!-- In this case we're using reftest-wait to make sure the test doesn't
+ get snapshotted before it's been focused. We're not testing
+ invalidation so we don't need to listen for MozReftestInvalidate.
+ -->
+ <head>
+ <meta charset="utf-8">
+ <script>
+
+function focusHandler() {
+ setTimeout(function() {
+ document.documentElement.removeAttribute('class');
+ }, 0);
+}
+
+ </script>
+ </head>
+ <body>
+ <input type="number" autofocus onfocus="focusHandler();"
+ style="-moz-appearance: none;">
+ <!-- div to cover spin box area for type=number to type=text comparison -->
+ <div style="display:block; position:absolute; background-color:black; width:200px; height:100px; top:0px; left:100px;">
+ </body>
+</html>
+
diff --git a/dom/html/reftests/autofocus/input-ref.html b/dom/html/reftests/autofocus/input-ref.html
new file mode 100644
index 000000000..6e2e546d2
--- /dev/null
+++ b/dom/html/reftests/autofocus/input-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <body onload="document.getElementsByTagName('input')[1].focus();">
+ <input><input onfocus="document.documentElement.removeAttribute('class');"><input>
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/input-time-ref.html b/dom/html/reftests/autofocus/input-time-ref.html
new file mode 100644
index 000000000..abaa6feea
--- /dev/null
+++ b/dom/html/reftests/autofocus/input-time-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <!-- In this case we're using reftest-wait to make sure the test doesn't
+ get snapshotted before it's been focused. We're not testing
+ invalidation so we don't need to listen for MozReftestInvalidate.
+ -->
+ <head>
+ <script>
+ function focusHandler() {
+ setTimeout(function() {
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ </head>
+ <body onload="document.getElementById('t').focus();">
+ <input type="time" id="t" onfocus="focusHandler();"
+ style="-moz-appearance: none;">
+ </body>
+</html>
+
+
diff --git a/dom/html/reftests/autofocus/input-time.html b/dom/html/reftests/autofocus/input-time.html
new file mode 100644
index 000000000..a86a91bbf
--- /dev/null
+++ b/dom/html/reftests/autofocus/input-time.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <!-- In this case we're using reftest-wait to make sure the test doesn't
+ get snapshotted before it's been focused. We're not testing
+ invalidation so we don't need to listen for MozReftestInvalidate.
+ -->
+ <head>
+ <script>
+ function focusHandler() {
+ setTimeout(function() {
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ </head>
+ <body>
+ <input type="time" autofocus onfocus="focusHandler();"
+ style="-moz-appearance: none;">
+ </body>
+</html>
+
+
diff --git a/dom/html/reftests/autofocus/reftest-stylo.list b/dom/html/reftests/autofocus/reftest-stylo.list
new file mode 100644
index 000000000..5e2300b52
--- /dev/null
+++ b/dom/html/reftests/autofocus/reftest-stylo.list
@@ -0,0 +1,36 @@
+# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
+default-preferences pref(dom.forms.number,true)
+fails skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-load.html input-load.html
+# B2G timed out waiting for reftest-wait to be removed
+# Initial mulet triage: parity with B2G/B2G Desktop
+fails skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-create.html input-create.html
+# B2G timed out waiting for reftest-wait to be removed
+# Initial mulet triage: parity with B2G/B2G Desktop
+# skip skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-number.html input-number.html
+# B2G timed out waiting for reftest-wait to be removed
+# Initial mulet triage: parity with B2G/B2G Desktop
+fails skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == button-load.html button-load.html
+# B2G timed out waiting for reftest-wait to be removed
+# Initial mulet triage: parity with B2G/B2G Desktop
+random needs-focus == button-create.html button-create.html
+# B2G timed out waiting for reftest-wait to be removed
+# Initial mulet triage: parity with B2G/B2G Desktop
+random needs-focus == textarea-load.html textarea-load.html
+# B2G timed out waiting for reftest-wait to be removed
+# Initial mulet triage: parity with B2G/B2G Desktop
+random needs-focus == textarea-create.html textarea-create.html
+# B2G timed out waiting for reftest-wait to be removed
+# Initial mulet triage: parity with B2G/B2G Desktop
+fails skip-if(B2G||Mulet) fuzzy-if(skiaContent,2,4) needs-focus == select-load.html select-load.html
+# B2G timed out waiting for reftest-wait to be removed
+# Initial mulet triage: parity with B2G/B2G Desktop
+fails skip-if(B2G||Mulet) fuzzy-if(skiaContent,2,4) needs-focus == select-create.html select-create.html
+# B2G timed out waiting for reftest-wait to be removed
+# Initial mulet triage: parity with B2G/B2G Desktop
+needs-focus == autofocus-after-load.html autofocus-after-load.html
+fails-if(B2G||Mulet) fuzzy-if(skiaContent,2,5) needs-focus == autofocus-leaves-iframe.html autofocus-leaves-iframe.html
+# B2G focus difference between test and reference
+# Initial mulet triage: parity with B2G/B2G Desktop
+skip == autofocus-after-body-focus.html autofocus-after-body-focus.html
+# bug 773482
+# Initial mulet triage: parity with B2G/B2G Desktop
diff --git a/dom/html/reftests/autofocus/reftest.list b/dom/html/reftests/autofocus/reftest.list
new file mode 100644
index 000000000..703b414e6
--- /dev/null
+++ b/dom/html/reftests/autofocus/reftest.list
@@ -0,0 +1,14 @@
+default-preferences pref(dom.forms.number,true) pref(dom.forms.datetime,true)
+fuzzy-if(skiaContent,1,3) needs-focus == input-load.html input-ref.html
+fuzzy-if(skiaContent,1,3) needs-focus == input-create.html input-ref.html
+fuzzy-if(skiaContent,1,3) needs-focus == input-number.html input-number-ref.html
+fuzzy-if(skiaContent,1,3) needs-focus == input-time.html input-time-ref.html
+fuzzy-if(skiaContent,1,3) needs-focus == button-load.html button-ref.html
+fuzzy-if(skiaContent,1,3) needs-focus == button-create.html button-ref.html
+fuzzy-if(skiaContent,1,3) needs-focus == textarea-load.html textarea-ref.html
+fuzzy-if(skiaContent,1,3) needs-focus == textarea-create.html textarea-ref.html
+fuzzy-if(skiaContent,9,6) needs-focus == select-load.html select-ref.html
+fuzzy-if(skiaContent,2,4) needs-focus == select-create.html select-ref.html
+needs-focus == autofocus-after-load.html autofocus-after-load-ref.html
+fuzzy-if(skiaContent,2,5) needs-focus == autofocus-leaves-iframe.html autofocus-leaves-iframe-ref.html
+fuzzy-if(skiaContent,2,5) needs-focus == autofocus-after-body-focus.html autofocus-after-body-focus-ref.html
diff --git a/dom/html/reftests/autofocus/select-create.html b/dom/html/reftests/autofocus/select-create.html
new file mode 100644
index 000000000..fd9c29c95
--- /dev/null
+++ b/dom/html/reftests/autofocus/select-create.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <body>
+ <script>
+ var body = document.body;
+
+ var i = document.createElement('select');
+ i.autofocus = false;
+ body.appendChild(i);
+
+ i = document.createElement('select');
+ i.autofocus = true;
+ i.onfocus = function() { setTimeout(document.documentElement.removeAttribute('class'), 0); };
+ body.appendChild(i);
+
+ i = document.createElement('select');
+ i.autofocus = true;
+ i.onfocus = function() { setTimeout(document.documentElement.removeAttribute('class'), 0); };
+ body.appendChild(i);
+ </script>
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/select-load.html b/dom/html/reftests/autofocus/select-load.html
new file mode 100644
index 000000000..976005bec
--- /dev/null
+++ b/dom/html/reftests/autofocus/select-load.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <script>
+ function focusHandler()
+ {
+ setTimeout(document.documentElement.removeAttribute('class'), 0);
+ }
+ </script>
+ <body>
+ <select></select><select autofocus onfocus="focusHandler();"></select><select autofocus onfocus="focusHandler();"></select>
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/select-ref.html b/dom/html/reftests/autofocus/select-ref.html
new file mode 100644
index 000000000..7fa9cd655
--- /dev/null
+++ b/dom/html/reftests/autofocus/select-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <body onload="document.getElementsByTagName('select')[1].focus();">
+ <select></select><select onfocus="document.documentElement.removeAttribute('class');"></select><select></select>
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/style.css b/dom/html/reftests/autofocus/style.css
new file mode 100644
index 000000000..4216a05cf
--- /dev/null
+++ b/dom/html/reftests/autofocus/style.css
@@ -0,0 +1,7 @@
+:focus { background-color: green; }
+
+/**
+ * autofocus is considered like a keyboard focus and .focus() isn't.
+ * We might change that with bug 620056 but for these tests, we don't really care.
+ */
+::-moz-focus-inner { border: none; }
diff --git a/dom/html/reftests/autofocus/textarea-create.html b/dom/html/reftests/autofocus/textarea-create.html
new file mode 100644
index 000000000..e506bb2b7
--- /dev/null
+++ b/dom/html/reftests/autofocus/textarea-create.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <body>
+ <script>
+ var body = document.body;
+
+ var i = document.createElement('textarea');
+ i.autofocus = false;
+ body.appendChild(i);
+
+ i = document.createElement('textarea');
+ i.autofocus = true;
+ i.onfocus = function() { setTimeout(document.documentElement.removeAttribute('class'), 0); };
+ body.appendChild(i);
+
+ i = document.createElement('textarea');
+ i.autofocus = true;
+ i.onfocus = function() { setTimeout(document.documentElement.removeAttribute('class'), 0); };
+ body.appendChild(i);
+ </script>
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/textarea-load.html b/dom/html/reftests/autofocus/textarea-load.html
new file mode 100644
index 000000000..13ab2cb2c
--- /dev/null
+++ b/dom/html/reftests/autofocus/textarea-load.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <script>
+ function focusHandler()
+ {
+ setTimeout(document.documentElement.removeAttribute('class'), 0);
+ }
+ </script>
+ <body>
+ <textarea></textarea><textarea autofocus onfocus="focusHandler();"></textarea><textarea autofocus onfocus="focusHandler();"></textarea>
+ </body>
+</html>
diff --git a/dom/html/reftests/autofocus/textarea-ref.html b/dom/html/reftests/autofocus/textarea-ref.html
new file mode 100644
index 000000000..b79bd7abe
--- /dev/null
+++ b/dom/html/reftests/autofocus/textarea-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel='stylesheet' type='text/css' href='style.css'>
+ <body onload="document.getElementsByTagName('textarea')[1].focus();">
+ <textarea></textarea><textarea onfocus="document.documentElement.removeAttribute('class');"></textarea><textarea></textarea>
+ </body>
+</html>
diff --git a/dom/html/reftests/bug1106522-1.html b/dom/html/reftests/bug1106522-1.html
new file mode 100644
index 000000000..db07c1010
--- /dev/null
+++ b/dom/html/reftests/bug1106522-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<body>
+ <picture>
+ <source srcset="lime100x100.svg" type="image/svg+xml">
+ <img src="red.png" width="100" height="100">
+ </picture>
+</body>
+</html>
diff --git a/dom/html/reftests/bug1106522-2.html b/dom/html/reftests/bug1106522-2.html
new file mode 100644
index 000000000..15520982f
--- /dev/null
+++ b/dom/html/reftests/bug1106522-2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<body>
+ <picture>
+ <source srcset="lime100x100.svg">
+ <img src="red.png" width="100" height="100">
+ </picture>
+</body>
+</html>
diff --git a/dom/html/reftests/bug1106522-ref.html b/dom/html/reftests/bug1106522-ref.html
new file mode 100644
index 000000000..476c47c12
--- /dev/null
+++ b/dom/html/reftests/bug1106522-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<body>
+ <img src="lime100x100.svg">
+</body>
+</html>
diff --git a/dom/html/reftests/bug1196784-no-srcset.html b/dom/html/reftests/bug1196784-no-srcset.html
new file mode 100644
index 000000000..df55d4863
--- /dev/null
+++ b/dom/html/reftests/bug1196784-no-srcset.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+ <img height="100" width="100" src="bug1196784.png">
+</body>
+</html>
diff --git a/dom/html/reftests/bug1196784-with-srcset.html b/dom/html/reftests/bug1196784-with-srcset.html
new file mode 100644
index 000000000..1cd77bad9
--- /dev/null
+++ b/dom/html/reftests/bug1196784-with-srcset.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+ <img height="100" width="100" src="bug1196784.png" srcset="bug1196784.png">
+</body>
+</html>
diff --git a/dom/html/reftests/bug1196784.png b/dom/html/reftests/bug1196784.png
new file mode 100644
index 000000000..8d0ed5682
--- /dev/null
+++ b/dom/html/reftests/bug1196784.png
Binary files differ
diff --git a/dom/html/reftests/bug1228601-video-rotated-ref.html b/dom/html/reftests/bug1228601-video-rotated-ref.html
new file mode 100644
index 000000000..e489c9a75
--- /dev/null
+++ b/dom/html/reftests/bug1228601-video-rotated-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+function done() {
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload="setTimeout(done, 3);">
+ <video src="video_rotated.mp4" onended="done()" autoplay="true">
+</body>
+</html>
diff --git a/dom/html/reftests/bug1228601-video-rotation-90.html b/dom/html/reftests/bug1228601-video-rotation-90.html
new file mode 100644
index 000000000..94c57d750
--- /dev/null
+++ b/dom/html/reftests/bug1228601-video-rotation-90.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+function done() {
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload="setTimeout(done, 3);">
+ <video src="video_rotation_90.mp4" onended="done()" autoplay="true">
+</body>
+</html>
diff --git a/dom/html/reftests/bug448564-1_ideal.html b/dom/html/reftests/bug448564-1_ideal.html
new file mode 100644
index 000000000..e93c1771f
--- /dev/null
+++ b/dom/html/reftests/bug448564-1_ideal.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+ <link rel="stylesheet" type="text/css"
+ href="bug448564_forms.css">
+ </link>
+</head>
+<body>
+ <i><b>
+ <form>a</form>
+ <form>b</form>
+ </b></i>
+</body>
+</html>
diff --git a/dom/html/reftests/bug448564-1_malformed.html b/dom/html/reftests/bug448564-1_malformed.html
new file mode 100644
index 000000000..404517c70
--- /dev/null
+++ b/dom/html/reftests/bug448564-1_malformed.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+ <link rel="stylesheet" type="text/css"
+ href="bug448564_forms.css">
+ </link>
+</head>
+<body>
+ <i><b>
+ <form>a</form> <!-- These forms should not end up nested! -->
+ <form>b</form>
+ <!-- Why does it matter whether we explicitly close this tag? -->
+ <!-- It matters because nsHTMLTokenizer::ScanDocStructure checks
+ whether there are any malformed tags before parsing begins,
+ and, if there are any, residual style tags (<i>, <b>, &c.)
+ must be pushed inside block elements (e.g., <form>). -->
+ <div><!-- </div> -->
+ </b></i>
+</body>
+</html>
diff --git a/dom/html/reftests/bug448564-1_well-formed.html b/dom/html/reftests/bug448564-1_well-formed.html
new file mode 100644
index 000000000..46dbb8bdd
--- /dev/null
+++ b/dom/html/reftests/bug448564-1_well-formed.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+ <link rel="stylesheet" type="text/css"
+ href="bug448564_forms.css">
+ </link>
+</head>
+<body>
+ <form><i><b>a</b></i></form>
+ <form><i><b>b</b></i></form>
+</body>
+</html>
diff --git a/dom/html/reftests/bug448564-4a.html b/dom/html/reftests/bug448564-4a.html
new file mode 100644
index 000000000..6fbaf85c2
--- /dev/null
+++ b/dom/html/reftests/bug448564-4a.html
@@ -0,0 +1,10 @@
+<html>
+<body>
+ <b><i>
+ <!-- Closing a form causes any open residual style tags to be closed
+ as well. This test ensures that these tags get reopened. -->
+ <form>form contents</form>
+ bold text
+ </i></b>
+</body>
+</html>
diff --git a/dom/html/reftests/bug448564-4b.html b/dom/html/reftests/bug448564-4b.html
new file mode 100644
index 000000000..f04d5fe48
--- /dev/null
+++ b/dom/html/reftests/bug448564-4b.html
@@ -0,0 +1,6 @@
+<html>
+<body>
+ <form><b><i>form contents</i></b></form>
+ <b><i>bold text</i></b>
+</body>
+</html>
diff --git a/dom/html/reftests/bug448564_forms.css b/dom/html/reftests/bug448564_forms.css
new file mode 100644
index 000000000..b98788862
--- /dev/null
+++ b/dom/html/reftests/bug448564_forms.css
@@ -0,0 +1,2 @@
+/* make nesting obvious */
+form { border: 1px solid black; }
diff --git a/dom/html/reftests/bug502168-1_malformed.html b/dom/html/reftests/bug502168-1_malformed.html
new file mode 100644
index 000000000..efe23ac47
--- /dev/null
+++ b/dom/html/reftests/bug502168-1_malformed.html
@@ -0,0 +1,10 @@
+<html><head>
+<title> Bug 502168 - Particular images are displayed multiple times in a formated way - only FF 3.5</title>
+</head><body>
+
+<table><tbody><tr><td >You should see this text only once</td>
+<embed type="*" style="display: none;"/>
+</td></tr></tbody></table>
+
+</body>
+</html>
diff --git a/dom/html/reftests/bug502168-1_well-formed.html b/dom/html/reftests/bug502168-1_well-formed.html
new file mode 100644
index 000000000..5eb25c6b3
--- /dev/null
+++ b/dom/html/reftests/bug502168-1_well-formed.html
@@ -0,0 +1,9 @@
+<html><head>
+<title> Bug 502168 - Particular images are displayed multiple times in a formated way - only FF 3.5</title>
+</head><body>
+
+<embed type="*" style="display: none;">
+<table><tbody><tr><td>You should see this text only once</td>
+</tr></tbody></table>
+
+</body></html>
diff --git a/dom/html/reftests/bug917595-1-ref.html b/dom/html/reftests/bug917595-1-ref.html
new file mode 100644
index 000000000..6bb9e2dc9
--- /dev/null
+++ b/dom/html/reftests/bug917595-1-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<style>
+ iframe {
+ width: 100%;
+ height: 100%;
+ border: 0px;
+ }
+</style>
+<script>
+ document.addEventListener('MozReftestInvalidate',
+ () => document.documentElement.removeAttribute('class'),
+ false);
+</script>
+<body>
+ <iframe src="bug917595-unrotated.jpg" scrolling="no" marginwidth="0" marginheight="0"></iframe>
+</body>
+</html>
diff --git a/dom/html/reftests/bug917595-exif-rotated.jpg b/dom/html/reftests/bug917595-exif-rotated.jpg
new file mode 100644
index 000000000..e7b0c22f3
--- /dev/null
+++ b/dom/html/reftests/bug917595-exif-rotated.jpg
Binary files differ
diff --git a/dom/html/reftests/bug917595-iframe-1.html b/dom/html/reftests/bug917595-iframe-1.html
new file mode 100644
index 000000000..032c4f4ff
--- /dev/null
+++ b/dom/html/reftests/bug917595-iframe-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<style>
+ iframe {
+ width: 100%;
+ height: 100%;
+ border: 0px;
+ }
+</style>
+<script>
+ document.addEventListener('MozReftestInvalidate',
+ () => document.documentElement.removeAttribute('class'),
+ false);
+</script>
+<body>
+ <iframe src="bug917595-exif-rotated.jpg" scrolling="no" marginwidth="0" marginheight="0"></iframe>
+</body>
+</html>
diff --git a/dom/html/reftests/bug917595-pixel-rotated.jpg b/dom/html/reftests/bug917595-pixel-rotated.jpg
new file mode 100644
index 000000000..ac39faada
--- /dev/null
+++ b/dom/html/reftests/bug917595-pixel-rotated.jpg
Binary files differ
diff --git a/dom/html/reftests/bug917595-unrotated.jpg b/dom/html/reftests/bug917595-unrotated.jpg
new file mode 100644
index 000000000..a787797c5
--- /dev/null
+++ b/dom/html/reftests/bug917595-unrotated.jpg
Binary files differ
diff --git a/dom/html/reftests/figure-ref.html b/dom/html/reftests/figure-ref.html
new file mode 100644
index 000000000..23ca9f603
--- /dev/null
+++ b/dom/html/reftests/figure-ref.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>The figure element</title>
+<link rel=author title=Ms2ger href=ms2ger@gmail.com>
+<link rel=help href=http://www.whatwg.org/html5/#the-figure-element>
+<style>
+body > div { margin: 1em 40px; }
+</style>
+<div>
+<div>Caption</div>
+Figure
+</div>
diff --git a/dom/html/reftests/figure.html b/dom/html/reftests/figure.html
new file mode 100644
index 000000000..ad83670b8
--- /dev/null
+++ b/dom/html/reftests/figure.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<title>The figure element</title>
+<link rel=author title=Ms2ger href=ms2ger@gmail.com>
+<link rel=help href=http://www.whatwg.org/html5/#the-figure-element>
+<figure>
+<figcaption>Caption</figcaption>
+Figure
+</figure>
diff --git a/dom/html/reftests/href-attr-change-restyles-ref.html b/dom/html/reftests/href-attr-change-restyles-ref.html
new file mode 100644
index 000000000..4ebaec924
--- /dev/null
+++ b/dom/html/reftests/href-attr-change-restyles-ref.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for bug 549797 - Removing href attribute doesn't remove link styling</title>
+ <style type="text/css">
+ :link, :visited {
+ color:blue;
+ }
+ link {
+ display:block;
+ }
+ #link2::before {
+ content:"Test link 1";
+ }
+ #link4::before {
+ content:"Test link 2";
+ }
+ #link6::before {
+ content:"Test link 3";
+ }
+ </style>
+</head>
+<body>
+<p>
+ <a>Test anchor 1</a>
+ <link id="link2"/>
+ <a href="http://example.com/1">Test anchor 2</a>
+ <link id="link4" href="http://example.com/1"/>
+ <a href="">Test anchor 3</a>
+ <link id="link6" href=""/>
+</p>
+</body>
+</html>
diff --git a/dom/html/reftests/href-attr-change-restyles.html b/dom/html/reftests/href-attr-change-restyles.html
new file mode 100644
index 000000000..1fa54bfd6
--- /dev/null
+++ b/dom/html/reftests/href-attr-change-restyles.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for bug 549797 - Removing href attribute doesn't remove link styling</title>
+ <style type="text/css">
+ :link, :visited {
+ color:blue;
+ }
+ link {
+ display:block;
+ }
+ #link2::before {
+ content:"Test link 1";
+ }
+ #link4::before {
+ content:"Test link 2";
+ }
+ #link6::before {
+ content:"Test link 3";
+ }
+ </style>
+</head>
+<body onload="run_test();">
+<script type="text/javascript">
+function run_test()
+{
+ // Remove the href attributes of the links so they should be restyled as
+ // non-links.
+ document.getElementById("link1").removeAttribute("href");
+ document.getElementById("link2").removeAttribute("href");
+
+ // Add the href attribute to the links so they should be restyled as links.
+ document.getElementById("link3").href = "http://example.com/1";
+ document.getElementById("link4").href = "http://example.com/1";
+ document.getElementById("link5").setAttribute("href", "");
+ document.getElementById("link6").setAttribute("href", "");
+}
+</script>
+<p>
+ <a id="link1" href="http://example.com/1">Test anchor 1</a>
+ <link id="link2" href="http://example.com/1"/>
+ <a id="link3">Test anchor 2</a>
+ <link id="link4"/>
+ <a id="link5">Test anchor 3</a>
+ <link id="link6"/>
+</p>
+</body>
+</html>
diff --git a/dom/html/reftests/image-load-shortcircuit-1.html b/dom/html/reftests/image-load-shortcircuit-1.html
new file mode 100644
index 000000000..28e16b746
--- /dev/null
+++ b/dom/html/reftests/image-load-shortcircuit-1.html
@@ -0,0 +1,8 @@
+<html>
+<div></div>
+<script>
+ var d = (new DOMParser()).parseFromString("<img src=pass.png>", "text/html");
+ var n = d.adoptNode(d.querySelector('img'));
+ document.querySelector('div').appendChild(n);
+</script>
+</html>
diff --git a/dom/html/reftests/image-load-shortcircuit-2.html b/dom/html/reftests/image-load-shortcircuit-2.html
new file mode 100644
index 000000000..3c4baa43b
--- /dev/null
+++ b/dom/html/reftests/image-load-shortcircuit-2.html
@@ -0,0 +1,10 @@
+<html>
+<body>
+<template id="template">
+<img src="pass.png" alt="Alt Text" />
+</template>
+<script>
+ document.body.appendChild(document.getElementById('template').content.children[0].cloneNode(1));
+</script>
+</body>
+</html>
diff --git a/dom/html/reftests/image-load-shortcircuit-ref.html b/dom/html/reftests/image-load-shortcircuit-ref.html
new file mode 100644
index 000000000..7dd28922d
--- /dev/null
+++ b/dom/html/reftests/image-load-shortcircuit-ref.html
@@ -0,0 +1 @@
+<div><img src=pass.png></div>
diff --git a/dom/html/reftests/lime100x100.svg b/dom/html/reftests/lime100x100.svg
new file mode 100644
index 000000000..8bdec62c1
--- /dev/null
+++ b/dom/html/reftests/lime100x100.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
+ width="100" height="100">
+ <rect width="100%" height="100%" fill="lime"/>
+</svg>
diff --git a/dom/html/reftests/pass.png b/dom/html/reftests/pass.png
new file mode 100644
index 000000000..3b30b1de7
--- /dev/null
+++ b/dom/html/reftests/pass.png
Binary files differ
diff --git a/dom/html/reftests/pre-1-ref.html b/dom/html/reftests/pre-1-ref.html
new file mode 100644
index 000000000..a79b4f46a
--- /dev/null
+++ b/dom/html/reftests/pre-1-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<div style="width: 15em">
+<pre>
+MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
+</pre>
+<pre>
+MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
+</pre>
+<pre>
+MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
+</pre>
+<pre wrap>
+MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
+</pre>
+<pre wrap>
+MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
+</pre>
+<pre wrap>
+MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
+</pre>
+</div>
+12
diff --git a/dom/html/reftests/pre-1.html b/dom/html/reftests/pre-1.html
new file mode 100644
index 000000000..1b21bcd74
--- /dev/null
+++ b/dom/html/reftests/pre-1.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<div style="width: 15em">
+<pre>
+MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
+</pre>
+<pre width=12>
+MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
+</pre>
+<pre cols=12>
+MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
+</pre>
+<pre wrap>
+MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
+</pre>
+<pre wrap width=12>
+MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
+</pre>
+<pre wrap cols=12>
+MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM MMMMM
+</pre>
+</div>
+<script>document.write(document.querySelectorAll('pre')[1].width);</script>
diff --git a/dom/html/reftests/red.png b/dom/html/reftests/red.png
new file mode 100644
index 000000000..aa9ce2526
--- /dev/null
+++ b/dom/html/reftests/red.png
Binary files differ
diff --git a/dom/html/reftests/reftest-stylo.list b/dom/html/reftests/reftest-stylo.list
new file mode 100644
index 000000000..dd6339edf
--- /dev/null
+++ b/dom/html/reftests/reftest-stylo.list
@@ -0,0 +1,66 @@
+# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
+# autofocus attribute (we can't test with mochitests)
+# many stylo intermittents in files below
+# include autofocus/reftest-stylo.list
+# include toblob-todataurl/reftest-stylo.list
+
+skip-if(B2G) == 41464-1a.html 41464-1a.html
+skip-if(B2G) == 41464-1b.html 41464-1b.html
+== 52019-1.html 52019-1.html
+== 82711-1.html 82711-1.html
+== 82711-2.html 82711-2.html
+== 82711-1-ref.html 82711-1-ref.html
+random == 468263-1a.html 468263-1a.html
+random == 468263-1b.html 468263-1b.html
+random == 468263-1c.html 468263-1c.html
+random == 468263-1d.html 468263-1d.html
+random == 468263-2.html 468263-2.html
+random == 468263-2.html 468263-2.html
+== 484200-1.html 484200-1.html
+== 485377.html 485377.html
+== 557840.html 557840.html
+== 560059-video-dimensions.html 560059-video-dimensions.html
+== 573322-quirks.html 573322-quirks.html
+== 573322-no-quirks.html 573322-no-quirks.html
+# == 596455-1a.html 596455-1a.html
+== 596455-1b.html 596455-1b.html
+== 596455-2a.html 596455-2a.html
+== 596455-2b.html 596455-2b.html
+== 610935.html 610935.html
+== 649134-1.html 649134-1.html
+skip-if(Android||B2G) == 649134-2.html 649134-2.html
+
+== bug448564-1_malformed.html bug448564-1_malformed.html
+== bug448564-1_malformed.html bug448564-1_malformed.html
+
+== bug448564-4a.html bug448564-4a.html
+== bug502168-1_malformed.html bug502168-1_malformed.html
+
+random == responsive-image-load-shortcircuit.html responsive-image-load-shortcircuit.html
+
+# Test that image documents taken into account CSS properties like
+# image-orientation when determining the size of the image.
+# (Fuzzy necessary due to pixel-wise comparison of different JPEGs.
+# The vast majority of the fuzziness comes from Linux and WinXP.)
+skip == bug917595-iframe-1.html bug917595-iframe-1.html
+skip == bug917595-exif-rotated.jpg bug917595-exif-rotated.jpg
+# bug 1060869
+# Bug 1150490 disabling on Mulet as on B2G
+
+# Test support for SVG-as-image in <picture> elements.
+== bug1106522-1.html bug1106522-1.html
+== bug1106522-2.html bug1106522-2.html
+
+== href-attr-change-restyles.html href-attr-change-restyles.html
+== figure.html figure.html
+== pre-1.html pre-1.html
+== table-border-1.html table-border-1.html
+== table-border-2.html table-border-2.html
+== table-border-2.html table-border-2.html
+
+# Test imageset is using permissions.default.image
+pref(permissions.default.image,1) HTTP == bug1196784-with-srcset.html bug1196784-with-srcset.html
+pref(permissions.default.image,2) HTTP == bug1196784-with-srcset.html bug1196784-with-srcset.html
+
+# Test video with rotation information can be rotated.
+== bug1228601-video-rotation-90.html bug1228601-video-rotation-90.html
diff --git a/dom/html/reftests/reftest.list b/dom/html/reftests/reftest.list
new file mode 100644
index 000000000..27a13e7c9
--- /dev/null
+++ b/dom/html/reftests/reftest.list
@@ -0,0 +1,64 @@
+# autofocus attribute (we can't test with mochitests)
+include autofocus/reftest.list
+include toblob-todataurl/reftest.list
+
+== 41464-1a.html 41464-1-ref.html
+== 41464-1b.html 41464-1-ref.html
+== 52019-1.html 52019-1-ref.html
+== 82711-1.html 82711-1-ref.html
+== 82711-2.html 82711-2-ref.html
+!= 82711-1-ref.html 82711-2-ref.html
+!= 468263-1a.html about:blank
+!= 468263-1b.html about:blank
+!= 468263-1c.html about:blank
+!= 468263-1d.html about:blank
+== 468263-2.html 468263-2-ref.html
+== 468263-2.html 468263-2-alternate-ref.html
+== 484200-1.html 484200-1-ref.html
+== 485377.html 485377-ref.html
+== 557840.html 557840-ref.html
+== 560059-video-dimensions.html 560059-video-dimensions-ref.html
+== 573322-quirks.html 573322-quirks-ref.html
+== 573322-no-quirks.html 573322-no-quirks-ref.html
+== 596455-1a.html 596455-ref-1.html
+== 596455-1b.html 596455-ref-1.html
+== 596455-2a.html 596455-ref-2.html
+== 596455-2b.html 596455-ref-2.html
+== 610935.html 610935-ref.html
+== 649134-1.html 649134-ref.html
+skip-if(Android) == 649134-2.html 649134-2-ref.html
+
+== bug448564-1_malformed.html bug448564-1_well-formed.html
+== bug448564-1_malformed.html bug448564-1_ideal.html
+
+== bug448564-4a.html bug448564-4b.html
+== bug502168-1_malformed.html bug502168-1_well-formed.html
+
+== responsive-image-load-shortcircuit.html responsive-image-load-shortcircuit-ref.html
+== image-load-shortcircuit-1.html image-load-shortcircuit-ref.html
+== image-load-shortcircuit-2.html image-load-shortcircuit-ref.html
+
+# Test that image documents taken into account CSS properties like
+# image-orientation when determining the size of the image.
+# (Fuzzy necessary due to pixel-wise comparison of different JPEGs.
+# The vast majority of the fuzziness comes from Linux and WinXP.)
+fuzzy(1,149) == bug917595-iframe-1.html bug917595-1-ref.html
+fuzzy(3,640) == bug917595-exif-rotated.jpg bug917595-pixel-rotated.jpg # bug 1060869
+
+# Test support for SVG-as-image in <picture> elements.
+== bug1106522-1.html bug1106522-ref.html
+== bug1106522-2.html bug1106522-ref.html
+
+== href-attr-change-restyles.html href-attr-change-restyles-ref.html
+== figure.html figure-ref.html
+== pre-1.html pre-1-ref.html
+== table-border-1.html table-border-1-ref.html
+== table-border-2.html table-border-2-ref.html
+!= table-border-2.html table-border-2-notref.html
+
+# Test imageset is using permissions.default.image
+pref(permissions.default.image,1) HTTP == bug1196784-with-srcset.html bug1196784-no-srcset.html
+pref(permissions.default.image,2) HTTP == bug1196784-with-srcset.html bug1196784-no-srcset.html
+
+# Test video with rotation information can be rotated.
+== bug1228601-video-rotation-90.html bug1228601-video-rotated-ref.html
diff --git a/dom/html/reftests/responsive-image-load-shortcircuit-ref.html b/dom/html/reftests/responsive-image-load-shortcircuit-ref.html
new file mode 100644
index 000000000..59d8925ba
--- /dev/null
+++ b/dom/html/reftests/responsive-image-load-shortcircuit-ref.html
@@ -0,0 +1 @@
+<iframe srcdoc="<img src=pass.png>" width="300px"></iframe>
diff --git a/dom/html/reftests/responsive-image-load-shortcircuit.html b/dom/html/reftests/responsive-image-load-shortcircuit.html
new file mode 100644
index 000000000..1cfb92cb2
--- /dev/null
+++ b/dom/html/reftests/responsive-image-load-shortcircuit.html
@@ -0,0 +1,15 @@
+<html class="reftest-wait">
+<iframe srcdoc="<img srcset=red.png>" width="150px"></iframe>
+<script>
+ var iframe = document.querySelector('iframe');
+ iframe.onload = function() {
+ var doc = iframe.contentDocument;
+ var img = doc.querySelector('img');
+ img.srcset = "pass.png";
+ iframe.width = "300px";
+ img.onload = function() {
+ document.documentElement.classList.remove('reftest-wait');
+ };
+ }
+</script>
+</html>
diff --git a/dom/html/reftests/table-border-1-ref.html b/dom/html/reftests/table-border-1-ref.html
new file mode 100644
index 000000000..ceac88e9a
--- /dev/null
+++ b/dom/html/reftests/table-border-1-ref.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Table borders</title>
+<style>
+table {
+ border-width: 1px;
+ border-style: outset;
+}
+td {
+ border-width: 1px;
+ border-style: inset;
+}
+</style>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
diff --git a/dom/html/reftests/table-border-1.html b/dom/html/reftests/table-border-1.html
new file mode 100644
index 000000000..12bfb2af4
--- /dev/null
+++ b/dom/html/reftests/table-border-1.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Table borders</title>
+<table border>
+<tr><td>Test
+</table>
+<table border="">
+<tr><td>Test
+</table>
+<table border=null>
+<tr><td>Test
+</table>
+<table border=undefined>
+<tr><td>Test
+</table>
+<table border=foo>
+<tr><td>Test
+</table>
+<table border=1>
+<tr><td>Test
+</table>
+<table border=1foo>
+<tr><td>Test
+</table>
+<table border=1%>
+<tr><td>Test
+</table>
+<table border=-1>
+<tr><td>Test
+</table>
+<table border=-1foo>
+<tr><td>Test
+</table>
+<table border=-1%>
+<tr><td>Test
+</table>
diff --git a/dom/html/reftests/table-border-2-notref.html b/dom/html/reftests/table-border-2-notref.html
new file mode 100644
index 000000000..7558e5271
--- /dev/null
+++ b/dom/html/reftests/table-border-2-notref.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Table borders</title>
+<style>
+table {
+ border-width: 1px;
+ border-style: outset;
+}
+td {
+ border-width: 1px;
+ border-style: inset;
+}
+</style>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
diff --git a/dom/html/reftests/table-border-2-ref.html b/dom/html/reftests/table-border-2-ref.html
new file mode 100644
index 000000000..36d1e4510
--- /dev/null
+++ b/dom/html/reftests/table-border-2-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Table borders</title>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
+<table>
+<tr><td>Test
+</table>
diff --git a/dom/html/reftests/table-border-2.html b/dom/html/reftests/table-border-2.html
new file mode 100644
index 000000000..4f209545c
--- /dev/null
+++ b/dom/html/reftests/table-border-2.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Table borders</title>
+<table border=0>
+<tr><td>Test
+</table>
+<table border=0foo>
+<tr><td>Test
+</table>
+<table border=0%>
+<tr><td>Test
+</table>
+<table border=+0>
+<tr><td>Test
+</table>
+<table border=+0foo>
+<tr><td>Test
+</table>
+<table border=+0%>
+<tr><td>Test
+</table>
+<table border=-0>
+<tr><td>Test
+</table>
+<table border=-0foo>
+<tr><td>Test
+</table>
+<table border=-0%>
+<tr><td>Test
+</table>
diff --git a/dom/html/reftests/toblob-todataurl/blob.js b/dom/html/reftests/toblob-todataurl/blob.js
new file mode 100644
index 000000000..4ed9fdb37
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/blob.js
@@ -0,0 +1,68 @@
+function init() {
+ function end() {
+ document.documentElement.className = '';
+ }
+
+ function next() {
+ compressAndDisplay(original, end);
+ }
+
+ var original = getImageFromDataUrl(sample);
+ setImgLoadListener(original, next);
+}
+
+function compressAndDisplay(image, next) {
+ var canvas = document.createElement('canvas');
+ canvas.width = image.naturalWidth;
+ canvas.height = image.naturalHeight;
+ var ctx = canvas.getContext('2d');
+ ctx.drawImage(image, 0, 0);
+
+ function gotBlob(blob) {
+ var img = getImageFromBlob(blob);
+ setImgLoadListener(img, next);
+ document.body.appendChild(img);
+ }
+
+ // I want to test passing 'undefined' as quality as well
+ if ('quality' in window) {
+ canvas.toBlob(gotBlob, 'image/jpeg', quality);
+ } else {
+ canvas.toBlob(gotBlob, 'image/jpeg');
+ }
+}
+
+function setImgLoadListener(img, func) {
+ if (img.complete) {
+ func.call(img, { target: img});
+ } else {
+ img.addEventListener('load', func);
+ }
+}
+
+function naturalDimensionsHandler(e) {
+ var img = e.target;
+ img.width = img.naturalWidth;
+ img.height = img.naturalHeight;
+}
+
+function getImageFromBlob(blob) {
+ var img = document.createElement('img');
+ img.src = window.URL.createObjectURL(blob);
+ setImgLoadListener(img, naturalDimensionsHandler);
+ setImgLoadListener(img, function(e) {
+ window.URL.revokeObjectURL(e.target.src);
+ });
+
+ return img;
+}
+
+function getImageFromDataUrl(url) {
+ var img = document.createElement('img');
+ img.src = url;
+ setImgLoadListener(img, naturalDimensionsHandler);
+
+ return img;
+}
+
+init();
diff --git a/dom/html/reftests/toblob-todataurl/dataurl.js b/dom/html/reftests/toblob-todataurl/dataurl.js
new file mode 100644
index 000000000..8ffba1fa8
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/dataurl.js
@@ -0,0 +1,56 @@
+function init() {
+ function end() {
+ document.documentElement.className = '';
+ }
+
+ function next() {
+ compressAndDisplay(original, end);
+ }
+
+ var original = getImageFromDataUrl(sample);
+ setImgLoadListener(original, next);
+}
+
+function compressAndDisplay(image, next) {
+ var canvas = document.createElement('canvas');
+ canvas.width = image.naturalWidth;
+ canvas.height = image.naturalHeight;
+ var ctx = canvas.getContext('2d');
+ ctx.drawImage(image, 0, 0);
+
+ var dataUrl;
+ // I want to test passing undefined as well
+ if ('quality' in window) {
+ dataUrl = canvas.toDataURL('image/jpeg', quality);
+ } else {
+ dataUrl = canvas.toDataURL('image/jpeg');
+ }
+
+ var img = getImageFromDataUrl(dataUrl);
+ setImgLoadListener(img, next);
+ document.body.appendChild(img);
+}
+
+function setImgLoadListener(img, func) {
+ if (img.complete) {
+ func.call(img, { target: img});
+ } else {
+ img.addEventListener('load', func);
+ }
+}
+
+function naturalDimensionsHandler(e) {
+ var img = e.target;
+ img.width = img.naturalWidth;
+ img.height = img.naturalHeight;
+}
+
+function getImageFromDataUrl(url) {
+ var img = document.createElement('img');
+ img.src = url;
+ setImgLoadListener(img, naturalDimensionsHandler);
+
+ return img;
+}
+
+init();
diff --git a/dom/html/reftests/toblob-todataurl/images/original.png b/dom/html/reftests/toblob-todataurl/images/original.png
new file mode 100644
index 000000000..c2da5b359
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/images/original.png
Binary files differ
diff --git a/dom/html/reftests/toblob-todataurl/images/q0.jpg b/dom/html/reftests/toblob-todataurl/images/q0.jpg
new file mode 100644
index 000000000..eb41ad3e9
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/images/q0.jpg
Binary files differ
diff --git a/dom/html/reftests/toblob-todataurl/images/q100.jpg b/dom/html/reftests/toblob-todataurl/images/q100.jpg
new file mode 100644
index 000000000..aaa79f2d3
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/images/q100.jpg
Binary files differ
diff --git a/dom/html/reftests/toblob-todataurl/images/q25.jpg b/dom/html/reftests/toblob-todataurl/images/q25.jpg
new file mode 100644
index 000000000..d8b1c9bfb
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/images/q25.jpg
Binary files differ
diff --git a/dom/html/reftests/toblob-todataurl/images/q50.jpg b/dom/html/reftests/toblob-todataurl/images/q50.jpg
new file mode 100644
index 000000000..f93356ef2
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/images/q50.jpg
Binary files differ
diff --git a/dom/html/reftests/toblob-todataurl/images/q75.jpg b/dom/html/reftests/toblob-todataurl/images/q75.jpg
new file mode 100644
index 000000000..6c25c55a1
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/images/q75.jpg
Binary files differ
diff --git a/dom/html/reftests/toblob-todataurl/images/q92.jpg b/dom/html/reftests/toblob-todataurl/images/q92.jpg
new file mode 100644
index 000000000..1de242a17
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/images/q92.jpg
Binary files differ
diff --git a/dom/html/reftests/toblob-todataurl/quality-0-ref.html b/dom/html/reftests/toblob-todataurl/quality-0-ref.html
new file mode 100644
index 000000000..3d6923fd3
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/quality-0-ref.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<html><body><img src='images/q0.jpg'/></table></body></html>
diff --git a/dom/html/reftests/toblob-todataurl/quality-100-ref.html b/dom/html/reftests/toblob-todataurl/quality-100-ref.html
new file mode 100644
index 000000000..8b157d0ab
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/quality-100-ref.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<html><body><img src='images/q100.jpg'/></table></body></html>
diff --git a/dom/html/reftests/toblob-todataurl/quality-25-ref.html b/dom/html/reftests/toblob-todataurl/quality-25-ref.html
new file mode 100644
index 000000000..385f2ab35
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/quality-25-ref.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<html><body><img src='images/q25.jpg'/></table></body></html>
diff --git a/dom/html/reftests/toblob-todataurl/quality-50-ref.html b/dom/html/reftests/toblob-todataurl/quality-50-ref.html
new file mode 100644
index 000000000..68b91f43f
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/quality-50-ref.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<html><body><img src='images/q50.jpg'/></table></body></html>
diff --git a/dom/html/reftests/toblob-todataurl/quality-75-ref.html b/dom/html/reftests/toblob-todataurl/quality-75-ref.html
new file mode 100644
index 000000000..7e610d231
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/quality-75-ref.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<html><body><img src='images/q75.jpg'/></table></body></html>
diff --git a/dom/html/reftests/toblob-todataurl/quality-92-ref.html b/dom/html/reftests/toblob-todataurl/quality-92-ref.html
new file mode 100644
index 000000000..15a930c94
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/quality-92-ref.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<html><body><img src='images/q92.jpg'/></table></body></html>
diff --git a/dom/html/reftests/toblob-todataurl/reftest-stylo.list b/dom/html/reftests/toblob-todataurl/reftest-stylo.list
new file mode 100644
index 000000000..c19af123f
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/reftest-stylo.list
@@ -0,0 +1,17 @@
+# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
+fuzzy-if(Android,105,482) == toblob-quality-0.html toblob-quality-0.html
+fuzzy-if(Android,38,2024) == toblob-quality-25.html toblob-quality-25.html
+fuzzy-if(Android,29,2336) == toblob-quality-50.html toblob-quality-50.html
+fuzzy-if(Android,23,3533) == toblob-quality-75.html toblob-quality-75.html
+fuzzy-if(Android,16,4199) == toblob-quality-92.html toblob-quality-92.html
+fuzzy-if(Android,8,2461) == toblob-quality-100.html toblob-quality-100.html
+fuzzy-if(Android,16,4199) == toblob-quality-undefined.html toblob-quality-undefined.html
+fuzzy-if(Android,16,4199) == toblob-quality-default.html toblob-quality-default.html
+fuzzy-if(Android,105,482) == todataurl-quality-0.html todataurl-quality-0.html
+fails fuzzy-if(Android,38,2024) == todataurl-quality-25.html todataurl-quality-25.html
+fuzzy-if(Android,29,2336) == todataurl-quality-50.html todataurl-quality-50.html
+fuzzy-if(Android,23,3533) == todataurl-quality-75.html todataurl-quality-75.html
+fails fuzzy-if(Android,16,4199) == todataurl-quality-92.html todataurl-quality-92.html
+fuzzy-if(Android,8,2461) == todataurl-quality-100.html todataurl-quality-100.html
+fuzzy-if(Android,16,4199) == todataurl-quality-undefined.html todataurl-quality-undefined.html
+fuzzy-if(Android,16,4199) == todataurl-quality-default.html todataurl-quality-default.html
diff --git a/dom/html/reftests/toblob-todataurl/reftest.list b/dom/html/reftests/toblob-todataurl/reftest.list
new file mode 100644
index 000000000..3994cca6b
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/reftest.list
@@ -0,0 +1,16 @@
+fuzzy-if(Android,105,482) == toblob-quality-0.html quality-0-ref.html
+fuzzy-if(Android,38,2024) == toblob-quality-25.html quality-25-ref.html
+fuzzy-if(Android,29,2336) == toblob-quality-50.html quality-50-ref.html
+fuzzy-if(Android,23,3533) == toblob-quality-75.html quality-75-ref.html
+fuzzy-if(Android,16,4199) == toblob-quality-92.html quality-92-ref.html
+fuzzy-if(Android,8,2461) == toblob-quality-100.html quality-100-ref.html
+fuzzy-if(Android,16,4199) == toblob-quality-undefined.html quality-92-ref.html
+fuzzy-if(Android,16,4199) == toblob-quality-default.html quality-92-ref.html
+fuzzy-if(Android,105,482) == todataurl-quality-0.html quality-0-ref.html
+fuzzy-if(Android,38,2024) == todataurl-quality-25.html quality-25-ref.html
+fuzzy-if(Android,29,2336) == todataurl-quality-50.html quality-50-ref.html
+fuzzy-if(Android,23,3533) == todataurl-quality-75.html quality-75-ref.html
+fuzzy-if(Android,16,4199) == todataurl-quality-92.html quality-92-ref.html
+fuzzy-if(Android,8,2461) == todataurl-quality-100.html quality-100-ref.html
+fuzzy-if(Android,16,4199) == todataurl-quality-undefined.html quality-92-ref.html
+fuzzy-if(Android,16,4199) == todataurl-quality-default.html quality-92-ref.html \ No newline at end of file
diff --git a/dom/html/reftests/toblob-todataurl/sample.js b/dom/html/reftests/toblob-todataurl/sample.js
new file mode 100644
index 000000000..8948312c9
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/sample.js
@@ -0,0 +1,2 @@
+var sample =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAADwCAYAAABxLb1rAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QcMEAgbxBgmbQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAACAASURBVHja5L151OVnWSW69zlfKklVSCVkBBOmhJiQECAJCVOYJ4OIQouzLSC0LY2ubvXq6nuvii677eVV1+peiAOiKDYgIkJkRkgIIQlVFTJPkIHMU1UmQpIavn3/OOf3vs/0O98XvH/keKsbU/V9Z/z93vd5n2fv/eyH+E+PF+wfAtCCf+NR/A71a+07XcGm6Qo2TqfYuLKCTdMpNk5WsHH+s00rs7/vO51i03QFGyYTTAhw/v8mAEiCECYkiPnv5o8ZfjaZ/2wy/zATzh8j9MeY589+r/m7DI8nJhrer7/PZP7+xOyzTQhQxISzL9o/L/rf2/P6689eu7/+xHwPDq9DmN/Z7w//WYH2u/4Y+xrm5+YagLPHTOavRfP3CYk9q8RDuyZ4eM8ED++c4OHdUzy8e/6z3bN/t7+3n02wczexe3WCXavE7j3ErlVi1x5i9+rsf7v29N/tXiV27pn/XbPfDc/pjwV275nM/j1/rQmB6VRYmQrTKTCdrmI6BVamwmQirEyB6WT2+8kKsDJZnT9OmE6AlekqJpP5PZoIJDAlwKlm62vC2e8hcKLZdZoApOb/xfz+C5wAk4lm13iC/vj570jNXnui2T2aqP9s/rqT+WflZPa5p8P3msw+72Sq9vfhO8z+LUxXZp9pOpl999l1mX//FXONwr9XpqvtfVbmz1mZCpwC23dtxo7dB2D7rs3YvnszduzejO3h3/dsuBT37rMN9+yzDffusxX37nshhN1Yhj+TFqgwHrDSY9YbGLXgtecPoMyvaB4g9bcVoOK5ACEB7ZGKH1Xz/z88Yv5+HD6meTQF2Q9Izn8s2BcfghFaMBy+gPnF/H37pZj9juEaSOkDh8uv8nsJ5npQw0cFOPsYcN9k/vf+f2bPnT/JfmwNbyTzeu3e9LWg9p2YPrPsc8xCcI+JX5rmPVgcosP3BGEXDMPjRPXvKbj7NntqXwkU+r0za4/z7zB7GttaFLVwi8T1lu7p8Hk5rJzZJ6HYvrvMhWe/RW0Nyq5huxY4e3HNF4bihWW47vSfTilxUXuavcN2eZcJTntPYVn+TNLdrHajwuIk8xfngqDJ/AZqW1w+aAwbqHi+pHz1bVRhCJ5EGWBY3b0hEqi/ht+s7NuZ/pO3JTN8BTIvLvUPSBsewnUkzXvRLXvY9csUPeympb8fIaTONr7mQSVeh357ZS5Ivh0Kv1Pf1O4x9FHWbUb129c/EoZzYbg2UrEg4/ebZ7U2sNvvHK+zaD+tuaocO9H7/yi5OMt4QtGHFZr76g/A4S3pDmS1C2GulebvI3OSmp/JvJHbZeY6tHWnuPr7daDKbKUfxrDvbZ7fKhITuZciAI5maCOZH8JqYhEsU1oQw14/aXosZcgMCMWNbTMOmZs2klH0j9lvH4edRRtg2I/+IfOjCVY+UWxbl7BBzcdOmSOcbfn57KWf/DSLfbYBNJY9Dl9qyFgULriK09olgHSpnRagFpQP/P4S5xNP8RXIllXS3Uu13T9kwTSBiT4Jb/81eXyxXMNhVpzFLVgpZ2s2GWeVwLQ0TvPPyBakxPn3cCHBBDuy2B5M+bH7fBrfmAorzn7uliKw2sD5cIh5udxJLFdZKGSJQEigh8NbyxgAuSADxEhJW9UDWF8QHTaX5EuuvlPlg5XLnOYnpC0HXAXKlLCSVfpOn/mYiCApLX7JVZIurvcQYAIWenkqKr2tDaEaLSlCAhleRAzZ1XBIqLgNNEdBW6wYL9dG76+5AJxFe3osw2TirAPn8LnZyzSyfnO17FDm3qplspyn5QqHpsn//JcYIgVVrB2g/CHzHelZrHp+xVDZqN48bIecP0rd5XPHq/9YdLm3bAHlAnrfX0qFEmvAxWeqlLlMcrhzLv0mWLY/kzUDXFoMC8oErYMECSee3dS0x/BwmrWMTDVoVJR4wx2Teb+2L0xp0VPR2Wayi49kKoFnJWNIneTLDLWgPDs2JX/q+8U2ZBByZYs9vBmzalvuoJ0DKe2lLcAVIAVF/Mfnc64urT4HadJY+ghOX84rRFhRvRwzUIVC8LS5TT9EzL01a0Ct5FTP2IfMOpzsqmoGoYBSNL+1xbqb486UT/1t2U6TtkuhRpCHO2wAw4Ls1sYxOTyxwJjnWSdzoheyUZbvmOENu6qUsFktYfmL9YdspTJ2cSoYQTyMg/3ivKyU2b9MS3Y44X3167OyBnGLvjiwC1k0IHdAahhL+PkNd6Xy/HPMg5bb9w6MVy5RHCFRvKctnTRS4rEobRl3lTx2yXHkziRr4YswERssIBC2Q0CJZFJRdtFlTwjXw38VKWdFPWzLlZZp2Smf6ywgAYbr4lCG+ecUe9aJmEm791Vet+aeargXVF6zKeFkmYMw7Un5XJQDudIzdIb7zRDc8qGgUJ+wvZZQYLisYJZ/UwFwcaxbX0AceR4j7tA3iN9w/aa1JG4Ut1bfmFUErnBKKCyYGCroYhCFkfN6joHY09khgaMfoIdIVhxdxXQz4RSymKbJAyml7ysyo0D2sCDrZJ6m+h2yjfmp5DLshlHKlWYDkRCvaScAaM4GFfwHR89kKlIX8hlUzNgCJOPvxXyTS55dLZQRrCoSBRwUaNeJBsuWuwNqJErHgAciJ5SpNtOWD8CiTRxMkNb4Hs5YfGCnhhXd1sgAYc1FW1qu7C8EwHjEcLQaXh9TwnU8Rwbjm20KGmypn0OzrK2DvErHvWjxpcj20mUaJDKz6/CbUGIpBy0RBvj2i9hLJtTSisQdFaqEWenNhJlxyERpJUMR28qgdmQnHaVh0mcHLzoMT8V1s5tdIfNhDZ2411AqlVweInMYkTU0TWbCwjKk7h7TrqTqyxQno8FvQ0FDh6/4ENvXCgu5lZAuIS1hQg+5GFKErdzMFJAKWQrFOQRhscA5iTP/bHJ7QI7Nd5+WIUOkOSLoSUsGhHA5AqBLqxYBv9XJsaAkrogHhlTCaZXmeE4gQxoQy754GUhpyqAToQ5qGRt9ySn6zUvRYGhZakEUq3e+Welw4yDTYeKi++eMxIrCd05McdatQxEPDHWobH6olD/2pymGAIf+9EsyJz1EE6CUyMGGBKTsmwXGyC4fMcROfWioPERItQyrMbwmmDYEt3+whAVarZ5kg7IR99CXAgm1HBawKVHbP6lU+guKkr1+D0zkHSoLaSyhsEoJmSydLqBTkcBTBTrl66IIxYQdQY95L08AFMblK/+aYK6Rw9VIYKw+YiifegDpILZKIFcjyWbEJvoddEWEY5+JSl1rQ4B80uQij8rzopcFLD6nnBYvbkabJc+zY0buGP3ASBmkeqbUJDPMsLc5TBSSSIt5yZEVgx5NJv+bl62B9IiJlpzQlsjyuKi+zhksmbdrl390fIQOL/Z3NSJglvjy8iqadWMPCBUKySK9j4BF4lRUHJD2rLdElswXYta/WMBkfiLLys1kqimXoY8w9W0v5WqO9iAYHqHeffRvBwNcK5xnoq2W1DCmLDRloVxCUKru5U/DknlG0FvJnHzqolzXrWC01R0D87VVDLYM+kGaBeBil+kuaRmVBcHpQ2w8iXuQmuF1FqGXlPI4cYzNoxNSYwRDpGGSWbDDCmyCSqKM/uAJZBOVs8+IwfryXmZD96xM0uzvVTbEnvF5CFmOcs4kjXqAN3fOqhF6UT1ngd1FGQg2BSyXRjtKdy04JwHljudOZri8QfRrUZkwoZWD0neN5CqvwlGTGrTfD5cjDK+tRNgtVwb4rwnWXJA5an2l86jWmpkizPtXHUOBzSotrtQBek8pDFiey+86syjD3EUaQ3Dlb0pYiu9KevzIw60dN4wsMS0xYREDExAppkOAiHrVcFMcTEVfwpsPIlOHNj1lIh2UunCsxE8252TNjDF0IpD0oL6r5dV6qxE6dWLAY8jK0j5HF5S36xbIH1q8lLYjCX29mPKSpt97uB6tHJ+TKl4InxleBq6WKdmQZ0ASxyeziWZ7gGIInIYsJFzALw+2yPjGqoOsGezHdABcb/dH9Quts1Qmi/LVhzYFKp0unQpYmmW4rL5CcH3DLLjUyPjRYGws+VQfMn0ywQIj8YLsmIcF8tM8J7fP2U0vedG4Am7qvimLM6h4zAI5YygdvWTF8TSUL6ppsUSmslDyxx9HyDLJ0wYUi+/DdLVc0FM+dOK9GukZNDs96OkUssbYsz5kp+g910plUaScqtbMKFwiEJNji+pqrMBnI7MUgmgLlGYdS0gdTr31LvSxOM6Ja8SPZcgA656nOq3hOkplBppqpDc3EwemZKMvWxCAYatNauJPg+XIbViWujJfUpuTlVWwLhqx6Ht1RZUi1KYDC6ylzeBiNtvxNYWFWesZrAbMZRR2gyRCKmQAytIfxFsRA7sDiIrvI4QG/5GDtK0FpXK6PZ8MB4dG6Vo1rFLZcMNlzb3P1hFCBIjCwGE4bDn0B8d4L9/SN5T2ET6wMhVbsrPoXgknrJokxTPyg3ax47vhoquX1J08k5OlptKYEcII4m3bh65lzQC1ILBxwWHpsJexoBhbcfqFkgWYjbzB91eabM+cQPZkj+mDaMBfdZyMpv1KRmaghqOZr0Kj82U+6XrWYWQu83JDId1tj5FPvRSYaTpsMNgSJEMKjp5RVenXsTya8sy2xDE39VD+WgRIIBHPkcGvUJKi82KIcBYDplWt2N1J+cOnHYAqYeJGLIspUPX1ZO4PWZInDDh3J1pMpsYgjTLBWhopa1MeQt/54fIA20LJorNDTfepXpfPg20XSjfYhXYNhnBvqxwGRDxwKC2ocpncYGLqOpYBilEmVUfKRb3BRI2ZJTDcat4KBxDzQmTxYQblvljLQiL2xCy47TKCfvR5+QCdy0cSEjA1XnrB99AmNsdwFKSRoQDMSjsFongMdvDwnC8YycI+q2+ihg2Jzqgp9hHQSoIQelVVvbrP+F2WIvWspkwSfV+N4vMzTNyocjndGgDH79pN7mUsWJAjtGxRhXjY3Rub1ZqWPac1DtmIdXcZ0X+y2HtKZGRvEaWVmsHf1ybAFn2lozrXiR1NXJwhPVZLYI1kDxyjica/oBZkgLlpIWnS4mtQxQ0O5a+HPzqo24oe+tILHPFeiFKAgQShgkVXEN2pCnrxzehhTGkx2161uyWsK3yvhDvmbo5UmBq2t9RoMJTMVvQsjWJ4skGEOXD7e8dkAJBvR5dHWR6U8o1sLmu3pZ69iuG2MWY7ipBGXtC+7FuDEIzPY/hEtnR1WDm9MYiQyksvxl+g7Zact2RfrkOwo7t+Q7+285x0pEvE4g1p+G+mF3hhGqtHaZLKbOM3agRq82mvsbG6waEFxwttbb9j4A+V3UrkRAkelBd7s3/LSFS1rnVWspUmwUTV25PW2TbN+zMAs865TgGBjIJeKkkmWpLBIs8zLjgx0GFoARs0iAYjI4myXdB0FvisJ2KNoYdGHnaIhhUVRNHxYNaBiUh9yaRnv5u/ifl8dKJuucjOsgRiFvsX2RKNr1QXKSs/icZdp9SUWca5PliAQNDY8n8gZ9iJHF+qsEiFPPZcfjqajHspMUCy5rkxnp0szABjVHSdDTKdAsPiTBajbrF50SpMhufbzPrJb8x7yPxBpZAJmA1tsk+7SCJTzJTamK6N2EsbA3/0I3TBWohWz3IliVAmr6IzabGPpdJD0x52oclYVil0u7igbjtqjJtOlztXUoooMfd7uIfPonxu+souexpwR9G6KQaRzIjVng3jUnDGs3IgKhtJlOQc64IqyBNE3yFuA5cqWb1K3bLfKa3cpwtyUiEDU1+ncnihT3J6+Zw4EZPtI2PSS8UCe/OwBQK9NV5RIxu8SCC91EKhhShrObwHGr3Lhi0tybQwUoBOEg8uZv2Mz15y0h36QZkPCVtitqCggIOZvky/wjsTKaLUtBF28ZrM1coeRBdqGJ2zXS+qQUDtgi70ak2jKNMnoWhZX7CvaYl5TY6IZk9G21ObnGu6NCjnzApcuYK43bujjFutzjV09NcieSnSVB6qrVpFi6BytGxyJhU2aXBjG4LvI3OrYLu/YkJw+83UaNUWDeGEXPp63eZ6AsRjNQMcw+sWGiQsiPVEnstgzyxZVxXWp2YMVIpGSqEnlGO9kjalyFqOeLYp86Zu1/qlq6JXWE7X2HGnILWwnJ+McQJVOHL4rM1Ku61Am7GcDIxpSApD94jdMPRGCNn1vfftwjvkIOBwM7t6VioZH05p8FdbSqsH8uY8Yja4AkYsVA6AEd1Ryro6hOFNEOmYbZSefK7v18EYSssw07shOXDGGQqlvrnzjLpoFtKrMKLATCmond1ZUk7+S5j3MZpcNvZ7skQBsIpjo10dlZIfiwkRInuT01j8BEcRD+mpYX22qE29jLFzKnqqO989Jl83MpsHiEFyUwj7ZqxibbPFEWSByZ0UrT2Phqnr4t15BhI2sljoFZNfm8cqrc9dDWUwtXD1RDx4GbqeeGfp7HkidksslWYb3dNRds2o9y87NJOGezZldkWAZ0rULxHJEmt94ysYdqjrRjJiJI0d3XUDeBH4KytGMqPNtENSxO6qboK1V02wBXtvdZUhicbpscLw1C2wrFNTYKzUlBFasgCokZuzLjeYkZsd2KH83Hwy51KV41b9I10oWsDC1cR2yPWopDt1eJ5QDk+q7Y/i0e/FbLbdzxm2wqXH83LQyzWsbESWIFlgXDs4uCh20DQZQ8dGC19mVG5YMtCF7LyS5pfAyLUm2/oBX2qfz5IljEIZBXzQl6UdpugstMrrQcOxyb9nyKaKxriepTlSh+1AsGauPVt1AIO7wgpMtUKrJiLEwqLzxSGKAUF1fKK1hSkYaeaBYH7WSG4s9fj30pshaHGVW+4EjuCHKl49XHhUAme4XluGBSCyLrlt4FBBqjiJhr3f/lQTsZjpTiMhffOcSj2lFXt3tw66RSPXe6oRHVZ29QytFsoGoD3YqJ3SlomMhqdOz1yQ9CkDDIGoY3GqYFeX1dsslqoPSQXwgQvECDNijNUoj7B5PcvroF0h8Og9e9YCjetA6MkaQZDOqYVh9glH/AabkS0ZYFQmwfSgoYxaVZs0MPJrUJ01a2SQqYFzWNAiXAsae8wGQK2RaY0SHYVgVIuDJsVicXt8EMVbuHaucvqZupmIC5jsJUMsCaKlj03azMwJ+7NuEMBoHI56olAXEjJ8MedjWCSsNBq9mjH0OBQrtp5KbjVyeGcIdIG8KWwby/vmYCHWi8fBRuH+UczMostiZEB4zrsgxg/r5hMQCa9gltEigrWTLydnaDTc+uusRPEgmIUq4rOIZm1jwXVYR0JSgHFk3rI5oIc2zS40YNJuj1V4zER+GI9ELFsz8GTN8pYLSIm6sqj/obAD6E9XFjhNnxwWs0dALGJ0w8PoQXmpKKUz12u1YjQ+hTQSAQ6taolpplM51N2CMt2aGCF+aAKAHw/mrZC4+NZlJ32o6sONga6lB0ojJLPCiW78Ix1ASz8D2G68MBAOqueGDKlWJBgViBnv4I10fSL2SCGGIoenKcA6ZVtYOn+KCWusTZG07u1lqS4/YbA4AfuxHh3ImTPLhu8qlMnyWCFZjmB2ZF+HX8oj+jEeANcK2lrA7FZ3brEvlJuqZRm3OGjaT18rSmvl7KO7MxduJOG45BjPZZ5L52un7kko23nQ3TbI+FZpQLGRv8j77jEzKQoezmZKaGLHGUTGjdJQRvFUTQKzs5eFbEaBwq6rkk0NzOm8PYzsjYIM2jLZaGI6a5JlWYnKxHnNvb6LshuicsYx/pDFYS75TFUazxEGSEeyUiqNuqRQZnjUgJGWVVQ47Kx5QVGxmFDpXdODiaofTBXQPMLjwfHUiXNh1Q+4ZZPAzAKgsEDAXJVTqNOc9djpm/vR+1kNmhAGYFfT03waruS+W87tdRhTWN1wpFmxuenzfxPYrJ2QA8+Ve5NzzPJSC6Xjms3r0E6z6EFQ8K1pMEaeQjmEKfmEMY9KSy4tXDjusKF4kRSiRQDp5yzHJSRLIQzuKvQ6Ndu9YSzr+6RAbw6hwTzVzZZBHlLu2ryC76E5VBEwTy+gD+M7NSIgcfNCwmAn1ycf+upc/Kun4flApwKcgB9QH3B3RokUc3dVdwynKbhsps7crroUJTCxeNbHmHXRQgusRWyxo12DAUDOzGQJARusTPZTzQNGsBjvNH4x6n5gLovRMG7kQiE18FWXFpAgoZs0aGXkNrpS/wsTkWzHhNIHq0qlGxlMt9UsQu5Z6tEJp+w4agzurkc1srQuv2MY1SgzItV8p0Qde5NZhm6NlpGrYuQtwWDeUSGjsykRipkzcZRCMb9EkYlF0OANpNRgpKqaSHDYogxR5MhH5b40ulQtLHkhCqoZU3RbvcgfpEruTksHARoZDNYZsNZ6XLXv0mPo7cwb+KwUG0vsgYWkofD65mjN0hF/hsBZzuVQZCKJYqK3wcKK8YhCmvgV5TzpvWX7lb00QUTpICOTgSotSJV8F71DZidslEvDaDjlJogZp+FYrmaiuHayVsKZOsAfm+JUduYatbUQKIBw2rONNjFZa7XwRsTUircwjDaXNWwwxJ27FkQ5fMEs+oqm88MWWJBKVvHMskhzYnOG0pve/4cZTPRmCAQKJ90lI0HGohoXMMWjGSQWOJx0H7KeeUX9fiH/EBOC5zIx9ckLVayBAXblJofBMXk2KCc3ZDcvw1MaXZoXJrpFMNzMmbWcQYWIxySmn8J+I7PpF8OidF5PZiMVGanYe0gNNWsyZ3gcKZqSmtqRMdsL5gkYYYr9ZGglVjzkHOGA9EFcUaRuDl1Z92PmzmMNchx5j7tWApf7gMGkIVYzLGG0eMNZVV/MrZapr1tFm2DCuOSaD0Q/Q1uMJJKqYtozvgMZ1oZWLSsLzBzli+tXy2XGrMDGSmayG0Q2I8wiSworXcjm551dtMOawwZgkQExF91yQGV38fW2iL4Xqm15jZ8dvq+yg9TRkJRV+ayqVVDe0det29B+Q3+Ouz5fVcCDHEMrWogAwWYqlqdKRq5wji9RL8YiXjtHwcLFRn6aIHK2r6KRnBYEy40rbgvQeOiJGnFIqftiK9zIZuOk938cgRvNPZn7DYbh8H42TCZ5xmY1M3rj2wogtMCS1ZQ4Y4ARu6hQ45+PbRYYMbfPFyJJDLiOGQCj/oi0QiSU9SKyAFYO8M84kMWD+kmqXDUHaYfXxFkdPdNcDxfOyBGhcs8N+8ziRZmz6R22wcSx4lFeSD9LObHjxqxewajAGKGWJBK8G5mfoWLKcfrDLJfJoeE/mxd66cZw3zigcl3wnKoILcBlDJ6WznYVmabytasafaLriYUqktV9sT9ksveEmQ73eYBKZPUQdDheNnmothCLzE3p/9Yytjg5L/bBy0yfs8THJBAty8IChwu3sDc4DUgpr3H+2YiNfg1whzYe+Wwhl3AoW+dkx0EmxxUgmhEx6A1ju4/vJvIGnDS9skykRTHdbFikzOXdUKKnCq5Tb04XaB2U6RQP8p+VocWhbfqggJaLBqikLnGKmXeyRaPUZZjGcbBZ3iNFiCIMtw6SdFi+fQzMAKjL/U12G3HIHqDky2qZAGYF2OaSynUHqbDeX6BNHLqbQktieXqymF+tMNeYKhAV+iqkUkDbCXGKbuV5fh3zibJkGGACoOSBqUdtf7U2kRKtiuJKV9CvW9t7D5TTZ5Ty7GgnwZj2sRXn2qZ8OwLSO+mH01PjM+QtwePk1k6PFXGkPBHMO5AULGgoPHtfsFLGaufdsszIfRZk2UAlu+6cSnmvQoZOgfHJa2wSioJZHEpRVtZVvae5bBdnUdio6uND0bkjYI1pdQyle5xU6YdPW9NVX35bwopw0zdRtRf2rEupjdN3zHDkkImXgRibA9PukCIabgTSWRG+ZCywCoCuam9bJG/hGiaqKjJEqlSXsoK7KxFsLBdJ1yAe/ycVH1PVaTw09rP36ZpTNTX20w4XwqhZpT3pXb+sCtA8Rt9it8bEoJm/yhtzotjjUeTL9Blp3JdlmF0ra2ERygJwrjjPN/PRqjlcN5nNlaixl5tdBlQFQUXstlhHFYkBpfmjroxWH+8RrOO99X11yKkooBjKhmx+KyTTCvpGPSo+J2DUvqUoidPt4CuHWQ5ONCHhyL6YY2zesrHA62F0XdKoep+OiKudEN9McKuyFpFFC1dkg2lss+zAF3pZhRCm25eMA6zw2gXZ0D7XpDscawFn9FYKiv/c54pme2TeizEK2CxrRKQnn0rKQQRMyZ8Ye42J6C7CgME6PTkVyA5PHsFDS2FkJI2jc5G5Uannu/bcoSex4j0xSncnVk6BOWedJTEnuEmGZXLpDn9D/TDYjaqrB3I/d84+WI0CpDw2HI4ajCGwlU12dMClX/PWtJXee33JMsDv5c9Y83QcDKM1hg4P7CYzg2aZvdxOpGANFU90hhmz5kRzTi6mZcgyWHL+qoVJQEwO5OYCR9dUjg1Ddklexyvt5lNS0WJ0il2bgxszidh1kPvp5q7OISCYHurOCHuop+B/vDBWOR73hJqtnCNG+khZ8Mv0GCX9FfQJu1sT8tckloRuLKQ3h/WhRo61j2YdjLb64XCPGvz4XwW21xFRo6lj4NwL9YUKq7PRQd0WSy2dr4V6ajaWrBd4PX/4KAJjouOKOtb0mTqm16bmUllE+gxQhc2UJybgSTqnlWOpW5GZthXiqZPh+BJkTC/liZqefqYT02U2hihptu/Zhsl1ldjxncHJuXeeKLG9ibQxeg66utOmfQuY6bgdCW8a63zv4TtXikNDg/knh9LcGCmUnTddVO/cDA1M4e3G7CEqJ+zWKH5jK5ZKklyTrG4ovAo9oBnkHjNmOaMKCw8lpNckgv1gZms3rWzoUn7pkKXAmCUopmsvuIQl8Hpc7hfhfusJmqOEsZ+RYUeCdAyZrSRUeSOqGOvNJP3MZwUb+GCOO9KK1MphZWxNBfJO1GYNlkSPrh4eUWCeAAkvVgAAIABJREFULqZoPxSAco0k5BFnZJxah2LWl2IqFUpn5tZhKpSRVVZAP9/CDhQfWvSMbUwrFRUnC9ILl1i02AUe2jHrI7CFTJ9uqiYcLMBGxA36b9rMnCqKpiyRyJWsF9VyhLSKSzFaafU1JEj1K6fPQgZnmxEvTxN4Rb8SCZR938uTAWq92Z/WkR4SNXyiVA419+Aw39eFsHoCTB44Zt2S0+yPkQJAi3kcpvnCnr2r2ML8PU2nBTHaKUPmCXh9hETO4siivkT2kiCjIWBYuAb7oiF4usGAyeZat4tCnKym3fce47HrHhwPiqpBwbrdTkKDKaN9WyMRgr5i73MkQYRkk+ZsqFQpidxMERUMHov16rxsaeVdSkiExx2rAVNehuQCHWPNEKfx9WzbkU6hnZBpbHQm6Wp0dhkCIB9FnFtkixUn77CGGVTNiLBGpSooZBaGdrKP90ULQ6dlLy29j10e1iOfRapnBjJatTx3dUyfGw1gu5QgZY+hEcfqIhg+Y+4gsL2kvS1QKrpI7AdWxraaGDdsYhfCCvZVrhBi2egWTVfrdnPfKuHa3NjNbTvZwjbQG8YtxzVvRTgkdTYb/RszOZWxX4/RqiKiyBosDS4rfdxGoblxD6xyDBmZU+RNVF9TjKmB6Bcz/Q1jsMiKqkBbZi9PAFykUa0y89EsceSFCssthhF93kOzp+TRXtCOQbQCXjpjXj/JXiVe1zEcDrZcRfSKkiqqmLMQFxFZ2IMVKZ8GFtmz0i4HYdhg4gLrMYadObRLjaW3dJsobjym1+w4xdgURDoIQ2laHJNDSu1LhzZwapAlIU/Toy913ZjRyAIrxHOarLFSM8h78Sno4ZwjeTlEzGBkUln2JkY8dNw5V+7sDOmJEvth5EN78RGQWj/LIwxhWhUKO++wtjRZMhKkaiviOoJgWTZrbfyQoedzwJJEo+ZnoD9qW6WIiWTfKJUKnZidWb1foXEPuKL/HCqdNrwu0BI82W6PwQyCgW1jWO9xYHWQFsVOzGoWLqpMlckm1dEBpKcGVCwL0s3GZVVEGIeU1o2Rgm7I6lUxlaEHNdnjx0knxWhRWhQxjDKojAds2W3JCIZZpWNjZcWcf6XuOlW8XJaTk5l4shZBynN04gw9jUD1zlyXecRDVFINE/qWyQdwBAPU4gyP64D+1nKXdnNb5QZoFyvO9TSqWSgZhq8I4LKntmEdxppWKB/g0nEexhHa2a4sO9irUg+hwyP7AdqFTBV7PeIuUY5USAJzH2+uhmRxxASUj/icOfPQMNeSWrgWFFoOhTBWEzk18phWUAgM2Ry9zr5i43zHTvFTLZhRXUiuSu9HVyKHoKwsrYIhD1h0ZlhCUNa0iKHrmoXjtplFI/k2wDGUywV3sHtpjuQ72aF6KYXQHA9oa/b0Lsj40uspMHXKVlFuE+SNp1A2tw3CmLtFBIqjG1Iq+kZF5N7hwHQWIHMU9CpimM6E0jCgo+A83DSuDtiHfEIKzOeYCQNHSzIV8048FMACXKeRy6mIdCzRPcAOflIyrHDdLlSbL2xbCmla9yQG+MrABizMKSkP28tKSegy1oCcBszWTyHM5xpzBlgltXRRuXWZ0Mp05IkXJ9VTBW94Kp5SwjZ75dyvqZehuU7tXPM0cHWpM0AtJjsKC77vKdin7gofnuhErgYncxs22DqJSdricDZHoPSbrdSsbssNOkso2gHULAgTBjA4YC3Dam6YnJSoX3YKGJWmpc0TkcUK4Q0FK3urEWPUNN5NfkwmjXi4g7Ryxg/VFD+LW2UTBBXEiHeuaR6KZkqbNRtwmKV1KLGHA9doTw0D0aM42mk/6UeQWjsuKrTkwXoZDkFZBXPqzPTr/IHBOiu21rGTO35f2OWuNvfYExRE7nf2xrtEnolHFfqzIOhf7rnAWsffx+qZdX5vMreUDVlT6pNk5nFdALJS/IB1kQr8i/xUMifjiN4mxliARYN6wl7sXIowktP8gGaFskqpLdgfAowMBsgkppZj6vwcDLTPd8UNO3Dg6/4MW6+6A4DwpQtvwqYz3ovbd3w3BaU+LFxuFKjNdl74jt/DK9/5O2kb03asWAKI9GctffeNM21VL8kYhy4lLadn6J2KQMVUOwKf+JO34x//1zu8DXw1E9i1nI/0WZtpan/wa7+M3/+VX+6fU0gsrR2UXgMoWajdWXo5k4/ocmS1eFIf5iFVfI/RFBZ4NmHZ/ziICt4hZvlmIo10gqwZyHL6/+iyQgU7czlgOqbodjyhvYFMFz2Whda9WE7Zrmi3pQzoc2QgOc0AIpY1nYBqQRHZ4kvBr5BwLVDthJcPILk/wQ9b98xuz1a2Xn0HNqxMcOJRBwMAzr/idhx56H44/PEbO6Gi4HDbNhFc9pLyuJGhZr4PcYSN4sgwrCGbtrNwHc6L2pZn/lLnfeQt+OqH3oJq3FAifEtm1rpcq09dC59bQMLKLAbICApzgRWfvJGtEztbd6IAFEuZOmxO4XHwVeW+g9pNKBkhuEudJ9WRy5oBjtiyj9LBWgMHHHmtXvAwya2GUkRK6qLs4pKwliBitaLhwVShsZC+RchJb1Ljak6Bk2UeqhI4D+elwzJHQGvUPngOi1MneNIMjkiOqBsnbL3qDjz76EOwYa8pAOKCK2/HqccePjrmeRB0uzGJVOIs0pDzEucqJDxWUuPIE6vF7NkMNVJ+MbBMpUkt8xS7BJcx4bCVmpHhEOuMa3bJSQ5ilpgwF8y7+ISRoGF6qHs924yskWY0hf50K2divKR5iiGDWzhHybJlHIxeGTKvNd1tPfluBGHS4Gqlf7oOkDB4hsrizDhaQmB1931GZrgNhSk8Up8mVs0WZhX0pJHASA8k02N2zhw+db4p60eGYOA84AZPvD7JjSaL9W2Fwtar78RzjzsMFLC6Ovv3qcceauadxAzCkhxmBKI0agJUboKg8axbJSu/PnXml+HaKfI5ZkRmYerLERTST4NTptGaU40XKbcR0S5zDgclhWgVlaZOVtpOJ+gOGDb9RCy54OYR0Z4VxqlwtZzJwhu+tY6luw8RW+KWqxd4pQxYUVqhtejh9UTCOt9vHRtR5zaAz/YAZTgB68PNjV4d0ncKfsi37ZVkQs18sS4AnKT36thgZ3XZWGlmptOURQShyYjIZO4IkzIN1LNMEgliHvTIrj148pve777TVTfeg/d8/JL271//86/h1//8a7PHf+adba899Mhu/P5HtuKjX/kWbrzrO1iZTPDsow/Hz772FLzwxGPcp35o1078yUc+g/MuuQKTyQQvOOlUvPqlZ7iT5rpvX40Ltp2Dm2+9Dnv27MHBBx+Bk07+QRz6xGNdRXHmh94BAHjZG9+Lb170Udx58xbs2vkdnPy6v27f+f7bzsW9t3wZOx/4NrS6E3vtcxAed/gpOOzpb8B0n30BAVv+4d+3z3f2B9/S/v4DP/8+99l373oEF539Idz0zQsxmUxx9IkvxEkv/RGjWZ1dkG9dfAGuvuhcbL/9ZuzevQv77X8gjj7+WTj1Za/Bvhv3NRP88vrfs3s3zj/rLFy27UJsv/MuAMDBhx2C55x2Ek5/1Uuw12SCBx/4Dn7nV34Hq3tW8Xvv+b9wwIGPcw4+jzy8E7/0lt/DXnut4E//7jcw3Xu2Js/6wkX4/Ke24rpv3Yadj+zGIYftj5e8/Bn49z9/OjZv3htWUNgPd2to6DFuue6fwUtTrbXUdh6JRWa/RL3AK6Nkx782A8zzmcc8zjseZ5Sbkkq0YchEGjZDmZJyYuOaZ7DouzoUhhm5U1kdHKAhbazomIuAU7Fwbcm2XvZ9KaOpA6NpSg+qYWSBJ827mWm3pHp0i7G1Y1H4xf91Fv7mi1e5359z6Y0459IbccXf/LpDUP74b/8J5158Rfv3F849G/vtdwCed9KL2jr40Mf+zL3WHXdcj89+9j143Q/9GjYf+JSEKV9+wftw5y3bkoLglkvei/tvP8+91q6H7sSO6z+NB++6GEe/+Dcxne6ziIVz77Pl83+Fm791Yfv3FV//PPbb/0Acd8pL2/o8++Pvx7WXb3Uvc/89d+PCr/4Lbrjmcvz4f/zP2GffjfmtBOzevQf/+73vxY3XXut+d/vNt+IzN9+Kqy+7Em//L/8Bm/bfhBNPfia+ccFF2HLuRXjVD57uzrbzzrkYD333Ebz8jadiZWUKra7ij/773+MrX7rEve5tt9yDD//tubjg3Gvw53/7NmzevLdf20UZUzpfyRzqJhGlmWVs05i+widLFADXGmnJkSA2NgdYC+KjGMZgegZLsqmeYafUfz9x9eIsENJhYoERs76OpqRiSnzpA45slqqUG8bXDCHOUAU0AdUaTc5edZLWpXHkYP2eucpkk1gQHjjfZ8MK7jzz7SCI9378EvzxR7+Bqz/4M9iwsoLf+LOv4uyLb8WWP/lxTIxEQ/Ng+4nzrgMA/OE7XoSfePnxmK7sja3XbMf//PjX0wlw+/Z78Me/+k5s2u9AfPCfP4evbN2CLZdsw2knvah9jWOffiJOfe6rcMABT8S999+Hc879BK6//mJcdskX8cKXvG1e5fbNc9+Oa/HMF/wy9nv892PPZCP27AF23Hw27r/9PKzs/Xg8/uifxoYDnoHpXntjz3dvwN1X/w0evu963HnNJ3HkM9+MU//dX+Pr//BzAIAX//T7sdcUmEyUoI3v3Hc3XvMzv4n9Nh+Ii7/yMXzrkq/i2svOawHwmovOxbWXb8Wmxx2A57/mTTjiqcdgw94bsP2Om3DOp/8Bd95yI75+1hfwkh94Qwl8f/3sL+PGa6/F3vvui9e+6Q049sTjMSFw9SWX4VMf/Sdcd/W1+OoXvoKXve5leN5Ln4dvXHARtp57IV79+tNdh9OXP3s+AODlrzkFAPDFz27DV750CQ46eH+8/V1n4DknPwWbNq3gumtvx5/80adx1RW34gPvOwe/9F9e2YwOcv9v8HVWMfSqLTHfedOwRPJ7rAwfqyzwIixTI1jhumBBpV0cp3XZ29RdnJVOVTsgxxjfOwzDBaqAJ6qmNQqfB6VZEq5c0FrpssyEMo4w6Tbb08hkLqtYU5xdPR8270ejdGv62Zt89Kxv4kdOPwp7TVewumcV/3jOtXjzS5/emMKIXB6yeV8AwJcuuhnv+eRF2HL1bXjW0Yfhvb/yI+HAAX7xR1+HIw8/DBv32Qc/9PJXAADuvPsOB8+96fVvwRMOPQLT6Qo2bz4YL3nxj80ed8e1ZQfGsc/+SRx0+ImYTPdtn2nHTWcBAA5/5jux6dDTMN1rf0ynG7DvAU/HE5/1rlngvG1rYqqdjjOs25Ne/pM44ODvw4YNG3HCC35w9hrbb2/r5ZqLzgUAvOKNb8VRzzgJ+2zaDysbNuAJT3oafuDH3jorjy+7OK2BwWn7sm2zLPa1b/xhPOf5p2G//fbDpv32wykvOg2ve/MbAADfuOBCQMLTjzsahxx2MG664RbcevOdLbjc8K1bcP23bsHTj30SnnjkoQCAf/nMFgDAr/3mm3H6S4/H5gM3Ye99NuD4Zx6J3/pv/25WHv/LlfO5OqEvOgpEh6qFdO0tlHfY6RVWF47SDERQipyP9QxwDN+T6gyv8gJab9BnBLzldFZuGqwpD1XYv9OWwkOgmYNqIg32p9YyRBoXGA6BScDEc8ETLGKv6XUfHGkAkiBOeovTJJfPtMAhfSZIjuCl9JlmI4dopr6hTwk7/If+wn2VS6/bjvd/upeq7/7ABXj3By4AADzwz7+AvVYmLQ1+z7teip/+/c/jU1+/AZ/6+g0ALsBTDj8A7/nPb8BTn3i4u+dHHXE49sw/6+bH7Q8A2LlrV4MjVves4rytZ+GyKy/EjnvuxO7du9pzH37oAeTZl8CBh3x/EzVjdfZtH37gZgDAzVt/d3SZ7fzu3VjQB+NsuQDggIOPaJnvvhs3z3HBna0a2HHnLQCAT37gj0bf8/57thdTXGb/GjC/Y044fuaCPVHTqB73rBMA/D3uvP3Oht08/6Wn4ZMf+RS2nPsNHHHkq+c43+weveRVp7T99+3rZwfMb/zS+0Y/12233ltuz2J5hZZQhgAnPxIBdYW3fIPRuSDNY4EHaj2GBxwPfowiAa+sdNPs5RUJtOBr6hgLrh7MLrWRtWxN7crGC6W+MU0GN67FsG67phHLEUtx3h3SpCaaPlCbveYppOydEjZjLEwI1g8AejD7lc85Ejf87c/hU7/7evynNzwbhx+4CTfcfi9++/1f9LIVAHutrJRlxXDPvnD2mfjSV87EnXfd4oIfAKyu7ik/78remzxcpfWqD/aMzJu2H7v/e2Vlr35oTCJ5tb4/q6urBUOlBWMjOFohnXb6czGdTrHlqxdCEB5+6BGc/5WLsPc+G/C80080a2bta7Fn92rJz7cRB0Y/2JnmPCXQDat36y3lNEulh15JqVkYHlRnd+WEabfR1wySioWr78eN8z+IOKUtHF8x8KmLQAX07M+QIBP1HctgI+VqhjBogqNKD4PrVQ9il1OwBPbgZogP2S842Pb4bn+a5vbJCJ1/+5lvx0TABz5zJX7nry/AdR95C1amE7z7r87Hp86/ARf+2U+AICYTlb3S++69gjNOfQpeftLT8Is//Fw84y1/gUvnmccochL1gQQuunxWrr3u1T+JJz3lBEynG/HgQw/jr/76N+rD0FyMLoOZYJ/9jsBD91+PI5/7bqzsdzRWRUxXhMlEc4xvFSvTvN5WV4XplC7LzrcgsLjzUvGAQ56A7bfdiB9+2/+Bw454MkhhMiEmc1Z1whm2yAKuAYSDDjkEd952G755+RV4zvOe68wbrrrk8hnccPih7ftu2rwJJ55yPL5xwSW49upv47abbscjD+/E6a88Bfvsu3f7jEc+5TBce80t+MM//Q849hlHYDoFJhSmU2A6EaZTYTrprtuy7e2K0JMxhVAcvqTUbig3Tc5IukiH4y4HBkjVGF9ZAi84hdcT+oWgo2bRU2CIEevoYv/noArPGPteTY+npY6eELRJT63aaQ9sBI4XNtsDeUwH54w51efEqhqjyTAN0fWd05knFsilbzKcB8ktV92B5xxzCKaTWdm/5ao7cNqxh6WoZV2Jz/g/P4kvbLsRDz68C995aBc+ff6MFFnVAiH8GuL4DRv2wV4re+OBB7bj7LM/tPDJHqCffceDnjTDF2+9+I9w/61fxq6H7sKePTuh1V145MHbsOPGs/DNc36nn/AbNgEA7r39ckirycrJUelRITh/3HEnvRgA8LkP/ymuvPBcPHDPduzetRN7du/CPXffgcu2nIsPv/cP51rWOPycOOHkGWnx2Y99HBddsAUPfuc7ePA7D2Lr17bgU3//CQDAc047qUUlgnjRy58HADj/Kxfi7C/MyI+XvPK5jq197etPAwD87n/9O3zuzK24/dZ78Mgju7Bz5y7c9O278cl/3IZ3/OxfmtZQ+N55qejaUBoT3tAesx/IAsDW91p6POYwwBFCRwvY3lR3jQypAMqZDe5qdwQ5kR82/XaKdHuacajts/NZwxVp271pyllve09MTN+mba1jwBvRhMe0w+WDcNdiXc02n2zvlQxSGV7PhkoRE9Pnm9GHfkG2XnUH3nD6URCA3avCN755F37sZcckw4tm/kDhc9tuxOe23ZiWxWtPPSYBScR4bysAHP3UY3HZVd/Ax/+5axKf8pQTxzhuU361LwiBOOiIF+PB+76Ne276ArZf/b41D9v9Dz0OO27eiku/1PG71/78XwYDWLbWQRZf4ZhnvwA77rwZV2w5C18583+vgSIoZcOnvuwl+OYVl+Om667Dx/82P/9pxxyF0191elNJCMIxxx+NQw47COedtRW7d+/B4d93CI457knOTv/lrz0Z1197Kz79T+fjf/7BJ8a38ZD6WbcgwQ3p8kFsNSGFRDen8EPJwqS9JRsLt7JmtlbFMxUlc+U4MhJAOdHIOEt4W6hgROANG+mzRNkMq8eLFgxpSxyvpysbRxwDzfxjWy4wlODWqqot6tB01x5qzBLoP5sATIYZrLFTJUpuhvcWA4tCbL//IVx32/045fsPBQVcfsMOfPeR3TjVZID+9s3+9S//44fxnk9eirMuuQXffWQXjjh4f/zI6c/AW8547shMXtZVP4AzXvlGCMS1118FSXja007EC1/4o3jfX/6qQ9yqOb4zfacaDHL4cT+LjQefjHtu/hIeue+b2LPrfpAT7LXxUDzukGfioCe/qL3kU57zUyCE++68Crt3PpiwS0uKsDi0h4PrBa95M57y/c/Eldu+ijtvvg4PPfgAOJli8+MPwpOffhyOP/nUPEVt/vfpygp+5p2/iAvOPguXbd02I0VIHHTowXjOaSfj9Fe/GNOVqT/oJ8QLX34q/ulDnwEAvPgVp6TMQwLe8a4fxPNfdBw+e+bXcdXlN+Leex7EdGWCJ37fgTjtBUfjjB96VkK2iskP1n5z3umziiy98CaSzjecXGBY/Nj9Q7zroOxntIg2Emqs8NFEXRIbpyvYtLKCTdMpNk5XsO90BZumK9i4MsXGyeznw882ray07THga5N5CTmZu9FO5jduwlldP7BRxOxnBDAZdHKczB5jHFUm7fGYv+5k/hyjrxveb/45+mv29xowoSHcTeavDWr2d/aG8fb68+8w/HxivuvEGCJMhtecB0TKfieY7zA83n7/+euaDpPhddx3H67nHM0cnvPIbuKhXRM8vHuCh+f/fWj3FA/vYvvv7HfT/pg9E+xaJXbvIXavErvMv91/3c8m2OmeQ+xanbTHxJ/t0QzvWplghnmtCCsGC5xOhZU5FjYdHjOd4WQr09UZXjbV/D53PG8ymUtHJsMaWMV0/m8SmExmuOxkMhcGTzS7npMZDjeZ/3f4t3vt+Q2I7zU8b8DwJg3Hw/zvwMpkeMzq7OfD9+H8e0wG/A8NA3TXYSqsrAgrE3MNVjB/7dX2s5UpMF0RHlrdB9v3bMaOnQfg7t2bsWP3Adi++wBs37U/tu8+ADt2bcYOfgf37rsV9+69Dffssw337bsN3125aVkwwDWCX2XxPYoNhh+MuH6QDDZ0zK7JNusxrKoS6DqchnJzfhWH2VjzaCcKDaMArb+cn7dYNNHH0ZByMh6aEZC09v9kgXQZzFJ+bAArikAJLs0HdjQcY5RohIHpw0+trTJRYmNikIjTm8c2w04lE5S+rJwdYWfjs2Gq7ByeoHGEnytcvEYaM1pYkUVXbOdk0/qD6U0JaOzJEPuBlVvETI/5bM5x8k7z7G7owGgPHeao0BoRmolwrNdILPkD7tGfm0xUY7upUldTs3sL5sKP/QCotdhalFPmS1ywehHbtR5wurGkVMWbN3++xqR6mQHDVC47tzUF5fBchUUo25liopFsuVsQ4VZDWvuaBm2B24ZjIz6VDLntyM3BnstLfNYmqbziRcGIUUVw9Bsotg9GI4A4BlL0qLpQDOemEUOPEhUpsoWhPcPXqaQiCrOhzfcJIn05UogtEMv4IzZLtjRh0GJkIzIeMTaCFtHOXMNiqJGdfmcPUBXjMd1oFYaZT3ak6ZAcuJEUdHZekjV9RZBiESWQ+phmgUcSOKynnCfGNaexQbqt1zwdLUVY40hB1YO7MhdDxCE6eV6DF1NLIxHdtKv5wdjGRaY4uJsHmz1VRRdknZV/SlRNq6AdlWcGUXMwcAjDkcQx/iibvmZr9IqUSr1Q4dZz0SmYCBm57Lhna24uDKO7T8BbjJmtzyJlZhUP93Zgzf2smOS/TCUvwi6FkwkKjPVHGDoUFAbN0bwg+QqL3ZgBxsZKN12vuRMyaUft+/TvbNea0kQ368vp5iBZcssaidNvBD/vmkseANfb1ZFYXy74ff2wvgDlb2CzeTL8Ytv00XxpOMGVjAhStR6HlxeGhI5QSd/H2ItH40jWPIrozdxkgWT67+yWXph13A9hFrMOg8SnmkskZcH3kM25gTreraEtcTt2EkWGSy8UVxEqFzJu4giG3q9mnAAq5UOgZ+RqnTLOph7DocTE5dVoEL3dYHhAF8KrkCbJxW8Pm6utb8RCS2YQ+3CAhKEpcUAWDZZrkwrNtaeKKotS2UE/Ua9w/KVtUIgbain9ALEg41tEhlQT1Mo5AfFhftpbD2JMenU3lyg4ovbFpJSmJ3rLGaVWVZUpgQsxqzVHiOZZVMgu5ecKe+ww9AUrZiQKQ9DZFlr7FoysXLxq3kJeVl3dcMng5ZbMOAcHbflA7kNk95WbM9BMWGpm+RctKmnstMwi3WgDX64OOyCJTJUFokvKyLoPc+jcQUZTXdSuUMxrZ6gMuCCfENJICAZ8MnYQZi9YmjGldE5wSEYe3X3bF1Ie2lB0XrP7VsvuCB2j+qJ+oEWiV1bBxWZiylPNskeMa1uizRJVvKlj6Zlwl+RtYPwb5T6XsUBXZ2Hdwi/KO9kN7MTKXiOnUsDMsJ3oTv5qKI1GMFeabFBV8DAYnpvDbIkKVbNPqoOSKexUsyX6aUBjLxbuHcddTHv2GVoe3b3uYw/ktKLzo0Nhoh4DPjbgsrLB3ueGVI0I5CAVTS1C8B/ew8yQpksFwqWpQFFmLDddQapAhukxb6N0nmWxcrySzyNUDEQYSu0wHWqpAiAD87UWvodHExST20F3LtHIRKwGOCN745VH/tjoRY0gLXGRyZ+ggy0/gzW5m8DVs5/apqoHUJHusnbMy5o5VMjBPFuWzTfsRi8yWJih1qo3rcJQJYYBcQizU9w1lKOL+sZvbHf8RL62U2HwquAQHpOPxXvKwwddYI4G2MsGT3soytpADaVl/nysPhQC+2W7SejZdJaET3xgNIj2LAYF79gyNlgkVCAqu0f7L1hQ/Cpy30WH73Ia4s+7YEcD16IB6cDiOaAxKDnBtJlsxgVvoIInqfjiNApsBItUcuKvyxX4eR3VTZ/5q400pdOX6EO0ZypT6D8zi88kpfAWbZ46Bx7m3Ka9YmACAAAgAElEQVTrST8Qx2a0Gtmb1lCW2UCsYbOadaYoSFYsu5s8GOWDpw0GGoFlZJvzmeErH5q99qYTUT1j7/3WTHhWGjwly44GEo/DdQisSJh0qBGIk+M1bIZiIHggzmev7j4xm+KqddakqIg8niUP9upXWCiO9GULgBoP2zGtScN21vq6eZSefZrSP4o3j906ox+XCDlSjp6RoZ9noHFWBEWnx3OfqBxsRidHGy0Vq+4XMvy6yxuIPIzLYldWppNGnJddLup4n+tb9l0340Y4hS3Z4D7DAvhte9qmvmxDzhWjmsEnujGJwrg2jcPUSQoU5CTNBg2OzW0uREbb5+AX+/HFXEY6JCNMXxv6bRnWplBYw/mDhtDIvWDzf2xD20cgp2H8QqVfY8Q8C/2vs4/lWN++hSm4Lsrrsc0C83uhhUdep2iVoxPMuVSpaI0zMwfEhEkmeQm8JTylov/HnsbybjGNbSvKpSrkJqIglhPwNkMBJWPavUwZD8KAJBVUpP+oPqTHwd5tylrRCGyxHgQMiBGjDXZg1qfB0qauLbDClqPP4wDwJw7Bgl4auSvmzqrObCtpJv1sATNSNag0DXat6JBrF0DZBMAys5U1AB6GToXdkAe3D62BRkBf9LgxtHH64p4eopxXNl4bHTJ3aXwcBJeyBH4Upe+ib8jxsjOFDZkB3oGOl+LG9NqONAGuLM3hCQfl05EjixDO21Ydc3FAMJMKJTnhqgtn7eLpQ2dYsp3VEHjHdjonGyscGrIIVryIL2tUZElGIiKwOO2LLlljAhFHRNqyrvsk+iDjB4bTfyKaa2xGKdiGEyoce4FvSJaACHN0gULVwLLEtwexJ8aC4sBoO6NYP56QjEdYxBEtfshqhrIXgAsRF3bhNbOd1j+SNgBbqtgDAQ43p4L20cyQXqoAKNQn84j6vDwAxsrh2IJjZmdpXo6IfqOzYLvqTF21lEVmtq+YaXk7NDwBwfKjDlPWSJ/JRhLEpyx+bKZhIKQcxBgEwmRxeSvMVCZfoAphbWAoE67K7gBclMAFsd0FsDLeC5FsRkGqwdwPhoHh8FJCL7WmY7crDLp0247BvnLvtLNoHOiPwOjaz9xFyGJB2KQgEnFrtbg/TCWUKSPhjgyL8cbcPeLNdUbDgriQYYE5rEnbwUMVnUmhzLeZa9Fy+dgPgHaU56OxEmbBHq+ZTUY5Acu5HP6ssVon+j5HuVTAdGAqQIgjoq6xQfBtAQ9ZiNGTmc0s+XIxdnYoAIZs3oUWf+lBShWrW02sM1FRIa8duk6cRo2xzc7TEQm3NCRPvERMM3szMNoDpZysps2bjW1z9rXjcPRAYogBL7XrJdR5Cga4/XpFvSaLgpoo57bA6CbV51JX3V9k+GxmtnDTatoqmgauGAvUPowaLWomIVSoI/JgJGPkat6KsdpASGTcHBkv/l2mMniSpgRpjahXOUCP1kq135SqFEOhN9e284QE1LaSOfdawZ/Ibo37FrNobKCFMKYQfEhhbPwSGZJYm2I4j8oh4JHtDcHLymbEoGfzpgqRt0opvdtM0VDW2m9FVCNovZohhHpJ2kgOjiypIL5smZCXtvug62c6syI9KM+GmiDAik6Ow8nbt/Omuu59nKxoxBk95W9KLKsVZDc9ozyjbVwoG07dcLiYZjsXjc66y5T7DPexry+Ll8ohGu7+GD0uQ80tx2QvWwlMrNPHS4tNUVPZqxF3GcKr/qPAWanf1iNdrBkvqpi34HuQODKzgehaOyiHamm8G4DWVZoBiLH1K5k9DVN7SnIndcPRNTJuoMp8YfpAc9lbpCpmOs5wRxJ6QCUygIwT3Zj4DjuIyNmsM7rjFIPk3QxaS+xatpNzGY7HBINmuuN2Lv1S6pxRMKeQEWs3sTBMH2yKlCwn3dk+Xw0mDAxID+W1qfapA0FCX/1YtxpXFjMCOWFNmPZIhkUi18+ukdzOJxRcula4ymOJCxjdaieNz58BYjuQbGSz05Ztg3U2Myg6v1LfNc1JmEXOTDON4tyPwWPP5gLOdosIYzBHcEopKY5b4GjBPdv0+6JSbY86LI0MIAFj2uAzdPqSj0UrGUszA+bDKdyP6NrjNmwhuGR04aG5P+oMryr8mZ3kKVVntDSKshkErUlFRESZDh33BWUxD2NaCwZ2nE4b6udpwHe7OLfw2lrK4p406afGOp5c84lyW2hVcpgW0lhBsJA0MSY3Zo2LLLHZJSmBQ49pvRdGyI11YIWmfrW3N58n8u4ciar31ktMBjI5ODdeLYDuTBtNrYPCWRmFstcFq4IAYrXOZBv6jPeKncdqMcYhYLNr7wwZ6k9oW065/aBkmtBFv9l1p6oO5QHMbvllR3kaD8fhO7A6H1xvPR3cmgcd54qBhizwqRHLbgZGK6ysiow5VsfThELKA+9c41xi4lHRC0+ZcrR19CSwjkYwXa0U5p7zuAlpD5/oHhMDv1LMz8W7TH8wAjThh6RTkcBathLYTQ1XtZs8EKK1Ah7H1t18pGMwHQjK82RYYIwBhOAbx5BhmDdmNQ7QFgG0zi5mhGby5KMrEWmimlBkYIGsURApKzXJKEWL7mSC1szejEg5ciaphmtdZaZo7s8+v9hwjm6niimT8zI+kyGRZuYKwuQxz18KVq4S5EZhbcT2MOvkI3bjjDbDJajIq6BTCXsV9eRi0ePMNKbFz8QlEPDcKCGxA8d74U3fsmcCcT4eVB++abYJMx8cu3XMfcxGrsGey7bn0e+P5dcBEmundlqQBVayfMX0XtknLSMeSLaZQijZwox2uSUQofR0/vu/0eE9jJtOcu7AbsPSlJIsDA0GzCj2wBqcpxKX2g6L2DHtha9MIHXpKlrMrBE9SG68jMN1y2463V/UzvIckjmawyV08ETyQ5HbLDZ60aYl58vF3mc8L8ecvVq6XkxtRalHnFlAEj/fcEgNRI3GpqPJn+qSF/i4UBkbB8xnVqVqgHXUjp89k0UWP/XTETnyDYfM31ZN6nIuGZs6LlcUnIwGtPX8WcvrryyTfVrfS0m11jRZPV/YMbF1tno7mvKKWlQE2ddS03wxsbbqriIhYATeOm9kO4owsZoGNymGr/vwT29/hJw9DhliutSMoQxOsFwAPOVpZxl4AsFhrwg0oueFQruc+6eyj3Vyio+liLKv2bjBDFPh0oX4JrCwBwgVmRWCVyIMPNE4/nKetCecyBGdYMAOVByUtDl6JbSgL2Lt4dh5PqX0WsleWHnv2BKdpmIQGxTB5WKBuTi7W487dOz8WHNuSDjpk0K+g9YMNS4NPpjSeVPCpObz3NlW2r+F0diucTw/t2jGMkSFt1lXmD5YOHEo5LwMeTGTVtfkRnTu2U1TJuReZwPYOzJhKAuD66gWHmY5wGY/RmeQ3TskYsSK90IoGHt6rWDT5CXXg7I10HX5xMzGtKFhrOWLxbgHqTw2mFQDLCGFDBFYIkPJgMEdiva7JcaeJnB6pp+xmwrGpqyIqnSEJd1oWH8rlo4FVh38tIjRXXC37aJbwyMntbQykwxJKC2fcdSG7vYkN7CmsUmKWI8ZbpE+HENg7kGWgeBAL62UNPou2+vEhhyh0nCk0ElAyhEj9rMqykraexQGrTHTMGYTDh9VikDJmIFchI3E0t07NCNdi5FFVmJZ8bor3PNq4FPOtjqBY7vICwsiBuNAYxpaFesdgAmBiSMzsAMsEhODUXVXEILbPhHK+FPGzFcqAlUurH0bvWG3ZRzORVfC+06WZWGBx2yoUlnLtUvcgvV1mSH9JWdwQam0dv1+M2A7SK4VFhMi6GdfVI32lRbRLDA/woGunCKDwSaCs7ir89Bs+xnlQHFGBvzkPCyYDTGGH3qGV8mBRKWYeIAZeqpG44ySBRWsO2kEjOi8/RB7KWWHYCjvbMLHPIKAhin2pZd86Rx9SR2Oq6ATzAek7c2NX0Yjw6AsI40kvLcZnfKcahWlvYqDuC0j+fUxYM6Ea490zaiqCjPfpihzbdIsEJfMPppBQo85FrhI90pDA62dEa5jylwqMYOJZCvrpIQ9xZjsknyDwVo5iZ/n0MFkv8+C9DavWSdCdmWsj2W+nJXJxmTA6eTiEQytFNyWNHIgmc+lWJSTPlBgmFvBwJjDZU79+qkNAnLltcmIczCee/upKKWi/ROrsQCB0Ag4arLqLB0N5Dp9ekBRSuj8z+jGblLRyYaOvOj69my/C1RKSrpsk86BXa1CUUzVyVDrsDH5xmHM6BRVSmESq0ufQTpk3hwSNC7j0cQYFLBqcdRlZIEXtrP9f/CnbBMy3nsoZtEOpZ4K0NvcINn5u7Z0ZihoaAB7W+UyFNR24aQhPXSjKK2LcGc/w9dmBtQdX0svylaEmwalflNFq8Bn2AIPDc6WNWd0wHpzjWaeCBeFxNaJSkGP5txKJDe3hLGHtY9r65uXlXEC/TSywniid7Bley9//HkoYGyOUx2+bBYfOmkZb788dOF1/mHsF8sZQhH2ZNFT7phjSxoHq3+5A6zQWMhTXAqUluv1tkbGBggf2ibpB4kuWQAcY4K5nqi2zsekLg564NfOcy1SfRTEmgVnWx8wrKlTn3ClgKPHmtDO6GAyt1SJEnSGjclqHtZ8WXLdAqZT040KZYmLMu1Fn1maItfMZSD8TGTZkz85G8sdQkmYI4xkasocOMNckeikIHkuQb78te0vSjWhJxycQavtLFTFHAfzBRUtaGmODMMoBLpuk+gLSKOFixIsbwIc4BHnp2iGZpGuQsnYeG9JTNMrGKAcZ4JbZa7VPTVsPeUPdQT4YMACtZQlMMaJkNHvonXEROUyutAJctBPmRSqZr68ebAVkvWMXWngEF0wNRPsY0kC1YarNozkYXO+OT6qC9jLZdLjTc6BkBxlP2lobQdMMzqoq/fxOjxRyV24Z3pcKIOi6dCP1vO2gShmPUIxIFvjTLGal6Cf/2uzXKSe4+Dvp3oLK4J7g9+hSdjEAuoNA8cd7sc+65lRRZB8BeXGC0SZCmkdgWR8BNUUDQkbDzfDC62Z8dYE6TAM2eo2EDFH8MW7irnKfekzlw9LUgKPBbFHoxFc9FiOM31tMTArzaPFUu4YMF0RTcaRCYTROU8tsATG1Lp3GHsMFSScnF9/kBFbUxH5NkPXt4lMNLlZySNN7XFkqIoSVpbLk2d+2RyAfR8TvZeJ6f5QrgwL4oEGxPQIQthwvj3CdedUBxHLfKUSJGQpN0POrzhxDp7UkDWHYEDzZEYT2AVhsrnQRWjUA2G7Ob+OcFHlWzZsG54Fh12patr0kPrgo0GGH97updaGAEtuTdEAgW4g1vKVwEg48vf+RwHE9WPrC8CUHvNpIHNhgiefzdm5CDSkQGxdio0ogbZzU7JkFP2yhIR1zR1V+YQhJqznc7DMYuxaV59NkmgGWw6yEUAaZAjCqDNHH4EIgy0WGIWJ4ATq7gEDTSY/DFaHTVAAxAyvibNZGCKYnMkbMCd8X3GEqQsIDFrOedueYphl0S7GWg5JS25FoLQofhg5xqJ/WKZCaS40yJgrWEDkdA4tMuUrKcdKd+dsZb/EhKXGN1KYR5wwhSUMgLYOWK/hQYXzVanS2BOGi+/aiGhkD/QdT0DC7RrD6dqmCoPVsJjHLZzkGMLezN5PWsVBXoVbjrSYDPLN9YM0qGeKlcRWTgQbJ7zJBGqFnIY+3bCzVlzya9lOBga/qpN9B0o1l64TGzWnFSxc+yQ6WP1kzgMV8aggMWIqP+TkNIRvbSyhgkJdozSz2M8DVqlD9KWkHX7VBdjmMKdy35SCHVw00Q0mQ+V2pJ962Ds86Vj0LqiHsf/yeUP7Vg3PXDYdYLrR6xnPtxYsyDod5AjDUtYxSvJBJkdcjajP5dKTKKEpFe4Rc2MAhtzwIabnJp0WPJBd73qOjBqlz7BCEPEEDguUIffDuVmwDtD2Nl0NWGfVLS8zLD6zkgTrzhNHWLFvGAeeeXcIGYaWCmuI8t81uMh4D7xsP9XT+YLnoT8fhvZDq4NUWI9C1cdsr3s46BzhQj+e1DKsYnUrPVIn1ChWHD4fiOy43MicjccZLiLTNrZjCpa8BOZ4trbm2EyMb3IHUNkeLhlMZQQ0DItGxvONo3NWbXofECFFqUeU9UbjUNu8ntElJ45FxNwKH7bkba+0IanYCpChAY60FTp8ywTumCG4JFAsB1TJZCK+o4V9GI75WkPblvt+ScVkWM8kNLcatmqdxa6Hwt16xJrev6dho5WZ324F34e8K2aLVVtGcHyIOLDDWS3LPLqHaOCaaGlvsGgqKFjDNQG9XZhrH5SbWey+inlvd5SUmmGOjNxbhgBIjLUEjGdoY72+XIsstpZSxGL0SWmotA2YHsuTA4A5J1ToSI5shc/Sf4S+rBGj0QlCU2ubw9G0be7962xPQYw2gMwy834lM+nNpCj5JZVHaTBubqTOC1TN7kb8mjHTwhhhyOaURxmwixl76VWYM/hsmeNrwiU1XiDvrNESgEhjleWpXgXL+uyLR4ylUcmlPFwrFjiaHwBmbHcZmSUZTLSidkxwVRwf4Nl5O0vFd1nSKI+YKw4y45Mq2iubUxKxvDNB1pvdlVGe4yVwZH/lk/2MT4RFY4TE1j6rwWTVKVl0OZAcmQFbsNGujFYzFeiyNrpkR2Y8oIRkXJpIncIwom0C0QlKFes0p6FjM+SUdXywmgSG7oKRPm2W3992qjCQSp5gGLoRGFrc6Czj4QwNVJAztH2BHIdWCL/BbZ6q4IItZ2irLmEx/nedJGOTLXWtZRgLSYPBKo4VNWWp9dFr5TzdIaSSKRkSBGbr/xpzcWYHcV+lLg5rZhveNsq+GCocxrkpiHIyLKEMpjhlH10qG/3WtHDCHIPgmIUMk24Ga6Hxt7h46WhemGEydztUHzkhOSbwqFg4VHBOUBC8aqxUhpHNGMKiZU1yNkgtEIcMkAoqMfmZtM6Gikplozsk2nWnc7FJGsiUr3PhLCwr0fZjmOQzdX+z1gU3+wZ85t7dRCUEXzuLa5qB8HmucOiiaQdkt5yyjtcxulWWe9WZ1D4Hs/ibscRlFK7EgV/983ksXT6sR+MLKwKHF2T7Qe3Rc20ZSZC1Pu96mz7G+oRVkx7iSDqUnJWN8WjQwvkE0Ey7D3NcSdWfJwH6UTVmGT0TtNm7N9R815T5jHJDWKd3P9BcVQSJg4PYA0XpTEU/NLwHFHn3lYYzMXeghG4MjUz4g3Nh7q9n63y6ofdKAlzvdIfOKBYOCslwuWhvg7nfJSkkJoPUJEF1s0uYBMU0i5jmuqrwgpQTa1rAsrqmAdMsRlqmo6caa2qZ95Gl32DbdmIqeUf3gUg2aVB3LLJC7rHe48d8ABwLXOuZFEfUbATH62YvmchtWp5is/2s0cHC31k3qhR2xCJKO3GvGfOzb2TkJg4jsnMwOJrbGlwq+uIpZKUan70bzbGdHT9KLZjH5Psx4Ufg0JMDVm7ReqC7RMmbUShBmkDc/CHrsgRueLJMwBrKahX8Bsd2FhU67hRE4yyhaATXbCmfImIYsJ4SJfYMEIXUSsW578gJn7fZLg8x28FZVx6X7ZrUzlhBNGImZq2uccmZc9DZZ2kMxkrQl+rrvJQlcIpZa+uixvRtC7S1mbeSm+kVnJ898+l1UPJOJz7FKca2VmIEOSNM7zYd5P6qGc6BaBkjwBkGQsk14dlODLrZrv1krt15rSaN8TAJLokdAujXKUl7GaeqhUmyUZxuO9pcQA4zKWGGKQU9WR7A1DelTZyYUasOwsc0SV7e45yho/edfFnPavKdTetlLKUSLslxiMgoFNzWEI0TuXHeceWNiqREYVjS0JMbqqIxRkmR0lKy5LIsPaMYl94AYdlGYq4dAB8FBLiQAU7SJ6UWgUrNplDiKJkkeIsiOhFX1OjROQXTbjQMTtNxUE6ctkZng5UHShrczI7eFD1nWEqDonrOR04VZH2zUCITk+u0j/QjSO21DAlJIUlSOTHMtiWwjPZp2o6/YuLIe3g37Tybi6kHthrnqOiYHJQEjHKdNZft3C9FUVwezoJgB6QYWKkwtrJYq+iDsOykRjdUihUsQZcpx1nKAyxCxRzG9837SyxH6vRZ0DJONWk01P8PAiA5im8tzigZ5AABuYitkByzGFQqiUUVU9/84zmWsJqT1rX1JMazb07StyWxkndIxobcu2168Fu+rzfaRBGFfRLhHcOUA5tjikzXJhky3DAHQt7hJOGTlQJXBQtI79iTHKADA0CDPXJkqM9MJCyPpTqIrwhnzEPvUeCDduPTavrsrBgWxlkDViYWDH/GgjnCbsuYJrh5MbIaWPrB6cqZPMcWOg3pk4x5c5vgWGtbHJ5FE/ykpe4EWQcJokgfLtDRFFigoh5N1qHY3W2k9vYqmNh5road8gysnaqFuhRxGY0StucwN1eVFEJvVzgaVtdYqNi+V1vukEgFOxOwD2fYUHA53cCVnJfnavimi2FhRKT9rpLPrMgI4BdT7RhAPHFk+cgn7EHMqKpZyLYutLZb2+bmizG6Zu7OGkvZUsrDBkw83LhnSNJkwcqI3MEuX3mowLKrzFhJ5gPHVkvyT7EEFpWxenqD3zgfWn42hRkKptDYFT1iNI7XPuZZYK2D3S0OUAgLHhAjSwCTydKqJ3a7MkpL7ElEuhR92NB24LgU/cwsJKx2w4dB5RQ9uO+U/rEXmC6oyskGDE7CfgLny8YiuaJzIrYGDbHUZ7CasmMzpflQcfrdrDBP2QPvqivfwMSyMmwUi0NLXp8XTRM5Qm7FJKSw0XdXW4X7CvNAdAbnY2+KG7uNenZMMm0NGUKjNA8gC/IFyT1XgWBp2WRyN5KTIcqPtPZ4uE0yjIHIKJOV9pjMBDvl39OP9fQzsJcxA4wpkR5Ffx8XBU+F0WYo3tosRHkhqlhIjGLpEvFEZ+/EgCHFhVrgf+z6uDQfIregmjLW+LjRSHNk/y6PUSs6kthyTk7fjDCRTsE+qydU1ptu/v2k0HZbtHbROHWnLgiWmYpcxW8NB1QEgXiAMlSrzKqRgbJs7sMK891sCAsETUnOhXkDyeWHfriUlwjAtxtmckEBE3ZWLuzSEzeNLrThZ1ebSMqhNmiMBBiZs3Mt6LYh4iy5TAYjujyx2FPLEgA1zhAlW4n1ZIIcTwLbkm0kbe8OSV2tVpEfSp1uTxWHxjCZosfMhcUEOw7zJpg/tEIWF22GKkt2xmsieBEyo6iWSfrAog2NMVC50htew+hObiLaQzHDdT2wjDT3dJmPUttVNGp1bGLF4lbC8Jj5MbivGLxqfBhhl98Mma8COaCIeSiQV7COPMpcjZ2ZgbFpbyYoUW4+tAzezHkLjcpsuMJZo8WakPQFBm+PwTKOIWhzchSTimwu7FKJmLEakm35MsBkX1VgeMVkt7GblLLC5PzhF5lvmjKnC2OW5i3oETzz1NqymMF8KEGY/navpefxWSaLUOm8DtueFRKlCaTBRHGYt0U/RaCafKFIxSbbNm97RFOmaxS3LJxuwmBthoNGI+ypwKopIoVrhEQGJgv0MDN936+rH3ziRztCRRli6elWLgVVzQC2WiZ2UwjL4zCojGUHCtGu3u6UI6E0brHTC+37yATWJKG3SZmiMT+6AYI7YK1AXoF0iWyOmSEzWGQZd+3l6gOJJXAN+hQn2qN4BxXZodWOKctnZYBbKr8pjRloz/rlsqaY2Shgi/YmDk/kCGifdGaInpdMiTKl7ALG+hI7QoDyZQ6ZAzi1Rs+ldcZmGnXsxiCTBRNekaRxArYNJhyDkmqWCXZiWTX3RQWF2rPNqNRN2ZJ1lDYT6jiOwPTknF2ywrKbSchGNDSlq+rGWlg3mSwdkRl67w66rAMK1wIB5oBzNSJr/0Y3X6uPUgwYXjwVKpNLOI2j8G+xFW7dVvdrPB9MAulMIjvE1pAcASInRz6WF7M2I4XErNk2rTjG0fsVZzYy+KKFsiVp0BzrHJlcZhlHmlAW7PQrrz9F49OgX8Q4PKEyfjJUqsbaIBz1UgEtBNLCg+aZ4U/kD6qZwoV7SlqjzKUq/egiV+sF/z8WMFtF9PSWvo7xOaMXxwIz+R3GmDL0k4s0k/3osy6rfWUYljQSm2xW2Ocnx4y7S2OsJEvyK15mjCfDGu7Jx3KJoSfrtr/ngh8ULFRNrBgshYFgUYFxhTkLuXSyaiQGo+NsoS70IUU90FXBLXaRZHqMRHZNLoAbsSqeCwFvApFrF2Q4595OuDiY0RICKtO5UBbn0lyGqRSzmaeVUHBhBxjrbFcByjDBmvSBPXNa8jq4Mi2X4S2izIDe71f18ysKovsm0s2kdqWzEnBQYuq2nVKmR1lRs8jsjTnM6VDoEHHWXGa+MtEnLooFWSj63pYBm7TCIDud1WaZQjHOcFkCoB5N0Ctq26pvdQ1sUN3qtwHSM4uiPueXoy+kvCQln/7H0tH0SaayJLF/KgnsiIcNgLXzy1O25KINQmRuNheS8+5Q+yjhpeF60BwELCj8CsWISSRiqd4HdPfMxLqM2JYpLSgI6sVDVtmHPx8jflFehyBGjgOzHH9dCKM5ZELOG9C3mGFkT1tihhm8MewrQreHr9kZJvINmCLFJGRX0XkSMQl55HUEv4LrD45SJg7VDO3rBl2ukb44wTRYzmlZEgyQa5euWhQlF3SDFKSINdxkaHErmo1GN3fcKfKUsAtprdXNyHHyCV0QEFYJFuZi0/VEGr2YEWoxUqPIIyZz1cjODEYUMNlfcXEwYjdegOI1r1j2IptTnw0xeNWVN4K21M2PkkcNgqDdPlfBUkyhfbGgNKrhRQpJtMlWmkYwOQ8ZFlVx6p6XkYhRtQpnvc9IrATaSBZ+s3NNHHNDrwhgYMtc6DOmBqh9BGlMyTxkaaAfl1TYg6l2daLLepcpA2RguEpcb1Fke/Rj5BqRHt1rU7GYxcEo8CZV4loxLLfOvilhU8TP/fyn8LNv+2e3j3/irZ/Ej7/lE1CKMd0AACAASURBVEmWECYSwiF7GiuelIIcS7zOs3FRVc/CoSeP6FSuTd2UIb8NSWDTGX+CjWe8x9KZxq9wyFZmz3nSm/8Ax/zU/0gbPCf8xtmYOVAOM3ClLL0pPTYYM7tQVbMw0Kk4GFnUJgJoKrJGIvZjD22XVBeMWC++5NsnZgBHppxktkuz4lNFsbFQKm78/VD+/iq5cOM+bS3xO3bZeZauVx3kO5Tvj19OR2h5jO5RG6Kut3xWlqF0qUgMHLMH/94vfwW//UtnIXTetyf/+ju/iF/7xS8GhTociM/Ym89Fn77ugnCgucIAbXnqUKrmgQE/+q4z8aKf+DCu+fa97sUvvupOnPLmv8PJb/4gnvPmD+LG2x6ARbmuvH4HjnvTB/CKX/hYKVQtPVeToLA2KChvUNi4Kp5DFMbvMQtrLWsM6K16t40jqoZ+Whassi2/6cwxVJmwugPKD1G31l8Inib9+yr4+Pmh7a4102JjRJYnJRxWLqwK1fjU2CpoStR5kE4hjot2ZtdHersr42Zts/JkGCwzIc5313N9uNljNAMsj5B1fpfv5fu2E4R5VlBBGOS3KnCjiGlFgTH9KMjRE6tytGUVygq8XGHjF1jmSSccCgD4xhV3OLebbVfeBQBYmc7+veXy290C3HLFbQCA5z3zCSWD6zWFfQqcc1g2Dr5VBuEylcrCTBq//WIwORineBaiyuQcI5bPloeBUcbqKmaAkWjoXnc1RKPgtSUWYmqMscEhfJkEwrGp1nrMOTHTDQSjFqcUbuSCm0kzAr5bZ+uE2ysw1sZyjWFBa9VdYze3OZbBY/53S0WCrCVozinAeAscFyx19lOPRpy5OG4WuF6SKkWvslRzhsFGPjOSNEbowg9eMqWvN3dz2YzrMyVw8vGzAHjh5Xe497jw8jsAAK9/6VGzgHfZHe6CbLls9vvnn3h46TbiWNvW1xvssWTkGhyBbVlxCho9/FKnSHB7GUZaShVskl200lRnVtNUzQD4keyHroncaESNKUaV2bLmNEpGV8bqguHwUDzEA87Tna8LGGNk7Xdo0OrH/EEeYRnlyTTO1n7WcqqiPTBYephs05mNsc+DWUZHwJUycFX/1voqX4eKWsZQwXDRZmPsU+JsVpQ+Dm3tWfnKzSUUZmF9/YJbcN5Xb8HNNz+AXTv34MDH74uTTjoMZ5xxFPbbuCGcXGMD7/rvd+9ZxZmfvRZfPf8m3Hr7dwAA33f44/DSFx6JN77m6VjZa5qticyJcNLxh81L3ruwuipMp8Ce3cLFV98FEnjbG4/HJ758LbbOAyIArK4CW6+Y/ft5JzyhLb6du/fgr868HGeecz2uv/U+AMDTnrgZb3zJUXj7Dz0TG/aatmt+yOv/AgBw88feit/+qwvwT+dcix0PPIL7zvyFsMlnmblE3HDH/fiVPzsHX7zwJuyzYQU/8qKn4zd/9mUpR7eZYbIvY2UZXvf2eGx2UrSXBNTOBTyFaX8y80KUzjE371ar8xdbNa3CCpirWlOkodN86c5cslMKozWtwYfAyfwWTWxwT700c5jAjmlFrapPxPjcCcheea7ChUnTZO4kklZ+GHSO0XbOuugsUxhccZFGa7C3a02PW5iWe02GN1iOxuAq42szELAhxffBNSGpAHzg/Zdi25bb3Gvcfdd38fnPXY9LL70L//U3no+N+24oRbWeZJnhUrt2r+J3/+BruOKa7e7h1990H67/8H3YdvEd+G+/fjomK5N24iswkgcfsBFPeuLjcOOtD+Cab9+DY5/6eFxx3XY8snMPjnnyAXjCIY/DcU99PC6/djuuu/k+HHXEZlzz7R24/8GdOPrIA3DQgftCAHbu2oO3vPvz2HLFHe6zXHnDDvzeDTvw5Qtvxod++wew915T9/v/+Idfxplfu35NTGP7/Q/hpb/6Mdxy94MAgO8+sht/+ZlLcfuOhwqqOc+CiMwsK3DWCOOVCB6ZcbN082No4JN2390IzpFUSnB6026qQNO5M5Tgw85fNWRcMUi19fJW/j5qn5+WcaJvH3TnehNYzwKyMw3u3lezgBkqH+tk0zO61XLmSAzEMNm23NEdSoKk5TYkiJBda5YiAGoNfE9Yn8RlrTw+HVad/aORYtACxebm/da7zlo3Hnn+V2/Bti234YAD9sYbf/RYHHvs47Fh7xXcetP9+PuPXIUbbrgPn/r0tfjRNx1XMTIZtSLwz5+/Fldcsx0bN+6Ft/3EM3Hqsw8HSGy56Db8xQcvwSVX3oWPf/abePMPfn/GttrJKZx8/GG48dYHsO3yO3Dc0w7CtsvvBACccvzhAISTjz8Ul1+7HVsuvx1HHbkZX59ng6edcHj7qH995hXYcsUdeNzGDfi/33oqXvHcI0EQ/7LlRrz7Ly/A1y69DX9x5mV455ue5YLT1qvvwId/6wfwohOeiMdt3ODM+W0J+P989ELccveDeNKhj8MHfvVVePbRh+K8K+/G2//4c6mkst2DlJdjpPOvlcRmJ63aC2XbsegxstZhMWnCYfp6F0gylXycikYUb956Yjf0RE7+Q+Pjx2TiMAugfUi5PNkT+nPUgq+VUtO1TzZmmH1OygzeWQ3D17tawMmEmi9YpKp8RkxYtt/MIp63hqrf0AZVWRiodS1Jflzo0mCAa+F765kZvNbwpIpcYR9w3nXRSrYielSHSFe6n3/uLQCAn/v5Z+GkUw7Hpv02YO8NUzztqMfj7W9/1gx3u/D2LM/QOBT61fNuBgC89SdPwCte8mTsv3kfbN5/A1794qfi7T91IgDgy1+7MbRqefaPIE4+4bA5ETILfBdeOQ+AJxz2/3b3tjG3rdd10Bz7nOvrL2zHaWI7TeraTRR/pbXskC85NCk0RYARgkD5U1EUJKSqQpVoKKoQKaACP/oDghSpqoIEiFaqVBAU/hEljSOgaZLSfDQ0DbSJHROTunZtIju+H4MfZ6/1jDHmfPb7XoPE3Vzp+vqc877v2XvtteYz5xhjjlGFoxBW/eTP/3qhqv7atQB+2+9++/lY/eWP/e9VVfVvfd+31Pf+o99Qb33z6+or3/za+oP/2DfWD3zft1ZV1X/9V37Z0+Gq6j/8Vz9Sf+Bbfke94fXPlYWGx8v97/7nZ13iD/6R31vf9Xu+pt74uufrO7/pa+tPf993R5+HdvhxAtR0XSo92eFdsq3hxa1lGBeC/dYA8WM5KLZ5iGpu17N0xm0IuDnxO1Si2jpaVIJtnjDiOLF2erF1lO65MK4zdJJowyzOMD9XpAQj2nV2qUGkFl7iL7iPIvg0xaGr8A2n6gOs7s3OjNMoQmPjFr4yLzb+u//Jd52uxCjUBVUXoP7EH/mRBbhfcZ9P/trnq6rqP/ozP7l9WZ/+9BfaOhYL7b45xoJPfuoZ5vfNH3y7nLrPnrJv+9A76gd/uOoT/8fnF2DNi13XowP40Pu+uoCq/+UXf6N+60sv1c/+r79RF6A+9L6vqiLqQ+/96npyQf3UL3yqXnzp5frpv/mpulxQ3/L+d5zj0t/55Oeqqur3ffPXyUPy7H+/51veWd9fP1H/26/9g3ZgfeR3v6PI68mH6bx+9mL/zqee/fzv/MDX2CbLd7z/ax1ySBExpnMxdkPYWUnbxuECn565WF+L3ctqT4WNgIFeY8pjkE7bs4rg8GPsHPSKsDEVYnuyxmhK8Wbx2fVN15XroX+RkRntDVyv6UXJXHHilrAog0g5BCed5gxsfNGIcWOQCk39zvk6aXvoJff8fQzAVRcXx+bJHac1HsACcWOE3nWQ7F3hGgH28zMm4S1eGQ/10ktDwpiOAa6XGF4+tm+eRGeHr79+0xufr69/51vqN7/wQv23P/LL9YXferG+8V1fUW983fNVxXrD656r9777rfXZz/9W/fc//nfrc7/5pXrfu95ab3rDa5pmCFU39i36L9/yhtfe+sq6CYgS22FhSghiaIxVXAuz1detH88UVvDd1xbZ1vmo/pHY8XNhexYOxs1oQfAtDXvS8ZgWQsVlNw/f4PDNODcTo+y1owW50cm0m1tZaqGt4+x8D+vhwEk/D6GeWJXOnTqtnZb5xN2YIjw1Wfxs6Hab9Y2sg24CusMKAy4lbDtD3QGnTnJ5xnEctd/+jjfWx3/1c/XH/81vrXe96y3Pqv3ROdaSgoA0y3TW/iH/mre9sX7lE5+rn/4bn6rf95F3nmaWZNVP/vVPVlXV177jHzLV/EkQHOTM9aH48PvfXn/77362/vP/5m9WVdWHr+zw8Ta++f1vr5//5U/Xn/1LP/sM//umtxuG/q6veVP9rV/5TP3oT3+ivve7v+FZV3d94f/DX/t4VVX9rt/+ljY6FYK/zHDk65+/621vqr/1ic/Wx37+k/VPf/u7zz/7H3/hE9EdZU4wrBk8ogYAVL1MW+8yDjHY4hWO3udf8CIkAyr3X5GEBPcElwaA674Qbo0y6KTewSYDMNsrinj5xAivHV6v032Hm8bkirFt2Ouvs0TiKy8ywp8n0MtjQ0FpeBj87ugPil30N+phEuDV1AFOzcMreQ+8gRne/DnRrh8B08oIs6+PWQIEOA5wVVUf+a5no+Gf/aG/Xj/xE5+ov//3vlBf+tLL9cILL9evf+o368c/9vH69/+D/6k5+h6u0BO385Fvfzb+/fB/+XP1oz/xK/W5z3+x/sHnv1Q/8rFfqT/353+uqqq++zt+x8pqRcdtjq7mw1dB9G985gtXAuRtxjR+8/ue/fnHf/3zV/zvHbaq9NHvfFaU/r0f/qv1l370b9dnPv/F+vTnvlh/8Ud+qf6d//SvVlXVP/t7f9eATvi+Jjbd0ke/7V1VVfWv/dCP11/52V+r/+sLX6qP/dzH60/+8I9uGsa1SZruNF6YpFzt7jWUOK5M3DLP71ezWS3taXPf/q4UgovNU8Mz05SVHm/Q/MzdWU1IH98YDxg0RniM2dTmfZmrbAyrEAasGju73eRDdLTSgLhutuOjZu122njfSwf4SvR9txhuPoL82NRBXj881UydOFAbnwU3ypNePtdv/Y7fXp/41c/Vx37s4/UX/otfeMQbarN0YB5V/9T3vLt+5m98qn7xlz5d//Gf+5n2U77pPb+t/pk/8A2O3nP2N//ge76qnlxQL73MenJBffC9X2Vj2Qff+7bzz58+vdSHvvGr7Rr84Y++r37spz9RP/WLn6o//oMfa6/l2z/wjvpXPvqB7Y71GuHQ95xR9a//Cx+qv/Bjv1S/+n9+vr77+/+r88//yW/9+vq1v/f59gAiPHycvFDD0OPhvl6jly+1dSBvpBTbQn53phbiIX3/3AUi2sJVxnE2aTilLMjuB0PhRCoXYFm6bd0UkDW0lfFMzLvT2rCDkSAtP+uU2UB53zBa2OyNIskry+g+CrW/JwvpmkSar24WOBr+x6zCNWv7B752/JmoCWYjg1Fr6n6RR3ByvF03zvf+i++tP/rHPlwf/NDb6s1veb4uT1DPPXepd7zjjfX7f/+76gf+7Y+EX97sZHH86dPnntYP/BvfUX/on39/vfPr3lTPPXep1zx3qd/5dW+qf/kPfqD+9J/4R+q55xAnqZ/Kx09//WtfU+/7+q+sqqr3vPut9frXPWeX6g2ve1rve/ezP/893/Db6rXPPye4KOr5557Uf/anvqe+/w99uN7zzq+o5597Us+/5km9551fUX/yX/qH68//qX+8nnv6ZALB7ERPZ+njcn7lm15XP/Zn/rn66Le9q17//NP6ijc+X3/4ez5QP/TH/onhSrvUBBtn8exy0MLQVxcOexg9o8vpC9b0TAMDRgNOGextIDks2eJdmdmBV3oGhikF5Er4AcOBkCudEBkO0SdK4jzA2MLLeDYSagq8kMW0mEhnGj1fXbhzZkkra13LNgt6MCFsvl7l/6D+6Fu57e6w6/AwBE0/1DHCghle/+RJvf7J0/O/b3jy9Nmvnz6t11+ePPvvkyfX339Sr7k8ORngyxXeeBZ4cw00wqHjQl0udf4e6hlTfLk+FeepXpeFCR7WTdeT3LBCrLWly4EZAHUp1uWwnBd33ovYDa2/9/qaBQ88f43h9Rj4jOvX6Z9dX2etkPP1Pdevv/76cvz5EY0poTiXgl1Te/3X9whc6lJVLxL1xS9d6osvPvv3Cy8+qS++gPriC5f64otP6osvor74wpP6wouX6+89+/dLL6Je5KVeeBH1AqtefOlSL7yMZ/99qerFl1EvvHR59t+XsX79UtULL6NeeAn14suX889efOn4+qoXrj/rgqonT1lPL6wnT1hPLs82bJ48ZT19UtdfH39W9fTJy89+72nV0wvrcv0aXK6fPVhP4L/Gk6onVYULn/3epZ7dN6i6XH/vcnl2jS9XDaF+rf68y/n/5fcu15+FZ6/xcn3Nl0vVkye013m5VD19cv2ay8t1eVL19PyeOq/Bsz+/fr9cn6dPn33d04v8/tP1vU+v/z55UnW5VP39F99cn37pzfXpL73l2f9/8c316RfeUp9+8c3Pfv3CW+qzz/9cfea1P1Wffe3P1Gdf+1P1mdf9TLFeuIcO8IFRVoK8K91P+ACBuCl+u05LCRnserEDn9AUK8uAGGQ5rKZhwjKkOxfmVbm/Y30Z/+unc4WZAAeYq5sGoHKfdzQysk7L43fTGXg4vK6Gs8vHEO2TcOIpNpqtgyvz51PYoBuHw+24qseJwrpRdXeBmL7qeLXHVpbBC0UbuO6d5ZiMkdN3SgV23nNi/OVjP6QoK9IAzRBmNXKeNGf5MsIoHgFfaWJFidozJ3q45MXgOMBIo8IocrDOECKEzlB0FVE7M3I/HeClfaab3N3wz3kcLthQ5NpY68PU+cftYxp68uxWHPdjk6r6WIWI11y6LarekfI1zNsgSa6MtJTCFZ0xXKlqgtfzgT1GGw0yN1xnPdlHl6dKOPtf0X6tksl1o4ZvVldBpjqOtq6Wi2QmQ4kzh9f/6Qv517/rKkFhjkzUrd9BbYTBCVrcjWFmt46xcLAlrYE0UMlLvKuySEhdKTGzXyn2pkmkbJVMQCctka6/inWoQnOAj4OUaXbr5g3r1+ldiTgEYO9BDaGPIk+Rx7AiEwR3UwBxe9tjLFocdoCziG6Ca7jRJjh+W6O9eGOuBkeQeMBVVO3AP1yoG+/XF5r8JmC422mXAGWAD5Zy6kgZGd0YQFMOYWo17cBjjNI8Xk92ehTvJw44bMPnDnNWdmv9vA4eEelFthuZsqYUWWIhgbZMwsDX1CJLQnx4A9M1Hzvt1M46RiHvJVg+D62B2AKnj5D9udAij9VdN+PTUsfx24YdBMzOi7W0iEeLCP3Qbuw4bMzW7c1BiBq0uL370QFemqvKdGFYw7ob2l67fy/3kwo26ofDXdcS7mu0noIgx3oSrQ4fM1GTGQ2W2SvZqdofAHH6wkbRfLyIea3Puzv/2afP2yEJSj86CtgcDxojk/PM4kWk+7KqMwYM+3SNC+3xo2v/lMsVGcufD5v8ZzUtte4YsYVwinY7BMLRftq9m25xbsqGImWCpwTmWvSwEx277BwnEQAJX1fiZTwihOo5vCq77Tgb053khT87mTfT2We6uUFONJsiaAcVnOs/DzfkVHI3BbBqjN8alwvkzuVmqfCmPAbDRL0AOntubASwEugNYtzhVNCqVoegoyhkxD4xkbyrpWjA3QzKzSO1iaVAO67iorBmllFsxpVH7OSyREJq38YNGYgZAVducuJ05RCAZ0HWnI0x4GOe6+ul6vCWQ2a/TEqDwdPPks70v+hs/aiSihN2ypVB7ACu67XhAS0BcPlIHk7mKw8YYxd/utXU3MHDDPVdk0p0vn2olZvODc4YEyumYEZPo+RymLo4Rz4Yc34vi3C3YjF5S7rySAt8PNhcWzhLPYCfUoAmc6kY/4pIFqPjFyuxTULGp9wR67AYnQcibKa6CWvYBC2sxMHqo1tLGRU5FIBhswEhXu2Xf7XHpxIQGE8wDmH0CwMUSYmdnRNZ4QcB2M1bBxSk4Ygn5nmsrZEBRZRv0HHWujDjlGKy4Y37DyXaPLX9hx4JHEmktTPsvVE/RDPBGqckpnQFmW6obwd/6WBDDY1duCt71URDvbs0at2/ue6lg/ul7gkEvDzqq4Y9z0aa3BJC33CGNjsm85WDjaRLAnfDuob9WPQi05lRfWKbGz6zw4KNrYjXtLBwFBJrUYOkeNKRD61aOMG7NXcZoXkJlLHYODEtt6KSk9vGf4Qvn82NDcLSywzd/52cTzA9WsGt54pVrnjJvuyKOUXLNwbKVhuzkHOYJCZXa3+9tGJuyYP2tdhmGcZ681mOwY4xKlpxQgykB5xTEjmY0wCCfONJClFP9lArVGQa90F2iNsE5OF6pXkarxYS5KF/cKMoPlTkWCMWmA5p580o4TIHkItIsK82EHMlWMVncZxWkF3j9aRUtyvirX51U/PbDjOHm2aNIc0+HU5uYnMj7fDQ5HDZQDNUF+OIo4k++JmYxn6+IEfk2JCAni1RQREFwDvy8rwWxL0CN1pdjQzOnWxT3KTNlpfcqHp5yGMkLtbrzIIya/zD+KgyBEe73BYrUBpSj5MIYowdDNONfn4f2yVHFyjFNLsCk1XNBmEGiRy2O4lw3I0MprUjN4rdK/UNrNrogrDZeFI2GDLWdTLBts2OmMID9zIXEkbr3m2TGpiizBwdekbo7jojK3ub+bBxIMfYH2Z1vlY9WROUcIpSlCdO7LpwI63V9h00mmC64XUsP9fGELOXNtlcrHO5Yfip/0P12EjcwFTMJflq2imiNlLJrIFn5hQfOYcTwW4Sh2qQMiAIu9wC+GDXWMEB2H0aO0MMEgjVV/HSrYid71ooyPVo42KYaWP1RAfQ5bWUyeJ60Pi7wZ2RIDsCZCp0NY9H2wLK4a5y9MbTpGLPsZ3AUqSOMQ6T9AiQm807zdOVONk+xV/WTpQEwkCW9N2XbdkoojfGEdjOfNKMMKA7YovmEIwHsjV2zUGzjc/MAsP8c9oyfGiY/TOBkiH73GM7TOjsr2FcyWKmpoy5gyZ4phWm3oEziQVKNb9OBzhwWfhfqa4s8w6xG/ce15fZUWFhmMeLYNXaF7ZsFY5Mr3rwqWUddh3IxubOIBpiOu2NrfYpPXwMs0u8EwnMIkGm2Q5DL58XkrVdXn/o9xD7nI1VUl2TqPgV9AH34805YHFgAdWMUs04MefLHlZdNPU8zVeFk5K31KNwzGT0DoQ5scpJLcV1jU/sLU1bhqXB88ozZqTm5C0IVNuXhlgFq0kThrGYuzGK/SB12IoiK2mAoIEoPYLZ2WVKZKeLGnzMPfFLhooK04KRYrMedENxTVHvBRwu0PA7Bk0xGd8rPoPHe+MU37q5xVSVxpHzrWYxqylvzXtRnRnK1Qrkwwbxr7IRuObub+woHmC4H5sjbFsDbM0iRW2vgS1bA4W49xUYxiChOYsK86263s8bU4RPLNYWR7MJkqULFbKm6FHeBERvw4gknICsGWfJEV9geXGbgPKVsXHAHq2xruNgnIuGelLePwfNoh9qjM7iFO2qKJ7+Gaq/YuJPlSSwkVuCoqJXZMh1OqVSQT7px+cLVDy3dCwzQ/+FCNARgeJwORLICDDH+fsZ57rW++xXV4frGmbo7FTXgYYgarREr1fcC5+GzPOuhNA7Nhcb/I61N0mYRuQdKbKljKOAJFkyGTJi/1IpYmZMtVSsjOg0nWFbbNz10heSsJHuGK+xOW01ZtD74VkA3Pc2afIHhPicgQ+eVw8l3aAnAy1BK7s7VTO9Da86lQtlcfQF27VVo6pp1f5Q+8aN7lH3b0+fPHZmWT+vlSzkUCVDdoKelrYAfx3VMcQdJRMf28P0btu6xVRYDZ3dUUQXMei5KLknbQoqC0Oi7VWvnxV2ZvR1SAruO/GedpjdXQe4LWxfJp0D3iRPMHVNMlBx03KujFPETuPqcBgYINtYz3BJK5t3aKHe/XsrmGMdKo8NCsRirBpOrhSukvxZhMoodP1UphZnFwr2ERDTHA/H6kjG++bNg4vTZ8CZ4HIca0Ow2bXlWUDReP6uz6RKEc3cgT4qTqSbmV74FlA1u6c11vlGjkIgnYl3nWDQ4wcWyDhlMH12vfOA0RL79Xq1PGPDHg4D4g4c1XgrLAb+LP7wNMC6ujKd0p67MkNwIOBhR2hWywm9ifsNX7/GlBRSDoVUbwCsDQrCbMgGf0BdXKfsigoSdm5OSNEjO2NpWE8QCJpZK1SdavbadirFN/DAKhF9ZkvimiEFBCGsCwm9Q0msMMobfRVxwW+zA8pyckFHKUzyQ2tJ6C3RAiq2QU1ayyV4M2Cb894w7O0BQg9+ndoWSeCB/XVxc9bLZpBZ/YhtP/1AUDONPAQh98uZKtdnpxmTEsE/EHjENXgKTMCI5xocEl45HKvphRu4r3D0S7shmtkj5oeG9Qj5y46Ngp8u5/mD1jEshxb4DRUjJTdVmvRxzOQ2qZs7CTHfi7b4RHgGqz884birujoG+6aL5Cy7uTWlDPFZcNB6mbgYC09cIyqlGxARN2BW8g4nbJyTK40h3Mm2pYxyZNDOThbCriPf1y7YSGdps1qBYXgnsgfzUBG8rLo1vuruJCJyEWoaXK4dJtzNpZft289LssDlphsGN6Sc3MTYx3YT1n68QYBs7jVcTh5xaE+bAQzWP7ZxiDtahLu1CQJ0IoQPTcW3WONE2hVo7eIr/bPJGVhFgBSmKs9iGGq3Im9atoLhUWzXQBcaGNbvnjkkoomJuLE8iiUiZbCRHAgiBa4bZ6VuN4bfIAL+zFGw+jZDGbtJ6QQxaPNgZoswfwLIylzrTpEekWwFBYzeGf7JImna4hBIxtk0IT5A7YAdU2Vn8CplKgzsluIOFO8PbCweVX86WJzlZNBYKgz3B9n0CdBxSR304z49ozhPSInDXIzonqXTx730f1kAJ40TbhTIm3PvI2hhw/OWJ96aBvtpQsTF1uUcTAnasAE7DBoAwAAAIABJREFUrelTUpLeeoUc0GlExqZEuWQiNlNY+XP9WdX9Wl+bpc/ldj6hYzixXD/9ZbgxW/s2CE3iwOpLPkeeRtdCaKAF7aFva2gnPrp+hu/bRl07pDicJtw07PI/YcR9JsyLxIZQI6544LoMwaTJuPSGApqRR7uf9HxQQis3VDToCMN9BN84SmI+x2lU7PpCulvADzeZ22xIvpf27yyAuzjMAQuyArPLAkbVOA+1MUMY/aPtTpvA9FAaWGdV0zv1r84Z4uwSb3Sb2DlpQ5HAIG1VqzTN0Rg/GNCfu5j2PpCFcgavToF2Ylg1QRhwYwLtJ7ArQgreuzkrovAtCcT1gW5dJ0vsSqNLCASNeRPW6LwM69494BGpegvM0cir6MracHdYPXGOPVR+g9NHwGXSalBCIsMYGteqYd2tmuWcmVKYiYd8ZtfOArG9xjweyAE3vQJUdK0sVDYEOMxwFwXwlhM0osqkceiUAMXNqJxO4sMq6aSoQbJtA8ZLOhwN6hhVfrMhXFyOWyATaCiaOHVCYd+QhGlU6JIQIKJ1fFvNCAco+sOBZ2IDYz0+l7HlQduzholL0IwHzHSUMCMKe2TRIKta8giaMzYVFLfI01XIVnPEQR+KFfnIdU+c+DDSy3G5xkjUeHfvLsf6phZTmXyEr8qQNxr3s24b9ZVJRgF1F56YDg5d4ANAe5pfbOj8ItLwwkkRQvKcuSkLU3QEHz37vYoKILfP1jB7coMDcsMuNrIsqh2m2XaEDV2gOl11Z7BOmr4iCcw09+yVtNxdxQB+u8npPSRr7ia9OovWjEawWC9KxM/j0DPM8CnTtzGwmkb6ROfB1lXWNqMlrRRPORIzLL3kMFCX75UV7CqE7hTD1pGbKMM6dN1MQa4AhrbPuD/2jv0WRMSEcpjgYmKlOab7PjqqC/eb1jUnEawRHBklILvHOrCC2a3iNLhdgw3dZahh3VXzKha6W/qrewTetGHc4Hu4wX+kNf5EirAmd/ielNOOHR+/zqIR3pM2TtKxMXeGY++tMoAH1d2SFZuj4F7gpkHFuAHhdxI7voPJfCgQfMzQwfLPO4oaetYD/EHmY3YaJ03yeLjUgMzXknbEpj5kw9+DjEIDWQuwP3R0y9y2OitZ82R97AObk3iakUKJLPY2DhluVcvwtjUPfVPGhin4uccGFR12VhHEdaocBuxdPnu2TvbGOCUX+pZzvtd6OMhY97QLfAuxHOXee0x9V+TGwjk4/O5yZu3Mhxt36omJuJtMa8Z4RDmRCK10DX50ZfvDbfV+stNigObKgMsInKbDQBcn94y4yRqLp1h3YamqM/NtCESnj6HxP78XG8LE6irmbgeeB55LimjWahKgjhVkRcHFuMOYS9ylyZDbI4q5uDToiqOMyRxMaanax1wWnk0NzXGlua/Qrwvke9g6XV0fwabPnFP6kNsjKveSYDAObkgOd1Q5NhHGJnfFAvOB8fWxe75Z4DaiajZQpree3MlqzlD0G0UZtHsDtp7G9kEZbAIGS5tPZgD4bGigiErZxsQFHsPkL/2YxYzPoZ/0aH0iQ4YxeNYpk1g1TgIaB4n2kHZG+SYByBtR05USnBPR9euJ9KJDGBNFxxNsusmwggUyAwb0NLu2B32Ovh5/4Kt2fiqOg46iABDRCb3gJEyTBKFup2B7T10JEGxIT7BFnTa2erK3Y8RI3KUj9Bhu9EDntyuWuxU7DKMTet6EN09KLMynXWSetfUwRpUj2P9uOgsIpqaQLYcXovfjY06LJo2QxA8MItoxQGrybYtO+Nzt5KnH087Gvnd8zuWogBd6Dkwloe7Q4Vh9YEiyYUGkt+A8ICx5CDuSn7ggV4sJbeFTmQBsBmaYxCmgtrY9swoaG3bduugJ2mGP48RZ7OVuuo6xjS8bCEn2mhs3kqnzjehIIRfAYWNGhaw84QIiiuDdZILseuVbYmc88HW8YdPDSsvgxuSuMuhrY5aTtr3GcC0dqxtvDU4gBQ1cQE0BbC2pjhMZiM15sIkSpS/Xq3yD8PG1gc7wkV79CBVfU+cRBEY6haqoFQSasycHB2icDi2NIuJEA6AZ6HRjcTfAQBiWcpwYeJ6sDjNyylXqk6p+pkj3ZcEu2roj+mGiGB8lR1CF4UCDSnW8d6EKAsOOWo7ytcOUTFXfvDoPLuSBFBi0daK6RrrJN8H9uCFcZqO0W90eR4HvzeqZe6TMf9l437yxanDvwKZvcZzEU9UsKnA4hY1kEdfcOpyHqb58DYKpCt52rXnV7JZjtk8eNwj2y5fjKUxH2LskHaUYeJSrfnxUJxDCnRgXEXBGVDR9ILA5MRGfG9uvRM6DISR9RF1ELRDxnNM9xY2N/NbTDlFsawhnGrguGvgLW4RB3Nqs0LNS0nN0QQCH9tXa5YmNvIrFY8OIixFGgy4hVm4JcsPuu2cv/FLmdXc3dlhto/w2htMYX9YDcoHqnm2wYLVnBWWwNWEUm8y49aga5XdFiRygCz2u9ebIysAQIaMxJ9pcnXUxknJVbXc2b1w0bjaxsRw7U6SsWjp3t5sfTjvb0nlFMS75jGaLyLBpqV4sNPiKQ0St9SenpT+WN2QjrTzRzQYNqAfioNpQd5bqWbiqydQ85DNdrYnMNShLXpuGYh3ieUlsOw/q3H7SZL3DAAJwgkjFQKxmGssRhMW4rUw5/CYmdLnvhIYi7wvgbojgy2B1W1sdQT+qR3D4wSKqQTucOMNAbGsKak5Xd80KGVaHylPsKbgSZa0qQV99Gbry1K2/3axhFIRD+O6E4tDn9sX+iR0JNNuE5s7RrMshwPYxXLcDK7wPTR7C6LTSUzqws9hSwCY3WtcZwd7cz/aTV0wznFpshZEd2mAGu3O+nS3wyDpJec9A0GdyiJkvYkwl7MEeAMyMoj874VLUTlLWqF0g99lskzG5jNyTD6KSMPtVuuhqeD/rcJcHBdBJXU2t7T6FcF8U8yIziQQfRc6bDWvFm0lQXItE28rlOjEpAJglcZnLd1dKQVxgoB0CNweARhPGAi1rXoNBLJxPtlC0wCfIvmwUmFPK0N07VhcboxOSHEYUQfponYx73viIh776g7VW4jjAuxD8lG38D4R1FcQ2sbPBmDacsNy3r/zQiNVYmQwi14bsmCFcc3CuTzLctvN1K8iaOhmbdiaSrY+gaKRZNGqHqQj0154KqJ0tKqz6m4/eva3C3cIAVag17bvcGoMfSo3zZ6QnkcKxE8cA1dQUfloNAt+dWWNCjRxTlsJ44SAtAIfvNl1PG93IgcmV+ieJXYpvnfq+YbcKo6GDWpTjQZ0WY8R2xc1yQsEYSM9NmBv7RMURdbNsZRun0+d02Chig5thbt2W2XEkD6JcFG3Em8a1wjBj0A0deBbzCCqmF4rJooOp5hd5KM2dBhamNMEAYG0wQMR3QHSg6OmfgAzZyXTHUoId8ndkB12ZCfJg11bDOtuNURe3fq6gPZwsOzs3nyp2VtpqrU4LSO6RFnqU3ejkJgJ6QloyjocZAbSDvBanlemwOtclCZmyLGEg8lr6l5NeHMqA6dxC5K1I93yakLB9POejcePyu8MM5s/a2mWtf1jmCtklTQcRhhsImAaIwkPjiBYPiNTtjFJVhnZa3B3qbXTvwLQfDI9tFYHxIpno3x/s8bLmQuiCcBYwdc9BTaHw1eMShI0+qvkRGUGqP2ey5PCzDGEurHj53WCAt3C+W2PuA9hCb2k2ALRhP7orGiC6OUjD+Y0bXYxLCgZ5Jvvy1OGtx4HSU4NMA6lt2o4uVfNigZpTei1FqcUgm9wB7jY9F5Llkui42QLjqetU40e9AnhOkW0IzMdGT/SUSN+/9GE8ZTqqg1MWLJxdGJOC6eJga2e+JTbYlZ3XIbqvSbJnus293pMIgoHHoUjzL8SGKQblZwB91KXLFdSmf8Lu3bZeulwzBBYs9nR9kc+/LQEvJF7Ne10acG+O0De2Nh4kOvhA1zjRjiG+4rVrOtLWWreHga5EDnzS0dAxnhrQRVbP14UAPVvXi4q9TKzRCZTMEQ74Hh3LtJEBqe+TQsFOB4NNBBbUtbKL8jDGPq5KRmAtplhXxed4i0RfWOnCutyh+Nb+uc+6o1qJM/+cK4ftVt6aR/h4Pm7PDFHMZjVlhqISMKRdIlpMy8Cmc6kMFDaxmM8OTZ1JDmLmS6qw2kdhBnFiRD+G7tnYwqnrkVS8O/rn8jin5023h1dQJEfQL1grLHujhMs9IhHNZorijtDs1GGokmAwas/uA/Nh6cQh+Jngfk06d97SSXcj6nNWWWBzu+e4zV5FPOkMXK0iBkrXAiNHdO7EMRQX9LfoNpIrTpEYTpr11FqXq4H3EF8XluxHw/2/V3GPm1WzntFhGKhRBKuF2KcLnIZJAZivOBxWOK8n3VbfPRaUxIvCHpj6xDnAzBlWx0kdd9n34jg8A/35y65/I3HokoZ7GoEfKHoYxlo+VCSxvRaMNh2tguQ4Q+nb2G8Qv3Ml5rGp6VpqViHA8yY0DjcYwuc9af+5uWOZ7QsXcM4E8JkuxJGTJsv+E/PCyLKEjqS5NlYh5E1mshbDy9gwAFuc8poQ6Z8aJhsgLLYcxXDeSW2hwANNy82RWAGdpWfTf9Lupm5egxFewFQYrMbQN3gaK7iIFc2oXi7OAvNkzSYte2PKoz7vOYTZCHoaFOKmZxfJNvKp2fbTaZ176gEvD3ZtWxaXG2IjWOOapREYnH59tF1ecYqrnfgI0VA9TnT2NNn7vk/fncQCxalJZhYNCcFf2LSJwP5S6QAC1Q0HWN+jAERuEkGyNBlH8IPq44nZZ2dlIZfpz9qGdhf8+e9voAMy5kuUhQhx4KcR0z3i/upGBOjSzMrAo261jCQETj336tRy80X3qnUN0MlpbGZvFGaORlAAN9/lGLnA2D5CH53jvsPgJ7SEgGKCW74O6pkzffTtd8q9miHUA/gebhXMQWXJR1iAyImY6zwZUd6E1OJE4iHckIcrdheH0BYHrtFwS5gbi7g1Z9ZH1vHoKk9G0P4Kegh1dIuQLYH1XEWGcWKuMuh79C2a1s7HJNjrA8PVQpVQiPwT5j5VJtPtJoXVSc0Hx5LfVPgemp8i6FnC6UqS57HAKlNEgO/2e5eFwyxg0yuiuQjoCE8LuLdPnlx2Vep809ZVKYSLWQFv0gQ4Q/HlsIPPGgzPDncpB2dGnnclg9ki2XUDqI4xZjxubxfWlkqG+JAyJDyc+ZeQemO6KhgQiMZuji4kJ/Y/heCUr5u5CX8K9CtjA9VpWdnhWWma740BnfV9WhPWciLheaNz165Fgt0hZA/Qu1HWttP0DJWe8KZdkjkwsUnelqKT2KwYIuIFpCMCt8XOHGIEGuCAU+t9Q/jI55KsrmKAhb+X7fRqWUFt8tBr3mA5OlQd42HDQTQO2Fqb9s9xkzRM9O4RoKC9uLMRGI8R9VXN1bHcR2zAJGrLFnL8Ocwc3hugJPun1uIYM2nrHMcwcDJQA0w/jRliXujNC8rCFKMHwMgZWdSgQQWUTiUPhL794jdhHsWO6vcsY++mRkzpXMm64mUN+JsPOwqOe4a850hm8EI1j0eMiP/UZYtBp3UtYZsFL47a8TvRdP1MMURByrwHNiBADtu098XpUK4bSSkydqQgjTXiekUxTPmkTiXE3qeP4dTjms/qZgmDXrOrHu7GDIE3itSu9sx26TGH9R86uKFwahN5g13F/AWUjsJ1dnIjc4AsI7nNz+TBxADbEu43Kvpr13G9b7ek7AC2qjemvtWSp5wYm+wje1/BDdnP6GjdTol2svsKFMAsJdEs9zw7feAJHxkh7iaY/MYmrdOVgFKZDIExiAikj4vsqYDr8EPEezBvGf9OMogHzJIhusciGwBz7Kkj9pHDRNeYO1R1sKhNPS1WwgiqRNI9KJ2aW8ONe7roiHiXGOAjjEzbNshD3oDYVZ5lad/yzxBMnjzU9ISh2ml42opODQWaCKyuwbsTZl8VJWLqMqvDYWcBYY7UA94KWz2K7GJEZxAGAW742Ue8eVsNa5NFyhXOcZhhZOMuJC1cO28nOtOrYyBj44OSDrddbBAt4wqyw4BcqFEBGjCbRX1Jc/bLglOWMW7s0DMS8SowaTzgju7jtWss9T3mZ13jsFtmRDIkzqzDd4ygFWyejhWq5dd9BqPvPMp30MFj7P9HzLC39GVmlrGDKTcTmPuP8E0sNn7LMLM8cXF0jKwh9OdEbEy31TlQDoOuq+kXox26OohsBZF7nhsSQ1C8SiKgq3RSfdV2n4l7ib/fEi2z74Xy+Lly4meYvWKVwDavIw9HtRc7xPAzCcdNAKFm3055JCiX1nuOsUZ3MvIBWv3lMNFgsvOPN4uB5IJrWCcTJsuc5g2GEhgOfbrHYpjSADtU3jN59NA8CCA1U/AAdtyVDuaygdce988t55ddcZwweG6cPtLufAjXQI91tuJDVjcsGF4Qgrhu7N+xxsZMbo02ZCIFWNt8CRLLfohhHEO28B5f70J0JZy7UBmPkYWC9I6TcAAAHlwPI3+cVvUmxmPYEU4rPeJRvQ3z/MWAO3LIjPQc5Ir+2fzAbHsnEFvFYAVKRWtJ9aZBj3QgB/QEQrRgYIy13kMOiTKp0XHINERZI1XpxIruLKMi8CnT9/TaaVQqlH2uteMdmOH9CqFzkfbmaLvp8ioLx752yhDiCNFoM8W2NeVFAJGZCWfQoF6dkA/Z5y1bbg/ssaeLXXVkYGBqiAk9HjrVwcnD21bgxNlm2vlUEmRGCxH6xTrHKQ3OIQ+PuuPrRPZyvZYthkNagGjUxRIOQoKE8WqsxaUPZ/ortOLJPuVag8fJzMGBZQpXQ5lnmZZaYxj8jBFOJHNWF7O9RR9BaZkjErVJYZOZWDCkKNVp1rEWPGhSIAsIYw3piTmMsCrymJ/992Upqqi7cUKoYxUOj6ls++57HoN588fMLBcHR1kuX7rAImKt3YKv7eRFNzOo2DEhelD1aNVq3nBrbB8LPgfAXgM2QcdaIiCWuXuLHpx+dG4kIh9krWWYhksVzSn+1u62CbqdUTXWmEfHx2rKnSh8Filq2hdO9My6QzhZgM4P62l11fn2sWgx0/PI2Fpk5Tataz5DDzSY+HozQbtrzaKN1IVcz5KGxFY2yCcE9o01ZyNbUlazrORmCYxmm+RJhXOtkFuc+VXKAteNBeeH/rmVJYKJjXiQ1aQUFEKCnYGbWBIjcLr54iJGjSwwnOKMJtFsNS86qnZQj9c0QrWvO5/Ua8eJNjojO9r8qET5DQz8H0Wfdebd8lyaZ67/WYaH7yLT3s8CvrUsMB7OxlFhXMvpf2e7lRZBYxAcYHZfiggSDjm7pKYaY9oDgFT+54wwyWn6rgBsT2zUjvmYPnxdENk2XovKVKw8eiG3hzlMApp1s3PHYWjWDUvGjd1XxDN9N47QE1v7EHj9mK/fGcpt7DmI2PCHtNkWiUk70JgYVG5UmKMVhnYuyIF8RA5nXGyK+1ELuPAS8/K0wuwjhHeb7MFM0jUxHR5igZX04gNzJhfzA8BH3+zUyUq+0rM65LVLQcJk22XFyt9iZrL01zHA0XF9kESZFjzryg79ot4/cL07nFCo3WDUv8yJE0RTi95DuqG/jq2M5DnZDmmXgbLlwyHYGcN1VSOGmcg5cL7Jsxaj+8ZCxFWofn8ymK3/Hx4xAr9y5idvsWM5n+ICspatQys3nHi5lYB6AKO0w4uuzNduAvvVFhVUn4Mt1/7wwS6aALu8aO9wUl9Z9VBw3diYOqWz+8vVqUqDWH3P8/tEW3afgml3yAfMbOZkjsFdHI8VF2YrKYFT6m0DUSuwnEny7QS1ued1S8j7fTU6S51iwiD2kG+iIrCBg9ZbYewiV1nmbmBzfT+zKj0dLdOli6gE8kBzHT8F/RzUhSalgh2a3rje2y4wbtzI4COIEN72BNzWUHYu8wyBVkGmipm7N5/folcyYbK4OgkO1mxKmQVFrN+PcB2oKn5uadByPjJqPKyUFKNMTU84jcgj3K7tiQeCZg92A9XuY3/T+i1hBFJHUf39eTAZNUJXChraPQO0nE5/bRPzzWgHOeEErRoZs+tkWuT0WuxLS2cZ1wu5yThAi3EqcQK6tc5JgSvochf2CFIlidKuDLZLHml19H7O85rDeg4vS1a1wARja3MXHSAfX7W0wLP25ga38MDBD5NxyhRnxi0fivTKO7IMoDGO6DcqdlhfvBeGeBsxcq1AoBm60ht1jl0U/C8ycHCysCJyHRtaL0wQLB5h206gyWzPLmLKVq5IDDPPK5WpTL6GB7fS5Su5w73yNSLq0YFdN29trjkMJjSKVGRKO2kyYGKC5aEto8P3h6YDWjNBCJeWKCHEAQfF2mrRYEPdRS5v8PvfTy90JhEK6Q8xtRVDyPz1OqaNvzwhdU9CwMvNp3/3Z7dCjsYNkdipJJYrSoD/5wMmqv6tM3To15TJpFrnt30OhFfbukkb+msYCW7i9I4c9Aw0KzSEE8QHQTFQMabnmjDIhg0NfjpcbeQM317JGGKQ3A3d4unvCrvWTS4kxQVpqjp4zDVW8pw3kbj/jM1R7Zmw7O7jkG3BV+4a4LJpKt++p/Bat0mcpMloo2nW99Obc/b2vN9ZXWMppNoSrK+1PiOgML3fCX6BO/sgtbtsYy/urAe8jJ3dK2gGm/3XxAo3WzSWisGOMWCFx6w/Xw+w6NQHJ40D02juvxb2zWC2YGtBS6uGMSYAYTuuf1evJwxLK43cxLlyZtl1Eao+J6C50QMSg4UA6KpPQ5i6et9tTC1qZ/CEIXRH4kV30ijDQMcwxzK0jimNkgc7bQXnXEhvrklb31NZ0MjV0YPKq6XFSRc/Rj5EFCl859in9UW0bZY5W/6Mbmy00YMQ+ZO8Di65zcqCSSG1hrcLzIiydU+gd4pon2TdYQHkhtx4iNJ+KAh5p0ZF7Kbms2WCZPp5NxQYC0qiL0GVECpZT7LSsMllyhfdofgfzjyTGh5vxkpSekbYLgCHQ0QlCKHfw2hLztZVIXBND4kKHeP5UDqjfNqss1y4fnWKxk6pxNGJfUBO4HKbwEgR62Ldv9ZFwHYqDWal2KQbGmkFmDmojbX2+Tw09nHBMlzpeHqQYngxdm+gH0eVudYNb9ZxuiciAoGRszv9MJ4/gw1OiAkGnrR1xrspgAFq9jSvXXEbNtdv3RPmicc2VihO7tkQOpm6zoISJ3k8NOoQrJm2hXnCXytp6IF21GSK2i0vOXutI/3VAWRlFsPOjRwd0v14uqG6hZFQrqLD4MhEskZnq63HvXol6gjE7RnXzVQs8rsJNiqLGva4imVA1eSYHOoAlIuyESuPnWI5pTQzGRMVFLPXoR7GbkFrNt1VKcI/tc/MoBVHoFrch68NcpfwSE19wVqVAwQm4TADVKUUrR2zuFdHaFQ9KvR3Wwz5OJyQiEmlp91j82M7TO6xmjCnimpGkKAXM/tAJYZwmaLQTlErULh1YdwhI1X6oLN1drKHsHsEuBuZwgGTwMBktESHcdymjeiIyV4C0LEIJ+6MM1rFc1ySk5cwPB5z7JCb/A5GxDAsxqy4EC0ma73v7K/zdcJeIEdn2dosQ8noiU6sINfi0giVaXo63BHgWqzUvWHWKKg2p6Uzk7puGBv0jGZ3rcYG5riLEXgIrpmoxy+3wOvswIZfV1/jwax5s+4oGV02yUOvVWpDnsUAzS2lqswVxkwJTqaOSxwcD6ha07MyzH3CoXR3Mx5ODJXldGxZA6UiOxwwOsYpk6qSCd5YsJqu9XFjgIkhqyCuW6sUG2aeJTuu8QhDr+eSTfkwoylpx2elmGFncacAv/Tx4/B1LRs4Q7fSl09crxVyGc9XHfHtkqVnUCf1DmoHUaSar7ASTgzv63OfnS1QyeAG3OsIPHVrt+fYRxAnqPGuxrQ8TnFCZs9jiG/mZhzFeD4yXM7lJLbdUFYNkUsaR2nSAbWoJ/oo0qFMG5NvEYk1Fdx2PWkFYmq9MCTvIHBQSFFyF2qOnd3a+9xYX5HxgMq/2MXnYMZBIewsK0E4cbe5GoqaSF0tb9m6qLaBpIv9gZmRyVJ3qSXCWw1T2rpBOQszo4i7WRwttkCfmtzDkdKRsQ8BRDtYOE1tGnYI714d4tERDCsgnf9/KYBbd0UPn2lHyc4hOhbg2Zi8MfRB8A9ER8PZfIKo7qqV+8iTgr0absY2k02fb1tuCicX1kw7bvwkOHQM6PtYpsijmqVC9Hls2eurfITjzNTlHy1WAHc0WynuyqD49TkuxVvyUyMiXEmthWgZJdC2Q56N5Bj8+SANn3ZiNKdldtN5EV5jgHmaC8SIbuaKGcyea5h+JlQKFfIepKn9/EBumDaAHZSt7roDMmJpxaCXF4cf74QIvvw/+3bexj4eSpkLRTqHdv8Mk4GPag2g5qTUR2PZToNU1LhfOdzOxjpzu+OMfZE6/j72BsdkCdomyjXpNgcCH2DY4JgApMFfrNnWD7KZvg6VwP96jZSF+wUhsWnOajfJD5sgBvuDgTn5Qw7Dalc2DKNjasQG/LKDdHGzvH9EXvXROS+DBJirDsN6n52GDblNYGhau8AbwQbIdXIPMsdmsGuel0LctUXvQ2WDLTF3XzLoWyzwY9/VK/19bSA5qYfkJjdjg5D/G6gs7AYxH7XZzbWRsPr6H/rIrfSLbCfJzU8/4Yeg6daDYXWyhO4MZ9ANA9QMf0I1PDi6J7hmkWKnzy7pk5GaIyzs9lZo1AAHl5i5/4mQeqb42dndLFgT6bREv/r9CB8zWjlLhoFtzQariGMuoBMk7rkvtNBziDidgqP6NzruiArDUzugNtKn8UdGWM3husQZfTDzxgw7wkRuYpjq7qEAJiU5PRwziOEn2i0SOY/8cc8YDbPoAqYsl1oEVFi8hy3He2KA1vReOTGk5HK0AAPjYLyXUy4oILf2zCyUfmBzinks1x16CJAX8pY5tMl4wTYZQ8rVFdc5AAAgAElEQVTb1QeOad0V+SmWrIb5VtPrHVFMa8A9zwGOmKPuJPSQ9wELsE6I7kRdKop2jagbuMLyehmVB+cIvbaQVvFCS+XDELmJY/wP7I6bbrq3D+wGp+KqzrV/1zCa5ZhDMT9wMueU02TOy6u6AGLqlupxfl6NleTtFbkBfeVgNT9WIbmh0ytg5WusG78FvOTciRuv9zR+RDRffu5zrJ4uroPgkWrMqmthxODuLg3EWfROFi9XIWIXN2UK2O9VNwghCi/FrYQ3kRCYbMWG0yMJjWtYduG1jK6cnO10uZEm2Tl0nYch5xqT/e/JXQvQxRqQSNZcmvFuGRNhLXM5hiZVjGljYiDWaDsm1zTFdvpeDvBRpX0Flha1NcMi5rtKYKYNpP4Ueh047s3CRnVxFyPwK12D4zzVjCHpTYwJiQecRlsfW2nxjwHimhhUxczJ0MyZthyKGBOph7uw2INSFUE6MxtrecS5Hj1MJyVsIOhpwAsSFcaJ6gDjYzIkdGhVYLdpN4Ul+vXBANlRLEeYokK7rJwT+qZw9eywwqiVe2jrrFrNYDVso9pWjnZMrMD18qwcbLCATqgQs8Y899WJ5qXgDDAlJiKG750oMCRiGEf7au0kJCDMOkBoF0v7XrfAwl1Fo19uJr/dOvYxzCv1iC7SgFm6RCIWxXWkAxgREBpMPoHCR6cE9y2Bd4bEEGEu8NCRGrcegiUQZs55MYY6QsbYiFAyfIly3cU49YwckAeaIw6T9zheN+jjmeoLByCexqJz5hipXS2kSDPs5LVYSNbI1OifpgxDNAGn9lX0lYpbhqdfWwtE7+g3uadnoBPkczhFOUgskAMRFu/BzgAG8mknUqUci/DXdW4vRcB8Y9NrVyPlYIbY+0cioRGAsXbHaA7uyw+wHbubzm8EPON4fOh7AnzNjtFGIwWgxw6k24JAlcZYC/RZ289ikO8F3XGZckIuNpkW89HgdQyXqFq0tuT8+l1rHmwcOiJb1Ifja8qJItjl6ItoLHXIWOBd5lm6NFsCuhGhweBoxWvKtQ8zsPOincqQ0FnO0blshRPoLSYVtzrh2tTZ4bTZP52FBsyt0GPjVaZFPsQeBp4Jrp0bcuyG7bs4pOnEvjOGVCjblkuLOFbb58am688HHI3RvhMG5OwAMSCqfGgM5sO/xS3gVMteh74ShWrM3iF+pYl+IxJSraTOMRCCBznw7T8rPrjISIH8l0KFpFkkboLSycLCcn4p4zyuGyXwhb+tTEg7teXcEWMPMBIH1vui21K3c8GKcCJXV2E0uz2YduFm1FLqcI1z0yAf+6bmHJzIqaMiOn5MA+AwWmstE9TI6oWHP7K8S3Jim33sta7fc3VXFOmULRzXniI3qi669xD1sr1cTKRmQh2c+HeKFDQF2M4UesrjvXSAo1ITMwv8ZcpeBtBFIh1r8MFzo3LNsoA/OeElqJBi7HUeZIQEifeQc/k77DRADL9sGEmHR3UfE56wFvvLa1SdLxwH72vHuhjjj/vnQbpN7k4pOq7KHOUQurw42c6+FlyWZlaHcO4Rc5BunFm2QMzG8M767Fi6gRTUAsuSAieaIKIwazJwMLJ+FfoTv5DfwwRqxucKfx/GhLMMCtFqaGJ6uBlBxf1ATMx4rl2pzVy5WYSG9JHp0h9v0Y1esY+JfBV3gLcY3h3x8Vis8KG6eK4eMc8ce7BAd0XuCWMr/cwZOhijlrrPOS6EZghpyBJqcPYtV5ZSe8cZF0inYjSPww12VW5YSeoTFjbdGlSn60rWbQYwBh3PNEBnMjKF51kYVuahPEi2DBhxsCWz4NA1y9WZtq18084ZzsJMAkz3PfyoOTSVueiUfF+KcIxROX6XsO0+tG512d4Di+AHA+Y5JqjMxmaNoZapz8y2lSI5OMXOkOeqsb4T1J8d6V2wwJhX2G6NwLc2PbQFw66PNx4kk32L7cHoWuCtNrE9kDomwm5KVEtKPUcbDOzjia1w6lS96DDFvGTP86hZlIzWo/RcE80lgRSOFrh0PBQIUQlqHrko+6lhfnpEaqI1jWzxkN0ggN6xskFUC8gHWhB7G6eHnavT/uyYFOgBH+DAQFOS11i+/1yO9fnCBMdKiGBw1eOW8hemh0+pD6JmBzMkU8QwFgd5N7iEd8faafXOXxiu5BkyPdBw5+mAqztahQMfYXj6Cn4iblTHKfsFKFd3sj8cA+69QF+eN4EKRGCBEavwjaxWY2oZD4WspulaLDD4SfUYSRs/GE7WR0GrYAYtiY05ZJauoZj9PzgiF2QH0hPYXh0W/dpJRq2vJapBLUbDILTQHReqW5ASIkM+aB3ycL0RyIPs9x5FnI5kwqUgQUOuZmyQ2dHAV95q2LCZOi2eAehweRIm8wcOXVRMQLY6OTwloTd18ISbKQ/j6UXQDIcT+11Y/b0Z4ucmSOZgfFlZlzeCkZknhmMkRmLgAf2N59/YPucJ9iPPLG+zGqvF7uV+XhKugugPM8bxmYmvVCR1r4i31TGAW3SNDtF1drCZC9JwzvM8AJuslgrtQ4BsxEmUvp8Q98bsvDk06RigZYELLIBctmdoQmIGux7QgxQqRkqc7VCHacVa9VrXIGvEEevJIMMagpwEXamYPZUAXvwsMVjlTKhyPR87naTOPNCtlo1fEg0ktuJ5frKMTULU8oBsyB/uMBaTN9q9XRG6KY7mvnPsNsCRJvaYKXsIpFG7nqru0cbq+CITaytjtxCANcJn9Cbbb8utuYYhRzM4hk6n+/X5zgY/QE5I5mlS2gtNRfKYaRMt7Y19sNEuzR49HS/dfcbPFtgY6m7yYrsFNvy3QretBgFJ1JSRZb1TqQZA6OYLtjHSancF+f9mgBD5Hz4myvtp9wpilzfdjLrtGIevMjiiHfscQBbJkE6SblvHtIunWeube8/ddIDYkBt8hSMxH+gODTu6MoVmuc4bFxy13bKlkwvT2xnSPlanGJmtFIpuvyFIe8BAThHB65RuNh1SkgmjCpnXIbG9zVZCQ6EEPMXG2JbBKmd/Ylf+RgfqYliEC/aSGZ3ynMBybXga/PUPPGrvAXkVrQt7WbEOxnaoc4mJtcAy6U7N4+3ZGO3kZgYbCKGEaWSFRUA3bhuzLUQ/VOVaWT+STwQHu69SC5/NSr/PJ+syR/DXHdnBXB5NajzU+d1iv0czTVmtwSZjgxO2iNC4cug0aG283vTtRBQ2kpmaI+zjIfDV/FgdcVMXjtShqkGDSY06cIwMgj3Y1umjmAgJJzt9NEZGHpYwv3VqCB/Ssw5mTEOiL2xhY43rCMuqVF4pBCEOIyVRBmHG2YB6oKZ4OlCT7SSIKReyaxgj2sujMeSTyUZLombNn48AKkgikLgdyRM/cEqP7vvw0/KD6AmJhmf38HpKljUSgbkzDHBb9xhJRQ98EjOd2e6OEw8Be0d3EBDw7oTDWM5cKhUPs8TQjGwpn9gYGFErwLotcMoQKLv7yX5HuCS2d+zWQee0tLqVQQDHPBn284p5ncROORZLqm6x+oiM+ZnhAH1wGa+H55xic/JAQpL6zPlbCiRpNji5OqcjtrK0NKNW1ThG+8mcDtjuLhv8KMV3GPnPrGQOh1vVGCBABRTOvfd4VrJYycndsONgSFgeFeHbN7nWGWTSCdLrtcR6XuTF4XCFvstQpOE08c73MWV9FwvWO0LrAs/1OAq7WaOzSit85axkK44cOAJ4iPkpbMXckQKRD3t+OdoDu0Sr8ELQLIa6IwomMxwRcJfgTC6mTl2dYDRqea+9BvsQDbDVZPIG2aU/R1+PZNJaWSUGNQD9/Uf852mLhkOPh0bw69hr3nnd3yuCIxPO9z+dWOCFNXP4TslIgZMd5+oka3Acl0MphewqZk+to2bSqw+iWmIRalcbYNCUlCSroYioUL0yoJmF++19bxjgOLry8WQwbpAkuQXCTCxCYB+oyUlv6gCb3GDI6Tg5M67cOF2ZYqIiExTJ6jdXimA550PoupMv5/sX82TDIwMZcdMqWz+YK3gBRWSRYO2JlmsQuenujp3ZzBc+bcOwxibcuDlOfaHKjhgzPGrs0pldHKZsqH4ju7oE4SbkeCMZ5hp0KwtzQAm2OyeHSCIw/b6RIpx12i07TF8baiycqatkrfspbxUMp7y6SqscS5uDybYN5mEICQm7Jwxw2tvlK5jnuR/l8kQ3gfPVTBOD+0kNy9ljNCRWh4OJhcbaPZ6c7c4OQMZmSpHZqfxtST46G5jtkbyGY1wZSBMUG0c3sB7hdkhzGkbKytuKHqMLhk9VVDssF6bn5GiFEUE3aE4Hh34Pw9rW+aHzNCVoXV4Tht9Y60F6EvZDU+NGPG3O8a7dqqP+MASj687aXIebXHBW95FENANLI0kvhKwZ/z4Z9+rZPOd9yTaFaLLKUT2Xuw/EWgsiIN8Ys94NBjjR3XwEo1vj03VjKp5XS1DuuoFwKW7R2yYLUe1fF4QAHBlSc1JiG8SWT2F7ONi3UXbkDyHsq8DbKiqteW3PjPBklMFginDqJ4k22jrOyF72OUAWYJP/tRxeVdJgWdRSc4SxIiuxCV5Pbn9BtJBgdzkIpSijbSyhdyhHxu0gYeJZtDOvSvEw2V4Jey5M2LAK5voatFA40/PimJ5epeOwNP5VU90ttMgDTjAM+xwf+OYw5uYhtq1EM3WCBDpxMpl4VRdA3ih42H1At2bFR7DCegGt5UaHlpgOCmwavv73xok9bCm0qEF0v2dW0xMMPhGcCQ3NeBVPw90BgzYLVzxICpDBrbyG5Xm0AyHNFJjQmahxrie+BSTNzieaG7HcguX7Rg/abpTgzUtqd8pYSWvLsd9qoHZBgbEZbhfOMkfhtFY+3BFPe3vAslBsV7lSfuRyG2bXDxfway8PdhNfZNA8qm2hTNA9QL/H21QzJxfGyvgiRYYFgvsmQcbQjI3JKV9hN7ghQUnvwRhdW0nCW4YVcZN2n07GbEI91GiYjgnrF20b+0i+Apw8GEmXWrjNoxDUj2ql6mWM2/MFKz3PDBxUw8fYXtDucnVMxiqXh5/Pu54cggJW2LiOVBrYpOOutoC2lcKoTQLccoKnh843t0bAKAysTYZ02HPruiJgkwj1TrtqEWGKgZq7vmPUt40RmPGtqx/i7w3rKt1Dpqz+cTQ7xobzvlG82HnrTmXdkx/0rgCm8+OtanZr6H/QV5CrgAXuiLlnNNfhaYslUnGDf+lSUu2kkGJlbrpdVE1bBRBSZ9tBqzVUjlTgKShOJ+Osm+5hzaEQhP8K4I44qURm+vH7nuEU/7KwNZjOT4mRlljL3MCWvtQkOgOTpoVnMomGHxbTRiWzC256Tbfg1UQSjvm8UiDFkbvll+w0ROxEH6ZshJgqGHDLQlo0Skp/nwPJiPLop37IJThk0iGBOarU1fyeO0DcaOOAfZf4Siyy8u/gfBOmOJYT+bH4BbNiYmzqMjpBDsWJupmfluLq+DtgTuokoyOdjeU6+oitChieGmJqOgzIcWYPo6RBiDNMkdZ1Lneg4UwIN+zeYeUci/73okMm5zYrSnwCuyUxYr8HA99RPGz8fW6Y97ITdNz8tmLIRFcaBEN/oLQWdhj6VaoJbxpd3Pr5zeEMpqFVjaJLpGq2l7YHyeeFZvCKcH0+mhZ2A+H7skNVGcyDY/D0/zF3etPPSju4oa4icD6QfepCtfH17Jz4bAG+ZZxiiEGabvwSeyHSyAe07tSddzneOW6RfnQRoKYFwR6GlE+YzT56EWQ4nDbIi052aLFNjZey3yocN/t09FzaPEzW+OxY33o24cccS8gIDhIhGuthYWaZwIY+wKAku2Qak6ObdsmLuPrICWomoTwKZBJ1aBo5KESRICFmv75MPjwxbTbAvMUIwEiS8naA6V3GYhw38/xOJ6FGx5l7KoDTLMdNy2DvlfsucGKVRw1ThvcIlmLzCe1EZRAE+rGlmLcPvxhDKmzwupoV9L2JHQJAs+JXUkKXimArQyxd2zvxRHRqhGm6Gt6GesooKz5t4FjGa7HROZ2n9Z1W7IJ89RE618smj2CW26dPQIav7rVVLnCQ8rF5gFFJFDVWbScjR7MC7UqpMhz6GWbXkXUL9zmLNpMM0oIL326x99Q8Lwe4xfDDxJ5nTD+fAkzwDXyLExrQpK0J72YExsZx8sY4u9MHtus7+R8FdtTwBmcVT882ofDBARujMpY1yziUIbVqKvukWoyJts/b7YtibcwcqBM0Zjn/gAGo96jJDEV/iGECslj39jAL6mJYJy9BRo5Ih0EghFDbh55YG8yFFAP80jNmEd1Qd7mZNHvJ9FKEmm3rA7fw8Fkg23Kom7GCRl9iey1tXA+jARszNZ7AGHgpwqDBGWxqaPQ8IPlradjqckly2Cdt0LCHxF6dBfBWxPxD/2AzR06tX14nOiBOP9jTesmaTHieBE0omsYHSKDLJo8xj/fMQZWVMnJ8mCE42VSc4Ob3doOiicUi92I03exR2LPMbqAaEGNj89Xzbk3X0txP+hYrCBdFD4LZc/snd3DZPFR60V8m3yKlQutk2W5PSYMT/7zc286ru0ikuK/Cf9KniY71+GMxPHCHblSYcfNugUvBXB0mhBrdedulkhiIxtWtZ2ZNU0+wF+709ZzMce+LBHko/GiiAvlA9zhSu2gplIgiSmW+1F5PvoCZvNsaThNWiMnkTiBcoq3DaViq6WDcFH9u3/ZgARIShwipOyU31lWyQsxaIh1Z2AzCFaSZDmNCNyLhzfJn9TObO1w/SFxc1PcqlGjBOFowhdpiEDAp/ioiQ83fkxPoD7GnZzzoqvzuUQh6Lhlxdr4amOB6pxSDsOK2wcNIvhsaiGYkhEb1rfuJ6IMZN00Fs2z67u95XzIDK8ps/S93MwLf8tl5lAfPg1PZZnRmc8U4HdwkD6JJ98iugGlTHrzTOWIiwUE/lotu8gDmA4oagmIRjZqbmZ6n92h/5Yv4CkajzDpww+Sh0g2lueMcmkjUOG96UcSJTWLT/RhWRNrKGcMsApKu5zwjxuhJ5bb7Ax3Rzdr9pHFPX6QZXG2EtcWsOOnGhUE7H5guOFTZCLRCSNJZzfA02XEDDEW2wxsJiw1uqGXlFpctUvWW9VjDw0ZIQYJhT2IPK0wL926GgPp/b59vuy42rgEIo0pLzVL79dyCYnUrpZb6QWFctdhwksLSuQYMYxn62l6WoUQBMv7EwHoOD4CAk6wo4Aj9HmkavAqPPOS1l07RDUiwd0Y4uR5YJ9s/XlgkqMpFEGYGNqJJwNPprszITg5aOT0HFcvQPeaM3QDVLRvpFnDDh08SXzCEC5k7RsRPVrokwKSjdv8y6jADEpGCfzrGjGMxElK0rlU78km6i10ErOCoFOeae3JE3a/CsW4F27+yf7iZcwfLq+PhXPjEkNuW4lbJfVVyU08hd8nwWYZZnM5OD6aFy+/VuE4YI7NOwsImi1cFY8jBNrVha4hskbnBpeaKINJPgV43m3RCIz2bvrLM/240N47XNe9XTFkaYjemDgUC+CPl7KaRixGVnNDJPkYq2TJ02Rw7NLaPcDzYK4wu2ACRkwzKoWLdkB3DbZlG6iKk9nFDDrXnwrPSspeh7Wsrlhy6Yn29uKc9kFt+gLUpjI/qDDHcaB02bM8NOdxI3vVwNKiWlTbANwYqOklMYwzjlC5JWENAo4j7HM3k0gaO82aFsJpw70Jzma628cBdR6uncGW2CtuoaILlAbcFNpjtYGOMwXzCosOibGC4h/qWAUrCbpv0ytdpYzdbzQuYC0xs+SAsD3sBdB0TbesGG+bXlQtyj6Czw7ofzKlrRXn05WQIwhXAPhGuHDzZoFtHFnCFxtw3US7hraUZ2OpBCzkbsc80u6sC+BD+t/UAjAeNE3HM24YAoQVzKAyRRS7+eawxtJmRTcQ5Vckttwb3D04bDoMqnjYvCo7W0rcG7snyacsW4d1PIbJFAkpwZnUaaNiwN6NaORAtaXpaFTKxZc9lOKgWjWMLBkOnQrrp5sbQFNFIW7AR1DxCP3NsxcZ+xNG3jKw26D2pn2M3Gz2NBUiLAOCwfwzVD6bRwwyuNPyvGYrkWZcySQT5k3vulUtRDIAGI/D04ObJq7oA7vA67Lu5kT1OIql5Ak7gUvgWKa4gM8ISrEZ3FWCzLeDTnTcwzImAOFnKnHEupSH8lOCCbP2LgQUE024iB7gRnap5up2+axzGoM6iOpEiOCEmu3es/Wd0fKltgGxG9Ok8PLYUDkFsmm+23DMd6RCbCMMtMwndgXjvEouJKQjq8CvkUGoxMYFo4X4u15pWDmE70dtNqpqcY3xK8W0dmDDcibuhOLKatMbGqSCUbI2Q7GsC8wtdF4P3Mwpfqh5JgHDfzfW7v2pK69u0fAPWQBuJidUJsTdbhiRxAsBGxszHkiz0kHWjcwTiYlwJDhb8NYZrwyDHoGsMF4K5ECOU+wvnkc0XcHOJdXkf1aMDfMLpaWm3P97GYdVQeFXThgmMhe/DCjt7klWU0B0GUCtdpvoHZkzLSWCFE0vGGbCCLamhcBRnCZB9fmgdVa7ddZMRTBY1jUwjSvKYy2U68cCyQlybNDo62QvScUC5uSjP5fJdYDO1GBjFV3EBnDq+WwSIzxQP44JTPqU+fWPWiFpcrYeAxhwurAk1YFohcp+a1HYSC/hO9JHPxp4Bv8vZ+sD7GCaXbchXhxZxCabhTz0lD/6n3eOwFq7VrcxjV3RUe8wMWHYzh6j7wC+nPI7ufl1jaDdi7fGUfWB+XZld1mM2rz+PaHKe8daNNLkm4k48mCGjhjvj5EgI0whOsQaonUHX7ALRx3gK9jeeWkSXk2EYX9MnsUIuzzLBKL5MjvT/exZ46tw2SxxezB5Z5odukoesYzpJQRertodfluZ1my3Aek7sKCul0/P6NjsmuTz7xO0EuwTVMMqUzRfUfM0B3O6UMS24sXcYgmEirO2LqptbIz+tfqjRZ6XkcenA4KM7EDPmVkOX+FEUt3A0sbF7yJahllLFAPOeieK+M+bSLh0GOA6RCOaeIzWmhpw5LJLCbMMQ0wg2uCy5KXxrapmfO5RHug7SKIVCbB0rOvgKKdO5bncpRsDU/YzAfGXFa/PsPYwZTq3ARENoRNhZ/9DxFzF9g3WLeeOxmoEjc3ymFAaMMXKoMKfk4LgMzbvNURKzTCHyiC2TRE/0DPSZrE/k9OLIVuozB9E3hjUUm33EGldx+7NcxUakLExUneI9oocOpWhz61M6WOYICwwLqk/f1cWIYWyuoHif+hxK2Lo7L2PMSUZzolAnatrxmxpNM9mokEsNbufWjG39TNEOsXMn3ILvPOgetlvI2FuuDQJ5byQIvozXjgdygm+ZodKbpzPLFX7jqikjdowzO5OXX5darhnnZKaoVveR6VKdFkBO8fTjrEppDavt3WoYTYnsIdj1HI05sYJDf5MbOcDqYtT3iy3v0smAFlg1ED3tofEjg8otoResFLyvJDk0uvK8auiPYzrMnDInNdXRwgx/rNe0AinSDAejzria67bCF8xbkS1ZDpubFbaBdGgn82CHYYCKB2t2dMvexrP7zMd61xVhBANgvTrvrgA+tM87dnMbF+hbRaY6dtRXK3zksQ20SdQaTh75fGrw92mgYIr6zawvIy51PMyshNC1Ab5/iQSlppyMjF1DxcjuY+yt+AVoYa75s+yZujFmWeHjfiDAMEJpgUBD0qsviET6HsSJGSJ1OUgTxHYYBu5C5RpA09PhFLqv05hMMk3GRsBMavu9uPZk7b1xAFIIix7gprvN9bZBBXrmm3DAUdfnp2arlIBzMQdmNffvXH5Gz5a4SqHYMOL7NETljUq2E0U/BNK2zrKvxLdR7yRCJIc+MLksIBgQ4zV19RUibOQ83DFZjA6UPU1tKf0pMYhqP+Umqgr2Y+yoZRRB4JWMABssURwxjaXdlsnwOnRkiCb/0S6c1qm5QL23/Wc3gSQF2EUDUOdnOFsNfe9szT5ixWyZSHQDXBbHHGrtZ3y6ztD1zWmiRJq9Pma/twpYM4kNGKjy/MjDhH5Nh7hLhGaIk3O5ZNKw+RP2g/GQYS3Z0+TXfS8s8E3wbzPL8gG8cCJVtG2Gpt0P6nlycBEpcxBu48WB76CHOk9tee72tgDowxka4ayLvlSSUUuW0ZBExuSqooytSjrsfl8d1lk07QZGoR0weZiHOyox9NRyU6uLtPr4gVuKnZE0AenWVrgUhizkPq6HXNg3TmyjBA2aYPB53KFjdGhhDMgaOjIY5lvd+l/ZVsSyjKIYp9MzPNKSsMCl5hIzrZPkOAy05xCkrWrqxxmJyAt/nohSuEsTt+Piq50F3uGA2FS0L5fzDsXCuuGmS4dwQ3GW7FhvYqqjUUGepEUSN8AxVgdC1QJxYMQjvgfLSSWTPKCCW65usDehEk0jRgBaVjGcJ5DRkyr3xxTLoVjYfMitFWCeC//QLlN7pEGUnJjpztXJTRsSrqdzJtqFMwq0Yk6R4qZJdCwdS491PqwioZhd07/EoVWCh5rBBSVBMA49ukcCbEyVyUDT3uBSJntmZO0JuhqV8GgaFyL8Js9njZFH0m3+V0e5doO1AbDXcVcY4M4BhJsC1jIBtk/ASI6cIufKAPGg7kyxr1IRdHEnHTO0tR2sUWHVScXVxN6JsZPKjg70UB5PGUvdmr0/GTm9e0TLIR4aRC+GLSEc5tipGb1SG3znNBH5XHWKdshwK2KDiLDz0bdY45qJnBHWgJijgEuWIS0zlcdh8M3Q7MDQkpLVnL9bV4kJjdNw32H4oWGeypJTu1A/Nip1Uy3iUycOsFoay6mlVOzUY2b1angzQltTBBzztqWPAyPFjNK/ukfgZgO0K2iYsb2HJmjc5kpilX/IFnLQWBFwjuN1d+rlgKNwOOEP12WyuoHC8JBmDCQFD+QwPgFjQFqs8UEW5pkJlecmwNpU8Q+EsaR+Xrc6ktO0DqHVPwRSdRRPDUpPvQu3WU0AAATVSURBVNccuib22s1eJEa1vpW1UFJIF0O38Ecu80cR91VDNN7uvBfYQZGMKlA8r7vvyDrlzeGIvfBQ8d7Eud0JhO0A0YwZdBgG7vBCPZCTmzogJ6gWAjODrx22dt28x1CkGoiL8aFn1RD4dbMLbPmWaGdZLv8nlc5t4cwHEIFJo6n8l7mFZ5o2F74EaXboEViWyCBuGWAfLpNk0V3lCiBcjR+iQdn0WzK5VBIuCCJJfZXmwnD0R9RtB9ijUbwBAnPyeBLGEWBz8fbJWvSW0nXg3M9W41w0P0APzpKCyNhuYJBIGStp+BqdXImjQ30S26qgiPPBQWo632Dnf08ogr5ix/w8KdOPWIo1Dm8TcOUHKMN+a1KXUu735Rp+fxhgDe/tVt7vEGi+fx5y7YFzq1axDIToDwf6FmDLObCVSqaVtxtlnroyWzJHZXfcV9jiRhaSII0ZmNcCbS22anjwaMXDT+71GpDBZA7ul4M0sMxhNjxSnxJ1dIaYcppG7HhQAHNNsRAdLkzxjHZkjp4ZgoTNpmas/6n8hEk21akRTOuqXa/S9OkiAj4t+qLInrvi6rDSsYtqqVqT2g8Y3vOyoQeq5eL0kQNxKNArPgJMT0dxPSjA1mUqKWbdafvk7qkDnJTGfITY+bG7w5HbYEVqKKIJC5rsQ4UDwnxqQE7Li2W3nE+0+ByxM+tBHGWAobNhh0fVU6kz1eUnJTV83Q0UkFXUApVCPH7KIPybcWB3lJQ9ZYGMXVlyCN/koGxtRYq3dJHqq2g2VdSHh+NJi8hcGSFosgUPUkPl88Qh5w1P+H0OgYGVtm2fd4ada74yY1sm4EFfd/PYV329EFswCzliHIoV65aV42oEzofRL8+/zz/Xfl6rJNzxXFRGeN6bEHpielEbFuQGtscHmF9Wt9GFWvDQk9N0vGF0SqdxAIOdVICXpiNMRgGcWH1YHOa4laJGqOF6cjBnmU2nVlrNhy1WmNrpSd9YsLxzFbFm6FTDGJXh1vyzvjrIPBjSMwEZecrNCDEwzYTt7Y5aJ7AH2puWCGdHX2LGAHaWvrD2GiCYpmf70jdomoFebF601pQBqVQNsUGlimIkzR2RlZ4PLP8fSbTAIR+OLWz7bPoKnspeVleN6mNxbkzldQd5RwXwlusz8LhRuB7AMYaFAH22TdMn3QdjafwYwxRJ3CJ01GjMUHKHKw2bGaTGXWLfmub2AGmbByvVjo2YGNEBdp/ObhYSfnCoOV4cXg7PG/eU7BxicwR+2EewgzVHYHQQo4Ddckq3z1SzAZf7ECJrGahY22HduJZMG0tQsT03LJy6gAepv+6H2MEmZjURHdJxRo7GUDeRYEw8GPV3OC3iEBgglY1mADjorjR2Tp8H4PEZCFZODge2/v3YQFX3MgKPhzZnVhebA39qEcENSaJmo1wHG/bUB88UMIQ95wLGVDiqNyOwaV2xE80t5wv9gxHBYe9WMe2OCjEB7In2alsN5TIGTuB7PHzhOH3EBaQbNzQgfPqouLKXuXlY9fWZ1ZM2npiIjn7OehCB41gZnVrdvLvxeaUbRTKB9MJU7p2IsDCjaCL1TwxPy8+uxo6LRrxUZ8grYQy0bRtz3uYizsDcAtd0LzpBZMYGjN1lyvqosv9lXaJh98AsIXqV/vN/AwfGJ1FgGOsuAAAAAElFTkSuQmCC';
diff --git a/dom/html/reftests/toblob-todataurl/toblob-quality-0.html b/dom/html/reftests/toblob-todataurl/toblob-quality-0.html
new file mode 100644
index 000000000..7e7298cb6
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/toblob-quality-0.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html class='reftest-wait'>
+ <body>
+ <script>
+ var quality = 0;
+ </script>
+ <script src='sample.js'></script>
+ <script src='blob.js'></script>
+ </body>
+</html>
diff --git a/dom/html/reftests/toblob-todataurl/toblob-quality-100.html b/dom/html/reftests/toblob-todataurl/toblob-quality-100.html
new file mode 100644
index 000000000..34f318e11
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/toblob-quality-100.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html class='reftest-wait'>
+ <body>
+ <script>
+ var quality = 1;
+ </script>
+ <script src='sample.js'></script>
+ <script src='blob.js'></script>
+ </body>
+</html>
diff --git a/dom/html/reftests/toblob-todataurl/toblob-quality-25.html b/dom/html/reftests/toblob-todataurl/toblob-quality-25.html
new file mode 100644
index 000000000..ed4350e6e
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/toblob-quality-25.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html class='reftest-wait'>
+ <body>
+ <script>
+ var quality = 0.25;
+ </script>
+ <script src='sample.js'></script>
+ <script src='blob.js'></script>
+ </body>
+</html>
diff --git a/dom/html/reftests/toblob-todataurl/toblob-quality-50.html b/dom/html/reftests/toblob-todataurl/toblob-quality-50.html
new file mode 100644
index 000000000..47e3b684f
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/toblob-quality-50.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html class='reftest-wait'>
+ <body>
+ <script>
+ var quality = 0.50;
+ </script>
+ <script src='sample.js'></script>
+ <script src='blob.js'></script>
+ </body>
+</html>
diff --git a/dom/html/reftests/toblob-todataurl/toblob-quality-75.html b/dom/html/reftests/toblob-todataurl/toblob-quality-75.html
new file mode 100644
index 000000000..45ccfe1fe
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/toblob-quality-75.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html class='reftest-wait'>
+ <body>
+ <script>
+ var quality = 0.75;
+ </script>
+ <script src='sample.js'></script>
+ <script src='blob.js'></script>
+ </body>
+</html>
diff --git a/dom/html/reftests/toblob-todataurl/toblob-quality-92.html b/dom/html/reftests/toblob-todataurl/toblob-quality-92.html
new file mode 100644
index 000000000..6a7f8788f
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/toblob-quality-92.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html class='reftest-wait'>
+ <body>
+ <script>
+ var quality = 0.92;
+ </script>
+ <script src='sample.js'></script>
+ <script src='blob.js'></script>
+ </body>
+</html>
diff --git a/dom/html/reftests/toblob-todataurl/toblob-quality-default.html b/dom/html/reftests/toblob-todataurl/toblob-quality-default.html
new file mode 100644
index 000000000..c5b404744
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/toblob-quality-default.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<html class='reftest-wait'>
+ <body>
+ <script src='sample.js'></script>
+ <script src='blob.js'></script>
+ </body>
+</html>
diff --git a/dom/html/reftests/toblob-todataurl/toblob-quality-undefined.html b/dom/html/reftests/toblob-todataurl/toblob-quality-undefined.html
new file mode 100644
index 000000000..325290020
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/toblob-quality-undefined.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html class='reftest-wait'>
+ <body>
+ <script>
+ var quality = undefined;
+ </script>
+ <script src='sample.js'></script>
+ <script src='blob.js'></script>
+ </body>
+</html>
diff --git a/dom/html/reftests/toblob-todataurl/todataurl-quality-0.html b/dom/html/reftests/toblob-todataurl/todataurl-quality-0.html
new file mode 100644
index 000000000..1d4eb9b7f
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/todataurl-quality-0.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html class='reftest-wait'>
+ <body>
+ <script>
+ var quality = 0;
+ </script>
+ <script src='sample.js'></script>
+ <script src='dataurl.js'></script>
+ </body>
+</html>
diff --git a/dom/html/reftests/toblob-todataurl/todataurl-quality-100.html b/dom/html/reftests/toblob-todataurl/todataurl-quality-100.html
new file mode 100644
index 000000000..66b627c13
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/todataurl-quality-100.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html class='reftest-wait'>
+ <body>
+ <script>
+ var quality = 1;
+ </script>
+ <script src='sample.js'></script>
+ <script src='dataurl.js'></script>
+ </body>
+</html>
diff --git a/dom/html/reftests/toblob-todataurl/todataurl-quality-25.html b/dom/html/reftests/toblob-todataurl/todataurl-quality-25.html
new file mode 100644
index 000000000..15237cea8
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/todataurl-quality-25.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html class='reftest-wait'>
+ <body>
+ <script>
+ var quality = 0.25;
+ </script>
+ <script src='sample.js'></script>
+ <script src='dataurl.js'></script>
+ </body>
+</html>
diff --git a/dom/html/reftests/toblob-todataurl/todataurl-quality-50.html b/dom/html/reftests/toblob-todataurl/todataurl-quality-50.html
new file mode 100644
index 000000000..93e820e68
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/todataurl-quality-50.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html class='reftest-wait'>
+ <body>
+ <script>
+ var quality = 0.50;
+ </script>
+ <script src='sample.js'></script>
+ <script src='dataurl.js'></script>
+ </body>
+</html>
diff --git a/dom/html/reftests/toblob-todataurl/todataurl-quality-75.html b/dom/html/reftests/toblob-todataurl/todataurl-quality-75.html
new file mode 100644
index 000000000..acdc7416f
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/todataurl-quality-75.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html class='reftest-wait'>
+ <body>
+ <script>
+ var quality = 0.75;
+ </script>
+ <script src='sample.js'></script>
+ <script src='dataurl.js'></script>
+ </body>
+</html>
diff --git a/dom/html/reftests/toblob-todataurl/todataurl-quality-92.html b/dom/html/reftests/toblob-todataurl/todataurl-quality-92.html
new file mode 100644
index 000000000..ca3de4ee0
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/todataurl-quality-92.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html class='reftest-wait'>
+ <body>
+ <script>
+ var quality = 0.92;
+ </script>
+ <script src='sample.js'></script>
+ <script src='dataurl.js'></script>
+ </body>
+</html>
diff --git a/dom/html/reftests/toblob-todataurl/todataurl-quality-default.html b/dom/html/reftests/toblob-todataurl/todataurl-quality-default.html
new file mode 100644
index 000000000..cc7771dbf
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/todataurl-quality-default.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<html class='reftest-wait'>
+ <body>
+ <script src='sample.js'></script>
+ <script src='dataurl.js'></script>
+ </body>
+</html>
diff --git a/dom/html/reftests/toblob-todataurl/todataurl-quality-undefined.html b/dom/html/reftests/toblob-todataurl/todataurl-quality-undefined.html
new file mode 100644
index 000000000..16801e482
--- /dev/null
+++ b/dom/html/reftests/toblob-todataurl/todataurl-quality-undefined.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html class='reftest-wait'>
+ <body>
+ <script>
+ var quality = undefined;
+ </script>
+ <script src='sample.js'></script>
+ <script src='dataurl.js'></script>
+ </body>
+</html>
diff --git a/dom/html/reftests/video_rotated.mp4 b/dom/html/reftests/video_rotated.mp4
new file mode 100644
index 000000000..38a1b77f9
--- /dev/null
+++ b/dom/html/reftests/video_rotated.mp4
Binary files differ
diff --git a/dom/html/reftests/video_rotation_90.mp4 b/dom/html/reftests/video_rotation_90.mp4
new file mode 100644
index 000000000..85aa055fb
--- /dev/null
+++ b/dom/html/reftests/video_rotation_90.mp4
Binary files differ
diff --git a/dom/html/test/347174transform.xsl b/dom/html/test/347174transform.xsl
new file mode 100644
index 000000000..1b201de3f
--- /dev/null
+++ b/dom/html/test/347174transform.xsl
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:template match="/">
+<html>
+<head>
+<script>
+window.parent.frameScriptTag(document.readyState);
+
+function attachCustomEventListener(element, eventName, command) {
+ if (window.addEventListener &amp;&amp; !window.opera)
+ element.addEventListener(eventName, command, true);
+ else if (window.attachEvent)
+ element.attachEvent("on" + eventName, command);
+}
+
+function load() {
+ window.parent.frameLoad(document.readyState);
+}
+
+function readyStateChange() {
+ window.parent.frameReadyStateChange(document.readyState);
+}
+
+function DOMContentLoaded() {
+ window.parent.frameDOMContentLoaded(document.readyState);
+}
+
+window.onload=load;
+
+attachCustomEventListener(document, "readystatechange", readyStateChange);
+attachCustomEventListener(document, "DOMContentLoaded", DOMContentLoaded);
+
+</script>
+</head>
+<body>
+</body>
+</html>
+</xsl:template>
+
+</xsl:stylesheet> \ No newline at end of file
diff --git a/dom/html/test/347174transformable.xml b/dom/html/test/347174transformable.xml
new file mode 100644
index 000000000..68f7bc6dc
--- /dev/null
+++ b/dom/html/test/347174transformable.xml
@@ -0,0 +1,3 @@
+<?xml version='1.0'?>
+<?xml-stylesheet type="text/xsl" href="347174transform.xsl"?>
+<doc>This is a sample document.</doc>
diff --git a/dom/html/test/allowMedia.sjs b/dom/html/test/allowMedia.sjs
new file mode 100644
index 000000000..f29619cd8
--- /dev/null
+++ b/dom/html/test/allowMedia.sjs
@@ -0,0 +1,12 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(req, resp) {
+ resp.setHeader("Cache-Control", "no-cache", false);
+ resp.setHeader("Content-Type", "text/plain", false);
+
+ let stateKey = "allowMediaState";
+ let state = getState(stateKey);
+ setState(stateKey, req.queryString ? "FAIL" : "");
+ resp.write(state || "PASS");
+}
diff --git a/dom/html/test/browser.ini b/dom/html/test/browser.ini
new file mode 100644
index 000000000..49caea726
--- /dev/null
+++ b/dom/html/test/browser.ini
@@ -0,0 +1,25 @@
+[DEFAULT]
+support-files =
+ bug592641_img.jpg
+ dummy_page.html
+ file_bug649778.html
+ file_bug649778.html^headers^
+ file_fullscreen-api-keys.html
+ file_content_contextmenu.html
+ head.js
+
+[browser_bug592641.js]
+[browser_bug649778.js]
+skip-if = e10s # Bug 1271025
+[browser_bug1081537.js]
+[browser_bug1108547.js]
+support-files =
+ file_bug1108547-1.html
+ file_bug1108547-2.html
+ file_bug1108547-3.html
+[browser_content_contextmenu_userinput.js]
+[browser_DOMDocElementInserted.js]
+[browser_fullscreen-api-keys.js]
+tags = fullscreen
+[browser_fullscreen-contextmenu-esc.js]
+tags = fullscreen
diff --git a/dom/html/test/browser_DOMDocElementInserted.js b/dom/html/test/browser_DOMDocElementInserted.js
new file mode 100644
index 000000000..9fa8335e6
--- /dev/null
+++ b/dom/html/test/browser_DOMDocElementInserted.js
@@ -0,0 +1,24 @@
+// Tests that the DOMDocElementInserted event is visible on the frame
+add_task(function*() {
+ let tab = gBrowser.addTab();
+ let uri = "data:text/html;charset=utf-8,<html/>"
+
+ let eventPromise = ContentTask.spawn(tab.linkedBrowser, null, function() {
+ Cu.import("resource://gre/modules/PromiseUtils.jsm");
+ let deferred = PromiseUtils.defer();
+
+ let listener = (event) => {
+ removeEventListener("DOMDocElementInserted", listener, true);
+ deferred.resolve(event.target.documentURIObject.spec);
+ };
+ addEventListener("DOMDocElementInserted", listener, true);
+
+ return deferred.promise;
+ });
+
+ tab.linkedBrowser.loadURI(uri);
+ let loadedURI = yield eventPromise;
+ is(loadedURI, uri, "Should have seen the event for the right URI");
+
+ gBrowser.removeTab(tab);
+});
diff --git a/dom/html/test/browser_bug1081537.js b/dom/html/test/browser_bug1081537.js
new file mode 100644
index 000000000..8ae53e4ff
--- /dev/null
+++ b/dom/html/test/browser_bug1081537.js
@@ -0,0 +1,11 @@
+// This test is useful because mochitest-browser runs as an addon, so we test
+// addon-scope paths here.
+var ifr;
+function test() {
+ ifr = document.createElement('iframe');
+ document.getElementById('main-window').appendChild(ifr);
+ is(ifr.contentDocument.nodePrincipal.origin, "[System Principal]");
+ ifr.contentDocument.open();
+ ok(true, "Didn't throw");
+}
+registerCleanupFunction(() => ifr.remove());
diff --git a/dom/html/test/browser_bug1108547.js b/dom/html/test/browser_bug1108547.js
new file mode 100644
index 000000000..711dac0aa
--- /dev/null
+++ b/dom/html/test/browser_bug1108547.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+requestLongerTimeout(2);
+
+function test() {
+ waitForExplicitFinish();
+
+ runPass("file_bug1108547-2.html", function() {
+ runPass("file_bug1108547-3.html", function() {
+ finish();
+ });
+ });
+}
+
+function runPass(getterFile, finishedCallback) {
+ var rootDir = "http://mochi.test:8888/browser/dom/html/test/";
+ var testBrowser;
+ var privateWin;
+
+ function whenDelayedStartupFinished(win, callback) {
+ let topic = "browser-delayed-startup-finished";
+ Services.obs.addObserver(function onStartup(aSubject) {
+ if (win != aSubject)
+ return;
+
+ Services.obs.removeObserver(onStartup, topic);
+ executeSoon(callback);
+ }, topic, false);
+ }
+
+ // First, set the cookie in a normal window.
+ gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug1108547-1.html");
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(afterOpenCookieSetter);
+
+ function afterOpenCookieSetter() {
+ gBrowser.removeCurrentTab();
+
+ // Now, open a private window.
+ privateWin = OpenBrowserWindow({private: true});
+ whenDelayedStartupFinished(privateWin, afterPrivateWindowOpened);
+ }
+
+ function afterPrivateWindowOpened() {
+ // In the private window, open the getter file, and wait for a new tab to be opened.
+ privateWin.gBrowser.selectedTab = privateWin.gBrowser.addTab(rootDir + getterFile);
+ testBrowser = privateWin.gBrowser.selectedBrowser;
+ privateWin.gBrowser.tabContainer.addEventListener("TabOpen", onNewTabOpened, true);
+ }
+
+ function fetchResult() {
+ return ContentTask.spawn(testBrowser, null, function() {
+ return content.document.getElementById("result").textContent;
+ });
+ }
+
+ function onNewTabOpened() {
+ // When the new tab is opened, wait for it to load.
+ privateWin.gBrowser.tabContainer.removeEventListener("TabOpen", onNewTabOpened, true);
+ BrowserTestUtils.browserLoaded(privateWin.gBrowser.tabs[privateWin.gBrowser.tabs.length - 1].linkedBrowser).then(fetchResult).then(onNewTabLoaded);
+ }
+
+ function onNewTabLoaded(result) {
+ // Now, ensure that the private tab doesn't have access to the cookie set in normal mode.
+ is(result, "", "Shouldn't have access to the cookies");
+
+ // We're done with the private window, close it.
+ privateWin.close();
+
+ // Clear all cookies.
+ Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager).removeAll();
+
+ // Open a new private window, this time to set a cookie inside it.
+ privateWin = OpenBrowserWindow({private: true});
+ whenDelayedStartupFinished(privateWin, afterPrivateWindowOpened2);
+ }
+
+ function afterPrivateWindowOpened2() {
+ // In the private window, open the setter file, and wait for it to load.
+ privateWin.gBrowser.selectedTab = privateWin.gBrowser.addTab(rootDir + "file_bug1108547-1.html");
+ BrowserTestUtils.browserLoaded(privateWin.gBrowser.selectedBrowser).then(afterOpenCookieSetter2);
+ }
+
+ function afterOpenCookieSetter2() {
+ // We're done with the private window now, close it.
+ privateWin.close();
+
+ // Now try to read the cookie in a normal window, and wait for a new tab to be opened.
+ gBrowser.selectedTab = gBrowser.addTab(rootDir + getterFile);
+ testBrowser = gBrowser.selectedBrowser;
+ gBrowser.tabContainer.addEventListener("TabOpen", onNewTabOpened2, true);
+ }
+
+ function onNewTabOpened2() {
+ // When the new tab is opened, wait for it to load.
+ gBrowser.tabContainer.removeEventListener("TabOpen", onNewTabOpened2, true);
+ BrowserTestUtils.browserLoaded(gBrowser.tabs[gBrowser.tabs.length - 1].linkedBrowser).then(fetchResult).then(onNewTabLoaded2);
+ }
+
+ function onNewTabLoaded2(result) {
+ // Now, ensure that the normal tab doesn't have access to the cookie set in private mode.
+ is(result, "", "Shouldn't have access to the cookies");
+
+ // Remove both of the tabs opened here.
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+
+ privateWin = null;
+ testBrowser = null;
+
+ finishedCallback();
+ }
+}
diff --git a/dom/html/test/browser_bug592641.js b/dom/html/test/browser_bug592641.js
new file mode 100644
index 000000000..94e2e92c2
--- /dev/null
+++ b/dom/html/test/browser_bug592641.js
@@ -0,0 +1,61 @@
+// Test for bug 592641 - Image document doesn't show dimensions of cached images
+
+// Globals
+var testPath = "http://mochi.test:8888/browser/dom/html/test/";
+var ctx = {loadsDone : 0};
+
+// Entry point from Mochikit
+function test() {
+
+ waitForExplicitFinish();
+
+ ctx.tab1 = gBrowser.addTab(testPath + "bug592641_img.jpg");
+ ctx.tab1Browser = gBrowser.getBrowserForTab(ctx.tab1);
+ ctx.tab1Browser.addEventListener("load", load1Soon, true);
+}
+
+function checkTitle(title) {
+
+ ctx.loadsDone++;
+ ok(/^bug592641_img\.jpg \(JPEG Image, 1500\u00A0\u00D7\u00A01500 pixels\)/.test(title),
+ "Title should be correct on load #" + ctx.loadsDone);
+}
+
+function load1Soon() {
+ ctx.tab1Browser.removeEventListener("load", load1Soon, true);
+ // onload is fired in OnStopDecode, so let's use executeSoon() to make sure
+ // that any other OnStopDecode event handlers get the chance to fire first.
+ executeSoon(load1Done);
+}
+
+function load1Done() {
+ // Check the title
+ var title = ctx.tab1Browser.contentTitle;
+ checkTitle(title);
+
+ // Try loading the same image in a new tab to make sure things work in
+ // the cached case.
+ ctx.tab2 = gBrowser.addTab(testPath + "bug592641_img.jpg");
+ ctx.tab2Browser = gBrowser.getBrowserForTab(ctx.tab2);
+ ctx.tab2Browser.addEventListener("load", load2Soon, true);
+}
+
+function load2Soon() {
+ ctx.tab2Browser.removeEventListener("load", load2Soon, true);
+ // onload is fired in OnStopDecode, so let's use executeSoon() to make sure
+ // that any other OnStopDecode event handlers get the chance to fire first.
+ executeSoon(load2Done);
+}
+
+function load2Done() {
+ // Check the title
+ var title = ctx.tab2Browser.contentTitle;
+ checkTitle(title);
+
+ // Clean up
+ gBrowser.removeTab(ctx.tab1);
+ gBrowser.removeTab(ctx.tab2);
+
+ // Test done
+ finish();
+}
diff --git a/dom/html/test/browser_bug649778.js b/dom/html/test/browser_bug649778.js
new file mode 100644
index 000000000..6356d20fe
--- /dev/null
+++ b/dom/html/test/browser_bug649778.js
@@ -0,0 +1,82 @@
+// Test for bug 649778 - document.write may cause a document to be written to disk cache even when the page has Cache-Control: no-store
+
+// Globals
+var testPath = "http://mochi.test:8888/browser/dom/html/test/";
+var popup;
+
+var {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", null);
+var {Services} = Cu.import("resource://gre/modules/Services.jsm", null);
+
+function checkCache(url, inMemory, shouldExist, cb)
+{
+ var cache = Services.cache2;
+ var storage = cache.diskCacheStorage(LoadContextInfo.default, false);
+
+ function CheckCacheListener(inMemory, shouldExist)
+ {
+ this.inMemory = inMemory;
+ this.shouldExist = shouldExist;
+ this.onCacheEntryCheck = function() {
+ return Components.interfaces.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ };
+
+ this.onCacheEntryAvailable = function oCEA(entry, isNew, appCache, status) {
+ if (shouldExist) {
+ ok(entry, "Entry not found");
+ is(this.inMemory, !entry.persistent, "Entry is " + (inMemory ? "" : " not ") + " in memory as expected");
+ is(status, Components.results.NS_OK, "Entry not found");
+ } else {
+ ok(!entry, "Entry found");
+ is(status, Components.results.NS_ERROR_CACHE_KEY_NOT_FOUND,
+ "Invalid error code");
+ }
+
+ setTimeout(cb, 0);
+ };
+ };
+
+ storage.asyncOpenURI(Services.io.newURI(url, null, null), "",
+ Components.interfaces.nsICacheStorage.OPEN_READONLY,
+ new CheckCacheListener(inMemory, shouldExist));
+}
+function getPopupURL() {
+ var sh = popup.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .sessionHistory;
+
+ return sh.getEntryAtIndex(sh.index, false).URI.spec;
+}
+
+var wyciwygURL;
+function testContinue() {
+ wyciwygURL = getPopupURL();
+ is(wyciwygURL.substring(0, 10), "wyciwyg://", "Unexpected URL.");
+ popup.close()
+
+ // We have to find the entry and it must not be persisted to disk
+ checkCache(wyciwygURL, true, true, finish);
+}
+
+function waitForWyciwygDocument() {
+ try {
+ var url = getPopupURL();
+ if (url.substring(0, 10) == "wyciwyg://") {
+ setTimeout(testContinue, 0);
+ return;
+ }
+ }
+ catch (e) {
+ }
+ setTimeout(waitForWyciwygDocument, 100);
+}
+
+// Entry point from Mochikit
+function test() {
+ waitForExplicitFinish();
+
+ popup = window.open(testPath + "file_bug649778.html", "popup 0",
+ "height=200,width=200,location=yes," +
+ "menubar=yes,status=yes,toolbar=yes,dependent=yes");
+
+ waitForWyciwygDocument();
+}
diff --git a/dom/html/test/browser_content_contextmenu_userinput.js b/dom/html/test/browser_content_contextmenu_userinput.js
new file mode 100644
index 000000000..7d0387715
--- /dev/null
+++ b/dom/html/test/browser_content_contextmenu_userinput.js
@@ -0,0 +1,50 @@
+"use strict";
+
+const kPage = "http://example.org/browser/" +
+ "dom/html/test/file_content_contextmenu.html";
+
+add_task(function* () {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: kPage
+ }, function*(aBrowser) {
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ ok(contextMenu, "Got context menu");
+
+ info("Open context menu");
+ is(contextMenu.state, "closed", "Should not have opened context menu");
+ let popupShownPromise = promiseWaitForEvent(window, "popupshown");
+ EventUtils.synthesizeMouse(aBrowser, window.innerWidth / 3,
+ window.innerHeight / 3,
+ {type: "contextmenu", button: 2}, window);
+ yield popupShownPromise;
+ is(contextMenu.state, "open", "Should have opened context menu");
+
+ let pageMenuSep = document.getElementById("page-menu-separator");
+ ok(pageMenuSep && !pageMenuSep.hidden,
+ "Page menu separator should be shown");
+
+ let testMenuSep = pageMenuSep.previousSibling;
+ ok(testMenuSep && !testMenuSep.hidden,
+ "User-added menu separator should be shown");
+
+ let testMenuItem = testMenuSep.previousSibling;
+ is(testMenuItem.label, "Test Context Menu Click", "Got context menu item");
+
+ let promiseCtxMenuClick = ContentTask.spawn(aBrowser, null, function*() {
+ yield new Promise(resolve => {
+ let Ci = Components.interfaces;
+ let windowUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let menuitem = content.document.getElementById("menuitem");
+ menuitem.addEventListener("click", function() {
+ Assert.ok(windowUtils.isHandlingUserInput,
+ "Content menu click should be a user input");
+ resolve();
+ });
+ });
+ });
+ EventUtils.synthesizeMouseAtCenter(testMenuItem, {}, window);
+ yield promiseCtxMenuClick;
+ });
+});
diff --git a/dom/html/test/browser_fullscreen-api-keys.js b/dom/html/test/browser_fullscreen-api-keys.js
new file mode 100644
index 000000000..92358efee
--- /dev/null
+++ b/dom/html/test/browser_fullscreen-api-keys.js
@@ -0,0 +1,170 @@
+"use strict";
+
+/** Test for Bug 545812 **/
+
+// List of key codes which should exit full-screen mode.
+const kKeyList = [
+ { code: "VK_ESCAPE", suppressed: true},
+ { code: "VK_F11", suppressed: false},
+];
+
+function frameScript() {
+ let doc = content.document;
+ addMessageListener("Test:RequestFullscreen", () => {
+ doc.body.requestFullscreen();
+ });
+ addMessageListener("Test:DispatchUntrustedKeyEvents", msg => {
+ var evt = new content.CustomEvent("Test:DispatchKeyEvents", {
+ detail: { code: msg.data }
+ });
+ content.dispatchEvent(evt);
+ });
+
+ doc.addEventListener("fullscreenchange", () => {
+ sendAsyncMessage("Test:FullscreenChanged", !!doc.fullscreenElement);
+ });
+
+ function keyHandler(evt) {
+ sendAsyncMessage("Test:KeyReceived", {
+ type: evt.type,
+ keyCode: evt.keyCode
+ });
+ }
+ doc.addEventListener("keydown", keyHandler, true);
+ doc.addEventListener("keyup", keyHandler, true);
+ doc.addEventListener("keypress", keyHandler, true);
+
+ function waitUntilActive() {
+ if (doc.docShell.isActive && doc.hasFocus()) {
+ sendAsyncMessage("Test:Activated");
+ } else {
+ setTimeout(waitUntilActive, 10);
+ }
+ }
+ waitUntilActive();
+}
+
+var gMessageManager;
+
+function listenOneMessage(aMsg, aListener) {
+ function listener({ data }) {
+ gMessageManager.removeMessageListener(aMsg, listener);
+ aListener(data);
+ }
+ gMessageManager.addMessageListener(aMsg, listener);
+}
+
+function promiseOneMessage(aMsg) {
+ return new Promise(resolve => listenOneMessage(aMsg, resolve));
+}
+
+function captureUnexpectedFullscreenChange() {
+ ok(false, "Caught an unexpected fullscreen change");
+}
+
+function* temporaryRemoveUnexpectedFullscreenChangeCapture(callback) {
+ gMessageManager.removeMessageListener(
+ "Test:FullscreenChanged", captureUnexpectedFullscreenChange);
+ yield* callback();
+ gMessageManager.addMessageListener(
+ "Test:FullscreenChanged", captureUnexpectedFullscreenChange);
+}
+
+function captureUnexpectedKeyEvent(type) {
+ ok(false, `Caught an unexpected ${type} event`);
+}
+
+function* temporaryRemoveUnexpectedKeyEventCapture(callback) {
+ gMessageManager.removeMessageListener(
+ "Test:KeyReceived", captureUnexpectedKeyEvent);
+ yield* callback();
+ gMessageManager.addMessageListener(
+ "Test:KeyReceived", captureUnexpectedKeyEvent);
+}
+
+function receiveExpectedKeyEvents(keyCode) {
+ return new Promise(resolve => {
+ let events = ["keydown", "keypress", "keyup"];
+ function listener({ data }) {
+ let expected = events.shift();
+ is(data.type, expected, `Should receive a ${expected} event`);
+ is(data.keyCode, keyCode,
+ `Should receive the event with key code ${keyCode}`);
+ if (!events.length) {
+ gMessageManager.removeMessageListener("Test:KeyReceived", listener);
+ resolve();
+ }
+ }
+ gMessageManager.addMessageListener("Test:KeyReceived", listener);
+ });
+}
+
+const kPage = "http://example.org/browser/" +
+ "dom/html/test/file_fullscreen-api-keys.html";
+
+add_task(function* () {
+ yield pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"]);
+
+ let tab = gBrowser.addTab(kPage);
+ let browser = tab.linkedBrowser;
+ gBrowser.selectedTab = tab;
+ registerCleanupFunction(() => gBrowser.removeTab(tab));
+ yield waitForDocLoadComplete();
+
+ gMessageManager = browser.messageManager;
+ gMessageManager.loadFrameScript(
+ "data:,(" + frameScript.toString() + ")();", false);
+
+ // Wait for the document being actived, so that
+ // fullscreen request won't be denied.
+ yield promiseOneMessage("Test:Activated");
+
+ // Register listener to capture unexpected events
+ gMessageManager.addMessageListener(
+ "Test:FullscreenChanged", captureUnexpectedFullscreenChange);
+ gMessageManager.addMessageListener(
+ "Test:KeyReceived", captureUnexpectedKeyEvent);
+ registerCleanupFunction(() => {
+ gMessageManager.removeMessageListener(
+ "Test:FullscreenChanged", captureUnexpectedFullscreenChange);
+ gMessageManager.removeMessageListener(
+ "Test:KeyReceived", captureUnexpectedKeyEvent);
+ });
+
+ for (let {code, suppressed} of kKeyList) {
+ var keyCode = KeyEvent["DOM_" + code];
+ info(`Test keycode ${code} (${keyCode})`);
+
+ info("Enter fullscreen");
+ yield* temporaryRemoveUnexpectedFullscreenChangeCapture(function* () {
+ gMessageManager.sendAsyncMessage("Test:RequestFullscreen");
+ let state = yield promiseOneMessage("Test:FullscreenChanged");
+ ok(state, "The content should have entered fullscreen");
+ ok(document.fullscreenElement,
+ "The chrome should also be in fullscreen");
+ });
+
+ info("Dispatch untrusted key events from content");
+ yield* temporaryRemoveUnexpectedKeyEventCapture(function* () {
+ let promiseExpectedKeyEvents = receiveExpectedKeyEvents(keyCode);
+ gMessageManager.sendAsyncMessage("Test:DispatchUntrustedKeyEvents", code);
+ yield promiseExpectedKeyEvents;
+ });
+
+ info("Send trusted key events");
+ yield* temporaryRemoveUnexpectedFullscreenChangeCapture(function* () {
+ yield* temporaryRemoveUnexpectedKeyEventCapture(function* () {
+ let promiseExpectedKeyEvents = suppressed ?
+ Promise.resolve() : receiveExpectedKeyEvents(keyCode);
+ EventUtils.synthesizeKey(code, {});
+ yield promiseExpectedKeyEvents;
+ let state = yield promiseOneMessage("Test:FullscreenChanged");
+ ok(!state, "The content should have exited fullscreen");
+ ok(!document.fullscreenElement,
+ "The chrome should also have exited fullscreen");
+ });
+ });
+ }
+});
diff --git a/dom/html/test/browser_fullscreen-contextmenu-esc.js b/dom/html/test/browser_fullscreen-contextmenu-esc.js
new file mode 100644
index 000000000..bbc741e47
--- /dev/null
+++ b/dom/html/test/browser_fullscreen-contextmenu-esc.js
@@ -0,0 +1,107 @@
+"use strict";
+
+function frameScript() {
+ addMessageListener("Test:RequestFullscreen", () => {
+ content.document.body.requestFullscreen();
+ });
+ content.document.addEventListener("fullscreenchange", () => {
+ sendAsyncMessage("Test:FullscreenChanged",
+ !!content.document.fullscreenElement);
+ });
+ addMessageListener("Test:QueryFullscreenState", () => {
+ sendAsyncMessage("Test:FullscreenState",
+ !!content.document.fullscreenElement);
+ });
+ function waitUntilActive() {
+ let doc = content.document;
+ if (doc.docShell.isActive && doc.hasFocus()) {
+ sendAsyncMessage("Test:Activated");
+ } else {
+ setTimeout(waitUntilActive, 10);
+ }
+ }
+ waitUntilActive();
+}
+
+var gMessageManager;
+
+function listenOneMessage(aMsg, aListener) {
+ function listener({ data }) {
+ gMessageManager.removeMessageListener(aMsg, listener);
+ aListener(data);
+ }
+ gMessageManager.addMessageListener(aMsg, listener);
+}
+
+function promiseOneMessage(aMsg) {
+ return new Promise(resolve => listenOneMessage(aMsg, resolve));
+}
+
+function captureUnexpectedFullscreenChange() {
+ ok(false, "Caught an unexpected fullscreen change");
+}
+
+const kPage = "http://example.org/browser/dom/html/test/dummy_page.html";
+
+add_task(function* () {
+ yield pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"]);
+
+ let tab = gBrowser.addTab(kPage);
+ registerCleanupFunction(() => gBrowser.removeTab(tab));
+ let browser = tab.linkedBrowser;
+ gBrowser.selectedTab = tab;
+ yield waitForDocLoadComplete();
+
+ gMessageManager = browser.messageManager;
+ gMessageManager.loadFrameScript(
+ "data:,(" + frameScript.toString() + ")();", false);
+
+ // Wait for the document being activated, so that
+ // fullscreen request won't be denied.
+ yield promiseOneMessage("Test:Activated");
+
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ ok(contextMenu, "Got context menu");
+
+ let state;
+ info("Enter DOM fullscreen");
+ gMessageManager.sendAsyncMessage("Test:RequestFullscreen");
+ state = yield promiseOneMessage("Test:FullscreenChanged");
+ ok(state, "The content should have entered fullscreen");
+ ok(document.fullscreenElement, "The chrome should also be in fullscreen");
+ gMessageManager.addMessageListener(
+ "Test:FullscreenChanged", captureUnexpectedFullscreenChange);
+
+ info("Open context menu");
+ is(contextMenu.state, "closed", "Should not have opened context menu");
+ let popupShownPromise = promiseWaitForEvent(window, "popupshown");
+ EventUtils.synthesizeMouse(browser, screen.width / 2, screen.height / 2,
+ {type: "contextmenu", button: 2}, window);
+ yield popupShownPromise;
+ is(contextMenu.state, "open", "Should have opened context menu");
+
+ info("Send the first escape");
+ let popupHidePromise = promiseWaitForEvent(window, "popuphidden");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield popupHidePromise;
+ is(contextMenu.state, "closed", "Should have closed context menu");
+
+ // Wait a small time to confirm that the first ESC key
+ // does not exit fullscreen.
+ yield new Promise(resolve => setTimeout(resolve, 1000));
+ gMessageManager.sendAsyncMessage("Test:QueryFullscreenState");
+ state = yield promiseOneMessage("Test:FullscreenState");
+ ok(state, "The content should still be in fullscreen");
+ ok(document.fullscreenElement, "The chrome should still be in fullscreen");
+
+ info("Send the second escape");
+ gMessageManager.removeMessageListener(
+ "Test:FullscreenChanged", captureUnexpectedFullscreenChange);
+ let fullscreenExitPromise = promiseOneMessage("Test:FullscreenChanged");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ state = yield fullscreenExitPromise;
+ ok(!state, "The content should have exited fullscreen");
+ ok(!document.fullscreenElement, "The chrome should have exited fullscreen");
+});
diff --git a/dom/html/test/bug100533_iframe.html b/dom/html/test/bug100533_iframe.html
new file mode 100644
index 000000000..ddf58a15c
--- /dev/null
+++ b/dom/html/test/bug100533_iframe.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<title></title>
+</head>
+<body>
+<form method='get' action='bug100533_load.html' id='b'><input type="submit"/></form>
+</body>
+</html>
diff --git a/dom/html/test/bug100533_load.html b/dom/html/test/bug100533_load.html
new file mode 100644
index 000000000..99cf26640
--- /dev/null
+++ b/dom/html/test/bug100533_load.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<title></title>
+</head>
+
+
+<body onload="parent.submitted();">
+
+<span id="foo"></span>
+
+
+
+</body>
+</html>
diff --git a/dom/html/test/bug1260704_iframe.html b/dom/html/test/bug1260704_iframe.html
new file mode 100644
index 000000000..41d238958
--- /dev/null
+++ b/dom/html/test/bug1260704_iframe.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript">
+ var noDefault = (location.search.indexOf("noDefault=true") !== -1);
+ var isMap = (location.search.indexOf("isMap=true") !== -1);
+
+ window.addEventListener("load", () => {
+ let image = document.getElementById("testImage");
+ isMap ? image.setAttribute("ismap", "") : image.removeAttribute("ismap");
+ image.addEventListener("click", event => {
+ if (noDefault) {
+ ok(true, "image element prevents default");
+ event.preventDefault();
+ }
+ }, false);
+
+ window.addEventListener("click", event => {
+ ok(true, "expected prevent default = " + noDefault);
+ ok(true, "actual prevent default = " + event.defaultPrevented);
+ ok(event.defaultPrevented == noDefault, "PreventDefault should work fine");
+ if (noDefault) {
+ window.parent.postMessage("finished", "http://mochi.test:8888");
+ }
+ }, false);
+ window.parent.postMessage("started", "http://mochi.test:8888");
+ }, false);
+ </script>
+</head>
+<body>
+<a href="bug1260704_iframe_empty.html">
+ <img id="testImage" src="file_bug1260704.png" width="100" height="100"/>
+</a>
+</body>
+</html>
diff --git a/dom/html/test/bug1260704_iframe_empty.html b/dom/html/test/bug1260704_iframe_empty.html
new file mode 100644
index 000000000..3c90f9f13
--- /dev/null
+++ b/dom/html/test/bug1260704_iframe_empty.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript">
+ window.addEventListener("load", () => {
+ window.parent.postMessage("empty_frame_loaded", "http://mochi.test:8888");
+ }, false);
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/html/test/bug1292522_iframe.html b/dom/html/test/bug1292522_iframe.html
new file mode 100644
index 000000000..99a3369d0
--- /dev/null
+++ b/dom/html/test/bug1292522_iframe.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html><head><title>iframe</title></head>
+ <body>
+ <p>var testvar = "testiframe"</p>
+ <script>
+ document.domain='example.org';
+ var testvar = "testiframe";
+ </script>
+ </body>
+</html>
diff --git a/dom/html/test/bug1292522_page.html b/dom/html/test/bug1292522_page.html
new file mode 100644
index 000000000..9570f12d2
--- /dev/null
+++ b/dom/html/test/bug1292522_page.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test for Bug 1292522</title>
+ <script>
+ var check_var = function() {
+ opener.postMessage(document.getElementsByTagName('iframe')[0].contentWindow.testvar, "http://mochi.test:8888");
+ }
+ </script>
+ </head>
+ <body>
+ <iframe src="http://test2.example.org:80/tests/dom/html/test/bug1292522_iframe.html" onload="document.domain='example.org';check_var();"></iframe>
+ </body>
+</html>
diff --git a/dom/html/test/bug1315146-iframe.html b/dom/html/test/bug1315146-iframe.html
new file mode 100644
index 000000000..280db5305
--- /dev/null
+++ b/dom/html/test/bug1315146-iframe.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+document.domain = "example.org";
+</script>
diff --git a/dom/html/test/bug1315146-main.html b/dom/html/test/bug1315146-main.html
new file mode 100644
index 000000000..e9f356dda
--- /dev/null
+++ b/dom/html/test/bug1315146-main.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<iframe src="http://example.org/tests/dom/html/test/bug1315146-iframe.html"></iframe>
+<input value="test">
+<script>
+document.domain = "example.org";
+onload = function() {
+ let iframe = document.querySelector("iframe");
+ let input = document.querySelector("input");
+ input.selectionStart = input.selectionEnd = 2;
+ document.body.style.overflow = "scroll";
+ iframe.contentDocument.body.offsetWidth;
+ opener.postMessage({start: input.selectionStart,
+ end: input.selectionEnd}, "*");
+}
+</script>
diff --git a/dom/html/test/bug196523-subframe.html b/dom/html/test/bug196523-subframe.html
new file mode 100644
index 000000000..ac53572a7
--- /dev/null
+++ b/dom/html/test/bug196523-subframe.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<script>
+ function checkDomain(str, msg) {
+ window.parent.postMessage((str == document.domain) + ";" +msg,
+ "http://mochi.test:8888");
+ }
+
+ function reportException(msg) {
+ window.parent.postMessage(false + ";" + msg, "http://mochi.test:8888");
+ }
+
+ var win1;
+ try {
+ win1 = window.open("", "", "width=100,height=100");
+ var otherDomain1 = win1.document.domain;
+ win1.close();
+ checkDomain(otherDomain1, "Opened document should have our domain");
+ } catch(e) {
+ reportException("Exception getting document.domain: " + e);
+ } finally {
+ win1.close();
+ }
+
+ document.domain = "example.org";
+
+ var win2;
+ try {
+ win2 = window.open("", "", "width=100,height=100");
+ var otherDomain2 = win2.document.domain;
+ checkDomain(otherDomain2, "Opened document should have our domain");
+ win2.close();
+ } catch(e) {
+ reportException("Exception getting document.domain after domain set: " + e);
+ } finally {
+ win2.close();
+ }
+</script>
diff --git a/dom/html/test/bug199692-nested-d2.html b/dom/html/test/bug199692-nested-d2.html
new file mode 100644
index 000000000..70064efe7
--- /dev/null
+++ b/dom/html/test/bug199692-nested-d2.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=199692
+-->
+<head>
+ <title>Nested, nested iframe for bug 199692 tests</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+ <div id="nest2div" style="border: 2px dotted blue;">nested, depth 2</div>
+</body>
+</html>
+
diff --git a/dom/html/test/bug199692-nested.html b/dom/html/test/bug199692-nested.html
new file mode 100644
index 000000000..27201a953
--- /dev/null
+++ b/dom/html/test/bug199692-nested.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=199692
+-->
+<head>
+ <title>Nested iframe for bug 199692 tests</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+ <div id="nest1div" style="border: 2px dotted green;">nested, depth 1</div>
+ <iframe src="bug199692-nested-d2.html"></iframe>
+</body>
+</html>
+
diff --git a/dom/html/test/bug199692-popup.html b/dom/html/test/bug199692-popup.html
new file mode 100644
index 000000000..29512823e
--- /dev/null
+++ b/dom/html/test/bug199692-popup.html
@@ -0,0 +1,188 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=199692
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Popup in test for Bug 199692</title>
+ <style type="text/css">
+#content * {
+ border: 2px solid black;
+ margin: 2px;
+ clear: both;
+ height: 20px;
+ overflow: hidden;
+}
+
+#txt, #static, #fixed, #absolute, #relative, #hidden, #float, #empty, #static, #relative {
+ width: 200px !important;
+}
+ </style>
+
+</head>
+<!--
+Elements are styled in such a way that they don't overlap visually
+unless they also overlap structurally.
+
+This file is designed to be opened from test_bug199692.html in a popup
+window, to guarantee that the window in which document.elementFromPoint runs
+is large enough to display all the elements being tested.
+-->
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=199692">Mozilla Bug 199692</a>
+
+<div id="content" style="width: 500px; background-color: #ccc;">
+
+<!-- element containing text -->
+<div id="txt" style="height: 30px;">txt</div>
+
+<!-- element not containing text -->
+<div id="empty" style="border: 2px solid blue;"></div>
+
+<!-- element with only whitespace -->
+<p id="whitespace" style="border: 2px solid magenta;"> </p>
+
+<!-- position: static -->
+<span id="static" style="position: static; border-color: green;">static</span>
+
+<!-- floated element -->
+<div id="float" style="border-color: red; float: right;">float</div>
+
+<!-- position: fixed -->
+<span id="fixed" style="position: fixed; top: 500px; left: 100px; border: 3px solid yellow;">fixed</span>
+
+<!-- position: absolute -->
+<span id="absolute" style="position: absolute; top: 550px; left: 150px; border-color: orange;">abs</span>
+
+<!-- position: relative -->
+<div id="relative" style="position: relative; top: 200px; border-color: teal;">rel</div>
+
+<!-- visibility: hidden -->
+<div id="hidden-wrapper" style="border: 1px dashed teal;">
+ <div id="hidden" style="opacity: 0.5; background-color: blue; visibility:hidden;">hidden</div>
+</div>
+
+<!-- iframe (within iframe) -->
+<iframe id="our-iframe" src="bug199692-nested.html" style="height: 100px; overflow: scroll;"></iframe>
+
+<input type="textbox" id="textbox" value="textbox"></input>
+</div>
+
+<!-- interaction with scrolling -->
+<iframe id="scrolled-iframe"
+ src="bug199692-scrolled.html#down"
+ style="position: absolute; top: 345px; left: 325px; height: 200px; width: 200px"></iframe>
+
+<script type="application/javascript">
+
+var SimpleTest = window.opener.SimpleTest;
+function ok() { window.opener.ok.apply(window.opener, arguments); }
+function is() { window.opener.is.apply(window.opener, arguments); }
+function todo() { window.opener.todo.apply(window.opener, arguments); }
+function todo_is() { window.opener.todo_is.apply(window.opener, arguments); }
+function $(id) { return document.getElementById(id); }
+
+/**
+ * Like is, but for tests which don't always succeed or always fail on all
+ * platforms.
+ */
+function random_fail(a, b, m)
+{
+ if (a != b)
+ todo_is(a, b, m);
+ else
+ is(a, b, m);
+}
+
+/* Test for Bug 199692 */
+
+function getCoords(elt)
+{
+ var x = 0, y = 0;
+
+ do
+ {
+ x += elt.offsetLeft;
+ y += elt.offsetTop;
+ } while ((elt = elt.offsetParent));
+
+ return { x: x, y: y };
+}
+
+var elts = ["txt", "empty", "whitespace", "static", "fixed", "absolute",
+ "relative", "float", "textbox"];
+
+function testPoints()
+{
+ ok('elementFromPoint' in document, "document.elementFromPoint must exist");
+ ok(typeof document.elementFromPoint === "function", "must be a function");
+
+ var doc = document;
+ doc.pt = doc.elementFromPoint; // for shorter lines
+ is(doc.pt(-1, 0), null, "Negative coordinates (-1, 0) should return null");
+ is(doc.pt(0, -1), null, "Negative coordinates (0, -1) should return null");
+ is(doc.pt(-1, -1), null, "Negative coordinates (-1, -1) should return null");
+
+ var pos;
+ for (var i = 0; i < elts.length; i++)
+ {
+ var id = elts[i];
+ var elt = $(id);
+
+ // The upper left corner of an element (with a moderate offset) will
+ // usually contain text, and the lower right corner usually won't.
+ var pos = getCoords(elt);
+ var x = pos.x, y = pos.y;
+ var w = elt.offsetWidth, h = elt.offsetHeight;
+
+ var d = 5;
+ is(doc.pt(x + d, y + d), elt,
+ "(" + (x + d) + "," + (y + d) + ") IDs should match (upper left " +
+ "corner of " + id + ")");
+ is(doc.pt(x + w - d, y + h - d), elt,
+ "(" + (x + w - d) + "," + (y + h - d) + ") IDs should match (lower " +
+ "right corner of " + id + ")");
+ }
+
+ // content
+ var c = $("content");
+ pos = getCoords(c);
+ x = pos.x + c.offsetWidth / 2;
+ y = pos.y;
+
+ // This fails on some platforms but not others for unknown reasons
+ random_fail(doc.pt(x, y), c, "Point to right of #txt should be #content");
+ is(doc.pt(x, y + 1), c, "Point to right of #txt should be #content");
+ random_fail(doc.pt(x + 1, y), c, "Point to right of #txt should be #content");
+ is(doc.pt(x + 1, y + 1), c, "Point to right of #txt should be #content");
+
+ // hidden
+ c = $("hidden");
+ pos = getCoords(c);
+ x = pos.x, y = pos.y;
+ is(doc.pt(x, y), $("hidden-wrapper"),
+ "Hit testing should bypass hidden elements.");
+
+ // iframe nested
+ var iframe = $("our-iframe");
+ pos = getCoords(iframe);
+ x = pos.x, y = pos.y;
+ is(doc.pt(x + 20, y + 20), $("our-iframe"),
+ "Element from nested iframe returned is from calling document");
+ // iframe, doubly nested
+ is(doc.pt(x + 60, y + 60), $("our-iframe"),
+ "Element from doubly nested iframe returned is from calling document");
+
+ // scrolled iframe tests
+ $("scrolled-iframe").contentWindow.runTests();
+
+ SimpleTest.finish();
+ window.close();
+}
+
+window.onload = testPoints;
+</script>
+</body>
+</html>
+
diff --git a/dom/html/test/bug199692-scrolled.html b/dom/html/test/bug199692-scrolled.html
new file mode 100644
index 000000000..f13bf7ab1
--- /dev/null
+++ b/dom/html/test/bug199692-scrolled.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=199692
+-->
+<head>
+ <title>Scrolled page for bug 199692 tests</title>
+ <style type="text/css">
+/* Disable default margins/padding/borders so (0, 0) gets a div. */
+* { margin: 0; padding: 0; border: 0; }
+ </style>
+ <script type="application/javascript">
+function $(id) { return document.getElementById(id); }
+
+function runTests()
+{
+ var is = window.parent.is;
+
+ is(document.elementFromPoint(0, 0), $("down"),
+ "document.elementFromPoint not respecting scrolling?");
+ is(document.elementFromPoint(200, 200), null,
+ "should have returned null for a not-visible point");
+ is(document.elementFromPoint(3, -5), null,
+ "should have returned null for a not-visible point");
+}
+ </script>
+</head>
+<!-- This page is loaded in a 200px-square iframe scrolled to #down. -->
+<body>
+<div style="height: 150px; background: lightblue;">first</div>
+<div id="down" style="height: 250px; background: lightgreen;">second</div>
+</body>
+</html>
+
diff --git a/dom/html/test/bug242709_iframe.html b/dom/html/test/bug242709_iframe.html
new file mode 100644
index 000000000..1ee3320ae
--- /dev/null
+++ b/dom/html/test/bug242709_iframe.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<title></title>
+<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+<script type="text/javascript">
+function submitIframeForm () {
+ document.getElementById('b').submit();
+ document.getElementById('thebutton').disabled = true;
+}
+</script>
+
+</head>
+<body onload="sendMouseEvent({type:'click'}, 'thebutton')">
+
+<form method="get" action="bug242709_load.html" id="b">
+<input type="submit" onclick="submitIframeForm()" id="thebutton">
+</form>
+
+</body>
+</html>
diff --git a/dom/html/test/bug242709_load.html b/dom/html/test/bug242709_load.html
new file mode 100644
index 000000000..c9be79b24
--- /dev/null
+++ b/dom/html/test/bug242709_load.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<title></title>
+</head>
+
+<body onload="parent.submitted();">
+
+<span id="foo"></span>
+
+</body>
+</html>
diff --git a/dom/html/test/bug277724_iframe1.html b/dom/html/test/bug277724_iframe1.html
new file mode 100644
index 000000000..d0d881b76
--- /dev/null
+++ b/dom/html/test/bug277724_iframe1.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Use an unload handler to prevent bfcache from messing with us -->
+<body onunload="parent.childUnloaded = true;">
+ <select id="select">
+ <option>aaa</option>
+ <option>bbbb</option>
+ </select>
+
+ <textarea id="textarea">
+ </textarea>
+
+ <input type="text" id="text">
+ <input type="password" id="password">
+ <input type="checkbox" id="checkbox">
+ <input type="radio" id="radio">
+ <input type="image" id="image">
+ <input type="submit" id="submit">
+ <input type="reset" id="reset">
+ <input type="button" id="button input">
+ <input type="hidden" id="hidden">
+ <input type="file" id="file">
+
+ <button type="submit" id="submit button"></button>
+ <button type="reset" id="reset button"></button>
+ <button type="button" id="button"></button>
+</body>
+</html>
diff --git a/dom/html/test/bug277724_iframe2.xhtml b/dom/html/test/bug277724_iframe2.xhtml
new file mode 100644
index 000000000..14423aa06
--- /dev/null
+++ b/dom/html/test/bug277724_iframe2.xhtml
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!-- Use an unload handler to prevent bfcache from messing with us -->
+<body onunload="parent.childUnloaded = true;">
+ <select id="select">
+ <option>aaa</option>
+ <option>bbbb</option>
+ </select>
+
+ <textarea id="textarea">
+ </textarea>
+
+ <input type="text" id="text" />
+ <input type="password" id="password" />
+ <input type="checkbox" id="checkbox" />
+ <input type="radio" id="radio" />
+ <input type="image" id="image" />
+ <input type="submit" id="submit" />
+ <input type="reset" id="reset" />
+ <input type="button" id="button input" />
+ <input type="hidden" id="hidden" />
+ <input type="file" id="file" />
+
+ <button type="submit" id="submit button"></button>
+ <button type="reset" id="reset button"></button>
+ <button type="button" id="button"></button>
+</body>
+</html>
diff --git a/dom/html/test/bug277890_iframe.html b/dom/html/test/bug277890_iframe.html
new file mode 100644
index 000000000..890ff839e
--- /dev/null
+++ b/dom/html/test/bug277890_iframe.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<title></title>
+<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+<script type="text/javascript">
+function submitIframeForm () {
+ document.getElementById('b').submit();
+ document.getElementById('thebutton').disabled = true;
+}
+</script>
+
+</head>
+<body onload="sendMouseEvent({type:'click'}, 'thebutton')">
+
+<form method="get" action="bug277890_load.html" id="b">
+<button onclick="submitIframeForm()" id="thebutton">Submit</button>
+</form>
+
+</body>
+</html>
diff --git a/dom/html/test/bug277890_load.html b/dom/html/test/bug277890_load.html
new file mode 100644
index 000000000..c9be79b24
--- /dev/null
+++ b/dom/html/test/bug277890_load.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<title></title>
+</head>
+
+<body onload="parent.submitted();">
+
+<span id="foo"></span>
+
+</body>
+</html>
diff --git a/dom/html/test/bug340800_iframe.txt b/dom/html/test/bug340800_iframe.txt
new file mode 100644
index 000000000..369dfe744
--- /dev/null
+++ b/dom/html/test/bug340800_iframe.txt
@@ -0,0 +1,4 @@
+Line 1.
+Line 2.
+Line 3.
+Line 4.
diff --git a/dom/html/test/bug369370-popup.png b/dom/html/test/bug369370-popup.png
new file mode 100644
index 000000000..9063d1264
--- /dev/null
+++ b/dom/html/test/bug369370-popup.png
Binary files differ
diff --git a/dom/html/test/bug372098-link-target.html b/dom/html/test/bug372098-link-target.html
new file mode 100644
index 000000000..b22b8e020
--- /dev/null
+++ b/dom/html/test/bug372098-link-target.html
@@ -0,0 +1,7 @@
+<html>
+<script type="text/javascript">
+
+parent.callback(location.search.substr(1));
+
+</script>
+</html>
diff --git a/dom/html/test/bug392567.jar b/dom/html/test/bug392567.jar
new file mode 100644
index 000000000..bca06515d
--- /dev/null
+++ b/dom/html/test/bug392567.jar
Binary files differ
diff --git a/dom/html/test/bug392567.jar^headers^ b/dom/html/test/bug392567.jar^headers^
new file mode 100644
index 000000000..28b8aa0a5
--- /dev/null
+++ b/dom/html/test/bug392567.jar^headers^
@@ -0,0 +1 @@
+Content-Type: application/java-archive
diff --git a/dom/html/test/bug441930_iframe.html b/dom/html/test/bug441930_iframe.html
new file mode 100644
index 000000000..532cd5c36
--- /dev/null
+++ b/dom/html/test/bug441930_iframe.html
@@ -0,0 +1,27 @@
+<html>
+<body>
+ The content of this <code>textarea</code> should not disappear on page reload:<br />
+ <textarea>This text should not disappear on page reload!</textarea>
+ <script>
+ var ta = document.getElementsByTagName("textarea").item(0);
+ if (!parent.reloaded) {
+ parent.reloaded = true;
+ ta.disabled = true;
+ location.reload();
+ } else {
+ // Primary regression test:
+ parent.isnot(ta.value, "",
+ "Content of dynamically disabled textarea disappeared on page reload.");
+
+ // Bonus regression test: changing the textarea's defaultValue after
+ // reloading should also update the textarea's value.
+ var newDefaultValue = "new default value";
+ ta.defaultValue = newDefaultValue;
+ parent.is(ta.value, newDefaultValue,
+ "Changing the defaultValue attribute of a textarea fails to update its value attribute.");
+
+ parent.SimpleTest.finish();
+ }
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/bug445004-inner.html b/dom/html/test/bug445004-inner.html
new file mode 100644
index 000000000..b946520ea
--- /dev/null
+++ b/dom/html/test/bug445004-inner.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <base href="http://test1.example.org/tests/dom/html/test/bug445004-inner.html">
+ <script src="bug445004-inner.js"></script>
+ </head>
+ <body>
+ <iframe name="w" id="w" width="100" height="100"></iframe>
+ <iframe name="x" id="x" width="100" height="100"></iframe>
+ <iframe name="y" id="y" width="100" height="100"></iframe>
+ <iframe name="z" id="z" width="100" height="100"></iframe>
+ <img src="test1.example.org.png">
+ </body>
+</html>
diff --git a/dom/html/test/bug445004-inner.js b/dom/html/test/bug445004-inner.js
new file mode 100644
index 000000000..3675f8e69
--- /dev/null
+++ b/dom/html/test/bug445004-inner.js
@@ -0,0 +1,23 @@
+document.domain = "example.org";
+function $(str) { return document.getElementById(str); }
+function hookLoad(str) {
+ $(str).onload = function() { window.parent.parent.postMessage('end', '*'); };
+ window.parent.parent.postMessage('start', '*');
+}
+window.onload = function() {
+ hookLoad("w");
+ $("w").contentWindow.location.href = "test1.example.org.png";
+ hookLoad("x");
+ var doc = $("x").contentDocument;
+ doc.write('<img src="test1.example.org.png">');
+ doc.close();
+};
+function doIt() {
+ hookLoad("y");
+ $("y").contentWindow.location.href = "example.org.png";
+ hookLoad("z");
+ var doc = $("z").contentDocument;
+ doc.write('<img src="example.org.png">');
+ doc.close();
+}
+window.addEventListener("message", doIt, false); \ No newline at end of file
diff --git a/dom/html/test/bug445004-outer-abs.html b/dom/html/test/bug445004-outer-abs.html
new file mode 100644
index 000000000..8a93ef2b7
--- /dev/null
+++ b/dom/html/test/bug445004-outer-abs.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <base href="http://example.org/tests/dom/html/test/bug445004-outer.html">
+ <script>document.domain = "example.org"</script>
+ </head>
+ <body>
+ <iframe width="500" height="200" src="http://test1.example.org/tests/dom/html/test/bug445004-inner.html"
+ onload="window.frames[0].doIt()"></iframe>
+ </body>
+</html>
diff --git a/dom/html/test/bug445004-outer-rel.html b/dom/html/test/bug445004-outer-rel.html
new file mode 100644
index 000000000..096733889
--- /dev/null
+++ b/dom/html/test/bug445004-outer-rel.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <base href="http://example.org/tests/dom/html/test/bug445004-outer.html">
+ <script>document.domain = "example.org"</script>
+ </head>
+ <body>
+ <iframe width="500" height="200" src="bug445004-inner.html"
+ onload="window.frames[0].doIt()"></iframe>
+ </body>
+</html>
diff --git a/dom/html/test/bug445004-outer-write.html b/dom/html/test/bug445004-outer-write.html
new file mode 100644
index 000000000..be6e37b6d
--- /dev/null
+++ b/dom/html/test/bug445004-outer-write.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <base href="http://example.org/tests/dom/html/test/bug445004-outer.html">
+ <script>document.domain = "example.org"</script>
+ </head>
+ <body>
+ <iframe width="500" height="200" src="javascript:&quot;<!DOCTYPE html> <html> <script> function $(str) { return document.getElementById(str); } function hookLoad(str) { $(str).onload = function() { window.parent.parent.postMessage('end', '*'); }; window.parent.parent.postMessage('start', '*'); } window.onload = function() { hookLoad(\&quot;w\&quot;); $(\&quot;w\&quot;).contentWindow.location.href = \&quot;example.org.png\&quot;; hookLoad(\&quot;x\&quot;); var doc = $(\&quot;x\&quot;).contentDocument; doc.write('<img src=\&quot;example.org.png\&quot;>'); doc.close(); }; function doIt() { hookLoad(\&quot;y\&quot;); $(\&quot;y\&quot;).contentWindow.location.href = \&quot;example.org.png\&quot;; hookLoad(\&quot;z\&quot;); var doc = $(\&quot;z\&quot;).contentDocument; doc.write('<img src=\&quot;example.org.png\&quot;>'); doc.close(); } </script> <body> <iframe name=\&quot;w\&quot; id=\&quot;w\&quot; width=\&quot;100\&quot; height=\&quot;100\&quot;></iframe> <iframe name=\&quot;x\&quot; id=\&quot;x\&quot; width=\&quot;100\&quot; height=\&quot;100\&quot;></iframe> <iframe name=\&quot;y\&quot; id=\&quot;y\&quot; width=\&quot;100\&quot; height=\&quot;100\&quot;></iframe> <iframe name=\&quot;z\&quot; id=\&quot;z\&quot; width=\&quot;100\&quot; height=\&quot;100\&quot;></iframe><img src=\&quot;example.org.png\&quot;> </body> </html>&quot; "
+ onload="window.frames[0].doIt();"></iframe>
+ </body>
+</html>
diff --git a/dom/html/test/bug446483-iframe.html b/dom/html/test/bug446483-iframe.html
new file mode 100644
index 000000000..fe5a6cf9f
--- /dev/null
+++ b/dom/html/test/bug446483-iframe.html
@@ -0,0 +1,10 @@
+<script>
+function doe(){
+window.focus();
+window.getSelection().collapse(document.body, 0);
+}
+setTimeout(doe,50);
+
+setTimeout(function() {window.location.reload()}, 200);
+</script>
+<span contenteditable="true"></span>
diff --git a/dom/html/test/bug448564-echo.sjs b/dom/html/test/bug448564-echo.sjs
new file mode 100644
index 000000000..1eee116fd
--- /dev/null
+++ b/dom/html/test/bug448564-echo.sjs
@@ -0,0 +1,6 @@
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+
+ response.write(request.queryString);
+}
diff --git a/dom/html/test/bug448564-iframe-1.html b/dom/html/test/bug448564-iframe-1.html
new file mode 100644
index 000000000..4f3e79e5d
--- /dev/null
+++ b/dom/html/test/bug448564-iframe-1.html
@@ -0,0 +1,16 @@
+<html>
+<body>
+
+ <table>
+ <form action="bug448564-echo.sjs" method="GET">
+ <tr><td><input name="a" value="aval"></td></tr>
+ <input type="hidden" name="b" value="bval">
+ <input name="c" value="cval">
+ <tr><td><input name="d" value="dval" type="submit"></td></tr>
+ </form>
+ </table>
+
+ <script src="bug448564-submit.js"></script>
+
+</body>
+</html>
diff --git a/dom/html/test/bug448564-iframe-2.html b/dom/html/test/bug448564-iframe-2.html
new file mode 100644
index 000000000..dba19b37e
--- /dev/null
+++ b/dom/html/test/bug448564-iframe-2.html
@@ -0,0 +1,16 @@
+<html>
+<body>
+
+ <form action="bug448564-echo.sjs" method="GET">
+ <table>
+ <tr><td><input name="a" value="aval"></td></tr>
+ <input type="hidden" name="b" value="bval">
+ <input name="c" value="cval">
+ <tr><td><input name="d" value="dval" type="submit"></td></tr>
+ </table>
+ </form>
+
+ <script src="bug448564-submit.js"></script>
+
+</body>
+</html>
diff --git a/dom/html/test/bug448564-iframe-3.html b/dom/html/test/bug448564-iframe-3.html
new file mode 100644
index 000000000..64288ebb1
--- /dev/null
+++ b/dom/html/test/bug448564-iframe-3.html
@@ -0,0 +1,16 @@
+<html>
+<body>
+
+ <table>
+ <span><form action="bug448564-echo.sjs" method="GET">
+ <tr><td><input name="a" value="aval"></td></tr>
+ <input type="hidden" name="b" value="bval">
+ <input name="c" value="cval">
+ <tr><td><input name="d" value="dval" type="submit"></td></tr>
+ </form></span>
+ </table>
+
+ <script src="bug448564-submit.js"></script>
+
+</body>
+</html>
diff --git a/dom/html/test/bug448564-submit.js b/dom/html/test/bug448564-submit.js
new file mode 100644
index 000000000..34de44d4d
--- /dev/null
+++ b/dom/html/test/bug448564-submit.js
@@ -0,0 +1,4 @@
+var inputs = document.getElementsByTagName("input");
+for (var input, i = 0; input = inputs[i]; ++i)
+ if ("submit" == input.type)
+ input.click();
diff --git a/dom/html/test/bug499092.html b/dom/html/test/bug499092.html
new file mode 100644
index 000000000..0476fa4e7
--- /dev/null
+++ b/dom/html/test/bug499092.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<script>
+var title = document.createElementNS("http://www.w3.org/1999/xhtml", "aa:title");
+title.textContent = "HTML OK";
+document.documentElement.firstChild.appendChild(title);
+</script>
diff --git a/dom/html/test/bug499092.xml b/dom/html/test/bug499092.xml
new file mode 100644
index 000000000..eedd2c77b
--- /dev/null
+++ b/dom/html/test/bug499092.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<doc xmlns:aa="http://www.w3.org/1999/xhtml">
+<aa:title>XML OK</aa:title>
+</doc>
diff --git a/dom/html/test/bug514856_iframe.html b/dom/html/test/bug514856_iframe.html
new file mode 100644
index 000000000..2abf9e91e
--- /dev/null
+++ b/dom/html/test/bug514856_iframe.html
@@ -0,0 +1,21 @@
+<html>
+ <head>
+ <style>
+ html, body, a, img {
+ padding: 0px;
+ margin: 0px;
+ border: 0px;
+ }
+ img {
+ width: 100%;
+ height: 100%;
+ }
+ </style>
+ </head>
+ <body>
+ <a href="bug514856_iframe.html">
+ <img ismap="ismap"
+ src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IAAAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1JREFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jqch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0vr4MkhoXe0rZigAAAABJRU5ErkJggg==">
+ </a>
+ </body>
+</html>
diff --git a/dom/html/test/bug592641_img.jpg b/dom/html/test/bug592641_img.jpg
new file mode 100644
index 000000000..c9103b8b0
--- /dev/null
+++ b/dom/html/test/bug592641_img.jpg
Binary files differ
diff --git a/dom/html/test/bug649134/file_bug649134-1.sjs b/dom/html/test/bug649134/file_bug649134-1.sjs
new file mode 100644
index 000000000..890f8aa5e
--- /dev/null
+++ b/dom/html/test/bug649134/file_bug649134-1.sjs
@@ -0,0 +1,12 @@
+function handleRequest(request, response)
+{
+ response.seizePower();
+ var r = 'HTTP/1.1 200 OK\r\n' +
+ 'Content-Type: text/html\r\n' +
+ 'Link: < \014>; rel="stylesheet"\r\n' +
+ '\r\n' +
+ '<!-- selector {} body {display:none;} --><body>PASS</body>\r\n';
+ response.bodyOutputStream.write(r, r.length);
+ response.bodyOutputStream.flush();
+ response.finish();
+}
diff --git a/dom/html/test/bug649134/file_bug649134-2.sjs b/dom/html/test/bug649134/file_bug649134-2.sjs
new file mode 100644
index 000000000..7f005eda6
--- /dev/null
+++ b/dom/html/test/bug649134/file_bug649134-2.sjs
@@ -0,0 +1,12 @@
+function handleRequest(request, response)
+{
+ response.seizePower();
+ var r = 'HTTP/1.1 200 OK\r\n' +
+ 'Content-Type: text/html\r\n' +
+ 'Link: < \014>; rel="stylesheet",\r\n' +
+ '\r\n' +
+ '<!-- selector {} body {display:none;} --><body>PASS</body>\r\n';
+ response.bodyOutputStream.write(r, r.length);
+ response.bodyOutputStream.flush();
+ response.finish();
+}
diff --git a/dom/html/test/bug649134/index.html b/dom/html/test/bug649134/index.html
new file mode 100644
index 000000000..2f3973704
--- /dev/null
+++ b/dom/html/test/bug649134/index.html
@@ -0,0 +1,3 @@
+body {
+ display:none;
+}
diff --git a/dom/html/test/chrome.ini b/dom/html/test/chrome.ini
new file mode 100644
index 000000000..fd8009690
--- /dev/null
+++ b/dom/html/test/chrome.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+support-files =
+ file_anchor_ping.html
+ wakelock.ogg
+ wakelock.ogv
+
+[test_anchor_ping.html]
+skip-if = os == 'android'
+[test_audio_wakelock.html]
+[test_video_wakelock.html]
diff --git a/dom/html/test/dummy_page.html b/dom/html/test/dummy_page.html
new file mode 100644
index 000000000..fd238954c
--- /dev/null
+++ b/dom/html/test/dummy_page.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<title>Dummy test page</title>
+<meta charset="utf-8"/>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
diff --git a/dom/html/test/file_anchor_ping.html b/dom/html/test/file_anchor_ping.html
new file mode 100644
index 000000000..3b9717263
--- /dev/null
+++ b/dom/html/test/file_anchor_ping.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>file_anchor_ping.html</title>
+ </head>
+ <body onload="document.body.firstElementChild.click()">
+ <a href="/">click me</a>
+ <script>
+ document.body.firstElementChild.ping = window.location.search.slice(1);
+ </script>
+ </body>
+</html>
diff --git a/dom/html/test/file_bug1108547-1.html b/dom/html/test/file_bug1108547-1.html
new file mode 100644
index 000000000..efc0eae49
--- /dev/null
+++ b/dom/html/test/file_bug1108547-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+document.cookie = "foo=bar";
+</script>
diff --git a/dom/html/test/file_bug1108547-2.html b/dom/html/test/file_bug1108547-2.html
new file mode 100644
index 000000000..af06c8c42
--- /dev/null
+++ b/dom/html/test/file_bug1108547-2.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<body onload="document.querySelector('form').submit();">
+<form action="javascript:opener.document.getElementById('result').textContent = document.cookie;" target="_blank">
+</form>
+<div id="result">not tested yet</div>
+</body>
diff --git a/dom/html/test/file_bug1108547-3.html b/dom/html/test/file_bug1108547-3.html
new file mode 100644
index 000000000..d99a2d355
--- /dev/null
+++ b/dom/html/test/file_bug1108547-3.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<body onload="document.querySelector('a').click();">
+<a href="javascript:opener.document.getElementById('result').textContent = document.cookie;" target="_blank">test</a>
+<div id="result">not tested yet</div>
+</body>
diff --git a/dom/html/test/file_bug1166138_1x.png b/dom/html/test/file_bug1166138_1x.png
new file mode 100644
index 000000000..df421453c
--- /dev/null
+++ b/dom/html/test/file_bug1166138_1x.png
Binary files differ
diff --git a/dom/html/test/file_bug1166138_2x.png b/dom/html/test/file_bug1166138_2x.png
new file mode 100644
index 000000000..6f76d4438
--- /dev/null
+++ b/dom/html/test/file_bug1166138_2x.png
Binary files differ
diff --git a/dom/html/test/file_bug1166138_def.png b/dom/html/test/file_bug1166138_def.png
new file mode 100644
index 000000000..144a2f0b9
--- /dev/null
+++ b/dom/html/test/file_bug1166138_def.png
Binary files differ
diff --git a/dom/html/test/file_bug1260704.png b/dom/html/test/file_bug1260704.png
new file mode 100644
index 000000000..df421453c
--- /dev/null
+++ b/dom/html/test/file_bug1260704.png
Binary files differ
diff --git a/dom/html/test/file_bug209275_1.html b/dom/html/test/file_bug209275_1.html
new file mode 100644
index 000000000..3f7233876
--- /dev/null
+++ b/dom/html/test/file_bug209275_1.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <base href="http://example.org" />
+</head>
+<body onload="load();">
+Initial state
+
+<script>
+function load() {
+ // Nuke and rebuild the page.
+ document.removeChild(document.documentElement);
+ var html = document.createElement("html");
+ var body = document.createElement("body");
+ html.appendChild(body);
+ var link = document.createElement("a");
+ link.href = "#";
+ link.id = "link";
+ body.appendChild(link);
+ document.appendChild(html);
+
+ // Tell our parent to have a look at us.
+ parent.gGen.next();
+}
+</script>
+
+</body>
+</html>
diff --git a/dom/html/test/file_bug209275_2.html b/dom/html/test/file_bug209275_2.html
new file mode 100644
index 000000000..36e9ff467
--- /dev/null
+++ b/dom/html/test/file_bug209275_2.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <base href="http://example.com" />
+</head>
+<body onload="load();">
+Page 2 initial state
+
+<script>
+function load() {
+ // Nuke and rebuild the page.
+ document.removeChild(document.documentElement);
+ html = document.createElement("html");
+ html.innerHTML = "<body><a href='/' id='link'>B</a></body>"
+ document.appendChild(html);
+
+ // Tell our parent to have a look at us
+ parent.gGen.next();
+}
+</script>
+
+</body>
+</html>
diff --git a/dom/html/test/file_bug209275_3.html b/dom/html/test/file_bug209275_3.html
new file mode 100644
index 000000000..254411590
--- /dev/null
+++ b/dom/html/test/file_bug209275_3.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <base href="http://example.org" />
+</head>
+<body onload="load();">
+Initial state
+
+<script>
+function load() {
+ // Nuke and rebuild the page. If document.open() clears the <base> properly,
+ // our new <base> will take precedence and the test will pass.
+ document.open();
+ document.write("<html><base href='http://mochi.test:8888' /><body>" +
+ "<a id='link' href='/'>A</a></body></html>");
+
+ // Tell our parent to have a look at us.
+ parent.gGen.next();
+}
+</script>
+
+</body>
+</html>
diff --git a/dom/html/test/file_bug297761.html b/dom/html/test/file_bug297761.html
new file mode 100644
index 000000000..5e861a00f
--- /dev/null
+++ b/dom/html/test/file_bug297761.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <base href="http://www.mozilla.org/">
+ </head>
+ <body>
+ <form action="">
+ <input type='submit' formaction="">
+ <button type='submit' formaction=""></button>
+ <input id='i' type='image' formaction="">
+ </form>
+ </body>
+</html>
diff --git a/dom/html/test/file_bug417760.png b/dom/html/test/file_bug417760.png
new file mode 100644
index 000000000..743292dc6
--- /dev/null
+++ b/dom/html/test/file_bug417760.png
Binary files differ
diff --git a/dom/html/test/file_bug649778.html b/dom/html/test/file_bug649778.html
new file mode 100644
index 000000000..48a9870e7
--- /dev/null
+++ b/dom/html/test/file_bug649778.html
@@ -0,0 +1,11 @@
+<html>
+<script>
+function test() {
+ document.open();
+ document.write('<html><body>WYCIWYG DOCUMENT</body></html>');
+ document.close();
+}
+</script>
+<body onload="setTimeout(test, 0);">
+</body>
+</html>
diff --git a/dom/html/test/file_bug649778.html^headers^ b/dom/html/test/file_bug649778.html^headers^
new file mode 100644
index 000000000..4030ea1d3
--- /dev/null
+++ b/dom/html/test/file_bug649778.html^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/dom/html/test/file_bug871161-1.html b/dom/html/test/file_bug871161-1.html
new file mode 100644
index 000000000..16015f0c4
--- /dev/null
+++ b/dom/html/test/file_bug871161-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset=windows-1251>
+<title>Page with non-default charset</title>
+<script>
+function run() {
+ document.forms[0].submit();
+}
+</script>
+</head>
+<body onload="run();">
+<form method=post action="http://example.org/tests/dom/html/test/file_bug871161-2.html"></form>
+</body>
+</html>
+
diff --git a/dom/html/test/file_bug871161-2.html b/dom/html/test/file_bug871161-2.html
new file mode 100644
index 000000000..18cf825b2
--- /dev/null
+++ b/dom/html/test/file_bug871161-2.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Page without declared charset</title>
+<script>
+function done() {
+ window.opener.postMessage(document.characterSet, "*");
+}
+</script>
+</head>
+<body onload="done();">
+</body>
+</html>
+
diff --git a/dom/html/test/file_bug893537.html b/dom/html/test/file_bug893537.html
new file mode 100644
index 000000000..1dcb454ff
--- /dev/null
+++ b/dom/html/test/file_bug893537.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=893537
+-->
+<body>
+<iframe id="iframe" src="data:text/html;charset=US-ASCII,Goodbye World" srcdoc="Hello World"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_content_contextmenu.html b/dom/html/test/file_content_contextmenu.html
new file mode 100644
index 000000000..4d6874d2f
--- /dev/null
+++ b/dom/html/test/file_content_contextmenu.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title></title>
+ <style>
+ body {
+ margin: 0;
+ width: 100vw;
+ height: 100vh;
+ }
+ </style>
+</head>
+<body contextmenu="testmenu">
+ <menu type="context" id="testmenu">
+ <menuitem label="Test Context Menu Click" id="menuitem"></menuitem>
+ <hr>
+ </menu>
+</body>
+</html>
diff --git a/dom/html/test/file_cookiemanager.js b/dom/html/test/file_cookiemanager.js
new file mode 100644
index 000000000..98f356534
--- /dev/null
+++ b/dom/html/test/file_cookiemanager.js
@@ -0,0 +1,20 @@
+let { classes: Cc, interfaces: Ci } = Components;
+addMessageListener("getCookieFromManager", ({ host, path }) => {
+ let cm = Cc["@mozilla.org/cookiemanager;1"]
+ .getService(Ci.nsICookieManager);
+ let values = [];
+ path = path.substring(0, path.lastIndexOf("/") + 1);
+ let e = cm.enumerator;
+ while (e.hasMoreElements()) {
+ let cookie = e.getNext().QueryInterface(Ci.nsICookie);
+ if (!cookie) {
+ break;
+ }
+ if (host != cookie.host || path != cookie.path) {
+ continue;
+ }
+ values.push(cookie.name + "=" + cookie.value);
+ }
+
+ sendAsyncMessage("getCookieFromManager:return", { cookie: values.join("; ") });
+});
diff --git a/dom/html/test/file_formSubmission_img.jpg b/dom/html/test/file_formSubmission_img.jpg
new file mode 100644
index 000000000..dcd99b967
--- /dev/null
+++ b/dom/html/test/file_formSubmission_img.jpg
Binary files differ
diff --git a/dom/html/test/file_formSubmission_text.txt b/dom/html/test/file_formSubmission_text.txt
new file mode 100644
index 000000000..a496efee8
--- /dev/null
+++ b/dom/html/test/file_formSubmission_text.txt
@@ -0,0 +1 @@
+This is a text file
diff --git a/dom/html/test/file_fullscreen-api-keys.html b/dom/html/test/file_fullscreen-api-keys.html
new file mode 100644
index 000000000..33a68d03e
--- /dev/null
+++ b/dom/html/test/file_fullscreen-api-keys.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+</head>
+<body>
+<script>
+window.addEventListener("Test:DispatchKeyEvents", aEvent => {
+ var keyCode = KeyEvent["DOM_" + aEvent.detail.code];
+
+ document.body.focus();
+ var evt = document.createEvent("KeyboardEvent");
+ evt.initKeyEvent("keydown", true, true, window,
+ false, false, false, false,
+ keyCode, 0);
+ document.body.dispatchEvent(evt);
+
+ evt = document.createEvent("KeyboardEvent");
+ evt.initKeyEvent("keypress", true, true, window,
+ false, false, false, false,
+ keyCode, 0);
+ document.body.dispatchEvent(evt);
+
+ evt = document.createEvent("KeyboardEvent");
+ evt.initKeyEvent("keyup", true, true, window,
+ false, false, false, false,
+ keyCode, 0);
+ document.body.dispatchEvent(evt);
+});
+</script>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-api.html b/dom/html/test/file_fullscreen-api.html
new file mode 100644
index 000000000..89162058e
--- /dev/null
+++ b/dom/html/test/file_fullscreen-api.html
@@ -0,0 +1,317 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=545812
+
+Test DOM full-screen API.
+
+-->
+<head>
+ <title>Test for Bug 545812</title>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ body {
+ background-color: black;
+ }
+ </style>
+</head>
+<body>
+<script type="application/javascript">
+
+/** Test for Bug 545812 **/
+
+function ok(condition, msg) {
+ opener.ok(condition, "[fullscreen] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[fullscreen] " + msg);
+}
+
+/*
+<html>
+ <body onload='document.body.requestFullscreen();'>
+ <iframe id='inner-frame'></iframe>
+ </body>
+</html>
+*/
+var iframeContents = "data:text/html;charset=utf-8,<html><body onload%3D'parent.SimpleTest.waitForFocus(function(){document.body.requestFullscreen();});'><iframe id%3D'inner-frame'><%2Fiframe><%2Fbody><%2Fhtml>";
+
+var iframe = null;
+var outOfDocElement = null;
+var inDocElement = null;
+var container = null;
+var button = null;
+
+
+function sendMouseClick(element) {
+ synthesizeMouseAtCenter(element, {});
+}
+
+function fullScreenElement() {
+ return document.getElementById('full-screen-element');
+}
+
+function enter1(event) {
+ is(event.target, document, "Event target should be full-screen document #1");
+ ok(document.fullscreen, "Document should be in fullscreen");
+ is(document.fullscreenElement, fullScreenElement(),
+ "Full-screen element should be div element.");
+ ok(document.fullscreenElement.matches(":fullscreen"),
+ "FSE should match :fullscreen");
+ var fse = fullScreenElement();
+ addFullscreenChangeContinuation("exit", exit1);
+ fse.parentNode.removeChild(fse);
+ is(document.fullscreenElement, null,
+ "Full-screen element should be null after removing.");
+ document.body.appendChild(fse);
+ is(document.fullscreenElement, null,
+ "Full-screen element should still be null after re-adding former FSE.");
+}
+
+function exit1(event) {
+ is(event.target, document, "Event target should be full-screen document #2");
+ ok(!document.fullscreen, "Document should not be in fullscreen");
+ is(document.fullscreenElement, null, "Full-screen element should be null.");
+ iframe = document.createElement("iframe");
+ iframe.allowFullscreen = true;
+ addFullscreenChangeContinuation("enter", enter2);
+ document.body.appendChild(iframe);
+ iframe.src = iframeContents;
+}
+
+function enter2(event) {
+ is(event.target, document, "Event target should be full-screen document #3");
+ is(document.fullscreenElement, iframe,
+ "Full-screen element should be iframe element.");
+ is(iframe.contentDocument.fullscreenElement, iframe.contentDocument.body,
+ "Full-screen element in subframe should be body");
+
+ // The iframe's body is full-screen. Cancel full-screen in the subdocument to return
+ // the full-screen element to the previous full-screen element. This causes
+ // a fullscreenchange event.
+ addFullscreenChangeContinuation("exit", exit2);
+ document.exitFullscreen();
+}
+
+function exit2(event) {
+ is(document.fullscreenElement, null,
+ "Full-screen element should have rolled back.");
+ is(iframe.contentDocument.fullscreenElement, null,
+ "Full-screen element in subframe should be null");
+
+ addFullscreenChangeContinuation("enter", enter3);
+ fullScreenElement().requestFullscreen();
+}
+
+function enter3(event) {
+ is(event.target, document, "Event target should be full-screen document #3");
+ is(document.fullscreenElement, fullScreenElement(),
+ "Full-screen element should be div.");
+
+ // Transplant the FSE into subdoc. Should exit full-screen.
+ addFullscreenChangeContinuation("exit", exit3);
+ var _innerFrame = iframe.contentDocument.getElementById("inner-frame");
+ var fse = fullScreenElement();
+ _innerFrame.contentDocument.body.appendChild(fse);
+ is(document.fullscreenElement, null,
+ "Full-screen element transplanted, should be null.");
+ is(iframe.contentDocument.fullscreenElement, null,
+ "Full-screen element in outer frame should be null.");
+ is(_innerFrame.contentDocument.fullscreenElement, null,
+ "Full-screen element in inner frame should be null.");
+
+ document.body.appendChild(fse);
+}
+
+function exit3(event) {
+ is(event.target, document, "Event target should be full-screen document #4");
+ is(document.fullscreenElement, null, "Full-screen element should be null.");
+ document.body.removeChild(iframe);
+ iframe = null;
+
+ // Do a request out of document. It should be denied.
+ // Continue test in the following fullscreenerror handler.
+ outOfDocElement = document.createElement("div");
+ addFullscreenErrorContinuation(error1);
+ outOfDocElement.requestFullscreen();
+}
+
+function error1(event) {
+ ok(!document.fullscreenElement,
+ "Requests for full-screen from not-in-doc elements should fail.");
+ container = document.createElement("div");
+ inDocElement = document.createElement("div");
+ container.appendChild(inDocElement);
+ fullScreenElement().appendChild(container);
+
+ addFullscreenChangeContinuation("enter", enter4);
+ inDocElement.requestFullscreen();
+}
+
+function enter4(event) {
+ is(event.target, document, "Event target should be full-screen document #5");
+ is(document.fullscreenElement, inDocElement, "FSE should be inDocElement.");
+
+ // Remove full-screen ancestor element from document, verify it stops being reported as current FSE.
+ addFullscreenChangeContinuation("exit", exit_to_arg_test_1);
+ container.parentNode.removeChild(container);
+ is(document.fullscreenElement, null,
+ "Should not have a full-screen element again.");
+}
+
+function exit_to_arg_test_1(event) {
+ ok(!document.fullscreenElement,
+ "Should have left full-screen mode (third time).");
+ addFullscreenChangeContinuation("enter", enter_from_arg_test_1);
+ var threw = false;
+ try {
+ fullScreenElement().requestFullscreen(123);
+ } catch (e) {
+ threw = true;
+ // trigger normal fullscreen so that we continue
+ fullScreenElement().requestFullscreen();
+ }
+ ok(!threw, "requestFullscreen with bogus arg (123) shouldn't throw exception");
+}
+
+function enter_from_arg_test_1(event) {
+ ok(document.fullscreenElement,
+ "Should have entered full-screen after calling with bogus (ignored) argument (fourth time)");
+ addFullscreenChangeContinuation("exit", exit_to_arg_test_2);
+ document.exitFullscreen();
+}
+
+function exit_to_arg_test_2(event) {
+ ok(!document.fullscreenElement,
+ "Should have left full-screen mode (fourth time).");
+ addFullscreenChangeContinuation("enter", enter_from_arg_test_2);
+ var threw = false;
+ try {
+ fullScreenElement().requestFullscreen({ vrDisplay: null });
+ } catch (e) {
+ threw = true;
+ // trigger normal fullscreen so that we continue
+ fullScreenElement().requestFullscreen();
+ }
+ ok(!threw, "requestFullscreen with { vrDisplay: null } shouldn't throw exception");
+}
+
+function enter_from_arg_test_2(event) {
+ ok(document.fullscreenElement,
+ "Should have entered full-screen after calling with vrDisplay null argument (fifth time)");
+ addFullscreenChangeContinuation("exit", exit4);
+ document.exitFullscreen();
+}
+
+function exit4(event) {
+ ok(!document.fullscreenElement,
+ "Should be back in non-full-screen mode (fifth time)");
+ SpecialPowers.pushPrefEnv({"set":[["full-screen-api.allow-trusted-requests-only", true]]}, function() {
+ addFullscreenErrorContinuation(error2);
+ fullScreenElement().requestFullscreen();
+ });
+}
+
+function error2(event) {
+ ok(!document.fullscreenElement,
+ "Should still be in normal mode, because calling context isn't trusted.");
+ button = document.createElement("button");
+ button.onclick = function(){fullScreenElement().requestFullscreen();}
+ fullScreenElement().appendChild(button);
+ addFullscreenChangeContinuation("enter", enter5);
+ sendMouseClick(button);
+}
+
+function enter5(event) {
+ ok(document.fullscreenElement, "Moved to full-screen after mouse click");
+ addFullscreenChangeContinuation("exit", exit5);
+ document.exitFullscreen();
+}
+
+function exit5(event) {
+ ok(!document.fullscreenElement,
+ "Should have left full-screen mode (last time).");
+ SpecialPowers.pushPrefEnv({
+ "set":[["full-screen-api.allow-trusted-requests-only", false],
+ ["full-screen-api.enabled", false]]}, function() {
+ is(document.fullscreenEnabled, false, "document.fullscreenEnabled should be false if full-screen-api.enabled is false");
+ addFullscreenErrorContinuation(error3);
+ fullScreenElement().requestFullscreen();
+ });
+}
+
+function error3(event) {
+ ok(!document.fullscreenElement,
+ "Should still be in normal mode, because pref is not enabled.");
+
+ SpecialPowers.pushPrefEnv({"set":[["full-screen-api.enabled", true]]}, function() {
+ is(document.fullscreenEnabled, true, "document.fullscreenEnabled should be true if full-screen-api.enabled is true");
+ opener.nextTest();
+ });
+}
+
+function begin() {
+ testNamespaces(() => {
+ addFullscreenChangeContinuation("enter", enter1);
+ fullScreenElement().requestFullscreen();
+ });
+}
+
+function testNamespaces(followupTestFn) {
+ let tests = [
+ {allowed: false, name: "element", ns: "http://www.w3.org/XML/1998/namespace"},
+ {allowed: false, name: "element", ns: "http://www.w3.org/1999/xlink"},
+ {allowed: false, name: "element", ns: "http://www.w3.org/2000/svg"},
+ {allowed: false, name: "element", ns: "http://www.w3.org/1998/Math/MathML"},
+ {allowed: false, name: "mathml", ns: "unknown"},
+ {allowed: false, name: "svg", ns: "unknown"},
+ {allowed: true, name: "element", ns: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"},
+ {allowed: true, name: "element", ns: "http://www.w3.org/1999/xhtml"},
+ {allowed: true, name: "svg", ns: "http://www.w3.org/1999/xhtml"},
+ {allowed: true, name: "math", ns: "http://www.w3.org/1999/xhtml"},
+ {allowed: true, name: "svg", ns: "http://www.w3.org/2000/svg"},
+ {allowed: true, name: "math", ns: "http://www.w3.org/1998/Math/MathML"},
+ {allowed: true, name: "element"},
+ ];
+
+ function runNextNamespaceTest() {
+ let test = tests.shift();
+ if (!test) {
+ followupTestFn();
+ return;
+ }
+
+ let elem = test.ns ? document.createElementNS(test.ns, test.name) :
+ document.createElement(test.name);
+ document.body.appendChild(elem);
+
+ if (test.allowed) {
+ addFullscreenChangeContinuation("enter", () => {
+ ok(document.fullscreen, "Document should be in fullscreen");
+ is(document.fullscreenElement, elem,
+ `Element named '${test.name}' in this namespace should be allowed: ${test.ns}`);
+ addFullscreenChangeContinuation("exit", runNextNamespaceTest);
+ document.body.removeChild(elem);
+ });
+ } else {
+ addFullscreenErrorContinuation(() => {
+ ok(!document.fullscreenElement,
+ `Element named '${test.name}' in this namespace should not be allowed: ${test.ns}`);
+ document.body.removeChild(elem);
+ runNextNamespaceTest();
+ });
+ }
+
+ elem.requestFullscreen();
+ }
+
+ runNextNamespaceTest();
+}
+</script>
+</pre>
+<div id="full-screen-element"></div>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-backdrop.html b/dom/html/test/file_fullscreen-backdrop.html
new file mode 100644
index 000000000..d3d4cc446
--- /dev/null
+++ b/dom/html/test/file_fullscreen-backdrop.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1064843</title>
+ <style id="style"></style>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="text/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ html {
+ overflow: hidden;
+ }
+ #placeholder {
+ height: 1000vh;
+ }
+ </style>
+</head>
+<body>
+<div id="fullscreen"></div>
+<div id="placeholder"></div>
+<script>
+
+const gStyle = document.getElementById("style");
+const gFullscreen = document.getElementById("fullscreen");
+
+function is(a, b, msg) {
+ opener.is(a, b, "[backdrop] " + msg);
+}
+
+function isnot(a, b, msg) {
+ opener.isnot(a, b, "[backdrop] " + msg);
+}
+
+function ok(cond, msg) {
+ opener.ok(cond, "[backdrop] " + msg);
+}
+
+function info(msg) {
+ opener.info("[backdrop] " + msg);
+}
+
+function synthesizeMouseAtWindowCenter() {
+ synthesizeMouseAtPoint(innerWidth / 2, innerHeight / 2, {});
+}
+
+const gFullscreenElementBackground = getComputedStyle(gFullscreen).background;
+
+function begin() {
+ info("The default background of window should be white");
+ assertWindowPureColor(window, "white");
+ addFullscreenChangeContinuation("enter", enterFullscreen);
+ gFullscreen.requestFullscreen();
+}
+
+function setBackdropStyle(style) {
+ gStyle.textContent = `#fullscreen::backdrop { ${style} }`;
+}
+
+function enterFullscreen() {
+ is(getComputedStyle(gFullscreen).background, gFullscreenElementBackground,
+ "Computed background of #fullscreen shouldn't be changed");
+
+ info("The default background of backdrop for fullscreen is black");
+ assertWindowPureColor(window, "black");
+
+ setBackdropStyle("background: green");
+ info("The background color of backdrop should be changed to green");
+ assertWindowPureColor(window, "green");
+
+ gFullscreen.style.background = "blue";
+ info("The blue fullscreen element should cover the backdrop");
+ assertWindowPureColor(window, "blue");
+
+ gFullscreen.style.background = "";
+ setBackdropStyle("display: none");
+ info("The white body should be shown when the backdrop is hidden");
+ assertWindowPureColor(window, "white");
+
+ setBackdropStyle("");
+ info("Content should return to black because we restore the backdrop");
+ assertWindowPureColor(window, "black");
+
+ gFullscreen.style.display = "none";
+ info("The backdrop should disappear with the fullscreen element");
+ assertWindowPureColor(window, "white");
+
+ gFullscreen.style.display = "";
+ setBackdropStyle("position: absolute");
+ info("Changing position shouldn't immediately affect the view");
+ assertWindowPureColor(window, "black");
+
+ window.scroll(0, screen.height);
+ info("Scrolled up the absolutely-positioned element");
+ assertWindowPureColor(window, "white");
+
+ addFullscreenChangeContinuation("exit", exitFullscreen);
+ document.exitFullscreen();
+}
+
+function exitFullscreen() {
+ opener.nextTest();
+}
+</script>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-denied-inner.html b/dom/html/test/file_fullscreen-denied-inner.html
new file mode 100644
index 000000000..6b5916b2e
--- /dev/null
+++ b/dom/html/test/file_fullscreen-denied-inner.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body onload="doRequestFullscreen()">
+<script>
+function doRequestFullscreen() {
+ function handler(evt) {
+ document.removeEventListener("fullscreenchange", handler);
+ document.removeEventListener("fullscreenerror", handler);
+ parent.is(evt.type, "fullscreenerror", "Request from " +
+ `document inside ${parent.testTargetName} should be denied`);
+ parent.continueTest();
+ }
+ parent.ok(!document.fullscreenEnabled, "Fullscreen " +
+ `should not be enabled in ${parent.testTargetName}`);
+ document.addEventListener("fullscreenchange", handler);
+ document.addEventListener("fullscreenerror", handler);
+ document.documentElement.requestFullscreen();
+}
+</script>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-denied.html b/dom/html/test/file_fullscreen-denied.html
new file mode 100644
index 000000000..322723083
--- /dev/null
+++ b/dom/html/test/file_fullscreen-denied.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=545812
+
+Test DOM fullscreen API.
+
+-->
+<head>
+ <title>Test for Bug 545812</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ body {
+ background-color: black;
+ }
+ </style>
+</head>
+<body>
+
+<script type="application/javascript">
+
+/** Test for Bug 545812 **/
+
+function ok(condition, msg) {
+ opener.ok(condition, "[denied] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[denied] " + msg);
+}
+
+const INNER_FILE = "file_fullscreen-denied-inner.html";
+function setupForInnerTest(targetName, callback) {
+ window.testTargetName = targetName;
+ window.continueTest = () => {
+ delete window.testTargetName;
+ delete window.continueTest;
+ callback();
+ };
+}
+
+function begin() {
+ document.addEventListener("fullscreenchange", () => {
+ ok(false, "Should never receive " +
+ "a fullscreenchange event in the main window.");
+ });
+ SimpleTest.executeSoon(testIFrameWithoutAllowFullscreen);
+}
+
+function testIFrameWithoutAllowFullscreen() {
+ // Create an iframe without an allowfullscreen attribute, whose
+ // contents request fullscreen. The request should be denied, and
+ // we should not receive a fullscreenchange event in this document.
+ var iframe = document.createElement("iframe");
+ iframe.src = INNER_FILE;
+ setupForInnerTest("an iframe without allowfullscreen", () => {
+ document.body.removeChild(iframe);
+ SimpleTest.executeSoon(testFrameElement);
+ });
+ document.body.appendChild(iframe);
+}
+
+function testFrameElement() {
+ var frameset = document.createElement("frameset");
+ var frame = document.createElement("frame");
+ frame.src = INNER_FILE;
+ frameset.appendChild(frame);
+ setupForInnerTest("a frame element", () => {
+ document.documentElement.removeChild(frameset);
+ SimpleTest.executeSoon(testObjectElement);
+ });
+ document.documentElement.appendChild(frameset);
+}
+
+function testObjectElement() {
+ var objectElem = document.createElement("object");
+ objectElem.data = INNER_FILE;
+ setupForInnerTest("an object element", () => {
+ document.body.removeChild(objectElem);
+ // In the following tests we want to test trust context requirement
+ // of fullscreen request, so temporary re-enable this pref.
+ SpecialPowers.pushPrefEnv({
+ "set":[["full-screen-api.allow-trusted-requests-only", true]]
+ }, testNonTrustContext);
+ });
+ document.body.appendChild(objectElem);
+}
+
+function testNonTrustContext() {
+ addFullscreenErrorContinuation(() => {
+ ok(!document.fullscreenElement,
+ "Should not grant request in non-trust context.");
+ SimpleTest.executeSoon(testLongRunningEventHandler);
+ });
+ document.documentElement.requestFullscreen();
+}
+
+function testLongRunningEventHandler() {
+ function longRunningHandler() {
+ window.removeEventListener("keypress", longRunningHandler);
+ // Busy loop until 2s has passed. We should then be past the one
+ // second threshold, and so our request for fullscreen should be
+ // rejected.
+ var end = (new Date()).getTime() + 2000;
+ while ((new Date()).getTime() < end) {
+ ; // Wait...
+ }
+ document.documentElement.requestFullscreen();
+ }
+ addFullscreenErrorContinuation(() => {
+ ok(!document.fullscreenElement,
+ "Should not grant request in long-running event handler.");
+ // Restore the pref environment we changed before
+ // entering testNonTrustContext.
+ SpecialPowers.popPrefEnv(finish);
+ });
+ window.addEventListener("keypress", longRunningHandler);
+ synthesizeKey("VK_A", {});
+}
+
+function finish() {
+ opener.nextTest();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-esc-exit-inner.html b/dom/html/test/file_fullscreen-esc-exit-inner.html
new file mode 100644
index 000000000..08744d644
--- /dev/null
+++ b/dom/html/test/file_fullscreen-esc-exit-inner.html
@@ -0,0 +1,58 @@
+ <!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=700764
+
+Verify that an ESC key press in a subdoc of a full-screen doc causes us to
+exit DOM full-screen mode.
+
+-->
+<head>
+ <title>Test for Bug 700764</title>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <style>
+ body:not(:fullscreen) {
+ background-color: blue;
+ }
+ </style>
+</head>
+<body>
+
+<script type="application/javascript">
+
+/** Test for Bug 700764 **/
+
+function ok(condition, msg) {
+ parent.ok(condition, msg);
+}
+
+function is(a, b, msg) {
+ parent.is(a, b, msg);
+}
+
+var escKeyReceived = false;
+var escKeySent = false;
+
+function keyHandler(event) {
+ if (escKeyReceived == SpecialPowers.Ci.nsIDOMKeyEvent.DOM_VK_ESC) {
+ escKeyReceived = true;
+ }
+}
+
+window.addEventListener("keydown", keyHandler, true);
+window.addEventListener("keyup", keyHandler, true);
+window.addEventListener("keypress", keyHandler, true);
+
+function startTest() {
+ ok(!document.fullscreenElement, "Subdoc should not be in full-screen mode");
+ ok(parent.document.fullscreenElement, "Parent should be in full-screen mode");
+ escKeySent = true;
+ window.focus();
+ synthesizeKey("VK_ESCAPE", {});
+}
+
+</script>
+</pre>
+<p>Inner frame</p>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-esc-exit.html b/dom/html/test/file_fullscreen-esc-exit.html
new file mode 100644
index 000000000..beb52f926
--- /dev/null
+++ b/dom/html/test/file_fullscreen-esc-exit.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=700764
+
+Verify that an ESC key press in a subdoc of a full-screen doc causes us to
+exit DOM full-screen mode.
+
+-->
+<head>
+ <title>Test for Bug 700764</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ body:fullscreen, div:fullscreen {
+ background-color: red;
+ }
+ </style>
+</head>
+<body>
+
+<script type="application/javascript">
+
+function ok(condition, msg) {
+ opener.ok(condition, "[esc-exit] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[esc-exit] " + msg);
+}
+
+function finish() {
+ opener.nextTest();
+}
+
+function fullscreenchange1(event) {
+ is(document.fullscreenElement, document.body, "FSE should be doc");
+ addFullscreenChangeContinuation("exit", fullscreenchange2);
+ ok(!document.getElementById("subdoc").contentWindow.escKeySent, "Should not yet have sent ESC key press.");
+ document.getElementById("subdoc").contentWindow.startTest();
+}
+
+function fullscreenchange2(event) {
+ ok(document.getElementById("subdoc").contentWindow.escKeySent, "Should have sent ESC key press.");
+ ok(!document.getElementById("subdoc").contentWindow.escKeyReceived, "ESC key press to exit should not be delivered.");
+ ok(!document.fullscreenElement, "Should have left full-screen mode on ESC key press");
+ finish();
+}
+
+function begin() {
+ addFullscreenChangeContinuation("enter", fullscreenchange1);
+ document.body.requestFullscreen();
+}
+
+</script>
+
+<!-- This subframe conducts the test. -->
+<iframe id="subdoc" src="file_fullscreen-esc-exit-inner.html"></iframe>
+
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-hidden.html b/dom/html/test/file_fullscreen-hidden.html
new file mode 100644
index 000000000..b458a1714
--- /dev/null
+++ b/dom/html/test/file_fullscreen-hidden.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=697636
+-->
+<head>
+ <title>Test for Bug 697636</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 id="f" src="data:text/html,<body text=green>1" allowfullscreen></iframe>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=697636">Mozilla Bug 697636</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 697636 **/
+
+var frameWin;
+var e1;
+
+function begin()
+{
+ frameWin = document.getElementById("f").contentWindow;
+ e1 = frameWin.document.documentElement;
+ frameWin.location = "data:text/html,<body text=blue onload='parent.b2()'>2";
+}
+
+function b2()
+{
+ try {
+ e1.requestFullscreen();
+ } catch(e) {
+ opener.ok(false, "[hidden] Should not enter full-screen");
+ }
+ setTimeout(done, 0);
+}
+
+function done() {
+ opener.ok(!document.fullscreenElement, "[hidden] Should not have entered full-screen mode in hidden document.");
+ opener.ok(!e1.ownerDocument.fullscreenElement, "[hidden] Requesting owner should not have entered full-screen mode.");
+ opener.nextTest();
+}
+
+</script>
+</pre>
+</body>
+
+</html>
diff --git a/dom/html/test/file_fullscreen-lenient-setters.html b/dom/html/test/file_fullscreen-lenient-setters.html
new file mode 100644
index 000000000..e8df91502
--- /dev/null
+++ b/dom/html/test/file_fullscreen-lenient-setters.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1268798</title>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<script>
+"use strict";
+
+function ok(condition, msg) {
+ opener.ok(condition, "[lenient-setters] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[lenient-setters] " + msg);
+}
+
+function info(msg) {
+ opener.info("[lenient-setters] " + msg);
+}
+
+let unattachedDiv = document.createElement("div");
+
+function begin() {
+ var originalValue = document.fullscreen;
+ try {
+ document.fullscreen = !document.fullscreen;
+ is(document.fullscreen, originalValue,
+ "fullscreen should not be changed");
+ } catch (e) {
+ ok(false, "Setting fullscreen should not throw");
+ }
+
+ var originalElem = document.fullscreenElement;
+ try {
+ document.fullscreenElement = unattachedDiv;
+ document.fullscreenElement = [];
+ is(document.fullscreenElement, originalElem,
+ "fullscreenElement should not be changed");
+ } catch (e) {
+ ok(false, "Setting fullscreenElement should not throw");
+ }
+
+ var originalEnabled = document.fullscreenEnabled;
+ try {
+ document.fullscreenEnabled = !originalEnabled;
+ is(document.fullscreenEnabled, originalEnabled,
+ "fullscreenEnabled should not be changed");
+ } catch (e) {
+ ok(false, "Setting fullscreenEnabled should not throw");
+ }
+
+ opener.nextTest();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-multiple-inner.html b/dom/html/test/file_fullscreen-multiple-inner.html
new file mode 100644
index 000000000..8390cffec
--- /dev/null
+++ b/dom/html/test/file_fullscreen-multiple-inner.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 724554</title>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+
+/** Test for Bug 545812 **/
+function begin(id) {
+ addFullscreenErrorContinuation(function() {
+ opener.ok(false, "Fullscreen denied " + id);
+ });
+ addFullscreenChangeContinuation("enter",
+ function() {
+ opener.enteredFullscreen(id);
+ });
+ document.body.requestFullscreen();
+}
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-multiple.html b/dom/html/test/file_fullscreen-multiple.html
new file mode 100644
index 000000000..1a457b361
--- /dev/null
+++ b/dom/html/test/file_fullscreen-multiple.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=724554
+
+Test that multiple windows can be fullscreen at the same time.
+
+Open one window, focus it and enter fullscreen, then open another, focus
+it and enter fullscreen, and check that both are still fullscreen.
+
+-->
+<head>
+ <title>Test for Bug 724554</title>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+/** Test for Bug 545812 **/
+
+function ok(condition, msg) {
+ opener.ok(condition, "[multiple] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[multiple] " + msg);
+}
+
+var window1, window2;
+
+function openWindow(id) {
+ var w = window.open("file_fullscreen-multiple-inner.html", "", "width=500,height=500");
+ w.addEventListener("load", function onload() {
+ w.focus();
+ SimpleTest.waitForFocus(function(){w.begin(id)}, w);
+ });
+ return w;
+}
+
+function begin() {
+ window1 = openWindow("one");
+}
+
+function enteredFullscreen(id) {
+ if (id == "one") {
+ window2 = openWindow("two");
+ } else if (id == "two") {
+ ok(window1.document.fullscreenElement &&
+ window2.document.fullscreenElement,
+ "Both windows should be fullscreen concurrently");
+ window1.close();
+ window2.close();
+ opener.nextTest();
+ }
+}
+
+</script>
+</pre>
+<div id="full-screen-element"></div>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-navigation.html b/dom/html/test/file_fullscreen-navigation.html
new file mode 100644
index 000000000..00a4ed4e1
--- /dev/null
+++ b/dom/html/test/file_fullscreen-navigation.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=685402
+-->
+<head>
+ <title>Test for Bug 685402</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body style="background-color: gray;">
+
+<iframe id="f" src="data:text/html,<body text=green>1" allowfullscreen></iframe>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=685402">Mozilla Bug 685402</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 685402 **/
+
+var frameWin;
+var e1;
+var prevEnabled;
+var prevTrusted;
+
+function begin()
+{
+ frameWin = document.getElementById("f").contentWindow;
+ e1 = frameWin.document.body;
+ document.addEventListener("fullscreenchange", function onfullscreen() {
+ document.removeEventListener("fullscreenchange", onfullscreen, false);
+ opener.ok(document.fullscreenElement, "[navigation] Request should be granted");
+ frameWin.location = "data:text/html,<body text=blue onload='parent.b2()'>2";
+ }, false);
+
+ e1.requestFullscreen();
+}
+
+function b2()
+{
+ opener.ok(!document.fullscreenElement, "[navigation] Should have left full-screen due to navigation.");
+ opener.nextTest();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-nested.html b/dom/html/test/file_fullscreen-nested.html
new file mode 100644
index 000000000..60989cd1c
--- /dev/null
+++ b/dom/html/test/file_fullscreen-nested.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 1187801</title>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+<iframe src="about:blank" allowfullscreen></iframe>
+<script type="text/javascript">
+
+/** Test for Bug 1187801 **/
+
+function info(msg) {
+ opener.info("[nested] " + msg);
+}
+
+function ok(condition, msg) {
+ opener.ok(condition, "[nested] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[nested] " + msg);
+}
+
+var gInnerDoc;
+var gTestSteps;
+var gTestIndex = 0;
+
+function begin() {
+ var root = document.documentElement;
+ var iframe = document.querySelector("iframe");
+ var innerDoc = gInnerDoc = iframe.contentDocument;
+ var innerRoot = innerDoc.documentElement;
+
+ // The format of each test step is:
+ // [[action, target], [fsOuter, fsInner]] where:
+ // * "action" is either "enter" or "exit", means whether we want to
+ // enter or exit fullscreen in this step.
+ // * "target" is where we apply this action. For "enter" action, it
+ // is the element we want to call requestFullscreen() on, and for
+ // "exit", it is the document we want to call exitFullscreen() on.
+ // * "fsOuter" and "fsInner" are the expected fullscreen elements of
+ // the outer and inner document respectively after executing the
+ // action in this step.
+ gTestSteps = [
+ // innerRoot
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", innerDoc], [ null, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", document], [ null, null]],
+ // root, innerRoot
+ [["enter", root], [ root, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", innerDoc], [ root, null]],
+ [[ "exit", document], [ null, null]],
+ [["enter", root], [ root, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", document], [ root, null]],
+ [[ "exit", document], [ null, null]],
+ // iframe, innerRoot
+ [["enter", iframe], [iframe, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", innerDoc], [iframe, null]],
+ [[ "exit", document], [ null, null]],
+ [["enter", iframe], [iframe, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", document], [ null, null]],
+ // root, iframe, innerRoot
+ [["enter", root], [ root, null]],
+ [["enter", iframe], [iframe, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", innerDoc], [iframe, null]],
+ [[ "exit", document], [ root, null]],
+ [[ "exit", document], [ null, null]],
+ [["enter", root], [ root, null]],
+ [["enter", iframe], [iframe, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", document], [ root, null]],
+ [[ "exit", document], [ null, null]],
+ ];
+
+ nextStep();
+}
+
+function nextStep() {
+ if (gTestIndex == gTestSteps.length) {
+ opener.nextTest();
+ return;
+ }
+
+ var index = gTestIndex;
+ var [[action, target], [fsOuter, fsInner]] = gTestSteps[gTestIndex++];
+
+ function checkAndNext() {
+ is(document.fullscreenElement, fsOuter,
+ `Fullscreen element of outer doc should match after step ${index}`);
+ is(gInnerDoc.fullscreenElement, fsInner,
+ `Fullscreen element of inner doc should match after step ${index}`);
+ nextStep();
+ }
+
+ info(`Executing step ${index}: ${action} on ${target}...`);
+ if (action == "enter") {
+ // For "enter" action, the target is the element
+ var doc = target.ownerDocument;
+ addFullscreenChangeContinuation("enter", checkAndNext, doc);
+ target.requestFullscreen();
+ } else if (action == "exit") {
+ // For "exit" action, the target is the document
+ addFullscreenChangeContinuation("exit", checkAndNext, target);
+ target.exitFullscreen();
+ } else {
+ ok(false, `Unknown action ${action}`);
+ }
+}
+</script>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-plugins.html b/dom/html/test/file_fullscreen-plugins.html
new file mode 100644
index 000000000..f28fbede9
--- /dev/null
+++ b/dom/html/test/file_fullscreen-plugins.html
@@ -0,0 +1,160 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=545812
+
+Test plugins with DOM full-screen API:
+* Presence of plugins has no effect on request for full-screen on MacOS.
+* Request for full-screen is denied when windowed plugin in current doc is present.
+* Request for full-screen is denied when windowed plugin in subdocument is present.
+* Request for full-screen is not denied when the only plugin present is windowless.
+* Adding an existing (out-of-doc) windowed plugin to a full-screen document causes document to exit full-screen.
+* Create windowed plugin and adding it to full-screen document caused exit from full-screen.
+
+-->
+<head>
+ <title>Test for Bug 545812</title>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="plugin-utils.js"></script>
+ <script type="application/javascript">
+ setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+ </script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ body:fullscreen, div:fullscreen {
+ background-color: red;
+ }
+ </style>
+</head>
+<body>
+
+
+<!-- Windowed plugin, focusing should revoke full-screen. -->
+<embed id="windowed-plugin" type="application/x-test" style="width:200px;height:100px;" wmode="window"></embed>
+
+<!-- Windowless plugin, focusing should not revoke full-screen. -->
+<embed id="windowless-plugin" type="application/x-test" style="width:200px;height:100px;"></embed>
+
+
+<!-- iframe contents:
+
+<html><body><embed id='windowed-plugin' type='application/x-test' style='width:200px;height:100px;' wmode='window'></embed></body></html>
+
+-->
+
+<iframe id="subdoc-plugin" src="data:text/html;charset=utf-8,<html><body><embed id%3D'windowed-plugin' type%3D'application%2Fx-test' style%3D'width%3A200px%3Bheight%3A100px%3B' wmode%3D'window'><%2Fembed><%2Fbody><%2Fhtml>%0D%0A"></iframe>
+
+<script type="application/javascript">
+
+/** Test for Bug 545812 **/
+
+function ok(condition, msg) {
+ opener.ok(condition, "[plugins] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[plugins] " + msg);
+}
+
+function e(id) {
+ return document.getElementById(id);
+}
+
+function removeElement(e) {
+ e.parentNode.removeChild(e);
+}
+
+const isMacOs = navigator.appVersion.indexOf("Macintosh") != -1;
+
+var windowedPlugin = null;
+
+function begin() {
+ // Delay test startup long enough for the windowed plugin in the subframe to
+ // start up and create its window.
+ opener.SimpleTest.executeSoon(function() {
+ opener.SimpleTest.executeSoon(function() {
+ startTest();
+ })
+ });
+}
+
+function startTest() {
+ ok(!document.fullscreenElement, "Should not be in full-screen mode initially");
+ document.body.requestFullscreen();
+
+ // Focus the windowed plugin. On MacOS we should still enter full-screen mode,
+ // on windows the pending request for full-screen should be denied.
+ e("windowed-plugin").focus();
+
+ if (isMacOs) {
+ // Running on MacOS, all plugins are effectively windowless, request for full-screen should be granted.
+ // Continue test in the (mac-specific) "fullscreenchange" handler.
+ addFullscreenChangeContinuation("enter", macFullScreenChange1);
+ } else {
+ // Non-MacOS, request should be denied, carry on the test after receiving error event.
+ addFullscreenErrorContinuation(nonMacTest);
+ }
+}
+
+function nonMacTest() {
+ ok(!document.fullscreenElement, "Request for full-screen with focused windowed plugin should be denied.");
+
+ // Focus a regular html element, and re-request full-screen, request should be granted.
+ e("windowless-plugin").focus();
+ addFullscreenChangeContinuation("enter", nonMacTest2);
+ document.body.requestFullscreen();
+}
+
+function nonMacTest2() {
+ ok(document.fullscreenElement, "Request for full-screen with non-plugin focused should be granted.");
+ // Focus a windowed plugin, full-screen should be revoked.
+ addFullscreenChangeContinuation("exit", nonMacTest3);
+ e("windowed-plugin").focus();
+}
+
+function nonMacTest3() {
+ ok(!document.fullscreenElement, "Full-screen should have been revoked when windowed-plugin was focused.");
+ // Remove windowed plugins before closing the window
+ // to work around bug 1237853.
+ removeElement(e("windowed-plugin"));
+ removeElement(e("subdoc-plugin").contentDocument.getElementById("windowed-plugin"));
+ opener.nextTest();
+}
+
+var fullScreenChangeCount = 0;
+
+function createWindowedPlugin() {
+ var p = document.createElement("embed");
+ p.setAttribute("type", "application/x-test");
+ p.setAttribute("wmode", "window");
+ return p;
+}
+
+function macFullScreenChange1(event) {
+ ok(document.fullscreenElement, "Requests for full-screen with focused windowed plugins should be granted on MacOS");
+
+ // Create a new windowed plugin, and add that to the document. Should *not* exit full-screen mode on MacOS.
+ windowedPlugin = createWindowedPlugin();
+ document.body.appendChild(windowedPlugin);
+
+ // Focus windowed plugin. Should not exit full-screen mode on MacOS.
+ windowedPlugin.focus();
+
+ setTimeout(
+ function() {
+ ok(document.fullscreenElement, "Adding & focusing a windowed plugin to document should not cause full-screen to exit on MacOS.");
+ addFullscreenChangeContinuation("exit", macFullScreenChange2);
+ document.exitFullscreen();
+ }, 0);
+}
+
+function macFullScreenChange2(event) {
+ ok(!document.fullscreenElement, "Should have left full-screen mode after calling document.exitFullscreen().");
+ opener.nextTest();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-prefixed.html b/dom/html/test/file_fullscreen-prefixed.html
new file mode 100644
index 000000000..5f3d60196
--- /dev/null
+++ b/dom/html/test/file_fullscreen-prefixed.html
@@ -0,0 +1,153 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 743198</title>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+ <div id="fullscreen"></div>
+<script>
+
+function ok(condition, msg) {
+ opener.ok(condition, "[prefixed] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[prefixed] " + msg);
+}
+
+function info(msg) {
+ opener.info("[prefixed] " + msg);
+}
+
+SimpleTest.requestFlakyTimeout(
+ "need to wait for a while to confirm no unexpected event is dispatched");
+
+let div = document.getElementById("fullscreen");
+let unattachedDiv = document.createElement('div');
+
+const NO_EVENT_HANDLER = 0;
+const PREFIXED_EVENT_ONLY = 1;
+const PREFIXED_AND_UNPREFIXED_EVENT = 2;
+
+class TestCase {
+ constructor(num, handlersOnWindow, handlersOnDocument) {
+ this.number = num;
+ this.handlersType = new Map([[window, handlersOnWindow],
+ [document, handlersOnDocument]]);
+ }
+
+ static checkState(inFullscreen, msg) {
+ var emptyOrNot = inFullscreen ? "" : "not ";
+ info(`Check fullscreen state ${msg}`);
+ is(document.mozFullScreen, inFullscreen,
+ `Should ${emptyOrNot}be in fullscreen`);
+ is(document.fullscreenElement, inFullscreen ? div : null,
+ `Fullscreen element should be ${inFullscreen ? "div" : "null"}`);
+ is(document.mozFullScreenElement, document.fullscreenElement,
+ "document.mozFullScreenElement should be identical to fullscreenElement");
+ is(div.matches(":fullscreen"), inFullscreen,
+ `Fullscreen element should ${emptyOrNot}match :fullscreen pseudo class`);
+ is(div.matches(":-moz-full-screen"), inFullscreen,
+ `Fullscreen element should ${emptyOrNot}match :-moz-full-screen pseudo class`);
+ }
+
+ changeListeners(action, eventType, handler) {
+ let method = `${action}EventListener`;
+ for (let [target, type] of this.handlersType.entries()) {
+ if (type == PREFIXED_EVENT_ONLY) {
+ target[method](`moz${eventType}`, handler);
+ } else if (type == PREFIXED_AND_UNPREFIXED_EVENT) {
+ target[method](eventType, handler);
+ target[method](`moz${eventType}`, handler);
+ } else if (type != NO_EVENT_HANDLER) {
+ ok(false, `Unknown handlers type ${type}`);
+ }
+ }
+ }
+
+ doTest(actionCallback, eventType, inFullscreen, msg) {
+ return new Promise(resolve => {
+ let timeout = 0;
+ let expectEvent = new Map();
+ for (let [target, type] of this.handlersType) {
+ expectEvent.set(target, this.handlersType != NO_EVENT_HANDLER);
+ }
+ let handleEvent = evt => {
+ let target = evt.currentTarget;
+ let type = this.handlersType.get(target);
+ if (type == PREFIXED_EVENT_ONLY) {
+ is(evt.type, `moz${eventType}`,
+ `Should get prefixed event on ${target}`);
+ } else if (type == PREFIXED_AND_UNPREFIXED_EVENT) {
+ is(evt.type, eventType,
+ `Should only get unprefixed event on ${target}`);
+ } else {
+ ok(false, `No event should be triggered on ${target}`);
+ }
+ // Ensure we receive each event exactly once.
+ if (expectEvent.get(target)) {
+ expectEvent.set(target, false);
+ } else {
+ ok(false, `Got an unexpected ${evt.type} event on ${target}`);
+ }
+ if (!timeout) {
+ timeout = setTimeout(() => {
+ this.changeListeners("remove", eventType, handleEvent);
+ TestCase.checkState(inFullscreen,
+ `${msg} in test case ${this.number}`);
+ resolve();
+ });
+ }
+ };
+ this.changeListeners("add", eventType, handleEvent);
+ actionCallback();
+ });
+ }
+
+ test() {
+ return new Promise(resolve => {
+ Promise.resolve().then(() => {
+ return this.doTest(() => div.mozRequestFullScreen(),
+ "fullscreenchange", true, "after request");
+ }).then(() => {
+ return this.doTest(() => document.mozCancelFullScreen(),
+ "fullscreenchange", false, "after exit");
+ }).then(() => {
+ return this.doTest(() => unattachedDiv.mozRequestFullScreen(),
+ "fullscreenerror", false, "after failed request");
+ }).then(resolve);
+ });
+ }
+}
+
+let gTestcases = [
+ new TestCase(1, PREFIXED_EVENT_ONLY, NO_EVENT_HANDLER),
+ new TestCase(2, PREFIXED_AND_UNPREFIXED_EVENT, NO_EVENT_HANDLER),
+ new TestCase(3, NO_EVENT_HANDLER, PREFIXED_EVENT_ONLY),
+ new TestCase(4, PREFIXED_EVENT_ONLY, PREFIXED_EVENT_ONLY),
+ new TestCase(5, PREFIXED_AND_UNPREFIXED_EVENT, PREFIXED_EVENT_ONLY),
+ new TestCase(6, NO_EVENT_HANDLER, PREFIXED_AND_UNPREFIXED_EVENT),
+ new TestCase(7, PREFIXED_EVENT_ONLY, PREFIXED_AND_UNPREFIXED_EVENT),
+ new TestCase(8, PREFIXED_AND_UNPREFIXED_EVENT, PREFIXED_AND_UNPREFIXED_EVENT),
+ ];
+
+function begin() {
+ TestCase.checkState(false, "at the beginning");
+ runNextTestCase();
+}
+
+function runNextTestCase() {
+ let testcase = gTestcases.shift();
+ if (!testcase) {
+ opener.nextTest();
+ return;
+ }
+ testcase.test().then(runNextTestCase);
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-rollback.html b/dom/html/test/file_fullscreen-rollback.html
new file mode 100644
index 000000000..605a917aa
--- /dev/null
+++ b/dom/html/test/file_fullscreen-rollback.html
@@ -0,0 +1,128 @@
+ <!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=700764
+
+Verifies that cancelFullScreen() rolls back to have the previous full-screen
+element full-screen.
+
+Tests:
+* Request full-screen in doc.
+* Request full-screen in doc on element not descended from full-screen element. Request should be denied.
+* Request full-screen in subdoc.
+* Cancel full-screen in subdoc, doc should be full-screen.
+* Request full-screen in subdoc.
+* Removing FSE should fully-exit full-screen.
+
+
+-->
+<head>
+ <title>Test for Bug 700764</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+
+<div id="fse">
+ <div id="fse-inner">
+ <iframe id="subdoc" allowfullscreen src="data:text/html,<html><body bgcolor='black'></body></html>"></iframe>
+ </div>
+</div>
+
+<div id="non-fse"></div>
+
+<script type="application/javascript">
+
+/** Test for Bug 700764 **/
+
+function ok(condition, msg) {
+ opener.ok(condition, "[rollback] " + msg);
+ if (!condition) {
+ opener.finish();
+ }
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[rollback] " + msg);
+ if (a != b) {
+ opener.finish();
+ }
+}
+
+function enterFullscreen(element, callback) {
+ addFullscreenChangeContinuation("enter", callback);
+ element.focus();
+ element.requestFullscreen();
+}
+
+function revertFullscreen(doc, callback) {
+ ok(doc.fullscreenElement != null, "Should only exit fullscreen on a fullscreen doc");
+ addFullscreenChangeContinuation("exit", callback, doc);
+ doc.exitFullscreen();
+}
+
+function e(id) {
+ return document.getElementById(id);
+}
+
+function requestFullscreen(element) {
+ element.focus();
+ element.requestFullscreen();
+}
+
+function begin() {
+ enterFullscreen(e("fse"), change1);
+}
+
+function change1() {
+ is(document.fullscreenElement, e("fse"), "Body should be FSE");
+ // Request full-screen from element not descendent from current FSE.
+ addFullscreenErrorContinuation(error1);
+ requestFullscreen(e("non-fse"));
+}
+
+function error1() {
+ is(document.fullscreenElement, e("fse"), "FSE should not change");
+ var iframe = e("subdoc");
+ enterFullscreen(iframe.contentDocument.body, change2);
+}
+
+function change2() {
+ var iframe = e("subdoc");
+ is(document.fullscreenElement, iframe, "Subdoc container should be FSE.");
+ is(iframe.contentDocument.fullscreenElement, iframe.contentDocument.body, "Subdoc body should be FSE in subdoc");
+ revertFullscreen(document, change3);
+}
+
+function change3() {
+ is(document.fullscreenElement, e("fse"), "FSE should rollback to FSE.");
+ revertFullscreen(document, change4);
+}
+
+function change4() {
+ is(document.fullscreenElement, null, "Should have left full-screen entirely");
+ enterFullscreen(e("fse"), change5);
+}
+
+function change5() {
+ is(document.fullscreenElement, e("fse"), "FSE should be e('fse')");
+ enterFullscreen(e("fse-inner"), change6);
+}
+
+function change6() {
+ addFullscreenChangeContinuation("exit", change7);
+ var element = e('fse-inner');
+ is(document.fullscreenElement, element, "FSE should be e('fse-inner')");
+ element.parentNode.removeChild(element);
+}
+
+function change7() {
+ is(document.fullscreenElement, null, "Should have fully exited full-screen mode when removed FSE from doc");
+ opener.nextTest();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-scrollbar.html b/dom/html/test/file_fullscreen-scrollbar.html
new file mode 100644
index 000000000..76a090681
--- /dev/null
+++ b/dom/html/test/file_fullscreen-scrollbar.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1201798</title>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ html, body, #measure {
+ width: 100%; height: 100%;
+ }
+ #ref-outer { width: 100px; height: 100px; overflow: scroll; }
+ #ref-inner { width: 100%; height: 100%; }
+ </style>
+</head>
+<body>
+<div id="measure"></div>
+<div style="height: 1000vh; width: 1000vw"></div>
+<div id="ref-outer">
+ <div id="ref-inner"></div>
+</div>
+<div id="fullscreen"></div>
+<script type="text/javascript">
+
+/** Test for Bug 1201798 */
+
+var info = msg => opener.info("[scrollbar] " + msg);
+var ok = (cond, msg) => opener.ok(cond, "[scrollbar] " + msg);
+var is = (a, b, msg) => opener.is(a, b, "[scrollbar] " + msg);
+
+var gVerticalScrollbarWidth, gHorizontalScrollbarWidth;
+var gMeasureDiv = document.getElementById("measure");
+var gFullscreenDiv = document.getElementById("fullscreen");
+
+function getMeasureRect() {
+ return gMeasureDiv.getBoundingClientRect();
+}
+
+function triggerFrameReconstruction() {
+ info("Triggering a force frame reconstruction");
+ var docElem = document.documentElement;
+ var wm = window.getComputedStyle(docElem).writingMode;
+ if (wm == "horizontal-tb") {
+ docElem.style.writingMode = "vertical-rl";
+ } else {
+ docElem.style.writingMode = "horizontal-tb";
+ }
+ docElem.getBoundingClientRect();
+}
+
+function assertHasScrollbars(elem) {
+ var rect = getMeasureRect();
+ is(rect.width, screen.width - gVerticalScrollbarWidth,
+ `Should have vertical scrollbar when ${elem} is in fullscreen`);
+ is(rect.height, screen.height - gHorizontalScrollbarWidth,
+ `Should have horizontal scrollbar when ${elem} is in fullscreen`);
+}
+
+function assertHasNoScrollbars(elem) {
+ var rect = getMeasureRect();
+ is(rect.width, screen.width,
+ `Should not have vertical scrollbar when ${elem} is in fullscreen`);
+ is(rect.height, screen.height,
+ `Should not have horizontal scrollbar when ${elem} is in fullscreen`);
+}
+
+function checkScrollbars(elem, shouldHaveScrollbars) {
+ is(elem, document.fullscreenElement,
+ "Should only check the current fullscreen element");
+ var assertFunc = shouldHaveScrollbars ?
+ assertHasScrollbars : assertHasNoScrollbars;
+ assertFunc(elem);
+ triggerFrameReconstruction();
+ assertFunc(elem);
+}
+
+function begin() {
+ if (window.matchMedia("(-moz-overlay-scrollbar").matches) {
+ // If overlay scrollbar is enabled, the scrollbar is not measurable,
+ // so we skip this test in that case.
+ info("Skip this test because of overlay scrollbar");
+ opener.nextTest();
+ return;
+ }
+
+ var rectOuter = document.getElementById("ref-outer").getBoundingClientRect();
+ var rectInner = document.getElementById("ref-inner").getBoundingClientRect();
+ gVerticalScrollbarWidth = rectOuter.width - rectInner.width;
+ gHorizontalScrollbarWidth = rectOuter.height - rectInner.height;
+ ok(gVerticalScrollbarWidth != 0, "Should have vertical scrollbar");
+ ok(gHorizontalScrollbarWidth != 0, "Should have horizontal scrollbar");
+
+ info("Entering fullscreen on root");
+ addFullscreenChangeContinuation("enter", enteredFullscreenOnRoot);
+ document.documentElement.requestFullscreen();
+}
+
+function enteredFullscreenOnRoot() {
+ checkScrollbars(document.documentElement, true);
+ info("Entering fullscreen on div");
+ addFullscreenChangeContinuation("enter", enteredFullscreenOnDiv);
+ gFullscreenDiv.requestFullscreen();
+}
+
+function enteredFullscreenOnDiv() {
+ checkScrollbars(gFullscreenDiv, false);
+ info("Exiting fullscreen on div");
+ addFullscreenChangeContinuation("exit", exitedFullscreenOnDiv);
+ document.exitFullscreen();
+}
+
+function exitedFullscreenOnDiv() {
+ checkScrollbars(document.documentElement, true);
+ info("Exiting fullscreen on root");
+ addFullscreenChangeContinuation("exit", exitedFullscreenOnRoot);
+ document.exitFullscreen();
+}
+
+function exitedFullscreenOnRoot() {
+ opener.nextTest();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-selector.html b/dom/html/test/file_fullscreen-selector.html
new file mode 100644
index 000000000..9aceb659e
--- /dev/null
+++ b/dom/html/test/file_fullscreen-selector.html
@@ -0,0 +1,178 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 1199522</title>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ div {
+ position: fixed;
+ top: 20px; height: 50px;
+ opacity: 0.3;
+ border: 5px solid black;
+ box-sizing: border-box;
+ }
+ #fullscreen0 {
+ left: 50px; width: 50px;
+ background: #ff0000;
+ border-color: #800000;
+ }
+ #fullscreen1 {
+ left: 100px; width: 50px;
+ background: #00ff00;
+ border-color: #008000;
+ }
+ #fullscreen2 {
+ left: 150px; width: 50px;
+ background: #0000ff;
+ border-color: #000080;
+ }
+ </style>
+</head>
+<body>
+<script type="application/javascript">
+
+/** Test for Bug 1199522 **/
+
+function info(msg) {
+ opener.info("[selector] " + msg);
+}
+
+function ok(condition, msg) {
+ opener.ok(condition, "[selector] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[selector] " + msg);
+}
+
+function rectEquals(rect1, rect2) {
+ return rect1.x == rect2.x && rect1.y == rect2.y &&
+ rect1.width == rect2.width && rect1.height == rect2.height;
+}
+
+function getViewportRect() {
+ return new DOMRect(0, 0, window.innerWidth, window.innerHeight);
+}
+
+var fullscreenElems = [];
+
+function checkFullscreenState(elem, hasState, viewportRect) {
+ var id = elem.id;
+ var rect = elem.getBoundingClientRect();
+ if (hasState) {
+ ok(elem.matches(":fullscreen"),
+ `${id} should match selector ":fullscreen"`);
+ ok(rectEquals(rect, viewportRect),
+ `The bounding rect of ${id} should match the viewport`);
+ } else {
+ ok(!elem.matches(":fullscreen"),
+ `${id} should not match selector ":fullscreen"`);
+ ok(rectEquals(rect, elem.initialRect),
+ `The bounding rect of ${id} should match its initial state`);
+ }
+}
+
+function checkFullscreenStates(states) {
+ var viewportRect = getViewportRect();
+ fullscreenElems.forEach((elem, index) => {
+ checkFullscreenState(elem, states[index], viewportRect);
+ });
+}
+
+function begin() {
+ fullscreenElems.push(document.getElementById('fullscreen0'));
+ fullscreenElems.push(document.getElementById('fullscreen1'));
+ fullscreenElems.push(document.getElementById('fullscreen2'));
+
+ var viewportRect = getViewportRect();
+ for (var elem of fullscreenElems) {
+ var rect = elem.getBoundingClientRect();
+ var id = elem.id;
+ elem.initialRect = rect;
+ ok(!elem.matches(":fullscreen"),
+ `${id} should not match selector ":fullscreen"`);
+ ok(!rectEquals(elem.initialRect, viewportRect),
+ `The initial bounding rect of ${id} should not match the viewport`);
+ }
+
+ info("Entering fullscreen on fullscreen0");
+ addFullscreenChangeContinuation("enter", enter0);
+ fullscreenElems[0].requestFullscreen();
+}
+
+function enter0() {
+ checkFullscreenStates([true, false, false]);
+ info("Entering fullscreen on fullscreen1");
+ addFullscreenChangeContinuation("enter", enter1);
+ fullscreenElems[1].requestFullscreen();
+}
+
+function enter1() {
+ checkFullscreenStates([true, true, false]);
+ info("Entering fullscreen on fullscreen2");
+ addFullscreenChangeContinuation("enter", enter2);
+ fullscreenElems[2].requestFullscreen();
+}
+
+function enter2() {
+ checkFullscreenStates([true, true, true]);
+ info("Leaving fullscreen on fullscreen2");
+ addFullscreenChangeContinuation("exit", exit2);
+ document.exitFullscreen();
+}
+
+function exit2() {
+ checkFullscreenStates([true, true, false]);
+ info("Leaving fullscreen on fullscreen1");
+ addFullscreenChangeContinuation("exit", exit1);
+ document.exitFullscreen();
+}
+
+function exit1() {
+ checkFullscreenStates([true, false, false]);
+ info("Leaving fullscreen on fullscreen0");
+ addFullscreenChangeContinuation("exit", exit0);
+ document.exitFullscreen();
+}
+
+function exit0() {
+ checkFullscreenStates([false, false, false]);
+
+ info("Entering fullscreen on all elements");
+ var count = 0;
+ function listener() {
+ if (++count == 3) {
+ document.removeEventListener("fullscreenchange", listener);
+ enterAll();
+ }
+ }
+ document.addEventListener("fullscreenchange", listener);
+ fullscreenElems[0].requestFullscreen();
+ fullscreenElems[1].requestFullscreen();
+ fullscreenElems[2].requestFullscreen();
+}
+
+function enterAll() {
+ checkFullscreenStates([true, true, true]);
+ info("Fully-exiting fullscreen");
+ addFullscreenChangeContinuation("exit", exitAll);
+ synthesizeKey("VK_ESCAPE", {});
+}
+
+function exitAll() {
+ checkFullscreenStates([false, false, false]);
+ opener.nextTest();
+}
+
+</script>
+</pre>
+<div id="fullscreen0">
+ <div id="fullscreen1">
+ <div id="fullscreen2">
+ </div>
+ </div>
+</div>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-svg-element.html b/dom/html/test/file_fullscreen-svg-element.html
new file mode 100644
index 000000000..dd0f3b593
--- /dev/null
+++ b/dom/html/test/file_fullscreen-svg-element.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=735031
+ Bug 735031 - Fullscreen API implementation assumes an HTML Element
+ -->
+ <head>
+ <title>Bug 735031</title>
+ <script 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=73503">
+ Mozilla Bug 735031</a>
+
+ <svg id="svg-elem" width="100" height="100" viewbox="0 0 100 100">
+ <rect x="10" y="10" width="50" height="50"
+ fill="black" stroke="blue" stroke-width="2"/>
+ </svg>
+
+ <pre id="test">
+ <script type="application/javascript">
+ /*
+ * Test for Bug 735031
+ * Test locking non-html element.
+ */
+ function begin() {
+ var elem = document.getElementById("svg-elem")
+ , elemWasLocked = false;
+
+ document.addEventListener("fullscreenchange", function (e) {
+ if (document.fullscreenElement === elem) {
+ elemWasLocked = true;
+ document.exitFullscreen();
+ } else {
+ opener.ok(elemWasLocked, "Expected SVG elem to become locked.");
+ opener.nextTest();
+ }
+ }, false);
+ elem.requestFullscreen();
+ }
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/html/test/file_fullscreen-top-layer.html b/dom/html/test/file_fullscreen-top-layer.html
new file mode 100644
index 000000000..fe3d2cf9e
--- /dev/null
+++ b/dom/html/test/file_fullscreen-top-layer.html
@@ -0,0 +1,160 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1126230</title>
+ <style>
+ #back {
+ position: fixed !important;
+ z-index: 2147483647 !important;
+ top: 0 !important; left: 0 !important;
+ right: 0 !important; bottom: 0 !important;
+ width: 100% !important; height: 100% !important;
+ }
+ #parent {
+ position: fixed;
+ z-index: -2147483748;
+ width: 0; height: 0;
+ overflow: hidden;
+ opacity: 0;
+ mask: url(#mask);
+ clip: rect(0, 0, 0, 0);
+ clip-path: url(#clipPath);
+ filter: opacity(0%);
+ will-change: transform;
+ perspective: 10px;
+ transform: scale(0);
+ }
+ /* The following styles are copied from ua.css to ensure that
+ * no other style change may trigger frame reconstruction */
+ :root {
+ overflow: hidden !important;
+ }
+ .two #fullscreen {
+ position: fixed !important;
+ top: 0 !important;
+ left: 0 !important;
+ right: 0 !important;
+ bottom: 0 !important;
+ z-index: 2147483647 !important;
+ width: 100% !important;
+ height: 100% !important;
+ margin: 0 !important;
+ min-width: 0 !important;
+ max-width: none !important;
+ min-height: 0 !important;
+ max-height: none !important;
+ box-sizing: border-box !important;
+ }
+ </style>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="text/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1126230">Mozilla Bug 1126230</a>
+<div id="parent">
+ <div id="fullscreen" style="background-color: green"></div>
+</div>
+<div id="back" style="background-color: red"></div>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <clipPath id="clipPath"></clipPath>
+ <mask id="mask"></mask>
+ </defs>
+</svg>
+<script>
+const gParentProperties = [
+ "position", "zIndex", "overflow",
+ "opacity", "mask", "clip", "clipPath",
+ "filter", "willChange", "transform"
+];
+
+var gInitialVals = {};
+
+const gParent = document.getElementById("parent");
+const gFullscreen = document.getElementById("fullscreen");
+const gBack = document.getElementById("back");
+
+function is(a, b, msg) {
+ opener.is(a, b, "[top-layer] " + msg);
+}
+
+function isnot(a, b, msg) {
+ opener.isnot(a, b, "[top-layer] " + msg);
+}
+
+function ok(cond, msg) {
+ opener.ok(cond, "[top-layer] " + msg);
+}
+
+function synthesizeMouseAtWindowCenter() {
+ synthesizeMouseAtPoint(innerWidth / 2, innerHeight / 2, {});
+}
+
+
+var tests = ["one", "two"];
+
+function begin() {
+ // record initial computed style of #parent
+ const style = getComputedStyle(gParent);
+ for (var prop of gParentProperties) {
+ gInitialVals[prop] = style[prop];
+ }
+
+ nextTest();
+}
+
+function nextTest() {
+ document.body.className = tests.shift();
+ // trigger a reflow to ensure the state of frames before fullscreen
+ gFullscreen.getBoundingClientRect();
+
+ ok(!document.fullscreenElement, "Shouldn't be in fullscreen");
+ // check window snapshot
+ assertWindowPureColor(window, "red");
+ // simulate click
+ window.addEventListener("click", firstClick);
+ synthesizeMouseAtWindowCenter();
+}
+
+function firstClick(evt) {
+ window.removeEventListener("click", firstClick);
+ is(evt.target, gBack, "Click target should be #back before fullscreen");
+ addFullscreenChangeContinuation("enter", enterFullscreen);
+ gFullscreen.requestFullscreen();
+}
+
+function enterFullscreen() {
+ ok(document.fullscreenElement, "Should now be in fullscreen");
+ // check window snapshot
+ assertWindowPureColor(window, "green");
+ // check computed style of #parent
+ const style = getComputedStyle(gParent);
+ for (var prop of gParentProperties) {
+ is(style[prop], gInitialVals[prop],
+ `Computed style ${prop} of #parent should not be changed`);
+ }
+ // simulate click
+ window.addEventListener("click", secondClick);
+ synthesizeMouseAtWindowCenter();
+}
+
+function secondClick(evt) {
+ window.removeEventListener("click", secondClick);
+ is(evt.target, gFullscreen, "Click target should be #fullscreen now");
+ addFullscreenChangeContinuation("exit", exitFullscreen);
+ document.exitFullscreen();
+}
+
+function exitFullscreen() {
+ if (tests.length > 0) {
+ nextTest();
+ } else {
+ opener.nextTest();
+ }
+}
+</script>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-unprefix-disabled-inner.html b/dom/html/test/file_fullscreen-unprefix-disabled-inner.html
new file mode 100644
index 000000000..632a6c0ce
--- /dev/null
+++ b/dom/html/test/file_fullscreen-unprefix-disabled-inner.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1268749</title>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+ <div id="fullscreen"></div>
+<script>
+
+function ok(condition, msg) {
+ opener.opener.ok(condition, "[unprefix-disabled] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.opener.is(a, b, "[unprefix-disabled] " + msg);
+}
+
+function info(msg) {
+ opener.opener.info("[unprefix-disabled] " + msg);
+}
+
+SimpleTest.requestFlakyTimeout(
+ "need to wait for a while to confirm no unexpected event is dispatched");
+
+let div = document.getElementById("fullscreen");
+let unattachedDiv = document.createElement('div');
+
+function begin() {
+ ok(!("requestFullscreen" in div), "No element.requestFullscreen");
+ ok(!("exitFullscreen" in document), "No document.exitFullscreen");
+ ok(!("fullscreen" in document), "No document.fullscreen");
+ ok(!("fullscreenElement" in document), "No document.fullscreenElement");
+ ok(!("fullscreenEnabled" in document), "No document.fullscreenEnabled");
+ ok(!("onfullscreenchange" in document), "No document.onfullscreenchange");
+ ok(!("onfullscreenerror" in document), "No document.onfullscreenerror");
+
+ for (var event of ["fullscreenchange", "fullscreenerror"]) {
+ let customEvent = new Event(event, {bubbles: true});
+ let gotCustomEventFromWindow = false;
+ let gotCustomEventFromDocument = false;
+ let listenerForWindow = evt => {
+ ok(!gotCustomEventFromWindow,
+ "Should get custom event from window only once");
+ ok(evt == customEvent, "Should get the desired custom event");
+ gotCustomEventFromWindow = true;
+ };
+ let listenerForDocument = evt => {
+ ok(!gotCustomEventFromDocument,
+ "Should get custom event from document only once");
+ ok(evt == customEvent, "Should get the desired custom event");
+ gotCustomEventFromDocument = true;
+ };
+ window.addEventListener(event, listenerForWindow);
+ document.addEventListener(event, listenerForDocument);
+ document.dispatchEvent(customEvent);
+ ok(gotCustomEventFromWindow, "Should get the custom event from window");
+ ok(gotCustomEventFromDocument, "Should get the custom event from document");
+ window.removeEventListener(event, listenerForWindow);
+ document.removeEventListener(event, listenerForDocument);
+
+ for (var target of [window, document]) {
+ target.addEventListener(event, () => {
+ ok(false, `No ${event} should be triggered on ${target}`);
+ });
+ }
+ }
+
+ document.addEventListener("mozfullscreenchange", enteredFullscreen);
+ SimpleTest.executeSoon(() => div.mozRequestFullScreen());
+}
+
+function enteredFullscreen() {
+ document.removeEventListener("mozfullscreenchange", enteredFullscreen);
+ document.addEventListener("mozfullscreenchange", exitedFullscreen);
+ SimpleTest.executeSoon(() => document.mozCancelFullScreen());
+}
+
+function exitedFullscreen() {
+ document.removeEventListener("mozfullscreenchange", exitedFullscreen);
+ document.addEventListener("mozfullscreenerror", errorFullscreen);
+ SimpleTest.executeSoon(() => unattachedDiv.mozRequestFullScreen());
+}
+
+function errorFullscreen() {
+ document.removeEventListener("mozfullscreenerror", errorFullscreen);
+ // Wait a short time before exiting this test to confirm that there is
+ // really no unwanted event gets dispatched.
+ setTimeout(() => opener.finish(), 200);
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-unprefix-disabled.html b/dom/html/test/file_fullscreen-unprefix-disabled.html
new file mode 100644
index 000000000..59737ec1d
--- /dev/null
+++ b/dom/html/test/file_fullscreen-unprefix-disabled.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1268749</title>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<script>
+
+var gWindow = null;
+
+function begin() {
+ SpecialPowers.pushPrefEnv({
+ "set": [["full-screen-api.unprefix.enabled", false]]
+ }, () => {
+ gWindow = window.open("file_fullscreen-unprefix-disabled-inner.html",
+ "", "width=500,height=500");
+ gWindow.addEventListener("load", () => {
+ gWindow.focus();
+ SimpleTest.waitForFocus(() => gWindow.begin(), gWindow);
+ });
+ });
+}
+
+function finish() {
+ gWindow.close();
+ SpecialPowers.popPrefEnv(opener.nextTest);
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/file_fullscreen-utils.js b/dom/html/test/file_fullscreen-utils.js
new file mode 100644
index 000000000..d1af72f8c
--- /dev/null
+++ b/dom/html/test/file_fullscreen-utils.js
@@ -0,0 +1,82 @@
+
+// Returns true if the window occupies the entire screen.
+// Note this only returns true once the transition from normal to
+// fullscreen mode is complete.
+function inFullscreenMode(win) {
+ return win.innerWidth == win.screen.width &&
+ win.innerHeight == win.screen.height;
+}
+
+// Returns true if the window is in normal mode, i.e. non fullscreen mode.
+// Note this only returns true once the transition from fullscreen back to
+// normal mode is complete.
+function inNormalMode(win) {
+ return win.innerWidth == win.normalSize.w &&
+ win.innerHeight == win.normalSize.h;
+}
+
+// Adds a listener that will be called once a fullscreen transition
+// is complete. When type==='enter', callback is called when we've
+// received a fullscreenchange event, and the fullscreen transition is
+// complete. When type==='exit', callback is called when we've
+// received a fullscreenchange event and the window dimensions match
+// the window dimensions when the window opened (so don't resize the
+// window while running your test!). inDoc is the document which
+// the listeners are added on, if absent, the listeners are added to
+// the current document.
+function addFullscreenChangeContinuation(type, callback, inDoc) {
+ var doc = inDoc || document;
+ var topWin = doc.defaultView.top;
+ // Remember the window size in non-fullscreen mode.
+ if (!topWin.normalSize) {
+ topWin.normalSize = {
+ w: window.innerWidth,
+ h: window.innerHeight
+ };
+ }
+ function checkCondition() {
+ if (type == "enter") {
+ return inFullscreenMode(topWin);
+ } else if (type == "exit") {
+ // If we just revert the state to a previous fullscreen state,
+ // the window won't back to the normal mode. Hence we check
+ // fullscreenElement first here. Note that we need to check
+ // the fullscreen element of the outmost document here instead
+ // of the current one.
+ return topWin.document.fullscreenElement || inNormalMode(topWin);
+ } else {
+ throw "'type' must be either 'enter', or 'exit'.";
+ }
+ }
+ function invokeCallback(event) {
+ // Use async call after a paint to workaround unfinished fullscreen
+ // change even when the window size has changed on Linux.
+ requestAnimationFrame(() => setTimeout(() => callback(event), 0), 0);
+ }
+ function onFullscreenChange(event) {
+ doc.removeEventListener("fullscreenchange", onFullscreenChange, false);
+ if (checkCondition()) {
+ invokeCallback(event);
+ return;
+ }
+ function onResize() {
+ if (checkCondition()) {
+ topWin.removeEventListener("resize", onResize, false);
+ invokeCallback(event);
+ }
+ }
+ topWin.addEventListener("resize", onResize, false);
+ }
+ doc.addEventListener("fullscreenchange", onFullscreenChange, false);
+}
+
+// Calls |callback| when the next fullscreenerror is dispatched to inDoc||document.
+function addFullscreenErrorContinuation(callback, inDoc) {
+ var doc = inDoc || document;
+ var listener = function(event) {
+ doc.removeEventListener("fullscreenerror", listener, false);
+ setTimeout(function(){callback(event);}, 0);
+ };
+ doc.addEventListener("fullscreenerror", listener, false);
+}
+
diff --git a/dom/html/test/file_iframe_sandbox_a_if1.html b/dom/html/test/file_iframe_sandbox_a_if1.html
new file mode 100644
index 000000000..b60d52ca0
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ I am sandboxed without any permissions
+ <iframe id="if_2a" src="file_iframe_sandbox_a_if2.html" height="10" width="10"></iframe>
+ <iframe id="if_2b" sandbox="allow-scripts" src="file_iframe_sandbox_a_if2.html" height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_a_if10.html b/dom/html/test/file_iframe_sandbox_a_if10.html
new file mode 100644
index 000000000..14306eb61
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if10.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<frameset>
+ <frame src="file_iframe_sandbox_a_if11.html">
+ <frame src="file_iframe_sandbox_a_if16.html">
+</frameset>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_a_if11.html b/dom/html/test/file_iframe_sandbox_a_if11.html
new file mode 100644
index 000000000..8eee71df1
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if11.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script>
+ function doStuff() {
+ try {
+ window.parent.parent.ok_wrapper(false, "a frame inside a sandboxed iframe should NOT be same origin with the iframe's parent");
+ }
+ catch (e) {
+ window.parent.parent.postMessage({ok: true, desc: "a frame inside a sandboxed iframe is not same origin with the iframe's parent"}, "*");
+ }
+ }
+ </script>
+</head>
+<frameset>
+ <frame onload='doStuff()' src="file_iframe_sandbox_a_if12.html">
+</frameset>
+I'm a &lt;frame&gt; inside an iframe which is sandboxed with 'allow-scripts allow-forms'
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_a_if12.html b/dom/html/test/file_iframe_sandbox_a_if12.html
new file mode 100644
index 000000000..d49d4e562
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if12.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script>
+function doStuff() {
+ try {
+ window.parent.parent.parent.ok_wrapper(false, "a frame inside a frame inside a sandboxed iframe should NOT be same origin with the iframe's parent");
+ }
+ catch (e) {
+ dump("caught some e if12\n");
+ window.parent.parent.parent.postMessage({ok: true, desc: "a frame inside a frame inside a sandboxed iframe is not same origin with the iframe's parent"}, "*");
+ }
+}
+</script>
+<body onload='doStuff()'>
+ I'm a &lt;frame&gt; inside a &lt;frame&gt; inside an iframe which is sandboxed with 'allow-scripts allow-forms'
+</body>
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_a_if13.html b/dom/html/test/file_iframe_sandbox_a_if13.html
new file mode 100644
index 000000000..8737a7682
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if13.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 886262</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body>
+ <object data="file_iframe_sandbox_a_if14.html"></object>
+</body>
+
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_a_if14.html b/dom/html/test/file_iframe_sandbox_a_if14.html
new file mode 100644
index 000000000..77210fca0
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if14.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 886262</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+ window.addEventListener("message", receiveMessage, false);
+
+ function receiveMessage(event)
+ {
+ window.parent.parent.postMessage({ok: event.data.ok, desc: "objects containing " + event.data.desc}, "*");
+ }
+
+ function doStuff() {
+ try {
+ window.parent.parent.ok_wrapper(false, "an object inside a sandboxed iframe should NOT be same origin with the iframe's parent");
+ }
+ catch (e) {
+ window.parent.parent.postMessage({ok: true, desc: "an object inside a sandboxed iframe is not same origin with the iframe's parent"}, "*");
+ }
+ }
+</script>
+
+<body onload='doStuff()'>
+I'm a &lt;object&gt; inside an iframe which is sandboxed with 'allow-scripts allow-forms'
+
+ <object data="file_iframe_sandbox_a_if15.html"></object>
+</body>
+
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_a_if15.html b/dom/html/test/file_iframe_sandbox_a_if15.html
new file mode 100644
index 000000000..9c5a003d7
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if15.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 886262</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+function doStuff() {
+ try {
+ window.parent.parent.parent.ok_wrapper(false, "an object inside a frame or object inside a sandboxed iframe should NOT be same origin with the iframe's parent");
+ }
+ catch (e) {
+ window.parent.parent.parent.postMessage({ok: true, desc: "an object inside a frame or object inside a sandboxed iframe is not same origin with the iframe's parent"}, "*");
+ }
+
+ // Check that sandboxed forms browsing context flag NOT set by attempting to submit a form.
+ document.getElementById('a_form').submit();
+}
+</script>
+
+<body onload='doStuff()'>
+ I'm a &lt;object&gt; inside a &lt;frame&gt; or &lt;object&gt; inside an iframe which is sandboxed with 'allow-scripts allow-forms'
+
+ <form method="get" action="file_iframe_sandbox_form_pass.html" id="a_form">
+ First name: <input type="text" name="firstname">
+ Last name: <input type="text" name="lastname">
+ <input type="submit" id="a_button">
+ </form>
+</body>
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_a_if16.html b/dom/html/test/file_iframe_sandbox_a_if16.html
new file mode 100644
index 000000000..f92531465
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if16.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 886262</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+ window.addEventListener("message", receiveMessage, false);
+
+ function receiveMessage(event)
+ {
+ window.parent.parent.postMessage({ok: event.data.ok, desc: "objects containing " + event.data.desc}, "*");
+ }
+</script>
+
+<body>
+I'm a &lt;frame&gt; inside an iframe which is sandboxed with 'allow-scripts allow-forms'
+
+ <object data="file_iframe_sandbox_a_if15.html"></object>
+</body>
+
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_a_if17.html b/dom/html/test/file_iframe_sandbox_a_if17.html
new file mode 100644
index 000000000..a736924bf
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if17.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 886262</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+ function doTest() {
+ var if_18_19 = document.getElementById('if_18_19');
+ if_18_19.sandbox = "allow-scripts allow-same-origin";
+ if_18_19.contentWindow.postMessage("go", "*");
+ }
+</script>
+
+<body onload="doTest()">
+ I am sandboxed but with "allow-scripts". I change the sandbox flags on if_18_19 to
+ "allow-scripts allow-same-origin" then get it to re-navigate itself to
+ file_iframe_sandbox_a_if18.html, which attemps to call a function in my parent.
+ This should fail since my sandbox flags should be copied to it when the sandbox
+ flags are changed.
+
+ <iframe sandbox="allow-scripts" id="if_18_19" src="file_iframe_sandbox_a_if19.html" height="10" width="10"></iframe>
+</body>
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_a_if18.html b/dom/html/test/file_iframe_sandbox_a_if18.html
new file mode 100644
index 000000000..bbe90970d
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if18.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 886262</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+ function doTest() {
+ try {
+ window.parent.parent.ok_wrapper(false, "an iframe in an iframe SHOULD copy its parent's sandbox flags when its sandbox flags are changed");
+ }
+ catch (e) {
+ window.parent.parent.postMessage({ok: true, desc: "an iframe in an iframe copies its parent's sandbox flags when its sandbox flags are changed"}, "*");
+ }
+ }
+</script>
+
+<body onload="doTest()">
+ I'm an iframe whose sandbox flags have been changed to include allow-same-origin.
+ I should not be able to call a function in my parent's parent because my parent's
+ iframe does not have allow-same-origin set.
+</body>
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_a_if19.html b/dom/html/test/file_iframe_sandbox_a_if19.html
new file mode 100644
index 000000000..a065a9450
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if19.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 886262</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<script>
+ window.addEventListener("message", function(e){
+ window.open("file_iframe_sandbox_a_if18.html", "_self");
+ }, false);
+</script>
+
+<body>
+ I'm just here to navigate to file_iframe_sandbox_a_if18.html after my owning
+ iframe has had allow-same-origin added.
+</body>
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_a_if2.html b/dom/html/test/file_iframe_sandbox_a_if2.html
new file mode 100644
index 000000000..72bde69e4
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if2.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="text/javascript">
+function doStuff() {
+ // should NOT be able to execute scripts
+ window.parent.parent.postMessage({ok: false, desc: "a document within an iframe sandboxed with sandbox='' should NOT be able to execute scripts"}, "*");
+}
+</script>
+
+<body onLoad="doStuff()">
+ I am NOT sandboxed or am sandboxed with "allow-scripts" but am contained within an iframe sandboxed with sandbox = ""
+ or am sandboxed with sandbox='' inside an iframe sandboxed with "allow-scripts"
+</body>
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_a_if3.html b/dom/html/test/file_iframe_sandbox_a_if3.html
new file mode 100644
index 000000000..c103c19f2
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if3.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<script type="text/javascript">
+ function ok_wrapper(condition, msg) {
+ window.parent.ok_wrapper(condition, msg);
+ }
+</script>
+
+<body>
+ I am sandboxed but with "allow-scripts"
+
+ <iframe id='if_4' src='file_iframe_sandbox_a_if4.html' height="10" width="10"></iframe>
+ <iframe id='if_7' src='file_iframe_sandbox_a_if7.html' height="10" width="10"></iframe>
+ <iframe id='if_2' sandbox='' src='file_iframe_sandbox_a_if2.html' height="10" width="10"></iframe>
+</body>
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_a_if4.html b/dom/html/test/file_iframe_sandbox_a_if4.html
new file mode 100644
index 000000000..2bd727e96
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if4.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<script type="text/javascript">
+function doStuff() {
+ try {
+ window.parent.ok_wrapper(false, "a document contained within a sandboxed document without 'allow-same-origin' should NOT be same domain with its parent");
+ } catch(e) {
+ window.parent.parent.postMessage({type: "ok", ok: true, desc: "a document contained within a sandboxed document without 'allow-same-origin' should NOT be same domain with its parent"}, "*");
+ }
+
+ try {
+ window.parent.parent.ok_wrapper(false, "a document contained within a sandboxed document without 'allow-same-origin' should NOT be same domain with the top level");
+ } catch(e) {
+ window.parent.parent.postMessage({type: "ok", ok: true, desc: "a document contained within a sandboxed document without 'allow-same-origin' should NOT be same domain with the top level"}, "*");
+ }
+}
+</script>
+
+<body onLoad="doStuff()">
+ I am not sandboxed but contained within a sandboxed document with 'allow-scripts'
+</body>
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_a_if5.html b/dom/html/test/file_iframe_sandbox_a_if5.html
new file mode 100644
index 000000000..dfa0fed01
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if5.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<script type="text/javascript">
+ function ok_wrapper(result, desc) {
+ window.parent.ok_wrapper(result, desc);
+ }
+</script>
+
+<body>
+ I am sandboxed but with "allow-scripts allow-same-origin"
+
+ <iframe sandbox='allow-scripts allow-same-origin' id='if_6' src='file_iframe_sandbox_a_if6.html' height="10" width="10"></iframe>
+</body>
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_a_if6.html b/dom/html/test/file_iframe_sandbox_a_if6.html
new file mode 100644
index 000000000..79f15e6ec
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if6.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<script type="text/javascript">
+function doStuff() {
+ window.parent.ok_wrapper(true, "a document sandboxed with 'allow-same-origin' and contained within a sandboxed document with 'allow-same-origin' should be same domain with its parent");
+ window.parent.parent.ok_wrapper(true, "a document sandboxed with 'allow-same-origin' contained within a sandboxed document with 'allow-same-origin' should be same domain with the top level");
+}
+</script>
+
+<body onLoad="doStuff()">
+ I am sandboxed with 'allow-scripts allow-same-origin' and contained within a sandboxed document with 'allow-scripts allow-same-origin'
+</body>
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_a_if7.html b/dom/html/test/file_iframe_sandbox_a_if7.html
new file mode 100644
index 000000000..6480eebdb
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if7.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="text/javascript">
+function doStuff() {
+ // should be able to execute scripts
+ window.parent.parent.postMessage({ok: true, desc: "a document contained within an iframe contained within an iframe sandboxed with 'allow-scripts' should be able to execute scripts"}, "*");
+}
+</script>
+
+<body onLoad="doStuff()">
+ I am NOT sandboxed but am contained within an iframe contained within an iframe sandboxed with sandbox = "allow-scripts"
+</body>
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_a_if8.html b/dom/html/test/file_iframe_sandbox_a_if8.html
new file mode 100644
index 000000000..002df5df5
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if8.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script>
+function doSubload() {
+ var if_9 = document.getElementById('if_9');
+ if_9.src = 'file_iframe_sandbox_a_if9.html';
+}
+
+window.doSubload = doSubload;
+
+</script>
+<body>
+ I am sandboxed but with "allow-scripts allow-same-origin". After my initial load, "allow-same-origin" is removed
+ and then I load file_iframe_sandbox_a_if9.html, which attemps to call a function in window.top. This should
+ succeed since the new sandbox flags shouldn't have taken affect on me until I'm reloaded.
+
+ <iframe id='if_9' src='about:blank' height="10" width="10"></iframe>
+</body>
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_a_if9.html b/dom/html/test/file_iframe_sandbox_a_if9.html
new file mode 100644
index 000000000..da2bcf1fa
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_a_if9.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script>
+function doStuff() {
+ window.parent.parent.ok_wrapper(true, "a subloaded document should inherit the flags of the document, not of the docshell/sandbox attribute");
+}
+</script>
+<body onload='doStuff()'>
+ I'm a subloaded document of file_iframe_sandbox_a_if8.html. I should be able to call a function in window.top
+ because I should be same-origin with it.
+</body>
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_b_if1.html b/dom/html/test/file_iframe_sandbox_b_if1.html
new file mode 100644
index 000000000..a65cbec6b
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_b_if1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ I am sandboxed without any permissions
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_b_if2.html b/dom/html/test/file_iframe_sandbox_b_if2.html
new file mode 100644
index 000000000..08e745357
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_b_if2.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script>
+ function ok(condition, msg) {
+ window.parent.ok_wrapper(condition, msg);
+ }
+
+ function testXHR() {
+ var xhr = new XMLHttpRequest();
+
+ xhr.open("GET", "file_iframe_sandbox_b_if1.html");
+
+ xhr.onreadystatechange = function (oEvent) {
+ var result = false;
+ if (xhr.readyState == 4) {
+ if (xhr.status == 200) {
+ result = true;
+ }
+ ok(result, "XHR should work normally in an iframe sandboxed with 'allow-same-origin'");
+ }
+ }
+
+ xhr.send(null);
+ }
+
+ function doStuff() {
+ ok(true, "documents sandboxed with 'allow-same-origin' should be able to access their parent");
+
+ // should be able to access document.cookie since we have 'allow-same-origin'
+ ok(document.cookie == "", "a document sandboxed with allow-same-origin should be able to access document.cookie");
+
+ // should be able to access localStorage since we have 'allow-same-origin'
+ ok(window.localStorage, "a document sandboxed with allow-same-origin should be able to access localStorage");
+
+ // should be able to access sessionStorage since we have 'allow-same-origin'
+ ok(window.sessionStorage, "a document sandboxed with allow-same-origin should be able to access sessionStorage");
+
+ testXHR();
+ }
+</script>
+<body onLoad="doStuff()">
+ I am sandboxed but with "allow-same-origin" and "allow-scripts"
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_b_if3.html b/dom/html/test/file_iframe_sandbox_b_if3.html
new file mode 100644
index 000000000..350e2ac47
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_b_if3.html
@@ -0,0 +1,92 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script>
+ function ok(result, message) {
+ window.parent.postMessage({ok: result, desc: message}, "*");
+ }
+
+ function testXHR() {
+ // Standard URL should be blocked as we have a unique origin.
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "file_iframe_sandbox_b_if1.html");
+ xhr.onreadystatechange = function (oEvent) {
+ var result = false;
+ if (xhr.readyState == 4) {
+ if (xhr.status == 0) {
+ result = true;
+ }
+ ok(result, "XHR should be blocked in an iframe sandboxed WITHOUT 'allow-same-origin'");
+ }
+ }
+ xhr.send(null);
+
+ // Blob URL should work as it will have our unique origin.
+ var blobXhr = new XMLHttpRequest();
+ var blobUrl = URL.createObjectURL(new Blob(["wibble"], {type: "text/plain"}));
+ blobXhr.open("GET", blobUrl);
+ blobXhr.onreadystatechange = function () {
+ if (this.readyState == 4) {
+ ok(this.status == 200 && this.response == "wibble", "XHR for a blob URL created in this document should NOT be blocked in an iframe sandboxed WITHOUT 'allow-same-origin'");
+ }
+ }
+ try {
+ blobXhr.send();
+ } catch(e) {
+ ok(false, "failed to send XHR for blob URL: error: " + e);
+ }
+
+ // Data URL should work as it inherits the loader's origin.
+ var dataXhr = new XMLHttpRequest();
+ dataXhr.open("GET", "data:text/html,wibble");
+ dataXhr.onreadystatechange = function () {
+ if (this.readyState == 4) {
+ ok(this.status == 200 && this.response == "wibble", "XHR for a data URL should NOT be blocked in an iframe sandboxed WITHOUT 'allow-same-origin'");
+ }
+ }
+ try {
+ dataXhr.send();
+ } catch(e) {
+ ok(false, "failed to send XHR for data URL: error: " + e);
+ }
+ }
+
+ function doStuff() {
+ try {
+ window.parent.ok(false, "documents sandboxed without 'allow-same-origin' should NOT be able to access their parent");
+ } catch (error) {
+ ok(true, "documents sandboxed without 'allow-same-origin' should NOT be able to access their parent");
+ }
+
+ // should NOT be able to access document.cookie
+ try {
+ var foo = document.cookie;
+ } catch(error) {
+ ok(true, "a document sandboxed without allow-same-origin should NOT be able to access document.cookie");
+ }
+
+ // should NOT be able to access localStorage
+ try {
+ var foo = window.localStorage;
+ } catch(error) {
+ ok(true, "a document sandboxed without allow-same-origin should NOT be able to access localStorage");
+ }
+
+ // should NOT be able to access sessionStorage
+ try {
+ var foo = window.sessionStorage;
+ } catch(error) {
+ ok(true, "a document sandboxed without allow-same-origin should NOT be able to access sessionStorage");
+ }
+
+ testXHR();
+ }
+</script>
+<body onLoad="doStuff()">
+ I am sandboxed but with "allow-scripts"
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_c_if1.html b/dom/html/test/file_iframe_sandbox_c_if1.html
new file mode 100644
index 000000000..fff2f6d40
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_c_if1.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.postMessage({ok: result, desc: desc}, "*");
+ }
+
+ function doStuff() {
+ ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts");
+
+ document.getElementById('a_form').submit();
+
+ // trigger the javascript: url test
+ sendMouseEvent({type:'click'}, 'a_link');
+ }
+</script>
+<script src='file_iframe_sandbox_pass.js'></script>
+<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'>
+ I am sandboxed but with "allow-scripts"
+
+ <form method="get" action="file_iframe_sandbox_form_fail.html" id="a_form">
+ First name: <input type="text" name="firstname">
+ Last name: <input type="text" name="lastname">
+ <input type="submit" onclick="doSubmit()" id="a_button">
+ </form>
+
+ <a href = 'javascript:ok(true, "documents sandboxed with allow-scripts should be able to run script from javascript: URLs");' id='a_link'>click me</a>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_c_if2.html b/dom/html/test/file_iframe_sandbox_c_if2.html
new file mode 100644
index 000000000..b39231919
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_c_if2.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+</head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.postMessage({ok: result, desc: desc}, "*");
+ }
+
+ function doStuff() {
+ ok(false, "documents sandboxed without allow-scripts should NOT be able to run inline scripts");
+ }
+</script>
+<script src='file_iframe_sandbox_fail.js'></script>
+<body onLoad='window.parent.postmessage({ok: false, desc: "documents sandboxed without allow-scripts should NOT be able to run script from event handlers"}, "*");doStuff();'>
+ I am sandboxed with no permissions
+ <img src="about:blank" onerror='ok(false, "documents sandboxed without allow-scripts should NOT be able to run script from event handlers");')>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_c_if3.html b/dom/html/test/file_iframe_sandbox_c_if3.html
new file mode 100644
index 000000000..aca5d3356
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_c_if3.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+</head>
+<script type="text/javascript">
+ function doStuff() {
+ dump("*** c_if3 has loaded\n");
+ // try and submit the form - this should succeed
+ document.getElementById('a_form').submit();
+ }
+</script>
+<body onLoad="doStuff()">
+ I am sandboxed but with "allow-scripts allow-forms"
+
+ <form method="get" action="file_iframe_sandbox_form_pass.html" id="a_form">
+ First name: <input type="text" name="firstname">
+ Last name: <input type="text" name="lastname">
+ <input type="submit" onclick="doSubmit()" id="a_button">
+ </form>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_c_if4.html b/dom/html/test/file_iframe_sandbox_c_if4.html
new file mode 100644
index 000000000..53bf49559
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_c_if4.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.ok_wrapper(result, desc);
+ }
+
+ function doStuff() {
+ // try to open a new window via target="_blank", target="BC341604", window.open(), and showModalDialog()
+ // the window we try to open closes itself once it opens
+ sendMouseEvent({type:'click'}, 'target_blank');
+ sendMouseEvent({type:'click'}, 'target_BC341604');
+
+ var threw = false;
+ try {
+ window.open("about:blank");
+ } catch (error) {
+ threw = true;
+ }
+
+ ok(threw, "window.open threw a JS exception and was not allowed");
+
+ threw = false;
+ try {
+ window.showModalDialog("about:blank");
+ } catch(error) {
+ threw = true;
+ }
+
+ ok(threw, "window.showModalDialog threw a JS exception and was not allowed");
+ }
+</script>
+<body onLoad="doStuff()">
+ I am sandboxed but with "allow-scripts allow-same-origin"
+
+ <a href="file_iframe_sandbox_open_window_fail.html" target="_blank" id="target_blank">open window</a>
+ <a href="file_iframe_sandbox_open_window_fail.html" target="BC341604" id="target_BC341604">open window</a>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_c_if5.html b/dom/html/test/file_iframe_sandbox_c_if5.html
new file mode 100644
index 000000000..267864bd8
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_c_if5.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+</head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.ok_wrapper(result, desc);
+ }
+</script>
+<body onLoad="doStuff()">
+ I am sandboxed but with "allow-same-origin"
+
+ <a href = 'javascript:ok(false, "documents sandboxed without allow-scripts should not be able to run script with javascript: URLs");' id='a_link'>click me</a>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_c_if6.html b/dom/html/test/file_iframe_sandbox_c_if6.html
new file mode 100644
index 000000000..3dab59771
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_c_if6.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+</head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.ok_wrapper(result, desc);
+ window.parent.postMessage({ok: result, desc: desc}, "*");
+ }
+
+ function doStuff() {
+ ok(true, "a document sandboxed with allow-same-origin and allow-scripts should be same origin with its parent and able to run scripts " +
+ "regardless of what kind of whitespace was used in its sandbox attribute");
+ }
+</script>
+<body onLoad="doStuff()">
+ I am sandboxed but with "allow-same-origin" and "allow-scripts"
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_c_if7.html b/dom/html/test/file_iframe_sandbox_c_if7.html
new file mode 100644
index 000000000..fbd55dbd4
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_c_if7.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.postMessage({ok: result, desc: desc}, "*");
+ }
+
+ function doStuff() {
+ try {
+ var thing = indexedDB.open("sandbox");
+ ok(false, "documents sandboxed without allow-same-origin should NOT be able to access indexedDB");
+ }
+
+ catch(e) {
+ ok(true, "documents sandboxed without allow-same-origin should NOT be able to access indexedDB");
+ }
+ }
+</script>
+<body onLoad='doStuff();'>
+ I am sandboxed but with "allow-scripts"
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_c_if8.html b/dom/html/test/file_iframe_sandbox_c_if8.html
new file mode 100644
index 000000000..a03cc77de
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_c_if8.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.postMessage({ok: result, desc: desc}, "*");
+ }
+
+ function doStuff() {
+ var thing = indexedDB.open("sandbox");
+
+ thing.onerror = function(event) {
+ ok(false, "documents sandboxed with allow-same-origin SHOULD be able to access indexedDB");
+ };
+ thing.onsuccess = function(event) {
+ ok(true, "documents sandboxed with allow-same-origin SHOULD be able to access indexedDB");
+ };
+ }
+</script>
+<body onLoad='doStuff();'>
+ I am sandboxed but with "allow-scripts allow-same-origin"
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_c_if9.html b/dom/html/test/file_iframe_sandbox_c_if9.html
new file mode 100644
index 000000000..0c88a677c
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_c_if9.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 671389</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ I am
+ <ul>
+ <li>sandboxed but with "allow-forms", "allow-pointer-lock", "allow-popups", "allow-same-origin", "allow-scripts", and "allow-top-navigation", </li>
+ <li>sandboxed but with "allow-same-origin", "allow-scripts", </li>
+ <li>sandboxed, or </li>
+ <li>not sandboxed.</li>
+ </ul>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_close.html b/dom/html/test/file_iframe_sandbox_close.html
new file mode 100644
index 000000000..3b8753497
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_close.html
@@ -0,0 +1,3 @@
+<script>
+ self.close();
+</script>
diff --git a/dom/html/test/file_iframe_sandbox_d_if1.html b/dom/html/test/file_iframe_sandbox_d_if1.html
new file mode 100644
index 000000000..054edbfe1
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="application/javascript">
+function doTest() {
+ sendMouseEvent({type:'click'}, 'anchor');
+}
+</script>
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts'
+
+ <a href="file_iframe_sandbox_navigation_pass.html?Test 1:%20" target="_self" id='anchor'>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if10.html b/dom/html/test/file_iframe_sandbox_d_if10.html
new file mode 100644
index 000000000..8811c8897
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if10.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="application/javascript">
+function doTest() {
+ window.parent.postMessage({type: "if_10"}, "*");
+}
+</script>
+<body onload='doTest()'>
+ I am sandboxed with 'allow-scripts'
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if11.html b/dom/html/test/file_iframe_sandbox_d_if11.html
new file mode 100644
index 000000000..b6d431c89
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if11.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="application/javascript">
+function doTest() {
+ // this should fail the first time, but work the second
+ try {
+ window.parent.ok_wrapper(true, "a document that was loaded, navigated to another document, had 'allow-same-origin' added and then was" +
+ " navigated back should be same-origin with its parent");
+ }
+ catch (e) {
+ sendMouseEvent({type:'click'}, 'anchor');
+ }
+}
+
+</script>
+<body onload='doTest()'>
+ I am sandboxed with 'allow-scripts'
+ <a href='file_iframe_sandbox_d_if12.html' id='anchor'>CLICK ME</a>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if12.html b/dom/html/test/file_iframe_sandbox_d_if12.html
new file mode 100644
index 000000000..0d7936512
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if12.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script type="application/javascript">
+function doTest() {
+ window.parent.postMessage({test:'if_11'}, "*");
+}
+</script>
+<body onload='doTest()'>
+ I am sandboxed with 'allow-scripts'
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if13.html b/dom/html/test/file_iframe_sandbox_d_if13.html
new file mode 100644
index 000000000..275490f87
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if13.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="application/javascript">
+window.addEventListener("message", receiveMessage, false);
+
+function receiveMessage(event) {
+ // this message is part of if_11's test
+ if (event.data.test == 'if_11') {
+ doIf11TestPart2();
+ return;
+ }
+}
+
+function ok_wrapper(result, msg) {
+ window.opener.postMessage({ok: result, desc: msg}, "*");
+ window.close();
+}
+
+function doIf11TestPart2() {
+ var if_11 = document.getElementById('if_11');
+ if_11.sandbox = 'allow-scripts allow-same-origin';
+ // window.history is no longer cross-origin accessible in gecko.
+ SpecialPowers.wrap(if_11).contentWindow.history.back();
+}
+</script>
+<body>
+ <iframe sandbox='allow-scripts' id="if_11" src="file_iframe_sandbox_d_if11.html" height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if14.html b/dom/html/test/file_iframe_sandbox_d_if14.html
new file mode 100644
index 000000000..c670b5ff6
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if14.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 838692</title>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="text/javascript">
+ var test20Context = "Test 20: Navigate another window (not opened by us): ";
+
+ function doTest() {
+ // Try to navigate auxiliary browsing context (window) not opened by us.
+ // We should not be able to do this as we are sandboxed.
+ sendMouseEvent({type:'click'}, 'navigate_window');
+ window.parent.postMessage({type: "attempted"}, "*");
+
+ // Try to navigate auxiliary browsing context (window) not opened by us, using window.open().
+ // We should not be able to do this as we are sandboxed.
+ try {
+ window.open("file_iframe_sandbox_window_navigation_fail.html?" + escape(test20Context), "window_to_navigate2");
+ window.parent.postMessage({type: "attempted"}, "*");
+ } catch(error) {
+ window.parent.postMessage({ok: true, desc: test20Context + "as expected, error thrown during window.open(..., \"window_to_navigate2\")"}, "*");
+ }
+ }
+</script>
+
+<body onload="doTest()">
+ I am sandboxed but with "allow-scripts allow-same-origin allow-top-navigation".
+
+ <a href="file_iframe_sandbox_window_navigation_fail.html?Test 14: Navigate another window (not opened by us):%20" target="window_to_navigate" id="navigate_window">navigate window</a>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if15.html b/dom/html/test/file_iframe_sandbox_d_if15.html
new file mode 100644
index 000000000..6c969c8fe
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if15.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body>
+ I am an unsandboxed iframe.
+
+ <iframe sandbox="allow-same-origin allow-scripts" id="if_16" src="file_iframe_sandbox_d_if16.html" height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if16.html b/dom/html/test/file_iframe_sandbox_d_if16.html
new file mode 100644
index 000000000..aba5d4b96
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if16.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<script type="application/javascript">
+function doTest() {
+ window.parent.parent.postMessage({type: "attempted"}, "*");
+ sendMouseEvent({type:'click'}, 'anchor');
+}
+</script>
+
+<body onload="doTest()">
+ I am sandboxed with 'allow-same-origin allow-scripts'
+
+ <a href="file_iframe_sandbox_navigation_fail.html?Test 16: Navigate parent/ancestor by name:%20" target='if_parent' id='anchor'>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if17.html b/dom/html/test/file_iframe_sandbox_d_if17.html
new file mode 100644
index 000000000..047a08137
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if17.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="application/javascript">
+ var testContext = "Test 17: navigate _self with window.open(): ";
+
+ function doTest() {
+ try {
+ window.open("file_iframe_sandbox_navigation_pass.html?" + escape(testContext), "_self");
+ } catch(error) {
+ window.parent.postMessage({ok: false, desc: testContext + "error thrown during window.open(..., \"_self\")"}, "*");
+ }
+ }
+</script>
+
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts'
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if18.html b/dom/html/test/file_iframe_sandbox_d_if18.html
new file mode 100644
index 000000000..03aa0aa9b
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if18.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<script type="application/javascript">
+ window.addEventListener("message", receiveMessage, false);
+
+ function receiveMessage(event) {
+ window.parent.postMessage(event.data, "*");
+ }
+
+ var testContext = "Test 18: navigate child with window.open(): ";
+
+ function doTest() {
+ try {
+ window.open("file_iframe_sandbox_navigation_pass.html?" + escape(testContext), "foo");
+ } catch(error) {
+ window.parent.postMessage({ok: false, desc: testContext + " error thrown during window.open(..., \"foo\")"}, "*");
+ }
+ }
+</script>
+
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts'
+
+ <iframe name="foo" height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if19.html b/dom/html/test/file_iframe_sandbox_d_if19.html
new file mode 100644
index 000000000..d766d2649
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if19.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ I am sandboxed with 'allow-scripts'
+
+ <iframe sandbox="allow-scripts" id="if_20" src="file_iframe_sandbox_d_if20.html" height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if2.html b/dom/html/test/file_iframe_sandbox_d_if2.html
new file mode 100644
index 000000000..763585c62
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if2.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="application/javascript">
+// needed to forward the message to the main test page
+window.addEventListener("message", receiveMessage, false);
+
+function receiveMessage(event) {
+ window.parent.postMessage(event.data, "*");
+}
+
+function doTest() {
+ sendMouseEvent({type:'click'}, 'anchor');
+}
+</script>
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts'
+
+ <iframe name="foo" src="file_iframe_sandbox_navigation_start.html" height="10" width="10"></iframe>
+
+ <a href="file_iframe_sandbox_navigation_pass.html?Test 2:%20" target='foo' id='anchor'>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if20.html b/dom/html/test/file_iframe_sandbox_d_if20.html
new file mode 100644
index 000000000..005c4bc82
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if20.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="application/javascript">
+ var testContext = "Test 19: navigate _parent with window.open(): ";
+
+ function doTest() {
+ try {
+ window.open("file_iframe_sandbox_navigation_fail.html?" + escape(testContext), "_parent");
+ window.parent.parent.postMessage({type: "attempted"}, "*");
+ } catch(error) {
+ window.parent.parent.postMessage({ok: true, desc: testContext + "as expected, error thrown during window.open(..., \"_parent\")"}, "*");
+ }
+ }
+</script>
+
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts'
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if21.html b/dom/html/test/file_iframe_sandbox_d_if21.html
new file mode 100644
index 000000000..6d0ab232e
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if21.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body>
+ I am an unsandboxed iframe.
+
+ <iframe sandbox="allow-same-origin allow-scripts" id="if_22" src="file_iframe_sandbox_d_if22.html" height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if22.html b/dom/html/test/file_iframe_sandbox_d_if22.html
new file mode 100644
index 000000000..bd2715792
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if22.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="application/javascript">
+ var testContext = "Test 21: navigate parent by name with window.open(): ";
+
+ function doTest() {
+ try {
+ window.open("file_iframe_sandbox_navigation_fail.html?" + escape(testContext), "if_parent2");
+ window.parent.parent.postMessage({type: "attempted"}, "*");
+ } catch(error) {
+ window.parent.parent.postMessage({ok: true, desc: testContext + "as expected, error thrown during window.open(..., \"if_parent2\")"}, "*");
+ }
+ }
+</script>
+
+<body onload="doTest()">
+ I am sandboxed with 'allow-same-origin allow-scripts'
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if23.html b/dom/html/test/file_iframe_sandbox_d_if23.html
new file mode 100644
index 000000000..04688b3d9
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if23.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="application/javascript">
+ var test27Context = "Test 27: navigate opened window by name with anchor: ";
+ var test28Context = "Test 28: navigate opened window by name with window.open(): ";
+
+ var windowsToClose = new Array();
+
+ function closeWindows() {
+ for (var i = 0; i < windowsToClose.length; i++) {
+ windowsToClose[i].close();
+ }
+ }
+
+ // Add message listener to forward messages on to parent
+ window.addEventListener("message", receiveMessage, false);
+
+ function receiveMessage(event) {
+ switch (event.data.type) {
+ case "closeWindows":
+ closeWindows();
+ break;
+ default:
+ window.parent.postMessage(event.data, "*");
+ }
+ }
+
+ function doTest() {
+ try {
+ windowsToClose.push(window.open("about:blank", "test27window"));
+ var test27Anchor = document.getElementById("test27Anchor");
+ test27Anchor.href = "file_iframe_sandbox_window_navigation_pass.html?" + escape(test27Context);
+ sendMouseEvent({type:"click"}, "test27Anchor");
+ window.parent.postMessage({type: "attempted"}, "*");
+ } catch(error) {
+ window.parent.postMessage({ok: false, desc: test27Context + "error thrown during window.open(): " + error}, "*");
+ }
+
+ try {
+ windowsToClose.push(window.open("about:blank", "test28window"));
+ window.open("file_iframe_sandbox_window_navigation_pass.html?" + escape(test28Context), "test28window");
+ window.parent.postMessage({type: "attempted"}, "*");
+ } catch(error) {
+ window.parent.postMessage({ok: false, desc: test28Context + "error thrown during window.open(): " + error}, "*");
+ }
+ }
+</script>
+
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts allow-popups'
+
+ <a id="test27Anchor" target="test27window">Test 27 anchor</a>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if3.html b/dom/html/test/file_iframe_sandbox_d_if3.html
new file mode 100644
index 000000000..cd2d53bce
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if3.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ I am sandboxed with 'allow-scripts'
+
+ <iframe sandbox="allow-scripts" id="if_4" src="file_iframe_sandbox_d_if4.html" height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if4.html b/dom/html/test/file_iframe_sandbox_d_if4.html
new file mode 100644
index 000000000..64e173a64
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if4.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="application/javascript">
+function doTest() {
+ window.parent.parent.postMessage({type: "attempted"}, "*");
+ sendMouseEvent({type:'click'}, 'anchor');
+}
+</script>
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts'
+
+ <a href="file_iframe_sandbox_navigation_fail.html" target='_parent' id='anchor'>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if5.html b/dom/html/test/file_iframe_sandbox_d_if5.html
new file mode 100644
index 000000000..ba31053ad
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if5.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="application/javascript">
+function doTest() {
+ window.parent.postMessage({type: "attempted"}, "*");
+ sendMouseEvent({type:'click'}, 'anchor');
+}
+</script>
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts allow-same-origin'
+
+ <a href="file_iframe_sandbox_navigation_fail.html?Test 4: Navigate sibling iframe by name:%20" target='if_sibling' id='anchor'>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if6.html b/dom/html/test/file_iframe_sandbox_d_if6.html
new file mode 100644
index 000000000..e7e19fc2e
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if6.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="application/javascript">
+function doTest() {
+ sendMouseEvent({type:'click'}, 'anchor');
+}
+</script>
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts'
+
+ <a href="file_iframe_sandbox_d_if7.html" target='_self' id='anchor'>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if7.html b/dom/html/test/file_iframe_sandbox_d_if7.html
new file mode 100644
index 000000000..5023ee029
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if7.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script type="application/javascript">
+function doTest() {
+ try {
+ window.parent.ok_wrapper(false, "a sandboxed document when navigated should still NOT be same-origin with its parent");
+ } catch(error) {
+ window.parent.postMessage({ok: true, desc: "sandboxed document's attempt to access parent after navigation blocked, as not same-origin."}, "*");
+ }
+}
+</script>
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts'
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if8.html b/dom/html/test/file_iframe_sandbox_d_if8.html
new file mode 100644
index 000000000..2b4398ef0
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if8.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="application/javascript">
+ function doTest() {
+ window.parent.modify_if_8();
+ }
+</script>
+
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts' and 'allow-same-origin' the first time I am loaded, and with 'allow-scripts' the second time
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_d_if9.html b/dom/html/test/file_iframe_sandbox_d_if9.html
new file mode 100644
index 000000000..ee641904f
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_d_if9.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script type="application/javascript">
+function doTest() {
+ window.parent.modify_if_9();
+}
+</script>
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts' and 'allow-same-origin' the first time I am loaded, and with 'allow-same-origin' the second time
+</body>
+</html>
+
diff --git a/dom/html/test/file_iframe_sandbox_e_if1.html b/dom/html/test/file_iframe_sandbox_e_if1.html
new file mode 100644
index 000000000..69f827ce4
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_e_if1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+
+<script>
+ function doTest() {
+ var testContext = location.search == "" ? "?Test 10: Navigate _top:%20" : location.search;
+ document.getElementById("if_6").src = "file_iframe_sandbox_e_if6.html" + testContext;
+ }
+</script>
+
+<body onload="doTest()">
+ <iframe sandbox='allow-scripts' id='if_6' height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_e_if10.html b/dom/html/test/file_iframe_sandbox_e_if10.html
new file mode 100644
index 000000000..2484b8f34
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_e_if10.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+ function doTest() {
+ var testContext = "?Test 23: Nested navigate _top with window.open():%20";
+ document.getElementById("if_9").src = "file_iframe_sandbox_e_if9.html" + testContext;
+ }
+</script>
+
+<body onload="doTest()">
+ <iframe sandbox='allow-scripts allow-top-navigation' id='if_9' height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_e_if11.html b/dom/html/test/file_iframe_sandbox_e_if11.html
new file mode 100644
index 000000000..42c28021a
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_e_if11.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script>
+ function doTest() {
+ var testContext = location.search.substring(1);
+ try {
+ var topsOpener = window.top.opener;
+ window.open("file_iframe_sandbox_top_navigation_pass.html?" + testContext, "_top");
+ topsOpener.postMessage({ok: true, desc: unescape(testContext) + "top navigation should be allowed by a document sandboxed with 'allow-top-navigation.'"}, "*");
+ } catch(error) {
+ window.top.opener.postMessage({ok: false, desc: unescape(testContext) + "error thrown during window.open(..., \"_top\")"}, "*");
+ window.top.close();
+ }
+ }
+</script>
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts and allow-top-navigation'
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_e_if12.html b/dom/html/test/file_iframe_sandbox_e_if12.html
new file mode 100644
index 000000000..0b1b87e09
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_e_if12.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+ function doTest() {
+ var testContext = location.search == "" ? "?Test 24: Navigate _top with window.open():%20" : location.search;
+ document.getElementById("if_14").src = "file_iframe_sandbox_e_if14.html" + testContext;
+ }
+</script>
+
+<body onload="doTest()">
+ <iframe sandbox='allow-scripts' id='if_14' height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_e_if13.html b/dom/html/test/file_iframe_sandbox_e_if13.html
new file mode 100644
index 000000000..f5cf912f6
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_e_if13.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+ function doTest() {
+ var testContext = "?Test 25: Nested navigate _top with window.open():%20";
+ document.getElementById("if_12").src = "file_iframe_sandbox_e_if12.html" + testContext;
+ }
+</script>
+
+<body onload="doTest()">
+ <iframe sandbox='allow-scripts allow-top-navigation' id='if_12' height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_e_if14.html b/dom/html/test/file_iframe_sandbox_e_if14.html
new file mode 100644
index 000000000..76d978702
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_e_if14.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script>
+ function doTest() {
+ var testContext = location.search.substring(1);
+ try {
+ var topsOpener = window.top.opener;
+ window.open("file_iframe_sandbox_top_navigation_fail.html?" + testContext, "_top");
+ topsOpener.postMessage({ok: false, desc: unescape(testContext) + "top navigation should NOT be allowed by a document sandboxed without 'allow-top-navigation.'"}, "*");
+ } catch(error) {
+ window.top.opener.postMessage({ok: true, desc: unescape(testContext) + "as expected error thrown during window.open(..., \"_top\")"}, "*");
+ window.top.close();
+ }
+ }
+</script>
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts'
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_e_if15.html b/dom/html/test/file_iframe_sandbox_e_if15.html
new file mode 100644
index 000000000..bf4138e1d
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_e_if15.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+ // Set our name, to allow an attempt to navigate us by name.
+ window.name = "e_if15";
+</script>
+
+<body>
+ <iframe sandbox='allow-scripts' id='if_16' src="file_iframe_sandbox_e_if16.html" height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_e_if16.html b/dom/html/test/file_iframe_sandbox_e_if16.html
new file mode 100644
index 000000000..06c8bf871
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_e_if16.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+ var testContext = "Test 26: navigate top by name with window.open(): ";
+
+ function doTest() {
+ try {
+ var topsOpener = window.top.opener;
+ window.open("file_iframe_sandbox_top_navigation_fail.html?" + escape(testContext), "e_if15");
+ topsOpener.postMessage({ok: false, desc: unescape(testContext) + "top navigation should NOT be allowed by a document sandboxed without 'allow-top-navigation.'"}, "*");
+ } catch(error) {
+ window.top.opener.postMessage({ok: true, desc: testContext + "as expected, error thrown during window.open(..., \"e_if15\")"}, "*");
+ window.top.close();
+ }
+ }
+</script>
+
+<body onload="doTest()">
+ I am sandboxed but with "allow-scripts"
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_e_if2.html b/dom/html/test/file_iframe_sandbox_e_if2.html
new file mode 100644
index 000000000..47251d6dc
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_e_if2.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+ <iframe sandbox='allow-scripts allow-top-navigation allow-same-origin' id='if_1' src="file_iframe_sandbox_e_if1.html?Test 11: Nested navigate _top:%20" height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_e_if3.html b/dom/html/test/file_iframe_sandbox_e_if3.html
new file mode 100644
index 000000000..ce010e689
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_e_if3.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <iframe sandbox='allow-scripts allow-top-navigation' id='if_5' src="file_iframe_sandbox_e_if5.html" height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_e_if4.html b/dom/html/test/file_iframe_sandbox_e_if4.html
new file mode 100644
index 000000000..740a33a94
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_e_if4.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <iframe sandbox='allow-scripts allow-top-navigation' id='if_3' src="file_iframe_sandbox_e_if3.html" height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_e_if5.html b/dom/html/test/file_iframe_sandbox_e_if5.html
new file mode 100644
index 000000000..46bfff357
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_e_if5.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="application/javascript">
+function doTest() {
+ sendMouseEvent({type:'click'}, 'anchor');
+}
+</script>
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts and allow-top-navigation'
+
+ <a href="file_iframe_sandbox_top_navigation_pass.html" target='_top' id='anchor'>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_e_if6.html b/dom/html/test/file_iframe_sandbox_e_if6.html
new file mode 100644
index 000000000..b1266416c
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_e_if6.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="application/javascript">
+function doTest() {
+ document.getElementById('anchor').href = "file_iframe_sandbox_top_navigation_fail.html" + location.search;
+ window.top.opener.postMessage({type: "attempted"}, "*");
+ sendMouseEvent({type:'click'}, 'anchor');
+}
+</script>
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts'
+
+ <a target='_top' id='anchor'>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_e_if7.html b/dom/html/test/file_iframe_sandbox_e_if7.html
new file mode 100644
index 000000000..9d60ed2db
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_e_if7.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+ // Set our name, to allow an attempt to navigate us by name.
+ window.name = "e_if7";
+</script>
+
+<body>
+ <iframe sandbox='allow-scripts' id='if_8' src="file_iframe_sandbox_e_if8.html" height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_e_if8.html b/dom/html/test/file_iframe_sandbox_e_if8.html
new file mode 100644
index 000000000..324ccf90c
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_e_if8.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<script>
+ function doTest() {
+ // Try to navigate top using its name (e_if7). We should not be able to do this as allow-top-navigation is not specified.
+ window.top.opener.postMessage({type: "attempted"}, "*");
+ sendMouseEvent({type:'click'}, 'navigate_top');
+ }
+</script>
+
+<body onload="doTest()">
+ I am sandboxed but with "allow-scripts"
+
+ <a href="file_iframe_sandbox_top_navigation_fail.html?Test 15: Navigate top by name:%20" target="e_if7" id="navigate_top">navigate top</a>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_e_if9.html b/dom/html/test/file_iframe_sandbox_e_if9.html
new file mode 100644
index 000000000..f18a16dba
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_e_if9.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+ function doTest() {
+ var testContext = location.search == "" ? "?Test 22: Navigate _top with window.open():%20" : location.search;
+ document.getElementById("if_11").src = "file_iframe_sandbox_e_if11.html" + testContext;
+ }
+</script>
+
+<body onload="doTest()">
+ <iframe sandbox='allow-scripts allow-top-navigation' id='if_11' height="10" width="10"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_f_if1.html b/dom/html/test/file_iframe_sandbox_f_if1.html
new file mode 100644
index 000000000..294bf0797
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_f_if1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+I have 2 plugin instances embedded in me.
+<embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
+<object type="application/x-test" width="200" height="200"></object>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_f_if2.html b/dom/html/test/file_iframe_sandbox_f_if2.html
new file mode 100644
index 000000000..7b3010d53
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_f_if2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ I am a document that should be handled by a plugin load.
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_f_if2.html^headers^ b/dom/html/test/file_iframe_sandbox_f_if2.html^headers^
new file mode 100644
index 000000000..9af65b111
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_f_if2.html^headers^
@@ -0,0 +1 @@
+Content-Type: application/x-test
diff --git a/dom/html/test/file_iframe_sandbox_fail.js b/dom/html/test/file_iframe_sandbox_fail.js
new file mode 100644
index 000000000..63397c7de
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_fail.js
@@ -0,0 +1 @@
+ok(false, "documents sandboxed with allow-scripts should NOT be able to run <script src=...>"); \ No newline at end of file
diff --git a/dom/html/test/file_iframe_sandbox_form_fail.html b/dom/html/test/file_iframe_sandbox_form_fail.html
new file mode 100644
index 000000000..6254f87c7
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_form_fail.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</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="doStuff()">
+ I should NOT be loaded by a form submit from a sandbox without 'allow-forms'
+</body>
+</html>
+
+<script>
+ function doStuff() {
+ window.parent.postMessage({ok: false, desc: "documents sandboxed without allow-forms should NOT be able to submit forms"}, "*");
+ }
+</script> \ No newline at end of file
diff --git a/dom/html/test/file_iframe_sandbox_form_pass.html b/dom/html/test/file_iframe_sandbox_form_pass.html
new file mode 100644
index 000000000..1ba8853fa
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_form_pass.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+</head>
+
+<body onLoad="doStuff()">
+ I should be loaded by a form submit from a sandbox with 'allow-forms'
+</body>
+</html>
+
+<script>
+ function doStuff() {
+ window.parent.postMessage({ok: true, desc: "documents sandboxed with allow-forms should be able to submit forms"}, "*");
+ }
+</script> \ No newline at end of file
diff --git a/dom/html/test/file_iframe_sandbox_g_if1.html b/dom/html/test/file_iframe_sandbox_g_if1.html
new file mode 100644
index 000000000..9a985faf9
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_g_if1.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.postMessage({ok: result, desc: desc}, "*");
+ }
+
+ function doStuff() {
+ // test data URI
+
+ // self.onmessage = function(event) {
+ // self.postMessage('make it so');
+ // };
+ var data_url = "data:text/plain;charset=utf-8;base64,c2VsZi5vbm1lc3NhZ2UgPSBmdW5jdGlvbihldmVudCkgeyAgDQogICAgc2VsZi5wb3N0TWVzc2FnZSgnbWFrZSBpdCBzbycpOyAgDQp9Ow==";
+ var worker_data = new Worker(data_url);
+ worker_data.addEventListener('message', function(event) {
+ ok(true, "a worker in a sandboxed document should be able to be loaded from a data: URI");
+ }, false);
+
+ worker_data.postMessage("engage!");
+
+ // test a blob URI we created (will have the same null principal
+ // as us
+ var b = new Blob(["onmessage = function(event) { self.postMessage('make it so');};"]);
+
+ var blobURL = URL.createObjectURL(b);
+
+ var worker_blob = new Worker(blobURL);
+
+ worker_blob.addEventListener('message', function(event) {
+ ok(true, "a worker in a sandboxed document should be able to be loaded from a blob URI " +
+ "created by that sandboxed document");
+ }, false);
+
+ worker_blob.postMessage("engage!");
+
+ // test loading with relative url - this should fail since we are
+ // sandboxed and have a null principal
+ var worker_js = new Worker('file_iframe_sandbox_worker.js');
+ worker_js.onerror = function(error) {
+ ok(true, "a worker in a sandboxed document should tell the load error via error event");
+ }
+
+ worker_js.addEventListener('message', function(event) {
+ ok(false, "a worker in a sandboxed document should not be able to load from a relative URI");
+ }, false);
+
+ worker_js.postMessage('engage');
+ }
+</script>
+<body onload='doStuff();'>
+ I am sandboxed but with "allow-scripts"
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_h_if1.html b/dom/html/test/file_iframe_sandbox_h_if1.html
new file mode 100644
index 000000000..3cb6781f1
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_h_if1.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 766282</title>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+</head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.ok_wrapper(result, desc);
+ }
+
+ function doStuff() {
+ // Try to open a new window via target="_blank", target="BC766282" and window.open().
+ // The window we try to open closes itself once it opens.
+ sendMouseEvent({type:'click'}, 'target_blank');
+ sendMouseEvent({type:'click'}, 'target_BC766282');
+
+ try {
+ window.open("file_iframe_sandbox_open_window_pass.html");
+ } catch(e) {
+ ok(false, "Test 3: iframes sandboxed with allow-popups, should be able to open windows");
+ }
+ }
+</script>
+<body onLoad="doStuff()">
+ I am sandboxed but with "allow-popups allow-scripts allow-same-origin"
+
+ <a href="file_iframe_sandbox_open_window_pass.html" target="_blank" id="target_blank">open window</a>
+ <a href="file_iframe_sandbox_open_window_pass.html?BC766282" target="BC766282" id="target_BC766282">open window</a>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_j_if1.html b/dom/html/test/file_iframe_sandbox_j_if1.html
new file mode 100644
index 000000000..6d4347dfc
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_j_if1.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="text/javascript">
+ function doStuff() {
+ // Open a new window via showModalDialog().
+ try {
+ window.showModalDialog("file_iframe_sandbox_k_if5.html");
+ } catch(e) {
+ window.parent.ok_wrapper(false, "iframes sandboxed with allow-popups and allow-modals should be able to open a modal dialog");
+ }
+
+ // Open a new window via showModalDialog().
+ try {
+ window.showModalDialog("file_iframe_sandbox_k_if7.html");
+ } catch(e) {
+ window.parent.ok_wrapper(false, "iframes sandboxed with allow-popups and allow-modals should be able to open a modal dialog");
+ }
+ }
+</script>
+
+<body onLoad="doStuff()">
+ I am sandboxed with "allow-scripts allow-popups allow-same-origin allow-forms allow-top-navigation".
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_j_if2.html b/dom/html/test/file_iframe_sandbox_j_if2.html
new file mode 100644
index 000000000..9552307ee
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_j_if2.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="text/javascript">
+ function doSubOpens() {
+ // Open a new window showModalDialog().
+ try {
+ window.showModalDialog("file_iframe_sandbox_k_if9.html");
+ } catch(e) {
+ window.parent.ok_wrapper(false, "iframes sandboxed with allow-popups and allow-modals should be able to open a modal dialog");
+ }
+ }
+
+ window.doSubOpens = doSubOpens;
+</script>
+
+<body>
+ I am sandboxed but with "allow-scripts allow-popups allow-same-origin".
+ After my initial load, "allow-same-origin" is removed and then I open file_iframe_sandbox_k_if9.html,
+ which attemps to call a function in my parent.
+ This should succeed since the new sandbox flags shouldn't have taken affect on me until I'm reloaded.
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_j_if3.html b/dom/html/test/file_iframe_sandbox_j_if3.html
new file mode 100644
index 000000000..07c5b66c1
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_j_if3.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 766282</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+</head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.ok_wrapper(result, desc);
+ }
+
+ function doStuff() {
+ // Try to open a new window via showModalDialog().
+ // The window we try to open closes itself once it opens.
+ try {
+ window.showModalDialog("file_iframe_sandbox_open_window_pass.html");
+ } catch(e) {
+ ok(false, "iframes sandboxed with allow-popups and allow-modals should be able to open a modal dialog");
+ }
+ }
+</script>
+<body onLoad="doStuff()">
+ I am sandboxed but with "allow-popups allow-scripts allow-same-origin"
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_k_if1.html b/dom/html/test/file_iframe_sandbox_k_if1.html
new file mode 100644
index 000000000..cb0719556
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_k_if1.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script type="text/javascript">
+ var windowsToClose = new Array();
+
+ function closeWindows() {
+ for (var i = 0; i < windowsToClose.length; i++) {
+ windowsToClose[i].close();
+ }
+ window.open("file_iframe_sandbox_close.html", "blank_if2");
+ window.open("file_iframe_sandbox_close.html", "BC766282_if2");
+ }
+
+ // Add message listener to forward messages on to parent
+ window.addEventListener("message", receiveMessage, false);
+
+ function receiveMessage(event) {
+ switch (event.data.type) {
+ case "closeWindows":
+ closeWindows();
+ break;
+ }
+ }
+
+ function doStuff() {
+ // Open a new window via target="_blank", target="BC766282_if2" and window.open().
+ sendMouseEvent({type:'click'}, 'target_blank_if2');
+ sendMouseEvent({type:'click'}, 'target_BC766282_if2');
+
+ windowsToClose.push(window.open("file_iframe_sandbox_k_if2.html"));
+ }
+</script>
+<body onLoad="doStuff()">
+ I am navigated to from file_iframe_sandbox_k_if8.html.
+ This was opened in an iframe with "allow-scripts allow-popups allow-same-origin".
+ However allow-same-origin was removed from the iframe before navigating to me,
+ so I should only have "allow-scripts allow-popups" in force.
+ <a href="file_iframe_sandbox_k_if2.html" target="_blank" id="target_blank_if2">open window</a>
+ <a href="file_iframe_sandbox_k_if2.html" target="BC766282_if2" id="target_BC766282_if2">open window</a>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_k_if2.html b/dom/html/test/file_iframe_sandbox_k_if2.html
new file mode 100644
index 000000000..dce42aef5
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_k_if2.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="text/javascript">
+ if (window.name == "") {
+ window.name = "blank_if2";
+ }
+
+ function ok(result, message) {
+ window.opener.parent.postMessage({type: "ok", ok: result, desc: message}, "*");
+ }
+
+ function doStuff() {
+ // Check that sandboxed forms browsing context flag copied by attempting to submit a form.
+ document.getElementById('a_form').submit();
+ window.opener.parent.postMessage({type: "attempted"}, "*");
+
+ // Check that sandboxed origin browsing context flag copied by attempting to access cookies.
+ try {
+ var foo = document.cookie;
+ ok(false, "Sandboxed origin browsing context flag NOT copied to new auxiliary browsing context.");
+ } catch(error) {
+ ok(true, "Sandboxed origin browsing context flag copied to new auxiliary browsing context.");
+ }
+
+ // Check that sandboxed top-level navigation browsing context flag copied.
+ // if_3 tries to navigate this document.
+ var if_3 = document.getElementById('if_3');
+ if_3.src = "file_iframe_sandbox_k_if3.html";
+ }
+</script>
+
+<body onLoad="doStuff()">
+ I am not sandboxed directly, but opened from a sandboxed document with 'allow-scripts allow-popups'
+
+ <form method="get" action="file_iframe_sandbox_window_form_fail.html" id="a_form">
+ First name: <input type="text" name="firstname">
+ Last name: <input type="text" name="lastname">
+ <input type="submit" id="a_button">
+ </form>
+
+ <iframe id="if_3" src="about:blank" height="10" width="10"></iframe>
+
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_k_if3.html b/dom/html/test/file_iframe_sandbox_k_if3.html
new file mode 100644
index 000000000..b6a005aa5
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_k_if3.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="application/javascript">
+ function doTest() {
+ sendMouseEvent({type:'click'}, 'anchor');
+ window.parent.opener.parent.postMessage({type: "attempted"}, "*");
+ }
+</script>
+<body onload="doTest()">
+ I am sandboxed with 'allow-scripts allow-popups'
+
+ <a href="file_iframe_sandbox_window_top_navigation_fail.html" target='_top' id='anchor'>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_k_if4.html b/dom/html/test/file_iframe_sandbox_k_if4.html
new file mode 100644
index 000000000..b9b4427ed
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_k_if4.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="text/javascript">
+ function doStuff() {
+ // Open a new window via target="_blank", target="BC766282_if5" and window.open().
+ sendMouseEvent({type:'click'}, 'target_blank_if5');
+ sendMouseEvent({type:'click'}, 'target_BC766282_if5');
+
+ window.open("file_iframe_sandbox_k_if5.html");
+
+ // Open a new window via target="_blank", target="BC766282_if7" and window.open().
+ sendMouseEvent({type:'click'}, 'target_blank_if7');
+ sendMouseEvent({type:'click'}, 'target_BC766282_if7');
+
+ window.open("file_iframe_sandbox_k_if7.html");
+ }
+</script>
+
+<body onLoad="doStuff()">
+ I am sandboxed with "allow-scripts allow-popups allow-same-origin allow-forms allow-top-navigation".
+ <a href="file_iframe_sandbox_k_if5.html" target="_blank" id="target_blank_if5">open window</a>
+ <a href="file_iframe_sandbox_k_if5.html" target="BC766282_if5" id="target_BC766282_if5">open window</a>
+
+ <a href="file_iframe_sandbox_k_if7.html" target="_blank" id="target_blank_if7">open window</a>
+ <a href="file_iframe_sandbox_k_if7.html" target="BC766282_if7" id="target_BC766282_if7">open window</a>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_k_if5.html b/dom/html/test/file_iframe_sandbox_k_if5.html
new file mode 100644
index 000000000..8deb65852
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_k_if5.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="text/javascript">
+ function doStuff() {
+ // Check that sandboxed origin browsing context flag NOT set by attempting to access cookies.
+ try {
+ var foo = document.cookie;
+ window.opener.parent.ok_wrapper(true, "Sandboxed origin browsing context flag NOT set on new auxiliary browsing context.");
+ } catch(error) {
+ window.opener.parent.ok_wrapper(false, "Sandboxed origin browsing context flag set on new auxiliary browsing context.");
+ }
+
+ // Check that sandboxed top-level navigation browsing context flag NOT set.
+ // if_6 tries to navigate this document.
+ var if_6 = document.getElementById('if_6');
+ if_6.src = "file_iframe_sandbox_k_if6.html";
+ }
+</script>
+
+<body onLoad="doStuff()">
+ I am not sandboxed directly, but opened from a sandboxed document with at least
+ 'allow-scripts allow-popups allow-same-origin allow-top-navigation'
+
+ <iframe id="if_6" src="about:blank" height="10" width="10"></iframe>
+
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_k_if6.html b/dom/html/test/file_iframe_sandbox_k_if6.html
new file mode 100644
index 000000000..22dec5c1c
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_k_if6.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<script type="application/javascript">
+ function doTest() {
+ sendMouseEvent({type:'click'}, 'anchor');
+ }
+</script>
+
+<body onload="doTest()">
+ I am sandboxed with at least 'allow-scripts allow-popups allow-top-navigation'
+
+ <a href="file_iframe_sandbox_window_top_navigation_pass.html" target='_top' id='anchor'>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_k_if7.html b/dom/html/test/file_iframe_sandbox_k_if7.html
new file mode 100644
index 000000000..269e31eb5
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_k_if7.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="text/javascript">
+ function doStuff() {
+ // Check that sandboxed forms browsing context flag NOT set by attempting to submit a form.
+ document.getElementById('a_form').submit();
+ }
+</script>
+
+<body onLoad="doStuff()">
+ I am not sandboxed directly, but opened from a sandboxed document with at least
+ 'allow-scripts allow-popups allow-forms allow-same-origin'
+
+ <form method="get" action="file_iframe_sandbox_window_form_pass.html" id="a_form">
+ First name: <input type="text" name="firstname">
+ Last name: <input type="text" name="lastname">
+ <input type="submit" id="a_button">
+ </form>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_k_if8.html b/dom/html/test/file_iframe_sandbox_k_if8.html
new file mode 100644
index 000000000..6f425c428
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_k_if8.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="text/javascript">
+ function doSubOpens() {
+ // Open a new window via target="_blank", target="BC766282_if9" and window.open().
+ sendMouseEvent({type:'click'}, 'target_blank_if9');
+ sendMouseEvent({type:'click'}, 'target_BC766282_if9');
+
+ window.open("file_iframe_sandbox_k_if9.html");
+
+ sendMouseEvent({type:'click'}, 'target_if1');
+ }
+
+ window.doSubOpens = doSubOpens;
+</script>
+
+<body>
+ I am sandboxed but with "allow-scripts allow-popups allow-same-origin".
+ After my initial load, "allow-same-origin" is removed and then I open file_iframe_sandbox_k_if9.html
+ in 3 different ways, which attemps to call a function in my parent.
+ This should succeed since the new sandbox flags shouldn't have taken affect on me until I'm reloaded.
+ <a href="file_iframe_sandbox_k_if9.html" target="_blank" id="target_blank_if9">open window</a>
+ <a href="file_iframe_sandbox_k_if9.html" target="BC766282_if9" id="target_BC766282_if9">open window</a>
+
+ Now navigate to file_iframe_sandbox_k_if1.html to do tests for a sandbox opening a window
+ when only "allow-scripts allow-popups" are specified.
+ <a href="file_iframe_sandbox_k_if1.html" id="target_if1">navigate to if1</a>
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_k_if9.html b/dom/html/test/file_iframe_sandbox_k_if9.html
new file mode 100644
index 000000000..56e8db3f9
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_k_if9.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+ function doStuff() {
+ window.opener.parent.ok_wrapper(true, "A window opened from within a sandboxed document should inherit the flags of the document, not of the docshell/sandbox attribute.");
+ self.close();
+ }
+</script>
+
+<body onload='doStuff()'>
+ I'm a window opened from the sandboxed document of file_iframe_sandbox_k_if8.html.
+ I should be able to call ok_wrapper in main test page directly because I should be same-origin with it.
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_navigation_fail.html b/dom/html/test/file_iframe_sandbox_navigation_fail.html
new file mode 100644
index 000000000..bae5276bd
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_navigation_fail.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onLoad="doStuff()">
+FAIL
+</body>
+<script>
+ function doStuff() {
+ var testContext = unescape(location.search.substring(1));
+ window.parent.postMessage({ok: false, desc: testContext + "this navigation should NOT be allowed by a sandboxed document", addToAttempted: false}, "*");
+ }
+</script>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_navigation_pass.html b/dom/html/test/file_iframe_sandbox_navigation_pass.html
new file mode 100644
index 000000000..e07248247
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_navigation_pass.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script>
+function doStuff() {
+ var testContext = unescape(location.search.substring(1));
+ window.parent.postMessage({ok: true, desc: testContext + "this navigation should be allowed by a sandboxed document"}, "*");
+}
+</script>
+<body onLoad="doStuff()">
+PASS
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_navigation_start.html b/dom/html/test/file_iframe_sandbox_navigation_start.html
new file mode 100644
index 000000000..fa5642517
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_navigation_start.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+I am just a normal HTML document, probably contained in a sandboxed iframe
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_open_window_fail.html b/dom/html/test/file_iframe_sandbox_open_window_fail.html
new file mode 100644
index 000000000..64e0d3618
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_open_window_fail.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body onLoad="doStuff()">
+ I should NOT be opened by a sandboxed iframe via any method
+</body>
+</html>
+
+<script>
+ function doStuff() {
+ window.opener.ok(false, "sandboxed documents should NOT be able to open windows");
+ self.close();
+ }
+</script>
diff --git a/dom/html/test/file_iframe_sandbox_open_window_pass.html b/dom/html/test/file_iframe_sandbox_open_window_pass.html
new file mode 100644
index 000000000..ac45c7fd3
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_open_window_pass.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body onLoad="doStuff()">
+ I should be opened by a sandboxed iframe via any method when "allow-popups" is specified.
+</body>
+</html>
+
+<script>
+ function doStuff() {
+ // Check that the browsing context's (window's) name is as expected.
+ var expectedName = location.search.substring(1);
+ if (expectedName == window.name) {
+ window.opener.ok(true, "sandboxed documents should be able to open windows when \"allow-popups\" is specified");
+ } else {
+ window.opener.ok(false, "window opened with \"allow-popups\", but expected name was " + expectedName + " and actual was " + window.name);
+ }
+ self.close();
+ }
+</script>
diff --git a/dom/html/test/file_iframe_sandbox_pass.js b/dom/html/test/file_iframe_sandbox_pass.js
new file mode 100644
index 000000000..bd633056f
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_pass.js
@@ -0,0 +1 @@
+ok(true, "documents sandboxed with allow-scripts should be able to run <script src=...>"); \ No newline at end of file
diff --git a/dom/html/test/file_iframe_sandbox_redirect.html b/dom/html/test/file_iframe_sandbox_redirect.html
new file mode 100644
index 000000000..62419d7f4
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_redirect.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<body>redirect</body>
diff --git a/dom/html/test/file_iframe_sandbox_redirect.html^headers^ b/dom/html/test/file_iframe_sandbox_redirect.html^headers^
new file mode 100644
index 000000000..71b739c42
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_redirect.html^headers^
@@ -0,0 +1,2 @@
+HTTP 301 Moved Permanently
+Location: file_iframe_sandbox_redirect_target.html
diff --git a/dom/html/test/file_iframe_sandbox_redirect_target.html b/dom/html/test/file_iframe_sandbox_redirect_target.html
new file mode 100644
index 000000000..c134ac0ff
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_redirect_target.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<head>
+ <script>
+ onmessage = function(event) {
+ parent.postMessage(event.data + " redirect target", "*");
+ }
+ </script>
+</head>
+<body>I have been redirected</body>
diff --git a/dom/html/test/file_iframe_sandbox_refresh.html b/dom/html/test/file_iframe_sandbox_refresh.html
new file mode 100644
index 000000000..1fad80c42
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_refresh.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<body>refresh</body>
diff --git a/dom/html/test/file_iframe_sandbox_refresh.html^headers^ b/dom/html/test/file_iframe_sandbox_refresh.html^headers^
new file mode 100644
index 000000000..a7cc383b4
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_refresh.html^headers^
@@ -0,0 +1 @@
+Refresh: 0 url=data:text/html,Refreshed
diff --git a/dom/html/test/file_iframe_sandbox_top_navigation_fail.html b/dom/html/test/file_iframe_sandbox_top_navigation_fail.html
new file mode 100644
index 000000000..dad6b2c00
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_top_navigation_fail.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script>
+function doStuff() {
+ var testContext = unescape(location.search.substring(1));
+ window.opener.postMessage({ok: false, desc: testContext + "top navigation should NOT be allowed by a document sandboxed without 'allow-top-navigation'", addToAttempted: false}, "*");
+ window.close();
+}
+</script>
+<body onLoad="doStuff()">
+FAIL\
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_top_navigation_pass.html b/dom/html/test/file_iframe_sandbox_top_navigation_pass.html
new file mode 100644
index 000000000..fe016ab23
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_top_navigation_pass.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script>
+function doStuff() {
+ var testContext = unescape(location.search.substring(1));
+ window.opener.postMessage({ok: true, desc: testContext + "top navigation should be allowed by a document sandboxed with 'allow-top-navigation'"}, "*");
+ window.close();
+}
+</script>
+<body onLoad="doStuff()">
+PASS
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_window_form_fail.html b/dom/html/test/file_iframe_sandbox_window_form_fail.html
new file mode 100644
index 000000000..2d678b3ac
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_window_form_fail.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<body onLoad="doStuff()">
+ I should NOT be loaded by a form submit from a window opened from a sandbox without 'allow-forms'.
+</body>
+</html>
+
+<script>
+ function doStuff() {
+ window.opener.parent.postMessage({ok: false, desc: "documents sandboxed without allow-forms should NOT be able to submit forms"}, "*");
+
+ self.close();
+ }
+</script>
diff --git a/dom/html/test/file_iframe_sandbox_window_form_pass.html b/dom/html/test/file_iframe_sandbox_window_form_pass.html
new file mode 100644
index 000000000..dd2656c1e
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_window_form_pass.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+ function doStuff() {
+ window.opener.parent.ok_wrapper(true, "Sandboxed forms browsing context flag NOT set on new auxiliary browsing context.");
+
+ self.close();
+ }
+</script>
+
+<body onLoad="doStuff()">
+ I should be loaded by a form submit from a window opened from a sandbox with 'allow-forms allow-same-origin'.
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_window_navigation_fail.html b/dom/html/test/file_iframe_sandbox_window_navigation_fail.html
new file mode 100644
index 000000000..f8e3c83ce
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_window_navigation_fail.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 838692</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+function doStuff() {
+ var testContext = unescape(location.search.substring(1));
+ window.opener.postMessage({ok: false, desc: testContext + "a sandboxed document should not be able to navigate a window it hasn't opened.", addToAttempted: false}, "*");
+ window.close();
+}
+</script>
+
+<body onLoad="doStuff()">
+FAIL
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_window_navigation_pass.html b/dom/html/test/file_iframe_sandbox_window_navigation_pass.html
new file mode 100644
index 000000000..a1bff9eb8
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_window_navigation_pass.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+function doStuff() {
+ var testContext = unescape(location.search.substring(1));
+ window.opener.postMessage({type: "ok", ok: true, desc: testContext + "a permitted sandboxed document should be able to navigate a window it has opened.", addToAttempted: false}, "*");
+ window.close();
+}
+</script>
+
+<body onLoad="doStuff()">
+PASS
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_window_top_navigation_fail.html b/dom/html/test/file_iframe_sandbox_window_top_navigation_fail.html
new file mode 100644
index 000000000..af5047604
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_window_top_navigation_fail.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script>
+ function doStuff() {
+ window.opener.parent.postMessage({ok: false, desc: "Sandboxed top-level navigation browsing context flag NOT copied to new auxiliary browsing context."}, "*");
+
+ // Check that when no browsing context returned by "target='_top'", a new browsing context isn't opened by mistake.
+ try {
+ window.opener.parent.opener.parent.postMessage({ok: false, desc: "An attempt at top navigation without 'allow-top-navigation' should not have opened a new browsing context."}, "*");
+ } catch (error) {
+ }
+
+ self.close();
+ }
+</script>
+<body onLoad="doStuff()">
+FAIL
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_window_top_navigation_pass.html b/dom/html/test/file_iframe_sandbox_window_top_navigation_pass.html
new file mode 100644
index 000000000..d3637fb04
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_window_top_navigation_pass.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 766282</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+ function doStuff() {
+ window.opener.parent.ok_wrapper(true, "Sandboxed top-level navigation browsing context flag NOT copied to new auxiliary browsing context.");
+
+ self.close();
+ }
+</script>
+
+<body onLoad="doStuff()">
+ I am navigated to from a window opened from a sandbox with allow-top-navigation.
+</body>
+</html>
diff --git a/dom/html/test/file_iframe_sandbox_worker.js b/dom/html/test/file_iframe_sandbox_worker.js
new file mode 100644
index 000000000..edb63eb6c
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_worker.js
@@ -0,0 +1,3 @@
+self.onmessage = function(event) {
+ self.postMessage('make it so');
+}; \ No newline at end of file
diff --git a/dom/html/test/file_ignoreuserfocus.html b/dom/html/test/file_ignoreuserfocus.html
new file mode 100644
index 000000000..b9e330212
--- /dev/null
+++ b/dom/html/test/file_ignoreuserfocus.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+ <body>
+ <map name=a>
+ <area shape=rect coords=0,0,100,100 href=#fail>
+ </map>
+ <img usemap=#a src=image.png>
+ <input><iframe></iframe>
+ </body>
+</html>
diff --git a/dom/html/test/file_imports_basics.html b/dom/html/test/file_imports_basics.html
new file mode 100644
index 000000000..0abf4e848
--- /dev/null
+++ b/dom/html/test/file_imports_basics.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ </head>
+ <body>
+ <div id="foo">bar</div>
+ <script>
+ counter++;
+ var importDone = true;
+ is(document.currentScript.ownerDocument.getElementById("foo").textContent, "bar",
+ "currentScript.ownerDocument works in imported document,");
+ try{
+ document.currentScript.ownerDocument.open();
+ document.currentScript.ownerDocument.write("<h1>This should not show up!</h1>");
+ document.currentScript.ownerDocument.close();
+ ok(false, "document.write should have thrown (import)")
+ } catch (e) {
+ ok(true, "document.write has thrown (import)")
+ }
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/file_imports_redirect.html b/dom/html/test/file_imports_redirect.html
new file mode 100644
index 000000000..eaca3f49f
--- /dev/null
+++ b/dom/html/test/file_imports_redirect.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<body>
+</body>
+</html>
diff --git a/dom/html/test/file_imports_redirect.html^headers^ b/dom/html/test/file_imports_redirect.html^headers^
new file mode 100644
index 000000000..39585a340
--- /dev/null
+++ b/dom/html/test/file_imports_redirect.html^headers^
@@ -0,0 +1,2 @@
+HTTP 301 Moved Permanently
+Location: http://mochi.test:8888/tests/dom/html/test/file_imports_redirected.html
diff --git a/dom/html/test/file_imports_redirected.html b/dom/html/test/file_imports_redirected.html
new file mode 100644
index 000000000..031ca313d
--- /dev/null
+++ b/dom/html/test/file_imports_redirected.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script> var redirected = true; </script>
+</body>
+</html>
diff --git a/dom/html/test/file_mozaudiochannel.html b/dom/html/test/file_mozaudiochannel.html
new file mode 100644
index 000000000..588ae338b
--- /dev/null
+++ b/dom/html/test/file_mozaudiochannel.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div id="content" style="display: none">
+ <audio id="audio1" />
+ <audio id="audio2" mozaudiochannel="foo" />
+</div>
+
+<script type="application/javascript">
+
+function is(a, b, msg) {
+ parent.postMessage({ status: a === b, msg: msg }, '*');
+}
+
+function ok(a, msg) {
+ parent.postMessage({ status: !!a, msg: msg }, '*');
+}
+
+function finish() {
+ parent.postMessage({ finish: true }, '*');
+}
+
+function test_basic() {
+ var audio1 = document.getElementById("audio1");
+ ok(audio1, "Audio Element exists");
+ is(audio1.mozAudioChannelType, "normal", "Default audio1 channel == 'normal'");
+ try {
+ audio1.mozAudioChannelType = "foo";
+ } catch(e) {}
+ is(audio1.mozAudioChannelType, "normal", "Default audio1 channel == 'normal'");
+
+ var audio2 = document.getElementById("audio2");
+ ok(audio2, "Audio Element exists");
+ is(audio2.mozAudioChannelType, "normal", "Default audio2 channel == 'normal'");
+ try {
+ audio2.mozAudioChannelType = "foo";
+ } catch(e) {}
+ is(audio2.mozAudioChannelType, "normal", "Default audio2 channel == 'normal'");
+
+ runTest();
+}
+
+function test_preferences(aChannel) {
+ SpecialPowers.pushPrefEnv({"set": [["media.defaultAudioChannel", aChannel ]]},
+ function() {
+ var audio = document.createElement('audio');
+ ok(audio, "Audio Element created");
+ is(audio.mozAudioChannelType, aChannel, "Default audio channel == '" + aChannel + "'");
+ runTest();
+ }
+ );
+}
+
+function test_wrong_preferences() {
+ SpecialPowers.pushPrefEnv({"set": [["media.defaultAudioChannel", 'foobar' ]]},
+ function() {
+ var audio = document.createElement('audio');
+ ok(audio, "Audio Element created");
+ is(audio.mozAudioChannelType, 'normal', "Default audio channel == 'normal'");
+ runTest();
+ }
+ );
+}
+var tests = [
+ test_basic,
+
+ function() { test_preferences("content"); },
+ function() { test_preferences("notification"); },
+ function() { test_preferences("alarm"); },
+ function() { test_preferences("telephony"); },
+ function() { test_preferences("ringer"); },
+ function() { test_preferences("publicnotification"); },
+
+ test_wrong_preferences,
+];
+
+function runTest() {
+ if (!tests.length) {
+ finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+runTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/file_srcdoc-2.html b/dom/html/test/file_srcdoc-2.html
new file mode 100644
index 000000000..bd75f5e05
--- /dev/null
+++ b/dom/html/test/file_srcdoc-2.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=802895
+-->
+<body>
+<iframe id="iframe" srcdoc="Hello World"></iframe>
+</body>
+
+</html>
diff --git a/dom/html/test/file_srcdoc.html b/dom/html/test/file_srcdoc.html
new file mode 100644
index 000000000..70fbff42f
--- /dev/null
+++ b/dom/html/test/file_srcdoc.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=802895
+-->
+<body>
+<iframe id="iframe" srcdoc="Hello World"></iframe>
+
+<iframe id="iframe1" src="about:mozilla"
+ srcdoc="Goodbye World"></iframe>
+<iframe id="iframe2" srcdoc="Peeking test" sandbox=""></iframe>
+<iframe id="iframe3" src="data:text/html;charset=US-ASCII,Gone"
+ srcdoc="Going"></iframe>
+</body>
+
+</html>
diff --git a/dom/html/test/file_window_open_close_inner.html b/dom/html/test/file_window_open_close_inner.html
new file mode 100644
index 000000000..dbc7e3aba
--- /dev/null
+++ b/dom/html/test/file_window_open_close_inner.html
@@ -0,0 +1,7 @@
+<html>
+<body>
+<script>
+window.close();
+</script>
+</html>
+</body>
diff --git a/dom/html/test/file_window_open_close_outer.html b/dom/html/test/file_window_open_close_outer.html
new file mode 100644
index 000000000..b1450cee4
--- /dev/null
+++ b/dom/html/test/file_window_open_close_outer.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<a id="link" href="file_window_open_close_inner.html" target="_blank" onclick="setTimeout(function () { window.close() }, 0)">link</a>
+</html>
+</body>
diff --git a/dom/html/test/formData_test.js b/dom/html/test/formData_test.js
new file mode 100644
index 000000000..50341276f
--- /dev/null
+++ b/dom/html/test/formData_test.js
@@ -0,0 +1,212 @@
+function testHas() {
+ var f = new FormData();
+ f.append("foo", "bar");
+ f.append("another", "value");
+ ok(f.has("foo"), "has() on existing name should be true.");
+ ok(f.has("another"), "has() on existing name should be true.");
+ ok(!f.has("nonexistent"), "has() on non-existent name should be false.");
+}
+
+function testGet() {
+ var f = new FormData();
+ f.append("foo", "bar");
+ f.append("foo", "bar2");
+ f.append("blob", new Blob(["hey"], { type: 'text/plain' }));
+ f.append("file", new File(["hey"], 'testname', {type: 'text/plain'}));
+
+ is(f.get("foo"), "bar", "get() on existing name should return first value");
+ ok(f.get("blob") instanceof Blob, "get() on existing name should return first value");
+ is(f.get("blob").type, 'text/plain', "get() on existing name should return first value");
+ ok(f.get("file") instanceof File, "get() on existing name should return first value");
+ is(f.get("file").name, 'testname', "get() on existing name should return first value");
+
+ is(f.get("nonexistent"), null, "get() on non-existent name should return null.");
+}
+
+function testGetAll() {
+ var f = new FormData();
+ f.append("other", "value");
+ f.append("foo", "bar");
+ f.append("foo", "bar2");
+ f.append("foo", new Blob(["hey"], { type: 'text/plain' }));
+
+ var arr = f.getAll("foo");
+ is(arr.length, 3, "getAll() should retrieve all matching entries.");
+ is(arr[0], "bar", "values should match and be in order");
+ is(arr[1], "bar2", "values should match and be in order");
+ ok(arr[2] instanceof Blob, "values should match and be in order");
+
+ is(f.get("nonexistent"), null, "get() on non-existent name should return null.");
+}
+
+function testDelete() {
+ var f = new FormData();
+ f.append("other", "value");
+ f.append("foo", "bar");
+ f.append("foo", "bar2");
+ f.append("foo", new Blob(["hey"], { type: 'text/plain' }));
+
+ ok(f.has("foo"), "has() on existing name should be true.");
+ f.delete("foo");
+ ok(!f.has("foo"), "has() on deleted name should be false.");
+ is(f.getAll("foo").length, 0, "all entries should be deleted.");
+
+ is(f.getAll("other").length, 1, "other names should still be there.");
+ f.delete("other");
+ is(f.getAll("other").length, 0, "all entries should be deleted.");
+}
+
+function testSet() {
+ var f = new FormData();
+
+ f.set("other", "value");
+ ok(f.has("other"), "set() on new name should be similar to append()");
+ is(f.getAll("other").length, 1, "set() on new name should be similar to append()");
+
+ f.append("other", "value2");
+ is(f.getAll("other").length, 2, "append() should not replace existing entries.");
+
+ f.append("foo", "bar");
+ f.append("other", "value3");
+ f.append("other", "value3");
+ f.append("other", "value3");
+ is(f.getAll("other").length, 5, "append() should not replace existing entries.");
+
+ f.set("other", "value4");
+ is(f.getAll("other").length, 1, "set() should replace existing entries.");
+ is(f.getAll("other")[0], "value4", "set() should replace existing entries.");
+}
+
+function testFilename() {
+ var f = new FormData();
+ f.append("blob", new Blob(["hi"]));
+ ok(f.get("blob") instanceof Blob, "We should have a blob back.");
+
+ // If a filename is passed, that should replace the original.
+ f.append("blob2", new Blob(["hi"]), "blob2.txt");
+ is(f.get("blob2").name, "blob2.txt", "Explicit filename should override \"blob\".");
+
+ var file = new File(["hi"], "file1.txt");
+ f.append("file1", file);
+ // If a file is passed, the "create entry" algorithm should not create a new File, but reuse the existing one.
+ is(f.get("file1"), file, "Retrieved File object should be original File object and not a copy.");
+ is(f.get("file1").name, "file1.txt", "File's filename should be original's name if no filename is explicitly passed.");
+
+ file = new File(["hi"], "file2.txt");
+ f.append("file2", file, "fakename.txt");
+ ok(f.get("file2") !== file, "Retrieved File object should be new File object if explicit filename is passed.");
+ is(f.get("file2").name, "fakename.txt", "File's filename should be explicitly passed name.");
+ f.append("file3", new File(["hi"], ""));
+ is(f.get("file3").name, "", "File's filename is returned even if empty.");
+}
+
+function testIterable() {
+ var fd = new FormData();
+ fd.set('1','2');
+ fd.set('2','4');
+ fd.set('3','6');
+ fd.set('4','8');
+ fd.set('5','10');
+
+ var key_iter = fd.keys();
+ var value_iter = fd.values();
+ var entries_iter = fd.entries();
+ for (var i = 0; i < 5; ++i) {
+ var v = i + 1;
+ var key = key_iter.next();
+ var value = value_iter.next();
+ var entry = entries_iter.next();
+ is(key.value, v.toString(), "Correct Key iterator: " + v.toString());
+ ok(!key.done, "key.done is false");
+ is(value.value, (v * 2).toString(), "Correct Value iterator: " + (v * 2).toString());
+ ok(!value.done, "value.done is false");
+ is(entry.value[0], v.toString(), "Correct Entry 0 iterator: " + v.toString());
+ is(entry.value[1], (v * 2).toString(), "Correct Entry 1 iterator: " + (v * 2).toString());
+ ok(!entry.done, "entry.done is false");
+ }
+
+ var last = key_iter.next();
+ ok(last.done, "Nothing more to read.");
+ is(last.value, undefined, "Undefined is the last key");
+
+ last = value_iter.next();
+ ok(last.done, "Nothing more to read.");
+ is(last.value, undefined, "Undefined is the last value");
+
+ last = entries_iter.next();
+ ok(last.done, "Nothing more to read.");
+
+ key_iter = fd.keys();
+ key_iter.next();
+ key_iter.next();
+ fd.delete('1');
+ fd.delete('2');
+ fd.delete('3');
+ fd.delete('4');
+ fd.delete('5');
+
+ last = key_iter.next();
+ ok(last.done, "Nothing more to read.");
+ is(last.value, undefined, "Undefined is the last key");
+}
+
+function testSend(doneCb) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", "form_submit_server.sjs");
+ xhr.onload = function () {
+ var response = xhr.response;
+
+ for (var entry of response) {
+ is(entry.body, 'hey');
+ is(entry.headers['Content-Type'], 'text/plain');
+ }
+
+ is(response[0].headers['Content-Disposition'],
+ 'form-data; name="empty"; filename="blob"');
+
+ is(response[1].headers['Content-Disposition'],
+ 'form-data; name="explicit"; filename="explicit-file-name"');
+
+ is(response[2].headers['Content-Disposition'],
+ 'form-data; name="explicit-empty"; filename=""');
+
+ is(response[3].headers['Content-Disposition'],
+ 'form-data; name="file-name"; filename="testname"');
+
+ is(response[4].headers['Content-Disposition'],
+ 'form-data; name="empty-file-name"; filename=""');
+
+ is(response[5].headers['Content-Disposition'],
+ 'form-data; name="file-name-overwrite"; filename="overwrite"');
+
+ doneCb();
+ }
+
+ var file, blob = new Blob(['hey'], {type: 'text/plain'});
+
+ var fd = new FormData();
+ fd.append("empty", blob);
+ fd.append("explicit", blob, "explicit-file-name");
+ fd.append("explicit-empty", blob, "");
+ file = new File([blob], 'testname', {type: 'text/plain'});
+ fd.append("file-name", file);
+ file = new File([blob], '', {type: 'text/plain'});
+ fd.append("empty-file-name", file);
+ file = new File([blob], 'testname', {type: 'text/plain'});
+ fd.append("file-name-overwrite", file, "overwrite");
+ xhr.responseType = 'json';
+ xhr.send(fd);
+}
+
+function runTest(doneCb) {
+ testHas();
+ testGet();
+ testGetAll();
+ testDelete();
+ testSet();
+ testFilename();
+ testIterable();
+ // Finally, send an XHR and verify the response matches.
+ testSend(doneCb);
+}
+
diff --git a/dom/html/test/formData_worker.js b/dom/html/test/formData_worker.js
new file mode 100644
index 000000000..d83823554
--- /dev/null
+++ b/dom/html/test/formData_worker.js
@@ -0,0 +1,19 @@
+function ok(a, msg) {
+ postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
+}
+
+function is(a, b, msg) {
+ postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
+}
+
+function todo(a, msg) {
+ postMessage({type: 'todo', status: !!a, msg: a + ": " + msg });
+}
+
+importScripts("formData_test.js");
+
+onmessage = function() {
+ runTest(function() {
+ postMessage({type: 'finish'});
+ });
+}
diff --git a/dom/html/test/formSubmission_chrome.js b/dom/html/test/formSubmission_chrome.js
new file mode 100644
index 000000000..540a11755
--- /dev/null
+++ b/dom/html/test/formSubmission_chrome.js
@@ -0,0 +1,6 @@
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.importGlobalProperties(["File"]);
+
+addMessageListener("files.open", function (message) {
+ sendAsyncMessage("files.opened", message.map(path => File.createFromFileName(path)));
+});
diff --git a/dom/html/test/form_submit_server.sjs b/dom/html/test/form_submit_server.sjs
new file mode 100644
index 000000000..95c019d16
--- /dev/null
+++ b/dom/html/test/form_submit_server.sjs
@@ -0,0 +1,71 @@
+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 result = [];
+ var requestBody = "";
+ while ((bodyAvail = bodyStream.available()) > 0)
+ requestBody += bodyStream.readBytes(bodyAvail);
+
+ 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});
+ });
+ }
+ if (contentTypeParams[''] == "text/plain" &&
+ request.queryString == "plain") {
+ result = utf8decode(requestBody);
+ }
+ if (contentTypeParams[''] == "application/x-www-form-urlencoded" &&
+ request.queryString == "url") {
+ result = requestBody;
+ }
+ }
+ else if (request.method == "GET") {
+ result = request.queryString;
+ }
+
+ // Send response body
+ response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+ response.write(utf8encode(JSON.stringify(result)));
+}
diff --git a/dom/html/test/forms/chrome.ini b/dom/html/test/forms/chrome.ini
new file mode 100644
index 000000000..0c8fa8231
--- /dev/null
+++ b/dom/html/test/forms/chrome.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+ submit_invalid_file.sjs
+[test_autocompleteinfo.html]
+[test_submit_invalid_file.html]
diff --git a/dom/html/test/forms/mochitest.ini b/dom/html/test/forms/mochitest.ini
new file mode 100644
index 000000000..35955b189
--- /dev/null
+++ b/dom/html/test/forms/mochitest.ini
@@ -0,0 +1,107 @@
+[DEFAULT]
+support-files =
+ save_restore_radio_groups.sjs
+ test_input_number_data.js
+ !/dom/html/test/reflect.js
+
+[test_bug1039548.html]
+[test_bug1283915.html]
+[test_bug1286509.html]
+skip-if = os == "android" # up/down arrow keys not supported on android
+[test_button_attributes_reflection.html]
+[test_input_radio_indeterminate.html]
+[test_input_radio_radiogroup.html]
+[test_input_radio_required.html]
+[test_change_event.html]
+[test_datalist_element.html]
+[test_form_attribute-1.html]
+[test_form_attribute-2.html]
+[test_form_attribute-3.html]
+[test_form_attribute-4.html]
+[test_form_attributes_reflection.html]
+[test_form_named_getter_dynamic.html]
+[test_formaction_attribute.html]
+[test_formnovalidate_attribute.html]
+[test_input_attributes_reflection.html]
+[test_input_autocomplete.html]
+[test_input_color_input_change_events.html]
+[test_input_color_picker_initial.html]
+[test_input_color_picker_popup.html]
+skip-if = android_version == '18' # Android, bug 1147974
+[test_input_color_picker_update.html]
+skip-if = android_version == '18' # Android, bug 1147974
+[test_input_datetime_focus_blur.html]
+skip-if = os == "android"
+[test_input_datetime_tabindex.html]
+skip-if = os == "android"
+[test_input_defaultValue.html]
+[test_input_email.html]
+[test_input_event.html]
+skip-if = android_version == '18' # bug 1147974
+[test_input_file_picker.html]
+[test_input_list_attribute.html]
+[test_input_number_l10n.html]
+# We don't build ICU for Firefox for Android:
+skip-if = os == "android"
+[test_input_number_key_events.html]
+[test_input_number_mouse_events.html]
+# Not run on Firefox for Android where the spin buttons are hidden:
+skip-if = os == "android"
+[test_input_number_rounding.html]
+skip-if = os == "android"
+[test_input_number_validation.html]
+# We don't build ICU for Firefox for Android:
+skip-if = os == "android"
+[test_input_number_focus.html]
+[test_input_range_attr_order.html]
+[test_input_range_key_events.html]
+[test_input_range_mouse_and_touch_events.html]
+[test_input_range_rounding.html]
+[test_input_sanitization.html]
+[test_input_textarea_set_value_no_scroll.html]
+[test_input_time_key_events.html]
+skip-if = os == "android"
+[test_input_time_focus_blur_events.html]
+skip-if = os == "android"
+[test_input_types_pref.html]
+[test_input_typing_sanitization.html]
+[test_input_untrusted_key_events.html]
+[test_input_url.html]
+[test_interactive_content_in_label.html]
+[test_label_control_attribute.html]
+[test_label_input_controls.html]
+[test_max_attribute.html]
+[test_maxlength_attribute.html]
+[test_minlength_attribute.html]
+[test_meter_element.html]
+[test_meter_pseudo-classes.html]
+[test_min_attribute.html]
+[test_mozistextfield.html]
+[test_novalidate_attribute.html]
+[test_option_disabled.html]
+[test_option_index_attribute.html]
+[test_option_text.html]
+[test_output_element.html]
+[test_pattern_attribute.html]
+[test_progress_element.html]
+[test_radio_in_label.html]
+[test_radio_radionodelist.html]
+[test_required_attribute.html]
+[test_restore_form_elements.html]
+[test_save_restore_radio_groups.html]
+[test_select_change_event.html]
+skip-if = android_version == '18' || os == 'mac'
+[test_select_input_change_event.html]
+skip-if = android_version == '18' || os == 'mac'
+[test_select_selectedOptions.html]
+[test_select_validation.html]
+[test_set_range_text.html]
+[test_step_attribute.html]
+[test_stepup_stepdown.html]
+[test_textarea_attributes_reflection.html]
+[test_validation.html]
+[test_valueAsDate_pref.html]
+[test_valueasdate_attribute.html]
+[test_valueasnumber_attribute.html]
+[test_validation_not_in_doc.html]
+[test_reportValidation_preventDefault.html]
diff --git a/dom/html/test/forms/save_restore_radio_groups.sjs b/dom/html/test/forms/save_restore_radio_groups.sjs
new file mode 100644
index 000000000..f0e36cb52
--- /dev/null
+++ b/dom/html/test/forms/save_restore_radio_groups.sjs
@@ -0,0 +1,50 @@
+var pages = [
+ "<!DOCTYPE html>" +
+ "<html><body>" +
+ "<form>" +
+ "<input name='a' type='radio' checked><input name='a' type='radio'><input name='a' type='radio'>" +
+ "</form>" +
+ "</body></html>",
+ "<!DOCTYPE html>" +
+ "<html><body>" +
+ "<form>" +
+ "<input name='a' type='radio'><input name='a' type='radio' checked><input name='a' type='radio'>" +
+ "</form>" +
+ "</body></html>",
+ ];
+
+/**
+ * This SJS is going to send the same page the two first times it will be called
+ * and another page the two following times. After that, the response will have
+ * no content.
+ * The use case is to have two iframes using this SJS and both being reloaded
+ * once.
+ */
+
+function handleRequest(request, response)
+{
+ var counter = +getState("counter"); // convert to number; +"" === 0
+
+ response.setStatusLine(request.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/html");
+ response.setHeader("Cache-Control", "no-cache");
+
+ switch (counter) {
+ case 0:
+ case 1:
+ response.write(pages[0]);
+ break;
+ case 2:
+ case 3:
+ response.write(pages[1]);
+ break;
+ }
+
+ // When we finish the test case we need to reset the counter
+ if (counter == 3) {
+ setState("counter", "0");
+ } else {
+ setState("counter", "" + ++counter);
+ }
+}
+
diff --git a/dom/html/test/forms/submit_invalid_file.sjs b/dom/html/test/forms/submit_invalid_file.sjs
new file mode 100644
index 000000000..8f8b46957
--- /dev/null
+++ b/dom/html/test/forms/submit_invalid_file.sjs
@@ -0,0 +1,14 @@
+function handleRequest(request, response)
+{
+ response.setStatusLine(request.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/html");
+ response.setHeader("Cache-Control", "no-cache");
+
+ var result = {};
+ request.bodyInputStream.search("testfile", true, result, {});
+ if (result.value) {
+ response.write("SUCCESS");
+ } else {
+ response.write("FAIL");
+ }
+}
diff --git a/dom/html/test/forms/test_autocompleteinfo.html b/dom/html/test/forms/test_autocompleteinfo.html
new file mode 100644
index 000000000..310b7c9f3
--- /dev/null
+++ b/dom/html/test/forms/test_autocompleteinfo.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<html>
+<!--
+Test getAutocompleteInfo() on <input>
+-->
+<head>
+ <title>Test for getAutocompleteInfo()</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">
+ <form>
+ <input id="input"/>
+ </form>
+</div>
+<pre id="test">
+<script>
+"use strict";
+
+var values = [
+ // Missing or empty attribute
+ [undefined, {}],
+ ["", {}],
+
+ // One token
+ ["on", {fieldName: "on" }],
+ ["On", {fieldName: "on" }],
+ ["off", {fieldName: "off" } ],
+ ["username", {fieldName: "username" }],
+ [" username ", {fieldName: "username" }],
+ ["foobar", {}],
+
+ // Two tokens
+ ["on off", {}],
+ ["off on", {}],
+ ["username tel", {}],
+ ["tel username ", {}],
+ [" username tel ", {}],
+ ["tel mobile", {}],
+ ["tel shipping", {}],
+ ["shipping tel", {addressType: "shipping", fieldName: "tel"}],
+ ["shipPING tel", {addressType: "shipping", fieldName: "tel"}],
+ ["mobile tel", {contactType: "mobile", fieldName: "tel"}],
+ [" MoBiLe TeL ", {contactType: "mobile", fieldName: "tel"}],
+ ["XXX tel", {}],
+ ["XXX username", {}],
+
+ // Three tokens
+ ["billing invalid tel", {}],
+ ["___ mobile tel", {}],
+ ["mobile foo tel", {}],
+ ["mobile tel foo", {}],
+ ["tel mobile billing", {}],
+ ["billing mobile tel", {addressType: "billing", contactType: "mobile", fieldName: "tel"}],
+ [" BILLing MoBiLE tEl ", {addressType: "billing", contactType: "mobile", fieldName: "tel"}],
+ ["billing home tel", {addressType: "billing", contactType: "home", fieldName: "tel"}],
+
+ // Four tokens (invalid)
+ ["billing billing mobile tel", {}],
+
+ // Five tokens (invalid)
+ ["billing billing billing mobile tel", {}],
+];
+
+var autocompleteEnabledTypes = ["hidden", "text", "search", "url", "tel",
+ "email", "password", "date", "time", "number",
+ "range", "color"];
+var autocompleteDisabledTypes = ["reset", "submit", "image", "button", "radio",
+ "checkbox", "file"];
+
+function start() {
+ const fieldid = "input";
+ var field = document.getElementById(fieldid);
+ for (var test of values) {
+ if (typeof(test[0]) === "undefined")
+ field.removeAttribute("autocomplete");
+ else
+ field.setAttribute("autocomplete", test[0]);
+
+ var info = field.getAutocompleteInfo();
+
+ is(info.section, "section" in test[1] ? test[1].section : "",
+ "Checking autocompleteInfo.section for " + fieldid + ": " + test[0]);
+ is(info.addressType, "addressType" in test[1] ? test[1].addressType : "",
+ "Checking autocompleteInfo.addressType for " + fieldid + ": " + test[0]);
+ is(info.contactType, "contactType" in test[1] ? test[1].contactType : "",
+ "Checking autocompleteInfo.contactType for " + fieldid + ": " + test[0]);
+ is(info.fieldName, "fieldName" in test[1] ? test[1].fieldName : "",
+ "Checking autocompleteInfo.fieldName for " + fieldid + ": " + test[0]);
+
+ }
+
+ for (var type of autocompleteEnabledTypes) {
+ testAutocomplete(field, type, true);
+ }
+
+ for (var type of autocompleteDisabledTypes) {
+ testAutocomplete(field, type, false);
+ }
+ SimpleTest.finish();
+}
+
+function testAutocomplete(aField, aType, aEnabled) {
+ aField.type = aType;
+ if (aEnabled) {
+ ok(aField.getAutocompleteInfo() !== null, "getAutocompleteInfo shouldn't return null");
+ } else {
+ is(aField.getAutocompleteInfo(), null, "getAutocompleteInfo should return null");
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["dom.forms.autocomplete.experimental", true]]}, start);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_bug1039548.html b/dom/html/test/forms/test_bug1039548.html
new file mode 100644
index 000000000..0405d5482
--- /dev/null
+++ b/dom/html/test/forms/test_bug1039548.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1039548
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1039548</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="application/javascript">
+
+ /** Test for Bug 1039548 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ SimpleTest.waitForFocus(test);
+
+ var didTryToSubmit;
+ function test() {
+ var r = document.getElementById("radio");
+ r.focus();
+ didTryToSubmit = false;
+ sendKey("return");
+ ok(!didTryToSubmit, "Shouldn't have tried to submit!");
+
+ var t = document.getElementById("text");
+ t.focus();
+ didTryToSubmit = false;
+ sendKey("return");
+ ok(didTryToSubmit, "Should have tried to submit!");
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1039548">Mozilla Bug 1039548</a>
+<p id="display"></p>
+<div id="content">
+
+ <form onsubmit="didTryToSubmit = true; event.preventDefault();">
+ <input type="radio" id="radio">
+ </form>
+
+ <form onsubmit="didTryToSubmit = true; event.preventDefault();">
+ <input type="text" id="text">
+ </form>
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_bug1283915.html b/dom/html/test/forms/test_bug1283915.html
new file mode 100644
index 000000000..0b0ac5f13
--- /dev/null
+++ b/dom/html/test/forms/test_bug1283915.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1283915
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1283915</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="application/javascript">
+
+ /** Test for Bug 1283915 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function isCursorAtEnd(field){
+ is(field.selectionStart, field.value.length);
+ is(field.selectionEnd, field.value.length);
+ }
+
+ function test() {
+ var tField = document.getElementById("textField");
+ tField.focus();
+
+ synthesizeKey("a", {});
+ is(tField.value, "a");
+ isCursorAtEnd(tField);
+ document.body.offsetWidth; // frame must be created after type change
+
+ synthesizeKey("b", {});
+ is(tField.value, "ab");
+ isCursorAtEnd(tField);
+
+ synthesizeKey("c", {});
+ is(tField.value, "abc");
+ isCursorAtEnd(tField);
+
+ var nField = document.getElementById("numField");
+ nField.focus();
+
+ synthesizeKey("1", {});
+ is(nField.value, "1");
+ document.body.offsetWidth;
+
+ synthesizeKey("2", {});
+ is(nField.value, "12");
+
+ synthesizeKey("3", {});
+ is(nField.value, "123");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForFocus(test);
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1283915">Mozilla Bug 1283915</a>
+<p id="display"></p>
+<input id="textField" type="text" oninput="if (this.type !='password') this.type = 'password';">
+<input id="numField" type="text" oninput="if (this.type !='number') this.type = 'number';">
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_bug1286509.html b/dom/html/test/forms/test_bug1286509.html
new file mode 100644
index 000000000..05fbbac31
--- /dev/null
+++ b/dom/html/test/forms/test_bug1286509.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1286509
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1286509</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=1286509">Mozilla Bug 1286509</a>
+<p id="display"></p>
+<div id="content">
+ <input type="range" id="test_input" min="0" max="10" value="5">
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ /** Test for Bug 1286509 **/
+ SimpleTest.waitForExplicitFinish();
+ var expectedEventSequence = ['keydown', 'change', 'keyup'];
+ var eventCounts = {};
+ var expectedEventIdx = 0;
+
+ function test() {
+ var range = document.getElementById("test_input");
+ range.focus();
+ expectedEventSequence.forEach((eventName) => {
+ eventCounts[eventName] = 0;
+ range.addEventListener(eventName, (e) => {
+ ++eventCounts[eventName];
+ is(expectedEventSequence[expectedEventIdx], e.type, "Events sequence should be keydown, change, keyup");
+ expectedEventIdx = (expectedEventIdx + 1) % 3;
+ }, false);
+ });
+ synthesizeKey("VK_UP", {});
+ synthesizeKey("VK_DOWN", {});
+ synthesizeKey("VK_LEFT", {});
+ synthesizeKey("VK_RIGHT", {});
+ is(eventCounts['change'], 4, "Expect key up/down/left/right should trigger range input to fire change events");
+ SimpleTest.finish();
+ }
+ addLoadEvent(test);
+ </script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_button_attributes_reflection.html b/dom/html/test/forms/test_button_attributes_reflection.html
new file mode 100644
index 000000000..26858e939
--- /dev/null
+++ b/dom/html/test/forms/test_button_attributes_reflection.html
@@ -0,0 +1,137 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLButtonElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="../reflect.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">
+
+/** Test for HTMLButtonElement attributes reflection **/
+
+// .autofocus
+reflectBoolean({
+ element: document.createElement("button"),
+ attribute: "autofocus",
+});
+
+// .disabled
+reflectBoolean({
+ element: document.createElement("button"),
+ attribute: "disabled",
+});
+
+// TODO: formAction (URL). But note that we currently reflect it weirdly; see
+// dom/html/test/test_bug607145.html
+
+// .formEnctype
+reflectLimitedEnumerated({
+ element: document.createElement("button"),
+ attribute: "formEnctype",
+ validValues: [
+ "application/x-www-form-urlencoded",
+ "multipart/form-data",
+ "text/plain",
+ ],
+ invalidValues: [ "text/html", "", "tulip" ],
+ defaultValue: {
+ invalid: "application/x-www-form-urlencoded",
+ missing: "",
+ }
+});
+
+// .formMethod
+reflectLimitedEnumerated({
+ element: document.createElement("button"),
+ attribute: "formMethod",
+ validValues: [ "get", "post" ],
+ invalidValues: [ "put", "", "tulip" ],
+ unsupportedValues: [ "dialog" ],
+ defaultValue: {
+ invalid: "get",
+ missing: "",
+ }
+});
+
+// .formNoValidate
+reflectBoolean({
+ element: document.createElement("button"),
+ attribute: "formNoValidate",
+});
+
+// .formTarget
+reflectString({
+ element: document.createElement("button"),
+ attribute: "formTarget",
+ otherValues: [ "_blank", "_self", "_parent", "_top" ],
+});
+
+// .name
+reflectString({
+ element: document.createElement("button"),
+ attribute: "name",
+ otherValues: [ "isindex", "_charset_" ]
+});
+
+// .type
+reflectLimitedEnumerated({
+ element: document.createElement("button"),
+ attribute: "type",
+ validValues: [ "submit", "reset", "button" ],
+ invalidValues: [ "this-is-probably-a-wrong-type", "", "tulip" ],
+ unsupportedValues: [ "menu" ],
+ defaultValue: "submit",
+});
+
+// .value
+reflectString({
+ element: document.createElement("button"),
+ attribute: "value",
+});
+
+// .willValidate
+ok("willValidate" in document.createElement("button"),
+ "willValidate should be an IDL attribute of the button element");
+is(typeof(document.createElement("button").willValidate), "boolean",
+ "button.willValidate should be a boolean");
+
+// .validity
+ok("validity" in document.createElement("button"),
+ "validity should be an IDL attribute of the button element");
+is(typeof(document.createElement("button").validity), "object",
+ "button.validity should be an object");
+ok(document.createElement("button").validity instanceof ValidityState,
+ "button.validity sohuld be an instance of ValidityState");
+
+// .validationMessage
+ok("validationMessage" in document.createElement("button"),
+ "validationMessage should be an IDL attribute of the button element");
+is(typeof(document.createElement("button").validationMessage), "string",
+ "button.validationMessage should be a string");
+
+// .checkValidity()
+ok("checkValidity" in document.createElement("button"),
+ "checkValidity() should be a method of the button element");
+is(typeof(document.createElement("button").checkValidity), "function",
+ "button.checkValidity should be a function");
+
+// .setCustomValidity()
+ok("setCustomValidity" in document.createElement("button"),
+ "setCustomValidity() should be a method of the button element");
+is(typeof(document.createElement("button").setCustomValidity), "function",
+ "button.setCustomValidity should be a function");
+
+// .labels
+todo("labels" in document.createElement("button"),
+ "button.labels isn't implemented yet");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_change_event.html b/dom/html/test/forms/test_change_event.html
new file mode 100644
index 000000000..d1f0f827e
--- /dev/null
+++ b/dom/html/test/forms/test_change_event.html
@@ -0,0 +1,287 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=722599
+-->
+<head>
+<title>Test for Bug 722599</title>
+<script type="text/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=722599">Mozilla Bug 722599</a>
+<p id="display"></p>
+<div id="content">
+<input type="file" id="fileInput"></input>
+<textarea id="textarea" onchange="++textareaChange;"></textarea>
+<input type="text" id="input_text" onchange="++textInputChange[0];"></input>
+<input type="email" id="input_email" onchange="++textInputChange[1];"></input>
+<input type="search" id="input_search" onchange="++textInputChange[2];"></input>
+<input type="tel" id="input_tel" onchange="++textInputChange[3];"></input>
+<input type="url" id="input_url" onchange="++textInputChange[4];"></input>
+<input type="password" id="input_password" onchange="++textInputChange[5];"></input>
+
+<!-- "Non-text" inputs-->
+<input type="button" id="input_button" onchange="++NonTextInputChange[0];"></input>
+<input type="submit" id="input_submit" onchange="++NonTextInputChange[1];"></input>
+<input type="image" id="input_image" onchange="++NonTextInputChange[2];"></input>
+<input type="reset" id="input_reset" onchange="++NonTextInputChange[3];"></input>
+<input type="radio" id="input_radio" onchange="++NonTextInputChange[4];"></input>
+<input type="checkbox" id="input_checkbox" onchange="++NonTextInputChange[5];"></input>
+<input type="number" id="input_number" onchange="++numberChange;"></input>
+<input type="range" id="input_range" onchange="++rangeChange;"></input>
+
+<!-- Input text with default value and blurs on focus-->
+<input type="text" id="input_text_value" onchange="++textInputValueChange"
+ onfocus="this.blur();" value="foo"></input>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ /** Test for Bug 722599 **/
+
+ const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent);
+
+ var textareaChange = 0;
+ var fileInputChange = 0;
+ var textInputValueChange = 0;
+
+ var textInputTypes = ["text", "email", "search", "tel", "url", "password"];
+ var textInputChange = [0, 0, 0, 0, 0, 0];
+
+ var NonTextInputTypes = ["button", "submit", "image", "reset", "radio", "checkbox"];
+ var NonTextInputChange = [0, 0, 0, 0, 0, 0];
+
+ var numberChange = 0;
+ var rangeChange = 0;
+
+ var blurTestCalled = false; //Sentinel to prevent infinite loop.
+
+ SimpleTest.waitForExplicitFinish();
+ var MockFilePicker = SpecialPowers.MockFilePicker;
+ MockFilePicker.init(window);
+
+ function fileInputBlurTest() {
+ var btn = document.getElementById('fileInput');
+ btn.focus()
+ btn.blur();
+ is(fileInputChange, 1, "change event shouldn't be dispatched on blur for file input element(1)");
+ }
+
+ function testUserInput() {
+ //Simulating an OK click and with a file name return.
+ MockFilePicker.useBlobFile();
+ MockFilePicker.returnValue = MockFilePicker.returnOK;
+ var input = document.getElementById('fileInput');
+ input.focus();
+
+ input.addEventListener("change", function (aEvent) {
+ ++fileInputChange;
+ if (!blurTestCalled) {
+ is(fileInputChange, 1, "change event should have been dispatched on file input.");
+ blurTestCalled = true;
+ fileInputBlurTest();
+ }
+ else {
+ is(fileInputChange, 1, "change event shouldn't be dispatched on blur for file input element (2)");
+ }
+ }, false);
+ input.click();
+ // blur the file input, we can't use blur() because of bug 760283
+ document.getElementById('input_text').focus();
+ setTimeout(testUserInput2, 0);
+ }
+
+ function testUserInput2() {
+ var input = document.getElementById('fileInput');
+ // remove it, otherwise cleanup() opens a native file picker!
+ input.parentNode.removeChild(input);
+ MockFilePicker.cleanup();
+
+ //text, email, search, telephone, url & password input tests
+ for (var i = 0; i < textInputTypes.length; ++i) {
+ input = document.getElementById("input_" + textInputTypes[i]);
+ input.focus();
+ synthesizeKey("VK_RETURN", {});
+ is(textInputChange[i], 0, "Change event shouldn't be dispatched on " + textInputTypes[i] + " input element");
+
+ synthesizeKey("m", {});
+ synthesizeKey("VK_RETURN", {});
+ is(textInputChange[i], 1, textInputTypes[i] + " input element should have dispatched change event.");
+ }
+
+ //focus and blur text input
+ input = document.getElementById("input_text");
+ input.focus();
+ synthesizeKey("f", {});
+ input.blur();
+ is(textInputChange[0], 2, "text input element should have dispatched change event (2).");
+
+ // value being set while focused
+ input.focus();
+ input.value = 'foo';
+ input.blur();
+ is(textInputChange[0], 2, "text input element should not have dispatched change event (2).");
+
+ // value being set while focused after being modified manually
+ input.focus();
+ synthesizeKey("f", {});
+ input.value = 'bar';
+ input.blur();
+ is(textInputChange[0], 3, "text input element should have dispatched change event (3).");
+
+ //focus and blur textarea
+ var textarea = document.getElementById("textarea");
+ textarea.focus();
+ synthesizeKey("f", {});
+ textarea.blur();
+ is(textareaChange, 1, "Textarea element should have dispatched change event.");
+
+ // value being set while focused
+ textarea.focus();
+ textarea.value = 'foo';
+ textarea.blur();
+ is(textareaChange, 1, "textarea should not have dispatched change event (1).");
+
+ // value being set while focused after being modified manually
+ textarea.focus();
+ synthesizeKey("f", {});
+ textarea.value = 'bar';
+ textarea.blur();
+ is(textareaChange, 2, "textearea should have dispatched change event (2).");
+
+ //Non-text input tests:
+ for (var i = 0; i < NonTextInputTypes.length; ++i) {
+ //button, submit, image and reset input type tests.
+ if (i < 4) {
+ input = document.getElementById("input_" + NonTextInputTypes[i]);
+ input.focus();
+ input.click();
+ is(NonTextInputChange[i], 0, "Change event shouldn't be dispatched on " + NonTextInputTypes[i] + " input element");
+ input.blur();
+ is(NonTextInputChange[i], 0, "Change event shouldn't be dispatched on " + NonTextInputTypes[i] + " input element(2)");
+ }
+ //for radio and and checkboxes, we require that change event should ONLY be dispatched on setting the value.
+ else {
+ input = document.getElementById("input_" + NonTextInputTypes[i]);
+ input.focus();
+ input.click();
+ is(NonTextInputChange[i], 1, NonTextInputTypes[i] + " input element should have dispatched change event.");
+ input.blur();
+ is(NonTextInputChange[i], 1, "Change event shouldn't be dispatched on " + NonTextInputTypes[i] + " input element");
+
+ // Test that change event is not dispatched if click event is cancelled.
+ function preventDefault(e) {
+ e.preventDefault();
+ }
+ input.addEventListener("click", preventDefault, false);
+ input.click();
+ is(NonTextInputChange[i], 1, "Change event shouldn't be dispatched if click event is cancelled");
+ input.removeEventListener("click", preventDefault, false);
+ }
+ }
+
+ // Special case type=number
+ var number = document.getElementById("input_number");
+ number.focus();
+ synthesizeKey("a", {});
+ number.blur();
+ is(numberChange, 0, "Change event shouldn't be dispatched on number input element for key changes that don't change its value");
+ number.value = "";
+ number.focus();
+ synthesizeKey("1", {});
+ synthesizeKey("2", {});
+ is(numberChange, 0, "Change event shouldn't be dispatched on number input element for keyboard input until it loses focus");
+ number.blur();
+ is(numberChange, 1, "Change event should be dispatched on number input element on blur");
+ is(number.value, "12", "Sanity check that number keys were actually handled");
+ if (isDesktop) { // up/down arrow keys not supported on android/b2g
+ number.value = "";
+ number.focus();
+ synthesizeKey("VK_UP", {});
+ synthesizeKey("VK_UP", {});
+ synthesizeKey("VK_DOWN", {});
+ is(numberChange, 4, "Change event should be dispatched on number input element for up/down arrow keys (a special case)");
+ is(number.value, "1", "Sanity check that number and arrow keys were actually handled");
+ }
+
+ // Special case type=range
+ var range = document.getElementById("input_range");
+ range.focus();
+ synthesizeKey("a", {});
+ range.blur();
+ is(rangeChange, 0, "Change event shouldn't be dispatched on range input element for key changes that don't change its value");
+ range.focus();
+ synthesizeKey("VK_HOME", {});
+ is(rangeChange, 1, "Change event should be dispatched on range input element for key changes");
+ range.blur();
+ is(rangeChange, 1, "Change event shouldn't be dispatched on range input element on blur");
+ range.focus();
+ var bcr = range.getBoundingClientRect();
+ var centerOfRangeX = bcr.width / 2;
+ var centerOfRangeY = bcr.height / 2;
+ synthesizeMouse(range, centerOfRangeX - 10, centerOfRangeY, { type: "mousedown" });
+ is(rangeChange, 1, "Change event shouldn't be dispatched on range input element for mousedown");
+ synthesizeMouse(range, centerOfRangeX - 5, centerOfRangeY, { type: "mousemove" });
+ is(rangeChange, 1, "Change event shouldn't be dispatched on range input element during drag of thumb");
+ synthesizeMouse(range, centerOfRangeX, centerOfRangeY, { type: "mouseup" });
+ is(rangeChange, 2, "Change event should be dispatched on range input element at end of drag");
+ range.blur();
+ is(rangeChange, 2, "Change event shouldn't be dispatched on range input element when range loses focus after a drag");
+ synthesizeMouse(range, centerOfRangeX - 10, centerOfRangeY, {});
+ is(rangeChange, 3, "Change event should be dispatched on range input element for a click that gives the range focus");
+
+ if (isDesktop) { // up/down arrow keys not supported on android/b2g
+ synthesizeKey("VK_UP", {});
+ is(rangeChange, 4, "Change event should be dispatched on range input element for key changes that change its value (VK_UP)");
+ synthesizeKey("VK_DOWN", {});
+ is(rangeChange, 5, "Change event should be dispatched on range input element for key changes that change its value (VK_DOWN)");
+ synthesizeKey("VK_RIGHT", {});
+ is(rangeChange, 6, "Change event should be dispatched on range input element for key changes that change its value (VK_RIGHT)");
+ synthesizeKey("VK_LEFT", {});
+ is(rangeChange, 7, "Change event should be dispatched on range input element for key changes that change its value (VK_LEFT)");
+ synthesizeKey("VK_UP", {shiftKey: true});
+ is(rangeChange, 8, "Change event should be dispatched on range input element for key changes that change its value (Shift+VK_UP)");
+ synthesizeKey("VK_DOWN", {shiftKey: true});
+ is(rangeChange, 9, "Change event should be dispatched on range input element for key changes that change its value (Shift+VK_DOWN)");
+ synthesizeKey("VK_RIGHT", {shiftKey: true});
+ is(rangeChange, 10, "Change event should be dispatched on range input element for key changes that change its value (Shift+VK_RIGHT)");
+ synthesizeKey("VK_LEFT", {shiftKey: true});
+ is(rangeChange, 11, "Change event should be dispatched on range input element for key changes that change its value (Shift+VK_LEFT)");
+ synthesizeKey("VK_PAGE_UP", {});
+ is(rangeChange, 12, "Change event should be dispatched on range input element for key changes that change its value (VK_PAGE_UP)");
+ synthesizeKey("VK_PAGE_DOWN", {});
+ is(rangeChange, 13, "Change event should be dispatched on range input element for key changes that change its value (VK_PAGE_DOWN");
+ synthesizeKey("VK_RIGHT", {shiftKey: true});
+ is(rangeChange, 14, "Change event should be dispatched on range input element for key changes that change its value (Shift+VK_PAGE_UP)");
+ synthesizeKey("VK_LEFT", {shiftKey: true});
+ is(rangeChange, 15, "Change event should be dispatched on range input element for key changes that change its value (Shift+VK_PAGE_DOWN)");
+ }
+ //Input type change test.
+ input = document.getElementById("input_checkbox");
+ input.type = "text";
+ input.focus();
+ input.click();
+ input.blur();
+ is(NonTextInputChange[5], 1, "Change event shouldn't be dispatched for checkbox ---> text input type change");
+
+ setTimeout(testInputWithDefaultValue, 0);
+ }
+
+ function testInputWithDefaultValue() {
+ // focus and blur an input text should not trigger change event if content hasn't changed.
+ var input = document.getElementById('input_text_value');
+ input.focus();
+ is(textInputValueChange, 0, "change event shouldn't be dispatched on input text with default value");
+
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(testUserInput);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_datalist_element.html b/dom/html/test/forms/test_datalist_element.html
new file mode 100644
index 000000000..67c8e854a
--- /dev/null
+++ b/dom/html/test/forms/test_datalist_element.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for the datalist element</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">
+ <datalist>
+ </datalist>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 555840 **/
+
+function checkClassesAndAttributes()
+{
+ var d = document.getElementsByTagName('datalist');
+ is(d.length, 1, "One datalist has been found");
+
+ d = d[0];
+ ok(d instanceof HTMLDataListElement,
+ "The datalist should be instance of HTMLDataListElement");
+
+ ok('options' in d, "datalist has an options IDL attribute");
+
+ ok(d.options, "options IDL attribute is not null");
+ ok(!d.getAttribute('options'), "datalist has no options content attribute");
+
+ ok(d.options instanceof HTMLCollection,
+ "options IDL attribute should be instance of HTMLCollection");
+}
+
+function checkOptions()
+{
+ var testData = [
+ /* [ Child list, Function modifying children, Recognized options ] */
+ [['option'], null, 1],
+ [['option', 'option', 'option', 'option'], null, 4],
+ /* Disabled options are not valid. */
+ [['option'], function(d) { d.childNodes[0].disabled = true; }, 0],
+ [['option', 'option'], function(d) { d.childNodes[0].disabled = true; }, 1],
+ /* Non-option elements are not recognized. */
+ [['input'], null, 0],
+ [['input', 'option'], null, 1],
+ [['input', 'textarea'], null, 0],
+ /* .value and .label are not needed to be valid options. */
+ [['option', 'option'], function(d) { d.childNodes[0].value = 'value'; }, 2],
+ [['option', 'option'], function(d) { d.childNodes[0].label = 'label'; }, 2],
+ [['option', 'option'], function(d) { d.childNodes[0].value = 'value'; d.childNodes[0].label = 'label'; }, 2],
+ [['select'],
+ function(d) {
+ var s = d.childNodes[0];
+ s.appendChild(new Option("foo"));
+ s.appendChild(new Option("bar"));
+ },
+ 2],
+ [['select'],
+ function(d) {
+ var s = d.childNodes[0];
+ s.appendChild(new Option("foo"));
+ s.appendChild(new Option("bar"));
+ var label = document.createElement("label");
+ d.appendChild(label);
+ label.appendChild(new Option("foobar"));
+ },
+ 3],
+ [['select'],
+ function(d) {
+ var s = d.childNodes[0];
+ s.appendChild(new Option("foo"));
+ s.appendChild(new Option("bar"));
+ var label = document.createElement("label");
+ d.appendChild(label);
+ label.appendChild(new Option("foobar"));
+ s.appendChild(new Option())
+ },
+ 4],
+ [[], function(d) { d.appendChild(document.createElementNS("foo", "option")); }, 0]
+ ];
+
+ var d = document.getElementsByTagName('datalist')[0];
+ var cachedOptions = d.options;
+
+ testData.forEach(function(data) {
+ data[0].forEach(function(e) {
+ d.appendChild(document.createElement(e));
+ })
+
+ /* Modify children. */
+ if (data[1]) {
+ data[1](d);
+ }
+
+ is(d.options, cachedOptions, "Should get the same object")
+ is(d.options.length, data[2],
+ "The number of recognized options should be " + data[2])
+
+ for (var i = 0; i < d.options.length; ++i) {
+ is(d.options[i].localName, "option",
+ "Should get an option for d.options[" + i + "]")
+ }
+
+ /* Cleaning-up. */
+ d.textContent = "";
+ })
+}
+
+checkClassesAndAttributes();
+checkOptions();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_form_attribute-1.html b/dom/html/test/forms/test_form_attribute-1.html
new file mode 100644
index 000000000..bb82cb0da
--- /dev/null
+++ b/dom/html/test/forms/test_form_attribute-1.html
@@ -0,0 +1,473 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=588683
+-->
+<head>
+ <title>Test for form attributes 1</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=588683">Mozilla Bug 588683</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for form attributes 1 **/
+
+/**
+ * All functions take an array of forms in first argument and an array of
+ * elements in second argument.
+ * Then, it returns an array containing an array of form and an array of array
+ * of elements. The array represent the form association with elements like this:
+ * [ [ form1, form2 ], [ [ elmt1ofForm1, elmt2ofForm2 ], [ elmtofForm2 ] ] ]
+ */
+
+/**
+ * test0a and test0b are testing the regular behavior of form ownership.
+ */
+function test0a(aForms, aElements)
+{
+ // <form><element></form>
+ // <form><element></form>
+ aForms[0].appendChild(aElements[0]);
+ aForms[1].appendChild(aElements[1]);
+
+ return [[aForms[0],aForms[1]],[[aElements[0]],[aElements[1]]]];
+}
+
+function test0b(aForms, aElements)
+{
+ // <form><element><form><element></form></form>
+ aForms[0].appendChild(aElements[0]);
+ aForms[0].appendChild(aForms[1]);
+ aForms[1].appendChild(aElements[1]);
+
+ return [[aForms[0],aForms[1]],[[aElements[0]],[aElements[1]]]];
+}
+
+/**
+ * This function test that, when an element is not a descendant of a form
+ * element and has @form set to a valid form id, it's form owner is the form
+ * which has the id.
+ */
+function test1(aForms, aElements)
+{
+ // <form id='f'></form><element id='f'>
+ aForms[0].id = 'f';
+ aElements[0].setAttribute('form', 'f');
+
+ return [[aForms[0]], [[aElements[0]]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form
+ * element and has @form set to a valid form id (not it's descendant), it's form
+ * owner is the form which has the id.
+ */
+function test2(aForms, aElements)
+{
+ // <form id='f'></form><form><element form='f'></form>
+ aForms[0].id = 'f';
+ aForms[1].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', 'f');
+
+ return [[aForms[0], aForms[1]], [[aElements[0]],[]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form
+ * element and has @form set to a valid form id (not it's descendant), then the
+ * form attribute is removed, it does not have a form owner.
+ */
+function test3(aForms, aElements)
+{
+ // <form id='f'></form><form><element form='f'></form>
+ aForms[0].id = 'f';
+ aForms[1].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', 'f');
+ aElements[0].removeAttribute('form');
+
+ return [[aForms[0], aForms[1]], [[],[aElements[0]]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form
+ * element and has @form set to a valid form id (not it's descendant), then the
+ * form's id attribute is removed, it does not have a form owner.
+ */
+function test4(aForms, aElements)
+{
+ // <form id='f'></form><form><element form='f'></form>
+ aForms[0].id = 'f';
+ aForms[1].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', 'f');
+ aForms[0].removeAttribute('id');
+
+ return [[aForms[0], aForms[1]], [[],[]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form
+ * element and has @form set to an invalid form id, then it does not have a form
+ * owner.
+ */
+function test5(aForms, aElements)
+{
+ // <form id='f'></form><form><element form='foo'></form>
+ aForms[0].id = 'f';
+ aForms[1].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', 'foo');
+
+ return [[aForms[0], aForms[1]], [[],[]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form
+ * element and has @form set to a valid form id (not it's descendant), then the
+ * form id attribute is changed to an invalid id, it does not have a form owner.
+ */
+function test6(aForms, aElements)
+{
+ // <form id='f'></form><form><element form='f'></form>
+ aForms[0].id = 'f';
+ aForms[1].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', 'f');
+ aElements[0].setAttribute('form', 'foo');
+
+ return [[aForms[0], aForms[1]], [[],[]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form
+ * element and has @form set to an invalid form id, then the form id attribute
+ * is changed to a valid form id, it's form owner is the form which has this id.
+ */
+function test7(aForms, aElements)
+{
+ // <form id='f'></form><form><element form='foo'></form>
+ aForms[0].id = 'f';
+ aForms[1].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', 'foo');
+ aElements[0].setAttribute('form', 'f');
+
+ return [[aForms[0], aForms[1]], [[aElements[0]],[]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form
+ * element and has @form set to a list of ids containing one valid form, then
+ * it does not have a form owner.
+ */
+function test8(aForms, aElements)
+{
+ // <form id='f'></form><form><element form='f foo'></form>
+ aForms[0].id = 'f';
+ aForms[1].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', 'f foo');
+
+ return [[aForms[0], aForms[1]], [[],[]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form
+ * element and has @form set to a form id which is valid in a case insensitive
+ * way, then it does not have a form owner.
+ */
+function test9(aForms, aElements)
+{
+ // <form id='f'></form><form><element form='F'></form>
+ aForms[0].id = 'f';
+ aForms[1].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', 'F');
+
+ return [[aForms[0], aForms[1]], [[],[]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form
+ * element and has @form set to a form id which is not a valid id, then it's
+ * form owner is it does not have a form owner.
+ */
+function test10(aForms, aElements)
+{
+ // <form id='F'></form><form><element form='f'></form>
+ aForms[0].id = 'F';
+ aForms[1].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', 'f');
+
+ return [[aForms[0], aForms[1]], [[],[]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form
+ * element and has @form set to a form id which is not a valid id, then it's
+ * form owner is it does not have a form owner.
+ */
+function test11(aForms, aElements)
+{
+ // <form id='foo bar'></form><form><element form='foo bar'></form>
+ aForms[0].id = 'foo bar';
+ aForms[1].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', 'foo bar');
+
+ return [[aForms[0], aForms[1]], [[aElements[0]],[]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form
+ * element and has @form set to a valid form id and the form id change, then
+ * it does not have a form owner.
+ */
+function test12(aForms, aElements)
+{
+ // <form id='f'></form><form><element form='f'></form>
+ aForms[0].id = 'f';
+ aForms[1].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', 'f');
+ aForms[0].id = 'foo';
+
+ return [[aForms[0], aForms[1]], [[],[]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form
+ * element and has @form set to an invalid form id and the form id change to a
+ * valid one, then it's form owner is the form which has the id.
+ */
+function test13(aForms, aElements)
+{
+ // <form id='foo'></form><form><element form='f'></form>
+ aForms[0].id = 'foo';
+ aForms[1].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', 'f');
+ aForms[0].id = 'f';
+
+ return [[aForms[0], aForms[1]], [[aElements[0]],[]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form
+ * element and has @form set to a valid form id and a form with the same id is
+ * inserted before in the tree, then it's form owner is the form which has the
+ * id.
+ */
+function test14(aForms, aElements)
+{
+ // <form id='f'></form><form><element form='f'></form>
+ aForms[0].id = 'f';
+ aForms[1].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', 'f');
+ aForms[2].id = 'f';
+
+ document.getElementById('content').insertBefore(aForms[2], aForms[0]);
+
+ return [[aForms[0], aForms[1], aForms[2]], [[],[],[aElements[0]]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form
+ * element and has @form set to a valid form id and an element with the same id is
+ * inserted before in the tree, then it does not have a form owner.
+ */
+function test15(aForms, aElements)
+{
+ // <form id='f'></form><form><element form='f'></form>
+ aForms[0].id = 'f';
+ aForms[1].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', 'f');
+ aElements[1].id = 'f';
+
+ document.getElementById('content').insertBefore(aElements[1], aForms[0]);
+
+ return [[aForms[0], aForms[1]], [[],[]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form
+ * element and has @form set to a valid form id and the form is removed from
+ * the tree, then it does not have a form owner.
+ */
+function test16(aForms, aElements)
+{
+ // <form id='f'></form><form><element form='f'></form>
+ aForms[0].id = 'f';
+ aForms[1].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', 'f');
+ aElements[1].id = 'f';
+
+ document.getElementById('content').removeChild(aForms[0]);
+
+ return [[aForms[0], aForms[1]], [[],[]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form element
+ * and has @form set to the empty string, it does not have a form owner.
+ */
+function test17(aForms, aElements)
+{
+ // <form><element form=''></form>
+ aForms[0].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', '');
+
+ return [[aForms[0]], [[]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form element
+ * and has @form set to the empty string, it does not have a form owner even if
+ * it's parent has its id equals to the empty string.
+ */
+function test18(aForms, aElements)
+{
+ // <form id=''><element form=''></form>
+ aForms[0].id = '';
+ aForms[0].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', '');
+
+ return [[aForms[0]], [[]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form element
+ * and has @form set to a valid form id and the element is being moving inside
+ * it's parent, it's form owner will remain the form with the id.
+ */
+function test19(aForms, aElements)
+{
+ // <form id='f'></form><form><element form='f'><element></form>
+ aForms[0].id = 'f';
+ aForms[1].appendChild(aElements[0]);
+ aForms[1].appendChild(aElements[1]);
+ aElements[0].setAttribute('form', 'f');
+ aForms[1].appendChild(aElements[0]);
+
+ return [[aForms[0],aForms[1]],[[aElements[0]],[aElements[1]]]];
+}
+
+/**
+ * This function test that, when an element is a descendant of a form element
+ * and has @form set to a valid form id and the element is being moving inside
+ * another form, it's form owner will remain the form with the id.
+ */
+function test20(aForms, aElements)
+{
+ // <form id='f'></form><form><element form='f'><element></form>
+ aForms[0].id = 'f';
+ aForms[1].appendChild(aElements[0]);
+ aForms[1].appendChild(aElements[1]);
+ aElements[0].setAttribute('form', 'f');
+ aForms[2].appendChild(aElements[0]);
+
+ return [[aForms[0],aForms[1],aForms[2]],[[aElements[0]],[aElements[1]],[]]];
+}
+
+/**
+ * This function test that when removing a form, the elements with a @form set
+ * will be correctly removed from there form owner.
+ */
+function test21(aForms, aElements)
+{
+ // <form id='f'><form><form><element form='f'></form>
+ aForms[0].id = 'f';
+ aForms[1].appendChild(aElements[0]);
+ aElements[0].setAttribute('form', 'f');
+ document.getElementById('content').removeChild(aForms[1]);
+
+ return [[aForms[0]],[[]]];
+}
+
+var functions = [
+ test0a, test0b,
+ test1, test2, test3, test4, test5, test6, test7, test8, test9,
+ test10, test11, test12, test13, test14, test15, test16, test17, test18, test19,
+ test20, test21,
+];
+
+// Global variable to have an easy access to <div id='content'>.
+var content = document.getElementById('content');
+
+// Initializing the needed elements.
+var forms = [
+ document.createElement('form'),
+ document.createElement('form'),
+ document.createElement('form'),
+];
+
+var elementNames = [
+ 'button', 'fieldset', 'input', 'label', 'object', 'output', 'select',
+ 'textarea'
+];
+
+var todoElements = [
+ ['keygen', 'Keygen'],
+];
+
+for (var e of todoElements) {
+ var node = document.createElement(e[0]);
+ var nodeString = HTMLElement.prototype.toString.apply(node);
+ nodeString = nodeString.replace(/Element[\] ].*/, "Element");
+ todo_is(nodeString, "[object HTML" + e[1] + "Element",
+ e[0] + " should not be implemented");
+}
+
+for (var name of elementNames) {
+ var elements = [
+ document.createElement(name),
+ document.createElement(name),
+ ];
+
+ for (var func of functions) {
+ // Clean-up.
+ while (content.firstChild) {
+ content.removeChild(content.firstChild);
+ }
+ for (form of forms) {
+ content.appendChild(form);
+ form.removeAttribute('id');
+ }
+ for (e of elements) {
+ content.appendChild(e);
+ e.removeAttribute('form');
+ is(e.form, null, "The element should not have a form owner");
+ }
+
+ // Calling the test.
+ var results = func(forms, elements);
+
+ // Checking the results.
+ var formsList = results[0];
+ for (var i=0; i<formsList.length; ++i) {
+ var elementsList = results[1][i];
+ if (name != 'label' && name != 'meter' && name != 'progress') {
+ is(formsList[i].elements.length, elementsList.length,
+ "The form should contain " + elementsList.length + " elements");
+ }
+ for (var j=0; j<elementsList.length; ++j) {
+ if (name != 'label' && name != 'meter' && name != 'progress') {
+ is(formsList[i].elements[j], elementsList[j],
+ "The form should contain " + elementsList[j]);
+ }
+ if (name != 'label') {
+ is(elementsList[j].form, formsList[i],
+ "The form owner should be the form associated to the list");
+ }
+ }
+ }
+ }
+
+ // Cleaning-up.
+ for (e of elements) {
+ e.parentNode.removeChild(e);
+ e = null;
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_form_attribute-2.html b/dom/html/test/forms/test_form_attribute-2.html
new file mode 100644
index 000000000..96c706b3a
--- /dev/null
+++ b/dom/html/test/forms/test_form_attribute-2.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=588683
+-->
+<head>
+ <title>Test for form attributes 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=588683">Mozilla Bug 588683</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <form id='a'>
+ <form id='b'>
+ <input id='i' form='b'>
+ <script>
+ is(document.getElementById('i').form, document.getElementById('b'),
+ "While parsing, the form property should work.");
+ </script>
+ </form>
+ </form>
+ <form id='c'>
+ <form id='d'>
+ <input id='i2' form='c'>
+ <script>
+ is(document.getElementById('i2').form, document.getElementById('c'),
+ "While parsing, the form property should work.");
+ </script>
+ </form>
+ </form>
+ <!-- Let's tests without @form -->
+ <form id='e'>
+ <form id='f'>
+ <input id='i3'>
+ <script>
+ // bug 589073
+ todo_is(document.getElementById('i3').form, document.getElementById('f'),
+ "While parsing, the form property should work.");
+ </script>
+ </form>
+ </form>
+ <form id='g'>
+ <input id='i4'>
+ <script>
+ is(document.getElementById('i4').form, document.getElementById('g'),
+ "While parsing, the form property should work.");
+ </script>
+ </form>
+</div>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_form_attribute-3.html b/dom/html/test/forms/test_form_attribute-3.html
new file mode 100644
index 000000000..a8d4fabef
--- /dev/null
+++ b/dom/html/test/forms/test_form_attribute-3.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=588683
+-->
+<head>
+ <title>Test for form attributes 3</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=588683">Mozilla Bug 588683</a>
+<p id="display"></p>
+<div id="content">
+ <form id='f'>
+ <input name='e1'>
+ </form>
+ <form id='f2'>
+ <input name='e2'>
+ <input id='i3' form='f'
+ onfocus="var catched=false;
+ try { e1; } catch(e) { catched=true; }
+ ok(!catched, 'e1 should be in the scope of i3');
+ catched = false;
+ try { e2; } catch(e) { catched=true; }
+ ok(catched, 'e2 should not be in the scope of i3');
+ document.getElementById('i4').focus();"
+ >
+ <input id='i4' form='f2'
+ onfocus="var catched=false;
+ try { e2; } catch(e) { catched=true; }
+ ok(!catched, 'e2 should be in the scope of i4');
+ document.getElementById('i5').focus();"
+ >
+ <input id='i5'
+ onfocus="var catched=false;
+ try { e2; } catch(e) { catched=true; }
+ ok(!catched, 'e2 should be in the scope of i5');
+ document.getElementById('i6').focus();"
+ >
+ </form>
+ <input id='i6' form='f'
+ onfocus="var catched=false;
+ try { e1; } catch(e) { catched=true; }
+ ok(!catched, 'e1 should be in the scope of i6');
+ document.getElementById('i7').focus();"
+ >
+ <input id='i7' form='f2'
+ onfocus="var catched=false;
+ try { e2; } catch(e) { catched=true; }
+ ok(!catched, 'e2 should be in the scope of i7');
+ this.blur();
+ SimpleTest.finish();"
+ >
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for form attributes 3 **/
+
+SimpleTest.waitForExplicitFinish();
+
+document.getElementById('i3').focus();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_form_attribute-4.html b/dom/html/test/forms/test_form_attribute-4.html
new file mode 100644
index 000000000..67484f72e
--- /dev/null
+++ b/dom/html/test/forms/test_form_attribute-4.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=588683
+-->
+<head>
+ <title>Test for form attributes 4</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=588683">Mozilla Bug 588683</a>
+<p id="display"></p>
+<div id="content" style='display:none;'>
+ <form id='f'>
+ </form>
+ <table id='t'>
+ <form id='f2'>
+ <tr><td><input id='i1'></td></tr>
+ <tr><td><input id='i2' form='f'></td></tr>
+ </form>
+ </table>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for form attributes 4 **/
+
+var table = document.getElementById('t');
+var i1 = document.getElementById('i1');
+var i2 = document.getElementById('i2');
+
+is(i1.form, document.getElementById('f2'),
+ "i1 form should be it's parent");
+is(i2.form, document.getElementById('f'),
+ "i1 form should be the form with the id in @form");
+
+table.removeChild(document.getElementById('f2'));
+is(i1, document.getElementById('i1'),
+ "i1 should still be in the document");
+is(i1.form, null, "i1 should not have any form owner");
+is(i2.form, document.getElementById('f'),
+ "i1 form should be the form with the id in @form");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_form_attributes_reflection.html b/dom/html/test/forms/test_form_attributes_reflection.html
new file mode 100644
index 000000000..0c64ac4ea
--- /dev/null
+++ b/dom/html/test/forms/test_form_attributes_reflection.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLFormElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="../reflect.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">
+
+/** Test for HTMLFormElement attributes reflection **/
+
+// .acceptCharset
+reflectString({
+ element: document.createElement("form"),
+ attribute: { idl: "acceptCharset", content: "accept-charset" },
+ otherValues: [ "ISO-8859-1", "UTF-8" ],
+});
+
+// TODO: action (URL). But note that we currently reflect it weirdly; see
+// dom/html/test/test_bug607145.html
+
+// .autocomplete
+reflectLimitedEnumerated({
+ element: document.createElement("form"),
+ attribute: "autocomplete",
+ validValues: [ "on", "off" ],
+ invalidValues: [ "", "foo", "tulip", "default" ],
+ defaultValue: "on",
+});
+
+// .enctype
+reflectLimitedEnumerated({
+ element: document.createElement("form"),
+ attribute: "enctype",
+ validValues: [ "application/x-www-form-urlencoded", "multipart/form-data",
+ "text/plain" ],
+ invalidValues: [ "", "foo", "tulip", "multipart/foo" ],
+ defaultValue: "application/x-www-form-urlencoded"
+});
+
+// .encoding
+reflectLimitedEnumerated({
+ element: document.createElement("form"),
+ attribute: { idl: "encoding", content: "enctype" },
+ validValues: [ "application/x-www-form-urlencoded", "multipart/form-data",
+ "text/plain" ],
+ invalidValues: [ "", "foo", "tulip", "multipart/foo" ],
+ defaultValue: "application/x-www-form-urlencoded"
+});
+
+// .method
+reflectLimitedEnumerated({
+ element: document.createElement("form"),
+ attribute: "method",
+ validValues: [ "get", "post" ],
+ invalidValues: [ "", "foo", "tulip" ],
+ defaultValue: "get"
+});
+
+// .name
+reflectString({
+ element: document.createElement("form"),
+ attribute: "name",
+});
+
+// .noValidate
+reflectBoolean({
+ element: document.createElement("form"),
+ attribute: "noValidate",
+});
+
+// .target
+reflectString({
+ element: document.createElement("form"),
+ attribute: "target",
+ otherValues: [ "_blank", "_self", "_parent", "_top" ],
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_form_named_getter_dynamic.html b/dom/html/test/forms/test_form_named_getter_dynamic.html
new file mode 100644
index 000000000..4a1976845
--- /dev/null
+++ b/dom/html/test/forms/test_form_named_getter_dynamic.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=377413
+-->
+<head>
+ <title>Test for Bug 377413</title>
+ <script type="text/javascript" src="/resources/testharness.js"></script>
+ <link rel='stylesheet' href='/resources/testharness.css'>
+ <script type="text/javascript" src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=377413">Mozilla Bug 377413</a>
+<p id="log"></p>
+<div id="content">
+ <form>
+ <table>
+ <tbody>
+ </tbody>
+ </table>
+ </form>
+</div>
+
+<script type="text/javascript">
+
+/** Tests for Bug 377413 **/
+var tb = document.getElementsByTagName('tbody')[0];
+
+test(function(){
+ tb.innerHTML = '<tr><td><input name="fooboo"></td></tr>';
+ document.forms[0].fooboo.value = 'testme';
+ document.getElementsByTagName('table')[0].deleteRow(0);
+ assert_equals(document.forms[0].fooboo, undefined);
+}, "no element reference after deleting it with deleteRow()");
+
+test(function(){
+ var b = tb.appendChild(document.createElement('tr')).appendChild(document.createElement('td')).appendChild(document.createElement('button'));
+ b.name = b.value = 'boofoo';
+ assert_equals(document.forms[0].elements[0].value, 'boofoo');
+}, 'element value set correctly');
+
+test(function(){
+ assert_true('boofoo' in document.forms[0]);
+}, 'element name has created property on form');
+
+test(function(){
+ tb.innerHTML = '';
+ assert_false('boofoo' in document.forms[0]);
+}, "no element reference after deleting it by setting innerHTML");
+
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_formaction_attribute.html b/dom/html/test/forms/test_formaction_attribute.html
new file mode 100644
index 000000000..24af8b2db
--- /dev/null
+++ b/dom/html/test/forms/test_formaction_attribute.html
@@ -0,0 +1,176 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=566160
+-->
+<head>
+ <title>Test for Bug 566160</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=566160">Mozilla Bug 566160</a>
+<p id="display"></p>
+<style>
+ iframe { width: 130px; height: 100px;}
+</style>
+<iframe name='frame1' id='frame1'></iframe>
+<iframe name='frame2' id='frame2'></iframe>
+<iframe name='frame3' id='frame3'></iframe>
+<iframe name='frame3bis' id='frame3bis'></iframe>
+<iframe name='frame4' id='frame4'></iframe>
+<iframe name='frame5' id='frame5'></iframe>
+<iframe name='frame6' id='frame6'></iframe>
+<iframe name='frame7' id='frame7'></iframe>
+<div id="content">
+ <!-- submit controls with formaction that are validated with a CLICK -->
+ <form target="frame1" action="data:text/html,FAIL" method="GET">
+ <input name='foo' value='foo'>
+ <input type='submit' id='is' formaction="data:text/html,">
+ </form>
+ <form target="frame2" action="data:text/html,FAIL" method="GET">
+ <input name='bar' value='bar'>
+ <input type='image' id='ii' formaction="data:text/html,">
+ </form>
+ <form target="frame3" action="data:text/html,FAIL" method="GET">
+ <input name='tulip' value='tulip'>
+ <button type='submit' id='bs' formaction="data:text/html,">submit</button>
+ </form>
+ <form target="frame3bis" action="data:text/html,FAIL" method="GET">
+ <input name='tulipbis' value='tulipbis'>
+ <button type='submit' id='bsbis' formaction="data:text/html,">submit</button>
+ </form>
+
+ <!-- submit controls with formaction that are validated with ENTER -->
+ <form target="frame4" action="data:text/html,FAIL" method="GET">
+ <input name='footulip' value='footulip'>
+ <input type='submit' id='is2' formaction="data:text/html,">
+ </form>
+ <form target="frame5" action="data:text/html,FAIL" method="GET">
+ <input name='foobar' value='foobar'>
+ <input type='image' id='ii2' formaction="data:text/html,">
+ </form>
+ <form target="frame6" action="data:text/html,FAIL" method="GET">
+ <input name='tulip2' value='tulip2'>
+ <button type='submit' id='bs2' formaction="data:text/html,">submit</button>
+ </form>
+
+ <!-- check that when submitting a from from an element
+ which is not a submit control, @formaction isn't used -->
+ <form target='frame7' action="data:text/html," method="GET">
+ <input id='enter' name='input' value='enter' formaction="data:text/html,FAIL">
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 566160 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+var gTestResults = {
+ frame1: "data:text/html,?foo=foo",
+ frame2: "data:text/html,?bar=bar&x=0&y=0",
+ frame3: "data:text/html,?tulip=tulip",
+ frame3bis: "data:text/html,?tulipbis=tulipbis",
+ frame4: "data:text/html,?footulip=footulip",
+ frame5: "data:text/html,?foobar=foobar&x=0&y=0",
+ frame6: "data:text/html,?tulip2=tulip2",
+ frame7: "data:text/html,?input=enter",
+};
+
+var gPendingLoad = 0; // Has to be set after depending on the frames number.
+
+function runTests()
+{
+ // We add a load event for the frames which will be called when the forms
+ // will be submitted.
+ var frames = [ document.getElementById('frame1'),
+ document.getElementById('frame2'),
+ document.getElementById('frame3'),
+ document.getElementById('frame3bis'),
+ document.getElementById('frame4'),
+ document.getElementById('frame5'),
+ document.getElementById('frame6'),
+ document.getElementById('frame7'),
+ ];
+ gPendingLoad = frames.length;
+
+ for (var i=0; i<frames.length; i++) {
+ frames[i].setAttribute('onload', "frameLoaded(this);");
+ }
+
+ /**
+ * We are going to focus each element before interacting with either for
+ * simulating the ENTER key (synthesizeKey) or a click (synthesizeMouse) or
+ * using .click(). This because it may be needed (ENTER) and because we want
+ * to have the element visible in the iframe.
+ *
+ * Focusing the first element (id='is') is launching the tests.
+ */
+ document.getElementById('is').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeMouse(document.getElementById('is'), 5, 5, {});
+ document.getElementById('ii').focus();
+ }, false);
+
+ document.getElementById('ii').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeMouse(document.getElementById('ii'), 5, 5, {});
+ document.getElementById('bs').focus();
+ }, false);
+
+ document.getElementById('bs').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeMouse(document.getElementById('bs'), 5, 5, {});
+ document.getElementById('bsbis').focus();
+ }, false);
+
+ document.getElementById('bsbis').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ document.getElementById('bsbis').click();
+ document.getElementById('is2').focus();
+ }, false);
+
+ document.getElementById('is2').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('ii2').focus();
+ }, false);
+
+ document.getElementById('ii2').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('bs2').focus();
+ }, false);
+
+ document.getElementById('bs2').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('enter').focus();
+ }, false);
+
+ document.getElementById('enter').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ }, false);
+
+ document.getElementById('is').focus();
+}
+
+function frameLoaded(aFrame) {
+ // Check if formaction/action has the correct behavior.
+ is(aFrame.contentWindow.location.href, gTestResults[aFrame.name],
+ "the action attribute doesn't have the correct behavior");
+
+ if (--gPendingLoad == 0) {
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_formnovalidate_attribute.html b/dom/html/test/forms/test_formnovalidate_attribute.html
new file mode 100644
index 000000000..067060a5c
--- /dev/null
+++ b/dom/html/test/forms/test_formnovalidate_attribute.html
@@ -0,0 +1,142 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=589696
+-->
+<head>
+ <title>Test for Bug 589696</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=589696">Mozilla Bug 589696</a>
+<p id="display"></p>
+<iframe style='width:50px; height: 50px;' name='t'></iframe>
+<div id="content">
+ <!-- Next forms should not submit because formnovalidate isn't set on the
+ element used for the submission. -->
+ <form target='t' action='data:text/html,'>
+ <input id='av' required>
+ <input type='submit' formnovalidate>
+ <input id='a' type='submit'>
+ </form>
+ <form target='t' action='data:text/html,'>
+ <input id='bv' type='checkbox' required>
+ <button type='submit' formnovalidate></button>
+ <button id='b' type='submit'></button>
+ </form>
+ <!-- Next form should not submit because formnovalidate only applies for
+ submit controls. -->
+ <form target='t' action='data:text/html,'>
+ <input id='c' required formnovalidate>
+ </form>
+ <!--- Next forms should submit without any validation check. -->
+ <form target='t' action='data:text/html,'>
+ <input id='dv' required>
+ <input id='d' type='submit' formnovalidate>
+ </form>
+ <form target='t' action='data:text/html,'>
+ <input id='ev' type='checkbox' required>
+ <button id='e' type='submit' formnovalidate></button>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 589696 **/
+
+var os = SpecialPowers.Cc['@mozilla.org/observer-service;1']
+ .getService(SpecialPowers.Ci.nsIObserverService);
+var observers = os.enumerateObservers("invalidformsubmit");
+
+/**
+ * formnovalidate should prevent form validation if set on the submit control
+ * used to submit the form.
+ *
+ * The following test should not be done if there is no observer for
+ * "invalidformsubmit" because the form submission will not be canceled in that
+ * case.
+ */
+
+if (observers.hasMoreElements()) {
+ document.getElementById('av').addEventListener("invalid", function(aEvent) {
+ aEvent.target.removeAttribute("invalid", arguments.callee, false);
+ ok(true, "formnovalidate should not apply on if not set on the submit " +
+ "control used for the submission");
+ document.getElementById('b').click();
+ }, false);
+
+ document.getElementById('bv').addEventListener("invalid", function(aEvent) {
+ aEvent.target.removeAttribute("invalid", arguments.callee, false);
+ ok(true, "formnovalidate should not apply on if not set on the submit " +
+ "control used for the submission");
+ var c = document.getElementById('c');
+ c.focus();
+ synthesizeKey("KEY_Enter", { code: "Enter" });
+ }, false);
+
+ document.getElementById('c').addEventListener("invalid", function(aEvent) {
+ aEvent.target.removeAttribute("invalid", arguments.callee, false);
+ ok(true, "formnovalidate should only apply on submit controls");
+ document.getElementById('d').click();
+ }, false);
+
+ document.forms[3].addEventListener("submit", function(aEvent) {
+ aEvent.target.removeAttribute("submit", arguments.callee, false);
+ ok(true, "formnovalidate applies if set on the submit control used for the submission");
+ document.getElementById('e').click();
+ }, false);
+
+ document.forms[4].addEventListener("submit", function(aEvent) {
+ aEvent.target.removeAttribute("submit", arguments.callee, false);
+ ok(true, "formnovalidate applies if set on the submit control used for the submission");
+ SimpleTest.executeSoon(SimpleTest.finish);
+ }, false);
+
+ /**
+ * We have to be sure invalid events behave as expected.
+ * They should be sent before the submit event so we can just create a test
+ * failure if we got one when unexpected. All of them should be caught if
+ * sent.
+ * At worst, we got random green which isn't harmful.
+ * If expected, they will be part of the chain reaction.
+ */
+ function unexpectedInvalid(aEvent)
+ {
+ aEvent.target.removeAttribute("invalid", unexpectedInvalid, false);
+ ok(false, "invalid event should not be sent");
+ }
+
+ document.getElementById('dv').addEventListener("invalid", unexpectedInvalid, false);
+ document.getElementById('ev').addEventListener("invalid", unexpectedInvalid, false);
+
+ /**
+ * Some submission have to be canceled. In that case, the submit events should
+ * not be sent.
+ * Same behavior as unexpected invalid events.
+ */
+ function unexpectedSubmit(aEvent)
+ {
+ aEvent.target.removeAttribute("submit", unexpectedSubmit, false);
+ ok(false, "submit event should not be sent");
+ }
+
+ document.forms[0].addEventListener("submit", unexpectedSubmit, false);
+ document.forms[1].addEventListener("submit", unexpectedSubmit, false);
+ document.forms[2].addEventListener("submit", unexpectedSubmit, false);
+
+ SimpleTest.waitForExplicitFinish();
+
+ // This is going to call all the tests (with a chain reaction).
+ SimpleTest.waitForFocus(function() {
+ document.getElementById('a').click();
+ });
+} else {
+ todo(false, "No 'invalidformsubmit' observers. Skip test.");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_attributes_reflection.html b/dom/html/test/forms/test_input_attributes_reflection.html
new file mode 100644
index 000000000..6a0eaf225
--- /dev/null
+++ b/dom/html/test/forms/test_input_attributes_reflection.html
@@ -0,0 +1,275 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLInputElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="../reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for HTMLInputElement attributes reflection **/
+
+// TODO: maybe make those reflections be tested against all input types.
+
+function testWidthHeight(attr) {
+ var element = document.createElement('input');
+ is(element[attr], 0, attr + ' always returns 0 if not type=image');
+ element.setAttribute(attr, '42');
+ is(element[attr], 0, attr + ' always returns 0 if not type=image');
+ is(element.getAttribute(attr), '42');
+ element[attr] = 0;
+ is(element.getAttribute(attr), '0', 'setting ' + attr + ' changes the content attribute');
+ element[attr] = 12;
+ is(element.getAttribute(attr), '12', 'setting ' + attr + ' changes the content attribute');
+
+ element.removeAttribute(attr);
+ is(element.getAttribute(attr), null);
+
+ element = document.createElement('input');
+ element.type = 'image';
+ document.getElementById('content').appendChild(element);
+ isnot(element[attr], 0, attr + ' represents the dimension of the element if type=image');
+
+ element.setAttribute(attr, '42');
+ isnot(element[attr], 0, attr + ' represents the dimension of the element if type=image');
+ isnot(element[attr], 42, attr + ' represents the dimension of the element if type=image');
+ is(element.getAttribute(attr), '42');
+ element[attr] = 0;
+ is(element.getAttribute(attr), '0', 'setting ' + attr + ' changes the content attribute');
+ element[attr] = 12;
+ is(element.getAttribute(attr), '12', 'setting ' + attr + ' changes the content attribute');
+
+ element.removeAttribute(attr);
+ is(element.getAttribute(attr), null);
+}
+
+// .accept
+reflectString({
+ element: document.createElement("input"),
+ attribute: "accept",
+ otherValues: [ "audio/*", "video/*", "image/*", "image/png",
+ "application/msword", "appplication/pdf" ],
+});
+
+// .alt
+reflectString({
+ element: document.createElement("input"),
+ attribute: "alt",
+});
+
+// .autocomplete
+reflectLimitedEnumerated({
+ element: document.createElement("input"),
+ attribute: "autocomplete",
+ validValues: [ "on", "off" ],
+ invalidValues: [ "", "default", "foo", "tulip" ],
+});
+
+// .autofocus
+reflectBoolean({
+ element: document.createElement("input"),
+ attribute: "autofocus",
+});
+
+// .defaultChecked
+reflectBoolean({
+ element: document.createElement("input"),
+ attribute: { idl: "defaultChecked", content: "checked" },
+});
+
+// .checked doesn't reflect a content attribute.
+
+// .dirName
+todo("dirName" in document.createElement("input"),
+ "dirName isn't implemented yet");
+
+// .disabled
+reflectBoolean({
+ element: document.createElement("input"),
+ attribute: "disabled",
+});
+
+// TODO: form (HTMLFormElement)
+// TODO: files (FileList)
+// TODO: formAction (URL). But note that we currently reflect it weirdly; see
+// dom/html/test/test_bug607145.html
+
+// .formEnctype
+reflectLimitedEnumerated({
+ element: document.createElement("input"),
+ attribute: "formEnctype",
+ validValues: [ "application/x-www-form-urlencoded", "multipart/form-data",
+ "text/plain" ],
+ invalidValues: [ "", "foo", "tulip", "multipart/foo" ],
+ defaultValue: { invalid: "application/x-www-form-urlencoded", missing: "" }
+});
+
+// .formMethod
+reflectLimitedEnumerated({
+ element: document.createElement("input"),
+ attribute: "formMethod",
+ validValues: [ "get", "post" ],
+ invalidValues: [ "", "foo", "tulip" ],
+ defaultValue: { invalid: "get", missing: "" }
+});
+
+// .formNoValidate
+reflectBoolean({
+ element: document.createElement("input"),
+ attribute: "formNoValidate",
+});
+
+// .formTarget
+reflectString({
+ element: document.createElement("input"),
+ attribute: "formTarget",
+ otherValues: [ "_blank", "_self", "_parent", "_top" ],
+});
+
+// .height
+testWidthHeight('height');
+
+// .indeterminate doesn't reflect a content attribute.
+
+// .inputmode
+if (SpecialPowers.getBoolPref("dom.forms.inputmode")) {
+reflectLimitedEnumerated({
+ element: document.createElement("input"),
+ attribute: "inputMode",
+ validValues: [ "numeric", "digit", "uppercase", "lowercase", "titlecase", "autocapitalized", "auto" ],
+ invalidValues: [ "", "foo", "tulip" ],
+ defaultValue: "auto"
+});
+}
+
+// TODO: list (HTMLElement)
+
+// .max
+reflectString({
+ element: document.createElement('input'),
+ attribute: 'max',
+});
+
+// .maxLength
+reflectInt({
+ element: document.createElement("input"),
+ attribute: "maxLength",
+ nonNegative: true,
+});
+
+// .min
+reflectString({
+ element: document.createElement('input'),
+ attribute: 'min',
+});
+
+// .multiple
+reflectBoolean({
+ element: document.createElement("input"),
+ attribute: "multiple",
+});
+
+// .name
+reflectString({
+ element: document.createElement("input"),
+ attribute: "name",
+ otherValues: [ "isindex", "_charset_" ],
+});
+
+// .pattern
+reflectString({
+ element: document.createElement("input"),
+ attribute: "pattern",
+ otherValues: [ "[0-9][A-Z]{3}" ],
+});
+
+// .placeholder
+reflectString({
+ element: document.createElement("input"),
+ attribute: "placeholder",
+ otherValues: [ "foo\nbar", "foo\rbar", "foo\r\nbar" ],
+});
+
+// .readOnly
+reflectBoolean({
+ element: document.createElement("input"),
+ attribute: "readOnly",
+});
+
+// .required
+reflectBoolean({
+ element: document.createElement("input"),
+ attribute: "required",
+});
+
+// .size
+reflectUnsignedInt({
+ element: document.createElement("input"),
+ attribute: "size",
+ nonZero: true,
+ defaultValue: 20,
+});
+
+// .src (URL)
+reflectURL({
+ element: document.createElement('input'),
+ attribute: 'src',
+});
+
+// .step
+reflectString({
+ element: document.createElement('input'),
+ attribute: 'step',
+});
+
+// .type
+reflectLimitedEnumerated({
+ element: document.createElement("input"),
+ attribute: "type",
+ validValues: [ "hidden", "text", "search", "tel", "url", "email", "password",
+ "checkbox", "radio", "file", "submit", "image", "reset",
+ "button", "date", "time", "number", "range", "color", "month",
+ "week", "datetime-local" ],
+ invalidValues: [ "this-is-probably-a-wrong-type", "", "tulip" ],
+ defaultValue: "text"
+});
+
+// .defaultValue
+reflectString({
+ element: document.createElement("input"),
+ attribute: { idl: "defaultValue", content: "value" },
+ otherValues: [ "foo\nbar", "foo\rbar", "foo\r\nbar" ],
+});
+
+// .value doesn't reflect a content attribute.
+
+// .valueAsDate
+is("valueAsDate" in document.createElement("input"), true,
+ "valueAsDate should be available");
+
+// Deeper check will be done with bug 763305.
+is('valueAsNumber' in document.createElement("input"), true,
+ "valueAsNumber should be available");
+
+// .selectedOption
+todo("selectedOption" in document.createElement("input"),
+ "selectedOption isn't implemented yet");
+
+// .width
+testWidthHeight('width');
+
+// .willValidate doesn't reflect a content attribute.
+// .validity doesn't reflect a content attribute.
+// .validationMessage doesn't reflect a content attribute.
+// .labels doesn't reflect a content attribute.
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_autocomplete.html b/dom/html/test/forms/test_input_autocomplete.html
new file mode 100644
index 000000000..2aaeac020
--- /dev/null
+++ b/dom/html/test/forms/test_input_autocomplete.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html>
+<!--
+Test @autocomplete on <input>
+-->
+<head>
+ <title>Test for &lt;input autocomplete='…'&gt;</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script>
+"use strict";
+
+var values = [
+ // @autocomplete content attribute, expected IDL attribute value
+
+ // Missing or empty attribute
+ [undefined, ""],
+ ["", ""],
+
+ // One token
+ ["on", "on"],
+ ["On", "on"],
+ ["off", "off"],
+ ["OFF", "off"],
+ ["username", "username"],
+ [" username ", "username"],
+ ["foobar", ""],
+
+ // Two tokens
+ ["on off", ""],
+ ["off on", ""],
+ ["username tel", ""],
+ ["tel username ", ""],
+ [" username tel ", ""],
+ ["tel mobile", ""],
+ ["tel shipping", ""],
+ ["shipping tel", "shipping tel"],
+ ["shipPING tel", "shipping tel"],
+ ["mobile tel", "mobile tel"],
+ [" MoBiLe TeL ", "mobile tel"],
+ ["XXX tel", ""],
+ ["XXX username", ""],
+
+ // Three tokens
+ ["billing invalid tel", ""],
+ ["___ mobile tel", ""],
+ ["mobile foo tel", ""],
+ ["mobile tel foo", ""],
+ ["tel mobile billing", ""],
+ ["billing mobile tel", "billing mobile tel"],
+ [" BILLing MoBiLE tEl ", "billing mobile tel"],
+ ["billing home tel", "billing home tel"],
+
+ // Four tokens (invalid)
+ ["billing billing mobile tel", ""],
+
+ // Five tokens (invalid)
+ ["billing billing billing mobile tel", ""],
+];
+
+var types = [undefined, "hidden", "text", "search"]; // Valid types for all non-multiline hints.
+
+function checkAutocompleteValues(field, type) {
+ for (var test of values) {
+ if (typeof(test[0]) === "undefined")
+ field.removeAttribute("autocomplete");
+ else
+ field.setAttribute("autocomplete", test[0]);
+ is(field.autocomplete, test[1], "Checking @autocomplete for @type=" + type + " of: " + test[0]);
+ is(field.autocomplete, test[1], "Checking cached @autocomplete for @type=" + type + " of: " + test[0]);
+ }
+}
+
+function start() {
+ var inputField = document.getElementById("input-field");
+ for (var type of types) {
+ // Switch the input type
+ if (typeof(type) === "undefined")
+ inputField.removeAttribute("type");
+ else
+ inputField.type = type;
+ checkAutocompleteValues(inputField, type || "");
+ }
+
+ var selectField = document.getElementById("select-field");
+ checkAutocompleteValues(selectField, "select");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["dom.forms.autocomplete.experimental", true]]}, start);
+</script>
+</head>
+
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <form>
+ <input id="input-field" />
+ <select id="select-field" />
+ </form>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_color_input_change_events.html b/dom/html/test/forms/test_input_color_input_change_events.html
new file mode 100644
index 000000000..d9e7558f8
--- /dev/null
+++ b/dom/html/test/forms/test_input_color_input_change_events.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=885996
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1234567</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="application/javascript;version=1.8">
+
+ /** Test that update() modifies the element value such as done() when it is
+ * not called as a concellation.
+ */
+
+ SimpleTest.waitForExplicitFinish();
+
+ var MockColorPicker = SpecialPowers.MockColorPicker;
+
+ var test = runTest();
+
+ SimpleTest.waitForFocus(function() {
+ test.next();
+ });
+
+ function runTest() {
+ MockColorPicker.init(window);
+ var element = null;
+
+ MockColorPicker.showCallback = function(picker, update) {
+ is(picker.initialColor, element.value);
+
+ var inputEvent = false;
+ var changeEvent = false;
+ element.oninput = function() {
+ inputEvent = true;
+ };
+ element.onchange = function() {
+ changeEvent = true;
+ };
+
+ if (element.dataset.type == 'update') {
+ update('#f00ba4');
+
+ is(inputEvent, true, 'input event should have been received');
+ is(changeEvent, false, 'change event should not have been received');
+
+ inputEvent = changeEvent = false;
+
+ is(element.value, '#f00ba4');
+
+ MockColorPicker.returnColor = '#f00ba7';
+ isnot(element.value, MockColorPicker.returnColor);
+ } else if (element.dataset.type == 'cancel') {
+ MockColorPicker.returnColor = '#bababa';
+ isnot(element.value, MockColorPicker.returnColor);
+ } else if (element.dataset.type == 'done') {
+ MockColorPicker.returnColor = '#098766';
+ isnot(element.value, MockColorPicker.returnColor);
+ } else if (element.dataset.type == 'noop-done') {
+ MockColorPicker.returnColor = element.value;
+ is(element.value, MockColorPicker.returnColor);
+ }
+
+ SimpleTest.executeSoon(function() {
+ if (element.dataset.type == 'cancel') {
+ isnot(element.value, MockColorPicker.returnColor);
+ is(inputEvent, false, 'no input event should have been sent');
+ is(changeEvent, false, 'no change event should have been sent');
+ } else if (element.dataset.type == 'noop-done') {
+ is(element.value, MockColorPicker.returnColor);
+ is(inputEvent, false, 'no input event should have been sent');
+ is(changeEvent, false, 'no change event should have been sent');
+ } else {
+ is(element.value, MockColorPicker.returnColor);
+ is(inputEvent, true, 'input event should have been sent');
+ is(changeEvent, true, 'change event should have been sent');
+ }
+
+ changeEvent = false;
+ element.blur();
+
+ setTimeout(function() {
+ is(changeEvent, false, "change event should not be fired on blur");
+ test.next();
+ });
+ });
+
+ return element.dataset.type == 'cancel' ? "" : MockColorPicker.returnColor;
+ };
+
+ for (var i = 0; i < document.getElementsByTagName('input').length; ++i) {
+ element = document.getElementsByTagName('input')[i];
+ element.focus();
+ synthesizeMouseAtCenter(element, {});
+ yield undefined;
+ };
+
+ MockColorPicker.cleanup();
+ SimpleTest.finish();
+ yield undefined;
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=885996">Mozilla Bug 885996</a>
+<p id="display"></p>
+<div id="content">
+ <input type='color' data-type='update'>
+ <input type='color' data-type='cancel'>
+ <input type='color' data-type='done'>
+ <input type='color' data-type='noop-done'>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_color_picker_initial.html b/dom/html/test/forms/test_input_color_picker_initial.html
new file mode 100644
index 000000000..4b6a45cbb
--- /dev/null
+++ b/dom/html/test/forms/test_input_color_picker_initial.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=885996
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1234567</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="application/javascript;version=1.8">
+
+ /** Test that the initial value of the nsIColorPicker is the current value of
+ the <input type='color'> element. **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ var MockColorPicker = SpecialPowers.MockColorPicker;
+
+ var test = runTest();
+
+ SimpleTest.waitForFocus(function() {
+ test.next();
+ });
+
+ function runTest() {
+ MockColorPicker.init(window);
+ var element = null;
+
+ MockColorPicker.showCallback = function(picker) {
+ is(picker.initialColor, element.value);
+ SimpleTest.executeSoon(function() {
+ test.next();
+ });
+ return "";
+ };
+
+ for (var i = 0; i < document.getElementsByTagName('input').length; ++i) {
+ element = document.getElementsByTagName('input')[i];
+ if (element.parentElement.id === 'dynamic-values') {
+ element.value = '#deadbe';
+ }
+ synthesizeMouseAtCenter(element, {});
+ yield undefined;
+ };
+
+ MockColorPicker.cleanup();
+ SimpleTest.finish();
+ yield undefined;
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=885996">Mozilla Bug 885996</a>
+<p id="display"></p>
+<div id="content">
+ <div id='valid-values'>
+ <input type='color' value='#ff00ff'>
+ <input type='color' value='#ab3275'>
+ <input type='color' value='#abcdef'>
+ <input type='color' value='#ABCDEF'>
+ </div>
+ <div id='invalid-values'>
+ <input type='color' value='ffffff'>
+ <input type='color' value='#abcdez'>
+ <input type='color' value='#0123456'>
+ </div>
+ <div id='dynamic-values'>
+ <input type='color' value='#ab4594'>
+ <input type='color' value='#984534'>
+ <input type='color' value='#f8b9a0'>
+ </div>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_color_picker_popup.html b/dom/html/test/forms/test_input_color_picker_popup.html
new file mode 100644
index 000000000..239f14014
--- /dev/null
+++ b/dom/html/test/forms/test_input_color_picker_popup.html
@@ -0,0 +1,140 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=885996
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1234567</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"/>
+ <style> body { font-family: serif } </style>
+ <script type="application/javascript;version=1.8">
+
+ /** Test the behaviour of the <input type='color'> when clicking on it from
+ different ways. **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ var MockColorPicker = SpecialPowers.MockColorPicker;
+
+ var test = runTest();
+ var testData = [
+ { id: 'normal', result: true },
+ { id: 'hidden', result: false },
+ { id: 'normal', type: 'untrusted', result: true },
+ { id: 'normal', type: 'prevent-default-1', result: false },
+ { id: 'normal', type: 'prevent-default-2', result: false },
+ { id: 'normal', type: 'click-method', result: true },
+ { id: 'normal', type: 'right-click', result: false },
+ { id: 'normal', type: 'middle-click', result: false },
+ { id: 'label-1', result: true },
+ { id: 'label-2', result: true },
+ { id: 'label-3', result: true },
+ { id: 'label-4', result: true },
+ { id: 'button-click', result: true },
+ { id: 'button-down', result: true },
+ { id: 'button-up', result: true },
+ { id: 'div-click', result: true },
+ { id: 'div-click-on-demand', result: true },
+ ];
+ var currentTest = null;
+
+ SimpleTest.waitForFocus(function() {
+ test.next();
+ });
+
+ function runTest() {
+ MockColorPicker.init(window);
+ var element = null;
+
+ MockColorPicker.showCallback = function(picker) {
+ ok(currentTest.result);
+ SimpleTest.executeSoon(function() {
+ test.next();
+ });
+ return "";
+ };
+
+ while (testData.length != 0) {
+ var currentTest = testData.shift();
+ element = document.getElementById(currentTest.id);
+
+ // To make sure we can actually click on the element.
+ element.focus();
+
+ switch (currentTest.type) {
+ case 'untrusted':
+ var e = document.createEvent('MouseEvents');
+ e.initEvent('click', true, false);
+ document.getElementById(element.dispatchEvent(e));
+ break;
+ case 'prevent-default-1':
+ element.onclick = function() {
+ return false;
+ };
+ element.click();
+ element.onclick = function() {};
+ break;
+ case 'prevent-default-2':
+ element.onclick = function(e) {
+ e.preventDefault();
+ };
+ element.click();
+ element.onclick = function() {};
+ break;
+ case 'click-method':
+ element.click();
+ break;
+ case 'right-click':
+ synthesizeMouseAtCenter(element, { button: 2 });
+ break;
+ case 'middle-click':
+ synthesizeMouseAtCenter(element, { button: 1 });
+ break;
+ default:
+ synthesizeMouseAtCenter(element, {});
+ }
+
+ if (!currentTest.result) {
+ setTimeout(function() {
+ setTimeout(function() {
+ ok(true);
+ SimpleTest.executeSoon(function() {
+ test.next();
+ });
+ });
+ });
+ }
+ yield undefined;
+ };
+
+ MockColorPicker.cleanup();
+ SimpleTest.finish();
+ yield undefined;
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=885996">Mozilla Bug 885996</a>
+<p id="display"></p>
+<div id="content">
+ <input type='color' id='normal'>
+ <input type='color' id='hidden' hidden>
+ <label id='label-1'>foo<input type='color'></label>
+ <label id='label-2' for='labeled-2'>foo</label><input id='labeled-2' type='color'></label>
+ <label id='label-3'>foo<input type='color'></label>
+ <label id='label-4' for='labeled-4'>foo</label><input id='labeled-4' type='color'></label>
+ <input id='by-button' type='color'>
+ <button id='button-click' onclick="document.getElementById('by-button').click();">click</button>
+ <button id='button-down' onclick="document.getElementById('by-button').click();">click</button>
+ <button id='button-up' onclick="document.getElementById('by-button').click();">click</button>
+ <div id='div-click' onclick="document.getElementById('by-button').click();">click</div>
+ <div id='div-click-on-demand' onclick="var i=document.createElement('input'); i.type='color'; i.click();">click</div>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_color_picker_update.html b/dom/html/test/forms/test_input_color_picker_update.html
new file mode 100644
index 000000000..c40791323
--- /dev/null
+++ b/dom/html/test/forms/test_input_color_picker_update.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=885996
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1234567</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"/>
+ <style> body { font-family: serif } </style>
+ <script type="application/javascript;version=1.8">
+
+ /** Test that update() modifies the element value such as done() when it is
+ * not called as a concellation.
+ */
+
+ SimpleTest.waitForExplicitFinish();
+
+ var MockColorPicker = SpecialPowers.MockColorPicker;
+
+ var test = runTest();
+
+ SimpleTest.waitForFocus(function() {
+ test.next();
+ });
+
+ function runTest() {
+ MockColorPicker.init(window);
+ var element = null;
+
+ MockColorPicker.showCallback = function(picker, update) {
+ is(picker.initialColor, element.value);
+
+ if (element.dataset.type == 'update') {
+ update('#f00ba4');
+ is(element.value, '#f00ba4');
+
+ MockColorPicker.returnColor = '#f00ba7';
+ isnot(element.value, MockColorPicker.returnColor);
+ } else if (element.dataset.type == 'cancel') {
+ MockColorPicker.returnColor = '#bababa';
+ isnot(element.value, MockColorPicker.returnColor);
+ } else if (element.dataset.type == 'done') {
+ MockColorPicker.returnColor = '#098766';
+ isnot(element.value, MockColorPicker.returnColor);
+ }
+
+ SimpleTest.executeSoon(function() {
+ if (element.dataset.type == 'cancel') {
+ isnot(element.value, MockColorPicker.returnColor);
+ } else {
+ is(element.value, MockColorPicker.returnColor);
+ }
+
+ test.next();
+ });
+
+ return element.dataset.type == 'cancel' ? "" : MockColorPicker.returnColor;
+ };
+
+ for (var i = 0; i < document.getElementsByTagName('input').length; ++i) {
+ element = document.getElementsByTagName('input')[i];
+ synthesizeMouseAtCenter(element, {});
+ yield undefined;
+ };
+
+ MockColorPicker.cleanup();
+ SimpleTest.finish();
+ yield undefined;
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=885996">Mozilla Bug 885996</a>
+<p id="display"></p>
+<div id="content">
+ <input type='color' data-type='update'>
+ <input type='color' data-type='cancel'>
+ <input type='color' data-type='done'>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_datetime_focus_blur.html b/dom/html/test/forms/test_input_datetime_focus_blur.html
new file mode 100644
index 000000000..5b8d95b25
--- /dev/null
+++ b/dom/html/test/forms/test_input_datetime_focus_blur.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
+-->
+<head>
+ <title>Test focus/blur behaviour for &lt;input type='time'&gt;</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=1288591">Mozilla Bug 1288591</a>
+<p id="display"></p>
+<div id="content">
+ <input id="input" type="time">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 1288591.
+ * This test checks whether date/time input types' .focus()/.blur() works
+ * correctly. This test also checks when focusing on an date/time input element,
+ * the focus is redirected to the anonymous text control, but the
+ * document.activeElement still returns date/time input element.
+ **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ test();
+ SimpleTest.finish();
+});
+
+function test() {
+ let time = document.getElementById("input");
+ time.focus();
+
+ // The active element returns the input type=time.
+ let activeElement = document.activeElement;
+ is(activeElement, time, "activeElement should be the time element");
+ is(activeElement.localName, "input", "activeElement should be an input element");
+ is(activeElement.type, "time", "activeElement should be of type time");
+
+ // Use FocusManager to check that the actual focus is on the anonymous
+ // text control.
+ let fm = SpecialPowers.Cc["@mozilla.org/focus-manager;1"]
+ .getService(SpecialPowers.Ci.nsIFocusManager);
+ let focusedElement = fm.focusedElement;
+ is(focusedElement.localName, "input", "focusedElement should be an input element");
+ is(focusedElement.type, "text", "focusedElement should be of type text");
+
+ time.blur();
+ isnot(document.activeElement, time, "activeElement should no longer be the time element");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_datetime_tabindex.html b/dom/html/test/forms/test_input_datetime_tabindex.html
new file mode 100644
index 000000000..fb7c9b2f1
--- /dev/null
+++ b/dom/html/test/forms/test_input_datetime_tabindex.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
+-->
+<head>
+ <title>Test tabindex attribute for &lt;input type='time'&gt;</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=1288591">Mozilla Bug 1288591</a>
+<p id="display"></p>
+<div id="content">
+ <input id="time1" type="time" tabindex="0">
+ <input id="time2" type="time" tabindex="-1">
+ <input id="time3" type="time" tabindex="0">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 1288591.
+ * This test checks whether date/time input types' tabindex attribute works
+ * correctly.
+ **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ test();
+ SimpleTest.finish();
+});
+
+function test() {
+ let time1 = document.getElementById("time1");
+ let time2 = document.getElementById("time2");
+ let time3 = document.getElementById("time3");
+
+ time1.focus();
+ is(document.activeElement, time1,
+ "input element with tabindex=0 is focusable");
+
+ // Advance to time1 minute field
+ synthesizeKey("VK_TAB", {});
+ is(document.activeElement, time1,
+ "input element with tabindex=0 is tabbable");
+
+ // Advance to time1 AM/PM field
+ synthesizeKey("VK_TAB", {});
+ is(document.activeElement, time1,
+ "input element with tabindex=0 is tabbable");
+
+ // Advance to next element
+ synthesizeKey("VK_TAB", {});
+ is(document.activeElement, time3,
+ "input element with tabindex=-1 is not tabbable");
+
+ time2.focus();
+ is(document.activeElement, time2,
+ "input element with tabindex=-1 is still focusable");
+
+ // Changing the tabindex attribute dynamically.
+ time3.setAttribute("tabindex", "-1");
+ synthesizeKey("VK_TAB", {}); // need only one TAB since time2 is not tabbable
+ isnot(document.activeElement, time3,
+ "element with tabindex changed to -1 should not be tabbable");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_defaultValue.html b/dom/html/test/forms/test_input_defaultValue.html
new file mode 100644
index 000000000..53d2dd43a
--- /dev/null
+++ b/dom/html/test/forms/test_input_defaultValue.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=977029
+-->
+<head>
+ <title>Test for Bug 977029</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<div id="content">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=977029">Bug 977029</a>
+ <p>
+ Goal of this test is to check that modifying defaultValue and value attribute
+ of input types is working as expected.
+ </p>
+ <form>
+ <input id='a' type="color" value="#00ff00">
+ <input id='b' type="text" value="foo">
+ <input id='c' type="email" value="foo">
+ <input id='d' type="date" value="2010-09-20">
+ <input id='e' type="search" value="foo">
+ <input id='f' type="tel" value="foo">
+ <input id='g' type="url" value="foo">
+ <input id='h' type="number" value="42">
+ <input id='i' type="range" value="42" min="0" max="100">
+ <input id='j' type="time" value="17:00:25.54">
+ </form>
+</div>
+<script type="application/javascript">
+
+// [ element id | original defaultValue | another value | another default value]
+// Preferably use only valid values: the goal of this test isn't to test the
+// value sanitization algorithm (for input types which have one) as this is
+// already part of another test)
+var testData = [["a", "#00ff00", "#00aaaa", "#00ccaa"],
+ ["b", "foo", "bar", "tulip"],
+ ["c", "foo", "foo@bar.org", "tulip"],
+ ["d", "2010-09-20", "2012-09-21", ""],
+ ["e", "foo", "bar", "tulip"],
+ ["f", "foo", "bar", "tulip"],
+ ["g", "foo", "bar", "tulip"],
+ ["h", "42", "1337", "3"],
+ ["i", "42", "17", "3"],
+ ["j", "17:00:25.54", "07:00:25", "03:00:03"],
+ ];
+
+for (var data of testData) {
+ id = data[0];
+ input = document.getElementById(id);
+ originalDefaultValue = data[1];
+ is(originalDefaultValue, input.defaultValue,
+ "Default value isn't the expected one");
+ is(originalDefaultValue, input.value,
+ "input.value original value is different from defaultValue");
+ input.defaultValue = data[2]
+ is(input.defaultValue, input.value,
+ "Changing default value before value was changed should change value too");
+ input.value = data[3];
+ input.defaultValue = originalDefaultValue;
+ is(input.value, data[3],
+ "Changing default value after value was changed should not change value");
+ input.value = data[2];
+ is(originalDefaultValue, input.defaultValue,
+ "defaultValue shouldn't change when changing value");
+ input.defaultValue = data[3];
+ is(input.defaultValue, data[3],
+ "defaultValue should have changed");
+ // Change the value...
+ input.value = data[2];
+ is(input.value, data[2],
+ "value should have changed");
+ // ...then reset the form
+ input.form.reset();
+ is(input.defaultValue, input.value,
+ "reset form should bring back the default value");
+}
+</script>
+</body>
+</html>
+
diff --git a/dom/html/test/forms/test_input_email.html b/dom/html/test/forms/test_input_email.html
new file mode 100644
index 000000000..9f35cbe22
--- /dev/null
+++ b/dom/html/test/forms/test_input_email.html
@@ -0,0 +1,237 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=555559
+https://bugzilla.mozilla.org/show_bug.cgi?id=668817
+https://bugzilla.mozilla.org/show_bug.cgi?id=854812
+-->
+<head>
+ <title>Test for &lt;input type='email'&gt; validity</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=555559">Mozilla Bug 555559</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=668817">Mozilla Bug 668817</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=854812">Mozilla Bug 854812</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <form>
+ <input type='email' name='email' id='i' oninvalid="invalidEventHandler(event);">
+ <form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for <input type='email'> validity **/
+
+var gInvalid = false;
+
+function invalidEventHandler(e)
+{
+ is(e.type, "invalid", "Invalid event type should be invalid");
+ gInvalid = true;
+}
+
+function checkValidEmailAddress(element)
+{
+ gInvalid = false;
+ ok(!element.validity.typeMismatch && !element.validity.badInput,
+ "Element should not suffer from type mismatch or bad input (with value='"+element.value+"')");
+ ok(element.validity.valid, "Element should be valid");
+ ok(element.checkValidity(), "Element should be valid");
+ ok(!gInvalid, "The invalid event should not have been thrown");
+ is(element.validationMessage, '',
+ "Validation message should be the empty string");
+ ok(element.matches(":valid"), ":valid pseudo-class should apply");
+}
+
+const VALID = 0;
+const TYPE_MISMATCH = 1 << 0;
+const BAD_INPUT = 1 << 1;
+
+function checkInvalidEmailAddress(element, failedValidityStates)
+{
+ info("Checking " + element.value);
+ gInvalid = false;
+ var expectTypeMismatch = !!(failedValidityStates & TYPE_MISMATCH);
+ var expectBadInput = !!(failedValidityStates & BAD_INPUT);
+ ok(element.validity.typeMismatch == expectTypeMismatch,
+ "Element should " + (expectTypeMismatch ? "" : "not ") + "suffer from type mismatch (with value='"+element.value+"')");
+ ok(element.validity.badInput == expectBadInput,
+ "Element should " + (expectBadInput ? "" : "not ") + "suffer from bad input (with value='"+element.value+"')");
+ ok(!element.validity.valid, "Element should not be valid");
+ ok(!element.checkValidity(), "Element should not be valid");
+ ok(gInvalid, "The invalid event should have been thrown");
+ is(element.validationMessage, "Please enter an email address.",
+ "Validation message is not valid");
+ ok(element.matches(":invalid"), ":invalid pseudo-class should apply");
+}
+
+function testEmailAddress(aElement, aValue, aMultiple, aValidityFailures)
+{
+ aElement.multiple = aMultiple;
+ aElement.value = aValue;
+
+ if (!aValidityFailures) {
+ checkValidEmailAddress(aElement);
+ } else {
+ checkInvalidEmailAddress(aElement, aValidityFailures);
+ }
+}
+
+var email = document.forms[0].elements[0];
+
+// Simple values, checking the e-mail syntax validity.
+var values = [
+ [ '' ], // The empty string shouldn't be considered as invalid.
+ [ 'foo@bar.com', VALID ],
+ [ ' foo@bar.com', VALID ],
+ [ 'foo@bar.com ', VALID ],
+ [ '\r\n foo@bar.com', VALID ],
+ [ 'foo@bar.com \n\r', VALID ],
+ [ '\n\n \r\rfoo@bar.com\n\n \r\r', VALID ],
+ [ '\n\r \n\rfoo@bar.com\n\r \n\r', VALID ],
+ [ 'tulip', TYPE_MISMATCH ],
+ // Some checks on the user part of the address.
+ [ '@bar.com', TYPE_MISMATCH ],
+ [ 'f\noo@bar.com', VALID ],
+ [ 'f\roo@bar.com', VALID ],
+ [ 'f\r\noo@bar.com', VALID ],
+ [ 'fü@foo.com', TYPE_MISMATCH ],
+ // Some checks for the domain part.
+ [ 'foo@bar', VALID ],
+ [ 'foo@b', VALID ],
+ [ 'foo@', TYPE_MISMATCH ],
+ [ 'foo@bar.', TYPE_MISMATCH ],
+ [ 'foo@foo.bar', VALID ],
+ [ 'foo@foo..bar', TYPE_MISMATCH ],
+ [ 'foo@.bar', TYPE_MISMATCH ],
+ [ 'foo@tulip.foo.bar', VALID ],
+ [ 'foo@tulip.foo-bar', VALID ],
+ [ 'foo@1.2', VALID ],
+ [ 'foo@127.0.0.1', VALID ],
+ [ 'foo@1.2.3', VALID ],
+ [ 'foo@b\nar.com', VALID ],
+ [ 'foo@b\rar.com', VALID ],
+ [ 'foo@b\r\nar.com', VALID ],
+ [ 'foo@.', TYPE_MISMATCH ],
+ [ 'foo@fü.com', VALID ],
+ [ 'foo@fu.cüm', VALID ],
+ [ 'thisUsernameIsLongerThanSixtyThreeCharactersInLengthRightAboutNow@mozilla.tld', VALID ],
+ // Long strings with UTF-8 in username.
+ [ 'this.is.email.should.be.longer.than.sixty.four.characters.föö@mözillä.tld', TYPE_MISMATCH ],
+ [ 'this-is-email-should-be-longer-than-sixty-four-characters-föö@mözillä.tld', TYPE_MISMATCH, true ],
+ // Long labels (labels greater than 63 chars long are not allowed).
+ [ 'foo@thislabelisexactly63characterssssssssssssssssssssssssssssssssss', VALID ],
+ [ 'foo@thislabelisexactly63characterssssssssssssssssssssssssssssssssss.com', VALID ],
+ [ 'foo@foo.thislabelisexactly63characterssssssssssssssssssssssssssssssssss.com', VALID ],
+ [ 'foo@foo.thislabelisexactly63characterssssssssssssssssssssssssssssssssss', VALID ],
+ [ 'foo@thislabelisexactly64charactersssssssssssssssssssssssssssssssssss', TYPE_MISMATCH | BAD_INPUT ],
+ [ 'foo@thislabelisexactly64charactersssssssssssssssssssssssssssssssssss.com', TYPE_MISMATCH | BAD_INPUT ],
+ [ 'foo@foo.thislabelisexactly64charactersssssssssssssssssssssssssssssssssss.com', TYPE_MISMATCH | BAD_INPUT ],
+ [ 'foo@foo.thislabelisexactly64charactersssssssssssssssssssssssssssssssssss', TYPE_MISMATCH | BAD_INPUT ],
+ // Long labels with UTF-8 (punycode encoding will increase the label to more than 63 chars).
+ [ 'foo@thisläbelisexäctly63charäcterssssssssssssssssssssssssssssssssss', TYPE_MISMATCH | BAD_INPUT ],
+ [ 'foo@thisläbelisexäctly63charäcterssssssssssssssssssssssssssssssssss.com', TYPE_MISMATCH | BAD_INPUT ],
+ [ 'foo@foo.thisläbelisexäctly63charäcterssssssssssssssssssssssssssssssssss.com', TYPE_MISMATCH | BAD_INPUT ],
+ [ 'foo@foo.thisläbelisexäctly63charäcterssssssssssssssssssssssssssssssssss', TYPE_MISMATCH | BAD_INPUT ],
+ // The domains labels (sub-domains or tld) can't start or finish with a '-'
+ [ 'foo@foo-bar', VALID ],
+ [ 'foo@-foo', TYPE_MISMATCH ],
+ [ 'foo@foo-.bar', TYPE_MISMATCH ],
+ [ 'foo@-.-', TYPE_MISMATCH ],
+ [ 'foo@fo-o.bar', VALID ],
+ [ 'foo@fo-o.-bar', TYPE_MISMATCH ],
+ [ 'foo@fo-o.bar-', TYPE_MISMATCH ],
+ [ 'foo@fo-o.-', TYPE_MISMATCH ],
+ [ 'foo@fo--o', VALID ],
+];
+
+// Multiple values, we don't check e-mail validity, only multiple stuff.
+var multipleValues = [
+ [ 'foo@bar.com, foo@bar.com', VALID ],
+ [ 'foo@bar.com,foo@bar.com', VALID ],
+ [ 'foo@bar.com,foo@bar.com,foo@bar.com', VALID ],
+ [ ' foo@bar.com , foo@bar.com ', VALID ],
+ [ '\tfoo@bar.com\t,\tfoo@bar.com\t', VALID ],
+ [ '\rfoo@bar.com\r,\rfoo@bar.com\r', VALID ],
+ [ '\nfoo@bar.com\n,\nfoo@bar.com\n', VALID ],
+ [ '\ffoo@bar.com\f,\ffoo@bar.com\f', VALID ],
+ [ '\t foo@bar.com\r,\nfoo@bar.com\f', VALID ],
+ [ 'foo@b,ar.com,foo@bar.com', TYPE_MISMATCH ],
+ [ 'foo@bar.com,foo@bar.com,', TYPE_MISMATCH ],
+ [ ' foo@bar.com , foo@bar.com , ', TYPE_MISMATCH ],
+ [ ',foo@bar.com,foo@bar.com', TYPE_MISMATCH ],
+ [ ',foo@bar.com,foo@bar.com', TYPE_MISMATCH ],
+ [ 'foo@bar.com,,,foo@bar.com', TYPE_MISMATCH ],
+ [ 'foo@bar.com;foo@bar.com', TYPE_MISMATCH ],
+ [ '<foo@bar.com>, <foo@bar.com>', TYPE_MISMATCH ],
+ [ 'foo@bar, foo@bar.com', VALID ],
+ [ 'foo@bar.com, foo', TYPE_MISMATCH ],
+ [ 'foo, foo@bar.com', TYPE_MISMATCH ],
+];
+
+/* Additional username checks. */
+
+var legalCharacters = "abcdefghijklmnopqrstuvwxyz";
+legalCharacters += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+legalCharacters += "0123456789";
+legalCharacters += "!#$%&'*+-/=?^_`{|}~.";
+
+// Add all username legal characters individually to the list.
+for (c of legalCharacters) {
+ values.push([c + "@bar.com", VALID]);
+}
+// Add the concatenation of all legal characters too.
+values.push([legalCharacters + "@bar.com", VALID]);
+
+// Add username illegal characters, the same way.
+var illegalCharacters = "()<>[]:;@\\, \t";
+for (c of illegalCharacters) {
+ values.push([illegalCharacters + "@bar.com", TYPE_MISMATCH]);
+}
+
+/* Additional domain checks. */
+
+legalCharacters = "abcdefghijklmnopqrstuvwxyz";
+legalCharacters += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+legalCharacters += "0123456789";
+
+// Add domain legal characters (except '.' and '-' because they are special).
+for (c of legalCharacters) {
+ values.push(["foo@foo.bar" + c, VALID]);
+}
+// Add the concatenation of all legal characters too.
+values.push(["foo@bar." + legalCharacters, VALID]);
+
+// Add domain illegal characters.
+illegalCharacters = "()<>[]:;@\\,!#$%&'*+/=?^_`{|}~ \t";
+for (c of illegalCharacters) {
+ values.push(['foo@foo.ba' + c + 'r', TYPE_MISMATCH]);
+}
+
+values.forEach(function([value, valid, todo]) {
+ if (todo === true) {
+ email.value = value;
+ todo_is(email.validity.valid, true, "value should be valid");
+ } else {
+ testEmailAddress(email, value, false, valid);
+ }
+});
+
+multipleValues.forEach(function([value, valid]) {
+ testEmailAddress(email, value, true, valid);
+});
+
+// Make sure setting multiple changes the value.
+email.multiple = false;
+email.value = "foo@bar.com, foo@bar.com";
+checkInvalidEmailAddress(email, TYPE_MISMATCH);
+email.multiple = true;
+checkValidEmailAddress(email);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_event.html b/dom/html/test/forms/test_input_event.html
new file mode 100644
index 000000000..f458b1b48
--- /dev/null
+++ b/dom/html/test/forms/test_input_event.html
@@ -0,0 +1,234 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=851780
+-->
+<head>
+<title>Test for input event</title>
+<script type="text/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=851780">Mozilla Bug 851780</a>
+<p id="display"></p>
+<div id="content">
+<input type="file" id="fileInput"></input>
+<textarea id="textarea" oninput="++textareaInput;"></textarea>
+<input type="text" id="input_text" oninput="++textInput[0];"></input>
+<input type="email" id="input_email" oninput="++textInput[1];"></input>
+<input type="search" id="input_search" oninput="++textInput[2];"></input>
+<input type="tel" id="input_tel" oninput="++textInput[3];"></input>
+<input type="url" id="input_url" oninput="++textInput[4];"></input>
+<input type="password" id="input_password" oninput="++textInput[5];"></input>
+
+<!-- "Non-text" inputs-->
+<input type="button" id="input_button" oninput="++NonTextInput[0];"></input>
+<input type="submit" id="input_submit" oninput="++NonTextInput[1];"></input>
+<input type="image" id="input_image" oninput="++NonTextInput[2];"></input>
+<input type="reset" id="input_reset" oninput="++NonTextInput[3];"></input>
+<input type="radio" id="input_radio" oninput="++NonTextInput[4];"></input>
+<input type="checkbox" id="input_checkbox" oninput="++NonTextInput[5];"></input>
+<input type="range" id="input_range" oninput="++rangeInput;"></input>
+<input type="number" id="input_number" oninput="++numberInput;"></input>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ /** Test for input event. This is highly based on test_change_event.html **/
+
+ const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent);
+
+ var textareaInput = 0;
+
+ // Those are types were the input event apply.
+ var textTypes = ["text", "email", "search", "tel", "url", "password"];
+ var textInput = [0, 0, 0, 0, 0, 0];
+
+ // Those are events were the input event does not apply.
+ var NonTextTypes = ["button", "submit", "image", "reset", "radio", "checkbox"];
+ var NonTextInput = [0, 0, 0, 0, 0, 0];
+
+ var rangeInput = 0;
+ var numberInput = 0;
+
+ SimpleTest.waitForExplicitFinish();
+ var MockFilePicker = SpecialPowers.MockFilePicker;
+ MockFilePicker.init(window);
+
+ function testUserInput() {
+ // Simulating an OK click and with a file name return.
+ MockFilePicker.useBlobFile();
+ MockFilePicker.returnValue = MockFilePicker.returnOK;
+ var input = document.getElementById('fileInput');
+ input.focus();
+
+ input.addEventListener("input", function (aEvent) {
+ ok(true, "input event should have been dispatched on file input.");
+ }, false);
+
+ input.click();
+ setTimeout(testUserInput2, 0);
+ }
+
+ function testUserInput2() {
+ // Some generic checks for types that support the input event.
+ for (var i = 0; i < textTypes.length; ++i) {
+ input = document.getElementById("input_" + textTypes[i]);
+ input.focus();
+ synthesizeKey("VK_RETURN", {});
+ is(textInput[i], 0, "input event shouldn't be dispatched on " + textTypes[i] + " input element");
+
+ synthesizeKey("m", {});
+ is(textInput[i], 1, textTypes[i] + " input element should have dispatched input event.");
+ synthesizeKey("VK_RETURN", {});
+ is(textInput[i], 1, "input event shouldn't be dispatched on " + textTypes[i] + " input element");
+
+ synthesizeKey("VK_BACK_SPACE", {});
+ is(textInput[i], 2, textTypes[i] + " input element should have dispatched input event.");
+ }
+
+ // Some scenarios of value changing from script and from user input.
+ input = document.getElementById("input_text");
+ input.focus();
+ synthesizeKey("f", {});
+ is(textInput[0], 3, "input event should have been dispatched");
+ input.blur();
+ is(textInput[0], 3, "input event should not have been dispatched");
+
+ input.focus();
+ input.value = 'foo';
+ is(textInput[0], 3, "input event should not have been dispatched");
+ input.blur();
+ is(textInput[0], 3, "input event should not have been dispatched");
+
+ input.focus();
+ synthesizeKey("f", {});
+ is(textInput[0], 4, "input event should have been dispatched");
+ input.value = 'bar';
+ is(textInput[0], 4, "input event should not have been dispatched");
+ input.blur();
+ is(textInput[0], 4, "input event should not have been dispatched");
+
+ // Same for textarea.
+ var textarea = document.getElementById("textarea");
+ textarea.focus();
+ synthesizeKey("f", {});
+ is(textareaInput, 1, "input event should have been dispatched");
+ textarea.blur();
+ is(textareaInput, 1, "input event should not have been dispatched");
+
+ textarea.focus();
+ textarea.value = 'foo';
+ is(textareaInput, 1, "input event should not have been dispatched");
+ textarea.blur();
+ is(textareaInput, 1, "input event should not have been dispatched");
+
+ textarea.focus();
+ synthesizeKey("f", {});
+ is(textareaInput, 2, "input event should have been dispatched");
+ textarea.value = 'bar';
+ is(textareaInput, 2, "input event should not have been dispatched");
+ synthesizeKey("VK_BACK_SPACE", {});
+ is(textareaInput, 3, "input event should have been dispatched");
+ textarea.blur();
+ is(textareaInput, 3, "input event should not have been dispatched");
+
+ // Non-text input tests:
+ for (var i = 0; i < NonTextTypes.length; ++i) {
+ // Button, submit, image and reset input type tests.
+ if (i < 4) {
+ input = document.getElementById("input_" + NonTextTypes[i]);
+ input.focus();
+ input.click();
+ is(NonTextInput[i], 0, "input event doesn't apply");
+ input.blur();
+ is(NonTextInput[i], 0, "input event doesn't apply");
+ }
+ // For radio and checkboxes, input event should be dispatched.
+ else {
+ input = document.getElementById("input_" + NonTextTypes[i]);
+ input.focus();
+ input.click();
+ is(NonTextInput[i], 1, "input event should have been dispatched");
+ input.blur();
+ is(NonTextInput[i], 1, "input event should not have been dispatched");
+
+ // Test that input event is not dispatched if click event is cancelled.
+ function preventDefault(e) {
+ e.preventDefault();
+ }
+ input.addEventListener("click", preventDefault, false);
+ input.click();
+ is(NonTextInput[i], 1, "input event shouldn't be dispatched if click event is cancelled");
+ input.removeEventListener("click", preventDefault, false);
+ }
+ }
+
+ // Type changes.
+ var input = document.createElement('input');
+ input.type = 'text';
+ input.value = 'foo';
+ input.oninput = function() {
+ ok(false, "we shouldn't get an input event when the type changes");
+ };
+ input.type = 'range';
+ isnot(input.value, 'foo');
+
+ // Tests for type='range'.
+ var range = document.getElementById("input_range");
+
+ range.focus();
+ synthesizeKey("a", {});
+ range.blur();
+ is(rangeInput, 0, "input event shouldn't be dispatched on range input " +
+ "element for key changes that don't change its value");
+
+ range.focus();
+ synthesizeKey("VK_HOME", {});
+ is(rangeInput, 1, "input event should be dispatched for key changes");
+ range.blur();
+ is(rangeInput, 1, "input event shouldn't be dispatched on blur");
+
+ range.focus();
+ var bcr = range.getBoundingClientRect();
+ var centerOfRangeX = bcr.width / 2;
+ var centerOfRangeY = bcr.height / 2;
+ synthesizeMouse(range, centerOfRangeX - 10, centerOfRangeY, { type: "mousedown" });
+ is(rangeInput, 2, "Input event should be dispatched on mousedown if the value changes");
+ synthesizeMouse(range, centerOfRangeX - 5, centerOfRangeY, { type: "mousemove" });
+ is(rangeInput, 3, "Input event should be dispatched during a drag");
+ synthesizeMouse(range, centerOfRangeX, centerOfRangeY, { type: "mouseup" });
+ is(rangeInput, 4, "Input event should be dispatched at the end of a drag");
+
+ // Tests for type='number'.
+ // We only test key events here since input events for mouse event changes
+ // are tested in test_input_number_mouse_events.html
+ var number = document.getElementById("input_number");
+
+ if (isDesktop) { // up/down arrow keys not supported on android/b2g
+ number.value = "";
+ number.focus();
+ synthesizeKey("KEY_ArrowUp", { code: "ArrowUp" });
+ is(numberInput, 1, "input event should be dispatched for up/down arrow key keypress");
+ is(number.value, "1", "sanity check value of number control after keypress");
+
+ synthesizeKey("KEY_ArrowDown", { code: "ArrowDown", repeat: 3 });
+ is(numberInput, 4, "input event should be dispatched for each up/down arrow key keypress event, even when rapidly repeated");
+ is(number.value, "-2", "sanity check value of number control after multiple keydown events");
+
+ number.blur();
+ is(numberInput, 4, "input event shouldn't be dispatched on blur");
+ }
+
+ MockFilePicker.cleanup();
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(testUserInput);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_file_picker.html b/dom/html/test/forms/test_input_file_picker.html
new file mode 100644
index 000000000..6d3a98631
--- /dev/null
+++ b/dom/html/test/forms/test_input_file_picker.html
@@ -0,0 +1,267 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for &lt;input type='file'&gt; file picker</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=377624">Mozilla Bug 36619</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=377624">Mozilla Bug 377624</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=565274">Mozilla Bug 565274</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=701353">Mozilla Bug 701353</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=826176">Mozilla Bug 826176</a>
+<p id="display"></p>
+<div id="content">
+ <input id='a' type='file' accept="image/*">
+ <input id='b' type='file' accept="audio/*">
+ <input id='c' type='file' accept="video/*">
+ <input id='d' type='file' accept="image/*, audio/* ">
+ <input id='e' type='file' accept=" image/*,video/*">
+ <input id='f' type='file' accept="audio/*,video/*">
+ <input id='g' type='file' accept="image/*, audio/* ,video/*">
+ <input id='h' type='file' accept="foo/baz,image/*,bogus/duh">
+ <input id='i' type='file' accept="mime/type;parameter,video/*">
+ <input id='j' type='file' accept="audio/*, audio/*, audio/*">
+ <input id='k' type="file" accept="image/gif,image/png">
+ <input id='l' type="file" accept="image/*,image/gif,image/png">
+ <input id='m' type="file" accept="image/gif,image/gif">
+ <input id='n' type="file" accept="">
+ <input id='o' type="file" accept=".test">
+ <input id='p' type="file" accept="image/gif,.csv">
+ <input id='q' type="file" accept="image/gif,.gif">
+ <input id='r' type="file" accept=".prefix,.prefixPlusSomething">
+ <input id='s' type="file" accept=".xls,.xlsx">
+ <input id='t' type="file" accept=".mp3,.wav,.flac">
+ <input id='u' type="file" accept=".xls, .xlsx">
+ <input id='v' type="file" accept=".xlsx, .xls">
+ <input id='w' type="file" accept=".xlsx; .xls">
+ <input id='x' type="file" accept=".xls, .xlsx">
+ <input id='y' type="file" accept=".xlsx, .xls">
+ <input id='z' type='file' accept="i/am,a,pathological,;,,,,test/case">
+ <input id='A' type="file" accept=".xlsx, .xls*">
+ <input id='mix-ref' type="file" accept="image/jpeg">
+ <input id='mix' type="file" accept="image/jpeg,.jpg">
+ <input id='hidden' hidden type='file'>
+ <input id='untrusted-click' type='file'>
+ <input id='prevent-default' type='file'>
+ <input id='prevent-default-false' type='file'>
+ <input id='right-click' type='file'>
+ <input id='middle-click' type='file'>
+ <input id='left-click' type='file'>
+ <label id='label-1'>foo<input type='file'></label>
+ <label id='label-2' for='labeled-2'>foo</label><input id='labeled-2' type='file'></label>
+ <label id='label-3'>foo<input type='file'></label>
+ <label id='label-4' for='labeled-4'>foo</label><input id='labeled-4' type='file'></label>
+ <input id='by-button' type='file'>
+ <button id='button-click' onclick="document.getElementById('by-button').click();">foo</button>
+ <button id='button-down' onclick="document.getElementById('by-button').click();">foo</button>
+ <button id='button-up' onclick="document.getElementById('by-button').click();">foo</button>
+ <div id='div-click' onclick="document.getElementById('by-button').click();" tabindex='1'>foo</div>
+ <div id='div-click-on-demand' onclick="var i=document.createElement('input'); i.type='file'; i.click();" tabindex='1'>foo</div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * This test checks various scenarios and make sure that a file picker is being
+ * shown in all of them (minus a few exceptions).
+ * |testData| defines the tests to do and |launchNextTest| can be used to have
+ * specific behaviour for some tests. Everything else should just work.
+ */
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+// The following lists are from toolkit/content/filepicker.properties which is used by filePicker
+var imageExtensionList = "*.jpe; *.jpg; *.jpeg; *.gif; *.png; *.bmp; *.ico; *.svg; *.svgz; *.tif; *.tiff; *.ai; *.drw; *.pct; *.psp; *.xcf; *.psd; *.raw"
+var audioExtensionList = "*.aac; *.aif; *.flac; *.iff; *.m4a; *.m4b; *.mid; *.midi; *.mp3; *.mpa; *.mpc; *.oga; *.ogg; *.ra; *.ram; *.snd; *.wav; *.wma"
+var videoExtensionList = "*.avi; *.divx; *.flv; *.m4v; *.mkv; *.mov; *.mp4; *.mpeg; *.mpg; *.ogm; *.ogv; *.ogx; *.rm; *.rmvb; *.smil; *.webm; *.wmv; *.xvid"
+
+// [ element name | number of filters | extension list or filter mask | filter index ]
+var testData = [["a", 1, MockFilePicker.filterImages, 1],
+ ["b", 1, MockFilePicker.filterAudio, 1],
+ ["c", 1, MockFilePicker.filterVideo, 1],
+ ["d", 3, imageExtensionList + "; " + audioExtensionList, 1],
+ ["e", 3, imageExtensionList + "; " + videoExtensionList, 1],
+ ["f", 3, audioExtensionList + "; " + videoExtensionList, 1],
+ ["g", 4, imageExtensionList + "; " + audioExtensionList + "; " + videoExtensionList, 1],
+ ["h", 1, MockFilePicker.filterImages, 0],
+ ["i", 1, MockFilePicker.filterVideo, 0],
+ ["j", 1, MockFilePicker.filterAudio, 1],
+ ["k", 3, "*.gif; *.png", 1],
+ ["l", 4, imageExtensionList + "; " + "*.gif; *.png", 1],
+ ["m", 1, "*.gif", 1],
+ ["n", 0, undefined, 0],
+ ["o", 1, "*.test", 1],
+ ["p", 3, "*.gif; *.csv", 1],
+ ["q", 1, "*.gif", 1],
+ ["r", 3, "*.prefix; *.prefixPlusSomething", 1],
+ ["s", 3, "*.xls; *.xlsx", 1],
+ ["t", 4, "*.mp3; *.wav; *.flac", 1],
+ ["u", 3, "*.xls; *.xlsx", 1],
+ ["v", 3, "*.xlsx; *.xls", 1],
+ ["w", 0, undefined, 0],
+ ["x", 3, "*.xls; *.xlsx", 1],
+ ["y", 3, "*.xlsx; *.xls", 1],
+ ["z", 0, undefined, 0],
+ ["A", 1, "*.xlsx", 1],
+ // Note: mix and mix-ref tests extension lists are checked differently: see SimpleTest.executeSoon below
+ ["mix-ref", undefined, undefined, undefined],
+ ["mix", 1, undefined, 1],
+ ["hidden", 0, undefined, 0],
+ ["untrusted-click", 0, undefined, 0],
+ ["prevent-default", 0, undefined, 0, true],
+ ["prevent-default-false", 0, undefined, 0, true],
+ ["right-click", 0, undefined, 0, true],
+ ["middle-click", 0, undefined, 0, true],
+ ["left-click", 0, undefined, 0],
+ ["label-1", 0, undefined, 0],
+ ["label-2", 0, undefined, 0],
+ ["label-3", 0, undefined, 0],
+ ["label-4", 0, undefined, 0],
+ ["button-click", 0, undefined, 0],
+ ["button-down", 0, undefined, 0],
+ ["button-up", 0, undefined, 0],
+ ["div-click", 0, undefined, 0],
+ ["div-click-on-demand", 0, undefined, 0],
+ ];
+
+var currentTest = 0;
+var filterAllAdded;
+var filters;
+var filterIndex;
+var mixRefExtensionList;
+
+// disable popups to make sure that the popup blocker does not interfere
+// with manually opened file pickers.
+SpecialPowers.pushPrefEnv({'set': [["dom.disable_open_during_load", false]]}, runTests);
+
+function launchNextTest() {
+ MockFilePicker.shown = false;
+ filterAllAdded = false;
+ filters = [];
+ filterIndex = 0;
+
+ // Focusing the element will scroll them into view so making sure the clicks
+ // will work.
+ document.getElementById(testData[currentTest][0]).focus();
+
+ if (testData[currentTest][0] == "untrusted-click") {
+ var e = document.createEvent('MouseEvents');
+ e.initEvent('click', true, false);
+ document.getElementById(testData[currentTest][0]).dispatchEvent(e);
+ // All tests that should *NOT* show a file picker.
+ } else if (testData[currentTest][0] == "prevent-default" ||
+ testData[currentTest][0] == "prevent-default-false" ||
+ testData[currentTest][0] == "right-click" ||
+ testData[currentTest][0] == "middle-click") {
+ if (testData[currentTest][0] == "right-click" ||
+ testData[currentTest][0] == "middle-click") {
+ var b = testData[currentTest][0] == "middle-click" ? 1 : 2;
+ synthesizeMouseAtCenter(document.getElementById(testData[currentTest][0]),
+ { button: b });
+ } else {
+ if (testData[currentTest][0] == "prevent-default-false") {
+ document.getElementById(testData[currentTest][0]).onclick = function() {
+ return false;
+ };
+ } else {
+ document.getElementById(testData[currentTest][0]).onclick = function(e) {
+ e.preventDefault();
+ };
+ }
+ document.getElementById(testData[currentTest][0]).click();
+ }
+
+ // Wait a bit and assume we can continue. If the file picker shows later,
+ // behaviour is uncertain but that would be a random green, no big deal...
+ setTimeout(function() {
+ ok(true, "we should be there without a file picker being opened");
+ ++currentTest;
+ launchNextTest();
+ }, 500);
+ } else if (testData[currentTest][0] == 'label-3' ||
+ testData[currentTest][0] == 'label-4') {
+ synthesizeMouse(document.getElementById(testData[currentTest][0]), 5, 5, {});
+ } else if (testData[currentTest][0] == 'button-click' ||
+ testData[currentTest][0] == 'button-down' ||
+ testData[currentTest][0] == 'button-up' ||
+ testData[currentTest][0] == 'div-click' ||
+ testData[currentTest][0] == 'div-click-on-demand') {
+ synthesizeMouseAtCenter(document.getElementById(testData[currentTest][0]), {});
+ } else {
+ document.getElementById(testData[currentTest][0]).click();
+ }
+}
+
+function runTests() {
+ MockFilePicker.appendFilterCallback = function(filepicker, title, val) {
+ filters.push(val);
+ };
+ MockFilePicker.appendFiltersCallback = function(filepicker, val) {
+ if (val === MockFilePicker.filterAll) {
+ filterAllAdded = true;
+ } else {
+ filters.push(val);
+ }
+ };
+ MockFilePicker.showCallback = function(filepicker) {
+ if (testData[currentTest][4]) {
+ ok(false, "we shouldn't have a file picker showing!");
+ return;
+ }
+
+ filterIndex = filepicker.filterIndex;
+ testName = testData[currentTest][0];
+ SimpleTest.executeSoon(function () {
+ ok(MockFilePicker.shown,
+ "File picker show method should have been called (" + testName + ")");
+ ok(filterAllAdded,
+ "filterAll is missing (" + testName + ")");
+ if (testName == "mix-ref") {
+ // Used only for reference for next test: nothing to be tested here
+ mixRefExtensionList = filters[0];
+ if (mixRefExtensionList == undefined) {
+ mixRefExtensionList = "";
+ }
+ } else {
+ if (testName == "mix") {
+ // Mixing mime type and file extension filters ("image/jpeg" and
+ // ".jpg" here) shouldn't restrict the list but only extend it, if file
+ // extension filter isn't a duplicate
+ ok(filters[0].includes(mixRefExtensionList),
+ "Mixing mime types and file extension filters shouldn't restrict extension list: " +
+ mixRefExtensionList + " | " + filters[0]);
+ ok(filters[0].includes("*.jpg"),
+ "Filter should contain '.jpg' extension", filters[0]);
+ } else {
+ is(filters[0], testData[currentTest][2],
+ "Correct filters should have been added (" + testName + ")");
+ is(filters.length, testData[currentTest][1],
+ "appendFilters not called as often as expected (" + testName + ")");
+ }
+ is(filterIndex, testData[currentTest][3],
+ "File picker should show the correct filter index (" + testName + ")");
+ }
+
+ if (++currentTest == testData.length) {
+ MockFilePicker.cleanup();
+ SimpleTest.finish();
+ } else {
+ launchNextTest();
+ }
+ });
+ };
+
+ launchNextTest();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_list_attribute.html b/dom/html/test/forms/test_input_list_attribute.html
new file mode 100644
index 000000000..ff2be85d0
--- /dev/null
+++ b/dom/html/test/forms/test_input_list_attribute.html
@@ -0,0 +1,253 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=556007
+-->
+<head>
+ <title>Test for Bug 556007</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=556007">Mozilla Bug 556007</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 556007 **/
+
+function test0() {
+ var input = document.createElement("input");
+ ok("list" in input, "list should be an input element IDL attribute");
+}
+
+// Default .list returns null.
+function test1(aContent, aInput) {
+ return null;
+}
+
+// Regular test case.
+function test2(aContent, aInput) {
+ var datalist = document.createElement("datalist");
+ datalist.id = 'd';
+
+ aContent.appendChild(aInput);
+ aContent.appendChild(datalist);
+ aInput.setAttribute('list', 'd');
+
+ return datalist;
+}
+
+// If none of the element is in doc.
+function test3(aContent, aInput) {
+ var datalist = document.createElement("datalist");
+ datalist.id = 'd';
+
+ aInput.setAttribute('list', 'd');
+
+ return null;
+}
+
+// If one of the element isn't in doc.
+function test4(aContent, aInput) {
+ var datalist = document.createElement("datalist");
+ datalist.id = 'd';
+
+ aContent.appendChild(aInput);
+ aInput.setAttribute('list', 'd');
+
+ return null;
+}
+
+// If one of the element isn't in doc.
+function test5(aContent, aInput) {
+ var datalist = document.createElement("datalist");
+ datalist.id = 'd';
+
+ aContent.appendChild(datalist);
+ aInput.setAttribute('list', 'd');
+
+ return null;
+}
+
+// If datalist is added before input.
+function test6(aContent, aInput) {
+ var datalist = document.createElement("datalist");
+ datalist.id = 'd';
+
+ aContent.appendChild(datalist);
+ aContent.appendChild(aInput);
+ aInput.setAttribute('list', 'd');
+
+ return datalist;
+}
+
+// If setAttribute is set before datalist and input are in doc.
+function test7(aContent, aInput) {
+ var datalist = document.createElement("datalist");
+ datalist.id = 'd';
+
+ aInput.setAttribute('list', 'd');
+
+ aContent.appendChild(datalist);
+ aContent.appendChild(aInput);
+
+ return datalist;
+}
+
+// If setAttribute is set before datalist is in doc.
+function test8(aContent, aInput) {
+ var datalist = document.createElement("datalist");
+ datalist.id = 'd';
+
+ aContent.appendChild(aInput);
+ aInput.setAttribute('list', 'd');
+
+ aContent.appendChild(datalist);
+
+ return datalist;
+}
+
+// If setAttribute is set before datalist is created.
+function test9(aContent, aInput) {
+ aContent.appendChild(aInput);
+ aInput.setAttribute('list', 'd');
+
+ var datalist = document.createElement("datalist");
+ datalist.id = 'd';
+ aContent.appendChild(datalist);
+
+ return datalist;
+}
+
+// If another datalist is added _after_ the first one, with the same id.
+function test10(aContent, aInput) {
+ var datalist = document.createElement("datalist");
+ datalist.id = 'd';
+ var datalist2 = document.createElement("datalist");
+ datalist2.id = 'd';
+
+ aInput.setAttribute('list', 'd');
+ aContent.appendChild(aInput);
+ aContent.appendChild(datalist);
+ aContent.appendChild(datalist2);
+
+ return datalist;
+}
+
+// If another datalist is added _before_ the first one with the same id.
+function test11(aContent, aInput) {
+ var datalist = document.createElement("datalist");
+ datalist.id = 'd';
+ var datalist2 = document.createElement("datalist");
+ datalist2.id = 'd';
+
+ aInput.setAttribute('list', 'd');
+ aContent.appendChild(aInput);
+ aContent.appendChild(datalist);
+ aContent.insertBefore(datalist2, datalist);
+
+ return datalist2;
+}
+
+// If datalist changes it's id.
+function test12(aContent, aInput) {
+ var datalist = document.createElement("datalist");
+ datalist.id = 'd';
+
+ aInput.setAttribute('list', 'd');
+ aContent.appendChild(aInput);
+ aContent.appendChild(datalist);
+
+ datalist.id = 'foo';
+
+ return null;
+}
+
+// If datalist is removed.
+function test13(aContent, aInput) {
+ var datalist = document.createElement("datalist");
+ datalist.id = 'd';
+
+ aInput.setAttribute('list', 'd');
+ aContent.appendChild(aInput);
+ aContent.appendChild(datalist);
+ aContent.removeChild(datalist);
+
+ return null;
+}
+
+// If id contain spaces.
+function test14(aContent, aInput) {
+ var datalist = document.createElement("datalist");
+ datalist.id = 'a b c d';
+
+ aInput.setAttribute('list', 'a b c d');
+ aContent.appendChild(aInput);
+ aContent.appendChild(datalist);
+
+ return datalist;
+}
+
+// If id is the empty string.
+function test15(aContent, aInput) {
+ var datalist = document.createElement("datalist");
+ datalist.id = '';
+
+ aInput.setAttribute('list', '');
+ aContent.appendChild(aInput);
+ aContent.appendChild(datalist);
+
+ return null;
+}
+
+// If the id doesn't point to a datalist.
+function test16(aContent, aInput) {
+ var input = document.createElement("input");
+ input.id = 'd';
+
+ aInput.setAttribute('list', 'd');
+ aContent.appendChild(aInput);
+ aContent.appendChild(input);
+
+ return null;
+}
+
+// If the first element with the id isn't a datalist.
+function test17(aContent, aInput) {
+ var input = document.createElement("input");
+ input.id = 'd';
+ var datalist = document.createElement("datalist");
+ datalist.id = 'd';
+
+ aInput.setAttribute('list', 'd');
+ aContent.appendChild(aInput);
+ aContent.appendChild(input);
+ aContent.appendChild(datalist);
+
+ return null;
+}
+
+var tests = [ test1, test2, test3, test4, test5, test6, test7, test8, test9,
+ test10, test11, test12, test13, test14, test15, test16, test17 ];
+
+test0();
+
+for (var test of tests) {
+ var content = document.getElementById('content');
+
+ // Clean-up.
+ content.textContent = '';
+
+ var input = document.createElement("input");
+ var res = test(content, input);
+
+ is(input.list, res, "input.list should be " + res);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_number_data.js b/dom/html/test/forms/test_input_number_data.js
new file mode 100644
index 000000000..0a995010f
--- /dev/null
+++ b/dom/html/test/forms/test_input_number_data.js
@@ -0,0 +1,38 @@
+
+var tests = [
+ { desc: "British English",
+ langTag: "en-GB", inputWithGrouping: "123,456.78",
+ inputWithoutGrouping: "123456.78", value: 123456.78
+ },
+ { desc: "Farsi",
+ langTag: "fa", inputWithGrouping: "Û±Û²Û³Ù¬Û´ÛµÛ¶Ù«Û·Û¸",
+ inputWithoutGrouping: "Û±Û²Û³Û´ÛµÛ¶Ù«Û·Û¸", value: 123456.78
+ },
+ { desc: "French",
+ langTag: "fr-FR", inputWithGrouping: "123 456,78",
+ inputWithoutGrouping: "123456,78", value: 123456.78
+ },
+ { desc: "German",
+ langTag: "de", inputWithGrouping: "123.456,78",
+ inputWithoutGrouping: "123456,78", value: 123456.78
+ },
+ // Extra german test to check that a locale that uses '.' as its grouping
+ // separator doesn't result in it being invalid (due to step mismatch) due
+ // to the de-localization code mishandling numbers that look like other
+ // numbers formatted for English speakers (i.e. treating this as 123.456
+ // instead of 123456):
+ { desc: "German (test 2)",
+ langTag: "de", inputWithGrouping: "123.456",
+ inputWithoutGrouping: "123456", value: 123456
+ },
+ { desc: "Hebrew",
+ langTag: "he", inputWithGrouping: "123,456.78",
+ inputWithoutGrouping: "123456.78", value: 123456.78
+ },
+];
+
+var invalidTests = [
+ // Right now this will pass in a 'de' build, but not in the 'en' build that
+ // are used for testing. See bug .
+ // { desc: "Invalid German", langTag: "de", input: "12.34" }
+];
diff --git a/dom/html/test/forms/test_input_number_focus.html b/dom/html/test/forms/test_input_number_focus.html
new file mode 100644
index 000000000..121485463
--- /dev/null
+++ b/dom/html/test/forms/test_input_number_focus.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1268556
+-->
+<head>
+ <title>Test focus behaviour for &lt;input type='number'&gt;</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=1268556">Mozilla Bug 1268556</a>
+<p id="display"></p>
+<div id="content">
+ <input id="input" type="number">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 1268556.
+ * This test checks that when focusing on an input type=number, the focus is
+ * redirected to the anonymous text control, but the document.activeElement
+ * still returns the <input type=number>.
+ **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ test();
+ SimpleTest.finish();
+});
+
+function test() {
+ var number = document.getElementById("input");
+ number.focus();
+
+ // The active element returns the input type=number.
+ var activeElement = document.activeElement;
+ is (activeElement, number, "activeElement should be the number element");
+ is (activeElement.localName, "input", "activeElement should be an input element");
+ is (activeElement.getAttribute("type"), "number", "activeElement should of type number");
+
+ // Use FocusManager to check that the actual focus is on the anonymous
+ // text control.
+ var fm = SpecialPowers.Cc["@mozilla.org/focus-manager;1"]
+ .getService(SpecialPowers.Ci.nsIFocusManager);
+ var focusedElement = fm.focusedElement;
+ is (focusedElement.localName, "input", "focusedElement should be an input element");
+ is (focusedElement.getAttribute("type"), "text", "focusedElement should of type text");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_number_key_events.html b/dom/html/test/forms/test_input_number_key_events.html
new file mode 100644
index 000000000..89578541d
--- /dev/null
+++ b/dom/html/test/forms/test_input_number_key_events.html
@@ -0,0 +1,244 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=935506
+-->
+<head>
+ <title>Test key events for number control</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"/>
+ <meta charset="UTF-8">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=935506">Mozilla Bug 935506</a>
+<p id="display"></p>
+<div id="content">
+ <input id="input" type="number">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 935506
+ * This test checks how the value of <input type=number> changes in response to
+ * key events while it is in various states.
+ **/
+SimpleTest.waitForExplicitFinish();
+// Turn off Spatial Navigation because it hijacks arrow keydown events:
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[["snav.enabled", false]]}, function() {
+ test();
+ SimpleTest.finish();
+ });
+});
+const defaultMinimum = "NaN";
+const defaultMaximum = "NaN";
+const defaultStep = 1;
+
+// Helpers:
+// For the sake of simplicity, we do not currently support fractional value,
+// step, etc.
+
+function getMinimum(element) {
+ return Number(element.min || defaultMinimum);
+}
+
+function getMaximum(element) {
+ return Number(element.max || defaultMaximum);
+}
+
+function getDefaultValue(element) {
+ return 0;
+}
+
+function getValue(element) {
+ return Number(element.value || getDefaultValue(element));
+}
+
+function getStep(element) {
+ if (element.step == "any") {
+ return "any";
+ }
+ var step = Number(element.step || defaultStep);
+ return step <= 0 ? defaultStep : step;
+}
+
+function getStepBase(element) {
+ return Number(element.getAttribute("min") || "NaN") ||
+ Number(element.getAttribute("value") || "NaN") || 0;
+}
+
+function hasStepMismatch(element) {
+ var value = element.value;
+ if (value == "") {
+ value = 0;
+ }
+ var step = getStep(element);
+ if (step == "any") {
+ return false;
+ }
+ return ((value - getStepBase(element)) % step) != 0;
+}
+
+function floorModulo(x, y) {
+ return (x - y * Math.floor(x / y));
+}
+
+function expectedValueAfterStepUpOrDown(stepFactor, element) {
+ var value = getValue(element);
+ if (isNaN(value)) {
+ value = 0;
+ }
+ var step = getStep(element);
+ if (step == "any") {
+ step = 1;
+ }
+
+ var minimum = getMinimum(element);
+ var maximum = getMaximum(element);
+ if (!isNaN(maximum)) {
+ // "max - (max - stepBase) % step" is the nearest valid value to max.
+ maximum = maximum - floorModulo(maximum - getStepBase(element), step);
+ }
+
+ // Cases where we are clearly going in the wrong way.
+ // We don't use ValidityState because we can be higher than the maximal
+ // allowed value and still not suffer from range overflow in the case of
+ // of the value specified in @max isn't in the step.
+ if ((value <= minimum && stepFactor < 0) ||
+ (value >= maximum && stepFactor > 0)) {
+ return value;
+ }
+
+ if (hasStepMismatch(element) &&
+ value != minimum && value != maximum) {
+ if (stepFactor > 0) {
+ value -= floorModulo(value - getStepBase(element), step);
+ } else if (stepFactor < 0) {
+ value -= floorModulo(value - getStepBase(element), step);
+ value += step;
+ }
+ }
+
+ value += step * stepFactor;
+
+ // When stepUp() is called and the value is below minimum, we should clamp on
+ // minimum unless stepUp() moves us higher than minimum.
+ if (element.validity.rangeUnderflow && stepFactor > 0 &&
+ value <= minimum) {
+ value = minimum;
+ } else if (element.validity.rangeOverflow && stepFactor < 0 &&
+ value >= maximum) {
+ value = maximum;
+ } else if (stepFactor < 0 && !isNaN(minimum)) {
+ value = Math.max(value, minimum);
+ } else if (stepFactor > 0 && !isNaN(maximum)) {
+ value = Math.min(value, maximum);
+ }
+
+ return value;
+}
+
+function expectedValAfterKeyEvent(key, element) {
+ return expectedValueAfterStepUpOrDown(key == "VK_UP" ? 1 : -1, element);
+}
+
+function test() {
+ var elem = document.getElementById("input");
+ elem.focus();
+
+ elem.min = -5;
+ elem.max = 5;
+ elem.step = 2;
+ var defaultValue = 0;
+ var oldVal, expectedVal;
+
+ for (key of ["VK_UP", "VK_DOWN"]) {
+ // Start at middle:
+ oldVal = elem.value = -1;
+ expectedVal = expectedValAfterKeyEvent(key, elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test " + key + " for number control with value set between min/max (" + oldVal + ")");
+
+ // Same again:
+ expectedVal = expectedValAfterKeyEvent(key, elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test repeat of " + key + " for number control");
+
+ // Start at maximum:
+ oldVal = elem.value = elem.max;
+ expectedVal = expectedValAfterKeyEvent(key, elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test " + key + " for number control with value set to the maximum (" + oldVal + ")");
+
+ // Same again:
+ expectedVal = expectedValAfterKeyEvent(key, elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test repeat of " + key + " for number control");
+
+ // Start at minimum:
+ oldVal = elem.value = elem.min;
+ expectedVal = expectedValAfterKeyEvent(key, elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test " + key + " for number control with value set to the minimum (" + oldVal + ")");
+
+ // Same again:
+ expectedVal = expectedValAfterKeyEvent(key, elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test repeat of " + key + " for number control");
+
+ // Test preventDefault():
+ elem.addEventListener("keypress", function(evt) {
+ evt.preventDefault();
+ elem.removeEventListener("keypress", arguments.callee, false);
+ }, false);
+ oldVal = elem.value = 0;
+ expectedVal = 0;
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test " + key + " for number control where scripted preventDefault() should prevent the value changing");
+
+ // Test step="any" behavior:
+ var oldStep = elem.step;
+ elem.step = "any";
+ oldVal = elem.value = 0;
+ expectedVal = expectedValAfterKeyEvent(key, elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test " + key + " for number control with value set to the midpoint and step='any' (" + oldVal + ")");
+ elem.step = oldStep; // restore
+
+ // Test that invalid input blocks UI initiated stepping:
+ oldVal = elem.value = "";
+ elem.select();
+ sendString("abc");
+ synthesizeKey(key, {});
+ is(elem.value, "", "Test " + key + " does nothing when the input is invalid");
+
+ // Test that no value does not block UI initiated stepping:
+ oldVal = elem.value = "";
+ elem.setAttribute("required", "required");
+ elem.select();
+ expectedVal = expectedValAfterKeyEvent(key, elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test " + key + " for number control with value set to the empty string and with the 'required' attribute set");
+
+ // Same again:
+ expectedVal = expectedValAfterKeyEvent(key, elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test repeat of " + key + " for number control");
+
+ // Reset 'required' attribute:
+ elem.removeAttribute("required");
+ }
+
+ // Test that key events are correctly dispatched
+ elem.max = "";
+ elem.value = "";
+ sendString("7837281");
+ is(elem.value, "7837281", "Test keypress event dispatch for number control");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_number_l10n.html b/dom/html/test/forms/test_input_number_l10n.html
new file mode 100644
index 000000000..4d46f7a80
--- /dev/null
+++ b/dom/html/test/forms/test_input_number_l10n.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=844744
+-->
+<head>
+ <title>Test localization of number control input</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="test_input_number_data.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta charset="UTF-8">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=844744">Mozilla Bug 844744</a>
+<p id="display"></p>
+<div id="content">
+ <input id="input" type="number" step="any">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 844744
+ * This test checks that localized input that is typed into <input type=number>
+ * is correctly handled.
+ **/
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function() {
+ startTests();
+ SimpleTest.finish();
+});
+
+var elem;
+
+function runTest(test) {
+ elem.lang = test.langTag;
+ elem.value = 0;
+ elem.focus();
+ elem.select();
+ sendString(test.inputWithGrouping);
+ is(elem.value, String(test.value), "Test " + test.desc + " ('" + test.langTag +
+ "') localization with grouping separator");
+ elem.value = 0;
+ elem.select();
+ sendString(test.inputWithoutGrouping);
+ is(elem.value, String(test.value), "Test " + test.desc + " ('" + test.langTag +
+ "') localization without grouping separator");
+}
+
+function runInvalidInputTest(test) {
+ elem.lang = test.langTag;
+ elem.value = 0;
+ elem.focus();
+ elem.select();
+ sendString(test.input);
+ is(elem.value, "", "Test " + test.desc + " ('" + test.langTag +
+ "') with invalid input: " + test.input);
+}
+
+function startTests() {
+ elem = document.getElementById("input");
+ for (var test of tests) {
+ runTest(test, elem);
+ }
+ for (var test of invalidTests) {
+ runInvalidInputTest(test, elem);
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_number_mouse_events.html b/dom/html/test/forms/test_input_number_mouse_events.html
new file mode 100644
index 000000000..45f310761
--- /dev/null
+++ b/dom/html/test/forms/test_input_number_mouse_events.html
@@ -0,0 +1,196 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=935501
+-->
+<head>
+ <title>Test mouse events for number</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"/>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <style>
+ input {
+ margin: 0 ! important;
+ border: 0 ! important;
+ padding: 0 ! important;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=935501">Mozilla Bug 935501</a>
+<p id="display"></p>
+<div id="content">
+ <input id="input" type="number">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 935501
+ * This test checks how the value of <input type=number> changes in response to
+ * various mouse events.
+ **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+SimpleTest.waitForFocus(function() {
+ test();
+});
+
+var input = document.getElementById("input");
+var inputRect = input.getBoundingClientRect();
+
+// Points over the input's spin-up and spin-down buttons (as offsets from the
+// top-left of the input's bounding client rect):
+const SPIN_UP_X = inputRect.width - 3;
+const SPIN_UP_Y = 3;
+const SPIN_DOWN_X = inputRect.width - 3;
+const SPIN_DOWN_Y = inputRect.height - 3;
+
+function test() {
+ input.value = 0;
+
+ // Test click on spin-up button:
+ synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousedown" });
+ is(input.value, "1", "Test step-up on mousedown on spin-up button");
+ synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mouseup" });
+ is(input.value, "1", "Test mouseup on spin-up button");
+
+ // Test click on spin-down button:
+ synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousedown" });
+ is(input.value, "0", "Test step-down on mousedown on spin-down button");
+ synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mouseup" });
+ is(input.value, "0", "Test mouseup on spin-down button");
+
+ // Test step="any" behavior:
+ input.value = 0;
+ var oldStep = input.step;
+ input.step = "any";
+ synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousedown" });
+ is(input.value, "1", "Test step-up on mousedown on spin-up button with step='any'");
+ synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mouseup" });
+ is(input.value, "1", "Test mouseup on spin-up button with step='any'");
+ synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousedown" });
+ is(input.value, "0", "Test step-down on mousedown on spin-down button with step='any'");
+ synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mouseup" });
+ is(input.value, "0", "Test mouseup on spin-down button with step='any'");
+ input.step = oldStep; // restore
+
+ // Test that preventDefault() works:
+ function preventDefault(e) {
+ e.preventDefault();
+ }
+ input.value = 1;
+ input.addEventListener("mousedown", preventDefault, false);
+ synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, {});
+ is(input.value, "1", "Test that preventDefault() works for click on spin-up button");
+ synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, {});
+ is(input.value, "1", "Test that preventDefault() works for click on spin-down button");
+ input.removeEventListener("mousedown", preventDefault, false);
+
+ // Run the spin tests:
+ runNextSpinTest();
+}
+
+function runNextSpinTest() {
+ var test = spinTests.shift();
+ if (!test) {
+ SimpleTest.finish();
+ return;
+ }
+ test();
+}
+
+const SETTIMEOUT_DELAY = 500;
+
+var spinTests = [
+ // Test spining when the mouse button is kept depressed on the spin-up
+ // button, then moved over the spin-down button:
+ function() {
+ var inputEventCount = 0;
+ input.value = 0;
+ input.addEventListener("input", function(evt) {
+ ++inputEventCount;
+ if (inputEventCount == 3) {
+ is(input.value, "3", "Testing spin-up button");
+ synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousemove" });
+ } else if (inputEventCount == 6) {
+ is(input.value, "0", "Testing spin direction is reversed after mouse moves from spin-up button to spin-down button");
+ input.removeEventListener("input", arguments.callee, false);
+ synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mouseup" });
+ runNextSpinTest();
+ }
+ }, false);
+ synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousedown" });
+ },
+
+ // Test spining when the mouse button is kept depressed on the spin-down
+ // button, then moved over the spin-up button:
+ function() {
+ var inputEventCount = 0;
+ input.value = 0;
+ input.addEventListener("input", function(evt) {
+ ++inputEventCount;
+ if (inputEventCount == 3) {
+ is(input.value, "-3", "Testing spin-down button");
+ synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousemove" });
+ } else if (inputEventCount == 6) {
+ is(input.value, "0", "Testing spin direction is reversed after mouse moves from spin-down button to spin-up button");
+ input.removeEventListener("input", arguments.callee, false);
+ synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mouseup" });
+ runNextSpinTest();
+ }
+ }, false);
+ synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousedown" });
+ },
+
+ // Test that the spin is stopped when the mouse button is depressod on the
+ // spin-up button, then moved outside both buttons once the spin starts:
+ function() {
+ var inputEventCount = 0;
+ input.value = 0;
+ input.addEventListener("input", function(evt) {
+ ++inputEventCount;
+ if (inputEventCount == 3) {
+ synthesizeMouse(input, -1, -1, { type: "mousemove" });
+ var eventHandler = arguments.callee;
+ setTimeout(function() {
+ is(input.value, "3", "Testing moving the mouse outside the spin buttons stops the spin");
+ is(inputEventCount, 3, "Testing moving the mouse outside the spin buttons stops the spin input events");
+ input.removeEventListener("input", eventHandler, false);
+ synthesizeMouse(input, -1, -1, { type: "mouseup" });
+ runNextSpinTest();
+ }, SETTIMEOUT_DELAY);
+ }
+ }, false);
+ synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousedown" });
+ },
+
+ // Test that changing the input type in the middle of a spin cancels the spin:
+ function() {
+ var inputEventCount = 0;
+ input.value = 0;
+ input.addEventListener("input", function(evt) {
+ ++inputEventCount;
+ if (inputEventCount == 3) {
+ input.type = "text"
+ var eventHandler = arguments.callee;
+ setTimeout(function() {
+ is(input.value, "-3", "Testing changing input type during a spin stops the spin");
+ is(inputEventCount, 3, "Testing changing input type during a spin stops the spin input events");
+ input.removeEventListener("input", eventHandler, false);
+ synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mouseup" });
+ input.type = "number"; // restore
+ runNextSpinTest();
+ }, SETTIMEOUT_DELAY);
+ }
+ }, false);
+ synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousedown" });
+ }
+];
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_number_rounding.html b/dom/html/test/forms/test_input_number_rounding.html
new file mode 100644
index 000000000..16bc8ec52
--- /dev/null
+++ b/dom/html/test/forms/test_input_number_rounding.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=783607
+-->
+<head>
+ <title>Test rounding behaviour for &lt;input type='number'&gt;</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"/>
+ <meta charset="UTF-8">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783607">Mozilla Bug 783607</a>
+<p id="display"></p>
+<div id="content">
+ <input id=number type=number value=0 step=0.01 max=1>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 783607.
+ * This test checks that when <input type=number> has fractional step values,
+ * the values that a content author will see in their script will not have
+ * ugly rounding errors.
+ **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ test();
+ SimpleTest.finish();
+});
+
+/**
+ * We can _NOT_ generate these values by looping and simply incrementing a
+ * variable by 0.01 and stringifying it, since we'll end up with strings like
+ * "0.060000000000000005" due to the inability of binary floating point numbers
+ * to accurately represent decimal values.
+ */
+var stepVals = [
+ "0", "0.01", "0.02", "0.03", "0.04", "0.05", "0.06", "0.07", "0.08", "0.09",
+ "0.1", "0.11", "0.12", "0.13", "0.14", "0.15", "0.16", "0.17", "0.18", "0.19",
+ "0.2", "0.21", "0.22", "0.23", "0.24", "0.25", "0.26", "0.27", "0.28", "0.29",
+ "0.3", "0.31", "0.32", "0.33", "0.34", "0.35", "0.36", "0.37", "0.38", "0.39",
+ "0.4", "0.41", "0.42", "0.43", "0.44", "0.45", "0.46", "0.47", "0.48", "0.49",
+ "0.5", "0.51", "0.52", "0.53", "0.54", "0.55", "0.56", "0.57", "0.58", "0.59",
+ "0.6", "0.61", "0.62", "0.63", "0.64", "0.65", "0.66", "0.67", "0.68", "0.69",
+ "0.7", "0.71", "0.72", "0.73", "0.74", "0.75", "0.76", "0.77", "0.78", "0.79",
+ "0.8", "0.81", "0.82", "0.83", "0.84", "0.85", "0.86", "0.87", "0.88", "0.89",
+ "0.9", "0.91", "0.92", "0.93", "0.94", "0.95", "0.96", "0.97", "0.98", "0.99",
+ "1"
+];
+
+var pgUpDnVals = [
+ "0", "0.1", "0.2", "0.3", "0.4", "0.5", "0.6", "0.7", "0.8", "0.9", "1"
+];
+
+function test() {
+ var elem = document.getElementById("number");
+
+ elem.focus();
+
+ /**
+ * TODO:
+ * When <input type='number'> widget will have a widge we should test PAGE_UP,
+ * PAGE_DOWN, UP and DOWN keys. For the moment, there is no widget so those
+ * keys do not have any effect.
+ * The tests using those keys as marked as todo_is() hoping that at least part
+ * of them will fail when the widget will be implemented.
+ */
+
+/* No other implementations implement this, so we don't either, for now.
+ Seems like it might be nice though.
+
+ for (var i = 1; i < pgUpDnVals.length; ++i) {
+ synthesizeKey("VK_PAGE_UP", {});
+ todo_is(elem.value, pgUpDnVals[i], "Test VK_PAGE_UP");
+ is(elem.validity.valid, true, "Check element is valid for value " + pgUpDnVals[i]);
+ }
+
+ for (var i = pgUpDnVals.length - 2; i >= 0; --i) {
+ synthesizeKey("VK_PAGE_DOWN", {});
+ // TODO: this condition is there because the todo_is() below would pass otherwise.
+ if (stepVals[i] == 0) { continue; }
+ todo_is(elem.value, pgUpDnVals[i], "Test VK_PAGE_DOWN");
+ is(elem.validity.valid, true, "Check element is valid for value " + pgUpDnVals[i]);
+ }
+*/
+
+ for (var i = 1; i < stepVals.length; ++i) {
+ synthesizeKey("VK_UP", {});
+ is(elem.value, stepVals[i], "Test VK_UP");
+ is(elem.validity.valid, true, "Check element is valid for value " + stepVals[i]);
+ }
+
+ for (var i = stepVals.length - 2; i >= 0; --i) {
+ synthesizeKey("VK_DOWN", {});
+ // TODO: this condition is there because the todo_is() below would pass otherwise.
+ if (stepVals[i] == 0) { continue; }
+ is(elem.value, stepVals[i], "Test VK_DOWN");
+ is(elem.validity.valid, true, "Check element is valid for value " + stepVals[i]);
+ }
+
+ for (var i = 1; i < stepVals.length; ++i) {
+ elem.stepUp();
+ is(elem.value, stepVals[i], "Test stepUp()");
+ is(elem.validity.valid, true, "Check element is valid for value " + stepVals[i]);
+ }
+
+ for (var i = stepVals.length - 2; i >= 0; --i) {
+ elem.stepDown();
+ is(elem.value, stepVals[i], "Test stepDown()");
+ is(elem.validity.valid, true, "Check element is valid for value " + stepVals[i]);
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_number_validation.html b/dom/html/test/forms/test_input_number_validation.html
new file mode 100644
index 000000000..5f551337b
--- /dev/null
+++ b/dom/html/test/forms/test_input_number_validation.html
@@ -0,0 +1,143 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=827161
+-->
+<head>
+ <title>Test validation of number control input</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="test_input_number_data.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta charset="UTF-8">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=827161">Mozilla Bug 827161</a>
+<p id="display"></p>
+<div id="content">
+ <input id="input" type="number" step="0.01" oninvalid="invalidEventHandler(event);">
+ <input id="requiredinput" type="number" step="0.01" required
+ oninvalid="invalidEventHandler(event);">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 827161.
+ * This test checks that validation works correctly for <input type=number>.
+ **/
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function() {
+ startTests();
+ SimpleTest.finish();
+});
+
+var elem;
+
+function runTest(test) {
+ elem.lang = test.langTag;
+
+ gInvalid = false; // reset
+ var desc = `${test.desc} (lang='${test.langTag}', id='${elem.id}')`;
+ elem.value = 0;
+ elem.focus();
+ elem.select();
+ sendString(test.inputWithGrouping);
+ checkIsValid(elem, `${desc} with grouping separator`);
+ sendChar("a");
+ checkIsInvalid(elem, `${desc} with grouping separator`);
+
+ gInvalid = false; // reset
+ elem.value = 0;
+ elem.select();
+ sendString(test.inputWithoutGrouping);
+ checkIsValid(elem, `${desc} without grouping separator`);
+ sendChar("a");
+ checkIsInvalid(elem, `${desc} without grouping separator`);
+}
+
+function runInvalidInputTest(test) {
+ elem.lang = test.langTag;
+
+ gInvalid = false; // reset
+ var desc = `${test.desc} (lang='${test.langTag}', id='${elem.id}')`;
+ elem.value = 0;
+ elem.focus();
+ elem.select();
+ sendString(test.input);
+ checkIsInvalid(elem, `${desc} with invalid input ${test.input}`);
+}
+
+function startTests() {
+ elem = document.getElementById("input");
+ for (var test of tests) {
+ runTest(test);
+ }
+ for (var test of invalidTests) {
+ runInvalidInputTest(test);
+ }
+ elem = document.getElementById("requiredinput");
+ for (var test of tests) {
+ runTest(test);
+ }
+
+ gInvalid = false; // reset
+ elem.value = "";
+ checkIsInvalidEmptyValue(elem, "empty value");
+}
+
+var gInvalid = false;
+
+function invalidEventHandler(e)
+{
+ is(e.type, "invalid", "Invalid event type should be 'invalid'");
+ gInvalid = true;
+}
+
+function checkIsValid(element, infoStr)
+{
+ ok(!element.validity.badInput,
+ "Element should not suffer from bad input for " + infoStr);
+ ok(element.validity.valid, "Element should be valid for " + infoStr);
+ ok(element.checkValidity(), "checkValidity() should return true for " + infoStr);
+ ok(!gInvalid, "The invalid event should not have been thrown for " + infoStr);
+ is(element.validationMessage, '',
+ "Validation message should be the empty string for " + infoStr);
+ ok(element.matches(":valid"), ":valid pseudo-class should apply for " + infoStr);
+}
+
+function checkIsInvalid(element, infoStr)
+{
+ ok(element.validity.badInput,
+ "Element should suffer from bad input for " + infoStr);
+ if (element.id == "requiredinput") {
+ ok(element.validity.valueMissing,
+ "Element should suffer from value missing for " + infoStr);
+ }
+ ok(!element.validity.valid, "Element should not be valid for " + infoStr);
+ ok(!element.checkValidity(), "checkValidity() should return false for " + infoStr);
+ ok(gInvalid, "The invalid event should have been thrown for " + infoStr);
+ is(element.validationMessage, "Please enter a number.",
+ "Validation message is not the expected message for " + infoStr);
+ ok(element.matches(":invalid"), ":invalid pseudo-class should apply for " + infoStr);
+}
+
+function checkIsInvalidEmptyValue(element, infoStr)
+{
+ ok(!element.validity.badInput,
+ "Element should not suffer from bad input for " + infoStr);
+ ok(element.validity.valueMissing,
+ "Element should suffer from value missing for " + infoStr);
+ ok(!element.validity.valid, "Element should not be valid for " + infoStr);
+ ok(!element.checkValidity(), "checkValidity() should return false for " + infoStr);
+ ok(gInvalid, "The invalid event should have been thrown for " + infoStr);
+ is(element.validationMessage, "Please enter a number.",
+ "Validation message is not the expected message for " + infoStr);
+ ok(element.matches(":invalid"), ":invalid pseudo-class should apply for " + infoStr);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_radio_indeterminate.html b/dom/html/test/forms/test_input_radio_indeterminate.html
new file mode 100644
index 000000000..aa7ca21bf
--- /dev/null
+++ b/dom/html/test/forms/test_input_radio_indeterminate.html
@@ -0,0 +1,109 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=885359
+-->
+<head>
+ <title>Test for Bug 885359</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=885359">Mozilla Bug 343444</a>
+<p id="display"></p>
+<form>
+ <input type="radio" id='radio1'/><br/>
+
+ <input type="radio" id="g1radio1" name="group1"/>
+ <input type="radio" id="g1radio2" name="group1"/></br>
+ <input type="radio" id="g1radio3" name="group1"/></br>
+
+ <input type="radio" id="g2radio1" name="group2"/>
+ <input type="radio" id="g2radio2" name="group2" checked/></br>
+</form>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var radio1 = document.getElementById("radio1");
+var g1radio1 = document.getElementById("g1radio1");
+var g1radio2 = document.getElementById("g1radio2");
+var g1radio3 = document.getElementById("g1radio3");
+var g2radio1 = document.getElementById("g2radio1");
+var g2radio2 = document.getElementById("g2radio2");
+
+SimpleTest.waitForFocus(function() {
+ test();
+ SimpleTest.finish();
+});
+
+function verifyIndeterminateState(aElement, aIsIndeterminate, aMessage) {
+ is(aElement.mozMatchesSelector(':indeterminate'), aIsIndeterminate, aMessage);
+}
+
+function test() {
+ // Initial State.
+ verifyIndeterminateState(radio1, true,
+ "Unchecked radio in its own group (no name attribute)");
+ verifyIndeterminateState(g1radio1, true, "No selected radio in its group");
+ verifyIndeterminateState(g1radio2, true, "No selected radio in its group");
+ verifyIndeterminateState(g1radio3, true, "No selected radio in its group");
+ verifyIndeterminateState(g2radio1, false, "Selected radio in its group");
+ verifyIndeterminateState(g2radio2, false, "Selected radio in its group");
+
+ // Selecting radio buttion.
+ g1radio1.checked = true;
+ verifyIndeterminateState(g1radio1, false,
+ "Selecting a radio should affect all radios in the group");
+ verifyIndeterminateState(g1radio2, false,
+ "Selecting a radio should affect all radios in the group");
+ verifyIndeterminateState(g1radio3, false,
+ "Selecting a radio should affect all radios in the group");
+
+ // Changing the selected radio button.
+ g1radio3.checked = true;
+ verifyIndeterminateState(g1radio1, false,
+ "Selecting a radio should affect all radios in the group");
+ verifyIndeterminateState(g1radio2, false,
+ "Selecting a radio should affect all radios in the group");
+ verifyIndeterminateState(g1radio3, false,
+ "Selecting a radio should affect all radios in the group");
+
+ // Deselecting radio button.
+ g2radio2.checked = false;
+ verifyIndeterminateState(g2radio1, true,
+ "Deselecting a radio should affect all radios in the group");
+ verifyIndeterminateState(g2radio2, true,
+ "Deselecting a radio should affect all radios in the group");
+
+ // Move a selected radio button to another group.
+ g1radio3.name = "group2";
+
+ // The radios' state in the original group becomes indeterminated.
+ verifyIndeterminateState(g1radio1, true,
+ "Removing a radio from a group should affect all radios in the group");
+ verifyIndeterminateState(g1radio2, true,
+ "Removing a radio from a group should affect all radios in the group");
+
+ // The radios' state in the new group becomes determinated.
+ verifyIndeterminateState(g1radio3, false,
+ "Adding a radio from a group should affect all radios in the group");
+ verifyIndeterminateState(g2radio1, false,
+ "Adding a radio from a group should affect all radios in the group");
+ verifyIndeterminateState(g2radio2, false,
+ "Adding a radio from a group should affect all radios in the group");
+
+ // Change input type to 'text'.
+ g1radio3.type = "text";
+ verifyIndeterminateState(g1radio3, false,
+ "Input type text does not have an indeterminate state");
+ verifyIndeterminateState(g2radio1, true,
+ "Changing input type should affect all radios in the group");
+ verifyIndeterminateState(g2radio2, true,
+ "Changing input type should affect all radios in the group");
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/forms/test_input_radio_radiogroup.html b/dom/html/test/forms/test_input_radio_radiogroup.html
new file mode 100644
index 000000000..86dd212b8
--- /dev/null
+++ b/dom/html/test/forms/test_input_radio_radiogroup.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=343444
+-->
+<head>
+ <title>Test for Bug 343444</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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=343444">Mozilla Bug 343444</a>
+<p id="display"></p>
+<form>
+ <fieldset id="testradio">
+ <input type="radio" name="testradio" id="start"></input>
+ <input type="text" name="testradio"></input>
+ <input type="text" name="testradio"></input>
+ <input type="radio" name="testradio"></input>
+ <input type="text" name="testradio"></input>
+ <input type="radio" name="testradio"></input>
+ <input type="text" name="testradio"></input>
+ <input type="radio" name="testradio"></input>
+ <input type="radio" name="testradio"></input>
+ <input type="text" name="testradio"></input>
+ </fieldset>
+
+ <fieldset>
+ <input type="radio" name="testtwo" id="start2"></input>
+ <input type="radio" name="testtwo"></input>
+ <input type="radio" name="error" id="testtwo"></input>
+ <input type="radio" name="testtwo" id="end"></input>
+ </fieldset>
+
+ <fieldset>
+ <input type="radio" name="testthree" id="start3"></input>
+ <input type="radio" name="errorthree" id="testthree"></input>
+ </fieldset>
+</form>
+<script class="testbody" type="text/javascript">
+/** Test for Bug 343444 **/
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set":[["snav.enabled", false]]}, startTest);
+function startTest() {
+ document.getElementById("start").focus();
+ var count=0;
+ while (count < 2) {
+ sendKey("DOWN");
+ is(document.activeElement.type, "radio", "radioGroup should ignore non-radio input fields");
+ if (document.activeElement.id == "start") {
+ count++;
+ }
+ }
+
+ document.getElementById("start2").focus();
+ count = 0;
+ while (count < 3) {
+ is(document.activeElement.name, "testtwo",
+ "radioGroup should only contain elements with the same @name")
+ sendKey("DOWN");
+ count++;
+ }
+
+ document.getElementById("start3").focus();
+ sendKey("DOWN");
+ is(document.activeElement.name, "testthree", "we don't have an infinite-loop");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/forms/test_input_radio_required.html b/dom/html/test/forms/test_input_radio_required.html
new file mode 100644
index 000000000..e1ba42914
--- /dev/null
+++ b/dom/html/test/forms/test_input_radio_required.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id={BUGNUMBER}
+-->
+<head>
+ <title>Test for Bug 1100535</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=1100535">Mozilla Bug 1100535</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<form>
+ <input type="radio" name="a">
+</form>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ var input = document.querySelector("input");
+ input.setAttribute("required", "x");
+ input.setAttribute("required", "y");
+ is(document.forms[0].checkValidity(), false);
+ input.required = false;
+ is(document.forms[0].checkValidity(), true);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_range_attr_order.html b/dom/html/test/forms/test_input_range_attr_order.html
new file mode 100644
index 000000000..3d3d6d6f7
--- /dev/null
+++ b/dom/html/test/forms/test_input_range_attr_order.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=841941
+-->
+<head>
+ <title>Test @min/@max/@step order for range</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"/>
+ <meta charset="UTF-8">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=841941">Mozilla Bug 841941</a>
+<p id="display"></p>
+<div id="content">
+ <input type=range value=2 max=1.5 step=0.5>
+ <input type=range value=2 step=0.5 max=1.5>
+ <input type=range value=2 max=1.5 step=0.5>
+ <input type=range value=2 step=0.5 max=1.5>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 841941
+ * This test checks that the order in which @min/@max/@step are specified in
+ * markup makes no difference to the value that <input type=range> will be
+ * given. Basically this checks that sanitization of the value does not occur
+ * until after the parser has finished with the element.
+ */
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ test();
+ SimpleTest.finish();
+});
+
+function test() {
+ var ranges = document.querySelectorAll("input[type=range]");
+ for (var i = 0; i < ranges.length; i++) {
+ is(ranges.item(i).value, "1.5", "Check sanitization order for range " + i);
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_range_key_events.html b/dom/html/test/forms/test_input_range_key_events.html
new file mode 100644
index 000000000..efb1f7e4b
--- /dev/null
+++ b/dom/html/test/forms/test_input_range_key_events.html
@@ -0,0 +1,210 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=843725
+-->
+<head>
+ <title>Test key events for range</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"/>
+ <meta charset="UTF-8">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=843725">Mozilla Bug 843725</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 843725
+ * This test checks how the value of <input type=range> changes in response to
+ * various key events while it is in various states.
+ **/
+SimpleTest.waitForExplicitFinish();
+
+// Turn off Spatial Navigation because it hijacks arrow keydown events:
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[["snav.enabled", false]]}, function() {
+ test();
+ SimpleTest.finish();
+ });
+});
+
+const defaultMinimum = 0;
+const defaultMaximum = 100;
+const defaultStep = 1;
+
+// Helpers:
+// For the sake of simplicity, we do not currently support fractional value,
+// step, etc.
+
+function minimum(element) {
+ return Number(element.min || defaultMinimum);
+}
+
+function maximum(element) {
+ return Number(element.max || defaultMaximum);
+}
+
+function range(element) {
+ var max = maximum(element);
+ var min = minimum(element);
+ if (max < min) {
+ return 0;
+ }
+ return max - min;
+}
+
+function defaultValue(element) {
+ return minimum(element) + range(element)/2;
+}
+
+function value(element) {
+ return Number(element.value || defaultValue(element));
+}
+
+function step(element) {
+ var step = Number(element.step || defaultStep);
+ return step <= 0 ? defaultStep : step;
+}
+
+function clampToRange(value, element) {
+ var min = minimum(element);
+ var max = maximum(element);
+ if (max < min) {
+ return min;
+ }
+ if (value < min) {
+ return min;
+ }
+ if (value > max) {
+ return max;
+ }
+ return value;
+}
+
+// Functions used to specify expected test results:
+
+function valuePlusStep(element) {
+ return clampToRange(value(element) + step(element), element);
+}
+
+function valueMinusStep(element) {
+ return clampToRange(value(element) - step(element), element);
+}
+
+/**
+ * Returns the current value of the range plus whichever is greater of either
+ * 10% of the range or its current step value, clamped to the range's minimum/
+ * maximum. The reason for using the step if it is greater than 10% of the
+ * range is because otherwise the PgUp/PgDn keys would do nothing in that case.
+ */
+function valuePlusTenPctOrStep(element) {
+ var tenPct = range(element)/10;
+ var stp = step(element);
+ return clampToRange(value(element) + Math.max(tenPct, stp), element);
+}
+
+function valueMinusTenPctOrStep(element) {
+ var tenPct = range(element)/10;
+ var stp = step(element);
+ return clampToRange(value(element) - Math.max(tenPct, stp), element);
+}
+
+// Test table:
+
+const LTR = "ltr";
+const RTL = "rtl";
+
+var testTable = [
+ ["VK_LEFT", LTR, valueMinusStep],
+ ["VK_LEFT", RTL, valuePlusStep],
+ ["VK_RIGHT", LTR, valuePlusStep],
+ ["VK_RIGHT", RTL, valueMinusStep],
+ ["VK_UP", LTR, valuePlusStep],
+ ["VK_UP", RTL, valuePlusStep],
+ ["VK_DOWN", LTR, valueMinusStep],
+ ["VK_DOWN", RTL, valueMinusStep],
+ ["VK_PAGE_UP", LTR, valuePlusTenPctOrStep],
+ ["VK_PAGE_UP", RTL, valuePlusTenPctOrStep],
+ ["VK_PAGE_DOWN", LTR, valueMinusTenPctOrStep],
+ ["VK_PAGE_DOWN", RTL, valueMinusTenPctOrStep],
+ ["VK_HOME", LTR, minimum],
+ ["VK_HOME", RTL, minimum],
+ ["VK_END", LTR, maximum],
+ ["VK_END", RTL, maximum],
+]
+
+function test() {
+ var elem = document.createElement("input");
+ elem.type = "range";
+
+ var content = document.getElementById("content");
+ content.appendChild(elem);
+ elem.focus();
+
+ for (test of testTable) {
+ var [key, dir, expectedFunc] = test;
+ var oldVal, expectedVal;
+
+ elem.step = "2";
+ elem.style.direction = dir;
+ var flush = document.body.clientWidth;
+
+ // Start at middle:
+ elem.value = oldVal = defaultValue(elem);
+ expectedVal = expectedFunc(elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test " + key + " for " + dir + " range with value set to the midpoint (" + oldVal + ")");
+
+ // Same again:
+ expectedVal = expectedFunc(elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test repeat of " + key + " for " + dir + " range");
+
+ // Start at maximum:
+ elem.value = oldVal = maximum(elem);
+ expectedVal = expectedFunc(elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test " + key + " for " + dir + " range with value set to the maximum (" + oldVal + ")");
+
+ // Same again:
+ expectedVal = expectedFunc(elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test repeat of " + key + " for " + dir + " range");
+
+ // Start at minimum:
+ elem.value = oldVal = minimum(elem);
+ expectedVal = expectedFunc(elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test " + key + " for " + dir + " range with value set to the minimum (" + oldVal + ")");
+
+ // Same again:
+ expectedVal = expectedFunc(elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test repeat of " + key + " for " + dir + " range");
+
+ // Test for a step value that is greater than 10% of the range:
+ elem.step = 20;
+ elem.value = 60;
+ expectedVal = expectedFunc(elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test " + key + " for " + dir + " range with a step that is greater than 10% of the range (step=" + elem.step + ")");
+
+ // Same again:
+ expectedVal = expectedFunc(elem);
+ synthesizeKey(key, {});
+ is(elem.value, String(expectedVal), "Test repeat of " + key + " for " + dir + " range");
+
+ // reset step:
+ elem.step = 2;
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_range_mouse_and_touch_events.html b/dom/html/test/forms/test_input_range_mouse_and_touch_events.html
new file mode 100644
index 000000000..4073fe139
--- /dev/null
+++ b/dom/html/test/forms/test_input_range_mouse_and_touch_events.html
@@ -0,0 +1,199 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=846380
+-->
+<head>
+ <title>Test mouse and touch events for range</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"/>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <style>
+ /* synthesizeMouse and synthesizeFunc uses getBoundingClientRect. We set
+ * the following properties to avoid fractional values in the rect returned
+ * by getBoundingClientRect in order to avoid rounding that would occur
+ * when event coordinates are internally converted to be relative to the
+ * top-left of the element. (Such rounding would make it difficult to
+ * predict exactly what value the input should take on for events at
+ * certain coordinates.)
+ */
+ input { margin: 0 ! important; width: 200px ! important; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=846380">Mozilla Bug 846380</a>
+<p id="display"></p>
+<div id="content">
+ <input id="range" type="range">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 846380
+ * This test checks how the value of <input type=range> changes in response to
+ * various mouse and touch events.
+ **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ test(synthesizeMouse, "click", "mousedown", "mousemove", "mouseup");
+ test(synthesizeTouch, "tap", "touchstart", "touchmove", "touchend");
+ SimpleTest.finish();
+});
+
+const MIDDLE_OF_RANGE = "50";
+const MINIMUM_OF_RANGE = "0";
+const MAXIMUM_OF_RANGE = "100";
+const QUARTER_OF_RANGE = "25";
+const THREE_QUARTERS_OF_RANGE = "75";
+
+function flush() {
+ // Flush style, specifically to flush the 'direction' property so that the
+ // browser uses the new value for thumb positioning.
+ var flush = document.body.clientWidth;
+}
+
+function test(synthesizeFunc, clickOrTap, startName, moveName, endName) {
+ var elem = document.getElementById("range");
+ elem.focus();
+ flush();
+
+ var width = parseFloat(window.getComputedStyle(elem).width);
+ var height = parseFloat(window.getComputedStyle(elem).height);
+ var borderLeft = parseFloat(window.getComputedStyle(elem).borderLeftWidth);
+ var borderTop = parseFloat(window.getComputedStyle(elem).borderTopWidth);
+ var paddingLeft = parseFloat(window.getComputedStyle(elem).paddingLeft);
+ var paddingTop = parseFloat(window.getComputedStyle(elem).paddingTop);
+
+ // Extrema for mouse/touch events:
+ var midY = height / 2 + borderTop + paddingTop;
+ var minX = borderLeft + paddingLeft;
+ var midX = minX + width / 2;
+ var maxX = minX + width;
+
+ // Test click/tap in the middle of the range:
+ elem.value = QUARTER_OF_RANGE;
+ synthesizeFunc(elem, midX, midY, {});
+ is(elem.value, MIDDLE_OF_RANGE, "Test " + clickOrTap + " in middle of range");
+
+ // Test mouse/touch dragging of ltr range:
+ elem.value = QUARTER_OF_RANGE;
+ synthesizeFunc(elem, midX, midY, { type: startName });
+ is(elem.value, MIDDLE_OF_RANGE, "Test " + startName + " in middle of range");
+ synthesizeFunc(elem, minX, midY, { type: moveName });
+ is(elem.value, MINIMUM_OF_RANGE, "Test dragging of range to left of ltr range");
+
+ synthesizeFunc(elem, maxX, midY, { type: moveName });
+ is(elem.value, MAXIMUM_OF_RANGE, "Test dragging of range to right of ltr range (" + moveName + ")");
+
+ synthesizeFunc(elem, maxX, midY, { type: endName });
+ is(elem.value, MAXIMUM_OF_RANGE, "Test dragging of range to right of ltr range (" + endName + ")");
+
+ // Test mouse/touch dragging of rtl range:
+ elem.value = QUARTER_OF_RANGE;
+ elem.style.direction = "rtl";
+ flush();
+ synthesizeFunc(elem, midX, midY, { type: startName });
+ is(elem.value, MIDDLE_OF_RANGE, "Test " + startName + " in middle of rtl range");
+ synthesizeFunc(elem, minX, midY, { type: moveName });
+ is(elem.value, MAXIMUM_OF_RANGE, "Test dragging of range to left of rtl range");
+
+ synthesizeFunc(elem, maxX, midY, { type: moveName });
+ is(elem.value, MINIMUM_OF_RANGE, "Test dragging of range to right of rtl range (" + moveName + ")");
+
+ synthesizeFunc(elem, maxX, midY, { type: endName });
+ is(elem.value, MINIMUM_OF_RANGE, "Test dragging of range to right of rtl range (" + endName + ")");
+
+ elem.style.direction = "ltr"; // reset direction
+ flush();
+
+ // Test mouse/touch capturing by moving pointer to a position outside the range:
+ elem.value = QUARTER_OF_RANGE;
+ synthesizeFunc(elem, midX, midY, { type: startName });
+ is(elem.value, MIDDLE_OF_RANGE, "Test " + startName + " in middle of range");
+ synthesizeFunc(elem, maxX+100, midY, { type: moveName });
+ is(elem.value, MAXIMUM_OF_RANGE, "Test dragging of range to position outside range (" + moveName + ")");
+
+ synthesizeFunc(elem, maxX+100, midY, { type: endName });
+ is(elem.value, MAXIMUM_OF_RANGE, "Test dragging of range to position outside range (" + endName + ")");
+
+ // Test mouse/touch capturing by moving pointer to a position outside a rtl range:
+ elem.value = QUARTER_OF_RANGE;
+ elem.style.direction = "rtl";
+ flush();
+ synthesizeFunc(elem, midX, midY, { type: startName });
+ is(elem.value, MIDDLE_OF_RANGE, "Test " + startName + " in middle of rtl range");
+ synthesizeFunc(elem, maxX+100, midY, { type: moveName });
+ is(elem.value, MINIMUM_OF_RANGE, "Test dragging of range to position outside range (" + moveName + ")");
+
+ synthesizeFunc(elem, maxX+100, midY, { type: endName });
+ is(elem.value, MINIMUM_OF_RANGE, "Test dragging of range to position outside range (" + endName + ")");
+
+ elem.style.direction = "ltr"; // reset direction
+ flush();
+
+ // Test mouse/touch events with modifiers are ignored:
+ var modifiers = ["shiftKey", "ctrlKey", "altKey", "metaKey", "accelKey", "altGrKey", "fnKey", "osKey"];
+ for (var modifier of modifiers) {
+ elem.value = QUARTER_OF_RANGE;
+ var eventParams = {};
+ eventParams[modifier] = true;
+ synthesizeFunc(elem, midX, midY, eventParams);
+ is(elem.value, QUARTER_OF_RANGE, "Test " + clickOrTap + " in the middle of range with " + modifier + " modifier key is ignored");
+ }
+
+ // Test that preventDefault() works:
+ function preventDefault(e) {
+ e.preventDefault();
+ }
+ elem.value = QUARTER_OF_RANGE;
+ elem.addEventListener(startName, preventDefault, false);
+ synthesizeFunc(elem, midX, midY, {});
+ is(elem.value, QUARTER_OF_RANGE, "Test that preventDefault() works");
+ elem.removeEventListener(startName, preventDefault, false);
+
+ // Test that changing the input type in the middle of a drag cancels the drag:
+ elem.value = QUARTER_OF_RANGE;
+ synthesizeFunc(elem, midX, midY, { type: startName });
+ is(elem.value, MIDDLE_OF_RANGE, "Test " + startName + " in middle of range");
+ elem.type = "text";
+ is(elem.value, QUARTER_OF_RANGE, "Test that changing the input type cancels a drag");
+ synthesizeFunc(elem, midX, midY, { type: endName });
+ is(elem.value, QUARTER_OF_RANGE, "Test that changing the input type cancels a drag (after " + endName + ")");
+ elem.type = "range";
+
+ // Check that we do not drag when the mousedown/touchstart occurs outside the range:
+ elem.value = QUARTER_OF_RANGE;
+ synthesizeFunc(elem, maxX+100, midY, { type: startName });
+ is(elem.value, QUARTER_OF_RANGE, "Test " + startName + " outside range doesn't change its value");
+ synthesizeFunc(elem, midX, midY, { type: moveName });
+ is(elem.value, QUARTER_OF_RANGE, "Test dragging is not occurring when " + startName + " was outside range");
+
+ synthesizeFunc(elem, midX, midY, { type: endName });
+ is(elem.value, QUARTER_OF_RANGE, "Test dragging is not occurring when " + startName + " was outside range");
+
+ elem.focus(); // RESTORE FOCUS SO WE GET THE FOCUSED STYLE FOR TESTING OR ELSE minX/midX/maxX may be wrong!
+
+ // Check what happens when a value changing key is pressed during a drag:
+ elem.value = QUARTER_OF_RANGE;
+ synthesizeFunc(elem, midX, midY, { type: startName });
+ is(elem.value, MIDDLE_OF_RANGE, "Test " + startName + " in middle of range");
+ synthesizeKey("VK_HOME", {});
+ // The VK_HOME tests are disabled until I can figure out why they fail on Android -jwatt
+ //is(elem.value, MINIMUM_OF_RANGE, "Test VK_HOME during a drag sets the value to the minimum of the range");
+ synthesizeFunc(elem, midX+100, midY, { type: moveName });
+ is(elem.value, MAXIMUM_OF_RANGE, "Test " + moveName + " outside range after key press that occurred during a drag changes the value");
+ synthesizeFunc(elem, midX, midY, { type: moveName });
+ is(elem.value, MIDDLE_OF_RANGE, "Test " + moveName + " in middle of range");
+ synthesizeKey("VK_HOME", {});
+ //is(elem.value, MINIMUM_OF_RANGE, "Test VK_HOME during a drag sets the value to the minimum of the range (second time)");
+ synthesizeFunc(elem, maxX+100, midY, { type: endName });
+ is(elem.value, MAXIMUM_OF_RANGE, "Test " + endName + " outside range after key press that occurred during a drag changes the value");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_range_rounding.html b/dom/html/test/forms/test_input_range_rounding.html
new file mode 100644
index 000000000..dfbca36ce
--- /dev/null
+++ b/dom/html/test/forms/test_input_range_rounding.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=853525
+-->
+<head>
+ <title>Test key events for range</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"/>
+ <meta charset="UTF-8">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=853525">Mozilla Bug 853525</a>
+<p id="display"></p>
+<div id="content">
+ <input id=range type=range value=0 step=0.01 max=1>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 853525
+ * This test checks that when <input type=range> has fractional step values,
+ * the values that a content author will see in their script will not have
+ * ugly rounding errors.
+ **/
+SimpleTest.waitForExplicitFinish();
+// Turn off Spatial Navigation because it hijacks arrow keydown events:
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[["snav.enabled", false]]}, function() {
+ test();
+ SimpleTest.finish();
+ });
+});
+
+/**
+ * We can _NOT_ generate these values by looping and simply incrementing a
+ * variable by 0.01 and stringifying it, since we'll end up with strings like
+ * "0.060000000000000005" due to the inability of binary floating point numbers
+ * to accurately represent decimal values.
+ */
+var stepVals = [
+ "0", "0.01", "0.02", "0.03", "0.04", "0.05", "0.06", "0.07", "0.08", "0.09",
+ "0.1", "0.11", "0.12", "0.13", "0.14", "0.15", "0.16", "0.17", "0.18", "0.19",
+ "0.2", "0.21", "0.22", "0.23", "0.24", "0.25", "0.26", "0.27", "0.28", "0.29",
+ "0.3", "0.31", "0.32", "0.33", "0.34", "0.35", "0.36", "0.37", "0.38", "0.39",
+ "0.4", "0.41", "0.42", "0.43", "0.44", "0.45", "0.46", "0.47", "0.48", "0.49",
+ "0.5", "0.51", "0.52", "0.53", "0.54", "0.55", "0.56", "0.57", "0.58", "0.59",
+ "0.6", "0.61", "0.62", "0.63", "0.64", "0.65", "0.66", "0.67", "0.68", "0.69",
+ "0.7", "0.71", "0.72", "0.73", "0.74", "0.75", "0.76", "0.77", "0.78", "0.79",
+ "0.8", "0.81", "0.82", "0.83", "0.84", "0.85", "0.86", "0.87", "0.88", "0.89",
+ "0.9", "0.91", "0.92", "0.93", "0.94", "0.95", "0.96", "0.97", "0.98", "0.99",
+ "1"
+];
+
+var pgUpDnVals = [
+ "0", "0.1", "0.2", "0.3", "0.4", "0.5", "0.6", "0.7", "0.8", "0.9", "1"
+];
+
+function test() {
+ var elem = document.getElementById("range");
+
+ elem.focus();
+
+ for (var i = 1; i < pgUpDnVals.length; ++i) {
+ synthesizeKey("VK_PAGE_UP", {});
+ is(elem.value, pgUpDnVals[i], "Test VK_PAGE_UP");
+ is(elem.validity.valid, true, "Check element is valid for value " + pgUpDnVals[i]);
+ }
+
+ for (var i = pgUpDnVals.length - 2; i >= 0; --i) {
+ synthesizeKey("VK_PAGE_DOWN", {});
+ is(elem.value, pgUpDnVals[i], "Test VK_PAGE_DOWN");
+ is(elem.validity.valid, true, "Check element is valid for value " + pgUpDnVals[i]);
+ }
+
+ for (var i = 1; i < stepVals.length; ++i) {
+ synthesizeKey("VK_UP", {});
+ is(elem.value, stepVals[i], "Test VK_UP");
+ is(elem.validity.valid, true, "Check element is valid for value " + stepVals[i]);
+ }
+
+ for (var i = stepVals.length - 2; i >= 0; --i) {
+ synthesizeKey("VK_DOWN", {});
+ is(elem.value, stepVals[i], "Test VK_DOWN");
+ is(elem.validity.valid, true, "Check element is valid for value " + stepVals[i]);
+ }
+
+ for (var i = 1; i < stepVals.length; ++i) {
+ elem.stepUp();
+ is(elem.value, stepVals[i], "Test stepUp()");
+ is(elem.validity.valid, true, "Check element is valid for value " + stepVals[i]);
+ }
+
+ for (var i = stepVals.length - 2; i >= 0; --i) {
+ elem.stepDown();
+ is(elem.value, stepVals[i], "Test stepDown()");
+ is(elem.validity.valid, true, "Check element is valid for value " + stepVals[i]);
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_sanitization.html b/dom/html/test/forms/test_input_sanitization.html
new file mode 100644
index 000000000..cd9877fdf
--- /dev/null
+++ b/dom/html/test/forms/test_input_sanitization.html
@@ -0,0 +1,565 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=549475
+-->
+<head>
+ <title>Test for Bug 549475</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=549475">Mozilla Bug 549475</a>
+<p id="display"></p>
+<pre id="test">
+<div id='content'>
+ <form>
+ </form>
+</div>
+<script type="application/javascript">
+
+SimpleTest.requestLongerTimeout(2);
+
+/**
+ * This files tests the 'value sanitization algorithm' for the various input
+ * types. Note that an input's value is affected by more than just its type's
+ * value sanitization algorithm; e.g. some type=range has actions that the user
+ * agent must perform to change the element's value to avoid underflow/overflow
+ * and step mismatch (when possible). We specifically avoid triggering these
+ * other actions here so that this test only tests the value sanitization
+ * algorithm for the various input types.
+ *
+ * XXXjwatt splitting out testing of the value sanitization algorithm and
+ * "other things" that affect .value makes it harder to know what we're testing
+ * and what we've missed, because what's included in the value sanitization
+ * algorithm and what's not is different from input type to input type. It
+ * seems to me it would be better to have a test (maybe one per type) focused
+ * on testing .value for permutations of all other inputs that can affect it.
+ * The value sanitization algorithm is just an internal spec concept after all.
+ */
+
+// We buffer up the results of sets of sub-tests, and avoid outputting log
+// entries for them all if they all pass. Otherwise, we have an enormous amount
+// of test output.
+
+var delayedTests = [];
+var anyFailedDelayedTests = false;
+
+function delayed_is(actual, expected, description)
+{
+ var result = actual == expected;
+ delayedTests.push({ actual: actual, expected: expected, description: description });
+ if (!result) {
+ anyFailedDelayedTests = true;
+ }
+}
+
+function flushDelayedTests(description)
+{
+ if (anyFailedDelayedTests) {
+ info("Outputting individual results for \"" + description + "\" due to failures in subtests");
+ for (var test of delayedTests) {
+ is(test.actual, test.expected, test.description);
+ }
+ } else {
+ ok(true, description + " (" + delayedTests.length + " subtests)");
+ }
+ delayedTests = [];
+ anyFailedDelayedTests = false;
+}
+
+// We are excluding "file" because it's too different from the other types.
+// And it has no sanitizing algorithm.
+var inputTypes =
+[
+ "text", "password", "search", "tel", "hidden", "checkbox", "radio",
+ "submit", "image", "reset", "button", "email", "url", "number", "date",
+ "time", "range", "color", "month", "week", "datetime-local"
+];
+
+var valueModeValue =
+[
+ "text", "search", "url", "tel", "email", "password", "date", "datetime",
+ "month", "week", "time", "datetime-local", "number", "range", "color",
+];
+
+function sanitizeDate(aValue)
+{
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-date-string
+ function getNumbersOfDaysInMonth(aMonth, aYear) {
+ if (aMonth === 2) {
+ return (aYear % 400 === 0 || (aYear % 100 != 0 && aYear % 4 === 0)) ? 29 : 28;
+ }
+ return (aMonth === 1 || aMonth === 3 || aMonth === 5 || aMonth === 7 ||
+ aMonth === 8 || aMonth === 10 || aMonth === 12) ? 31 : 30;
+ }
+
+ var match = /^([0-9]{4,})-([0-9]{2})-([0-9]{2})$/.exec(aValue);
+ if (!match) {
+ return "";
+ }
+ var year = Number(match[1]);
+ if (year === 0) {
+ return "";
+ }
+ var month = Number(match[2]);
+ if (month > 12 || month < 1) {
+ return "";
+ }
+ var day = Number(match[3]);
+ return 1 <= day && day <= getNumbersOfDaysInMonth(month, year) ? aValue : "";
+}
+
+function sanitizeTime(aValue)
+{
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-time-string
+ var match = /^([0-9]{2}):([0-9]{2})(.*)$/.exec(aValue);
+ if (!match) {
+ return "";
+ }
+ var hours = match[1];
+ if (hours < 0 || hours > 23) {
+ return "";
+ }
+ var minutes = match[2];
+ if (minutes < 0 || minutes > 59) {
+ return "";
+ }
+ var other = match[3];
+ if (other == "") {
+ return aValue;
+ }
+ match = /^:([0-9]{2})(.*)$/.exec(other);
+ if (!match) {
+ return "";
+ }
+ var seconds = match[1];
+ if (seconds < 0 || seconds > 59) {
+ return "";
+ }
+ var other = match[2];
+ if (other == "") {
+ return aValue;
+ }
+ match = /^.([0-9]{1,3})$/.exec(other);
+ if (!match) {
+ return "";
+ }
+ return aValue;
+}
+
+function sanitizeDateTimeLocal(aValue)
+{
+ // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-local-date-and-time-string
+ if (aValue.length < 16) {
+ return "";
+ }
+
+ var separator = aValue[10];
+ if (separator != "T" && separator != " ") {
+ return "";
+ }
+
+ var [date, time] = aValue.split(separator);
+ if (!sanitizeDate(date)) {
+ return "";
+ }
+
+ if (!sanitizeTime(time)) {
+ return "";
+ }
+
+ // Normalize datetime-local string.
+ // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-normalised-local-date-and-time-string
+ if (separator == " ") {
+ aValue = date + "T" + time;
+ }
+
+ if (aValue.length == 16) {
+ return aValue;
+ }
+
+ if (aValue.length > 19) {
+ var milliseconds = aValue.substring(20);
+ if (Number(milliseconds) != 0) {
+ return aValue;
+ }
+ aValue = aValue.slice(0, 19);
+ }
+
+ var seconds = aValue.substring(17);
+ if (Number(seconds) != 0) {
+ return aValue;
+ }
+ aValue = aValue.slice(0, 16);
+
+ return aValue;
+}
+
+function sanitizeValue(aType, aValue)
+{
+ // http://www.whatwg.org/html/#value-sanitization-algorithm
+ switch (aType) {
+ case "text":
+ case "password":
+ case "search":
+ case "tel":
+ return aValue.replace(/[\n\r]/g, "");
+ case "url":
+ case "email":
+ return aValue.replace(/[\n\r]/g, "").replace(/^[\u0020\u0009\t\u000a\u000c\u000d]+|[\u0020\u0009\t\u000a\u000c\u000d]+$/g, "");
+ case "number":
+ return isNaN(Number(aValue)) ? "" : aValue;
+ case "range":
+ var defaultMinimum = 0;
+ var defaultMaximum = 100;
+ var value = Number(aValue);
+ if (isNaN(value)) {
+ return ((defaultMaximum - defaultMinimum)/2).toString(); // "50"
+ }
+ if (value < defaultMinimum) {
+ return defaultMinimum.toString();
+ }
+ if (value > defaultMaximum) {
+ return defaultMaximum.toString();
+ }
+ return aValue;
+ case "date":
+ return sanitizeDate(aValue);
+ case "time":
+ return sanitizeTime(aValue);
+ case "month":
+ // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-month-string
+ var match = /^([0-9]{4,})-([0-9]{2})$/.exec(aValue);
+ if (!match) {
+ return "";
+ }
+ var year = Number(match[1]);
+ if (year === 0) {
+ return "";
+ }
+ var month = Number(match[2]);
+ if (month > 12 || month < 1) {
+ return "";
+ }
+ return aValue;
+ case "week":
+ // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-week-string
+ function isLeapYear(aYear) {
+ return ((aYear % 4 == 0) && (aYear % 100 != 0)) || (aYear % 400 == 0);
+ }
+ function getDayofWeek(aYear, aMonth, aDay) { /* 0 = Sunday */
+ // Tomohiko Sakamoto algorithm.
+ var monthTable = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4];
+ aYear -= Number(aMonth < 3);
+
+ return (aYear + parseInt(aYear / 4) - parseInt(aYear / 100) +
+ parseInt(aYear / 400) + monthTable[aMonth - 1] + aDay) % 7;
+ }
+ function getMaximumWeekInYear(aYear) {
+ var day = getDayofWeek(aYear, 1, 1);
+ return day == 4 || (day == 3 && isLeapYear(aYear)) ? 53 : 52;
+ }
+
+ var match = /^([0-9]{4,})-W([0-9]{2})$/.exec(aValue);
+ if (!match) {
+ return "";
+ }
+ var year = Number(match[1]);
+ if (year === 0) {
+ return "";
+ }
+ var week = Number(match[2]);
+ if (week > 53 || month < 1) {
+ return "";
+ }
+ return 1 <= week && week <= getMaximumWeekInYear(year) ? aValue : "";
+ case "datetime-local":
+ return sanitizeDateTimeLocal(aValue);
+ case "color":
+ return /^#[0-9A-Fa-f]{6}$/.exec(aValue) ? aValue.toLowerCase() : "#000000";
+ default:
+ return aValue;
+ }
+}
+
+function checkSanitizing(element, inputTypeDescription)
+{
+ var testData =
+ [
+ // For text, password, search, tel, email:
+ "\n\rfoo\n\r",
+ "foo\n\rbar",
+ " foo ",
+ " foo\n\r bar ",
+ // For url:
+ "\r\n foobar \n\r",
+ "\u000B foo \u000B",
+ "\u000A foo \u000A",
+ "\u000C foo \u000C",
+ "\u000d foo \u000d",
+ "\u0020 foo \u0020",
+ " \u0009 foo \u0009 ",
+ // For number and range:
+ "42",
+ "13.37",
+ "1.234567898765432",
+ "12foo",
+ "1e2",
+ "3E42",
+ // For date:
+ "1970-01-01",
+ "1234-12-12",
+ "1234567890-01-02",
+ "2012-12-31",
+ "2012-02-29",
+ "2000-02-29",
+ "1234",
+ "1234-",
+ "12345",
+ "1234-01",
+ "1234-012",
+ "1234-01-",
+ "12-12",
+ "999-01-01",
+ "1234-56-78-91",
+ "1234-567-78",
+ "1234--7-78",
+ "abcd-12-12",
+ "thisinotadate",
+ "2012-13-01",
+ "1234-12-42",
+ " 2012-13-01",
+ " 123-01-01",
+ "2012- 3-01",
+ "12- 10- 01",
+ " 12-0-1",
+ "2012-3-001",
+ "2012-12-00",
+ "2012-12-1r",
+ "2012-11-31",
+ "2011-02-29",
+ "2100-02-29",
+ "a2000-01-01",
+ "2000a-01-0'",
+ "20aa00-01-01",
+ "2000a2000-01-01",
+ "2000-1-1",
+ "2000-1-01",
+ "2000-01-1",
+ "2000-01-01 ",
+ "2000- 01-01",
+ "-1970-01-01",
+ "0000-00-00",
+ "0001-00-00",
+ "0000-01-01",
+ "1234-12 12",
+ "1234 12-12",
+ "1234 12 12",
+ // For time:
+ "1",
+ "10",
+ "10:",
+ "10:1",
+ "21:21",
+ ":21:21",
+ "-21:21",
+ " 21:21",
+ "21-21",
+ "21:21:",
+ "21:211",
+ "121:211",
+ "21:21 ",
+ "00:00",
+ "-1:00",
+ "24:00",
+ "00:60",
+ "01:01",
+ "23:59",
+ "99:99",
+ "8:30",
+ "19:2",
+ "19:a2",
+ "4c:19",
+ "10:.1",
+ "1.:10",
+ "13:37:42",
+ "13:37.42",
+ "13:37:42 ",
+ "13:37:42.",
+ "13:37:61.",
+ "13:37:00",
+ "13:37:99",
+ "13:37:b5",
+ "13:37:-1",
+ "13:37:.1",
+ "13:37:1.",
+ "13:37:42.001",
+ "13:37:42.001",
+ "13:37:42.abc",
+ "13:37:42.00c",
+ "13:37:42.a23",
+ "13:37:42.12e",
+ "13:37:42.1e1",
+ "13:37:42.e11",
+ "13:37:42.1",
+ "13:37:42.99",
+ "13:37:42.0",
+ "13:37:42.00",
+ "13:37:42.000",
+ "13:37:42.-1",
+ "13:37:42.1.1",
+ "13:37:42.1,1",
+ "13:37:42.",
+ "foo12:12",
+ "13:37:42.100000000000",
+ // For color
+ "#00ff00",
+ "#000000",
+ "red",
+ "#0f0",
+ "#FFFFAA",
+ "FFAABB",
+ "fFAaBb",
+ "FFAAZZ",
+ "ABCDEF",
+ "#7654321",
+ // For month
+ "1970-01",
+ "1234-12",
+ "123456789-01",
+ "2013-13",
+ "0000-00",
+ "2015-00",
+ "0001-01",
+ "1-1",
+ "888-05",
+ "2013-3",
+ "2013-may",
+ "2000-1a",
+ "2013-03-13",
+ "december",
+ "abcdef",
+ "12",
+ " 2013-03",
+ "2013 - 03",
+ "2013 03",
+ "2013/03",
+ // For week
+ "1970-W01",
+ "1970-W53",
+ "1964-W53",
+ "1900-W10",
+ "2004-W53",
+ "2065-W53",
+ "2099-W53",
+ "2010-W53",
+ "2016-W30",
+ "1900-W3",
+ "2016-w30",
+ "2016-30",
+ "16-W30",
+ "2016-Week30",
+ "2000-100",
+ "0000-W01",
+ "00-W01",
+ "123456-W05",
+ "1985-W100",
+ "week",
+ // For datetime-local
+ "1970-01-01T00:00",
+ "1970-01-01Z12:00",
+ "1970-01-01 00:00:00",
+ "1970-01-01T00:00:00.0",
+ "1970-01-01T00:00:00.00",
+ "1970-01-01T00:00:00.000",
+ "1970-01-01 00:00:00.20",
+ "1234567-01-01T12:00",
+ "2016-13-01T12:00",
+ "2016-12-32T12:00",
+ "2016-11-08 15:40:30.0",
+ "2016-11-08T15:40:30.00",
+ "2016-11-07T17:30:10",
+ "2016-12-1T12:45",
+ "2016-12-01T12:45:30.123456",
+ "2016-12-01T24:00",
+ "2016-12-01T12:88:30",
+ "2016-12-01T12:30:99",
+ "2016-12-01T12:30:100",
+ "2016-12-01",
+ "2016-12-01T",
+ "2016-Dec-01T00:00",
+ "12-05-2016T00:00",
+ "datetime-local"
+ ];
+
+ for (value of testData) {
+ element.setAttribute('value', value);
+ delayed_is(element.value, sanitizeValue(type, value),
+ "The value has not been correctly sanitized for type=" + type);
+ delayed_is(element.getAttribute('value'), value,
+ "The content value should not have been sanitized");
+
+ if (type in valueModeValue) {
+ element.setAttribute('value', 'tulip');
+ element.value = value;
+ delayed_is(element.value, sanitizeValue(type, value),
+ "The value has not been correctly sanitized for type=" + type);
+ delayed_is(element.getAttribute('value'), 'tulip',
+ "The content value should not have been sanitized");
+ }
+
+ element.setAttribute('value', '');
+ form.reset();
+ element.type = 'checkbox'; // We know this type has no sanitizing algorithm.
+ element.setAttribute('value', value);
+ delayed_is(element.value, value, "The value should not have been sanitized");
+ element.type = type;
+ delayed_is(element.value, sanitizeValue(type, value),
+ "The value has not been correctly sanitized for type=" + type);
+ delayed_is(element.getAttribute('value'), value,
+ "The content value should not have been sanitized");
+
+ element.setAttribute('value', '');
+ form.reset();
+ element.setAttribute('value', value);
+ form.reset();
+ delayed_is(element.value, sanitizeValue(type, value),
+ "The value has not been correctly sanitized for type=" + type);
+ delayed_is(element.getAttribute('value'), value,
+ "The content value should not have been sanitized");
+
+ // Cleaning-up.
+ element.setAttribute('value', '');
+ form.reset();
+ }
+
+ flushDelayedTests(inputTypeDescription);
+}
+
+for (type of inputTypes) {
+ var form = document.forms[0];
+ var element = document.createElement("input");
+ element.style.display = "none";
+ element.type = type;
+ form.appendChild(element);
+
+ checkSanitizing(element, "type=" + type + ", no frame, no editor");
+
+ element.style.display = "";
+ checkSanitizing(element, "type=" + type + ", frame, no editor");
+
+ element.focus();
+ element.blur();
+ checkSanitizing(element, "type=" + type + ", frame, editor");
+
+ element.style.display = "none";
+ checkSanitizing(element, "type=" + type + ", no frame, editor");
+
+ form.removeChild(element);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_textarea_set_value_no_scroll.html b/dom/html/test/forms/test_input_textarea_set_value_no_scroll.html
new file mode 100644
index 000000000..829daa8c9
--- /dev/null
+++ b/dom/html/test/forms/test_input_textarea_set_value_no_scroll.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=829606
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 829606</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript;version=1.7">
+
+ /** Test for Bug 829606 **/
+ /*
+ * This test checks that setting .value on an text field (input or textarea)
+ * doesn't scroll the field to its beginning.
+ */
+
+ SimpleTest.waitForExplicitFinish();
+
+ var gTestRunner = null;
+
+ function test(aElementName)
+ {
+ var element = document.getElementsByTagName(aElementName)[0];
+ element.focus();
+
+ var baseSnapshot = snapshotWindow(window);
+
+ // This is a sanity check.
+ var s2 = snapshotWindow(window);
+ var results = compareSnapshots(baseSnapshot, snapshotWindow(window), true);
+ ok(results[0], "sanity check: screenshots should be the same");
+
+ element.selectionStart = element.selectionEnd = element.value.length;
+
+ setTimeout(function() {
+ synthesizeKey('f', {});
+
+ var selectionAtTheEndSnapshot = snapshotWindow(window);
+ results = compareSnapshots(baseSnapshot, selectionAtTheEndSnapshot, false);
+ ok(results[0], "after appending a character, string should have changed");
+
+ element.value = element.value;
+ var tmpSnapshot = snapshotWindow(window);
+
+ results = compareSnapshots(baseSnapshot, tmpSnapshot, false);
+ ok(results[0], "re-settig the value should change nothing");
+
+ results = compareSnapshots(selectionAtTheEndSnapshot, tmpSnapshot, true);
+ ok(results[0], "re-settig the value should change nothing");
+
+ element.selectionStart = element.selectionEnd = 0;
+ element.blur();
+
+ gTestRunner.next();
+ }, 0);
+ }
+
+ // This test checks that when a textarea has a long list of values and the
+ // textarea's value is then changed, the values are shown correctly.
+ function testCorrectUpdateOnScroll()
+ {
+ var textarea = document.createElement('textarea');
+ textarea.rows = 5;
+ textarea.cols = 10;
+ textarea.value = 'a\nb\nc\nd';
+ document.getElementById('content').appendChild(textarea);
+
+ var baseSnapshot = snapshotWindow(window);
+
+ textarea.value = '1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n';
+ textarea.selectionStart = textarea.selectionEnd = textarea.value.length;
+
+ var fullSnapshot = snapshotWindow(window);
+ var results = compareSnapshots(baseSnapshot, fullSnapshot, false);
+ ok(results[0], "sanity check: screenshots should not be the same");
+
+ textarea.value = 'a\nb\nc\nd';
+
+ var tmpSnapshot = snapshotWindow(window);
+ results = compareSnapshots(baseSnapshot, tmpSnapshot, true);
+ ok(results[0], "textarea view should look like the beginning");
+
+ setTimeout(function() {
+ gTestRunner.next();
+ }, 0);
+ }
+
+ function runTest()
+ {
+ test('input');
+ yield undefined;
+ test('textarea');
+ yield undefined;
+ testCorrectUpdateOnScroll();
+ yield undefined;
+ SimpleTest.finish();
+ yield undefined;
+ }
+
+ gTestRunner = runTest();
+
+ SimpleTest.waitForFocus(function() {
+ gTestRunner.next();
+ });;
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=829606">Mozilla Bug 829606</a>
+<p id="display"></p>
+<div id="content">
+ <textarea rows='1' cols='5' style='-moz-appearance:none;'>this is a \n long text</textarea>
+ <input size='5' value="this is a very long text" style='-moz-appearance:none;'>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_time_focus_blur_events.html b/dom/html/test/forms/test_input_time_focus_blur_events.html
new file mode 100644
index 000000000..483741477
--- /dev/null
+++ b/dom/html/test/forms/test_input_time_focus_blur_events.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1301306
+-->
+<head>
+<title>Test for Bug 1301306</title>
+<script type="text/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=1301306">Mozilla Bug 722599</a>
+<p id="display"></p>
+<div id="content">
+<input type="time" id="input_time" onfocus="++focusEvent" onblur="++blurEvent"
+ onfocusin="++focusInEvent" onfocusout="++focusOutEvent">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/**
+ * Test for Bug 1301306.
+ * This test checks that when moving inside the time input element, e.g. jumping
+ * through the inner text boxes, does not fire extra focus/blur events.
+ **/
+
+var focusEvent = 0;
+var focusInEvent = 0;
+var focusOutEvent = 0;
+var blurEvent = 0;
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ test();
+ SimpleTest.finish();
+});
+
+function test() {
+ var time = document.getElementById("input_time");
+ time.focus();
+ is(focusEvent, 1, "time input element should have dispatched focus event.");
+ is(focusInEvent, 1, "time input element should have dispatched focusin event.");
+ is(focusOutEvent, 0, "time input element should not have dispatched focusout event.");
+ is(blurEvent, 0, "time input element should not have dispatched blur event.");
+
+ // Move around inside the input element's input box.
+ synthesizeKey("VK_TAB", {});
+ is(focusEvent, 1, "time input element should not have dispatched focus event.");
+ is(focusInEvent, 1, "time input element should have dispatched focusin event.");
+ is(focusOutEvent, 0, "time input element should not have dispatched focusout event.");
+ is(blurEvent, 0, "time input element should not have dispatched blur event.");
+
+ synthesizeKey("VK_RIGHT", {});
+ is(focusEvent, 1, "time input element should not have dispatched focus event.");
+ is(focusInEvent, 1, "time input element should have dispatched focusin event.");
+ is(focusOutEvent, 0, "time input element should not have dispatched focusout event.");
+ is(blurEvent, 0, "time input element should not have dispatched blur event.");
+
+ synthesizeKey("VK_LEFT", {});
+ is(focusEvent, 1, "time input element should not have dispatched focus event.");
+ is(focusInEvent, 1, "time input element should have dispatched focusin event.");
+ is(focusOutEvent, 0, "time input element should not have dispatched focusout event.");
+ is(blurEvent, 0, "time input element should not have dispatched blur event.");
+
+ synthesizeKey("VK_RIGHT", {});
+ is(focusEvent, 1, "time input element should not have dispatched focus event.");
+ is(focusInEvent, 1, "time input element should have dispatched focusin event.");
+ is(focusOutEvent, 0, "time input element should not have dispatched focusout event.");
+ is(blurEvent, 0, "time input element should not have dispatched blur event.");
+
+ time.blur();
+ is(focusEvent, 1, "time input element should not have dispatched focus event.");
+ is(focusInEvent, 1, "time input element should have dispatched focusin event.");
+ is(focusOutEvent, 1, "time input element should not have dispatched focusout event.");
+ is(blurEvent, 1, "time input element should have dispatched blur event.");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_time_key_events.html b/dom/html/test/forms/test_input_time_key_events.html
new file mode 100644
index 000000000..755db38ff
--- /dev/null
+++ b/dom/html/test/forms/test_input_time_key_events.html
@@ -0,0 +1,197 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
+-->
+<head>
+ <title>Test key events for time control</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"/>
+ <meta charset="UTF-8">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1288591">Mozilla Bug 1288591</a>
+<p id="display"></p>
+<div id="content">
+ <input id="input" type="time">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+// Turn off Spatial Navigation because it hijacks arrow keydown events:
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[["snav.enabled", false]]}, function() {
+ test();
+ SimpleTest.finish();
+ });
+});
+
+var testData = [
+ /**
+ * keys: keys to send to the input element.
+ * initialVal: initial value set to the input element.
+ * expectedVal: expected value of the input element after sending the keys.
+ */
+ {
+ // Type 1030 and select AM.
+ keys: ["1030", "VK_DOWN"],
+ initialVal: "",
+ expectedVal: "10:30"
+ },
+ {
+ // Type 3 in the hour field will automatically advance to the minute field.
+ keys: ["330", "VK_DOWN"],
+ initialVal: "",
+ expectedVal: "03:30"
+ },
+ {
+ // Type 5 in the hour field will automatically advance to the minute field.
+ // Type 7 in the minute field will automatically advance to the AM/PM field.
+ keys: ["57", "VK_DOWN"],
+ initialVal: "",
+ expectedVal: "05:07"
+ },
+ {
+ // Advance to AM/PM field and change to PM.
+ keys: ["VK_TAB", "VK_TAB", "VK_DOWN"],
+ initialVal: "10:30",
+ expectedVal: "22:30"
+ },
+ {
+ // Right key should do the same thing as TAB key.
+ keys: ["VK_RIGHT", "VK_RIGHT", "VK_DOWN"],
+ initialVal: "10:30",
+ expectedVal: "22:30"
+ },
+ {
+ // Advance to minute field then back to hour field and decrement.
+ keys: ["VK_RIGHT", "VK_LEFT", "VK_DOWN"],
+ initialVal: "10:30",
+ expectedVal: "09:30"
+ },
+ {
+ // Focus starts on the first field, hour in this case, and increment.
+ keys: ["VK_UP"],
+ initialVal: "16:00",
+ expectedVal: "17:00"
+ },
+ {
+ // Advance to minute field and decrement.
+ keys: ["VK_TAB", "VK_DOWN"],
+ initialVal: "16:00",
+ expectedVal: "16:59"
+ },
+ {
+ // Advance to minute field and increment.
+ keys: ["VK_TAB", "VK_UP"],
+ initialVal: "16:59",
+ expectedVal: "16:00"
+ },
+ {
+ // PageUp on hour field increments hour by 3.
+ keys: ["VK_PAGE_UP"],
+ initialVal: "05:00",
+ expectedVal: "08:00"
+ },
+ {
+ // PageDown on hour field decrements hour by 3.
+ keys: ["VK_PAGE_DOWN"],
+ initialVal: "05:00",
+ expectedVal: "02:00"
+ },
+ {
+ // PageUp on minute field increments minute by 10.
+ keys: ["VK_TAB", "VK_PAGE_UP"],
+ initialVal: "14:00",
+ expectedVal: "14:10"
+ },
+ {
+ // PageDown on minute field decrements minute by 10.
+ keys: ["VK_TAB", "VK_PAGE_DOWN"],
+ initialVal: "14:00",
+ expectedVal: "14:50"
+ },
+ {
+ // Home key on hour field sets it to the minimum hour, which is 1 in 12-hour
+ // clock.
+ keys: ["VK_HOME"],
+ initialVal: "03:10",
+ expectedVal: "01:10"
+ },
+ {
+ // End key on hour field sets it to the maximum hour, which is 12 in 12-hour
+ // clock.
+ keys: ["VK_END"],
+ initialVal: "03:10",
+ expectedVal: "00:10"
+ },
+ {
+ // Home key on minute field sets it to the minimum minute, which is 0.
+ keys: ["VK_TAB", "VK_HOME"],
+ initialVal: "19:30",
+ expectedVal: "19:00"
+ },
+ {
+ // End key on minute field sets it to the minimum minute, which is 59.
+ keys: ["VK_TAB", "VK_END"],
+ initialVal: "19:30",
+ expectedVal: "19:59"
+ },
+ // Second field will show up when needed.
+ {
+ // PageUp on second field increments second by 10.
+ keys: ["VK_TAB", "VK_TAB", "VK_PAGE_UP"],
+ initialVal: "08:10:10",
+ expectedVal: "08:10:20"
+ },
+ {
+ // PageDown on second field increments second by 10.
+ keys: ["VK_TAB", "VK_TAB", "VK_PAGE_DOWN"],
+ initialVal: "08:10:10",
+ expectedVal: "08:10:00"
+ },
+ {
+ // Home key on second field sets it to the minimum second, which is 0.
+ keys: ["VK_TAB", "VK_TAB", "VK_HOME"],
+ initialVal: "16:00:30",
+ expectedVal: "16:00:00"
+ },
+ {
+ // End key on second field sets it to the minimum second, which is 59.
+ keys: ["VK_TAB", "VK_TAB", "VK_END"],
+ initialVal: "16:00:30",
+ expectedVal: "16:00:59"
+ },
+];
+
+function sendKeys(aKeys) {
+ for (let i = 0; i < aKeys.length; i++) {
+ let key = aKeys[i];
+ if (key.startsWith("VK")) {
+ synthesizeKey(key, {});
+ } else {
+ sendString(key);
+ }
+ }
+}
+
+function test() {
+ var elem = document.getElementById("input");
+
+ for (let { keys, initialVal, expectedVal } of testData) {
+ elem.focus();
+ elem.value = initialVal;
+ sendKeys(keys);
+ elem.blur();
+ is(elem.value, expectedVal,
+ "Test with " + keys + ", result should be " + expectedVal);
+ elem.value = "";
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_types_pref.html b/dom/html/test/forms/test_input_types_pref.html
new file mode 100644
index 000000000..243836f34
--- /dev/null
+++ b/dom/html/test/forms/test_input_types_pref.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=764481
+-->
+<head>
+ <title>Test for Bug 764481</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=764481">Mozilla Bug 764481</a>
+<p id="display"></p>
+<div id="content" style="display: none" >
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+ var input = document.createElement("input");
+
+ var testData = [
+ {
+ prefs: [["dom.forms.number", false]],
+ inputType: "number",
+ expectedType: "text"
+ }, {
+ prefs: [["dom.forms.number", true]],
+ inputType: "number",
+ expectedType: "number"
+ }, {
+ prefs: [["dom.forms.color", false]],
+ inputType: "color",
+ expectedType: "text"
+ }, {
+ prefs: [["dom.forms.color", true]],
+ inputType: "color",
+ expectedType: "color"
+ }, {
+ prefs: [["dom.experimental_forms", false], ["dom.forms.datepicker", false],
+ ["dom.forms.datetime", false]],
+ inputType: "date",
+ expectedType: "text"
+ }, {
+ prefs: [["dom.experimental_forms", true], ["dom.forms.datepicker", false],
+ ["dom.forms.datetime", false]],
+ inputType: "date",
+ expectedType: "date"
+ }, {
+ prefs: [["dom.experimental_forms", false], ["dom.forms.datepicker", true],
+ ["dom.forms.datetime", false]],
+ inputType: "date",
+ expectedType: "date"
+ }, {
+ prefs: [["dom.experimental_forms", false], ["dom.forms.datepicker", false],
+ ["dom.forms.datetime", true]],
+ inputType: "date",
+ expectedType: "date"
+ }, {
+ prefs: [["dom.forms.datetime", false]],
+ inputType: "month",
+ expectedType: "text"
+ }, {
+ prefs: [["dom.forms.datetime", true]],
+ inputType: "month",
+ expectedType: "month"
+ }, {
+ prefs: [["dom.forms.datetime", false]],
+ inputType: "week",
+ expectedType: "text"
+ }, {
+ prefs: [["dom.forms.datetime", true]],
+ inputType: "week",
+ expectedType: "week"
+ }, {
+ prefs: [["dom.forms.datetime", false]],
+ inputType: "datetime-local",
+ expectedType: "text"
+ }, {
+ prefs: [["dom.forms.datetime", true]],
+ inputType: "datetime-local",
+ expectedType: "datetime-local"
+ }
+ ];
+
+ function testInputTypePreference(aData) {
+ return SpecialPowers.pushPrefEnv({'set': aData.prefs})
+ .then(() => {
+ // Change the type of input to text and then back to the tested input type,
+ // so that HTMLInputElement::ParseAttribute gets called with the pref enabled.
+ input.type = "text";
+ input.type = aData.inputType;
+ is(input.type, aData.expectedType, "input type should be '" +
+ aData.expectedType + "'' when pref " + aData.prefs + " is set");
+ is(input.getAttribute('type'), aData.inputType,
+ "input 'type' attribute should not change");
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ let promise = Promise.resolve();
+ for (let i = 0; i < testData.length; i++) {
+ let data = testData[i];
+ promise = promise.then(() => testInputTypePreference(data));
+ }
+
+ promise.catch(error => ok(false, "Promise reject: " + error))
+ .then(() => SimpleTest.finish());
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_typing_sanitization.html b/dom/html/test/forms/test_input_typing_sanitization.html
new file mode 100644
index 000000000..0896f19df
--- /dev/null
+++ b/dom/html/test/forms/test_input_typing_sanitization.html
@@ -0,0 +1,260 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=765772
+-->
+<head>
+ <title>Test for Bug 765772</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <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=">Mozilla Bug 765772</a>
+<p id="display"></p>
+<iframe name="submit_frame" style="visibility: hidden;"></iframe>
+<div id="content">
+ <form id='f' target="submit_frame" action="foo">
+ <input name=i id="i" step='any' >
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+/*
+ * This test checks that when a user types in some input types, it will not be
+ * in a state where the value will be un-sanitized and usable (by a script).
+ */
+
+var input = document.getElementById('i');
+var form = document.getElementById('f');
+var submitFrame = document.getElementsByTagName('iframe')[0];
+var testData = [];
+var gCurrentTest = null;
+var gValidData = [];
+var gInvalidData = [];
+
+function submitForm() {
+ form.submit();
+}
+
+function sendKeyEventToSubmitForm() {
+ sendKey("return");
+}
+
+function urlify(aStr) {
+ return aStr.replace(/:/g, '%3A');
+}
+
+function runTestsForNextInputType()
+{
+ try {
+ testRunner.next();
+ } catch (e) {
+ if (e.toString() == '[object StopIteration]') {
+ SimpleTest.finish();
+ } else {
+ throw StopIteration;
+ }
+ }
+}
+
+function checkValueSubmittedIsValid()
+{
+ is(frames['submit_frame'].location.href,
+ 'http://mochi.test:8888/tests/dom/html/test/forms/foo?i='
+ + urlify(gValidData[valueIndex++]),
+ "The submitted value should not have been sanitized");
+
+ input.value = "";
+
+ if (valueIndex >= gValidData.length) {
+ if (gCurrentTest.canHaveBadInputValidityState) {
+ // Don't run the submission tests on the invalid input if submission
+ // will be blocked by invalid input.
+ runTestsForNextInputType();
+ return;
+ }
+ valueIndex = 0;
+ submitFrame.onload = checkValueSubmittedIsInvalid;
+ testData = gInvalidData;
+ }
+ testSubmissions();
+}
+
+function checkValueSubmittedIsInvalid()
+{
+ is(frames['submit_frame'].location.href,
+ 'http://mochi.test:8888/tests/dom/html/test/forms/foo?i=',
+ "The submitted value should have been sanitized");
+
+ valueIndex++;
+ input.value = "";
+
+ if (valueIndex >= gInvalidData.length) {
+ if (submitMethod == sendKeyEventToSubmitForm) {
+ runTestsForNextInputType();
+ return;
+ }
+ valueIndex = 0;
+ submitMethod = sendKeyEventToSubmitForm;
+ submitFrame.onload = checkValueSubmittedIsValid;
+ testData = gValidData;
+ }
+ testSubmissions();
+}
+
+function testSubmissions() {
+ input.focus();
+ sendString(testData[valueIndex]);
+ submitMethod();
+}
+
+var valueIndex = 0;
+var submitMethod = submitForm;
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest()
+{
+ SimpleTest.requestLongerTimeout(4);
+
+ var data = [
+ {
+ type: 'number',
+ canHaveBadInputValidityState: true,
+ validData: [
+ "42",
+ "-42", // should work for negative values
+ "42.1234",
+ "123.123456789123", // double precision
+ "1e2", // e should be usable
+ "2e1",
+ "1e-1", // value after e can be negative
+ "1E2", // E can be used instead of e
+ ],
+ invalidData: [
+ "e",
+ "e2",
+ "1e0.1",
+ "foo",
+ "42,13", // comma can't be used as a decimal separator
+ ]
+ },
+ {
+ type: 'date',
+ validData: [
+ '0001-01-01',
+ '2012-12-21',
+ '2013-01-28',
+ '100000-01-01',
+ ],
+ invalidData: [
+ '1-01-01',
+ 'a',
+ '-',
+ '2012-01',
+ '2013-01-1',
+ '1011-23-21',
+ '1000-12-99',
+ ]
+ },
+ {
+ type: 'month',
+ validData: [
+ '0001-01',
+ '2012-12',
+ '100000-01',
+ ],
+ invalidData: [
+ '1-01',
+ '-',
+ 'december',
+ '2012-dec',
+ '2012/12',
+ '2012-99',
+ '2012-1',
+ ]
+ },
+ {
+ type: 'week',
+ validData: [
+ '0001-W01',
+ '1970-W53',
+ '100000-W52',
+ '2016-W30',
+ ],
+ invalidData: [
+ '1-W01',
+ 'week',
+ '2016-30',
+ '2010-W80',
+ '2000/W30',
+ '1985-W00',
+ '1000-W'
+ ]
+ },
+ {
+ type: 'datetime-local',
+ validData: [
+ '0001-01-01T00:00',
+ '2016-11-07T16:45',
+ '2016-11-07T16:45:30',
+ '2016-11-07T16:45:30.10',
+ '2016-11-07T16:45:00.111',
+ ],
+ invalidData: [
+ '1-01-01T00:00',
+ '1970-01-01T9:30',
+ '2016/11/07T16:45',
+ '2016-11-07T16.45',
+ 'T',
+ 'datetime-local'
+ ]
+ },
+ ];
+
+ for (test of data) {
+ gCurrentTest = test;
+
+ input.type = test.type;
+ gValidData = test.validData;
+ gInvalidData = test.invalidData;
+
+ for (data of gValidData) {
+ input.value = "";
+ input.focus();
+ sendString(data);
+ input.blur();
+ is(input.value, data, "valid user input should not be sanitized");
+ }
+
+ for (data of gInvalidData) {
+ input.value = "";
+ input.focus();
+ sendString(data);
+ input.blur();
+ is(input.value, "", "invalid user input should be sanitized");
+ }
+
+ input.value = '';
+
+ testData = gValidData;
+ valueIndex = 0;
+ submitFrame.onload = checkValueSubmittedIsValid;
+ testSubmissions();
+ yield undefined;
+ }
+}
+
+var testRunner = runTest();
+
+addLoadEvent(function () {
+ testRunner.next();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_untrusted_key_events.html b/dom/html/test/forms/test_input_untrusted_key_events.html
new file mode 100644
index 000000000..b356316ce
--- /dev/null
+++ b/dom/html/test/forms/test_input_untrusted_key_events.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for untrusted DOM KeyboardEvent on input element</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>
+<p id="display"></p>
+<div id="content">
+ <input id="input">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runNextTest, window);
+
+const kTests = [
+ { type: "text", value: "foo", key: "b", expectedNewValue: "foo" },
+ { type: "number", value: "123", key: "4", expectedNewValue: "123" },
+ { type: "number", value: "123", key: KeyEvent.DOM_VK_UP, expectedNewValue: "123" },
+ { type: "number", value: "123", key: KeyEvent.DOM_VK_DOWN, expectedNewValue: "123" },
+];
+
+function sendUntrustedKeyEvent(eventType, keyCode, target) {
+ var evt = document.createEvent("KeyboardEvent");
+ var canBubbleArg = true;
+ var cancelableArg = true;
+ var viewArg = document.defaultView;
+ var ctrlKeyArg = false;
+ var altKeyArg = false;
+ var shiftKeyArg = false;
+ var metaKeyArg = false;
+ var keyCodeArg = keyCode;
+ var charCodeArg = 0;
+ evt.initKeyEvent(eventType, canBubbleArg, cancelableArg, viewArg,
+ ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
+ keyCodeArg, charCodeArg);
+ target.dispatchEvent(evt);
+}
+
+var input = document.getElementById("input");
+
+var gotEvents = {};
+
+function handleEvent(event) {
+ gotEvents[event.type] = true;
+}
+
+input.addEventListener("keydown", handleEvent, false);
+input.addEventListener("keyup", handleEvent, false);
+input.addEventListener("keypress", handleEvent, false);
+
+var previousTest = null;
+
+function runNextTest() {
+ if (previousTest) {
+ var msg = "For <input " + "type=" + previousTest.type + ">, ";
+ is(gotEvents.keydown, true, msg + "checking got keydown");
+ is(gotEvents.keyup, true, msg + "checking got keyup");
+ is(gotEvents.keypress, true, msg + "checking got keypress");
+ is(input.value, previousTest.expectedNewValue, msg + "checking element " +
+ " after being sent '" + previousTest.key + "' key events");
+ }
+
+ // reset flags
+ gotEvents.keydown = false;
+ gotEvents.keyup = false;
+ gotEvents.keypress = false;
+
+
+ var test = kTests.shift();
+ if (!test) {
+ SimpleTest.finish();
+ return; // We're all done
+ }
+
+ input.type = test.type;
+ input.focus(); // make sure we still have focus after type change
+ input.value = test.value;
+
+ sendUntrustedKeyEvent("keydown", test.key, input);
+ sendUntrustedKeyEvent("keyup", test.key, input);
+ sendUntrustedKeyEvent("keypress", test.key, input);
+
+ previousTest = test;
+
+ SimpleTest.executeSoon(runNextTest);
+};
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_input_url.html b/dom/html/test/forms/test_input_url.html
new file mode 100644
index 000000000..b335f5983
--- /dev/null
+++ b/dom/html/test/forms/test_input_url.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Tests for &lt;input type='url'&gt; validity</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">
+ <input type='url' id='i' oninvalid='invalidEventHandler(event);'>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Tests for <input type='url'> validity **/
+
+// More checks are done in test_bug551670.html.
+
+var gInvalid = false;
+
+function invalidEventHandler(e)
+{
+ is(e.type, "invalid", "Invalid event type should be invalid");
+ gInvalid = true;
+}
+
+function checkValidURL(element)
+{
+ gInvalid = false;
+ ok(!element.validity.typeMismatch,
+ "Element should not suffer from type mismatch");
+ ok(element.validity.valid, "Element should be valid");
+ ok(element.checkValidity(), "Element should be valid");
+ ok(!gInvalid, "The invalid event should not have been thrown");
+ is(element.validationMessage, '',
+ "Validation message should be the empty string");
+ ok(element.matches(":valid"), ":valid pseudo-class should apply");
+}
+
+function checkInvalidURL(element)
+{
+ gInvalid = false;
+ ok(element.validity.typeMismatch,
+ "Element should suffer from type mismatch");
+ ok(!element.validity.valid, "Element should not be valid");
+ ok(!element.checkValidity(), "Element should not be valid");
+ ok(gInvalid, "The invalid event should have been thrown");
+ is(element.validationMessage, "Please enter a URL.",
+ "Validation message should be related to invalid URL");
+ ok(element.matches(":invalid"),
+ ":invalid pseudo-class should apply");
+}
+
+var url = document.getElementById('i');
+
+var values = [
+ // [ value, validity ]
+ // The empty string should be considered as valid.
+ [ "", true ],
+ [ "foo", false ],
+ [ "http://mozilla.com/", true ],
+ [ "http://mozilla.com", true ],
+ [ "http://mozil\nla\r.com/", true ],
+ [ " http://mozilla.com/ ", true ],
+ [ "\r http://mozilla.com/ \n", true ],
+ [ "file:///usr/bin/tulip", true ],
+ [ "../../bar.html", false ],
+ [ "http://mozillá.org", true ],
+ [ "https://mózillä.org", true ],
+ [ "http://mózillä.órg", true ],
+ [ "ht://mózillä.órg", true ],
+ [ "httŭ://mózillä.órg", false ],
+];
+
+values.forEach(function([value, valid]) {
+ url.value = value;
+
+ if (valid) {
+ checkValidURL(url);
+ } else {
+ checkInvalidURL(url);
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_interactive_content_in_label.html b/dom/html/test/forms/test_interactive_content_in_label.html
new file mode 100644
index 000000000..3df64eae2
--- /dev/null
+++ b/dom/html/test/forms/test_interactive_content_in_label.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=229925
+-->
+<head>
+ <title>Test for Bug 229925</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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=229925">Mozilla Bug 229925</a>
+<p id="display"></p>
+<form action="#">
+ <label>
+ <span id="text">label</span>
+ <input type="button" id="target" value="target">
+
+ <a class="yes" href="#">a</a>
+ <audio class="yes" controls></audio>
+ <button class="yes">button</button>
+ <details class="yes">details</details>
+ <embed class="yes">embed</embed>
+ <iframe class="yes" src="data:text/plain," style="width: 16px; height: 16px;"></iframe>
+ <img class="yes" src="data:image/png," usemap="#map">
+ <input class="yes" type="text" size="4">
+ <keygen class="yes">
+ <label class="yes">label</label>
+ <object class="yes" usemap="#map">object</object>
+ <select class="yes"><option>select</option></select>
+ <textarea class="yes" cols="1" rows="1"></textarea>
+ <video class="yes" controls></video>
+
+ <a class="no">a</a>
+ <audio class="no"></audio>
+ <img class="no" src="data:image/png,">
+ <input class="no" type="hidden">
+ <object class="no">object</object>
+ <video class="no"></video>
+
+ <span class="no" tabindex="1">tabindex</span>
+ <audio class="no" tabindex="1"></audio>
+ <img class="no" src="data:image/png," tabindex="1">
+ <input class="no" type="hidden" tabindex="1">
+ <object class="no" tabindex="1">object</object>
+ <video class="no" tabindex="1"></video>
+ </label>
+</form>
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 229925 **/
+
+var target = document.getElementById("target");
+
+var yes_nodes = Array.from(document.getElementsByClassName("yes"));
+
+var no_nodes = Array.from(document.getElementsByClassName("no"));
+
+var target_clicked = false;
+target.addEventListener("click", function() {
+ target_clicked = true;
+});
+
+var node;
+for (node of yes_nodes) {
+ target_clicked = false;
+ node.click();
+ is(target_clicked, false, "mouse click on interactive content " + node.nodeName + " shouldn't dispatch event to label target");
+}
+
+for (node of no_nodes) {
+ target_clicked = false;
+ node.click();
+ is(target_clicked, true, "mouse click on non interactive content " + node.nodeName + " should dispatch event to label target");
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/forms/test_label_control_attribute.html b/dom/html/test/forms/test_label_control_attribute.html
new file mode 100644
index 000000000..66b761c93
--- /dev/null
+++ b/dom/html/test/forms/test_label_control_attribute.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=562932
+-->
+<head>
+ <title>Test for Bug 562932</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=562932">Mozilla Bug 562932</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <!-- No @for, we have to check the content -->
+ <label id='l1'><input id='i1'></label>
+ <label id='l2'><input id='i2'><input></label>
+ <label id='l3'></label>
+ <label id='l4a'><fieldset id='f'>foo</fieldset></label>
+ <label id='l4b'><label id='l4c'><input id='i3'></label></label>
+ <label id='l4d'><label id='l4e'><input id='i3b'></label><input></label>
+
+ <!-- With @for, we do no check the content -->
+ <label id='l5' for='i1'></label>
+ <label id='l6' for='i4'></label>
+ <label id='l7' for='i4'><input></label>
+ <label id='l8' for='i1 i2'></label>
+ <label id='l9' for='i1 i2'><input></label>
+ <label id='l10' for='f'></label>
+ <label id='l11' for='i4'></label>
+ <label id='l12' for='i5'></label>
+ <label id='l13' for=''><input></label>
+ <!-- <label id='l14'> is created in script -->
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 562932 **/
+
+function checkControl(aLabelId, aElementId, aMsg)
+{
+ var element = null;
+
+ if (aElementId != null) {
+ element = document.getElementById(aElementId);
+ }
+
+ is(document.getElementById(aLabelId).control, element, aMsg);
+}
+
+ok('control' in document.createElement('label'),
+ "label element should have a control IDL attribute");
+
+checkControl('l1', 'i1', "label control should be the first form element");
+checkControl('l2', 'i2', "label control should be the first form element");
+checkControl('l3', null, "label control should be null when there is no child");
+checkControl('l4a', null, "label control should be null when there is no \
+ labelable form element child");
+checkControl('l4b', 'i3', "label control should be the first labelable element \
+ in tree order");
+checkControl('l4c', 'i3', "label control should be the first labelable element \
+ in tree order");
+checkControl('l4d', 'i3b', "label control should be the first labelable element \
+ in tree order");
+checkControl('l4e', 'i3b', "label control should be the first labelable element \
+ in tree order");
+checkControl('l5', 'i1', "label control should be the id in @for");
+checkControl('l6', null,
+ "label control should be null if the id in @for is not valid");
+checkControl('l7', null,
+ "label control should be null if the id in @for is not valid");
+checkControl('l8', null,
+ "label control should be null if there are more than one id in @for");
+checkControl('l9', null,
+ "label control should be null if there are more than one id in @for");
+checkControl('l10', null, "label control should be null if the id in @for \
+ is not an id from a labelable form element");
+
+var inputOutOfDocument = document.createElement('input');
+inputOutOfDocument.id = 'i4';
+checkControl('l11', null, "label control should be null if the id in @for \
+ is not an id from an element in the document");
+
+var inputInDocument = document.createElement('input');
+inputInDocument.id = 'i5';
+document.getElementById('content').appendChild(inputInDocument);
+checkControl('l12', 'i5', "label control should be the id in @for");
+
+checkControl('l13', null, "label control should be null if the id in @for \
+ is empty");
+
+var labelOutOfDocument = document.createElement('label');
+labelOutOfDocument.htmlFor = 'i1';
+is(labelOutOfDocument.control, null, "out of document label shouldn't \
+ labelize a form control");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_label_input_controls.html b/dom/html/test/forms/test_label_input_controls.html
new file mode 100644
index 000000000..6dee2a5bb
--- /dev/null
+++ b/dom/html/test/forms/test_label_input_controls.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=597650
+-->
+<head>
+ <title>Test for Bug 597650</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=597650">Mozilla Bug 597650</a>
+ <p id="display"></p>
+ <div id="content">
+ <label id="l">
+ <input id="h"></input>
+ <input type="text" id="i"></input>
+ </label>
+ <label id="lh" for="h"></label>
+ </div>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ /** Test for Bug 597650 **/
+ label = document.getElementById("l");
+ labelForH = document.getElementById("lh");
+ inputI = document.getElementById("i");
+ inputH = document.getElementById("h");
+
+ var labelableTypes = ["text", "search", "tel", "url", "email", "password",
+ "datetime", "date", "month", "week", "time",
+ "number", "range", "color", "checkbox", "radio",
+ "file", "submit", "image", "reset", "button"];
+ var nonLabelableTypes = ["hidden"];
+
+ for (var i in labelableTypes) {
+ test(labelableTypes[i], true);
+ }
+
+ for (var i in nonLabelableTypes) {
+ test(nonLabelableTypes[i], false);
+ }
+
+ function test(type, isLabelable) {
+ inputH.type = type;
+ if (isLabelable) {
+ testControl(label, inputH, type, true);
+ testControl(labelForH, inputH, type, true);
+ } else {
+ testControl(label, inputI, type, false);
+ testControl(labelForH, null, type, false);
+
+ inputH.type = "text";
+ testControl(label, inputH, "text", true);
+ testControl(labelForH, inputH, "text", true);
+
+ inputH.type = type;
+ testControl(label, inputI, type, false);
+ testControl(labelForH, null, type, false);
+
+ label.removeChild(inputH);
+ testControl(label, inputI, "text", true);
+
+ var element = document.createElement('input');
+ element.type = type;
+ label.insertBefore(element, inputI);
+ testControl(label, inputI, "text", true);
+ }
+ }
+
+ function testControl(label, control, type, labelable) {
+ if (labelable) {
+ is(label.control, control, "Input controls of type " + type
+ + " should be labeled");
+ } else {
+ is(label.control, control, "Input controls of type " + type
+ + " should be ignored by <label>");
+ }
+ }
+ </script>
+ </pre>
+ </body>
+</html>
+
diff --git a/dom/html/test/forms/test_max_attribute.html b/dom/html/test/forms/test_max_attribute.html
new file mode 100644
index 000000000..4007cfad6
--- /dev/null
+++ b/dom/html/test/forms/test_max_attribute.html
@@ -0,0 +1,437 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=635499
+-->
+<head>
+ <title>Test for Bug 635499</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=635499">Mozilla Bug 635499</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 635499 **/
+
+var data = [
+ { type: 'hidden', apply: false },
+ { type: 'text', apply: false },
+ { type: 'search', apply: false },
+ { type: 'tel', apply: false },
+ { type: 'url', apply: false },
+ { type: 'email', apply: false },
+ { type: 'password', apply: false },
+ { type: 'date', apply: true },
+ { type: 'month', apply: true },
+ { type: 'week', apply: true },
+ { type: 'time', apply: true },
+ // TODO: temporary set to false until bug 888331 is fixed.
+ { type: 'datetime-local', apply: false },
+ { type: 'number', apply: true },
+ { type: 'range', apply: true },
+ { type: 'color', apply: false },
+ { type: 'checkbox', apply: false },
+ { type: 'radio', apply: false },
+ { type: 'file', apply: false },
+ { type: 'submit', apply: false },
+ { type: 'image', apply: false },
+ { type: 'reset', apply: false },
+ { type: 'button', apply: false },
+];
+
+var input = document.createElement("input");
+document.getElementById('content').appendChild(input);
+
+/**
+ * @aValidity - boolean indicating whether the element is expected to be valid
+ * (aElement.validity.valid is true) or not. The value passed is ignored and
+ * overridden with true if aApply is false.
+ * @aApply - boolean indicating whether the min/max attributes apply to this
+ * element type.
+ * @aRangeApply - A boolean that's set to true if the current input type is a
+ * "[candidate] for constraint validation" and it "[has] range limitations"
+ * per http://www.whatwg.org/specs/web-apps/current-work/multipage/selectors.html#selector-in-range
+ * (in other words, one of the pseudo classes :in-range and :out-of-range
+ * should apply (which, depends on aValidity)).
+ * Else (neither :in-range or :out-of-range should match) set to false.
+ */
+function checkValidity(aElement, aValidity, aApply, aRangeApply)
+{
+ aValidity = aApply ? aValidity : true;
+
+ is(aElement.validity.valid, aValidity,
+ "element validity should be " + aValidity);
+ is(aElement.validity.rangeOverflow, !aValidity,
+ "element overflow status should be " + !aValidity);
+ var overflowMsg =
+ (aElement.type == "date" || aElement.type == "time" ||
+ aElement.type == "month" || aElement.type == "week") ?
+ ("Please select a value that is no later than " + aElement.max + ".") :
+ ("Please select a value that is no more than " + aElement.max + ".");
+ is(aElement.validationMessage,
+ aValidity ? "" : overflowMsg, "Checking range overflow validation message");
+
+ is(aElement.matches(":valid"), aElement.willValidate && aValidity,
+ (aElement.willValidate && aValidity) ? ":valid should apply" : "valid shouldn't apply");
+ is(aElement.matches(":invalid"), aElement.willValidate && !aValidity,
+ (aElement.wil && aValidity) ? ":invalid shouldn't apply" : "valid should apply");
+
+ if (!aRangeApply) {
+ ok(!aElement.matches(":in-range"), ":in-range should not match");
+ ok(!aElement.matches(":out-of-range"),
+ ":out-of-range should not match");
+ } else {
+ is(aElement.matches(":in-range"), aValidity,
+ ":in-range matches status should be " + aValidity);
+ is(aElement.matches(":out-of-range"), !aValidity,
+ ":out-of-range matches status should be " + !aValidity);
+ }
+}
+
+for (var test of data) {
+ input.type = test.type;
+ var apply = test.apply;
+
+ // The element should be valid. Range should not apply when @min and @max are
+ // undefined, except if the input type is 'range' (since that type has a
+ // default minimum and maximum).
+ if (input.type == 'range') {
+ checkValidity(input, true, apply, true);
+ } else {
+ checkValidity(input, true, apply, false);
+ }
+ checkValidity(input, true, apply, test.type == 'range');
+
+ switch (input.type) {
+ case 'hidden':
+ case 'text':
+ case 'search':
+ case 'password':
+ case 'url':
+ case 'tel':
+ case 'email':
+ case 'number':
+ case 'checkbox':
+ case 'radio':
+ case 'file':
+ case 'submit':
+ case 'reset':
+ case 'button':
+ case 'image':
+ case 'color':
+ input.max = '-1';
+ break;
+ case 'date':
+ input.max = '2012-06-27';
+ break;
+ case 'time':
+ input.max = '02:20';
+ break;
+ case 'range':
+ // range is special, since setting max to -1 will make it invalid since
+ // it's default would then be 0, meaning it suffers from overflow.
+ input.max = '-1';
+ checkValidity(input, false, apply, apply);
+ // Now make it something that won't cause an error below:
+ input.max = '10';
+ break;
+ case 'month':
+ input.max = '2016-12';
+ break;
+ case 'week':
+ input.max = '2016-W39';
+ break;
+ case 'datetime-local':
+ // TODO: this is temporary until bug 888331 is fixed.
+ break;
+ default:
+ ok(false, 'please, add a case for this new type (' + input.type + ')');
+ }
+
+ checkValidity(input, true, apply, apply);
+
+ switch (input.type) {
+ case 'text':
+ case 'hidden':
+ case 'search':
+ case 'password':
+ case 'tel':
+ case 'radio':
+ case 'checkbox':
+ case 'reset':
+ case 'button':
+ case 'submit':
+ case 'image':
+ input.value = '0';
+ checkValidity(input, true, apply, apply);
+ break;
+ case 'url':
+ input.value = 'http://mozilla.org';
+ checkValidity(input, true, apply, apply);
+ break;
+ case 'email':
+ input.value = 'foo@bar.com';
+ checkValidity(input, true, apply, apply);
+ break;
+ case 'file':
+ var file = new File([''], '635499_file');
+
+ SpecialPowers.wrap(input).mozSetFileArray([file]);
+ checkValidity(input, true, apply, apply);
+
+ break;
+ case 'date':
+ input.max = '2012-06-27';
+ input.value = '2012-06-26';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2012-06-27';
+ checkValidity(input, true, apply, apply);
+
+ input.value = 'foo';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2012-06-28';
+ checkValidity(input, false, apply, apply);
+
+ input.max = '2012-06-30';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2012-07-05';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '1000-01-01';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '20120-01-01';
+ checkValidity(input, false, apply, apply);
+
+ input.max = '0050-01-01';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '0049-01-01';
+ checkValidity(input, true, apply, apply);
+
+ input.max = '';
+ checkValidity(input, true, apply, false);
+
+ input.max = 'foo';
+ checkValidity(input, true, apply, false);
+
+ break;
+ case 'number':
+ input.max = '2';
+ input.value = '1';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2';
+ checkValidity(input, true, apply, apply);
+
+ input.value = 'foo';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '3';
+ checkValidity(input, false, apply, apply);
+
+ input.max = '5';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '42';
+ checkValidity(input, false, apply, apply);
+
+ input.max = '';
+ checkValidity(input, true, apply, false);
+
+ input.max = 'foo';
+ checkValidity(input, true, apply, false);
+
+ // Check that we correctly convert input.max to a double in validationMessage.
+ if (input.type == 'number') {
+ input.max = "4.333333333333333333333333333333333331";
+ input.value = "5";
+ is(input.validationMessage,
+ "Please select a value that is no more than 4.33333333333333.",
+ "validation message");
+ }
+
+ break;
+ case 'range':
+ input.max = '2';
+ input.value = '1';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2';
+ checkValidity(input, true, apply, apply);
+
+ input.value = 'foo';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '3';
+ checkValidity(input, true, apply, apply);
+
+ is(input.value, input.max, "the value should have been set to max");
+
+ input.max = '5';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '42';
+ checkValidity(input, true, apply, apply);
+
+ is(input.value, input.max, "the value should have been set to max");
+
+ input.max = '';
+ checkValidity(input, true, apply, apply);
+
+ input.max = 'foo';
+ checkValidity(input, true, apply, apply);
+
+ // Check that we correctly convert input.max to a double in validationMessage.
+ input.step = 'any';
+ input.min = 5;
+ input.max = 0.66666666666666666666666666666666666
+ input.value = 1;
+ is(input.validationMessage,
+ "Please select a value that is no more than 0.666666666666667.",
+ "validation message")
+
+ break;
+ case 'time':
+ // Don't worry about that.
+ input.step = 'any';
+
+ input.max = '10:10';
+ input.value = '10:09';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '10:10';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '10:10:00';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '10:10:00.000';
+ checkValidity(input, true, apply, apply);
+
+ input.value = 'foo';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '10:11';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '10:10:00.001';
+ checkValidity(input, false, apply, apply);
+
+ input.max = '01:00:00.01';
+ input.value = '01:00:00.001';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '01:00:00';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '01:00:00.1';
+ checkValidity(input, false, apply, apply);
+
+ input.max = '';
+ checkValidity(input, true, apply, false);
+
+ input.max = 'foo';
+ checkValidity(input, true, apply, false);
+
+ break;
+ case 'month':
+ input.value = '2016-06';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2016-12';
+ checkValidity(input, true, apply, apply);
+
+ input.value = 'foo';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2017-01';
+ checkValidity(input, false, apply, apply);
+
+ input.max = '2017-07';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2017-12';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '1000-01';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '20160-01';
+ checkValidity(input, false, apply, apply);
+
+ input.max = '0050-01';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '0049-12';
+ checkValidity(input, true, apply, apply);
+
+ input.max = '';
+ checkValidity(input, true, apply, false);
+
+ input.max = 'foo';
+ checkValidity(input, true, apply, false);
+
+ break;
+ case 'week':
+ input.value = '2016-W01';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2016-W39';
+ checkValidity(input, true, apply, apply);
+
+ input.value = 'foo';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2017-W01';
+ checkValidity(input, false, apply, apply);
+
+ input.max = '2017-W01';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2017-W52';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '1000-W01';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2100-W01';
+ checkValidity(input, false, apply, apply);
+
+ input.max = '0050-W01';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '0049-W52';
+ checkValidity(input, true, apply, apply);
+
+ input.max = '';
+ checkValidity(input, true, apply, false);
+
+ input.max = 'foo';
+ checkValidity(input, true, apply, false);
+
+ break;
+ case 'datetime-local':
+ // TODO: this is temporary until bug 888331 is fixed.
+
+ break;
+ }
+
+ // Cleaning up,
+ input.removeAttribute('max');
+ input.value = '';
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_maxlength_attribute.html b/dom/html/test/forms/test_maxlength_attribute.html
new file mode 100644
index 000000000..cfc50b67c
--- /dev/null
+++ b/dom/html/test/forms/test_maxlength_attribute.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=345624
+-->
+<head>
+ <title>Test for Bug 345624</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"/>
+ <style>
+ input, textarea { background-color: rgb(0,0,0) !important; }
+ :-moz-any(input,textarea):valid { background-color: rgb(0,255,0) !important; }
+ :-moz-any(input,textarea):invalid { background-color: rgb(255,0,0) !important; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=345624">Mozilla Bug 345624</a>
+<p id="display"></p>
+<div id="content">
+ <input id='i'>
+ <textarea id='t'></textarea>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 345624 **/
+
+/**
+ * This test is checking only tooLong related features
+ * related to constraint validation.
+ */
+
+function checkTooLongValidity(element)
+{
+ element.value = "foo";
+ ok(!element.validity.tooLong,
+ "Element should not be too long when maxlength is not set");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+ ok(element.validity.valid, "Element should be valid");
+ ok(element.checkValidity(), "The element should be valid");
+
+ element.maxLength = 1;
+ ok(!element.validity.tooLong,
+ "Element should not be too long unless the user edits it");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+ ok(element.validity.valid, "Element should be valid");
+ ok(element.checkValidity(), "The element should be valid");
+
+ element.focus();
+
+ synthesizeKey("VK_BACK_SPACE", {});
+ is(element.value, "fo", "value should have changed");
+ ok(element.validity.tooLong,
+ "Element should be too long after a user edit that does not make it short enough");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(255, 0, 0)", ":invalid pseudo-class should apply");
+ ok(!element.validity.valid, "Element should be invalid");
+ ok(!element.checkValidity(), "The element should not be valid");
+ is(element.validationMessage,
+ "Please shorten this text to 1 characters or less (you are currently using 2 characters).",
+ "The validation message text is not correct");
+
+ synthesizeKey("VK_BACK_SPACE", {});
+ is(element.value, "f", "value should have changed");
+ ok(!element.validity.tooLong,
+ "Element should not be too long after a user edit makes it short enough");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+ ok(element.validity.valid, "Element should be valid");
+
+ element.maxLength = 2;
+ ok(!element.validity.tooLong,
+ "Element should remain valid if maxlength changes but maxlength > length");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+ ok(element.validity.valid, "Element should be valid");
+
+ element.maxLength = 1;
+ ok(!element.validity.tooLong,
+ "Element should remain valid if maxlength changes but maxlength = length");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+ ok(element.validity.valid, "Element should be valid");
+ ok(element.checkValidity(), "The element should be valid");
+
+ element.maxLength = 0;
+ ok(element.validity.tooLong,
+ "Element should become invalid if maxlength changes and maxlength < length");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(255, 0, 0)", ":invalid pseudo-class should apply");
+ ok(!element.validity.valid, "Element should be invalid");
+ ok(!element.checkValidity(), "The element should not be valid");
+ is(element.validationMessage,
+ "Please shorten this text to 0 characters or less (you are currently using 1 characters).",
+ "The validation message text is not correct");
+
+ element.maxLength = 1;
+ ok(!element.validity.tooLong,
+ "Element should become valid if maxlength changes and maxlength = length");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+ ok(element.validity.valid, "Element should be valid");
+ ok(element.checkValidity(), "The element should be valid");
+
+ element.value = "test";
+ ok(!element.validity.tooLong,
+ "Element should stay valid after programmatic edit (even if value is too long)");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+ ok(element.validity.valid, "Element should be valid");
+ ok(element.checkValidity(), "The element should be valid");
+
+ element.setCustomValidity("custom message");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(255, 0, 0)", ":invalid pseudo-class should apply");
+ is(element.validationMessage, "custom message",
+ "Custom message should be shown instead of too long one");
+}
+
+checkTooLongValidity(document.getElementById('i'));
+checkTooLongValidity(document.getElementById('t'));
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_meter_element.html b/dom/html/test/forms/test_meter_element.html
new file mode 100644
index 000000000..7b9c597ce
--- /dev/null
+++ b/dom/html/test/forms/test_meter_element.html
@@ -0,0 +1,384 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=657938
+-->
+<head>
+ <title>Test for <meter></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=657938">Mozilla Bug 657938</a>
+<p id="display"></p>
+<iframe name="submit_frame" style="visibility: hidden;"></iframe>
+<div id="content" style="visibility: hidden;">
+ <form id='f' method='get' target='submit_frame' action='foo'>
+ <meter id='m' value=0.5></meter>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for <meter> **/
+
+function checkFormIDLAttribute(aElement)
+{
+ is('form' in aElement, false, "<meter> shouldn't have a form attribute");
+}
+
+function checkAttribute(aElement, aAttribute, aNewValue, aExpectedValueForIDL)
+{
+ var expectedValueForIDL = aNewValue;
+ var expectedValueForContent = String(aNewValue);
+
+ if (aExpectedValueForIDL !== undefined) {
+ expectedValueForIDL = aExpectedValueForIDL;
+ }
+
+ if (aNewValue != null) {
+ aElement.setAttribute(aAttribute, aNewValue);
+ is(aElement.getAttribute(aAttribute), expectedValueForContent,
+ aAttribute + " content attribute should be " + expectedValueForContent);
+ is(aElement[aAttribute], expectedValueForIDL,
+ aAttribute + " IDL attribute should be " + expectedValueForIDL);
+
+ if (parseFloat(aNewValue) == aNewValue) {
+ aElement[aAttribute] = aNewValue;
+ is(aElement.getAttribute(aAttribute), expectedValueForContent,
+ aAttribute + " content attribute should be " + expectedValueForContent);
+ is(aElement[aAttribute], parseFloat(expectedValueForIDL),
+ aAttribute + " IDL attribute should be " + parseFloat(expectedValueForIDL));
+ }
+ } else {
+ aElement.removeAttribute(aAttribute);
+ is(aElement.getAttribute(aAttribute), null,
+ aAttribute + " content attribute should be null");
+ is(aElement[aAttribute], expectedValueForIDL,
+ aAttribute + " IDL attribute should be " + expectedValueForIDL);
+ }
+}
+
+function checkValueAttribute()
+{
+ var tests = [
+ // value has to be a valid float, its default value is 0.0 otherwise.
+ [ null, 0.0 ],
+ [ 'foo', 0.0 ],
+ // If value < 0.0, 0.0 is used instead.
+ [ -1.0, 0.0 ],
+ // If value >= max, max is used instead (max default value is 1.0).
+ [ 2.0, 1.0 ],
+ [ 1.0, 0.5, 0.5 ],
+ [ 10.0, 5.0, 5.0 ],
+ [ 13.37, 13.37, 42.0 ],
+ // If value <= min, min is used instead (min default value is 0.0).
+ [ 0.5, 1.0, 10.0 ,1.0 ],
+ [ 10.0, 13.37, 42.0 , 13.37],
+ // Regular reflection.
+ [ 0.0 ],
+ [ 0.5 ],
+ [ 1.0 ],
+ // Check double-precision value.
+ [ 0.234567898765432 ],
+ ];
+
+ var element = document.createElement('meter');
+
+ for (var test of tests) {
+ if (test[2]) {
+ element.setAttribute('max', test[2]);
+ }
+
+ if (test[3]) {
+ element.setAttribute('min', test[3]);
+ }
+
+ checkAttribute(element, 'value', test[0], test[1]);
+
+ element.removeAttribute('max');
+ element.removeAttribute('min');
+ }
+}
+
+function checkMinAttribute()
+{
+ var tests = [
+ // min default value is 0.0.
+ [ null, 0.0 ],
+ [ 'foo', 0.0 ],
+ // Regular reflection.
+ [ 0.5 ],
+ [ 1.0 ],
+ [ 2.0 ],
+ // Check double-precision value.
+ [ 0.234567898765432 ],
+ ];
+
+ var element = document.createElement('meter');
+
+ for (var test of tests) {
+ checkAttribute(element, 'min', test[0], test[1]);
+ }
+}
+
+function checkMaxAttribute()
+{
+ var tests = [
+ // max default value is 1.0.
+ [ null, 1.0 ],
+ [ 'foo', 1.0 ],
+ // If value <= min, min is used instead.
+ [ -1.0, 0.0 ],
+ [ 0.0, 0.5, 0.5 ],
+ [ 10.0, 15.0, 15.0 ],
+ [ 42, 42, 13.37 ],
+ // Regular reflection.
+ [ 0.5 ],
+ [ 1.0 ],
+ [ 2.0 ],
+ // Check double-precision value.
+ [ 0.234567898765432 ],
+ ];
+
+ var element = document.createElement('meter');
+
+ for (var test of tests) {
+ if (test[2]) {
+ element.setAttribute('min', test[2]);
+ }
+
+ checkAttribute(element, 'max', test[0], test[1]);
+
+ element.removeAttribute('min');
+ }
+}
+
+function checkLowAttribute()
+{
+ var tests = [
+ // low default value is min (min default value is 0.0).
+ [ null, 0.0 ],
+ [ 'foo', 0.0 ],
+ [ 'foo', 1.0, 1.0],
+ // If low <= min, min is used instead.
+ [ -1.0, 0.0 ],
+ [ 0.0, 0.5, 0.5 ],
+ [ 10.0, 15.0, 15.0, 42.0 ],
+ [ 42.0, 42.0, 13.37, 100.0 ],
+ // If low >= max, max is used instead.
+ [ 2.0, 1.0 ],
+ [ 10.0, 5.0 , 0.5, 5.0 ],
+ [ 13.37, 13.37, 0.0, 42.0 ],
+ // Regular reflection.
+ [ 0.0 ],
+ [ 0.5 ],
+ [ 1.0 ],
+ // Check double-precision value.
+ [ 0.234567898765432 ],
+ ];
+
+ var element = document.createElement('meter');
+
+ for (var test of tests) {
+ if (test[2]) {
+ element.setAttribute('min', test[2]);
+ }
+ if (test[3]) {
+ element.setAttribute('max', test[3]);
+ }
+
+ checkAttribute(element, 'low', test[0], test[1]);
+
+ element.removeAttribute('min');
+ element.removeAttribute('max');
+ }
+}
+
+function checkHighAttribute()
+{
+ var tests = [
+ // high default value is max (max default value is 1.0).
+ [ null, 1.0 ],
+ [ 'foo', 1.0 ],
+ [ 'foo', 42.0, 0.0, 42.0],
+ // If high <= min, min is used instead.
+ [ -1.0, 0.0 ],
+ [ 0.0, 0.5, 0.5 ],
+ [ 10.0, 15.0, 15.0, 42.0 ],
+ [ 42.0, 42.0, 13.37, 100.0 ],
+ // If high >= max, max is used instead.
+ [ 2.0, 1.0 ],
+ [ 10.0, 5.0 , 0.5, 5.0 ],
+ [ 13.37, 13.37, 0.0, 42.0 ],
+ // Regular reflection.
+ [ 0.0 ],
+ [ 0.5 ],
+ [ 1.0 ],
+ // Check double-precision value.
+ [ 0.234567898765432 ],
+ ];
+
+ var element = document.createElement('meter');
+
+ for (var test of tests) {
+ if (test[2]) {
+ element.setAttribute('min', test[2]);
+ }
+ if (test[3]) {
+ element.setAttribute('max', test[3]);
+ }
+
+ checkAttribute(element, 'high', test[0], test[1]);
+
+ element.removeAttribute('min');
+ element.removeAttribute('max');
+ }
+}
+
+function checkOptimumAttribute()
+{
+ var tests = [
+ // opt default value is (max-min)/2 (thus default value is 0.5).
+ [ null, 0.5 ],
+ [ 'foo', 0.5 ],
+ [ 'foo', 2.0, 1.0, 3.0],
+ // If opt <= min, min is used instead.
+ [ -1.0, 0.0 ],
+ [ 0.0, 0.5, 0.5 ],
+ [ 10.0, 15.0, 15.0, 42.0 ],
+ [ 42.0, 42.0, 13.37, 100.0 ],
+ // If opt >= max, max is used instead.
+ [ 2.0, 1.0 ],
+ [ 10.0, 5.0 , 0.5, 5.0 ],
+ [ 13.37, 13.37, 0.0, 42.0 ],
+ // Regular reflection.
+ [ 0.0 ],
+ [ 0.5 ],
+ [ 1.0 ],
+ // Check double-precision value.
+ [ 0.234567898765432 ],
+ ];
+
+ var element = document.createElement('meter');
+
+ for (var test of tests) {
+ if (test[2]) {
+ element.setAttribute('min', test[2]);
+ }
+ if (test[3]) {
+ element.setAttribute('max', test[3]);
+ }
+
+ checkAttribute(element, 'optimum', test[0], test[1]);
+
+ element.removeAttribute('min');
+ element.removeAttribute('max');
+ }
+}
+
+function checkFormListedElement(aElement)
+{
+ is(document.forms[0].elements.length, 0, "the form should have no element");
+}
+
+function checkLabelable(aElement)
+{
+ var content = document.getElementById('content');
+ var label = document.createElement('label');
+
+ content.appendChild(label);
+ label.appendChild(aElement);
+ is(label.control, aElement, "meter should be labelable");
+
+ // Cleaning-up.
+ content.removeChild(label);
+ content.appendChild(aElement);
+}
+
+function checkNotResetableAndFormSubmission(aElement)
+{
+ // Creating an input element to check the submission worked.
+ var form = document.forms[0];
+ var input = document.createElement('input');
+
+ input.name = 'a';
+ input.value = 'tulip';
+ form.appendChild(input);
+
+ // Setting values.
+ aElement.value = 42.0;
+ aElement.max = 100.0;
+
+ document.getElementsByName('submit_frame')[0].addEventListener("load", function() {
+ document.getElementsByName('submit_frame')[0].removeEventListener("load", arguments.callee, false);
+
+ /**
+ * All elements values have been set just before the submission.
+ * The input element value should be in the submit url but the meter
+ * element value should not appear.
+ */
+ is(frames['submit_frame'].location.href,
+ 'http://mochi.test:8888/tests/dom/html/test/forms/foo?a=tulip',
+ "The meter element value should not be submitted");
+
+ checkNotResetable();
+ }, false);
+
+ form.submit();
+}
+
+function checkNotResetable()
+{
+ // Try to reset the form.
+ var form = document.forms[0];
+ var element = document.getElementById('m');
+
+ element.value = 3.0;
+ element.max = 42.0;
+
+ form.reset();
+
+ SimpleTest.executeSoon(function() {
+ is(element.value, 3.0, "meter.value should not have changed");
+ is(element.max, 42.0, "meter.max should not have changed");
+
+ SimpleTest.finish();
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+
+var m = document.getElementById('m');
+
+ok(m instanceof HTMLMeterElement,
+ "The meter element should be instance of HTMLMeterElement");
+is(m.constructor, HTMLMeterElement,
+ "The meter element constructor should be HTMLMeterElement");
+
+// There is no such attribute.
+checkFormIDLAttribute(m);
+
+checkValueAttribute();
+
+checkMinAttribute();
+
+checkMaxAttribute();
+
+checkLowAttribute();
+
+checkHighAttribute();
+
+checkOptimumAttribute();
+
+checkFormListedElement(m);
+
+checkLabelable(m);
+
+checkNotResetableAndFormSubmission(m);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_meter_pseudo-classes.html b/dom/html/test/forms/test_meter_pseudo-classes.html
new file mode 100644
index 000000000..e11f48a53
--- /dev/null
+++ b/dom/html/test/forms/test_meter_pseudo-classes.html
@@ -0,0 +1,170 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=660238
+-->
+<head>
+ <title>Test for Bug 660238</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=770238">Mozilla Bug 660238</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 660238 **/
+
+function checkOptimum(aElement, aValue, aOptimum, expectedResult)
+{
+ var errorString = expectedResult
+ ? "value attribute should be in the optimum region"
+ : "value attribute should not be in the optimum region";
+
+ aElement.setAttribute('value', aValue);
+ aElement.setAttribute('optimum', aOptimum);
+ is(aElement.matches(":-moz-meter-optimum"),
+ expectedResult, errorString);
+}
+
+function checkSubOptimum(aElement, aValue, aOptimum, expectedResult)
+{
+ var errorString = "value attribute should be in the suboptimal region";
+ if (!expectedResult) {
+ errorString = "value attribute should not be in the suboptimal region";
+ }
+ aElement.setAttribute('value', aValue);
+ aElement.setAttribute('optimum', aOptimum);
+ is(aElement.matches(":-moz-meter-sub-optimum"),
+ expectedResult, errorString);
+}
+
+function checkSubSubOptimum(aElement, aValue, aOptimum, expectedResult)
+{
+ var errorString = "value attribute should be in the sub-suboptimal region";
+ if (!expectedResult) {
+ errorString = "value attribute should not be in the sub-suboptimal region";
+ }
+ aElement.setAttribute('value', aValue);
+ aElement.setAttribute('optimum', aOptimum);
+ is(aElement.matches(":-moz-meter-sub-sub-optimum"),
+ expectedResult, errorString);
+}
+
+function checkMozMatchesSelector()
+{
+ var element = document.createElement('meter');
+ // all tests realised with default values for min and max (0 and 1)
+ // low = 0.3 and high = 0.7
+ element.setAttribute('low', 0.3);
+ element.setAttribute('high', 0.7);
+
+ var tests = [
+ /*
+ * optimum = 0.0 =>
+ * optimum region = [ 0.0, 0.3 [
+ * suboptimal region = [ 0.3, 0.7 ]
+ * sub-suboptimal region = ] 0.7, 1.0 ]
+ */
+ [ 0.0, 0.0, true, false, false ],
+ [ 0.1, 0.0, true, false, false ],
+ [ 0.3, 0.0, false, true, false ],
+ [ 0.5, 0.0, false, true, false ],
+ [ 0.7, 0.0, false, true, false ],
+ [ 0.8, 0.0, false, false, true ],
+ [ 1.0, 0.0, false, false, true ],
+ /*
+ * optimum = 0.1 =>
+ * optimum region = [ 0.0, 0.3 [
+ * suboptimal region = [ 0.3, 0.7 ]
+ * sub-suboptimal region = ] 0.7, 1.0 ]
+ */
+ [ 0.0, 0.1, true, false, false ],
+ [ 0.1, 0.1, true, false, false ],
+ [ 0.3, 0.1, false, true, false ],
+ [ 0.5, 0.1, false, true, false ],
+ [ 0.7, 0.1, false, true, false ],
+ [ 0.8, 0.1, false, false, true ],
+ [ 1.0, 0.1, false, false, true ],
+ /*
+ * optimum = 0.3 =>
+ * suboptimal region = [ 0.0, 0.3 [
+ * optimum region = [ 0.3, 0.7 ]
+ * suboptimal region = ] 0.7, 1.0 ]
+ */
+ [ 0.0, 0.3, false, true, false ],
+ [ 0.1, 0.3, false, true, false ],
+ [ 0.3, 0.3, true, false, false ],
+ [ 0.5, 0.3, true, false, false ],
+ [ 0.7, 0.3, true, false, false ],
+ [ 0.8, 0.3, false, true, false ],
+ [ 1.0, 0.3, false, true, false ],
+ /*
+ * optimum = 0.5 =>
+ * suboptimal region = [ 0.0, 0.3 [
+ * optimum region = [ 0.3, 0.7 ]
+ * suboptimal region = ] 0.7, 1.0 ]
+ */
+ [ 0.0, 0.5, false, true, false ],
+ [ 0.1, 0.5, false, true, false ],
+ [ 0.3, 0.5, true, false, false ],
+ [ 0.5, 0.5, true, false, false ],
+ [ 0.7, 0.5, true, false, false ],
+ [ 0.8, 0.5, false, true, false ],
+ [ 1.0, 0.5, false, true, false ],
+ /*
+ * optimum = 0.7 =>
+ * suboptimal region = [ 0.0, 0.3 [
+ * optimum region = [ 0.3, 0.7 ]
+ * suboptimal region = ] 0.7, 1.0 ]
+ */
+ [ 0.0, 0.7, false, true, false ],
+ [ 0.1, 0.7, false, true, false ],
+ [ 0.3, 0.7, true, false, false ],
+ [ 0.5, 0.7, true, false, false ],
+ [ 0.7, 0.7, true, false, false ],
+ [ 0.8, 0.7, false, true, false ],
+ [ 1.0, 0.7, false, true, false ],
+ /*
+ * optimum = 0.8 =>
+ * sub-suboptimal region = [ 0.0, 0.3 [
+ * suboptimal region = [ 0.3, 0.7 ]
+ * optimum region = ] 0.7, 1.0 ]
+ */
+ [ 0.0, 0.8, false, false, true ],
+ [ 0.1, 0.8, false, false, true ],
+ [ 0.3, 0.8, false, true, false ],
+ [ 0.5, 0.8, false, true, false ],
+ [ 0.7, 0.8, false, true, false ],
+ [ 0.8, 0.8, true, false, false ],
+ [ 1.0, 0.8, true, false, false ],
+ /*
+ * optimum = 1.0 =>
+ * sub-suboptimal region = [ 0.0, 0.3 [
+ * suboptimal region = [ 0.3, 0.7 ]
+ * optimum region = ] 0.7, 1.0 ]
+ */
+ [ 0.0, 1.0, false, false, true ],
+ [ 0.1, 1.0, false, false, true ],
+ [ 0.3, 1.0, false, true, false ],
+ [ 0.5, 1.0, false, true, false ],
+ [ 0.7, 1.0, false, true, false ],
+ [ 0.8, 1.0, true, false, false ],
+ [ 1.0, 1.0, true, false, false ],
+ ];
+
+ for (var test of tests) {
+ checkOptimum(element, test[0], test[1], test[2]);
+ checkSubOptimum(element, test[0], test[1], test[3]);
+ checkSubSubOptimum(element, test[0], test[1], test[4]);
+ }
+}
+
+checkMozMatchesSelector();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_min_attribute.html b/dom/html/test/forms/test_min_attribute.html
new file mode 100644
index 000000000..1258babec
--- /dev/null
+++ b/dom/html/test/forms/test_min_attribute.html
@@ -0,0 +1,437 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=635553
+-->
+<head>
+ <title>Test for Bug 635553</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=635499">Mozilla Bug 635499</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 635553 **/
+
+var data = [
+ { type: 'hidden', apply: false },
+ { type: 'text', apply: false },
+ { type: 'search', apply: false },
+ { type: 'tel', apply: false },
+ { type: 'url', apply: false },
+ { type: 'email', apply: false },
+ { type: 'password', apply: false },
+ { type: 'date', apply: true },
+ { type: 'month', apply: true },
+ { type: 'week', apply: true },
+ { type: 'time', apply: true },
+ // TODO: temporary set to false until bug 888331 is fixed.
+ { type: 'datetime-local', apply: false },
+ { type: 'number', apply: true },
+ { type: 'range', apply: true },
+ { type: 'color', apply: false },
+ { type: 'checkbox', apply: false },
+ { type: 'radio', apply: false },
+ { type: 'file', apply: false },
+ { type: 'submit', apply: false },
+ { type: 'image', apply: false },
+ { type: 'reset', apply: false },
+ { type: 'button', apply: false },
+];
+
+var input = document.createElement("input");
+document.getElementById('content').appendChild(input);
+
+/**
+ * @aValidity - boolean indicating whether the element is expected to be valid
+ * (aElement.validity.valid is true) or not. The value passed is ignored and
+ * overridden with true if aApply is false.
+ * @aApply - boolean indicating whether the min/max attributes apply to this
+ * element type.
+ * @aRangeApply - A boolean that's set to true if the current input type is a
+ * "[candidate] for constraint validation" and it "[has] range limitations"
+ * per http://www.whatwg.org/specs/web-apps/current-work/multipage/selectors.html#selector-in-range
+ * (in other words, one of the pseudo classes :in-range and :out-of-range
+ * should apply (which, depends on aValidity)).
+ * Else (neither :in-range or :out-of-range should match) set to false.
+ */
+function checkValidity(aElement, aValidity, aApply, aRangeApply)
+{
+ aValidity = aApply ? aValidity : true;
+
+ is(aElement.validity.valid, aValidity,
+ "element validity should be " + aValidity);
+ is(aElement.validity.rangeUnderflow, !aValidity,
+ "element underflow status should be " + !aValidity);
+ var underflowMsg =
+ (aElement.type == "date" || aElement.type == "time" ||
+ aElement.type == "month" || aElement.type == "week") ?
+ ("Please select a value that is no earlier than " + aElement.min + ".") :
+ ("Please select a value that is no less than " + aElement.min + ".");
+ is(aElement.validationMessage,
+ aValidity ? "" : underflowMsg, "Checking range underflow validation message");
+
+ is(aElement.matches(":valid"), aElement.willValidate && aValidity,
+ (aElement.willValidate && aValidity) ? ":valid should apply" : "valid shouldn't apply");
+ is(aElement.matches(":invalid"), aElement.willValidate && !aValidity,
+ (aElement.wil && aValidity) ? ":invalid shouldn't apply" : "valid should apply");
+
+ if (!aRangeApply) {
+ ok(!aElement.matches(":in-range"), ":in-range should not match");
+ ok(!aElement.matches(":out-of-range"),
+ ":out-of-range should not match");
+ } else {
+ is(aElement.matches(":in-range"), aValidity,
+ ":in-range matches status should be " + aValidity);
+ is(aElement.matches(":out-of-range"), !aValidity,
+ ":out-of-range matches status should be " + !aValidity);
+ }
+}
+
+for (var test of data) {
+ input.type = test.type;
+ var apply = test.apply;
+
+ if (test.todo) {
+ todo_is(input.type, test.type, test.type + " isn't implemented yet");
+ continue;
+ }
+
+ // The element should be valid. Range should not apply when @min and @max are
+ // undefined, except if the input type is 'range' (since that type has a
+ // default minimum and maximum).
+ if (input.type == 'range') {
+ checkValidity(input, true, apply, true);
+ } else {
+ checkValidity(input, true, apply, false);
+ }
+
+ switch (input.type) {
+ case 'hidden':
+ case 'text':
+ case 'search':
+ case 'password':
+ case 'url':
+ case 'tel':
+ case 'email':
+ case 'number':
+ case 'checkbox':
+ case 'radio':
+ case 'file':
+ case 'submit':
+ case 'reset':
+ case 'button':
+ case 'image':
+ case 'color':
+ input.min = '999';
+ break;
+ case 'date':
+ input.min = '2012-06-27';
+ break;
+ case 'time':
+ input.min = '20:20';
+ break;
+ case 'range':
+ // range is special, since setting min to 999 will make it invalid since
+ // it's default maximum is 100, its value would be 999, and it would
+ // suffer from overflow.
+ break;
+ case 'month':
+ input.min = '2016-06';
+ break;
+ case 'week':
+ input.min = "2016-W39";
+ break;
+ case 'datetime-local':
+ // TODO: this is temporary until bug 888331 is fixed.
+ break;
+ default:
+ ok(false, 'please, add a case for this new type (' + input.type + ')');
+ }
+
+ // The element should still be valid and range should apply if it can.
+ checkValidity(input, true, apply, apply);
+
+ switch (input.type) {
+ case 'text':
+ case 'hidden':
+ case 'search':
+ case 'password':
+ case 'tel':
+ case 'radio':
+ case 'checkbox':
+ case 'reset':
+ case 'button':
+ case 'submit':
+ case 'image':
+ case 'color':
+ input.value = '0';
+ checkValidity(input, true, apply, apply);
+ break;
+ case 'url':
+ input.value = 'http://mozilla.org';
+ checkValidity(input, true, apply, apply);
+ break;
+ case 'email':
+ input.value = 'foo@bar.com';
+ checkValidity(input, true, apply, apply);
+ break;
+ case 'file':
+ var file = new File([''], '635499_file');
+
+ SpecialPowers.wrap(input).mozSetFileArray([file]);
+ checkValidity(input, true, apply, apply);
+
+ break;
+ case 'date':
+ input.value = '2012-06-28';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2012-06-27';
+ checkValidity(input, true, apply, apply);
+
+ input.value = 'foo';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2012-06-26';
+ checkValidity(input, false, apply, apply);
+
+ input.min = '2012-02-29';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2012-02-28';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '1000-01-01';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '20120-01-01';
+ checkValidity(input, true, apply, apply);
+
+ input.min = '0050-01-01';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '0049-01-01';
+ checkValidity(input, false, apply, apply);
+
+ input.min = '';
+ checkValidity(input, true, apply, false);
+
+ input.min = 'foo';
+ checkValidity(input, true, apply, false);
+ break;
+ case 'number':
+ input.min = '0';
+ input.value = '1';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '0';
+ checkValidity(input, true, apply, apply);
+
+ input.value = 'foo';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '-1';
+ checkValidity(input, false, apply, apply);
+
+ input.min = '-1';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '-42';
+ checkValidity(input, false, apply, apply);
+
+ input.min = '';
+ checkValidity(input, true, apply, false);
+
+ input.min = 'foo';
+ checkValidity(input, true, apply, false);
+
+ // Check that we correctly convert input.min to a double in
+ // validationMessage.
+ input.min = "4.333333333333333333333333333333333331";
+ input.value = "2";
+ is(input.validationMessage,
+ "Please select a value that is no less than 4.33333333333333.",
+ "validation message");
+ break;
+ case 'range':
+ input.min = '0';
+ input.value = '1';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '0';
+ checkValidity(input, true, apply, apply);
+
+ input.value = 'foo';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '-1';
+ checkValidity(input, true, apply, apply);
+
+ is(input.value, input.min, "the value should have been set to min");
+
+ input.min = '-1';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '-42';
+ checkValidity(input, true, apply, apply);
+
+ is(input.value, input.min, "the value should have been set to min");
+
+ input.min = '';
+ checkValidity(input, true, apply, true);
+
+ input.min = 'foo';
+ checkValidity(input, true, apply, true);
+
+ // We don't check the conversion of input.min to a double in
+ // validationMessage for 'range' since range will always clamp the value
+ // up to at least the minimum (so we will never see the min in a
+ // validationMessage).
+
+ break;
+ case 'time':
+ // Don't worry about that.
+ input.step = 'any';
+
+ input.min = '20:20';
+ input.value = '20:20:01';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '20:20:00';
+ checkValidity(input, true, apply, apply);
+
+ input.value = 'foo';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '10:00';
+ checkValidity(input, false, apply, apply);
+
+ input.min = '20:20:00.001';
+ input.value = '20:20';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '00:00';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '23:59';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '20:20:01';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '20:20:00.01';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '20:20:00.1';
+ checkValidity(input, true, apply, apply);
+
+ input.min = '00:00:00';
+ input.value = '01:00';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '00:00:00.000';
+ checkValidity(input, true, apply, apply);
+
+ input.min = '';
+ checkValidity(input, true, apply, false);
+
+ input.min = 'foo';
+ checkValidity(input, true, apply, false);
+ break;
+ case 'month':
+ input.value = '2016-07';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2016-06';
+ checkValidity(input, true, apply, apply);
+
+ input.value = 'foo';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2016-05';
+ checkValidity(input, false, apply, apply);
+
+ input.min = '2016-01';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2015-12';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '1000-01';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '10000-01';
+ checkValidity(input, true, apply, apply);
+
+ input.min = '0010-01';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '0001-01';
+ checkValidity(input, false, apply, apply);
+
+ input.min = '';
+ checkValidity(input, true, apply, false);
+
+ input.min = 'foo';
+ checkValidity(input, true, apply, false);
+ break;
+ case 'week':
+ input.value = '2016-W40';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2016-W39';
+ checkValidity(input, true, apply, apply);
+
+ input.value = 'foo';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2016-W38';
+ checkValidity(input, false, apply, apply);
+
+ input.min = '2016-W01';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '2015-W53';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '1000-W01';
+ checkValidity(input, false, apply, apply);
+
+ input.value = '10000-01';
+ checkValidity(input, true, apply, apply);
+
+ input.min = '0010-W01';
+ checkValidity(input, true, apply, apply);
+
+ input.value = '0001-W01';
+ checkValidity(input, false, apply, apply);
+
+ input.min = '';
+ checkValidity(input, true, apply, false);
+
+ input.min = 'foo';
+ checkValidity(input, true, apply, false);
+ break;
+ case 'datetime-local':
+ // TODO: this is temporary until bug 888331 is fixed.
+ break;
+ default:
+ ok(false, 'write tests for ' + input.type);
+ }
+
+ // Cleaning up,
+ input.removeAttribute('min');
+ input.value = '';
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_minlength_attribute.html b/dom/html/test/forms/test_minlength_attribute.html
new file mode 100644
index 000000000..ca3743bd9
--- /dev/null
+++ b/dom/html/test/forms/test_minlength_attribute.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=345624
+-->
+<head>
+ <title>Test for Bug 345624</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"/>
+ <style>
+ input, textarea { background-color: rgb(0,0,0) !important; }
+ :-moz-any(input,textarea):valid { background-color: rgb(0,255,0) !important; }
+ :-moz-any(input,textarea):invalid { background-color: rgb(255,0,0) !important; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=345624">Mozilla Bug 345624</a>
+<p id="display"></p>
+<div id="content">
+ <input id='i'>
+ <textarea id='t'></textarea>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 345624 **/
+
+/**
+ * This test is checking only tooShort related features
+ * related to constraint validation.
+ */
+
+function checkTooShortValidity(element)
+{
+ element.value = "foo";
+ ok(!element.validity.tooShort,
+ "Element should not be too short when minlength is not set");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+ ok(element.validity.valid, "Element should be valid");
+ ok(element.checkValidity(), "The element should be valid");
+
+ element.minLength = 5;
+ ok(!element.validity.tooShort,
+ "Element should not be too short unless the user edits it");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+ ok(element.validity.valid, "Element should be valid");
+ ok(element.checkValidity(), "The element should be valid");
+
+ element.focus();
+
+ synthesizeKey("o", {});
+ is(element.value, "fooo", "value should have changed");
+ ok(element.validity.tooShort,
+ "Element should be too short after a user edit that does not make it short enough");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(255, 0, 0)", ":invalid pseudo-class should apply");
+ ok(!element.validity.valid, "Element should be invalid");
+ ok(!element.checkValidity(), "The element should not be valid");
+ is(element.validationMessage,
+ "Please use at least 5 characters (you are currently using 4 characters).",
+ "The validation message text is not correct");
+
+ synthesizeKey("o", {});
+ is(element.value, "foooo", "value should have changed");
+ ok(!element.validity.tooShort,
+ "Element should not be too short after a user edit makes it long enough");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+ ok(element.validity.valid, "Element should be valid");
+
+ element.minLength = 2;
+ ok(!element.validity.tooShort,
+ "Element should remain valid if minlength changes but minlength < length");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+ ok(element.validity.valid, "Element should be valid");
+
+ element.minLength = 1;
+ ok(!element.validity.tooShort,
+ "Element should remain valid if minlength changes but minlength = length");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+ ok(element.validity.valid, "Element should be valid");
+ ok(element.checkValidity(), "The element should be valid");
+
+ element.minLength = 6;
+ ok(element.validity.tooShort,
+ "Element should become invalid if minlength changes and minlength > length");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(255, 0, 0)", ":invalid pseudo-class should apply");
+ ok(!element.validity.valid, "Element should be invalid");
+ ok(!element.checkValidity(), "The element should not be valid");
+ is(element.validationMessage,
+ "Please use at least 6 characters (you are currently using 5 characters).",
+ "The validation message text is not correct");
+
+ element.minLength = 5;
+ ok(!element.validity.tooShort,
+ "Element should become valid if minlength changes and minlength = length");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+ ok(element.validity.valid, "Element should be valid");
+ ok(element.checkValidity(), "The element should be valid");
+
+ element.value = "test";
+ ok(!element.validity.tooShort,
+ "Element should stay valid after programmatic edit (even if value is too short)");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+ ok(element.validity.valid, "Element should be valid");
+ ok(element.checkValidity(), "The element should be valid");
+
+ element.setCustomValidity("custom message");
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(255, 0, 0)", ":invalid pseudo-class should apply");
+ is(element.validationMessage, "custom message",
+ "Custom message should be shown instead of too short one");
+}
+
+checkTooShortValidity(document.getElementById('i'));
+checkTooShortValidity(document.getElementById('t'));
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/forms/test_mozistextfield.html b/dom/html/test/forms/test_mozistextfield.html
new file mode 100644
index 000000000..7c5a6bc5a
--- /dev/null
+++ b/dom/html/test/forms/test_mozistextfield.html
@@ -0,0 +1,111 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=565538
+-->
+<head>
+ <title>Test for Bug 565538</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=565538">Mozilla Bug 565538</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 565538 **/
+
+var gElementTestData = [
+/* element result */
+ ['input', true],
+ ['button', false],
+ ['fieldset', false],
+ ['label', false],
+ ['option', false],
+ ['optgroup', false],
+ ['output', false],
+ ['legend', false],
+ ['select', false],
+ ['textarea', false],
+ ['object', false],
+];
+
+var gInputTestData = [
+/* type result */
+ ['password', true],
+ ['tel', true],
+ ['text', true],
+ ['button', false],
+ ['checkbox', false],
+ ['file', false],
+ ['hidden', false],
+ ['reset', false],
+ ['image', false],
+ ['radio', false],
+ ['submit', false],
+ ['search', true],
+ ['email', true],
+ ['url', true],
+ ['number', false],
+ ['range', false],
+ ['date', false],
+ ['time', false],
+ ['color', false],
+ ['month', false],
+ ['week', false],
+ ['datetime-local', false],
+];
+
+function checkMozIsTextFieldDefined(aElement, aResult)
+{
+ var element = document.createElement(aElement);
+
+ var msg = "mozIsTextField should be "
+ if (aResult) {
+ msg += "defined";
+ } else {
+ msg += "undefined";
+ }
+
+ is('mozIsTextField' in element, aResult, msg);
+}
+
+function checkMozIsTextFieldValue(aInput, aResult)
+{
+ is(aInput.mozIsTextField(false), aResult,
+ "mozIsTextField(false) should return " + aResult);
+
+ if (aInput.type == 'password') {
+ ok(!aInput.mozIsTextField(true),
+ "mozIsTextField(true) should return false for password");
+ } else {
+ is(aInput.mozIsTextField(true), aResult,
+ "mozIsTextField(true) should return " + aResult);
+ }
+}
+
+function checkMozIsTextFieldValueTodo(aInput, aResult)
+{
+ todo_is(aInput.mozIsTextField(false), aResult,
+ "mozIsTextField(false) should return " + aResult);
+ todo_is(aInput.mozIsTextField(true), aResult,
+ "mozIsTextField(true) should return " + aResult);
+}
+
+// Check if the method is defined for the correct elements.
+for (data of gElementTestData) {
+ checkMozIsTextFieldDefined(data[0], data[1]);
+}
+
+// Check if the method returns the correct value.
+var input = document.createElement('input');
+for (data of gInputTestData) {
+ input.type = data[0];
+ checkMozIsTextFieldValue(input, data[1]);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_novalidate_attribute.html b/dom/html/test/forms/test_novalidate_attribute.html
new file mode 100644
index 000000000..38b789f5f
--- /dev/null
+++ b/dom/html/test/forms/test_novalidate_attribute.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=556013
+-->
+<head>
+ <title>Test for Bug 556013</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=556013">Mozilla Bug 556013</a>
+<p id="display"></p>
+<iframe style='width:50px; height: 50px;' name='t'></iframe>
+<div id="content">
+ <form target='t' action='data:text/html,' novalidate>
+ <input id='av' required>
+ <input id='a' type='submit'>
+ </form>
+ <form target='t' action='data:text/html,' novalidate>
+ <input id='bv' type='checkbox' required>
+ <button id='b' type='submit'></button>
+ </form>
+ <form target='t' action='data:text/html,' novalidate>
+ <input id='c' required>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 556013 **/
+
+/**
+ * novalidate should prevent form validation, thus not blocking form submission.
+ *
+ * NOTE: if there is no invalidformsubmit observer, the form submission will
+ * never be blocked and this test might be a false-positive but that should not
+ * be a problem.
+ */
+document.forms[0].addEventListener("submit", function(aEvent) {
+ aEvent.target.removeEventListener("submit", arguments.callee, false);
+ ok(true, "novalidate has been correctly used for first form");
+ document.getElementById('b').click();
+}, false);
+
+document.forms[1].addEventListener("submit", function(aEvent) {
+ aEvent.target.removeEventListener("submit", arguments.callee, false);
+ ok(true, "novalidate has been correctly used for second form");
+ var c = document.getElementById('c');
+ c.focus();
+ synthesizeKey("KEY_Enter", { code: "Enter" });
+}, false);
+
+document.forms[2].addEventListener("submit", function(aEvent) {
+ aEvent.target.removeEventListener("submit", arguments.callee, false);
+ ok(true, "novalidate has been correctly used for third form");
+ SimpleTest.executeSoon(SimpleTest.finish);
+}, false);
+
+/**
+ * We have to be sure invalid events are not send too.
+ * They should be sent before the submit event so we can just create a test
+ * failure if we got one. All of them should be catched if sent.
+ * At worst, we got random green which isn't harmful.
+ */
+function invalidHandling(aEvent)
+{
+ aEvent.target.removeEventListener("invalid", invalidHandling, false);
+ ok(false, "invalid event should not be sent");
+}
+
+document.getElementById('av').addEventListener("invalid", invalidHandling, false);
+document.getElementById('bv').addEventListener("invalid", invalidHandling, false);
+document.getElementById('c').addEventListener("invalid", invalidHandling, false);
+
+SimpleTest.waitForExplicitFinish();
+
+// This is going to call all the tests (with a chain reaction).
+SimpleTest.waitForFocus(function() {
+ document.getElementById('a').click();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_option_disabled.html b/dom/html/test/forms/test_option_disabled.html
new file mode 100644
index 000000000..1383b0846
--- /dev/null
+++ b/dom/html/test/forms/test_option_disabled.html
@@ -0,0 +1,123 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=759666
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for HTMLOptionElement disabled attribute and pseudo-class</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=759666">Mozilla Bug 759666</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for HTMLOptionElement disabled attribute and pseudo-class **/
+
+var testCases = [
+ // Static checks.
+ { html: "<option></option>",
+ result: { attr: null, idl: false, pseudo: false } },
+ { html: "<option disabled></option>",
+ result: { attr: "", idl: true, pseudo: true } },
+ { html: "<optgroup><option></option></otpgroup>",
+ result: { attr: null, idl: false, pseudo: false } },
+ { html: "<optgroup><option disabled></option></optgroup>",
+ result: { attr: "", idl: true, pseudo: true } },
+ { html: "<optgroup disabled><option disabled></option></optgroup>",
+ result: { attr: "", idl: true, pseudo: true } },
+ { html: "<optgroup disabled><option></option></optgroup>",
+ result: { attr: null, idl: false, pseudo: true } },
+ { html: "<optgroup><optgroup disabled><option></option></optgroup></optgroup>",
+ result: { attr: null, idl: false, pseudo: true } },
+ { html: "<optgroup disabled><optgroup><option></option></optgroup></optgroup>",
+ result: { attr: null, idl: false, pseudo: false } },
+ { html: "<optgroup disabled><optgroup><option disabled></option></optgroup></optgroup>",
+ result: { attr: "", idl: true, pseudo: true } },
+
+ // Dynamic checks: changing disable value.
+ { html: "<option></option>",
+ modifier: function(c) { c.querySelector('option').disabled = true; },
+ result: { attr: "", idl: true, pseudo: true } },
+ { html: "<option disabled></option>",
+ modifier: function(c) { c.querySelector('option').disabled = false; },
+ result: { attr: null, idl: false, pseudo: false } },
+ { html: "<optgroup><option></option></otpgroup>",
+ modifier: function(c) { c.querySelector('optgroup').disabled = true; },
+ result: { attr: null, idl: false, pseudo: true } },
+ { html: "<optgroup><option disabled></option></optgroup>",
+ modifier: function(c) { c.querySelector('option').disabled = false; },
+ result: { attr: null, idl: false, pseudo: false } },
+ { html: "<optgroup disabled><option disabled></option></optgroup>",
+ modifier: function(c) { c.querySelector('optgroup').disabled = false; },
+ result: { attr: "", idl: true, pseudo: true } },
+ { html: "<optgroup disabled><option disabled></option></optgroup>",
+ modifier: function(c) { c.querySelector('option').disabled = false; },
+ result: { attr: null, idl: false, pseudo: true } },
+ { html: "<optgroup disabled><option disabled></option></optgroup>",
+ modifier: function(c) { c.querySelector('optgroup').disabled = c.querySelector('option').disabled = false; },
+ result: { attr: null, idl: false, pseudo: false } },
+ { html: "<optgroup disabled><option></option></optgroup>",
+ modifier: function(c) { c.querySelector('optgroup').disabled = false; },
+ result: { attr: null, idl: false, pseudo: false } },
+ { html: "<optgroup><optgroup disabled><option></option></optgroup></optgroup>",
+ modifier: function(c) { c.querySelector('optgroup[disabled]').disabled = false; },
+ result: { attr: null, idl: false, pseudo: false } },
+ { html: "<optgroup disabled><optgroup><option></option></optgroup></optgroup>",
+ modifier: function(c) { c.querySelector('optgroup[disabled]').disabled = false; },
+ result: { attr: null, idl: false, pseudo: false } },
+ { html: "<optgroup disabled><optgroup><option disabled></option></optgroup></optgroup>",
+ modifier: function(c) { c.querySelector('optgroup').disabled = false; },
+ result: { attr: "", idl: true, pseudo: true } },
+ { html: "<optgroup disabled><optgroup><option disabled></option></optgroup></optgroup>",
+ modifier: function(c) { c.querySelector('option').disabled = false; },
+ result: { attr: null, idl: false, pseudo: false } },
+ { html: "<optgroup disabled><optgroup><option disabled></option></optgroup></optgroup>",
+ modifier: function(c) { c.querySelector('option').disabled = c.querySelector('option').disabled = false; },
+ result: { attr: null, idl: false, pseudo: false } },
+
+ // Dynamic checks: moving option element.
+ { html: "<optgroup id='a'><option></option></optgroup><optgroup id='b'></optgroup>",
+ modifier: function(c) { c.querySelector('#b').appendChild(c.querySelector('option')); },
+ result: { attr: null, idl: false, pseudo: false } },
+ { html: "<optgroup id='a'><option disabled></option></optgroup><optgroup id='b'></optgroup>",
+ modifier: function(c) { c.querySelector('#b').appendChild(c.querySelector('option')); },
+ result: { attr: "", idl: true, pseudo: true } },
+ { html: "<optgroup id='a'><option></option></optgroup><optgroup disabled id='b'></optgroup>",
+ modifier: function(c) { c.querySelector('#b').appendChild(c.querySelector('option')); },
+ result: { attr: null, idl: false, pseudo: true } },
+ { html: "<optgroup disabled id='a'><option></option></optgroup><optgroup id='b'></optgroup>",
+ modifier: function(c) { c.querySelector('#b').appendChild(c.querySelector('option')); },
+ result: { attr: null, idl: false, pseudo: false } },
+];
+
+var content = document.getElementById('content');
+
+testCases.forEach(function(testCase) {
+ var result = testCase.result;
+
+ content.innerHTML = testCase.html;
+
+ if (testCase.modifier !== undefined) {
+ testCase.modifier(content);
+ }
+
+ var option = content.querySelector('option');
+ is(option.getAttribute('disabled'), result.attr, "disabled content attribute value should be " + result.attr);
+ is(option.disabled, result.idl, "disabled idl attribute value should be " + result.idl);
+ is(option.matches(":disabled"), result.pseudo, ":disabled state should be " + result.pseudo);
+ is(option.matches(":enabled"), !result.pseudo, ":enabled state should be " + !result.pseudo);
+
+ content.innerHTML = "";
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_option_index_attribute.html b/dom/html/test/forms/test_option_index_attribute.html
new file mode 100644
index 000000000..5d51d2991
--- /dev/null
+++ b/dom/html/test/forms/test_option_index_attribute.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+See those bugs:
+https://bugzilla.mozilla.org/show_bug.cgi?id=720385
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for option.index</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=720385">Mozilla Bug 720385</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <datalist>
+ <option></option>
+ <option></option>
+ </datalist>
+ <select>
+ <option></option>
+ <foo>
+ <option></option>
+ <optgroup>
+ <option></option>
+ </optgroup>
+ <option></option>
+ </foo>
+ <option></option>
+ </select>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 720385 **/
+
+var initialIndexes = [ 0, 0, 0, 1, 2, 3, 4 ];
+var options = document.getElementsByTagName('option');
+
+is(options.length, initialIndexes.length,
+ "Must have " + initialIndexes.length +" options");
+
+for (var i=0; i<options.length; ++i) {
+ is(options[i].index, initialIndexes[i], "test");
+}
+
+var o = document.createElement('option');
+is(o.index, 0, "option outside of a document have index=0");
+
+document.body.appendChild(o);
+is(o.index, 0, "option outside of a select have index=0");
+
+var datalist = document.getElementsByTagName('datalist')[0];
+
+datalist.appendChild(o);
+is(o.index, 0, "option outside of a select have index=0");
+
+datalist.removeChild(o);
+is(o.index, 0, "option outside of a select have index=0");
+
+var select = document.getElementsByTagName('select')[0];
+
+select.appendChild(o);
+is(o.index, 5, "option inside a select have an index");
+
+select.removeChild(select.options[0]);
+is(o.index, 4, "option inside a select have an index");
+
+select.insertBefore(o, select.options[0]);
+is(o.index, 0, "option inside a select have an index");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_option_text.html b/dom/html/test/forms/test_option_text.html
new file mode 100644
index 000000000..3afe3e786
--- /dev/null
+++ b/dom/html/test/forms/test_option_text.html
@@ -0,0 +1,57 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>HTMLOptionElement.text</title>
+<link rel=author title=Ms2ger href="mailto:Ms2ger@gmail.com">
+<link rel=help href="http://www.whatwg.org/html/#dom-option-text">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createElement("font"))
+ .appendChild(document.createTextNode(" font "));
+ assert_equals(option.text, "font");
+}, "option.text should recurse");
+test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(" before "));
+ option.appendChild(document.createElement("script"))
+ .appendChild(document.createTextNode(" script "));
+ option.appendChild(document.createTextNode(" after "));
+ assert_equals(option.text, "before after");
+}, "option.text should not recurse into HTML script elements");
+test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(" before "));
+ option.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "script"))
+ .appendChild(document.createTextNode(" script "));
+ option.appendChild(document.createTextNode(" after "));
+ assert_equals(option.text, "before after");
+}, "option.text should not recurse into SVG script elements");
+test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(" before "));
+ option.appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "script"))
+ .appendChild(document.createTextNode(" script "));
+ option.appendChild(document.createTextNode(" after "));
+ assert_equals(option.text, "before script after");
+}, "option.text should recurse into MathML script elements");
+test(function() {
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(" before "));
+ option.appendChild(document.createElementNS(null, "script"))
+ .appendChild(document.createTextNode(" script "));
+ option.appendChild(document.createTextNode(" after "));
+ assert_equals(option.text, "before script after");
+}, "option.text should recurse into null script elements");
+test(function() {
+ var option = document.createElement("option");
+ var span = option.appendChild(document.createElement("span"));
+ span.appendChild(document.createTextNode(" Some "));
+ span.appendChild(document.createElement("script"))
+ .appendChild(document.createTextNode(" script "));
+ option.appendChild(document.createTextNode(" Text "));
+ assert_equals(option.text, "Some Text");
+}, "option.text should work if a child of the option ends with a script");
+</script>
diff --git a/dom/html/test/forms/test_output_element.html b/dom/html/test/forms/test_output_element.html
new file mode 100644
index 000000000..5b4097047
--- /dev/null
+++ b/dom/html/test/forms/test_output_element.html
@@ -0,0 +1,182 @@
+<!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>
+ <script type="application/javascript" src="../reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ frameLoaded = function() {
+ is(frames['submit_frame'].location.href, "about:blank",
+ "Blank frame loaded");
+ }
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=346485">Mozilla Bug 346485</a>
+<p id="display"></p>
+<iframe name="submit_frame" onload="frameLoaded()" style="visibility: hidden;"></iframe>
+<div id="content" style="display: none">
+ <form id='f' method='get' target='submit_frame' action='foo'>
+ <input name='a' id='a'>
+ <input name='b' id='b'>
+ <output id='o' for='a b' name='output-name'>tulip</output>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 346485 **/
+
+function checkNameAttribute(element)
+{
+ is(element.name, "output-name", "Output name IDL attribute is not correct");
+ is(element.getAttribute('name'), "output-name",
+ "Output name content attribute is not correct");
+}
+
+function checkValueAndDefaultValueIDLAttribute(element)
+{
+ is(element.value, element.textContent,
+ "The value IDL attribute should act like the textContent IDL attribute");
+
+ element.value = "foo";
+ is(element.value, "foo", "Value should be 'foo'");
+
+ is(element.defaultValue, "", "Default defaultValue is ''");
+
+ element.defaultValue = "bar";
+ is(element.defaultValue, "bar", "defaultValue should be 'bar'");
+
+ // More complex situation.
+ element.textContent = 'foo';
+ var b = document.createElement('b');
+ b.textContent = 'bar'
+ element.appendChild(b);
+ is(element.value, element.textContent,
+ "The value IDL attribute should act like the textContent IDL attribute");
+}
+
+function checkValueModeFlag(element)
+{
+ /**
+ * The value mode flag is the flag used to know if value should represent the
+ * textContent or the default value.
+ */
+ // value mode flag should be 'value'
+ isnot(element.defaultValue, element.value,
+ "When value is set, defaultValue keeps its value");
+
+ var f = document.getElementById('f');
+ f.reset();
+ // value mode flag should be 'default'
+ is(element.defaultValue, element.value, "When reset, defaultValue=value");
+ is(element.textContent, element.defaultValue,
+ "textContent should contain the defaultValue");
+}
+
+function checkDescendantChanged(element)
+{
+ /**
+ * Whenever a descendant is changed if the value mode flag is value,
+ * the default value should be the textContent value.
+ */
+ element.defaultValue = 'tulip';
+ element.value = 'foo';
+
+ // set value mode flag to 'default'
+ var f = document.getElementById('f');
+ f.reset();
+
+ is(element.textContent, element.defaultValue,
+ "textContent should contain the defaultValue");
+ element.textContent = "bar";
+ is(element.textContent, element.defaultValue,
+ "textContent should contain the defaultValue");
+}
+
+function checkFormIDLAttribute(element)
+{
+ is(element.form, document.getElementById('f'),
+ "form IDL attribute is invalid");
+}
+
+function checkHtmlForIDLAttribute(element)
+{
+ is(String(element.htmlFor), 'a b',
+ "htmlFor IDL attribute should reflect the for content attribute");
+
+ // DOMTokenList is tested in another bug so we just test assignation
+ element.htmlFor.value = 'a b c';
+ is(String(element.htmlFor), 'a b c', "htmlFor should have changed");
+}
+
+function submitForm()
+{
+ // Setting the values for the submit.
+ document.getElementById('o').value = 'foo';
+ document.getElementById('a').value = 'afield';
+ document.getElementById('b').value = 'bfield';
+
+ frameLoaded = checkFormSubmission;
+
+ // This will call checkFormSubmission() which is going to call ST.finish().
+ document.getElementById('f').submit();
+}
+
+function checkFormSubmission()
+{
+ /**
+ * All elements values have been set just before the submission.
+ * The input elements values should be in the submit url but the ouput
+ * element value should not appear.
+ */
+
+ is(frames['submit_frame'].location.href,
+ 'http://mochi.test:8888/tests/dom/html/test/forms/foo?a=afield&b=bfield',
+ "The output element value should not be submitted");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ reflectString({
+ element: document.createElement("output"),
+ attribute: "name",
+ });
+
+ var o = document.getElementsByTagName('output');
+ is(o.length, 1, "There should be one output element");
+
+ o = o[0];
+ ok(o instanceof HTMLOutputElement,
+ "The output should be instance of HTMLOutputElement");
+
+ o = document.getElementById('o');
+ ok(o instanceof HTMLOutputElement,
+ "The output should be instance of HTMLOutputElement");
+
+ is(o.type, "output", "Output type IDL attribute should be 'output'");
+
+ checkNameAttribute(o);
+
+ checkValueAndDefaultValueIDLAttribute(o);
+
+ checkValueModeFlag(o);
+
+ checkDescendantChanged(o);
+
+ checkFormIDLAttribute(o);
+
+ checkHtmlForIDLAttribute(o);
+
+ submitForm();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_pattern_attribute.html b/dom/html/test/forms/test_pattern_attribute.html
new file mode 100644
index 000000000..efa1a1543
--- /dev/null
+++ b/dom/html/test/forms/test_pattern_attribute.html
@@ -0,0 +1,324 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=345512
+-->
+<head>
+ <title>Test for Bug 345512</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ input { background-color: rgb(0,0,0) !important; }
+ input:valid { background-color: rgb(0,255,0) !important; }
+ input:invalid { background-color: rgb(255,0,0) !important; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=345512">Mozilla Bug 345512</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <input id='i' pattern="tulip" oninvalid="invalidEventHandler(event);">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 345512 **/
+
+var gInvalid = false;
+
+function invalidEventHandler(e)
+{
+ is(e.type, "invalid", "Invalid event type should be invalid");
+ gInvalid = true;
+}
+
+function completeValidityCheck(element, alwaysValid, isBarred)
+{
+ // Check when pattern matches.
+ if (element.type == 'email') {
+ element.pattern = ".*@bar.com";
+ element.value = "foo@bar.com";
+ } else if (element.type == 'url') {
+ element.pattern = "http://.*\\.com$";
+ element.value = "http://mozilla.com";
+ } else if (element.type == 'file') {
+ element.pattern = "foo";
+ SpecialPowers.wrap(element).mozSetFileArray([new File(["foo"], "foo")]);
+ } else {
+ element.pattern = "foo";
+ element.value = "foo";
+ }
+
+ checkValidPattern(element, true, isBarred);
+
+ // Check when pattern does not match.
+
+ if (element.type == 'email') {
+ element.pattern = ".*@bar.com";
+ element.value = "foo@foo.com";
+ } else if (element.type == 'url') {
+ element.pattern = "http://.*\\.com$";
+ element.value = "http://mozilla.org";
+ } else if (element.type == 'file') {
+ element.pattern = "foo";
+ SpecialPowers.wrap(element).mozSetFileArray([new File(["bar"], "bar")]);
+ } else {
+ element.pattern = "foo";
+ element.value = "bar";
+ }
+
+ if (!alwaysValid) {
+ checkInvalidPattern(element, true);
+ } else {
+ checkValidPattern(element, true, isBarred);
+ }
+}
+
+function checkValidPattern(element, completeCheck, isBarred)
+{
+ if (completeCheck) {
+ gInvalid = false;
+
+ ok(!element.validity.patternMismatch,
+ "Element should not suffer from pattern mismatch");
+ ok(element.validity.valid, "Element should be valid");
+ ok(element.checkValidity(), "Element should be valid");
+ ok(!gInvalid, "Invalid event shouldn't have been thrown");
+ is(element.validationMessage, '',
+ "Validation message should be the empty string");
+ if (element.type != 'radio' && element.type != 'checkbox') {
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ isBarred ? "rgb(0, 0, 0)" : "rgb(0, 255, 0)",
+ "The pseudo-class is not correctly applied");
+ }
+ } else {
+ ok(!element.validity.patternMismatch,
+ "Element should not suffer from pattern mismatch");
+ }
+}
+
+function checkInvalidPattern(element, completeCheck)
+{
+ if (completeCheck) {
+ gInvalid = false;
+
+ ok(element.validity.patternMismatch,
+ "Element should suffer from pattern mismatch");
+ ok(!element.validity.valid, "Element should not be valid");
+ ok(!element.checkValidity(), "Element should not be valid");
+ ok(gInvalid, "Invalid event should have been thrown");
+ is(element.validationMessage,
+ "Please match the requested format.",
+ "Validation message is not valid");
+ } else {
+ ok(element.validity.patternMismatch,
+ "Element should suffer from pattern mismatch");
+ }
+
+ if (element.type != 'radio' && element.type != 'checkbox') {
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(255, 0, 0)", ":invalid pseudo-class should apply");
+ }
+}
+
+function checkSyntaxError(element)
+{
+ ok(!element.validity.patternMismatch,
+ "On SyntaxError, element should not suffer");
+}
+
+function checkPatternValidity(element)
+{
+ element.pattern = "foo";
+
+ element.value = '';
+ checkValidPattern(element);
+
+ element.value = "foo";
+ checkValidPattern(element);
+
+ element.value = "bar";
+ checkInvalidPattern(element);
+
+ element.value = "foobar";
+ checkInvalidPattern(element);
+
+ element.value = "foofoo";
+ checkInvalidPattern(element);
+
+ element.pattern = "foo\"bar";
+ element.value = "foo\"bar";
+ checkValidPattern(element);
+
+ element.value = 'foo"bar';
+ checkValidPattern(element);
+
+ element.pattern = "foo'bar";
+ element.value = "foo\'bar";
+ checkValidPattern(element);
+
+ element.pattern = "foo\\(bar";
+ element.value = "foo(bar";
+ checkValidPattern(element);
+
+ element.value = "foo";
+ checkInvalidPattern(element);
+
+ element.pattern = "foo\\)bar";
+ element.value = "foo)bar";
+ checkValidPattern(element);
+
+ element.value = "foo";
+ checkInvalidPattern(element);
+
+ // Check for 'i' flag disabled. Should be case sensitive.
+ element.value = "Foo";
+ checkInvalidPattern(element);
+
+ // We can't check for the 'g' flag because we only test, we don't execute.
+ // We can't check for the 'm' flag because .value shouldn't contain line breaks.
+
+ // We need '\\\\' because '\\' will produce '\\' and we want to escape the '\'
+ // for the regexp.
+ element.pattern = "foo\\\\bar";
+ element.value = "foo\\bar";
+ checkValidPattern(element);
+
+ // We may want to escape the ' in the pattern, but this is a SyntaxError
+ // when unicode flag is set.
+ element.pattern = "foo\\'bar";
+ element.value = "foo'bar";
+ checkSyntaxError(element);
+ element.value = "baz";
+ checkSyntaxError(element);
+
+ // We should check the pattern attribute do not pollute |RegExp.lastParen|.
+ is(RegExp.lastParen, "", "RegExp.lastParen should be the empty string");
+
+ element.pattern = "(foo)";
+ element.value = "foo";
+ checkValidPattern(element);
+ is(RegExp.lastParen, "", "RegExp.lastParen should be the empty string");
+
+ // That may sound weird but the empty string is a valid pattern value.
+ element.pattern = "";
+ element.value = "";
+ checkValidPattern(element);
+
+ element.value = "foo";
+ checkInvalidPattern(element);
+
+ // Checking some complex patterns. As we are using js regexp mechanism, these
+ // tests doesn't aim to test the regexp mechanism.
+ element.pattern = "\\d{2}\\s\\d{2}\\s\\d{4}"
+ element.value = "01 01 2010"
+ checkValidPattern(element);
+
+ element.value = "01/01/2010"
+ checkInvalidPattern(element);
+
+ element.pattern = "[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z_+])*@([0-9a-zA-Z][-\\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9}";
+ element.value = "foo@bar.com";
+ checkValidPattern(element);
+
+ element.value = "...@bar.com";
+ checkInvalidPattern(element);
+
+ element.pattern = "^(?:\\w{3,})$";
+ element.value = "foo";
+ checkValidPattern(element);
+
+ element.value = "f";
+ checkInvalidPattern(element);
+
+ // If @title is specified, it should be added in the validation message.
+ if (element.type == 'email') {
+ element.pattern = "foo@bar.com"
+ element.value = "bar@foo.com";
+ } else if (element.type == 'url') {
+ element.pattern = "http://mozilla.com";
+ element.value = "http://mozilla.org";
+ } else {
+ element.pattern = "foo";
+ element.value = "bar";
+ }
+ element.title = "this is an explanation of the regexp";
+ is(element.validationMessage,
+ "Please match the requested format: " + element.title + ".",
+ "Validation message is not valid");
+ element.title = "";
+ is(element.validationMessage,
+ "Please match the requested format.",
+ "Validation message is not valid");
+
+ element.pattern = "foo";
+ if (element.type == 'email') {
+ element.value = "bar@foo.com";
+ } else if (element.type == 'url') {
+ element.value = "http://mozilla.org";
+ } else {
+ element.value = "bar";
+ }
+ checkInvalidPattern(element);
+
+ element.removeAttribute('pattern');
+ checkValidPattern(element, true);
+
+ // Unicode pattern
+ for (var pattern of ["\\u{1F438}{2}", "\u{1F438}{2}",
+ "\\uD83D\\uDC38{2}", "\uD83D\uDC38{2}",
+ "\u{D83D}\u{DC38}{2}"]) {
+ element.pattern = pattern;
+
+ element.value = "\u{1F438}\u{1F438}";
+ checkValidPattern(element);
+
+ element.value = "\uD83D\uDC38\uD83D\uDC38";
+ checkValidPattern(element);
+
+ element.value = "\uD83D\uDC38\uDC38";
+ checkInvalidPattern(element);
+ }
+
+ element.pattern = "\\u{D83D}\\u{DC38}{2}";
+
+ element.value = "\u{1F438}\u{1F438}";
+ checkInvalidPattern(element);
+
+ element.value = "\uD83D\uDC38\uD83D\uDC38";
+ checkInvalidPattern(element);
+
+ element.value = "\uD83D\uDC38\uDC38";
+ checkInvalidPattern(element);
+}
+
+var input = document.getElementById('i');
+
+// |validTypes| are the types which accept @pattern
+// and |invalidTypes| are the ones which do not accept it.
+var validTypes = Array('text', 'password', 'search', 'tel', 'email', 'url');
+var barredTypes = Array('hidden', 'reset', 'button');
+var invalidTypes = Array('checkbox', 'radio', 'file', 'number', 'range', 'date',
+ 'time', 'color', 'submit', 'image', 'month', 'week',
+ 'datetime-local');
+
+for (type of validTypes) {
+ input.type = type;
+ completeValidityCheck(input, false);
+ checkPatternValidity(input);
+}
+
+for (type of barredTypes) {
+ input.type = type;
+ completeValidityCheck(input, true, true);
+}
+
+for (type of invalidTypes) {
+ input.type = type;
+ completeValidityCheck(input, true);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_progress_element.html b/dom/html/test/forms/test_progress_element.html
new file mode 100644
index 000000000..bb1f801b2
--- /dev/null
+++ b/dom/html/test/forms/test_progress_element.html
@@ -0,0 +1,314 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=514437
+https://bugzilla.mozilla.org/show_bug.cgi?id=633913
+-->
+<head>
+ <title>Test for progress element content and layout</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=514437">Mozilla Bug 514437</a>
+and
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=633913">Mozilla Bug 633913</a>
+<p id="display"></p>
+<iframe name="submit_frame" style="visibility: hidden;"></iframe>
+<div id="content" style="visibility: hidden;">
+ <form id='f' method='get' target='submit_frame' action='foo'>
+ <progress id='p'></progress>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.expectAssertions(0, 1);
+
+/** Test for progress element content and layout **/
+
+function checkFormIDLAttribute(aElement)
+{
+ is("form" in aElement, false, "<progress> shouldn't have a form attribute");
+}
+
+function checkAttribute(aElement, aAttribute, aNewValue, aExpectedValueForIDL)
+{
+ var expectedValueForIDL = aNewValue;
+ var expectedValueForContent = String(aNewValue);
+
+ if (aExpectedValueForIDL !== undefined) {
+ expectedValueForIDL = aExpectedValueForIDL;
+ }
+
+ if (aNewValue != null) {
+ aElement.setAttribute(aAttribute, aNewValue);
+ is(aElement.getAttribute(aAttribute), expectedValueForContent,
+ aAttribute + " content attribute should be " + expectedValueForContent);
+ is(aElement[aAttribute], expectedValueForIDL,
+ aAttribute + " IDL attribute should be " + expectedValueForIDL);
+
+ if (parseFloat(aNewValue) == aNewValue) {
+ aElement[aAttribute] = aNewValue;
+ is(aElement.getAttribute(aAttribute), expectedValueForContent,
+ aAttribute + " content attribute should be " + expectedValueForContent);
+ is(aElement[aAttribute], parseFloat(expectedValueForIDL),
+ aAttribute + " IDL attribute should be " + parseFloat(expectedValueForIDL));
+ }
+ } else {
+ aElement.removeAttribute(aAttribute);
+ is(aElement.getAttribute(aAttribute), null,
+ aAttribute + " content attribute should be null");
+ is(aElement[aAttribute], expectedValueForIDL,
+ aAttribute + " IDL attribute should be " + expectedValueForIDL);
+ }
+}
+
+function checkValueAttribute()
+{
+ var tests = [
+ // value has to be a valid float, its default value is 0.0 otherwise.
+ [ null, 0.0 ],
+ [ 'fo', 0.0 ],
+ // If value < 0.0, 0.0 is used instead.
+ [ -1.0, 0.0 ],
+ // If value >= max, max is used instead (max default value is 1.0).
+ [ 2.0, 1.0 ],
+ [ 1.0, 0.5, 0.5 ],
+ [ 10.0, 5.0, 5.0 ],
+ [ 13.37, 13.37, 42.0 ],
+ // Regular reflection.
+ [ 0.0 ],
+ [ 0.5 ],
+ [ 1.0 ],
+ // Check double-precision value.
+ [ 0.234567898765432 ],
+ ];
+
+ var element = document.createElement('progress');
+
+ for (var test of tests) {
+ if (test[2]) {
+ element.setAttribute('max', test[2]);
+ }
+
+ checkAttribute(element, 'value', test[0], test[1]);
+
+ element.removeAttribute('max');
+ }
+}
+
+function checkMaxAttribute()
+{
+ var tests = [
+ // max default value is 1.0.
+ [ null, 1.0 ],
+ // If value <= 0.0, 1.0 is used instead.
+ [ 0.0, 1.0 ],
+ [ -1.0, 1.0 ],
+ // Regular reflection.
+ [ 0.5 ],
+ [ 1.0 ],
+ [ 2.0 ],
+ // Check double-precision value.
+ [ 0.234567898765432 ],
+ ];
+
+ var element = document.createElement('progress');
+
+ for (var test of tests) {
+ checkAttribute(element, 'max', test[0], test[1]);
+ }
+}
+
+function checkPositionAttribute()
+{
+ function checkPositionValue(aElement, aValue, aMax, aExpected) {
+ if (aValue != null) {
+ aElement.setAttribute('value', aValue);
+ } else {
+ aElement.removeAttribute('value');
+ }
+
+ if (aMax != null) {
+ aElement.setAttribute('max', aMax);
+ } else {
+ aElement.removeAttribute('max');
+ }
+
+ is(aElement.position, aExpected, "position IDL attribute should be " + aExpected);
+ }
+
+ var tests = [
+ // value has to be defined (indeterminate state).
+ [ null, null, -1.0 ],
+ [ null, 1.0, -1.0 ],
+ // value has to be defined to a valid float (indeterminate state).
+ [ 'foo', 1.0, -1.0 ],
+ // If value < 0.0, 0.0 is used instead.
+ [ -1.0, 1.0, 0.0 ],
+ // If value >= max, max is used instead.
+ [ 2.0, 1.0, 1.0 ],
+ // If max isn't present, max is set to 1.0.
+ [ 1.0, null, 1.0 ],
+ // If max isn't a valid float, max is set to 1.0.
+ [ 1.0, 'foo', 1.0 ],
+ // If max isn't > 0, max is set to 1.0.
+ [ 1.0, -1.0, 1.0 ],
+ // A few simple and valid values.
+ [ 0.0, 1.0, 0.0 ],
+ [ 0.1, 1.0, 0.1/1.0 ],
+ [ 0.1, 2.0, 0.1/2.0 ],
+ [ 10, 50, 10/50 ],
+ // Values implying .position is a double.
+ [ 1.0, 3.0, 1.0/3.0 ],
+ [ 0.1, 0.7, 0.1/0.7 ],
+ ];
+
+ var element = document.createElement('progress');
+
+ for (var test of tests) {
+ checkPositionValue(element, test[0], test[1], test[2], test[3]);
+ }
+}
+
+function checkIndeterminatePseudoClass()
+{
+ function checkIndeterminate(aElement, aValue, aMax, aIndeterminate) {
+ if (aValue != null) {
+ aElement.setAttribute('value', aValue);
+ } else {
+ aElement.removeAttribute('value');
+ }
+
+ if (aMax != null) {
+ aElement.setAttribute('max', aMax);
+ } else {
+ aElement.removeAttribute('max');
+ }
+
+ is(aElement.matches("progress:indeterminate"), aIndeterminate,
+ "<progress> indeterminate state should be " + aIndeterminate);
+ }
+
+ var tests = [
+ // Indeterminate state: (value is undefined, or not a float)
+ // value has to be defined (indeterminate state).
+ [ null, null, true ],
+ [ null, 1.0, true ],
+ [ 'foo', 1.0, true ],
+ // Determined state:
+ [ -1.0, 1.0, false ],
+ [ 2.0, 1.0, false ],
+ [ 1.0, null, false ],
+ [ 1.0, 'foo', false ],
+ [ 1.0, -1.0, false ],
+ [ 0.0, 1.0, false ],
+ ];
+
+ var element = document.createElement('progress');
+
+ for (var test of tests) {
+ checkIndeterminate(element, test[0], test[1], test[2]);
+ }
+}
+
+function checkFormListedElement(aElement)
+{
+ is(document.forms[0].elements.length, 0, "the form should have no element");
+}
+
+function checkLabelable(aElement)
+{
+ var content = document.getElementById('content');
+ var label = document.createElement('label');
+
+ content.appendChild(label);
+ label.appendChild(aElement);
+ is(label.control, aElement, "progress should be labelable");
+
+ // Cleaning-up.
+ content.removeChild(label);
+ content.appendChild(aElement);
+}
+
+function checkNotResetableAndFormSubmission(aElement)
+{
+ // Creating an input element to check the submission worked.
+ var form = document.forms[0];
+ var input = document.createElement('input');
+
+ input.name = 'a';
+ input.value = 'tulip';
+ form.appendChild(input);
+
+ // Setting values.
+ aElement.value = 42.0;
+ aElement.max = 100.0;
+
+ document.getElementsByName('submit_frame')[0].addEventListener("load", function() {
+ document.getElementsByName('submit_frame')[0].removeEventListener("load", arguments.callee, false);
+
+ /**
+ * All elements values have been set just before the submission.
+ * The input element value should be in the submit url but the progress
+ * element value should not appear.
+ */
+ is(frames['submit_frame'].location.href,
+ 'http://mochi.test:8888/tests/dom/html/test/forms/foo?a=tulip',
+ "The progress element value should not be submitted");
+
+ checkNotResetable();
+ }, false);
+
+ form.submit();
+}
+
+function checkNotResetable()
+{
+ // Try to reset the form.
+ var form = document.forms[0];
+ var element = document.getElementById('p');
+
+ element.value = 3.0;
+ element.max = 42.0;
+
+ form.reset();
+
+ SimpleTest.executeSoon(function() {
+ is(element.value, 3.0, "progress.value should not have changed");
+ is(element.max, 42.0, "progress.max should not have changed");
+
+ SimpleTest.finish();
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+
+var p = document.getElementById('p');
+
+ok(p instanceof HTMLProgressElement,
+ "The progress element should be instance of HTMLProgressElement");
+is(p.constructor, HTMLProgressElement,
+ "The progress element constructor should be HTMLProgressElement");
+
+checkFormIDLAttribute(p);
+
+checkValueAttribute();
+
+checkMaxAttribute();
+
+checkPositionAttribute();
+
+checkIndeterminatePseudoClass();
+
+checkFormListedElement(p);
+
+checkLabelable(p);
+
+checkNotResetableAndFormSubmission(p);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_radio_in_label.html b/dom/html/test/forms/test_radio_in_label.html
new file mode 100644
index 000000000..1676c9819
--- /dev/null
+++ b/dom/html/test/forms/test_radio_in_label.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=229925
+-->
+<head>
+ <title>Test for Bug 229925</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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=229925">Mozilla Bug 229925</a>
+<p id="display"></p>
+<form>
+ <label>
+ <span id="s1">LABEL</span>
+ <input type="radio" name="rdo" value="1" id="r1" onmousedown="document.body.appendChild(document.createTextNode('down'));">
+ <input type="radio" name="rdo" value="2" id="r2" checked="checked">
+ </label>
+</form>
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 229925 **/
+SimpleTest.waitForExplicitFinish();
+var r1 = document.getElementById("r1");
+var r2 = document.getElementById("r2");
+var s1 = document.getElementById("s1");
+SpecialPowers.pushPrefEnv({"set":[["snav.enabled", false]]}, startTest);
+function startTest() {
+ r1.click();
+ ok(r1.checked,
+ "The first radio input element should be checked by clicking the element");
+ r2.click();
+ ok(r2.checked,
+ "The second radio input element should be checked by clicking the element");
+ s1.click();
+ ok(r1.checked,
+ "The first radio input element should be checked by clicking other element");
+
+ r1.focus();
+ synthesizeKey("VK_LEFT", {});
+ ok(r2.checked,
+ "The second radio input element should be checked by key");
+ synthesizeKey("VK_LEFT", {});
+ ok(r1.checked,
+ "The first radio input element should be checked by key");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_radio_radionodelist.html b/dom/html/test/forms/test_radio_radionodelist.html
new file mode 100644
index 000000000..7f915bba9
--- /dev/null
+++ b/dom/html/test/forms/test_radio_radionodelist.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=779723
+-->
+<head>
+ <title>Test for Bug 779723</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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=779723">Mozilla Bug 779723</a>
+<p id="display"></p>
+<form>
+ <input type="checkbox" name="rdo" value="0" id="r0" checked="checked">
+ <input type="radio" name="rdo" id="r1">
+ <input type="radio" name="rdo" id="r2" value="2">
+</form>
+<script class="testbody" type="text/javascript">
+/** Test for Bug 779723 **/
+
+var rdoList = document.forms[0].elements.namedItem('rdo');
+is(rdoList.value, "", "The value attribute should be empty");
+
+document.getElementById('r2').checked = true;
+ok(rdoList.value, "2", "The value attribute should be 2");
+
+document.getElementById('r1').checked = true;
+ok(rdoList.value, "on", "The value attribute should be on");
+
+document.getElementById('r1').value = 1;
+ok(rdoList.value, "1", "The value attribute should be 1");
+
+is(rdoList.value, document.getElementById('r1').value,
+ "The value attribute should be equal to the first checked radio input element's value");
+ok(!document.getElementById('r2').checked,
+ "The second radio input element should not be checked");
+
+rdoList.value = '2';
+is(rdoList.value, document.getElementById('r2').value,
+ "The value attribute should be equal to the second radio input element's value");
+ok(document.getElementById('r2').checked,
+ "The second radio input element should be checked");
+
+rdoList.value = '3';
+is(rdoList.value, document.getElementById('r2').value,
+ "The value attribute should be the second radio input element's value");
+ok(document.getElementById('r2').checked,
+ "The second radio input element should be checked");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/forms/test_reportValidation_preventDefault.html b/dom/html/test/forms/test_reportValidation_preventDefault.html
new file mode 100644
index 000000000..034bb2ed2
--- /dev/null
+++ b/dom/html/test/forms/test_reportValidation_preventDefault.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1088761
+-->
+<head>
+ <title>Test for Bug 1088761</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ input, textarea, fieldset, button, select, keygen, output, object { background-color: rgb(0,0,0) !important; }
+ :valid { background-color: rgb(0,255,0) !important; }
+ :invalid { background-color: rgb(255,0,0) !important; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1088761">Mozilla Bug 1088761</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <fieldset id='f' oninvalid="invalidEventHandler(event, true);"></fieldset>
+ <input id='i' required oninvalid="invalidEventHandler(event, true);">
+ <button id='b' oninvalid="invalidEventHandler(event, true);"></button>
+ <select id='s' required oninvalid="invalidEventHandler(event, true);"></select>
+ <textarea id='t' required oninvalid="invalidEventHandler(event, true);"></textarea>
+ <output id='o' oninvalid="invalidEventHandler(event, true);"></output>
+ <keygen id='k' oninvalid="invalidEventHandler(event, true);"></keygen>
+ <object id='obj' oninvalid="invalidEventHandler(event, true);"></object>
+</div>
+<div id="content2" style="display: none">
+ <fieldset id='f2' oninvalid="invalidEventHandler(event, false);"></fieldset>
+ <input id='i2' required oninvalid="invalidEventHandler(event, false);">
+ <button id='b2' oninvalid="invalidEventHandler(event, false);"></button>
+ <select id='s2' required oninvalid="invalidEventHandler(event, false);"></select>
+ <textarea id='t2' required oninvalid="invalidEventHandler(event, false);"></textarea>
+ <output id='o2' oninvalid="invalidEventHandler(event, false);"></output>
+ <keygen id='k2' oninvalid="invalidEventHandler(event, false);"></keygen>
+ <object id='obj2' oninvalid="invalidEventHandler(event, false);"></object>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1088761 **/
+
+var gInvalid = false;
+
+function invalidEventHandler(aEvent, isPreventDefault)
+{
+ if (isPreventDefault) {
+ aEvent.preventDefault();
+ }
+
+ is(aEvent.type, "invalid", "Invalid event type should be invalid");
+ ok(!aEvent.bubbles, "Invalid event should not bubble");
+ ok(aEvent.cancelable, "Invalid event should be cancelable");
+ gInvalid = true;
+}
+
+function checkReportValidityForInvalid(element)
+{
+ gInvalid = false;
+ ok(!element.reportValidity(), "reportValidity() should return false when the element is not valid");
+ ok(gInvalid, "Invalid event should have been handled");
+}
+
+function checkReportValidityForValid(element)
+{
+ gInvalid = false;
+ ok(element.reportValidity(), "reportValidity() should return true when the element is valid");
+ ok(!gInvalid, "Invalid event shouldn't have been handled");
+}
+
+checkReportValidityForInvalid(document.getElementById('i'));
+checkReportValidityForInvalid(document.getElementById('s'));
+checkReportValidityForInvalid(document.getElementById('t'));
+
+checkReportValidityForInvalid(document.getElementById('i2'));
+checkReportValidityForInvalid(document.getElementById('s2'));
+checkReportValidityForInvalid(document.getElementById('t2'));
+
+checkReportValidityForValid(document.getElementById('o'));
+checkReportValidityForValid(document.getElementById('k'));
+checkReportValidityForValid(document.getElementById('obj'));
+checkReportValidityForValid(document.getElementById('f'));
+
+checkReportValidityForValid(document.getElementById('o2'));
+checkReportValidityForValid(document.getElementById('k2'));
+checkReportValidityForValid(document.getElementById('obj2'));
+checkReportValidityForValid(document.getElementById('f2'));
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_required_attribute.html b/dom/html/test/forms/test_required_attribute.html
new file mode 100644
index 000000000..1f9d76cf1
--- /dev/null
+++ b/dom/html/test/forms/test_required_attribute.html
@@ -0,0 +1,382 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=345822
+-->
+<head>
+ <title>Test for Bug 345822</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=345822">Mozilla Bug 345822</a>
+<p id="display"></p>
+<div id="content">
+ <form>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 345822 **/
+
+function checkNotSufferingFromBeingMissing(element, doNotApply)
+{
+ ok(!element.validity.valueMissing,
+ "Element should not suffer from value missing");
+ ok(element.validity.valid, "Element should be valid");
+ ok(element.checkValidity(), "Element should be valid");
+ is(element.validationMessage, "",
+ "Validation message should be the empty string");
+
+ if (doNotApply) {
+ ok(!element.matches(':valid'), ":valid should not apply");
+ ok(!element.matches(':invalid'), ":invalid should not apply");
+ ok(!element.matches(':-moz-ui-valid'), ":-moz-ui-valid should not apply");
+ ok(!element.matches(':-moz-ui-invalid'), ":-moz-ui-invalid should not apply");
+ } else {
+ ok(element.matches(':valid'), ":valid should apply");
+ ok(!element.matches(':invalid'), ":invalid should not apply");
+ ok(element.matches(':-moz-ui-valid'), ":-moz-ui-valid should apply");
+ ok(!element.matches(':-moz-ui-invalid'), ":-moz-ui-invalid should not apply");
+ }
+}
+
+function checkSufferingFromBeingMissing(element, hasMozUIInvalid)
+{
+ ok(element.validity.valueMissing, "Element should suffer from value missing");
+ ok(!element.validity.valid, "Element should not be valid");
+ ok(!element.checkValidity(), "Element should not be valid");
+
+ if (element.type == 'checkbox')
+ {
+ is(element.validationMessage,
+ "Please check this box if you want to proceed.",
+ "Validation message is wrong");
+ }
+ else if (element.type == 'radio')
+ {
+ is(element.validationMessage,
+ "Please select one of these options.",
+ "Validation message is wrong");
+ }
+ else if (element.type == 'file')
+ {
+ is(element.validationMessage,
+ "Please select a file.",
+ "Validation message is wrong");
+ }
+ else if (element.type == 'number')
+ {
+ is(element.validationMessage,
+ "Please enter a number.",
+ "Validation message is wrong");
+ }
+ else // text fields
+ {
+ is(element.validationMessage,
+ "Please fill out this field.",
+ "Validation message is wrong");
+ }
+
+ ok(!element.matches(':valid'), ":valid should apply");
+ ok(element.matches(':invalid'), ":invalid should not apply");
+ ok(!element.matches(':-moz-ui-valid'), ":-moz-ui-valid should not apply");
+ is(element.matches(':-moz-ui-invalid'), hasMozUIInvalid, ":-moz-ui-invalid expected state is " + hasMozUIInvalid);
+}
+
+function checkTextareaRequiredValidity()
+{
+ var element = document.createElement('textarea');
+ document.forms[0].appendChild(element);
+
+ SpecialPowers.wrap(element).value = '';
+ element.required = false;
+ checkNotSufferingFromBeingMissing(element);
+
+ element.required = true;
+ checkSufferingFromBeingMissing(element, true);
+
+ element.readOnly = true;
+ checkNotSufferingFromBeingMissing(element, true);
+
+ element.readOnly = false;
+ checkSufferingFromBeingMissing(element, true);
+
+ SpecialPowers.wrap(element).value = 'foo';
+ checkNotSufferingFromBeingMissing(element);
+
+ SpecialPowers.wrap(element).value = '';
+ checkSufferingFromBeingMissing(element, true);
+
+ element.required = false;
+ checkNotSufferingFromBeingMissing(element);
+
+ element.focus();
+ element.required = true;
+ SpecialPowers.wrap(element).value = 'foobar';
+ element.blur();
+ element.form.reset();
+ checkSufferingFromBeingMissing(element, false);
+
+ // TODO: for the moment, a textarea outside of a document is mutable.
+ SpecialPowers.wrap(element).value = ''; // To make -moz-ui-valid apply.
+ element.required = false;
+ document.forms[0].removeChild(element);
+ checkNotSufferingFromBeingMissing(element);
+}
+
+function checkInputRequiredNotApply(type, isBarred)
+{
+ var element = document.createElement('input');
+ element.type = type;
+ document.forms[0].appendChild(element);
+
+ SpecialPowers.wrap(element).value = '';
+ element.required = false;
+ checkNotSufferingFromBeingMissing(element, isBarred);
+
+ element.required = true;
+ checkNotSufferingFromBeingMissing(element, isBarred);
+
+ element.required = false;
+
+ document.forms[0].removeChild(element);
+ checkNotSufferingFromBeingMissing(element, isBarred);
+}
+
+function checkInputRequiredValidity(type)
+{
+ var element = document.createElement('input');
+ element.type = type;
+ document.forms[0].appendChild(element);
+
+ SpecialPowers.wrap(element).value = '';
+ element.required = false;
+ checkNotSufferingFromBeingMissing(element);
+
+ element.required = true;
+ checkSufferingFromBeingMissing(element, true);
+
+ element.readOnly = true;
+ checkNotSufferingFromBeingMissing(element, true);
+
+ element.readOnly = false;
+ checkSufferingFromBeingMissing(element, true);
+
+ if (element.type == 'email') {
+ SpecialPowers.wrap(element).value = 'foo@bar.com';
+ } else if (element.type == 'url') {
+ SpecialPowers.wrap(element).value = 'http://mozilla.org/';
+ } else if (element.type == 'number') {
+ SpecialPowers.wrap(element).value = '42';
+ } else if (element.type == 'date') {
+ SpecialPowers.wrap(element).value = '2010-10-10';
+ } else if (element.type == 'time') {
+ SpecialPowers.wrap(element).value = '21:21';
+ } else if (element.type = 'month') {
+ SpecialPowers.wrap(element).value = '2010-10';
+ } else {
+ SpecialPowers.wrap(element).value = 'foo';
+ }
+ checkNotSufferingFromBeingMissing(element);
+
+ SpecialPowers.wrap(element).value = '';
+ checkSufferingFromBeingMissing(element, true);
+
+ element.focus();
+ element.required = true;
+ SpecialPowers.wrap(element).value = 'foobar';
+ element.blur();
+ element.form.reset();
+ checkSufferingFromBeingMissing(element, false);
+
+ element.required = true;
+ SpecialPowers.wrap(element).value = ''; // To make :-moz-ui-valid apply.
+ checkSufferingFromBeingMissing(element, true);
+ document.forms[0].removeChild(element);
+ // Removing the child changes nothing about whether it's valid
+ checkSufferingFromBeingMissing(element, true);
+}
+
+function checkInputRequiredValidityForCheckbox()
+{
+ var element = document.createElement('input');
+ element.type = 'checkbox';
+ document.forms[0].appendChild(element);
+
+ element.checked = false;
+ element.required = false;
+ checkNotSufferingFromBeingMissing(element);
+
+ element.required = true;
+ checkSufferingFromBeingMissing(element, true);
+
+ element.checked = true;
+ checkNotSufferingFromBeingMissing(element);
+
+ element.checked = false;
+ checkSufferingFromBeingMissing(element, true);
+
+ element.required = false;
+ checkNotSufferingFromBeingMissing(element);
+
+ element.focus();
+ element.required = true;
+ element.checked = true;
+ element.blur();
+ element.form.reset();
+ checkSufferingFromBeingMissing(element, false);
+
+ element.required = true;
+ element.checked = false;
+ document.forms[0].removeChild(element);
+ checkSufferingFromBeingMissing(element, true);
+}
+
+function checkInputRequiredValidityForRadio()
+{
+ var element = document.createElement('input');
+ element.type = 'radio';
+ element.name = 'test'
+ document.forms[0].appendChild(element);
+
+ element.checked = false;
+ element.required = false;
+ checkNotSufferingFromBeingMissing(element);
+
+ element.required = true;
+ checkSufferingFromBeingMissing(element, true);
+
+ element.checked = true;
+ checkNotSufferingFromBeingMissing(element);
+
+ element.checked = false;
+ checkSufferingFromBeingMissing(element, true);
+
+ // A required radio button should not suffer from value missing if another
+ // radio button from the same group is checked.
+ var element2 = document.createElement('input');
+ element2.type = 'radio';
+ element2.name = 'test';
+
+ element2.checked = true;
+ element2.required = false;
+ document.forms[0].appendChild(element2);
+
+ // Adding a checked radio should make required radio in the group not
+ // suffering from being missing.
+ checkNotSufferingFromBeingMissing(element);
+
+ element.checked = false;
+ element2.checked = false;
+ checkSufferingFromBeingMissing(element, true);
+
+ // The other radio button should not be disabled.
+ // A disabled checked radio button in the radio group
+ // is enough to not suffer from value missing.
+ element2.checked = true;
+ element2.disabled = true;
+ checkNotSufferingFromBeingMissing(element);
+
+ // If a radio button is not required but another radio button is required in
+ // the same group, the not required radio button should suffer from value
+ // missing.
+ element2.disabled = false;
+ element2.checked = false;
+ element.required = false;
+ element2.required = true;
+ checkSufferingFromBeingMissing(element, true);
+ checkSufferingFromBeingMissing(element2, true);
+
+ element.checked = true;
+ checkNotSufferingFromBeingMissing(element2);
+
+ // The checked radio is not in the group anymore, element2 should be invalid.
+ element.form.removeChild(element);
+ checkNotSufferingFromBeingMissing(element);
+ checkSufferingFromBeingMissing(element2, true);
+
+ element2.focus();
+ element2.required = true;
+ element2.checked = true;
+ element2.blur();
+ element2.form.reset();
+ checkSufferingFromBeingMissing(element2, false);
+
+ element2.required = true;
+ element2.checked = false;
+ document.forms[0].removeChild(element2);
+ checkSufferingFromBeingMissing(element2, true);
+}
+
+function checkInputRequiredValidityForFile()
+{
+ var element = document.createElement('input');
+ element.type = 'file'
+ document.forms[0].appendChild(element);
+
+ var file = new File([""], "345822_file");
+
+ SpecialPowers.wrap(element).value = "";
+ element.required = false;
+ checkNotSufferingFromBeingMissing(element);
+
+ element.required = true;
+ checkSufferingFromBeingMissing(element, true);
+
+ SpecialPowers.wrap(element).mozSetFileArray([file]);
+ checkNotSufferingFromBeingMissing(element);
+
+ SpecialPowers.wrap(element).value = "";
+ checkSufferingFromBeingMissing(element, true);
+
+ element.required = false;
+ checkNotSufferingFromBeingMissing(element);
+
+ element.focus();
+ SpecialPowers.wrap(element).mozSetFileArray([file]);
+ element.required = true;
+ element.blur();
+ element.form.reset();
+ checkSufferingFromBeingMissing(element, false);
+
+ element.required = true;
+ SpecialPowers.wrap(element).value = '';
+ document.forms[0].removeChild(element);
+ checkSufferingFromBeingMissing(element, true);
+}
+
+checkTextareaRequiredValidity();
+
+// The require attribute behavior depend of the input type.
+// First of all, checks for types that make the element barred from
+// constraint validation.
+var typeBarredFromConstraintValidation = ["hidden", "button", "reset"];
+for (type of typeBarredFromConstraintValidation) {
+ checkInputRequiredNotApply(type, true);
+}
+
+// Then, checks for the types which do not use the required attribute.
+var typeRequireNotApply = ['range', 'color', 'submit', 'image'];
+for (type of typeRequireNotApply) {
+ checkInputRequiredNotApply(type, false);
+}
+
+// Now, checking for all types which accept the required attribute.
+var typeRequireApply = ["text", "password", "search", "tel", "email", "url",
+ "number", "date", "time", "month", "week",
+ "datetime-local"];
+
+for (type of typeRequireApply) {
+ checkInputRequiredValidity(type);
+}
+
+checkInputRequiredValidityForCheckbox();
+checkInputRequiredValidityForRadio();
+checkInputRequiredValidityForFile();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_restore_form_elements.html b/dom/html/test/forms/test_restore_form_elements.html
new file mode 100644
index 000000000..d92c5c7e9
--- /dev/null
+++ b/dom/html/test/forms/test_restore_form_elements.html
@@ -0,0 +1,174 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=737851
+-->
+<head>
+ <meta charset="utf-8">
+
+ <title>Test for Bug 737851</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=737851">Mozilla Bug 737851</a>
+
+<p id="display"></p>
+
+
+<div id="content">
+
+ <iframe id="frame" width="800px" height="600px" src='data:text/html,
+ <html>
+ <body style="display:none;">
+
+ <h3>Checking persistence of inputs through js inserts and moves</h3>
+ <div id="test">
+ <input id="a"/>
+ <input id="b"/>
+ <form id="form1">
+ <input id="c"/>
+ <input id="d"/>
+ </form>
+ <form id="form2">
+ <input id="radio1" type="radio" name="radio"/>
+ <input type="radio" name="radio"/>
+ <input type="radio" name="radio"/>
+ <input type="radio" name="radio"/>
+ </form>
+ <input id="e"/>
+ </div>
+
+ <h3>Bug 728798: checking persistence of inputs when forward-using @form</h3>
+ <div>
+ <input id="728798-a" form="728798-form" name="a"/>
+ <form id="728798-form">
+ <input id="728798-b" form="728798-form" name="b"/>
+ <input id="728798-c" name="c"/>
+ </form>
+ <input id="728798-d" form="728798-form" name="d"/>
+ </div>
+
+ </body>
+ </html>
+ '></iframe>
+
+</div>
+
+
+<pre id="test">
+<script type="text/javascript">
+
+var frameElem = document.getElementById("frame");
+var frame = frameElem.contentWindow;
+
+
+/* -- Main test run -- */
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ shuffle();
+ fill();
+ frameElem.addEventListener("load", function() {
+ shuffle();
+ checkAllFields();
+ SimpleTest.finish();
+ });
+ frame.location.reload();
+})
+
+
+/* -- Input fields js changes and moves -- */
+
+function shuffle() {
+ var framedoc = frame.document;
+
+ // Insert a button (toplevel)
+ var btn = framedoc.createElement("button");
+ var testdiv = framedoc.getElementById("test");
+ testdiv.insertBefore(btn, framedoc.getElementById("b"));
+
+ // Insert a dynamically generated input (in a form)
+ var newInput = framedoc.createElement("input");
+ newInput.setAttribute("id","c0");
+ var form1 = framedoc.getElementById("form1");
+ form1.insertBefore(newInput, form1.firstChild);
+
+ // Move an input around
+ var inputD = framedoc.getElementById("d");
+ var form2 = framedoc.getElementById("form2");
+ form2.insertBefore(inputD, form2.firstChild)
+
+ // Clone an existing input
+ var inputE2 = framedoc.getElementById("e").cloneNode(true);
+ inputE2.setAttribute("id","e2");
+ testdiv.appendChild(inputE2);
+}
+
+
+/* -- Input fields fill & check -- */
+
+/* Values entered in the input fields (by id) */
+
+var fieldValues = {
+ 'a':'simple input',
+ 'b':'moved by inserting a button before (no form)',
+ 'c0':'dynamically generated input',
+ 'c':'moved by inserting an input before (in a form)',
+ 'd':'moved from a form to another',
+ 'e':'the original',
+ 'e2':'the clone',
+ '728798-a':'before the form',
+ '728798-b':'from within the form',
+ '728798-c':'no form attribute in the form',
+ '728798-d':'after the form'
+}
+
+/* Fields for which the input is changed, and corresponding value
+ (clone and creation, same behaviour as webkit) */
+
+var changedFields = {
+ // dynamically generated input field not preserved
+ 'c0':'',
+ // cloned input field is restored with the value of the original
+ 'e2':fieldValues.e
+}
+
+/* Simulate user input by entering the values */
+
+function fill() {
+ for (id in fieldValues) {
+ frame.document.getElementById(id).value = fieldValues[id];
+ }
+ // an input is inserted before the radios (that may move the selected one by 1)
+ frame.document.getElementById('radio1').checked = true;
+}
+
+/* Check that all the fields are as they have been entered */
+
+function checkAllFields() {
+
+ for (id in fieldValues) {
+ var fieldValue = frame.document.getElementById(id).value;
+ if (changedFields[id] === undefined) {
+ is(fieldValue, fieldValues[id],
+ "Field "+id+" should be restored after reload");
+ } else {
+ is(fieldValue, changedFields[id],
+ "Field "+id+" normally gets a different value after reload");
+ }
+ }
+
+ ok(frame.document.getElementById('radio1').checked,
+ "Radio button radio1 should be restored after reload")
+
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_save_restore_radio_groups.html b/dom/html/test/forms/test_save_restore_radio_groups.html
new file mode 100644
index 000000000..837d5d681
--- /dev/null
+++ b/dom/html/test/forms/test_save_restore_radio_groups.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=350022
+-->
+<head>
+ <title>Test for Bug 350022</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=350022">Mozilla Bug 350022</a>
+<p id="display"></p>
+<div id="content"><!-- style="display: none">-->
+ <iframe src="save_restore_radio_groups.sjs"></iframe>
+ <iframe src="save_restore_radio_groups.sjs"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 350022 **/
+
+function checkRadioGroup(aFrame, aResults)
+{
+ var radios = frames[aFrame].document.getElementsByTagName('input');
+
+ is(radios.length, aResults.length,
+ "Radio group should have " + aResults.length + "elements");
+
+ for (var i=0; i<aResults.length; ++i) {
+ is(radios[i].checked, aResults[i],
+ "Radio checked state should be " + aResults[i]);
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ /**
+ * We have two iframes each containing one radio button group.
+ * We are going to change the selected radio button in one group.
+ * Then, both iframes will be reloaded and the new groups will have another
+ * radio checked by default.
+ * For the first group (which had a selection change), nothing should change.
+ * For the second, the selected radio button should change.
+ */
+ checkRadioGroup(0, [true, false, false]);
+ checkRadioGroup(1, [true, false, false]);
+
+ frames[0].document.getElementsByTagName('input')[2].checked = true;
+ checkRadioGroup(0, [false, false, true]);
+
+ framesElts = document.getElementsByTagName('iframe');
+ framesElts[0].addEventListener("load", function() {
+ framesElts[0].removeEventListener("load", arguments.callee, false);
+ checkRadioGroup(0, [false, false, true]);
+
+ framesElts[1].addEventListener("load", function() {
+ framesElts[1].removeEventListener("load", arguments.callee, false);
+
+ checkRadioGroup(1, [false, true, false]);
+ SimpleTest.finish();
+ }, false);
+
+ frames[1].location.reload();
+ }, false);
+
+ frames[0].location.reload();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_select_change_event.html b/dom/html/test/forms/test_select_change_event.html
new file mode 100644
index 000000000..80d3cd09d
--- /dev/null
+++ b/dom/html/test/forms/test_select_change_event.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1265968
+-->
+<head>
+ <title>Test for Bug 1265968</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=1265968">Mozilla Bug 1265968</a>
+<p id="display"></p>
+<div id="content">
+ <select id="select" onchange="++selectChange;">
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+ <option>four</option>
+ <option>five</option>
+ </select>
+</div>
+<pre id="test">
+<script type="application/javascript">
+ var select = document.getElementById("select");
+ var selectChange = 0;
+ var expectedChange = 0;
+
+ select.focus();
+ for (var i = 1; i < select.length; i++) {
+ synthesizeKey("VK_DOWN", {});
+ is(select.options[i].selected, true, "Option should be selected");
+ is(selectChange, ++expectedChange, "Down key should fire change event.");
+ }
+
+ // We are at the end of the list, going down should not fire change event.
+ synthesizeKey("VK_DOWN", {});
+ is(selectChange, expectedChange, "Down key should not fire change event when reaching end of the list.");
+
+ for (var i = select.length - 2; i >= 0; i--) {
+ synthesizeKey("VK_UP", {});
+ is(select.options[i].selected, true, "Option should be selected");
+ is(selectChange, ++expectedChange, "Up key should fire change event.");
+ }
+
+ // We are at the top of the list, going up should not fire change event.
+ synthesizeKey("VK_UP", {});
+ is(selectChange, expectedChange, "Up key should not fire change event when reaching top of the list.");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_select_input_change_event.html b/dom/html/test/forms/test_select_input_change_event.html
new file mode 100644
index 000000000..fa1b24b41
--- /dev/null
+++ b/dom/html/test/forms/test_select_input_change_event.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1265968
+-->
+<head>
+ <title>Test for Bug 1024350</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=1024350">Mozilla Bug 1024350</a>
+<p id="display"></p>
+<div id="content">
+ <select oninput='++selectInput;' onchange="++selectChange;">
+ <option>one</option>
+ </select>
+ <select oninput='++selectInput;' onchange="++selectChange;">
+ <option>one</option>
+ <option>two</option>
+ </select>
+ <select multiple size='1' oninput='++selectInput;' onchange="++selectChange;">
+ <option>one</option>
+ </select>
+ <select multiple oninput='++selectInput;' onchange="++selectChange;">
+ <option>one</option>
+ <option>two</option>
+ </select>
+</div>
+<pre id="test">
+<script type="application/javascript">
+ var selectSingleOneItem = document.getElementsByTagName('select')[0];
+ var selectSingle = document.getElementsByTagName('select')[1];
+ var selectMultipleOneItem = document.getElementsByTagName('select')[2];
+ var selectMultiple = document.getElementsByTagName('select')[3];
+
+ var selectChange = 0;
+ var selectInput = 0;
+ var expectedChange = 0;
+ var expectedInput = 0;
+
+ selectSingleOneItem.focus();
+ synthesizeKey("VK_DOWN", {});
+ is(selectInput, expectedInput, "Down key should not fire input event when reaching end of the list.");
+ is(selectChange, expectedChange, "Down key should not fire change event when reaching end of the list.");
+
+ synthesizeKey("VK_UP", {});
+ is(selectInput, expectedInput, "Up key should not fire input event when reaching top of the list.");
+ is(selectChange, expectedChange, "Up key should not fire change event when reaching top of the list.");
+
+ selectSingle.focus();
+ for (var i = 1; i < selectSingle.length; i++) {
+ synthesizeKey("VK_DOWN", {});
+
+ is(selectSingle.options[i].selected, true, "Option should be selected");
+ is(selectInput, ++expectedInput, "Down key should fire input event.");
+ is(selectChange, ++expectedChange, "Down key should fire change event.");
+ }
+
+ // We are at the end of the list, going down should not fire change event.
+ synthesizeKey("VK_DOWN", {});
+ is(selectInput, expectedInput, "Down key should not fire input event when reaching end of the list.");
+ is(selectChange, expectedChange, "Down key should not fire change event when reaching end of the list.");
+
+ for (var i = selectSingle.length - 2; i >= 0; i--) {
+ synthesizeKey("VK_UP", {});
+
+ is(selectSingle.options[i].selected, true, "Option should be selected");
+ is(selectInput, ++expectedInput, "Up key should fire input event.");
+ is(selectChange, ++expectedChange, "Up key should fire change event.");
+ }
+
+ // We are at the top of the list, going up should not fire change event.
+ synthesizeKey("VK_UP", {});
+ is(selectInput, expectedInput, "Up key should not fire input event when reaching top of the list.");
+ is(selectChange, expectedChange, "Up key should not fire change event when reaching top of the list.");
+
+ selectMultipleOneItem.focus();
+ synthesizeKey("VK_DOWN", {});
+ is(selectInput, ++expectedInput, "Down key should fire input event when reaching end of the list.");
+ is(selectChange, ++expectedChange, "Down key should fire change event when reaching end of the list.");
+
+ synthesizeKey("VK_DOWN", {});
+ is(selectInput, expectedInput, "Down key should not fire input event when reaching end of the list.");
+ is(selectChange, expectedChange, "Down key should not fire change event when reaching end of the list.");
+
+ synthesizeKey("VK_UP", {});
+ is(selectInput, expectedInput, "Up key should not fire input event when reaching top of the list.");
+ is(selectChange, expectedChange, "Up key should not fire change event when reaching top of the list.");
+
+ selectMultiple.focus();
+ for (var i = 0; i < selectMultiple.length; i++) {
+ synthesizeKey("VK_DOWN", {});
+
+ is(selectMultiple.options[i].selected, true, "Option should be selected");
+ is(selectInput, ++expectedInput, "Down key should fire input event.");
+ is(selectChange, ++expectedChange, "Down key should fire change event.");
+ }
+
+ // We are at the end of the list, going down should not fire change event.
+ synthesizeKey("VK_DOWN", {});
+ is(selectInput, expectedInput, "Down key should not fire input event when reaching end of the list.");
+ is(selectChange, expectedChange, "Down key should not fire change event when reaching end of the list.");
+
+ for (var i = selectMultiple.length - 2; i >= 0; i--) {
+ synthesizeKey("VK_UP", {});
+
+ is(selectMultiple.options[i].selected, true, "Option should be selected");
+ is(selectInput, ++expectedInput, "Up key should fire input event.");
+ is(selectChange, ++expectedChange, "Up key should fire change event.");
+ }
+
+ // We are at the top of the list, going up should not fire change event.
+ synthesizeKey("VK_UP", {});
+ is(selectInput, expectedInput, "Up key should not fire input event when reaching top of the list.");
+ is(selectChange, expectedChange, "Up key should not fire change event when reaching top of the list.");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_select_selectedOptions.html b/dom/html/test/forms/test_select_selectedOptions.html
new file mode 100644
index 000000000..e93222459
--- /dev/null
+++ b/dom/html/test/forms/test_select_selectedOptions.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=596681
+-->
+<head>
+ <title>Test for HTMLSelectElement.selectedOptions</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=596681">Mozilla Bug 596681</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+/** Test for HTMLSelectElement's selectedOptions attribute.
+ *
+ * selectedOptions is a live list of the options that have selectedness of true
+ * (not the selected content attribute).
+ *
+ * See http://www.whatwg.org/html/#dom-select-selectedoptions
+ **/
+
+function checkSelectedOptions(size, elements)
+{
+ is(selectedOptions.length, size,
+ "select should have " + size + " selected options");
+ for (let i = 0; i < size; ++i) {
+ ok(selectedOptions[i], "selected option is valid");
+ if (selectedOptions[i]) {
+ is(selectedOptions[i].value, elements[i].value, "selected options are correct");
+ }
+ }
+}
+
+let select = document.createElement("select");
+document.body.appendChild(select);
+let selectedOptions = select.selectedOptions;
+
+ok("selectedOptions" in select,
+ "select element should have a selectedOptions IDL attribute");
+
+ok(select.selectedOptions instanceof HTMLCollection,
+ "selectedOptions should be an HTMLCollection instance");
+
+let option1 = document.createElement("option");
+let option2 = document.createElement("option");
+let option3 = document.createElement("option");
+option1.id = "option1";
+option1.value = "option1";
+option2.value = "option2";
+option3.value = "option3";
+
+checkSelectedOptions(0, null);
+
+select.add(option1, null);
+is(selectedOptions.namedItem("option1").value, "option1", "named getter works");
+checkSelectedOptions(1, [option1]);
+
+select.add(option2, null);
+checkSelectedOptions(1, [option1]);
+
+select.options[1].selected = true;
+checkSelectedOptions(1, [option2]);
+
+select.multiple = true;
+checkSelectedOptions(1, [option2]);
+
+select.options[0].selected = true;
+checkSelectedOptions(2, [option1, option2]);
+
+option1.selected = false;
+// Usinig selected directly on the option should work.
+checkSelectedOptions(1, [option2]);
+
+select.remove(1);
+select.add(option2, 0);
+select.options[0].selected = true;
+select.options[1].selected = true;
+// Should be in tree order.
+checkSelectedOptions(2, [option2, option1]);
+
+select.add(option3, null);
+checkSelectedOptions(2, [option2, option1]);
+
+select.options[2].selected = true;
+checkSelectedOptions(3, [option2, option1, option3]);
+
+select.length = 0;
+option1.selected = false;
+option2.selected = false;
+option3.selected = false;
+var optgroup1 = document.createElement("optgroup");
+optgroup1.appendChild(option1);
+optgroup1.appendChild(option2);
+select.add(optgroup1)
+var optgroup2 = document.createElement("optgroup");
+optgroup2.appendChild(option3);
+select.add(optgroup2);
+
+checkSelectedOptions(0, null);
+
+option2.selected = true;
+checkSelectedOptions(1, [option2]);
+
+option3.selected = true;
+checkSelectedOptions(2, [option2, option3]);
+
+optgroup1.removeChild(option2);
+checkSelectedOptions(1, [option3]);
+
+document.body.removeChild(select);
+option1.selected = true;
+checkSelectedOptions(2, [option1, option3]);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_select_validation.html b/dom/html/test/forms/test_select_validation.html
new file mode 100644
index 000000000..1ae134801
--- /dev/null
+++ b/dom/html/test/forms/test_select_validation.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=942321
+-->
+<head>
+ <title>Test for Bug 942321</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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=942321">Mozilla Bug 942321</a>
+<p id="display"></p>
+<form id="form" href="">
+ <select required id="testselect">
+ <option id="placeholder" value="" selected>placeholder</option>
+ <option value="test" id="actualvalue">test</option>
+ <select>
+ <input type="submit" />
+</form>
+<script class="testbody" type="text/javascript">
+/** Test for Bug 942321 **/
+var option = document.getElementById("actualvalue");
+option.selected = true;
+is(form.checkValidity(), true, "Select is required and should be valid");
+
+var placeholder = document.getElementById("placeholder");
+placeholder.selected = true;
+is(form.checkValidity(), false, "Select is required and should be invalid");
+
+placeholder.value = "not-invalid-anymore";
+is(form.checkValidity(), true, "Select is required and should be valid when option's value is changed by javascript");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/forms/test_set_range_text.html b/dom/html/test/forms/test_set_range_text.html
new file mode 100644
index 000000000..160c8e58e
--- /dev/null
+++ b/dom/html/test/forms/test_set_range_text.html
@@ -0,0 +1,244 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=850364
+-->
+<head>
+<title>Tests for Bug 850364 && Bug 918940</title>
+<script type="text/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=850364">Mozilla Bug 850364</a>
+<p id="display"></p>
+<div id="content">
+
+<!-- "SetRangeText() supported types"-->
+<input type="text" id="input_text"></input>
+<input type="search" id="input_search"></input>
+<input type="url" id="input_url"></input>
+<input type="tel" id="input_tel"></input>
+<input type="password" id="input_password"></input>
+<textarea id="input_textarea"></textarea>
+
+<!-- "SetRangeText() non-supported types" -->
+<input type="button" id="input_button"></input>
+<input type="submit" id="input_submit"></input>
+<input type="image" id="input_image"></input>
+<input type="reset" id="input_reset"></input>
+<input type="radio" id="input_radio"></input>
+<input type="checkbox" id="input_checkbox"></input>
+<input type="range" id="input_range"></input>
+<input type="file" id="input_file"></input>
+<input type="email" id="input_email"></input>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ /** Tests for Bug 850364 && Bug 918940**/
+
+ var SupportedTypes = ["text", "search", "url", "tel", "password", "textarea"];
+ var NonSupportedTypes = ["button", "submit", "image", "reset", "radio",
+ "checkbox", "range", "file", "email"];
+
+ SimpleTest.waitForExplicitFinish();
+
+ function TestInputs() {
+
+ var opThrows, elem, i, msg;
+
+ //Non-supported types should throw
+ for (i = 0; i < NonSupportedTypes.length; ++i) {
+ opThrows = false;
+ msg = "input_" + NonSupportedTypes[i];
+ elem = document.getElementById(msg);
+ elem.focus();
+ try {
+ elem.setRangeText("abc");
+ } catch (ex) {
+ opThrows = true;
+ }
+ ok(opThrows, msg + " should throw InvalidStateError");
+ }
+
+ var numOfSelectCalls = 0, expectedNumOfSelectCalls = 0;
+ //Supported types should not throw
+ for (i = 0; i < SupportedTypes.length; ++i) {
+ opThrows = false;
+ msg = "input_" + SupportedTypes[i];
+ elem = document.getElementById(msg);
+ elem.focus();
+ try {
+ elem.setRangeText("abc");
+ expectedNumOfSelectCalls += 1;
+ } catch (ex) {
+ opThrows = true;
+ }
+ is(opThrows, false, msg + " should not throw InvalidStateError");
+
+ elem.addEventListener("select", function (aEvent) {
+ ok(true, "select event should be fired for " + aEvent.target.id);
+ if (++numOfSelectCalls == expectedNumOfSelectCalls) {
+ SimpleTest.finish();
+ } else if (numOfSelectCalls > expectedNumOfSelectCalls) {
+ ok(false, "Too many select events were fired");
+ }
+ }, false);
+
+ elem.addEventListener("input", function (aEvent) {
+ ok(false, "input event should NOT be fired for " + + aEvent.target.id);
+ }, false);
+
+ var test = " setRange(replacement), shrink";
+ elem.value = "0123456789ABCDEF";
+ elem.setSelectionRange(1, 6);
+ elem.setRangeText("xyz");
+ is(elem.value, "0xyz6789ABCDEF", msg + test);
+ is(elem.selectionStart, 1, msg + test);
+ is(elem.selectionEnd, 4, msg + test);
+ elem.setRangeText("mnk");
+ is(elem.value, "0mnk6789ABCDEF", msg + test);
+ expectedNumOfSelectCalls += 3;
+
+ test = " setRange(replacement), expand";
+ elem.value = "0123456789ABCDEF";
+ elem.setSelectionRange(1, 2);
+ elem.setRangeText("xyz");
+ is(elem.value, "0xyz23456789ABCDEF", msg + test);
+ is(elem.selectionStart, 1, msg + test);
+ is(elem.selectionEnd, 4, msg + test);
+ elem.setRangeText("mnk");
+ is(elem.value, "0mnk23456789ABCDEF", msg + test);
+ expectedNumOfSelectCalls += 3;
+
+ test = " setRange(replacement) pure insertion at start";
+ elem.value = "0123456789ABCDEF";
+ elem.setSelectionRange(0, 0);
+ elem.setRangeText("xyz");
+ is(elem.value, "xyz0123456789ABCDEF", msg + test);
+ is(elem.selectionStart, 0, msg + test);
+ is(elem.selectionEnd, 0, msg + test);
+ elem.setRangeText("mnk");
+ is(elem.value, "mnkxyz0123456789ABCDEF", msg + test);
+ expectedNumOfSelectCalls += 3;
+
+ test = " setRange(replacement) pure insertion in the middle";
+ elem.value = "0123456789ABCDEF";
+ elem.setSelectionRange(4, 4);
+ elem.setRangeText("xyz");
+ is(elem.value, "0123xyz456789ABCDEF", msg + test);
+ is(elem.selectionStart, 4, msg + test);
+ is(elem.selectionEnd, 4, msg + test);
+ elem.setRangeText("mnk");
+ is(elem.value, "0123mnkxyz456789ABCDEF", msg + test);
+ expectedNumOfSelectCalls += 3;
+
+ test = " setRange(replacement) pure insertion at the end";
+ elem.value = "0123456789ABCDEF";
+ elem.setSelectionRange(16, 16);
+ elem.setRangeText("xyz");
+ is(elem.value, "0123456789ABCDEFxyz", msg + test);
+ is(elem.selectionStart, 16, msg + test);
+ is(elem.selectionEnd, 16, msg + test);
+ elem.setRangeText("mnk");
+ is(elem.value, "0123456789ABCDEFmnkxyz", msg + test);
+ expectedNumOfSelectCalls += 3;
+
+ //test SetRange(replacement, start, end, mode) with start > end
+ try {
+ elem.setRangeText("abc", 20, 4);
+ } catch (ex) {
+ opThrows = (ex.name == "IndexSizeError" && ex.code == DOMException.INDEX_SIZE_ERR);
+ }
+ is(opThrows, true, msg + " should throw IndexSizeError");
+
+ //test SelectionMode 'select'
+ elem.value = "0123456789ABCDEF";
+ elem.setRangeText("xyz", 4, 9, "select");
+ is(elem.value, "0123xyz9ABCDEF", msg + ".value == \"0123xyz9ABCDEF\"");
+ is(elem.selectionStart, 4, msg + ".selectionStart == 4, with \"select\"");
+ is(elem.selectionEnd, 7, msg + ".selectionEnd == 7, with \"select\"");
+ expectedNumOfSelectCalls += 1;
+
+ elem.setRangeText("pqm", 6, 25, "select");
+ is(elem.value, "0123xypqm", msg + ".value == \"0123xypqm\"");
+ is(elem.selectionStart, 6, msg + ".selectionStart == 6, with \"select\"");
+ is(elem.selectionEnd, 9, msg + ".selectionEnd == 9, with \"select\"");
+ expectedNumOfSelectCalls += 1;
+
+ //test SelectionMode 'start'
+ elem.value = "0123456789ABCDEF";
+ elem.setRangeText("xyz", 4, 9, "start");
+ is(elem.value, "0123xyz9ABCDEF", msg + ".value == \"0123xyz9ABCDEF\"");
+ is(elem.selectionStart, 4, msg + ".selectionStart == 4, with \"start\"");
+ is(elem.selectionEnd, 4, msg + ".selectionEnd == 4, with \"start\"");
+ expectedNumOfSelectCalls += 1;
+
+ elem.setRangeText("pqm", 6, 25, "start");
+ is(elem.value, "0123xypqm", msg + ".value == \"0123xypqm\"");
+ is(elem.selectionStart, 6, msg + ".selectionStart == 6, with \"start\"");
+ is(elem.selectionEnd, 6, msg + ".selectionEnd == 6, with \"start\"");
+ expectedNumOfSelectCalls += 1;
+
+ //test SelectionMode 'end'
+ elem.value = "0123456789ABCDEF";
+ elem.setRangeText("xyz", 4, 9, "end");
+ is(elem.value, "0123xyz9ABCDEF", msg + ".value == \"0123xyz9ABCDEF\"");
+ is(elem.selectionStart, 7, msg + ".selectionStart == 7, with \"end\"");
+ is(elem.selectionEnd, 7, msg + ".selectionEnd == 7, with \"end\"");
+ expectedNumOfSelectCalls += 1;
+
+ elem.setRangeText("pqm", 6, 25, "end");
+ is(elem.value, "0123xypqm", msg + ".value == \"0123xypqm\"");
+ is(elem.selectionStart, 9, msg + ".selectionStart == 9, with \"end\"");
+ is(elem.selectionEnd, 9, msg + ".selectionEnd == 9, with \"end\"");
+ expectedNumOfSelectCalls += 1;
+
+ //test SelectionMode 'preserve' (default)
+
+ //subcase: selection{Start|End} > end
+ elem.value = "0123456789";
+ elem.setSelectionRange(6, 9);
+ elem.setRangeText("Z", 1, 2, "preserve");
+ is(elem.value, "0Z23456789", msg + ".value == \"0Z23456789\"");
+ is(elem.selectionStart, 6, msg + ".selectionStart == 6, with \"preserve\"");
+ is(elem.selectionEnd, 9, msg + ".selectionEnd == 9, with \"preserve\"");
+ expectedNumOfSelectCalls += 2;
+
+ //subcase: selection{Start|End} < end
+ elem.value = "0123456789";
+ elem.setSelectionRange(4, 5);
+ elem.setRangeText("QRST", 2, 9, "preserve");
+ is(elem.value, "01QRST9", msg + ".value == \"01QRST9\"");
+ is(elem.selectionStart, 2, msg + ".selectionStart == 2, with \"preserve\"");
+ is(elem.selectionEnd, 6, msg + ".selectionEnd == 6, with \"preserve\"");
+ expectedNumOfSelectCalls += 2;
+
+ //subcase: selectionStart > end, selectionEnd < end
+ elem.value = "0123456789";
+ elem.setSelectionRange(8, 4);
+ elem.setRangeText("QRST", 1, 5);
+ is(elem.value, "0QRST56789", msg + ".value == \"0QRST56789\"");
+ is(elem.selectionStart, 1, msg + ".selectionStart == 1, with \"default\"");
+ is(elem.selectionEnd, 5, msg + ".selectionEnd == 5, with \"default\"");
+ expectedNumOfSelectCalls += 2;
+
+ //subcase: selectionStart < end, selectionEnd > end
+ elem.value = "0123456789";
+ elem.setSelectionRange(4, 9);
+ elem.setRangeText("QRST", 2, 6);
+ is(elem.value, "01QRST6789", msg + ".value == \"01QRST6789\"");
+ is(elem.selectionStart, 2, msg + ".selectionStart == 2, with \"default\"");
+ is(elem.selectionEnd, 9, msg + ".selectionEnd == 9, with \"default\"");
+ expectedNumOfSelectCalls += 2;
+ }
+ }
+
+ addLoadEvent(TestInputs);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_step_attribute.html b/dom/html/test/forms/test_step_attribute.html
new file mode 100644
index 000000000..31277860c
--- /dev/null
+++ b/dom/html/test/forms/test_step_attribute.html
@@ -0,0 +1,965 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=635553
+-->
+<head>
+ <title>Test for Bug 635553</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=635499">Mozilla Bug 635499</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 635553 **/
+
+var data = [
+ { type: 'hidden', apply: false },
+ { type: 'text', apply: false },
+ { type: 'search', apply: false },
+ { type: 'tel', apply: false },
+ { type: 'url', apply: false },
+ { type: 'email', apply: false },
+ { type: 'password', apply: false },
+ { type: 'date', apply: true },
+ { type: 'month', apply: true },
+ { type: 'week', apply: true },
+ { type: 'time', apply: true },
+ // TODO: temporary set to false until bug 888331 is fixed.
+ { type: 'datetime-local', apply: false },
+ { type: 'number', apply: true },
+ { type: 'range', apply: true },
+ { type: 'color', apply: false },
+ { type: 'checkbox', apply: false },
+ { type: 'radio', apply: false },
+ { type: 'file', apply: false },
+ { type: 'submit', apply: false },
+ { type: 'image', apply: false },
+ { type: 'reset', apply: false },
+ { type: 'button', apply: false },
+];
+
+function getFreshElement(type) {
+ var elmt = document.createElement('input');
+ elmt.type = type;
+ return elmt;
+}
+
+function checkValidity(aElement, aValidity, aApply, aData)
+{
+ aValidity = aApply ? aValidity : true;
+
+ is(aElement.validity.valid, aValidity,
+ "element validity should be " + aValidity);
+ is(aElement.validity.stepMismatch, !aValidity,
+ "element step mismatch status should be " + !aValidity);
+
+ if (aValidity) {
+ is(aElement.validationMessage, "", "There should be no validation message.");
+ } else {
+ if (aElement.validity.rangeUnderflow) {
+ var underflowMsg =
+ (aElement.type == "date" || aElement.type == "time") ?
+ ("Please select a value that is no earlier than " + aElement.min + ".") :
+ ("Please select a value that is no less than " + aElement.min + ".");
+ is(aElement.validationMessage, underflowMsg,
+ "Checking range underflow validation message.");
+ } else if (aData.low == aData.high) {
+ is(aElement.validationMessage, "Please select a valid value. " +
+ "The nearest valid value is " + aData.low + ".",
+ "There should be a validation message.");
+ } else {
+ is(aElement.validationMessage, "Please select a valid value. " +
+ "The two nearest valid values are " + aData.low + " and " + aData.high + ".",
+ "There should be a validation message.");
+ }
+ }
+
+ is(aElement.matches(":valid"), aElement.willValidate && aValidity,
+ (aElement.willValidate && aValidity) ? ":valid should apply" : "valid shouldn't apply");
+ is(aElement.matches(":invalid"), aElement.willValidate && !aValidity,
+ (aElement.wil && aValidity) ? ":invalid shouldn't apply" : "valid should apply");
+}
+
+for (var test of data) {
+ var input = getFreshElement(test.type);
+ var apply = test.apply;
+
+ if (test.todo) {
+ todo_is(input.type, test.type, test.type + " isn't implemented yet");
+ continue;
+ }
+
+ // The element should be valid, there should be no step mismatch.
+ checkValidity(input, true, apply);
+
+ // Checks to do for all types that support step:
+ // - check for @step=0,
+ // - check for @step behind removed,
+ // - check for @step being 'any' with different case variations.
+ switch (input.type) {
+ case 'text':
+ case 'hidden':
+ case 'search':
+ case 'password':
+ case 'tel':
+ case 'radio':
+ case 'checkbox':
+ case 'reset':
+ case 'button':
+ case 'submit':
+ case 'image':
+ case 'color':
+ input.value = '0';
+ checkValidity(input, true, apply);
+ break;
+ case 'url':
+ input.value = 'http://mozilla.org';
+ checkValidity(input, true, apply);
+ break;
+ case 'email':
+ input.value = 'foo@bar.com';
+ checkValidity(input, true, apply);
+ break;
+ case 'file':
+ var file = new File([''], '635499_file');
+
+ SpecialPowers.wrap(input).mozSetFileArray([file]);
+ checkValidity(input, true, apply);
+
+ break;
+ case 'date':
+ // For date, the step is calulated on the timestamp since 1970-01-01
+ // which mean that for all dates prior to the epoch, this timestamp is < 0
+ // and the behavior might differ, therefore we have to test for these cases.
+
+ // When step is invalid, every date is valid
+ input.step = 0;
+ input.value = '2012-07-05';
+ checkValidity(input, true, apply);
+
+ input.step = 'foo';
+ input.value = '1970-01-01';
+ checkValidity(input, true, apply);
+
+ input.step = '-1';
+ input.value = '1969-12-12';
+ checkValidity(input, true, apply);
+
+ input.removeAttribute('step');
+ input.value = '1500-01-01';
+ checkValidity(input, true, apply);
+
+ input.step = 'any';
+ input.value = '1966-12-12';
+ checkValidity(input, true, apply);
+
+ input.step = 'ANY';
+ input.value = '2013-02-03';
+ checkValidity(input, true, apply);
+
+ // When min is set to a valid date, there is a step base.
+ input.min = '2008-02-28';
+ input.step = '2';
+ input.value = '2008-03-01';
+ checkValidity(input, true, apply);
+
+ input.value = '2008-02-29';
+ checkValidity(input, false, apply, { low: "2008-02-28", high: "2008-03-01" });
+
+ input.min = '2008-02-27';
+ input.value = '2008-02-28';
+ checkValidity(input, false, apply, { low: "2008-02-27", high: "2008-02-29" });
+
+ input.min = '2009-02-27';
+ input.value = '2009-02-28';
+ checkValidity(input, false, apply, { low: "2009-02-27", high: "2009-03-01" });
+
+ input.min = '2009-02-01';
+ input.step = '1.1';
+ input.value = '2009-02-02';
+ checkValidity(input, true, apply);
+
+ // Without any step attribute the date is valid
+ input.removeAttribute('step');
+ checkValidity(input, true, apply);
+
+ input.min = '1950-01-01';
+ input.step = '366';
+ input.value = '1951-01-01';
+ checkValidity(input, false, apply, { low: "1950-01-01", high: "1951-01-02" });
+
+ input.min = '1951-01-01';
+ input.step = '365';
+ input.value = '1952-01-01';
+ checkValidity(input, true, apply);
+
+ input.step = '0.9';
+ input.value = '1951-01-02';
+ is(input.step, '0.9', "check that step value is unchanged");
+ checkValidity(input, true, apply);
+
+ input.step = '0.4';
+ input.value = '1951-01-02';
+ is(input.step, '0.4', "check that step value is unchanged");
+ checkValidity(input, true, apply);
+
+ input.step = '1.5';
+ input.value = '1951-01-02';
+ is(input.step, '1.5', "check that step value is unchanged");
+ checkValidity(input, false, apply, { low: "1951-01-01", high: "1951-01-03" });
+
+ input.value = '1951-01-08';
+ checkValidity(input, false, apply, { low: "1951-01-07", high: "1951-01-09" });
+
+ input.step = '3000';
+ input.min= '1968-01-01';
+ input.value = '1968-05-12';
+ checkValidity(input, false, apply, { low: "1968-01-01", high: "1976-03-19" });
+
+ input.value = '1971-01-01';
+ checkValidity(input, false, apply, { low: "1968-01-01", high: "1976-03-19" });
+
+ input.value = '1991-01-01';
+ checkValidity(input, false, apply, { low: "1984-06-05", high: "1992-08-22" });
+
+ input.value = '1984-06-05';
+ checkValidity(input, true, apply);
+
+ input.value = '1992-08-22';
+ checkValidity(input, true, apply);
+
+ input.step = '2.1';
+ input.min = '1991-01-01';
+ input.value = '1991-01-01';
+ checkValidity(input, true, apply);
+
+ input.value = '1991-01-02';
+ checkValidity(input, false, apply, { low: "1991-01-01", high: "1991-01-03" });
+
+ input.value = '1991-01-03';
+ checkValidity(input, true, apply);
+
+ input.step = '2.1';
+ input.min = '1969-12-20';
+ input.value = '1969-12-20';
+ checkValidity(input, true, apply);
+
+ input.value = '1969-12-21';
+ checkValidity(input, false, apply, { low: "1969-12-20", high: "1969-12-22" });
+
+ input.value = '1969-12-22';
+ checkValidity(input, true, apply);
+
+ break;
+ case 'number':
+ // When step=0, the allowed step is 1.
+ input.step = '0';
+ input.value = '1.2';
+ checkValidity(input, false, apply, { low: 1, high: 2 });
+
+ input.value = '1';
+ checkValidity(input, true, apply);
+
+ input.value = '0';
+ checkValidity(input, true, apply);
+
+ // When step is NaN, the allowed step value is 1.
+ input.step = 'foo';
+ input.value = '1';
+ checkValidity(input, true, apply);
+
+ input.value = '1.5';
+ checkValidity(input, false, apply, { low: 1, high: 2 });
+
+ // When step is negative, the allowed step value is 1.
+ input.step = '-0.1';
+ checkValidity(input, false, apply, { low: 1, high: 2 });
+
+ input.value = '1';
+ checkValidity(input, true, apply);
+
+ // When step is missing, the allowed step value is 1.
+ input.removeAttribute('step');
+ input.value = '1.5';
+ checkValidity(input, false, apply, { low: 1, high: 2 });
+
+ input.value = '1';
+ checkValidity(input, true, apply);
+
+ // When step is 'any', all values are fine wrt to step.
+ input.step = 'any';
+ checkValidity(input, true, apply);
+
+ input.step = 'aNy';
+ input.value = '1337';
+ checkValidity(input, true, apply);
+
+ input.step = 'AnY';
+ input.value = '0.1';
+ checkValidity(input, true, apply);
+
+ input.step = 'ANY';
+ input.value = '-13.37';
+ checkValidity(input, true, apply);
+
+ // When min is set to a valid float, there is a step base.
+ input.min = '1';
+ input.step = '2';
+ input.value = '3';
+ checkValidity(input, true, apply);
+
+ input.value = '2';
+ checkValidity(input, false, apply, { low: 1, high: 3 });
+
+ input.removeAttribute('step'); // step = 1
+ input.min = '0.5';
+ input.value = '5.5';
+ checkValidity(input, true, apply);
+
+ input.value = '1';
+ checkValidity(input, false, apply, { low: 0.5, high: 1.5 });
+
+ input.min = '-0.1';
+ input.step = '1';
+ input.value = '0.9';
+ checkValidity(input, true, apply);
+
+ input.value = '0.1';
+ checkValidity(input, false, apply, { low: -0.1, high: 0.9 });
+
+ // When min is set to NaN, there is no step base (step base=0 actually).
+ input.min = 'foo';
+ input.step = '1';
+ input.value = '1';
+ checkValidity(input, true, apply);
+
+ input.value = '0.5';
+ checkValidity(input, false, apply, { low: 0, high: 1 });
+
+ input.min = '';
+ input.value = '1';
+ checkValidity(input, true, apply);
+
+ input.value = '0.5';
+ checkValidity(input, false, apply, { low: 0, high: 1 });
+
+ input.removeAttribute('min');
+
+ // If value isn't a number, the element isn't invalid.
+ input.value = '';
+ checkValidity(input, true, apply);
+
+ // Regular situations.
+ input.step = '2';
+ input.value = '1.5';
+ checkValidity(input, false, apply, { low: 0, high: 2 });
+
+ input.value = '42.0';
+ checkValidity(input, true, apply);
+
+ input.step = '0.1';
+ input.value = '-0.1';
+ checkValidity(input, true, apply);
+
+ input.step = '2';
+ input.removeAttribute('min');
+ input.max = '10';
+ input.value = '-9';
+ checkValidity(input, false, apply, {low: -10, high: -8});
+
+ // If there is a value defined but no min, the step base is the value.
+ input = getFreshElement(test.type);
+ input.setAttribute('value', '1');
+ input.step = 2;
+ checkValidity(input, true, apply);
+
+ input.value = 3;
+ checkValidity(input, true, apply);
+
+ input.value = 2;
+ checkValidity(input, false, apply, {low: 1, high: 3});
+
+ // Should also work with defaultValue.
+ input = getFreshElement(test.type);
+ input.defaultValue = 1;
+ input.step = 2;
+ checkValidity(input, true, apply);
+
+ input.value = 3;
+ checkValidity(input, true, apply);
+
+ input.value = 2;
+ checkValidity(input, false, apply, {low: 1, high: 3});
+
+ // Rounding issues.
+ input = getFreshElement(test.type);
+ input.min = 0.1;
+ input.step = 0.2;
+ input.value = 0.3;
+ checkValidity(input, true, apply);
+
+ // Check that when the higher value is higher than max, we don't show it.
+ input = getFreshElement(test.type);
+ input.step = '2';
+ input.min = '1';
+ input.max = '10.9';
+ input.value = '10';
+
+ is(input.validationMessage, "Please select a valid value. " +
+ "The nearest valid value is 9.",
+ "The validation message should not include the higher value.");
+ break;
+ case 'range':
+ // Range is special in that it clamps to valid values, so it is much
+ // rarer for it to be invalid.
+
+ // When step=0, the allowed value step is 1.
+ input.step = '0';
+ input.value = '1.2';
+ is(input.value, '1', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+ checkValidity(input, true, apply);
+
+ input.value = '1';
+ is(input.value, '1', "check that the value coincides with a step");
+ checkValidity(input, true, apply);
+
+ input.value = '0';
+ is(input.value, '0', "check that the value coincides with a step");
+ checkValidity(input, true, apply);
+
+ // When step is NaN, the allowed step value is 1.
+ input.step = 'foo';
+ input.value = '1';
+ is(input.value, '1', "check that the value coincides with a step");
+ checkValidity(input, true, apply);
+
+ input.value = '1.5';
+ is(input.value, '2', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+ checkValidity(input, true, apply);
+
+ // When step is negative, the allowed step value is 1.
+ input.step = '-0.1';
+ is(input.value, '2', "check that the value still coincides with a step");
+ checkValidity(input, true, apply);
+
+ input.value = '1';
+ is(input.value, '1', "check that the value coincides with a step");
+ checkValidity(input, true, apply);
+
+ // When step is missing, the allowed step value is 1.
+ input.removeAttribute('step');
+ input.value = '1.5';
+ is(input.value, '2', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+ checkValidity(input, true, apply);
+
+ input.value = '1';
+ is(input.value, '1', "check that the value coincides with a step");
+ checkValidity(input, true, apply);
+
+ // When step is 'any', all values are fine wrt to step.
+ input.step = 'any';
+ checkValidity(input, true, apply);
+
+ input.step = 'aNy';
+ input.value = '97';
+ is(input.value, '97', "check that the value for step=aNy is unchanged");
+ checkValidity(input, true, apply);
+
+ input.step = 'AnY';
+ input.value = '0.1';
+ is(input.value, '0.1', "check that a positive fractional value with step=AnY is unchanged");
+ checkValidity(input, true, apply);
+
+ input.step = 'ANY';
+ input.min = -100;
+ input.value = '-13.37';
+ is(input.value, '-13.37', "check that a negative fractional value with step=ANY is unchanged");
+ checkValidity(input, true, apply);
+
+ // When min is set to a valid float, there is a step base.
+ input.min = '1'; // the step base
+ input.step = '2';
+ input.value = '3';
+ is(input.value, '3', "check that the value coincides with a step");
+ checkValidity(input, true, apply);
+
+ input.value = '2';
+ is(input.value, '3', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+ checkValidity(input, true, apply);
+
+ input.value = '1.99';
+ is(input.value, '1', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+ checkValidity(input, true, apply);
+
+ input.removeAttribute('step'); // step = 1
+ input.min = '0.5'; // step base
+ input.value = '5.5';
+ is(input.value, '5.5', "check that the value coincides with a step");
+ checkValidity(input, true, apply);
+
+ input.value = '1';
+ is(input.value, '1.5', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+ checkValidity(input, true, apply);
+
+ input.min = '-0.1'; // step base
+ input.step = '1';
+ input.value = '0.9';
+ is(input.value, '0.9', "the value should be a valid step");
+ checkValidity(input, true, apply);
+
+ input.value = '0.1';
+ is(input.value, '-0.1', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+ checkValidity(input, true, apply);
+
+ // When min is set to NaN, the step base is the value.
+ input.min = 'foo';
+ input.step = '1';
+ input.value = '1';
+ is(input.value, '1', "check that the value coincides with a step");
+ checkValidity(input, true, apply);
+
+ input.value = '0.5';
+ is(input.value, '1', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+ checkValidity(input, true, apply);
+
+ input.min = '';
+ input.value = '1';
+ is(input.value, '1', "check that the value coincides with a step");
+ checkValidity(input, true, apply);
+
+ input.value = '0.5';
+ is(input.value, '1', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+ checkValidity(input, true, apply);
+
+ input.removeAttribute('min');
+
+ // Test when the value isn't a number
+ input.value = '';
+ is(input.value, '50', "value be should default to the value midway between the minimum (0) and the maximum (100)");
+ checkValidity(input, true, apply);
+
+ // Regular situations.
+ input.step = '2';
+ input.value = '1.5';
+ is(input.value, '2', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+ checkValidity(input, true, apply);
+
+ input.value = '42.0';
+ is(input.value, '42.0', "check that the value coincides with a step");
+ checkValidity(input, true, apply);
+
+ input.step = '0.1';
+ input.value = '-0.1';
+ is(input.value, '0', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+ checkValidity(input, true, apply);
+
+ input.step = '2';
+ input.removeAttribute('min');
+ input.max = '10';
+ input.value = '-9';
+ is(input.value, '0', "check the value is clamped to the minimum's default of zero");
+ checkValidity(input, true, apply);
+
+ // If @value is defined but not @min, the step base is @value.
+ input = getFreshElement(test.type);
+ input.setAttribute('value', '1');
+ input.step = 2;
+ is(input.value, '1', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+ checkValidity(input, true, apply);
+
+ input.value = 3;
+ is(input.value, '3', "check that the value coincides with a step");
+ checkValidity(input, true, apply);
+
+ input.value = 2;
+ is(input.value, '3', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+ checkValidity(input, true, apply);
+
+ // Should also work with defaultValue.
+ input = getFreshElement(test.type);
+ input.defaultValue = 1;
+ input.step = 2;
+ is(input.value, '1', "check that the value coincides with a step");
+ checkValidity(input, true, apply);
+
+ input.value = 3;
+ is(input.value, '3', "check that the value coincides with a step");
+ checkValidity(input, true, apply);
+
+ input.value = 2;
+ is(input.value, '3', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+ checkValidity(input, true, apply);
+
+ // Check contrived error case where there are no valid steps in range:
+ // No @min, so the step base is the default minimum, zero, the valid
+ // range is 0-1, -1 gets clamped to zero.
+ input = getFreshElement(test.type);
+ input.step = '3';
+ input.max = '1';
+ input.defaultValue = '-1';
+ is(input.value, '0', "the value should have been clamped to the default minimum, zero");
+ checkValidity(input, false, apply, {low: -1, high: -1});
+
+ // Check that when the closest of the two steps that the value is between
+ // is greater than the maximum we sanitize to the lower step.
+ input = getFreshElement(test.type);
+ input.step = '2';
+ input.min = '1';
+ input.max = '10.9';
+ input.value = '10.8'; // closest step in 11, but 11 > maximum
+ is(input.value, '9', "check that the value coincides with a step");
+
+ // The way that step base is defined, the converse (the value not being
+ // on a step, and the nearest step being a value that would be underflow)
+ // is not possible, so nothing to test there.
+
+ is(input.validationMessage, "",
+ "The validation message should be empty.");
+ break;
+ case 'time':
+ // Tests invalid step values. That defaults to step = 1 minute (60).
+ var values = [ '0', '-1', 'foo', 'any', 'ANY', 'aNy' ];
+ for (var value of values) {
+ input.step = value;
+ input.value = '19:06:00';
+ checkValidity(input, true, apply);
+ input.value = '19:06:51';
+ if (value.toLowerCase() != 'any') {
+ checkValidity(input, false, apply, {low: '19:06', high: '19:07'});
+ } else {
+ checkValidity(input, true, apply);
+ }
+ }
+
+ // No step means that we use the default step value.
+ input.removeAttribute('step');
+ input.value = '19:06:00';
+ checkValidity(input, true, apply);
+ input.value = '19:06:51';
+ checkValidity(input, false, apply, {low: '19:06', high: '19:07'});
+
+ var tests = [
+ // With step=1, we allow values by the second.
+ { step: '1', value: '19:11:01', min: '00:00', result: true },
+ { step: '1', value: '19:11:01.001', min: '00:00', result: false,
+ low: '19:11:01', high: '19:11:02' },
+ { step: '1', value: '19:11:01.1', min: '00:00', result: false,
+ low: '19:11:01', high: '19:11:02' },
+ // When step >= 86400000, only the minimum value is valid.
+ // This is actually @value if there is no @min.
+ { step: '86400000', value: '00:00', result: true },
+ { step: '86400000', value: '00:01', result: true },
+ { step: '86400000', value: '00:00', min: '00:01', result: false },
+ { step: '86400000', value: '00:01', min: '00:00', result: false,
+ low: '00:00', high: '00:00' },
+ // When step < 1, it should just work.
+ { step: '0.1', value: '15:05:05.1', min: '00:00', result: true },
+ { step: '0.1', value: '15:05:05.101', min: '00:00', result: false,
+ low: '15:05:05.100', high: '15:05:05.200' },
+ { step: '0.2', value: '15:05:05.2', min: '00:00', result: true },
+ { step: '0.2', value: '15:05:05.1', min: '00:00', result: false,
+ low: '15:05:05', high: '15:05:05.200' },
+ { step: '0.01', value: '15:05:05.01', min: '00:00', result: true },
+ { step: '0.01', value: '15:05:05.011', min: '00:00', result: false,
+ low: '15:05:05.010', high: '15:05:05.020' },
+ { step: '0.02', value: '15:05:05.02', min: '00:00', result: true },
+ { step: '0.02', value: '15:05:05.01', min: '00:00', result: false,
+ low: '15:05:05', high: '15:05:05.020' },
+ { step: '0.002', value: '15:05:05.002', min: '00:00', result: true },
+ { step: '0.002', value: '15:05:05.001', min: '00:00', result: false,
+ low: '15:05:05', high: '15:05:05.002' },
+ // When step<=0.001, any value is allowed.
+ { step: '0.001', value: '15:05:05.001', min: '00:00', result: true },
+ { step: '0.001', value: '15:05:05', min: '00:00', result: true },
+ { step: '0.000001', value: '15:05:05', min: '00:00', result: true },
+ // This value has conversion to double issues.
+ { step: '0.0000001', value: '15:05:05', min: '00:00', result: true },
+ // Some random values.
+ { step: '100', value: '15:06:40', min: '00:00', result: true },
+ { step: '100', value: '15:05:05.010', min: '00:00', result: false,
+ low: '15:05', high: '15:06:40' },
+ { step: '3600', value: '15:00', min: '00:00', result: true },
+ { step: '3600', value: '15:14', min: '00:00', result: false,
+ low: '15:00', high: '16:00' },
+ { step: '7200', value: '14:00', min: '00:00', result: true },
+ { step: '7200', value: '15:14', min: '00:00', result: false,
+ low: '14:00', high: '16:00' },
+ { step: '7260', value: '14:07', min: '00:00', result: true },
+ { step: '7260', value: '15:14', min: '00:00', result: false,
+ low: '14:07', high: '16:08' },
+ ];
+
+ var type = test.type;
+ for (var test of tests) {
+ var input = getFreshElement(type);
+ input.step = test.step;
+ input.setAttribute('value', test.value);
+ if (test.min !== undefined) {
+ input.min = test.min;
+ }
+
+ if (test.todo) {
+ todo(input.validity.valid, test.result,
+ "This test should fail for the moment because of precission issues");
+ continue;
+ }
+
+ if (test.result) {
+ checkValidity(input, true, apply);
+ } else {
+ checkValidity(input, false, apply,
+ { low: test.low, high: test.high });
+ }
+ }
+
+ break;
+ case 'month':
+ // When step is invalid, every date is valid
+ input.step = 0;
+ input.value = '2016-07';
+ checkValidity(input, true, apply);
+
+ input.step = 'foo';
+ input.value = '1970-01';
+ checkValidity(input, true, apply);
+
+ input.step = '-1';
+ input.value = '1970-01';
+ checkValidity(input, true, apply);
+
+ input.removeAttribute('step');
+ input.value = '1500-01';
+ checkValidity(input, true, apply);
+
+ input.step = 'any';
+ input.value = '1966-12';
+ checkValidity(input, true, apply);
+
+ input.step = 'ANY';
+ input.value = '2013-02';
+ checkValidity(input, true, apply);
+
+ // When min is set to a valid month, there is a step base.
+ input.min = '2000-01';
+ input.step = '2';
+ input.value = '2000-03';
+ checkValidity(input, true, apply);
+
+ input.value = '2000-02';
+ checkValidity(input, false, apply, { low: "2000-01", high: "2000-03" });
+
+ input.min = '2012-12';
+ input.value = '2013-01';
+ checkValidity(input, false, apply, { low: "2012-12", high: "2013-02" });
+
+ input.min = '2010-10';
+ input.value = '2010-11';
+ checkValidity(input, false, apply, { low: "2010-10", high: "2010-12" });
+
+ input.min = '2010-01';
+ input.step = '1.1';
+ input.value = '2010-02';
+ checkValidity(input, true, apply);
+
+ input.min = '2010-05';
+ input.step = '1.9';
+ input.value = '2010-06';
+ checkValidity(input, false, apply, { low: "2010-05", high: "2010-07" });
+
+ // Without any step attribute the date is valid
+ input.removeAttribute('step');
+ checkValidity(input, true, apply);
+
+ input.min = '1950-01';
+ input.step = '13';
+ input.value = '1951-01';
+ checkValidity(input, false, apply, { low: "1950-01", high: "1951-02" });
+
+ input.min = '1951-01';
+ input.step = '12';
+ input.value = '1952-01';
+ checkValidity(input, true, apply);
+
+ input.step = '0.9';
+ input.value = '1951-02';
+ checkValidity(input, true, apply);
+
+ input.step = '1.5';
+ input.value = '1951-04';
+ checkValidity(input, false, apply, { low: "1951-03", high: "1951-05" });
+
+ input.value = '1951-08';
+ checkValidity(input, false, apply, { low: "1951-07", high: "1951-09" });
+
+ input.step = '300';
+ input.min= '1968-01';
+ input.value = '1968-05';
+ checkValidity(input, false, apply, { low: "1968-01", high: "1993-01" });
+
+ input.value = '1971-01';
+ checkValidity(input, false, apply, { low: "1968-01", high: "1993-01" });
+
+ input.value = '1994-01';
+ checkValidity(input, false, apply, { low: "1993-01", high: "2018-01" });
+
+ input.value = '2018-01';
+ checkValidity(input, true, apply);
+
+ input.value = '2043-01';
+ checkValidity(input, true, apply);
+
+ input.step = '2.1';
+ input.min = '1991-01';
+ input.value = '1991-01';
+ checkValidity(input, true, apply);
+
+ input.value = '1991-02';
+ checkValidity(input, false, apply, { low: "1991-01", high: "1991-03" });
+
+ input.value = '1991-03';
+ checkValidity(input, true, apply);
+
+ input.step = '2.1';
+ input.min = '1969-12';
+ input.value = '1969-12';
+ checkValidity(input, true, apply);
+
+ input.value = '1970-01';
+ checkValidity(input, false, apply, { low: "1969-12", high: "1970-02" });
+
+ input.value = '1970-02';
+ checkValidity(input, true, apply);
+
+ break;
+ case 'week':
+ // When step is invalid, every week is valid
+ input.step = 0;
+ input.value = '2016-W30';
+ checkValidity(input, true, apply);
+
+ input.step = 'foo';
+ input.value = '1970-W01';
+ checkValidity(input, true, apply);
+
+ input.step = '-1';
+ input.value = '1970-W01';
+ checkValidity(input, true, apply);
+
+ input.removeAttribute('step');
+ input.value = '1500-W01';
+ checkValidity(input, true, apply);
+
+ input.step = 'any';
+ input.value = '1966-W52';
+ checkValidity(input, true, apply);
+
+ input.step = 'ANY';
+ input.value = '2013-W10';
+ checkValidity(input, true, apply);
+
+ // When min is set to a valid week, there is a step base.
+ input.min = '2000-W01';
+ input.step = '2';
+ input.value = '2000-W03';
+ checkValidity(input, true, apply);
+
+ input.value = '2000-W02';
+ checkValidity(input, false, apply, { low: "2000-W01", high: "2000-W03" });
+
+ input.min = '2012-W52';
+ input.value = '2013-W01';
+ checkValidity(input, false, apply, { low: "2012-W52", high: "2013-W02" });
+
+ input.min = '2010-W01';
+ input.step = '1.1';
+ input.value = '2010-W02';
+ checkValidity(input, true, apply);
+
+ input.min = '2010-W05';
+ input.step = '1.9';
+ input.value = '2010-W06';
+ checkValidity(input, false, apply, { low: "2010-W05", high: "2010-W07" });
+
+ // Without any step attribute the week is valid
+ input.removeAttribute('step');
+ checkValidity(input, true, apply);
+
+ input.min = '1950-W01';
+ input.step = '53';
+ input.value = '1951-W01';
+ checkValidity(input, false, apply, { low: "1950-W01", high: "1951-W02" });
+
+ input.min = '1951-W01';
+ input.step = '52';
+ input.value = '1952-W01';
+ checkValidity(input, true, apply);
+
+ input.step = '0.9';
+ input.value = '1951-W02';
+ checkValidity(input, true, apply);
+
+ input.step = '1.5';
+ input.value = '1951-W04';
+ checkValidity(input, false, apply, { low: "1951-W03", high: "1951-W05" });
+
+ input.value = '1951-W20';
+ checkValidity(input, false, apply, { low: "1951-W19", high: "1951-W21" });
+
+ input.step = '300';
+ input.min= '1968-W01';
+ input.value = '1968-W05';
+ checkValidity(input, false, apply, { low: "1968-W01", high: "1973-W40" });
+
+ input.value = '1971-W01';
+ checkValidity(input, false, apply, { low: "1968-W01", high: "1973-W40" });
+
+ input.value = '1975-W01';
+ checkValidity(input, false, apply, { low: "1973-W40", high: "1979-W27" });
+
+ input.value = '1985-W14';
+ checkValidity(input, true, apply);
+
+ input.step = '2.1';
+ input.min = '1991-W01';
+ input.value = '1991-W01';
+ checkValidity(input, true, apply);
+
+ input.value = '1991-W02';
+ checkValidity(input, false, apply, { low: "1991-W01", high: "1991-W03" });
+
+ input.value = '1991-W03';
+ checkValidity(input, true, apply);
+
+ input.step = '2.1';
+ input.min = '1969-W52';
+ input.value = '1969-W52';
+ checkValidity(input, true, apply);
+
+ input.value = '1970-W01';
+ checkValidity(input, false, apply, { low: "1969-W52", high: "1970-W02" });
+
+ input.value = '1970-W02';
+ checkValidity(input, true, apply);
+
+ break;
+ case 'datetime-local':
+ // TODO: this is temporary until bug 888331 is fixed.
+
+ break;
+ default:
+ ok(false, "Implement the tests for <input type='" + test.type + " >");
+ break;
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_stepup_stepdown.html b/dom/html/test/forms/test_stepup_stepdown.html
new file mode 100644
index 000000000..d96895180
--- /dev/null
+++ b/dom/html/test/forms/test_stepup_stepdown.html
@@ -0,0 +1,1018 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=636627
+-->
+<head>
+ <title>Test for Bug 636627</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=636627">Mozilla Bug 636627</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 636627 **/
+
+/**
+ * This test is testing stepDown() and stepUp().
+ */
+
+function checkPresence()
+{
+ var input = document.createElement('input');
+ is('stepDown' in input, true, 'stepDown() should be an input function');
+ is('stepUp' in input, true, 'stepUp() should be an input function');
+}
+
+function checkAvailability()
+{
+ var testData =
+ [
+ ["text", false],
+ ["password", false],
+ ["search", false],
+ ["telephone", false],
+ ["email", false],
+ ["url", false],
+ ["hidden", false],
+ ["checkbox", false],
+ ["radio", false],
+ ["file", false],
+ ["submit", false],
+ ["image", false],
+ ["reset", false],
+ ["button", false],
+ ["number", true],
+ ["range", true],
+ ["date", true],
+ ["time", true],
+ ["month", true],
+ ["week", true],
+ ["color", false],
+ ];
+
+ var todoList =
+ [
+ ["datetime", true],
+ ["datetime-local", true],
+ ];
+
+ var element = document.createElement("input");
+ element.setAttribute('value', '0');
+
+ for (data of testData) {
+ var exceptionCaught = false;
+ element.type = data[0];
+ try {
+ element.stepDown();
+ } catch (e) {
+ exceptionCaught = true;
+ }
+ is(exceptionCaught, !data[1], "stepDown() availability is not correct");
+
+ exceptionCaught = false;
+ try {
+ element.stepUp();
+ } catch (e) {
+ exceptionCaught = true;
+ }
+ is(exceptionCaught, !data[1], "stepUp() availability is not correct");
+ }
+
+ for (data of todoList) {
+ var exceptionCaught = false;
+ element.type = data[0];
+ try {
+ element.stepDown();
+ } catch (e) {
+ exceptionCaught = true;
+ }
+ todo_is(exceptionCaught, !data[1],
+ "stepDown() availability is not correct");
+
+ exceptionCaught = false;
+ try {
+ element.stepUp();
+ } catch (e) {
+ exceptionCaught = true;
+ }
+ todo_is(exceptionCaught, !data[1],
+ "stepUp() availability is not correct");
+ }
+}
+
+function checkStepDown()
+{
+ // This testData is very similar to the one in checkStepUp with some changes
+ // relative to stepDown.
+ var testData = [
+ /* Initial value | step | min | max | stepDown arg | final value | exception */
+ { type: 'number', data: [
+ // Regular case.
+ [ '1', null, null, null, null, '0', false ],
+ // Argument testing.
+ [ '1', null, null, null, 1, '0', false ],
+ [ '9', null, null, null, 9, '0', false ],
+ [ '1', null, null, null, -1, '2', false ],
+ [ '1', null, null, null, 0, '1', false ],
+ // Float values are rounded to integer (1.1 -> 1).
+ [ '1', null, null, null, 1.1, '0', false ],
+ // With step values.
+ [ '1', '0.5', null, null, null, '0.5', false ],
+ [ '1', '0.25', null, null, 4, '0', false ],
+ // step = 0 isn't allowed (-> step = 1).
+ [ '1', '0', null, null, null, '0', false ],
+ // step < 0 isn't allowed (-> step = 1).
+ [ '1', '-1', null, null, null, '0', false ],
+ // step = NaN isn't allowed (-> step = 1).
+ [ '1', 'foo', null, null, null, '0', false ],
+ // Min values testing.
+ [ '1', '1', 'foo', null, null, '0', false ],
+ [ '1', null, '-10', null, null, '0', false ],
+ [ '1', null, '0', null, null, '0', false ],
+ [ '1', null, '10', null, null, '1', false ],
+ [ '1', null, '2', null, null, '1', false ],
+ [ '1', null, '1', null, null, '1', false ],
+ // Max values testing.
+ [ '1', '1', null, 'foo', null, '0', false ],
+ [ '1', null, null, '10', null, '0', false ],
+ [ '1', null, null, '0', null, '0', false ],
+ [ '1', null, null, '-10', null, '-10', false ],
+ [ '1', null, null, '1', null, '0', false ],
+ [ '5', null, null, '3', '3', '2', false ],
+ [ '5', '2', '-6', '3', '2', '2', false ],
+ [ '-3', '5', '-10', '-3', null, '-5', false ],
+ // Step mismatch.
+ [ '1', '2', '-2', null, null, '0', false ],
+ [ '3', '2', '-2', null, null, '2', false ],
+ [ '3', '2', '-2', null, '2', '0', false ],
+ [ '3', '2', '-2', null, '-2', '6', false ],
+ [ '1', '2', '-6', null, null, '0', false ],
+ [ '1', '2', '-2', null, null, '0', false ],
+ [ '1', '3', '-6', null, null, '0', false ],
+ [ '2', '3', '-6', null, null, '0', false ],
+ [ '2', '3', '1', null, null, '1', false ],
+ [ '5', '3', '1', null, null, '4', false ],
+ [ '3', '2', '-6', null, null, '2', false ],
+ [ '5', '2', '-6', null, null, '4', false ],
+ [ '6', '2', '1', null, null, '5', false ],
+ [ '8', '3', '1', null, null, '7', false ],
+ [ '9', '2', '-10', null, null, '8', false ],
+ [ '7', '3', '-10', null, null, '5', false ],
+ [ '-2', '3', '-10', null, null, '-4', false ],
+ // Clamping.
+ [ '0', '2', '-1', null, null, '-1', false ],
+ [ '10', '2', '0', '4', '10', '0', false ],
+ [ '10', '2', '0', '4', '5', '0', false ],
+ // value = "" (NaN).
+ [ '', null, null, null, null, '-1', false ],
+ [ '', '2', null, null, null, '-2', false ],
+ [ '', '2', '3', null, null, '3', false ],
+ [ '', null, '3', null, null, '3', false ],
+ [ '', '2', '3', '8', null, '3', false ],
+ [ '', null, '-10', '10', null, '-1', false ],
+ [ '', '3', '-10', '10', null, '-1', false ],
+ // With step = 'any'.
+ [ '0', 'any', null, null, 1, null, true ],
+ [ '0', 'ANY', null, null, 1, null, true ],
+ [ '0', 'AnY', null, null, 1, null, true ],
+ [ '0', 'aNy', null, null, 1, null, true ],
+ // With @value = step base.
+ [ '1', '2', null, null, null, '-1', false ],
+ ]},
+ { type: 'range', data: [
+ // Regular case.
+ [ '1', null, null, null, null, '0', false ],
+ // Argument testing.
+ [ '1', null, null, null, 1, '0', false ],
+ [ '9', null, null, null, 9, '0', false ],
+ [ '1', null, null, null, -1, '2', false ],
+ [ '1', null, null, null, 0, '1', false ],
+ // Float values are rounded to integer (1.1 -> 1).
+ [ '1', null, null, null, 1.1, '0', false ],
+ // With step values.
+ [ '1', '0.5', null, null, null, '0.5', false ],
+ [ '1', '0.25', null, null, 4, '0', false ],
+ // step = 0 isn't allowed (-> step = 1).
+ [ '1', '0', null, null, null, '0', false ],
+ // step < 0 isn't allowed (-> step = 1).
+ [ '1', '-1', null, null, null, '0', false ],
+ // step = NaN isn't allowed (-> step = 1).
+ [ '1', 'foo', null, null, null, '0', false ],
+ // Min values testing.
+ [ '1', '1', 'foo', null, null, '0', false ],
+ [ '1', null, '-10', null, null, '0', false ],
+ [ '1', null, '0', null, null, '0', false ],
+ [ '1', null, '10', null, null, '10', false ],
+ [ '1', null, '2', null, null, '2', false ],
+ [ '1', null, '1', null, null, '1', false ],
+ // Max values testing.
+ [ '1', '1', null, 'foo', null, '0', false ],
+ [ '1', null, null, '10', null, '0', false ],
+ [ '1', null, null, '0', null, '0', false ],
+ [ '1', null, null, '-10', null, '0', false ],
+ [ '1', null, null, '1', null, '0', false ],
+ [ '5', null, null, '3', '3', '0', false ],
+ [ '5', '2', '-6', '3', '2', '-2', false ],
+ [ '-3', '5', '-10', '-3', null, '-10', false ],
+ // Step mismatch.
+ [ '1', '2', '-2', null, null, '0', false ],
+ [ '3', '2', '-2', null, null, '2', false ],
+ [ '3', '2', '-2', null, '2', '0', false ],
+ [ '3', '2', '-2', null, '-2', '8', false ],
+ [ '1', '2', '-6', null, null, '0', false ],
+ [ '1', '2', '-2', null, null, '0', false ],
+ [ '1', '3', '-6', null, null, '-3', false ],
+ [ '2', '3', '-6', null, null, '0', false ],
+ [ '2', '3', '1', null, null, '1', false ],
+ [ '5', '3', '1', null, null, '1', false ],
+ [ '3', '2', '-6', null, null, '2', false ],
+ [ '5', '2', '-6', null, null, '4', false ],
+ [ '6', '2', '1', null, null, '5', false ],
+ [ '8', '3', '1', null, null, '4', false ],
+ [ '9', '2', '-10', null, null, '8', false ],
+ [ '7', '3', '-10', null, null, '5', false ],
+ [ '-2', '3', '-10', null, null, '-4', false ],
+ // Clamping.
+ [ '0', '2', '-1', null, null, '-1', false ],
+ [ '10', '2', '0', '4', '10', '0', false ],
+ [ '10', '2', '0', '4', '5', '0', false ],
+ // value = "" (default will be 50).
+ [ '', null, null, null, null, '49', false ],
+ // With step = 'any'.
+ [ '0', 'any', null, null, 1, null, true ],
+ [ '0', 'ANY', null, null, 1, null, true ],
+ [ '0', 'AnY', null, null, 1, null, true ],
+ [ '0', 'aNy', null, null, 1, null, true ],
+ // With @value = step base.
+ [ '1', '2', null, null, null, '1', false ],
+ ]},
+ { type: 'date', data: [
+ // Regular case.
+ [ '2012-07-09', null, null, null, null, '2012-07-08', false ],
+ // Argument testing.
+ [ '2012-07-09', null, null, null, 1, '2012-07-08', false ],
+ [ '2012-07-09', null, null, null, 5, '2012-07-04', false ],
+ [ '2012-07-09', null, null, null, -1, '2012-07-10', false ],
+ [ '2012-07-09', null, null, null, 0, '2012-07-09', false ],
+ // Month/Year wrapping.
+ [ '2012-08-01', null, null, null, 1, '2012-07-31', false ],
+ [ '1969-01-02', null, null, null, 4, '1968-12-29', false ],
+ [ '1969-01-01', null, null, null, -365, '1970-01-01', false ],
+ [ '2012-02-29', null, null, null, -1, '2012-03-01', false ],
+ // Float values are rounded to integer (1.1 -> 1).
+ [ '2012-01-02', null, null, null, 1.1, '2012-01-01', false ],
+ [ '2012-01-02', null, null, null, 1.9, '2012-01-01', false ],
+ // With step values.
+ [ '2012-01-03', '0.5', null, null, null, '2012-01-02', false ],
+ [ '2012-01-02', '0.5', null, null, null, '2012-01-01', false ],
+ [ '2012-01-01', '2', null, null, null, '2011-12-30', false ],
+ [ '2012-01-02', '0.25',null, null, 4, '2011-12-29', false ],
+ [ '2012-01-15', '1.1', '2012-01-01', null, 1, '2012-01-14', false ],
+ [ '2012-01-12', '1.1', '2012-01-01', null, 2, '2012-01-10', false ],
+ [ '2012-01-23', '1.1', '2012-01-01', null, 10, '2012-01-13', false ],
+ [ '2012-01-23', '1.1', '2012-01-01', null, 11, '2012-01-12', false ],
+ [ '1968-01-12', '1.1', '1968-01-01', null, 8, '1968-01-04', false ],
+ // step = 0 isn't allowed (-> step = 1).
+ [ '2012-01-02', '0', null, null, null, '2012-01-01', false ],
+ // step < 0 isn't allowed (-> step = 1).
+ [ '2012-01-02', '-1', null, null, null, '2012-01-01', false ],
+ // step = NaN isn't allowed (-> step = 1).
+ [ '2012-01-02', 'foo', null, null, null, '2012-01-01', false ],
+ // Min values testing.
+ [ '2012-01-03', '1', 'foo', null, 2, '2012-01-01', false ],
+ [ '2012-01-02', '1', '2012-01-01', null, null, '2012-01-01', false ],
+ [ '2012-01-01', '1', '2012-01-01', null, null, '2012-01-01', false ],
+ [ '2012-01-01', '1', '2012-01-10', null, 1, '2012-01-01', false ],
+ [ '2012-01-05', '3', '2012-01-01', null, null, '2012-01-04', false ],
+ [ '1969-01-01', '5', '1969-01-01', '1969-01-02', null, '1969-01-01', false ],
+ // Max values testing.
+ [ '2012-01-02', '1', null, 'foo', null, '2012-01-01', false ],
+ [ '2012-01-02', null, null, '2012-01-05', null, '2012-01-01', false ],
+ [ '2012-01-03', null, null, '2012-01-03', null, '2012-01-02', false ],
+ [ '2012-01-07', null, null, '2012-01-04', 4, '2012-01-03', false ],
+ [ '2012-01-07', '2', null, '2012-01-04', 3, '2012-01-01', false ],
+ // Step mismatch.
+ [ '2012-01-04', '2', '2012-01-01', null, null, '2012-01-03', false ],
+ [ '2012-01-06', '2', '2012-01-01', null, 2, '2012-01-03', false ],
+ [ '2012-01-05', '2', '2012-01-04', '2012-01-08', null, '2012-01-04', false ],
+ [ '1970-01-04', '2', null, null, null, '1970-01-02', false ],
+ [ '1970-01-09', '3', null, null, null, '1970-01-06', false ],
+ // Clamping.
+ [ '2012-05-01', null, null, '2012-01-05', null, '2012-01-05', false ],
+ [ '1970-01-05', '2', '1970-01-02', '1970-01-05', null, '1970-01-04', false ],
+ [ '1970-01-01', '5', '1970-01-02', '1970-01-09', 10, '1970-01-01', false ],
+ [ '1970-01-07', '5', '1969-12-27', '1970-01-06', 2, '1970-01-01', false ],
+ [ '1970-03-08', '3', '1970-02-01', '1970-02-07', 15, '1970-02-01', false ],
+ [ '1970-01-10', '3', '1970-01-01', '1970-01-06', 2, '1970-01-04', false ],
+ // value = "" (NaN).
+ [ '', null, null, null, null, '1969-12-31', false ],
+ // With step = 'any'.
+ [ '2012-01-01', 'any', null, null, 1, null, true ],
+ [ '2012-01-01', 'ANY', null, null, 1, null, true ],
+ [ '2012-01-01', 'AnY', null, null, 1, null, true ],
+ [ '2012-01-01', 'aNy', null, null, 1, null, true ],
+ ]},
+ { type: 'time', data: [
+ // Regular case.
+ [ '16:39', null, null, null, null, '16:38', false ],
+ // Argument testing.
+ [ '16:40', null, null, null, 1, '16:39', false ],
+ [ '16:40', null, null, null, 5, '16:35', false ],
+ [ '16:40', null, null, null, -1, '16:41', false ],
+ [ '16:40', null, null, null, 0, '16:40', false ],
+ // hour/minutes/seconds wrapping.
+ [ '05:00', null, null, null, null, '04:59', false ],
+ [ '05:00:00', 1, null, null, null, '04:59:59', false ],
+ [ '05:00:00', 0.1, null, null, null, '04:59:59.900', false ],
+ [ '05:00:00', 0.01, null, null, null, '04:59:59.990', false ],
+ [ '05:00:00', 0.001, null, null, null, '04:59:59.999', false ],
+ // stepDown() on '00:00' gives '23:59'.
+ [ '00:00', null, null, null, 1, '23:59', false ],
+ [ '00:00', null, null, null, 3, '23:57', false ],
+ // Some random step values..
+ [ '16:56', '0.5', null, null, null, '16:55:59.500', false ],
+ [ '16:56', '2', null, null, null, '16:55:58', false ],
+ [ '16:56', '0.25',null, null, 4, '16:55:59', false ],
+ [ '16:57', '1.1', '16:00', null, 1, '16:56:59.900', false ],
+ [ '16:57', '1.1', '16:00', null, 2, '16:56:58.800', false ],
+ [ '16:57', '1.1', '16:00', null, 10, '16:56:50', false ],
+ [ '16:57', '1.1', '16:00', null, 11, '16:56:48.900', false ],
+ [ '16:57', '1.1', '16:00', null, 8, '16:56:52.200', false ],
+ // Invalid @step, means that we use the default value.
+ [ '17:01', '0', null, null, null, '17:00', false ],
+ [ '17:01', '-1', null, null, null, '17:00', false ],
+ [ '17:01', 'foo', null, null, null, '17:00', false ],
+ // Min values testing.
+ [ '17:02', '60', 'foo', null, 2, '17:00', false ],
+ [ '17:10', '60', '17:09', null, null, '17:09', false ],
+ [ '17:10', '60', '17:10', null, null, '17:10', false ],
+ [ '17:10', '60', '17:30', null, 1, '17:10', false ],
+ [ '17:10', '180', '17:05', null, null, '17:08', false ],
+ [ '17:10', '300', '17:10', '17:11', null, '17:10', false ],
+ // Max values testing.
+ [ '17:15', '60', null, 'foo', null, '17:14', false ],
+ [ '17:15', null, null, '17:20', null, '17:14', false ],
+ [ '17:15', null, null, '17:15', null, '17:14', false ],
+ [ '17:15', null, null, '17:13', 4, '17:11', false ],
+ [ '17:15', '120', null, '17:13', 3, '17:09', false ],
+ // Step mismatch.
+ [ '17:19', '120', '17:10', null, null, '17:18', false ],
+ [ '17:19', '120', '17:10', null, 2, '17:16', false ],
+ [ '17:19', '120', '17:18', '17:25', null, '17:18', false ],
+ [ '17:19', '120', null, null, null, '17:17', false ],
+ [ '17:19', '180', null, null, null, '17:16', false ],
+ // Clamping.
+ [ '17:22', null, null, '17:11', null, '17:11', false ],
+ [ '17:22', '120', '17:20', '17:22', null, '17:20', false ],
+ [ '17:22', '300', '17:12', '17:20', 10, '17:12', false ],
+ [ '17:22', '300', '17:18', '17:20', 2, '17:18', false ],
+ [ '17:22', '180', '17:00', '17:20', 15, '17:00', false ],
+ [ '17:22', '180', '17:10', '17:20', 2, '17:16', false ],
+ // value = "" (NaN).
+ [ '', null, null, null, null, '23:59', false ],
+ // With step = 'any'.
+ [ '17:26', 'any', null, null, 1, null, true ],
+ [ '17:26', 'ANY', null, null, 1, null, true ],
+ [ '17:26', 'AnY', null, null, 1, null, true ],
+ [ '17:26', 'aNy', null, null, 1, null, true ],
+ ]},
+ { type: 'month', data: [
+ // Regular case.
+ [ '2016-08', null, null, null, null, '2016-07', false ],
+ // Argument testing.
+ [ '2016-08', null, null, null, 1, '2016-07', false ],
+ [ '2016-08', null, null, null, 5, '2016-03', false ],
+ [ '2016-08', null, null, null, -1, '2016-09', false ],
+ [ '2016-08', null, null, null, 0, '2016-08', false ],
+ // Month/Year wrapping.
+ [ '2016-01', null, null, null, 1, '2015-12', false ],
+ [ '1969-02', null, null, null, 4, '1968-10', false ],
+ [ '1969-01', null, null, null, -12, '1970-01', false ],
+ // Float values are rounded to integer (1.1 -> 1).
+ [ '2016-08', null, null, null, 1.1, '2016-07', false ],
+ [ '2016-01', null, null, null, 1.9, '2015-12', false ],
+ // With step values.
+ [ '2016-03', '0.5', null, null, null, '2016-02', false ],
+ [ '2016-03', '2', null, null, null, '2016-01', false ],
+ [ '2016-03', '0.25',null, null, 4, '2015-11', false ],
+ [ '2016-12', '1.1', '2016-01', null, 1, '2016-11', false ],
+ [ '2016-12', '1.1', '2016-01', null, 2, '2016-10', false ],
+ [ '2016-12', '1.1', '2016-01', null, 10, '2016-02', false ],
+ [ '2016-12', '1.1', '2016-01', null, 12, '2016-01', false ],
+ [ '1968-12', '1.1', '1968-01', null, 8, '1968-04', false ],
+ // step = 0 isn't allowed (-> step = 1).
+ [ '2016-02', '0', null, null, null, '2016-01', false ],
+ // step < 0 isn't allowed (-> step = 1).
+ [ '2016-02', '-1', null, null, null, '2016-01', false ],
+ // step = NaN isn't allowed (-> step = 1).
+ [ '2016-02', 'foo', null, null, null, '2016-01', false ],
+ // Min values testing.
+ [ '2016-03', '1', 'foo', null, 2, '2016-01', false ],
+ [ '2016-02', '1', '2016-01', null, null, '2016-01', false ],
+ [ '2016-01', '1', '2016-01', null, null, '2016-01', false ],
+ [ '2016-01', '1', '2016-01', null, 1, '2016-01', false ],
+ [ '2016-05', '3', '2016-01', null, null, '2016-04', false ],
+ [ '1969-01', '5', '1969-01', '1969-02', null, '1969-01', false ],
+ // Max values testing.
+ [ '2016-02', '1', null, 'foo', null, '2016-01', false ],
+ [ '2016-02', null, null, '2016-05', null, '2016-01', false ],
+ [ '2016-03', null, null, '2016-03', null, '2016-02', false ],
+ [ '2016-07', null, null, '2016-04', 4, '2016-03', false ],
+ [ '2016-07', '2', null, '2016-04', 3, '2016-01', false ],
+ // Step mismatch.
+ [ '2016-04', '2', '2016-01', null, null, '2016-03', false ],
+ [ '2016-06', '2', '2016-01', null, 2, '2016-03', false ],
+ [ '2016-05', '2', '2016-04', '2016-08', null, '2016-04', false ],
+ [ '1970-04', '2', null, null, null, '1970-02', false ],
+ [ '1970-09', '3', null, null, null, '1970-06', false ],
+ // Clamping.
+ [ '2016-05', null, null, '2016-01', null, '2016-01', false ],
+ [ '1970-05', '2', '1970-02', '1970-05', null, '1970-04', false ],
+ [ '1970-01', '5', '1970-02', '1970-09', 10, '1970-01', false ],
+ [ '1970-07', '5', '1969-12', '1970-10', 2, '1969-12', false ],
+ [ '1970-08', '3', '1970-01', '1970-07', 15, '1970-01', false ],
+ [ '1970-10', '3', '1970-01', '1970-06', 2, '1970-04', false ],
+ // value = "" (NaN).
+ [ '', null, null, null, null, '1969-12', false ],
+ // With step = 'any'.
+ [ '2016-01', 'any', null, null, 1, null, true ],
+ [ '2016-01', 'ANY', null, null, 1, null, true ],
+ [ '2016-01', 'AnY', null, null, 1, null, true ],
+ [ '2016-01', 'aNy', null, null, 1, null, true ],
+ ]},
+ { type: 'week', data: [
+ // Regular case.
+ [ '2016-W40', null, null, null, null, '2016-W39', false ],
+ // Argument testing.
+ [ '2016-W40', null, null, null, 1, '2016-W39', false ],
+ [ '2016-W40', null, null, null, 5, '2016-W35', false ],
+ [ '2016-W40', null, null, null, -1, '2016-W41', false ],
+ [ '2016-W40', null, null, null, 0, '2016-W40', false ],
+ // Week/Year wrapping.
+ [ '2016-W01', null, null, null, 1, '2015-W53', false ],
+ [ '1969-W02', null, null, null, 4, '1968-W50', false ],
+ [ '1969-W01', null, null, null, -52, '1970-W01', false ],
+ // Float values are rounded to integer (1.1 -> 1).
+ [ '2016-W40', null, null, null, 1.1, '2016-W39', false ],
+ [ '2016-W01', null, null, null, 1.9, '2015-W53', false ],
+ // With step values.
+ [ '2016-W03', '0.5', null, null, null, '2016-W02', false ],
+ [ '2016-W03', '2', null, null, null, '2016-W01', false ],
+ [ '2016-W03', '0.25', null, null, 4, '2015-W52', false ],
+ [ '2016-W52', '1.1', '2016-W01', null, 1, '2016-W51', false ],
+ [ '2016-W52', '1.1', '2016-W01', null, 2, '2016-W50', false ],
+ [ '2016-W52', '1.1', '2016-W01', null, 10, '2016-W42', false ],
+ [ '2016-W52', '1.1', '2016-W01', null, 52, '2016-W01', false ],
+ [ '1968-W52', '1.1', '1968-W01', null, 8, '1968-W44', false ],
+ // step = 0 isn't allowed (-> step = 1).
+ [ '2016-W02', '0', null, null, null, '2016-W01', false ],
+ // step < 0 isn't allowed (-> step = 1).
+ [ '2016-W02', '-1', null, null, null, '2016-W01', false ],
+ // step = NaN isn't allowed (-> step = 1).
+ [ '2016-W02', 'foo', null, null, null, '2016-W01', false ],
+ // Min values testing.
+ [ '2016-W03', '1', 'foo', null, 2, '2016-W01', false ],
+ [ '2016-W02', '1', '2016-01', null, null, '2016-W01', false ],
+ [ '2016-W01', '1', '2016-W01', null, null, '2016-W01', false ],
+ [ '2016-W01', '1', '2016-W01', null, 1, '2016-W01', false ],
+ [ '2016-W05', '3', '2016-W01', null, null, '2016-W04', false ],
+ [ '1969-W01', '5', '1969-W01', '1969-W02', null, '1969-W01', false ],
+ // Max values testing.
+ [ '2016-W02', '1', null, 'foo', null, '2016-W01', false ],
+ [ '2016-W02', null, null, '2016-W05', null, '2016-W01', false ],
+ [ '2016-W03', null, null, '2016-W03', null, '2016-W02', false ],
+ [ '2016-W07', null, null, '2016-W04', 4, '2016-W03', false ],
+ [ '2016-W07', '2', null, '2016-W04', 3, '2016-W01', false ],
+ // Step mismatch.
+ [ '2016-W04', '2', '2016-W01', null, null, '2016-W03', false ],
+ [ '2016-W06', '2', '2016-W01', null, 2, '2016-W03', false ],
+ [ '2016-W05', '2', '2016-W04', '2016-W08', null, '2016-W04', false ],
+ [ '1970-W04', '2', null, null, null, '1970-W02', false ],
+ [ '1970-W09', '3', null, null, null, '1970-W06', false ],
+ // Clamping.
+ [ '2016-W05', null, null, '2016-W01', null, '2016-W01', false ],
+ [ '1970-W05', '2', '1970-W02', '1970-W05', null, '1970-W04', false ],
+ [ '1970-W01', '5', '1970-W02', '1970-W09', 10, '1970-W01', false ],
+ [ '1970-W07', '5', '1969-W52', '1970-W10', 2, '1969-W52', false ],
+ [ '1970-W08', '3', '1970-W01', '1970-W07', 15, '1970-W01', false ],
+ [ '1970-W10', '3', '1970-W01', '1970-W06', 2, '1970-W04', false ],
+ // value = "" (NaN).
+ [ '', null, null, null, null, '1970-W01', false ],
+ // With step = 'any'.
+ [ '2016-W01', 'any', null, null, 1, null, true ],
+ [ '2016-W01', 'ANY', null, null, 1, null, true ],
+ [ '2016-W01', 'AnY', null, null, 1, null, true ],
+ [ '2016-W01', 'aNy', null, null, 1, null, true ],
+ ]},
+ ];
+
+ for (var test of testData) {
+ for (var data of test.data) {
+ var element = document.createElement("input");
+ element.type = test.type;
+
+ if (data[1] != null) {
+ element.step = data[1];
+ }
+
+ if (data[2] != null) {
+ element.min = data[2];
+ }
+
+ if (data[3] != null) {
+ element.max = data[3];
+ }
+
+ // Set 'value' last for type=range, because the final sanitized value
+ // after setting 'step', 'min' and 'max' can be affected by the order in
+ // which those attributes are set. Setting 'value' last makes it simpler
+ // to reason about what the final value should be.
+ if (data[0] != null) {
+ element.setAttribute('value', data[0]);
+ }
+
+ var exceptionCaught = false;
+ try {
+ if (data[4] != null) {
+ element.stepDown(data[4]);
+ } else {
+ element.stepDown();
+ }
+
+ is(element.value, data[5], "The value for type=" + test.type + " should be " + data[5]);
+ } catch (e) {
+ exceptionCaught = true;
+ is(element.value, data[0], e.name + "The value should not have changed");
+ is(e.name, 'InvalidStateError',
+ "It should be a InvalidStateError exception.");
+ } finally {
+ is(exceptionCaught, data[6], "exception status should be " + data[6]);
+ }
+ }
+ }
+}
+
+function checkStepUp()
+{
+ // This testData is very similar to the one in checkStepDown with some changes
+ // relative to stepUp.
+ var testData = [
+ /* Initial value | step | min | max | stepUp arg | final value | exception */
+ { type: 'number', data: [
+ // Regular case.
+ [ '1', null, null, null, null, '2', false ],
+ // Argument testing.
+ [ '1', null, null, null, 1, '2', false ],
+ [ '9', null, null, null, 9, '18', false ],
+ [ '1', null, null, null, -1, '0', false ],
+ [ '1', null, null, null, 0, '1', false ],
+ // Float values are rounded to integer (1.1 -> 1).
+ [ '1', null, null, null, 1.1, '2', false ],
+ // With step values.
+ [ '1', '0.5', null, null, null, '1.5', false ],
+ [ '1', '0.25', null, null, 4, '2', false ],
+ // step = 0 isn't allowed (-> step = 1).
+ [ '1', '0', null, null, null, '2', false ],
+ // step < 0 isn't allowed (-> step = 1).
+ [ '1', '-1', null, null, null, '2', false ],
+ // step = NaN isn't allowed (-> step = 1).
+ [ '1', 'foo', null, null, null, '2', false ],
+ // Min values testing.
+ [ '1', '1', 'foo', null, null, '2', false ],
+ [ '1', null, '-10', null, null, '2', false ],
+ [ '1', null, '0', null, null, '2', false ],
+ [ '1', null, '10', null, null, '10', false ],
+ [ '1', null, '2', null, null, '2', false ],
+ [ '1', null, '1', null, null, '2', false ],
+ [ '0', null, '4', null, '5', '5', false ],
+ [ '0', '2', '5', null, '3', '5', false ],
+ // Max values testing.
+ [ '1', '1', null, 'foo', null, '2', false ],
+ [ '1', null, null, '10', null, '2', false ],
+ [ '1', null, null, '0', null, '1', false ],
+ [ '1', null, null, '-10', null, '1', false ],
+ [ '1', null, null, '1', null, '1', false ],
+ [ '-3', '5', '-10', '-3', null, '-3', false ],
+ // Step mismatch.
+ [ '1', '2', '0', null, null, '2', false ],
+ [ '1', '2', '0', null, '2', '4', false ],
+ [ '8', '2', null, '9', null, '8', false ],
+ [ '-3', '2', '-6', null, null, '-2', false ],
+ [ '9', '3', '-10', null, null, '11', false ],
+ [ '7', '3', '-10', null, null, '8', false ],
+ [ '7', '3', '5', null, null, '8', false ],
+ [ '9', '4', '3', null, null, '11', false ],
+ [ '-2', '3', '-6', null, null, '0', false ],
+ [ '7', '3', '6', null, null, '9', false ],
+ // Clamping.
+ [ '1', '2', '0', '3', null, '2', false ],
+ [ '0', '5', '1', '8', '10', '6', false ],
+ [ '-9', '3', '-8', '-1', '5', '-2', false ],
+ [ '-9', '3', '8', '15', '15', '14', false ],
+ [ '-1', '3', '-1', '4', '3', '2', false ],
+ [ '-3', '2', '-6', '-2', null, '-2', false ],
+ [ '-3', '2', '-6', '-1', null, '-2', false ],
+ // value = "" (NaN).
+ [ '', null, null, null, null, '1', false ],
+ [ '', null, null, null, null, '1', false ],
+ [ '', '2', null, null, null, '2', false ],
+ [ '', '2', '3', null, null, '3', false ],
+ [ '', null, '3', null, null, '3', false ],
+ [ '', '2', '3', '8', null, '3', false ],
+ [ '', null, '-10', '10', null, '1', false ],
+ [ '', '3', '-10', '10', null, '2', false ],
+ // With step = 'any'.
+ [ '0', 'any', null, null, 1, null, true ],
+ [ '0', 'ANY', null, null, 1, null, true ],
+ [ '0', 'AnY', null, null, 1, null, true ],
+ [ '0', 'aNy', null, null, 1, null, true ],
+ // With @value = step base.
+ [ '1', '2', null, null, null, '3', false ],
+ ]},
+ { type: 'range', data: [
+ // Regular case.
+ [ '1', null, null, null, null, '2', false ],
+ // Argument testing.
+ [ '1', null, null, null, 1, '2', false ],
+ [ '9', null, null, null, 9, '18', false ],
+ [ '1', null, null, null, -1, '0', false ],
+ [ '1', null, null, null, 0, '1', false ],
+ // Float values are rounded to integer (1.1 -> 1).
+ [ '1', null, null, null, 1.1, '2', false ],
+ // With step values.
+ [ '1', '0.5', null, null, null, '1.5', false ],
+ [ '1', '0.25', null, null, 4, '2', false ],
+ // step = 0 isn't allowed (-> step = 1).
+ [ '1', '0', null, null, null, '2', false ],
+ // step < 0 isn't allowed (-> step = 1).
+ [ '1', '-1', null, null, null, '2', false ],
+ // step = NaN isn't allowed (-> step = 1).
+ [ '1', 'foo', null, null, null, '2', false ],
+ // Min values testing.
+ [ '1', '1', 'foo', null, null, '2', false ],
+ [ '1', null, '-10', null, null, '2', false ],
+ [ '1', null, '0', null, null, '2', false ],
+ [ '1', null, '10', null, null, '11', false ],
+ [ '1', null, '2', null, null, '3', false ],
+ [ '1', null, '1', null, null, '2', false ],
+ [ '0', null, '4', null, '5', '9', false ],
+ [ '0', '2', '5', null, '3', '11', false ],
+ // Max values testing.
+ [ '1', '1', null, 'foo', null, '2', false ],
+ [ '1', null, null, '10', null, '2', false ],
+ [ '1', null, null, '0', null, '0', false ],
+ [ '1', null, null, '-10', null, '0', false ],
+ [ '1', null, null, '1', null, '1', false ],
+ [ '-3', '5', '-10', '-3', null, '-5', false ],
+ // Step mismatch.
+ [ '1', '2', '0', null, null, '4', false ],
+ [ '1', '2', '0', null, '2', '6', false ],
+ [ '8', '2', null, '9', null, '8', false ],
+ [ '-3', '2', '-6', null, null, '0', false ],
+ [ '9', '3', '-10', null, null, '11', false ],
+ [ '7', '3', '-10', null, null, '11', false ],
+ [ '7', '3', '5', null, null, '11', false ],
+ [ '9', '4', '3', null, null, '15', false ],
+ [ '-2', '3', '-6', null, null, '0', false ],
+ [ '7', '3', '6', null, null, '9', false ],
+ // Clamping.
+ [ '1', '2', '0', '3', null, '2', false ],
+ [ '0', '5', '1', '8', '10', '6', false ],
+ [ '-9', '3', '-8', '-1', '5', '-2', false ],
+ [ '-9', '3', '8', '15', '15', '14', false ],
+ [ '-1', '3', '-1', '4', '3', '2', false ],
+ [ '-3', '2', '-6', '-2', null, '-2', false ],
+ [ '-3', '2', '-6', '-1', null, '-2', false ],
+ // value = "" (default will be 50).
+ [ '', null, null, null, null, '51', false ],
+ // With step = 'any'.
+ [ '0', 'any', null, null, 1, null, true ],
+ [ '0', 'ANY', null, null, 1, null, true ],
+ [ '0', 'AnY', null, null, 1, null, true ],
+ [ '0', 'aNy', null, null, 1, null, true ],
+ // With @value = step base.
+ [ '1', '2', null, null, null, '3', false ],
+ ]},
+ { type: 'date', data: [
+ // Regular case.
+ [ '2012-07-09', null, null, null, null, '2012-07-10', false ],
+ // Argument testing.
+ [ '2012-07-09', null, null, null, 1, '2012-07-10', false ],
+ [ '2012-07-09', null, null, null, 9, '2012-07-18', false ],
+ [ '2012-07-09', null, null, null, -1, '2012-07-08', false ],
+ [ '2012-07-09', null, null, null, 0, '2012-07-09', false ],
+ // Month/Year wrapping.
+ [ '2012-07-31', null, null, null, 1, '2012-08-01', false ],
+ [ '1968-12-29', null, null, null, 4, '1969-01-02', false ],
+ [ '1970-01-01', null, null, null, -365, '1969-01-01', false ],
+ [ '2012-03-01', null, null, null, -1, '2012-02-29', false ],
+ // Float values are rounded to integer (1.1 -> 1).
+ [ '2012-01-01', null, null, null, 1.1, '2012-01-02', false ],
+ [ '2012-01-01', null, null, null, 1.9, '2012-01-02', false ],
+ // With step values.
+ [ '2012-01-01', '0.5', null, null, null, '2012-01-02', false ],
+ [ '2012-01-01', '2', null, null, null, '2012-01-03', false ],
+ [ '2012-01-01', '0.25', null, null, 4, '2012-01-05', false ],
+ [ '2012-01-01', '1.1', '2012-01-01', null, 1, '2012-01-02', false ],
+ [ '2012-01-01', '1.1', '2012-01-01', null, 2, '2012-01-03', false ],
+ [ '2012-01-01', '1.1', '2012-01-01', null, 10, '2012-01-11', false ],
+ [ '2012-01-01', '1.1', '2012-01-01', null, 11, '2012-01-12', false ],
+ // step = 0 isn't allowed (-> step = 1).
+ [ '2012-01-01', '0', null, null, null, '2012-01-02', false ],
+ // step < 0 isn't allowed (-> step = 1).
+ [ '2012-01-01', '-1', null, null, null, '2012-01-02', false ],
+ // step = NaN isn't allowed (-> step = 1).
+ [ '2012-01-01', 'foo', null, null, null, '2012-01-02', false ],
+ // Min values testing.
+ [ '2012-01-01', '1', 'foo', null, null, '2012-01-02', false ],
+ [ '2012-01-01', null, '2011-12-01', null, null, '2012-01-02', false ],
+ [ '2012-01-01', null, '2012-01-02', null, null, '2012-01-02', false ],
+ [ '2012-01-01', null, '2012-01-01', null, null, '2012-01-02', false ],
+ [ '2012-01-01', null, '2012-01-04', null, 4, '2012-01-05', false ],
+ [ '2012-01-01', '2', '2012-01-04', null, 3, '2012-01-06', false ],
+ // Max values testing.
+ [ '2012-01-01', '1', null, 'foo', 2, '2012-01-03', false ],
+ [ '2012-01-01', '1', null, '2012-01-10', 1, '2012-01-02', false ],
+ [ '2012-01-02', null, null, '2012-01-01', null, '2012-01-02', false ],
+ [ '2012-01-02', null, null, '2012-01-02', null, '2012-01-02', false ],
+ [ '1969-01-02', '5', '1969-01-01', '1969-01-02', null, '1969-01-02', false ],
+ // Step mismatch.
+ [ '2012-01-02', '2', '2012-01-01', null, null, '2012-01-03', false ],
+ [ '2012-01-02', '2', '2012-01-01', null, 2, '2012-01-05', false ],
+ [ '2012-01-05', '2', '2012-01-01', '2012-01-06', null, '2012-01-05', false ],
+ [ '1970-01-02', '2', null, null, null, '1970-01-04', false ],
+ [ '1970-01-05', '3', null, null, null, '1970-01-08', false ],
+ [ '1970-01-03', '3', null, null, null, '1970-01-06', false ],
+ [ '1970-01-03', '3', '1970-01-02', null, null, '1970-01-05', false ],
+ // Clamping.
+ [ '2012-01-01', null, '2012-01-31', null, null, '2012-01-31', false ],
+ [ '1970-01-02', '2', '1970-01-01', '1970-01-04', null, '1970-01-03', false ],
+ [ '1970-01-01', '5', '1970-01-02', '1970-01-09', 10, '1970-01-07', false ],
+ [ '1969-12-28', '5', '1969-12-29', '1970-01-06', 3, '1970-01-03', false ],
+ [ '1970-01-01', '3', '1970-02-01', '1970-02-07', 15, '1970-02-07', false ],
+ [ '1970-01-01', '3', '1970-01-01', '1970-01-06', 2, '1970-01-04', false ],
+ // value = "" (NaN).
+ [ '', null, null, null, null, '1970-01-02', false ],
+ // With step = 'any'.
+ [ '2012-01-01', 'any', null, null, 1, null, true ],
+ [ '2012-01-01', 'ANY', null, null, 1, null, true ],
+ [ '2012-01-01', 'AnY', null, null, 1, null, true ],
+ [ '2012-01-01', 'aNy', null, null, 1, null, true ],
+ ]},
+ { type: 'time', data: [
+ // Regular case.
+ [ '16:39', null, null, null, null, '16:40', false ],
+ // Argument testing.
+ [ '16:40', null, null, null, 1, '16:41', false ],
+ [ '16:40', null, null, null, 5, '16:45', false ],
+ [ '16:40', null, null, null, -1, '16:39', false ],
+ [ '16:40', null, null, null, 0, '16:40', false ],
+ // hour/minutes/seconds wrapping.
+ [ '04:59', null, null, null, null, '05:00', false ],
+ [ '04:59:59', 1, null, null, null, '05:00', false ],
+ [ '04:59:59.900', 0.1, null, null, null, '05:00', false ],
+ [ '04:59:59.990', 0.01, null, null, null, '05:00', false ],
+ [ '04:59:59.999', 0.001, null, null, null, '05:00', false ],
+ // stepUp() on '23:59' gives '00:00'.
+ [ '23:59', null, null, null, 1, '00:00', false ],
+ [ '23:59', null, null, null, 3, '00:02', false ],
+ // Some random step values..
+ [ '16:56', '0.5', null, null, null, '16:56:00.500', false ],
+ [ '16:56', '2', null, null, null, '16:56:02', false ],
+ [ '16:56', '0.25',null, null, 4, '16:56:01', false ],
+ [ '16:57', '1.1', '16:00', null, 1, '16:57:01', false ],
+ [ '16:57', '1.1', '16:00', null, 2, '16:57:02.100', false ],
+ [ '16:57', '1.1', '16:00', null, 10, '16:57:10.900', false ],
+ [ '16:57', '1.1', '16:00', null, 11, '16:57:12', false ],
+ [ '16:57', '1.1', '16:00', null, 8, '16:57:08.700', false ],
+ // Invalid @step, means that we use the default value.
+ [ '17:01', '0', null, null, null, '17:02', false ],
+ [ '17:01', '-1', null, null, null, '17:02', false ],
+ [ '17:01', 'foo', null, null, null, '17:02', false ],
+ // Min values testing.
+ [ '17:02', '60', 'foo', null, 2, '17:04', false ],
+ [ '17:10', '60', '17:09', null, null, '17:11', false ],
+ [ '17:10', '60', '17:10', null, null, '17:11', false ],
+ [ '17:10', '60', '17:30', null, 1, '17:30', false ],
+ [ '17:10', '180', '17:05', null, null, '17:11', false ],
+ [ '17:10', '300', '17:10', '17:11', null,'17:10', false ],
+ // Max values testing.
+ [ '17:15', '60', null, 'foo', null, '17:16', false ],
+ [ '17:15', null, null, '17:20', null, '17:16', false ],
+ [ '17:15', null, null, '17:15', null, '17:15', false ],
+ [ '17:15', null, null, '17:13', 4, '17:15', false ],
+ [ '17:15', '120', null, '17:13', 3, '17:15', false ],
+ // Step mismatch.
+ [ '17:19', '120', '17:10', null, null, '17:20', false ],
+ [ '17:19', '120', '17:10', null, 2, '17:22', false ],
+ [ '17:19', '120', '17:18', '17:25', null, '17:20', false ],
+ [ '17:19', '120', null, null, null, '17:21', false ],
+ [ '17:19', '180', null, null, null, '17:22', false ],
+ // Clamping.
+ [ '17:22', null, null, '17:11', null, '17:22', false ],
+ [ '17:22', '120', '17:20', '17:22', null, '17:22', false ],
+ [ '17:22', '300', '17:12', '17:20', 10, '17:22', false ],
+ [ '17:22', '300', '17:18', '17:20', 2, '17:22', false ],
+ [ '17:22', '180', '17:00', '17:20', 15, '17:22', false ],
+ [ '17:22', '180', '17:10', '17:20', 2, '17:22', false ],
+ // value = "" (NaN).
+ [ '', null, null, null, null, '00:01', false ],
+ // With step = 'any'.
+ [ '17:26', 'any', null, null, 1, null, true ],
+ [ '17:26', 'ANY', null, null, 1, null, true ],
+ [ '17:26', 'AnY', null, null, 1, null, true ],
+ [ '17:26', 'aNy', null, null, 1, null, true ],
+ ]},
+ { type: 'month', data: [
+ // Regular case.
+ [ '2016-08', null, null, null, null, '2016-09', false ],
+ // Argument testing.
+ [ '2016-08', null, null, null, 1, '2016-09', false ],
+ [ '2016-08', null, null, null, 9, '2017-05', false ],
+ [ '2016-08', null, null, null, -1, '2016-07', false ],
+ [ '2016-08', null, null, null, 0, '2016-08', false ],
+ // Month/Year wrapping.
+ [ '2015-12', null, null, null, 1, '2016-01', false ],
+ [ '1968-12', null, null, null, 4, '1969-04', false ],
+ [ '1970-01', null, null, null, -12, '1969-01', false ],
+ // Float values are rounded to integer (1.1 -> 1).
+ [ '2016-01', null, null, null, 1.1, '2016-02', false ],
+ [ '2016-01', null, null, null, 1.9, '2016-02', false ],
+ // With step values.
+ [ '2016-01', '0.5', null, null, null, '2016-02', false ],
+ [ '2016-01', '2', null, null, null, '2016-03', false ],
+ [ '2016-01', '0.25', null, null, 4, '2016-05', false ],
+ [ '2016-01', '1.1', '2016-01', null, 1, '2016-02', false ],
+ [ '2016-01', '1.1', '2016-01', null, 2, '2016-03', false ],
+ [ '2016-01', '1.1', '2016-01', null, 10, '2016-11', false ],
+ [ '2016-01', '1.1', '2016-01', null, 11, '2016-12', false ],
+ // step = 0 isn't allowed (-> step = 1).
+ [ '2016-01', '0', null, null, null, '2016-02', false ],
+ // step < 0 isn't allowed (-> step = 1).
+ [ '2016-01', '-1', null, null, null, '2016-02', false ],
+ // step = NaN isn't allowed (-> step = 1).
+ [ '2016-01', 'foo', null, null, null, '2016-02', false ],
+ // Min values testing.
+ [ '2016-01', '1', 'foo', null, null, '2016-02', false ],
+ [ '2016-01', null, '2015-12', null, null, '2016-02', false ],
+ [ '2016-01', null, '2016-02', null, null, '2016-02', false ],
+ [ '2016-01', null, '2016-01', null, null, '2016-02', false ],
+ [ '2016-01', null, '2016-04', null, 4, '2016-05', false ],
+ [ '2016-01', '2', '2016-04', null, 3, '2016-06', false ],
+ // Max values testing.
+ [ '2016-01', '1', null, 'foo', 2, '2016-03', false ],
+ [ '2016-01', '1', null, '2016-02', 1, '2016-02', false ],
+ [ '2016-02', null, null, '2016-01', null, '2016-02', false ],
+ [ '2016-02', null, null, '2016-02', null, '2016-02', false ],
+ [ '1969-02', '5', '1969-01', '1969-02', null, '1969-02', false ],
+ // Step mismatch.
+ [ '2016-02', '2', '2016-01', null, null, '2016-03', false ],
+ [ '2016-02', '2', '2016-01', null, 2, '2016-05', false ],
+ [ '2016-05', '2', '2016-01', '2016-06', null, '2016-05', false ],
+ [ '1970-02', '2', null, null, null, '1970-04', false ],
+ [ '1970-05', '3', null, null, null, '1970-08', false ],
+ [ '1970-03', '3', null, null, null, '1970-06', false ],
+ [ '1970-03', '3', '1970-02', null, null, '1970-05', false ],
+ // Clamping.
+ [ '2016-01', null, '2016-12', null, null, '2016-12', false ],
+ [ '1970-02', '2', '1970-01', '1970-04', null, '1970-03', false ],
+ [ '1970-01', '5', '1970-02', '1970-09', 10, '1970-07', false ],
+ [ '1969-11', '5', '1969-12', '1970-06', 3, '1970-05', false ],
+ [ '1970-01', '3', '1970-02', '1971-07', 15, '1971-05', false ],
+ [ '1970-01', '3', '1970-01', '1970-06', 2, '1970-04', false ],
+ // value = "" (NaN).
+ [ '', null, null, null, null, '1970-02', false ],
+ // With step = 'any'.
+ [ '2016-01', 'any', null, null, 1, null, true ],
+ [ '2016-01', 'ANY', null, null, 1, null, true ],
+ [ '2016-01', 'AnY', null, null, 1, null, true ],
+ [ '2016-01', 'aNy', null, null, 1, null, true ],
+ ]},
+ { type: 'week', data: [
+ // Regular case.
+ [ '2016-W40', null, null, null, null, '2016-W41', false ],
+ // Argument testing.
+ [ '2016-W40', null, null, null, 1, '2016-W41', false ],
+ [ '2016-W40', null, null, null, 20, '2017-W08', false ],
+ [ '2016-W40', null, null, null, -1, '2016-W39', false ],
+ [ '2016-W40', null, null, null, 0, '2016-W40', false ],
+ // Week/Year wrapping.
+ [ '2015-W53', null, null, null, 1, '2016-W01', false ],
+ [ '1968-W52', null, null, null, 4, '1969-W04', false ],
+ [ '1970-W01', null, null, null, -52, '1969-W01', false ],
+ // Float values are rounded to integer (1.1 -> 1).
+ [ '2016-W01', null, null, null, 1.1, '2016-W02', false ],
+ [ '2016-W01', null, null, null, 1.9, '2016-W02', false ],
+ // With step values.
+ [ '2016-W01', '0.5', null, null, null, '2016-W02', false ],
+ [ '2016-W01', '2', null, null, null, '2016-W03', false ],
+ [ '2016-W01', '0.25', null, null, 4, '2016-W05', false ],
+ [ '2016-W01', '1.1', '2016-01', null, 1, '2016-W02', false ],
+ [ '2016-W01', '1.1', '2016-01', null, 2, '2016-W03', false ],
+ [ '2016-W01', '1.1', '2016-01', null, 10, '2016-W11', false ],
+ [ '2016-W01', '1.1', '2016-01', null, 20, '2016-W21', false ],
+ // step = 0 isn't allowed (-> step = 1).
+ [ '2016-W01', '0', null, null, null, '2016-W02', false ],
+ // step < 0 isn't allowed (-> step = 1).
+ [ '2016-W01', '-1', null, null, null, '2016-W02', false ],
+ // step = NaN isn't allowed (-> step = 1).
+ [ '2016-W01', 'foo', null, null, null, '2016-W02', false ],
+ // Min values testing.
+ [ '2016-W01', '1', 'foo', null, null, '2016-W02', false ],
+ [ '2016-W01', null, '2015-W53', null, null, '2016-W02', false ],
+ [ '2016-W01', null, '2016-W02', null, null, '2016-W02', false ],
+ [ '2016-W01', null, '2016-W01', null, null, '2016-W02', false ],
+ [ '2016-W01', null, '2016-W04', null, 4, '2016-W05', false ],
+ [ '2016-W01', '2', '2016-W04', null, 3, '2016-W06', false ],
+ // Max values testing.
+ [ '2016-W01', '1', null, 'foo', 2, '2016-W03', false ],
+ [ '2016-W01', '1', null, '2016-W02', 1, '2016-W02', false ],
+ [ '2016-W02', null, null, '2016-W01', null, '2016-W02', false ],
+ [ '2016-W02', null, null, '2016-W02', null, '2016-W02', false ],
+ [ '1969-W02', '5', '1969-W01', '1969-W02', null, '1969-W02', false ],
+ // Step mismatch.
+ [ '2016-W02', '2', '2016-W01', null, null, '2016-W03', false ],
+ [ '2016-W02', '2', '2016-W01', null, 2, '2016-W05', false ],
+ [ '2016-W05', '2', '2016-W01', '2016-W06', null, '2016-W05', false ],
+ [ '1970-W02', '2', null, null, null, '1970-W04', false ],
+ [ '1970-W05', '3', null, null, null, '1970-W08', false ],
+ [ '1970-W03', '3', null, null, null, '1970-W06', false ],
+ [ '1970-W03', '3', '1970-W02', null, null, '1970-W05', false ],
+ // Clamping.
+ [ '2016-W01', null, '2016-W52', null, null, '2016-W52', false ],
+ [ '1970-W02', '2', '1970-W01', '1970-W04', null, '1970-W03', false ],
+ [ '1970-W01', '5', '1970-W02', '1970-W09', 10, '1970-W07', false ],
+ [ '1969-W50', '5', '1969-W52', '1970-W06', 3, '1970-W05', false ],
+ [ '1970-W01', '3', '1970-W02', '1971-W07', 15, '1970-W44', false ],
+ [ '1970-W01', '3', '1970-W01', '1970-W06', 2, '1970-W04', false ],
+ // value = "" (NaN).
+ [ '', null, null, null, null, '1970-W02', false ],
+ // With step = 'any'.
+ [ '2016-W01', 'any', null, null, 1, null, true ],
+ [ '2016-W01', 'ANY', null, null, 1, null, true ],
+ [ '2016-W01', 'AnY', null, null, 1, null, true ],
+ [ '2016-W01', 'aNy', null, null, 1, null, true ],
+ ]},
+ ];
+
+ for (var test of testData) {
+ for (var data of test.data) {
+ var element = document.createElement("input");
+ element.type = test.type;
+
+ if (data[1] != null) {
+ element.step = data[1];
+ }
+
+ if (data[2] != null) {
+ element.min = data[2];
+ }
+
+ if (data[3] != null) {
+ element.max = data[3];
+ }
+
+ // Set 'value' last for type=range, because the final sanitized value
+ // after setting 'step', 'min' and 'max' can be affected by the order in
+ // which those attributes are set. Setting 'value' last makes it simpler
+ // to reason about what the final value should be.
+ if (data[0] != null) {
+ element.setAttribute('value', data[0]);
+ }
+
+ var exceptionCaught = false;
+ try {
+ if (data[4] != null) {
+ element.stepUp(data[4]);
+ } else {
+ element.stepUp();
+ }
+
+ is(element.value, data[5], "The value for type=" + test.type + " should be " + data[5]);
+ } catch (e) {
+ exceptionCaught = true;
+ is(element.value, data[0], e.name + "The value should not have changed");
+ is(e.name, 'InvalidStateError',
+ "It should be a InvalidStateError exception.");
+ } finally {
+ is(exceptionCaught, data[6], "exception status should be " + data[6]);
+ }
+ }
+ }
+}
+
+checkPresence();
+checkAvailability();
+
+checkStepDown();
+checkStepUp();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_submit_invalid_file.html b/dom/html/test/forms/test_submit_invalid_file.html
new file mode 100644
index 000000000..3941bf2b6
--- /dev/null
+++ b/dom/html/test/forms/test_submit_invalid_file.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=702949
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test invalid file submission</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=702949">Mozilla Bug 702949</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <form action='http://mochi.test:8888/chrome/dom/html/test/forms/submit_invalid_file.sjs' method='post' target='result'
+ enctype='multipart/form-data'>
+ <input type='file' name='file'>
+ </form>
+ <iframe name='result'></iframe>
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+ /*
+ * Test invalid file submission by submitting a file that has been deleted
+ * from the file system before the form has been submitted.
+ * The form submission triggers a sjs file that shows its output in a frame.
+ * That means the test might time out if it fails.
+ */
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ var FileUtils = SpecialPowers.Cu.import("resource://gre/modules/FileUtils.jsm").FileUtils;
+
+ var i = document.getElementsByTagName('input')[0];
+
+ var file = FileUtils.getDir("TmpD", [], false);
+ file.append("testfile");
+ file.createUnique(SpecialPowers.Ci.nsIFile.NORMAL_FILE_TYPE, 0644);
+
+ SpecialPowers.wrap(i).value = file.path;
+ file.remove(/* recursive = */ false);
+
+ document.getElementsByName('result')[0].addEventListener('load', function() {
+ is(window.frames[0].document.body.textContent, "SUCCESS");
+ SimpleTest.finish();
+ });
+ document.forms[0].submit();
+ });
+</script>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_textarea_attributes_reflection.html b/dom/html/test/forms/test_textarea_attributes_reflection.html
new file mode 100644
index 000000000..a285b9955
--- /dev/null
+++ b/dom/html/test/forms/test_textarea_attributes_reflection.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLTextAreaElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="../reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for HTMLTextAreaElement attributes reflection **/
+
+// .autofocus
+reflectBoolean({
+ element: document.createElement("textarea"),
+ attribute: "autofocus",
+});
+
+//.cols
+reflectUnsignedInt({
+ element: document.createElement("textarea"),
+ attribute: "cols",
+ nonZero: true,
+ defaultValue: 20,
+ fallback: true,
+});
+
+todo("dirName" in document.createElement("textarea"),
+ "dirName isn't implemented yet");
+
+// .disabled
+reflectBoolean({
+ element: document.createElement("textarea"),
+ attribute: "disabled",
+});
+
+// TODO: form (HTMLFormElement)
+
+// .maxLength
+reflectInt({
+ element: document.createElement("textarea"),
+ attribute: "maxLength",
+ nonNegative: true,
+});
+
+// .name
+reflectString({
+ element: document.createElement("textarea"),
+ attribute: "name",
+ otherValues: [ "isindex", "_charset_" ],
+});
+
+// .placeholder
+reflectString({
+ element: document.createElement("textarea"),
+ attribute: "placeholder",
+ otherValues: [ "foo\nbar", "foo\rbar", "foo\r\nbar" ],
+});
+
+// .readOnly
+reflectBoolean({
+ element: document.createElement("textarea"),
+ attribute: "readOnly",
+});
+
+// .required
+reflectBoolean({
+ element: document.createElement("textarea"),
+ attribute: "required",
+});
+
+// .rows
+reflectUnsignedInt({
+ element: document.createElement("textarea"),
+ attribute: "rows",
+ nonZero: true,
+ defaultValue: 2,
+ fallback: true,
+});
+
+// .wrap
+// TODO: make it an enumerated attributes limited to only known values, bug 670869.
+reflectString({
+ element: document.createElement("textarea"),
+ attribute: "wrap",
+ otherValues: [ "soft", "hard" ],
+});
+
+// .type doesn't reflect a content attribute.
+// .defaultValue doesn't reflect a content attribute.
+// .value doesn't reflect a content attribute.
+// .textLength doesn't reflect a content attribute.
+// .willValidate doesn't reflect a content attribute.
+// .validity doesn't reflect a content attribute.
+// .validationMessage doesn't reflect a content attribute.
+// .labels doesn't reflect a content attribute.
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_validation.html b/dom/html/test/forms/test_validation.html
new file mode 100644
index 000000000..ee0a93a99
--- /dev/null
+++ b/dom/html/test/forms/test_validation.html
@@ -0,0 +1,358 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=345624
+-->
+<head>
+ <title>Test for Bug 345624</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ input, textarea, fieldset, button, select, keygen, output, object { background-color: rgb(0,0,0) !important; }
+ :valid { background-color: rgb(0,255,0) !important; }
+ :invalid { background-color: rgb(255,0,0) !important; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=345624">Mozilla Bug 345624</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <fieldset id='f'></fieldset>
+ <input id='i' oninvalid="invalidEventHandler(event);">
+ <button id='b' oninvalid="invalidEventHandler(event);"></button>
+ <select id='s' oninvalid="invalidEventHandler(event);"></select>
+ <textarea id='t' oninvalid="invalidEventHandler(event);"></textarea>
+ <output id='o' oninvalid="invalidEventHandler(event);"></output>
+ <keygen id='k'></keygen>
+ <object id='obj'></object>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 345624 **/
+
+var gInvalid = false;
+
+function invalidEventHandler(aEvent)
+{
+ function checkInvalidEvent(aEvent)
+ {
+ is(aEvent.type, "invalid", "Invalid event type should be invalid");
+ ok(!aEvent.bubbles, "Invalid event should not bubble");
+ ok(aEvent.cancelable, "Invalid event should be cancelable");
+ }
+
+ checkInvalidEvent(aEvent);
+
+ gInvalid = true;
+}
+
+function checkConstraintValidationAPIExist(element)
+{
+ ok('willValidate' in element, "willValidate is not available in the DOM");
+ ok('validationMessage' in element, "validationMessage is not available in the DOM");
+ ok('validity' in element, "validity is not available in the DOM");
+
+ if ('validity' in element) {
+ validity = element.validity;
+ ok('valueMissing' in validity, "validity.valueMissing is not available in the DOM");
+ ok('typeMismatch' in validity, "validity.typeMismatch is not available in the DOM");
+ ok('badInput' in validity, "validity.badInput is not available in the DOM");
+ ok('patternMismatch' in validity, "validity.patternMismatch is not available in the DOM");
+ ok('tooLong' in validity, "validity.tooLong is not available in the DOM");
+ ok('rangeUnderflow' in validity, "validity.rangeUnderflow is not available in the DOM");
+ ok('rangeOverflow' in validity, "validity.rangeOverflow is not available in the DOM");
+ ok('stepMismatch' in validity, "validity.stepMismatch is not available in the DOM");
+ ok('customError' in validity, "validity.customError is not available in the DOM");
+ ok('valid' in validity, "validity.valid is not available in the DOM");
+ }
+}
+
+function checkConstraintValidationAPIDefaultValues(element)
+{
+ // Not checking willValidate because the default value depends of the element
+
+ is(element.validationMessage, "", "validationMessage default value should be empty string");
+
+ ok(!element.validity.valueMissing, "The element should not suffer from a constraint validation");
+ ok(!element.validity.typeMismatch, "The element should not suffer from a constraint validation");
+ ok(!element.validity.badInput, "The element should not suffer from a constraint validation");
+ ok(!element.validity.patternMismatch, "The element should not suffer from a constraint validation");
+ ok(!element.validity.tooLong, "The element should not suffer from a constraint validation");
+ ok(!element.validity.rangeUnderflow, "The element should not suffer from a constraint validation");
+ ok(!element.validity.rangeOverflow, "The element should not suffer from a constraint validation");
+ ok(!element.validity.stepMismatch, "The element should not suffer from a constraint validation");
+ ok(!element.validity.customError, "The element should not suffer from a constraint validation");
+ ok(element.validity.valid, "The element should be valid by default");
+
+ ok(element.checkValidity(), "The element should be valid by default");
+}
+
+function checkDefaultPseudoClass()
+{
+ is(window.getComputedStyle(document.getElementById('f'), null)
+ .getPropertyValue('background-color'), "rgb(0, 255, 0)",
+ ":valid should apply");
+
+ is(window.getComputedStyle(document.getElementById('o'), null)
+ .getPropertyValue('background-color'), "rgb(0, 255, 0)",
+ ":valid should apply");
+
+ is(window.getComputedStyle(document.getElementById('obj'), null)
+ .getPropertyValue('background-color'), "rgb(0, 0, 0)",
+ "Nor :valid and :invalid should apply");
+
+ todo_is(window.getComputedStyle(document.getElementById('k'), null)
+ .getPropertyValue('background-color'), "rgb(0, 0, 0)",
+ "Nor :valid and :invalid should apply");
+
+ is(window.getComputedStyle(document.getElementById('s'), null)
+ .getPropertyValue('background-color'), "rgb(0, 255, 0)",
+ ":valid pseudo-class should apply");
+
+ is(window.getComputedStyle(document.getElementById('i'), null)
+ .getPropertyValue('background-color'), "rgb(0, 255, 0)",
+ ":valid pseudo-class should apply");
+
+ is(window.getComputedStyle(document.getElementById('t'), null)
+ .getPropertyValue('background-color'), "rgb(0, 255, 0)",
+ ":valid pseudo-class should apply");
+
+ is(window.getComputedStyle(document.getElementById('b'), null)
+ .getPropertyValue('background-color'), "rgb(0, 255, 0)",
+ ":valid pseudo-class should apply");
+}
+
+function checkSpecificWillValidate()
+{
+ // fieldset, output, object, keygen (TODO) and select elements
+ ok(!document.getElementById('f').willValidate, "Fielset element should be barred from constraint validation");
+ ok(!document.getElementById('obj').willValidate, "Object element should be barred from constraint validation");
+ todo(!document.getElementById('k').willValidate, "Keygen element should be barred from constraint validation");
+ ok(document.getElementById('o').willValidate, "Output element should not be barred from constraint validation");
+ ok(document.getElementById('s').willValidate, "Select element should not be barred from constraint validation");
+
+ // input element
+ i = document.getElementById('i');
+ i.type = "hidden";
+ ok(!i.willValidate, "Hidden state input should be barred from constraint validation");
+ is(window.getComputedStyle(i, null).getPropertyValue('background-color'),
+ "rgb(0, 0, 0)", "Nor :valid and :invalid should apply");
+ i.type = "reset";
+ ok(!i.willValidate, "Reset button state input should be barred from constraint validation");
+ is(window.getComputedStyle(i, null).getPropertyValue('background-color'),
+ "rgb(0, 0, 0)", "Nor :valid and :invalid should apply");
+ i.type = "button";
+ ok(!i.willValidate, "Button state input should be barred from constraint validation");
+ is(window.getComputedStyle(i, null).getPropertyValue('background-color'),
+ "rgb(0, 0, 0)", "Nor :valid and :invalid should apply");
+ i.type = "image";
+ ok(i.willValidate, "Image state input should not be barred from constraint validation");
+ is(window.getComputedStyle(i, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid and :invalid should apply");
+ i.type = "submit";
+ ok(i.willValidate, "Submit state input should not be barred from constraint validation");
+ is(window.getComputedStyle(i, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid and :invalid should apply");
+ i.type = "number";
+ ok(i.willValidate, "Number state input should not be barred from constraint validation");
+ is(window.getComputedStyle(i, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+ i.type = "";
+ i.readOnly = 'true';
+ ok(!i.willValidate, "Readonly input should be barred from constraint validation");
+ is(window.getComputedStyle(i, null).getPropertyValue('background-color'),
+ "rgb(0, 0, 0)", "Nor :valid and :invalid should apply");
+ i.removeAttribute('readOnly');
+ ok(i.willValidate, "Default input element should not be barred from constraint validation");
+ is(window.getComputedStyle(i, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+
+ // button element
+ b = document.getElementById('b');
+ b.type = "reset";
+ ok(!b.willValidate, "Reset state button should be barred from constraint validation");
+ is(window.getComputedStyle(b, null).getPropertyValue('background-color'),
+ "rgb(0, 0, 0)", "Nor :valid and :invalid should apply");
+ b.type = "button";
+ ok(!b.willValidate, "Button state button should be barred from constraint validation");
+ is(window.getComputedStyle(b, null).getPropertyValue('background-color'),
+ "rgb(0, 0, 0)", "Nor :valid and :invalid should apply");
+ b.type = "submit";
+ ok(b.willValidate, "Submit state button should not be barred from constraint validation");
+ is(window.getComputedStyle(b, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid and :invalid should apply");
+ b.type = "";
+ ok(b.willValidate, "Default button element should not be barred from constraint validation");
+ is(window.getComputedStyle(b, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+
+ // textarea element
+ t = document.getElementById('t');
+ t.readOnly = true;
+ ok(!t.willValidate, "Readonly textarea should be barred from constraint validation");
+ is(window.getComputedStyle(t, null).getPropertyValue('background-color'),
+ "rgb(0, 0, 0)", "Nor :valid and :invalid should apply");
+ t.removeAttribute('readOnly');
+ ok(t.willValidate, "Default textarea element should not be barred from constraint validation");
+ is(window.getComputedStyle(t, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+
+ // TODO: PROGRESS
+ // TODO: METER
+}
+
+function checkCommonWillValidate(element)
+{
+ // Not checking the default value because it has been checked previously.
+
+ // Not checking output elements because they can't be disabled.
+ if (element.tagName != 'OUTPUT') {
+ element.disabled = true;
+ ok(!element.willValidate, "Disabled element should be barred from constraint validation");
+
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(0, 0, 0)", "Nor :valid and :invalid should apply");
+
+ element.removeAttribute('disabled');
+ }
+
+ // TODO: If an element has a datalist element ancestor, it is barred from constraint validation.
+}
+
+function checkCustomError(element, isBarred)
+{
+ element.setCustomValidity("message");
+ if (!isBarred) {
+ is(element.validationMessage, "message",
+ "When the element has a custom validity message, validation message should return it");
+ } else {
+ is(element.validationMessage, "",
+ "An element barred from constraint validation can't have a validation message");
+ }
+ ok(element.validity.customError, "The element should suffer from a custom error");
+ ok(!element.validity.valid, "The element should not be valid with a custom error");
+
+ if (element.tagName == "FIELDSET") {
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ isBarred ? "rgb(0, 255, 0)" : "rgb(255, 0, 0)",
+ ":invalid pseudo-classs should apply" + element.tagName);
+ }
+ else {
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ isBarred ? "rgb(0, 0, 0)" : "rgb(255, 0, 0)",
+ ":invalid pseudo-classs should apply" + element.tagName);
+ }
+
+ element.setCustomValidity("");
+ is(element.validationMessage, "", "The element should not have a validation message when reseted");
+ ok(!element.validity.customError, "The element should not suffer anymore from a custom error");
+ ok(element.validity.valid, "The element should now be valid");
+
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ isBarred && element.tagName != "FIELDSET" ? "rgb(0, 0, 0)" : "rgb(0, 255, 0)",
+ ":valid pseudo-classs should apply");
+}
+
+function checkCheckValidity(element)
+{
+ element.setCustomValidity("message");
+ ok(!element.checkValidity(), "checkValidity() should return false when the element is not valid");
+
+ ok(gInvalid, "Invalid event should have been handled");
+
+ gInvalid = false;
+ element.setCustomValidity("");
+
+ ok(element.checkValidity(), "Element should be valid");
+ ok(!gInvalid, "Invalid event should not have been handled");
+}
+
+function checkValidityStateObjectAliveWithoutElement(element)
+{
+ // We are creating a temporary element and getting it's ValidityState object.
+ // Then, we make sure it is removed by the garbage collector and we check the
+ // ValidityState default values (it should not crash).
+
+ var v = document.createElement(element).validity;
+ SpecialPowers.gc();
+
+ ok(!v.valueMissing,
+ "When the element is not alive, it shouldn't suffer from constraint validation");
+ ok(!v.typeMismatch,
+ "When the element is not alive, it shouldn't suffer from constraint validation");
+ ok(!v.badInput,
+ "When the element is not alive, it shouldn't suffer from constraint validation");
+ ok(!v.patternMismatch,
+ "When the element is not alive, it shouldn't suffer from constraint validation");
+ ok(!v.tooLong,
+ "When the element is not alive, it shouldn't suffer from constraint validation");
+ ok(!v.rangeUnderflow,
+ "When the element is not alive, it shouldn't suffer from constraint validation");
+ ok(!v.rangeOverflow,
+ "When the element is not alive, it shouldn't suffer from constraint validation");
+ ok(!v.stepMismatch,
+ "When the element is not alive, it shouldn't suffer from constraint validation");
+ ok(!v.customError,
+ "When the element is not alive, it shouldn't suffer from constraint validation");
+ ok(v.valid, "When the element is not alive, it should be valid");
+}
+
+checkConstraintValidationAPIExist(document.getElementById('f'));
+checkConstraintValidationAPIExist(document.getElementById('i'));
+checkConstraintValidationAPIExist(document.getElementById('b'));
+checkConstraintValidationAPIExist(document.getElementById('s'));
+checkConstraintValidationAPIExist(document.getElementById('t'));
+checkConstraintValidationAPIExist(document.getElementById('k'));
+checkConstraintValidationAPIExist(document.getElementById('o'));
+checkConstraintValidationAPIExist(document.getElementById('obj'));
+
+checkConstraintValidationAPIDefaultValues(document.getElementById('f'));
+checkConstraintValidationAPIDefaultValues(document.getElementById('i'));
+checkConstraintValidationAPIDefaultValues(document.getElementById('b'));
+checkConstraintValidationAPIDefaultValues(document.getElementById('s'));
+checkConstraintValidationAPIDefaultValues(document.getElementById('t'));
+checkConstraintValidationAPIDefaultValues(document.getElementById('k'));
+checkConstraintValidationAPIDefaultValues(document.getElementById('o'));
+checkConstraintValidationAPIDefaultValues(document.getElementById('obj'));
+
+checkDefaultPseudoClass();
+
+checkSpecificWillValidate();
+
+// Not checking button, fieldset, object and keygen
+// because they are always barred from constraint validation.
+checkCommonWillValidate(document.getElementById('i'));
+checkCommonWillValidate(document.getElementById('s'));
+checkCommonWillValidate(document.getElementById('t'));
+checkCommonWillValidate(document.getElementById('o'));
+
+/* TODO: add "keygen" element */
+checkCustomError(document.getElementById('i'), false);
+checkCustomError(document.getElementById('s'), false);
+checkCustomError(document.getElementById('t'), false);
+checkCustomError(document.getElementById('o'), false);
+checkCustomError(document.getElementById('b'), false);
+checkCustomError(document.getElementById('f'), true);
+checkCustomError(document.getElementById('obj'), true);
+
+// Not checking button, fieldset, object and keygen
+// because they are always barred from constraint validation.
+checkCheckValidity(document.getElementById('i'));
+checkCheckValidity(document.getElementById('s'));
+checkCheckValidity(document.getElementById('t'));
+checkCheckValidity(document.getElementById('o'));
+
+/* TODO: add "keygen" element */
+checkValidityStateObjectAliveWithoutElement("fieldset");
+checkValidityStateObjectAliveWithoutElement("input");
+checkValidityStateObjectAliveWithoutElement("button");
+checkValidityStateObjectAliveWithoutElement("select");
+checkValidityStateObjectAliveWithoutElement("textarea");
+checkValidityStateObjectAliveWithoutElement("output");
+checkValidityStateObjectAliveWithoutElement("object");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_validation_not_in_doc.html b/dom/html/test/forms/test_validation_not_in_doc.html
new file mode 100644
index 000000000..1500c6086
--- /dev/null
+++ b/dom/html/test/forms/test_validation_not_in_doc.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for constraint validation of form controls not in documents</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var input = document.createElement('input');
+ input.required = true;
+ assert_false(input.checkValidity());
+}, "Should validate input not in document");
+
+test(function() {
+ var textarea = document.createElement('textarea');
+ textarea.required = true;
+ assert_false(textarea.checkValidity());
+}, "Should validate textarea not in document");
+</script>
diff --git a/dom/html/test/forms/test_valueAsDate_pref.html b/dom/html/test/forms/test_valueAsDate_pref.html
new file mode 100644
index 000000000..8518c291b
--- /dev/null
+++ b/dom/html/test/forms/test_valueAsDate_pref.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=874640
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 874640</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 874640 **/
+ var states = [
+ // dom.experimental_forms, dom.forms.datepicker, dom.forms.datetime, expectedValueAsDate
+ [ 'true', 'true', 'true', 'true' ],
+ [ 'true', 'false', 'false', 'true' ],
+ [ 'false', 'true', 'false', 'true' ],
+ [ 'false', 'false', 'true', 'true' ],
+ [ 'false', 'false', 'false', 'false' ],
+ 'end'
+ ];
+
+ SimpleTest.waitForExplicitFinish();
+
+ function runTest(iframe) {
+ var state = states.shift();
+
+ if (state == 'end') {
+ SimpleTest.finish();
+ return;
+ }
+
+ SpecialPowers.pushPrefEnv({"set":[
+ ["dom.experimental_forms", state[0] === 'true'],
+ ["dom.forms.datepicker", state[1] === 'true'],
+ ["dom.forms.datetime", state[2] === 'true']]},
+ function() {
+ iframe.src = 'data:text/html,<script>' +
+ 'parent.is("valueAsDate" in document.createElement("input"), ' +
+ state[3] + ', "valueAsDate presence state should be ' + state[3] + '");' +
+ '<\/script>'
+ });
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=874640">Mozilla Bug 874640</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe onload='runTest(this);'></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/forms/test_valueasdate_attribute.html b/dom/html/test/forms/test_valueasdate_attribute.html
new file mode 100644
index 000000000..8c19fefd9
--- /dev/null
+++ b/dom/html/test/forms/test_valueasdate_attribute.html
@@ -0,0 +1,649 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=769370
+-->
+<head>
+ <title>Test for input.valueAsDate</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=769370">Mozilla Bug 769370</a>
+<iframe name="testFrame" style="display: none"></iframe>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 769370**/
+
+/**
+ * This test is checking .valueAsDate.
+ */
+
+var element = document.createElement("input");
+
+var validTypes =
+[
+ ["text", false],
+ ["password", false],
+ ["search", false],
+ ["tel", false],
+ ["email", false],
+ ["url", false],
+ ["hidden", false],
+ ["checkbox", false],
+ ["radio", false],
+ ["file", false],
+ ["submit", false],
+ ["image", false],
+ ["reset", false],
+ ["button", false],
+ ["number", false],
+ ["range", false],
+ ["date", true],
+ ["time", true],
+ ["color", false],
+ ["month", true],
+ ["week", true],
+ // TODO: temporary set to false until bug 888331 is fixed.
+ ["datetime-local", false],
+];
+
+function checkAvailability()
+{
+ for (let data of validTypes) {
+ var exceptionCatched = false;
+ element.type = data[0];
+ try {
+ element.valueAsDate;
+ } catch (e) {
+ exceptionCatched = true;
+ }
+ is(exceptionCatched, false,
+ "valueAsDate shouldn't throw exception on getting");
+
+ exceptionCatched = false;
+ try {
+ element.valueAsDate = new Date();
+ } catch (e) {
+ exceptionCatched = true;
+ }
+ is(exceptionCatched, !data[1], "valueAsDate for " + data[0] +
+ " availability is not correct");
+ }
+}
+
+function checkGarbageValues()
+{
+ for (let type of validTypes) {
+ if (!type[1]) {
+ continue;
+ }
+ type = type[0];
+
+ var element = document.createElement('input');
+ element.type = type;
+
+ element.value = "test";
+ element.valueAsDate = null;
+ is(element.value, "", "valueAsDate should set the value to the empty string");
+
+ element.value = "test";
+ element.valueAsDate = undefined;
+ is(element.value, "", "valueAsDate should set the value to the empty string");
+
+ element.value = "test";
+ element.valueAsDate = new Date(NaN);
+ is(element.value, "", "valueAsDate should set the value to the empty string");
+
+ var illegalValues = [
+ "foobar", 42, {}, function() { return 42; }, function() { return Date(); }
+ ];
+
+ for (let value of illegalValues) {
+ try {
+ var caught = false;
+ element.valueAsDate = value;
+ } catch(e) {
+ is(e.name, "TypeError", "Exception should be 'TypeError'.");
+ caught = true;
+ }
+ ok(caught, "Assigning " + value + " to .valueAsDate should throw");
+ }
+ }
+}
+
+function checkDateGet()
+{
+ var validData =
+ [
+ [ "2012-07-12", 1342051200000 ],
+ [ "1970-01-01", 0 ],
+ [ "1970-01-02", 86400000 ],
+ [ "1969-12-31", -86400000 ],
+ [ "0311-01-31", -52350451200000 ],
+ [ "275760-09-13", 8640000000000000 ],
+ [ "0001-01-01", -62135596800000 ],
+ [ "2012-02-29", 1330473600000 ],
+ [ "2011-02-28", 1298851200000 ],
+ ];
+
+ var invalidData =
+ [
+ [ "invaliddate" ],
+ [ "-001-12-31" ],
+ [ "901-12-31" ],
+ [ "1901-13-31" ],
+ [ "1901-12-32" ],
+ [ "1901-00-12" ],
+ [ "1901-01-00" ],
+ [ "1900-02-29" ],
+ [ "0000-01-01" ],
+ [ "" ],
+ // This date is valid for the input element, but is out of
+ // the date object range. In this case, on getting valueAsDate,
+ // a Date object will be created, but it will have a NaN internal value,
+ // and will return the string "Invalid Date".
+ [ "275760-09-14", true ],
+ ];
+
+ element.type = "date";
+ for (let data of validData) {
+ element.value = data[0];
+ is(element.valueAsDate.valueOf(), data[1],
+ "valueAsDate should return the " +
+ "valid date object representing this date");
+ }
+
+ for (let data of invalidData) {
+ element.value = data[0];
+ if (data[1]) {
+ is(String(element.valueAsDate), "Invalid Date",
+ "valueAsDate should return an invalid Date object " +
+ "when the element value is not a valid date");
+ } else {
+ is(element.valueAsDate, null,
+ "valueAsDate should return null " +
+ "when the element value is not a valid date");
+ }
+ }
+}
+
+function checkDateSet()
+{
+ var testData =
+ [
+ [ 1342051200000, "2012-07-12" ],
+ [ 0, "1970-01-01" ],
+ // Maximum valid date (limited by the ecma date object range).
+ [ 8640000000000000, "275760-09-13" ],
+ // Minimum valid date (limited by the input element minimum valid value).
+ [ -62135596800000 , "0001-01-01" ],
+ [ 1330473600000, "2012-02-29" ],
+ [ 1298851200000, "2011-02-28" ],
+ // "Values must be truncated to valid dates"
+ [ 42.1234, "1970-01-01" ],
+ [ 123.123456789123, "1970-01-01" ],
+ [ 1e-1, "1970-01-01" ],
+ [ 1298851200010, "2011-02-28" ],
+ [ -1, "1969-12-31" ],
+ [ -86400000, "1969-12-31" ],
+ [ 86400000, "1970-01-02" ],
+ // Negative years, this is out of range for the input element,
+ // the corresponding date string is the empty string
+ [ -62135596800001, "" ],
+ // Invalid dates.
+ ];
+
+ element.type = "date";
+ for (let data of testData) {
+ element.valueAsDate = new Date(data[0]);
+ is(element.value, data[1], "valueAsDate should set the value to "
+ + data[1]);
+ element.valueAsDate = new testFrame.Date(data[0]);
+ is(element.value, data[1], "valueAsDate with other-global date should " +
+ "set the value to " + data[1]);
+ }
+}
+
+function checkTimeGet()
+{
+ var tests = [
+ // Some invalid values to begin.
+ { value: "", result: null },
+ { value: "foobar", result: null },
+ { value: "00:", result: null },
+ { value: "24:00", result: null },
+ { value: "00:99", result: null },
+ { value: "00:00:", result: null },
+ { value: "00:00:99", result: null },
+ { value: "00:00:00:", result: null },
+ { value: "00:00:00.", result: null },
+ { value: "00:00:00.0000", result: null },
+ // Some simple valid values.
+ { value: "00:00", result: { time: 0, hours: 0, minutes: 0, seconds: 0, ms: 0 } },
+ { value: "00:01", result: { time: 60000, hours: 0, minutes: 1, seconds: 0, ms: 0 } },
+ { value: "01:00", result: { time: 3600000, hours: 1, minutes: 0, seconds: 0, ms: 0 } },
+ { value: "01:01", result: { time: 3660000, hours: 1, minutes: 1, seconds: 0, ms: 0 } },
+ { value: "13:37", result: { time: 49020000, hours: 13, minutes: 37, seconds: 0, ms: 0 } },
+ // Valid values including seconds.
+ { value: "00:00:01", result: { time: 1000, hours: 0, minutes: 0, seconds: 1, ms: 0 } },
+ { value: "13:37:42", result: { time: 49062000, hours: 13, minutes: 37, seconds: 42, ms: 0 } },
+ // Valid values including seconds fractions.
+ { value: "00:00:00.001", result: { time: 1, hours: 0, minutes: 0, seconds: 0, ms: 1 } },
+ { value: "00:00:00.123", result: { time: 123, hours: 0, minutes: 0, seconds: 0, ms: 123 } },
+ { value: "00:00:00.100", result: { time: 100, hours: 0, minutes: 0, seconds: 0, ms: 100 } },
+ { value: "00:00:00.000", result: { time: 0, hours: 0, minutes: 0, seconds: 0, ms: 0 } },
+ { value: "20:17:31.142", result: { time: 73051142, hours: 20, minutes: 17, seconds: 31, ms: 142 } },
+ // Highest possible value.
+ { value: "23:59:59.999", result: { time: 86399999, hours: 23, minutes: 59, seconds: 59, ms: 999 } },
+ // Some values with one or two digits for the fraction of seconds.
+ { value: "00:00:00.1", result: { time: 100, hours: 0, minutes: 0, seconds: 0, ms: 100 } },
+ { value: "00:00:00.14", result: { time: 140, hours: 0, minutes: 0, seconds: 0, ms: 140 } },
+ { value: "13:37:42.7", result: { time: 49062700, hours: 13, minutes: 37, seconds: 42, ms: 700 } },
+ { value: "23:31:12.23", result: { time: 84672230, hours: 23, minutes: 31, seconds: 12, ms: 230 } },
+ ];
+
+ var element = document.createElement('input');
+ element.type = 'time';
+
+ for (let test of tests) {
+ element.value = test.value;
+ if (test.result === null) {
+ is(element.valueAsDate, null, "element.valueAsDate should return null");
+ } else {
+ var date = element.valueAsDate;
+ isnot(date, null, "element.valueAsDate should not be null");
+
+ is(date.getTime(), test.result.time);
+ is(date.getUTCHours(), test.result.hours);
+ is(date.getUTCMinutes(), test.result.minutes);
+ is(date.getUTCSeconds(), test.result.seconds);
+ is(date.getUTCMilliseconds(), test.result.ms);
+ }
+ }
+}
+
+function checkTimeSet()
+{
+ var tests = [
+ // Simple tests.
+ { value: 0, result: "00:00" },
+ { value: 1, result: "00:00:00.001" },
+ { value: 100, result: "00:00:00.100" },
+ { value: 1000, result: "00:00:01" },
+ { value: 60000, result: "00:01" },
+ { value: 3600000, result: "01:00" },
+ { value: 83622234, result: "23:13:42.234" },
+ // Some edge cases.
+ { value: 86400000, result: "00:00" },
+ { value: 86400001, result: "00:00:00.001" },
+ { value: 170022234, result: "23:13:42.234" },
+ { value: 432000000, result: "00:00" },
+ { value: -1, result: "23:59:59.999" },
+ { value: -86400000, result: "00:00" },
+ { value: -86400001, result: "23:59:59.999" },
+ { value: -56789, result: "23:59:03.211" },
+ { value: 0.9, result: "00:00" },
+ ];
+
+ var element = document.createElement('input');
+ element.type = 'time';
+
+ for (let test of tests) {
+ element.valueAsDate = new Date(test.value);
+ is(element.value, test.result,
+ "element.value should have been changed by setting valueAsDate");
+ }
+}
+
+function checkWithBustedPrototype()
+{
+ for (let type of validTypes) {
+ if (!type[1]) {
+ continue;
+ }
+
+ type = type[0];
+
+ var element = document.createElement('input');
+ element.type = type;
+
+ var backupPrototype = {};
+ backupPrototype.getUTCFullYear = Date.prototype.getUTCFullYear;
+ backupPrototype.getUTCMonth = Date.prototype.getUTCMonth;
+ backupPrototype.getUTCDate = Date.prototype.getUTCDate;
+ backupPrototype.getTime = Date.prototype.getTime;
+ backupPrototype.setUTCFullYear = Date.prototype.setUTCFullYear;
+
+ Date.prototype.getUTCFullYear = function() { return {}; };
+ Date.prototype.getUTCMonth = function() { return {}; };
+ Date.prototype.getUTCDate = function() { return {}; };
+ Date.prototype.getTime = function() { return {}; };
+ Date.prototype.setUTCFullYear = function(y,m,d) { };
+
+ element.valueAsDate = new Date();
+
+ isnot(element.valueAsDate, null, ".valueAsDate should not return null");
+ // The object returned by element.valueAsDate should return a Date object
+ // with the same prototype:
+ is(element.valueAsDate.getUTCFullYear, Date.prototype.getUTCFullYear,
+ "prototype is the same");
+ is(element.valueAsDate.getUTCMonth, Date.prototype.getUTCMonth,
+ "prototype is the same");
+ is(element.valueAsDate.getUTCDate, Date.prototype.getUTCDate,
+ "prototype is the same");
+ is(element.valueAsDate.getTime, Date.prototype.getTime,
+ "prototype is the same");
+ is(element.valueAsDate.setUTCFullYear, Date.prototype.setUTCFullYear,
+ "prototype is the same");
+
+ // However the Date should have the correct information.
+ // Skip type=month for now, since .valueAsNumber returns number of months
+ // and not milliseconds.
+ if (type != "month") {
+ var witnessDate = new Date(element.valueAsNumber);
+ is(element.valueAsDate.valueOf(), witnessDate.valueOf(), "correct Date");
+ }
+
+ // Same test as above but using NaN instead of {}.
+
+ Date.prototype.getUTCFullYear = function() { return NaN; };
+ Date.prototype.getUTCMonth = function() { return NaN; };
+ Date.prototype.getUTCDate = function() { return NaN; };
+ Date.prototype.getTime = function() { return NaN; };
+ Date.prototype.setUTCFullYear = function(y,m,d) { };
+
+ element.valueAsDate = new Date();
+
+ isnot(element.valueAsDate, null, ".valueAsDate should not return null");
+ // The object returned by element.valueAsDate should return a Date object
+ // with the same prototype:
+ is(element.valueAsDate.getUTCFullYear, Date.prototype.getUTCFullYear,
+ "prototype is the same");
+ is(element.valueAsDate.getUTCMonth, Date.prototype.getUTCMonth,
+ "prototype is the same");
+ is(element.valueAsDate.getUTCDate, Date.prototype.getUTCDate,
+ "prototype is the same");
+ is(element.valueAsDate.getTime, Date.prototype.getTime,
+ "prototype is the same");
+ is(element.valueAsDate.setUTCFullYear, Date.prototype.setUTCFullYear,
+ "prototype is the same");
+
+ // However the Date should have the correct information.
+ // Skip type=month for now, since .valueAsNumber returns number of months
+ // and not milliseconds.
+ if (type != "month") {
+ var witnessDate = new Date(element.valueAsNumber);
+ is(element.valueAsDate.valueOf(), witnessDate.valueOf(), "correct Date");
+ }
+
+ Date.prototype.getUTCFullYear = backupPrototype.getUTCFullYear;
+ Date.prototype.getUTCMonth = backupPrototype.getUTCMonth;
+ Date.prototype.getUTCDate = backupPrototype.getUTCDate;
+ Date.prototype.getTime = backupPrototype.getTime;
+ Date.prototype.setUTCFullYear = backupPrototype.setUTCFullYear;
+ }
+}
+
+function checkMonthGet()
+{
+ var validData =
+ [
+ [ "2016-07", 1467331200000 ],
+ [ "1970-01", 0 ],
+ [ "1970-02", 2678400000 ],
+ [ "1969-12", -2678400000 ],
+ [ "0001-01", -62135596800000 ],
+ [ "275760-09", 8639998963200000 ],
+ ];
+
+ var invalidData =
+ [
+ [ "invalidmonth" ],
+ [ "0000-01" ],
+ [ "2016-00" ],
+ [ "123-01" ],
+ [ "2017-13" ],
+ [ "" ],
+ // This month is valid for the input element, but is out of
+ // the date object range. In this case, on getting valueAsDate,
+ // a Date object will be created, but it will have a NaN internal value,
+ // and will return the string "Invalid Date".
+ [ "275760-10", true ],
+ ];
+
+ element.type = "month";
+ for (let data of validData) {
+ element.value = data[0];
+ is(element.valueAsDate.valueOf(), data[1],
+ "valueAsDate should return the " +
+ "valid date object representing this month");
+ }
+
+ for (let data of invalidData) {
+ element.value = data[0];
+ if (data[1]) {
+ is(String(element.valueAsDate), "Invalid Date",
+ "valueAsDate should return an invalid Date object " +
+ "when the element value is not a valid month");
+ } else {
+ is(element.valueAsDate, null,
+ "valueAsDate should return null " +
+ "when the element value is not a valid month");
+ }
+ }
+}
+
+function checkMonthSet()
+{
+ var testData =
+ [
+ [ 1342051200000, "2012-07" ],
+ [ 0, "1970-01" ],
+ // Maximum valid month (limited by the ecma date object range).
+ [ 8640000000000000, "275760-09" ],
+ // Minimum valid month (limited by the input element minimum valid value).
+ [ -62135596800000 , "0001-01" ],
+ [ 1330473600000, "2012-02" ],
+ [ 1298851200000, "2011-02" ],
+ // "Values must be truncated to valid months"
+ [ 42.1234, "1970-01" ],
+ [ 123.123456789123, "1970-01" ],
+ [ 1e-1, "1970-01" ],
+ [ 1298851200010, "2011-02" ],
+ [ -1, "1969-12" ],
+ [ -86400000, "1969-12" ],
+ [ 86400000, "1970-01" ],
+ // Negative years, this is out of range for the input element,
+ // the corresponding month string is the empty string
+ [ -62135596800001, "" ],
+ ];
+
+ element.type = "month";
+ for (let data of testData) {
+ element.valueAsDate = new Date(data[0]);
+ is(element.value, data[1], "valueAsDate should set the value to "
+ + data[1]);
+ element.valueAsDate = new testFrame.Date(data[0]);
+ is(element.value, data[1], "valueAsDate with other-global date should " +
+ "set the value to " + data[1]);
+ }
+}
+
+function checkWeekGet()
+{
+ var validData =
+ [
+ // Common years starting on different days of week.
+ [ "2007-W01", Date.UTC(2007, 0, 1) ], // Mon
+ [ "2013-W01", Date.UTC(2012, 11, 31) ], // Tue
+ [ "2014-W01", Date.UTC(2013, 11, 30) ], // Wed
+ [ "2015-W01", Date.UTC(2014, 11, 29) ], // Thu
+ [ "2010-W01", Date.UTC(2010, 0, 4) ], // Fri
+ [ "2011-W01", Date.UTC(2011, 0, 3) ], // Sat
+ [ "2017-W01", Date.UTC(2017, 0, 2) ], // Sun
+ // Common years ending on different days of week.
+ [ "2007-W52", Date.UTC(2007, 11, 24) ], // Mon
+ [ "2013-W52", Date.UTC(2013, 11, 23) ], // Tue
+ [ "2014-W52", Date.UTC(2014, 11, 22) ], // Wed
+ [ "2015-W53", Date.UTC(2015, 11, 28) ], // Thu
+ [ "2010-W52", Date.UTC(2010, 11, 27) ], // Fri
+ [ "2011-W52", Date.UTC(2011, 11, 26) ], // Sat
+ [ "2017-W52", Date.UTC(2017, 11, 25) ], // Sun
+ // Leap years starting on different days of week.
+ [ "1996-W01", Date.UTC(1996, 0, 1) ], // Mon
+ [ "2008-W01", Date.UTC(2007, 11, 31) ], // Tue
+ [ "2020-W01", Date.UTC(2019, 11, 30) ], // Wed
+ [ "2004-W01", Date.UTC(2003, 11, 29) ], // Thu
+ [ "2016-W01", Date.UTC(2016, 0, 4) ], // Fri
+ [ "2000-W01", Date.UTC(2000, 0, 3) ], // Sat
+ [ "2012-W01", Date.UTC(2012, 0, 2) ], // Sun
+ // Leap years ending on different days of week.
+ [ "2012-W52", Date.UTC(2012, 11, 24) ], // Mon
+ [ "2024-W52", Date.UTC(2024, 11, 23) ], // Tue
+ [ "1980-W52", Date.UTC(1980, 11, 22) ], // Wed
+ [ "1992-W53", Date.UTC(1992, 11, 28) ], // Thu
+ [ "2004-W53", Date.UTC(2004, 11, 27) ], // Fri
+ [ "1988-W52", Date.UTC(1988, 11, 26) ], // Sat
+ [ "2000-W52", Date.UTC(2000, 11, 25) ], // Sun
+ // Other normal cases.
+ [ "2016-W36", 1473033600000 ],
+ [ "1969-W52", -864000000 ],
+ [ "1970-W01", -259200000 ],
+ [ "275760-W37", 8639999568000000 ],
+ ];
+
+ var invalidData =
+ [
+ [ "invalidweek" ],
+ [ "0000-W01" ],
+ [ "2016-W00" ],
+ [ "123-W01" ],
+ [ "2016-W53" ],
+ [ "" ],
+ // This week is valid for the input element, but is out of
+ // the date object range. In this case, on getting valueAsDate,
+ // a Date object will be created, but it will have a NaN internal value,
+ // and will return the string "Invalid Date".
+ [ "275760-W38", true ],
+ ];
+
+ element.type = "week";
+ for (let data of validData) {
+ element.value = data[0];
+ is(element.valueAsDate.valueOf(), data[1],
+ "valueAsDate should return the " +
+ "valid date object representing this week");
+ }
+
+ for (let data of invalidData) {
+ element.value = data[0];
+ if (data[1]) {
+ is(String(element.valueAsDate), "Invalid Date",
+ "valueAsDate should return an invalid Date object " +
+ "when the element value is not a valid week");
+ } else {
+ is(element.valueAsDate, null,
+ "valueAsDate should return null " +
+ "when the element value is not a valid week");
+ }
+ }
+}
+
+function checkWeekSet()
+{
+ var testData =
+ [
+ // Common years starting on different days of week.
+ [ Date.UTC(2007, 0, 1), "2007-W01" ], // Mon
+ [ Date.UTC(2013, 0, 1), "2013-W01" ], // Tue
+ [ Date.UTC(2014, 0, 1), "2014-W01" ], // Wed
+ [ Date.UTC(2015, 0, 1), "2015-W01" ], // Thu
+ [ Date.UTC(2010, 0, 1), "2009-W53" ], // Fri
+ [ Date.UTC(2011, 0, 1), "2010-W52" ], // Sat
+ [ Date.UTC(2017, 0, 1), "2016-W52" ], // Sun
+ // Common years ending on different days of week.
+ [ Date.UTC(2007, 11, 31), "2008-W01" ], // Mon
+ [ Date.UTC(2013, 11, 31), "2014-W01" ], // Tue
+ [ Date.UTC(2014, 11, 31), "2015-W01" ], // Wed
+ [ Date.UTC(2015, 11, 31), "2015-W53" ], // Thu
+ [ Date.UTC(2010, 11, 31), "2010-W52" ], // Fri
+ [ Date.UTC(2011, 11, 31), "2011-W52" ], // Sat
+ [ Date.UTC(2017, 11, 31), "2017-W52" ], // Sun
+ // Leap years starting on different days of week.
+ [ Date.UTC(1996, 0, 1), "1996-W01" ], // Mon
+ [ Date.UTC(2008, 0, 1), "2008-W01" ], // Tue
+ [ Date.UTC(2020, 0, 1), "2020-W01" ], // Wed
+ [ Date.UTC(2004, 0, 1), "2004-W01" ], // Thu
+ [ Date.UTC(2016, 0, 1), "2015-W53" ], // Fri
+ [ Date.UTC(2000, 0, 1), "1999-W52" ], // Sat
+ [ Date.UTC(2012, 0, 1), "2011-W52" ], // Sun
+ // Leap years ending on different days of week.
+ [ Date.UTC(2012, 11, 31), "2013-W01" ], // Mon
+ [ Date.UTC(2024, 11, 31), "2025-W01" ], // Tue
+ [ Date.UTC(1980, 11, 31), "1981-W01" ], // Wed
+ [ Date.UTC(1992, 11, 31), "1992-W53" ], // Thu
+ [ Date.UTC(2004, 11, 31), "2004-W53" ], // Fri
+ [ Date.UTC(1988, 11, 31), "1988-W52" ], // Sat
+ [ Date.UTC(2000, 11, 31), "2000-W52" ], // Sun
+ // Other normal cases.
+ [ Date.UTC(2016, 8, 9), "2016-W36" ],
+ [ Date.UTC(2010, 0, 3), "2009-W53" ],
+ [ Date.UTC(2010, 0, 4), "2010-W01" ],
+ [ Date.UTC(2010, 0, 10), "2010-W01" ],
+ [ Date.UTC(2010, 0, 11), "2010-W02" ],
+ [ 0, "1970-W01" ],
+ // Maximum valid month (limited by the ecma date object range).
+ [ 8640000000000000, "275760-W37" ],
+ // Minimum valid month (limited by the input element minimum valid value).
+ [ -62135596800000 , "0001-W01" ],
+ // "Values must be truncated to valid week"
+ [ 42.1234, "1970-W01" ],
+ [ 123.123456789123, "1970-W01" ],
+ [ 1e-1, "1970-W01" ],
+ [ -1.1, "1970-W01" ],
+ [ -345600000, "1969-W52" ],
+ // Negative years, this is out of range for the input element,
+ // the corresponding week string is the empty string
+ [ -62135596800001, "" ],
+ ];
+
+ element.type = "week";
+ for (let data of testData) {
+ element.valueAsDate = new Date(data[0]);
+ is(element.value, data[1], "valueAsDate should set the value to "
+ + data[1]);
+ element.valueAsDate = new testFrame.Date(data[0]);
+ is(element.value, data[1], "valueAsDate with other-global date should " +
+ "set the value to " + data[1]);
+ }
+}
+
+checkAvailability();
+checkGarbageValues();
+checkWithBustedPrototype();
+
+// Test <input type='date'>.
+checkDateGet();
+checkDateSet();
+
+// Test <input type='time'>.
+checkTimeGet();
+checkTimeSet();
+
+// Test <input type='month'>.
+checkMonthGet();
+checkMonthSet();
+
+// Test <input type='week'>.
+checkWeekGet();
+checkWeekSet();
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/forms/test_valueasnumber_attribute.html b/dom/html/test/forms/test_valueasnumber_attribute.html
new file mode 100644
index 000000000..d7471502b
--- /dev/null
+++ b/dom/html/test/forms/test_valueasnumber_attribute.html
@@ -0,0 +1,744 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=636737
+-->
+<head>
+ <title>Test for Bug input.valueAsNumber</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=636737">Mozilla Bug 636737</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 636737 **/
+
+/**
+ * This test is checking .valueAsNumber.
+ */
+
+function checkAvailability()
+{
+ var testData =
+ [
+ ["text", false],
+ ["password", false],
+ ["search", false],
+ ["tel", false],
+ ["email", false],
+ ["url", false],
+ ["hidden", false],
+ ["checkbox", false],
+ ["radio", false],
+ ["file", false],
+ ["submit", false],
+ ["image", false],
+ ["reset", false],
+ ["button", false],
+ ["number", true],
+ ["range", true],
+ ["date", true],
+ ["time", true],
+ ["color", false],
+ ["month", true],
+ ["week", true],
+ // TODO: temporary set to false until bug 888331 is fixed.
+ ["datetime-local", false],
+ ];
+
+ var element = document.createElement('input');
+
+ for (let data of testData) {
+ var exceptionCatched = false;
+ element.type = data[0];
+ try {
+ element.valueAsNumber;
+ } catch (e) {
+ exceptionCatched = true;
+ }
+ is(exceptionCatched, false,
+ "valueAsNumber shouldn't throw exception on getting");
+
+ exceptionCatched = false;
+ try {
+ element.valueAsNumber = 42;
+ } catch (e) {
+ exceptionCatched = true;
+ }
+ is(exceptionCatched, !data[1], "valueAsNumber for " + data[0] +
+ " availability is not correct");
+ }
+}
+
+function checkNumberGet()
+{
+ var testData =
+ [
+ ["42", 42],
+ ["-42", -42], // should work for negative values
+ ["42.1234", 42.1234],
+ ["123.123456789123", 123.123456789123], // double precision
+ ["1e2", 100], // e should be usable
+ ["2e1", 20],
+ ["1e-1", 0.1], // value after e can be negative
+ ["1E2", 100], // E can be used instead of e
+ ["e", null],
+ ["e2", null],
+ ["1e0.1", null],
+ ["", null], // the empty string is not a number
+ ["foo", null],
+ ["42,13", null], // comma can't be used as a decimal separator
+ ];
+
+ var element = document.createElement('input');
+ element.type = "number";
+ for (let data of testData) {
+ element.value = data[0];
+
+ // Given that NaN != NaN, we have to use null when the expected value is NaN.
+ if (data[1] != null) {
+ is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
+ "floating point representation of the value");
+ } else {
+ ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN " +
+ "when the element value is not a number");
+ }
+ }
+}
+
+function checkNumberSet()
+{
+ var testData =
+ [
+ [42, "42"],
+ [-42, "-42"], // should work for negative values
+ [42.1234, "42.1234"],
+ [123.123456789123, "123.123456789123"], // double precision
+ [1e2, "100"], // e should be usable
+ [2e1, "20"],
+ [1e-1, "0.1"], // value after e can be negative
+ [1E2, "100"], // E can be used instead of e
+ // Setting a string will set NaN.
+ ["foo", ""],
+ // "" is converted to 0.
+ ["", "0"],
+ [42, "42"], // Keep this here, it is used by the next test.
+ // Setting Infinity should throw and not change the current value.
+ [Infinity, "42", true],
+ [-Infinity, "42", true],
+ // Setting NaN should change the value to the empty string.
+ [NaN, ""],
+ ];
+
+ var element = document.createElement('input');
+ element.type = "number";
+ for (let data of testData) {
+ var caught = false;
+ try {
+ element.valueAsNumber = data[0];
+ is(element.value, data[1],
+ "valueAsNumber should be able to set the value");
+ } catch (e) {
+ caught = true;
+ }
+
+ if (data[2]) {
+ ok(caught, "valueAsNumber should have thrown");
+ is(element.value, data[1], "value should not have changed");
+ } else {
+ ok(!caught, "valueAsNumber should not have thrown");
+ }
+ }
+}
+
+function checkRangeGet()
+{
+ // For type=range we should never get NaN since the user agent is required
+ // to fix up the input's value to be something sensible.
+
+ var min = -200;
+ var max = 200;
+ var defaultValue = min + (max - min)/2;
+
+ var testData =
+ [
+ ["42", 42],
+ ["-42", -42], // should work for negative values
+ ["42.1234", 42.1234],
+ ["123.123456789123", 123.123456789123], // double precision
+ ["1e2", 100], // e should be usable
+ ["2e1", 20],
+ ["1e-1", 0.1], // value after e can be negative
+ ["1E2", 100], // E can be used instead of e
+ ["e", defaultValue],
+ ["e2", defaultValue],
+ ["1e0.1", defaultValue],
+ ["", defaultValue],
+ ["foo", defaultValue],
+ ["42,13", defaultValue],
+ ];
+
+ var element = document.createElement('input');
+ element.type = "range";
+ element.setAttribute("min", min); // avoids out of range sanitization
+ element.setAttribute("max", max);
+ element.setAttribute("step", "any"); // avoids step mismatch sanitization
+ for (let data of testData) {
+ element.value = data[0];
+
+ // Given that NaN != NaN, we have to use null when the expected value is NaN.
+ is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
+ "floating point representation of the value");
+ }
+}
+
+function checkRangeSet()
+{
+ var min = -200;
+ var max = 200;
+ var defaultValue = String(min + (max - min)/2);
+
+ var testData =
+ [
+ [42, "42"],
+ [-42, "-42"], // should work for negative values
+ [42.1234, "42.1234"],
+ [123.123456789123, "123.123456789123"], // double precision
+ [1e2, "100"], // e should be usable
+ [2e1, "20"],
+ [1e-1, "0.1"], // value after e can be negative
+ [1E2, "100"], // E can be used instead of e
+ ["foo", defaultValue],
+ ["", defaultValue],
+ [42, "42"], // Keep this here, it is used by the next test.
+ // Setting Infinity should throw and not change the current value.
+ [Infinity, "42", true],
+ [-Infinity, "42", true],
+ // Setting NaN should change the value to the empty string.
+ [NaN, defaultValue],
+ ];
+
+ var element = document.createElement('input');
+ element.type = "range";
+ element.setAttribute("min", min); // avoids out of range sanitization
+ element.setAttribute("max", max);
+ element.setAttribute("step", "any"); // avoids step mismatch sanitization
+ for (let data of testData) {
+ var caught = false;
+ try {
+ element.valueAsNumber = data[0];
+ is(element.value, data[1],
+ "valueAsNumber should be able to set the value");
+ } catch (e) {
+ caught = true;
+ }
+
+ if (data[2]) {
+ ok(caught, "valueAsNumber should have thrown");
+ is(element.value, data[1], "value should not have changed");
+ } else {
+ ok(!caught, "valueAsNumber should not have thrown");
+ }
+ }
+}
+
+function checkDateGet()
+{
+ var validData =
+ [
+ [ "2012-07-12", 1342051200000 ],
+ [ "1970-01-01", 0 ],
+ // We are supposed to support at least until this date.
+ // (corresponding to the date object maximal value)
+ [ "275760-09-13", 8640000000000000 ],
+ // Minimum valid date (limited by the input element minimum valid value)
+ [ "0001-01-01", -62135596800000 ],
+ [ "2012-02-29", 1330473600000 ],
+ [ "2011-02-28", 1298851200000 ],
+ ];
+
+ var invalidData =
+ [
+ "invaliddate",
+ "",
+ "275760-09-14",
+ "999-12-31",
+ "-001-12-31",
+ "0000-01-01",
+ "2011-02-29",
+ "1901-13-31",
+ "1901-12-32",
+ "1901-00-12",
+ "1901-01-00",
+ "1900-02-29",
+ ];
+
+ var element = document.createElement('input');
+ element.type = "date";
+ for (let data of validData) {
+ element.value = data[0];
+ is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
+ "timestamp representing this date");
+ }
+
+ for (let data of invalidData) {
+ element.value = data;
+ ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN " +
+ "when the element value is not a valid date");
+ }
+}
+
+function checkDateSet()
+{
+ var testData =
+ [
+ [ 1342051200000, "2012-07-12" ],
+ [ 0, "1970-01-01" ],
+ // Maximum valid date (limited by the ecma date object range).
+ [ 8640000000000000, "275760-09-13" ],
+ // Minimum valid date (limited by the input element minimum valid value)
+ [ -62135596800000, "0001-01-01" ],
+ [ 1330473600000, "2012-02-29" ],
+ [ 1298851200000, "2011-02-28" ],
+ // "Values must be truncated to valid dates"
+ [ 42.1234, "1970-01-01" ],
+ [ 123.123456789123, "1970-01-01" ],
+ [ 1e2, "1970-01-01" ],
+ [ 1E9, "1970-01-12" ],
+ [ 1e-1, "1970-01-01" ],
+ [ 2e10, "1970-08-20" ],
+ [ 1298851200010, "2011-02-28" ],
+ [ -1, "1969-12-31" ],
+ [ -86400000, "1969-12-31" ],
+ [ 86400000, "1970-01-02" ],
+ // Invalid numbers.
+ // Those are implicitly converted to numbers
+ [ "", "1970-01-01" ],
+ [ true, "1970-01-01" ],
+ [ false, "1970-01-01" ],
+ [ null, "1970-01-01" ],
+ // Those are converted to NaN, the corresponding date string is the empty string
+ [ "invaliddatenumber", "" ],
+ [ NaN, "" ],
+ [ undefined, "" ],
+ // Out of range, the corresponding date string is the empty string
+ [ -62135596800001, "" ],
+ // Infinity will keep the current value and throw (so we need to set a current value).
+ [ 1298851200010, "2011-02-28" ],
+ [ Infinity, "2011-02-28", true ],
+ [ -Infinity, "2011-02-28", true ],
+ ];
+
+ var element = document.createElement('input');
+ element.type = "date";
+ for (let data of testData) {
+ var caught = false;
+
+ try {
+ element.valueAsNumber = data[0];
+ is(element.value, data[1], "valueAsNumber should set the value to " + data[1]);
+ } catch(e) {
+ caught = true;
+ }
+
+ if (data[2]) {
+ ok(caught, "valueAsNumber should have thrown");
+ is(element.value, data[1], "the value should not have changed");
+ } else {
+ ok(!caught, "valueAsNumber should not have thrown");
+ }
+ }
+
+}
+
+function checkTimeGet()
+{
+ var tests = [
+ // Some invalid values to begin.
+ { value: "", result: NaN },
+ { value: "foobar", result: NaN },
+ { value: "00:", result: NaN },
+ { value: "24:00", result: NaN },
+ { value: "00:99", result: NaN },
+ { value: "00:00:", result: NaN },
+ { value: "00:00:99", result: NaN },
+ { value: "00:00:00:", result: NaN },
+ { value: "00:00:00.", result: NaN },
+ { value: "00:00:00.0000", result: NaN },
+ // Some simple valid values.
+ { value: "00:00", result: 0 },
+ { value: "00:01", result: 60000 },
+ { value: "01:00", result: 3600000 },
+ { value: "01:01", result: 3660000 },
+ { value: "13:37", result: 49020000 },
+ // Valid values including seconds.
+ { value: "00:00:01", result: 1000 },
+ { value: "13:37:42", result: 49062000 },
+ // Valid values including seconds fractions.
+ { value: "00:00:00.001", result: 1 },
+ { value: "00:00:00.123", result: 123 },
+ { value: "00:00:00.100", result: 100 },
+ { value: "00:00:00.000", result: 0 },
+ { value: "20:17:31.142", result: 73051142 },
+ // Highest possible value.
+ { value: "23:59:59.999", result: 86399999 },
+ // Some values with one or two digits for the fraction of seconds.
+ { value: "00:00:00.1", result: 100 },
+ { value: "00:00:00.14", result: 140 },
+ { value: "13:37:42.7", result: 49062700 },
+ { value: "23:31:12.23", result: 84672230 },
+ ];
+
+ var element = document.createElement('input');
+ element.type = 'time';
+
+ for (let test of tests) {
+ element.value = test.value;
+ if (isNaN(test.result)) {
+ ok(isNaN(element.valueAsNumber),
+ "invalid value should have .valueAsNumber return NaN");
+ } else {
+ is(element.valueAsNumber, test.result,
+ ".valueAsNumber should return " + test.result);
+ }
+ }
+}
+
+function checkTimeSet()
+{
+ var tests = [
+ // Some NaN values (should set to empty string).
+ { value: NaN, result: "" },
+ { value: "foobar", result: "" },
+ { value: function() {}, result: "" },
+ // Inifinity (should throw).
+ { value: Infinity, throw: true },
+ { value: -Infinity, throw: true },
+ // "" converts to 0... JS is fun :)
+ { value: "", result: "00:00" },
+ // Simple tests.
+ { value: 0, result: "00:00" },
+ { value: 1, result: "00:00:00.001" },
+ { value: 100, result: "00:00:00.100" },
+ { value: 1000, result: "00:00:01" },
+ { value: 60000, result: "00:01" },
+ { value: 3600000, result: "01:00" },
+ { value: 83622234, result: "23:13:42.234" },
+ // Some edge cases.
+ { value: 86400000, result: "00:00" },
+ { value: 86400001, result: "00:00:00.001" },
+ { value: 170022234, result: "23:13:42.234" },
+ { value: 432000000, result: "00:00" },
+ { value: -1, result: "23:59:59.999" },
+ { value: -86400000, result: "00:00" },
+ { value: -86400001, result: "23:59:59.999" },
+ { value: -56789, result: "23:59:03.211" },
+ { value: 0.9, result: "00:00" },
+ ];
+
+ var element = document.createElement('input');
+ element.type = 'time';
+
+ for (let test of tests) {
+ try {
+ var caught = false;
+ element.valueAsNumber = test.value;
+ is(element.value, test.result, "value should return " + test.result);
+ } catch(e) {
+ caught = true;
+ }
+
+ if (!test.throw) {
+ test.throw = false;
+ }
+
+ is(caught, test.throw, "the test throwing status should be " + test.throw);
+ }
+}
+
+function checkMonthGet()
+{
+ var validData =
+ [
+ [ "2016-07", 558 ],
+ [ "1970-01", 0 ],
+ [ "1969-12", -1 ],
+ [ "0001-01", -23628 ],
+ [ "10000-12", 96371 ],
+ [ "275760-09", 3285488 ],
+ ];
+
+ var invalidData =
+ [
+ "invalidmonth",
+ "0000-01",
+ "2000-00",
+ "2012-13",
+ // Out of range.
+ "275760-10",
+ ];
+
+ var element = document.createElement('input');
+ element.type = "month";
+ for (let data of validData) {
+ element.value = data[0];
+ is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
+ "integer value representing this month");
+ }
+
+ for (let data of invalidData) {
+ element.value = data;
+ ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN " +
+ "when the element value is not a valid month");
+ }
+}
+
+function checkMonthSet()
+{
+ var testData =
+ [
+ [ 558, "2016-07" ],
+ [ 0, "1970-01" ],
+ [ -1, "1969-12" ],
+ [ 96371, "10000-12" ],
+ [ 12, "1971-01" ],
+ [ -12, "1969-01" ],
+ // Maximum valid month (limited by the ecma date object range)
+ [ 3285488, "275760-09" ],
+ // Minimum valid month (limited by the input element minimum valid value)
+ [ -23628, "0001-01" ],
+ // "Values must be truncated to valid months"
+ [ 0.3, "1970-01" ],
+ [ -1.1, "1969-11" ],
+ [ 1e2, "1978-05" ],
+ [ 1e-1, "1970-01" ],
+ // Invalid numbers.
+ // Those are implicitly converted to numbers
+ [ "", "1970-01" ],
+ [ true, "1970-02" ],
+ [ false, "1970-01" ],
+ [ null, "1970-01" ],
+ // Those are converted to NaN, the corresponding month string is the empty string
+ [ "invalidmonth", "" ],
+ [ NaN, "" ],
+ [ undefined, "" ],
+ // Out of range, the corresponding month string is the empty string
+ [ -23629, "" ],
+ [ 3285489, "" ],
+ // Infinity will keep the current value and throw (so we need to set a current value)
+ [ 558, "2016-07" ],
+ [ Infinity, "2016-07", true ],
+ [ -Infinity, "2016-07", true ],
+ ];
+
+ var element = document.createElement('input');
+ element.type = "month";
+ for (let data of testData) {
+ var caught = false;
+
+ try {
+ element.valueAsNumber = data[0];
+ is(element.value, data[1], "valueAsNumber should set the value to " + data[1]);
+ } catch(e) {
+ caught = true;
+ }
+
+ if (data[2]) {
+ ok(caught, "valueAsNumber should have thrown");
+ is(element.value, data[1], "the value should not have changed");
+ } else {
+ ok(!caught, "valueAsNumber should not have thrown");
+ }
+ }
+}
+
+function checkWeekGet()
+{
+ var validData =
+ [
+ // Common years starting on different days of week.
+ [ "2007-W01", Date.UTC(2007, 0, 1) ], // Mon
+ [ "2013-W01", Date.UTC(2012, 11, 31) ], // Tue
+ [ "2014-W01", Date.UTC(2013, 11, 30) ], // Wed
+ [ "2015-W01", Date.UTC(2014, 11, 29) ], // Thu
+ [ "2010-W01", Date.UTC(2010, 0, 4) ], // Fri
+ [ "2011-W01", Date.UTC(2011, 0, 3) ], // Sat
+ [ "2017-W01", Date.UTC(2017, 0, 2) ], // Sun
+ // Common years ending on different days of week.
+ [ "2007-W52", Date.UTC(2007, 11, 24) ], // Mon
+ [ "2013-W52", Date.UTC(2013, 11, 23) ], // Tue
+ [ "2014-W52", Date.UTC(2014, 11, 22) ], // Wed
+ [ "2015-W53", Date.UTC(2015, 11, 28) ], // Thu
+ [ "2010-W52", Date.UTC(2010, 11, 27) ], // Fri
+ [ "2011-W52", Date.UTC(2011, 11, 26) ], // Sat
+ [ "2017-W52", Date.UTC(2017, 11, 25) ], // Sun
+ // Leap years starting on different days of week.
+ [ "1996-W01", Date.UTC(1996, 0, 1) ], // Mon
+ [ "2008-W01", Date.UTC(2007, 11, 31) ], // Tue
+ [ "2020-W01", Date.UTC(2019, 11, 30) ], // Wed
+ [ "2004-W01", Date.UTC(2003, 11, 29) ], // Thu
+ [ "2016-W01", Date.UTC(2016, 0, 4) ], // Fri
+ [ "2000-W01", Date.UTC(2000, 0, 3) ], // Sat
+ [ "2012-W01", Date.UTC(2012, 0, 2) ], // Sun
+ // Leap years ending on different days of week.
+ [ "2012-W52", Date.UTC(2012, 11, 24) ], // Mon
+ [ "2024-W52", Date.UTC(2024, 11, 23) ], // Tue
+ [ "1980-W52", Date.UTC(1980, 11, 22) ], // Wed
+ [ "1992-W53", Date.UTC(1992, 11, 28) ], // Thu
+ [ "2004-W53", Date.UTC(2004, 11, 27) ], // Fri
+ [ "1988-W52", Date.UTC(1988, 11, 26) ], // Sat
+ [ "2000-W52", Date.UTC(2000, 11, 25) ], // Sun
+ // Other normal cases.
+ [ "2015-W53", Date.UTC(2015, 11, 28) ],
+ [ "2016-W36", Date.UTC(2016, 8, 5) ],
+ [ "1970-W01", Date.UTC(1969, 11, 29) ],
+ [ "275760-W37", Date.UTC(275760, 8, 8) ],
+ ];
+
+ var invalidData =
+ [
+ "invalidweek",
+ "0000-W01",
+ "2016-W00",
+ "2016-W53",
+ // Out of range.
+ "275760-W38",
+ ];
+
+ var element = document.createElement('input');
+ element.type = "week";
+ for (let data of validData) {
+ dump("Test: " + data[0]);
+ element.value = data[0];
+ is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
+ "integer value representing this week");
+ }
+
+ for (let data of invalidData) {
+ element.value = data;
+ ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN " +
+ "when the element value is not a valid week");
+ }
+}
+
+function checkWeekSet()
+{
+ var testData =
+ [
+ // Common years starting on different days of week.
+ [ Date.UTC(2007, 0, 1), "2007-W01" ], // Mon
+ [ Date.UTC(2013, 0, 1), "2013-W01" ], // Tue
+ [ Date.UTC(2014, 0, 1), "2014-W01" ], // Wed
+ [ Date.UTC(2015, 0, 1), "2015-W01" ], // Thu
+ [ Date.UTC(2010, 0, 1), "2009-W53" ], // Fri
+ [ Date.UTC(2011, 0, 1), "2010-W52" ], // Sat
+ [ Date.UTC(2017, 0, 1), "2016-W52" ], // Sun
+ // Common years ending on different days of week.
+ [ Date.UTC(2007, 11, 31), "2008-W01" ], // Mon
+ [ Date.UTC(2013, 11, 31), "2014-W01" ], // Tue
+ [ Date.UTC(2014, 11, 31), "2015-W01" ], // Wed
+ [ Date.UTC(2015, 11, 31), "2015-W53" ], // Thu
+ [ Date.UTC(2010, 11, 31), "2010-W52" ], // Fri
+ [ Date.UTC(2011, 11, 31), "2011-W52" ], // Sat
+ [ Date.UTC(2017, 11, 31), "2017-W52" ], // Sun
+ // Leap years starting on different days of week.
+ [ Date.UTC(1996, 0, 1), "1996-W01" ], // Mon
+ [ Date.UTC(2008, 0, 1), "2008-W01" ], // Tue
+ [ Date.UTC(2020, 0, 1), "2020-W01" ], // Wed
+ [ Date.UTC(2004, 0, 1), "2004-W01" ], // Thu
+ [ Date.UTC(2016, 0, 1), "2015-W53" ], // Fri
+ [ Date.UTC(2000, 0, 1), "1999-W52" ], // Sat
+ [ Date.UTC(2012, 0, 1), "2011-W52" ], // Sun
+ // Leap years ending on different days of week.
+ [ Date.UTC(2012, 11, 31), "2013-W01" ], // Mon
+ [ Date.UTC(2024, 11, 31), "2025-W01" ], // Tue
+ [ Date.UTC(1980, 11, 31), "1981-W01" ], // Wed
+ [ Date.UTC(1992, 11, 31), "1992-W53" ], // Thu
+ [ Date.UTC(2004, 11, 31), "2004-W53" ], // Fri
+ [ Date.UTC(1988, 11, 31), "1988-W52" ], // Sat
+ [ Date.UTC(2000, 11, 31), "2000-W52" ], // Sun
+ // Other normal cases.
+ [ Date.UTC(2008, 8, 26), "2008-W39" ],
+ [ Date.UTC(2016, 0, 4), "2016-W01" ],
+ [ Date.UTC(2016, 0, 10), "2016-W01" ],
+ [ Date.UTC(2016, 0, 11), "2016-W02" ],
+ // Maximum valid week (limited by the ecma date object range).
+ [ 8640000000000000, "275760-W37" ],
+ // Minimum valid week (limited by the input element minimum valid value)
+ [ -62135596800000, "0001-W01" ],
+ // "Values must be truncated to valid weeks"
+ [ 0.3, "1970-W01" ],
+ [ 1e-1, "1970-W01" ],
+ [ -1.1, "1970-W01" ],
+ [ -345600000, "1969-W52" ],
+ // Invalid numbers.
+ // Those are implicitly converted to numbers
+ [ "", "1970-W01" ],
+ [ true, "1970-W01" ],
+ [ false, "1970-W01" ],
+ [ null, "1970-W01" ],
+ // Those are converted to NaN, the corresponding week string is the empty string
+ [ "invalidweek", "" ],
+ [ NaN, "" ],
+ [ undefined, "" ],
+ // Infinity will keep the current value and throw (so we need to set a current value).
+ [ Date.UTC(2016, 8, 8), "2016-W36" ],
+ [ Infinity, "2016-W36", true ],
+ [ -Infinity, "2016-W36", true ],
+ ];
+
+ var element = document.createElement('input');
+ element.type = "week";
+ for (let data of testData) {
+ var caught = false;
+
+ try {
+ element.valueAsNumber = data[0];
+ is(element.value, data[1], "valueAsNumber should set the value to " + data[1]);
+ } catch(e) {
+ caught = true;
+ }
+
+ if (data[2]) {
+ ok(caught, "valueAsNumber should have thrown");
+ is(element.value, data[1], "the value should not have changed");
+ } else {
+ ok(!caught, "valueAsNumber should not have thrown");
+ }
+ }
+}
+
+checkAvailability();
+
+// <input type='number'> test
+checkNumberGet();
+checkNumberSet();
+
+// <input type='range'> test
+checkRangeGet();
+checkRangeSet();
+
+// <input type='date'> test
+checkDateGet();
+checkDateSet();
+
+// <input type='time'> test
+checkTimeGet();
+checkTimeSet();
+
+// <input type='month'> test
+checkMonthGet();
+checkMonthSet();
+
+// <input type='week'> test
+checkWeekGet();
+checkWeekSet();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/head.js b/dom/html/test/head.js
new file mode 100644
index 000000000..7f5db315e
--- /dev/null
+++ b/dom/html/test/head.js
@@ -0,0 +1,54 @@
+function pushPrefs(...aPrefs) {
+ return new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve);
+ });
+}
+
+function promiseWaitForEvent(object, eventName, capturing = false, chrome = false) {
+ return new Promise((resolve) => {
+ function listener(event) {
+ info("Saw " + eventName);
+ object.removeEventListener(eventName, listener, capturing, chrome);
+ resolve(event);
+ }
+
+ info("Waiting for " + eventName);
+ object.addEventListener(eventName, listener, capturing, chrome);
+ });
+}
+
+/**
+ * Waits for the next load to complete in any browser or the given browser.
+ * If a <tabbrowser> is given it waits for a load in any of its browsers.
+ *
+ * @return promise
+ */
+function waitForDocLoadComplete(aBrowser=gBrowser) {
+ return new Promise(resolve => {
+ let listener = {
+ onStateChange: function (webProgress, req, flags, status) {
+ let docStop = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
+ Ci.nsIWebProgressListener.STATE_STOP;
+ info("Saw state " + flags.toString(16) + " and status " + status.toString(16));
+ // When a load needs to be retargetted to a new process it is cancelled
+ // with NS_BINDING_ABORTED so ignore that case
+ if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) {
+ aBrowser.removeProgressListener(this);
+ waitForDocLoadComplete.listeners.delete(this);
+ let chan = req.QueryInterface(Ci.nsIChannel);
+ info("Browser loaded " + chan.originalURI.spec);
+ resolve();
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference])
+ };
+ aBrowser.addProgressListener(listener);
+ waitForDocLoadComplete.listeners.add(listener);
+ info("Waiting for browser load");
+ });
+}
+// Keep a set of progress listeners for waitForDocLoadComplete() to make sure
+// they're not GC'ed before we saw the page load.
+waitForDocLoadComplete.listeners = new Set();
+registerCleanupFunction(() => waitForDocLoadComplete.listeners.clear());
diff --git a/dom/html/test/image-allow-credentials.png b/dom/html/test/image-allow-credentials.png
new file mode 100644
index 000000000..df24ac6d3
--- /dev/null
+++ b/dom/html/test/image-allow-credentials.png
Binary files differ
diff --git a/dom/html/test/image-allow-credentials.png^headers^ b/dom/html/test/image-allow-credentials.png^headers^
new file mode 100644
index 000000000..a03f99a9c
--- /dev/null
+++ b/dom/html/test/image-allow-credentials.png^headers^
@@ -0,0 +1,2 @@
+Access-Control-Allow-Origin: http://mochi.test:8888
+Access-Control-Allow-Credentials: true
diff --git a/dom/html/test/image.png b/dom/html/test/image.png
new file mode 100644
index 000000000..d26878c9f
--- /dev/null
+++ b/dom/html/test/image.png
Binary files differ
diff --git a/dom/html/test/imports/file_CSP_sandbox.html b/dom/html/test/imports/file_CSP_sandbox.html
new file mode 100644
index 000000000..72d3bbeb3
--- /dev/null
+++ b/dom/html/test/imports/file_CSP_sandbox.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<head>
+</head>
+<body>
+ <link rel="import" href="file_CSP_sandbox_import.html" id="import"></link>
+</body>
+
diff --git a/dom/html/test/imports/file_CSP_sandbox_import.html b/dom/html/test/imports/file_CSP_sandbox_import.html
new file mode 100644
index 000000000..33f88aecc
--- /dev/null
+++ b/dom/html/test/imports/file_CSP_sandbox_import.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<head>
+</head>
+<body>
+<script>
+ var scriptExecuted = true;
+</script>
+</body>
+
diff --git a/dom/html/test/imports/file_blocking_DOMContentLoaded_A.html b/dom/html/test/imports/file_blocking_DOMContentLoaded_A.html
new file mode 100644
index 000000000..2a18604ed
--- /dev/null
+++ b/dom/html/test/imports/file_blocking_DOMContentLoaded_A.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <script>
+ order.push("AS0");
+ </script>
+ <link rel="import" href="file_blocking_DOMContentLoaded_B.html" onload="loaded()" onerror="failed()"></link>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_blocking_DOMContentLoaded_B.html b/dom/html/test/imports/file_blocking_DOMContentLoaded_B.html
new file mode 100644
index 000000000..a69a38d16
--- /dev/null
+++ b/dom/html/test/imports/file_blocking_DOMContentLoaded_B.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <script>
+ order.push("BS0");
+ </script>
+ <link rel="import" href="file_blocking_DOMContentLoaded_C.html" onload="loaded()" onerror="failed()"></link>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_blocking_DOMContentLoaded_C.html b/dom/html/test/imports/file_blocking_DOMContentLoaded_C.html
new file mode 100644
index 000000000..76719b1b2
--- /dev/null
+++ b/dom/html/test/imports/file_blocking_DOMContentLoaded_C.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <script>
+ order.push("CS0");
+ </script>
+ <link rel="import" href="file_blocking_DOMContentLoaded_D.html" onload="loaded()" onerror="failed()"></link>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_blocking_DOMContentLoaded_D.html b/dom/html/test/imports/file_blocking_DOMContentLoaded_D.html
new file mode 100644
index 000000000..0ae10e702
--- /dev/null
+++ b/dom/html/test/imports/file_blocking_DOMContentLoaded_D.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <script>
+ order.push("DS0");
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_cycle_1_A.html b/dom/html/test/imports/file_cycle_1_A.html
new file mode 100644
index 000000000..17f2499d9
--- /dev/null
+++ b/dom/html/test/imports/file_cycle_1_A.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <link rel="import" href="file_cycle_1_B.html" onload="loaded()" onerror="failed()"></link>
+</head>
+<body>
+ <script>
+ order.push("A");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_cycle_1_B.html b/dom/html/test/imports/file_cycle_1_B.html
new file mode 100644
index 000000000..a7d77c823
--- /dev/null
+++ b/dom/html/test/imports/file_cycle_1_B.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <link rel="import" href="file_cycle_1_C.html" onload="loaded()" onerror="failed()"></link>
+ <link rel="import" href="file_cycle_1_A.html" onload="loaded()" onerror="failed()"></link>
+</head>
+<body>
+ <script>
+ order.push("B");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_cycle_1_C.html b/dom/html/test/imports/file_cycle_1_C.html
new file mode 100644
index 000000000..0c563c6ce
--- /dev/null
+++ b/dom/html/test/imports/file_cycle_1_C.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <link rel="import" href="file_cycle_1_B.html" onload="loaded()" onerror="failed()"></link>
+ <link rel="import" href="file_cycle_1_A.html" onload="loaded()" onerror="failed()"></link>
+</head>
+<body>
+ <script>
+ order.push("C");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_cycle_2_A.html b/dom/html/test/imports/file_cycle_2_A.html
new file mode 100644
index 000000000..5e4b6bfd3
--- /dev/null
+++ b/dom/html/test/imports/file_cycle_2_A.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <link rel="import" href="file_cycle_2_B.html" onload="loaded()" onerror="failed()"></link>
+</head>
+<body>
+ <script>
+ order.push("A");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_cycle_2_B.html b/dom/html/test/imports/file_cycle_2_B.html
new file mode 100644
index 000000000..ed5c4acf6
--- /dev/null
+++ b/dom/html/test/imports/file_cycle_2_B.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <link rel="import" href="file_cycle_2_A.html" onload="loaded()" onerror="failed()"></link>
+ <link rel="import" href="file_cycle_2_C.html" onload="loaded()" onerror="failed()"></link>
+ <link rel="import" href="file_cycle_2_D.html" onload="loaded()" onerror="failed()"></link>
+</head>
+<body>
+ <script>
+ order.push("B");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_cycle_2_C.html b/dom/html/test/imports/file_cycle_2_C.html
new file mode 100644
index 000000000..4492c068a
--- /dev/null
+++ b/dom/html/test/imports/file_cycle_2_C.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+</head>
+<body>
+ <script>
+ order.push("C");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_cycle_2_D.html b/dom/html/test/imports/file_cycle_2_D.html
new file mode 100644
index 000000000..eae5280e0
--- /dev/null
+++ b/dom/html/test/imports/file_cycle_2_D.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+</head>
+<body>
+ <script>
+ order.push("D");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_cycle_3_A.html b/dom/html/test/imports/file_cycle_3_A.html
new file mode 100644
index 000000000..bc2a31d1c
--- /dev/null
+++ b/dom/html/test/imports/file_cycle_3_A.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <link rel="import" href="file_cycle_3_C.html" onload="loaded()" onerror="failed()"></link>
+</head>
+<body>
+ <script>
+ order.push("A");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_cycle_3_B.html b/dom/html/test/imports/file_cycle_3_B.html
new file mode 100644
index 000000000..19d51a6ed
--- /dev/null
+++ b/dom/html/test/imports/file_cycle_3_B.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <link rel="import" href="file_cycle_3_C.html" onload="loaded()" onerror="failed()"></link>
+</head>
+<body>
+ <script>
+ order.push("B");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_cycle_3_C.html b/dom/html/test/imports/file_cycle_3_C.html
new file mode 100644
index 000000000..4b391c683
--- /dev/null
+++ b/dom/html/test/imports/file_cycle_3_C.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <link rel="import" href="file_cycle_3_A.html" onload="loaded()" onerror="failed()"></link>
+ <link rel="import" href="file_cycle_3_B.html" onload="loaded()" onerror="failed()"></link>
+</head>
+<body>
+ <script>
+ order.push("C");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_cycle_4_A.html b/dom/html/test/imports/file_cycle_4_A.html
new file mode 100644
index 000000000..0668bdf80
--- /dev/null
+++ b/dom/html/test/imports/file_cycle_4_A.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <link rel="import" href="file_cycle_4_B.html" onload="loaded()" onerror="failed()"></link>
+</head>
+<body>
+ <script>
+ order.push("A");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_cycle_4_B.html b/dom/html/test/imports/file_cycle_4_B.html
new file mode 100644
index 000000000..596e5c3e7
--- /dev/null
+++ b/dom/html/test/imports/file_cycle_4_B.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <link rel="import" href="file_cycle_4_C.html" onload="loaded()" onerror="failed()"></link>
+</head>
+<body>
+ <script>
+ order.push("B");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_cycle_4_C.html b/dom/html/test/imports/file_cycle_4_C.html
new file mode 100644
index 000000000..7c676e85c
--- /dev/null
+++ b/dom/html/test/imports/file_cycle_4_C.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <link rel="import" href="file_cycle_4_D.html" onload="loaded()" onerror="failed()"></link>
+</head>
+<body>
+ <script>
+ order.push("C");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_cycle_4_D.html b/dom/html/test/imports/file_cycle_4_D.html
new file mode 100644
index 000000000..31ae5de32
--- /dev/null
+++ b/dom/html/test/imports/file_cycle_4_D.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <link rel="import" href="file_cycle_4_B.html" onload="loaded()" onerror="failed()"></link>
+ <link rel="import" href="file_cycle_4_E.html" onload="loaded()" onerror="failed()"></link>
+</head>
+<body>
+ <script>
+ order.push("D");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_cycle_4_E.html b/dom/html/test/imports/file_cycle_4_E.html
new file mode 100644
index 000000000..42cd3c296
--- /dev/null
+++ b/dom/html/test/imports/file_cycle_4_E.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+ <link rel="import" href="file_cycle_4_C.html" onload="loaded()" onerror="failed()"></link>
+</head>
+<body>
+ <script>
+ order.push("E");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_element_upgrade.html b/dom/html/test/imports/file_element_upgrade.html
new file mode 100644
index 000000000..b41bd0a42
--- /dev/null
+++ b/dom/html/test/imports/file_element_upgrade.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en-US">
+<head>
+</head>
+<body>
+ <x-foo></x-foo>
+ <script>
+ var prototype = Object.create(HTMLElement.prototype);
+ prototype.createdCallback = function() {
+ createdCallbackCalled = true;
+ }
+ document.registerElement('x-foo', {prototype: prototype});
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/file_encoding.html b/dom/html/test/imports/file_encoding.html
new file mode 100644
index 000000000..d97bb0437
--- /dev/null
+++ b/dom/html/test/imports/file_encoding.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<head>
+<meta charset="EUC-KR">
+</head>
+<body>Ignore my encoding</body> \ No newline at end of file
diff --git a/dom/html/test/imports/file_importA1.html b/dom/html/test/imports/file_importA1.html
new file mode 100644
index 000000000..ecce6f061
--- /dev/null
+++ b/dom/html/test/imports/file_importA1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ <link rel="import" href="file_importA2.html" id="A2" onload="loaded()" onerror="failed()"></link>
+ </head>
+ <body>
+ <script>
+ order.push("A1");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/file_importA2.html b/dom/html/test/imports/file_importA2.html
new file mode 100644
index 000000000..d03e80a4b
--- /dev/null
+++ b/dom/html/test/imports/file_importA2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ </head>
+ <body>
+ <script>
+ order.push("A2");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/file_importB1.html b/dom/html/test/imports/file_importB1.html
new file mode 100644
index 000000000..82fb55f9f
--- /dev/null
+++ b/dom/html/test/imports/file_importB1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ <link rel="import" href="file_importB2.html" id="B2" onload="loaded()" onerror="failed()"></link>
+ </head>
+ <body>
+ <script>
+ order.push("B1");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/file_importB2.html b/dom/html/test/imports/file_importB2.html
new file mode 100644
index 000000000..01b6cc215
--- /dev/null
+++ b/dom/html/test/imports/file_importB2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ </head>
+ <body>
+ <script>
+ order.push("B2");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/file_importC1.html b/dom/html/test/imports/file_importC1.html
new file mode 100644
index 000000000..9ac117e65
--- /dev/null
+++ b/dom/html/test/imports/file_importC1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ <link rel="import" href="file_importC2.html" onload="loaded()" onerror="failed()"></link>
+ </head>
+ <body>
+ <script>
+ order.push("C1");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/file_importC10.html b/dom/html/test/imports/file_importC10.html
new file mode 100644
index 000000000..801d0f085
--- /dev/null
+++ b/dom/html/test/imports/file_importC10.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ <link rel="import" href="file_importE.html" onload="loaded()" onerror="failed()"></link>
+ </head>
+ <body>
+ <script>
+ order.push("C10");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/file_importC2.html b/dom/html/test/imports/file_importC2.html
new file mode 100644
index 000000000..f0193be44
--- /dev/null
+++ b/dom/html/test/imports/file_importC2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ <link rel="import" href="file_importC3.html" onload="loaded()" onerror="failed()"></link>
+ </head>
+ <body>
+ <script>
+ order.push("C2");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/file_importC3.html b/dom/html/test/imports/file_importC3.html
new file mode 100644
index 000000000..eb942b707
--- /dev/null
+++ b/dom/html/test/imports/file_importC3.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ <link rel="import" href="file_importC4.html" onload="loaded()" onerror="failed()"></link>
+ </head>
+ <body>
+ <script>
+ order.push("C3");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/file_importC4.html b/dom/html/test/imports/file_importC4.html
new file mode 100644
index 000000000..5a172772a
--- /dev/null
+++ b/dom/html/test/imports/file_importC4.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ <link rel="import" href="file_importC5.html" onload="loaded()" onerror="failed()"></link>
+ </head>
+ <body>
+ <script>
+ order.push("C4");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/file_importC5.html b/dom/html/test/imports/file_importC5.html
new file mode 100644
index 000000000..c29dc24ba
--- /dev/null
+++ b/dom/html/test/imports/file_importC5.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ <link rel="import" href="file_importC6.html" onload="loaded()" onerror="failed()"></link>
+ </head>
+ <body>
+ <script>
+ order.push("C5");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/file_importC6.html b/dom/html/test/imports/file_importC6.html
new file mode 100644
index 000000000..a53b62bb4
--- /dev/null
+++ b/dom/html/test/imports/file_importC6.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ <link rel="import" href="file_importC7.html" onload="loaded()" onerror="failed()"></link>
+ </head>
+ <body>
+ <script>
+ order.push("C6");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/file_importC7.html b/dom/html/test/imports/file_importC7.html
new file mode 100644
index 000000000..1c7d60114
--- /dev/null
+++ b/dom/html/test/imports/file_importC7.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ <link rel="import" href="file_importC8.html" onload="loaded()" onerror="failed()"></link>
+ </head>
+ <body>
+ <script>
+ order.push("C7");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/file_importC8.html b/dom/html/test/imports/file_importC8.html
new file mode 100644
index 000000000..04bef617d
--- /dev/null
+++ b/dom/html/test/imports/file_importC8.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ <link rel="import" href="file_importC9.html" onload="loaded()" onerror="failed()"></link>
+ </head>
+ <body>
+ <script>
+ order.push("C8");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/file_importC9.html b/dom/html/test/imports/file_importC9.html
new file mode 100644
index 000000000..1a2749755
--- /dev/null
+++ b/dom/html/test/imports/file_importC9.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ <link rel="import" href="file_importC10.html" onload="loaded()" onerror="failed()"></link>
+ </head>
+ <body>
+ <script>
+ order.push("C9");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/file_importD.html b/dom/html/test/imports/file_importD.html
new file mode 100644
index 000000000..a4fbda536
--- /dev/null
+++ b/dom/html/test/imports/file_importD.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <body>
+ <script>
+ order.push("D");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/file_importE.html b/dom/html/test/imports/file_importE.html
new file mode 100644
index 000000000..6a8792acf
--- /dev/null
+++ b/dom/html/test/imports/file_importE.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en-US">
+ <head>
+ <link rel="import" href="file_importD.html" onload="loaded()" onerror="failed()"></link>
+ </head>
+ <body>
+ <script>
+ order.push("E");
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/file_simple_import.html b/dom/html/test/imports/file_simple_import.html
new file mode 100644
index 000000000..d9358bc38
--- /dev/null
+++ b/dom/html/test/imports/file_simple_import.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<head>
+</head>
+<body>Simple import</body> \ No newline at end of file
diff --git a/dom/html/test/imports/mochitest.ini b/dom/html/test/imports/mochitest.ini
new file mode 100644
index 000000000..88ad3a6f5
--- /dev/null
+++ b/dom/html/test/imports/mochitest.ini
@@ -0,0 +1,52 @@
+[DEFAULT]
+support-files =
+ file_importA1.html
+ file_importA2.html
+ file_importB1.html
+ file_importB2.html
+ file_importC1.html
+ file_importC2.html
+ file_importC3.html
+ file_importC4.html
+ file_importC5.html
+ file_importC6.html
+ file_importC7.html
+ file_importC8.html
+ file_importC9.html
+ file_importC10.html
+ file_importD.html
+ file_importE.html
+ file_cycle_1_A.html
+ file_cycle_1_B.html
+ file_cycle_1_C.html
+ file_cycle_2_A.html
+ file_cycle_2_B.html
+ file_cycle_2_C.html
+ file_cycle_2_D.html
+ file_cycle_3_A.html
+ file_cycle_3_B.html
+ file_cycle_3_C.html
+ file_cycle_4_A.html
+ file_cycle_4_B.html
+ file_cycle_4_C.html
+ file_cycle_4_D.html
+ file_cycle_4_E.html
+ file_encoding.html
+ file_simple_import.html
+ file_blocking_DOMContentLoaded_A.html
+ file_blocking_DOMContentLoaded_B.html
+ file_blocking_DOMContentLoaded_C.html
+ file_blocking_DOMContentLoaded_D.html
+ file_element_upgrade.html
+ file_CSP_sandbox.html
+ file_CSP_sandbox_import.html
+
+[test_cycle_1.html]
+[test_cycle_2.html]
+[test_cycle_3.html]
+[test_cycle_4.html]
+[test_blocking_DOMContentLoaded.html]
+[test_encoding.html]
+[test_defaultView.html]
+[test_element_upgrade.html]
+[test_CSP_sandbox.html]
diff --git a/dom/html/test/imports/test_CSP_sandbox.html b/dom/html/test/imports/test_CSP_sandbox.html
new file mode 100644
index 000000000..c1df21289
--- /dev/null
+++ b/dom/html/test/imports/test_CSP_sandbox.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1106713
+-->
+<head>
+ <title>Test for Bug 1106713</title>
+ <script type="text/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>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1106713">Mozilla Bug 1106713</a>
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ function go() {
+ var ifr = document.getElementById('iframe1').contentWindow;
+ ok(!ifr.scriptExecuted, "script is not allowed to run");
+ SimpleTest.finish();
+ }
+
+ </script>
+ <iframe src='file_CSP_sandbox.html' sandbox="allow-same-origin" onload="go()" id="iframe1"></iframe>
+</body>
+</html>
+
diff --git a/dom/html/test/imports/test_blocking_DOMContentLoaded.html b/dom/html/test/imports/test_blocking_DOMContentLoaded.html
new file mode 100644
index 000000000..9fa32e5ca
--- /dev/null
+++ b/dom/html/test/imports/test_blocking_DOMContentLoaded.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1093028
+-->
+<head>
+ <title>Test for Bug 1093028</title>
+ <script type="text/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>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1093028">Mozilla Bug 1093028</a>
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ var counter = 0;
+ var fcounter = 0;
+ var order = [];
+ function loaded() {
+ counter++;
+ }
+ function failed() {
+ fcounter++;
+ }
+ </script>
+ <link rel="import" href="file_blocking_DOMContentLoaded_A.html" onload="loaded()" onerror="failed()"></link>
+ <script type="text/javascript">
+ is(counter, 4, "Imports are loaded");
+ is(fcounter, 0, "No error in imports");
+ var expected = ["AS0","BS0","CS0","DS0"];
+ for (i in expected)
+ is(order[i], expected[i], "import " + i + " should be " + expected[i]);
+ SimpleTest.finish();
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/test_cycle_1.html b/dom/html/test/imports/test_cycle_1.html
new file mode 100644
index 000000000..5d170a7de
--- /dev/null
+++ b/dom/html/test/imports/test_cycle_1.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1061469
+-->
+<head>
+ <title>Test for Bug 1061469</title>
+ <script type="text/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>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1061469">Mozilla Bug 1061469</a>
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ var counter = 0;
+ var fcounter = 0;
+ var order = [];
+ function loaded() {
+ counter++;
+ }
+ function failed() {
+ fcounter++;
+ }
+ </script>
+ <link rel="import" href="file_cycle_1_A.html" onload="loaded()" onerror="failed()"></link>
+ <script type="text/javascript">
+ is(counter, 6, "Imports are loaded");
+ is(fcounter, 0, "No error in imports");
+ var expected = ["C","B","A"];
+ for (i in expected)
+ is(order[i], expected[i], "import " + i + " should be " + expected[i]);
+ SimpleTest.finish();
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/test_cycle_2.html b/dom/html/test/imports/test_cycle_2.html
new file mode 100644
index 000000000..23daa5953
--- /dev/null
+++ b/dom/html/test/imports/test_cycle_2.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1061469
+-->
+<head>
+ <title>Test for Bug 1061469</title>
+ <script type="text/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>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1061469">Mozilla Bug 1061469</a>
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ var counter = 0;
+ var fcounter = 0;
+ var order = [];
+ function loaded() {
+ counter++;
+ }
+ function failed() {
+ fcounter++;
+ }
+ </script>
+ <link rel="import" href="file_cycle_2_A.html" onload="loaded()" onerror="failed()"></link>
+ <script type="text/javascript">
+ is(counter, 5, "Imports are loaded");
+ is(fcounter, 0, "No error in imports");
+ var expected = ["C","D","B","A"];
+ for (i in expected)
+ is(order[i], expected[i], "import " + i + " should be " + expected[i]);
+ SimpleTest.finish();
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/test_cycle_3.html b/dom/html/test/imports/test_cycle_3.html
new file mode 100644
index 000000000..8db664601
--- /dev/null
+++ b/dom/html/test/imports/test_cycle_3.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1061469
+-->
+<head>
+ <title>Test for Bug 1061469</title>
+ <script type="text/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>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1061469">Mozilla Bug 1061469</a>
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ var counter = 0;
+ var fcounter = 0;
+ var order = [];
+ function loaded() {
+ counter++;
+ }
+ function failed() {
+ fcounter++;
+ }
+ </script>
+ <link rel="import" href="file_cycle_3_A.html" onload="loaded()" onerror="failed()"></link>
+ <link rel="import" href="file_cycle_3_B.html" onload="loaded()" onerror="failed()"></link>
+ <script type="text/javascript">
+ is(counter, 6, "Imports are loaded");
+ is(fcounter, 0, "No error in imports");
+ var expected = ["B","C","A"];
+ for (i in expected)
+ is(order[i], expected[i], "import " + i + " should be " + expected[i]);
+ SimpleTest.finish();
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/test_cycle_4.html b/dom/html/test/imports/test_cycle_4.html
new file mode 100644
index 000000000..441ffbac8
--- /dev/null
+++ b/dom/html/test/imports/test_cycle_4.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1061469
+-->
+<head>
+ <title>Test for Bug 1061469</title>
+ <script type="text/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>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1061469">Mozilla Bug 1061469</a>
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ var counter = 0;
+ var fcounter = 0;
+ var order = [];
+ function loaded() {
+ counter++;
+ }
+ function failed() {
+ fcounter++;
+ }
+ </script>
+ <link rel="import" href="file_cycle_4_A.html" onload="loaded()" onerror="failed()"></link>
+ <link rel="import" href="file_cycle_4_D.html" onload="loaded()" onerror="failed()"></link>
+ <script type="text/javascript">
+ is(counter, 8, "Imports are loaded");
+ is(fcounter, 0, "No error in imports");
+ var expected = ["E","D","C","B","A"];
+ for (i in expected)
+ is(order[i], expected[i], "import " + i + " should be " + expected[i]);
+ SimpleTest.finish();
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/test_defaultView.html b/dom/html/test/imports/test_defaultView.html
new file mode 100644
index 000000000..5f9c02bd8
--- /dev/null
+++ b/dom/html/test/imports/test_defaultView.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1061469
+-->
+<head>
+ <title>Test for Bug 1061469</title>
+ <script type="text/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>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1061469">Mozilla Bug 1061469</a>
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ var success = false;
+ function loaded() {
+ success = true;
+ }
+ function failed() {
+ ok(false, "Import loading failed");
+ }
+ </script>
+ <link rel="import" href="file_simple_import.html" id="import" onload="loaded()" onerror="failed()"></link>
+ <script type="text/javascript">
+ document.defaultView;
+ is(document.getElementById("import").import.defaultView, null, "defaultView is always null for imports");
+ SimpleTest.finish();
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/imports/test_element_upgrade.html b/dom/html/test/imports/test_element_upgrade.html
new file mode 100644
index 000000000..006d56d35
--- /dev/null
+++ b/dom/html/test/imports/test_element_upgrade.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1081107
+-->
+<head>
+ <title>Test for Bug 1081107</title>
+ <script type="text/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>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1081107">Mozilla Bug 1081107</a>
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ var counter = 0;
+ var fcounter = 0;
+ var createdCallbackCalled = false;
+ function loaded() {
+ counter++;
+ }
+ function failed() {
+ fcounter++;
+ }
+ </script>
+ <link rel="import" href="file_element_upgrade.html" onload="loaded()" onerror="failed()"></link>
+ <script type="text/javascript">
+ is(counter, 1, "Imports are loaded");
+ is(fcounter, 0, "No error in imports");
+ ok(createdCallbackCalled, "custom element in import has been upgraded");
+ SimpleTest.finish();
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/imports/test_encoding.html b/dom/html/test/imports/test_encoding.html
new file mode 100644
index 000000000..fa4033f20
--- /dev/null
+++ b/dom/html/test/imports/test_encoding.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1061469
+-->
+<head>
+ <title>Test for Bug 1061469</title>
+ <script type="text/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>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1061469">Mozilla Bug 1061469</a>
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ var success = false;
+ function loaded() {
+ success = true;
+ }
+ function failed() {
+ ok(false, "Import loading failed");
+ }
+ </script>
+ <link rel="import" href="file_encoding.html" id="import" onload="loaded()" onerror="failed()"></link>
+ <script type="text/javascript">
+ is(document.getElementById("import").import.characterSet, "UTF-8", "characterSet should be UTF-8 for imports");
+ SimpleTest.finish();
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/mochitest.ini b/dom/html/test/mochitest.ini
new file mode 100644
index 000000000..99b425df8
--- /dev/null
+++ b/dom/html/test/mochitest.ini
@@ -0,0 +1,608 @@
+[DEFAULT]
+support-files =
+ 347174transform.xsl
+ 347174transformable.xml
+ allowMedia.sjs
+ bug100533_iframe.html
+ bug100533_load.html
+ bug196523-subframe.html
+ bug199692-nested-d2.html
+ bug199692-nested.html
+ bug199692-popup.html
+ bug199692-scrolled.html
+ bug242709_iframe.html
+ bug242709_load.html
+ bug277724_iframe1.html
+ bug277724_iframe2.xhtml
+ bug277890_iframe.html
+ bug277890_load.html
+ bug340800_iframe.txt
+ bug369370-popup.png
+ bug372098-link-target.html
+ bug392567.jar
+ bug392567.jar^headers^
+ bug441930_iframe.html
+ bug445004-inner.html
+ bug445004-inner.js
+ bug445004-outer-abs.html
+ bug445004-outer-rel.html
+ bug445004-outer-write.html
+ bug446483-iframe.html
+ bug448564-echo.sjs
+ bug448564-iframe-1.html
+ bug448564-iframe-2.html
+ bug448564-iframe-3.html
+ bug448564-submit.js
+ bug499092.html
+ bug499092.xml
+ bug514856_iframe.html
+ bug1260704_iframe.html
+ bug1260704_iframe_empty.html
+ bug1292522_iframe.html
+ bug1292522_page.html
+ bug1315146-iframe.html
+ bug1315146-main.html
+ ../../plugins/test/mochitest/plugin-utils.js
+ test_non-ascii-cookie.html^headers^
+ file_bug209275_1.html
+ file_bug209275_2.html
+ file_bug209275_3.html
+ file_bug297761.html
+ file_bug417760.png
+ file_bug893537.html
+ file_bug1260704.png
+ file_formSubmission_img.jpg
+ file_formSubmission_text.txt
+ file_fullscreen-api.html
+ file_fullscreen-backdrop.html
+ file_fullscreen-denied-inner.html
+ file_fullscreen-denied.html
+ file_fullscreen-esc-exit-inner.html
+ file_fullscreen-esc-exit.html
+ file_fullscreen-hidden.html
+ file_fullscreen-lenient-setters.html
+ file_fullscreen-multiple-inner.html
+ file_fullscreen-multiple.html
+ file_fullscreen-navigation.html
+ file_fullscreen-nested.html
+ file_fullscreen-prefixed.html
+ file_fullscreen-plugins.html
+ file_fullscreen-rollback.html
+ file_fullscreen-scrollbar.html
+ file_fullscreen-selector.html
+ file_fullscreen-svg-element.html
+ file_fullscreen-top-layer.html
+ file_fullscreen-unprefix-disabled-inner.html
+ file_fullscreen-unprefix-disabled.html
+ file_fullscreen-utils.js
+ file_iframe_sandbox_a_if1.html
+ file_iframe_sandbox_a_if10.html
+ file_iframe_sandbox_a_if11.html
+ file_iframe_sandbox_a_if12.html
+ file_iframe_sandbox_a_if13.html
+ file_iframe_sandbox_a_if14.html
+ file_iframe_sandbox_a_if15.html
+ file_iframe_sandbox_a_if16.html
+ file_iframe_sandbox_a_if17.html
+ file_iframe_sandbox_a_if18.html
+ file_iframe_sandbox_a_if19.html
+ file_iframe_sandbox_a_if2.html
+ file_iframe_sandbox_a_if3.html
+ file_iframe_sandbox_a_if4.html
+ file_iframe_sandbox_a_if5.html
+ file_iframe_sandbox_a_if6.html
+ file_iframe_sandbox_a_if7.html
+ file_iframe_sandbox_a_if8.html
+ file_iframe_sandbox_a_if9.html
+ file_iframe_sandbox_b_if1.html
+ file_iframe_sandbox_b_if2.html
+ file_iframe_sandbox_b_if3.html
+ file_iframe_sandbox_c_if1.html
+ file_iframe_sandbox_c_if2.html
+ file_iframe_sandbox_c_if3.html
+ file_iframe_sandbox_c_if4.html
+ file_iframe_sandbox_c_if5.html
+ file_iframe_sandbox_c_if6.html
+ file_iframe_sandbox_c_if7.html
+ file_iframe_sandbox_c_if8.html
+ file_iframe_sandbox_c_if9.html
+ file_iframe_sandbox_close.html
+ file_iframe_sandbox_d_if1.html
+ file_iframe_sandbox_d_if10.html
+ file_iframe_sandbox_d_if11.html
+ file_iframe_sandbox_d_if12.html
+ file_iframe_sandbox_d_if13.html
+ file_iframe_sandbox_d_if14.html
+ file_iframe_sandbox_d_if15.html
+ file_iframe_sandbox_d_if16.html
+ file_iframe_sandbox_d_if17.html
+ file_iframe_sandbox_d_if18.html
+ file_iframe_sandbox_d_if19.html
+ file_iframe_sandbox_d_if2.html
+ file_iframe_sandbox_d_if20.html
+ file_iframe_sandbox_d_if21.html
+ file_iframe_sandbox_d_if22.html
+ file_iframe_sandbox_d_if23.html
+ file_iframe_sandbox_d_if3.html
+ file_iframe_sandbox_d_if4.html
+ file_iframe_sandbox_d_if5.html
+ file_iframe_sandbox_d_if6.html
+ file_iframe_sandbox_d_if7.html
+ file_iframe_sandbox_d_if8.html
+ file_iframe_sandbox_d_if9.html
+ file_iframe_sandbox_e_if1.html
+ file_iframe_sandbox_e_if10.html
+ file_iframe_sandbox_e_if11.html
+ file_iframe_sandbox_e_if12.html
+ file_iframe_sandbox_e_if13.html
+ file_iframe_sandbox_e_if14.html
+ file_iframe_sandbox_e_if15.html
+ file_iframe_sandbox_e_if16.html
+ file_iframe_sandbox_e_if2.html
+ file_iframe_sandbox_e_if3.html
+ file_iframe_sandbox_e_if4.html
+ file_iframe_sandbox_e_if5.html
+ file_iframe_sandbox_e_if6.html
+ file_iframe_sandbox_e_if7.html
+ file_iframe_sandbox_e_if8.html
+ file_iframe_sandbox_e_if9.html
+ file_iframe_sandbox_f_if1.html
+ file_iframe_sandbox_f_if2.html
+ file_iframe_sandbox_f_if2.html^headers^
+ file_iframe_sandbox_fail.js
+ file_iframe_sandbox_form_fail.html
+ file_iframe_sandbox_form_pass.html
+ file_iframe_sandbox_g_if1.html
+ file_iframe_sandbox_h_if1.html
+ file_iframe_sandbox_j_if1.html
+ file_iframe_sandbox_j_if2.html
+ file_iframe_sandbox_j_if3.html
+ file_iframe_sandbox_k_if1.html
+ file_iframe_sandbox_k_if2.html
+ file_iframe_sandbox_k_if3.html
+ file_iframe_sandbox_k_if4.html
+ file_iframe_sandbox_k_if5.html
+ file_iframe_sandbox_k_if6.html
+ file_iframe_sandbox_k_if7.html
+ file_iframe_sandbox_k_if8.html
+ file_iframe_sandbox_k_if9.html
+ file_iframe_sandbox_navigation_fail.html
+ file_iframe_sandbox_navigation_pass.html
+ file_iframe_sandbox_navigation_start.html
+ file_iframe_sandbox_open_window_fail.html
+ file_iframe_sandbox_open_window_pass.html
+ file_iframe_sandbox_pass.js
+ file_iframe_sandbox_redirect.html
+ file_iframe_sandbox_redirect.html^headers^
+ file_iframe_sandbox_redirect_target.html
+ file_iframe_sandbox_refresh.html
+ file_iframe_sandbox_refresh.html^headers^
+ file_iframe_sandbox_top_navigation_fail.html
+ file_iframe_sandbox_top_navigation_pass.html
+ file_iframe_sandbox_window_form_fail.html
+ file_iframe_sandbox_window_form_pass.html
+ file_iframe_sandbox_window_navigation_fail.html
+ file_iframe_sandbox_window_navigation_pass.html
+ file_iframe_sandbox_window_top_navigation_pass.html
+ file_iframe_sandbox_window_top_navigation_fail.html
+ file_iframe_sandbox_worker.js
+ file_imports_basics.html
+ file_imports_redirect.html
+ file_imports_redirect.html^headers^
+ file_imports_redirected.html
+ file_srcdoc-2.html
+ file_srcdoc.html
+ file_window_open_close_outer.html
+ file_window_open_close_inner.html
+ formSubmission_chrome.js
+ form_submit_server.sjs
+ formData_worker.js
+ formData_test.js
+ image.png
+ image-allow-credentials.png
+ image-allow-credentials.png^headers^
+ nnc_lockup.gif
+ reflect.js
+ file_ignoreuserfocus.html
+ simpleFileOpener.js
+ file_mozaudiochannel.html
+ file_bug1166138_1x.png
+ file_bug1166138_2x.png
+ file_bug1166138_def.png
+
+[test_a_text.html]
+[test_anchor_href_cache_invalidation.html]
+[test_applet_attributes_reflection.html]
+[test_base_attributes_reflection.html]
+[test_bug100533.html]
+[test_bug109445.html]
+[test_bug109445.xhtml]
+[test_bug1297.html]
+[test_bug1366.html]
+[test_bug1400.html]
+[test_bug143220.html]
+[test_bug182279.html]
+[test_bug2082.html]
+[test_bug209275.xhtml]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_bug237071.html]
+[test_bug242709.html]
+[test_bug24958.html]
+[test_bug274626.html]
+[test_bug277724.html]
+[test_bug277890.html]
+[test_bug287465.html]
+[test_bug295561.html]
+[test_bug297761.html]
+[test_bug300691-1.html]
+[test_bug300691-2.html]
+[test_bug300691-3.xhtml]
+[test_bug330705-1.html]
+[test_bug332246.html]
+[test_bug332893-1.html]
+[test_bug332893-2.html]
+[test_bug332893-3.html]
+[test_bug332893-4.html]
+[test_bug332893-5.html]
+[test_bug332893-6.html]
+[test_bug332893-7.html]
+[test_bug3348.html]
+[test_bug340800.html]
+[test_bug347174.html]
+[test_bug347174_write.html]
+[test_bug347174_xsl.html]
+[test_bug347174_xslp.html]
+[test_bug353415-1.html]
+[test_bug353415-2.html]
+[test_bug371375.html]
+[test_bug372098.html]
+[test_bug373589.html]
+[test_bug375003-1.html]
+[test_bug375003-2.html]
+[test_bug377624.html]
+[test_bug383383.html]
+[test_bug383383_2.xhtml]
+[test_bug384419.html]
+[test_bug386496.html]
+[test_bug386728.html]
+[test_bug386996.html]
+[test_bug388558.html]
+[test_bug388746.html]
+[test_bug388794.html]
+[test_bug389797.html]
+[test_bug390975.html]
+[test_bug391994.html]
+[test_bug392567.html]
+[test_bug394700.html]
+[test_bug395107.html]
+[test_bug401160.xhtml]
+[test_bug405242.html]
+[test_bug406596.html]
+[test_bug417760.html]
+[test_bug421640.html]
+[test_bug424698.html]
+[test_bug428135.xhtml]
+[test_bug430351.html]
+[test_bug430392.html]
+[test_bug441930.html]
+[test_bug442801.html]
+[test_bug448166.html]
+[test_bug456229.html]
+[test_bug458037.xhtml]
+[test_bug460568.html]
+[test_bug481335.xhtml]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_bug500885.html]
+[test_bug514856.html]
+skip-if = toolkit == 'android'
+[test_bug518122.html]
+[test_bug519987.html]
+[test_bug523771.html]
+[test_bug529819.html]
+[test_bug529859.html]
+[test_bug535043.html]
+[test_bug536891.html]
+[test_bug536895.html]
+[test_bug546995.html]
+[test_bug547850.html]
+[test_bug551846.html]
+[test_bug555567.html]
+[test_bug556645.html]
+[test_bug557087-1.html]
+[test_bug557087-2.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_bug557087-3.html]
+[test_bug557087-4.html]
+[test_bug557087-5.html]
+[test_bug557087-6.html]
+[test_bug557620.html]
+[test_bug558788-1.html]
+[test_bug558788-2.html]
+[test_bug560112.html]
+[test_bug561634.html]
+[test_bug561636.html]
+[test_bug561640.html]
+[test_bug564001.html]
+[test_bug566046.html]
+[test_bug567938-1.html]
+[test_bug567938-2.html]
+[test_bug567938-3.html]
+[test_bug567938-4.html]
+[test_bug569955.html]
+[test_bug573969.html]
+[test_bug579079.html]
+[test_bug582412-1.html]
+[test_bug582412-2.html]
+[test_bug583514.html]
+[test_bug583533.html]
+[test_bug586763.html]
+[test_bug586786.html]
+[test_bug587469.html]
+[test_bug589.html]
+[test_bug590353-1.html]
+[test_bug590353-2.html]
+[test_bug590363.html]
+[test_bug592802.html]
+[test_bug593689.html]
+[test_bug595429.html]
+[test_bug595447.html]
+[test_bug595449.html]
+[test_bug596350.html]
+[test_bug596511.html]
+[test_bug598643.html]
+[test_bug598833-1.html]
+[test_bug600155.html]
+[test_bug601030.html]
+[test_bug605124-1.html]
+[test_bug605124-2.html]
+[test_bug605125-1.html]
+[test_bug605125-2.html]
+[test_bug606817.html]
+[test_bug607145.html]
+[test_bug610212.html]
+[test_bug610687.html]
+[test_bug611189.html]
+[test_bug612730.html]
+skip-if = toolkit == 'android' # form control not selected/checked with synthesizeMouse
+[test_bug613113.html]
+[test_bug613019.html]
+[test_bug613722.html]
+[test_bug613979.html]
+[test_bug615595.html]
+[test_bug615833.html]
+skip-if = toolkit == 'android' || os == 'mac' #TIMED_OUT # form control not selected/checked with synthesizeMouse, osx(bug 1275664)
+[test_bug617528.html]
+[test_bug618948.html]
+[test_bug619278.html]
+[test_bug622558.html]
+[test_bug622597.html]
+[test_bug623291.html]
+[test_bug6296.html]
+[test_bug629801.html]
+[test_bug633058.html]
+[test_bug636336.html]
+[test_bug641219.html]
+[test_bug643051.html]
+[test_bug646157.html]
+[test_bug649134.html]
+# This extra subdirectory is needed due to the nature of this test.
+# With the bug, the test loads the base URL of the bug649134/file_*.sjs
+# files, and the mochitest server responds with the contents of index.html if
+# it exists in that case, which we use to detect failure.
+# We cannot have index.html in this directory because it would prevent
+# running the tests here.
+support-files =
+ bug649134/file_bug649134-1.sjs
+ bug649134/file_bug649134-2.sjs
+ bug649134/index.html
+[test_bug651956.html]
+[test_bug658746.html]
+[test_bug659596.html]
+[test_bug659743.xml]
+[test_bug660663.html]
+[test_bug660959-1.html]
+[test_bug660959-2.html]
+[test_bug660959-3.html]
+[test_bug666200.html]
+[test_bug666666.html]
+[test_bug669012.html]
+[test_bug674558.html]
+[test_bug674927.html]
+[test_bug677463.html]
+[test_bug677658.html]
+[test_bug682886.html]
+[test_bug691.html]
+[test_bug694.html]
+[test_bug694503.html]
+[test_bug696.html]
+[test_bug717819.html]
+[test_bug742030.html]
+[test_bug742549.html]
+[test_bug745685.html]
+[test_bug763626.html]
+[test_bug780993.html]
+[test_bug787134.html]
+[test_bug797113.html]
+[test_bug803677.html]
+[test_bug821307.html]
+[test_bug827126.html]
+[test_bug838582.html]
+[test_bug839371.html]
+[test_bug839913.html]
+[test_bug841466.html]
+[test_bug845057.html]
+[test_bug869040.html]
+[test_bug870787.html]
+[test_bug874758.html]
+[test_bug879319.html]
+[test_bug885024.html]
+[test_bug893537.html]
+[test_bug95530.html]
+[test_bug969346.html]
+[test_bug982039.html]
+[test_bug1003539.html]
+[test_bug1045270.html]
+[test_bug1146116.html]
+[test_bug1264157.html]
+[test_bug1287321.html]
+[test_change_crossorigin.html]
+[test_checked.html]
+[test_dir_attributes_reflection.html]
+[test_dl_attributes_reflection.html]
+[test_element_prototype.html]
+[test_embed_attributes_reflection.html]
+[test_focusshift_button.html]
+[test_formData.html]
+[test_formSubmission.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_formSubmission2.html]
+skip-if = toolkit == 'android'
+[test_formelements.html]
+[test_fullscreen-api.html]
+tags = fullscreen
+skip-if = toolkit == 'android'
+[test_fullscreen-api-race.html]
+tags = fullscreen
+skip-if = toolkit == 'android' # just copy the conditions from the test above
+[test_hidden.html]
+[test_html_attributes_reflection.html]
+[test_htmlcollection.html]
+[test_iframe_sandbox_general.html]
+tags = openwindow
+[test_iframe_sandbox_inheritance.html]
+tags = openwindow
+[test_iframe_sandbox_modal.html]
+tags = openwindow
+skip-if = toolkit == 'android' || e10s #modal tests fail on android
+[test_iframe_sandbox_navigation.html]
+tags = openwindow
+[test_iframe_sandbox_navigation2.html]
+tags = openwindow
+[test_iframe_sandbox_plugins.html]
+skip-if = toolkit == 'android' # plugins not supported
+[test_iframe_sandbox_popups.html]
+tags = openwindow
+[test_iframe_sandbox_popups_inheritance.html]
+tags = openwindow
+skip-if = toolkit == 'android' # bug 939642
+[test_iframe_sandbox_redirect.html]
+[test_iframe_sandbox_refresh.html]
+[test_iframe_sandbox_same_origin.html]
+[test_iframe_sandbox_workers.html]
+[test_img_attributes_reflection.html]
+[test_imageSrcSet.html]
+[test_imports_basics.html]
+[test_imports_redirect.html]
+[test_imports_nonhttp.html]
+[test_imports_nested.html]
+[test_imports_nested_2.html]
+[test_li_attributes_reflection.html]
+[test_link_attributes_reflection.html]
+[test_link_sizes.html]
+[test_map_attributes_reflection.html]
+[test_meta_attributes_reflection.html]
+[test_mod_attributes_reflection.html]
+[test_mozaudiochannel.html]
+[test_named_options.html]
+[test_nested_invalid_fieldsets.html]
+[test_object_attributes_reflection.html]
+[test_object_plugin_nav.html]
+skip-if = toolkit == 'android' # plugins not supported
+[test_ol_attributes_reflection.html]
+[test_option_defaultSelected.html]
+[test_option_selected_state.html]
+[test_param_attributes_reflection.html]
+[test_q_attributes_reflection.html]
+[test_restore_from_parser_fragment.html]
+[test_rowscollection.html]
+[test_srcdoc-2.html]
+[test_srcdoc.html]
+[test_style_attributes_reflection.html]
+[test_track.html]
+[test_ul_attributes_reflection.html]
+[test_input_files_not_nsIFile.html]
+[test_ignoreuserfocus.html]
+[test_fragment_form_pointer.html]
+[test_bug1682.html]
+[test_bug1823.html]
+[test_bug57600.html]
+[test_bug196523.html]
+[test_bug199692.html]
+skip-if = toolkit == 'android' #bug 811644
+[test_bug172261.html]
+[test_bug255820.html]
+[test_bug259332.html]
+[test_bug311681.html]
+[test_bug311681.xhtml]
+[test_bug324378.html]
+[test_bug332848.xhtml]
+[test_bug340017.xhtml]
+[test_bug359657.html]
+[test_bug369370.html]
+skip-if = toolkit == "android" || toolkit == "windows" # disabled on Windows because of bug 1234520
+[test_bug380383.html]
+[test_bug391777.html]
+skip-if = toolkit == 'android' || e10s
+[test_bug402680.html]
+[test_bug403868.html]
+[test_bug403868.xhtml]
+[test_bug435128.html]
+skip-if = true # Disabled for timeouts.
+[test_bug463104.html]
+[test_form-parsing.html]
+[test_viewport.html]
+[test_documentAll.html]
+[test_document-element-inserted.html]
+[test_document.watch.html]
+[test_bug445004.html]
+skip-if = true || toolkit == 'android' # Disabled permanently (bug 559932).
+[test_bug446483.html]
+skip-if = toolkit == 'android'
+[test_bug448564.html]
+[test_bug478251.html]
+[test_bug481440.html]
+[test_bug481647.html]
+[test_bug482659.html]
+[test_bug486741.html]
+[test_bug489532.html]
+[test_bug497242.xhtml]
+[test_bug499092.html]
+[test_bug512367.html]
+[test_bug677495.html]
+[test_bug677495-1.html]
+[test_bug741266.html]
+skip-if = toolkit == "android" || toolkit == "windows" # Android: needs control of popup window size, windows(bug 1234520)
+[test_non-ascii-cookie.html]
+support-files = file_cookiemanager.js
+[test_bug765780.html]
+[test_bug871161.html]
+support-files = file_bug871161-1.html file_bug871161-2.html
+[test_bug1013316.html]
+[test_hash_encoded.html]
+[test_bug1081037.html]
+[test_window_open_close.html]
+tags = openwindow
+[test_viewport_resize.html]
+[test_image_clone_load.html]
+[test_bug1203668.html]
+[test_bug1166138.html]
+[test_bug1230665.html]
+[test_filepicker_default_directory.html]
+skip-if = toolkit == 'android'
+[test_bug1233598.html]
+[test_bug1250401.html]
+[test_bug1260664.html]
+[test_bug1261673.html]
+skip-if = (os == 'android' || os == 'mac')
+[test_bug1261674-1.html]
+skip-if = (os == 'android' || os == 'mac')
+[test_bug1261674-2.html]
+skip-if = (os == 'android' || os == 'mac')
+[test_bug1260704.html]
+[test_allowMedia.html]
+[test_bug1292522_same_domain_with_different_port_number.html]
+[test_bug1295719_event_sequence_for_arrow_keys.html]
+skip-if = os == "android" # up/down arrow keys not supported on android
+[test_bug1295719_event_sequence_for_number_keys.html]
+[test_bug1310865.html]
+[test_bug1315146.html]
diff --git a/dom/html/test/nnc_lockup.gif b/dom/html/test/nnc_lockup.gif
new file mode 100644
index 000000000..f746bb71d
--- /dev/null
+++ b/dom/html/test/nnc_lockup.gif
Binary files differ
diff --git a/dom/html/test/reflect.js b/dom/html/test/reflect.js
new file mode 100644
index 000000000..abc2697a9
--- /dev/null
+++ b/dom/html/test/reflect.js
@@ -0,0 +1,625 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * reflect.js is a collection of methods to test HTML attribute reflection.
+ * Each of attribute is reflected differently, depending on various parameters,
+ * see:
+ * http://www.whatwg.org/html/#reflecting-content-attributes-in-idl-attributes
+ *
+ * Do not forget to add these line at the beginning of each new reflect* method:
+ * ok(attr in element, attr + " should be an IDL attribute of this element");
+ * is(typeof element[attr], <type>, attr + " IDL attribute should be a <type>");
+ */
+
+/**
+ * Checks that a given attribute is correctly reflected as a string.
+ *
+ * @param aParameters Object object containing the parameters, which are:
+ * - element Element node to test
+ * - attribute String name of the attribute
+ * OR
+ * attribute Object object containing two attributes, 'content' and 'idl'
+ * - otherValues Array [optional] other values to test in addition of the default ones
+ * - extendedAttributes Object object which can have 'TreatNullAs': "EmptyString"
+ */
+function reflectString(aParameters)
+{
+ var element = aParameters.element;
+ var contentAttr = typeof aParameters.attribute === "string"
+ ? aParameters.attribute : aParameters.attribute.content;
+ var idlAttr = typeof aParameters.attribute === "string"
+ ? aParameters.attribute : aParameters.attribute.idl;
+ var otherValues = aParameters.otherValues !== undefined
+ ? aParameters.otherValues : [];
+ var treatNullAs = aParameters.extendedAttributes ?
+ aParameters.extendedAttributes.TreatNullAs : null;
+
+ ok(idlAttr in element,
+ idlAttr + " should be an IDL attribute of this element");
+ is(typeof element[idlAttr], "string",
+ "'" + idlAttr + "' IDL attribute should be a string");
+
+ // Tests when the attribute isn't set.
+ is(element.getAttribute(contentAttr), null,
+ "When not set, the content attribute should be null.");
+ is(element[idlAttr], "",
+ "When not set, the IDL attribute should return the empty string");
+
+ /**
+ * TODO: as long as null stringification doesn't follow the WebIDL
+ * specifications, don't add it to the loop below and keep it here.
+ */
+ element.setAttribute(contentAttr, null);
+ is(element.getAttribute(contentAttr), "null",
+ "null should have been stringified to 'null' for '" + contentAttr + "'");
+ is(element[idlAttr], "null",
+ "null should have been stringified to 'null' for '" + idlAttr + "'");
+ element.removeAttribute(contentAttr);
+
+ element[idlAttr] = null;
+ if (treatNullAs == "EmptyString") {
+ is(element.getAttribute(contentAttr), "",
+ "null should have been stringified to '' for '" + contentAttr + "'");
+ is(element[idlAttr], "",
+ "null should have been stringified to '' for '" + idlAttr + "'");
+ } else {
+ is(element.getAttribute(contentAttr), "null",
+ "null should have been stringified to 'null' for '" + contentAttr + "'");
+ is(element[idlAttr], "null",
+ "null should have been stringified to 'null' for '" + contentAttr + "'");
+ }
+ element.removeAttribute(contentAttr);
+
+ // Tests various strings.
+ var stringsToTest = [
+ // [ test value, expected result ]
+ [ "", "" ],
+ [ "null", "null" ],
+ [ "undefined", "undefined" ],
+ [ "foo", "foo" ],
+ [ contentAttr, contentAttr ],
+ [ idlAttr, idlAttr ],
+ // TODO: uncomment this when null stringification will follow the specs.
+ // [ null, "null" ],
+ [ undefined, "undefined" ],
+ [ true, "true" ],
+ [ false, "false" ],
+ [ 42, "42" ],
+ // ES5, verse 8.12.8.
+ [ { toString: function() { return "foo" } },
+ "foo" ],
+ [ { valueOf: function() { return "foo" } },
+ "[object Object]" ],
+ [ { valueOf: function() { return "quux" },
+ toString: undefined },
+ "quux" ],
+ [ { valueOf: function() { return "foo" },
+ toString: function() { return "bar" } },
+ "bar" ]
+ ];
+
+ otherValues.forEach(function(v) { stringsToTest.push([v, v]) });
+
+ stringsToTest.forEach(function([v, r]) {
+ element.setAttribute(contentAttr, v);
+ is(element[idlAttr], r,
+ "IDL attribute '" + idlAttr + "' should return the value it has been set to.");
+ is(element.getAttribute(contentAttr), r,
+ "Content attribute '" + contentAttr + "'should return the value it has been set to.");
+ element.removeAttribute(contentAttr);
+
+ element[idlAttr] = v;
+ is(element[idlAttr], r,
+ "IDL attribute '" + idlAttr + "' should return the value it has been set to.");
+ is(element.getAttribute(contentAttr), r,
+ "Content attribute '" + contentAttr + "' should return the value it has been set to.");
+ element.removeAttribute(contentAttr);
+ });
+
+ // Tests after removeAttribute() is called. Should be equivalent with not set.
+ is(element.getAttribute(contentAttr), null,
+ "When not set, the content attribute should be null.");
+ is(element[idlAttr], "",
+ "When not set, the IDL attribute should return the empty string");
+}
+
+/**
+ * Checks that a given attribute name for a given element is correctly reflected
+ * as an unsigned int.
+ *
+ * @param aParameters Object object containing the parameters, which are:
+ * - element Element node to test on
+ * - attribute String name of the attribute
+ * - nonZero Boolean whether the attribute should be non-null
+ * - defaultValue Integer [optional] default value, if different from the default one
+ */
+function reflectUnsignedInt(aParameters)
+{
+ var element = aParameters.element;
+ var attr = aParameters.attribute;
+ var nonZero = aParameters.nonZero;
+ var defaultValue = aParameters.defaultValue;
+ var fallback = aParameters.fallback;
+
+ if (defaultValue === undefined) {
+ if (nonZero) {
+ defaultValue = 1;
+ } else {
+ defaultValue = 0;
+ }
+ }
+
+ if (fallback === undefined) {
+ fallback = false;
+ }
+
+ ok(attr in element, attr + " should be an IDL attribute of this element");
+ is(typeof element[attr], "number", attr + " IDL attribute should be a number");
+
+ // Check default value.
+ is(element[attr], defaultValue, "default value should be " + defaultValue);
+ ok(!element.hasAttribute(attr), attr + " shouldn't be present");
+
+ var values = [ 1, 3, 42, 2147483647 ];
+
+ for (var value of values) {
+ element[attr] = value;
+ is(element[attr], value, "." + attr + " should be equals " + value);
+ is(element.getAttribute(attr), String(value),
+ "@" + attr + " should be equals " + value);
+
+ element.setAttribute(attr, value);
+ is(element[attr], value, "." + attr + " should be equals " + value);
+ is(element.getAttribute(attr), String(value),
+ "@" + attr + " should be equals " + value);
+ }
+
+ // -3000000000 is equivalent to 1294967296 when using the IDL attribute.
+ element[attr] = -3000000000;
+ is(element[attr], 1294967296, "." + attr + " should be equals to 1294967296");
+ is(element.getAttribute(attr), "1294967296",
+ "@" + attr + " should be equals to 1294967296");
+
+ // When setting the content attribute, it's a string so it will be invalid.
+ element.setAttribute(attr, -3000000000);
+ is(element.getAttribute(attr), "-3000000000",
+ "@" + attr + " should be equals to " + -3000000000);
+ is(element[attr], defaultValue,
+ "." + attr + " should be equals to " + defaultValue);
+
+ // When interpreted as unsigned 32-bit integers, all of these fall between
+ // 2^31 and 2^32 - 1, so per spec they return the default value.
+ var nonValidValues = [ -2147483648, -1, 3147483647];
+
+ for (var value of nonValidValues) {
+ element[attr] = value;
+ is(element.getAttribute(attr), String(defaultValue),
+ "@" + attr + " should be equals to " + defaultValue);
+ is(element[attr], defaultValue,
+ "." + attr + " should be equals to " + defaultValue);
+ }
+
+ for (var values of nonValidValues) {
+ element.setAttribute(attr, values[0]);
+ is(element.getAttribute(attr), String(values[0]),
+ "@" + attr + " should be equals to " + values[0]);
+ is(element[attr], defaultValue,
+ "." + attr + " should be equals to " + defaultValue);
+ }
+
+ // Setting to 0 should throw an error if nonZero is true.
+ var caught = false;
+ try {
+ element[attr] = 0;
+ } catch(e) {
+ caught = true;
+ is(e.name, "IndexSizeError", "exception should be IndexSizeError");
+ is(e.code, DOMException.INDEX_SIZE_ERR, "exception code should be INDEX_SIZE_ERR");
+ }
+
+ if (nonZero && !fallback) {
+ ok(caught, "an exception should have been caught");
+ } else {
+ ok(!caught, "no exception should have been caught");
+ }
+
+ // If 0 is set in @attr, it will be ignored when calling .attr.
+ element.setAttribute(attr, "0");
+ is(element.getAttribute(attr), "0", "@" + attr + " should be equals to 0");
+ if (nonZero) {
+ is(element[attr], defaultValue,
+ "." + attr + " should be equals to " + defaultValue);
+ } else {
+ is(element[attr], 0, "." + attr + " should be equals to 0");
+ }
+}
+
+/**
+ * Checks that a given attribute is correctly reflected as limited to known
+ * values enumerated attribute.
+ *
+ * @param aParameters Object object containing the parameters, which are:
+ * - element Element node to test on
+ * - attribute String name of the attribute
+ * OR
+ * attribute Object object containing two attributes, 'content' and 'idl'
+ * - validValues Array valid values we support
+ * - invalidValues Array invalid values
+ * - defaultValue String [optional] default value when no valid value is set
+ * OR
+ * defaultValue Object [optional] object containing two attributes, 'invalid' and 'missing'
+ * - unsupportedValues Array [optional] valid values we do not support
+ * - nullable boolean [optional] whether the attribute is nullable
+ */
+function reflectLimitedEnumerated(aParameters)
+{
+ var element = aParameters.element;
+ var contentAttr = typeof aParameters.attribute === "string"
+ ? aParameters.attribute : aParameters.attribute.content;
+ var idlAttr = typeof aParameters.attribute === "string"
+ ? aParameters.attribute : aParameters.attribute.idl;
+ var validValues = aParameters.validValues;
+ var invalidValues = aParameters.invalidValues;
+ var defaultValueInvalid = aParameters.defaultValue === undefined
+ ? "" : typeof aParameters.defaultValue === "string"
+ ? aParameters.defaultValue : aParameters.defaultValue.invalid
+ var defaultValueMissing = aParameters.defaultValue === undefined
+ ? "" : typeof aParameters.defaultValue === "string"
+ ? aParameters.defaultValue : aParameters.defaultValue.missing
+ var unsupportedValues = aParameters.unsupportedValues !== undefined
+ ? aParameters.unsupportedValues : [];
+ var nullable = aParameters.nullable;
+
+ ok(idlAttr in element, idlAttr + " should be an IDL attribute of this element");
+ if (nullable) {
+ // The missing value default is null, which is typeof == "object"
+ is(typeof element[idlAttr], "object", "'" + idlAttr + "' IDL attribute should be null, which has typeof == object");
+ is(element[idlAttr], null, "'" + idlAttr + "' IDL attribute should be null");
+ } else {
+ is(typeof element[idlAttr], "string", "'" + idlAttr + "' IDL attribute should be a string");
+ }
+
+ if (nullable) {
+ element.setAttribute(contentAttr, "something");
+ // Now it will be a string
+ is(typeof element[idlAttr], "string", "'" + idlAttr + "' IDL attribute should be a string");
+ }
+
+ // Explicitly check the default value.
+ element.removeAttribute(contentAttr);
+ is(element[idlAttr], defaultValueMissing,
+ "When no attribute is set, the value should be the default value.");
+
+ // Check valid values.
+ validValues.forEach(function (v) {
+ element.setAttribute(contentAttr, v);
+ is(element[idlAttr], v,
+ "'" + v + "' should be accepted as a valid value for " + idlAttr);
+ is(element.getAttribute(contentAttr), v,
+ "Content attribute should return the value it has been set to.");
+ element.removeAttribute(contentAttr);
+
+ element.setAttribute(contentAttr, v.toUpperCase());
+ is(element[idlAttr], v,
+ "Enumerated attributes should be case-insensitive.");
+ is(element.getAttribute(contentAttr), v.toUpperCase(),
+ "Content attribute should not be lower-cased.");
+ element.removeAttribute(contentAttr);
+
+ element[idlAttr] = v;
+ is(element[idlAttr], v,
+ "'" + v + "' should be accepted as a valid value for " + idlAttr);
+ is(element.getAttribute(contentAttr), v,
+ "Content attribute should return the value it has been set to.");
+ element.removeAttribute(contentAttr);
+
+ element[idlAttr] = v.toUpperCase();
+ is(element[idlAttr], v,
+ "Enumerated attributes should be case-insensitive.");
+ is(element.getAttribute(contentAttr), v.toUpperCase(),
+ "Content attribute should not be lower-cased.");
+ element.removeAttribute(contentAttr);
+ });
+
+ // Check invalid values.
+ invalidValues.forEach(function (v) {
+ element.setAttribute(contentAttr, v);
+ is(element[idlAttr], defaultValueInvalid,
+ "When the content attribute is set to an invalid value, the default value should be returned.");
+ is(element.getAttribute(contentAttr), v,
+ "Content attribute should not have been changed.");
+ element.removeAttribute(contentAttr);
+
+ element[idlAttr] = v;
+ is(element[idlAttr], defaultValueInvalid,
+ "When the value is set to an invalid value, the default value should be returned.");
+ is(element.getAttribute(contentAttr), v,
+ "Content attribute should not have been changed.");
+ element.removeAttribute(contentAttr);
+ });
+
+ // Check valid values we currently do not support.
+ // Basically, it's like the checks for the valid values but with some todo's.
+ unsupportedValues.forEach(function (v) {
+ element.setAttribute(contentAttr, v);
+ todo_is(element[idlAttr], v,
+ "'" + v + "' should be accepted as a valid value for " + idlAttr);
+ is(element.getAttribute(contentAttr), v,
+ "Content attribute should return the value it has been set to.");
+ element.removeAttribute(contentAttr);
+
+ element.setAttribute(contentAttr, v.toUpperCase());
+ todo_is(element[idlAttr], v,
+ "Enumerated attributes should be case-insensitive.");
+ is(element.getAttribute(contentAttr), v.toUpperCase(),
+ "Content attribute should not be lower-cased.");
+ element.removeAttribute(contentAttr);
+
+ element[idlAttr] = v;
+ todo_is(element[idlAttr], v,
+ "'" + v + "' should be accepted as a valid value for " + idlAttr);
+ is(element.getAttribute(contentAttr), v,
+ "Content attribute should return the value it has been set to.");
+ element.removeAttribute(contentAttr);
+
+ element[idlAttr] = v.toUpperCase();
+ todo_is(element[idlAttr], v,
+ "Enumerated attributes should be case-insensitive.");
+ is(element.getAttribute(contentAttr), v.toUpperCase(),
+ "Content attribute should not be lower-cased.");
+ element.removeAttribute(contentAttr);
+ });
+
+ if (nullable) {
+ is(defaultValueMissing, null,
+ "Missing default value should be null for nullable attributes");
+ ok(validValues.length > 0, "We better have at least one valid value");
+ element.setAttribute(contentAttr, validValues[0]);
+ ok(element.hasAttribute(contentAttr),
+ "Should have content attribute: we just set it");
+ element[idlAttr] = null;
+ ok(!element.hasAttribute(contentAttr),
+ "Should have removed content attribute");
+ }
+}
+
+/**
+ * Checks that a given attribute is correctly reflected as a boolean.
+ *
+ * @param aParameters Object object containing the parameters, which are:
+ * - element Element node to test on
+ * - attribute String name of the attribute
+ * OR
+ * attribute Object object containing two attributes, 'content' and 'idl'
+ */
+function reflectBoolean(aParameters)
+{
+ var element = aParameters.element;
+ var contentAttr = typeof aParameters.attribute === "string"
+ ? aParameters.attribute : aParameters.attribute.content;
+ var idlAttr = typeof aParameters.attribute === "string"
+ ? aParameters.attribute : aParameters.attribute.idl;
+
+ ok(idlAttr in element,
+ idlAttr + " should be an IDL attribute of this element");
+ is(typeof element[idlAttr], "boolean",
+ idlAttr + " IDL attribute should be a boolean");
+
+ // Tests when the attribute isn't set.
+ is(element.getAttribute(contentAttr), null,
+ "When not set, the content attribute should be null.");
+ is(element[idlAttr], false,
+ "When not set, the IDL attribute should return false");
+
+ /**
+ * Test various values.
+ * Each value to test is actually an object containing a 'value' property
+ * containing the value to actually test, a 'stringified' property containing
+ * the stringified value and a 'result' property containing the expected
+ * result when the value is set to the IDL attribute.
+ */
+ var valuesToTest = [
+ { value: true, stringified: "true", result: true },
+ { value: false, stringified: "false", result: false },
+ { value: "true", stringified: "true", result: true },
+ { value: "false", stringified: "false", result: true },
+ { value: "foo", stringified: "foo", result: true },
+ { value: idlAttr, stringified: idlAttr, result: true },
+ { value: contentAttr, stringified: contentAttr, result: true },
+ { value: "null", stringified: "null", result: true },
+ { value: "undefined", stringified: "undefined", result: true },
+ { value: "", stringified: "", result: false },
+ { value: undefined, stringified: "undefined", result: false },
+ { value: null, stringified: "null", result: false },
+ { value: +0, stringified: "0", result: false },
+ { value: -0, stringified: "0", result: false },
+ { value: NaN, stringified: "NaN", result: false },
+ { value: 42, stringified: "42", result: true },
+ { value: Infinity, stringified: "Infinity", result: true },
+ { value: -Infinity, stringified: "-Infinity", result: true },
+ // ES5, verse 9.2.
+ { value: { toString: function() { return "foo" } }, stringified: "foo",
+ result: true },
+ { value: { valueOf: function() { return "foo" } },
+ stringified: "[object Object]", result: true },
+ { value: { valueOf: function() { return "quux" }, toString: undefined },
+ stringified: "quux", result: true },
+ { value: { valueOf: function() { return "foo" },
+ toString: function() { return "bar" } }, stringified: "bar",
+ result: true },
+ { value: { valueOf: function() { return false } },
+ stringified: "[object Object]", result: true },
+ { value: { foo: false, bar: false }, stringified: "[object Object]",
+ result: true },
+ { value: { }, stringified: "[object Object]", result: true },
+ ];
+
+ valuesToTest.forEach(function(v) {
+ element.setAttribute(contentAttr, v.value);
+ is(element[idlAttr], true,
+ "IDL attribute should return always return 'true' if the content attribute has been set");
+ is(element.getAttribute(contentAttr), v.stringified,
+ "Content attribute should return the stringified value it has been set to.");
+ element.removeAttribute(contentAttr);
+
+ element[idlAttr] = v.value;
+ is(element[idlAttr], v.result, "IDL attribute should return " + v.result);
+ is(element.getAttribute(contentAttr), v.result ? "" : null,
+ v.result ? "Content attribute should return the empty string."
+ : "Content attribute should return null.");
+ is(element.hasAttribute(contentAttr), v.result,
+ v.result ? contentAttr + " should not be present"
+ : contentAttr + " should be present");
+ element.removeAttribute(contentAttr);
+ });
+
+ // Tests after removeAttribute() is called. Should be equivalent with not set.
+ is(element.getAttribute(contentAttr), null,
+ "When not set, the content attribute should be null.");
+ is(element[contentAttr], false,
+ "When not set, the IDL attribute should return false");
+}
+
+/**
+ * Checks that a given attribute name for a given element is correctly reflected
+ * as an signed integer.
+ *
+ * @param aParameters Object object containing the parameters, which are:
+ * - element Element node to test on
+ * - attribute String name of the attribute
+ * - nonNegative Boolean true if the attribute is limited to 'non-negative numbers', false otherwise
+ * - defaultValue Integer [optional] default value, if one exists
+ */
+function reflectInt(aParameters)
+{
+ // Expected value returned by .getAttribute() when |value| has been previously passed to .setAttribute().
+ function expectedGetAttributeResult(value) {
+ return String(value);
+ }
+
+ function stringToInteger(value, nonNegative, defaultValue) {
+ // Parse: Ignore leading whitespace, find [+/-][numbers]
+ var result = /^[ \t\n\f\r]*([\+\-]?[0-9]+)/.exec(value);
+ if (result) {
+ var resultInt = parseInt(result[1], 10);
+ if ((nonNegative ? 0 : -0x80000000) <= resultInt && resultInt <= 0x7FFFFFFF) {
+ // If the value is within allowed value range for signed/unsigned
+ // integer, return it -- but add 0 to it to convert a possible -0 into
+ // +0, the only zero present in the signed integer range.
+ return resultInt + 0;
+ }
+ }
+ return defaultValue;
+ }
+
+ // Expected value returned by .getAttribute(attr) or .attr if |value| has been set via the IDL attribute.
+ function expectedIdlAttributeResult(value) {
+ // This returns the result of calling the ES ToInt32 algorithm on value.
+ return value << 0;
+ }
+
+ var element = aParameters.element;
+ var attr = aParameters.attribute;
+ var nonNegative = aParameters.nonNegative;
+
+ var defaultValue = aParameters.defaultValue !== undefined
+ ? aParameters.defaultValue
+ : nonNegative ? -1 : 0;
+
+ ok(attr in element, attr + " should be an IDL attribute of this element");
+ is(typeof element[attr], "number", attr + " IDL attribute should be a number");
+
+ // Check default value.
+ is(element[attr], defaultValue, "default value should be " + defaultValue);
+ ok(!element.hasAttribute(attr), attr + " shouldn't be present");
+
+ /**
+ * Test various values.
+ * value: The test value that will be set using both setAttribute(value) and
+ * element[attr] = value
+ */
+ var valuesToTest = [
+ // Test numeric inputs up to max signed integer
+ 0, 1, 55555, 2147483647, +42,
+ // Test string inputs up to max signed integer
+ "0", "1", "777777", "2147483647", "+42",
+ // Test negative numeric inputs up to min signed integer
+ -0, -1, -3333, -2147483648,
+ // Test negative string inputs up to min signed integer
+ "-0", "-1", "-222", "-2147483647", "-2147483648",
+ // Test numeric inputs that are outside legal 32 bit signed values
+ -2147483649, -3000000000, -4294967296, 2147483649, 4000000000, -4294967297,
+ // Test string inputs with extra padding
+ " 1111111", " 23456 ",
+ // Test non-numeric string inputs
+ "", " ", "+", "-", "foo", "+foo", "-foo", "+ foo", "- foo", "+-2", "-+2", "++2", "--2", "hello1234", "1234hello",
+ "444 world 555", "why 567 what", "-3 nots", "2e5", "300e2", "42+-$", "+42foo", "-514not", "\vblah", "0x10FFFF", "-0xABCDEF",
+ // Test decimal numbers
+ 1.2345, 42.0, 3456789.1, -2.3456, -6789.12345, -2147483649.1234,
+ // Test decimal strings
+ "1.2345", "42.0", "3456789.1", "-2.3456", "-6789.12345", "-2147483649.1234",
+ // Test special values
+ undefined, null, NaN, Infinity, -Infinity,
+ ];
+
+ valuesToTest.forEach(function(v) {
+ var intValue = stringToInteger(v, nonNegative, defaultValue);
+
+ element.setAttribute(attr, v);
+
+ is(element.getAttribute(attr), expectedGetAttributeResult(v), element.localName + ".setAttribute(" +
+ attr + ", " + v + "), " + element.localName + ".getAttribute(" + attr + ") ");
+
+ is(element[attr], intValue, element.localName +
+ ".setAttribute(" + attr + ", " + v + "), " + element.localName + "[" + attr + "] ");
+ element.removeAttribute(attr);
+
+ if (nonNegative && expectedIdlAttributeResult(v) < 0) {
+ try {
+ element[attr] = v;
+ ok(false, element.localName + "[" + attr + "] = " + v + " should throw IndexSizeError");
+ } catch(e) {
+ is(e.name, "IndexSizeError", element.localName + "[" + attr + "] = " + v +
+ " should throw IndexSizeError");
+ is(e.code, DOMException.INDEX_SIZE_ERR, element.localName + "[" + attr + "] = " + v +
+ " should throw INDEX_SIZE_ERR");
+ }
+ } else {
+ element[attr] = v;
+ is(element[attr], expectedIdlAttributeResult(v), element.localName + "[" + attr + "] = " + v +
+ ", " + element.localName + "[" + attr + "] ");
+ is(element.getAttribute(attr), String(expectedIdlAttributeResult(v)),
+ element.localName + "[" + attr + "] = " + v + ", " +
+ element.localName + ".getAttribute(" + attr + ") ");
+ }
+ element.removeAttribute(attr);
+ });
+
+ // Tests after removeAttribute() is called. Should be equivalent with not set.
+ is(element.getAttribute(attr), null,
+ "When not set, the content attribute should be null.");
+ is(element[attr], defaultValue,
+ "When not set, the IDL attribute should return default value.");
+}
+
+/**
+ * Checks that a given attribute is correctly reflected as a url.
+ *
+ * @param aParameters Object object containing the parameters, which are:
+ * - element Element node to test
+ * - attribute String name of the attribute
+ * OR
+ * attribute Object object containing two attributes, 'content' and 'idl'
+ */
+function reflectURL(aParameters)
+{
+ var element = aParameters.element;
+ var contentAttr = typeof aParameters.attribute === "string"
+ ? aParameters.attribute : aParameters.attribute.content;
+ var idlAttr = typeof aParameters.attribute === "string"
+ ? aParameters.attribute : aParameters.attribute.idl;
+
+ element[idlAttr] = "";
+ is(element[idlAttr], document.URL, "Empty string should resolve to document URL");
+}
diff --git a/dom/html/test/simpleFileOpener.js b/dom/html/test/simpleFileOpener.js
new file mode 100644
index 000000000..eeffdb6a1
--- /dev/null
+++ b/dom/html/test/simpleFileOpener.js
@@ -0,0 +1,32 @@
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.importGlobalProperties(["File"]);
+
+var file;
+
+addMessageListener("file.open", function (stem) {
+ try {
+ if (!file) {
+ file = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
+ file.append(stem);
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+ }
+ sendAsyncMessage("file.opened", {
+ fullPath: file.path,
+ leafName: file.leafName,
+ domFile: File.createFromNsIFile(file),
+ });
+ } catch(e) {
+ sendAsyncMessage("fail", e.toString());
+ }
+});
+
+addMessageListener("file.remove", function () {
+ try {
+ file.remove(/* recursive: */ false);
+ file = undefined;
+ sendAsyncMessage("file.removed", null);
+ } catch(e) {
+ sendAsyncMessage("fail", e.toString());
+ }
+});
diff --git a/dom/html/test/test_a_text.html b/dom/html/test/test_a_text.html
new file mode 100644
index 000000000..5ffc1995f
--- /dev/null
+++ b/dom/html/test/test_a_text.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for a.text</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <link rel="help" href="http://www.whatwg.org/html/#dom-a-text"/>
+</head>
+<body>
+<div id="content">
+<a href="a">a b c</a>
+<a href="b">a <!--b--> c</a>
+<a href="c">a <b>b</b> c</a>
+</div>
+<pre id="test">
+<script>
+var d = document.getElementById("content")
+ .appendChild(document.createElement("a"));
+d.href = "d";
+d.appendChild(document.createTextNode("a "));
+d.appendChild(document.createTextNode("b "));
+d.appendChild(document.createTextNode("c "));
+var expected = ["a b c", "a c", "a b c", "a b c "];
+var list = document.getElementById("content").getElementsByTagName("a");
+for (var i = 0, il = list.length; i < il; ++i) {
+ is(list[i].text, list[i].textContent);
+ is(list[i].text, expected[i]);
+
+ list[i].text = "x";
+ is(list[i].text, "x");
+ is(list[i].textContent, "x");
+ is(list[i].firstChild.data, "x");
+ is(list[i].childNodes.length, 1);
+
+ list[i].textContent = "y";
+ is(list[i].text, "y");
+ is(list[i].textContent, "y");
+ is(list[i].firstChild.data, "y");
+ is(list[i].childNodes.length, 1);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_allowMedia.html b/dom/html/test/test_allowMedia.html
new file mode 100644
index 000000000..f4e7b5c67
--- /dev/null
+++ b/dom/html/test/test_allowMedia.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=759964
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 759964</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 759964 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runNextTest);
+
+var SJS = "http://mochi.test:8888/tests/dom/html/test/allowMedia.sjs";
+var TEST_PAGE = "data:text/html,<audio src='" + SJS + "?audio'></audio>";
+
+var Ci = Components.interfaces;
+
+function runNextTest() {
+ var test = tests.shift();
+ if (!test) {
+ SimpleTest.finish();
+ return;
+ }
+ test();
+}
+
+var tests = [
+
+ // Set allowMedia = false, load a page with <audio>, verify the <audio>
+ // doesn't load its source.
+ function basic() {
+ var iframe = insertIframe();
+ SpecialPowers.allowMedia(iframe.contentWindow, false);
+ loadIframe(iframe, TEST_PAGE, function () {
+ verifyPass();
+ iframe.remove();
+ runNextTest();
+ });
+ },
+
+ // Set allowMedia = false on parent docshell, load a page with <audio> in a
+ // child iframe, verify the <audio> doesn't load its source.
+ function inherit() {
+ SpecialPowers.allowMedia(window, false);
+
+ var iframe = insertIframe();
+ loadIframe(iframe, TEST_PAGE, function () {
+ verifyPass();
+ iframe.remove();
+ SpecialPowers.allowMedia(window, true);
+ runNextTest();
+ });
+ },
+
+ // In a display:none iframe, set allowMedia = false, load a page with <audio>,
+ // verify the <audio> doesn't load its source.
+ function displayNone() {
+ var iframe = insertIframe();
+ iframe.style.display = "none";
+ SpecialPowers.allowMedia(iframe.contentWindow, false);
+ loadIframe(iframe, TEST_PAGE, function () {
+ verifyPass();
+ iframe.remove();
+ runNextTest();
+ });
+ },
+];
+
+function insertIframe() {
+ var iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ return iframe;
+}
+
+function loadIframe(iframe, url, onDone) {
+ iframe.setAttribute("src", url);
+ iframe.addEventListener("load", onDone);
+}
+
+function verifyPass() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", SJS, false);
+ xhr.send();
+ is(xhr.responseText, "PASS", "<audio> source should not have been loaded.");
+}
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=759964">Mozilla Bug 759964</a>
+<p id="display">
+</p>
+</body>
+</html>
diff --git a/dom/html/test/test_anchor_href_cache_invalidation.html b/dom/html/test/test_anchor_href_cache_invalidation.html
new file mode 100644
index 000000000..554f35e10
--- /dev/null
+++ b/dom/html/test/test_anchor_href_cache_invalidation.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for anchor cache invalidation</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">
+ <a id="x" href="http://example.com"></a>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+is($("x").href, "http://example.com/");
+is($("x").host, "example.com");
+
+$("x").href = "http://www.example.com";
+
+is($("x").href, "http://www.example.com/");
+is($("x").host, "www.example.com");
+
+$("x").setAttribute("href", "http://www.example.net/");
+is($("x").host, "www.example.net");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_anchor_ping.html b/dom/html/test/test_anchor_ping.html
new file mode 100644
index 000000000..4a39bcefe
--- /dev/null
+++ b/dom/html/test/test_anchor_ping.html
@@ -0,0 +1,309 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=786347
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 786347</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;version=1.8">
+
+ /** Test for Bug 786347 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+addLoadEvent(function () {
+ Task.spawn(function run_tests() {
+ while (tests.length) {
+ let test = tests.shift();
+ info("-- running " + test.name);
+ yield Task.spawn(test);
+ }
+
+ SimpleTest.finish();
+ });
+});
+
+let tests = [
+
+ // Ensure that sending pings is enabled.
+ function* setup() {
+ Services.prefs.setBoolPref("browser.send_pings", true);
+ Services.prefs.setIntPref("browser.send_pings.max_per_link", -1);
+ Services.prefs.setBoolPref("security.mixed_content.block_active_content", false);
+ // The server we create can't handle the priming HEAD requests
+ Services.prefs.setBoolPref("security.mixed_content.send_hsts_priming", false);
+
+ SimpleTest.registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.send_pings");
+ Services.prefs.clearUserPref("browser.send_pings.max_per_link");
+ Services.prefs.clearUserPref("security.mixed_content.block_active_content");
+ Services.prefs.clearUserPref("security.mixed_content.send_hsts_priming");
+ });
+ },
+
+ // If both the address of the document containing the hyperlink being audited
+ // and ping URL have the same origin then the request must include a Ping-From
+ // HTTP header with, as its value, the address of the document containing the
+ // hyperlink, and a Ping-To HTTP header with, as its value, the target URL.
+ // The request must not include a Referer (sic) HTTP header.
+ function* same_origin() {
+ let from = "/ping-from/" + Math.random();
+ let to = "/ping-to/" + Math.random();
+ let ping = "/ping/" + Math.random();
+
+ let base;
+ let server = new HttpServer();
+
+ // The page that contains the link.
+ createFromPathHandler(server, from, to, () => ping);
+
+ // The page that the link's href points to.
+ let promiseHref = createToPathHandler(server, to);
+
+ // The ping we want to receive.
+ let promisePing = createPingPathHandler(server, ping, () => {
+ return {from: base + from, to: base + to};
+ });
+
+ // Start the server, get its base URL and run the test.
+ server.start(-1);
+ base = "http://localhost:" + server.identity.primaryPort;
+ navigate(base + from);
+
+ // Wait until the target and ping url have loaded.
+ yield Promise.all([promiseHref, promisePing]);
+
+ // Cleanup.
+ yield stopServer(server);
+ },
+
+ // If the origins are different, but the document containing the hyperlink
+ // being audited was not retrieved over an encrypted connection then the
+ // request must include a Referer (sic) HTTP header with, as its value, the
+ // address of the document containing the hyperlink, a Ping-From HTTP header
+ // with the same value, and a Ping-To HTTP header with, as its value, target
+ // URL.
+ function* diff_origin() {
+ let from = "/ping-from/" + Math.random();
+ let to = "/ping-to/" + Math.random();
+ let ping = "/ping/" + Math.random();
+
+ // We will use two servers to simulate two different origins.
+ let base, base2;
+ let server = new HttpServer();
+ let server2 = new HttpServer();
+
+ // The page that contains the link.
+ createFromPathHandler(server, from, to, () => base2 + ping);
+
+ // The page that the link's href points to.
+ let promiseHref = createToPathHandler(server, to);
+
+ // Start the first server and get its base URL.
+ server.start(-1);
+ base = "http://localhost:" + server.identity.primaryPort;
+
+ // The ping we want to receive.
+ let promisePing = createPingPathHandler(server2, ping, () => {
+ return {referrer: base + from, from: base + from, to: base + to};
+ });
+
+ // Start the second server, get its base URL and run the test.
+ server2.start(-1);
+ base2 = "http://localhost:" + server2.identity.primaryPort;
+ navigate(base + from);
+
+ // Wait until the target and ping url have loaded.
+ yield Promise.all([promiseHref, promisePing]);
+
+ // Cleanup.
+ yield stopServer(server);
+ yield stopServer(server2);
+ },
+
+ // If the origins are different and the document containing the hyperlink
+ // being audited was retrieved over an encrypted connection then the request
+ // must include a Ping-To HTTP header with, as its value, target URL. The
+ // request must neither include a Referer (sic) HTTP header nor include a
+ // Ping-From HTTP header.
+ function* diff_origin_secure_referrer() {
+ let ping = "/ping/" + Math.random();
+ let server = new HttpServer();
+
+ // The ping we want to receive.
+ let promisePing = createPingPathHandler(server, ping, () => {
+ return {to: "https://example.com/"};
+ });
+
+ // Start the server and run the test.
+ server.start(-1);
+
+ // The referrer will be loaded using a secure channel.
+ navigate("https://example.com/chrome/dom/html/test/" +
+ "file_anchor_ping.html?" + "http://localhost:" +
+ server.identity.primaryPort + ping);
+
+ // Wait until the ping has been sent.
+ yield promisePing;
+
+ // Cleanup.
+ yield stopServer(server);
+ },
+
+ // Test that the <a ping> attribute is properly tokenized using ASCII white
+ // space characters as separators.
+ function* tokenize_white_space() {
+ let from = "/ping-from/" + Math.random();
+ let to = "/ping-to/" + Math.random();
+
+ let base;
+ let server = new HttpServer();
+
+ let pings = [
+ "/ping1/" + Math.random(),
+ "/ping2/" + Math.random(),
+ "/ping3/" + Math.random(),
+ "/ping4/" + Math.random()
+ ];
+
+ // The page that contains the link.
+ createFromPathHandler(server, from, to, () => {
+ return " " + pings[0] + " \r " + pings[1] + " \t " +
+ pings[2] + " \n " + pings[3] + " ";
+ });
+
+ // The page that the link's href points to.
+ let promiseHref = createToPathHandler(server, to);
+
+ // The pings we want to receive.
+ let pingPathHandlers = createPingPathHandlers(server, pings, () => {
+ return {from: base + from, to: base + to};
+ });
+
+ // Start the server, get its base URL and run the test.
+ server.start(-1);
+ base = "http://localhost:" + server.identity.primaryPort;
+ navigate(base + from);
+
+ // Wait until the target and ping url have loaded.
+ yield Promise.all([promiseHref, ...pingPathHandlers]);
+
+ // Cleanup.
+ yield stopServer(server);
+ }
+];
+
+// Navigate the iframe used for testing to a new URL.
+function navigate(uri) {
+ document.getElementById("frame").src = uri;
+}
+
+// Registers a path handler for the given server that will serve a page
+// containing an <a ping> element. The page will automatically simulate
+// clicking the link after it has loaded.
+function createFromPathHandler(server, path, href, lazyPing) {
+ server.registerPathHandler(path, function (request, response) {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ let body = '<body onload="document.body.firstChild.click()">' +
+ '<a href="' + href + '" ping="' + lazyPing() + '"></a></body>';
+ response.write(body);
+ });
+}
+
+// Registers a path handler for the given server that will serve a simple empty
+// page we can use as the href attribute for links. It returns a promise that
+// will be resolved once the page has been requested.
+function createToPathHandler(server, path) {
+ let deferred = Promise.defer();
+
+ server.registerPathHandler(path, function (request, response) {
+ 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("OK");
+
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+// Register multiple path handlers for the given server that will receive
+// pings as sent when an <a ping> element is clicked. This method uses
+// createPingPathHandler() defined below to ensure all headers are sent
+// and received as expected.
+function createPingPathHandlers(server, paths, lazyHeaders) {
+ return Array.from(paths, (path) => createPingPathHandler(server, path, lazyHeaders));
+}
+
+// Registers a path handler for the given server that will receive pings as
+// sent when an <a ping> element has been clicked. It will check that the
+// correct http method has been used, the post data is correct and all headers
+// are given as expected. It returns a promise that will be resolved once the
+// ping has been received.
+function createPingPathHandler(server, path, lazyHeaders) {
+ let deferred = Promise.defer();
+
+ server.registerPathHandler(path, function (request, response) {
+ let headers = lazyHeaders();
+
+ is(request.method, "POST", "correct http method used");
+ is(request.getHeader("Ping-To"), headers.to, "valid ping-to header");
+
+ if ("from" in headers) {
+ is(request.getHeader("Ping-From"), headers.from, "valid ping-from header");
+ } else {
+ ok(!request.hasHeader("Ping-From"), "no ping-from header");
+ }
+
+ if ("referrer" in headers) {
+ is(request.getHeader("Referer"), headers.referrer, "valid referer header");
+ } else {
+ ok(!request.hasHeader("Referer"), "no referer header");
+ }
+
+ let bs = request.bodyInputStream;
+ let body = NetUtil.readInputStreamToString(bs, bs.available());
+ is(body, "PING", "correct body sent");
+
+ 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("OK");
+
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+// Returns a promise that is resolved when the given http server instance has
+// been stopped.
+function stopServer(server) {
+ let deferred = Promise.defer();
+ server.stop(deferred.resolve);
+ return deferred.promise;
+}
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=786347">Mozilla Bug 786347</a>
+<p id="display"></p>
+<iframe id="frame" />
+</body>
+</html>
diff --git a/dom/html/test/test_applet_attributes_reflection.html b/dom/html/test/test_applet_attributes_reflection.html
new file mode 100644
index 000000000..dd04a2022
--- /dev/null
+++ b/dom/html/test/test_applet_attributes_reflection.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLAppletElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLAppletElement attributes reflection **/
+
+// .align (String)
+reflectString({
+ element: document.createElement("applet"),
+ attribute: "align",
+});
+
+// .alt (String)
+reflectString({
+ element: document.createElement("applet"),
+ attribute: "alt",
+});
+
+// .archive (String)
+reflectString({
+ element: document.createElement("applet"),
+ attribute: "archive",
+});
+
+// .code (String)
+reflectString({
+ element: document.createElement("applet"),
+ attribute: "code",
+});
+
+// .codeBase (URL)
+reflectURL({
+ element: document.createElement("applet"),
+ attribute: "codeBase",
+});
+
+// .height (String)
+reflectString({
+ element: document.createElement("applet"),
+ attribute: "height",
+});
+
+// .hspace (unsigned int)
+reflectUnsignedInt({
+ element: document.createElement("applet"),
+ attribute: "hspace",
+});
+
+// .name (String)
+reflectString({
+ element: document.createElement("applet"),
+ attribute: "name",
+});
+
+// .object (URL)
+reflectURL({
+ element: document.createElement("applet"),
+ attribute: "object",
+});
+
+// .vspace (unsigned int)
+reflectUnsignedInt({
+ element: document.createElement("applet"),
+ attribute: "vspace",
+});
+
+// .width (String)
+reflectString({
+ element: document.createElement("applet"),
+ attribute: "width",
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_audio_wakelock.html b/dom/html/test/test_audio_wakelock.html
new file mode 100644
index 000000000..a45f2405b
--- /dev/null
+++ b/dom/html/test/test_audio_wakelock.html
@@ -0,0 +1,125 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=868943
+-->
+<head>
+ <title>Test for Bug 868943</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <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=868943">Mozilla Bug 868943</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 868943 **/
+
+function testAudioPlayPause() {
+ var lockState = true;
+ var count = 0;
+
+ var content = document.getElementById('content');
+
+ var audio = document.createElement('audio');
+ audio.src = "wakelock.ogg";
+ content.appendChild(audio);
+
+ var startDate;
+ function testAudioPlayListener(topic, state) {
+ is(topic, "cpu", "#1 Audio element locked the target == cpu");
+ var locked = state == "locked-foreground" ||
+ state == "locked-background";
+
+ var s = locked ? "locked" : "unlocked";
+ is(locked, lockState, "#1 Audio element " + s + " the cpu");
+ count++;
+
+ // count == 1 is when the cpu wakelock is created
+ // count == 2 is when the cpu wakelock is released
+
+ if (count == 1) {
+ // The next step is to unlock the resource.
+ lockState = false;
+ audio.pause();
+ startDate = new Date();
+ return;
+ }
+
+ is(count, 2, "The count should be 2 which indicates wakelock release");
+
+ if (count == 2) {
+ var diffDate = (new Date() - startDate);
+ ok(diffDate > 200, "#1 There was at least 200 milliseconds between the stop and the wakelock release");
+
+ content.removeChild(audio);
+ navigator.mozPower.removeWakeLockListener(testAudioPlayListener);
+ runTests();
+ }
+ };
+
+ navigator.mozPower.addWakeLockListener(testAudioPlayListener);
+ audio.play();
+}
+
+function testAudioPlay() {
+ var lockState = true;
+ var count = 0;
+
+ var content = document.getElementById('content');
+
+ var audio = document.createElement('audio');
+ audio.src = "wakelock.ogg";
+ content.appendChild(audio);
+
+ function testAudioPlayListener(topic, state) {
+ is(topic, "cpu", "#2 Audio element locked the target == cpu");
+ var locked = state == "locked-foreground" ||
+ state == "locked-background";
+
+ var s = locked ? "locked" : "unlocked";
+ is(locked, lockState, "#2 Audio element " + s + " the cpu");
+ count++;
+
+ // count == 1 is when the cpu wakelock is created: the wakelock must be
+ // created when the media element starts playing.
+ // count == 2 is when the cpu wakelock is released.
+
+ if (count == 1) {
+ // The next step is to unlock the resource.
+ lockState = false;
+ } else if (count == 2) {
+ content.removeChild(audio);
+ navigator.mozPower.removeWakeLockListener(testAudioPlayListener);
+ runTests();
+ }
+ };
+
+ navigator.mozPower.addWakeLockListener(testAudioPlayListener);
+ audio.play();
+}
+
+var tests = [ testAudioPlayPause, testAudioPlay ];
+function runTests() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.pop();
+ test();
+};
+
+SpecialPowers.pushPrefEnv({"set": [["media.wakelock_timeout", 500]]}, runTests);
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_base_attributes_reflection.html b/dom/html/test/test_base_attributes_reflection.html
new file mode 100644
index 000000000..e8398f88a
--- /dev/null
+++ b/dom/html/test/test_base_attributes_reflection.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLBaseElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLBaseElement attributes reflection **/
+
+// .href is sort of like a URL reflection, but with some special rules. Watch
+// out for that!
+reflectURL({
+ element: document.createElement("base"),
+ attribute: "href"
+});
+
+// .target
+reflectString({
+ element: document.createElement("base"),
+ attribute: "target"
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1003539.html b/dom/html/test/test_bug1003539.html
new file mode 100644
index 000000000..041e813d2
--- /dev/null
+++ b/dom/html/test/test_bug1003539.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1003539
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1003539</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 1003539 **/
+// Refering to this specification: http://www.whatwg.org/specs/web-apps/current-work/multipage/tabular-data.html#dom-table-insertrow
+var tab;
+tab = document.createElement("table");
+tab.createTHead();
+tab.insertRow();
+is(tab.innerHTML, '<thead></thead><tbody><tr></tr></tbody>', "Row should be inserted in the tbody.");
+
+tab = document.createElement("table");
+tab.createTBody();
+tab.createTBody();
+tab.insertRow();
+is(tab.innerHTML, '<tbody></tbody><tbody><tr></tr></tbody>', "Row should be inserted in the last tbody.");
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1003539">Mozilla Bug 1003539</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug100533.html b/dom/html/test/test_bug100533.html
new file mode 100644
index 000000000..d5e45fa47
--- /dev/null
+++ b/dom/html/test/test_bug100533.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=100533
+-->
+<head>
+ <title>Test for Bug 100533</title>
+ <script type="text/javascript" src="/MochiKit/Base.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="/MochiKit/Signal.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=100533">Mozilla Bug 100533</a>
+<p id="display"></p>
+<div id="content" >
+
+<button id="thebutton">Test</button>
+<iframe style='display: none;' src='bug100533_iframe.html' id='a'></iframe>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+/** Test for Bug 100533 **/
+var submitIframeForm = function() {
+ $('a').contentDocument.getElementById('b').submit();
+}
+
+submitted = function() {
+ ok(true, "Finished. Form submits when located in iframe set to display:none;");
+ SimpleTest.finish();
+};
+
+addLoadEvent(function() {
+ connect("thebutton", "click", submitIframeForm);
+ signal("thebutton", "click");
+});
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug1013316.html b/dom/html/test/test_bug1013316.html
new file mode 100644
index 000000000..bb7aa928f
--- /dev/null
+++ b/dom/html/test/test_bug1013316.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1013316
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1013316</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 1013316 **/
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ is(Object.keys(document.all).length, 15, "We have 15 indexed props");
+ var props = Object.getOwnPropertyNames(document.all);
+ is(props.length, 20, "Should have five names");
+ is(props[15], "display", "display first");
+ is(props[16], "content", "content second");
+ is(props[17], "bar", "bar third");
+ is(props[18], "foo", "foo fourth");
+ is(props[19], "test", "test fifth");
+
+ is(Object.keys(document.images).length, 2, "We have 2 indexed props");
+ props = Object.getOwnPropertyNames(document.images);
+ is(props.length, 5, "Should have 3 names");
+ is(props[2], "display", "display first on document.images");
+ is(props[3], "bar", "bar second on document.images");
+ is(props[4], "foo", "foo third on document.images");
+ SimpleTest.finish();
+ })
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1013316">Mozilla Bug 1013316</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <img id="display">
+ <img name="foo" id="bar">
+ <div name="baz">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1045270.html b/dom/html/test/test_bug1045270.html
new file mode 100644
index 000000000..9011f91ec
--- /dev/null
+++ b/dom/html/test/test_bug1045270.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1045270
+-->
+ <head>
+ <title>Test for Bug 583514</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=1045270">Mozilla Bug 1045270</a>
+ <p id="display"></p>
+ <div id="content">
+ <input type=number>
+ </div>
+ <pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 1045270 **/
+
+ var input = document.querySelector("input");
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(function() {
+ input.focus();
+ input.addEventListener("input", function() {
+ // reframe
+ document.body.style.display = "none";
+ document.body.style.display = "";
+ document.body.offsetLeft; // flush
+ }, false);
+ synthesizeKey("1", {});
+ SimpleTest.executeSoon(function() {
+ synthesizeKey("2", {});
+ SimpleTest.executeSoon(function() {
+ is(input.value, "12", "Reframe should restore focus and selection properly");
+ SimpleTest.finish();
+ });
+ });
+ });
+
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/html/test/test_bug1081037.html b/dom/html/test/test_bug1081037.html
new file mode 100644
index 000000000..9d8782580
--- /dev/null
+++ b/dom/html/test/test_bug1081037.html
@@ -0,0 +1,133 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1081037
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1081037</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 1081037 **/
+
+function shouldThrow(fun, msg, ex, todo) {
+ try {
+ fun();
+ ok(todo, msg);
+ } catch (e) {
+ ok(new RegExp(ex).test(e), msg + " (thrown:" + e + ")")
+ }
+}
+
+var Foo = document.registerElement('x-foo', {
+ prototype: {bar: 5}
+});
+
+Foo.prototype.bar = 6;
+var foo = new Foo();
+is(foo.bar, 6, "prototype of the ctor returned from registerElement works");
+
+var protoDesc = Object.getOwnPropertyDescriptor(Foo, "prototype");
+is(protoDesc.configurable, false, "proto should be non-configurable");
+is(protoDesc.enumerable, false, "proto should be non-enumerable");
+is(protoDesc.writable, false, "proto should be non-writable");
+
+// TODO: FIXME!
+shouldThrow(function() {
+ document.registerElement('x-foo2', {
+ prototype: Foo.prototype
+ });
+ },
+ "if proto is an interface prototype object, registerElement should throw",
+ "not supported",
+ /* todo = */ true);
+
+var nonConfigReadonlyProto = Object.create(HTMLElement.prototype,
+ { constructor: { configurable: false, writable: false, value: 42 } });
+
+shouldThrow(function() {
+ document.registerElement('x-nonconfig-readonly', {
+ prototype: nonConfigReadonlyProto
+ });
+ },
+ "non-configurable and not-writable constructor property",
+ "not supported");
+
+
+// this is not defined in current spec:
+var readonlyProto = Object.create(HTMLElement.prototype,
+ { constructor: { configurable: true, writable: false, value: 42 } });
+
+var Readonly = document.registerElement('x-nonconfig-readonly', {
+ prototype: readonlyProto
+});
+
+is(Readonly.prototype, readonlyProto, "configurable readonly constructor property");
+
+var handler = {
+ getOwnPropertyDescriptor: function(target, name) {
+ return name == "constructor" ? undefined : Object.getOwnPropertyDescriptor(target,name);
+ },
+ defineProperty: function(target, name, propertyDescriptor) {
+ if (name == "constructor") {
+ throw "spec this";
+ }
+
+ return Object.defineProperty(target, name, propertyDescriptor);
+ },
+ has: function(target, name) {
+ if (name == "constructor") {
+ return false;
+ }
+ return name in target;
+ }
+};
+var proxy = new Proxy({}, handler);
+
+shouldThrow(function() {
+ document.registerElement('x-proxymagic', {
+ prototype: proxy
+ });
+ },
+ "proxy magic",
+ "spec this");
+
+var getOwn = 0;
+var defineProp = 0;
+var handler2 = {
+ getOwnPropertyDescriptor: function(target, name) {
+ if (name == "constructor") {
+ getOwn++;
+ }
+ return Object.getOwnPropertyDescriptor(target,name);
+ },
+ defineProperty: function(target, name, propertyDescriptor) {
+ if (name == "constructor") {
+ defineProp++;
+ }
+ return Object.defineProperty(target, name, propertyDescriptor);
+ }
+};
+var proxy2 = new Proxy({}, handler2);
+
+document.registerElement('x-proxymagic2', {
+ prototype: proxy2
+});
+
+is(getOwn, 1, "number of getOwnPropertyDescriptor calls from registerElement: " + getOwn);
+is(defineProp, 1, "number of defineProperty calls from registerElement: " + defineProp);
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1081037">Mozilla Bug 1081037</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug109445.html b/dom/html/test/test_bug109445.html
new file mode 100644
index 000000000..27ffe2294
--- /dev/null
+++ b/dom/html/test/test_bug109445.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=109445
+-->
+<head>
+ <title>Test for Bug 109445</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=109445">Mozilla Bug 109445</a>
+<p id="display">
+<map name=a>
+<area shape=rect coords=25,25,75,75 href=#x>
+</map>
+<map id=b>
+<area shape=rect coords=25,25,75,75 href=#y>
+</map>
+<map name=a>
+<area shape=rect coords=25,25,75,75 href=#FAIL>
+</map>
+<map id=b>
+<area shape=rect coords=25,25,75,75 href=#FAIL>
+</map>
+
+<img usemap=#a src=image.png>
+<img usemap=#b src=image.png>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 109445 **/
+SimpleTest.waitForExplicitFinish();
+var images = document.getElementsByTagName("img");
+var second = false;
+onhashchange = function() {
+ if (!second) {
+ second = true;
+ is(location.hash, "#x", "First map");
+ SimpleTest.waitForFocus(() => synthesizeMouse(images[1], 50, 50, {}));
+ } else {
+ is(location.hash, "#y", "Second map");
+ SimpleTest.finish();
+ }
+};
+SimpleTest.waitForFocus(() => synthesizeMouse(images[0], 50, 50, {}));
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug109445.xhtml b/dom/html/test/test_bug109445.xhtml
new file mode 100644
index 000000000..b1524c8ea
--- /dev/null
+++ b/dom/html/test/test_bug109445.xhtml
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=109445
+-->
+<head>
+ <title>Test for Bug 109445</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=109445">Mozilla Bug 109445</a>
+<p id="display">
+<map name="a">
+<area shape="rect" coords="25,25,75,75" href="#x"/>
+</map>
+<map id="b">
+<area shape="rect" coords="25,25,75,75" href="#y"/>
+</map>
+<map name="a">
+<area shape="rect" coords="25,25,75,75" href="#FAIL"/>
+</map>
+<map id="b">
+<area shape="rect" coords="25,25,75,75" href="#FAIL"/>
+</map>
+
+<img usemap="#a" src="image.png"/>
+<img usemap="#b" src="image.png"/>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 109445 **/
+SimpleTest.waitForExplicitFinish();
+var images = document.getElementsByTagName("img");
+var second = false;
+onhashchange = function() {
+ if (!second) {
+ second = true;
+ is(location.hash, "#x", "First map");
+ SimpleTest.waitForFocus(() => synthesizeMouse(images[1], 50, 50, {}));
+ } else {
+ is(location.hash, "#y", "Second map");
+ SimpleTest.finish();
+ }
+};
+SimpleTest.waitForFocus(() => synthesizeMouse(images[0], 50, 50, {}));
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1146116.html b/dom/html/test/test_bug1146116.html
new file mode 100644
index 000000000..a4a4431cf
--- /dev/null
+++ b/dom/html/test/test_bug1146116.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1146116
+-->
+<head>
+ <title>Test for Bug 1146116</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=1146116">Mozilla Bug 1146116</a>
+<p id="display">
+ <input type="file" id="file">
+</p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.7">
+/** Test for bug 1146116 **/
+
+SimpleTest.waitForExplicitFinish();
+
+const helperURL = SimpleTest.getTestFileURL("simpleFileOpener.js");
+const helper = SpecialPowers.loadChromeScript(helperURL);
+helper.addMessageListener("fail", function onFail(message) {
+ is(message, null, "chrome script failed");
+ SimpleTest.finish();
+});
+helper.addMessageListener("file.opened", onFileOpened);
+helper.sendAsyncMessage("file.open", "test_bug1146116.txt");
+
+function getGlobal(thing) {
+ return SpecialPowers.unwrap(SpecialPowers.Cu.getGlobalForObject(thing));
+}
+
+function onFileOpened(message) {
+ const file = message.domFile;
+ const elem = document.getElementById("file");
+ is(getGlobal(elem), window,
+ "getGlobal() works as expected");
+ isnot(getGlobal(file), window,
+ "File from MessageManager is wrapped");
+ SpecialPowers.wrap(elem).mozSetFileArray([file]);
+ is(getGlobal(elem.files[0]), window,
+ "File read back from input element is not wrapped");
+ helper.addMessageListener("file.removed", onFileRemoved);
+ helper.sendAsyncMessage("file.remove", null);
+}
+
+function onFileRemoved() {
+ helper.destroy();
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1166138.html b/dom/html/test/test_bug1166138.html
new file mode 100644
index 000000000..889416775
--- /dev/null
+++ b/dom/html/test/test_bug1166138.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1166138
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1166138</title>
+ <script type="application/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>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1166138">Mozilla Bug 1166138</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+
+ <img srcset="file_bug1166138_1x.png 1x, file_bug1166138_2x.png 2x"
+ src="file_bug1166138_def.png"
+ onload="onLoad()">
+
+ <script type="application/javascript">
+ var img1x = "http://mochi.test:8888/tests/dom/html/test/file_bug1166138_1x.png";
+ var img2x = "http://mochi.test:8888/tests/dom/html/test/file_bug1166138_2x.png";
+ var imgdef = "http://mochi.test:8888/tests/dom/html/test/file_bug1166138_def.png";
+ var onLoadCallback = null;
+ var done = false;
+
+ var startPromise = new Promise((a) => {
+ onLoadCallback = () => {
+ var image = document.querySelector('img');
+ // If we aren't starting at 2x scale, resize to 2x scale, and wait for a load
+ if (image.currentSrc != img2x) {
+ onLoadCallback = a;
+ SpecialPowers.pushPrefEnv({'set': [['layout.css.devPixelsPerPx', 2]]});
+ } else {
+ a();
+ }
+ };
+ });
+
+ // if aLoad is true, waits for a load event. Otherwise, spins the event loop twice to
+ // ensure that no events were queued to be fired.
+ function spin(aLoad) {
+ if (aLoad) {
+ return new Promise((a) => {
+ ok(!onLoadCallback, "Shouldn't be an existing callback");
+ onLoadCallback = a;
+ });
+ } else {
+ return new Promise((a) => SimpleTest.executeSoon(() => SimpleTest.executeSoon(a)));
+ }
+ }
+
+ function onLoad() {
+ if (done) return;
+ ok(onLoadCallback, "Expected a load event");
+ if (onLoadCallback) {
+ var cb = onLoadCallback;
+ onLoadCallback = null;
+ cb();
+ }
+ }
+
+ add_task(function* () {
+ yield startPromise;
+ var image = document.querySelector('img');
+ is(image.currentSrc, img2x, "initial scale must be 2x");
+
+ SpecialPowers.pushPrefEnv({'set': [['layout.css.devPixelsPerPx', 1]]});
+ yield spin(true);
+ is(image.currentSrc, img1x, "pre-existing img tag to 1x");
+
+ SpecialPowers.pushPrefEnv({'set': [['layout.css.devPixelsPerPx', 2]]});
+ yield spin(true);
+ is(image.currentSrc, img2x, "pre-existing img tag to 2x");
+
+ // Try removing & re-adding the image
+ document.body.removeChild(image);
+
+ SpecialPowers.pushPrefEnv({'set': [['layout.css.devPixelsPerPx', 1]]});
+ yield spin(false); // No load should occur because the element is unbound
+
+ document.body.appendChild(image);
+ yield spin(true);
+ is(image.currentSrc, img1x, "remove and re-add tag after changing to 1x");
+
+ document.body.removeChild(image);
+ SpecialPowers.pushPrefEnv({'set': [['layout.css.devPixelsPerPx', 2]]});
+ yield spin(false); // No load should occur because the element is unbound
+
+ document.body.appendChild(image);
+ yield spin(true);
+ is(image.currentSrc, img2x, "remove and re-add tag after changing to 2x");
+
+ // get rid of the srcset attribute! It should become the default
+ image.removeAttribute('srcset');
+ yield spin(true);
+ is(image.currentSrc, imgdef, "remove srcset attribute");
+
+ // Setting srcset again should return it to the correct value
+ image.setAttribute('srcset', "file_bug1166138_1x.png 1x, file_bug1166138_2x.png 2x");
+ yield spin(true);
+ is(image.currentSrc, img2x, "restore srcset attribute");
+
+ // Create a new image
+ var newImage = document.createElement('img');
+ // Switch load listening over to newImage
+ newImage.addEventListener('load', onLoad);
+ image.removeEventListener('load', onLoad);
+
+ document.body.appendChild(newImage);
+ yield spin(false); // no load event should fire - as the image has no attributes
+ is(newImage.currentSrc, "", "New element with no attributes");
+ newImage.setAttribute('srcset', "file_bug1166138_1x.png 1x, file_bug1166138_2x.png 2x");
+ yield spin(true);
+ is(newImage.currentSrc, img2x, "Adding srcset attribute");
+
+ SpecialPowers.pushPrefEnv({'set': [['layout.css.devPixelsPerPx', 1]]});
+ yield spin(true);
+ is(newImage.currentSrc, img1x, "new image after switching to 1x");
+ is(image.currentSrc, img1x, "old image after switching to 1x");
+
+ // Clear the listener
+ done = true;
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1203668.html b/dom/html/test/test_bug1203668.html
new file mode 100644
index 000000000..8d9ad9a63
--- /dev/null
+++ b/dom/html/test/test_bug1203668.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1203668
+-->
+<head>
+ <title>Test for Bug 1203668</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=1203668">Mozilla Bug 1203668</a>
+<p id="display"></p>
+<div id="content">
+ <select class="select" multiple>
+ <option value="foo" selected>foo</option>
+ <option value="bar" selected>bar</option>
+ </select>
+ <select class="select" multiple>
+ <option value="foo">foo</option>
+ <option value="bar" selected>bar</option>
+ </select>
+ <select class="select" multiple>
+ <option value="foo">foo</option>
+ <option value="bar">bar</option>
+ </select>
+ <select class="select" size=1>
+ <option value="foo">foo</option>
+ <option value="bar" selected>bar</option>
+ </select>
+ <select class="select" size=1>
+ <option value="foo">foo</option>
+ <option value="bar">bar</option>
+ </select>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1203668 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest()
+{
+ var selects = document.querySelectorAll('.select');
+ for (i=0; i < selects.length; i++) {
+ var select = selects[i];
+ select.value = "bogus"
+ is(select.selectedIndex, -1, "no option is selected");
+ is(select.children[0].selected, false, "first option is not selected");
+ is(select.children[1].selected, false, "second option is not selected");
+ }
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(runTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1230665.html b/dom/html/test/test_bug1230665.html
new file mode 100644
index 000000000..2bad51cdb
--- /dev/null
+++ b/dom/html/test/test_bug1230665.html
@@ -0,0 +1,46 @@
+<html>
+<head>
+ <title>Test for Bug 1230665</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>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function() {
+ document.getElementById("flexbutton1").focus();
+ synthesizeKey("VK_TAB", { });
+ var e = document.getElementById("flexbutton2");
+ is(document.activeElement, e, "focus in flexbutton2 after TAB");
+
+ document.getElementById("gridbutton1").focus();
+ synthesizeKey("VK_TAB", { });
+ e = document.getElementById("gridbutton2");
+ is(document.activeElement, e, "focus in gridbutton2 after TAB");
+
+ SimpleTest.finish();
+});
+
+</script>
+
+<div tabindex="0" style="display:flex">
+ <button id="flexbutton1"></button>
+ text <!-- this text will force a :-moz-anonymous-flex-item frame -->
+ <div style="">
+ <button id="flexbutton2"></button>
+ </div>
+</div>
+
+
+<div tabindex="0" style="display:grid">
+ <button id="gridbutton1"></button>
+ text <!-- this text will force a :-moz-anonymous-grid-item frame -->
+ <div style="">
+ <button id="gridbutton2"></button>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/dom/html/test/test_bug1233598.html b/dom/html/test/test_bug1233598.html
new file mode 100644
index 000000000..c193219ed
--- /dev/null
+++ b/dom/html/test/test_bug1233598.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1233598
+-->
+<head>
+ <title>Test for Bug 1233598</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=1233598">Mozilla Bug 1233598</a>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1233598 **/
+
+var i; // must be out here to trigger the leak
+
+function runTest()
+{
+ i = document.createElement("input");
+ i.setAttribute("type", "file");
+ i.getFilesAndDirectories(); // returns a promise
+ ok(true, "Are we leaking?");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set":[["dom.input.dirpicker", true]]}, runTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1250401.html b/dom/html/test/test_bug1250401.html
new file mode 100644
index 000000000..ec9fc6cbf
--- /dev/null
+++ b/dom/html/test/test_bug1250401.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1250401
+-->
+<head>
+ <title>Test for Bug 1250401</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=1250401">Bug 1250401</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1250401 **/
+function test_add() {
+ var select = document.createElement("select");
+
+ var g1 = document.createElement("optgroup");
+ var o1 = document.createElement("option");
+ g1.appendChild(o1);
+ select.appendChild(g1);
+
+ var g2 = document.createElement("optgroup");
+ var o2 = document.createElement("option");
+ g2.appendChild(o2);
+ select.add(g2, 0);
+
+ is(select.children.length, 1, "Select has 1 item");
+ is(select.firstChild, g1, "First item is g1");
+ is(select.firstChild.children.length, 2, "g2 has 2 children");
+ is(select.firstChild.children[0], g2, "g1 has 2 children: g2");
+ is(select.firstChild.children[1], o1, "g1 has 2 children: o1");
+ is(o1.index, 0, "o1.index should be 0");
+ is(o2.index, 0, "o2.index should be 0");
+}
+
+function test_append() {
+ var select = document.createElement("select");
+
+ var g1 = document.createElement("optgroup");
+ var o1 = document.createElement("option");
+ g1.appendChild(o1);
+ select.appendChild(g1);
+
+ var g2 = document.createElement("optgroup");
+ var o2 = document.createElement("option");
+ g2.appendChild(o2);
+ g1.appendChild(g2);
+
+ is(select.children.length, 1, "Select has 1 item");
+ is(select.firstChild, g1, "First item is g1");
+ is(select.firstChild.children.length, 2, "g2 has 2 children");
+ is(select.firstChild.children[0], o1, "g1 has 2 children: o1");
+ is(select.firstChild.children[1], g2, "g1 has 2 children: g1");
+ is(o1.index, 0, "o1.index should be 0");
+ is(o2.index, 0, "o2.index should be 0");
+}
+
+function test_no_select() {
+ var g1 = document.createElement("optgroup");
+ var o1 = document.createElement("option");
+ g1.appendChild(o1);
+
+ var g2 = document.createElement("optgroup");
+ var o2 = document.createElement("option");
+ g2.appendChild(o2);
+ g1.appendChild(g2);
+
+ is(g1.children.length, 2, "g2 has 2 children");
+ is(g1.children[0], o1, "g1 has 2 children: o1");
+ is(g1.children[1], g2, "g1 has 2 children: g1");
+ is(o1.index, 0, "o1.index should be 0");
+ is(o2.index, 0, "o2.index should be 0");
+}
+
+function test_no_parent() {
+ var o1 = document.createElement("option");
+ var o2 = document.createElement("option");
+
+ is(o1.index, 0, "o1.index should be 0");
+ is(o2.index, 0, "o2.index should be 0");
+}
+
+test_add();
+test_append();
+test_no_select();
+test_no_parent();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1260664.html b/dom/html/test/test_bug1260664.html
new file mode 100644
index 000000000..f03432895
--- /dev/null
+++ b/dom/html/test/test_bug1260664.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1260664
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1260664</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1260664">Mozilla Bug 1260664</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1260664 **/
+SpecialPowers.setBoolPref("network.http.enablePerElementReferrer", true);
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+function runTests() {
+ var elements = [ "iframe", "img", "a", "area", "link" ];
+
+ for (var i = 0; i < elements.length; ++i) {
+ reflectLimitedEnumerated({
+ element: document.createElement(elements[i]),
+ attribute: { content: "referrerpolicy", idl: "referrerPolicy" },
+ validValues: [ "no-referrer",
+ "origin",
+ /** These 2 below values are still invalid, please see
+ Bug 1178337 - Valid referrer attribute values **/
+ /** "no-referrer-when-downgrade",
+ "origin-when-cross-origin", **/
+ "unsafe-url" ],
+ invalidValues: [
+ "", " orIgin ", " unsafe-uRl ", " No-RefeRRer ", " fOoBaR "
+ ],
+ defaultValue: "",
+ });
+ }
+
+ SpecialPowers.clearUserPref("network.http.enablePerElementReferrer");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1260704.html b/dom/html/test/test_bug1260704.html
new file mode 100644
index 000000000..36eead3a2
--- /dev/null
+++ b/dom/html/test/test_bug1260704.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1260704
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1260704</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"/>
+ <script type="text/javascript">
+ /** Test for Bug 1260704 **/
+
+function runTests() {
+ let testIdx = -1;
+ let testUrls = [
+ "bug1260704_iframe.html?noDefault=true&isMap=true",
+ "bug1260704_iframe.html?noDefault=true&isMap=false",
+ "bug1260704_iframe.html?noDefault=false&isMap=true",
+ "bug1260704_iframe.html?noDefault=false&isMap=false"
+ ];
+
+ let runningTest = false;
+ let iframe = document.getElementById("testFrame");
+ let iframeWin = iframe.contentWindow;
+ let rect;
+ let x;
+ let y;
+
+ window.addEventListener("message", event => {
+ if (event.data == "started") {
+ ok(!runningTest, "Start to test " + testIdx);
+ runningTest = true;
+ rect = iframeWin.document.getElementById("testImage").getBoundingClientRect();
+ x = rect.width / 2;
+ y = rect.height / 2;
+ synthesizeMouseAtPoint(rect.left + x, rect.top + y, { type: 'mousedown' }, iframeWin);
+ synthesizeMouseAtPoint(rect.left + x, rect.top + y, { type: 'mouseup' }, iframeWin);
+ }
+ else if (runningTest && event.data == "empty_frame_loaded") {
+ ok(testUrls[testIdx].includes("noDefault=false"), "Page unload");
+ let search = iframeWin.location.search;
+ if (testUrls[testIdx].includes("isMap=true")) {
+ // url trigger by image with ismap attribute should contains coordinates
+ // try to parse coordinates and check them with small tolerance
+ let coorStr = search.split("?");
+ let coordinates = coorStr[1].split(",");
+ ok(Math.abs(coordinates[0] - x) <= 1, "expect X=" + x + " got " + coordinates[0]);
+ ok(Math.abs(coordinates[1] - y) <= 1, "expect Y=" + y + " got " + coordinates[1]);
+ } else {
+ ok(search == "", "expect empty search string got:" + search);
+ }
+ nextTest();
+ }
+ else if (runningTest && event.data == "finished") {
+ ok(testUrls[testIdx].includes("noDefault=true"), "Page should not leave");
+ nextTest();
+ }
+ }, false);
+
+ function nextTest() {
+ testIdx++;
+ runningTest = false;
+ if (testIdx >= testUrls.length) {
+ SimpleTest.finish();
+ } else {
+ ok(true, "Test " + testIdx + " - Set url to " + testUrls[testIdx]);
+ iframeWin.location.href = testUrls[testIdx];
+ }
+ }
+ nextTest();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+ </script>
+</head>
+<body>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<iframe id="testFrame" src="about:blank" width="400" height="400">
+</iframe>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1261673.html b/dom/html/test/test_bug1261673.html
new file mode 100644
index 000000000..006b229e1
--- /dev/null
+++ b/dom/html/test/test_bug1261673.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1261673
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1261673</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=1261673">Mozilla Bug 1261673</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<input id="test_number" type="number" value=5>
+<script type="text/javascript">
+
+/** Test for Bug 1261673 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+function runTests() {
+ let input = window.document.getElementById("test_number");
+
+ // focus: whether the target input element is focused
+ // deltaY: deltaY of WheelEvent
+ // deltaMode: deltaMode of WheelEvent
+ // valueChanged: expected value changes after input element handled the wheel event
+ let params = [
+ {focus: true, deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_LINE, valueChanged: -1},
+ {focus: true, deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_LINE, valueChanged: 1},
+ {focus: true, deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_PAGE, valueChanged: -1},
+ {focus: true, deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_PAGE, valueChanged: 1},
+ {focus: true, deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL, valueChanged: 0},
+ {focus: true, deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL, valueChanged: 0},
+ {focus: false, deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_LINE, valueChanged: 0},
+ {focus: false, deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_LINE, valueChanged: 0}
+ ];
+
+ let testIdx = 0;
+ let result = parseInt(input.value);
+ let numberChange = 0;
+ let expectChange = 0;
+
+ input.addEventListener("change", () => {
+ ++numberChange;
+ }, false);
+
+ function runNext() {
+ let p = params[testIdx];
+ (p["focus"]) ? input.focus() : input.blur();
+ expectChange = p["valueChanged"] == 0 ? expectChange : expectChange + 1;
+ result += parseInt(p["valueChanged"]);
+ synthesizeWheel(input, 1, 1, { deltaY: p["deltaY"], deltaMode: p["deltaMode"] });
+ window.postMessage("finished", "http://mochi.test:8888");
+ testIdx++;
+ }
+
+ window.addEventListener("message", event => {
+ if (event.data == "finished") {
+ ok(input.value == result,
+ "Handle wheel in number input test-" + testIdx + " expect " + result +
+ " get " + input.value);
+ ok(numberChange == expectChange,
+ "UA should fire change event when input's value changed, expect " + expectChange + " get " + numberChange);
+ (testIdx >= params.length) ? SimpleTest.finish() : runNext();
+ }
+ });
+ runNext();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1261674-1.html b/dom/html/test/test_bug1261674-1.html
new file mode 100644
index 000000000..3745c1930
--- /dev/null
+++ b/dom/html/test/test_bug1261674-1.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1261674
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1261674</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="/tests/SimpleTest/paint_listener.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1261674">Mozilla Bug 1261674</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<input id="test_input" type="range" value=5 max=10 min=0>
+<script type="text/javascript">
+
+/** Test for Bug 1261674 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+function runTests() {
+ let input = window.document.getElementById("test_input");
+
+ // focus: whether the target input element is focused
+ // deltaY: deltaY of WheelEvent
+ // deltaMode: deltaMode of WheelEvent
+ // valueChanged: expected value changes after input element handled the wheel event
+ let params = [
+ {focus: true, deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_LINE, valueChanged: -1},
+ {focus: true, deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_LINE, valueChanged: 1},
+ {focus: true, deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_PAGE, valueChanged: -1},
+ {focus: true, deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_PAGE, valueChanged: 1},
+ {focus: true, deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL, valueChanged: 0},
+ {focus: true, deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL, valueChanged: 0},
+ {focus: false, deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_LINE, valueChanged: 0},
+ {focus: false, deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_LINE, valueChanged: 0}
+ ];
+
+ let testIdx = 0;
+ let result = parseInt(input.value);
+ let rangeChange = 0;
+ let expectChange = 0;
+
+ input.addEventListener("change", () => {
+ ++rangeChange;
+ }, false);
+
+ function runNext() {
+ let p = params[testIdx];
+ (p["focus"]) ? input.focus() : input.blur();
+ expectChange = p["valueChanged"] == 0 ? expectChange : expectChange + 1;
+ result += parseInt(p["valueChanged"]);
+ sendWheelAndPaint(input, 1, 1, { deltaY: p["deltaY"], deltaMode: p["deltaMode"] }, () => {
+ ok(input.value == result,
+ "Handle wheel in range input test-" + testIdx + " expect " + result + " get " + input.value);
+ ok(rangeChange == expectChange,
+ "UA should fire change event when input's value changed, expect " + expectChange + " get " + rangeChange);
+ (++testIdx >= params.length) ? SimpleTest.finish() : runNext();
+ });
+ }
+
+ input.addEventListener("input", () => {
+ ok(input.value == result,
+ "Test-" + testIdx + " receive input event, expect " + result + " get " + input.value);
+ }, false);
+
+ runNext();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1261674-2.html b/dom/html/test/test_bug1261674-2.html
new file mode 100644
index 000000000..ffb42ee6d
--- /dev/null
+++ b/dom/html/test/test_bug1261674-2.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1261674
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1261674</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="/tests/SimpleTest/paint_listener.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1261674">Mozilla Bug 1261674</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<input id="test_input" type="range" max=0 min=10>
+<script type="text/javascript">
+
+/** Test for Bug 1261674 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+function runTests() {
+ let input = window.document.getElementById("test_input");
+
+ // deltaY: deltaY of WheelEvent
+ // deltaMode: deltaMode of WheelEvent
+ let params = [
+ {deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_LINE},
+ {deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_LINE},
+ {deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_PAGE},
+ {deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_PAGE},
+ {deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL},
+ {deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL},
+ {deltaY: 1.0, deltaMode: WheelEvent.DOM_DELTA_LINE},
+ {deltaY: -1.0, deltaMode: WheelEvent.DOM_DELTA_LINE}
+ ];
+
+ let testIdx = 0;
+ let result = parseInt(input.value);
+ let rangeChange = 0;
+
+ input.addEventListener("change", () => {
+ ++rangeChange;
+ }, false);
+
+ function runNext() {
+ let p = params[testIdx];
+ (p["focus"]) ? input.focus() : input.blur();
+ sendWheelAndPaint(input, 1, 1, { deltaY: p["deltaY"], deltaMode: p["deltaMode"] }, () => {
+ ok(input.value == result,
+ "Handle wheel in range input test-" + testIdx + " expect " + result + " get " + input.value);
+ ok(rangeChange == 0, "Wheel event should not trigger change event when max < min");
+ testIdx++;
+ (testIdx >= params.length) ? SimpleTest.finish() : runNext();
+ });
+ }
+
+ input.addEventListener("input", () => {
+ ok(false, "Wheel event should be no effect to range input element with max < min");
+ }, false);
+
+ runNext();
+}
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1264157.html b/dom/html/test/test_bug1264157.html
new file mode 100644
index 000000000..a087b0f41
--- /dev/null
+++ b/dom/html/test/test_bug1264157.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=535043
+-->
+<head>
+ <title>Test for Bug 535043</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"/>
+ <style>
+ input {
+ outline: 2px solid lime;
+ }
+ input:in-range {
+ outline: 2px solid red;
+ }
+ input:out-of-range {
+ outline: 2px solid orange;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=535043">Mozilla Bug 535043</a>
+<p id="display"></p>
+<div id="content">
+
+</head>
+<body>
+ <input type="number" value=0 min=0 max=10> Active in-range
+ <br><br>
+ <input type="number" value=0 min=0 max=10 disabled> Disabled in-range
+ <br><br>
+ <input type="number" value=0 min=0 max=10 readonly> Read-only in-range
+ <br><br>
+ <input type="number" value=11 min=0 max=10> Active out-of-range
+ <br><br>
+ <input type="number" value=11 min=0 max=10 disabled> Disabled out-of-range
+ <br><br>
+ <input type="number" value=11 min=0 max=10 readonly> Read-only out-of-range
+</div>
+<pre id="test">
+<script type="text/javascript">
+
+/** Test for Bug 1264157 **/
+SimpleTest.waitForFocus(function() {
+ // Check the initial values.
+ let active = [].slice.call(document.querySelectorAll("input:not(:disabled):not(:-moz-read-only)"));
+ let disabled = [].slice.call(document.querySelectorAll("input:disabled"));
+ let readonly = [].slice.call(document.querySelectorAll("input:-moz-read-only"));
+ ok(active.length == 2, "Test is messed up: missing non-disabled/non-readonly inputs");
+ ok(disabled.length == 2, "Test is messed up: missing disabled inputs");
+ ok(readonly.length == 2, "Test is messed up: missing readonly inputs");
+
+ is(document.querySelectorAll("input:in-range").length, 1,
+ "Wrong number of in-range elements selected.");
+ is(document.querySelectorAll("input:out-of-range").length, 1,
+ "Wrong number of out-of-range elements selected.");
+
+ // Dynamically change the values to see if that works too.
+ active[0].value = -1;
+ is(document.querySelectorAll("input:in-range").length, 0,
+ "Wrong number of in-range elements selected after value changed.");
+ is(document.querySelectorAll("input:out-of-range").length, 2,
+ "Wrong number of out-of-range elements selected after value changed.");
+ active[0].value = 0;
+ is(document.querySelectorAll("input:in-range").length, 1,
+ "Wrong number of in-range elements selected after value changed back.");
+ is(document.querySelectorAll("input:out-of-range").length, 1,
+ "Wrong number of out-of-range elements selected after value changed back.");
+
+ // Dynamically change the attributes to see if that works too.
+ disabled.forEach(function(e) { e.removeAttribute("disabled"); });
+ readonly.forEach(function(e) { e.removeAttribute("readonly"); });
+ active.forEach(function(e) { e.setAttribute("readonly", true); });
+
+ is(document.querySelectorAll("input:in-range").length, 2,
+ "Wrong number of in-range elements selected after attribute changed.");
+ is(document.querySelectorAll("input:out-of-range").length, 2,
+ "Wrong number of out-of-range elements selected after attribute changed.");
+
+ SimpleTest.finish();
+});
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1287321.html b/dom/html/test/test_bug1287321.html
new file mode 100644
index 000000000..2de0bd0f2
--- /dev/null
+++ b/dom/html/test/test_bug1287321.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1287321
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1287321</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="application/javascript">
+
+ /** Test for Bug 1287321 **/
+
+ function test() {
+ var r = document.getElementById("range");
+ var rect = r.getBoundingClientRect();
+ var y = parseInt((rect.height / 2));
+ var movement = parseInt(rect.width / 10);
+ var x = movement;
+ synthesizeMouse(r, x, y, { type: "mousedown" });
+ x += movement;
+ var eventCount = 0;
+ r.oninput = function() {
+ ++eventCount;
+ }
+ synthesizeMouse(r, x, y, { type: "mousemove" });
+ is(eventCount, 1, "Got the expected input event");
+
+ x += movement;
+ synthesizeMouse(r, x, y, { type: "mousemove" });
+ is(eventCount, 2, "Got the expected input event");
+
+ synthesizeMouse(r, x, y, { type: "mousemove" });
+ is(eventCount, 2, "Got the expected input event");
+
+ x += movement;
+ synthesizeMouse(r, x, y, { type: "mousemove" });
+ is(eventCount, 3, "Got the expected input event");
+
+ synthesizeMouse(r, x, y, { type: "mouseup" });
+ is(eventCount, 3, "Got the expected input event");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(test);
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1287321">Mozilla Bug 1287321</a>
+<input type="range" id="range">
+</body>
+</html>
diff --git a/dom/html/test/test_bug1292522_same_domain_with_different_port_number.html b/dom/html/test/test_bug1292522_same_domain_with_different_port_number.html
new file mode 100644
index 000000000..0dec91558
--- /dev/null
+++ b/dom/html/test/test_bug1292522_same_domain_with_different_port_number.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1292522
+If we set domain using document.domain = "...", a page and iframe must be
+treated as the same domain if they differ in port number,
+e.g. test1.example.org:8000 and test2.example.org:80 are the same domain if
+document.domain = "example.org".
+-->
+<head>
+ <title>Test for Bug 1292522</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=1292522">Mozilla Bug 1292522</a>
+ <p id="display"></p>
+
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+
+ if (navigator.platform.startsWith("Linux")) {
+ SimpleTest.expectAssertions(0, 1);
+ }
+ SimpleTest.waitForExplicitFinish();
+ window.addEventListener("message", onMessageReceived, false);
+
+ var page;
+
+ function onMessageReceived(event)
+ {
+ is(event.data, "testiframe", "Must be able to access the variable," +
+ " because page and iframe are the " +
+ "same domain.");
+ page.close();
+ SimpleTest.finish();
+ }
+
+ page = window.open("http://test1.example.org:8000/tests/dom/html/test/bug1292522_page.html");
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/html/test/test_bug1295719_event_sequence_for_arrow_keys.html b/dom/html/test/test_bug1295719_event_sequence_for_arrow_keys.html
new file mode 100644
index 000000000..b780cb913
--- /dev/null
+++ b/dom/html/test/test_bug1295719_event_sequence_for_arrow_keys.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1295719
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1295719</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=1295719">Mozilla Bug 1295719</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<input id="test_number" type="number" value=50>
+<input id="test_range" type="range" value=50 max=100 min=0>
+<script type="text/javascript">
+
+/** Test for Bug 1295719 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+function runTests() {
+ let number = window.document.getElementById("test_number");
+ let range = window.document.getElementById("test_range");
+ let waiting_event_sequence = ["keydown", "keypress", "input", "change"];
+
+ let waiting_event_idx = 0;
+ waiting_event_sequence.forEach((eventType) => {
+ number.addEventListener(eventType, (event) => {
+ let waiting_event = waiting_event_sequence[waiting_event_idx];
+ is(waiting_event, eventType, "Waiting " + waiting_event + " get " + eventType);
+ // Input element will fire input and change events when handling keypress
+ // with keycode=arrows. When user press and hold the keyboard, we expect
+ // that input element repeatedly fires "keydown", "keypress", "input", and
+ // "change" events until user release the keyboard. Using
+ // waiting_event_sequence as a circular buffer and reset waiting_event_idx
+ // when it point to the end of buffer.
+ waiting_event_idx = waiting_event_idx == waiting_event_sequence.length -1 ? 0 : waiting_event_idx + 1;
+ }, false);
+ range.addEventListener(eventType, (event) => {
+ let waiting_event = waiting_event_sequence[waiting_event_idx];
+ is(waiting_event, eventType, "Waiting " + waiting_event + " get " + eventType);
+ waiting_event_idx = waiting_event_idx == waiting_event_sequence.length - 1 ? 0 : waiting_event_idx + 1;
+ }, false);
+ });
+
+ number.focus();
+ synthesizeKey("VK_DOWN", {type: "keydown"});
+ synthesizeKey("VK_DOWN", {type: "keydown"});
+ synthesizeKey("VK_DOWN", {type: "keyup"});
+ number.blur();
+ range.focus();
+ waiting_event_idx = 0;
+ synthesizeKey("VK_DOWN", {type: "keydown"});
+ synthesizeKey("VK_DOWN", {type: "keydown"});
+ synthesizeKey("VK_DOWN", {type: "keyup"});
+
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1295719_event_sequence_for_number_keys.html b/dom/html/test/test_bug1295719_event_sequence_for_number_keys.html
new file mode 100644
index 000000000..2dac8c55b
--- /dev/null
+++ b/dom/html/test/test_bug1295719_event_sequence_for_number_keys.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1295719
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1295719</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=1295719">Mozilla Bug 1295719</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<input id="test_number" type="number" value=50>
+<script type="text/javascript">
+
+/** Test for Bug 1295719 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+function runTests() {
+ let number = window.document.getElementById("test_number");
+ let waiting_event_sequence = ["keydown", "keypress", "input"];
+ let change_event_of_number = 0;
+ let keyup_event_of_number = 0;
+ let waiting_event_idx = 0;
+ waiting_event_sequence.forEach((eventType) => {
+ number.addEventListener(eventType, (event) => {
+ let waiting_event = waiting_event_sequence[waiting_event_idx];
+ is(eventType, waiting_event, "Waiting " + waiting_event + " get " + eventType);
+ // Input element will fire input event when handling keypress with
+ // keycode=numbers. When user press and hold the keyboard, we expect that
+ // input element repeatedly fires "keydown", "keypress", and "input" until
+ // user release the keyboard. Input element will fire change event when
+ // it's blurred. Using waiting_event_sequence as a circular buffer and
+ // reset waiting_event_idx when it point to the end of buffer.
+ waiting_event_idx = waiting_event_idx == waiting_event_sequence.length - 1 ? 0 : waiting_event_idx + 1;
+ }, false);
+ });
+ number.addEventListener("change", (event) => {
+ is(keyup_event_of_number, 1, "change event should be fired after blurred");
+ ++change_event_of_number;
+ }, false);
+ number.addEventListener("keyup", (event) => {
+ is(keyup_event_of_number, 0, "keyup event should be fired once");
+ is(change_event_of_number, 0, "keyup event should be fired before change event");
+ ++keyup_event_of_number;
+ }, false);
+ number.focus();
+ synthesizeKey("5", {type: "keydown"});
+ synthesizeKey("5", {type: "keydown"});
+ synthesizeKey("5", {type: "keyup"});
+ is(change_event_of_number, 0, "change event shouldn't be fired when input element is focused");
+ number.blur();
+ is(change_event_of_number, 1, "change event should be fired when input element is blurred");
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1297.html b/dom/html/test/test_bug1297.html
new file mode 100644
index 000000000..8b9dc3039
--- /dev/null
+++ b/dom/html/test/test_bug1297.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1297
+-->
+<head>
+ <title>Test for Bug 1297</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=1297">Mozilla Bug 1297</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<table border=1>
+<tr>
+<td id="td1" onmousedown="alert(this.cellIndex)">cellIndex=0</td>
+<td id="td2" onmousedown="alert(this.cellIndex)">cellIndex=1</td>
+<td id="td3" onmousedown="alert(this.cellIndex)">cellIndex=2</td>
+<tr id="tr1"
+onmousedown="alert(this.rowIndex)"><td>rowIndex=1<td>rowIndex=1<td>rowIndex=1</t
+r>
+<tr id="tr2"
+onmousedown="alert(this.rowIndex)"><td>rowIndex=2<td>rowIndex=2<td>rowIndex=2</t
+r>
+</tr>
+</table>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1297 **/
+is($('td1').cellIndex, 0, "cellIndex / rowIndex working td1");
+is($('td2').cellIndex, 1, "cellIndex / rowIndex working td2");
+is($('td3').cellIndex, 2, "cellIndex / rowIndex working td3");
+is($('tr1').rowIndex, 1, "cellIndex / rowIndex working tr1");
+is($('tr2').rowIndex, 2, "cellIndex / rowIndex working tr2");
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug1310865.html b/dom/html/test/test_bug1310865.html
new file mode 100644
index 000000000..4dcccbfa0
--- /dev/null
+++ b/dom/html/test/test_bug1310865.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<title>Test for Bug 1310865</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<input value="a
+b" type="hidden">
+<input type="hidden" value="a
+b">
+<script>
+var input1 = document.querySelector("input");
+var input2 = document.querySelector("input + input");
+var clone1 = input1.cloneNode(false);
+var clone2 = input2.cloneNode(false);
+// Newlines must not be stripped
+is(clone1.value, "a\nb");
+is(clone2.value, "a\nb");
+</script>
diff --git a/dom/html/test/test_bug1315146.html b/dom/html/test/test_bug1315146.html
new file mode 100644
index 000000000..261b815b5
--- /dev/null
+++ b/dom/html/test/test_bug1315146.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1315146
+-->
+<head>
+ <title>Test for Bug 1315146</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=1315146">Mozilla Bug 1315146</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1315146 **/
+
+SimpleTest.waitForExplicitFinish();
+onmessage = function(e) {
+ win.close();
+ is(e.data.start, 2, "Correct start offset expected");
+ is(e.data.end, 2, "Correct end offset expected");
+ SimpleTest.finish();
+};
+let win = window.open("http://test1.example.org/tests/dom/html/test/bug1315146-main.html", "_blank");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1366.html b/dom/html/test/test_bug1366.html
new file mode 100644
index 000000000..f5b4f880d
--- /dev/null
+++ b/dom/html/test/test_bug1366.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1366
+-->
+<head>
+ <title>Test for Bug 1366</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=1366">Mozilla Bug 1366</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<table id="testtable" width=150 border>
+ <tbody id="testbody">
+ <tr>
+ <td>cell content</td>
+ </tr>
+ </tbody>
+</table>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1366 **/
+$('testtable').removeChild($('testbody'));
+$('display').innerHTML = "SCRIPT: deleted first ROWGROUP\n";
+is($('testbody'), null, "deleting tbody works");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug1400.html b/dom/html/test/test_bug1400.html
new file mode 100644
index 000000000..12f36833b
--- /dev/null
+++ b/dom/html/test/test_bug1400.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1400
+-->
+<head>
+ <title>Test for Bug 1400</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=1400">Mozilla Bug 1400</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1400 **/
+
+table = document.createElement("TABLE");
+thead = table.createTHead();
+thead2 = table.createTHead();
+
+table.appendChild(thead);
+table.appendChild(thead);
+table.appendChild(thead);
+table.appendChild(thead2);
+table.appendChild(thead2);
+table.appendChild(thead2);
+table.appendChild(thead);
+table.appendChild(thead2);
+
+is(table.childNodes.length, 1,
+ "adding multiple theads results in one thead child");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug143220.html b/dom/html/test/test_bug143220.html
new file mode 100644
index 000000000..355e91ea2
--- /dev/null
+++ b/dom/html/test/test_bug143220.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=143220
+-->
+<head>
+ <title>Test for Bug 143220</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=143220">Mozilla Bug 143220</a>
+<p id="display">
+ <input type="file" id="i1">
+ <input type="file" id="i2">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.7">
+
+/** Test for Bug 143220 **/
+SimpleTest.waitForExplicitFinish();
+const helperURL = SimpleTest.getTestFileURL("simpleFileOpener.js");
+const helper = SpecialPowers.loadChromeScript(helperURL);
+helper.addMessageListener("fail", function onFail(message) {
+ is(message, null, "chrome script failed");
+ SimpleTest.finish();
+});
+helper.addMessageListener("file.opened", onFileOpened);
+helper.sendAsyncMessage("file.open", "test_bug143220.txt");
+
+function onFileOpened(message) {
+ const { leafName, fullPath, domFile } = message;
+
+ function initControl1() {
+ SpecialPowers.wrap($("i1")).mozSetFileArray([domFile]);
+ }
+
+ function initControl2() {
+ SpecialPowers.wrap($("i2")).mozSetFileArray([domFile]);
+ }
+
+ // Check that we can't just set the value
+ try {
+ $("i1").value = fullPath;
+ is(0, 1, "Should have thrown exception on set!");
+ } catch(e) {
+ is($("i1").value, "", "Shouldn't have value here");
+ }
+
+ initControl1();
+ initControl2();
+
+ is($("i1").value, leafName, "Leaking full value?");
+ is($("i2").value, leafName, "Leaking full value?");
+
+ helper.addMessageListener("file.removed", onFileRemoved);
+ helper.sendAsyncMessage("file.remove", null);
+}
+
+function onFileRemoved() {
+ helper.destroy();
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug1682.html b/dom/html/test/test_bug1682.html
new file mode 100644
index 000000000..c8e5b830d
--- /dev/null
+++ b/dom/html/test/test_bug1682.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1682
+-->
+<head>
+ <title>Test for Bug 1682</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=1682">Mozilla Bug 1682</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1682 **/
+var count = 1;
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function () {
+ is(count, 1, "onload executes once");
+ ++count;
+});
+addLoadEvent(SimpleTest.finish);
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug172261.html b/dom/html/test/test_bug172261.html
new file mode 100644
index 000000000..2b5d752cd
--- /dev/null
+++ b/dom/html/test/test_bug172261.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=172261
+-->
+<head>
+ <title>Test for Bug 172261</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=172261">Mozilla Bug 172261</a>
+<p id="display">
+ <iframe id="test"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ /** Test for Bug 172261 **/
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+
+ var callable = false;
+ function toggleCallable() { callable = true; }
+
+ var doTestInIframe = false;
+
+ // Shouldn't do history stuff from inside onload
+ addLoadEvent(function() { setTimeout(startTest, 10) });
+
+ function startTest() {
+ // First, create a dummy document. Use onunload handlers to make sure
+ // bfcache doesn't screw us up.
+ var doc = $("test").contentDocument;
+
+ doc.write("<html><body onunload=''>First</body></html>");
+ doc.close();
+
+ // Now write our test document
+ doc.write("<html><script>window.onerror = parent.onerror; if (parent.doTestInIframe) { parent.is(document.domain, parent.document.domain, 'Domains should match'); parent.toggleCallable(); } <" + "/script><body>Second</body></html>");
+ doc.close();
+
+ $("test").onload = goForward;
+ history.back();
+ }
+
+ function goForward() {
+ $("test").onload = doTest;
+ doTestInIframe = true;
+ history.forward();
+ }
+
+ function doTest() {
+ is($("test").contentDocument.domain, document.domain,
+ "Domains should match 2");
+ is($("test").contentDocument.location.href, location.href,
+ "Locations should match");
+ is(callable, true, "Subframe should be able to call us");
+ SimpleTest.finish();
+ }
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug182279.html b/dom/html/test/test_bug182279.html
new file mode 100644
index 000000000..56c73a676
--- /dev/null
+++ b/dom/html/test/test_bug182279.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=182279
+-->
+<head>
+ <title>Test for Bug 182279</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=182279">Moozilla Bug 182279</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 182279 **/
+var sel = document.createElement("select");
+var opt1 = new Option();
+var opt2 = new Option();
+var opt3 = new Option();
+opt1.value = 1;
+opt2.value = 2;
+opt3.value = 3;
+sel.add(opt1, null);
+sel.add(opt2, opt1);
+sel.add(opt3);
+is(sel[0], opt2, "1st item should be 2");
+is(sel[1], opt1, "2nd item should be 1");
+is(sel[2], opt3, "3rd item should be 3");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug1823.html b/dom/html/test/test_bug1823.html
new file mode 100644
index 000000000..1297b0dd6
--- /dev/null
+++ b/dom/html/test/test_bug1823.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1823
+-->
+<head>
+ <title>Test for Bug 1823</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=1823">Mozilla Bug 1823</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1823 **/
+ok((document.location + "").indexOf("[") == -1, "location object has a toString()");
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug196523.html b/dom/html/test/test_bug196523.html
new file mode 100644
index 000000000..a380fe06c
--- /dev/null
+++ b/dom/html/test/test_bug196523.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=196523
+-->
+<head>
+ <title>Test for Bug 196523</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=196523">Mozilla Bug 196523</a>
+<script>
+ var expectedMessages = 2;
+ SimpleTest.waitForExplicitFinish();
+ window.addEventListener("message", function(e) {
+ --expectedMessages;
+ var str = e.data;
+ var idx = str.indexOf(';');
+ var val = str.substring(0, idx);
+ var msg = str.substring(idx+1);
+ ok(val == "true", msg);
+ if (!expectedMessages) { SimpleTest.finish(); }
+ }, false);
+</script>
+<p id="display">
+ <iframe src="http://test1.example.org/tests/dom/html/test/bug196523-subframe.html"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 196523 **/
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug199692.html b/dom/html/test/test_bug199692.html
new file mode 100644
index 000000000..91f99ccc4
--- /dev/null
+++ b/dom/html/test/test_bug199692.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=199692
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Test for Bug 199692</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 type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ // The popup calls MochiTest methods in this window through window.opener
+ window.open("bug199692-popup.html", "bug199692", "width=600,height=600");
+ </script>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug2082.html b/dom/html/test/test_bug2082.html
new file mode 100644
index 000000000..b693e96d1
--- /dev/null
+++ b/dom/html/test/test_bug2082.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=2082
+-->
+<head>
+ <title>Test for Bug 2082</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=2082">Mozilla Bug 2082</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<FORM name="gui" id="gui">
+<INPUT TYPE="text" NAME="field" VALUE="some value">
+</FORM>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 2082 **/
+var guiform = document.getElementById("gui");
+ok(document.getElementById("gui").hasChildNodes(), "form elements should be treated as form's children");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug209275.xhtml b/dom/html/test/test_bug209275.xhtml
new file mode 100644
index 000000000..f74e5e5fb
--- /dev/null
+++ b/dom/html/test/test_bug209275.xhtml
@@ -0,0 +1,261 @@
+<!DOCTYPE html [
+<!ATTLIST foo:base
+ id ID #IMPLIED
+>
+]>
+<html xmlns:foo="http://foo.com" xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=209275
+-->
+<head>
+ <title>Test for Bug 209275</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+ <style>
+ @namespace svg url("http://www.w3.org/2000/svg");
+ svg|a { fill:blue; }
+ svg|a:visited { fill:purple; }
+ </style>
+
+ <!--
+ base0 should be ignored because it's not in the XHTML namespace
+ -->
+ <foo:base id="base0" href="http://www.foo.com" />
+
+ <!--
+ baseEmpty should be ignored because it has no href and never gets one.
+ -->
+ <base id="baseEmpty" />
+
+ <!--
+ baseWrongAttrNS should be ignored because its href attribute isn't in the empty
+ namespace.
+ -->
+ <base id="baseWrongAttrNS" foo:href="http://foo.com" />
+
+ <base id="base1" />
+ <base id="base2" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=209275">Mozilla Bug 209275</a>
+<p id="display">
+</p>
+<div id="content">
+ <a href="/" id="link1">link1</a>
+ <div style="display:none">
+ <a href="/" id="link2">link2</a>
+ </div>
+ <a href="/" id="link3" style="display:none">link3</a>
+ <a href="#" id="link4">link4</a>
+ <a href="" id="colorlink">colorlink</a>
+ <a href="#" id="link5">link5</a>
+ <iframe id="iframe"></iframe>
+
+ <svg width="5cm" height="3cm" viewBox="0 0 5 3" version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <a xlink:href="" id="ellipselink">
+ <ellipse cx="2.5" cy="1.5" rx="2" ry="1" id="ellipse" />
+ </a>
+ </svg>
+
+</div>
+<pre id="test">
+<script type="text/javascript;version=1.7">
+<![CDATA[
+
+/** Test for Bug 209275 **/
+SimpleTest.waitForExplicitFinish();
+
+function link123HrefIs(href, testNum) {
+ is($('link1').href, href, "link1 test " + testNum);
+ is($('link2').href, href, "link2 test " + testNum);
+ is($('link3').href, href, "link3 test " + testNum);
+}
+
+var gGen;
+
+function visitedDependentComputedStyle(win, elem, property) {
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ return utils.getVisitedDependentComputedStyle(elem, "", property);
+}
+
+function getColor(elem) {
+ return visitedDependentComputedStyle(document.defaultView, elem, "color");
+}
+
+function getFill(elem) {
+ return visitedDependentComputedStyle(document.defaultView, elem, "fill");
+}
+
+function setXlinkHref(elem, href) {
+ elem.setAttributeNS("http://www.w3.org/1999/xlink", "href", href);
+}
+
+function continueTest() {
+ gGen.next();
+}
+
+var iframe = document.getElementById("iframe");
+var iframeCw = iframe.contentWindow;
+
+function run() {
+ var iframe = document.getElementById("iframe");
+ var iframeCw = iframe.contentWindow;
+
+ // First, set the visited/unvisited link/ellipse colors.
+ const unvisitedColor = "rgb(0, 0, 238)";
+ const visitedColor = "rgb(85, 26, 139)";
+ const unvisitedFill = "rgb(0, 0, 255)";
+ const visitedFill = "rgb(128, 0, 128)";
+
+ const rand = Date.now() + "-" + Math.random();
+
+ // Now we can start the tests in earnest.
+
+ var loc = location;
+ // everything from the location up to and including the final forward slash
+ var path = /(.*\/)[^\/]*/.exec(location)[1];
+
+ // Set colorlink's href so we can check that it changes colors after we
+ // change the base href.
+ $('colorlink').href = "http://example.com/" + rand;
+ setXlinkHref($("ellipselink"), "http://example.com/" + rand);
+
+ // Load http://example.com/${rand} into an iframe so we can test that changing
+ // the document's base changes the visitedness of our links.
+ iframe.onload = continueTest;
+ iframeCw.location = "http://example.com/" + rand;
+ yield undefined; // wait for onload to fire.
+
+ // Make sure things are what as we expect them at the beginning.
+ link123HrefIs("http://mochi.test:8888/", 1);
+ is($('link4').href, loc + "#", "link 4 test 1");
+ is($('link5').href, loc + "#", "link 5 test 1");
+
+ // Remove link5 from the document. We're going to test that its href changes
+ // properly when we change our base.
+ var link5 = $('link5');
+ link5.parentNode.removeChild(link5);
+
+ $('base1').href = "http://example.com";
+
+ // Were the links' hrefs updated after the base change?
+ link123HrefIs("http://example.com/", 2);
+ is($('link4').href, "http://example.com/#", "link 4 test 2");
+ is(link5.href, "http://example.com/#", "link 5 test 2");
+
+ // Were colorlink's color and ellipse's fill updated appropriately?
+ // Because link coloring is asynchronous, we wait until it is updated (or we
+ // timeout and fail anyway).
+ while (getColor($('colorlink')) != visitedColor) {
+ setTimeout(continueTest, 0);
+ yield undefined;
+ }
+ is(getColor($('colorlink')), visitedColor,
+ "Wrong link color after base change.");
+ while (getFill($('ellipselink')) != visitedFill) {
+ setTimeout(continueTest, 0);
+ yield undefined;
+ }
+ is(getFill($('ellipselink')), visitedFill,
+ "Wrong ellipse fill after base change.");
+
+ $('base1').href = "foo/";
+ // Should be interpreted relative to current URI (not the current base), so
+ // base should now be http://mochi.test:8888/foo/
+
+ link123HrefIs("http://mochi.test:8888/", 3);
+ is($('link4').href, path + "foo/#", "link 4 test 3");
+
+ // Changing base2 shouldn't affect anything, because it's not the first base
+ // tag.
+ $('base2').href = "http://example.org/bar/";
+ link123HrefIs("http://mochi.test:8888/", 4);
+ is($('link4').href, path + "foo/#", "link 4 test 4");
+
+ // If we unset base1's href attribute, the document's base should come from
+ // base2, whose href is http://example.org/bar/.
+ $('base1').removeAttribute("href");
+ link123HrefIs("http://example.org/", 5);
+ is($('link4').href, "http://example.org/bar/#", "link 4 test 5");
+
+ // If we remove base1, base2 should become the first base tag, and the hrefs
+ // of all the links should change accordingly.
+ $('base1').parentNode.removeChild($('base1'));
+ link123HrefIs("http://example.org/", 6);
+ is($('link4').href, "http://example.org/bar/#", "link 4 test 6");
+
+ // If we add a new base after base2, nothing should change.
+ var base3 = document.createElement("base");
+ base3.href = "http://base3.example.org/";
+ $('base2').parentNode.insertBefore(base3, $('base2').nextSibling);
+ link123HrefIs("http://example.org/", 7);
+ is($('link4').href, "http://example.org/bar/#", "link 4 test 7");
+
+ // But now if we add a new base before base 2, it should become the primary
+ // base.
+ var base4 = document.createElement("base");
+ base4.href = "http://base4.example.org/";
+ $('base2').parentNode.insertBefore(base4, $('base2'));
+ link123HrefIs("http://base4.example.org/", 8);
+ is($('link4').href, "http://base4.example.org/#", "link 4 test 8");
+
+ // Now if we remove all the base tags, the base should become the page's URI
+ // again.
+ $('base2').parentNode.removeChild($('base2'));
+ base3.parentNode.removeChild(base3);
+ base4.parentNode.removeChild(base4);
+
+ link123HrefIs("http://mochi.test:8888/", 9);
+ is($('link4').href, loc + "#", "link 4 test 9");
+
+ // Setting the href of base0 shouldn't do anything because it's not in the
+ // XHTML namespace.
+ $('base0').href = "http://bar.com";
+ link123HrefIs("http://mochi.test:8888/", 10);
+ is($('link4').href, loc + "#", "link 4 test 10");
+
+ // We load into an iframe a document with a <base href="...">, then remove
+ // the document element. Then we add an <html>, <body>, and <a>, and make
+ // sure that the <a> is resolved relative to the page's location, not its
+ // original base. We do this twice, rebuilding the document in a different
+ // way each time.
+
+ iframe.onload = null;
+ iframeCw.location = "file_bug209275_1.html";
+ yield undefined; // wait for our child to call us back.
+ is(iframeCw.document.getElementById("link").href,
+ path + "file_bug209275_1.html#",
+ "Wrong href after nuking document.");
+
+ iframeCw.location = "file_bug209275_2.html";
+ yield undefined; // wait for callback from child
+ is(iframeCw.document.getElementById("link").href,
+ "http://mochi.test:8888/",
+ "Wrong href after nuking document second time.");
+
+ // Make sure that document.open() makes the document forget about any <base>
+ // tags it has.
+ iframeCw.location = "file_bug209275_3.html";
+ yield undefined; // wait for callback from child
+ is(iframeCw.document.getElementById("link").href,
+ "http://mochi.test:8888/",
+ "Wrong href after document.open().");
+
+ SimpleTest.finish();
+ yield undefined;
+}
+
+window.addEventListener("load", function() {
+ gGen = run();
+ gGen.next();
+}, false);
+
+]]>
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug237071.html b/dom/html/test/test_bug237071.html
new file mode 100644
index 000000000..82bb68dbc
--- /dev/null
+++ b/dom/html/test/test_bug237071.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=237071
+-->
+<head>
+ <title>Test for Bug 237071</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=237071">Mozilla Bug 237071</a>
+<p id="display"></p>
+<div id="content" >
+ <ol id="theOL" start="22">
+ <li id="foo" >should be 22</li>
+ <li id="foo23">should be 23</li>
+ </ol>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 237071 **/
+is($('theOL').start, 22, "OL start attribute mapped to .start, not just text attribute");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug242709.html b/dom/html/test/test_bug242709.html
new file mode 100644
index 000000000..af49eb93c
--- /dev/null
+++ b/dom/html/test/test_bug242709.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=242709
+-->
+<head>
+ <title>Test for Bug 242709</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=242709">Mozilla Bug 242709</a>
+<p id="display"></p>
+<div id="content">
+<iframe src="bug242709_iframe.html" id="a"></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 242709 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var submitted = function() {
+ ok(true, "Disabling button after form submission doesn't prevent submitting");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug24958.html b/dom/html/test/test_bug24958.html
new file mode 100644
index 000000000..3d62dae18
--- /dev/null
+++ b/dom/html/test/test_bug24958.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=24958
+-->
+<head>
+ <title>Test for Bug 24958</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <SCRIPT id="foo" TYPE="text/javascript">/*This space intentionally left blank*/</SCRIPT>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=24958">Mozilla Bug 24958</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 24958 **/
+ok($("foo").text, "\/*This space intentionally left blank*\/", "HTMLScriptElement.text should return text")
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug255820.html b/dom/html/test/test_bug255820.html
new file mode 100644
index 000000000..20727fee4
--- /dev/null
+++ b/dom/html/test/test_bug255820.html
@@ -0,0 +1,123 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=255820
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Test for Bug 255820</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=255820">Mozilla Bug 255820</a>
+<p id="display">
+ <iframe id="f1"></iframe>
+ <iframe id="f2"></iframe>
+ <iframe id="f3"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 255820 **/
+SimpleTest.waitForExplicitFinish();
+
+is(document.characterSet, "UTF-8",
+ "Unexpected character set for our document");
+
+var testsLeft = 4;
+
+function testFinished() {
+ --testsLeft;
+ if (testsLeft == 0) {
+ SimpleTest.finish();
+ }
+}
+
+function charsetTestFinished(id, doc, charsetTarget) {
+ is(doc.characterSet, charsetTarget, "Unexpected charset for subframe " + id);
+ testFinished();
+}
+
+function f2Continue() {
+// Commented out pending discussion at the WHATWG
+// $("f2").
+// setAttribute("onload",
+// "charsetTestFinished('f2 reloaded', this.contentDocument, 'us-ascii');");
+ $("f2").
+ setAttribute("onload",
+ "testFinished();");
+ $("f2").contentWindow.location.reload();
+}
+
+function f3Continue() {
+ var doc = $("f3").contentDocument;
+ is(doc.defaultView.getComputedStyle(doc.body, "").color, "rgb(0, 180, 0)",
+ "Wrong color before reload");
+ $("f3").
+ setAttribute("onload",
+ 'var doc = this.contentDocument; ' +
+ 'is(doc.defaultView.getComputedStyle(doc.body, "").color, ' +
+ ' "rgb(0, 180, 0)",' +
+ ' "Wrong color after reload");' +
+ "charsetTestFinished('f1', this.contentDocument, 'UTF-8')");
+ $("f3").contentWindow.location.reload();
+}
+
+function runTest() {
+ var doc = $("f1").contentDocument;
+ is(doc.characterSet, "UTF-8",
+ "Unexpected initial character set for first frame");
+ doc.open();
+ doc.write('<html></html>');
+ doc.close();
+ is(doc.characterSet, "UTF-8",
+ "Unexpected character set for first frame after write");
+ $("f1").
+ setAttribute("onload",
+ "charsetTestFinished('f1', this.contentDocument, 'UTF-8')");
+ $("f1").contentWindow.location.reload();
+
+ doc = $("f2").contentDocument;
+ is(doc.characterSet, "UTF-8",
+ "Unexpected initial character set for second frame");
+ doc.open();
+ var str = '<html><head>';
+ str += '<script src="data:application/javascript,"><'+'/script>';
+ str += '<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">';
+ str += '</head><body>';
+ str += '</body></html>';
+ doc.write(str);
+ doc.close();
+ is(doc.characterSet, "UTF-8",
+ "Unexpected character set for second frame after write");
+ $("f2").
+ setAttribute("onload",
+ "charsetTestFinished('f2', this.contentDocument, 'UTF-8');" +
+ "f2Continue()");
+
+ doc = $("f3").contentDocument;
+ is(doc.characterSet, "UTF-8",
+ "Unexpected initial character set for first frame");
+ doc.open();
+ var str = '<html><head>';
+ str += '<style>body { color: rgb(255, 0, 0) }</style>';
+ str += '<link type="text/css" rel="stylesheet" href="data:text/css, body { color: rgb(0, 180, 0) }">';
+ str += '</head><body>';
+ str += '</body></html>';
+ doc.write(str);
+ doc.close();
+ is(doc.characterSet, "UTF-8",
+ "Unexpected character set for first frame after write");
+ $("f3").setAttribute("onload", "f3Continue()");
+}
+
+addLoadEvent(runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug259332.html b/dom/html/test/test_bug259332.html
new file mode 100644
index 000000000..a0ad61f40
--- /dev/null
+++ b/dom/html/test/test_bug259332.html
@@ -0,0 +1,64 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=259332
+-->
+<head>
+ <title>Test for Bug 259332</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=259332">Mozilla Bug 259332</a>
+<p id="display"></p>
+<div id="content">
+ <div id="a">a
+ <div id="a">a</div>
+ <input name="a" value="a">
+ <div id="b">b</div>
+ <input name="b" value="b">
+ <div id="c">c</div>
+ </div>
+ <input name="write">
+ <input name="write">
+ <input id="write">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 259332 **/
+
+list = document.all.a;
+ok(list.length == 3, "initial a length");
+
+blist = document.all.b;
+ok(document.all.b.length == 2, "initial b length");
+document.getElementById('b').id = 'a';
+ok(document.all.b.nodeName == "INPUT", "just one b");
+
+ok(blist.length == 1, "just one b");
+ok(list.length == 4, "one more a");
+
+newDiv = document.createElement('div');
+newDiv.id = 'a';
+newDiv.innerHTML = 'a';
+list[0].appendChild(newDiv);
+ok(list.length == 5, "two more a");
+
+ok(document.all.c.textContent == 'c', "one c");
+document.all.c.id = 'a';
+ok(!document.all.c, "no c");
+ok(list.length == 6, "three more a");
+
+ok(document.all.write.length == 3, "name is write");
+
+newDiv = document.createElement('div');
+newDiv.id = 'd';
+newDiv.innerHTML = 'd';
+list[0].appendChild(newDiv);
+ok(document.all.d.textContent == 'd', "new d");
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug274626.html b/dom/html/test/test_bug274626.html
new file mode 100644
index 000000000..f4f045b21
--- /dev/null
+++ b/dom/html/test/test_bug274626.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=274626
+-->
+<head>
+ <title>Test for Bug 274626</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=274626">Mozilla Bug 274626</a>
+<br>
+
+<input id='textbox_enabled' title='hello' value='hello' />
+<input id='textbox_disabled' title='hello' value='hello' disabled/>
+
+<br>
+<input id='input_button_enabled' title='hello' value='hello' type='button' />
+<input id='input_button_disabled' title='hello' value='hello' type='button' disabled />
+
+<br>
+<input id='checkbox_enabled' title='hello' type='checkbox'>hello</input>
+<input id='checkbox_disabled' title='hello' type='checkbox' disabled >hello</input>
+
+<br>
+<button id='button_enabled' title='hello' value='hello' type='button'>test</button>
+<button id='button_disabled' title='hello' value='hello' type='button' disabled>test</button>
+
+<br>
+<textarea id='textarea_enabled' title='hello' value='hello' onclick="alert('click event');"> </textarea>
+<textarea id='textarea_disabled' title='hello' value='hello' onclick="alert('click event');" disabled></textarea>
+
+
+<br>
+<select id='select_enabled' title='hello' onclick="alert('click event');">
+ <option value='item1'>item1</option>
+ <option value='item2'>item2</option>
+</select>
+<select id='select_disabled' title='hello' onclick="alert('click event');" disabled>
+ <option value='item1'>item1</option>
+ <option value='item2'>item2</option>
+</select>
+
+<br>
+<form>
+ <fieldset id='fieldset_enabled' title='hello' onclick="alert('click event');">
+ <legend>Enabled fieldset:</legend>
+ Name: <input type='text' size='30' /><br />
+ Email: <input type='text' size='30' /><br />
+ Date of birth: <input type='text' size='10' />
+ </fieldset>
+</form>
+<form>
+ <fieldset id='fieldset_disabled' title='hello' onclick="alert('click event');" disabled>
+ <legend>Disabled fieldset:</legend>
+ Name: <input type='text' size='30' /><br />
+ Email: <input type='text' size='30' /><br />
+ Date of birth: <input type='text' size='10' />
+ </fieldset>
+</form>
+
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 274626 **/
+
+ function HandlesMouseMove(evt) {
+ evt.target.handlesMouseMove = true;
+ }
+
+ var controls=["textbox_enabled","textbox_disabled",
+ "input_button_enabled", "input_button_disabled", "checkbox_enabled",
+ "checkbox_disabled", "button_enabled", "button_disabled",
+ "textarea_enabled", "textarea_disabled", "select_enabled",
+ "select_disabled", "fieldset_enabled", "fieldset_disabled"];
+
+ for (id of controls) {
+ var ctrl = document.getElementById(id);
+ ctrl.addEventListener('mousemove', HandlesMouseMove, false);
+ ctrl.handlesMouseMove = false;
+ var evt = document.createEvent("MouseEvents");
+ evt.initMouseEvent("mousemove", true, true, window,
+ 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ ctrl.dispatchEvent(evt);
+
+ // Mouse move events are what causes tooltips to show up.
+ // Before this fix we would not allow mouse move events to go through
+ // which in turn did not allow tooltips to be displayed.
+ // This test will ensure that all HTML elements handle mouse move events
+ // so that tooltips can be displayed
+ ok(ctrl.handlesMouseMove, "Disabled element need mouse move for tooltips");
+ }
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug277724.html b/dom/html/test/test_bug277724.html
new file mode 100644
index 000000000..39b54cb7d
--- /dev/null
+++ b/dom/html/test/test_bug277724.html
@@ -0,0 +1,141 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=277724
+-->
+<head>
+ <title>Test for Bug 277724</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=277724">Mozilla Bug 277724</a>
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 277724 **/
+
+var childUnloaded = false;
+
+var nodes = [
+ [ "select", HTMLSelectElement ],
+ [ "textarea", HTMLTextAreaElement ],
+ [ "text", HTMLInputElement ],
+ [ "password", HTMLInputElement ],
+ [ "checkbox", HTMLInputElement ],
+ [ "radio", HTMLInputElement ],
+ [ "image", HTMLInputElement ],
+ [ "submit", HTMLInputElement ],
+ [ "reset", HTMLInputElement ],
+ [ "button input", HTMLInputElement ],
+ [ "hidden", HTMLInputElement ],
+ [ "file", HTMLInputElement ],
+ [ "submit button", HTMLButtonElement ],
+ [ "reset button", HTMLButtonElement ],
+ [ "button", HTMLButtonElement ]
+];
+
+function soon(f) {
+ return function() { setTimeout(f, 0); }
+}
+
+function startTest(frameid) {
+ is(childUnloaded, false, "Child not unloaded yet");
+
+ var doc = $(frameid).contentDocument;
+ ok(doc instanceof Document, "Check for doc", "doc should be a document");
+
+ for (var i = 0; i < nodes.length; ++i) {
+ var id = nodes[i][0];
+ var node = doc.getElementById(id);
+ ok(node instanceof nodes[i][1],
+ "Check for " + id, id + " should be a " + nodes[i][1]);
+ is(node.disabled, false, "check for " + id + " state");
+ node.disabled = true;
+ is(node.disabled, true, "check for " + id + " state change");
+ }
+
+ $(frameid).onload = soon(function() { continueTest(frameid) });
+
+ // Do this off a timeout so it's not treated like a replace load.
+ function loadBlank() {
+ $(frameid).contentWindow.location = "about:blank";
+ }
+ setTimeout(loadBlank, 0);
+}
+
+function continueTest(frameid) {
+ is(childUnloaded, true, "Unload handler should have fired");
+ var doc = $(frameid).contentDocument;
+ ok(doc instanceof Document, "Check for doc", "doc should be a document");
+
+ for (var i = 0; i < nodes.length; ++i) {
+ var id = nodes[i][0];
+ var node = doc.getElementById(id);
+ ok(node === null,
+ "Check for " + id, id + " should be null");
+ }
+
+ $(frameid).onload = soon(function() { finishTest(frameid); });
+
+ // Do this off a timeout too. Why, I'm not sure. Something in session
+ // history creates another history state if we don't. :(
+ function goBack() {
+ $(frameid).contentWindow.history.back();
+ }
+ setTimeout(goBack, 0);
+}
+
+// XXXbz this is a nasty hack to work around the XML content sink not being
+// incremental, so that the _first_ control we test is ok but others are not.
+var testIs = is;
+var once = false;
+function flipper(a, b, c) {
+ if (once) {
+ todo(a == b, c);
+ } else {
+ once = true;
+ is(a, b, c);
+ }
+}
+
+function finishTest(frameid) {
+ var doc = $(frameid).contentDocument;
+ ok(doc instanceof Document, "Check for doc", "doc should be a document");
+
+ for (var i = 0; i < nodes.length; ++i) {
+ var id = nodes[i][0];
+ var node = doc.getElementById(id);
+ ok(node instanceof nodes[i][1],
+ "Check for " + id, id + " should be a " + nodes[i][1]);
+ //testIs(node.disabled, true, "check for " + id + " state restore");
+ }
+
+ if (frameid == "frame2") {
+ SimpleTest.finish();
+ } else {
+ childUnloaded = false;
+
+ // XXXbz this is a nasty hack to deal with the content sink. See above.
+ testIs = flipper;
+
+ $("frame2").onload = soon(function() { startTest("frame2"); });
+ $("frame2").src = "bug277724_iframe2.xhtml";
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+
+<!-- Don't use display:none, since we don't support framestate restoration
+ without a frame tree -->
+<div id="content" style="visibility: hidden">
+ <iframe src="bug277724_iframe1.html" id="frame1"
+ onload="setTimeout(function() { startTest('frame1') }, 0)"></iframe>
+ <iframe src="" id="frame2"></iframe>
+</div>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug277890.html b/dom/html/test/test_bug277890.html
new file mode 100644
index 000000000..066271bd0
--- /dev/null
+++ b/dom/html/test/test_bug277890.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=277890
+-->
+<head>
+ <title>Test for Bug 277890</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=277890">Mozilla Bug 277890</a>
+<p id="display"></p>
+<div id="content">
+<iframe src="bug277890_iframe.html" id="a"></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 277890 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var submitted = function() {
+ ok(true, "Disabling button after form submission doesn't prevent submitting");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug287465.html b/dom/html/test/test_bug287465.html
new file mode 100644
index 000000000..fb5430e95
--- /dev/null
+++ b/dom/html/test/test_bug287465.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=287465
+-->
+<head>
+ <title>Test for Bug 287465</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=287465">Mozilla Bug 287465</a>
+<p id="display"></p>
+<div id="content" style="display:none">
+
+<iframe id="i1" src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'></svg>"></iframe>
+<object id="o1" data="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'></svg>"></object>
+<iframe id="i2" src="data:text/html,<html></html>"></iframe>
+<object id="o2" data="data:text/html,<html></html>"></object>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(doTest);
+
+function doTest() {
+ function checkSVGDocument(id) {
+ var e = document.getElementById(id);
+ ok(e.contentDocument != null, "check nonnull contentDocument '" + id + "'");
+ is(e.contentDocument, e.getSVGDocument(), "check documents match '" + id + "'");
+ }
+
+ checkSVGDocument("o1");
+ checkSVGDocument("i1");
+ checkSVGDocument("o2");
+ checkSVGDocument("i2");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug295561.html b/dom/html/test/test_bug295561.html
new file mode 100644
index 000000000..e283092a7
--- /dev/null
+++ b/dom/html/test/test_bug295561.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=295561
+-->
+<head>
+ <title>Test for Bug 295561</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=295561">Mozilla Bug 295561</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<table id="testTable">
+<thead>
+<tr id="headRow"><td></td></tr>
+</thead>
+<tfoot>
+<tr id="footRow"><td></td></tr>
+</tfoot>
+<tbody id="tBody" name="namedTBody">
+<tr id="trow" name="namedTRow">
+<td id="tcell" name="namedTCell"></td>
+<th id="tcellh" name="namedTH"></th>
+</tr>
+<tr><td></td></tr>
+</tbody>
+<tbody id="tBody2" name="namedTBody2">
+<tr id="trow2" name="namedTRow2">
+<td id="tcell2" name="namedTCell2"></td>
+<th id="tcellh2" name="namedTH2"></th>
+</tr>
+</table>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function testItById(id, collection, collectionName) {
+ is(collection[id], $(id),
+ "Should be able to get by id '" + id + "' from " + collectionName +
+ " collection using square brackets.")
+ is(collection.namedItem(id), $(id),
+ "Should be able to get by id '" + id + "' from " + collectionName +
+ " collection using namedItem.")
+}
+
+function testItByName(name, id, collection, collectionName) {
+ is(collection[name], $(id),
+ "Should be able to get by name '" + name + "' from " + collectionName +
+ " collection using square brackets.")
+ is(collection.namedItem(name), $(id),
+ "Should be able to get by name '" + name + "' from " + collectionName +
+ " collection using namedItem.")
+}
+
+function testIt(name, id, collection, collectionName) {
+ testItByName(name, id, collection, collectionName);
+ testItById(id, collection, collectionName);
+}
+
+var table = $("testTable")
+testIt("namedTBody", "tBody", table.tBodies, "tBodies")
+testIt("namedTRow", "trow", table.rows, "table rows")
+testIt("namedTRow", "trow", $("tBody").rows, "tbody rows")
+testIt("namedTCell", "tcell", $("trow").cells, "cells")
+testIt("namedTH", "tcellh", $("trow").cells, "cells")
+testIt("namedTBody2", "tBody2", table.tBodies, "tBodies")
+testIt("namedTRow2", "trow2", table.rows, "table rows")
+testIt("namedTRow2", "trow2", $("tBody2").rows, "tbody rows")
+testIt("namedTCell2", "tcell2", $("trow2").cells, "cells")
+testIt("namedTH2", "tcellh2", $("trow2").cells, "cells")
+is(table.tBodies.length, 2, "Incorrect tBodies length");
+is(table.rows.length, 5, "Incorrect rows length");
+is(table.rows[0], $("headRow"), "THead row in wrong spot");
+is(table.rows[1], $("trow"), "First tbody row in wrong spot");
+is(table.rows[3], $("trow2"), "Second tbody row in wrong spot");
+is(table.rows[4], $("footRow"), "TFoot row in wrong spot");
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug297761.html b/dom/html/test/test_bug297761.html
new file mode 100644
index 000000000..02d79f9bc
--- /dev/null
+++ b/dom/html/test/test_bug297761.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=297761
+-->
+<head>
+ <title>Test for Bug 297761</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=297761">Mozilla Bug 297761</a>
+<p id="display"></p>
+<div id="content">
+ <iframe src="file_bug297761.html"></iframe>
+ <iframe src="file_bug297761.html"></iframe>
+ <iframe src="file_bug297761.html"></iframe>
+ <iframe src="file_bug297761.html"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 297761 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var nbTests = 4;
+var curTest = 0;
+
+function nextTest()
+{
+ if (curTest == 3) {
+ frames[curTest].document.forms[0].submit();
+ } else {
+ var el = null;
+ if (curTest == 2) {
+ el = frames[curTest].document.getElementById('i');
+ } else {
+ el = frames[curTest].document.forms[0].elements[curTest];
+ }
+
+ el.focus();
+ el.click();
+ }
+}
+
+function frameLoaded(aFrame)
+{
+ var documentLocation = location.href.replace(/\.html.*/, "\.html");
+ is(aFrame.contentWindow.location.href.replace(/\?x=0&y=0/, ""),
+ documentLocation.replace(/test_bug/, "file_bug"),
+ "form should have been submitted to the document location");
+
+ if (++curTest == nbTests) {
+ SimpleTest.finish();
+ } else {
+ nextTest();
+ }
+}
+
+function runTest()
+{
+ // Initialize event handlers.
+ var frames = document.getElementsByTagName('iframe');
+ for (var i=0; i<nbTests; ++i) {
+ frames[i].setAttribute('onload', "frameLoaded(this);");
+ }
+
+ nextTest();
+}
+
+addLoadEvent(runTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug300691-1.html b/dom/html/test/test_bug300691-1.html
new file mode 100644
index 000000000..40d12f9bf
--- /dev/null
+++ b/dom/html/test/test_bug300691-1.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=300691
+-->
+<head>
+ <title>Test for Bug 300691</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=300691">Mozilla Bug 300691</a>
+<p id="display">
+ <textarea id="target"></textarea>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var t = $("target");
+
+/** Test for Bug 300691 **/
+function valueIs(arg, reason) {
+ is(t.value, arg, reason);
+}
+
+function defValueIs(arg, reason) {
+ is(t.defaultValue, arg, reason);
+}
+
+valueIs("", "Nothing in the textarea");
+defValueIs("", "Nothing in the textarea 2");
+
+t.appendChild(document.createTextNode("ab"));
+valueIs("ab", "Appended textnode");
+defValueIs("ab", "Appended textnode 2");
+
+t.firstChild.data = "abcd";
+valueIs("abcd", "Modified textnode text");
+defValueIs("abcd", "Modified textnode text 2");
+
+t.appendChild(document.createTextNode("efgh"));
+valueIs("abcdefgh", "Appended another textnode");
+defValueIs("abcdefgh", "Appended another textnode 2");
+
+t.removeChild(t.lastChild);
+valueIs("abcd", "Removed textnode");
+defValueIs("abcd", "Removed textnode 2");
+
+t.appendChild(document.createTextNode("efgh"));
+valueIs("abcdefgh", "Appended yet another textnode");
+defValueIs("abcdefgh", "Appended yet another textnode 2");
+
+t.normalize();
+valueIs("abcdefgh", "Normalization changes nothing for the value");
+defValueIs("abcdefgh", "Normalization changes nothing for the value 2");
+
+t.defaultValue = "abc";
+valueIs("abc", "Just set the default value on non-edited textarea");
+defValueIs("abc", "Just set the default value on non-edited textarea 2");
+
+t.appendChild(document.createTextNode("defgh"));
+valueIs("abcdefgh", "Appended another textnode again");
+defValueIs("abcdefgh", "Appended another textnode again 2");
+
+t.focus(); // This puts the caret at the end of the textarea, and doing
+ // something like "home" in a cross-platform way is kinda hard.
+sendKey("left");
+sendKey("left");
+sendKey("left");
+sendString("Test");
+
+valueIs("abcdeTestfgh", "Typed 'Test' after three left-arrows starting from end");
+defValueIs("abcdefgh", "Typing 'Test' shouldn't affect default value");
+
+sendKey("right");
+sendKey("right");
+sendKey("back_space");
+sendKey("back_space");
+
+valueIs("abcdeTesth",
+ "Backspaced twice after two right-arrows starting from end of typing");
+defValueIs("abcdefgh", "Deleting shouldn't affect default value");
+
+t.appendChild(document.createTextNode("ijk"));
+valueIs("abcdeTesth",
+ "Appending textnode shouldn't affect value in edited textarea");
+defValueIs("abcdefghijk", "Appended textnode 3");
+
+t.lastChild.data = "lmno";
+valueIs("abcdeTesth",
+ "Modifying textnode text shouldn't affect value in edited textarea");
+defValueIs("abcdefghlmno", "Modified textnode text 3");
+
+t.removeChild(t.firstChild);
+valueIs("abcdeTesth",
+ "Removing child textnode shouldn't affect value in edited textarea");
+defValueIs("defghlmno", "Removed textnode 3");
+
+t.insertBefore(document.createTextNode("abc"), t.firstChild);
+valueIs("abcdeTesth",
+ "Inserting child textnode shouldn't affect value in edited textarea");
+defValueIs("abcdefghlmno", "Inserted a text node");
+
+t.normalize();
+valueIs("abcdeTesth", "Normalization changes nothing for the value 3");
+defValueIs("abcdefghlmno", "Normalization changes nothing for the value 4");
+
+t.defaultValue = "abc";
+valueIs("abcdeTesth", "Setting default value shouldn't affect edited textarea");
+defValueIs("abc", "Just set the default value textarea");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug300691-2.html b/dom/html/test/test_bug300691-2.html
new file mode 100644
index 000000000..6dfa42621
--- /dev/null
+++ b/dom/html/test/test_bug300691-2.html
@@ -0,0 +1,142 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=300691
+-->
+<head>
+ <title>Test for Bug 300691</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=300691">Mozilla Bug 300691</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="text/javascript">
+ // First, setup. We'll be toggling these variables as we go.
+ var test1Ran = false;
+ var test2Ran = false;
+ var test3Ran = false;
+ var test4Ran = false;
+ var test5Ran = false;
+ var test6Ran = false;
+ var test7Ran = false;
+ var test8Ran = false;
+ var test9Ran = false;
+ var test10Ran = false;
+ var test11Ran = false;
+ var test12Ran = false;
+ var test13Ran = false;
+ var test14aRan = false;
+ var test14bRan = false;
+ var test15aRan = false;
+ var test15bRan = false;
+</script>
+<script id="test1" type="text/javascript">test1Ran = true;</script>
+<script id="test2" type="text/javascript"></script>
+<script id="test3" type="text/javascript">;</script>
+<script id="test4" type="text/javascript"> </script>
+<script id="test5" type="text/javascript"></script>
+<script id="test6" type="text/javascript"></script>
+<script id="test7" type="text/javascript"></script>
+<script id="test8" type="text/javascript"></script>
+<script id="test9" type="text/javascript"></script>
+<script id="test10" type="text/javascript" src="data:text/javascript,">
+ test10Ran = true;
+</script>
+<script id="test11" type="text/javascript"
+ src="data:text/javascript,test11Ran = true">
+ test11Ran = false;
+</script>
+<script id="test12" type="text/javascript"></script>
+<script id="test13" type="text/javascript"></script>
+<script id="test14" type="text/javascript"></script>
+<script id="test15" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+ /** Test for Bug 300691 **/
+ $("test2").appendChild(document.createTextNode("test2Ran = true"));
+ is(test2Ran, true, "Should be 2!");
+
+ $("test3").appendChild(document.createTextNode("test3Ran = true"));
+ is(test3Ran, false, "Should have run already 3!");
+
+ $("test4").appendChild(document.createTextNode("test4Ran = true"));
+ is(test4Ran, false, "Should have run already 4!");
+
+ $("test5").appendChild(document.createTextNode(" "));
+ $("test5").appendChild(document.createTextNode("test5Ran = true"));
+ is(test5Ran, false, "Should have run already 5!");
+
+ $("test6").appendChild(document.createTextNode(" "));
+
+ $("test7").appendChild(document.createTextNode(""));
+
+ $("test8").appendChild(document.createTextNode(""));
+
+ $("test9").appendChild(document.createTextNode(""));
+
+ $("test12").src = "data:text/javascript,test12Ran = true;";
+ is(test12Ran, false, "Not yet 12!");
+
+ $("test13").setAttribute("src", "data:text/javascript,test13Ran = true;");
+ is(test13Ran, false, "Not yet 13!");
+
+ $("test14").src = "data:text/javascript,test14aRan = true;";
+ $("test14").appendChild(document.createTextNode("test14bRan = true"));
+ is(test14aRan, false, "Not yet 14a!");
+ is(test14bRan, false, "Not yet 14b!");
+
+ $("test15").src = "data:text/javascript,test15aRan = true;";
+ $("test15").appendChild(document.createTextNode("test15bRan = true"));
+ $("test15").removeAttribute("src");
+ is(test15aRan, false, "Not yet 15a!");
+ is(test15bRan, false, "Not yet 15b!");
+</script>
+<script type="text/javascript">
+ // Follow up on some of those
+ $("test6").appendChild(document.createTextNode("test6Ran = true"));
+ is(test6Ran, false, "Should have run already 6!");
+
+ $("test7").appendChild(document.createTextNode("test7Ran = true"));
+ is(test7Ran, true, "Should be 7!");
+
+ $("test8").insertBefore(document.createTextNode("test8Ran = true"),
+ $("test8").firstChild);
+ is(test8Ran, true, "Should be 8!");
+
+ $("test9").firstChild.data = "test9Ran = true";
+ is(test9Ran, true, "Should be 9!");
+</script>
+<script type="text/javascript">
+function done() {
+ is(test1Ran, true, "Should have run!");
+ is(test3Ran, false, "Already executed test3 script once");
+ is(test4Ran, false,
+ "Should have executed whitespace-only script already");
+ is(test5Ran, false,
+ "Should have executed once the whitespace node was added");
+ is(test6Ran, false,
+ "Should have executed once the whitespace node was added 2");
+ is(test10Ran, false, "Has an src; inline part shouldn't run");
+ is(test11Ran, true, "Script with src should have run");
+ is(test12Ran, true, "Setting src should execute script");
+ is(test13Ran, true, "Setting src attribute should execute script");
+ is(test14aRan, true, "src attribute takes precedence over inline content");
+ is(test14bRan, false, "src attribute takes precedence over inline content 2");
+ is(test15aRan, true,
+ "src attribute load should have started before the attribute got removed");
+ is(test15bRan, false,
+ "src attribute still got executed, so this shouldn't have been");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(done);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug300691-3.xhtml b/dom/html/test/test_bug300691-3.xhtml
new file mode 100644
index 000000000..84c732c2b
--- /dev/null
+++ b/dom/html/test/test_bug300691-3.xhtml
@@ -0,0 +1,48 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=300691
+-->
+<head>
+ <title>Test for Bug 300691</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=300691">Mozilla Bug 300691</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="text/javascript">
+ // First, setup. We'll be toggling these variables as we go.
+ // Note that scripts don't execute immediately when you put text in them --
+ // they wait for the currently-running script to finish.
+ var test1Ran = false;
+ var test2Ran = false;
+ var test3Ran = false;
+</script>
+<script id="test1" type="text/javascript">test1Ran = true;</script>
+<script id="test2" type="text/javascript"></script>
+<script id="test3" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+<![CDATA[
+ /** Test for Bug 300691 **/
+ $("test2").appendChild(document.createCDATASection("test2Ran = true"));
+ is(test2Ran, true, "Should be 2!");
+
+ $("test3").appendChild(document.createCDATASection(""));
+]]>
+</script>
+<script type="text/javascript">
+ // Follow up on some of those
+ $("test3").firstChild.data = "test3Ran = true";
+ is(test3Ran, true, "Should be 3!");
+</script>
+<script type="text/javascript">
+ is(test1Ran, true, "Should have run!");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug311681.html b/dom/html/test/test_bug311681.html
new file mode 100644
index 000000000..b9139a391
--- /dev/null
+++ b/dom/html/test/test_bug311681.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+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">
+ // 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 new 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">
+ testNode = fun.call(document, "content");
+ isnot(testNode, null, "Should have node here");
+ </script>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug311681.xhtml b/dom/html/test/test_bug311681.xhtml
new file mode 100644
index 000000000..0c7d5fd6f
--- /dev/null
+++ b/dom/html/test/test_bug311681.xhtml
@@ -0,0 +1,102 @@
+<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 new 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");
+ ok(testNode != null, "Should have node here");
+ ]]>
+ </script>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug324378.html b/dom/html/test/test_bug324378.html
new file mode 100644
index 000000000..166e4f149
--- /dev/null
+++ b/dom/html/test/test_bug324378.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html id="a" id="b">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=324378
+-->
+<head id="c" id="d">
+ <head id="j" foo="k" foo="l">
+ <title>Test for Bug 324378</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body id="e" id="f">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=324378">Mozilla Bug 324378</a>
+<script>
+ var html = document.documentElement;
+ is(document.getElementsByTagName("html").length, 1,
+ "Unexpected number of htmls");
+ is(document.getElementsByTagName("html")[0], html,
+ "Unexpected <html> element");
+ is(document.getElementsByTagName("head").length, 1,
+ "Unexpected number of heads");
+ is(html.getElementsByTagName("head").length, 1,
+ "Unexpected number of heads in <html>");
+ is(document.getElementsByTagName("body").length, 1,
+ "Unexpected number of bodies");
+ is(html.getElementsByTagName("body").length, 1,
+ "Unexpected number of bodies in <html>");
+ var head = document.getElementsByTagName("head")[0];
+ var body = document.getElementsByTagName("body")[0];
+</script>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <html id="g" foo="h" foo="i">
+ <body id="m" foo="n" foo="o">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 324378 **/
+ is(document.getElementsByTagName("html").length, 1,
+ "Unexpected number of htmls after additions");
+ is(document.getElementsByTagName("html")[0], html,
+ "Unexpected <html> element");
+ is(document.documentElement, html,
+ "Unexpected root node");
+ is(document.getElementsByTagName("head").length, 1,
+ "Unexpected number of heads after additions");
+ is(document.getElementsByTagName("head")[0], head,
+ "Unexpected <head> element");
+ is(document.getElementsByTagName("body").length, 1,
+ "Unexpected number of bodies after additions");
+ is(document.getElementsByTagName("body")[0], body,
+ "Unexpected <body> element");
+
+ is(html.id, "a", "Unexpected <html> id");
+ is(head.id, "c", "Unexpected <head> id");
+ is(body.id, "e", "Unexpected <body> id");
+ is($("a"), html, "Unexpected node with id=a");
+ is($("b"), null, "Unexpected node with id=b");
+ is($("c"), head, "Unexpected node with id=c");
+ is($("d"), null, "Unexpected node with id=d");
+ is($("e"), body, "Unexpected node with id=e");
+ is($("f"), null, "Unexpected node with id=f");
+ is($("g"), null, "Unexpected node with id=g");
+ is($("j"), null, "Unexpected node with id=j");
+ is($("m"), null, "Unexpected node with id=m");
+
+ is(html.getAttribute("foo"), "h", "Unexpected 'foo' value on <html>");
+ is(head.getAttribute("foo"), null, "Unexpected 'foo' value on <head>");
+ is(body.getAttribute("foo"), "n", "Unexpected 'foo' value on <body>");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug330705-1.html b/dom/html/test/test_bug330705-1.html
new file mode 100644
index 000000000..2a5c737ed
--- /dev/null
+++ b/dom/html/test/test_bug330705-1.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=330705
+-->
+<head>
+ <title>Test for Bug 330705</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script>
+ /* variable is true if the element is focused, false otherwise */
+ var inputFocused = false;
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=330705">Mozilla Bug 330705</a>
+<p id="display">
+ <input onfocus="inputFocused = true" onblur="inputFocused = false" type="text">
+ <button></button>
+</p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 330705 **/
+ SimpleTest.waitForExplicitFinish();
+ var isFocused = false;
+
+ function onLoad() {
+ document.getElementsByTagName('input')[0].focus();
+ document.getElementsByTagName('button')[0].blur();
+ ok(inputFocused == true, "the input element is still focused after blur() has been called on the unfocused element");
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(onLoad);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug332246.html b/dom/html/test/test_bug332246.html
new file mode 100644
index 000000000..e1acbe309
--- /dev/null
+++ b/dom/html/test/test_bug332246.html
@@ -0,0 +1,75 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=332246
+-->
+<head>
+ <title>Test for Bug 332246 - scrollIntoView(false) doesn't work correctly for inline elements that wrap at multiple lines</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=332246">Mozilla Bug 332246</a>
+<p id="display"></p>
+<div id="content">
+
+<div id="a1" style="height: 100px; width: 100px; overflow: hidden; outline:1px dotted black;">
+<div style="height: 100px"></div>
+<a id="a2" href="#" style="display:block; background:yellow; height:200px;">Top</a>
+<div style="height: 100px"></div>
+</div>
+
+<div id="b1" style="height: 100px; width: 100px; overflow: hidden; outline:1px dotted black;">
+<div style="height: 100px"></div>
+<div id="b2" href="#" style="border:10px solid black; background:yellow; height:200px;"></div>
+<div style="height: 100px"></div>
+</div>
+
+<br>
+
+<div id="c1" style="height: 100px; width: 100px; overflow: hidden; position: relative; outline:1px dotted black;">
+<div id="c2" style="border: 10px solid black; height: 200px; width: 50px; position: absolute; top: 100px;"></div>
+<div style="height: 100px"></div>
+</div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 332246 **/
+
+function isWithFuzz(itIs, itShouldBe, fuzz, description) {
+ ok(Math.abs(itIs - itShouldBe) <= fuzz, `${description} - expected a value between ${itShouldBe - fuzz} and ${itShouldBe + fuzz}, got ${itIs}`);
+}
+
+var a1 = document.getElementById('a1');
+var a2 = document.getElementById('a2');
+isWithFuzz(a1.scrollHeight, 400, 1, "Wrong a1.scrollHeight");
+is(a1.offsetHeight, 100, "Wrong a1.offsetHeight");
+a2.scrollIntoView(true);
+is(a1.scrollTop, 100, "Wrong scrollTop value after a2.scrollIntoView(true)");
+a2.scrollIntoView(false);
+is(a1.scrollTop, 200, "Wrong scrollTop value after a2.scrollIntoView(false)");
+
+var b1 = document.getElementById('b1');
+var b2 = document.getElementById('b2');
+isWithFuzz(b1.scrollHeight, 420, 1, "Wrong b1.scrollHeight");
+is(b1.offsetHeight, 100, "Wrong b1.offsetHeight");
+b2.scrollIntoView(true);
+is(b1.scrollTop, 100, "Wrong scrollTop value after b2.scrollIntoView(true)");
+b2.scrollIntoView(false);
+is(b1.scrollTop, 220, "Wrong scrollTop value after b2.scrollIntoView(false)");
+
+var c1 = document.getElementById('c1');
+var c2 = document.getElementById('c2');
+isWithFuzz(c1.scrollHeight, 320, 1, "Wrong c1.scrollHeight");
+is(c1.offsetHeight, 100, "Wrong c1.offsetHeight");
+c2.scrollIntoView(true);
+is(c1.scrollTop, 100, "Wrong scrollTop value after c2.scrollIntoView(true)");
+c2.scrollIntoView(false);
+isWithFuzz(c1.scrollTop, 220, 1, "Wrong scrollTop value after c2.scrollIntoView(false)");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug332848.xhtml b/dom/html/test/test_bug332848.xhtml
new file mode 100644
index 000000000..2b19e6b78
--- /dev/null
+++ b/dom/html/test/test_bug332848.xhtml
@@ -0,0 +1,86 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=332848
+-->
+<head>
+ <title>Test for Bug 332848</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=332848">Mozilla Bug 332848</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 332848 **/
+
+// parseChecker will become true if we keep parsing after calling close().
+var parseChecker = false;
+
+function test() {
+ try {
+ document.open();
+ is(0, 1, "document.open succeeded");
+ } catch (e) {
+ is (e.name, "InvalidStateError",
+ "Wrong exception from document.open");
+ is (e.code, DOMException.INVALID_STATE_ERR,
+ "Wrong exception from document.open");
+ }
+
+ try {
+ document.write("aaa");
+ is(0, 1, "document.write succeeded");
+ } catch (e) {
+ is (e.name, "InvalidStateError",
+ "Wrong exception from document.write");
+ is (e.code, DOMException.INVALID_STATE_ERR,
+ "Wrong exception from document.write");
+ }
+
+ try {
+ document.writeln("aaa");
+ is(0, 1, "document.write succeeded");
+ } catch (e) {
+ is (e.name, "InvalidStateError",
+ "Wrong exception from document.write");
+ is (e.code, DOMException.INVALID_STATE_ERR,
+ "Wrong exception from document.write");
+ }
+
+ try {
+ document.close();
+ is(0, 1, "document.close succeeded");
+ } catch (e) {
+ is (e.name, "InvalidStateError",
+ "Wrong exception from document.close");
+ is (e.code, DOMException.INVALID_STATE_ERR,
+ "Wrong exception from document.close");
+ }
+}
+
+function loadTest() {
+ is(parseChecker, true, "Parsing stopped");
+ test();
+ SimpleTest.finish();
+}
+
+window.onload = loadTest;
+
+SimpleTest.waitForExplicitFinish();
+
+test();
+]]>
+</script>
+<script>
+ parseChecker = true;
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug332893-1.html b/dom/html/test/test_bug332893-1.html
new file mode 100644
index 000000000..4136c73e6
--- /dev/null
+++ b/dom/html/test/test_bug332893-1.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+
+<form id="form1">
+ <input id="F1I1" type="input" value="11"/>
+ <input id="F1I2" type="input" value="12"/>
+</form>
+<form id="form2">
+ <input id="F2I1" type="input" value="21"/>
+ <input id="F2I2" type="input" value="22"/>
+</form>
+<script>
+<!-- Create a new input, add it to the first form, move it to the 2nd form, then move it back to the first -->
+ var form1 = document.getElementById("form1");
+ var form2 = document.getElementById("form2");
+ var newInput = document.createElement("input");
+ newInput.value = "13";
+ form1.insertBefore(newInput, form1.firstChild);
+ var F2I2 = document.getElementById("F2I2");
+ form2.insertBefore(newInput, F2I2);
+ form1.insertBefore(newInput, form1.firstChild);
+
+ is(form1.elements.length, 3, "Form 1 has the correct length");
+ is(form1.elements[0].value, "13", "Form 1 element 1 is correct");
+ is(form1.elements[1].value, "11", "Form 1 element 2 is correct");
+ is(form1.elements[2].value, "12", "Form 1 element 3 is correct");
+
+ is(form2.elements.length, 2, "Form 2 has the correct length");
+ is(form2.elements[0].value, "21", "Form 2 element 1 is correct");
+ is(form2.elements[1].value, "22", "Form 2 element 2 is correct");
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug332893-2.html b/dom/html/test/test_bug332893-2.html
new file mode 100644
index 000000000..36083b6a1
--- /dev/null
+++ b/dom/html/test/test_bug332893-2.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+
+
+<form id="form1">
+ <table>
+ <tbody id="table1">
+ <tr id="F1I0"><td><input form='form1' type="input" value="10"/></td></tr>
+ <tr id="F1I1"><td><input type="input" value="11"/></td></tr>
+ <tr id="F1I2"><td><input type="input" value="12"/></td></tr>
+ </tbody>
+ </table>
+</form>
+<form id="form2">
+ <table>
+ <tbody id="table2">
+ <tr id="F2I1"><td><input type="input" value="21"/></td></tr>
+ <tr id="F2I2"><td><input type="input" value="22"/></td></tr>
+ </tbody>
+ </table>
+</form>
+
+<script>
+ var table1 = document.getElementById("table1");
+ var F1I0 = table1.getElementsByTagName("tr")[0];
+ var F1I1 = table1.getElementsByTagName("tr")[1];
+ table1.removeChild(F1I0);
+ table1.removeChild(F1I1);
+
+ var table2 = document.getElementById("table2");
+ table2.insertBefore(F1I0, table2.firstChild);
+ table2.insertBefore(F1I1, table2.firstChild);
+
+ var form1 = document.getElementById("form1");
+ var form2 = document.getElementById("form2");
+
+ is(form1.elements.length, 2, "Form 1 length is correct");
+ is(form1.elements[0].value, "12", "Form 1 first element is correct");
+ is(form1.elements[1].value, "10", "Form 2 second element is correct");
+ is(form2.elements.length, 3, "Form 2 length is correct");
+ is(form2.elements[0].value, "11", "Form 2 element 1 is correct");
+ is(form2.elements[1].value, "21", "Form 2 element 2 is correct");
+ is(form2.elements[2].value, "22", "Form 2 element 3 is correct");
+
+</script>
+
+</body>
+</html>
diff --git a/dom/html/test/test_bug332893-3.html b/dom/html/test/test_bug332893-3.html
new file mode 100644
index 000000000..372319c22
--- /dev/null
+++ b/dom/html/test/test_bug332893-3.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<form id="form1">
+ <table>
+ <tbody>
+ <tr>
+ <td>
+ <table>
+ <tbody id="table1">
+ <tr id="F1I0"><td><input form='form1' type="input" value="10"/></td></tr>
+ <tr id="F1I1"><td><input type="input" value="11"/></td></tr>
+ <tr id="F1I2"><td><input type="input" value="12"/></td></tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</form>
+<form id="form2">
+ <table>
+ <tbody id="table2">
+ <tr id="F2I1"><td><input type="input" value="21"/></td></tr>
+ <tr id="F2I2"><td><input type="input" value="22"/></td></tr>
+ </tbody>
+ </table>
+</form>
+
+<script>
+ var table1 = document.getElementById("table1");
+ var F1I0 = table1.getElementsByTagName("tr")[0];
+ var F1I1 = table1.getElementsByTagName("tr")[1];
+ table1.removeChild(F1I0);
+ table1.removeChild(F1I1);
+
+ var table2 = document.getElementById("table2");
+ table2.insertBefore(F1I0, table2.firstChild);
+ table2.insertBefore(F1I1, table2.firstChild);
+
+ var form1 = document.getElementById("form1");
+ var form2 = document.getElementById("form2");
+
+ is(form1.elements.length, 2, "Form 1 has the correct length");
+ is(form1.elements[0].value, "12", "Form 1 element 1 is correct");
+ is(form1.elements[1].value, "10", "Form 1 element 2 is correct");
+
+ is(form2.elements.length, 3, "Form 2 has the correct length");
+ is(form2.elements[0].value, "11", "Form 2 element 1 is correct");
+ is(form2.elements[1].value, "21", "Form 2 element 2 is correct");
+ is(form2.elements[2].value, "22", "Form 2 element 2 is correct");
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug332893-4.html b/dom/html/test/test_bug332893-4.html
new file mode 100644
index 000000000..abbc60ca6
--- /dev/null
+++ b/dom/html/test/test_bug332893-4.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<form id="form1">
+ <input id="input1" type="input" name="input" value="1"/>
+ <input id="input2" type="input" name="input" value="2"/>
+ <input id="input3" type="input" name="input" value="3"/>
+</form>
+<script>
+ var input1 = document.getElementById("input1");
+ var input2 = document.getElementById("input2");
+ var form1 = document.getElementById("form1");
+ form1.insertBefore(input2, input1);
+
+ is(form1.elements["input"].length, 3, "Form 1 'input' has the correct length");
+ is(form1.elements["input"][0].value, "2", "Form 1 element 1 is correct");
+ is(form1.elements["input"][1].value, "1", "Form 1 element 2 is correct");
+ is(form1.elements["input"][2].value, "3", "Form 1 element 3 is correct");
+
+ is(form1.elements["input"][0].id, "input2", "Form 1 element 1 id is correct");
+ is(form1.elements["input"][1].id, "input1", "Form 1 element 2 id is correct");
+ is(form1.elements["input"][2].id, "input3", "Form 1 element 3 id is correct");
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug332893-5.html b/dom/html/test/test_bug332893-5.html
new file mode 100644
index 000000000..6898f9157
--- /dev/null
+++ b/dom/html/test/test_bug332893-5.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<form id="form1">
+ <input id="input1" type="input" name="input" value="1"/>
+ <input id="input" type="input" name="input_other" value="2"/>
+ <input id="input3" type="input" name="input" value="3"/>
+</form>
+<script>
+ var input1 = document.getElementById("input1");
+ var input2 = document.getElementById("input");
+ var form1 = document.getElementById("form1");
+ form1.insertBefore(input2, input1);
+
+ is(form1.elements["input"].length, 3, "Form 1 'input' has the correct length");
+ is(form1.elements["input"][0].value, "2", "Form 1 element 1 is correct");
+ is(form1.elements["input"][1].value, "1", "Form 1 element 2 is correct");
+ is(form1.elements["input"][2].value, "3", "Form 1 element 3 is correct");
+
+ is(form1.elements["input"][0].id, "input", "Form 1 element 1 id is correct");
+ is(form1.elements["input"][1].id, "input1", "Form 1 element 2 id is correct");
+ is(form1.elements["input"][2].id, "input3", "Form 1 element 3 id is correct");
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug332893-6.html b/dom/html/test/test_bug332893-6.html
new file mode 100644
index 000000000..c5e6fe376
--- /dev/null
+++ b/dom/html/test/test_bug332893-6.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<form id="form1">
+ <input id="input1" type="input" name="input" value="1"/>
+ <input id="input" type="input" name="input_other" value="2"/>
+ <input id="input3" type="input" name="input" value="3"/>
+</form>
+<script>
+ var input1 = document.getElementById("input1");
+ var input2 = document.getElementById("input");
+ var form1 = document.getElementById("form1");
+ form1.insertBefore(input2, input1);
+
+ is(form1.elements["input"].length, 3, "Form 1 'input' has the correct length");
+ is(form1.elements["input"][0].value, "2", "Form 1 element 1 is correct");
+ is(form1.elements["input"][1].value, "1", "Form 1 element 2 is correct");
+
+ is(form1.elements["input"][0].id, "input", "Form 1 element 1 id is correct");
+ is(form1.elements["input"][1].id, "input1", "Form 1 element 2 id is correct");
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug332893-7.html b/dom/html/test/test_bug332893-7.html
new file mode 100644
index 000000000..7939b34ae
--- /dev/null
+++ b/dom/html/test/test_bug332893-7.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<form id="form1">
+ <input id="input1" type="input" name="input" value="1"/>
+ <input id="input2" type="input" name="input" value="2"/>
+ <input id="input3" type="input" name="input" value="3"/>
+ <input id="input4" type="input" name="input" value="4"/>
+ <input id="input5" type="input" name="input" value="5"/>
+ <input id="input6" type="input" name="input" value="6"/>
+ <input id="input7" type="input" name="input" value="7"/>
+ <input id="input8" type="input" name="input" value="8"/>
+ <input id="input9" type="input" name="input" value="9"/>
+ <input id="input10" type="input" name="input" value="10"/>
+
+
+
+
+</form>
+<script>
+ var input1 = document.getElementById("input1");
+ var input2 = document.getElementById("input2");
+ var input3 = document.getElementById("input3");
+ var input4 = document.getElementById("input4");
+ var input5 = document.getElementById("input5");
+ var input6 = document.getElementById("input6");
+ var input7 = document.getElementById("input7");
+ var input8 = document.getElementById("input8");
+ var input9 = document.getElementById("input9");
+ var input10 = document.getElementById("input10");
+
+
+ var form1 = document.getElementById("form1");
+
+ form1.insertBefore(input2, input1);
+ form1.insertBefore(input10, input6);
+ form1.insertBefore(input8, input4);
+ form1.insertBefore(input9, input2);
+
+ is(form1.elements["input"].length, 10, "Form 1 'input' has the correct length");
+ is(form1.elements["input"][0].value, "9", "Form 1 element 1 is correct");
+ is(form1.elements["input"][1].value, "2", "Form 1 element 2 is correct");
+ is(form1.elements["input"][2].value, "1", "Form 1 element 3 is correct");
+ is(form1.elements["input"][3].value, "3", "Form 1 element 4 is correct");
+ is(form1.elements["input"][4].value, "8", "Form 1 element 5 is correct");
+ is(form1.elements["input"][5].value, "4", "Form 1 element 6 is correct");
+ is(form1.elements["input"][6].value, "5", "Form 1 element 7 is correct");
+ is(form1.elements["input"][7].value, "10", "Form 1 element 8 is correct");
+ is(form1.elements["input"][8].value, "6", "Form 1 element 9 is correct");
+ is(form1.elements["input"][9].value, "7", "Form 1 element 10 is correct");
+
+ is(form1.elements["input"][0].id, "input9", "Form 1 element 1 id is correct");
+ is(form1.elements["input"][1].id, "input2", "Form 1 element 2 id is correct");
+ is(form1.elements["input"][2].id, "input1", "Form 1 element 3 id is correct");
+ is(form1.elements["input"][3].id, "input3", "Form 1 element 4 id is correct");
+ is(form1.elements["input"][4].id, "input8", "Form 1 element 5 id is correct");
+ is(form1.elements["input"][5].id, "input4", "Form 1 element 6 id is correct");
+ is(form1.elements["input"][6].id, "input5", "Form 1 element 7 id is correct");
+ is(form1.elements["input"][7].id, "input10", "Form 1 element 8 id is correct");
+ is(form1.elements["input"][8].id, "input6", "Form 1 element 9 id is correct");
+ is(form1.elements["input"][9].id, "input7", "Form 1 element 10 id is correct");
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug3348.html b/dom/html/test/test_bug3348.html
new file mode 100644
index 000000000..50280e0a4
--- /dev/null
+++ b/dom/html/test/test_bug3348.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=3348
+-->
+<head>
+ <title>Test for Bug 3348</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=3348">Mozilla Bug 3348</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<form id="form1">
+<input type="button" value="click here" onclick="buttonClick();">
+</form>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 3348 **/
+
+var oForm = document.getElementById("form1");
+is(oForm.tagName, "FORM", "tagName of HTML element gives tag in upper case");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug340017.xhtml b/dom/html/test/test_bug340017.xhtml
new file mode 100644
index 000000000..eabef837e
--- /dev/null
+++ b/dom/html/test/test_bug340017.xhtml
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=340017
+-->
+<head>
+ <title>Test for Bug 340017</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=340017">Mozilla Bug 340017</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <form id="frmfoo" name="foo" action="" />
+</div>
+<pre id="test">
+<script type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 340017 **/
+is(document.foo, document.getElementById("frmfoo"),
+ "The form with name 'foo' should be a document accessible property");
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug340800.html b/dom/html/test/test_bug340800.html
new file mode 100644
index 000000000..68e6ed0e4
--- /dev/null
+++ b/dom/html/test/test_bug340800.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=340800
+-->
+<head>
+ <title>Test for Bug 340800</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=340800">Mozilla Bug 340800</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <h1>iframe text/plain as DOM test</h1>
+
+ <div>
+
+ <iframe name="iframe1" width="100%" height="200"
+ src="bug340800_iframe.txt"></iframe>
+ </div>
+
+ <div>
+ <h2>textarea with iframe content</h2>
+ <textarea rows="10" cols="80" id="textarea1"></textarea>
+ </div>
+
+ <div>
+ <h2>div with white-space: pre and iframe content</h2>
+ <div id="div1"></div>
+ </div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 340800 **/
+function populateIframes () {
+ var iframe, iframeBody;
+ if ((iframe = window.frames.iframe1) && (iframeBody = iframe.document.body)) {
+ $('div1').innerHTML = iframeBody.innerHTML;
+ $('textarea1').value = iframeBody.innerHTML;
+ }
+ is($('div1').firstChild.tagName, "PRE", "innerHTML from txt iframe works with div");
+ ok($('textarea1').value.indexOf("<pre>") > -1, "innerHTML from txt iframe works with textarea.value");
+ SimpleTest.finish();
+}
+
+addLoadEvent(populateIframes);
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug347174.html b/dom/html/test/test_bug347174.html
new file mode 100644
index 000000000..aadef4423
--- /dev/null
+++ b/dom/html/test/test_bug347174.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=347174
+-->
+<head>
+ <title>Test for Bug 347174</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=347174">Mozilla Bug 347174</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 347174 **/
+// simple test of readyState during loading, DOMContentLoaded, and complete
+// this test passes in IE7
+window.readyStateText = [];
+window.readyStateText.push("script tag: " + document.readyState);
+is(document.readyState, "loading", "document.readyState should be 'loading' when scripts runs initially");
+
+function attachCustomEventListener(element, eventName, command) {
+ if (window.addEventListener && !window.opera)
+ element.addEventListener(eventName, command, true);
+ else if (window.attachEvent)
+ element.attachEvent("on" + eventName, command);
+}
+
+function showMessage(msg) {
+ window.readyStateText.push(msg);
+ document.getElementById("display").innerHTML = readyStateText.join("<br>");
+}
+
+function load() {
+ is(document.readyState, "complete", "document.readyState should be 'complete' on load");
+ showMessage("load: " + document.readyState);
+ SimpleTest.finish();
+}
+
+function readyStateChange() {
+ showMessage("readyStateChange: " + document.readyState);
+}
+
+function DOMContentLoaded() {
+ is(document.readyState, "interactive", "document.readyState should be 'interactive' on DOMContentLoaded");
+ showMessage("DOMContentLoaded: " + document.readyState);
+}
+
+window.onload=load;
+
+attachCustomEventListener(document, "readystatechange", readyStateChange);
+attachCustomEventListener(document, "DOMContentLoaded", DOMContentLoaded);
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug347174_write.html b/dom/html/test/test_bug347174_write.html
new file mode 100644
index 000000000..1ff870de2
--- /dev/null
+++ b/dom/html/test/test_bug347174_write.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=347174
+-->
+<head>
+ <title>Test for Bug 347174</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=347174">Mozilla Bug 347174</a>
+<p id="display"></p>
+
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 347174 **/
+// simple test of readyState during loading, DOMContentLoaded, and complete
+// this test passes in IE7
+window.readyStateText = [];
+window.loaded = false;
+function attachCustomEventListener(element, eventName, command) {
+ if (window.addEventListener && !window.opera)
+ element.addEventListener(eventName, command, true);
+ else if (window.attachEvent)
+ element.attachEvent("on" + eventName, command);
+}
+
+function showMessage(msg) {
+ window.readyStateText.push(msg);
+ document.getElementById("display").innerHTML = readyStateText.join("<br>");
+}
+
+function frameLoad() {
+ var doc = $('iframe').contentWindow.document;
+ is(doc.readyState, "complete", "frame document.readyState should be 'complete' on load");
+ showMessage("frame load: " + doc.readyState);
+ if (window.loaded) SimpleTest.finish();
+}
+
+function load() {
+ window.loaded = true;
+
+ var imgsrc = "<img onload ='window.parent.imgLoad()' src='image.png?noCache="
+ + (new Date().getTime()) + "'>\n";
+ var doc = $('iframe').contentWindow.document;
+ doc.writeln(imgsrc);
+ doc.close();
+ showMessage("frame after document.write: " + doc.readyState);
+ isnot(doc.readyState, "complete", "frame document.readyState should not be 'complete' after document.write");
+}
+
+function imgLoad() {
+ var doc = $('iframe').contentWindow.document;
+ showMessage("frame after imgLoad: " + doc.readyState);
+ is(doc.readyState, "interactive", "frame document.readyState should still be 'interactive' after img loads");
+}
+
+window.onload=load;
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<iframe src="404doesnotexist" id="iframe" onload="frameLoad();"></iframe>
+</body>
+</html>
diff --git a/dom/html/test/test_bug347174_xsl.html b/dom/html/test/test_bug347174_xsl.html
new file mode 100644
index 000000000..4fdfd9122
--- /dev/null
+++ b/dom/html/test/test_bug347174_xsl.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=347174
+-->
+<head>
+ <title>Test for Bug 347174</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=347174">Mozilla Bug 347174</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe src="347174transformable.xml" id="iframe"></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 347174 **/
+// Test of readyState of XML document transformed via XSLT to HTML
+// this test passes in IE7
+window.readyStateText = [];
+
+function showMessage(msg) {
+ window.readyStateText.push(msg);
+ document.getElementById("display").innerHTML = readyStateText.join("<br>");
+}
+
+function frameScriptTag(readyState) {
+ isnot(readyState, "complete", "document.readyState should not be 'complete' when scripts run initially");
+ showMessage("script tag: " + readyState);
+}
+
+function frameLoad(readyState) {
+ is(readyState, "complete", "document.readyState should be 'complete' on load");
+ showMessage("load: " + readyState);
+ SimpleTest.finish();
+}
+
+function frameReadyStateChange(readyState) {
+ showMessage("readyStateChange: " + readyState);
+}
+
+function frameDOMContentLoaded(readyState) {
+ is(readyState, "interactive", "document.readyState should be 'interactive' on DOMContentLoaded");
+ showMessage("DOMContentLoaded: " + readyState);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug347174_xslp.html b/dom/html/test/test_bug347174_xslp.html
new file mode 100644
index 000000000..ea4fc79b5
--- /dev/null
+++ b/dom/html/test/test_bug347174_xslp.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=347174
+-->
+<head>
+ <title>Test for Bug 347174</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=347174">Mozilla Bug 347174</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 347174 **/
+// verifies that documents created with createDocument are born in "complete" state
+// (so we don't accidentally leave them in "interactive" state)
+window.readyStateText = [];
+
+function xmlLoaded(e) {
+ var xslDoc = document.implementation.createDocument("", "test", null);
+ xslDoc.async=false;
+ xslDoc.load("347174transform.xsl");
+
+ var processor = new XSLTProcessor();
+ processor.importStylesheet(xslDoc);
+
+ window.transformedDoc = processor.transformToDocument(xmlDoc);
+
+ showMessage("loaded: " + xmlDoc.readyState);
+ is(xmlDoc.readyState, "complete", "XML document.readyState should be 'complete' after transform");
+ SimpleTest.finish();
+}
+
+var xmlDoc = document.implementation.createDocument("", "test", null);
+showMessage("createDocument: " + xmlDoc.readyState);
+is(xmlDoc.readyState, "complete", "created document readyState should be 'complete' before being associated with a parser");
+xmlDoc.async=true;
+xmlDoc.addEventListener("load", xmlLoaded, false);
+xmlDoc.load("347174transformable.xml");
+showMessage("load called: " + xmlDoc.readyState);
+isnot(xmlDoc.readyState, "complete", "created document readyState should not be 'complete' after load called");
+
+
+function showMessage(msg) {
+ window.readyStateText.push(msg);
+ $("display").innerHTML = readyStateText.join("<br>");
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug353415-1.html b/dom/html/test/test_bug353415-1.html
new file mode 100644
index 000000000..1f528ae6f
--- /dev/null
+++ b/dom/html/test/test_bug353415-1.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<iframe name="submit_frame"></iframe>
+<form method="get" id="form1" target="submit_frame" action="../../../../../blah">
+<input type="text" name="field1" value="teststring"><br>
+<input type="radio" name="field2" value="0" checked> 0
+<input type="radio" name="field3" value="1"> 1<br>
+<input type="checkbox" name="field4" value="1" checked> 1
+<input type="checkbox" name="field5" value="2"> 2
+<input type="checkbox" name="field6" value="3" checked> 3
+<select name="field7">
+<option value="1">1</option>
+<option value="2" selected>2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+</select>
+<input name="field8" value="8">
+<input name="field9" value="9">
+<input type="image" name="field10">
+<label name="field11">
+<input name="field12">
+<input type="button" name="field13" value="button">
+</form>
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ document.getElementsByName('submit_frame')[0].onload = function() {
+ is(frames['submit_frame'].location.href, "http://mochi.test:8888/blah?field1=teststring&field2=0&field4=1&field6=3&field7=2&field8=8&field9=9&field12=", "Submit string was correct.");
+ SimpleTest.finish();
+ };
+
+ document.forms[0].submit();
+ });
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug353415-2.html b/dom/html/test/test_bug353415-2.html
new file mode 100644
index 000000000..9e82e9df8
--- /dev/null
+++ b/dom/html/test/test_bug353415-2.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<iframe name="submit_frame"></iframe>
+<form method="get" id="form1" target="submit_frame" action="../../../../../blah">
+<table>
+<tr><td>
+<input type="text" name="field1" value="teststring"><br>
+<input type="radio" name="field2" value="0" checked> 0
+<input type="radio" name="field3" value="1"> 1<br>
+<input type="checkbox" name="field4" value="1" checked> 1
+<input type="checkbox" name="field5" value="2"> 2
+<input type="checkbox" name="field6" value="3" checked> 3
+<select name="field7">
+<option value="1">1</option>
+<option value="2" selected>2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+</select>
+<input name="field8" value="8">
+<input name="field9" value="9">
+<input type="image" name="field10">
+<label name="field11"></label>
+<input name="field12">
+<input type="button" name="field13" value="button">
+<input type="hidden" name="field14" value="14">
+</td>
+<input type="text" name="field1-2" value="teststring"><br>
+<input type="radio" name="field2-2" value="0" checked> 0
+<input type="radio" name="field3-2" value="1"> 1<br>
+<input type="checkbox" name="field4-2" value="1" checked> 1
+<input type="checkbox" name="field5-2" value="2"> 2
+<input type="checkbox" name="field6-2" value="3" checked> 3
+<select name="field7-2">
+<option value="1">1</option>
+<option value="2" selected>2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+</select>
+<input name="field8-2" value="8">
+<input name="field9-2" value="9">
+<input type="image" name="field10-2">
+<label name="field11-2"></label>
+<input name="field12-2">
+<input type="button" name="field13-2" value="button">
+<input type="hidden" name="field14-2" value="14">
+</tr>
+</table>
+</form>
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ document.getElementsByName('submit_frame')[0].onload = function() {
+ is(frames['submit_frame'].location.href, "http://mochi.test:8888/blah?field1-2=teststring&field2-2=0&field4-2=1&field6-2=3&field7-2=2&field8-2=8&field9-2=9&field12-2=&field1=teststring&field2=0&field4=1&field6=3&field7=2&field8=8&field9=9&field12=&field14=14&field14-2=14", "Submit string was correct.");
+ SimpleTest.finish();
+ };
+
+ document.forms[0].submit();
+ });
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug359657.html b/dom/html/test/test_bug359657.html
new file mode 100644
index 000000000..f6fcb716e
--- /dev/null
+++ b/dom/html/test/test_bug359657.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=359657
+-->
+<head>
+ <title>Test for Bug 359657</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=359657">Mozilla Bug 359657</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+/** Test for Bug 359657 **/
+function runTest() {
+ var span = document.createElement("span");
+ $("test").insertBefore(span, $("test").firstChild);
+ ok(true, "Reachability", "We should get here without crashing");
+ is($("test").firstChild, span, "First child is correct");
+ SimpleTest.finish();
+}
+</script>
+<div>
+ <iframe src=""></iframe>
+ <!-- Important: This test needs to run async at this point. The actual test
+ is not crashing while running this test! -->
+ <script type="text/javascript" src="data:text/javascript,runTest()"></script>
+</div>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug369370.html b/dom/html/test/test_bug369370.html
new file mode 100644
index 000000000..6b27b3849
--- /dev/null
+++ b/dom/html/test/test_bug369370.html
@@ -0,0 +1,151 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=369370
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Test for Bug 369370</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 type="text/javascript">
+ /*
+ * Test strategy:
+ */
+ function makeClickFor(x, y) {
+ var event = kidDoc.createEvent("mouseevent");
+ event.initMouseEvent("click",
+ true, true, kidWin, 1, // bubbles, cancelable, view, single-click
+ x, y, x, y, // screen X/Y, client X/Y
+ false, false, false, false, // no key modifiers
+ 0, null); // left click, not relatedTarget
+ return event;
+ }
+
+ function childLoaded() {
+ kidDoc = kidWin.document;
+ ok(true, "Child window loaded");
+
+ var elements = kidDoc.getElementsByTagName("img");
+ is(elements.length, 1, "looking for imagedoc img");
+ var img = elements[0];
+
+ // Need to use innerWidth/innerHeight of the window
+ // since the containing image is absolutely positioned,
+ // causing clientHeight to be zero.
+ is(kidWin.innerWidth, 400, "Checking doc width");
+ is(kidWin.innerHeight, 300, "Checking doc height");
+
+ // Image just loaded and is scaled to window size.
+ is(img.width, 400, "image width");
+ is(img.height, 300, "image height");
+ is(kidDoc.body.scrollLeft, 0, "Checking scrollLeft");
+ is(kidDoc.body.scrollTop, 0, "Checking scrollTop");
+
+ // ========== test 1 ==========
+ // Click in the upper left to zoom in
+ var event = makeClickFor(25,25);
+ img.dispatchEvent(event);
+ ok(true, "----- click 1 -----");
+
+ is(img.width, 800, "image width");
+ is(img.height, 600, "image height");
+ is(kidDoc.body.scrollLeft, 0, "Checking scrollLeft");
+ is(kidDoc.body.scrollTop, 0, "Checking scrollTop");
+
+ // ========== test 2 ==========
+ // Click there again to zoom out
+ event = makeClickFor(25,25);
+ img.dispatchEvent(event);
+ ok(true, "----- click 2 -----");
+
+ is(img.width, 400, "image width");
+ is(img.height, 300, "image height");
+ is(kidDoc.body.scrollLeft, 0, "Checking scrollLeft");
+ is(kidDoc.body.scrollTop, 0, "Checking scrollTop");
+
+ // ========== test 3 ==========
+ // Click in the lower right to zoom in
+ event = makeClickFor(350, 250);
+ img.dispatchEvent(event);
+ ok(true, "----- click 3 -----");
+
+ is(img.width, 800, "image width");
+ is(img.height, 600, "image height");
+ is(kidDoc.body.scrollLeft, 400, "Checking scrollLeft");
+ is(kidDoc.body.scrollTop, 300, "Checking scrollTop");
+
+ // ========== test 4 ==========
+ // Click there again to zoom out
+ event = makeClickFor(350, 250);
+ img.dispatchEvent(event);
+ ok(true, "----- click 4 -----");
+
+ is(img.width, 400, "image width");
+ is(img.height, 300, "image height");
+ is(kidDoc.body.scrollLeft, 0, "Checking scrollLeft");
+ is(kidDoc.body.scrollTop, 0, "Checking scrollTop");
+
+ // ========== test 5 ==========
+ // Click in the upper left to zoom in again
+ event = makeClickFor(25, 25);
+ img.dispatchEvent(event);
+ ok(true, "----- click 5 -----");
+ is(img.width, 800, "image width");
+ is(img.height, 600, "image height");
+ is(kidDoc.body.scrollLeft, 0, "Checking scrollLeft");
+ is(kidDoc.body.scrollTop, 0, "Checking scrollTop");
+ is(img.getBoundingClientRect().top, 0, "Image is in view vertically");
+
+ // ========== test 6 ==========
+ // Now try resizing the window so the image fits vertically.
+ function test6() {
+ kidWin.addEventListener("resize", function resizeListener() {
+ kidWin.removeEventListener("resize", resizeListener);
+ // Give the image document time to respond
+ SimpleTest.executeSoon(function() {
+ is(img.height, 600, "image height");
+ is(img.getBoundingClientRect().top, 25, "Image is vertically centered");
+ test7();
+ });
+ });
+
+ var decorationSize = kidWin.outerHeight - kidWin.innerHeight;
+ kidWin.resizeTo(400, 600 + 50 + decorationSize);
+ }
+
+ // ========== test 7 ==========
+ // Now try resizing the window so the image no longer fits vertically.
+ function test7() {
+ kidWin.addEventListener("resize", function resizeListener() {
+ kidWin.removeEventListener("resize", resizeListener);
+ // Give the image document time to respond
+ SimpleTest.executeSoon(function() {
+ is(img.height, 600, "image height");
+ is(img.getBoundingClientRect().top, 0, "Image is at top again");
+ kidWin.close();
+ SimpleTest.finish();
+ });
+ });
+
+ var decorationSize = kidWin.outerHeight - kidWin.innerHeight;
+ kidWin.resizeTo(400, 300 + decorationSize);
+ }
+
+ test6();
+ }
+ var kidWin;
+ var kidDoc;
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set":[["browser.enable_automatic_image_resizing", true]]}, function() {
+ kidWin = window.open("bug369370-popup.png", "bug369370", "width=400,height=300,scrollbars=no");
+ // will init onload
+ ok(kidWin, "opened child window");
+ kidWin.onload = childLoaded;
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug371375.html b/dom/html/test/test_bug371375.html
new file mode 100644
index 000000000..e0e48656c
--- /dev/null
+++ b/dom/html/test/test_bug371375.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=371375
+-->
+<head>
+ <title>Test for Bug 371375</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=371375">Mozilla Bug 371375</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ /** Test for Bug 371375 **/
+ var load1Called = false;
+ var error1Called = false;
+ var s = document.createElement('script');
+ s.type = 'text/javascript';
+ s.onload = function() { load1Called = true; };
+ s.onerror = function(event) { error1Called = true; event.stopPropagation(); };
+ s.src = 'about:cache-entry?client=image&sb=0&key=http://www.google.com';
+ document.body.appendChild(s);
+
+ var load2Called = false;
+ var error2Called = false;
+ var s2 = document.createElement('script');
+ s2.type = 'text/javascript';
+ s2.onload = function() { load2Called = true; };
+ s2.onerror = function(event) { error2Called = true; event.stopPropagation(); };
+ s2.src = 'data:text/plain, var x = 1;'
+ document.body.appendChild(s2);
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ is(load1Called, false, "Load handler should not be called");
+ is(error1Called, true, "Error handler should be called");
+ is(load2Called, true, "Load handler for valid script should be called");
+ is(error2Called, false,
+ "Error handler for valid script should not be called");
+ SimpleTest.finish();
+ });
+</script>
+</body>
+</html>
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug372098.html b/dom/html/test/test_bug372098.html
new file mode 100644
index 000000000..1f00868a7
--- /dev/null
+++ b/dom/html/test/test_bug372098.html
@@ -0,0 +1,75 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=372098
+-->
+<head>
+ <title>Test for Bug 372098</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" />
+ <base target="bug372098"></base>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=372098">Mozilla Bug 372098</a>
+ <p id="display"></p>
+ <div id="content" style="display:none;">
+ <iframe name="bug372098"></iframe>
+ <a id="a" href="bug372098-link-target.html?a" target="">link</a>
+ <link id="link" href="bug372098-link-target.html?link" target=""/>
+ <map>
+ <area id="area" shape="default" href="bug372098-link-target.html?area" target=""/>
+ </map>
+ </div>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+
+var a_passed = false;
+var link_passed = false;
+var area_passed = false;
+
+/* Start the test */
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(handle_load);
+
+function handle_load()
+{
+ sendMouseEvent({type:'click'}, 'a');
+}
+
+/* Finish the test */
+
+function finish_test()
+{
+ ok(a_passed, "The 'a' element used the correct target.");
+ ok(link_passed, "The 'link' element used the correct target.");
+ ok(area_passed, "The 'area' element used the correct target.");
+ SimpleTest.finish();
+}
+
+/* Callback function used by the linked document */
+
+function callback(tag)
+{
+ switch (tag) {
+ case 'a':
+ a_passed = true;
+ sendMouseEvent({type:'click'}, 'link');
+ return;
+ case 'link':
+ link_passed = true;
+ sendMouseEvent({type:'click'}, 'area');
+ return;
+ case 'area':
+ area_passed = true;
+ finish_test();
+ return;
+ }
+ throw new Error("Eh??? We only test the 'a', 'link' and 'area' elements.");
+}
+
+ </script>
+ </pre>
+
+</body>
+</html>
diff --git a/dom/html/test/test_bug373589.html b/dom/html/test/test_bug373589.html
new file mode 100644
index 000000000..eba0d9284
--- /dev/null
+++ b/dom/html/test/test_bug373589.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=373589
+-->
+<head>
+ <title>Test for Bug 373589</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=373589">Mozilla Bug 373589</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 373589 **/
+ var docElem = document.documentElement;
+ var body = document.body;
+ var numChildren = docElem.childNodes.length;
+ docElem.removeChild(body);
+ ok(numChildren > docElem.childNodes.length, "body was removed");
+ body.link;
+ ok(true, "didn't crash");
+ docElem.appendChild(body);
+ is(numChildren, docElem.childNodes.length, "body re-added");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug375003-1.html b/dom/html/test/test_bug375003-1.html
new file mode 100644
index 000000000..94f5aa7a8
--- /dev/null
+++ b/dom/html/test/test_bug375003-1.html
@@ -0,0 +1,156 @@
+<!DOCTYPE HTML>
+<html id="html">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=375003
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Test 1 for bug 375003</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">
+
+ html,body {
+ color:black; background-color:white; font-size:16px; padding:0; margin:0;
+ }
+
+ .s { display:block; width:20px; height:20px; background-color:lime; }
+ table { background:pink; }
+ #td5,#td6 { border:7px solid blue;}
+ </style>
+
+<script>
+var x = [ 'Left','Top','Width','Height' ];
+function test(id,s,expected) {
+ var el = document.getElementById(id);
+ for(var i = 0; i < x.length; ++i) {
+ var actual = eval('el.'+s+x[i]);
+ if (expected[i] != -1 && s+x[i]!='scrollHeight')
+ is(actual, expected[i], id+"."+s+x[i]);
+ }
+}
+function t3(id,c,o,s,pid) {
+ test(id,'client',c);
+ test(id,'offset',o);
+ test(id,'scroll',s);
+ var p = document.getElementById(id).offsetParent;
+ is(p.id, pid, id+".offsetParent");
+}
+
+function run_test() {
+ t3('span1',[0,0,20,20],[12,12,20,20],[0,0,20,20],'td1');
+ t3('td1' ,[1,1,69,44],[16,16,71,46],[0,0,69,46],'table1');
+ t3('tr1' ,[0,0,71,46],[16,16,71,46],[0,0,71,44],'table1');
+ t3('span2',[10,0,20,20],[27,12,30,20],[0,0,20,20],'td2');
+ t3('table1',[9,9,85,113],[10,10,103,131],[0,0,85,50],'body');
+ t3('div1',[10,10,-1,131],[0,0,-1,151],[0,0,-1,85],'body');
+
+ t3('span2b',[10,0,20,20],[25,-1,30,20],[0,0,20,20],'body');
+ // XXX not sure how to make reliable cross-platform tests for replaced-inline, inline
+ // t3('span2c',[10,2,18,2],[25,-1,30,6],[0,0,30,20],'body');
+ // t3('span2d',[0,0,0,0],[25,-1,10,19],[0,0,10,20],'body');
+
+ t3('span3' ,[0,0,20,20],[15,0,20,20],[0,0,20,20],'td3');
+ t3('td3' ,[0,0,35,20],[0,0,35,20],[0,0,35,20],'table3');
+ t3('tr3' ,[0,0,35,20],[0,0,35,20],[0,0,35,22],'table3');
+ t3('span4' ,[0,0,20,20],[0,0,20,20],[0,0,20,20],'td4');
+ t3('table3',[0,0,35,40],[0,0,35,40],[0,0,35,50],'div3');
+ t3('div3',[10,10,-1,40],[0,151,-1,60],[0,0,-1,70],'body');
+
+ t3('span5' ,[0,0,20,20],[1,1,20,20],[0,0,20,20],'td5');
+ t3('td5' ,[7,7,22,22],[2,2,36,36],[0,0,22,36],'table5');
+ t3('tr5' ,[0,0,36,36],[2,2,36,36],[0,0,36,22],'table5');
+ t3('span6' ,[0,0,20,20],[20,58,20,20],[0,0,20,20],'div5');
+ t3('table5',[0,0,40,78],[0,0,40,78],[0,0,40,78],'div5');
+ t3('div5',[10,10,-1,78],[0,211,-1,98],[0,0,-1,70],'body');
+
+ t3('span7' ,[0,0,20,20],[1,1,20,20],[0,0,20,20],'td7');
+ t3('td7' ,[1,1,37,22],[9,9,39,24],[0,0,37,22],'table7');
+ t3('tr7' ,[0,0,39,24],[9,9,39,24],[0,0,39,22],'table7');
+ t3('span8' ,[0,0,20,20],[26,37,20,20],[0,0,20,20],'table7');
+ t3('table7',[7,7,43,54],[10,319,57,68],[0,0,43,50],'body');
+ t3('div7',[10,10,-1,68],[0,309,-1,88],[0,0,-1,70],'body');
+
+ t3('span9' ,[0,0,20,20],[1,1,20,20],[0,0,20,20],'td9');
+ t3('td9' ,[1,1,22,22],[15,15,24,24],[0,0,22,24],'table9');
+ t3('tr9' ,[0,0,24,24],[15,15,24,24],[0,0,24,22],'table9');
+ t3('span10' ,[0,0,20,20],[17,43,20,20],[0,0,20,20],'table9');
+ t3('table9',[13,13,28,34],[10,407,54,60],[0,0,28,50],'body');
+ t3('div9',[10,10,-1,0],[0,397,-1,20],[0,0,-1,70],'body');
+
+ t3('span11' ,[0,0,20,20],[1,1,20,20],[0,0,20,20],'td11');
+ t3('td11' ,[0,0,22,22],[2,2,22,22],[0,0,22,22],'table11');
+ t3('tr11' ,[0,0,22,22],[2,2,22,22],[0,0,22,22],'table11');
+ t3('span12' ,[0,0,20,20],[28,454,20,20],[0,0,20,20],'body');
+ t3('table11',[0,0,26,30],[10,427,26,30],[0,0,26,50],'body');
+ t3('div11',[10,10,-1,30],[0,417,-1,50],[0,0,-1,70],'body');
+}
+</script>
+</head>
+<body id="body">
+
+<div id="content">
+<div id="div1" style="border:10px solid black">
+<table id="table1" cellspacing="7" cellpadding="12" border="9">
+ <tbody id="tbody1"><tr id="tr1"><td id="td1"><div class="s" id="span1"></div></td></tr></tbody>
+ <tbody id="tbody2"><tr id="tr2"><td id="td2"><div class="s" id="span2" style="margin-left:15px; border-left:10px solid blue;"></div></td></tr></tbody>
+</table>
+</div>
+
+<div id="div3" style="border:10px solid black; position:relative">
+<table id="table3" cellpadding="0" cellspacing="0" border="0">
+ <tbody id="tbody3"><tr id="tr3"><td id="td3"><div class="s" id="span3" style="margin-left:15px"></div></td></tr></tbody>
+ <tbody id="tbody4"><tr id="tr4"><td id="td4"><div class="s" id="span4"></div></td></tr></tbody>
+</table>
+</div>
+
+<div id="div5" style="border:10px solid black; position:relative">
+<table id="table5">
+ <tbody id="tbody5"><tr id="tr5"><td id="td5"><div class="s" id="span5"></div></td></tr></tbody>
+ <tbody id="tbody6"><tr id="tr6"><td id="td6"><div class="s" id="span6" style="left:10px; top:10px; position:relative"></div></td></tr></tbody>
+</table>
+</div>
+
+<div id="div7" style="border:10px solid black;">
+<table id="table7" style="position:relative" border=7>
+ <tbody id="tbody7"><tr id="tr7"><td id="td7"><div class="s" id="span7"></div></td></tr></tbody>
+ <tbody id="tbody8"><tr id="tr8"><td id="td8"><div class="s" id="span8" style="position:relative; margin-left:15px"></div></td></tr></tbody>
+</table>
+</div>
+
+<div id="div9" style="border:10px solid black;">
+<table id="table9" style="position:absolute" border="13">
+ <tbody id="tbody9"><tr id="tr9"><td id="td9"><div class="s" id="span9"></div></td></tr></tbody>
+ <tbody id="tbody10"><tr id="tr10"><td id="td10"><div class="s" id="span10" style="position:absolute"></div></td></tr></tbody>
+</table>
+</div>
+
+<div id="div11" style="border:10px solid black; ">
+<table id="table11">
+ <tbody id="tbody11"><tr id="tr11"><td id="td11"><div class="s" id="span11"></div></td></tr></tbody>
+ <tbody id="tbody12"><tr id="tr12"><td id="td12"><div class="s" id="span12" style="position:absolute;margin-left:15px"></div></td></tr></tbody>
+</table>
+</div>
+
+<div style="border:10px solid black">
+<div class="s" id="span2b" style="margin-left:15px; border-left:10px solid blue;"></div></div>
+
+<div style="border:10px solid black">
+<button id="span2c" style="margin-left:15px; border-left:10px solid blue;"></button></div>
+
+<div style="border:10px solid black">
+<span id="span2d" style="margin-left:15px; border-left:10px solid blue;"></span></div>
+</div>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=375003">Mozilla Bug 375003</a>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+run_test();
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/dom/html/test/test_bug375003-2.html b/dom/html/test/test_bug375003-2.html
new file mode 100644
index 000000000..f22397a61
--- /dev/null
+++ b/dom/html/test/test_bug375003-2.html
@@ -0,0 +1,109 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=375003
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Test 2 for bug 375003</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">
+
+ html {
+ padding:0; margin:0;
+ }
+ body {
+ color:black; background-color:white; font-size:12px; padding:10px; margin:0;
+ }
+
+ #div1,#abs1,#table1 {
+ border: 20px solid lime;
+ padding: 30px;
+ width: 100px;
+ height: 60px;
+ overflow:scroll;
+ }
+ #abs1,#table2parent {
+ position:absolute;
+ left:500px;
+ }
+ #table3parent {
+ position:fixed;
+ left:300px;
+ top:100px;
+ }
+ .content {
+ display:block;
+ width:200px;
+ height:200px;
+ background:yellow;
+ border: 0px dotted black;
+ }
+</style>
+
+
+<script type="text/javascript">
+var x = [ 'Left','Top','Width','Height' ];
+function test(id,s,expected) {
+ var el = document.getElementById(id);
+ for(var i = 0; i < x.length; ++i) {
+ var actual = eval('el.'+s+x[i]);
+ if (expected[i] != -1 && s+x[i]!='scrollHeight')
+ is(actual, expected[i], id+"."+s+x[i]);
+ }
+}
+function t3(id,c,o,s,pid) {
+ test(id,'client',c);
+ test(id,'offset',o);
+ test(id,'scroll',s);
+ var p = document.getElementById(id).offsetParent;
+ is(p.id, pid, id+".offsetParent");
+}
+
+function run_test() {
+ // XXX how test clientWidth/clientHeight (the -1 below) in cross-platform manner
+ // without hard-coding the scrollbar width?
+ t3('div1',[20,20,-1,-1],[10,10,200,160],[0,0,230,20],'body');
+ t3('abs1',[20,20,-1,-1],[500,170,200,160],[0,0,230,20],'body');
+ t3('table1',[20,20,266,266],[10,170,306,306],[0,0,266,20],'body');
+ t3('table2',[0,0,206,206],[0,0,206,206],[0,0,206,20],'table2parent');
+ t3('table3',[10,10,208,208],[0,0,228,228],[0,0,208,228],'table3parent');
+ t3('table3parent',[0,0,228,228],[300,100,228,228],[0,0,228,228],'body');
+}
+</script>
+
+</head>
+<body id="body">
+<div id="content">
+<div id="div1parent">
+ <div id="div1"><span class="content">DIV</span></div>
+</div>
+
+<div id="abs1parent">
+ <div id="abs1"><span class="content">abs.pos.DIV</span></div>
+</div>
+
+<div id="table1parent">
+ <table id="table1"><tbody><tr><td id="td1"><span class="content">TABLE</span></td></tr></tbody></table>
+</div>
+
+<div id="table2parent">
+ <table id="table2"><tbody><tr><td id="td2"><span class="content">TABLE in abs</span></td></tr></tbody></table>
+</div>
+
+<div id="table3parent">
+ <table id="table3" border="10"><tbody><tr><td id="td3"><span class="content">TABLE in fixed</span></td></tr></tbody></table>
+</div>
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+run_test();
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/dom/html/test/test_bug377624.html b/dom/html/test/test_bug377624.html
new file mode 100644
index 000000000..fa0a137ee
--- /dev/null
+++ b/dom/html/test/test_bug377624.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=377624
+-->
+<head>
+ <title>Test for Bug 377624</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=377624">Mozilla Bug 377624</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 377624 **/
+
+var input = document.createElement('input');
+ok("accept" in input, "'accept' is a valid input property");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug380383.html b/dom/html/test/test_bug380383.html
new file mode 100644
index 000000000..c5d9823ae
--- /dev/null
+++ b/dom/html/test/test_bug380383.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=380383
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+ <title>Test for Bug 380383</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=380383">Mozilla Bug 380383</a>
+<p id="display">
+ <iframe id="f1" name="f1"></iframe>
+ <iframe id="f2" name="f2"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ /** Test for Bug 380383 **/
+ is($("f1").contentDocument.characterSet, "UTF-8",
+ "Unexpected charset for f1");
+
+ function runTest() {
+ is($("f2").contentDocument.characterSet, "UTF-8",
+ "Unexpected charset for f2");
+ }
+
+ addLoadEvent(runTest);
+ addLoadEvent(SimpleTest.finish);
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug383383.html b/dom/html/test/test_bug383383.html
new file mode 100644
index 000000000..922dc5b4f
--- /dev/null
+++ b/dom/html/test/test_bug383383.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=383383
+-->
+<head>
+ <title>Test for Bug 383383</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=383383">Mozilla Bug 383383</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript" for=" window " event=" onload() ">
+
+var foo = "bar";
+
+</script>
+
+<script class="testbody" type="text/javascript" for="object" event="handler">
+
+// This script should fail to run
+foo = "baz";
+
+isnot(foo, "baz", "test failed");
+
+</script>
+
+<script class="testbody" type="text/javascript">
+
+ok(foo == "bar", "test passed");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug383383_2.xhtml b/dom/html/test/test_bug383383_2.xhtml
new file mode 100644
index 000000000..4dccd381c
--- /dev/null
+++ b/dom/html/test/test_bug383383_2.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for bug 383383</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script>
+ SimpleTest.waitForExplicitFinish()
+ </script>
+ <script for="window" event="bar">
+ // This script should not run, but should not cause a parse error either.
+ ok(false, "Script was unexpectedly run")
+ </script>
+ <script>
+ ok(true, "Script was run as it should")
+ SimpleTest.finish()
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug384419.html b/dom/html/test/test_bug384419.html
new file mode 100644
index 000000000..c66a4c3ad
--- /dev/null
+++ b/dom/html/test/test_bug384419.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=384419
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Test for bug 384419</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">
+ html,body {
+ color:black; background-color:white; font-size:16px; padding:0; margin:0;
+ }
+ body { margin: 10px; }
+ table { border:15px solid black; margin-left:100px; }
+</style>
+
+
+<script type="text/javascript">
+function t3(id,expected,pid) {
+ var el = document.getElementById(id);
+ var actual = el.offsetLeft;
+ is(actual, expected, id+".offsetLeft");
+
+ var p = document.getElementById(id).offsetParent;
+ is(p.id, pid, id+".offsetParent");
+}
+
+function run_test() {
+ t3('rel384419',135,'body');
+ t3('abs384419',135,'body');
+ t3('fix384419',135,'body');
+}
+</script>
+
+</head>
+<body id="body">
+<!-- It's important for the test that the tables below are directly inside body -->
+<table cellpadding="7" cellspacing="3"><tr><td width="100"><div id="rel384419" style="position:relative;border:1px solid blue">X</div> relative</table>
+<table cellpadding="7" cellspacing="3"><tr><td width="100"><div id="abs384419" style="position:absolute;border:1px solid blue">X</div> absolute</table>
+<table cellpadding="7" cellspacing="3"><tr><td width="100"><div id="fix384419" style="position:fixed;border:1px solid blue">X</div> fixed</table>
+
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+run_test();
+</script>
+</pre>
+
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=384419">bug 384419</a>
+
+</body>
+</html>
diff --git a/dom/html/test/test_bug386496.html b/dom/html/test/test_bug386496.html
new file mode 100644
index 000000000..2d4f14632
--- /dev/null
+++ b/dom/html/test/test_bug386496.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=386496
+-->
+<head>
+ <title>Test for Bug 386496</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=386496">Mozilla Bug 386496</a>
+<p id="display"></p>
+<div id="content">
+ <iframe style='display: block;' id="testIframe"
+ src="data:text/html,<div><a id='a' href='http://a.invalid/'>Link</a></div>">
+ </iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 386496 **/
+
+var frame = document.getElementById("testIframe");
+
+function testDesignMode() {
+ var unloadRequested = false;
+
+ frame.contentDocument.designMode = "on";
+
+ frame.contentWindow.addEventListener("beforeunload", function() {
+ unloadRequested = true;
+ }, false);
+
+ synthesizeMouseAtCenter(frame.contentDocument.getElementById("a"), {},
+ frame.contentWindow);
+
+ // The click has been sent. If 'beforeunload' event has been caught when we go
+ // back from the event loop that means the link has been activated.
+ setTimeout(function() {
+ ok(!unloadRequested, "The link should not be activated in designMode");
+ SimpleTest.finish();
+ }, 0);
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(testDesignMode);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug386728.html b/dom/html/test/test_bug386728.html
new file mode 100644
index 000000000..5a10b31a8
--- /dev/null
+++ b/dom/html/test/test_bug386728.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=386728
+-->
+<head>
+ <title>Test for Bug 386728</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=386728">Mozilla Bug 386728</a>
+<p id="display"></p>
+<div id="content">
+ <div id="frameContent">
+ <div id="edit">This text is editable</div>
+ <button id="button_on" onclick="document.getElementById('edit').setAttribute('contenteditable', 'true')"></button>
+ </div>
+ <iframe id="testIframe"></iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 386728 **/
+
+var frame = document.getElementById("testIframe");
+
+function testContentEditable() {
+ frame.style.display = 'block';
+ var frameContent = frame.contentDocument.adoptNode(document.getElementById("frameContent"));
+ frame.contentDocument.body.appendChild(frameContent);
+ frame.contentDocument.getElementById("edit").contentEditable = "true";
+ frame.contentDocument.getElementById("edit").contentEditable = "false";
+ frame.contentDocument.getElementById("button_on").click();
+ is(frame.contentDocument.getElementById("edit").contentEditable, "true");
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(testContentEditable);
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug386996.html b/dom/html/test/test_bug386996.html
new file mode 100644
index 000000000..4f667281f
--- /dev/null
+++ b/dom/html/test/test_bug386996.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=386996
+-->
+<head>
+ <title>Test for Bug 386996</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=386996">Mozilla Bug 386996</a>
+<p id="display"></p>
+<div id="content">
+ <input id="input1"><input disabled><input id="input2">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 386996 **/
+
+var frame = document.getElementById("testIframe");
+
+function testContentEditable() {
+ var focusedElement;
+ document.getElementById("input1").onfocus = function() { focusedElement = this };
+ document.getElementById("input2").onfocus = function() { focusedElement = this };
+
+ document.getElementById("input1").focus();
+ synthesizeKey("VK_TAB", {});
+
+ is(focusedElement.id, "input2");
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(testContentEditable);
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug388558.html b/dom/html/test/test_bug388558.html
new file mode 100644
index 000000000..8e5258777
--- /dev/null
+++ b/dom/html/test/test_bug388558.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=388558
+-->
+<head>
+ <title>Test for Bug 388558</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=388558">Mozilla Bug 388558</a>
+<p id="display"></p>
+<div id="content">
+ <input type="text" id="input" onchange="++inputChange;">
+ <textarea id="textarea" onchange="++textareaChange;"></textarea>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 388558 **/
+var inputChange = 0;
+var textareaChange = 0;
+
+function testUserInput() {
+ var input = document.getElementById("input");
+ var textarea = SpecialPowers.wrap(document.getElementById("textarea"));
+
+ input.focus();
+ SpecialPowers.wrap(input).setUserInput("foo");
+ input.blur();
+ is(inputChange, 1, "Input element should have got one change event.");
+
+ input.focus();
+ input.value = "bar";
+ input.blur();
+ is(inputChange, 1,
+ "Change event dispatched when setting the value of the input element");
+
+ input.value = "";
+ is(inputChange, 1,
+ "Change event dispatched when setting the value of the input element (2).");
+
+ SpecialPowers.wrap(input).setUserInput("foo");
+ is(inputChange, 2,
+ "Change event dispatched when input element doesn't have focus.");
+
+ textarea.focus();
+ textarea.setUserInput("foo");
+ textarea.blur();
+ is(textareaChange, 1, "Textarea element should have got one change event.");
+
+ textarea.focus();
+ textarea.value = "bar";
+ textarea.blur();
+ is(textareaChange, 1,
+ "Change event dispatched when setting the value of the textarea element.");
+
+ textarea.value = "";
+ is(textareaChange, 1,
+ "Change event dispatched when setting the value of the textarea element (2).");
+
+ textarea.setUserInput("foo");
+ is(textareaChange, 1,
+ "Change event dispatched when textarea element doesn't have focus.");
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(testUserInput);
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug388746.html b/dom/html/test/test_bug388746.html
new file mode 100644
index 000000000..8f5e0997b
--- /dev/null
+++ b/dom/html/test/test_bug388746.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=388746
+-->
+<head>
+ <title>Test for Bug 388746</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=388746">Mozilla Bug 388746</a>
+<p id="display"></p>
+<div id="content">
+ <input>
+ <textarea></textarea>
+ <select>
+ <option>option1</option>
+ <optgroup label="optgroup">
+ <option>option2</option>
+ </optgroup>
+ </select>
+ <button>Button</button>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 388746 **/
+
+var previousEventTarget = "";
+
+function handler(evt) {
+ if (evt.eventPhase == 2) {
+ previousEventTarget = evt.target.localName.toLowerCase();
+ }
+}
+
+function testElementType(type) {
+ var el = document.getElementsByTagName(type)[0];
+ el.addEventListener("DOMAttrModified", handler, true);
+ el.setAttribute("foo", "bar");
+ ok(previousEventTarget == type,
+ type + " element should have got DOMAttrModified event.");
+}
+
+function test() {
+ testElementType("input");
+ testElementType("textarea");
+ testElementType("select");
+ testElementType("option");
+ testElementType("optgroup");
+ testElementType("button");
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(test);
+addLoadEvent(SimpleTest.finish);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug388794.html b/dom/html/test/test_bug388794.html
new file mode 100644
index 000000000..0f3cfdbd2
--- /dev/null
+++ b/dom/html/test/test_bug388794.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=388794
+-->
+<head>
+ <title>Test for Bug 388794</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"/>
+ <style>
+ input { padding: 0; margin: 0; border: none; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=388794">Mozilla Bug 388794</a>
+<p id="display">
+ <form action="data:text/html," target="test1" method="GET">
+ <input id="test1image" type="image" name="testImage">
+ </form>
+ <form action="data:text/html," target="test2" method="GET">
+ <input id="test2image" type="image">
+ </form>
+ <form action="data:text/html," target="test3" method="GET">
+ <input id="test3image" type="image" src="nnc_lockup.gif" name="testImage">
+ </form>
+ <form action="data:text/html," target="test4" method="GET">
+ <input id="test4image" type="image" src="nnc_lockup.gif">
+ </form>
+ <form action="data:text/html," target="test5" method="GET">
+ <input id="test5image" type="image" src="nnc_lockup.gif" name="testImage">
+ </form>
+ <form action="data:text/html," target="test6" method="GET">
+ <input id="test6image" type="image" src="nnc_lockup.gif">
+ </form>
+ <iframe name="test1" id="test1"></iframe>
+ <iframe name="test2" id="test2"></iframe>
+ <iframe name="test3" id="test3"></iframe>
+ <iframe name="test4" id="test4"></iframe>
+ <iframe name="test5" id="test5"></iframe>
+ <iframe name="test6" id="test6"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 388794 **/
+SimpleTest.waitForExplicitFinish();
+
+var pendingLoads = 0;
+/* Use regex due to rounding error in Fennec with C++APZ enabled */
+var hrefs = {
+ test1: /data:text\/html,\?testImage\.x=0&testImage\.y=0/,
+ test2: /data:text\/html,\?x=0&y=0/,
+ test3: /data:text\/html,\?testImage\.x=0&testImage\.y=0/,
+ test4: /data:text\/html,\?x=0&y=0/,
+ test5: /data:text\/html,\?testImage\.x=[4-6]&testImage\.y=[4-6]/,
+ test6: /data:text\/html,\?x=[4-6]&y=[4-6]/,
+};
+
+function submitForm(idNum) {
+ $("test"+idNum).setAttribute("onload", "frameLoaded(this)");
+ $("test" + idNum + "image").focus();
+ sendKey("return");
+}
+
+function submitFormMouse(idNum) {
+ $("test"+idNum).setAttribute("onload", "frameLoaded(this)");
+ // Use 4.99 instead of 5 to guard against the possibility that the
+ // image's 'top' is exactly N + 0.5 pixels from the root. In that case
+ // we'd round up the widget mouse coordinate to N + 6, which relative
+ // to the image would be 5.5, which would get rounded up to 6 when
+ // submitting the form. Instead we round the widget mouse coordinate to
+ // N + 5, which relative to the image would be 4.5 which gets rounded up
+ // to 5.
+ synthesizeMouse($("test" + idNum + "image"), 4.99, 4.99, {});
+}
+
+addLoadEvent(function() {
+ // Need the timeout so painting has a chance to be unsuppressed.
+ setTimeout(function() {
+ submitForm(++pendingLoads);
+ submitForm(++pendingLoads);
+ submitForm(++pendingLoads);
+ submitForm(++pendingLoads);
+ submitFormMouse(++pendingLoads);
+ submitFormMouse(++pendingLoads);
+ }, 0);
+});
+
+function frameLoaded(frame) {
+ ok(hrefs[frame.name].test(frame.contentWindow.location.href),
+ "Unexpected href for frame " + frame.name, "expected to match: " + hrefs[frame.name].toString() + " got: " + frame.contentWindow.location.href);
+ if (--pendingLoads == 0) {
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug389797.html b/dom/html/test/test_bug389797.html
new file mode 100644
index 000000000..884348b12
--- /dev/null
+++ b/dom/html/test/test_bug389797.html
@@ -0,0 +1,267 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=389797
+-->
+<head>
+ <title>Test for Bug 389797</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=389797">Mozilla Bug 389797</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 389797 **/
+var allTags = [];
+var classInfos = {};
+var interfaces = {};
+var interfacesNonClassinfo = {};
+
+function getClassName(tag) {
+ return "HTML" + classInfos[tag] + "Element";
+}
+
+function HTML_TAG(aTagName, aImplClass) {
+ allTags.push(aTagName);
+ classInfos[aTagName] = aImplClass;
+ interfaces[aTagName] = [ "nsIDOMEventTarget" ];
+
+ // Some interfaces don't appear in classinfo because other interfaces that
+ // inherit from them do.
+ interfacesNonClassinfo[aTagName] =
+ [ "nsIDOMNode",
+ "nsIDOMElement" ];
+
+ var interfaceName = "nsIDOM" + getClassName(aTagName);
+ if (interfaceName in SpecialPowers.Ci) { // no nsIDOMHTMLSpanElement
+ interfaces[aTagName].push(interfaceName);
+ interfacesNonClassinfo[aTagName].push("nsIDOMHTMLElement");
+ } else {
+ // Inherits directly from nsIDOMHTMLElement.
+ interfaces[aTagName].push("nsIDOMHTMLElement");
+ }
+
+ var interfaceNameNS = "nsIDOMNS" + getClassName(aTagName);
+ if (interfaceNameNS in SpecialPowers.Ci) {
+ interfaces[aTagName].push(interfaceNameNS);
+ }
+
+ if (arguments.length > 2) {
+ for (var i = 0; i < arguments[2].length; ++i) {
+ interfaces[aTagName].push(arguments[2][i]);
+ }
+ }
+
+ if (arguments.length > 3) {
+ for (i = 0; i < arguments[3].length; ++i) {
+ interfacesNonClassinfo[aTagName].push(arguments[3][i]);
+ }
+ }
+}
+
+const objectIfaces = [
+ "imgINotificationObserver", "nsIRequestObserver", "nsIStreamListener",
+ "nsIFrameLoaderOwner", "nsIObjectLoadingContent", "nsIChannelEventSink"
+ ];
+
+var objectIfaces2 = [];
+for (var iface of objectIfaces) {
+ objectIfaces2.push(iface);
+}
+objectIfaces2.push("nsIImageLoadingContent");
+
+/* List copy/pasted from nsHTMLTagList.h, with the second field modified to the
+ correct classinfo (instead of the impl class) in the following cases:
+
+ applet
+ base
+ blockquote
+ dir
+ dl
+ embed
+ menu
+ ol
+ param
+ q
+ ul
+ wbr
+ head
+ html
+ */
+
+HTML_TAG("a", "Anchor");
+HTML_TAG("abbr", "");
+HTML_TAG("acronym", "");
+HTML_TAG("address", "");
+HTML_TAG("applet", "Applet", [], objectIfaces);
+HTML_TAG("area", "Area");
+HTML_TAG("article", "");
+HTML_TAG("aside", "");
+HTML_TAG("b", "");
+HTML_TAG("base", "Base");
+HTML_TAG("bdo", "");
+HTML_TAG("bgsound", "Unknown");
+HTML_TAG("big", "");
+HTML_TAG("blockquote", "Quote");
+HTML_TAG("body", "Body");
+HTML_TAG("br", "BR");
+HTML_TAG("button", "Button");
+HTML_TAG("canvas", "Canvas");
+HTML_TAG("caption", "TableCaption");
+HTML_TAG("center", "");
+HTML_TAG("cite", "");
+HTML_TAG("code", "");
+HTML_TAG("col", "TableCol");
+HTML_TAG("colgroup", "TableCol");
+HTML_TAG("data", "Data");
+HTML_TAG("datalist", "DataList");
+HTML_TAG("dd", "");
+HTML_TAG("del", "Mod");
+HTML_TAG("dfn", "");
+HTML_TAG("dir", "Directory");
+HTML_TAG("div", "Div");
+HTML_TAG("dl", "DList");
+HTML_TAG("dt", "");
+HTML_TAG("em", "");
+HTML_TAG("embed", "Embed", [], objectIfaces);
+HTML_TAG("fieldset", "FieldSet");
+HTML_TAG("figcaption", "")
+HTML_TAG("figure", "")
+HTML_TAG("font", "Font");
+HTML_TAG("footer", "")
+HTML_TAG("form", "Form", [], [ "nsIWebProgressListener" ]);
+HTML_TAG("frame", "Frame", [ "nsIDOMMozBrowserFrame" ], [ "nsIFrameLoaderOwner" ]);
+HTML_TAG("frameset", "FrameSet");
+HTML_TAG("h1", "Heading");
+HTML_TAG("h2", "Heading");
+HTML_TAG("h3", "Heading");
+HTML_TAG("h4", "Heading");
+HTML_TAG("h5", "Heading");
+HTML_TAG("h6", "Heading");
+HTML_TAG("head", "Head");
+HTML_TAG("header", "")
+HTML_TAG("hgroup", "")
+HTML_TAG("hr", "HR");
+HTML_TAG("html", "Html");
+HTML_TAG("i", "");
+HTML_TAG("iframe", "IFrame", [ "nsIDOMMozBrowserFrame" ],
+ [ "nsIFrameLoaderOwner" ]);
+HTML_TAG("image", "");
+HTML_TAG("img", "Image", [ "nsIImageLoadingContent" ], []);
+HTML_TAG("input", "Input", [], [ "imgINotificationObserver",
+ "nsIImageLoadingContent",
+ "nsIDOMNSEditableElement" ]);
+HTML_TAG("ins", "Mod");
+HTML_TAG("kbd", "");
+HTML_TAG("keygen", "Span");
+HTML_TAG("label", "Label");
+HTML_TAG("legend", "Legend");
+HTML_TAG("li", "LI");
+HTML_TAG("link", "Link");
+HTML_TAG("listing", "Pre");
+HTML_TAG("main", "");
+HTML_TAG("map", "Map");
+HTML_TAG("mark", "");
+HTML_TAG("marquee", "Div");
+HTML_TAG("menu", "Menu");
+HTML_TAG("meta", "Meta");
+HTML_TAG("meter", "Meter");
+HTML_TAG("multicol", "Unknown");
+HTML_TAG("nav", "")
+HTML_TAG("nobr", "");
+HTML_TAG("noembed", "");
+HTML_TAG("noframes", "");
+HTML_TAG("noscript", "");
+HTML_TAG("object", "Object", [],
+ objectIfaces.concat([ "nsIImageLoadingContent" ]));
+HTML_TAG("ol", "OList");
+HTML_TAG("optgroup", "OptGroup");
+HTML_TAG("option", "Option");
+HTML_TAG("p", "Paragraph");
+HTML_TAG("param", "Param");
+HTML_TAG("plaintext", "");
+HTML_TAG("pre", "Pre");
+HTML_TAG("q", "Quote");
+HTML_TAG("rb", "");
+HTML_TAG("rp", "");
+HTML_TAG("rt", "");
+HTML_TAG("rtc", "");
+HTML_TAG("ruby", "");
+HTML_TAG("s", "");
+HTML_TAG("samp", "");
+HTML_TAG("script", "Script", [ "nsIScriptLoaderObserver" ], []);
+HTML_TAG("section", "")
+HTML_TAG("select", "Select", ["nsIDOMHTMLSelectElement"]);
+HTML_TAG("small", "");
+HTML_TAG("span", "Span");
+HTML_TAG("strike", "");
+HTML_TAG("strong", "");
+HTML_TAG("style", "Style");
+HTML_TAG("sub", "");
+HTML_TAG("sup", "");
+HTML_TAG("table", "Table");
+HTML_TAG("tbody", "TableSection");
+HTML_TAG("td", "TableCell");
+HTML_TAG("textarea", "TextArea", [], [ "nsIDOMNSEditableElement" ]);
+HTML_TAG("tfoot", "TableSection");
+HTML_TAG("th", "TableCell");
+HTML_TAG("thead", "TableSection");
+HTML_TAG("template", "Template");
+HTML_TAG("time", "Time");
+HTML_TAG("title", "Title");
+HTML_TAG("tr", "TableRow");
+HTML_TAG("tt", "");
+HTML_TAG("u", "");
+HTML_TAG("ul", "UList");
+HTML_TAG("var", "");
+HTML_TAG("wbr", "");
+HTML_TAG("xmp", "Pre");
+
+function tagName(aTag) {
+ return "<" + aTag + ">";
+}
+
+for (var tag of allTags) {
+ var node = document.createElement(tag);
+
+ // Have to use the proto's toString(), since HTMLAnchorElement and company
+ // override toString().
+ var nodeString = HTMLElement.prototype.toString.apply(node);
+
+ // Debug builds have extra info, so chop off after "Element" if it's followed
+ // by ' ' or ']'
+ nodeString = nodeString.replace(/Element[\] ].*/, "Element");
+
+ var classInfoString = getClassName(tag);
+ is(nodeString, "[object " + classInfoString,
+ "Unexpected classname for " + tagName(tag));
+ is(node instanceof window[classInfoString], true,
+ tagName(tag) + " not an instance of " + classInfos[tag]);
+
+ if (classInfoString != 'HTMLUnknownElement') {
+ is(node instanceof HTMLUnknownElement, false,
+ tagName(tag) + " is an instance of HTMLUnknownElement");
+ } else {
+ is(node instanceof HTMLUnknownElement, true,
+ tagName(tag) + " is an instance of HTMLUnknownElement");
+ }
+
+ // Check that each node QIs to all the things we expect it to QI to
+ for (var iface of interfaces[tag].concat(interfacesNonClassinfo[tag])) {
+ is(iface in SpecialPowers.Ci, true,
+ iface + " not in Components.interfaces");
+ is(node instanceof SpecialPowers.Ci[iface], true,
+ tagName(tag) + " does not QI to " + iface);
+ }
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug390975.html b/dom/html/test/test_bug390975.html
new file mode 100644
index 000000000..9034d4e32
--- /dev/null
+++ b/dom/html/test/test_bug390975.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=390975
+-->
+<head>
+ <title>Test for Bug 390975</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=390975">Mozilla Bug 390975</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <table id="table1">
+ <form id="form1">
+ <input>
+ <input>
+ <tr><td>
+ <input>
+ <input>
+ <input>
+ </td></tr>
+ </form>
+ </table>
+
+ <table id="table2">
+ <form id="form2">
+ <input>
+ <input>
+ <tr id="row2"><td>
+ <input>
+ <input>
+ <input>
+ </td></tr>
+ </form>
+ </table>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 390975 **/
+var form = $("form1");
+is(form.elements.length, 5, "Unexpected elements length");
+
+$("table1").parentNode.removeChild($("table1"));
+is(form.elements.length, 3, "Should have lost control outside table");
+
+form.parentNode.removeChild(form);
+is(form.elements.length, 0, "Should have lost control outside form");
+
+form = $("form2");
+is(form.elements.length, 5, "Unexpected elements length");
+
+$("row2").parentNode.removeChild($("row2"));
+is(form.elements.length, 2, "Should have lost controls inside table row");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug391777.html b/dom/html/test/test_bug391777.html
new file mode 100644
index 000000000..aa01a45de
--- /dev/null
+++ b/dom/html/test/test_bug391777.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=391777
+-->
+<head>
+ <title>Test for Bug 391777</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=391777">Mozilla Bug 391777</a>
+<p id="display"></p>
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 391777 **/
+var arg = {};
+arg.testVal = "foo";
+var result = window.showModalDialog("javascript:window.returnValue = window.dialogArguments.testVal; window.close(); 'This window should close on its own.';", arg);
+ok(true, "We should get here without user interaction");
+is(result, "foo", "Unexpected result from showModalDialog");
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug391994.html b/dom/html/test/test_bug391994.html
new file mode 100644
index 000000000..f26ac560c
--- /dev/null
+++ b/dom/html/test/test_bug391994.html
@@ -0,0 +1,184 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=391994
+-->
+<head>
+ <title>Test for Bug 391994</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=391994">Mozilla Bug 391994</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 391994 **/
+var testNumber = 0;
+
+function assertSelected(aOption, aExpectDefaultSelected, aExpectSelected) {
+ ++testNumber;
+ is(aOption.defaultSelected, aExpectDefaultSelected,
+ "Asserting default-selected state for option " + testNumber);
+ is(aOption.selected, aExpectSelected,
+ "Asserting selected state for option " + testNumber);
+}
+
+function assertSame(aSel1, aSel2Str, aTestNumber) {
+ var div = document.createElement("div");
+ div.innerHTML = aSel2Str;
+ sel2 = div.firstChild;
+ is(aSel1.options.length, sel2.options.length,
+ "Length should be same in select test " + aTestNumber);
+ is(aSel1.selectedIndex, sel2.selectedIndex,
+ "Selected index should be same in select test " + aTestNumber);
+ for (var i = 0; i < aSel1.options.length; ++i) {
+ is(aSel1.options[i].selected, sel2.options[i].selected,
+ "Options[" + i + "].selected should be the same in select test " +
+ aTestNumber);
+ is(aSel1.options[i].defaultSelected, sel2.options[i].defaultSelected,
+ "Options[" + i +
+ "].defaultSelected should be the same in select test " +
+ aTestNumber);
+ }
+}
+
+// Creation methods
+var opt = document.createElement("option");
+assertSelected(opt, false, false);
+
+opt = new Option();
+assertSelected(opt, false, false);
+
+// Setting of defaultSelected
+opt = new Option();
+opt.setAttribute("selected", "selected");
+assertSelected(opt, true, true);
+
+opt = new Option();
+opt.defaultSelected = true;
+assertSelected(opt, true, true);
+is(opt.hasAttribute("selected"), true, "Attribute should be set");
+is(opt.getAttribute("selected"), "",
+ "Attribute should be set to empty string");
+
+// Setting of selected
+opt = new Option();
+opt.selected = false;
+assertSelected(opt, false, false);
+
+opt = new Option();
+opt.selected = true;
+assertSelected(opt, false, true);
+
+// Interaction of selected and defaultSelected
+opt = new Option();
+opt.selected;
+opt.setAttribute("selected", "selected");
+assertSelected(opt, true, true);
+
+opt = new Option();
+opt.selected = false;
+opt.setAttribute("selected", "selected");
+assertSelected(opt, true, false);
+
+opt = new Option();
+opt.setAttribute("selected", "selected");
+opt.selected = true;
+opt.removeAttribute("selected");
+assertSelected(opt, false, true);
+
+// First test of putting things in a <select>: Adding default-selected option
+// should select it.
+var sel = document.createElement("select");
+sel.appendChild(new Option());
+is(sel.selectedIndex, 0, "First option should be selected");
+assertSelected(sel.firstChild, false, true);
+
+sel.appendChild(new Option());
+is(sel.selectedIndex, 0, "First option should still be selected");
+assertSelected(sel.firstChild, false, true);
+assertSelected(sel.firstChild.nextSibling, false, false);
+
+opt = new Option();
+opt.defaultSelected = true;
+sel.appendChild(opt);
+assertSelected(sel.firstChild, false, false);
+assertSelected(sel.firstChild.nextSibling, false, false);
+assertSelected(opt, true, true);
+is(opt, sel.firstChild.nextSibling.nextSibling, "What happened here?");
+is(sel.options[0], sel.firstChild, "Unexpected option 0");
+is(sel.options[1], sel.firstChild.nextSibling, "Unexpected option 1");
+is(sel.options[2], opt, "Unexpected option 2");
+is(sel.selectedIndex, 2, "Unexpected selectedIndex in select test 1");
+
+assertSame(sel, "<select><option><option><option selected></select>", 1);
+
+// Second test of putting things in a <select>: Adding two default-selected
+// options should select the second one.
+sel = document.createElement("select");
+sel.appendChild(new Option());
+sel.appendChild(new Option());
+opt = new Option();
+opt.defaultSelected = true;
+sel.appendChild(opt);
+opt = new Option();
+opt.defaultSelected = true;
+sel.appendChild(opt);
+assertSelected(sel.options[0], false, false);
+assertSelected(sel.options[1], false, false);
+assertSelected(sel.options[2], true, false);
+assertSelected(sel.options[3], true, true);
+is(sel.selectedIndex, 3, "Unexpected selectedIndex in select test 2");
+
+assertSame(sel,
+ "<select><option><option><option selected><option selected></select>", 2);
+
+// Third test of putting things in a <select>: adding a selected option earlier
+// than another selected option should make the new option selected.
+sel = document.createElement("select");
+sel.appendChild(new Option());
+sel.appendChild(new Option());
+opt = new Option();
+opt.defaultSelected = true;
+sel.appendChild(opt);
+opt = new Option();
+opt.defaultSelected = true;
+sel.options[0] = opt;
+assertSelected(sel.options[0], true, true);
+assertSelected(sel.options[1], false, false);
+assertSelected(sel.options[2], true, false);
+is(sel.selectedIndex, 0, "Unexpected selectedIndex in select test 3");
+
+// Fourth test of putting things in a <select>: Just like second test, but with
+// a <select multiple>
+sel = document.createElement("select");
+sel.multiple = true;
+sel.appendChild(new Option());
+sel.appendChild(new Option());
+opt = new Option();
+opt.defaultSelected = true;
+sel.appendChild(opt);
+opt = new Option();
+opt.defaultSelected = true;
+sel.appendChild(opt);
+assertSelected(sel.options[0], false, false);
+assertSelected(sel.options[1], false, false);
+assertSelected(sel.options[2], true, true);
+assertSelected(sel.options[3], true, true);
+is(sel.selectedIndex, 2, "Unexpected selectedIndex in select test 4");
+
+assertSame(sel,
+ "<select multiple><option><option>" +
+ "<option selected><option selected></select>",
+ 4);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug392567.html b/dom/html/test/test_bug392567.html
new file mode 100644
index 000000000..f58c69e9c
--- /dev/null
+++ b/dom/html/test/test_bug392567.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=392567
+-->
+<head>
+ <title>Test for Bug 392567</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=392567">Mozilla Bug 392567</a>
+<p id="display"><iframe name="testFrame" id="testFrame" style="visibility: hidden;"></iframe></p>
+<div id="content" style="display: none">
+ <form name="testForm" target="testFrame">
+ <input type="text" name="key" />
+ </form>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 392567 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function runTests()
+{
+ if (window.location.search.match(/\?key=value/)) {
+ return;
+ }
+
+ var dataUrl = "http://mochi.test:8888/tests/dom/html/test/bug392567.jar";
+ var jarUrl = "jar:" + dataUrl + "!/index.html";
+ var httpUrl = location.href.replace(/\.html.*/, "_404");
+ var previousDir = location.href.replace(/test\/[^\/]*$/, "");
+ var documentURL = location.href.replace(/\.html.*/, "\.html");
+
+ var form = document.forms.testForm;
+ var frame = frames.testFrame;
+ document.getElementById("testFrame").onload = processTestResult;
+
+ // List of tests to run, each test consists of form action URL and expected result URL
+ var tests = [
+ [jarUrl, jarUrl + "?$PARAMS"],
+ [jarUrl + "?jarTest1=jarTest2", jarUrl + "?$PARAMS"],
+ [jarUrl + "?jarTest3=jarTest4#jarTest5", jarUrl + "?$PARAMS#jarTest5"],
+ ["data:text/html,<html></html>", "data:text/html,<html></html>?$PARAMS"],
+ ["data:text/html,<html>How%20about%20this?</html>", "data:text/html,<html>How%20about%20this?$PARAMS"],
+ [httpUrl, httpUrl + "?$PARAMS"],
+ [httpUrl + "?httpTest1=httpTest2", httpUrl + "?$PARAMS"],
+ [httpUrl + "?httpTest3=httpTest4#httpTest5", httpUrl + "?$PARAMS#httpTest5"],
+ ["", documentURL + "?$PARAMS"],
+ [" ", documentURL + "?$PARAMS"],
+ ["../", previousDir + "?$PARAMS"],
+ ];
+
+ var currentTest = -1;
+
+ runNextTest();
+
+ function runNextTest() {
+ currentTest++;
+ if (currentTest >= tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ form.setAttribute("action", tests[currentTest][0]);
+ form.key.value = "value" + currentTest;
+ form.submit();
+ }
+
+ function processTestResult() {
+ var expected = tests[currentTest][1].replace(/\$PARAMS/, "key=value" + currentTest);
+ is(frame.location.href, expected, "Submitting to " + tests[currentTest][0]);
+
+ setTimeout(runNextTest, 0);
+ }
+}
+
+addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({"set": [["network.jar.block-remote-files", false]]}, runTests);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug394700.html b/dom/html/test/test_bug394700.html
new file mode 100644
index 000000000..a2dd92129
--- /dev/null
+++ b/dom/html/test/test_bug394700.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=394700
+-->
+<head>
+ <title>Test for Bug 394700</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=394700">Mozilla Bug 394700</a>
+<p id="display"></p>
+<div id="content">
+ <select><option id="A">A</option><option id="B">B</option></select>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 394700 **/
+
+function remove(q1) { q1.parentNode.removeChild(q1); }
+
+function testSelectedIndex()
+{
+ document.addEventListener("DOMNodeRemoved", foo, false);
+ remove(document.getElementById("B"));
+ document.removeEventListener("DOMNodeRemoved", foo, false);
+
+ function foo()
+ {
+ document.removeEventListener("DOMNodeRemoved", foo, false);
+ remove(document.getElementById("A"));
+ }
+ var selectElement = document.getElementsByTagName("select")[0];
+ ok(selectElement.selectedIndex == -1, "Wrong selected index!");
+ ok(selectElement.length == 0, "Select shouldn't have any options!")
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(testSelectedIndex);
+addLoadEvent(SimpleTest.finish);
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug395107.html b/dom/html/test/test_bug395107.html
new file mode 100644
index 000000000..22e5e110d
--- /dev/null
+++ b/dom/html/test/test_bug395107.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=395107
+-->
+<head>
+ <title>Test for Bug 395107</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=395107">Mozilla Bug 395107</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 395107 **/
+var testNumber = 0;
+
+function assertSelected(aOption, aExpectDefaultSelected, aExpectSelected) {
+ ++testNumber;
+ is(aOption.defaultSelected, aExpectDefaultSelected,
+ "Asserting default-selected state for option " + testNumber);
+ is(aOption.selected, aExpectSelected,
+ "Asserting selected state for option " + testNumber);
+}
+
+function assertSame(aSel1, aSel2Str, aTestNumber) {
+ var div = document.createElement("div");
+ div.innerHTML = aSel2Str;
+ sel2 = div.firstChild;
+ is(aSel1.options.length, sel2.options.length,
+ "Length should be same in select test " + aTestNumber);
+ is(aSel1.selectedIndex, sel2.selectedIndex,
+ "Selected index should be same in select test " + aTestNumber);
+ for (var i = 0; i < aSel1.options.length; ++i) {
+ is(aSel1.options[i].selected, sel2.options[i].selected,
+ "Options[" + i + "].selected should be the same in select test " +
+ aTestNumber);
+ is(aSel1.options[i].defaultSelected, sel2.options[i].defaultSelected,
+ "Options[" + i +
+ "].defaultSelected should be the same in select test " +
+ aTestNumber);
+ }
+}
+
+// In a single-select, setting an option selected should deselect an
+// existing selected option.
+var sel = document.createElement("select");
+sel.appendChild(new Option());
+is(sel.selectedIndex, 0, "First option should be selected");
+assertSelected(sel.firstChild, false, true);
+sel.appendChild(new Option());
+is(sel.selectedIndex, 0, "First option should still be selected");
+assertSelected(sel.firstChild, false, true);
+assertSelected(sel.firstChild.nextSibling, false, false);
+
+opt = new Option();
+sel.appendChild(opt);
+opt.defaultSelected = true;
+assertSelected(sel.firstChild, false, false);
+assertSelected(sel.firstChild.nextSibling, false, false);
+assertSelected(opt, true, true);
+is(opt, sel.firstChild.nextSibling.nextSibling, "What happened here?");
+is(sel.options[0], sel.firstChild, "Unexpected option 0");
+is(sel.options[1], sel.firstChild.nextSibling, "Unexpected option 1");
+is(sel.options[2], opt, "Unexpected option 2");
+is(sel.selectedIndex, 2, "Unexpected selectedIndex in select test 1");
+
+assertSame(sel, "<select><option><option><option selected></select>", 1);
+
+// Same, but with the option that gets set selected earlier in the select
+sel = document.createElement("select");
+sel.appendChild(new Option());
+sel.appendChild(new Option());
+opt = new Option();
+opt.defaultSelected = true;
+sel.appendChild(opt);
+opt = new Option();
+sel.options[0] = opt;
+opt.defaultSelected = true;
+assertSelected(sel.options[0], true, true);
+assertSelected(sel.options[1], false, false);
+assertSelected(sel.options[2], true, false);
+is(sel.selectedIndex, 0, "Unexpected selectedIndex in select test 2");
+
+// And now try unselecting options
+sel = document.createElement("select");
+sel.appendChild(new Option());
+opt = new Option();
+opt.defaultSelected = true;
+sel.appendChild(opt);
+sel.appendChild(new Option());
+opt.defaultSelected = false;
+
+assertSelected(sel.options[0], false, true);
+assertSelected(sel.options[1], false, false);
+assertSelected(sel.options[2], false, false);
+is(sel.selectedIndex, 0, "Unexpected selectedIndex in select test 2");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug401160.xhtml b/dom/html/test/test_bug401160.xhtml
new file mode 100644
index 000000000..b99341fe3
--- /dev/null
+++ b/dom/html/test/test_bug401160.xhtml
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=401160
+-->
+<head>
+ <title>Test for Bug 401160</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=401160">Mozilla Bug 401160</a>
+<label id="label" contenteditable="true"><legend></legend><div></div></label>
+
+<pre id="test">
+<script type="text/javascript">
+
+function do_test() {
+ document.getElementById('label').focus();
+ ok(true, "This is crash test - the test succeeded if we reach this line")
+}
+
+do_test();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug402680.html b/dom/html/test/test_bug402680.html
new file mode 100644
index 000000000..191d2173d
--- /dev/null
+++ b/dom/html/test/test_bug402680.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=402680
+-->
+<head>
+ <title>Test for Bug 402680</title>
+ <script>
+ var activeElementIsNull = (document.activeElement == null);
+ </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=402680">Mozilla Bug 402680</a>
+<p id="display"></p>
+<div id="content">
+ <input type="text">
+ <textarea></textarea>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 402680 **/
+
+ok(activeElementIsNull,
+ "Before document has body, active element should be null");
+
+function testActiveElement() {
+ ok(document.body == document.activeElement,
+ "After page load body element should be the active element!");
+ var input = document.getElementsByTagName("input")[0];
+ input.focus();
+ ok(document.activeElement == input,
+ "Input element isn't the active element!");
+ var textarea = document.getElementsByTagName("textarea")[0];
+ textarea.focus();
+ ok(document.activeElement == textarea,
+ "Textarea element isn't the active element!");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(testActiveElement);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug403868.html b/dom/html/test/test_bug403868.html
new file mode 100644
index 000000000..efcb5bdca
--- /dev/null
+++ b/dom/html/test/test_bug403868.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+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">
+
+/** Test for Bug 403868 **/
+function createSpan(id, insertionPoint) {
+ var s = document.createElement("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/html/test/test_bug403868.xhtml b/dom/html/test/test_bug403868.xhtml
new file mode 100644
index 000000000..62fce0669
--- /dev/null
+++ b/dom/html/test/test_bug403868.xhtml
@@ -0,0 +1,86 @@
+<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/html/test/test_bug405242.html b/dom/html/test/test_bug405242.html
new file mode 100644
index 000000000..9ca3c1156
--- /dev/null
+++ b/dom/html/test/test_bug405242.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=405242
+-->
+<head>
+ <title>Test for Bug 405252</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=405242">Mozilla Bug 405242</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 405242 **/
+var sel = document.createElement("select");
+sel.appendChild(new Option());
+sel.appendChild(new Option());
+sel.appendChild(new Option());
+opt = new Option();
+opt.value = 10;
+sel.appendChild(opt);
+sel.options.remove(0);
+sel.options.remove(1000);
+sel.options.remove(-1);
+is(sel.length, 1, "Unexpected option collection length");
+is(sel[0].value, "10", "Unexpected remained option");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug406596.html b/dom/html/test/test_bug406596.html
new file mode 100644
index 000000000..adc638052
--- /dev/null
+++ b/dom/html/test/test_bug406596.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=406596
+-->
+<head>
+ <title>Test for Bug 406596</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=406596">Mozilla Bug 406596</a>
+<div id="content">
+ <div id="edit" contenteditable="true">This text is editable, you can change its content <a href="#" id="a" tabindex="0">ABCDEFGHIJKLMNOPQRSTUV</a> <input type="submit" value="abcd" id="b"></input> <img src="foo.png" id="c"></div>
+ <div tabindex="0">This text is not editable but is focusable</div>
+ <div tabindex="0">This text is not editable but is focusable</div>
+ <a href="#" id="d" contenteditable="true">ABCDEFGHIJKLMNOPQRSTUV</a>
+ <div tabindex="0">This text is not editable but is focusable</div>
+ <div tabindex="0">This text is not editable but is focusable</div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 406596 **/
+
+function testTabbing(click, focus, selectionOffset) {
+ var wu = SpecialPowers.getDOMWindowUtils(window);
+
+ var elem = document.getElementById(click);
+ var rect = elem.getBoundingClientRect();
+ var selection = window.getSelection();
+
+ var x = (rect.left + rect.right) / 4;
+ var y = (rect.top + rect.bottom) / 2;
+ wu.sendMouseEvent("mousedown", x, y, 0, 1, 0);
+ wu.sendMouseEvent("mousemove", x + selectionOffset, y, 0, 1, 0);
+ wu.sendMouseEvent("mouseup", x + selectionOffset, y, 0, 1, 0);
+ if (selectionOffset) {
+ is(selection.rangeCount, 1, "there should be one range in the selection");
+ var range = selection.getRangeAt(0);
+ }
+ var focusedElement = document.activeElement;
+ is(focusedElement, document.getElementById(focus),
+ "clicking should move focus to the contentEditable node");
+ synthesizeKey("VK_TAB", {});
+ synthesizeKey("VK_TAB", {});
+ synthesizeKey("VK_TAB", { shiftKey: true });
+ synthesizeKey("VK_TAB", { shiftKey: true });
+ is(document.activeElement, focusedElement,
+ "tab/shift-tab should move focus back to the contentEditable node");
+ if (selectionOffset) {
+ is(selection.rangeCount, 1,
+ "there should still be one range in the selection");
+ var newRange = selection.getRangeAt(0);
+ is(newRange.compareBoundaryPoints(Range.START_TO_START, range), 0,
+ "the selection should be the same as before the tabbing");
+ is(newRange.compareBoundaryPoints(Range.END_TO_END, range), 0,
+ "the selection should be the same as before the tabbing");
+ }
+}
+
+function test() {
+ window.getSelection().removeAllRanges();
+ testTabbing("edit", "edit", 0);
+ testTabbing("a", "edit", 0);
+ testTabbing("d", "d", 0);
+ testTabbing("edit", "edit", 10);
+ testTabbing("a", "edit", 10);
+ testTabbing("d", "d", 10);
+
+ SimpleTest.finish();
+}
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ setTimeout(test, 0);
+};
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug417760.html b/dom/html/test/test_bug417760.html
new file mode 100644
index 000000000..7a16914a0
--- /dev/null
+++ b/dom/html/test/test_bug417760.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=417760
+-->
+<head>
+ <title>cannot focus() img with tabindex="-1"</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ img {
+ border: 5px solid white;
+ }
+ img:focus {
+ border: 5px solid black;
+ }
+ </style>
+
+
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="text/javascript">
+ function checkFocus(aExpected, aTabIndex)
+ {
+ elemCurr = document.activeElement.getAttribute("id");
+ is(elemCurr, aExpected, "Element with tabIndex " + aTabIndex
+ + " did not receive focus!");
+ }
+
+ function doTest()
+ {
+ // First, test img with tabindex = 0
+ document.getElementById("img-tabindex-0").focus();
+ checkFocus("img-tabindex-0", 0);
+
+ // now test the img with tabindex = -1
+ document.getElementById("img-tabindex-minus-1").focus();
+ checkFocus("img-tabindex-minus-1", -1);
+
+ // now test the img without tabindex, should NOT receive focus!
+ document.getElementById("img-no-tabindex").focus();
+ checkFocus("img-tabindex-minus-1", null);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=417760">Mozilla Bug 417760</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <br>img tabindex="0":
+ <img id="img-tabindex-0"
+ src="file_bug417760.png"
+ alt="MoCo logo" tabindex="0"/>
+ <br>img tabindex="-1":
+ <img id="img-tabindex-minus-1"
+ src="file_bug417760.png"
+ alt="MoCo logo" tabindex="-1"/>
+ <br>img without tabindex:
+ <img id="img-no-tabindex"
+ src="file_bug417760.png"
+ alt="MoCo logo"/>
+</body>
+</html>
diff --git a/dom/html/test/test_bug421640.html b/dom/html/test/test_bug421640.html
new file mode 100644
index 000000000..59bac6a92
--- /dev/null
+++ b/dom/html/test/test_bug421640.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=421640
+-->
+<head>
+ <title>Test for Bug 421640</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=421640">Mozilla Bug 421640</a>
+<div id="content">
+ <div id="edit" contenteditable="true">This text is editable</div>
+ <div><button id="button">Test</button></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 421640 **/
+
+function test(click, focus, nextFocus) {
+ var wu = SpecialPowers.getDOMWindowUtils(window);
+
+ var selection = window.getSelection();
+ var edit = document.getElementById("edit");
+ var text = edit.firstChild;
+
+ selection.removeAllRanges();
+
+ var rect = edit.getBoundingClientRect();
+ wu.sendMouseEvent("mousedown", rect.left + 1, rect.top + 1, 0, 1, 0);
+ wu.sendMouseEvent("mousemove", rect.right - 1, rect.top + 1, 0, 1, 0);
+ wu.sendMouseEvent("mouseup", rect.right - 1, rect.top + 1, 0, 1, 0);
+
+ is(selection.anchorNode, text, "");
+
+ rect = document.getElementById("button").getBoundingClientRect();
+ wu.sendMouseEvent("mousedown", rect.left + 10, rect.top + 1, 0, 1, 0);
+ wu.sendMouseEvent("mouseup", rect.left + 10, rect.top + 1, 0, 1, 0);
+
+ is(selection.anchorNode, text, "");
+
+ SimpleTest.finish();
+}
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ setTimeout(test, 0);
+};
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug424698.html b/dom/html/test/test_bug424698.html
new file mode 100644
index 000000000..9bd6ea5ff
--- /dev/null
+++ b/dom/html/test/test_bug424698.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=424698
+-->
+<head>
+ <title>Test for Bug 424698</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=424698">Mozilla Bug 424698</a>
+<p id="display">
+<input id="i1">
+<input id="target">
+<textarea id="i2"></textarea>
+<textarea id="target2"></textarea>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 424698 **/
+var i = $("i1");
+is(i.value, "", "Value should be empty string");
+i.defaultValue = "test";
+is(i.value, "test", "Setting defaultValue should work");
+i.defaultValue = "test2";
+is(i.value, "test2", "Setting defaultValue multiple times should work");
+
+// Now let's hide and reshow things
+i.style.display = "none";
+is(i.offsetWidth, 0, "Input didn't hide?");
+i.style.display = "";
+isnot(i.offsetWidth, 0, "Input didn't show?");
+is(i.value, "test2", "Hiding/showing should not affect value");
+i.defaultValue = "test3";
+is(i.value, "test3", "Setting defaultValue after hide/show should work");
+
+// Make sure typing works ok
+i = $("target");
+i.focus(); // Otherwise editor gets confused when we send the key events
+is(i.value, "", "Value should be empty string in second control");
+sendString("2test2");
+is(i.value, "2test2", 'We just typed the string "2test2"');
+i.defaultValue = "2test3";
+is(i.value, "2test2", "Setting defaultValue after typing should not work");
+i.style.display = "none";
+is(i.offsetWidth, 0, "Second input didn't hide?");
+i.style.display = "";
+isnot(i.offsetWidth, 0, "Second input didn't show?");
+is(i.value, "2test2", "Hiding/showing second input should not affect value");
+i.defaultValue = "2test4";
+is(i.value, "2test2", "Setting defaultValue after hide/show should not work if we typed");
+
+i = $("i2");
+is(i.value, "", "Textarea value should be empty string");
+i.defaultValue = "test";
+is(i.value, "test", "Setting textarea defaultValue should work");
+i.defaultValue = "test2";
+is(i.value, "test2", "Setting textarea defaultValue multiple times should work");
+
+// Now let's hide and reshow things
+i.style.display = "none";
+is(i.offsetWidth, 0, "Textarea didn't hide?");
+i.style.display = "";
+isnot(i.offsetWidth, 0, "Textarea didn't show?");
+is(i.value, "test2", "Hiding/showing textarea should not affect value");
+i.defaultValue = "test3";
+is(i.value, "test3", "Setting textarea defaultValue after hide/show should work");
+
+// Make sure typing works ok
+i = $("target2");
+i.focus(); // Otherwise editor gets confused when we send the key events
+is(i.value, "", "Textarea value should be empty string in second control");
+sendString("2test2");
+is(i.value, "2test2", 'We just typed the string "2test2"');
+i.defaultValue = "2test3";
+is(i.value, "2test2", "Setting textarea defaultValue after typing should not work");
+i.style.display = "none";
+is(i.offsetWidth, 0, "Second textarea didn't hide?");
+i.style.display = "";
+isnot(i.offsetWidth, 0, "Second textarea didn't show?");
+is(i.value, "2test2", "Hiding/showing second textarea should not affect value");
+i.defaultValue = "2test4";
+is(i.value, "2test2", "Setting textarea defaultValue after hide/show should not work if we typed");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug428135.xhtml b/dom/html/test/test_bug428135.xhtml
new file mode 100644
index 000000000..27ff7aeef
--- /dev/null
+++ b/dom/html/test/test_bug428135.xhtml
@@ -0,0 +1,156 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=428135
+-->
+<head>
+ <title>Test for Bug 428135</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=428135">Mozilla Bug 428135</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 428135 **/
+
+var expectedCurrentTargets = new Array();
+
+function d(el, ename) {
+ var e = document.createEvent("Events");
+ e.initEvent(ename, true, true);
+ el.dispatchEvent(e);
+}
+
+function testListener(e) {
+ e.preventDefault();
+ var expected = expectedCurrentTargets.shift();
+ ok(expected == e.currentTarget,
+ "Unexpected current target [" + e.currentTarget + "], event=" + e.type +
+ ", phase=" + e.eventPhase + ", target should have been " + expected);
+}
+
+function getAndAddListeners(elname) {
+ var el = document;
+ if (elname) {
+ el = document.getElementById(elname);
+ }
+ el.addEventListener("submit", testListener, true);
+ el.addEventListener("submit", testListener, false);
+ el.addEventListener("reset", testListener, true);
+ el.addEventListener("reset", testListener, false);
+ el.addEventListener("fooEvent", testListener, true);
+ el.addEventListener("fooEvent", testListener, false);
+ return el;
+}
+
+function testSubmitResetEvents() {
+ getAndAddListeners(null);
+ var outerForm = getAndAddListeners("outerForm");
+ var outerSubmit = getAndAddListeners("outerSubmit");
+ var outerReset = getAndAddListeners("outerReset");
+ var outerSubmitDispatcher = getAndAddListeners("outerSubmitDispatcher");
+ var outerResetDispatcher = getAndAddListeners("outerResetDispatcher");
+ var outerChild = getAndAddListeners("outerChild");
+ var innerForm = getAndAddListeners("innerForm");
+ var innerSubmit = getAndAddListeners("innerSubmit");
+ var innerReset = getAndAddListeners("innerReset");
+ var innerSubmitDispatcher = getAndAddListeners("innerSubmitDispatcher");
+ var innerResetDispatcher = getAndAddListeners("innerResetDispatcher");
+
+ expectedCurrentTargets = new Array(document, outerForm, outerForm, document);
+ outerSubmit.click();
+ ok(expectedCurrentTargets.length == 0,
+ "(1) expectedCurrentTargets isn't empty!");
+
+ expectedCurrentTargets = new Array(document, outerForm, outerForm, document);
+ outerReset.click();
+ ok(expectedCurrentTargets.length == 0,
+ "(2) expectedCurrentTargets isn't empty!");
+
+ // Because of bug 428135, submit shouldn't propagate
+ // back to outerForm and document!
+ expectedCurrentTargets =
+ new Array(document, outerForm, outerSubmitDispatcher, outerSubmitDispatcher);
+ outerSubmitDispatcher.click();
+ ok(expectedCurrentTargets.length == 0,
+ "(3) expectedCurrentTargets isn't empty!");
+
+ // Because of bug 428135, reset shouldn't propagate
+ // back to outerForm and document!
+ expectedCurrentTargets =
+ new Array(document, outerForm, outerResetDispatcher, outerResetDispatcher);
+ outerResetDispatcher.click();
+ ok(expectedCurrentTargets.length == 0,
+ "(4) expectedCurrentTargets isn't empty!");
+
+ // Because of bug 428135, submit shouldn't propagate
+ // back to outerForm and document!
+ expectedCurrentTargets =
+ new Array(document, outerForm, outerChild, innerForm, innerForm, outerChild);
+ innerSubmit.click();
+ ok(expectedCurrentTargets.length == 0,
+ "(5) expectedCurrentTargets isn't empty!");
+
+ // Because of bug 428135, reset shouldn't propagate
+ // back to outerForm and document!
+ expectedCurrentTargets =
+ new Array(document, outerForm, outerChild, innerForm, innerForm, outerChild);
+ innerReset.click();
+ ok(expectedCurrentTargets.length == 0,
+ "(6) expectedCurrentTargets isn't empty!");
+
+ // Because of bug 428135, submit shouldn't propagate
+ // back to inner/outerForm or document!
+ expectedCurrentTargets =
+ new Array(document, outerForm, outerChild, innerForm, innerSubmitDispatcher,
+ innerSubmitDispatcher);
+ innerSubmitDispatcher.click();
+ ok(expectedCurrentTargets.length == 0,
+ "(7) expectedCurrentTargets isn't empty!");
+
+ // Because of bug 428135, reset shouldn't propagate
+ // back to inner/outerForm or document!
+ expectedCurrentTargets =
+ new Array(document, outerForm, outerChild, innerForm, innerResetDispatcher,
+ innerResetDispatcher);
+ innerResetDispatcher.click();
+ ok(expectedCurrentTargets.length == 0,
+ "(8) expectedCurrentTargets isn't empty!");
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(testSubmitResetEvents);
+addLoadEvent(SimpleTest.finish);
+
+
+]]>
+</script>
+</pre>
+<form id="outerForm">
+ <input type="submit" value="outer" id="outerSubmit"/>
+ <input type="reset" value="reset outer" id="outerReset"/>
+ <input type="button" value="dispatch submit" onclick="d(this, 'submit')"
+ id="outerSubmitDispatcher"/>
+ <input type="button" value="dispatch reset" onclick="d(this, 'reset')"
+ id="outerResetDispatcher"/>
+ <div id="outerChild">
+ <form id="innerForm">
+ <input type="submit" value="inner" id="innerSubmit"/>
+ <input type="reset" value="reset inner" id="innerReset"/>
+ <input type="button" value="dispatch submit" onclick="d(this, 'submit')"
+ id="innerSubmitDispatcher"/>
+ <input type="button" value="dispatch reset" onclick="d(this, 'reset')"
+ id="innerResetDispatcher"/>
+ </form>
+ </div>
+</form>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug430351.html b/dom/html/test/test_bug430351.html
new file mode 100644
index 000000000..585620e7d
--- /dev/null
+++ b/dom/html/test/test_bug430351.html
@@ -0,0 +1,523 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=430351
+-->
+<head>
+ <title>Test for Bug 430351</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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=430351">Mozilla Bug 430351</a>
+<p id="display"></p>
+<div id="content">
+ <div id="parent"></div>
+ <div id="editableParent" contenteditable="true"></div>
+ <iframe id="frame"></iframe>
+ <map name="map"><area></map>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 430351 **/
+
+var focusableElements = [
+ "<a tabindex=\"-1\"></a>",
+ "<a tabindex=\"0\"></a>",
+ "<a tabindex=\"0\" disabled></a>",
+ "<a tabindex=\"1\"></a>",
+ "<a contenteditable=\"true\"></a>",
+
+ "<a href=\"#\"></a>",
+ "<a href=\"#\" tabindex=\"-1\"></a>",
+ "<a href=\"#\" tabindex=\"0\"></a>",
+ "<a href=\"#\" tabindex=\"0\" disabled></a>",
+ "<a href=\"#\" tabindex=\"1\"></a>",
+ "<a href=\"#\" contenteditable=\"true\"></a>",
+ "<a href=\"#\" disabled></a>",
+
+ "<button></button>",
+ "<button tabindex=\"-1\"></button>",
+ "<button tabindex=\"0\"></button>",
+ "<button tabindex=\"1\"></button>",
+ "<button contenteditable=\"true\"></button>",
+
+ "<button type=\"reset\"></button>",
+ "<button type=\"reset\" tabindex=\"-1\"></button>",
+ "<button type=\"reset\" tabindex=\"0\"></button>",
+ "<button type=\"reset\" tabindex=\"1\"></button>",
+ "<button type=\"reset\" contenteditable=\"true\"></button>",
+
+ "<button type=\"submit\"></button>",
+ "<button type=\"submit\" tabindex=\"-1\"></button>",
+ "<button type=\"submit\" tabindex=\"0\"></button>",
+ "<button type=\"submit\" tabindex=\"1\"></button>",
+ "<button type=\"submit\" contenteditable=\"true\"></button>",
+
+ "<div tabindex=\"-1\"></div>",
+ "<div tabindex=\"0\"></div>",
+ "<div tabindex=\"1\"></div>",
+ "<div contenteditable=\"true\"></div>",
+ "<div tabindex=\"0\" disabled></div>",
+
+ "<embed>",
+ "<embed tabindex=\"-1\">",
+ "<embed tabindex=\"0\">",
+ "<embed tabindex=\"0\" disabled>",
+ "<embed tabindex=\"1\">",
+ "<embed disabled>",
+ "<embed contenteditable=\"true\">",
+
+ "<iframe contenteditable=\"true\"></iframe>",
+
+ "<iframe src=\"about:blank\"></iframe>",
+ "<iframe src=\"about:blank\" disabled></iframe>",
+ "<iframe src=\"about:blank\" tabindex=\"-1\"></iframe>",
+ "<iframe src=\"about:blank\" tabindex=\"0\"></iframe>",
+ "<iframe src=\"about:blank\" tabindex=\"0\" disabled></iframe>",
+ "<iframe src=\"about:blank\" tabindex=\"1\"></iframe>",
+ "<iframe src=\"about:blank\" contenteditable=\"true\"></iframe>",
+
+ "<iframe></iframe>",
+ "<iframe tabindex=\"-1\"></iframe>",
+ "<iframe tabindex=\"0\"></iframe>",
+ "<iframe tabindex=\"0\" disabled></iframe>",
+ "<iframe tabindex=\"1\"></iframe>",
+ "<iframe disabled></iframe>",
+
+ "<img tabindex=\"-1\">",
+ "<img tabindex=\"0\">",
+ "<img tabindex=\"0\" disabled>",
+ "<img tabindex=\"1\">",
+
+ "<input>",
+ "<input tabindex=\"-1\">",
+ "<input tabindex=\"0\">",
+ "<input tabindex=\"1\">",
+ "<input contenteditable=\"true\">",
+
+ "<input type=\"button\">",
+ "<input type=\"button\" tabindex=\"-1\">",
+ "<input type=\"button\" tabindex=\"0\">",
+ "<input type=\"button\" tabindex=\"1\">",
+ "<input type=\"button\" contenteditable=\"true\">",
+
+ "<input type=\"checkbox\">",
+ "<input type=\"checkbox\" tabindex=\"-1\">",
+ "<input type=\"checkbox\" tabindex=\"0\">",
+ "<input type=\"checkbox\" tabindex=\"1\">",
+ "<input type=\"checkbox\" contenteditable=\"true\">",
+
+ "<input type=\"image\">",
+ "<input type=\"image\" tabindex=\"-1\">",
+ "<input type=\"image\" tabindex=\"0\">",
+ "<input type=\"image\" tabindex=\"1\">",
+ "<input type=\"image\" contenteditable=\"true\">",
+
+ "<input type=\"password\">",
+ "<input type=\"password\" tabindex=\"-1\">",
+ "<input type=\"password\" tabindex=\"0\">",
+ "<input type=\"password\" tabindex=\"1\">",
+ "<input type=\"password\" contenteditable=\"true\">",
+
+ "<input type=\"radio\">",
+ "<input type=\"radio\" tabindex=\"-1\">",
+ "<input type=\"radio\" tabindex=\"0\">",
+ "<input type=\"radio\" tabindex=\"1\">",
+ "<input type=\"radio\" contenteditable=\"true\">",
+ "<input type=\"radio\" checked>",
+ "<form><input type=\"radio\" name=\"foo\"></form>",
+
+ "<input type=\"reset\">",
+ "<input type=\"reset\" tabindex=\"-1\">",
+ "<input type=\"reset\" tabindex=\"0\">",
+ "<input type=\"reset\" tabindex=\"1\">",
+ "<input type=\"reset\" contenteditable=\"true\">",
+
+ "<input type=\"submit\">",
+ "<input type=\"submit\" tabindex=\"-1\">",
+ "<input type=\"submit\" tabindex=\"0\">",
+ "<input type=\"submit\" tabindex=\"1\">",
+ "<input type=\"submit\" contenteditable=\"true\">",
+
+ "<input type=\"text\">",
+ "<input type=\"text\" tabindex=\"-1\">",
+ "<input type=\"text\" tabindex=\"0\">",
+ "<input type=\"text\" tabindex=\"1\">",
+ "<input type=\"text\" contenteditable=\"true\">",
+
+ "<input type=\"number\">",
+ "<input type=\"number\" tabindex=\"-1\">",
+ "<input type=\"number\" tabindex=\"0\">",
+ "<input type=\"number\" tabindex=\"1\">",
+ "<input type=\"number\" contenteditable=\"true\">",
+
+ "<object tabindex=\"-1\"></object>",
+ "<object tabindex=\"0\"></object>",
+ "<object tabindex=\"1\"></object>",
+ "<object contenteditable=\"true\"></object>",
+
+ "<object classid=\"java:a\"></object>",
+ "<object classid=\"java:a\" tabindex=\"-1\"></object>",
+ "<object classid=\"java:a\" tabindex=\"0\"></object>",
+ "<object classid=\"java:a\" tabindex=\"0\" disabled></object>",
+ "<object classid=\"java:a\" tabindex=\"1\"></object>",
+ "<object classid=\"java:a\" disabled></object>",
+ "<object classid=\"java:a\" contenteditable=\"true\"></object>",
+
+ "<select></select>",
+ "<select tabindex=\"-1\"></select>",
+ "<select tabindex=\"0\"></select>",
+ "<select tabindex=\"1\"></select>",
+ "<select contenteditable=\"true\"></select>",
+
+ "<option tabindex='-1'></option>",
+ "<option tabindex='0'></option>",
+ "<option tabindex='1'></option>",
+ "<option contenteditable></option>",
+
+ "<optgroup tabindex='-1'></optgroup>",
+ "<optgroup tabindex='0'></optgroup>",
+ "<optgroup tabindex='1'></optgroup>",
+ "<optgroup contenteditable></optgroup>"
+];
+
+var nonFocusableElements = [
+ "<a></a>",
+ "<a disabled></a>",
+
+ "<button tabindex=\"0\" disabled></button>",
+ "<button disabled></button>",
+
+ "<button type=\"reset\" tabindex=\"0\" disabled></button>",
+ "<button type=\"reset\" disabled></button>",
+
+ "<button type=\"submit\" tabindex=\"0\" disabled></button>",
+ "<button type=\"submit\" disabled></button>",
+
+ "<div></div>",
+ "<div disabled></div>",
+
+ "<img>",
+ "<img disabled>",
+ "<img contenteditable=\"true\">",
+
+ "<img usemap=\"#map\">",
+ "<img usemap=\"#map\" tabindex=\"-1\">",
+ "<img usemap=\"#map\" tabindex=\"0\">",
+ "<img usemap=\"#map\" tabindex=\"0\" disabled>",
+ "<img usemap=\"#map\" tabindex=\"1\">",
+ "<img usemap=\"#map\" disabled>",
+ "<img usemap=\"#map\" contenteditable=\"true\">",
+
+ "<input tabindex=\"0\" disabled>",
+ "<input disabled>",
+
+ "<input type=\"button\" tabindex=\"0\" disabled>",
+ "<input type=\"button\" disabled>",
+
+ "<input type=\"checkbox\" tabindex=\"0\" disabled>",
+ "<input type=\"checkbox\" disabled>",
+
+ "<input type=\"file\" tabindex=\"0\" disabled>",
+ "<input type=\"file\" disabled>",
+
+ "<input type=\"hidden\">",
+ "<input type=\"hidden\" tabindex=\"-1\">",
+ "<input type=\"hidden\" tabindex=\"0\">",
+ "<input type=\"hidden\" tabindex=\"0\" disabled>",
+ "<input type=\"hidden\" tabindex=\"1\">",
+ "<input type=\"hidden\" disabled>",
+ "<input type=\"hidden\" contenteditable=\"true\">",
+
+ "<input type=\"image\" tabindex=\"0\" disabled>",
+ "<input type=\"image\" disabled>",
+
+ "<input type=\"password\" tabindex=\"0\" disabled>",
+ "<input type=\"password\" disabled>",
+
+ "<input type=\"radio\" tabindex=\"0\" disabled>",
+ "<input type=\"radio\" disabled>",
+
+ "<input type=\"reset\" tabindex=\"0\" disabled>",
+ "<input type=\"reset\" disabled>",
+
+ "<input type=\"submit\" tabindex=\"0\" disabled>",
+ "<input type=\"submit\" disabled>",
+
+ "<input type=\"text\" tabindex=\"0\" disabled>",
+ "<input type=\"text\" disabled>",
+
+ "<object></object>",
+
+ "<select tabindex=\"0\" disabled></select>",
+ "<select disabled></select>",
+
+ "<option></option>",
+ "<option tabindex='1' disabled></option>",
+
+ "<optgroup></optgroup>",
+ "<optgroup tabindex='1' disabled></optgroup>"
+];
+
+var focusableInContentEditable = [
+ "<button></button>",
+ "<button tabindex=\"-1\"></button>",
+ "<button tabindex=\"0\"></button>",
+ "<button tabindex=\"1\"></button>",
+ "<button contenteditable=\"true\"></button>",
+
+ "<button type=\"reset\"></button>",
+ "<button type=\"reset\" tabindex=\"-1\"></button>",
+ "<button type=\"reset\" tabindex=\"0\"></button>",
+ "<button type=\"reset\" tabindex=\"1\"></button>",
+ "<button type=\"reset\" contenteditable=\"true\"></button>",
+
+ "<button type=\"submit\"></button>",
+ "<button type=\"submit\" tabindex=\"-1\"></button>",
+ "<button type=\"submit\" tabindex=\"0\"></button>",
+ "<button type=\"submit\" tabindex=\"1\"></button>",
+ "<button type=\"submit\" contenteditable=\"true\"></button>",
+
+ "<div tabindex=\"-1\"></div>",
+ "<div tabindex=\"0\"></div>",
+ "<div tabindex=\"1\"></div>",
+ "<div tabindex=\"0\" disabled></div>",
+
+ "<embed>",
+ "<embed tabindex=\"-1\">",
+ "<embed tabindex=\"0\">",
+ "<embed tabindex=\"0\" disabled>",
+ "<embed tabindex=\"1\">",
+ "<embed disabled>",
+ "<embed contenteditable=\"true\">",
+
+ "<iframe src=\"about:blank\"></iframe>",
+ "<iframe></iframe>",
+ "<iframe src=\"about:blank\" disabled></iframe>",
+ "<iframe disabled></iframe>",
+ "<iframe src=\"about:blank\" tabindex=\"-1\"></iframe>",
+ "<iframe tabindex=\"-1\"></iframe>",
+ "<iframe src=\"about:blank\" tabindex=\"0\"></iframe>",
+ "<iframe tabindex=\"0\"></iframe>",
+ "<iframe src=\"about:blank\" tabindex=\"0\" disabled></iframe>",
+ "<iframe tabindex=\"0\" disabled></iframe>",
+ "<iframe src=\"about:blank\" tabindex=\"1\"></iframe>",
+ "<iframe tabindex=\"1\"></iframe>",
+ "<iframe src=\"about:blank\" contenteditable=\"true\"></iframe>",
+ "<iframe contenteditable=\"true\"></iframe>",
+
+ "<img tabindex=\"-1\">",
+ "<img tabindex=\"0\">",
+ "<img tabindex=\"0\" disabled>",
+ "<img tabindex=\"1\">",
+
+ "<input>",
+ "<input tabindex=\"-1\">",
+ "<input tabindex=\"0\">",
+ "<input tabindex=\"1\">",
+ "<input contenteditable=\"true\">",
+
+ "<input type=\"button\">",
+ "<input type=\"button\" tabindex=\"-1\">",
+ "<input type=\"button\" tabindex=\"0\">",
+ "<input type=\"button\" tabindex=\"1\">",
+ "<input type=\"button\" contenteditable=\"true\">",
+
+ "<input type=\"file\">",
+ "<input type=\"file\" tabindex=\"-1\">",
+ "<input type=\"file\" tabindex=\"0\">",
+ "<input type=\"file\" tabindex=\"1\">",
+ "<input type=\"file\" contenteditable=\"true\">",
+
+ "<input type=\"checkbox\">",
+ "<input type=\"checkbox\" tabindex=\"-1\">",
+ "<input type=\"checkbox\" tabindex=\"0\">",
+ "<input type=\"checkbox\" tabindex=\"1\">",
+ "<input type=\"checkbox\" contenteditable=\"true\">",
+
+ "<input type=\"image\">",
+ "<input type=\"image\" tabindex=\"-1\">",
+ "<input type=\"image\" tabindex=\"0\">",
+ "<input type=\"image\" tabindex=\"1\">",
+ "<input type=\"image\" contenteditable=\"true\">",
+
+ "<input type=\"password\">",
+ "<input type=\"password\" tabindex=\"-1\">",
+ "<input type=\"password\" tabindex=\"0\">",
+ "<input type=\"password\" tabindex=\"1\">",
+ "<input type=\"password\" contenteditable=\"true\">",
+
+ "<input type=\"radio\">",
+ "<input type=\"radio\" tabindex=\"-1\">",
+ "<input type=\"radio\" tabindex=\"0\">",
+ "<input type=\"radio\" tabindex=\"1\">",
+ "<input type=\"radio\" contenteditable=\"true\">",
+ "<input type=\"radio\" checked>",
+ "<form><input type=\"radio\" name=\"foo\"></form>",
+
+ "<input type=\"reset\">",
+ "<input type=\"reset\" tabindex=\"-1\">",
+ "<input type=\"reset\" tabindex=\"0\">",
+ "<input type=\"reset\" tabindex=\"1\">",
+ "<input type=\"reset\" contenteditable=\"true\">",
+
+ "<input type=\"submit\">",
+ "<input type=\"submit\" tabindex=\"-1\">",
+ "<input type=\"submit\" tabindex=\"0\">",
+ "<input type=\"submit\" tabindex=\"1\">",
+ "<input type=\"submit\" contenteditable=\"true\">",
+
+ "<input type=\"text\">",
+ "<input type=\"text\" tabindex=\"-1\">",
+ "<input type=\"text\" tabindex=\"0\">",
+ "<input type=\"text\" tabindex=\"1\">",
+ "<input type=\"text\" contenteditable=\"true\">",
+
+ "<input type=\"number\">",
+ "<input type=\"number\" tabindex=\"-1\">",
+ "<input type=\"number\" tabindex=\"0\">",
+ "<input type=\"number\" tabindex=\"1\">",
+ "<input type=\"number\" contenteditable=\"true\">",
+
+ "<object tabindex=\"-1\"></object>",
+ "<object tabindex=\"0\"></object>",
+ "<object tabindex=\"1\"></object>",
+
+ // Disabled doesn't work for <object>.
+ "<object tabindex=\"0\" disabled></object>",
+ "<object disabled></object>",
+
+ "<select></select>",
+ "<select tabindex=\"-1\"></select>",
+ "<select tabindex=\"0\"></select>",
+ "<select tabindex=\"1\"></select>",
+ "<select contenteditable=\"true\"></select>",
+
+ "<option tabindex='-1'></option>",
+ "<option tabindex='0'></option>",
+ "<option tabindex='1'></option>",
+
+ "<optgroup tabindex='-1'></optgroup>",
+ "<optgroup tabindex='0'></optgroup>",
+ "<optgroup tabindex='1'></optgroup>"
+];
+
+var focusableInDesignMode = [
+ "<embed>",
+ "<embed tabindex=\"-1\">",
+ "<embed tabindex=\"0\">",
+ "<embed tabindex=\"0\" disabled>",
+ "<embed tabindex=\"1\">",
+ "<embed disabled>",
+ "<embed contenteditable=\"true\">",
+
+ "<img tabindex=\"-1\">",
+ "<img tabindex=\"0\">",
+ "<img tabindex=\"0\" disabled>",
+ "<img tabindex=\"1\">",
+];
+
+// Can't currently test these, need a plugin.
+var focusableElementsTODO = [
+ "<object classid=\"java:a\"></object>",
+ "<object classid=\"java:a\" tabindex=\"-1\"></object>",
+ "<object classid=\"java:a\" tabindex=\"0\"></object>",
+ "<object classid=\"java:a\" tabindex=\"0\" disabled></object>",
+ "<object classid=\"java:a\" tabindex=\"1\"></object>",
+ "<object classid=\"java:a\" disabled></object>",
+ "<object classid=\"java:a\" contenteditable=\"true\"></object>",
+];
+
+var serializer = new XMLSerializer();
+
+function testElements(parent, tags, shouldBeFocusable)
+{
+ var focusable, errorSuffix = "";
+ if (parent.ownerDocument.designMode == "on") {
+ focusable = focusableInDesignMode;
+ errorSuffix = " in a document with designMode=on";
+ }
+ else if (parent.contentEditable == "true") {
+ focusable = focusableInContentEditable;
+ }
+
+ for (var tag of tags) {
+ parent.ownerDocument.body.focus();
+
+ if (focusableElementsTODO.indexOf(tag) > -1) {
+ todo_is(parent.ownerDocument.activeElement, parent.firstChild,
+ tag + " should be focusable" + errorSuffix);
+ continue;
+ }
+
+ parent.innerHTML = tag;
+
+ // Focus the deepest descendant.
+ var descendant = parent;
+ while ((descendant = descendant.firstChild))
+ element = descendant;
+
+ if (element.nodeName == "IFRAME" && element.hasAttribute("src"))
+ var foo = element.contentDocument;
+
+ element.focus();
+
+ var errorPrefix = serializer.serializeToString(element) + " in " +
+ serializer.serializeToString(parent);
+
+ try {
+ // Make sure activeElement doesn't point to a
+ // native anonymous element.
+ parent.ownerDocument.activeElement.localName;
+ } catch (ex) {
+ ok(false, ex + errorPrefix + errorSuffix);
+ }
+ if (focusable ? focusable.indexOf(tag) > -1 : shouldBeFocusable) {
+ is(parent.ownerDocument.activeElement, element,
+ errorPrefix + " should be focusable" + errorSuffix);
+ }
+ else {
+ isnot(parent.ownerDocument.activeElement, element,
+ errorPrefix + " should not be focusable" + errorSuffix);
+ }
+
+ parent.innerHTML = "";
+ }
+}
+
+function test()
+{
+ var parent = document.getElementById("parent");
+ var editableParent = document.getElementById("editableParent");
+
+ testElements(parent, focusableElements, true);
+ testElements(parent, nonFocusableElements, false);
+
+ testElements(editableParent, focusableElements, true);
+ testElements(editableParent, nonFocusableElements, false);
+
+ var frame = document.getElementById("frame");
+ frame.contentDocument.body.innerHTML = document.getElementById("content").innerHTML;
+ frame.contentDocument.designMode = "on";
+ parent = frame.contentDocument.getElementById("parent");
+ editableParent = frame.contentDocument.getElementById("editableParent");
+
+ testElements(parent, focusableElements, false);
+ testElements(parent, nonFocusableElements, false);
+
+ testElements(editableParent, focusableElements, false);
+ testElements(editableParent, nonFocusableElements, false);
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(test);
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug430392.html b/dom/html/test/test_bug430392.html
new file mode 100644
index 000000000..f483e454d
--- /dev/null
+++ b/dom/html/test/test_bug430392.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=430392
+-->
+<head>
+ <title>Test for Bug 430392</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=430392">Mozilla Bug 430392</a>
+<p id="display"></p>
+<div id="content">
+ <div contenteditable="true" id="edit"> <span contenteditable="false">A</span> ; <span contenteditable="false">B</span> ; <span contenteditable="false">C</span> </div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 430392 **/
+
+function test() {
+ var edit = document.getElementById("edit");
+ var html = edit.innerHTML;
+ document.getElementById("edit").focus();
+
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("VK_RIGHT", {});
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("VK_RETURN", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+
+ is(edit.innerHTML, html,
+ "adding and then deleting returns should not change text");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug435128.html b/dom/html/test/test_bug435128.html
new file mode 100644
index 000000000..9d2c85b03
--- /dev/null
+++ b/dom/html/test/test_bug435128.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435128
+-->
+<head>
+ <title>Test for Bug 435128</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=435128">Mozilla Bug 435128</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<iframe id="content" src="data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3C/head%3E%3Cbody%3E%0A%3Ciframe%20id%3D%22a%22%3E%3C/iframe%3E%0A%3Cscript%3E%0Afunction%20doe%28%29%20%7B%0Avar%20x%20%3D%20window.frames%5B0%5D.document%3B%0A%0Avar%20y%3Ddocument.getElementById%28%27a%27%29%3B%0Ay.parentNode.removeChild%28y%29%3B%0A%0Atry%20%7Bx.write%28%27t%27%29%3B%7D%20catch%28e%29%20%7B%7D%0Atry%20%7Bx.write%28%27t%27%29%3B%7D%20catch%28e%29%20%7B%7D%0Atry%20%7Bx.write%28%27t%27%29%3B%7D%20catch%28e%29%20%7B%7D%0Atry%20%7Bx.write%28%27t%27%29%3B%7D%20catch%28e%29%20%7B%7D%0Atry%20%7Bx.write%28%27t%27%29%3B%7D%20catch%28e%29%20%7B%7D%0Atry%20%7Bx.write%28%27t%27%29%3B%7D%20catch%28e%29%20%7B%7D%0Atry%20%7Bx.write%28%27t%27%29%3B%7D%20catch%28e%29%20%7B%7D%0Atry%20%7Bx.write%28%27t%27%29%3B%7D%20catch%28e%29%20%7B%7D%0Atry%20%7Bx.write%28%27t%27%29%3B%7D%20catch%28e%29%20%7B%7D%0Atry%20%7Bx.write%28%27t%27%29%3B%7D%20catch%28e%29%20%7B%7D%0A%7D%0AsetTimeout%28%27doe%28%29%27%2C20%29%3B%0A%3C/script%3E%0A%3C/body%3E%3C/html%3E" style="width: 1000px; height: 200px;"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 435128 **/
+
+SimpleTest.waitForExplicitFinish();
+
+setTimeout(finish, 60000);
+
+function doe2() {
+ document.getElementById('content').src = document.getElementById('content').src;
+}
+setInterval(doe2, 400);
+
+function finish()
+{
+ ok(true, "This is a mochikit version of a crash test. To complete is to pass.");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug441930.html b/dom/html/test/test_bug441930.html
new file mode 100644
index 000000000..2a0b18130
--- /dev/null
+++ b/dom/html/test/test_bug441930.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=441930
+-->
+<head>
+ <title>Test for Bug 441930</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=441930">Mozilla Bug 441930</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 441930: see bug441930_iframe.html **/
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<p id="display">
+ <iframe src="bug441930_iframe.html"></iframe>
+</p>
+<div id="content" style="display: none">
+</div>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug442801.html b/dom/html/test/test_bug442801.html
new file mode 100644
index 000000000..b04b23ac8
--- /dev/null
+++ b/dom/html/test/test_bug442801.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=442801
+-->
+<head>
+ <title>Test for Bug 442801</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=442801">Mozilla Bug 442801</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<div contenteditable="true">
+<p id="ce_true" contenteditable="true">contenteditable true</p>
+</div>
+
+<div contenteditable="true">
+<p id="ce_false" contenteditable="false">contenteditable false</p>
+</div>
+
+<div contenteditable="true">
+<p id="ce_empty" contenteditable="">contenteditable empty</p>
+</div>
+
+<div contenteditable="true">
+<p id="ce_inherit" contenteditable="inherit">contenteditable inherit</p>
+</div>
+
+<div contenteditable="true">
+<p id="ce_none" >contenteditable none</p>
+</div>
+
+
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 442801 **/
+
+is(window.getComputedStyle($("ce_true"), null).getPropertyValue("-moz-user-modify"),
+ "read-write",
+ "parent contenteditable is true, contenteditable is true; user-modify should be read-write");
+is(window.getComputedStyle($("ce_false"), null).getPropertyValue("-moz-user-modify"),
+ "read-only",
+ "parent contenteditable is true, contenteditable is false; user-modify should be read-only");
+is(window.getComputedStyle($("ce_empty"), null).getPropertyValue("-moz-user-modify"),
+ "read-write",
+ "parent contenteditable is true, contenteditable is empty; user-modify should be read-write");
+is(window.getComputedStyle($("ce_inherit"), null).getPropertyValue("-moz-user-modify"),
+ "read-write",
+ "parent contenteditable is true, contenteditable is inherit; user-modify should be read-write");
+is(window.getComputedStyle($("ce_none"), null).getPropertyValue("-moz-user-modify"),
+ "read-write",
+ "parent contenteditable is true, contenteditable is none; user-modify should be read-write");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug445004.html b/dom/html/test/test_bug445004.html
new file mode 100644
index 000000000..ec98f56c8
--- /dev/null
+++ b/dom/html/test/test_bug445004.html
@@ -0,0 +1,138 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=445004
+-->
+<head>
+ <title>Test for Bug 445004</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=445004">Mozilla Bug 445004</a>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 445004 **/
+is(window.location.hostname, "mochi.test", "Unexpected hostname");
+is(window.location.port, "8888", "Unexpected port; fix testcase");
+
+SimpleTest.waitForExplicitFinish();
+
+var loads = 1;
+
+function loadStarted() {
+ ++loads;
+}
+function loadEnded() {
+ --loads;
+ if (loads == 0) {
+ doTest();
+ }
+}
+
+window.onload = loadEnded;
+
+function getMessage(evt) {
+ ok(evt.data == "start" || evt.data == "end", "Must have start or end");
+ if (evt.data == "start")
+ loadStarted();
+ else
+ loadEnded();
+}
+
+window.addEventListener("message", getMessage, false);
+
+function checkURI(uri, name, type) {
+ var host = uri.match(/^http:\/\/([a-z.0-9]*)/)[1];
+ var file = uri.match(/([^\/]*).png$/)[1];
+ is(host, file, "Unexpected base URI for test " + name +
+ " when testing " + type);
+}
+
+function checkFrame(num) {
+ // Just snarf our data
+ var outer = SpecialPowers.wrap(window.frames[num]);
+ name = outer.name;
+
+ is(outer.document.baseURI,
+ "http://example.org/tests/dom/html/test/bug445004-outer.html",
+ "Unexpected base URI for " + name);
+
+ var iswrite = name.match(/write/);
+
+ var inner = outer.frames[0];
+ if (iswrite) {
+ is(inner.document.baseURI,
+ "http://example.org/tests/dom/html/test/bug445004-outer.html",
+ "Unexpected inner base URI for " + name);
+ } else {
+ is(inner.document.baseURI,
+ "http://test1.example.org/tests/dom/html/test/bug445004-inner.html",
+ "Unexpected inner base URI for " + name);
+ }
+
+ var isrel = name.match(/rel/);
+ var offsite = name.match(/offsite/);
+
+ if (!iswrite) {
+ if ((isrel && !offsite) || (!isrel && offsite)) {
+ is(inner.location.hostname, outer.location.hostname,
+ "Unexpected hostnames for " + name);
+ } else {
+ isnot(inner.location.hostname, outer.location.hostname,
+ "Unexpected hostnames for " + name);
+ }
+ }
+
+ checkURI(inner.frames[0].location.href, name, "direct location");
+ checkURI(inner.frames[1].document.getElementsByTagName("img")[0].src,
+ name, "direct write");
+ if (!iswrite) {
+ is(inner.frames[1].location.hostname, inner.location.hostname,
+ "Incorrect hostname for " + name + " direct write")
+ }
+ checkURI(inner.frames[2].location.href, name, "indirect location");
+ checkURI(inner.frames[3].document.getElementsByTagName("img")[0].src,
+ name, "indirect write");
+ if (!iswrite) {
+ is(inner.frames[3].location.hostname, outer.location.hostname,
+ "Incorrect hostname for " + name + " indirect write")
+ }
+ checkURI(inner.document.getElementsByTagName("img")[0].src,
+ name, "direct image load");
+}
+
+
+function doTest() {
+ for (var num = 0; num < 5; ++num) {
+ checkFrame(num);
+ }
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+<p id="display">
+ <iframe
+ src="http://example.org/tests/dom/html/test/bug445004-outer-rel.html"
+ name="bug445004-outer-rel.html"></iframe>
+ <iframe
+ src="http://test1.example.org/tests/dom/html/test/bug445004-outer-rel.html"
+ name="bug445004-outer-rel.html offsite"></iframe>
+ <iframe
+ src="http://example.org/tests/dom/html/test/bug445004-outer-abs.html"
+ name="bug445004-outer-abs.html"></iframe>
+ <iframe
+ src="http://test1.example.org/tests/dom/html/test/bug445004-outer-abs.html"
+ name="bug445004-outer-abs.html offsite"></iframe>
+ <iframe
+ src="http://example.org/tests/dom/html/test/bug445004-outer-write.html"
+ name="bug445004-outer-write.html"></iframe>
+</p>
+</body>
+</html>
diff --git a/dom/html/test/test_bug446483.html b/dom/html/test/test_bug446483.html
new file mode 100644
index 000000000..def6553d5
--- /dev/null
+++ b/dom/html/test/test_bug446483.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=446483
+-->
+<head>
+ <title>Test for Bug 446483</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=446483">Mozilla Bug 446483</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 446483 **/
+
+function gc() {
+ SpecialPowers.gc();
+}
+
+function runTest() {
+ document.getElementById('display').innerHTML =
+ '<iframe src="bug446483-iframe.html"><\/iframe>\n' +
+ '<iframe src="bug446483-iframe.html"><\/iframe>\n';
+
+ setInterval(gc, 1000);
+
+ setTimeout(function() {
+ document.getElementById('display').innerHTML = '';
+ ok(true, '');
+ SimpleTest.finish();
+ }, 4000);
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+addLoadEvent(runTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug448166.html b/dom/html/test/test_bug448166.html
new file mode 100644
index 000000000..ec4b3d1a4
--- /dev/null
+++ b/dom/html/test/test_bug448166.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=448166
+-->
+<head>
+ <title>Test for Bug 448166</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=448166">Mozilla Bug 448166</a>
+<p id="display">
+ <a id="test" href="http://www.moz&#xdc00;illa.org">should not be Mozilla</a>
+ <a id="control" href="http://www.mozilla.org">should not be Mozilla</a>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 448166 **/
+isnot($("test").href, "http://www.mozilla.org/",
+ "Should notice unpaired surrogate");
+is($("test").href, "http://www.xn--mozilla-2e14b.org/",
+ "Should replace unpaired surrogate with replacement char");
+is($("control").href, "http://www.mozilla.org/",
+ "Just making sure .href works");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug448564.html b/dom/html/test/test_bug448564.html
new file mode 100644
index 000000000..27d5a272f
--- /dev/null
+++ b/dom/html/test/test_bug448564.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=448564
+-->
+<head>
+ <title>Test for Bug 448564</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=448564">Mozilla Bug 448564</a>
+<p id="display">
+ <iframe src="bug448564-iframe-1.html"></iframe>
+ <iframe src="bug448564-iframe-2.html"></iframe>
+ <iframe src="bug448564-iframe-3.html"></iframe>
+</p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 448564 **/
+
+/**
+ * The three iframes are going to be loaded with some dirty constructed forms.
+ * Each of them will be submitted before the load event and a SJS will replace
+ * the frame content with the query string.
+ * Then, on the load event, our test file will check the content of each iframes
+ * and check if the query string were correctly formatted (implying that all
+ * iframes were correctly submitted.
+ */
+
+function checkQueryString(frame) {
+ var queryString = frame.document.body.textContent;
+ is(queryString.split("&").sort().join("&"),
+ "a=aval&b=bval&c=cval&d=dval",
+ "Not all form fields were properly submitted.");
+}
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ checkQueryString(frames[0]);
+ checkQueryString(frames[1]);
+ checkQueryString(frames[2]);
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug456229.html b/dom/html/test/test_bug456229.html
new file mode 100644
index 000000000..bc12523c7
--- /dev/null
+++ b/dom/html/test/test_bug456229.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=456229
+-->
+<head>
+ <title>Test for Bug 456229</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=456229">Mozilla Bug 456229</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <input id='i' type="search">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 456229 **/
+
+// More checks are done in test_bug551670.html.
+
+var i = document.getElementById('i');
+is(i.type, 'search', "Search state should be recognized");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug458037.xhtml b/dom/html/test/test_bug458037.xhtml
new file mode 100644
index 000000000..965db499c
--- /dev/null
+++ b/dom/html/test/test_bug458037.xhtml
@@ -0,0 +1,112 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=458037
+-->
+<head>
+ <title>Test for Bug 458037</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=458037">Mozilla Bug 458037</a>
+<p id="display"></p>
+<div id="content" >
+<div id="a"></div>
+<div id="b" contenteditable="true"></div>
+<div id="c" contenteditable="false"></div>
+<div id="d" contenteditable="inherit"></div>
+<div contenteditable="true">
+ <div id="e"></div>
+</div>
+<div contenteditable="false">
+ <div id="f"></div>
+</div>
+<div contenteditable="true">
+ <div id="g" contenteditable="false"></div>
+</div>
+<div contenteditable="false">
+ <div id="h" contenteditable="true"></div>
+</div>
+<div contenteditable="true">
+ <div id="i" contenteditable="inherit"></div>
+</div>
+<div contenteditable="false">
+ <div id="j" contenteditable="inherit"></div>
+</div>
+<div contenteditable="true">
+ <xul:box>
+ <div id="k"></div>
+ </xul:box>
+</div>
+<div contenteditable="false">
+ <xul:box>
+ <div id="l"></div>
+ </xul:box>
+</div>
+<div contenteditable="true">
+ <xul:box>
+ <div id="m" contenteditable="inherit"></div>
+ </xul:box>
+</div>
+<div contenteditable="false">
+ <xul:box>
+ <div id="n" contenteditable="inherit"></div>
+ </xul:box>
+</div>
+<div id="x"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 458037 **/
+
+function test(id, expected) {
+ is(document.getElementById(id).isContentEditable, expected,
+ "Element " + id + " should " + (expected ? "" : "not ") + "be editable");
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+ test("a", false);
+ test("b", true);
+ test("c", false);
+ test("d", false);
+ test("e", true);
+ test("f", false);
+ test("g", false);
+ test("h", true);
+ test("i", true);
+ test("j", false);
+ test("k", true);
+ test("l", false);
+ test("m", true);
+ test("n", false);
+
+ var d = document.getElementById("x");
+ test("x", false);
+ d.setAttribute("contenteditable", "true");
+ test("x", true);
+ d.setAttribute("contenteditable", "false");
+ test("x", false);
+ d.setAttribute("contenteditable", "inherit");
+ test("x", false);
+ d.removeAttribute("contenteditable");
+ test("x", false);
+ d.contentEditable = "true";
+ test("x", true);
+ d.contentEditable = "false";
+ test("x", false);
+ d.contentEditable = "inherit";
+ test("x", false);
+
+ // Make sure that isContentEditable is read-only
+ var origValue = d.isContentEditable;
+ d.isContentEditable = !origValue;
+ is(d.isContentEditable, origValue, "isContentEditable should be read only");
+}, false);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug460568.html b/dom/html/test/test_bug460568.html
new file mode 100644
index 000000000..14bcbbc78
--- /dev/null
+++ b/dom/html/test/test_bug460568.html
@@ -0,0 +1,147 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=460568
+-->
+<head>
+ <title>Test for Bug 460568</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=460568">Mozilla Bug 460568</a>
+<p id="display"><a href="" id="anchor">a[href]</a></p>
+<div id="editor">
+ <a href="" id="anchorInEditor">a[href] in editor</a>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 460568 **/
+
+function runTest()
+{
+ var editor = document.getElementById("editor");
+ var anchor = document.getElementById("anchor");
+ var anchorInEditor = document.getElementById("anchorInEditor");
+
+ var focused;
+ anchorInEditor.onfocus = function() { focused = true; };
+
+ function isReallyEditable()
+ {
+ editor.focus();
+ var range = document.createRange();
+ range.selectNodeContents(editor);
+ var prevStr = range.toString();
+
+ var docShell = SpecialPowers.wrap(window)
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .QueryInterface(SpecialPowers.Ci.nsIDocShell);
+ var controller =
+ docShell.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsISelectionDisplay)
+ .QueryInterface(SpecialPowers.Ci.nsISelectionController);
+ var sel = controller.getSelection(controller.SELECTION_NORMAL);
+ sel.collapse(anchorInEditor, 0);
+ synthesizeKey('a', {});
+ range.selectNodeContents(editor);
+ return prevStr != range.toString();
+ }
+
+ focused = false;
+ anchor.focus();
+ editor.setAttribute("contenteditable", "true");
+ anchorInEditor.focus();
+ is(focused, false, "focus moved to element in contenteditable=true");
+ is(isReallyEditable(), true, "cannot edit by a key event");
+
+ // for bug 502273
+ focused = false;
+ anchor.focus();
+ editor.setAttribute("dummy", "dummy");
+ editor.removeAttribute("dummy");
+ anchorInEditor.focus();
+ is(focused, false, "focus moved to element in contenteditable=true (after dummy attribute was removed)");
+ is(isReallyEditable(), true, "cannot edit by a key event");
+
+ focused = false;
+ anchor.focus();
+ editor.setAttribute("contenteditable", "false");
+ anchorInEditor.focus();
+ is(focused, true, "focus didn't move to element in contenteditable=false");
+ is(isReallyEditable(), false, "can edit by a key event");
+
+ // for bug 502273
+ focused = false;
+ anchor.focus();
+ editor.setAttribute("dummy", "dummy");
+ editor.removeAttribute("dummy");
+ anchorInEditor.focus();
+ is(focused, true, "focus moved to element in contenteditable=true (after dummy attribute was removed)");
+ is(isReallyEditable(), false, "cannot edit by a key event");
+
+ focused = false;
+ anchor.focus();
+ editor.setAttribute("contenteditable", "true");
+ anchorInEditor.focus();
+ is(focused, false, "focus moved to element in contenteditable=true");
+ is(isReallyEditable(), true, "cannot edit by a key event");
+
+ // for bug 502273
+ focused = false;
+ anchor.focus();
+ editor.setAttribute("dummy", "dummy");
+ editor.removeAttribute("dummy");
+ anchorInEditor.focus();
+ is(focused, false, "focus moved to element in contenteditable=true (after dummy attribute was removed)");
+ is(isReallyEditable(), true, "cannot edit by a key event");
+
+ focused = false;
+ anchor.focus();
+ editor.removeAttribute("contenteditable");
+ anchorInEditor.focus();
+ is(focused, true, "focus didn't move to element in contenteditable removed element");
+ is(isReallyEditable(), false, "can edit by a key event");
+
+ focused = false;
+ anchor.focus();
+ editor.contentEditable = true;
+ anchorInEditor.focus();
+ is(focused, false, "focus moved to element in contenteditable=true by property");
+ is(isReallyEditable(), true, "cannot edit by a key event");
+
+ focused = false;
+ anchor.focus();
+ editor.contentEditable = false;
+ anchorInEditor.focus();
+ is(focused, true, "focus didn't move to element in contenteditable=false by property");
+ is(isReallyEditable(), false, "can edit by a key event");
+
+ focused = false;
+ anchor.focus();
+ editor.setAttribute("contenteditable", "true");
+ anchorInEditor.focus();
+ is(focused, false, "focus moved to element in contenteditable=true");
+ is(isReallyEditable(), true, "cannot edit by a key event");
+
+ // for bug 502273
+ focused = false;
+ anchor.focus();
+ editor.setAttribute("dummy", "dummy");
+ editor.removeAttribute("dummy");
+ anchorInEditor.focus();
+ is(focused, false, "focus moved to element in contenteditable=true (after dummy attribute was removed)");
+ is(isReallyEditable(), true, "cannot edit by a key event");
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug463104.html b/dom/html/test/test_bug463104.html
new file mode 100644
index 000000000..f424cd183
--- /dev/null
+++ b/dom/html/test/test_bug463104.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Noninteger coordinates 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>
+<div id="a" style="position: fixed; left: 5.5px; top: 5.5px; width: 100px; height: 100px; background: blue"></div>
+<p style="margin-top: 110px">
+<script>
+var a = document.getElementById("a");
+isnot(a, document.elementFromPoint(5, 5), "a shouldn't be found");
+isnot(a, document.elementFromPoint(5.25, 5.25), "a shouldn't be found");
+is(a, document.elementFromPoint(5.5, 5.5), "a should be found");
+is(a, document.elementFromPoint(5.75, 5.75), "a should be found");
+is(a, document.elementFromPoint(6, 6), "a should be found");
+is(a, document.elementFromPoint(105, 105), "a should be found");
+is(a, document.elementFromPoint(105.25, 105.25), "a should be found");
+isnot(a, document.elementFromPoint(105.5, 105.5), "a shouldn't be found");
+isnot(a, document.elementFromPoint(105.75, 105.75), "a shouldn't be found");
+isnot(a, document.elementFromPoint(106, 106), "a shouldn't be found");
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug478251.html b/dom/html/test/test_bug478251.html
new file mode 100644
index 000000000..537ccf12e
--- /dev/null
+++ b/dom/html/test/test_bug478251.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=478251
+-->
+<head>
+ <title>Test for Bug 478251</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=478251">Mozilla Bug 478251</a>
+<p id="display"><iframe id="t"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 478251 **/
+var doc = $("t").contentDocument;
+doc.open();
+doc.write();
+doc.close();
+is(doc.documentElement.textContent, "", "Writing || failed");
+
+doc.open();
+doc.write(null);
+doc.close();
+is(doc.documentElement.textContent, "null", "Writing |null| failed");
+
+doc.open();
+doc.write(null, null);
+doc.close();
+is(doc.documentElement.textContent, "nullnull", "Writing |null, null| failed");
+
+doc.open();
+doc.write(undefined);
+doc.close();
+is(doc.documentElement.textContent, "undefined", "Writing |undefined| failed");
+
+doc.open();
+doc.write(undefined, undefined);
+doc.close();
+is(doc.documentElement.textContent, "undefinedundefined", "Writing |undefined, undefined| failed");
+
+doc.open();
+doc.writeln();
+doc.close();
+ok(doc.documentElement.textContent == "\n" || doc.documentElement.textContent == "", "Writing |\\n| failed");
+
+doc.open();
+doc.writeln(null);
+doc.close();
+is(doc.documentElement.textContent, "null\n", "Writing |null\\n| failed");
+
+doc.open();
+doc.writeln(null, null);
+doc.close();
+is(doc.documentElement.textContent, "nullnull\n", "Writing |null, null\\n| failed");
+
+doc.open();
+doc.writeln(undefined);
+doc.close();
+is(doc.documentElement.textContent, "undefined\n", "Writing |undefined\\n| failed");
+
+doc.open();
+doc.writeln(undefined, undefined);
+doc.close();
+is(doc.documentElement.textContent, "undefinedundefined\n", "Writing |undefined, undefined\\n| failed");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug481335.xhtml b/dom/html/test/test_bug481335.xhtml
new file mode 100644
index 000000000..f8a51df69
--- /dev/null
+++ b/dom/html/test/test_bug481335.xhtml
@@ -0,0 +1,120 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=481335
+-->
+<head>
+ <title>Test for Bug 481335</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ a { color:blue; }
+ a:visited { color:red; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=481335">Mozilla Bug 481335</a>
+<p id="display">
+ <a id="t">A link</a>
+ <iframe id="i"></iframe>
+</p>
+<p id="newparent" xml:base="http://www.example.com/"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+<![CDATA[
+
+/** Test for Bug 481335 **/
+SimpleTest.waitForExplicitFinish();
+var rand = Date.now() + "-" + Math.random();
+
+is($("t").href, "",
+ "Unexpected href before set");
+is($("t").href, "",
+ "Unexpected cached href before set");
+
+$("t").setAttribute("href", rand);
+is($("t").href,
+ window.location.href.replace(/test_bug481335.xhtml([\?].*)?/, rand),
+ "Unexpected href after set");
+is($("t").href,
+ window.location.href.replace(/test_bug481335.xhtml([\?].*)?/, rand),
+ "Unexpected cached href after set");
+const unvisitedColor = "rgb(0, 0, 255)";
+const visitedColor = "rgb(255, 0, 0)";
+
+let tests = testIterator();
+function continueTest() {
+ tests.next();
+}
+
+function checkLinkColor(aElmId, aExpectedColor, aMessage) {
+ // Because link coloring is asynchronous, we wait until we get the right
+ // result, or we will time out (resulting in a failure).
+ function getColor() {
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ return utils.getVisitedDependentComputedStyle($(aElmId), "", "color");
+ }
+ while (getColor() != aExpectedColor) {
+ setTimeout(continueTest, 0);
+ return false;
+ }
+ is(getColor(), aExpectedColor, aMessage);
+ return true;
+}
+
+function testIterator() {
+ // After first load
+ $("newparent").appendChild($("t"));
+ is($("t").href, "http://www.example.com/" + rand,
+ "Unexpected href after move");
+ is($("t").href, "http://www.example.com/" + rand,
+ "Unexpected cached href after move");
+ while (!checkLinkColor("t", unvisitedColor, "Should be unvisited now"))
+ yield undefined;
+
+ $("i").src = $("t").href;
+ yield undefined;
+
+ // After second load
+ while (!checkLinkColor("t", visitedColor, "Should be visited now"))
+ yield undefined;
+ $("t").pathname = rand;
+ while (!checkLinkColor("t", visitedColor,
+ "Should still be visited after setting pathname to its existing value")) {
+ yield undefined;
+ }
+ /* TODO uncomment this test with the landing of bug 534526. See
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=461199#c167
+ $("t").pathname += "x";
+ while (!checkLinkColor("t", unvisitedColor,
+ "Should not be visited after changing pathname")) {
+ yield undefined;
+ }
+ $("t").pathname = $("t").pathname;
+ while (!checkLinkColor("t", unvisitedColor,
+ "Should not be visited after setting unvisited pathname to existing value")) {
+ yield undefined;
+ }
+ */
+
+ $("i").src = $("t").href;
+ yield undefined;
+
+ // After third load
+ while (!checkLinkColor("t", visitedColor,
+ "Should be visited now after third load")) {
+ yield undefined;
+ }
+ SimpleTest.finish();
+ yield undefined;
+}
+
+addLoadEvent(function() {
+ $("i").onload = continueTest;
+ $("i").src = $("t").href;
+});
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug481440.html b/dom/html/test/test_bug481440.html
new file mode 100644
index 000000000..b6b454232
--- /dev/null
+++ b/dom/html/test/test_bug481440.html
@@ -0,0 +1,30 @@
+<!--Test must be in quirks mode for document.all to work-->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=481440
+-->
+<head>
+ <title>Test for Bug 481440</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=481440">Mozilla Bug 481440</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <input name="x" id="y">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 481440 **/
+// Do a bunch of getElementById calls to catch hashtables auto-going live
+for (var i = 0; i < 500; ++i) {
+ document.getElementById(i);
+}
+is(document.all["x"], document.getElementById("y"),
+ "Unexpected node");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug481647.html b/dom/html/test/test_bug481647.html
new file mode 100644
index 000000000..24928bbf7
--- /dev/null
+++ b/dom/html/test/test_bug481647.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=481647
+-->
+<head>
+ <title>Test for Bug 481647</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=481647">Mozilla Bug 481647</a>
+<p id="display">
+ <iframe src="javascript:'aaa'"></iframe>
+ <iframe src="javascript:document.write('aaa'); document.close();"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 481647 **/
+SimpleTest.waitForExplicitFinish()
+
+function testFrame(num) {
+ is(window.frames[num].document.baseURI, document.baseURI,
+ "Unexpected base URI in frame " + num);
+}
+
+addLoadEvent(function() {
+ for (var i = 0; i < 2; ++i) {
+ testFrame(i);
+ }
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug482659.html b/dom/html/test/test_bug482659.html
new file mode 100644
index 000000000..2329ae999
--- /dev/null
+++ b/dom/html/test/test_bug482659.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=482659
+-->
+<head>
+ <title>Test for Bug 482659</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=482659">Mozilla Bug 482659</a>
+<p id="display">
+ <iframe></iframe>
+ <iframe src="about:blank"></iframe>
+ <iframe></iframe>
+ <iframe src="about:blank"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 482659 **/
+SimpleTest.waitForExplicitFinish()
+
+function testFrame(num) {
+ is(window.frames[num].document.baseURI, document.baseURI,
+ "Unexpected base URI in frame " + num);
+ is(window.frames[num].document.documentURI, "about:blank",
+ "Unexpected document URI in frame " + num);
+}
+
+function appendScript(doc) {
+ var s = doc.createElement("script");
+ s.textContent = "document.write('executed'); document.close()";
+ doc.body.appendChild(s);
+}
+
+function verifyScriptRan(num) {
+ is(window.frames[num].document.documentElement.textContent, "executed",
+ "write didn't happen in frame " + num);
+}
+
+addLoadEvent(function() {
+/* document.write part of test disabled due to bug 483818
+ appendScript(window.frames[2].document);
+ appendScript(window.frames[3].document);
+
+ verifyScriptRan(2);
+ verifyScriptRan(3);
+*/
+ for (var i = 0; i < 4; ++i) {
+ testFrame(i);
+ }
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug486741.html b/dom/html/test/test_bug486741.html
new file mode 100644
index 000000000..69b1e2265
--- /dev/null
+++ b/dom/html/test/test_bug486741.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=486741
+-->
+<head>
+ <title>Test for Bug 486741</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=486741">Mozilla Bug 486741</a>
+<p id="display"><iframe id="f"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 486741 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var d = $("f").contentDocument;
+ var root = d.documentElement;
+ is(root.tagName, "HTML", "Unexpected root");
+
+ d.open();
+ isnot(d.documentElement, root, "Shouldn't have the old root element");
+
+ d.write("Test");
+ d.close();
+
+ isnot(d.documentElement, root, "Still shouldn't have the old root element");
+ is(d.documentElement.tagName, "HTML", "Unexpected new root after write");
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug489532.html b/dom/html/test/test_bug489532.html
new file mode 100644
index 000000000..ac28c3548
--- /dev/null
+++ b/dom/html/test/test_bug489532.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=489532
+-->
+<head>
+ <title>Test for Bug 489532</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=489532">Mozilla Bug 489532</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script>
+/** Test for Bug 489532 **/
+try {
+ document.createElement("<div>");
+ ok(false, "Should throw.")
+} catch (e) {
+ is(e.name, "InvalidCharacterError",
+ "Expected InvalidCharacterError.");
+ ok(e instanceof DOMException, "Expected DOMException.");
+ is(e.code, DOMException.INVALID_CHARACTER_ERR,
+ "Expected INVALID_CHARACTER_ERR.");
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug497242.xhtml b/dom/html/test/test_bug497242.xhtml
new file mode 100644
index 000000000..ba3965ee2
--- /dev/null
+++ b/dom/html/test/test_bug497242.xhtml
@@ -0,0 +1,41 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=497242
+-->
+<head>
+ <title>Test for Bug 497242</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=497242">Mozilla Bug 497242</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <form name="foo"/>
+ <form name="foo"/>
+ <form name="bar"/>
+ <form name="bar" xmlns=""/>
+</div>
+<pre id="test">
+<script type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 497242 **/
+is(document.getElementsByName("foo").length, 2,
+ "Should find both forms with name 'foo'");
+is(document.getElementsByName("foo")[0],
+ document.getElementsByTagName("form")[0],
+ "Unexpected first foo");
+is(document.getElementsByName("foo")[1],
+ document.getElementsByTagName("form")[1],
+ "Unexpected second foo");
+is(document.getElementsByName("bar").length, 1,
+ "Should find only the HTML form with name 'bar'");
+is(document.getElementsByName("bar")[0],
+ document.getElementsByTagName("form")[2],
+ "Unexpected bar");
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug499092.html b/dom/html/test/test_bug499092.html
new file mode 100644
index 000000000..d09dcb830
--- /dev/null
+++ b/dom/html/test/test_bug499092.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=499092
+-->
+<head>
+ <title>Test for Bug 499092</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=499092">Mozilla Bug 499092</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 499092 **/
+SimpleTest.waitForExplicitFinish();
+var content = document.getElementById("content");
+
+function testHtml() {
+ is(this.contentDocument.title, "HTML OK");
+ SimpleTest.finish();
+}
+
+function testXml() {
+ is(this.contentDocument.title, "XML OK");
+ var iframeHtml = document.createElement("iframe");
+ iframeHtml.onload = testHtml;
+ iframeHtml.src = "bug499092.html";
+ content.appendChild(iframeHtml);
+}
+
+var iframeXml = document.createElement("iframe");
+iframeXml.onload = testXml;
+iframeXml.src = "bug499092.xml";
+content.appendChild(iframeXml);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug500885.html b/dom/html/test/test_bug500885.html
new file mode 100644
index 000000000..3e7b24883
--- /dev/null
+++ b/dom/html/test/test_bug500885.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=500885
+-->
+<head>
+ <title>Test for Bug 500885</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=500885">Mozilla Bug 500885</a>
+<div>
+ <input id="file" type="file" />
+</div>
+<script type="text/javascript">
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+MockFilePicker.returnValue = MockFilePicker.returnOK;
+
+function test() {
+ // SpecialPowers.DOMWindowUtils doesn't appear to fire mouseEvents correctly
+ var wu = SpecialPowers.getDOMWindowUtils(window);
+
+ try {
+ var domActivateEvents;
+ var fileInput = document.getElementById("file");
+ var rect = fileInput.getBoundingClientRect();
+
+ fileInput.addEventListener ("DOMActivate", function (e) {
+ ok("detail" in e, "DOMActivate should have .detail");
+ is(e.detail, 1, ".detail should be 1");
+ domActivateEvents++;
+ }, false);
+
+ domActivateEvents = 0;
+ wu.sendMouseEvent("mousedown", rect.left + 5, rect.top + 5, 0, 1, 0);
+ wu.sendMouseEvent("mouseup", rect.left + 5, rect.top + 5, 0, 1, 0);
+ is(domActivateEvents, 1, "click on button should fire 1 DOMActivate event");
+
+ domActivateEvents = 0;
+ wu.sendMouseEvent("mousedown", rect.right - 5, rect.top + 5, 0, 1, 0);
+ wu.sendMouseEvent("mouseup", rect.right - 5, rect.top + 5, 0, 1, 0);
+ is(domActivateEvents, 1, "click on text field should fire 1 DOMActivate event");
+
+ } finally {
+ SimpleTest.executeSoon(function() {
+ MockFilePicker.cleanup();
+ SimpleTest.finish();
+ });
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(test);
+
+</script>
+</body>
+
+</html>
diff --git a/dom/html/test/test_bug512367.html b/dom/html/test/test_bug512367.html
new file mode 100644
index 000000000..be10345a9
--- /dev/null
+++ b/dom/html/test/test_bug512367.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=512367
+-->
+<head>
+ <title>Test for Bug 512367</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=512367">Mozilla Bug 512367</a>
+<p id="display">
+ <iframe src="bug369370-popup.png" id="i" style="width:200px; height:200px"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+var frame = document.getElementById("i");
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var viewer =
+ SpecialPowers.wrap(frame.contentWindow)
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .QueryInterface(SpecialPowers.Ci.nsIDocShell)
+ .contentViewer;
+
+ viewer.fullZoom = 1.5;
+
+ setTimeout(function() {
+ synthesizeMouse(frame, 30, 30, {});
+
+ is(viewer.fullZoom, 1.5, "Zoom in the image frame should not have been reset");
+
+ SimpleTest.finish();
+ }, 0);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug514856.html b/dom/html/test/test_bug514856.html
new file mode 100644
index 000000000..50617bbba
--- /dev/null
+++ b/dom/html/test/test_bug514856.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=514856
+-->
+<head>
+ <title>Test for Bug 514856</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=514856">Mozilla Bug 514856</a>
+<p id="display"></p>
+<div id="content">
+ <iframe id="testFrame" src="bug514856_iframe.html"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 514856 **/
+
+function beginTest() {
+ var ifr = document.getElementById("testFrame");
+ var win = ifr.contentWindow;
+
+ // After the click, the load event should be fired.
+ ifr.addEventListener('load', function() {
+ testDone();
+ }, false);
+
+ // synthesizeMouse adds getBoundingClientRect left and top to the offsets but
+ // in that particular case, we don't want that.
+ var rect = ifr.getBoundingClientRect();
+ var left = rect.left;
+ var top = rect.top;
+
+ synthesizeMouse(ifr, 10 - left, 10 - top, { type: "mousemove" }, win);
+ synthesizeMouse(ifr, 12 - left, 12 - top, { type: "mousemove" }, win);
+ synthesizeMouse(ifr, 14 - left, 14 - top, { type: "mousemove" }, win);
+ synthesizeMouse(ifr, 16 - left, 16 - top, { }, win);
+}
+
+function testDone() {
+ var ifr = document.getElementById("testFrame");
+ var url = new String(ifr.contentWindow.location);
+
+ is(url.indexOf("?10,10"), -1, "Shouldn't have ?10,10 in the URL!");
+ is(url.indexOf("?12,12"), -1, "Shouldn't have ?12,12 in the URL!");
+ is(url.indexOf("?14,14"), -1, "Shouldn't have ?14,14 in the URL!");
+ isnot(url.indexOf("?16,16"), -1, "Should have ?16,16 in the URL!");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug518122.html b/dom/html/test/test_bug518122.html
new file mode 100644
index 000000000..66e9a95a3
--- /dev/null
+++ b/dom/html/test/test_bug518122.html
@@ -0,0 +1,126 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=518122
+-->
+<head>
+ <title>Test for Bug 518122</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=518122">Mozilla Bug 518122</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 518122 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTests);
+
+var simple_tests = [ ["foo", "foo"],
+ ["", ""],
+ [null, ""],
+ [undefined , "undefined"],
+ ["\n", "\n"],
+ ["\r", "\n"],
+ ["\rfoo", "\nfoo"],
+ ["foo\r", "foo\n"],
+ ["foo\rbar", "foo\nbar"],
+ ["foo\rbar\r", "foo\nbar\n"],
+ ["\r\n", "\n"],
+ ["\r\nfoo", "\nfoo"],
+ ["foo\r\n", "foo\n"],
+ ["foo\r\nbar", "foo\nbar"],
+ ["foo\r\nbar\r\n", "foo\nbar\n"] ];
+
+var value_append_tests = [ ["foo", "bar", "foobar"],
+ ["foo", "foo", "foofoo"],
+ ["foobar", "bar", "foobarbar"],
+ ["foobar", "foo", "foobarfoo"],
+ ["foo\n", "foo", "foo\nfoo"],
+ ["foo\r", "foo", "foo\nfoo"],
+ ["foo\r\n", "foo", "foo\nfoo"],
+ ["\n", "\n", "\n\n"],
+ ["\r", "\r", "\n\n"],
+ ["\r\n", "\r\n", "\n\n"],
+ ["\r", "\r\n", "\n\n"],
+ ["\r\n", "\r", "\n\n"],
+ [null, null, "null"],
+ [null, undefined, "undefined"],
+ ["", "", ""]
+ ];
+
+
+var simple_tests_for_input = [ ["foo", "foo"],
+ ["", ""],
+ [null, ""],
+ [undefined , "undefined"],
+ ["\n", ""],
+ ["\r", ""],
+ ["\rfoo", "foo"],
+ ["foo\r", "foo"],
+ ["foo\rbar", "foobar"],
+ ["foo\rbar\r", "foobar"],
+ ["\r\n", ""],
+ ["\r\nfoo", "foo"],
+ ["foo\r\n", "foo"],
+ ["foo\r\nbar", "foobar"],
+ ["foo\r\nbar\r\n", "foobar"] ];
+
+var value_append_tests_for_input = [ ["foo", "bar", "foobar"],
+ ["foo", "foo", "foofoo"],
+ ["foobar", "bar", "foobarbar"],
+ ["foobar", "foo", "foobarfoo"],
+ ["foo\n", "foo", "foofoo"],
+ ["foo\r", "foo", "foofoo"],
+ ["foo\r\n", "foo", "foofoo"],
+ ["\n", "\n", ""],
+ ["\r", "\r", ""],
+ ["\r\n", "\r\n", ""],
+ ["\r", "\r\n", ""],
+ ["\r\n", "\r", ""],
+ [null, null, "null"],
+ [null, undefined, "undefined"],
+ ["", "", ""]
+ ];
+function runTestsFor(el, simpleTests, appendTests) {
+ for(var i = 0; i < simpleTests.length; ++i) {
+ el.value = simpleTests[i][0];
+ is(el.value, simpleTests[i][1], "Wrong value (wrap=" + el.getAttribute('wrap') + ", simple_test=" + i + ")");
+ }
+ for (var j = 0; j < appendTests.length; ++j) {
+ el.value = appendTests[j][0];
+ el.value += appendTests[j][1];
+ is(el.value, appendTests[j][2], "Wrong value (wrap=" + el.getAttribute('wrap') + ", value_append_test=" + j + ")");
+ }
+}
+
+function runTests() {
+ var textareas = document.getElementsByTagName("textarea");
+ for (var i = 0; i < textareas.length; ++i) {
+ runTestsFor(textareas[i], simple_tests, value_append_tests);
+ }
+ var input = document.getElementsByTagName("input")[0];
+ runTestsFor(input, simple_tests_for_input, value_append_tests_for_input);
+ // initialize the editor
+ input.focus();
+ input.blur();
+ runTestsFor(input, simple_tests_for_input, value_append_tests_for_input);
+ SimpleTest.finish();
+}
+
+
+</script>
+</pre>
+<textarea cols="30" rows="7" wrap="none"></textarea>
+<textarea cols="30" rows="7" wrap="off"></textarea><br>
+<textarea cols="30" rows="7" wrap="soft"></textarea>
+<textarea cols="30" rows="7" wrap="hard"></textarea>
+<input type="text">
+</body>
+</html>
diff --git a/dom/html/test/test_bug519987.html b/dom/html/test/test_bug519987.html
new file mode 100644
index 000000000..a10d5e0b8
--- /dev/null
+++ b/dom/html/test/test_bug519987.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=519987
+-->
+<head>
+ <title>Test for Bug 519987</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=519987">Mozilla Bug 519987</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 519987 **/
+var xmlns = 'http://www.w3.org/1999/xhtml';
+is((new Image()).namespaceURI, xmlns, "Unexpected namespace for new Image()");
+is((new Audio()).namespaceURI, xmlns, "Unexpected namespace for new Audio()");
+var titles = document.getElementsByTagName("title");
+var t = titles[0];
+t.parentNode.removeChild(t);
+document.title = "abcdefg";
+is(titles[0].namespaceURI, xmlns, "Unexpected namespace for new <title>");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug523771.html b/dom/html/test/test_bug523771.html
new file mode 100644
index 000000000..80527ff93
--- /dev/null
+++ b/dom/html/test/test_bug523771.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=523771
+-->
+<head>
+ <title>Test for Bug 523771</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=523771">Mozilla Bug 523771</a>
+<p id="display"></p>
+<iframe name="target_iframe" id="target_iframe"></iframe>
+<form action="form_submit_server.sjs" target="target_iframe" id="form"
+method="POST" enctype="multipart/form-data">
+ <input id=singleFile name=singleFile type=file>
+ <input id=multiFile name=multiFile type=file multiple>
+</form>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+singleFileInput = document.getElementById('singleFile');
+multiFileInput = document.getElementById('multiFile');
+var input1File = { name: "523771_file1", type: "", body: "file1 contents"};
+var input2Files =
+ [{ name: "523771_file2", type: "", body: "second file contents" },
+ { name: "523771_file3.txt", type: "text/plain", body: "123456" },
+ { name: "523771_file4.html", type: "text/html", body: "<html>content</html>" }
+ ];
+
+SimpleTest.waitForExplicitFinish();
+
+function setFileInputs () {
+ var f = createFileWithData(input1File.name, input1File.body, input1File.type);
+ SpecialPowers.wrap(singleFileInput).mozSetFileArray([f]);
+
+ var input2FileNames = [];
+ for (file of input2Files) {
+ f = createFileWithData(file.name, file.body, file.type);
+ input2FileNames.push(f);
+ }
+ SpecialPowers.wrap(multiFileInput).mozSetFileArray(input2FileNames);
+}
+
+function createFileWithData(fileName, fileData, fileType) {
+ return new File([fileData], fileName, { type: fileType });
+}
+
+function cleanupFiles() {
+ singleFileInput.value = "";
+ multiFileInput.value = "";
+}
+
+is(singleFileInput.files.length, 0, "single-file .files.length"); // bug 524421
+is(multiFileInput.files.length, 0, "multi-file .files.length"); // bug 524421
+
+setFileInputs();
+
+is(singleFileInput.multiple, false, "single-file input .multiple");
+is(multiFileInput.multiple, true, "multi-file input .multiple");
+is(singleFileInput.value, input1File.name, "single-file input .value");
+is(multiFileInput.value, input2Files[0].name, "multi-file input .value");
+is(singleFileInput.files[0].name, input1File.name, "single-file input .files[n].name");
+is(singleFileInput.files[0].size, input1File.body.length, "single-file input .files[n].size");
+is(singleFileInput.files[0].type, input1File.type, "single-file input .files[n].type");
+for(i = 0; i < input2Files.length; ++i) {
+ is(multiFileInput.files[i].name, input2Files[i].name, "multi-file input .files[n].name");
+ is(multiFileInput.files[i].size, input2Files[i].body.length, "multi-file input .files[n].size");
+ is(multiFileInput.files[i].type, input2Files[i].type, "multi-file input .files[n].type");
+}
+
+document.getElementById('form').submit();
+iframe = document.getElementById('target_iframe');
+iframe.onload = function() {
+ response = JSON.parse(iframe.contentDocument.documentElement.textContent);
+ is(response[0].headers["Content-Disposition"],
+ "form-data; name=\"singleFile\"; filename=\"" + input1File.name +
+ "\"",
+ "singleFile Content-Disposition");
+ is(response[0].headers["Content-Type"], input1File.type || "application/octet-stream",
+ "singleFile Content-Type");
+ is(response[0].body, input1File.body, "singleFile body");
+
+ for(i = 0; i < input2Files.length; ++i) {
+ is(response[i + 1].headers["Content-Disposition"],
+ "form-data; name=\"multiFile\"; filename=\"" + input2Files[i].name +
+ "\"",
+ "multiFile Content-Disposition");
+ is(response[i + 1].headers["Content-Type"], input2Files[i].type || "application/octet-stream",
+ "multiFile Content-Type");
+ is(response[i + 1].body, input2Files[i].body, "multiFile body");
+ }
+
+ cleanupFiles();
+
+ is(singleFileInput.files.length, 0, "single-file .files.length"); // bug 524421
+ is(multiFileInput.files.length, 0, "multi-file .files.length"); // bug 524421
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug529819.html b/dom/html/test/test_bug529819.html
new file mode 100644
index 000000000..e744587a6
--- /dev/null
+++ b/dom/html/test/test_bug529819.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=529819
+-->
+<head>
+ <title>Test for Bug 529819</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=529819">Mozilla Bug 529819</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<form id="form">
+ <input name="foo" id="foo">
+ <input name="bar" type="radio">
+ <input name="bar" id="bar" type="radio">
+</form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 529819 **/
+is($("form").elements["foo"] instanceof HTMLInputElement, true, "Should have an element here");
+is($("form").elements["bar"] instanceof HTMLInputElement, false, "Should have a list here");
+is($("form").elements["bar"].length, 2, "Should have a list with two elements here");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug529859.html b/dom/html/test/test_bug529859.html
new file mode 100644
index 000000000..282a5c060
--- /dev/null
+++ b/dom/html/test/test_bug529859.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=529859
+-->
+<head>
+ <title>Test for Bug 529859</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=529859">Mozilla Bug 529859</a>
+<div id="content">
+ <iframe name="target_iframe" id="target_iframe"></iframe>
+ <form action="form_submit_server.sjs" target="target_iframe" id="form"
+ method="POST" enctype="multipart/form-data">
+ <input id="emptyFileInput" name="emptyFileInput" type="file">
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 529859 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ $("target_iframe").onload = function() {
+ var response = JSON.parse(this.contentDocument.documentElement.textContent);
+ is(response.length, 1, "Unexpected number of inputs");
+ is(response[0].headers["Content-Disposition"],
+ "form-data; name=\"emptyFileInput\"; filename=\"\"",
+ "Incorrect content-disposition");
+ is(response[0].headers["Content-Type"], "application/octet-stream",
+ "Unexpected content-type");
+ SimpleTest.finish();
+ }
+ $("form").submit();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug535043.html b/dom/html/test/test_bug535043.html
new file mode 100644
index 000000000..0123c4c2f
--- /dev/null
+++ b/dom/html/test/test_bug535043.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=535043
+-->
+<head>
+ <title>Test for Bug 535043</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=535043">Mozilla Bug 535043</a>
+<p id="display"></p>
+<div id="content">
+ <textarea></textarea>
+ <textarea maxlength="-1"></textarea>
+ <textarea maxlength="0"></textarea>
+ <textarea maxlength="2"></textarea>
+</div>
+<pre id="test">
+<script type="text/javascript">
+
+/** Test for Bug 535043 **/
+function checkTextArea(textArea) {
+ textArea.value = '';
+ textArea.focus();
+ for (var j = 0; j < 3; j++) {
+ synthesizeKey('x', {});
+ }
+ var htmlMaxLength = textArea.getAttribute('maxlength');
+ var domMaxLength = textArea.maxLength;
+ if (htmlMaxLength == null) {
+ is(domMaxLength, -1,
+ 'maxlength is unset but maxLength DOM attribute is not -1');
+ } else if (htmlMaxLength < 0) {
+ // Per the HTML5 spec, out-of-range values are supposed to translate to -1,
+ // not 0, but they don't?
+ is(domMaxLength, -1,
+ 'maxlength is out of range but maxLength DOM attribute is not -1');
+ } else {
+ is(domMaxLength, parseInt(htmlMaxLength),
+ 'maxlength in DOM does not match provided value');
+ }
+ if (textArea.maxLength == -1) {
+ is(textArea.value.length, 3,
+ 'textarea with maxLength -1 should have no length limit');
+ } else {
+ is(textArea.value.length, textArea.maxLength, 'textarea has maxLength ' +
+ textArea.maxLength + ' but length ' + textArea.value.length );
+ }
+}
+
+SimpleTest.waitForFocus(function() {
+ var textAreas = document.getElementsByTagName('textarea');
+ for (var i = 0; i < textAreas.length; i++) {
+ checkTextArea(textAreas[i]);
+ }
+
+ textArea = textAreas[0];
+ testNums = [-42, -1, 0, 2];
+ for (var i = 0; i < testNums.length; i++) {
+ textArea.removeAttribute('maxlength');
+
+ var caught = false;
+ try {
+ textArea.maxLength = testNums[i];
+ } catch (e) {
+ caught = true;
+ }
+ if (testNums[i] < 0) {
+ ok(caught, 'Setting negative maxLength should throw exception');
+ } else {
+ ok(!caught, 'Setting nonnegative maxLength should not throw exception');
+ }
+ checkTextArea(textArea);
+
+ textArea.setAttribute('maxlength', testNums[i]);
+ checkTextArea(textArea);
+ }
+
+ SimpleTest.finish();
+});
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug536891.html b/dom/html/test/test_bug536891.html
new file mode 100644
index 000000000..8fbfe0bc2
--- /dev/null
+++ b/dom/html/test/test_bug536891.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=536891
+-->
+<head>
+ <title>Test for Bug 536891</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=536891">Mozilla Bug 536891</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<textarea id="t" maxlength="-2" minlength="-2"></textarea>
+<input id="i" type="text" maxlength="-2" minlength="-2">
+<input id="p" type="password" maxlength="-2" minlength="-2">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 536891 **/
+
+function checkNegativeMinMaxLength(element)
+{
+ for(let type of ["min", "max"]) {
+ /* value is set to -2 initially in the document, see above */
+ is(element[type + "Length"], -1, "negative " + type + "Length should be considered invalid and represented as -1");
+
+ // changing the property to an negative value should throw (see bug 536895).
+ for(let value of [-15, -2147483648]) { // PR_INT32_MIN
+ let threw = false;
+ try {
+ element[type + "Length"] = value;
+ } catch(e) {
+ threw = true;
+ }
+ is(threw, true, "setting " + type + "Length property to " + value + " should throw");
+ }
+ element[type + "Length"] = "non-numerical value";
+ is(element[type + "Length"], 0, "setting " + type + "Length property to a non-numerical value should set it to zero");
+
+
+ element.setAttribute(type + 'Length', -15);
+ is(element[type + "Length"], -1, "negative " + type + "Length is not processed correctly when set dynamically");
+ is(element.getAttribute(type + 'Length'), "-15", type + "Length attribute doesn't return the correct value");
+
+ element.setAttribute(type + 'Length', 0);
+ is(element[type + "Length"], 0, "zero " + type + "Length is not processed correctly");
+ element.setAttribute(type + 'Length', 2147483647); // PR_INT32_MAX
+ is(element[type + "Length"], 2147483647, "negative " + type + "Length is not processed correctly");
+ element.setAttribute(type + 'Length', -2147483648); // PR_INT32_MIN
+ is(element[type + "Length"], -1, "negative " + type + "Length is not processed correctly");
+ element.setAttribute(type + 'Length', 'non-numerical-value');
+ is(element[type + "Length"], -1, "non-numerical value should be considered invalid and represented as -1");
+ }
+}
+
+/* TODO: correct behavior may be checked for email, telephone, url and search input types */
+checkNegativeMinMaxLength(document.getElementById('t'));
+checkNegativeMinMaxLength(document.getElementById('i'));
+checkNegativeMinMaxLength(document.getElementById('p'));
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug536895.html b/dom/html/test/test_bug536895.html
new file mode 100644
index 000000000..7ff5bedb1
--- /dev/null
+++ b/dom/html/test/test_bug536895.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=536895
+-->
+<head>
+ <title>Test for Bug 536895</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=536895">Mozilla Bug 536895</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<textarea id="t"></textarea>
+<input id="i" type="text">
+<input id="p" type="password">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 536895 **/
+
+function checkNegativeMaxLengthException(element)
+{
+ caught = false;
+ try {
+ element.setAttribute('maxLength', -10);
+ } catch(e) {
+ caught = true;
+ }
+ ok(!caught, "Setting maxLength attribute to a negative value shouldn't throw an exception");
+
+ caught = false;
+ try {
+ element.maxLength = -20;
+ } catch(e) {
+ is(e.name, "IndexSizeError", "Should be an IndexSizeError exception");
+ caught = true;
+ }
+ ok(caught, "Setting negative maxLength from the DOM should throw an exception");
+
+ is(element.getAttribute('maxLength'), "-10", "When the exception is raised, the maxLength attribute shouldn't change");
+}
+
+/* TODO: correct behavior may be checked for email, telephone, url and search input types */
+checkNegativeMaxLengthException(document.getElementById('t'));
+checkNegativeMaxLengthException(document.getElementById('i'));
+checkNegativeMaxLengthException(document.getElementById('p'));
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug546995.html b/dom/html/test/test_bug546995.html
new file mode 100644
index 000000000..0135da601
--- /dev/null
+++ b/dom/html/test/test_bug546995.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=546995
+-->
+<head>
+ <title>Test for Bug 546995</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=546995">Mozilla Bug 546995</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <select id='s'></select>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 546995 **/
+
+/* This test in only testing IDL reflection, another one is testing the behavior */
+
+function checkAutofocusIDLAttribute(element)
+{
+ ok('autofocus' in element, "Element has the autofocus IDL attribute");
+ ok(!element.autofocus, "autofocus default value is false");
+ element.setAttribute('autofocus', 'autofocus');
+ ok(element.autofocus, "autofocus should be enabled");
+ element.removeAttribute('autofocus');
+ ok(!element.autofocus, "autofocus should be disabled");
+}
+
+// TODO: keygen should be added when correctly implemented, see bug 101019.
+checkAutofocusIDLAttribute(document.getElementById('s'));
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug547850.html b/dom/html/test/test_bug547850.html
new file mode 100644
index 000000000..977366518
--- /dev/null
+++ b/dom/html/test/test_bug547850.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=547850
+-->
+<head>
+ <title>Test for Bug 547850</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=547850">Mozilla Bug 547850</a>
+<script>
+document.write("<div id=content><f\u00c5></f\u00c5><r\u00e5></r\u00e5>");
+document.write("<span g\u00c5=a1 t\u00e5=a2></span></div>");
+</script>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var ch = $('content').childNodes;
+is(ch[0].localName, "f\u00c5", "upper case localName");
+is(ch[1].localName, "r\u00e5", "lower case localName");
+is(ch[0].nodeName, "F\u00c5", "upper case nodeName");
+is(ch[1].nodeName, "R\u00e5", "lower case nodeName");
+is(ch[0].tagName, "F\u00c5", "upper case tagName");
+is(ch[1].tagName, "R\u00e5", "lower case tagName");
+is(ch[2].getAttribute("g\u00c5"), "a1", "upper case attr name");
+is(ch[2].getAttribute("t\u00e5"), "a2", "lower case attr name");
+is(ch[2].getAttribute("G\u00c5"), "a1", "upper case attr name");
+is(ch[2].getAttribute("T\u00e5"), "a2", "lower case attr name");
+is(ch[2].getAttribute("g\u00e5"), null, "wrong lower case attr name");
+is(ch[2].getAttribute("t\u00c5"), null, "wrong upper case attr name");
+is($('content').getElementsByTagName("f\u00c5")[0], ch[0], "gEBTN upper case");
+is($('content').getElementsByTagName("f\u00c5").length, 1, "gEBTN upper case length");
+is($('content').getElementsByTagName("r\u00e5")[0], ch[1], "gEBTN lower case");
+is($('content').getElementsByTagName("r\u00e5").length, 1, "gEBTN lower case length");
+is($('content').getElementsByTagName("F\u00c5")[0], ch[0], "gEBTN upper case");
+is($('content').getElementsByTagName("F\u00c5").length, 1, "gEBTN upper case length");
+is($('content').getElementsByTagName("R\u00e5")[0], ch[1], "gEBTN lower case");
+is($('content').getElementsByTagName("R\u00e5").length, 1, "gEBTN lower case length");
+is($('content').getElementsByTagName("f\u00e5").length, 0, "gEBTN wrong upper case");
+is($('content').getElementsByTagName("r\u00c5").length, 0, "gEBTN wrong lower case");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug551846.html b/dom/html/test/test_bug551846.html
new file mode 100644
index 000000000..e528e5a16
--- /dev/null
+++ b/dom/html/test/test_bug551846.html
@@ -0,0 +1,164 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=551846
+-->
+<head>
+ <title>Test for Bug 551846</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=551846">Mozilla Bug 551846</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <select id='s'>
+ <option>Tulip</option>
+ <option>Lily</option>
+ <option>Gagea</option>
+ <option>Snowflake</option>
+ <option>Ismene</option>
+ </select>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 551846 **/
+
+function checkSizeReflection(element, defaultValue)
+{
+ is(element.size, defaultValue, "Default size should be " + defaultValue);
+
+ element.setAttribute('size', -15);
+ is(element.size, defaultValue,
+ "The reflecting IDL attribute should return the default value when content attribute value is invalid");
+ is(element.getAttribute('size'), "-15",
+ "The content attribute should containt the previously set value");
+
+ element.setAttribute('size', 0);
+ is(element.size, 0,
+ "0 should be considered as a valid value");
+ is(element.getAttribute('size'), "0",
+ "The content attribute should containt the previously set value");
+
+ element.setAttribute('size', 2147483647); /* PR_INT32_MAX */
+ is(element.size, 2147483647,
+ "PR_INT32_MAX should be considered as a valid value");
+ is(element.getAttribute('size'), "2147483647",
+ "The content attribute should containt the previously set value");
+
+ element.setAttribute('size', -2147483648); /* PR_INT32_MIN */
+ is(element.size, defaultValue,
+ "The reflecting IDL attribute should return the default value when content attribute value is invalid");
+ is(element.getAttribute('size'), "-2147483648",
+ "The content attribute should containt the previously set value");
+
+ element.setAttribute('size', 'non-numerical-value');
+ is(element.size, defaultValue,
+ "The reflecting IDL attribute should return the default value when content attribute value is invalid");
+ is(element.getAttribute('size'), 'non-numerical-value',
+ "The content attribute should containt the previously set value");
+
+ element.setAttribute('size', 4294967294); /* PR_INT32_MAX * 2 */
+ is(element.size, defaultValue,
+ "Value greater than PR_INT32_MAX should be considered as invalid");
+ is(element.getAttribute('size'), "4294967294",
+ "The content attribute should containt the previously set value");
+
+ element.setAttribute('size', -4294967296); /* PR_INT32_MIN * 2 */
+ is(element.size, defaultValue,
+ "The reflecting IDL attribute should return the default value when content attribute value is invalid");
+ is(element.getAttribute('size'), "-4294967296",
+ "The content attribute should containt the previously set value");
+
+ element.size = defaultValue + 1;
+ element.removeAttribute('size');
+ is(element.size, defaultValue,
+ "When the attribute is removed, the size should be the default size");
+
+ element.setAttribute('size', 'foobar');
+ is(element.size, defaultValue,
+ "The reflecting IDL attribute should return the default value when content attribute value is invalid");
+ element.removeAttribute('size');
+ is(element.size, defaultValue,
+ "When the attribute is removed, the size should be the default size");
+}
+
+function checkSetSizeException(element)
+{
+ var caught = false;
+
+ try {
+ element.size = 1;
+ } catch(e) {
+ caught = true;
+ }
+ ok(!caught, "Setting a positive size shouldn't throw an exception");
+
+ caught = false;
+ try {
+ element.size = 0;
+ } catch(e) {
+ caught = true;
+ }
+ ok(!caught, "Setting a size to 0 from the IDL shouldn't throw an exception");
+
+ element.size = 1;
+
+ caught = false;
+ try {
+ element.size = -1;
+ } catch(e) {
+ caught = true;
+ }
+ ok(!caught, "Setting a negative size from the IDL shouldn't throw an exception");
+
+ is(element.size, 0, "The size should now be equal to the minimum non-negative value");
+
+ caught = false;
+ try {
+ element.setAttribute('size', -10);
+ } catch(e) {
+ caught = true;
+ }
+ ok(!caught, "Setting an invalid size in the content attribute shouldn't throw an exception");
+
+ // reverting to defalut
+ element.removeAttribute('size');
+}
+
+function checkSizeWhenChangeMultiple(element, aDefaultNonMultiple, aDefaultMultiple)
+{
+ s.setAttribute('size', -1)
+ is(s.size, aDefaultNonMultiple, "Size IDL attribute should be 1");
+
+ s.multiple = true;
+ is(s.size, aDefaultMultiple, "Size IDL attribute should be 4");
+
+ is(s.getAttribute('size'), "-1", "Size content attribute should be -1");
+
+ s.setAttribute('size', -2);
+ is(s.size, aDefaultMultiple, "Size IDL attribute should be 4");
+
+ s.multiple = false;
+ is(s.size, aDefaultNonMultiple, "Size IDL attribute should be 1");
+
+ is(s.getAttribute('size'), "-2", "Size content attribute should be -2");
+}
+
+var s = document.getElementById('s');
+
+checkSizeReflection(s, 0);
+checkSetSizeException(s);
+
+s.setAttribute('multiple', 'true');
+checkSizeReflection(s, 0);
+checkSetSizeException(s);
+s.removeAttribute('multiple');
+
+checkSizeWhenChangeMultiple(s, 0, 0);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug555567.html b/dom/html/test/test_bug555567.html
new file mode 100644
index 000000000..04c82147c
--- /dev/null
+++ b/dom/html/test/test_bug555567.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=555567
+-->
+<head>
+ <title>Test for Bug 555567</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=555567">Mozilla Bug 555567</a>
+<div id='content' style="display: none">
+ <form>
+ <fieldset>
+ <legend id="a"></legend>
+ </fieldset>
+ <legend id="b"></legend>
+ </form>
+ <legend id="c"></legend>
+</div>
+<pre id="test">
+<p id="display"></p>
+<script type="application/javascript">
+
+/** Test for Bug 555567 **/
+
+var a = document.getElementById('a');
+var b = document.getElementById('b');
+var c = document.getElementById('c');
+
+isnot(a.form, null,
+ "First legend element should have a not null form IDL attribute");
+is(b.form, null,
+ "Second legend element should have a null form IDL attribute");
+is(c.form, null,
+ "Third legend element should have a null form IDL attribute");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug556645.html b/dom/html/test/test_bug556645.html
new file mode 100644
index 000000000..449f956a6
--- /dev/null
+++ b/dom/html/test/test_bug556645.html
@@ -0,0 +1,50 @@
+<html>
+<head>
+ <title>Test for Bug 556645</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>
+<script>
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+function runTest()
+{
+ var obj = document.getElementById("obj");
+ var childDoc = obj.contentDocument;
+ var body = childDoc.body;
+ is(document.activeElement, document.body, "focus in parent before");
+ is(childDoc.activeElement, body, "focus in child before");
+
+ var button = childDoc.getElementsByTagName("button")[0];
+ button.focus();
+ childDoc.defaultView.focus();
+ is(document.activeElement, obj, "focus in parent after focus()");
+ is(childDoc.activeElement, button, "focus in child after focus()");
+
+ button.blur();
+ var pbutton = document.getElementById("pbutton");
+ pbutton.focus();
+
+ synthesizeKey("VK_TAB", { });
+ is(document.activeElement, obj, "focus in parent after tab");
+ is(childDoc.activeElement, childDoc.documentElement, "focus in child after tab");
+
+ synthesizeKey("VK_TAB", { });
+ is(document.activeElement, obj, "focus in parent after tab 2");
+ is(childDoc.activeElement, button, "focus in child after tab 2");
+
+ SimpleTest.finish();
+}
+
+</script>
+
+<button id="pbutton">Parent</button>
+<object id="obj" type="text/html"
+ data="data:text/html,%3Cbody%3E%3Cbutton%3EChild%3C/button%3E%3C/body%3E"
+ width="200" height="200"></object>
+
+</body>
+</html>
diff --git a/dom/html/test/test_bug557087-1.html b/dom/html/test/test_bug557087-1.html
new file mode 100644
index 000000000..32e36e59d
--- /dev/null
+++ b/dom/html/test/test_bug557087-1.html
@@ -0,0 +1,126 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=557087
+-->
+<head>
+ <title>Test for Bug 557087</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=557087">Mozilla Bug 557087</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 557087 **/
+
+function checkDisabledAttribute(aFieldset)
+{
+ ok('disabled' in aFieldset,
+ "fieldset elements should have the disabled attribute");
+
+ ok(!aFieldset.disabled,
+ "fieldset elements disabled attribute should be disabled");
+ is(aFieldset.getAttribute('disabled'), null,
+ "fieldset elements disabled attribute should be disabled");
+
+ aFieldset.disabled = true;
+ ok(aFieldset.disabled,
+ "fieldset elements disabled attribute should be enabled");
+ isnot(aFieldset.getAttribute('disabled'), null,
+ "fieldset elements disabled attribute should be enabled");
+
+ aFieldset.removeAttribute('disabled');
+ aFieldset.setAttribute('disabled', '');
+ ok(aFieldset.disabled,
+ "fieldset elements disabled attribute should be enabled");
+ isnot(aFieldset.getAttribute('disabled'), null,
+ "fieldset elements disabled attribute should be enabled");
+
+ aFieldset.removeAttribute('disabled');
+ ok(!aFieldset.disabled,
+ "fieldset elements disabled attribute should be disabled");
+ is(aFieldset.getAttribute('disabled'), null,
+ "fieldset elements disabled attribute should be disabled");
+}
+
+function checkDisabledPseudoClass(aFieldset)
+{
+ is(document.querySelector(":disabled"), null,
+ "no elements should have :disabled applied to them");
+
+ aFieldset.disabled = true;
+ is(document.querySelector(":disabled"), aFieldset,
+ ":disabled should apply to fieldset elements");
+
+ aFieldset.disabled = false;
+ is(document.querySelector(":disabled"), null,
+ "no elements should have :disabled applied to them");
+}
+
+function checkEnabledPseudoClass(aFieldset)
+{
+ is(document.querySelector(":enabled"), aFieldset,
+ ":enabled should apply to fieldset elements");
+
+ aFieldset.disabled = true;
+ is(document.querySelector(":enabled"), null,
+ "no elements should have :enabled applied to them");
+
+ aFieldset.disabled = false;
+ is(document.querySelector(":enabled"), aFieldset,
+ ":enabled should apply to fieldset elements");
+}
+
+function checkFocus(aFieldset)
+{
+ aFieldset.disabled = true;
+ aFieldset.setAttribute('tabindex', 1);
+
+ aFieldset.focus();
+
+ isnot(document.activeElement, aFieldset,
+ "fieldset can't be focused when disabled");
+ aFieldset.removeAttribute('tabindex');
+ aFieldset.disabled = false;
+}
+
+function checkClickEvent(aFieldset)
+{
+ var clickHandled = false;
+
+ aFieldset.disabled = true;
+
+ aFieldset.addEventListener("click", function(aEvent) {
+ aEvent.target.removeEventListener("click", arguments.callee, false);
+ clickHandled = true;
+ }, false);
+
+ sendMouseEvent({type:'click'}, aFieldset);
+ SimpleTest.executeSoon(function() {
+ ok(!clickHandled, "When disabled, fieldset should prevent click events");
+ SimpleTest.finish();
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+
+var fieldset = document.createElement("fieldset");
+var content = document.getElementById('content');
+content.appendChild(fieldset);
+
+checkDisabledAttribute(fieldset);
+checkDisabledPseudoClass(fieldset);
+checkEnabledPseudoClass(fieldset);
+checkFocus(fieldset);
+checkClickEvent(fieldset);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug557087-2.html b/dom/html/test/test_bug557087-2.html
new file mode 100644
index 000000000..923a136c5
--- /dev/null
+++ b/dom/html/test/test_bug557087-2.html
@@ -0,0 +1,359 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=557087
+-->
+<head>
+ <title>Test for Bug 557087</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=557087">Mozilla Bug 557087</a>
+<p id="display"></p>
+<div id="content" style="display:none;">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 557087 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var elementsPreventingClick = [ "input", "button", "select", "textarea", "fieldset" ];
+var elementsWithClick = [ "option", "optgroup", "output", "label", "object" ];
+var gHandled = 0;
+
+function clickShouldNotHappenHandler(aEvent)
+{
+ aEvent.target.removeEventListener("click", clickShouldNotHappenHandler, false);
+ ok(false, "click event should be prevented! (test1)");
+ if (++gHandled >= elementsWithClick.length) {
+ test2();
+ }
+}
+
+function clickShouldNotHappenHandler2(aEvent)
+{
+ aEvent.target.removeEventListener("click", clickShouldNotHappenHandler3, false);
+ ok(false, "click event should be prevented! (test2)");
+ if (++gHandled >= elementsWithClick.length) {
+ test3();
+ }
+}
+
+function clickShouldNotHappenHandler5(aEvent)
+{
+ aEvent.target.removeEventListener("click", clickShouldNotHappenHandler5, false);
+ ok(false, "click event should be prevented! (test5)");
+ if (++gHandled >= elementsWithClick.length) {
+ test6();
+ }
+}
+
+function clickShouldNotHappenHandler7(aEvent)
+{
+ aEvent.target.removeEventListener("click", clickShouldNotHappenHandler7, false);
+ ok(false, "click event should be prevented! (test7)");
+ if (++gHandled >= elementsWithClick.length) {
+ test8();
+ }
+}
+
+function clickShouldHappenHandler(aEvent)
+{
+ aEvent.target.removeEventListener("click", clickShouldHappenHandler, false);
+ ok(true, "click event has been correctly received (test1)");
+ if (++gHandled >= elementsWithClick.length) {
+ test2();
+ }
+}
+
+function clickShouldHappenHandler2(aEvent)
+{
+ aEvent.target.removeEventListener("click", clickShouldHappenHandler2, false);
+ ok(true, "click event has been correctly received (test2)");
+ if (++gHandled >= elementsWithClick.length) {
+ test3();
+ }
+}
+
+function clickShouldHappenHandler3(aEvent)
+{
+ aEvent.target.removeEventListener("click", clickShouldHappenHandler3, false);
+ ok(true, "click event has been correctly received (test3)");
+ if (++gHandled >= (elementsWithClick.length +
+ elementsPreventingClick.length)) {
+ test4();
+ }
+}
+
+function clickShouldHappenHandler4(aEvent)
+{
+ aEvent.target.removeEventListener("click", clickShouldHappenHandler4, false);
+ ok(true, "click event has been correctly received (test4)");
+ if (++gHandled >= (elementsWithClick.length +
+ elementsPreventingClick.length)) {
+ test5();
+ }
+}
+
+function clickShouldHappenHandler5(aEvent)
+{
+ aEvent.target.removeEventListener("click", clickShouldHappenHandler5, false);
+ ok(true, "click event has been correctly received (test5)");
+ if (++gHandled >= elementsWithClick.length) {
+ test6();
+ }
+}
+
+function clickShouldHappenHandler6(aEvent)
+{
+ aEvent.target.removeEventListener("click", clickShouldHappenHandler6, false);
+ ok(true, "click event has been correctly received (test6)");
+ if (++gHandled >= (elementsWithClick.length +
+ elementsPreventingClick.length)) {
+ test7();
+ }
+}
+
+function clickShouldHappenHandler7(aEvent)
+{
+ aEvent.target.removeEventListener("click", clickShouldHappenHandler7, false);
+ ok(true, "click event has been correctly received (test5)");
+ if (++gHandled >= elementsWithClick.length) {
+ test8();
+ }
+}
+
+function clickShouldHappenHandler8(aEvent)
+{
+ aEvent.target.removeEventListener("click", clickShouldHappenHandler8, false);
+ ok(true, "click event has been correctly received (test8)");
+ if (++gHandled >= (elementsWithClick.length +
+ elementsPreventingClick.length)) {
+ SimpleTest.finish();
+ }
+}
+
+var fieldset1 = document.createElement("fieldset");
+var fieldset2 = document.createElement("fieldset");
+var legendA = document.createElement("legend");
+var legendB = document.createElement("legend");
+var content = document.getElementById('content');
+fieldset1.disabled = true;
+content.appendChild(fieldset1);
+fieldset1.appendChild(fieldset2);
+
+function clean()
+{
+ var count = fieldset2.children.length;
+ for (var i=0; i<count; ++i) {
+ if (fieldset2.children[i] != legendA &&
+ fieldset2.children[i] != legendB) {
+ fieldset2.removeChild(fieldset2.children[i]);
+ }
+ }
+}
+
+function test1()
+{
+ gHandled = 0;
+
+ // Initialize children without click expected.
+ for (var name of elementsPreventingClick) {
+ var element = document.createElement(name);
+ fieldset2.appendChild(element);
+ element.addEventListener("click", clickShouldNotHappenHandler, false);
+ sendMouseEvent({type:'click'}, element);
+ }
+
+ // Initialize children with click expected.
+ for (var name of elementsWithClick) {
+ var element = document.createElement(name);
+ fieldset2.appendChild(element);
+ element.addEventListener("click", clickShouldHappenHandler, false);
+ sendMouseEvent({type:'click'}, element);
+ }
+}
+
+function test2()
+{
+ gHandled = 0;
+ fieldset1.disabled = false;
+ fieldset2.disabled = true;
+
+ // Initialize children without click expected.
+ for (var name of elementsPreventingClick) {
+ var element = document.createElement(name);
+ fieldset2.appendChild(element);
+ element.addEventListener("click", clickShouldNotHappenHandler2, false);
+ sendMouseEvent({type:'click'}, element);
+ }
+
+ // Initialize children with click expected.
+ for (var name of elementsWithClick) {
+ var element = document.createElement(name);
+ fieldset2.appendChild(element);
+ element.addEventListener("click", clickShouldHappenHandler2, false);
+ sendMouseEvent({type:'click'}, element);
+ }
+}
+
+function test3()
+{
+ gHandled = 0;
+ fieldset1.disabled = false;
+ fieldset2.disabled = false;
+
+ // All elements should accept the click.
+ for (var name of elementsPreventingClick) {
+ var element = document.createElement(name);
+ fieldset2.appendChild(element);
+ element.addEventListener("click", clickShouldHappenHandler3, false);
+ sendMouseEvent({type:'click'}, element);
+ }
+
+ // Initialize children with click expected.
+ for (var name of elementsWithClick) {
+ var element = document.createElement(name);
+ fieldset2.appendChild(element);
+ element.addEventListener("click", clickShouldHappenHandler3, false);
+ sendMouseEvent({type:'click'}, element);
+ }
+}
+
+function test4()
+{
+ gHandled = 0;
+ fieldset1.disabled = false;
+ fieldset2.disabled = true;
+
+ fieldset2.appendChild(legendA);
+
+ // All elements should accept the click.
+ for (var name of elementsPreventingClick) {
+ var element = document.createElement(name);
+ legendA.appendChild(element);
+ element.addEventListener("click", clickShouldHappenHandler4, false);
+ sendMouseEvent({type:'click'}, element);
+ }
+
+ // Initialize children with click expected.
+ for (var name of elementsWithClick) {
+ var element = document.createElement(name);
+ legendA.appendChild(element);
+ element.addEventListener("click", clickShouldHappenHandler4, false);
+ sendMouseEvent({type:'click'}, element);
+ }
+}
+
+function test5()
+{
+ gHandled = 0;
+ fieldset2.insertBefore(legendB, legendA);
+
+ // Initialize children without click expected.
+ for (var name of elementsPreventingClick) {
+ var element = document.createElement(name);
+ legendA.appendChild(element);
+ element.addEventListener("click", clickShouldNotHappenHandler5, false);
+ sendMouseEvent({type:'click'}, element);
+ }
+
+ // Initialize children with click expected.
+ for (var name of elementsWithClick) {
+ var element = document.createElement(name);
+ legendA.appendChild(element);
+ element.addEventListener("click", clickShouldHappenHandler5, false);
+ sendMouseEvent({type:'click'}, element);
+ }
+}
+
+function test6()
+{
+ gHandled = 0;
+ fieldset2.removeChild(legendB);
+ fieldset1.disabled = true;
+ fieldset2.disabled = false;
+
+ fieldset1.appendChild(legendA);
+ legendA.appendChild(fieldset2);
+
+ // All elements should accept the click.
+ for (var name of elementsPreventingClick) {
+ var element = document.createElement(name);
+ fieldset2.appendChild(element);
+ element.addEventListener("click", clickShouldHappenHandler6, false);
+ sendMouseEvent({type:'click'}, element);
+ }
+
+ // Initialize children with click expected.
+ for (var name of elementsWithClick) {
+ var element = document.createElement(name);
+ fieldset2.appendChild(element);
+ element.addEventListener("click", clickShouldHappenHandler6, false);
+ sendMouseEvent({type:'click'}, element);
+ }
+}
+
+function test7()
+{
+ gHandled = 0;
+ fieldset1.disabled = true;
+ fieldset2.disabled = false;
+
+ fieldset1.appendChild(fieldset2);
+ fieldset2.appendChild(legendA);
+
+ // All elements should accept the click.
+ for (var name of elementsPreventingClick) {
+ var element = document.createElement(name);
+ legendA.appendChild(element);
+ element.addEventListener("click", clickShouldNotHappenHandler7, false);
+ sendMouseEvent({type:'click'}, element);
+ }
+
+ // Initialize children with click expected.
+ for (var name of elementsWithClick) {
+ var element = document.createElement(name);
+ legendA.appendChild(element);
+ element.addEventListener("click", clickShouldHappenHandler7, false);
+ sendMouseEvent({type:'click'}, element);
+ }
+}
+
+function test8()
+{
+ gHandled = 0;
+ fieldset1.disabled = true;
+ fieldset2.disabled = true;
+
+ fieldset1.appendChild(legendA);
+ legendA.appendChild(fieldset2);
+ fieldset2.appendChild(legendB);
+
+ // All elements should accept the click.
+ for (var name of elementsPreventingClick) {
+ var element = document.createElement(name);
+ legendB.appendChild(element);
+ element.addEventListener("click", clickShouldHappenHandler8, false);
+ sendMouseEvent({type:'click'}, element);
+ }
+
+ // Initialize children with click expected.
+ for (var name of elementsWithClick) {
+ var element = document.createElement(name);
+ legendB.appendChild(element);
+ element.addEventListener("click", clickShouldHappenHandler8, false);
+ sendMouseEvent({type:'click'}, element);
+ }
+}
+
+test1();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug557087-3.html b/dom/html/test/test_bug557087-3.html
new file mode 100644
index 000000000..166007c49
--- /dev/null
+++ b/dom/html/test/test_bug557087-3.html
@@ -0,0 +1,215 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=557087
+-->
+<head>
+ <title>Test for Bug 557087</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=557087">Mozilla Bug 557087</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 557087 **/
+
+function checkValueMissing(aElement, aExpected)
+{
+ var msg = aExpected ? aElement.tagName + " should suffer from value missing"
+ : aElement.tagName + " should not suffer from value missing"
+ is(aElement.validity.valueMissing, aExpected, msg);
+}
+
+function checkCandidateForConstraintValidation(aElement, aExpected)
+{
+ var msg = aExpected ? aElement.tagName + " should be candidate for constraint validation"
+ : aElement.tagName + " should not be candidate for constraint validation"
+ is(aElement.willValidate, aExpected, msg);
+}
+
+function checkDisabledPseudoClass(aElement, aDisabled)
+{
+ var disabledElements = document.querySelectorAll(":disabled");
+ var found = false;
+
+ for (var e of disabledElements) {
+ if (aElement == e) {
+ found = true;
+ break;
+ }
+ }
+
+ var msg = aDisabled ? aElement.tagName + " should have :disabled applying"
+ : aElement.tagName + " should not have :disabled applying";
+ ok(aDisabled ? found : !found, msg);
+}
+
+function checkEnabledPseudoClass(aElement, aEnabled)
+{
+ var enabledElements = document.querySelectorAll(":enabled");
+ var found = false;
+
+ for (var e of enabledElements) {
+ if (aElement == e) {
+ found = true;
+ break;
+ }
+ }
+
+ var msg = aEnabled ? aElement.tagName + " should have :enabled applying"
+ : aElement.tagName + " should not have :enabled applying";
+ ok(aEnabled ? found : !found, msg);
+}
+
+function checkFocus(aElement, aExpected)
+{
+ aElement.setAttribute('tabindex', 1);
+
+ // We use the focus manager so we can test <label>.
+ var fm = SpecialPowers.Cc["@mozilla.org/focus-manager;1"]
+ .getService(SpecialPowers.Ci.nsIFocusManager);
+ fm.setFocus(aElement, 0);
+
+ if (aExpected) {
+ is(document.activeElement, aElement, "element should be focused");
+ } else {
+ isnot(document.activeElement, aElement, "element should not be focused");
+ }
+
+ aElement.blur();
+ aElement.removeAttribute('tabindex');
+}
+
+var elements = [ "input", "button", "select", "textarea", "fieldset", "option",
+ "optgroup", "label", "output", "object" ];
+
+var testData = {
+/* tag name | affected by disabled | test focus | test pseudo-classes | test willValidate */
+ "INPUT": [ true, true, true, true, true ],
+ "BUTTON": [ true, true, true, false, false ],
+ "SELECT": [ true, true, true, true, false ],
+ "TEXTAREA": [ true, true, true, true, true ],
+ "FIELDSET": [ true, true, true, false, false ],
+ "OPTION": [ false, true, true, false, false ],
+ "OPTGROUP": [ false, true, true, false, false ],
+ "OBJECT": [ false, true, false, false, false ],
+ "LABEL": [ false, true, false, false, false ],
+ "OUTPUT": [ false, true, false, false, false ],
+};
+
+/**
+ * For not candidate elements without disabled attribute and not submittable,
+ * we only have to check that focus and click works even inside a disabled
+ * fieldset.
+ */
+function checkElement(aElement, aDisabled)
+{
+ var data = testData[aElement.tagName];
+ var expected = data[0] ? !aDisabled : true;
+
+ if (data[1]) {
+ checkFocus(aElement, expected);
+ }
+
+ if (data[2]) {
+ checkEnabledPseudoClass(aElement, data[0] ? !aDisabled : true);
+ checkDisabledPseudoClass(aElement, data[0] ? aDisabled : false);
+ }
+
+ if (data[3]) {
+ checkCandidateForConstraintValidation(aElement, expected);
+ }
+
+ if (data[4]) {
+ checkValueMissing(aElement, expected);
+ }
+}
+
+var fieldset1 = document.createElement("fieldset");
+var fieldset2 = document.createElement("fieldset");
+var legendA = document.createElement("legend");
+var legendB = document.createElement("legend");
+var content = document.getElementById('content');
+content.appendChild(fieldset1);
+fieldset1.appendChild(fieldset2);
+fieldset2.disabled = true;
+
+for (var data of elements) {
+ var element = document.createElement(data);
+
+ if (data[4]) {
+ element.required = true;
+ }
+
+ fieldset1.disabled = false;
+ fieldset2.appendChild(element);
+
+ checkElement(element, fieldset2.disabled);
+
+ // Make sure changes are correctly managed.
+ fieldset2.disabled = false;
+ checkElement(element, fieldset2.disabled);
+ fieldset2.disabled = true;
+ checkElement(element, fieldset2.disabled);
+
+ // Make sure if a fieldset which is not the first fieldset is disabled, the
+ // elements inside the second fielset are disabled.
+ fieldset2.disabled = false;
+ fieldset1.disabled = true;
+ checkElement(element, fieldset1.disabled);
+
+ // Make sure the state change of the inner fieldset will not confuse.
+ fieldset2.disabled = true;
+ fieldset2.disabled = false;
+ checkElement(element, fieldset1.disabled);
+
+
+ /* legend tests */
+
+ // elements in the first legend of a disabled fieldset should not be disabled.
+ fieldset2.disabled = true;
+ fieldset1.disabled = false;
+ legendA.appendChild(element);
+ fieldset2.appendChild(legendA);
+ checkElement(element, false);
+
+ // elements in the second legend should be disabled
+ fieldset2.insertBefore(legendB, legendA);
+ checkElement(element, fieldset2.disabled);
+ fieldset2.removeChild(legendB);
+
+ // Elements in the first legend of a fieldset disabled by another fieldset
+ // should be disabled.
+ fieldset1.disabled = true;
+ checkElement(element, fieldset1.disabled);
+
+ // Elements inside a fieldset inside the first legend of a disabled fieldset
+ // should not be diasbled.
+ fieldset2.disabled = false;
+ fieldset1.appendChild(legendA);
+ legendA.appendChild(fieldset2);
+ fieldset2.appendChild(element);
+ checkElement(element, false);
+
+ // Elements inside the first legend of a disabled fieldset inside the first
+ // legend of a disabled fieldset should not be disabled.
+ fieldset2.disabled = false;
+ fieldset2.appendChild(legendB);
+ legendB.appendChild(element);
+ checkElement(element, false);
+ fieldset2.removeChild(legendB);
+ fieldset1.appendChild(fieldset2);
+
+ element.parentNode.removeChild(element);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug557087-4.html b/dom/html/test/test_bug557087-4.html
new file mode 100644
index 000000000..edb732dae
--- /dev/null
+++ b/dom/html/test/test_bug557087-4.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=557087
+-->
+<head>
+ <title>Test for Bug 557087</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=557087">Mozilla Bug 557087</a>
+<p id="display"></p>
+<div id="content">
+ <iframe name='f'></iframe>
+ <form target='f' action="data:text/html">
+ <input type='text' id='a'>
+ <input type='checkbox' id='b'>
+ <input type='radio' id='c'>
+ <fieldset disabled>
+ <fieldset>
+ <input type='submit' id='s'>
+ </fieldset>
+ </fieldset>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 557087 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var gExpectedSubmits = 6;
+var gSubmitReceived = 0;
+var gEnd = false;
+
+var fieldsets = document.getElementsByTagName("fieldset");
+var form = document.forms[0];
+
+form.addEventListener("submit", function() {
+ ok(gEnd, gEnd ? "expected submit" : "non expected submit");
+ if (++gSubmitReceived >= gExpectedSubmits) {
+ form.removeEventListener("submit", arguments.callee, false);
+ SimpleTest.finish();
+ }
+}, false);
+
+var inputs = [
+ document.getElementById('a'),
+ document.getElementById('b'),
+ document.getElementById('c'),
+];
+
+function doSubmit()
+{
+ for (e of inputs) {
+ e.focus();
+ synthesizeKey("VK_RETURN", {});
+ }
+}
+
+SimpleTest.waitForFocus(function() {
+ doSubmit();
+
+ fieldsets[1].disabled = true;
+ fieldsets[0].disabled = false;
+ doSubmit();
+
+ fieldsets[0].disabled = false;
+ fieldsets[1].disabled = false;
+
+ gEnd = true;
+ doSubmit();
+
+ // Simple check that we can submit from inside a legend even if the fieldset
+ // is disabled.
+ var legend = document.createElement("legend");
+ fieldsets[0].appendChild(legend);
+ fieldsets[0].disabled = true;
+ legend.appendChild(document.getElementById('s'));
+
+ doSubmit();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug557087-5.html b/dom/html/test/test_bug557087-5.html
new file mode 100644
index 000000000..4b8c021f8
--- /dev/null
+++ b/dom/html/test/test_bug557087-5.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=557087
+-->
+<head>
+ <title>Test for Bug 557087</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=557087">Mozilla Bug 557087</a>
+<p id="display"></p>
+<div id="content">
+ <iframe name='t'></iframe>
+ <form target='t' action="data:text/html,">
+ <fieldset disabled>
+ <fieldset>
+ <input name='i' value='i'>
+ <textarea name='t'>t</textarea>
+ <select name='s'><option>s</option></select>
+ </fieldset>
+ </fieldset>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 557087 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+var testResults = [
+ "data:text/html,?",
+ "data:text/html,?",
+ "data:text/html,?i=i&t=t&s=s",
+ "data:text/html,?i=i&t=t&s=s",
+];
+var gTestCount = 0;
+
+var form = document.forms[0];
+var iframe = document.getElementsByTagName('iframe')[0];
+var fieldsets = document.getElementsByTagName('fieldset');
+
+function runTest()
+{
+ iframe.addEventListener("load", function() {
+ is(iframe.contentWindow.location.href, testResults[gTestCount],
+ testResults[gTestCount] + " should have been loaded");
+
+ switch (++gTestCount) {
+ case 1:
+ fieldsets[1].disabled = true;
+ fieldsets[0].disabled = false;
+ form.submit();
+ SimpleTest.executeSoon(function() {
+ form.submit()
+ });
+ break;
+ case 2:
+ fieldsets[0].disabled = false;
+ fieldsets[1].disabled = false;
+ SimpleTest.executeSoon(function() {
+ form.submit()
+ });
+ break;
+ case 3:
+ // Elements inside the first legend of a disabled fieldset are submittable.
+ fieldsets[0].disabled = true;
+ fieldsets[1].disabled = true;
+ var legend = document.createElement("legend");
+ fieldsets[0].appendChild(legend);
+ while (fieldsets[1].firstChild) {
+ legend.appendChild(fieldsets[1].firstChild);
+ }
+ SimpleTest.executeSoon(function() {
+ form.submit()
+ });
+ break;
+ default:
+ iframe.removeEventListener("load", arguments.callee, false);
+ SimpleTest.executeSoon(SimpleTest.finish);
+ }
+ }, false);
+
+ form.submit();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug557087-6.html b/dom/html/test/test_bug557087-6.html
new file mode 100644
index 000000000..ea2e3efcc
--- /dev/null
+++ b/dom/html/test/test_bug557087-6.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=557087
+-->
+<head>
+ <title>Test for Bug 557087</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=557087">Mozilla Bug 557087</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <fieldset disabled>
+ <input>
+ </fieldset>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 557087 **/
+
+// Testing random stuff following review comments.
+
+var fieldset = document.getElementsByTagName("fieldset")[0];
+
+is(fieldset.elements.length, 1,
+ "there should be one element inside the fieldset");
+is(fieldset.elements[0], document.getElementsByTagName("input")[0],
+ "input should be the element inside the fieldset");
+
+document.body.removeChild(document.getElementById('content'));
+is(fieldset.querySelector("input:disabled"), fieldset.elements[0],
+ "the input should still be disabled");
+
+fieldset.disabled = false;
+is(fieldset.querySelector("input:enabled"), fieldset.elements[0],
+ "the input should be enabled");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug557620.html b/dom/html/test/test_bug557620.html
new file mode 100644
index 000000000..4fe9def3a
--- /dev/null
+++ b/dom/html/test/test_bug557620.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=557620
+-->
+<head>
+ <title>Test for Bug 557620</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=557620">Mozilla Bug 557620</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <input type="tel" id='i'>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 557620 **/
+
+// More checks are done in test_bug551670.html.
+
+var tel = document.getElementById('i');
+is(tel.type, 'tel', "input with type='tel' should return 'tel'");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug558788-1.html b/dom/html/test/test_bug558788-1.html
new file mode 100644
index 000000000..94b7a5f00
--- /dev/null
+++ b/dom/html/test/test_bug558788-1.html
@@ -0,0 +1,211 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=558788
+-->
+<head>
+ <title>Test for Bug 558788</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"/>
+ <style>
+ input, textarea { background-color: rgb(0,0,0) !important; }
+ :-moz-any(input,textarea):valid { background-color: rgb(0,255,0) !important; }
+ :-moz-any(input,textarea):invalid { background-color: rgb(255,0,0) !important; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=558788">Mozilla Bug 558788</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 558788 **/
+
+/**
+ * This test checks the behavior of :valid and :invalid pseudo-classes
+ * when the user is typing/interacting with the element.
+ * Only <input> and <textarea> elements can have there validity changed by an
+ * user input.
+ */
+
+var gContent = document.getElementById('content');
+
+function checkValidApplies(elmt)
+{
+ is(window.getComputedStyle(elmt, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+}
+
+function checkInvalidApplies(elmt, aTodo)
+{
+ if (aTodo) {
+ todo_is(window.getComputedStyle(elmt, null).getPropertyValue('background-color'),
+ "rgb(255, 0, 0)", ":invalid pseudo-class should apply");
+ return;
+ }
+ is(window.getComputedStyle(elmt, null).getPropertyValue('background-color'),
+ "rgb(255, 0, 0)", ":invalid pseudo-class should apply");
+}
+
+function checkMissing(elementName)
+{
+ var element = document.createElement(elementName);
+ element.required = true;
+ gContent.appendChild(element);
+ checkInvalidApplies(element);
+
+ element.focus();
+
+ synthesizeKey("a", {});
+ checkValidApplies(element);
+
+ synthesizeKey("VK_BACK_SPACE", {});
+ checkInvalidApplies(element);
+
+ gContent.removeChild(element);
+}
+
+function checkTooLong(elementName)
+{
+ var element = document.createElement(elementName);
+ element.value = "foo";
+ element.maxLength = 2;
+ gContent.appendChild(element);
+ checkInvalidApplies(element, true);
+
+ element.focus();
+
+ synthesizeKey("VK_BACK_SPACE", {});
+ checkValidApplies(element);
+ gContent.removeChild(element);
+}
+
+function checkTextAreaMissing()
+{
+ checkMissing('textarea');
+}
+
+function checkTextAreaTooLong()
+{
+ checkTooLong('textarea');
+}
+
+function checkTextArea()
+{
+ checkTextAreaMissing();
+ checkTextAreaTooLong();
+}
+
+function checkInputMissing()
+{
+ checkMissing('input');
+}
+
+function checkInputTooLong()
+{
+ checkTooLong('input');
+}
+
+function checkInputEmail()
+{
+ var element = document.createElement('input');
+ element.type = 'email';
+ gContent.appendChild(element);
+ checkValidApplies(element);
+
+ element.focus();
+
+ synthesizeKey("a", {});
+ checkInvalidApplies(element);
+
+ sendString("@b.c");
+ checkValidApplies(element);
+
+ synthesizeKey("VK_BACK_SPACE", {});
+ for (var i=0; i<4; ++i) {
+ if (i == 1) {
+ // a@b is a valid value.
+ checkValidApplies(element);
+ } else {
+ checkInvalidApplies(element);
+ }
+ synthesizeKey("VK_BACK_SPACE", {});
+ }
+ checkValidApplies(element);
+
+ gContent.removeChild(element);
+}
+
+function checkInputURL()
+{
+ var element = document.createElement('input');
+ element.type = 'url';
+ gContent.appendChild(element);
+ checkValidApplies(element);
+
+ element.focus();
+
+ synthesizeKey("h", {});
+ checkInvalidApplies(element);
+
+ sendString("ttp://mozilla.org");
+ checkValidApplies(element);
+
+ for (var i=0; i<13; ++i) {
+ synthesizeKey("VK_BACK_SPACE", {});
+ checkValidApplies(element);
+ }
+
+ synthesizeKey("VK_BACK_SPACE", {});
+ for (var i=0; i<4; ++i) {
+ checkInvalidApplies(element);
+ synthesizeKey("VK_BACK_SPACE", {});
+ }
+ checkValidApplies(element);
+
+ gContent.removeChild(element);
+}
+
+function checkInputPattern()
+{
+ var element = document.createElement('input');
+ element.pattern = "[0-9]*"
+ gContent.appendChild(element);
+ checkValidApplies(element);
+
+ element.focus();
+
+ synthesizeKey("0", {});
+ checkValidApplies(element);
+
+ synthesizeKey("a", {});
+ checkInvalidApplies(element);
+
+ synthesizeKey("VK_BACK_SPACE", {});
+ checkValidApplies(element);
+
+ synthesizeKey("VK_BACK_SPACE", {});
+ checkValidApplies(element);
+
+ gContent.removeChild(element);
+}
+
+function checkInput()
+{
+ checkInputMissing();
+ checkInputTooLong();
+ checkInputEmail();
+ checkInputURL();
+ checkInputPattern();
+}
+
+checkTextArea();
+checkInput();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug558788-2.html b/dom/html/test/test_bug558788-2.html
new file mode 100644
index 000000000..dcf5db00d
--- /dev/null
+++ b/dom/html/test/test_bug558788-2.html
@@ -0,0 +1,174 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=558788
+-->
+<head>
+ <title>Test for Bug 558788</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=558788">Mozilla Bug 558788</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 558788 **/
+
+var validElementsDescription = [
+ /* element type value required pattern maxlength minlength */
+ /* <input> */
+ [ "input", null, null, null, null, null, null ],
+ /* <input required value='foo'> */
+ [ "input", null, "foo", true, null, null, null ],
+ /* <input type='email'> */
+ [ "input", "email", null, null, null, null, null ],
+ /* <input type='email' value='foo@mozilla.org'> */
+ [ "input", "email", "foo@mozilla.org", null, null, null, null ],
+ /* <input type='url'> */
+ [ "input", "url", null, null, null, null, null ],
+ /* <input type='url' value='http://mozilla.org'> */
+ [ "input", "url", "http://mozilla.org", null, null, null, null ],
+ /* <input pattern='\\d\\d'> */
+ [ "input", null, null, null, "\\d\\d", null, null ],
+ /* <input pattern='\\d\\d' value='42'> */
+ [ "input", null, "42", null, "\\d\\d", null, null ],
+ /* <input maxlength='3'> - still valid until user interaction */
+ [ "input", null, null, null, null, "3", null ],
+ /* <input maxlength='3'> */
+ [ "input", null, "fooo", null, null, "3", null ],
+ /* <input minlength='3'> - still valid until user interaction */
+ [ "input", null, null, null, null, null, "3" ],
+ /* <input minlength='3'> */
+ [ "input", null, "fo", null, null, null, "3" ],
+ /* <textarea></textarea> */
+ [ "textarea", null, null, null, null, null, null ],
+ /* <textarea required>foo</textarea> */
+ [ "textarea", null, "foo", true, null, null, null ]
+];
+
+var invalidElementsDescription = [
+ /* element type value required pattern maxlength minlength valid-value */
+ /* <input required> */
+ [ "input", null, null, true, null, null, null, "foo" ],
+ /* <input type='email' value='foo'> */
+ [ "input", "email", "foo", null, null, null, null, "foo@mozilla.org" ],
+ /* <input type='url' value='foo'> */
+ [ "input", "url", "foo", null, null, null, null, "http://mozilla.org" ],
+ /* <input pattern='\\d\\d' value='foo'> */
+ [ "input", null, "foo", null, "\\d\\d", null, null, "42" ],
+ /* <input maxlength='3'> - still valid until user interaction */
+ [ "input", null, "foooo", null, null, "3", null, "foo" ],
+ /* <input minlength='3'> - still valid until user interaction */
+ [ "input", null, "foo", null, null, null, "3", "foo" ],
+ /* <textarea required></textarea> */
+ [ "textarea", null, null, true, null, null, null, "foo" ],
+];
+
+var validElements = [];
+var invalidElements = [];
+
+function appendElements(aElementsDesc, aElements)
+{
+ var content = document.getElementById('content');
+ var length = aElementsDesc.length;
+
+ for (var i=0; i<length; ++i) {
+ var e = document.createElement(aElementsDesc[i][0]);
+ if (aElementsDesc[i][1]) {
+ e.type = aElementsDesc[i][1];
+ }
+ if (aElementsDesc[i][2]) {
+ e.value = aElementsDesc[i][2];
+ }
+ if (aElementsDesc[i][3]) {
+ e.required = true;
+ }
+ if (aElementsDesc[i][4]) {
+ e.pattern = aElementsDesc[i][4];
+ }
+ if (aElementsDesc[i][5]) {
+ e.maxLength = aElementsDesc[i][5];
+ }
+ if (aElementsDesc[i][6]) {
+ e.minLength = aElementsDesc[i][6];
+ }
+
+ content.appendChild(e);
+
+ // Adding the element to the appropriate list.
+ aElements.push(e);
+ }
+}
+
+function compareArrayWithSelector(aElements, aSelector)
+{
+ var aSelectorElements = document.querySelectorAll(aSelector);
+
+ is(aSelectorElements.length, aElements.length,
+ aSelector + " selector should return the correct number of elements");
+
+ if (aSelectorElements.length != aElements.length) {
+ return;
+ }
+
+ var length = aElements.length;
+ for (var i=0; i<length; ++i) {
+ is(aSelectorElements[i], aElements[i],
+ aSelector + " should return the correct elements");
+ }
+}
+
+function makeMinMaxLengthElementsActuallyInvalid(aInvalidElements,
+ aInvalidElementsDesc)
+{
+ // min/maxlength elements are not invalid until user edits them
+ var length = aInvalidElementsDesc.length;
+
+ for (var i=0; i<length; ++i) {
+ var e = aInvalidElements[i];
+ if (aInvalidElementsDesc[i][5]) { // maxlength
+ e.focus();
+ synthesizeKey("VK_BACK_SPACE", {});
+ } else if (aInvalidElementsDesc[i][6]) { // minlength
+ e.focus();
+ synthesizeKey("VK_BACK_SPACE", {});
+ }
+ }
+}
+
+function makeInvalidElementsValid(aInvalidElements,
+ aInvalidElementsDesc,
+ aValidElements)
+{
+ var length = aInvalidElementsDesc.length;
+
+ for (var i=0; i<length; ++i) {
+ var e = aInvalidElements.shift();
+ e.value = aInvalidElementsDesc[i][7];
+ aValidElements.push(e);
+ }
+}
+
+appendElements(validElementsDescription, validElements);
+appendElements(invalidElementsDescription, invalidElements);
+
+makeMinMaxLengthElementsActuallyInvalid(invalidElements, invalidElementsDescription);
+
+compareArrayWithSelector(validElements, ":valid");
+compareArrayWithSelector(invalidElements, ":invalid");
+
+makeInvalidElementsValid(invalidElements, invalidElementsDescription,
+ validElements);
+
+compareArrayWithSelector(validElements, ":valid");
+compareArrayWithSelector(invalidElements, ":invalid");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug560112.html b/dom/html/test/test_bug560112.html
new file mode 100644
index 000000000..1b1daadc4
--- /dev/null
+++ b/dom/html/test/test_bug560112.html
@@ -0,0 +1,211 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=560112
+-->
+<head>
+ <title>Test for Bug 560112</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=560112">Mozilla Bug 560112</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 560112 **/
+
+/**
+ * Sets dataset property. Checks data attribute "attr".
+ * Gets dataset property. Checks data attribute "attr".
+ * Overwrites dataset property Checks data attribute "attr".
+ * Deletes dataset property. Checks data attribute "attr".
+ */
+function SetGetOverwriteDel(attr, prop)
+{
+ var el = document.createElement('div');
+
+ // Set property.
+ is(prop in el.dataset, false, 'Property should not be in dataset before setting.');
+ el.dataset[prop] = "zzzzzz";
+ is(prop in el.dataset, true, 'Property should be in dataset after setting.');
+ ok(el.hasAttribute(attr), 'Element should have data attribute for dataset property "' + prop + '".');
+
+ // Get property.
+ is(el.dataset[prop], "zzzzzz", 'Dataset property "' + prop + '" should have value "zzzzzz".');
+ is(el.getAttribute(attr), "zzzzzz", 'Attribute "' + attr + '" should have value "zzzzzz".');
+
+ // Overwrite property.
+ el.dataset[prop] = "yyyyyy";
+ is(el.dataset[prop], "yyyyyy", 'Dataset property "' + prop + '" should have value "yyyyyy".');
+ is(el.getAttribute(attr), "yyyyyy", 'Attribute "' + attr + '" should have value "yyyyyy".');
+
+ // Delete property.
+ delete el.dataset[prop];
+ ok(!el.hasAttribute(attr), 'Element should not have data attribute for dataset property "' + prop + '".');
+ is(prop in el.dataset, false, 'Deleted property should not be in dataset.');
+}
+
+/**
+ * Sets dataset property and expects exception.
+ */
+function SetExpectException(prop)
+{
+ var el = document.createElement('div');
+
+ try {
+ el.dataset[prop] = "xxxxxx";
+ ok(false, 'Exception should have been thrown.');
+ } catch (ex) {
+ ok(true, 'Exception should have been thrown.');
+ }
+}
+
+/**
+ * Adds attributes in "attrList" to element.
+ * Deletes attributes in "delList" from element.
+ * Checks for "numProp" properties in dataset.
+ */
+function DelAttrEnumerate(attrList, delList, numProp)
+{
+ var el = document.createElement('div');
+
+ // Adds attributes in "attrList".
+ for (var i = 0; i < attrList.length; ++i) {
+ el.setAttribute(attrList[i], "aaaaaa");
+ }
+
+ // Remove attributes in "delList".
+ for (var i = 0; i < delList.length; ++i) {
+ el.removeAttribute(delList[i]);
+ }
+
+ var numPropCounted = 0;
+
+ for (var prop in el.dataset) {
+ if (el.dataset[prop] == "aaaaaa") {
+ ++numPropCounted;
+ }
+ }
+
+ is(numPropCounted, numProp, 'Number of enumerable dataset properties is incorrent after attribute removal.');
+}
+
+/**
+ * Adds attributes in "attrList" to element.
+ * Checks for "numProp" properties in dataset.
+ */
+function Enumerate(attrList, numProp)
+{
+ var el = document.createElement('div');
+
+ // Adds attributes in "attrList" to element.
+ for (var i = 0; i < attrList.length; ++i) {
+ el.setAttribute(attrList[i], "aaaaaa");
+ }
+
+ var numPropCounted = 0;
+
+ for (var prop in el.dataset) {
+ if (el.dataset[prop] == "aaaaaa") {
+ ++numPropCounted;
+ }
+ }
+
+ is(numPropCounted, numProp, 'Number of enumerable dataset properties is incorrect.');
+}
+
+/**
+ * Adds dataset property then removes attribute from element and check for presence of
+ * properties using the "in" operator.
+ */
+function AddPropDelAttr(attr, prop)
+{
+ var el = document.createElement('div');
+
+ el.dataset[prop] = 'dddddd';
+ is(prop in el.dataset, true, 'Operator "in" should return true after setting property.');
+ el.removeAttribute(attr);
+ is(prop in el.dataset, false, 'Operator "in" should return false for removed attribute.');
+}
+
+/**
+ * Adds then removes attribute from element and check for presence of properties using the
+ * "in" operator.
+ */
+function AddDelAttr(attr, prop)
+{
+ var el = document.createElement('div');
+
+ el.setAttribute(attr, 'dddddd');
+ is(prop in el.dataset, true, 'Operator "in" should return true after setting attribute.');
+ el.removeAttribute(attr);
+ is(prop in el.dataset, false, 'Operator "in" should return false for removed attribute.');
+}
+
+// Typical use case.
+SetGetOverwriteDel('data-property', 'property');
+SetGetOverwriteDel('data-a-longer-property', 'aLongerProperty');
+
+AddDelAttr('data-property', 'property');
+AddDelAttr('data-a-longer-property', 'aLongerProperty');
+
+AddPropDelAttr('data-property', 'property');
+AddPropDelAttr('data-a-longer-property', 'aLongerProperty');
+
+// Empty property name.
+SetGetOverwriteDel('data-', '');
+
+// Leading dash characters.
+SetGetOverwriteDel('data--', '-');
+SetGetOverwriteDel('data--d', 'D');
+SetGetOverwriteDel('data---d', '-D');
+
+// Trailing dash characters.
+SetGetOverwriteDel('data-d-', 'd-');
+SetGetOverwriteDel('data-d--', 'd--');
+SetGetOverwriteDel('data-d-d-', 'dD-');
+
+// "data-" in attribute name.
+SetGetOverwriteDel('data-data-', 'data-');
+SetGetOverwriteDel('data-data-data-', 'dataData-');
+
+// Longer attribute.
+SetGetOverwriteDel('data-long-long-long-long-long-long-long-long-long-long-long-long-long', 'longLongLongLongLongLongLongLongLongLongLongLongLong');
+
+var longAttr = 'data-long';
+var longProp = 'long';
+for (var i = 0; i < 30000; ++i) {
+ // Create really long attribute and property names.
+ longAttr += '-long';
+ longProp += 'Long';
+}
+
+SetGetOverwriteDel(longAttr, longProp);
+
+// Syntax error in setting dataset property (dash followed by lower case).
+SetExpectException('-a');
+SetExpectException('a-a');
+SetExpectException('a-a-a');
+
+// Invalid character.
+SetExpectException('a a');
+
+// Enumeration over dataset properties.
+Enumerate(['data-a-big-fish'], 1);
+Enumerate(['dat-a-big-fish'], 0);
+Enumerate(['data-'], 1);
+Enumerate(['data-', 'data-more-data'], 2);
+Enumerate(['daaata-', 'data-more-data'], 1);
+
+// Delete data attributes and enumerate properties.
+DelAttrEnumerate(['data-one', 'data-two'], ['data-one'], 1);
+DelAttrEnumerate(['data-one', 'data-two'], ['data-three'], 2);
+DelAttrEnumerate(['data-one', 'data-two'], ['one'], 2);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug561634.html b/dom/html/test/test_bug561634.html
new file mode 100644
index 000000000..61a1486e7
--- /dev/null
+++ b/dom/html/test/test_bug561634.html
@@ -0,0 +1,126 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=561634
+-->
+<head>
+ <title>Test for Bug 561634</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=561634">Mozilla Bug 561634</a>
+<p id="display"></p>
+<div id="content" style="display: none;">
+ <form>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 561634 **/
+
+function checkEmptyForm()
+{
+ ok(document.forms[0].checkValidity(), "An empty form is valid");
+}
+
+function checkBarredFromConstraintValidation()
+{
+ var f = document.forms[0];
+ var fs = document.createElement('fieldset');
+ var i = document.createElement('input');
+
+ f.appendChild(fs);
+ i.type = 'hidden';
+ f.appendChild(i);
+
+ fs.setCustomValidity("foo");
+ i.setCustomValidity("foo");
+
+ ok(f.checkValidity(),
+ "A form with invalid element barred from constraint validation should be valid");
+
+ f.removeChild(i);
+ f.removeChild(fs);
+}
+
+function checkValid()
+{
+ var f = document.forms[0];
+ var i = document.createElement('input');
+ f.appendChild(i);
+
+ ok(f.checkValidity(), "A form with valid elements is valid");
+
+ f.removeChild(i);
+}
+
+function checkInvalid()
+{
+ var f = document.forms[0];
+ var i = document.createElement('input');
+ f.appendChild(i);
+
+ i.setCustomValidity("foo");
+ ok(!f.checkValidity(), "A form with invalid elements is invalid");
+
+ var i2 = document.createElement('input');
+ f.appendChild(i2);
+ ok(!f.checkValidity(),
+ "A form with at least one invalid element is invalid");
+
+ f.removeChild(i2);
+ f.removeChild(i);
+}
+
+function checkInvalidEvent()
+{
+ var f = document.forms[0];
+ var i = document.createElement('input');
+ f.appendChild(i);
+ var i2 = document.createElement('input');
+ f.appendChild(i2);
+
+ i.setCustomValidity("foo");
+
+ var invalidEventForInvalidElement = false;
+ var invalidEventForValidElement = false;
+
+ i.addEventListener("invalid", function (e) {
+ invalidEventForInvalidElement = true;
+ ok(e.cancelable, "invalid event should be cancelable");
+ ok(!e.bubbles, "invalid event should not bubble");
+ }, false);
+
+ i2.addEventListener("invalid", function (e) {
+ invalidEventForValidElement = true;
+ }, false);
+
+ f.checkValidity();
+
+ setTimeout(function() {
+ ok(invalidEventForInvalidElement,
+ "invalid event should be fired on invalid elements");
+ ok(!invalidEventForValidElement,
+ "invalid event should not be fired on valid elements");
+
+ f.removeChild(i2);
+ f.removeChild(i);
+
+ SimpleTest.finish();
+ }, 0);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+checkEmptyForm();
+checkBarredFromConstraintValidation();
+checkValid();
+checkInvalid();
+checkInvalidEvent(); // will call finish().
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug561636.html b/dom/html/test/test_bug561636.html
new file mode 100644
index 000000000..d259e8c83
--- /dev/null
+++ b/dom/html/test/test_bug561636.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=561636
+-->
+<head>
+ <title>Test for Bug 561636</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=561636">Mozilla Bug 561636</a>
+<p id="display"></p>
+<iframe style='width:50px; height: 50px;' name='t'></iframe>
+<iframe style='width:50px; height: 50px;' name='t2' id='i'></iframe>
+<div id="content">
+ <form target='t' action='data:text/html,'>
+ <input required>
+ <input id='a' type='submit'>
+ </form>
+ <form target='t' action='data:text/html,'>
+ <input type='checkbox' required>
+ <button id='b' type='submit'></button>
+ </form>
+ <form target='t' action='data:text/html,'>
+ <input id='c' required>
+ </form>
+ <form target='t' action='data:text/html,'>
+ <input>
+ <input id='s2' type='submit'>
+ </form>
+ <form target='t2' action='data:text/html,'>
+ <input required>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 561636 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+function runTest()
+{
+ var formSubmitted = [ false, false ];
+ var invalidHandled = false;
+
+ var os = SpecialPowers.Cc['@mozilla.org/observer-service;1']
+ .getService(SpecialPowers.Ci.nsIObserverService);
+ var observers = os.enumerateObservers("invalidformsubmit");
+
+ // The following test should not be done if there is no observer for
+ // "invalidformsubmit" because the form submission will not be canceled in that
+ // case.
+ if (!observers.hasMoreElements()) {
+ SimpleTest.finish();
+ return;
+ }
+
+ // Initialize
+ document.forms[0].addEventListener('submit', function(aEvent) {
+ aEvent.target.removeEventListener('submit', arguments.callee, false);
+ formSubmitted[0] = true;
+ }, false);
+
+ document.forms[1].addEventListener('submit', function(aEvent) {
+ aEvent.target.removeEventListener('submit', arguments.callee, false);
+ formSubmitted[1] = true;
+ }, false);
+
+ document.forms[2].addEventListener('submit', function(aEvent) {
+ aEvent.target.removeEventListener('submit', arguments.callee, false);
+ formSubmitted[2] = true;
+ }, false);
+
+ document.forms[3].addEventListener('submit', function(aEvent) {
+ aEvent.target.removeEventListener('submit', arguments.callee, false);
+ formSubmitted[3] = true;
+
+ ok(!formSubmitted[0], "Form 1 should not have been submitted because invalid");
+ ok(!formSubmitted[1], "Form 2 should not have been submitted because invalid");
+ ok(!formSubmitted[2], "Form 3 should not have been submitted because invalid");
+ ok(formSubmitted[3], "Form 4 should have been submitted because valid");
+
+ // Next test.
+ document.forms[4].submit();
+ }, false);
+
+ document.forms[4].elements[0].addEventListener('invalid', function(aEvent) {
+ aEvent.target.removeEventListener('invalid', arguments.callee, false);
+ invalidHandled = true;
+ }, false);
+
+ document.getElementById('i').addEventListener('load', function(aEvent) {
+ aEvent.target.removeEventListener('load', arguments.callee, false);
+
+ SimpleTest.executeSoon(function () {
+ ok(true, "Form 5 should have been submitted because submit() has been used even if invalid");
+ ok(!invalidHandled, "Invalid event should not have been sent");
+
+ SimpleTest.finish();
+ });
+ }, false);
+
+ document.getElementById('a').click();
+ document.getElementById('b').click();
+ var c = document.getElementById('c');
+ c.focus();
+ synthesizeKey("KEY_Enter", { code: "Enter" });
+ document.getElementById('s2').click();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug561640.html b/dom/html/test/test_bug561640.html
new file mode 100644
index 000000000..670d1b3a1
--- /dev/null
+++ b/dom/html/test/test_bug561640.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=561640
+-->
+<head>
+ <title>Test for Bug 561640</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ input, textarea { background-color: rgb(0,0,0) !important; }
+ :-moz-any(input,textarea):valid { background-color: rgb(0,255,0) !important; }
+ :-moz-any(input,textarea):invalid { background-color: rgb(255,0,0) !important; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=561640">Mozilla Bug 561640</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 561640 **/
+
+var elements = [ 'input', 'textarea' ];
+var content = document.getElementById('content');
+
+function checkValid(elmt)
+{
+ ok(!elmt.validity.tooLong, "element should not be too long");
+ is(window.getComputedStyle(elmt, null).getPropertyValue('background-color'),
+ "rgb(0, 255, 0)", ":valid pseudo-class should apply");
+}
+
+function checkInvalid(elmt)
+{
+ todo(elmt.validity.tooLong, "element should be too long");
+ todo_is(window.getComputedStyle(elmt, null).getPropertyValue('background-color'),
+ "rgb(255, 0, 0)", ":invalid pseudo-class should apply");
+}
+
+for (var elmtName of elements) {
+ var elmt = document.createElement(elmtName);
+ content.appendChild(elmt);
+
+ if (elmtName == 'textarea') {
+ elmt.textContent = 'foo';
+ } else {
+ elmt.setAttribute('value', 'foo');
+ }
+ elmt.maxLength = 2;
+ checkValid(elmt);
+
+ elmt.value = 'a';
+ checkValid(elmt);
+
+ if (elmtName == 'textarea') {
+ elmt.textContent = 'f';
+ } else {
+ elmt.setAttribute('value', 'f');
+ }
+ elmt.value = 'bar';
+ checkInvalid(elmt);
+
+ content.removeChild(elmt);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug564001.html b/dom/html/test/test_bug564001.html
new file mode 100644
index 000000000..3815ac8cf
--- /dev/null
+++ b/dom/html/test/test_bug564001.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=564001
+-->
+<head>
+ <title>Test for Bug 564001</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=564001">Mozilla Bug 564001</a>
+<p id="display"><img usemap=#map src=image.png></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script>
+/** Test for Bug 564001 **/
+SimpleTest.waitForExplicitFinish();
+
+var wrongArea = document.createElement("area");
+wrongArea.shape = "default";
+wrongArea.href = "#FAIL";
+var wrongMap = document.createElement("map");
+wrongMap.name = "map";
+wrongMap.appendChild(wrongArea);
+document.body.appendChild(wrongMap);
+
+var rightArea = document.createElement("area");
+rightArea.shape = "default";
+rightArea.href = "#PASS";
+var rightMap = document.createElement("map");
+rightMap.name = "map";
+rightMap.appendChild(rightArea);
+document.body.insertBefore(rightMap, wrongMap);
+
+var images = document.getElementsByTagName("img");
+onhashchange = function() {
+ is(location.hash, "#PASS", "Should get the first map in tree order.");
+ SimpleTest.finish();
+};
+SimpleTest.waitForFocus(() => synthesizeMouse(images[0], 50, 50, {}));
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug566046.html b/dom/html/test/test_bug566046.html
new file mode 100644
index 000000000..f12d12536
--- /dev/null
+++ b/dom/html/test/test_bug566046.html
@@ -0,0 +1,207 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=566046
+-->
+<head>
+ <title>Test for Bug 566046</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"/>
+ <base>
+ <base target='frame2'>
+ <base target=''>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=566046">Mozilla Bug 566046</a>
+<p id="display"></p>
+<style>
+ iframe { width: 130px; height: 100px;}
+</style>
+<iframe name='frame1' id='frame1'></iframe>
+<iframe name='frame2' id='frame2'></iframe>
+<iframe name='frame3' id='frame3'></iframe>
+<iframe name='frame4' id='frame4'></iframe>
+<iframe name='frame5' id='frame5'></iframe>
+<iframe name='frame5bis' id='frame5bis'></iframe>
+<iframe name='frame6' id='frame6'></iframe>
+<iframe name='frame7' id='frame7'></iframe>
+<iframe name='frame8' id='frame8'></iframe>
+<iframe name='frame9' id='frame9'></iframe>
+<div id="content">
+ <form target='frame1' action="data:text/html," method="GET">
+ <input name='foo' value='foo'>
+ </form>
+ <form action="data:text/html," method="GET">
+ <input name='bar' value='bar'>
+ </form>
+ <form target="">
+ </form>
+
+ <!-- submit controls with formtarget that are validated with a CLICK -->
+ <form target="tulip" action="data:text/html," method="GET">
+ <input name='tulip' value='tulip'>
+ <input type='submit' id='is' formtarget='frame3'>
+ </form>
+ <form action="data:text/html," method="GET">
+ <input name='foobar' value='foobar'>
+ <input type='image' id='ii' formtarget='frame4'>
+ </form>
+ <form action="data:text/html," method="GET">
+ <input name='tulip2' value='tulip2'>
+ <button type='submit' id='bs' formtarget='frame5'>submit</button>
+ </form>
+ <form action="data:text/html," method="GET">
+ <input name='tulip3' value='tulip3'>
+ <button type='submit' id='bsbis' formtarget='frame5bis'>submit</button>
+ </form>
+
+ <!-- submit controls with formtarget that are validated with ENTER -->
+ <form target="tulip" action="data:text/html," method="GET">
+ <input name='footulip' value='footulip'>
+ <input type='submit' id='is2' formtarget='frame6'>
+ </form>
+ <form action="data:text/html," method="GET">
+ <input name='tulipfoobar' value='tulipfoobar'>
+ <input type='image' id='ii2' formtarget='frame7'>
+ </form>
+ <form action="data:text/html," method="GET">
+ <input name='tulipbar' value='tulipbar'>
+ <button type='submit' id='bs2' formtarget='frame8'>submit</button>
+ </form>
+
+ <!-- check that a which is not a submit control do not use @formtarget -->
+ <form target='frame9' action="data:text/html," method="GET">
+ <input id='enter' name='input' value='enter' formtarget='frame6'>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 566046 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ setTimeout(runTests, 0);
+});
+
+var gTestResults = {
+ frame1: "data:text/html,?foo=foo",
+ frame2: "data:text/html,?bar=bar",
+ frame3: "data:text/html,?tulip=tulip",
+ frame4: "data:text/html,?foobar=foobar&x=0&y=0",
+ frame5: "data:text/html,?tulip2=tulip2",
+ frame5bis: "data:text/html,?tulip3=tulip3",
+ frame6: "data:text/html,?footulip=footulip",
+ frame7: "data:text/html,?tulipfoobar=tulipfoobar&x=0&y=0",
+ frame8: "data:text/html,?tulipbar=tulipbar",
+ frame9: "data:text/html,?input=enter",
+};
+
+var gPendingLoad = 0; // Has to be set after depending on the frames number.
+
+function runTests()
+{
+ // Check the target IDL attribute.
+ for (var i=0; i<document.forms.length; ++i) {
+ var testValue = document.forms[i].getAttribute('target');
+ is(document.forms[i].target, testValue ? testValue : "",
+ "target IDL attribute should reflect the target content attribute");
+ }
+
+ // We add a load event for the frames which will be called when the forms
+ // will be submitted.
+ var frames = [ document.getElementById('frame1'),
+ document.getElementById('frame2'),
+ document.getElementById('frame3'),
+ document.getElementById('frame4'),
+ document.getElementById('frame5'),
+ document.getElementById('frame5bis'),
+ document.getElementById('frame6'),
+ document.getElementById('frame7'),
+ document.getElementById('frame8'),
+ document.getElementById('frame9'),
+ ];
+ gPendingLoad = frames.length;
+
+ for (var i=0; i<frames.length; i++) {
+ frames[i].setAttribute('onload', "frameLoaded(this);");
+ }
+
+ // Submitting only the forms with a valid target.
+ document.forms[0].submit();
+ document.forms[1].submit();
+
+ /**
+ * We are going to focus each element before interacting with either for
+ * simulating the ENTER key (synthesizeKey) or a click (synthesizeMouse) or
+ * using .click(). This because it may be needed (ENTER) and because we want
+ * to have the element visible in the iframe.
+ *
+ * Focusing the first element (id='is') is launching the tests.
+ */
+ document.getElementById('is').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeMouse(document.getElementById('is'), 5, 5, {});
+ document.getElementById('ii').focus();
+ }, false);
+
+ document.getElementById('ii').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeMouse(document.getElementById('ii'), 5, 5, {});
+ document.getElementById('bs').focus();
+ }, false);
+
+ document.getElementById('bs').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeMouse(document.getElementById('bs'), 5, 5, {});
+ document.getElementById('bsbis').focus();
+ }, false);
+
+ document.getElementById('bsbis').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ document.getElementById('bsbis').click();
+ document.getElementById('is2').focus();
+ }, false);
+
+ document.getElementById('is2').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('ii2').focus();
+ }, false);
+
+ document.getElementById('ii2').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('bs2').focus();
+ }, false);
+
+ document.getElementById('bs2').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('enter').focus();
+ }, false);
+
+ document.getElementById('enter').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ }, false);
+
+ document.getElementById('is').focus();
+}
+
+function frameLoaded(aFrame) {
+ // Check if when target is unspecified, it fallback correctly to the base
+ // element target attribute.
+ is(aFrame.contentWindow.location.href, gTestResults[aFrame.name],
+ "the target attribute doesn't have the correct behavior");
+
+ if (--gPendingLoad == 0) {
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug567938-1.html b/dom/html/test/test_bug567938-1.html
new file mode 100644
index 000000000..b1c49bfb4
--- /dev/null
+++ b/dom/html/test/test_bug567938-1.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=567938
+-->
+<head>
+ <title>Test for Bug 567938</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 onload="runTests();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=567938">Mozilla Bug 567938</a>
+<p id="display"></p>
+<iframe id='iframe' name="submit_frame" style="visibility: hidden;"></iframe>
+<div id="content" style="display: none">
+ <form id='f' method='get' target='submit_frame'>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 567938 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var gTestData = ["submit", "image"];
+var gCurrentTest = 0;
+
+function initializeNextTest()
+{
+ var form = document.forms[0];
+
+ // Cleaning-up.
+ form.textContent = "";
+
+ // Add the new element.
+ var element = document.createElement("input");
+ element.id = 'i';
+ element.type = gTestData[gCurrentTest];
+ element.onclick = function() { form.submit(); return false; };
+ form.action = gTestData[gCurrentTest];
+ form.appendChild(element);
+
+ sendMouseEvent({type: 'click'}, 'i');
+}
+
+function runTests()
+{
+ document.getElementById('iframe').addEventListener('load', function(aEvent) {
+ is(frames['submit_frame'].location.href,
+ "http://mochi.test:8888/tests/dom/html/test/" + gTestData[gCurrentTest],
+ "The form should have been submitted");
+ gCurrentTest++;
+ if (gCurrentTest < gTestData.length) {
+ initializeNextTest();
+ } else {
+ aEvent.target.removeEventListener('load', arguments.callee, false);
+ SimpleTest.finish();
+ }
+ }, false);
+
+ initializeNextTest();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug567938-2.html b/dom/html/test/test_bug567938-2.html
new file mode 100644
index 000000000..38d4881ed
--- /dev/null
+++ b/dom/html/test/test_bug567938-2.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=567938
+-->
+<head>
+ <title>Test for Bug 567938</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=567938">Mozilla Bug 567938</a>
+<p id="display"></p>
+<iframe id='iframe' name="submit_frame" style="visibility: hidden;"></iframe>
+<div id="content" style="display: none">
+ <form id='f' method='get' target='submit_frame'>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 567938 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTests);
+
+var gTestData = ["submit", "image"];
+var gCurrentTest = 0;
+
+function initializeNextTest()
+{
+ var form = document.forms[0];
+
+ // Cleaning-up.
+ form.textContent = "";
+
+ // Add the new element.
+ var element = document.createElement("input");
+ element.id = 'i';
+ element.type = gTestData[gCurrentTest];
+ element.onclick = function() { form.submit(); element.type='text'; };
+ form.action = gTestData[gCurrentTest];
+ form.appendChild(element);
+
+ sendMouseEvent({type: 'click'}, 'i');
+}
+
+function runTests()
+{
+ document.getElementById('iframe').addEventListener('load', function(aEvent) {
+ is(frames['submit_frame'].location.href,
+ "http://mochi.test:8888/tests/dom/html/test/" + gTestData[gCurrentTest],
+ "The form should have been submitted");
+ gCurrentTest++;
+ if (gCurrentTest < gTestData.length) {
+ initializeNextTest();
+ } else {
+ aEvent.target.removeEventListener('load', arguments.callee, false);
+ SimpleTest.finish();
+ }
+ }, false);
+
+ initializeNextTest();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug567938-3.html b/dom/html/test/test_bug567938-3.html
new file mode 100644
index 000000000..ab78fb984
--- /dev/null
+++ b/dom/html/test/test_bug567938-3.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=567938
+-->
+<head>
+ <title>Test for Bug 567938</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=567938">Mozilla Bug 567938</a>
+<p id="display"></p>
+<iframe id='iframe' name="submit_frame" style="visibility: hidden;"></iframe>
+<div id="content" style="display: none">
+ <form id='f' method='get' target='submit_frame'>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 567938 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTests);
+
+var gTestData = ["submit", "image"];
+var gCurrentTest = 0;
+
+function initializeNextTest()
+{
+ var form = document.forms[0];
+
+ // Cleaning-up.
+ form.textContent = "";
+
+ // Add the new element.
+ var element = document.createElement("input");
+ element.id = 'i';
+ element.type = gTestData[gCurrentTest];
+ element.onclick = function() { setTimeout("document.forms[0].submit();",0); return false; };
+ form.appendChild(element);
+ form.action = gTestData[gCurrentTest];
+
+ sendMouseEvent({type: 'click'}, 'i');
+}
+
+function runTests()
+{
+ document.getElementById('iframe').addEventListener('load', function(aEvent) {
+ is(frames['submit_frame'].location.href,
+ "http://mochi.test:8888/tests/dom/html/test/" + gTestData[gCurrentTest],
+ "The form should have been submitted");
+ gCurrentTest++;
+ if (gCurrentTest < gTestData.length) {
+ initializeNextTest();
+ } else {
+ aEvent.target.removeEventListener('load', arguments.callee, false);
+ SimpleTest.finish();
+ }
+ }, false);
+
+ initializeNextTest();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug567938-4.html b/dom/html/test/test_bug567938-4.html
new file mode 100644
index 000000000..749a793ef
--- /dev/null
+++ b/dom/html/test/test_bug567938-4.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=567938
+-->
+<head>
+ <title>Test for Bug 567938</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=567938">Mozilla Bug 567938</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <input id='i' type='checkbox' onclick="return false;">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 567938 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTests);
+
+function runTests()
+{
+ document.getElementById('i').checked = false;
+
+ document.getElementById('i').addEventListener('click', function(aEvent) {
+ aEvent.target.removeEventListener('click', arguments.callee, false);
+ SimpleTest.executeSoon(function() {
+ ok(!aEvent.target.checked, "the input should not be checked");
+ SimpleTest.finish();
+ });
+ }, false);
+
+ sendMouseEvent({type: 'click'}, 'i');
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug569955.html b/dom/html/test/test_bug569955.html
new file mode 100644
index 000000000..252a4ee47
--- /dev/null
+++ b/dom/html/test/test_bug569955.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=569955
+-->
+<head>
+ <title>Test for Bug 569955</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=569955">Mozilla Bug 569955</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <input id='i' autofocus>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 569955 **/
+
+function runTests()
+{
+ isnot(document.activeElement, document.getElementById('i'),
+ "not rendered elements can't be autofocused");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ setTimeout(runTests, 0);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug573969.html b/dom/html/test/test_bug573969.html
new file mode 100644
index 000000000..308e89e51
--- /dev/null
+++ b/dom/html/test/test_bug573969.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=573969
+-->
+<head>
+ <title>Test for Bug 573969</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=573969">Mozilla Bug 573969</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <xmp id='x'></xmp>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 573969 **/
+
+var testData = [
+ '<div>foo</div>',
+ '&lt;div&gt;&lt;/div&gt;',
+];
+
+var x = document.getElementById('x');
+
+for (v of testData) {
+ x.innerHTML = v;
+ is(x.innerHTML, v, "innerHTML value should not be escaped");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug57600.html b/dom/html/test/test_bug57600.html
new file mode 100644
index 000000000..a8b976e22
--- /dev/null
+++ b/dom/html/test/test_bug57600.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=57600
+-->
+<head>
+ <title>Test for Bug 57600</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=57600">Mozilla Bug 57600</a>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 57600 **/
+SimpleTest.waitForExplicitFinish();
+var count = 0;
+function disp(win) {
+ var d = win ? win.document : self.testname.document;
+ var str = 'You should see this';
+ d.open();
+ d.write(str);
+ d.close();
+ is(d.documentElement.textContent, str, "Unexpected text");
+ if (++count == 2) {
+ SimpleTest.finish();
+ }
+}
+</script>
+</pre>
+<p id="display">
+ <iframe src="javascript:'<body onload=&quot;this.onerror = parent.onerror; parent.disp(self)&quot;></body>'">
+ </iframe>
+ <iframe name="testname" src="javascript:'<body onload=&quot;this.onerror = parent.onerror; parent.disp()&quot;></body>'">
+ </iframe>
+</p>
+</body>
+</html>
diff --git a/dom/html/test/test_bug579079.html b/dom/html/test/test_bug579079.html
new file mode 100644
index 000000000..2f098b3cf
--- /dev/null
+++ b/dom/html/test/test_bug579079.html
@@ -0,0 +1,43 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=579079
+-->
+<head>
+ <title>Test for Bug 579079</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=579079">Mozilla Bug 579079</a>
+
+<div id="foo">
+ <img name="img1">
+ <form name="form1"></form>
+ <applet name="applet1"></applet>
+ <embed name="embed1"></embed>
+ <object name="object1"></object>
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var img = document.img1;
+var form = document.form1;
+var applet = document.applet1;
+var embed = document.embed1;
+var object = document.object1;
+$("foo").innerHTML = $("foo").innerHTML;
+isnot(document.img1, img);
+ok(document.img1 instanceof HTMLImageElement);
+isnot(document.form1, form);
+ok(document.form1 instanceof HTMLFormElement);
+isnot(document.applet1, applet);
+ok(document.applet1 instanceof HTMLAppletElement);
+isnot(document.embed1, embed);
+ok(document.embed1 instanceof HTMLEmbedElement);
+isnot(document.object1, object);
+ok(document.object1 instanceof HTMLObjectElement);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug582412-1.html b/dom/html/test/test_bug582412-1.html
new file mode 100644
index 000000000..4883a3b76
--- /dev/null
+++ b/dom/html/test/test_bug582412-1.html
@@ -0,0 +1,209 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=566160
+-->
+<head>
+ <title>Test for Bug 566160</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=566160">Mozilla Bug 566160</a>
+<p id="display"></p>
+<style>
+ iframe { width: 130px; height: 100px;}
+</style>
+<iframe name='frame1' id='frame1'></iframe>
+<iframe name='frame2' id='frame2'></iframe>
+<iframe name='frame3' id='frame3'></iframe>
+<iframe name='frame3bis' id='frame3bis'></iframe>
+<iframe name='frame4' id='frame4'></iframe>
+<iframe name='frame5' id='frame5'></iframe>
+<iframe name='frame6' id='frame6'></iframe>
+<iframe name='frame7' id='frame7'></iframe>
+<iframe name='frame8' id='frame8'></iframe>
+<iframe name='frame9' id='frame9'></iframe>
+<div id="content">
+ <!-- submit controls with formaction that are validated with a CLICK -->
+ <form target="frame1" action="data:text/html," method="POST">
+ <input name='foo' value='foo'>
+ <input type='submit' id='is' formmethod="GET">
+ </form>
+ <form target="frame2" action="data:text/html," method="POST">
+ <input name='bar' value='bar'>
+ <input type='image' id='ii' formmethod="GET">
+ </form>
+ <form target="frame3" action="data:text/html," method="POST">
+ <input name='tulip' value='tulip'>
+ <button type='submit' id='bs' formmethod="GET">submit</button>
+ </form>
+ <form target="frame3bis" action="data:text/html," method="POST">
+ <input name='tulipbis' value='tulipbis'>
+ <button type='submit' id='bsbis' formmethod="GET">submit</button>
+ </form>
+
+ <!-- submit controls with formaction that are validated with ENTER -->
+ <form target="frame4" action="data:text/html," method="POST">
+ <input name='footulip' value='footulip'>
+ <input type='submit' id='is2' formmethod="GET">
+ </form>
+ <form target="frame5" action="data:text/html," method="POST">
+ <input name='foobar' value='foobar'>
+ <input type='image' id='ii2' formmethod="GET">
+ </form>
+ <form target="frame6" action="data:text/html," method="POST">
+ <input name='tulip2' value='tulip2'>
+ <button type='submit' id='bs2' formmethod="GET">submit</button>
+ </form>
+
+ <!-- check that when submitting a from from an element
+ which is not a submit control, @formaction isn't used -->
+ <form target='frame7' action="data:text/html," method="GET">
+ <input id='enter' name='input' value='enter' formmethod="POST">
+ </form>
+
+ <!-- If formmethod isn't set, it's default value shouldn't be used -->
+ <form target="frame8" action="data:text/html," method="POST">
+ <input name='tulip8' value='tulip8'>
+ <input type='submit' id='i8'>
+ </form>
+
+ <!-- If formmethod is set but has an invalid value, the default value should
+ be used. -->
+ <form target="frame9" action="data:text/html," method="POST">
+ <input name='tulip9' value='tulip9'>
+ <input type='submit' id='i9' formmethod="">
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 566160 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ setTimeout(runTests, 0);
+});
+
+var gTestResults = {
+ frame1: "data:text/html,?foo=foo",
+ frame2: "data:text/html,?bar=bar&x=0&y=0",
+ frame3: "data:text/html,?tulip=tulip",
+ frame3bis: "data:text/html,?tulipbis=tulipbis",
+ frame4: "data:text/html,?footulip=footulip",
+ frame5: "data:text/html,?foobar=foobar&x=0&y=0",
+ frame6: "data:text/html,?tulip2=tulip2",
+ frame7: "data:text/html,?input=enter",
+ frame8: "data:text/html,",
+ frame9: "data:text/html,?tulip9=tulip9",
+};
+
+var gPendingLoad = 0; // Has to be set after depending on the frames number.
+
+function runTests()
+{
+ // We add a load event for the frames which will be called when the forms
+ // will be submitted.
+ var frames = [ document.getElementById('frame1'),
+ document.getElementById('frame2'),
+ document.getElementById('frame3'),
+ document.getElementById('frame3bis'),
+ document.getElementById('frame4'),
+ document.getElementById('frame5'),
+ document.getElementById('frame6'),
+ document.getElementById('frame7'),
+ document.getElementById('frame8'),
+ document.getElementById('frame9'),
+ ];
+ gPendingLoad = frames.length;
+
+ for (var i=0; i<frames.length; i++) {
+ frames[i].setAttribute('onload', "frameLoaded(this);");
+ }
+
+ /**
+ * We are going to focus each element before interacting with either for
+ * simulating the ENTER key (synthesizeKey) or a click (synthesizeMouse) or
+ * using .click(). This because it may be needed (ENTER) and because we want
+ * to have the element visible in the iframe.
+ *
+ * Focusing the first element (id='is') is launching the tests.
+ */
+ document.getElementById('is').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeMouse(document.getElementById('is'), 5, 5, {});
+ document.getElementById('ii').focus();
+ }, false);
+
+ document.getElementById('ii').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeMouse(document.getElementById('ii'), 5, 5, {});
+ document.getElementById('bs').focus();
+ }, false);
+
+ document.getElementById('bs').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeMouse(document.getElementById('bs'), 5, 5, {});
+ document.getElementById('bsbis').focus();
+ }, false);
+
+ document.getElementById('bsbis').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ document.getElementById('bsbis').click();
+ document.getElementById('is2').focus();
+ }, false);
+
+ document.getElementById('is2').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('ii2').focus();
+ }, false);
+
+ document.getElementById('ii2').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('bs2').focus();
+ }, false);
+
+ document.getElementById('bs2').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('enter').focus();
+ }, false);
+
+ document.getElementById('enter').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('i8').focus();
+ }, false);
+
+ document.getElementById('i8').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('i9').focus();
+ }, false);
+
+ document.getElementById('i9').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ }, false);
+
+ document.getElementById('is').focus();
+}
+
+function frameLoaded(aFrame) {
+ // Check if formaction/action has the correct behavior.
+ is(aFrame.contentWindow.location.href, gTestResults[aFrame.name],
+ "the method/formmethod attribute doesn't have the correct behavior");
+
+ if (--gPendingLoad == 0) {
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug582412-2.html b/dom/html/test/test_bug582412-2.html
new file mode 100644
index 000000000..59175679c
--- /dev/null
+++ b/dom/html/test/test_bug582412-2.html
@@ -0,0 +1,209 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=566160
+-->
+<head>
+ <title>Test for Bug 566160</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=566160">Mozilla Bug 566160</a>
+<p id="display"></p>
+<style>
+ iframe { width: 130px; height: 100px;}
+</style>
+<iframe name='frame1' id='frame1'></iframe>
+<iframe name='frame2' id='frame2'></iframe>
+<iframe name='frame3' id='frame3'></iframe>
+<iframe name='frame3bis' id='frame3bis'></iframe>
+<iframe name='frame4' id='frame4'></iframe>
+<iframe name='frame5' id='frame5'></iframe>
+<iframe name='frame6' id='frame6'></iframe>
+<iframe name='frame7' id='frame7'></iframe>
+<iframe name='frame8' id='frame8'></iframe>
+<iframe name='frame9' id='frame9'></iframe>
+<div id="content">
+ <!-- submit controls with formaction that are validated with a CLICK -->
+ <form target="frame1" action="form_submit_server.sjs" method="POST">
+ <input name='foo' value='foo'>
+ <input type='submit' id='is' formenctype='multipart/form-data'>
+ </form>
+ <form target="frame2" action="form_submit_server.sjs" method="POST">
+ <input name='bar' value='bar'>
+ <input type='image' id='ii' formenctype='multipart/form-data'>
+ </form>
+ <form target="frame3" action="form_submit_server.sjs" method="POST">
+ <input name='tulip' value='tulip'>
+ <button type='submit' id='bs' formenctype="multipart/form-data">submit</button>
+ </form>
+ <form target="frame3bis" action="form_submit_server.sjs" method="POST">
+ <input name='tulipbis' value='tulipbis'>
+ <button type='submit' id='bsbis' formenctype="multipart/form-data">submit</button>
+ </form>
+
+ <!-- submit controls with formaction that are validated with ENTER -->
+ <form target="frame4" action="form_submit_server.sjs" method="POST">
+ <input name='footulip' value='footulip'>
+ <input type='submit' id='is2' formenctype="multipart/form-data">
+ </form>
+ <form target="frame5" action="form_submit_server.sjs" method="POST">
+ <input name='foobar' value='foobar'>
+ <input type='image' id='ii2' formenctype="multipart/form-data">
+ </form>
+ <form target="frame6" action="form_submit_server.sjs" method="POST">
+ <input name='tulip2' value='tulip2'>
+ <button type='submit' id='bs2' formenctype="multipart/form-data">submit</button>
+ </form>
+
+ <!-- check that when submitting a from from an element
+ which is not a submit control, @formaction isn't used -->
+ <form target='frame7' action="form_submit_server.sjs" method="POST">
+ <input id='enter' name='input' value='enter' formenctype="multipart/form-data">
+ </form>
+
+ <!-- If formenctype isn't set, it's default value shouldn't be used -->
+ <form target="frame8" action="form_submit_server.sjs" method="POST" enctype="multipart/form-data">
+ <input name='tulip8' value='tulip8'>
+ <input type='submit' id='i8'>
+ </form>
+
+ <!-- If formenctype is set but has an invalid value, the default value should
+ be used. -->
+ <form target="frame9" action="form_submit_server.sjs" method="POST" enctype="multipart/form-data">
+ <input name='tulip9' value='tulip9'>
+ <input type='submit' id='i9' formenctype="">
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 566160 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ setTimeout(runTests, 0);
+});
+
+var gTestResults = {
+ frame1: '[{\"headers\":{\"Content-Disposition\":\"form-data; name=\\\"foo\\\"\"},\"body\":\"foo\"}]',
+ frame2: '[{\"headers\":{\"Content-Disposition\":\"form-data; name=\\\"bar\\\"\"},\"body\":\"bar\"},{\"headers\":{\"Content-Disposition\":\"form-data; name=\\\"x\\\"\"},\"body\":\"0\"},{\"headers\":{\"Content-Disposition\":\"form-data; name=\\\"y\\\"\"},\"body\":\"0\"}]',
+ frame3: '[{\"headers\":{\"Content-Disposition\":\"form-data; name=\\\"tulip\\\"\"},\"body\":\"tulip\"}]',
+ frame3bis: '[{\"headers\":{\"Content-Disposition\":\"form-data; name=\\\"tulipbis\\\"\"},\"body\":\"tulipbis\"}]',
+ frame4: '[{\"headers\":{\"Content-Disposition\":\"form-data; name=\\\"footulip\\\"\"},\"body\":\"footulip\"}]',
+ frame5: '[{\"headers\":{\"Content-Disposition\":\"form-data; name=\\\"foobar\\\"\"},\"body\":\"foobar\"},{\"headers\":{\"Content-Disposition\":\"form-data; name=\\\"x\\\"\"},\"body\":\"0\"},{\"headers\":{\"Content-Disposition\":\"form-data; name=\\\"y\\\"\"},\"body\":\"0\"}]',
+ frame6: '[{\"headers\":{\"Content-Disposition\":\"form-data; name=\\\"tulip2\\\"\"},\"body\":\"tulip2\"}]',
+ frame7: '[]',
+ frame8: '[{\"headers\":{\"Content-Disposition\":\"form-data; name=\\\"tulip8\\\"\"},\"body\":\"tulip8\"}]',
+ frame9: '[]',
+};
+
+var gPendingLoad = 0; // Has to be set after depending on the frames number.
+
+function runTests()
+{
+ // We add a load event for the frames which will be called when the forms
+ // will be submitted.
+ var frames = [ document.getElementById('frame1'),
+ document.getElementById('frame2'),
+ document.getElementById('frame3'),
+ document.getElementById('frame3bis'),
+ document.getElementById('frame4'),
+ document.getElementById('frame5'),
+ document.getElementById('frame6'),
+ document.getElementById('frame7'),
+ document.getElementById('frame8'),
+ document.getElementById('frame9'),
+ ];
+ gPendingLoad = frames.length;
+
+ for (var i=0; i<frames.length; i++) {
+ frames[i].setAttribute('onload', "frameLoaded(this);");
+ }
+
+ /**
+ * We are going to focus each element before interacting with either for
+ * simulating the ENTER key (synthesizeKey) or a click (synthesizeMouse) or
+ * using .click(). This because it may be needed (ENTER) and because we want
+ * to have the element visible in the iframe.
+ *
+ * Focusing the first element (id='is') is launching the tests.
+ */
+ document.getElementById('is').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeMouse(document.getElementById('is'), 5, 5, {});
+ document.getElementById('ii').focus();
+ }, false);
+
+ document.getElementById('ii').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeMouse(document.getElementById('ii'), 5, 5, {});
+ document.getElementById('bs').focus();
+ }, false);
+
+ document.getElementById('bs').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeMouse(document.getElementById('bs'), 5, 5, {});
+ document.getElementById('bsbis').focus();
+ }, false);
+
+ document.getElementById('bsbis').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ document.getElementById('bsbis').click();
+ document.getElementById('is2').focus();
+ }, false);
+
+ document.getElementById('is2').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('ii2').focus();
+ }, false);
+
+ document.getElementById('ii2').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('bs2').focus();
+ }, false);
+
+ document.getElementById('bs2').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('enter').focus();
+ }, false);
+
+ document.getElementById('enter').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('i8').focus();
+ }, false);
+
+ document.getElementById('i8').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ document.getElementById('i9').focus();
+ }, false);
+
+ document.getElementById('i9').addEventListener('focus', function(aEvent) {
+ aEvent.target.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey("VK_RETURN", {});
+ }, false);
+
+ document.getElementById('is').focus();
+}
+
+function frameLoaded(aFrame) {
+ // Check if formaction/action has the correct behavior.
+ is(aFrame.contentDocument.documentElement.textContent, gTestResults[aFrame.name],
+ "the enctype/formenctype attribute doesn't have the correct behavior");
+
+ if (--gPendingLoad == 0) {
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug583514.html b/dom/html/test/test_bug583514.html
new file mode 100644
index 000000000..20e7824e6
--- /dev/null
+++ b/dom/html/test/test_bug583514.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=583514
+-->
+<head>
+ <title>Test for Bug 583514</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=583514">Mozilla Bug 583514</a>
+<p id="display"></p>
+<div id="content">
+ <input>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 583514 **/
+
+var gExpectDivClick = false;
+var gExpectInputClick = false;
+
+var div = document.getElementById('content');
+var input = document.getElementsByTagName('input')[0];
+
+div.addEventListener('click', function() {
+ ok(gExpectDivClick, "click event received on div and expected status was: " +
+ gExpectDivClick);
+}, false);
+
+input.addEventListener('click', function() {
+ ok(gExpectInputClick, "click event received on input and expected status was: " +
+ gExpectInputClick);
+}, false);
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function() {
+ var body = document.body;
+
+ body.addEventListener('click', function(aEvent) {
+ if (aEvent.target == input) {
+ body.removeEventListener('click', arguments.callee, false);
+ }
+
+ ok(true, "click event received on body");
+
+ SimpleTest.executeSoon(function() {
+ isnot(document.activeElement, input, "input shouldn't have been focused");
+ isnot(document.activeElement, div, "div shouldn't have been focused");
+
+ if (aEvent.target == input) {
+ SimpleTest.finish();
+ } else {
+ gExpectDivClick = true;
+ gExpectInputClick = true;
+ input.click();
+ }
+ });
+ }, false);
+
+ gExpectDivClick = true;
+ div.click();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug583533.html b/dom/html/test/test_bug583533.html
new file mode 100644
index 000000000..1042b6f09
--- /dev/null
+++ b/dom/html/test/test_bug583533.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=583533
+-->
+ <head>
+ <title>Test for Bug 583514</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=583533">Mozilla Bug 583533</a>
+ <p id="display"></p>
+ <div id="content">
+ <div id="e" accesskey="a">
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+ /** Test for Bug 583533 **/
+
+ var sbs = SpecialPowers.Cc['@mozilla.org/intl/stringbundle;1'].
+ getService(SpecialPowers.Ci.nsIStringBundleService);
+ var bundle = sbs.createBundle("chrome://global-platform/locale/platformKeys.properties");
+
+ var shiftText = bundle.GetStringFromName("VK_SHIFT");
+ var altText = bundle.GetStringFromName("VK_ALT");
+ var controlText = bundle.GetStringFromName("VK_CONTROL");
+ var metaText = bundle.GetStringFromName("VK_META");
+ var separatorText = bundle.GetStringFromName("MODIFIER_SEPARATOR");
+
+ var modifier = SpecialPowers.getIntPref("ui.key.contentAccess");
+
+ var isShift;
+ var isAlt;
+ var isControl;
+ var isMeta;
+
+ is(modifier < 16 && modifier >= 0, true, "Modifier in range");
+
+ // There are no consts for the mask of this prefs.
+ if (modifier & 8) {
+ isMeta = true;
+ }
+ if (modifier & 1) {
+ isShift = true;
+ }
+ if (modifier & 2) {
+ isControl = true;
+ }
+ if (modifier & 4) {
+ isAlt = true;
+ }
+
+ var label = "";
+
+ if (isControl)
+ label += controlText + separatorText;
+ if (isMeta)
+ label += metaText + separatorText;
+ if (isAlt)
+ label += altText + separatorText;
+ if (isShift)
+ label += shiftText + separatorText;
+
+ label += document.getElementById("e").accessKey;
+
+ is(label, document.getElementById("e").accessKeyLabel, "JS and C++ agree on accessKeyLabel");
+
+ /** Test for Bug 808964 **/
+
+ var div = document.createElement("div");
+ document.body.appendChild(div);
+
+ is(div.accessKeyLabel, "", "accessKeyLabel should be empty string");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug586763.html b/dom/html/test/test_bug586763.html
new file mode 100644
index 000000000..9ecf00ea8
--- /dev/null
+++ b/dom/html/test/test_bug586763.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=586763
+-->
+<title>Test for Bug 586763</title>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=586763">Mozilla Bug 586763</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script>
+/** Test for Bug 586763 **/
+var tests = [
+ ["ol", "start", 1],
+ ["li", "value", 0],
+ ["object", "hspace", 0],
+ ["object", "vspace", 0],
+ ["img", "hspace", 0],
+ ["img", "vspace", 0],
+ ["video", "height", 0],
+ ["video", "width", 0],
+ ["pre", "width", 0],
+ ["textarea", "cols", 20],
+ ["textarea", "rows", 2],
+ ["span", "tabIndex", -1],
+ ["frame", "tabIndex", 0],
+ ["a", "tabIndex", 0],
+ ["area", "tabIndex", 0],
+ ["button", "tabIndex", 0],
+ ["input", "tabIndex", 0],
+ ["object", "tabIndex", -1],
+ ["select", "tabIndex", 0],
+ ["textarea", "tabIndex", 0],
+];
+
+for (var i = 0; i < tests.length; i++) {
+ is(document.createElement(tests[i][0])[tests[i][1]], tests[i][2], "Reflected attribute " + tests[i][0] + "." + tests[i][1] + " should default to " + tests[i][2]);
+}
+</script>
+</pre>
diff --git a/dom/html/test/test_bug586786.html b/dom/html/test/test_bug586786.html
new file mode 100644
index 000000000..17d95c2a5
--- /dev/null
+++ b/dom/html/test/test_bug586786.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=586786
+-->
+<head>
+ <title>Test for Bug 586786</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=586786">Mozilla Bug 586786</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 586786 **/
+
+var elements = ["col", "colgroup", "tbody", "tfoot", "thead", "tr", "td", "th"];
+
+for(var i = 0; i < elements.length; i++)
+{
+ reflectString({
+ element: document.createElement(elements[i]),
+ attribute: "align",
+ otherValues: [ "left", "right", "center", "justify", "char" ]
+ });
+
+ reflectString({
+ element: document.createElement(elements[i]),
+ attribute: "vAlign",
+ otherValues: [ "top", "middle", "bottom", "baseline" ]
+ });
+
+ reflectString({
+ element: document.createElement(elements[i]),
+ attribute: {idl: "ch", content: "char"}
+ });
+}
+
+// table.border, table.width
+reflectString({
+ element: document.createElement("table"),
+ attribute: "border"
+});
+
+reflectString({
+ element: document.createElement("table"),
+ attribute: "width"
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug587469.html b/dom/html/test/test_bug587469.html
new file mode 100644
index 000000000..b0e941296
--- /dev/null
+++ b/dom/html/test/test_bug587469.html
@@ -0,0 +1,41 @@
+<!-- Quirks -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=587469
+-->
+<head>
+ <title>Test for Bug 587469</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=587469">Mozilla Bug 587469</a>
+<p id="display">
+<map name=a></map>
+<map name=a><area shape=rect coords=25,25,75,75 href=#fail></map>
+<img usemap=#a src=image.png>
+</p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 587469 **/
+SimpleTest.waitForExplicitFinish();
+function finish() {
+ is(location.hash, "", "Should not have changed the hash.");
+ SimpleTest.finish();
+}
+SimpleTest.waitForFocus(function() {
+ synthesizeMouse(document.getElementsByTagName("img")[0], 50, 50, {});
+ // Hit the event loop twice before doing the test
+ setTimeout(function() {
+ setTimeout(finish, 0);
+ }, 0);
+});
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug589.html b/dom/html/test/test_bug589.html
new file mode 100644
index 000000000..a5d0eabdb
--- /dev/null
+++ b/dom/html/test/test_bug589.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=589
+-->
+<head>
+ <title>Test for Bug 589</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">
+.letters {list-style-type: upper-alpha;}
+.numbers {list-style-type: decimal;}
+</style>
+
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=589">Mozilla Bug 589</a>
+<p id="display"></p>
+<div id="content" >
+
+<OL id="thelist" class="letters">
+<LI id="liA">This list should feature...
+<LI id="liB">...letters for each item...
+<LI id="li3" class="numbers">...except this one.
+</OL>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 589 **/
+
+is(computedStyle($("liA"),"list-style-type"),"upper-alpha");
+is(computedStyle($("liB"),"list-style-type"),"upper-alpha");
+is(computedStyle($("li3"),"list-style-type"),"decimal");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug590353-1.html b/dom/html/test/test_bug590353-1.html
new file mode 100644
index 000000000..c7aa52ab9
--- /dev/null
+++ b/dom/html/test/test_bug590353-1.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=590353
+-->
+<head>
+ <title>Test for Bug 590353</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=590353">Mozilla Bug 590353</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 590353 **/
+
+var testData = ['checkbox', 'radio'];
+
+for (var data of testData) {
+ var e = document.createElement('input');
+ e.type = data;
+ e.checked = true;
+ e.value = "foo";
+
+ is(e.value, "foo", "foo should be the new " + data + "value");
+ is(e.getAttribute('value'), "foo", "foo should be the new " + data +
+ " value attribute value");
+ ok(e.checked, data + " should still be checked");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug590353-2.html b/dom/html/test/test_bug590353-2.html
new file mode 100644
index 000000000..db44efab1
--- /dev/null
+++ b/dom/html/test/test_bug590353-2.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=590353
+-->
+<head>
+ <title>Test for Bug 590353</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=590353">Mozilla Bug 590353</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 590353 **/
+
+var testData = [
+ [ "text", "foo", "" ],
+ [ "email", "foo@bar.com", "" ],
+ [ "url", "http:///foo.com", "" ],
+ [ "tel", "555 555 555 555", "" ],
+ [ "search", "foo", "" ],
+ [ "password", "secret", "" ],
+ [ "hidden", "foo", "foo" ],
+ [ "button", "foo", "foo" ],
+ [ "reset", "foo", "foo" ],
+ [ "submit", "foo", "foo" ],
+ [ "checkbox", true, false ],
+ [ "radio", true, false ],
+ [ "file", "590353_file", "" ],
+];
+
+function createFileWithData(fileName, fileData) {
+ return new File([new Blob([fileData], { type: "text/plain" })], fileName);
+}
+
+var content = document.getElementById('content');
+var form = document.createElement('form');
+content.appendChild(form);
+
+for (var data of testData) {
+ var e = document.createElement('input');
+ e.type = data[0];
+
+ if (data[0] == 'checkbox' || data[0] == 'radio') {
+ e.checked = data[1];
+ } else if (data[0] == 'file') {
+ var file = createFileWithData(data[1], "file content");
+ SpecialPowers.wrap(e).mozSetFileArray([file]);
+ } else {
+ e.value = data[1];
+ }
+
+ form.appendChild(e);
+}
+
+form.reset();
+
+var size = form.elements.length;
+for (var i=0; i<size; ++i) {
+ var e = form.elements[i];
+
+ if (e.type == 'radio' || e.type == 'checkbox') {
+ is(e.checked, testData[i][2],
+ "the element checked value should be " + testData[i][2]);
+ } else {
+ is(e.value, testData[i][2],
+ "the element value should be " + testData[i][2]);
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug590363.html b/dom/html/test/test_bug590363.html
new file mode 100644
index 000000000..3e72b542c
--- /dev/null
+++ b/dom/html/test/test_bug590363.html
@@ -0,0 +1,133 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=590363
+-->
+<head>
+ <title>Test for Bug 590363</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=590363">Mozilla Bug 590363</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 590363 **/
+
+var testData = [
+ /* type to test | is the value reset when changing to file then reverting */
+ [ "button", false ],
+ [ "checkbox", false ],
+ [ "hidden", false ],
+ [ "reset", false ],
+ [ "image", false ],
+ [ "radio", false ],
+ [ "submit", false ],
+ [ "tel", true ],
+ [ "text", true ],
+ [ "url", true ],
+ [ "email", true ],
+ [ "search", true ],
+ [ "password", true ],
+ [ "number", true ],
+ [ "date", true ],
+ [ "time", true ],
+ [ "range", true ],
+ [ "color", true ],
+ [ 'month', true ],
+ [ 'week', true ],
+ [ 'datetime-local', true ]
+ // 'file' is treated separatly.
+];
+
+var nonTrivialSanitizing = [ 'number', 'date', 'time', 'color', 'month', 'week',
+ 'datetime-local' ];
+
+var length = testData.length;
+for (var i=0; i<length; ++i) {
+ for (var j=0; j<length; ++j) {
+ var e = document.createElement('input');
+ e.type = testData[i][0];
+
+ var expectedValue;
+
+ // range will sanitize its value to 50 (the default) if it isn't a valid
+ // number. We need to handle that specially.
+ if (testData[j][0] == 'range' || testData[i][0] == 'range') {
+ if (testData[j][0] == 'date' || testData[j][0] == 'time' ||
+ testData[j][0] == 'month' || testData[j][0] == 'week' ||
+ testData[j][0] == 'datetime-local') {
+ expectedValue = '';
+ } else if (testData[j][0] == 'color') {
+ expectedValue = '#000000';
+ } else {
+ expectedValue = '50';
+ }
+ } else if (testData[i][0] == 'color' || testData[j][0] == 'color') {
+ if (testData[j][0] == 'number' || testData[j][0] == 'date' ||
+ testData[j][0] == 'time' || testData[j][0] == 'month' ||
+ testData[j][0] == 'week' || testData[j][0] == 'datetime-local') {
+ expectedValue = ''
+ } else {
+ expectedValue = '#000000';
+ }
+ } else if (nonTrivialSanitizing.indexOf(testData[i][0]) != -1 &&
+ nonTrivialSanitizing.indexOf(testData[j][0]) != -1) {
+ expectedValue = '';
+ } else if (testData[i][0] == 'number' || testData[j][0] == 'number') {
+ expectedValue = '42';
+ } else if (testData[i][0] == 'date' || testData[j][0] == 'date') {
+ expectedValue = '2012-12-21';
+ } else if (testData[i][0] == 'time' || testData[j][0] == 'time') {
+ expectedValue = '21:21';
+ } else if (testData[i][0] == 'month' || testData[j][0] == 'month') {
+ expectedValue = '2013-03';
+ } else if (testData[i][0] == 'week' || testData[j][0] == 'week') {
+ expectedValue = '2016-W35';
+ } else if (testData[i][0] == 'datetime-local' ||
+ testData[j][0] == 'datetime-local') {
+ expectedValue = '2016-11-07T16:40';
+ } else {
+ expectedValue = "foo";
+ }
+ e.value = expectedValue;
+
+ e.type = testData[j][0];
+ is(e.value, expectedValue, ".value should still return the same value after " +
+ "changing type from " + testData[i][0] + " to " + testData[j][0]);
+ }
+}
+
+// For type='file' .value doesn't behave the same way.
+// We are just going to check that we do not loose the value.
+for (var data of testData) {
+ var e = document.createElement('input');
+ e.type = data[0];
+ e.value = 'foo';
+ e.type = 'file';
+ e.type = data[0];
+
+ if (data[0] == 'range') {
+ is(e.value, '50', ".value should still return the same value after " +
+ "changing type from " + data[0] + " to 'file' then reverting to " + data[0]);
+ } else if (data[0] == 'color') {
+ is(e.value, '#000000', ".value should have been reset to the default color after " +
+ "changing type from " + data[0] + " to 'file' then reverting to " + data[0]);
+ } else if (data[1]) {
+ is(e.value, '', ".value should have been reset to the empty string after " +
+ "changing type from " + data[0] + " to 'file' then reverting to " + data[0]);
+ } else {
+ is(e.value, 'foo', ".value should still return the same value after " +
+ "changing type from " + data[0] + " to 'file' then reverting to " + data[0]);
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug592802.html b/dom/html/test/test_bug592802.html
new file mode 100644
index 000000000..c2c9df9ed
--- /dev/null
+++ b/dom/html/test/test_bug592802.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=592802
+-->
+<head>
+ <title>Test for Bug 592802</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=592802">Mozilla Bug 592802</a>
+<p id="display"></p>
+<div id="content">
+ <input id='a' type='file'>
+ <input id='a2' type='file'>
+</div>
+<button id='b' onclick="document.getElementById('a').click();">Show Filepicker</button>
+<button id='b2' onclick="document.getElementById('a2').click();">Show Filepicker</button>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 592802 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+var testData = [
+/* visibility | display | multiple */
+ [ "", "", false ],
+ [ "hidden", "", false ],
+ [ "", "none", false ],
+ [ "", "", true ],
+ [ "hidden", "", true ],
+ [ "", "none", true ],
+];
+
+var testCounter = 0;
+var testNb = testData.length;
+
+function finished()
+{
+ MockFilePicker.cleanup();
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(function() {
+ // mockFilePicker will simulate a cancel for the first time the file picker will be shown.
+ MockFilePicker.returnValue = MockFilePicker.returnCancel;
+
+ var b2 = document.getElementById('b2');
+ b2.focus(); // Be sure the element is visible.
+ document.getElementById('b2').addEventListener("change", function(aEvent) {
+ aEvent.target.removeEventListener("change", arguments.callee, false);
+ ok(false, "When cancel is received, change should not fire");
+ }, false);
+ b2.click();
+
+ // Now, we can launch tests when file picker isn't canceled.
+ MockFilePicker.useBlobFile();
+ MockFilePicker.returnValue = MockFilePicker.returnOK;
+
+ var b = document.getElementById('b');
+ b.focus(); // Be sure the element is visible.
+
+ document.getElementById('a').addEventListener("change", function(aEvent) {
+ ok(true, "change event correctly sent");
+ ok(aEvent.bubbles, "change event should bubble");
+ ok(!aEvent.cancelable, "change event should not be cancelable");
+ testCounter++;
+
+ if (testCounter >= testNb) {
+ aEvent.target.removeEventListener("change", arguments.callee, false);
+ SimpleTest.executeSoon(finished);
+ } else {
+ var data = testData[testCounter];
+ var a = document.getElementById('a');
+ a.style.visibility = data[0];
+ a.style.display = data[1];
+ a.multiple = data[2];
+
+ SimpleTest.executeSoon(function() {
+ b.click();
+ });
+ }
+ }, false);
+
+ b.click();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug593689.html b/dom/html/test/test_bug593689.html
new file mode 100644
index 000000000..782601f35
--- /dev/null
+++ b/dom/html/test/test_bug593689.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=593689
+-->
+<head>
+ <title>Test for Bug 593689</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=593689">Mozilla Bug 593689</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 593689 **/
+function testWidth(w) {
+ var img = new Image(w);
+ is(img.width, w|0, "Unexpected handling of '" + w + "' width");
+}
+
+testWidth(1);
+testWidth(0);
+testWidth("xxx");
+testWidth(null);
+testWidth(undefined);
+testWidth({});
+testWidth({ valueOf: function() { return 10; } });
+
+function testHeight(h) {
+ var img = new Image(100, h);
+ is(img.height, h|0, "Unexpected handling of '" + h + "' height");
+}
+
+testHeight(1);
+testHeight(0);
+testHeight("xxx");
+testHeight(null);
+testHeight(undefined);
+testHeight({});
+testHeight({ valueOf: function() { return 10; } });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug595429.html b/dom/html/test/test_bug595429.html
new file mode 100644
index 000000000..74535705e
--- /dev/null
+++ b/dom/html/test/test_bug595429.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=595429
+-->
+<head>
+ <title>Test for Bug 595429</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=595429">Mozilla Bug 595429</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 595429 **/
+
+var fieldset = document.createElement("fieldset");
+
+ok("name" in fieldset, "<fieldset> should have a name IDL attribute");
+
+var testData = [
+ "",
+ " ",
+ "foo",
+ "foo bar",
+];
+
+is(fieldset.getAttribute("name"), null,
+ "By default, name content attribute should be null");
+is(fieldset.name, "",
+ "By default, name IDL attribute should be the empty string");
+
+for (var data of testData) {
+ fieldset.setAttribute("name", data);
+ is(fieldset.getAttribute("name"), data,
+ "name content attribute should be " + data);
+ is(fieldset.name, data, "name IDL attribute should be " + data);
+
+ fieldset.setAttribute("name", "");
+ fieldset.name = data;
+ is(fieldset.getAttribute("name"), data,
+ "name content attribute should be " + data);
+ is(fieldset.name, data, "name IDL attribute should be " + data);
+}
+
+fieldset.removeAttribute("name");
+is(fieldset.getAttribute("name"), null,
+ "name content attribute should be null");
+is(fieldset.name, "", "name IDL attribute should be the empty string");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug595447.html b/dom/html/test/test_bug595447.html
new file mode 100644
index 000000000..015af69d4
--- /dev/null
+++ b/dom/html/test/test_bug595447.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=595447
+-->
+<head>
+ <title>Test for Bug 595447</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=595447">Mozilla Bug 595447</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 595447 **/
+
+var fieldset = document.createElement("fieldset");
+
+ok("type" in fieldset, "fieldset element should have a type IDL attribute");
+is(fieldset.type, "fieldset", "fieldset.type should return 'fieldset'");
+fieldset.type = "foo";
+is(fieldset.type, "fieldset", "fieldset.type is readonly");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug595449.html b/dom/html/test/test_bug595449.html
new file mode 100644
index 000000000..14d5e5ced
--- /dev/null
+++ b/dom/html/test/test_bug595449.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=595449
+-->
+<head>
+ <title>Test for Bug 595449</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=595449">Mozilla Bug 595449</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 595449 **/
+
+var fieldset = document.createElement("fieldset");
+
+ok("elements" in fieldset,
+ "fieldset element should have an 'elements' IDL attribute");
+
+ok(fieldset.elements instanceof HTMLCollection,
+ "fieldset.elements should be an instance of HTMLCollection");
+
+// https://www.w3.org/Bugs/Public/show_bug.cgi?id=23356
+todo(fieldset.elements instanceof HTMLFormControlsCollection,
+ "fieldset.elements should be an instance of HTMLFormControlsCollection");
+
+is(fieldset.elements.length, 0, "Nothing should be in fieldset.elements");
+
+var oldElements = fieldset.elements;
+
+is(fieldset.elements, oldElements,
+ "fieldset.elements should always return the same object");
+
+var tmpElement = document.createElement("input");
+
+fieldset.appendChild(tmpElement);
+
+is(fieldset.elements.length, 1,
+ "fieldset.elements should now contain one element");
+
+is(fieldset.elements[0], tmpElement,
+ "fieldset.elements[0] should be the input element");
+
+tmpElement.name = "foo";
+is(fieldset.elements.foo, tmpElement,
+ "we should be able to access to an element using it's name as a property on .elements");
+
+is(fieldset.elements, oldElements,
+ "fieldset.elements should always return the same object");
+
+fieldset.removeChild(tmpElement);
+
+var testData = [
+ [ "<input>", 1 , [ HTMLInputElement ] ],
+ [ "<button></button>", 1, [ HTMLButtonElement ] ],
+ [ "<button><input></button>", 2, [ HTMLButtonElement, HTMLInputElement ] ],
+ [ "<object>", 1, [ HTMLObjectElement ] ],
+ [ "<output></output>", 1, [ HTMLOutputElement ] ],
+ [ "<select></select>", 1, [ HTMLSelectElement ] ],
+ [ "<select><option>foo</option></select>", 1, [ HTMLSelectElement ] ],
+ [ "<select><option>foo</option><input></select>", 2, [ HTMLSelectElement, HTMLInputElement ] ],
+ [ "<textarea></textarea>", 1, [ HTMLTextAreaElement ] ],
+ [ "<label>foo</label>", 0 ],
+ [ "<progress>", 0 ],
+ [ "<meter>", 0 ],
+ [ "<keygen>", 1, [ HTMLSelectElement ] ],
+ [ "<legend></legend>", 0 ],
+ [ "<legend><input></legend>", 1, [ HTMLInputElement ] ],
+ [ "<legend><input></legend><legend><input></legend>", 2, [ HTMLInputElement, HTMLInputElement ] ],
+ [ "<legend><input></legend><input>", 2, [ HTMLInputElement, HTMLInputElement ] ],
+ [ "<fieldset></fieldset>", 1, [ HTMLFieldSetElement ] ],
+ [ "<fieldset><input></fieldset>", 2, [ HTMLFieldSetElement, HTMLInputElement ] ],
+ [ "<fieldset><fieldset><input></fieldset></fieldset>", 3, [ HTMLFieldSetElement, HTMLFieldSetElement, HTMLInputElement ] ],
+ [ "<button></button><fieldset></fieldset><input><keygen><object><output></output><select></select><textarea></textarea>", 8, [ HTMLButtonElement, HTMLFieldSetElement, HTMLInputElement, HTMLSelectElement, HTMLObjectElement, HTMLOutputElement, HTMLSelectElement, HTMLTextAreaElement ] ],
+];
+
+for (var data of testData) {
+ fieldset.innerHTML = data[0];
+ is(fieldset.elements.length, data[1],
+ "fieldset.elements should contain " + data[1] + " elements");
+
+ for (var i=0; i<data[1]; ++i) {
+ ok(fieldset.elements[i] instanceof data[2][i],
+ "fieldset.elements[" + i + "] should be instance of " + data[2][i])
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug596350.html b/dom/html/test/test_bug596350.html
new file mode 100644
index 000000000..a92484ef7
--- /dev/null
+++ b/dom/html/test/test_bug596350.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=596350
+-->
+<head>
+ <title>Test for Bug 596350</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=596350">Mozilla Bug 596350</a>
+<p id="display"></p>
+<div id="content">
+ <object></object>
+ <object data="iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMsALGPC/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IAAAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1JREFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jqch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0vr4MkhoXe0rZigAAAABJRU5ErkJggg=="></object>
+ <object data="data:text/html,foo"></object>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 596350 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTests);
+
+var testData = [
+// Object 0
+ [ 0, null, -1 ],
+ [ 0, "1", 1 ],
+ [ 0, "-1", -1 ],
+ [ 0, "0", 0 ],
+ [ 0, "foo", -1 ],
+// Object 1
+ [ 1, null, -1 ],
+ [ 1, "1", 1 ],
+// Object 2
+ [ 2, null, 0 ],
+ [ 2, "1", 1 ],
+ [ 2, "-1", -1 ],
+];
+
+var objects = document.getElementsByTagName("object");
+
+function runTests()
+{
+ for (var data of testData) {
+ var obj = objects[data[0]];
+
+ if (data[1]) {
+ obj.setAttribute("tabindex", data[1]);
+ }
+
+ is(obj.tabIndex, data[2], "tabIndex value should be " + data[2]);
+
+ obj.removeAttribute("tabindex");
+ }
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug596511.html b/dom/html/test/test_bug596511.html
new file mode 100644
index 000000000..e539c5b73
--- /dev/null
+++ b/dom/html/test/test_bug596511.html
@@ -0,0 +1,229 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=596511
+-->
+<head>
+ <title>Test for Bug 596511</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ select:valid { background-color: green; }
+ select:invalid { background-color: red; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=596511">Mozilla Bug 596511</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 596511 **/
+
+function checkNotSufferingFromBeingMissing(element, aTodo)
+{
+ if (aTodo) {
+ ok = todo;
+ is = todo_is;
+ }
+
+ ok(!element.validity.valueMissing,
+ "Element should not suffer from value missing");
+ ok(element.validity.valid, "Element should be valid");
+ ok(element.checkValidity(), "Element should be valid");
+
+ is(element.validationMessage, "",
+ "Validation message should be the empty string");
+
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(0, 128, 0)", ":valid pseudo-class should apply");
+
+ if (aTodo) {
+ ok = SimpleTest.ok;
+ is = SimpleTest.is;
+ }
+}
+
+function checkSufferingFromBeingMissing(element, aTodo)
+{
+ if (aTodo) {
+ ok = todo;
+ is = todo_is;
+ }
+
+ ok(element.validity.valueMissing, "Element should suffer from value missing");
+ ok(!element.validity.valid, "Element should not be valid");
+ ok(!element.checkValidity(), "Element should not be valid");
+
+ is(element.validationMessage, "Please select an item in the list.",
+ "Validation message is wrong");
+
+ is(window.getComputedStyle(element, null).getPropertyValue('background-color'),
+ "rgb(255, 0, 0)", ":invalid pseudo-class should apply");
+
+ if (aTodo) {
+ ok = SimpleTest.ok;
+ is = SimpleTest.is;
+ }
+}
+
+function checkRequiredAttribute(element)
+{
+ ok('required' in element, "select should have a required attribute");
+
+ ok(!element.required, "select required attribute should be disabled");
+ is(element.getAttribute('required'), null,
+ "select required attribute should be disabled");
+
+ element.required = true;
+ ok(element.required, "select required attribute should be enabled");
+ isnot(element.getAttribute('required'), null,
+ "select required attribute should be enabled");
+
+ element.removeAttribute('required');
+ element.setAttribute('required', '');
+ ok(element.required, "select required attribute should be enabled");
+ isnot(element.getAttribute('required'), null,
+ "select required attribute should be enabled");
+
+ element.removeAttribute('required');
+ ok(!element.required, "select required attribute should be disabled");
+ is(element.getAttribute('required'), null,
+ "select required attribute should be disabled");
+}
+
+function checkRequiredAndOptionalSelectors(element)
+{
+ is(document.querySelector("select:optional"), element,
+ "select should be optional");
+ is(document.querySelector("select:required"), null,
+ "select shouldn't be required");
+
+ element.required = true;
+
+ is(document.querySelector("select:optional"), null,
+ "select shouldn't be optional");
+ is(document.querySelector("select:required"), element,
+ "select should be required");
+
+ element.required = false;
+}
+
+function checkInvalidWhenValueMissing(element)
+{
+ checkNotSufferingFromBeingMissing(select);
+
+ element.required = true;
+ checkSufferingFromBeingMissing(select);
+
+ /**
+ * Non-multiple and size=1.
+ */
+ select.appendChild(new Option());
+ checkSufferingFromBeingMissing(select);
+
+ // When removing the required attribute, element should not be invalid.
+ element.required = false;
+ checkNotSufferingFromBeingMissing(select);
+
+ element.required = true;
+ select.options[0].textContent = "foo";
+ // TODO: having that working would require us to add a mutation observer on
+ // the select element.
+ checkNotSufferingFromBeingMissing(select, true);
+
+ select.remove(0);
+ checkSufferingFromBeingMissing(select);
+
+ select.add(new Option("foo", "foo"), null);
+ checkNotSufferingFromBeingMissing(select);
+
+ select.add(new Option(), null);
+ checkNotSufferingFromBeingMissing(select);
+
+ select.options[1].selected = true;
+ checkSufferingFromBeingMissing(select);
+
+ select.selectedIndex = 0;
+ checkNotSufferingFromBeingMissing(select);
+
+ select.selectedIndex = 1;
+ checkSufferingFromBeingMissing(select);
+
+ select.remove(1);
+ checkNotSufferingFromBeingMissing(select);
+
+ select.options[0].disabled = true;
+ // TODO: having that working would require us to add a mutation observer on
+ // the select element.
+ checkSufferingFromBeingMissing(select, true);
+
+ select.options[0].disabled = false
+ select.remove(0);
+ checkSufferingFromBeingMissing(select);
+
+ var option = new Option("foo", "foo");
+ option.disabled = true;
+ select.add(option, null);
+ select.add(new Option("bar"), null);
+ option.selected = true;
+ checkSufferingFromBeingMissing(select);
+
+ select.remove(0);
+ select.remove(0);
+
+ /**
+ * Non-multiple and size > 1.
+ * Everything should be the same except moving the selection.
+ */
+ select.multiple = false;
+ select.size = 4;
+ checkSufferingFromBeingMissing(select);
+
+ select.add(new Option("", "", true), null);
+ checkSufferingFromBeingMissing(select);
+
+ select.add(new Option("foo", "foo"), null);
+ select.remove(0);
+ checkSufferingFromBeingMissing(select);
+
+ select.options[0].selected = true;
+ checkNotSufferingFromBeingMissing(select);
+
+ select.remove(0);
+
+ /**
+ * Multiple, any size.
+ * We can select more than one element and at least needs a value.
+ */
+ select.multiple = true;
+ select.size = 4;
+ checkSufferingFromBeingMissing(select);
+
+ select.add(new Option("", "", true), null);
+ checkSufferingFromBeingMissing(select);
+
+ select.add(new Option("", "", true), null);
+ checkSufferingFromBeingMissing(select);
+
+ select.add(new Option("foo"), null);
+ checkSufferingFromBeingMissing(select);
+
+ select.options[2].selected = true;
+ checkNotSufferingFromBeingMissing(select);
+}
+
+var select = document.createElement("select");
+var content = document.getElementById('content');
+content.appendChild(select);
+
+checkRequiredAttribute(select);
+checkRequiredAndOptionalSelectors(select);
+checkInvalidWhenValueMissing(select);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug598643.html b/dom/html/test/test_bug598643.html
new file mode 100644
index 000000000..53c1df97c
--- /dev/null
+++ b/dom/html/test/test_bug598643.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=598643
+-->
+<head>
+ <title>Test for Bug 598643</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=598643">Mozilla Bug 598643</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 598643 **/
+
+function createFileWithData(fileName, fileData)
+{
+ return new File([new Blob([fileData], { type: "text/plain" })], fileName);
+}
+
+function testFileControl(aElement)
+{
+ aElement.type = 'file';
+
+ var file = createFileWithData("file_bug598643", "file content");
+ SpecialPowers.wrap(aElement).mozSetFileArray([file]);
+
+ ok(aElement.validity.valid, "the file control should be valid");
+ ok(!aElement.validity.tooLong,
+ "the file control shouldn't suffer from being too long");
+}
+
+var types = [
+ // These types can be too long.
+ [ "text", "email", "password", "url", "search", "tel" ],
+ // These types can't be too long.
+ [ "radio", "checkbox", "submit", "button", "reset", "image", "hidden",
+ 'number', 'range', 'date', 'time', 'color', 'month', 'week',
+ 'datetime-local' ],
+];
+
+var input = document.createElement("input");
+input.maxLength = 1;
+input.value = "foo";
+
+// Too long types.
+for (type of types[0]) {
+ input.type = type
+ if (type == 'email') {
+ input.value = "foo@bar.com";
+ } else if (type == 'url') {
+ input.value = 'http://foo.org';
+ }
+
+ todo(!input.validity.valid, "the element should be invalid [type=" + type + "]");
+ todo(input.validity.tooLong,
+ "the element should suffer from being too long [type=" + type + "]");
+
+ if (type == 'email' || type == 'url') {
+ input.value = 'foo';
+ }
+}
+
+// Not too long types.
+for (type of types[1]) {
+ input.type = type
+ ok(input.validity.valid, "the element should be valid [type=" + type + "]");
+ ok(!input.validity.tooLong,
+ "the element shouldn't suffer from being too long [type=" + type + "]");
+}
+
+testFileControl(input);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug598833-1.html b/dom/html/test/test_bug598833-1.html
new file mode 100644
index 000000000..6b50b8df8
--- /dev/null
+++ b/dom/html/test/test_bug598833-1.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=598833
+-->
+<head>
+ <title>Test for Bug 598833</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=598833">Mozilla Bug 598833</a>
+<p id="display">
+ <fieldset disabled>
+ <select id="s" multiple required>
+ <option>one</option>
+ </select>
+ </fieldset>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 598833 **/
+var s = $("s");
+is(s.matches(":invalid"), false, "Disabled select should not be invalid");
+is(s.matches(":valid"), false, "Disabled select should not be valid");
+var p = s.parentNode;
+p.removeChild(s);
+is(s.matches(":invalid"), true,
+ "Required valueless select not in tree should be invalid");
+is(s.matches(":valid"), false,
+ "Required valueless select not in tree should not be valid");
+p.appendChild(s);
+p.disabled = false;
+is(s.matches(":invalid"), true,
+ "Required valueless select should be invalid");
+is(s.matches(":valid"), false,
+ "Required valueless select should not be valid");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug600155.html b/dom/html/test/test_bug600155.html
new file mode 100644
index 000000000..bb10ddfb7
--- /dev/null
+++ b/dom/html/test/test_bug600155.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=600155
+-->
+<head>
+ <title>Test for Bug 600155</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=600155">Mozilla Bug 600155</a>
+<p id="display"></p>
+<div id='content' style='display:none;'>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 600155 **/
+
+var subjectForConstraintValidation = [ "input", "select", "textarea" ];
+var content = document.getElementById('content');
+
+for (var eName of subjectForConstraintValidation) {
+ var e = document.createElement(eName);
+ content.appendChild(e);
+ e.setAttribute("x-moz-errormessage", "foo");
+ if ("required" in e) {
+ e.required = true;
+ } else {
+ e.setCustomValidity("bar");
+ }
+
+ // At this point, the element is invalid.
+ is(e.validationMessage, "foo",
+ "the validation message should be the author one");
+
+ content.removeChild(e);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug601030.html b/dom/html/test/test_bug601030.html
new file mode 100644
index 000000000..f4fc5b986
--- /dev/null
+++ b/dom/html/test/test_bug601030.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=601030
+-->
+<head>
+ <title>Test for Bug 601030</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=601030">Mozilla Bug 601030</a>
+<p id="display"></p>
+<div id="content">
+ <iframe src="data:text/html,<input autofocus>"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 601030 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var f = document.createElement("iframe");
+ var content = document.getElementById('content');
+
+ f.addEventListener("load", function() {
+ f.removeEventListener("load", arguments.callee, false);
+ SimpleTest.executeSoon(function() {
+ isnot(document.activeElement, f,
+ "autofocus should not work when another frame is inserted in the document");
+
+ content.removeChild(f);
+ content.removeChild(document.getElementsByTagName('iframe')[0]);
+ f = document.createElement('iframe');
+ f.addEventListener("load", function() {
+ f.removeEventListener("load", arguments.callee, false);
+ isnot(document.activeElement, f,
+ "autofocus should not work in a frame if the top document is already loaded");
+ SimpleTest.finish();
+ }, false);
+ f.src = "data:text/html,<input autofocus>";
+ content.appendChild(f);
+ });
+ }, false);
+
+ f.src = "data:text/html,<input autofocus>";
+ content.appendChild(f);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug605124-1.html b/dom/html/test/test_bug605124-1.html
new file mode 100644
index 000000000..7c3a8ee9b
--- /dev/null
+++ b/dom/html/test/test_bug605124-1.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=605124
+-->
+<head>
+ <title>Test for Bug 605124</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=605124">Mozilla Bug 605124</a>
+<p id="display"></p>
+<div id="content">
+ <form>
+ <textarea required></textarea>
+ <input required>
+ <select required></select>
+ <button type='submit'></button>
+ </form>
+
+ <table>
+ <form>
+ <tr>
+ <textarea required></textarea>
+ <input required>
+ <select required></select>
+ <button type='submit'></button>
+ </tr>
+ </form>
+ </table>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 605124 **/
+
+function checkPseudoClass(aElement, aExpected)
+{
+ is(aElement.matches(":-moz-ui-invalid"), aExpected,
+ "matches(':-moz-ui-invalid') should return " + aExpected + " for " + aElement);
+}
+
+var os = SpecialPowers.Cc['@mozilla.org/observer-service;1']
+ .getService(SpecialPowers.Ci.nsIObserverService);
+var observers = os.enumerateObservers("invalidformsubmit");
+
+if (observers.hasMoreElements()) {
+ var content = document.getElementById('content');
+ var textarea = document.getElementsByTagName('textarea')[0];
+ var input = document.getElementsByTagName('input')[0];
+ var select = document.getElementsByTagName('select')[0];
+ var button = document.getElementsByTagName('button')[0];
+ var form = document.forms[0];
+
+ checkPseudoClass(textarea, false);
+ checkPseudoClass(input, false);
+ checkPseudoClass(select, false);
+
+ // Try to submit.
+ button.click();
+ checkPseudoClass(textarea, true);
+ checkPseudoClass(input, true);
+ checkPseudoClass(select, true);
+
+ // No longer in the form.
+ content.appendChild(textarea);
+ content.appendChild(input);
+ content.appendChild(select);
+ checkPseudoClass(textarea, false);
+ checkPseudoClass(input, false);
+ checkPseudoClass(select, false);
+
+ // Back in the form.
+ form.appendChild(textarea);
+ form.appendChild(input);
+ form.appendChild(select);
+ checkPseudoClass(textarea, true);
+ checkPseudoClass(input, true);
+ checkPseudoClass(select, true);
+
+ /* Case when elements get orphaned. */
+ var textarea = document.getElementsByTagName('textarea')[1];
+ var input = document.getElementsByTagName('input')[1];
+ var select = document.getElementsByTagName('select')[1];
+ var button = document.getElementsByTagName('button')[1];
+ var form = document.forms[1];
+
+ // Try to submit.
+ button.click();
+ checkPseudoClass(textarea, true);
+ checkPseudoClass(input, true);
+ checkPseudoClass(select, true);
+
+ // Remove the form.
+ document.getElementsByTagName('table')[0].removeChild(form);
+ checkPseudoClass(textarea, false);
+ checkPseudoClass(input, false);
+ checkPseudoClass(select, false);
+} else {
+ todo(false, "No 'invalidformsubmit' observers. Skip test.");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug605124-2.html b/dom/html/test/test_bug605124-2.html
new file mode 100644
index 000000000..8ac9027f5
--- /dev/null
+++ b/dom/html/test/test_bug605124-2.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=605124
+-->
+<head>
+ <title>Test for Bug 605124</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=605124">Mozilla Bug 605124</a>
+<p id="display"></p>
+<div id="content">
+ <input required>
+ <textarea required></textarea>
+ <select required>
+ <option value="">foo</option>
+ <option>bar</option>
+ </select>
+ <select multiple required>
+ <option value="">foo</option>
+ <option>bar</option>
+ </select>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 605124 **/
+
+function checkPseudoClass(aElement, aExpected)
+{
+ is(aElement.matches(":-moz-ui-invalid"), aExpected,
+ "matches(':-moz-ui-invalid') should return " + aExpected + " for " + aElement);
+}
+
+function checkElement(aElement)
+{
+ checkPseudoClass(aElement, false);
+
+ // Focusing while :-moz-ui-invalid doesn't apply,
+ // the pseudo-class should not apply while typing.
+ aElement.focus();
+ checkPseudoClass(aElement, false);
+ // with keys
+ synthesizeKey('f', {});
+ checkPseudoClass(aElement, false);
+ synthesizeKey('VK_BACK_SPACE', {});
+ checkPseudoClass(aElement, false);
+ // with .value
+ aElement.value = 'f';
+ checkPseudoClass(aElement, false);
+ aElement.value = '';
+ checkPseudoClass(aElement, false);
+
+ aElement.blur();
+ checkPseudoClass(aElement, true);
+
+ // Focusing while :-moz-ui-invalid applies,
+ // the pseudo-class should apply while typing if appropriate.
+ aElement.focus();
+ checkPseudoClass(aElement, true);
+ // with keys
+ synthesizeKey('f', {});
+ checkPseudoClass(aElement, false);
+ synthesizeKey('VK_BACK_SPACE', {});
+ checkPseudoClass(aElement, true);
+ // with .value
+ aElement.value = 'f';
+ checkPseudoClass(aElement, false);
+ aElement.value = '';
+ checkPseudoClass(aElement, true);
+}
+
+function checkSelectElement(aElement)
+{
+ checkPseudoClass(aElement, false);
+
+ // Focusing while :-moz-ui-invalid doesn't apply,
+ // the pseudo-class should not apply while changing selection.
+ aElement.focus();
+ checkPseudoClass(aElement, false);
+
+ aElement.selectedIndex = 1;
+ checkPseudoClass(aElement, false);
+ aElement.selectedIndex = 0;
+ checkPseudoClass(aElement, false);
+
+ aElement.blur();
+ checkPseudoClass(aElement, true);
+
+ // Focusing while :-moz-ui-invalid applies,
+ // the pseudo-class should apply while changing selection if appropriate.
+ aElement.focus();
+ checkPseudoClass(aElement, true);
+
+ aElement.selectedIndex = 1;
+ checkPseudoClass(aElement, false);
+ aElement.selectedIndex = 0;
+ checkPseudoClass(aElement, true);
+}
+
+checkElement(document.getElementsByTagName('input')[0]);
+checkElement(document.getElementsByTagName('textarea')[0]);
+checkSelectElement(document.getElementsByTagName('select')[0]);
+checkSelectElement(document.getElementsByTagName('select')[1]);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug605125-1.html b/dom/html/test/test_bug605125-1.html
new file mode 100644
index 000000000..1a89af7ab
--- /dev/null
+++ b/dom/html/test/test_bug605125-1.html
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=605125
+-->
+<head>
+ <title>Test for Bug 605125</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=605125">Mozilla Bug 605125</a>
+<p id="display"></p>
+<div id="content">
+ <form id='f1'>
+ <textarea></textarea>
+ <input>
+ <button type='submit'></button>
+ <select></select>
+ </form>
+
+ <table>
+ <form id='f2'>
+ <tr>
+ <textarea></textarea>
+ <input>
+ <button type='submit'></button>
+ <select></select>
+ </tr>
+ </form>
+ </table>
+ <input form='f1' required>
+ <input form='f2' required>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 605125 **/
+
+/**
+ * NOTE: this test is very similar to 605124-1.html.
+ */
+
+function checkPseudoClass(aElement, aExpected)
+{
+ is(aElement.matches(":-moz-ui-valid"), aExpected,
+ "matches(':-moz-ui-valid') should return " + aExpected + " for " + aElement);
+}
+
+var os = SpecialPowers.Cc['@mozilla.org/observer-service;1']
+ .getService(SpecialPowers.Ci.nsIObserverService);
+var observers = os.enumerateObservers("invalidformsubmit");
+
+if (observers.hasMoreElements()) {
+ var content = document.getElementById('content');
+ var textarea = document.getElementsByTagName('textarea')[0];
+ var input = document.getElementsByTagName('input')[0];
+ var button = document.getElementsByTagName('button')[0];
+ var select = document.getElementsByTagName('select')[0];
+ var form = document.forms[0];
+
+ checkPseudoClass(textarea, false);
+ checkPseudoClass(input, false);
+ checkPseudoClass(select, false);
+
+ // Try to submit.
+ button.click();
+ checkPseudoClass(textarea, true);
+ checkPseudoClass(input, true);
+ checkPseudoClass(select, true);
+
+ // No longer in the form.
+ content.appendChild(textarea);
+ content.appendChild(input);
+ content.appendChild(select);
+ checkPseudoClass(textarea, false);
+ checkPseudoClass(input, false);
+ checkPseudoClass(select, false);
+
+ // Back in the form.
+ form.appendChild(textarea);
+ form.appendChild(input);
+ form.appendChild(select);
+ checkPseudoClass(textarea, true);
+ checkPseudoClass(input, true);
+ checkPseudoClass(select, true);
+
+ /* Case when elements get orphaned. */
+ var textarea = document.getElementsByTagName('textarea')[1];
+ var input = document.getElementsByTagName('input')[1];
+ var button = document.getElementsByTagName('button')[1];
+ var select = document.getElementsByTagName('select')[1];
+ var form = document.forms[1];
+
+ // Try to submit.
+ button.click();
+ checkPseudoClass(textarea, true);
+ checkPseudoClass(input, true);
+ checkPseudoClass(select, true);
+
+ // Remove the form.
+ document.getElementsByTagName('table')[0].removeChild(form);
+ checkPseudoClass(textarea, false);
+ checkPseudoClass(input, false);
+ checkPseudoClass(select, false);
+} else {
+ todo(false, "No 'invalidformsubmit' observers. Skip test.");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug605125-2.html b/dom/html/test/test_bug605125-2.html
new file mode 100644
index 000000000..4d5c4a46a
--- /dev/null
+++ b/dom/html/test/test_bug605125-2.html
@@ -0,0 +1,145 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=605125
+-->
+<head>
+ <title>Test for Bug 605125</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=605125">Mozilla Bug 605125</a>
+<p id="display"></p>
+<div id="content">
+ <input>
+ <textarea></textarea>
+ <select>
+ <option value="">foo</option>
+ <option>bar</option>
+ </select>
+ <select multiple>
+ <option value="">foo</option>
+ <option>bar</option>
+ </select>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 605125 **/
+
+function checkPseudoClass(aElement, aExpected)
+{
+ is(aElement.matches(":-moz-ui-valid"), aExpected,
+ "matches(':-moz-ui-valid') should return " + aExpected + " for " + aElement);
+}
+
+function checkElement(aElement)
+{
+ checkPseudoClass(aElement, false);
+
+ // Focusing while :-moz-ui-valid doesn't apply,
+ // the pseudo-class should not apply while typing.
+ aElement.focus();
+ checkPseudoClass(aElement, false);
+ // with keys
+ synthesizeKey('f', {});
+ checkPseudoClass(aElement, false);
+ synthesizeKey('VK_BACK_SPACE', {});
+ checkPseudoClass(aElement, false);
+ // with .value
+ aElement.value = 'f';
+ checkPseudoClass(aElement, false);
+ aElement.value = '';
+ checkPseudoClass(aElement, false);
+
+ aElement.blur();
+ checkPseudoClass(aElement, true);
+
+ // Focusing while :-moz-ui-valid applies,
+ // the pseudo-class should apply while typing if appropriate.
+ aElement.focus();
+ checkPseudoClass(aElement, true);
+ // with keys
+ synthesizeKey('f', {});
+ checkPseudoClass(aElement, true);
+ synthesizeKey('VK_BACK_SPACE', {});
+ checkPseudoClass(aElement, true);
+ // with .value
+ aElement.value = 'f';
+ checkPseudoClass(aElement, true);
+ aElement.value = '';
+ checkPseudoClass(aElement, true);
+
+ aElement.blur();
+ aElement.required = true;
+ checkPseudoClass(aElement, false);
+
+ // Focusing while :-moz-ui-invalid applies,
+ // the pseudo-class should apply while typing if appropriate.
+ aElement.focus();
+ checkPseudoClass(aElement, false);
+ // with keys
+ synthesizeKey('f', {});
+ checkPseudoClass(aElement, true);
+ synthesizeKey('VK_BACK_SPACE', {});
+ checkPseudoClass(aElement, false);
+ // with .value
+ aElement.value = 'f';
+ checkPseudoClass(aElement, true);
+ aElement.value = '';
+ checkPseudoClass(aElement, false);
+}
+
+function checkSelectElement(aElement)
+{
+ checkPseudoClass(aElement, false);
+
+ // Focusing while :-moz-ui-valid doesn't apply,
+ // the pseudo-class should not apply while changing selection.
+ aElement.focus();
+ checkPseudoClass(aElement, false);
+
+ aElement.selectedIndex = 1;
+ checkPseudoClass(aElement, false);
+ aElement.selectedIndex = 0;
+ checkPseudoClass(aElement, false);
+
+ aElement.blur();
+ checkPseudoClass(aElement, true);
+
+ // Focusing while :-moz-ui-valid applies,
+ // the pseudo-class should apply while changing selection if appropriate.
+ aElement.focus();
+ checkPseudoClass(aElement, true);
+
+ aElement.selectedIndex = 1;
+ checkPseudoClass(aElement, true);
+ aElement.selectedIndex = 0;
+ checkPseudoClass(aElement, true);
+
+ aElement.blur();
+ aElement.required = true;
+ checkPseudoClass(aElement, false);
+
+ // Focusing while :-moz-ui-invalid applies,
+ // the pseudo-class should apply while changing selection if appropriate.
+ aElement.focus();
+ checkPseudoClass(aElement, false);
+
+ aElement.selectedIndex = 1;
+ checkPseudoClass(aElement, true);
+ aElement.selectedIndex = 0;
+ checkPseudoClass(aElement, false);
+}
+
+checkElement(document.getElementsByTagName('input')[0]);
+checkElement(document.getElementsByTagName('textarea')[0]);
+checkSelectElement(document.getElementsByTagName('select')[0]);
+checkSelectElement(document.getElementsByTagName('select')[1]);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug606817.html b/dom/html/test/test_bug606817.html
new file mode 100644
index 000000000..3de26efc0
--- /dev/null
+++ b/dom/html/test/test_bug606817.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=606817
+-->
+<head>
+ <title>Test for Bug 606817</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=606817">Mozilla Bug 606817</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 606817 **/
+
+var messageMaxLength = 256;
+
+function checkMessage(aInput, aMsg, aWithTerminalPeriod)
+{
+ ok(aInput.validationMessage != aMsg,
+ "Original content-defined message should have been truncate");
+ is(aInput.validationMessage.length - aInput.validationMessage.indexOf("_42_"),
+ aWithTerminalPeriod ? messageMaxLength+1 : messageMaxLength,
+ "validation message should be 256 characters length");
+}
+
+var input = document.createElement("input");
+
+var msg = "";
+for (var i=0; i<75; ++i) {
+ msg += "_42_";
+}
+// msg is now 300 chars long
+
+// Testing with setCustomValidity().
+input.setCustomValidity(msg);
+checkMessage(input, msg, false);
+
+// The input is still invalid but x-moz-errormessage will be used as the message.
+input.setAttribute("x-moz-errormessage", msg);
+checkMessage(input, msg, false);
+
+// Cleaning.
+input.setCustomValidity("");
+input.removeAttribute("x-moz-errormessage");
+
+// Testing with pattern and titl.
+input.pattern = "[0-9]*";
+input.value = "foo";
+input.title = msg;
+checkMessage(input, msg, true);
+
+// Cleaning.
+input.removeAttribute("pattern");
+input.removeAttribute("title");
+input.value = "";
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug607145.html b/dom/html/test/test_bug607145.html
new file mode 100644
index 000000000..f32fff18f
--- /dev/null
+++ b/dom/html/test/test_bug607145.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=607145
+-->
+<head>
+ <title>Test for Bug 607145</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=607145">Mozilla Bug 607145</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 607145 **/
+
+/**
+ * This is not really reflecting an URL as the HTML5 specs want to.
+ * It's how .action is reflected in Gecko (might change later).
+ *
+ * If this changes, add reflectURL for "formAction" in
+ * dom/html/test/forms/test_input_attributes_reflection.html and
+ * "action" in
+ * dom/html/test/forms/test_form_attributes_reflection.html
+ */
+function reflectURL(aElement, aAttr)
+{
+ var idl = aAttr;
+ var attr = aAttr.toLowerCase();
+ var elmtName = aElement.tagName.toLowerCase();
+
+ ok(idl in aElement, idl + " should be available in " + elmtName);
+
+ // Default values.
+ is(aElement[idl], "", "." + idl + " default value should be the empty string");
+ is(aElement.getAttribute(attr), null,
+ "@" + attr + " default value should be null");
+
+ var previousDir = location.href.replace(/test\/[^\/]*$/, "");
+ var dir = location.href.replace(/test_bug607145.html[^\/]*$/, "");
+ var doc = location.href.replace(/\.html.*/, ".html")
+ var values = [
+ /* value to set, resolved value */
+ [ "foo.html", dir + "foo.html" ],
+ [ "data:text/html,<html></html>", "data:text/html,<html></html>" ],
+ [ "http://example.org/", "http://example.org/" ],
+ [ "//example.org/", "http://example.org/" ],
+ [ "?foo=bar", doc + "?foo=bar" ],
+ [ "#foo", location.href + "#foo" ],
+ [ "", "" ], // TODO: doesn't follow the specs, should be location.href.
+ [ " ", location.href ],
+ [ "../", previousDir ],
+ [ "...", dir + "..." ],
+ // invalid URL
+ [ "http://a b/", "http://a b/" ], // TODO: doesn't follow the specs, should be "".
+ ];
+
+ for (var value of values) {
+ aElement[idl] = value[0];
+ is(aElement[idl], value[1], "." + idl + " value should be " + value[1]);
+ is(aElement.getAttribute(attr), value[0],
+ "@" + attr + " value should be " + value[0]);
+ }
+
+ for (var value of values) {
+ aElement.setAttribute(attr, value[0]);
+ is(aElement[idl], value[1], "." + idl + " value should be " + value[1]);
+ is(aElement.getAttribute(attr), value[0],
+ "@" + attr + " value should be " + value[0]);
+ }
+}
+
+reflectURL(document.createElement("form"), "action");
+reflectURL(document.createElement("input"), "formAction");
+reflectURL(document.createElement("button"), "formAction");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug610212.html b/dom/html/test/test_bug610212.html
new file mode 100644
index 000000000..56bb18dc1
--- /dev/null
+++ b/dom/html/test/test_bug610212.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=610212
+-->
+<head>
+ <title>Test for Bug 610212</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=610212">Mozilla Bug 610212</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 610212 **/
+
+var canvas = document.createElement('canvas');
+
+reflectUnsignedInt({
+ element: canvas,
+ attribute: "width",
+ nonZero: false,
+ defaultValue: 300,
+});
+
+reflectUnsignedInt({
+ element: canvas,
+ attribute: "height",
+ nonZero: false,
+ defaultValue: 150,
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug610687.html b/dom/html/test/test_bug610687.html
new file mode 100644
index 000000000..f319685ed
--- /dev/null
+++ b/dom/html/test/test_bug610687.html
@@ -0,0 +1,201 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=610687
+-->
+<head>
+ <title>Test for Bug 610687</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=610687">Mozilla Bug 610687</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <form>
+ <input type='radio' name='a'>
+ <input type='radio' name='a'>
+ <input type='radio' name='b'>
+ </form>
+ <input type='radio' name='a'>
+ <input type='radio' name='a'>
+ <input type='radio' name='b'>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 610687 **/
+
+function checkPseudoClasses(aElement, aValid, aValidUI, aInvalidUI)
+{
+ if (aValid) {
+ ok(aElement.matches(":valid"), ":valid should apply");
+ } else {
+ ok(aElement.matches(":invalid"), ":invalid should apply");
+ }
+
+ is(aElement.matches(":-moz-ui-valid"), aValidUI,
+ aValid ? ":-moz-ui-valid should apply" : ":-moz-ui-valid should not apply");
+
+ is(aElement.matches(":-moz-ui-invalid"), aInvalidUI,
+ aInvalidUI ? ":-moz-ui-invalid should apply" : ":-moz-ui-invalid should not apply");
+
+ if (aInvalidUI && (aValid || aValidUI)) {
+ ok(false, ":invalid can't apply with :valid or :-moz-valid-ui");
+ }
+}
+
+/**
+ * r1 and r2 should be in the same group.
+ * r3 should be in another group.
+ * form can be null.
+ */
+function checkRadios(r1, r2, r3, form)
+{
+ // Default state.
+ checkPseudoClasses(r1, true, false, false);
+ checkPseudoClasses(r2, true, false, false);
+ checkPseudoClasses(r3, true, false, false);
+
+ // Suffering from being missing (without ui-invalid).
+ r1.required = true;
+ checkPseudoClasses(r1, false, false, false);
+ checkPseudoClasses(r2, false, false, false);
+ checkPseudoClasses(r3, true, false, false);
+
+ // Suffering from being missing (with ui-invalid).
+ r1.checked = false;
+ checkPseudoClasses(r1, false, false, true);
+ checkPseudoClasses(r2, false, false, true);
+ checkPseudoClasses(r3, true, false, false);
+
+ // Do not suffer from being missing (with ui-valid).
+ r1.checked = true;
+ checkPseudoClasses(r1, true, true, false);
+ checkPseudoClasses(r2, true, true, false);
+ checkPseudoClasses(r3, true, false, false);
+
+ // Do not suffer from being missing (with ui-valid).
+ r1.checked = false;
+ r1.required = false;
+ checkPseudoClasses(r1, true, true, false);
+ checkPseudoClasses(r2, true, true, false);
+ checkPseudoClasses(r3, true, false, false);
+
+ // Suffering from being missing (with ui-invalid) with required set on one radio
+ // and the checked state changed on another.
+ r1.required = true;
+ r2.checked = false;
+ checkPseudoClasses(r1, false, false, true);
+ checkPseudoClasses(r2, false, false, true);
+ checkPseudoClasses(r3, true, false, false);
+
+ // Do not suffer from being missing (with ui-valid) by checking the radio which
+ // hasn't the required attribute.
+ r2.checked = true;
+ checkPseudoClasses(r1, true, true, false);
+ checkPseudoClasses(r2, true, true, false);
+ checkPseudoClasses(r3, true, false, false);
+
+ // .setCustomValidity() should not affect the entire group.
+ r1.checked = r2.checked = r3.checked = false;
+ r1.required = false;
+ r1.setCustomValidity('foo');
+ checkPseudoClasses(r1, false, false, true);
+ checkPseudoClasses(r2, true, true, false);
+ checkPseudoClasses(r3, true, true, false);
+
+ r1.setCustomValidity('');
+ r2.setCustomValidity('foo');
+ checkPseudoClasses(r1, true, true, false);
+ checkPseudoClasses(r2, false, false, true);
+ checkPseudoClasses(r3, true, true, false);
+
+ r2.setCustomValidity('');
+ r3.setCustomValidity('foo');
+ checkPseudoClasses(r1, true, true, false);
+ checkPseudoClasses(r2, true, true, false);
+ checkPseudoClasses(r3, false, false, true);
+
+ // Removing the radio with the required attribute should make the group valid.
+ r1.setCustomValidity('');
+ r2.setCustomValidity('');
+ r1.required = false;
+ r2.required = true;
+ r1.checked = r2.checked = false;
+ checkPseudoClasses(r1, false, false, true);
+ checkPseudoClasses(r2, false, false, true);
+
+ var p = r2.parentNode;
+ p.removeChild(r2);
+ checkPseudoClasses(r1, true, true, false);
+ checkPseudoClasses(r2, false, false, true);
+
+ p.appendChild(r2);
+ checkPseudoClasses(r1, false, false, true);
+ checkPseudoClasses(r2, false, false, true);
+
+ // Adding a radio element to an invalid group should make it invalid.
+ p.removeChild(r1);
+ checkPseudoClasses(r1, true, true, false);
+ checkPseudoClasses(r2, false, false, true);
+
+ p.appendChild(r1);
+ checkPseudoClasses(r1, false, false, true);
+ checkPseudoClasses(r2, false, false, true);
+
+ // Adding a checked radio element to an invalid group should make it valid.
+ p.removeChild(r1);
+ checkPseudoClasses(r1, true, true, false);
+ checkPseudoClasses(r2, false, false, true);
+
+ r1.checked = true;
+ p.appendChild(r1);
+ checkPseudoClasses(r1, true, true, false);
+ checkPseudoClasses(r2, true, true, false);
+ r1.checked = false;
+
+ // Adding an invalid radio element by changing the name attribute.
+ r2.name = 'c';
+ checkPseudoClasses(r1, true, true, false);
+ checkPseudoClasses(r2, false, false, true);
+
+ r2.name = 'a';
+ checkPseudoClasses(r1, false, false, true);
+ checkPseudoClasses(r2, false, false, true);
+
+ // Adding an element to an invalid radio group by changing the name attribute.
+ r1.name = 'c';
+ checkPseudoClasses(r1, true, true, false);
+ checkPseudoClasses(r2, false, false, true);
+
+ r1.name = 'a';
+ checkPseudoClasses(r1, false, false, true);
+ checkPseudoClasses(r2, false, false, true);
+
+ // Adding a checked element to an invalid radio group with the name attribute.
+ r1.name = 'c';
+ checkPseudoClasses(r1, true, true, false);
+ checkPseudoClasses(r2, false, false, true);
+
+ r1.checked = true;
+ r1.name = 'a';
+ checkPseudoClasses(r1, true, true, false);
+ checkPseudoClasses(r2, true, true, false);
+ r1.checked = false;
+}
+
+var r1 = document.getElementsByTagName('input')[0];
+var r2 = document.getElementsByTagName('input')[1];
+var r3 = document.getElementsByTagName('input')[2];
+checkRadios(r1, r2, r3);
+
+r1 = document.getElementsByTagName('input')[3];
+r2 = document.getElementsByTagName('input')[4];
+r3 = document.getElementsByTagName('input')[5];
+checkRadios(r1, r2, r3);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug611189.html b/dom/html/test/test_bug611189.html
new file mode 100644
index 000000000..13ca618d6
--- /dev/null
+++ b/dom/html/test/test_bug611189.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=611189
+-->
+<head>
+ <title>Test for Bug 611189</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=611189">Mozilla Bug 611189</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 611189 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var i = document.createElement("input");
+ var b = document.getElementById("content");
+ b.appendChild(i);
+ b.clientWidth; // bind to frame
+ i.focus(); // initialize editor
+ var before = snapshotWindow(window, true);
+ i.value = "L"; // set the value
+ i.style.display = "none";
+ b.clientWidth; // unbind from frame
+ i.value = ""; // set the value without a frame
+ i.style.display = "";
+ b.clientWidth; // rebind to frame
+ is(i.value, "", "Input's value should be correctly updated");
+ var after = snapshotWindow(window, true);
+ ok(compareSnapshots(before, after, true), "The correct value should be rendered inside the control");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug612730.html b/dom/html/test/test_bug612730.html
new file mode 100644
index 000000000..4c6d906e3
--- /dev/null
+++ b/dom/html/test/test_bug612730.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=612730
+-->
+<head>
+ <title>Test for Bug 612730</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=612730">Mozilla Bug 612730</a>
+<p id="display"></p>
+<div id="content">
+ <select multiple required>
+ <option value="">foo</option>
+ <option value="">bar</option>
+ </select>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 612730 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest()
+{
+ var select = document.getElementsByTagName('select')[0];
+
+ select.addEventListener("focus", function() {
+ select.removeEventListener("focus", arguments.callee, false);
+
+ isnot(select.selectedIndex, -1, "Something should have been selected");
+
+ ok(!select.matches(":-moz-ui-valid"),
+ ":-moz-ui-valid should not apply");
+ todo(!select.matches(":-moz-ui-invalid"),
+ ":-moz-ui-invalid should not apply");
+
+ SimpleTest.finish();
+ }, false);
+
+ synthesizeMouse(select, 5, 5, {});
+}
+
+SimpleTest.waitForFocus(runTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug613019.html b/dom/html/test/test_bug613019.html
new file mode 100644
index 000000000..bb28d108a
--- /dev/null
+++ b/dom/html/test/test_bug613019.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=613019
+-->
+<head>
+ <title>Test for Bug 613019</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=613019">Mozilla Bug 613019</a>
+<div id="content">
+ <input type="text" maxlength="2" style="width:200px" value="Test">
+ <textarea maxlength="2" style="width:200px">Test</textarea>
+ <input type="text" minlength="6" style="width:200px" value="Test">
+ <textarea minlength="6" style="width:200px">Test</textarea>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 613019 **/
+
+function testInteractivityOfMaxLength(elem) {
+ // verify that user interactivity is necessary for validity state to apply.
+ is(elem.value, "Test", "Element has incorrect starting value.");
+ is(elem.validity.tooLong, false, "Element should not be tooLong.");
+
+ elem.setSelectionRange(elem.value.length, elem.value.length)
+ elem.focus();
+
+ synthesizeKey("VK_BACK_SPACE", {});
+ is(elem.value, "Tes", "Element value was not changed correctly.");
+ is(elem.validity.tooLong, true, "Element should still be tooLong.");
+
+ synthesizeKey("VK_BACK_SPACE", {});
+ is(elem.value, "Te", "Element value was not changed correctly.");
+ is(elem.validity.tooLong, false, "Element should no longer be tooLong.");
+
+ elem.value = "Test";
+ is(elem.validity.tooLong, false,
+ "Element should not be tooLong after non-interactive value change.");
+}
+
+function testInteractivityOfMinLength(elem) {
+ // verify that user interactivity is necessary for validity state to apply.
+ is(elem.value, "Test", "Element has incorrect starting value.");
+ is(elem.validity.tooLong, false, "Element should not be tooShort.");
+
+ elem.setSelectionRange(elem.value.length, elem.value.length)
+ elem.focus();
+
+ synthesizeKey("e", {});
+ is(elem.value, "Teste", "Element value was not changed correctly.");
+ is(elem.validity.tooShort, true, "Element should still be tooShort.");
+
+ synthesizeKey("d", {});
+ is(elem.value, "Tested", "Element value was not changed correctly.");
+ is(elem.validity.tooShort, false, "Element should no longer be tooShort.");
+
+ elem.value = "Test";
+ is(elem.validity.tooShort, false,
+ "Element should not be tooShort after non-interactive value change.");
+}
+
+function test() {
+ window.getSelection().removeAllRanges();
+ testInteractivityOfMaxLength(document.querySelector("input[type=text][maxlength]"));
+ testInteractivityOfMaxLength(document.querySelector("textarea[maxlength]"));
+ testInteractivityOfMinLength(document.querySelector("input[type=text][minlength]"));
+ testInteractivityOfMinLength(document.querySelector("textarea[minlength]"));
+ SimpleTest.finish();
+}
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ setTimeout(test, 0);
+};
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug613113.html b/dom/html/test/test_bug613113.html
new file mode 100644
index 000000000..3ffe0cf07
--- /dev/null
+++ b/dom/html/test/test_bug613113.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=613113
+-->
+<head>
+ <title>Test for Bug 613113</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=613113">Mozilla Bug 613113</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe name='f'></iframe>
+ <form target='f' action="data:text/html,">
+ <output></output>
+ <button></button>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 613113 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var invalidEvent = false;
+
+var form = document.forms[0];
+var button = document.getElementsByTagName('button')[0];
+var output = document.getElementsByTagName('output')[0];
+
+output.addEventListener("invalid", function() {
+ if (invalidEvent) {
+ ok(false, "invalid event has already been caught");
+ } else {
+ invalidEvent = true;
+ ok(true, "invalid event has been caught");
+ setTimeout(function() {
+ SimpleTest.finish();
+ }, 0);
+ }
+}, false);
+
+form.addEventListener("submit", function() {
+ ok(false, "submit event should not have been send");
+}, false);
+
+output.setCustomValidity("foo");
+
+button.click();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug613722.html b/dom/html/test/test_bug613722.html
new file mode 100644
index 000000000..3067c6476
--- /dev/null
+++ b/dom/html/test/test_bug613722.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=613722
+-->
+<head>
+ <title>Test for Bug 613722</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=613722">Mozilla Bug 613722</a>
+<p id="display"></p>
+<div id="content">
+ <embed src="test_plugin.tst" hidden>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 613722 **/
+
+var rect = document.getElementsByTagName('embed')[0].getBoundingClientRect();
+
+var hasFrame = rect.left != 0 || rect.right != 0 || rect.top != 0 ||
+ rect.bottom != 0;
+
+ok(hasFrame, "embed should have a frame with hidden set");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug613979.html b/dom/html/test/test_bug613979.html
new file mode 100644
index 000000000..cb77b333e
--- /dev/null
+++ b/dom/html/test/test_bug613979.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=613979
+-->
+<head>
+ <title>Test for Bug 613979</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=613979">Mozilla Bug 613979</a>
+<p id="display"></p>
+<div id="content">
+ <input required>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 613979 **/
+
+var testNum = 0;
+var input = document.getElementsByTagName('input')[0];
+
+input.addEventListener("input", function() {
+ if (testNum == 0) {
+ ok(input.validity.valid, "input should be valid");
+ testNum++;
+ SimpleTest.executeSoon(function() {
+ synthesizeKey("VK_BACK_SPACE", {});
+ });
+ } else if (testNum == 1) {
+ ok(!input.validity.valid, "input should not be valid");
+ input.removeEventListener("input", arguments.callee, false);
+ SimpleTest.finish();
+ }
+}, false);
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function() {
+ input.focus();
+ synthesizeKey("a", {});
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug615595.html b/dom/html/test/test_bug615595.html
new file mode 100644
index 000000000..b662103cd
--- /dev/null
+++ b/dom/html/test/test_bug615595.html
Binary files differ
diff --git a/dom/html/test/test_bug615833.html b/dom/html/test/test_bug615833.html
new file mode 100644
index 000000000..f72245d4c
--- /dev/null
+++ b/dom/html/test/test_bug615833.html
@@ -0,0 +1,156 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=615697
+-->
+<head>
+ <title>Test for Bug 615697</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=615697">Mozilla Bug 615697</a>
+<p id="display"></p>
+<div id="content">
+ <input>
+ <textarea></textarea>
+ <input type='radio'>
+ <input type='checkbox'>
+ <select>
+ <option>foo</option>
+ <option>bar</option>
+ </select>
+ <select multiple size='1'>
+ <option>tulip</option>
+ </select>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 615697 **/
+
+/**
+ * This test is making all elements trigger 'change' event.
+ * You should read the test from bottom to top:
+ * events are registered from the last one to the first one.
+ *
+ * Sometimes, elements are focused before a click. This might sound useless
+ * but it guarantees to have the element visible before simulating the click.
+ */
+
+var input = document.getElementsByTagName('input')[0];
+var textarea = document.getElementsByTagName('textarea')[0];
+var radio = document.getElementsByTagName('input')[1];
+var checkbox= document.getElementsByTagName('input')[2];
+var select = document.getElementsByTagName('select')[0];
+var selectMultiple = document.getElementsByTagName('select')[1];
+
+function checkChangeEvent(aEvent)
+{
+ ok(aEvent.bubbles, "change event should bubble");
+ ok(!aEvent.cancelable, "change event shouldn't be cancelable");
+}
+
+selectMultiple.addEventListener("change", function(aEvent) {
+ selectMultiple.removeEventListener("change", arguments.callee, false);
+ checkChangeEvent(aEvent);
+ SimpleTest.finish();
+}, false);
+
+selectMultiple.addEventListener("focus", function() {
+ selectMultiple.removeEventListener("focus", arguments.callee, false);
+ SimpleTest.executeSoon(function () {
+ synthesizeMouseAtCenter(selectMultiple, {});
+ });
+}, false);
+
+select.addEventListener("change", function(aEvent) {
+ select.removeEventListener("change", arguments.callee, false);
+ checkChangeEvent(aEvent);
+ selectMultiple.focus();
+}, false);
+
+select.addEventListener("keyup", function() {
+ select.removeEventListener("keyup", arguments.callee, false);
+ select.blur();
+}, false);
+
+select.addEventListener("focus", function() {
+ select.removeEventListener("focus", arguments.callee, false);
+ SimpleTest.executeSoon(function () {
+ synthesizeKey("VK_DOWN", {});
+ });
+}, false);
+
+checkbox.addEventListener("change", function(aEvent) {
+ checkbox.removeEventListener("change", arguments.callee, false);
+ checkChangeEvent(aEvent);
+ select.focus();
+}, false);
+
+checkbox.addEventListener("focus", function() {
+ checkbox.removeEventListener("focus", arguments.callee, false);
+ SimpleTest.executeSoon(function () {
+ synthesizeMouseAtCenter(checkbox, {});
+ });
+}, false);
+
+radio.addEventListener("change", function(aEvent) {
+ radio.removeEventListener("change", arguments.callee, false);
+ checkChangeEvent(aEvent);
+ checkbox.focus();
+}, false);
+
+radio.addEventListener("focus", function() {
+ radio.removeEventListener("focus", arguments.callee, false);
+ SimpleTest.executeSoon(function () {
+ synthesizeMouseAtCenter(radio, {});
+ });
+}, false);
+
+textarea.addEventListener("change", function(aEvent) {
+ textarea.removeEventListener("change", arguments.callee, false);
+ checkChangeEvent(aEvent);
+ radio.focus();
+}, false);
+
+textarea.addEventListener("input", function() {
+ textarea.removeEventListener("input", arguments.callee, false);
+ textarea.blur();
+}, false);
+
+textarea.addEventListener("focus", function() {
+ textarea.removeEventListener("focus", arguments.callee, false);
+ SimpleTest.executeSoon(function () {
+ synthesizeKey('f', {});
+ });
+}, false);
+
+input.addEventListener("change", function(aEvent) {
+ input.removeEventListener("change", arguments.callee, false);
+ checkChangeEvent(aEvent);
+ textarea.focus();
+}, false);
+
+input.addEventListener("input", function() {
+ input.removeEventListener("input", arguments.callee, false);
+ input.blur();
+}, false);
+
+input.addEventListener("focus", function() {
+ input.removeEventListener("focus", arguments.callee, false);
+ SimpleTest.executeSoon(function () {
+ synthesizeKey('f', {});
+ });
+}, false);
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ input.focus();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug617528.html b/dom/html/test/test_bug617528.html
new file mode 100644
index 000000000..c8cd62568
--- /dev/null
+++ b/dom/html/test/test_bug617528.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=617528
+-->
+<head>
+ <title>Test for Bug 617528</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=617528">Mozilla Bug 617528</a>
+<p id="display"></p>
+<div id="content">
+ <menu>
+ <menuitem id="checkbox" type="checkbox" label="Checkbox" checked></menuitem>
+ <menuitem id="radio1" type="radio" label="Radio1" checked></menuitem>
+ <menuitem id="radio2" type="radio" label="Radio2"></menuitem>
+ </menu>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 617528 **/
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ function click(element, preventDefault, checked) {
+ function handleClick(event) {
+ is(this.checked, checked,
+ "checking .checked (" + this.id + ")");
+ if (preventDefault)
+ event.preventDefault();
+ }
+ element.addEventListener("click", handleClick);
+ element.click();
+ element.removeEventListener("click", handleClick);
+ }
+
+ function verify(elements, data) {
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ is(element.checked, data[i*2],
+ "checking .checked (" + element.id + ")");
+ is(element.defaultChecked, data[i*2+1],
+ 'checking .defaultChecked (' + element.id + ")");
+ }
+ }
+
+ var checkbox = document.getElementById("checkbox");
+ click(checkbox, false, false);
+ verify([checkbox], [false, true]);
+
+ click(checkbox, true, true);
+ verify([checkbox], [false, true]);
+
+ var radio1 = document.getElementById("radio1");
+ var radio2 = document.getElementById("radio2");
+ click(radio2, false, true);
+ verify([radio1, radio2], [false, true,
+ true, false]);
+
+ click(radio1, true, true);
+ verify([radio1, radio2], [false, true,
+ true, false]);
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug618948.html b/dom/html/test/test_bug618948.html
new file mode 100644
index 000000000..258041bdb
--- /dev/null
+++ b/dom/html/test/test_bug618948.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=618948
+-->
+<head>
+ <title>Test for Bug 618948</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=618948">Mozilla Bug 618948</a>
+<p id="display"></p>
+<div id="content">
+ <form>
+ <input type='email' id='i'>
+ <button>submit</button>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 618948 **/
+
+var events = ["focus", "input", "change", "invalid" ];
+
+var handled = ({});
+
+function eventHandler(event)
+{
+ dump("\n" + event.type + "\n");
+ handled[event.type] = true;
+}
+
+function beginTest()
+{
+ for (var e of events) {
+ handled[e] = false;
+ }
+
+ i.focus();
+}
+
+function endTest()
+{
+ for (var e of events) {
+ ok(handled[e], "on" + e + " should have been called");
+ }
+
+ SimpleTest.finish();
+}
+
+var i = document.getElementsByTagName('input')[0];
+var b = document.getElementsByTagName('button')[0];
+
+i.onfocus = function(event) {
+ eventHandler(event);
+ synthesizeKey('f', {});
+ i.onfocus = null;
+};
+
+i.oninput = function(event) {
+ eventHandler(event);
+ b.focus();
+ i.oninput = null;
+};
+
+i.onchange = function(event) {
+ eventHandler(event);
+ i.onchange = null;
+ synthesizeMouseAtCenter(b, {});
+};
+
+i.oninvalid = function(event) {
+ eventHandler(event);
+ i.oninvalid = null;
+ endTest();
+};
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug619278.html b/dom/html/test/test_bug619278.html
new file mode 100644
index 000000000..88e4952ec
--- /dev/null
+++ b/dom/html/test/test_bug619278.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=619278
+-->
+<head>
+ <title>Test for Bug 619278</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=619278">Mozilla Bug 619278</a>
+<p id="display"></p>
+<div id="content">
+ <form>
+ <input required><button>submit</button>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 619278 **/
+
+function doElementMatchesSelector(aElement, aSelector)
+{
+ ok(aElement.matches(aSelector),
+ aSelector + " should match for " + aElement);
+}
+
+var e = document.forms[0].elements[0];
+
+e.addEventListener("invalid", function(event) {
+ e.addEventListener("invalid", arguments.callee, false);
+
+ SimpleTest.executeSoon(function() {
+ doElementMatchesSelector(e, ":-moz-ui-invalid");
+ SimpleTest.finish();
+ });
+}, false);
+
+e.addEventListener("focus", function() {
+ e.removeEventListener("focus", arguments.callee, false);
+
+ SimpleTest.executeSoon(function() {
+ synthesizeKey("VK_RETURN", {});
+ });
+}, false);
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ e.focus();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug622558.html b/dom/html/test/test_bug622558.html
new file mode 100644
index 000000000..025eeca03
--- /dev/null
+++ b/dom/html/test/test_bug622558.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=622558
+-->
+<head>
+ <title>Test for Bug 622558</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=622558">Mozilla Bug 622558</a>
+<p id="display"></p>
+<div id="content">
+ <form>
+ <input>
+ <textarea></textarea>
+ <select><option>foo</option></select>
+</div>
+<pre id="test">
+<script type="application/javascript">
+// Bug 940203
+if (navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+/** Test for Bug 622558 **/
+
+function checkSelectors(aElement)
+{
+ ok(aElement.matches(":-moz-ui-invalid"),
+ ":-moz-ui-invalid should match for " + aElement);
+ ok(!aElement.matches(":-moz-ui-valid"),
+ ":-moz-ui-valid should not match for " + aElement);
+}
+
+var input = document.getElementsByTagName('input')[0];
+var textarea = document.getElementsByTagName('textarea')[0];
+var select = document.getElementsByTagName('select')[0];
+
+select.addEventListener("focus", function() {
+ select.removeEventListener("focus", arguments.callee, false);
+
+ SimpleTest.executeSoon(function() {
+ select.setCustomValidity('foo');
+
+ SimpleTest.executeSoon(function() {
+ checkSelectors(select);
+ SimpleTest.finish();
+ });
+ });
+}, false);
+
+textarea.addEventListener("focus", function() {
+ textarea.removeEventListener("focus", arguments.callee, false);
+
+ SimpleTest.executeSoon(function() {
+ textarea.setCustomValidity('foo');
+
+ SimpleTest.executeSoon(function() {
+ checkSelectors(textarea);
+ select.focus();
+ });
+ });
+}, false);
+
+input.addEventListener("focus", function() {
+ input.removeEventListener("focus", arguments.callee, false);
+
+ SimpleTest.executeSoon(function() {
+ input.setCustomValidity('foo');
+
+ SimpleTest.executeSoon(function() {
+ checkSelectors(input);
+ textarea.focus();
+ });
+ });
+}, false);
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ input.focus();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug622597.html b/dom/html/test/test_bug622597.html
new file mode 100644
index 000000000..c0b25ae9e
--- /dev/null
+++ b/dom/html/test/test_bug622597.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=622597
+-->
+<head>
+ <title>Test for Bug 622597</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=622597">Mozilla Bug 622597</a>
+<p id="display"></p>
+<div id="content">
+ <form>
+ <input required>
+ <textarea required></textarea>
+ <select required><option>foo</option><option value="">bar</option></select>
+ <button>submit</button>
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 622597 **/
+
+var form = document.forms[0];
+var input = form.elements[0];
+var textarea = form.elements[1];
+var select = form.elements[2];
+var button = form.elements[3];
+
+function checkPseudoClasses(aElement, aValid, aInvalid)
+{
+ is(aElement.matches(":-moz-ui-valid"), aValid,
+ aValid ? aElement + " should match :-moz-ui-valid"
+ : aElement + " should not match :-moz-ui-valid");
+ is(aElement.matches(":-moz-ui-invalid"), aInvalid,
+ aInvalid ? aElement + " should match :-moz-ui-invalid"
+ : aElement + " should not match :-moz-ui-invalid");
+ if (aValid && aInvalid) {
+ ok(false,
+ aElement + " should not match :-moz-ui-valid AND :-moz-ui-invalid");
+ }
+}
+
+select.addEventListener("focus", function() {
+ select.removeEventListener("focus", arguments.callee, false);
+
+ SimpleTest.executeSoon(function() {
+ form.noValidate = false;
+ SimpleTest.executeSoon(function() {
+ checkPseudoClasses(select, false, true);
+ SimpleTest.finish();
+ });
+ });
+}, false);
+
+textarea.addEventListener("focus", function() {
+ textarea.removeEventListener("focus", arguments.callee, false);
+
+ SimpleTest.executeSoon(function() {
+ form.noValidate = false;
+ SimpleTest.executeSoon(function() {
+ checkPseudoClasses(textarea, false, true);
+ form.noValidate = true;
+ select.selectedIndex = 1;
+ select.focus();
+ });
+ });
+}, false);
+
+input.addEventListener("invalid", function() {
+ input.removeEventListener("invalid", arguments.callee, false);
+
+ input.addEventListener("focus", function() {
+ input.removeEventListener("focus", arguments.callee, false);
+
+ SimpleTest.executeSoon(function() {
+ form.noValidate = false;
+ SimpleTest.executeSoon(function() {
+ checkPseudoClasses(input, false, true);
+ form.noValidate = true;
+ textarea.value = '';
+ textarea.focus();
+ });
+ });
+ }, false);
+
+ SimpleTest.executeSoon(function() {
+ form.noValidate = true;
+ input.blur();
+ input.value = '';
+ input.focus();
+ });
+}, false);
+
+button.addEventListener("focus", function() {
+ button.removeEventListener("focus", arguments.callee, false);
+
+ SimpleTest.executeSoon(function() {
+ synthesizeKey("VK_RETURN", {});
+ });
+}, false);
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ button.focus();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug623291.html b/dom/html/test/test_bug623291.html
new file mode 100644
index 000000000..c61b9213d
--- /dev/null
+++ b/dom/html/test/test_bug623291.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=623291
+-->
+<head>
+ <title>Test for Bug 623291</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=623291">Mozilla Bug 623291</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<input id="textField" onfocus="next()" onblur="done();">
+<button id="b">a button</button>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 623291 **/
+
+function runTest() {
+ document.getElementById("textField").focus();
+}
+
+function next() {
+ synthesizeMouseAtCenter(document.getElementById('b'), {}, window);
+}
+
+function done() {
+ isnot(document.activeElement, document.getElementById("textField"),
+ "TextField should not be active anymore!");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug6296.html b/dom/html/test/test_bug6296.html
new file mode 100644
index 000000000..9b4b2fb7c
--- /dev/null
+++ b/dom/html/test/test_bug6296.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=6296
+-->
+<head>
+ <title>Test for Bug 6296</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=6296">Mozilla Bug 6296</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <A HREF="../testdata/test.gif" id="foo" NAME="anchor1" ALT="this is a test of the image
+ attribute">Hi</A>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 6296 **/
+is($("foo").name, "anchor1", "accessing an anchor name should work, and not crash either!")
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug629801.html b/dom/html/test/test_bug629801.html
new file mode 100644
index 000000000..fe3c8d9a9
--- /dev/null
+++ b/dom/html/test/test_bug629801.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=629801
+-->
+<head>
+ <title>Test for Bug 629801</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=629801">Mozilla Bug 629801</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<div itemscope>
+ This tests itemValue on time elements, first with no datetime attribute, then with no text content,
+ then with both.
+ <time id="t1" itemprop="a">May 10th 2009</time>
+ <time id="t2" itemprop="b" datetime="2009-05-10"></time>
+ <time id="t3" itemprop="c" datetime="2009-05-10">May 10th 2009</time>
+</div>
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 629801 **/
+
+var t1 = document.getElementById("t1"),
+ t2 = document.getElementById("t2"),
+ t3 = document.getElementById("t3"),
+ t4 = document.createElement("time");
+
+// .dateTime IDL
+is(t1.dateTime, "", "dateTime is properly set to empty string if datetime attributeis absent");
+is(t2.dateTime, "2009-05-10", "dateTime is properly set to datetime attribute with datetime and no text content");
+is(t3.dateTime, "2009-05-10", "dateTime is properly set to datetime attribute with datetime and text content");
+
+// dateTime reflects datetime attribute
+reflectString({
+ element: t4,
+ attribute: "dateTime"
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug633058.html b/dom/html/test/test_bug633058.html
new file mode 100644
index 000000000..8ffd1fecd
--- /dev/null
+++ b/dom/html/test/test_bug633058.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=633058
+-->
+<head>
+ <title>Test for Bug 633058</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=633058">Mozilla Bug 633058</a>
+<p id="display"></p>
+<div id="content">
+ <input>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 633058 **/
+
+SimpleTest.waitForExplicitFinish();
+
+// Turn off Spatial Navigation so that the 'keypress' event fires.
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[['snav.enabled', false]]}, startTest);
+});
+function startTest() {
+ var nbExpectedKeyPress = 8;
+ var inputGotKeyPress = 0;
+ var divGotKeyPress = 0;
+
+ var input = document.getElementsByTagName('input')[0];
+ var content = document.getElementById('content');
+
+ content.addEventListener('keypress', function() {
+ divGotKeyPress++;
+
+ if (divGotKeyPress == nbExpectedKeyPress) {
+ is(inputGotKeyPress, nbExpectedKeyPress, "input got all keypress events");
+ is(divGotKeyPress, nbExpectedKeyPress, "div got all keypress events");
+ SimpleTest.finish();
+ }
+ }, false);
+
+ input.addEventListener('keypress', function() {
+ inputGotKeyPress++;
+ }, false);
+
+ input.addEventListener('focus', function() {
+ input.removeEventListener('focus', arguments.callee, false);
+
+ synthesizeKey('VK_UP', {});
+ synthesizeKey('VK_LEFT', {});
+ synthesizeKey('VK_RIGHT', {});
+ synthesizeKey('VK_DOWN', {});
+ synthesizeKey('VK_BACK_SPACE', {});
+ synthesizeKey('VK_DELETE', {});
+ synthesizeKey('VK_ESCAPE', {});
+ synthesizeKey('VK_RETURN', {});
+ }, false);
+ input.focus();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug636336.html b/dom/html/test/test_bug636336.html
new file mode 100644
index 000000000..ed6812e8d
--- /dev/null
+++ b/dom/html/test/test_bug636336.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=636336
+-->
+<head>
+ <title>Test for Bug 636336</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=636336">Mozilla Bug 636336</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 636336 **/
+function testIt(tag) {
+ var elem = document.createElement(tag);
+ elem.setAttribute("src", " ");
+ is(elem.getAttribute("src"), " ",
+ tag + " src attribute setter should not strip whitespace");
+ elem.setAttribute("src", " test ");
+ is(elem.getAttribute("src"), " test ",
+ tag + " src attribute setter should not strip whitespace around non-whitespace");
+ is(elem.src, window.location.href.replace(/test_bug636336\.html$/, "test"),
+ tag + ".src should strip whitespace as needed");
+}
+
+testIt("img");
+testIt("source");
+testIt("audio");
+testIt("video");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug641219.html b/dom/html/test/test_bug641219.html
new file mode 100644
index 000000000..ad25d9a57
--- /dev/null
+++ b/dom/html/test/test_bug641219.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=641219
+-->
+<head>
+ <title>Test for Bug 641219</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=641219">Mozilla Bug 641219</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<div id="div">
+<font></font>
+<svg><font/></svg>
+</div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 641219 **/
+var HTML = "http://www.w3.org/1999/xhtml",
+ SVG = "http://www.w3.org/2000/svg";
+var wrapper = document.getElementById("div");
+is(wrapper.getElementsByTagName("FONT").length, 1);
+is(wrapper.getElementsByTagName("FONT")[0].namespaceURI, HTML);
+is(wrapper.getElementsByTagName("font").length, 2);
+is(wrapper.getElementsByTagName("font")[0].namespaceURI, HTML);
+is(wrapper.getElementsByTagName("font")[1].namespaceURI, SVG);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug643051.html b/dom/html/test/test_bug643051.html
new file mode 100644
index 000000000..4b94e1b0e
--- /dev/null
+++ b/dom/html/test/test_bug643051.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=643051
+-->
+<head>
+ <title>Test for Bug 643051</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=643051">Mozilla Bug 643051</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 643051 **/
+document.cookie = "a=; expires=Thu, 01-Jan-1970 00:00:01 GMT"; // clear cookie
+document.cookie = "a2=; expires=Thu, 01-Jan-1970 00:00:01 GMT"; // clear cookie
+document.cookie = "a3=; expires=Thu, 01-Jan-1970 00:00:01 GMT"; // clear cookie
+
+// single cookie, should work
+document.cookie = "a=bar";
+is(document.cookie, "a=bar", "Can't read stored cookie!");
+
+document.cookie = "a2=bar\na3=bar";
+is(document.cookie, "a=bar; a2=bar", "Wrong cookie value");
+
+document.cookie = "a2=baz; a3=bar";
+is(document.cookie, "a=bar; a2=baz", "Wrong cookie value");
+
+// clear cookies again to avoid affecting other tests
+document.cookie = "a=; expires=Thu, 01-Jan-1970 00:00:01 GMT";
+document.cookie = "a2=; expires=Thu, 01-Jan-1970 00:00:01 GMT";
+document.cookie = "a3=; expires=Thu, 01-Jan-1970 00:00:01 GMT";
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug646157.html b/dom/html/test/test_bug646157.html
new file mode 100644
index 000000000..3924c9f22
--- /dev/null
+++ b/dom/html/test/test_bug646157.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=646157
+-->
+<head>
+ <title>Test for Bug 646157</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=646157">Mozilla Bug 646157</a>
+<p id="display"></p>
+<div id="content">
+ <label id="l1"/><input id="c1" type='checkbox'>
+ <label id="l2"/><input id="c2" type='checkbox'>
+ <label id="l3"/><input id="c3" type='checkbox'>
+ <label id="l4"/><input id="c4" type='checkbox'>
+ <label id="l5"/><input id="c5" type='checkbox'>
+ <label id="l6"/><input id="c6" type='checkbox'>
+ <label id="l7"/><input id="c7" type='checkbox'>
+ <label id="l8"/><input id="c8" type='checkbox'>
+ <label id="l9"/><input id="c9" type='checkbox'>
+ <label id="l10"/><input id="c10" type='checkbox'>
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 646157 **/
+
+var expectedClicks = {
+ // [ Direct clicks, bubbled clicks, synthetic clicks]
+ l1: [0, 2, 1],
+ l2: [0, 2, 1],
+ l3: [0, 2, 1],
+ l4: [0, 2, 1],
+ l5: [0, 2, 1],
+ l6: [0, 2, 1],
+ l7: [0, 2, 1],
+ l8: [0, 2, 1],
+ l9: [0, 2, 1],
+ l10:[1, 2, 1],
+ c1: [0, 0, 0],
+ c2: [0, 0, 0],
+ c3: [0, 0, 0],
+ c4: [0, 0, 0],
+ c5: [0, 0, 0],
+ c6: [0, 0, 0],
+ c7: [0, 0, 0],
+ c8: [0, 0, 0],
+ c9: [0, 0, 0],
+ c10:[1, 1, 1]
+};
+
+function clickhandler(e) {
+ if (!e.currentTarget.clickCount)
+ e.currentTarget.clickCount = 1;
+ else
+ e.currentTarget.clickCount++;
+
+ if (e.currentTarget === e.target)
+ e.currentTarget.directClickCount = 1;
+
+ if (e.target != document.getElementById("l10")) {
+ if (!e.currentTarget.synthClickCount)
+ e.currentTarget.synthClickCount = 1;
+ else
+ e.currentTarget.synthClickCount++;
+ }
+}
+
+for (var i = 1; i <= 10; i++) {
+ document.getElementById("l" + i).addEventListener('click', clickhandler, false);
+ document.getElementById("c" + i).addEventListener('click', clickhandler, false);
+}
+
+document.getElementById("l10").click();
+
+function check(thing) {
+ var expected = expectedClicks[thing.id];
+ is(thing.directClickCount || 0, expected[0], "Wrong number of direct clicks");
+ is(thing.clickCount || 0, expected[1], "Wrong number of clicks");
+ is(thing.synthClickCount || 0, expected[2], "Wrong number of synthetic clicks");
+}
+
+// Compare them all
+for (var i = 1; i <= 10; i++) {
+ check(document.getElementById("l" + i));
+ check(document.getElementById("c" + i));
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug649134.html b/dom/html/test/test_bug649134.html
new file mode 100644
index 000000000..1bedfbe3c
--- /dev/null
+++ b/dom/html/test/test_bug649134.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=649134
+-->
+<head>
+ <title>Test for Bug 649134</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=649134">Mozilla Bug 649134</a>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 649134 **/
+SimpleTest.waitForExplicitFinish();
+
+var calls = 0;
+function finish() {
+ if (++calls == 4)
+ SimpleTest.finish();
+}
+function verifyNoLoad(iframe) {
+ ok(iframe.contentDocument.body.offsetHeight > 0,
+ "HTTP Link stylesheet was ignored " + iframe.src);
+ finish();
+}
+var verifyLoadCalls = 0;
+function verifyLoad(iframe) {
+ if (++verifyLoadCalls == 2) {
+ ok(indexContent == iframe.contentDocument.body.innerHTML,
+ "bug649134/ loads bug649134/index.html " + iframe.src);
+ }
+ finish();
+}
+function indexLoad(iframe) {
+ indexContent = iframe.contentDocument.body.innerHTML;
+ verifyLoad(iframe);
+}
+
+</script>
+</pre>
+<p id="display">
+<!-- Note: the extra sub-directory is needed for the test, see bug 649134 comment 14 -->
+<iframe onload="verifyNoLoad(this);" src="bug649134/file_bug649134-1.sjs"></iframe>
+<iframe onload="verifyNoLoad(this);" src="bug649134/file_bug649134-2.sjs"></iframe>
+<iframe onload="verifyLoad(this);" src="bug649134/"></iframe> <!-- verify that mochitest server loads index.html -->
+<iframe onload="indexLoad(this);" src="bug649134/index.html"></iframe>
+</p>
+</body>
+</html>
diff --git a/dom/html/test/test_bug651956.html b/dom/html/test/test_bug651956.html
new file mode 100644
index 000000000..59fbd3936
--- /dev/null
+++ b/dom/html/test/test_bug651956.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=651956
+-->
+<head>
+ <title>Test for Bug 651956</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <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=651956">Mozilla Bug 651956</a>
+<p id="display"></p>
+<div id="content">
+ <input>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 651956 **/
+
+var input = document.getElementsByTagName('input')[0];
+
+var gotInputEvent = false;
+
+input.addEventListener("input", function() {
+ input.removeEventListener("input", arguments.callee, false);
+ gotInputEvent = true;
+}, false);
+
+input.addEventListener("focus", function() {
+ input.removeEventListener("focus", arguments.callee, false);
+ synthesizeKey("VK_ESCAPE", {});
+
+ setTimeout(function() {
+ ok(!gotInputEvent, "No input event should have been sent.");
+ SimpleTest.finish();
+ });
+}, false);
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ input.focus();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug658746.html b/dom/html/test/test_bug658746.html
new file mode 100644
index 000000000..df5982547
--- /dev/null
+++ b/dom/html/test/test_bug658746.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=658746
+-->
+<head>
+ <title>Test for Bug 658746</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=658746">Mozilla Bug 658746</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 658746 **/
+
+/**
+ * Sets property, gets property and deletes property.
+ */
+function SetGetDelete(prop)
+{
+ var el = document.createElement('div');
+
+ el.dataset[prop] = 'aaaaaa';
+ is(el.dataset[prop], 'aaaaaa', 'Dataset property "' + prop + '" should have been set.');
+
+ delete el.dataset[prop];
+ is(el.dataset[prop], undefined, 'Dataset property"' + prop + '" should have been deleted.');
+}
+
+/**
+ * Gets, deletes and sets property. Expects exception while trying to set property.
+ */
+function SetExpectException(prop)
+{
+ var el = document.createElement('div');
+
+ is(el.dataset[prop], undefined, 'Dataset property "' + prop + '" should be undefined.');
+ delete el.dataset[prop];
+
+ try {
+ el.dataset[prop] = "xxxxxx";
+ ok(false, 'Exception should have been thrown when setting "' + prop + '".');
+ } catch (ex) {
+ ok(true, 'Exception should have been thrown.');
+ }
+}
+
+// Numbers as properties.
+SetGetDelete(-12345678901234567980);
+SetGetDelete(-1);
+SetGetDelete(0);
+SetGetDelete(1);
+SetGetDelete(12345678901234567980);
+
+// Floating point numbers as properties.
+SetGetDelete(-1.1);
+SetGetDelete(0.0);
+SetGetDelete(1.1);
+
+// Hexadecimal numbers as properties.
+SetGetDelete(0x3);
+SetGetDelete(0xa);
+
+// Octal numbers as properties.
+SetGetDelete(03);
+SetGetDelete(07);
+
+// String numbers as properties.
+SetGetDelete('0');
+SetGetDelete('01');
+SetGetDelete('0x1');
+
+// Undefined as property.
+SetGetDelete(undefined);
+
+// Empty arrays as properties.
+SetGetDelete(new Array());
+SetGetDelete([]);
+
+// Non-empty array and object as properties.
+SetExpectException(['a', 'b']);
+SetExpectException({'a':'b'});
+
+// Objects as properties.
+SetExpectException(new Object());
+SetExpectException(document);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug659596.html b/dom/html/test/test_bug659596.html
new file mode 100644
index 000000000..a8828cf0a
--- /dev/null
+++ b/dom/html/test/test_bug659596.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=659596
+-->
+<head>
+ <title>Test for Bug 659596</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=659596">Mozilla Bug 659596</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 659596 **/
+
+function checkReflection(option, attribute) {
+ /**
+ * Getting.
+ */
+
+ // When attribute isn't present.
+ var tests = [ "", "foo" ];
+ for (var test of tests) {
+ option.removeAttribute(attribute);
+ option.textContent = test;
+ is(option.getAttribute(attribute), null,
+ "option " + attribute + "'s value should be null");
+ is(option[attribute], option.textContent,
+ "option." + attribute + " should reflect the text content when the attribute isn't set");
+ }
+
+ // When attribute is present.
+ tests = [
+ [ "", "" ],
+ [ "", "foo" ],
+ [ "foo", "bar" ],
+ [ "foo", "" ],
+ ];
+ for (var test of tests) {
+ option.setAttribute(attribute, test[0]);
+ option.textContent = test[1];
+ is(option[attribute], option.getAttribute(attribute),
+ "option." + attribute + " should reflect the content attribute when it is set");
+ }
+
+ /**
+ * Setting.
+ */
+
+ // When attribute isn't present.
+ tests = [
+ [ "", "new" ],
+ [ "foo", "new" ],
+ ];
+ for (var test of tests) {
+ option.removeAttribute(attribute);
+ option.textContent = test[0];
+ option[attribute] = test[1]
+
+ is(option.getAttribute(attribute), test[1],
+ "when setting, the content attribute should change");
+ is(option.textContent, test[0],
+ "when setting, the text content should not change");
+ }
+
+ // When attribute is present.
+ tests = [
+ [ "", "", "new" ],
+ [ "", "foo", "new" ],
+ [ "foo", "bar", "new" ],
+ [ "foo", "", "new" ],
+ ];
+ for (var test of tests) {
+ option.setAttribute(attribute, test[0]);
+ option.textContent = test[1];
+ option[attribute] = test[2];
+
+ is(option.getAttribute(attribute), test[2],
+ "when setting, the content attribute should change");
+ is(option.textContent, test[1],
+ "when setting, the text content should not change");
+ }
+}
+
+var option = document.createElement("option");
+
+checkReflection(option, "value");
+checkReflection(option, "label");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug659743.xml b/dom/html/test/test_bug659743.xml
new file mode 100644
index 000000000..12236bdc0
--- /dev/null
+++ b/dom/html/test/test_bug659743.xml
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=659743
+-->
+<head>
+ <title>Test for Bug 659743</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=659743">Mozilla Bug 659743</a>
+<p id="display">
+<map name="a">
+<area shape="rect" coords="25,25,75,75" href="#x"/>
+</map>
+<map id="b">
+<area shape="rect" coords="25,25,75,75" href="#y"/>
+</map>
+<map name="a">
+<area shape="rect" coords="25,25,75,75" href="#FAIL"/>
+</map>
+<map id="b">
+<area shape="rect" coords="25,25,75,75" href="#FAIL"/>
+</map>
+
+<img usemap="#a" src="image.png"/>
+<img usemap="#b" src="image.png"/>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 659743 **/
+SimpleTest.waitForExplicitFinish();
+var images = document.getElementsByTagName("img");
+var second = false;
+onhashchange = function() {
+ if (!second) {
+ second = true;
+ is(location.hash, "#x", "First map");
+ SimpleTest.waitForFocus(() => synthesizeMouse(images[1], 50, 50, {}));
+ } else {
+ is(location.hash, "#y", "Second map");
+ SimpleTest.finish();
+ }
+};
+SimpleTest.waitForFocus(() => synthesizeMouse(images[0], 50, 50, {}));
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug660663.html b/dom/html/test/test_bug660663.html
new file mode 100644
index 000000000..2ce3f9ac6
--- /dev/null
+++ b/dom/html/test/test_bug660663.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=660663
+-->
+<head>
+ <title>Test for Bug 660663</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=660663">Mozilla Bug 660663</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 660663 **/
+reflectLimitedEnumerated({
+ element: document.createElement("div"),
+ attribute: "dir",
+ validValues: ["ltr", "rtl", "auto"],
+ invalidValues: ["cheesecake", ""]
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug660959-1.html b/dom/html/test/test_bug660959-1.html
new file mode 100644
index 000000000..0bffa0e05
--- /dev/null
+++ b/dom/html/test/test_bug660959-1.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=660959
+-->
+<head>
+ <title>Test for Bug 660959</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=660959">Mozilla Bug 660959</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <a href="#" id="testa"></a>
+</div>
+<pre id="test">
+<script>
+ is($("content").querySelector(":link, :visited"), $("testa"),
+ "Should find a link even in a display:none subtree");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug660959-2.html b/dom/html/test/test_bug660959-2.html
new file mode 100644
index 000000000..a4d74b6e3
--- /dev/null
+++ b/dom/html/test/test_bug660959-2.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=660959
+-->
+<head>
+ <title>Test for Bug 660959</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ :link, :visited {
+ color: red;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=660959">Mozilla Bug 660959</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <a href="#" id="a"></a>
+</div>
+<pre id="test">
+<script type="application/javascript">
+ var a = document.getElementById("a");
+ is(window.getComputedStyle(a).color, "rgb(255, 0, 0)", "Link is not right color?");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug660959-3.html b/dom/html/test/test_bug660959-3.html
new file mode 100644
index 000000000..4539a0449
--- /dev/null
+++ b/dom/html/test/test_bug660959-3.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=660959
+-->
+<head>
+ <title>Test for Bug 660959</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=660959">Mozilla Bug 660959</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <a href="http://www.example.com"></a>
+ <div id="foo">
+ <span id="test"></span>
+ </div>
+</div>
+<pre id="test">
+<script>
+ is($("foo").querySelector(":link + * span, :visited + * span"), $("test"),
+ "Should be able to find link siblings even in a display:none subtree");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug666200.html b/dom/html/test/test_bug666200.html
new file mode 100644
index 000000000..d2a748697
--- /dev/null
+++ b/dom/html/test/test_bug666200.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666200
+-->
+<head>
+ <title>Test for Bug 666200</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=666200">Mozilla Bug 666200</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 666200 **/
+var sel = document.createElement("select");
+var opt1 = new Option();
+var opt2 = new Option();
+var opt3 = new Option();
+var opt4 = new Option();
+var opt5 = new Option();
+opt1.value = 1;
+opt2.value = 2;
+opt3.value = 3;
+opt4.value = 4;
+opt5.value = 5;
+sel.add(opt1);
+sel.add(opt2, 0);
+sel.add(opt3, 1000);
+sel.options.add(opt4, opt3);
+sel.add(opt5, undefined);
+is(sel[0], opt2, "1st item should be 2");
+is(sel[1], opt1, "2nd item should be 1");
+is(sel[2], opt4, "3rd item should be 4");
+is(sel[3], opt3, "4th item should be 3");
+is(sel[4], opt5, "5th item should be 5");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug666666.html b/dom/html/test/test_bug666666.html
new file mode 100644
index 000000000..3bd1d5832
--- /dev/null
+++ b/dom/html/test/test_bug666666.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666666
+-->
+<head>
+ <title>Test for Bug 666666</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666666">Mozilla Bug 666666</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 666666 **/
+["audio", "video"].forEach(function(element) {
+ reflectLimitedEnumerated({
+ element: document.createElement(element),
+ attribute: "preload",
+ validValues: ["none", "metadata", "auto"],
+ invalidValues: ["cheesecake", ""]
+ });
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug669012.html b/dom/html/test/test_bug669012.html
new file mode 100644
index 000000000..e6be933fa
--- /dev/null
+++ b/dom/html/test/test_bug669012.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=669012
+-->
+<head>
+ <title>Test for Bug 669012</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=669012">Mozilla Bug 669012</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<script>
+var run = 0;
+</script>
+<svg>
+<script>
+run++;
+ok(true, "Should run SVG script without attributes")
+</script>
+<script for=window event=onload>
+run++;
+ok(true, "Should run SVG script with for=window event=onload")
+</script>
+<script for=window event=foo>
+run++;
+ok(true, "Should run SVG script with for=window event=foo")
+</script>
+<script for=foo event=onload>
+run++;
+ok(true, "Should run SVG script with for=foo event=onload")
+</script>
+</svg>
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 669012 **/
+is(run, 4, "Should have run all tests")
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug674558.html b/dom/html/test/test_bug674558.html
new file mode 100644
index 000000000..c728bc14e
--- /dev/null
+++ b/dom/html/test/test_bug674558.html
@@ -0,0 +1,290 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=674558
+-->
+<head>
+ <title>Test for Bug 674558</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=674558">Mozilla Bug 674558</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 674558 **/
+SimpleTest.waitForExplicitFinish();
+
+// Turn off spatial navigation because it hijacks VK_RIGHT and VK_LEFT keydown
+// events.
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[["snav.enabled", false]]}, startTest);
+});
+function startTest() {
+ function textAreaCtor() {
+ return document.createElement("textarea");
+ }
+ var ctors = [textAreaCtor];
+ ["text", "password", "search"].forEach(function(type) {
+ ctors.push(function inputCtor() {
+ var input = document.createElement("input");
+ input.type = type;
+ return input;
+ });
+ });
+
+ for (var ctor in ctors) {
+ test(ctors[ctor]);
+ }
+
+ SimpleTest.finish();
+}
+
+function test(ctor) {
+ var elem = ctor();
+ ok(true, "Testing " + name(elem));
+
+ ok("selectionDirection" in elem, "elem should have the selectionDirection property");
+
+ is(elem.selectionStart, elem.value.length, "Default value");
+ is(elem.selectionEnd, elem.value.length, "Default value");
+ is(elem.selectionDirection, "forward", "Default value");
+
+ var content = document.getElementById("content");
+ content.appendChild(elem);
+
+ function flush() { document.body.clientWidth; }
+ function hide() {
+ content.style.display = "none";
+ flush();
+ }
+ function show() {
+ content.style.display = "";
+ flush();
+ }
+
+ elem.value = "foobar";
+
+ is(elem.selectionStart, elem.value.length, "Default value");
+ is(elem.selectionEnd, elem.value.length, "Default value");
+ is(elem.selectionDirection, "forward", "Default value");
+
+ elem.setSelectionRange(1, 3);
+ is(elem.selectionStart, 1, "Correct value");
+ is(elem.selectionEnd, 3, "Correct value");
+ is(elem.selectionDirection, "forward", "If not set, should default to forward");
+
+ hide();
+ is(elem.selectionStart, 1, "Value unchanged");
+ is(elem.selectionEnd, 3, "Value unchanged");
+ is(elem.selectionDirection, "forward", "Value unchanged");
+
+ show();
+ is(elem.selectionStart, 1, "Value unchanged");
+ is(elem.selectionEnd, 3, "Value unchanged");
+ is(elem.selectionDirection, "forward", "Value unchanged");
+
+ // extend to right
+ elem.focus();
+ synthesizeKey("VK_RIGHT", {shiftKey: true});
+
+ is(elem.selectionStart, 1, "Value unchanged");
+ is(elem.selectionEnd, 4, "Correct value");
+ is(elem.selectionDirection, "forward", "Still forward");
+
+ hide();
+ is(elem.selectionStart, 1, "Value unchanged");
+ is(elem.selectionEnd, 4, "Value unchanged");
+ is(elem.selectionDirection, "forward", "Value unchanged");
+
+ show();
+ is(elem.selectionStart, 1, "Value unchanged");
+ is(elem.selectionEnd, 4, "Value unchanged");
+ is(elem.selectionDirection, "forward", "Value unchanged");
+
+ // change the direction
+ elem.selectionDirection = "backward";
+
+ is(elem.selectionStart, 1, "Value unchanged");
+ is(elem.selectionEnd, 4, "Value unchanged");
+ is(elem.selectionDirection, "backward", "Correct value");
+
+ hide();
+ is(elem.selectionStart, 1, "Value unchanged");
+ is(elem.selectionEnd, 4, "Value unchanged");
+ is(elem.selectionDirection, "backward", "Value unchanged");
+
+ show();
+ is(elem.selectionStart, 1, "Value unchanged");
+ is(elem.selectionEnd, 4, "Value unchanged");
+ is(elem.selectionDirection, "backward", "Value unchanged");
+
+ // extend to right again
+ synthesizeKey("VK_RIGHT", {shiftKey: true});
+
+ is(elem.selectionStart, 2, "Correct value");
+ is(elem.selectionEnd, 4, "Value unchanged");
+ is(elem.selectionDirection, "backward", "Still backward");
+
+ hide();
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 4, "Value unchanged");
+ is(elem.selectionDirection, "backward", "Value unchanged");
+
+ show();
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 4, "Value unchanged");
+ is(elem.selectionDirection, "backward", "Value unchanged");
+
+ elem.selectionEnd = 5;
+
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Correct value");
+ is(elem.selectionDirection, "backward", "Still backward");
+
+ hide();
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Value unchanged");
+ is(elem.selectionDirection, "backward", "Value unchanged");
+
+ show();
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Value unchanged");
+ is(elem.selectionDirection, "backward", "Value unchanged");
+
+ elem.selectionDirection = "none";
+
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Value unchanged");
+ is(elem.selectionDirection, "forward", "none not supported");
+
+ hide();
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Value unchanged");
+ is(elem.selectionDirection, "forward", "Value unchanged");
+
+ show();
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Value unchanged");
+ is(elem.selectionDirection, "forward", "Value unchanged");
+
+ elem.selectionDirection = "backward";
+
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Value unchanged");
+ is(elem.selectionDirection, "backward", "Correct Value");
+
+ hide();
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Value unchanged");
+ is(elem.selectionDirection, "backward", "Value unchanged");
+
+ show();
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Value unchanged");
+ is(elem.selectionDirection, "backward", "Value unchanged");
+
+ elem.selectionDirection = "invalid";
+
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Value unchanged");
+ is(elem.selectionDirection, "forward", "Treated as none");
+
+ hide();
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Value unchanged");
+ is(elem.selectionDirection, "forward", "Value unchanged");
+
+ show();
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Value unchanged");
+ is(elem.selectionDirection, "forward", "Value unchanged");
+
+ elem.selectionDirection = "backward";
+
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Value unchanged");
+ is(elem.selectionDirection, "backward", "Correct Value");
+
+ hide();
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Value unchanged");
+ is(elem.selectionDirection, "backward", "Value unchanged");
+
+ show();
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Value unchanged");
+ is(elem.selectionDirection, "backward", "Value unchanged");
+
+ elem.setSelectionRange(1, 4);
+
+ is(elem.selectionStart, 1, "Correct value");
+ is(elem.selectionEnd, 4, "Correct value");
+ is(elem.selectionDirection, "forward", "Correct value");
+
+ hide();
+ is(elem.selectionStart, 1, "Value unchanged");
+ is(elem.selectionEnd, 4, "Value unchanged");
+ is(elem.selectionDirection, "forward", "Value unchanged");
+
+ show();
+ is(elem.selectionStart, 1, "Value unchanged");
+ is(elem.selectionEnd, 4, "Value unchanged");
+ is(elem.selectionDirection, "forward", "Value unchanged");
+
+ elem.setSelectionRange(1, 1);
+ synthesizeKey("VK_RIGHT", {shiftKey: true});
+ synthesizeKey("VK_RIGHT", {shiftKey: true});
+ synthesizeKey("VK_RIGHT", {shiftKey: true});
+
+ is(elem.selectionStart, 1, "Correct value");
+ is(elem.selectionEnd, 4, "Correct value");
+ is(elem.selectionDirection, "forward", "Correct value");
+
+ hide();
+ is(elem.selectionStart, 1, "Value unchanged");
+ is(elem.selectionEnd, 4, "Value unchanged");
+ is(elem.selectionDirection, "forward", "Value unchanged");
+
+ show();
+ is(elem.selectionStart, 1, "Value unchanged");
+ is(elem.selectionEnd, 4, "Value unchanged");
+ is(elem.selectionDirection, "forward", "Value unchanged");
+
+ elem.setSelectionRange(5, 5);
+ synthesizeKey("VK_LEFT", {shiftKey: true});
+ synthesizeKey("VK_LEFT", {shiftKey: true});
+ synthesizeKey("VK_LEFT", {shiftKey: true});
+
+ is(elem.selectionStart, 2, "Correct value");
+ is(elem.selectionEnd, 5, "Correct value");
+ is(elem.selectionDirection, "backward", "Correct value");
+
+ hide();
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Value unchanged");
+ is(elem.selectionDirection, "backward", "Value unchanged");
+
+ show();
+ is(elem.selectionStart, 2, "Value unchanged");
+ is(elem.selectionEnd, 5, "Value unchanged");
+ is(elem.selectionDirection, "backward", "Value unchanged");
+}
+
+function name(elem) {
+ var tag = elem.localName;
+ if (tag == "input") {
+ tag += "[type=" + elem.type + "]";
+ }
+ return tag;
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug674927.html b/dom/html/test/test_bug674927.html
new file mode 100644
index 000000000..92af59453
--- /dev/null
+++ b/dom/html/test/test_bug674927.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=674927
+-->
+<title>Test for Bug 674927</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<p><span>Hello</span></p>
+<div contenteditable>Contenteditable <i>is</i> splelchecked by default</div>
+<textarea>Textareas are spellchekced by default</textarea>
+<input value="Inputs are not spellcheckde by default">
+<script>
+// Test the effect of setting spellcheck on various elements
+[
+ "html",
+ "body",
+ "p",
+ "span",
+ "div",
+ "i",
+ "textarea",
+ "input",
+].forEach(function(query) {
+ var element = document.querySelector(query);
+
+ // First check what happens if no attributes are set
+ var defaultSpellcheck;
+ if (element.isContentEditable || element.tagName == "TEXTAREA") {
+ defaultSpellcheck = true;
+ } else {
+ defaultSpellcheck = false;
+ }
+ is(element.spellcheck, defaultSpellcheck,
+ "Default spellcheck for <" + element.tagName.toLowerCase() + ">");
+
+ // Now try setting spellcheck on ancestors
+ var ancestor = element;
+ do {
+ testSpellcheck(ancestor, element);
+ ancestor = ancestor.parentNode;
+ } while (ancestor.nodeType == Node.ELEMENT_NODE);
+});
+
+function testSpellcheck(ancestor, element) {
+ ancestor.spellcheck = true;
+ is(element.spellcheck, true,
+ ".spellcheck on <" + element.tagName.toLowerCase() + "> with " +
+ "spellcheck=true on <" + ancestor.tagName.toLowerCase() + ">");
+ ancestor.spellcheck = false;
+ is(element.spellcheck, false,
+ ".spellcheck on <" + element.tagName.toLowerCase() + "> with " +
+ "spellcheck=false on <" + ancestor.tagName.toLowerCase() + ">");
+ ancestor.removeAttribute("spellcheck");
+}
+</script>
diff --git a/dom/html/test/test_bug677463.html b/dom/html/test/test_bug677463.html
new file mode 100644
index 000000000..2d895d8d2
--- /dev/null
+++ b/dom/html/test/test_bug677463.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677463
+-->
+<head>
+ <title>Test for Bug 677463</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=677463">Mozilla Bug 677463</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 677463 **/
+
+var o = document.createElement("option");
+var m = document.createElement("menuitem");
+is(o.label, m.label, "Should have same labels");
+o.textContent = " ";
+is(o.label, m.label, "Should have same labels");
+m.textContent = " ";
+is(o.label, m.label, "Should have same labels");
+o.textContent = " foo";
+isnot(o.label, m.label, "Shouldn't have same labels");
+m.textContent = "foo ";
+is(o.label, m.label, "Should have same labels");
+m.label = "bar";
+isnot(o.label, m.label, "Shouldn't have same labels");
+o.label = "bar";
+is(o.label, m.label, "Should have same labels");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug677495-1.html b/dom/html/test/test_bug677495-1.html
new file mode 100644
index 000000000..916997f00
--- /dev/null
+++ b/dom/html/test/test_bug677495-1.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677495
+
+As mandated by the spec, the body of a media document must only contain one child.
+-->
+<head>
+ <title>Test for Bug 571981</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 frameLoaded() {
+ var testframe = document.getElementById('testframe');
+ var testframeChildren = testframe.contentDocument.body.childNodes;
+ is(testframeChildren.length, 1, "Body of video document has 1 child");
+ is(testframeChildren[0].nodeName, "VIDEO", "Only child of body must be a <video> element");
+
+ SimpleTest.finish();
+ }
+</script>
+
+</head>
+<body>
+ <p id="display"></p>
+
+ <iframe id="testframe" name="testframe" onload="frameLoaded()"
+ src="data:video/webm,"></iframe>
+
+</body>
+</html>
diff --git a/dom/html/test/test_bug677495.html b/dom/html/test/test_bug677495.html
new file mode 100644
index 000000000..fec1912b4
--- /dev/null
+++ b/dom/html/test/test_bug677495.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677495
+
+As mandated by the spec, the body of a media document must only contain one child.
+-->
+<head>
+ <title>Test for Bug 571981</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 frameLoaded() {
+ var testframe = document.getElementById('testframe');
+ var testframeChildren = testframe.contentDocument.body.childNodes;
+ is(testframeChildren.length, 1, "Body of image document has 1 child");
+ is(testframeChildren[0].nodeName, "IMG", "Only child of body must be an <img> element");
+
+ SimpleTest.finish();
+ }
+</script>
+
+</head>
+<body>
+ <p id="display"></p>
+
+ <iframe id="testframe" name="testframe" onload="frameLoaded()"
+ src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oMFgQGMyFwHucAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC"></iframe>
+
+</body>
+</html>
diff --git a/dom/html/test/test_bug677658.html b/dom/html/test/test_bug677658.html
new file mode 100644
index 000000000..3aab5dba8
--- /dev/null
+++ b/dom/html/test/test_bug677658.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677658
+-->
+<head>
+ <title>Test for Bug 677658</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=677658">Mozilla Bug 677658</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript"><!--
+
+/** Test for Bug 677658 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function testDone() {
+ ok(window.testPassed, "Script shouldn't have run!");
+ SimpleTest.finish();
+}
+
+function test() {
+ window.testPassed = true;
+ document.getElementById("testtarget").innerHTML =
+ "<script async src='data:text/plain, window.testPassed = false;'></script>";
+ SimpleTest.executeSoon(testDone);
+}
+
+// -->
+</script>
+</pre>
+<div id="testtarget"></div>
+</body>
+</html>
diff --git a/dom/html/test/test_bug682886.html b/dom/html/test/test_bug682886.html
new file mode 100644
index 000000000..b321d017f
--- /dev/null
+++ b/dom/html/test/test_bug682886.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=682886
+-->
+<head>
+ <title>Test for Bug 682886</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=682886">Mozilla Bug 682886</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 682886 **/
+
+
+ var m = document.createElement("menu");
+ var s = "<menuitem>foo</menuitem>";
+ m.innerHTML = s;
+ is(m.innerHTML, s, "Wrong menuitem serialization!");
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug691.html b/dom/html/test/test_bug691.html
new file mode 100644
index 000000000..cfdb53985
--- /dev/null
+++ b/dom/html/test/test_bug691.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=691
+-->
+<head>
+ <title>Test for Bug 691</title>
+ <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 show(what) {
+ var stage = document.getElementById("stage");
+ if (what == "modularity") {
+ var spaghetti = document.createElement("IMG",null);
+ spaghetti.setAttribute("SRC","nnc_lockup.gif");
+ spaghetti.setAttribute("id","foo");
+ stage.insertBefore(spaghetti,stage.firstChild);
+ }
+}
+
+function remove() {
+ var stage = document.getElementById("stage");
+ var body = document.getElementsByTagName("BODY")[0];
+ while (stage.firstChild) {
+ stage.removeChild(stage.firstChild);
+ }
+}
+
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=691">Mozilla Bug 691</a>
+<p id="display"></p>
+<div id="content" >
+<ul>
+<li >foo</li>
+</ul>
+<div id="stage">
+</div>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 691 **/
+
+show("modularity");
+remove();
+show("modularity");
+remove();
+show("modularity");
+remove();
+show("modularity");
+
+ok($("foo"), "basic DOM manipulation doesn't crash");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug694.html b/dom/html/test/test_bug694.html
new file mode 100644
index 000000000..8e88dd1cf
--- /dev/null
+++ b/dom/html/test/test_bug694.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=694
+-->
+<head>
+ <title>Test for Bug 694</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=694">Mozilla Bug 694</a>
+<p id="display"></p>
+<div id="content" >
+<img src="/missing_on_purpose" width=123 height=25 alt="Hello, &quot;Quotes&quot; how are you?" id="testimg">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 694 **/
+
+is($("testimg").getAttribute("alt"), "Hello, \"Quotes\" how are you?", "entities in alt attribute works");
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug694503.html b/dom/html/test/test_bug694503.html
new file mode 100644
index 000000000..6ffd18d64
--- /dev/null
+++ b/dom/html/test/test_bug694503.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=694503
+-->
+<head>
+ <title>Test for Bug 694503</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=694503">Mozilla Bug 694503</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<div>
+<map name="map1">
+ <area onclick="++mapClickCount; event.preventDefault();"
+ coords="0,0,50,50" shape="rect">
+</map>
+</div>
+
+<img id="img"
+ usemap="#map1" alt="Foo bar" src="about:logo">
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 694503 **/
+
+var mapClickCount = 0;
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var m = document.getElementsByTagName("map")[0];
+ var img = document.getElementById('img');
+ var origName = m.name;
+
+ synthesizeMouse(img, 25, 25, {});
+ is(mapClickCount, 1, "Wrong click count (1)");
+
+ m.name = "foo"
+ synthesizeMouse(img, 25, 25, {});
+ is(mapClickCount, 1, "Wrong click count (2)");
+
+ m.removeAttribute("name");
+ m.id = origName;
+ synthesizeMouse(img, 25, 25, {});
+ is(mapClickCount, 2, "Wrong click count (3)");
+
+ // Back to original state
+ m.removeAttribute("id");
+ m.name = origName;
+ synthesizeMouse(img, 25, 25, {});
+ is(mapClickCount, 3, "Wrong click count (4)");
+
+ var p = m.parentNode;
+ p.removeChild(m);
+ synthesizeMouse(img, 25, 25, {});
+ is(mapClickCount, 3, "Wrong click count (5)");
+
+ // Back to original state
+ p.appendChild(m);
+ synthesizeMouse(img, 25, 25, {});
+ is(mapClickCount, 4, "Wrong click count (6)");
+
+ SimpleTest.finish();
+});
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug696.html b/dom/html/test/test_bug696.html
new file mode 100644
index 000000000..35669f565
--- /dev/null
+++ b/dom/html/test/test_bug696.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=696
+-->
+<head>
+ <title>Test for Bug 696</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=696">Mozilla Bug 696</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <table><tr id="mytr"><td>Foo</td><td>Bar</td></tr></table>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 696 **/
+var mytr = $("content").getElementsByTagName("TR")[0];
+is(mytr.getAttribute("ID"),"mytr","TR tags expose their ID attribute");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug717819.html b/dom/html/test/test_bug717819.html
new file mode 100644
index 000000000..9c574dd68
--- /dev/null
+++ b/dom/html/test/test_bug717819.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=717819
+-->
+<head>
+ <title>Test for Bug 717819</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <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=717819">Mozilla Bug 717819</a>
+<p id="display"></p>
+<div id="content">
+ <table style="position: relative; top: 100px;">
+ <tr>
+ <td>
+ <div id="test" style="position: absolute; top: 50px;"></div>
+ </td>
+ </tr>
+ </table>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 717819 **/
+var div = document.getElementById("test");
+is(div.offsetTop, 50, "The offsetTop must be calculated correctly");
+is(div.offsetParent, document.querySelector("table"),
+ "The offset should be calculated off of the correct parent");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug741266.html b/dom/html/test/test_bug741266.html
new file mode 100644
index 000000000..72db28079
--- /dev/null
+++ b/dom/html/test/test_bug741266.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=741266
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 741266</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=741266">Mozilla Bug 741266</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 741266 **/
+SimpleTest.waitForExplicitFinish();
+
+var url = URL.createObjectURL(new Blob([""], { type: "text/html" }));
+var w = window.open(url, "", "width=100,height=100");
+w.onload = function() {
+ is(w.innerHeight, 100, "Popup height should be 100 when opened with window.open");
+ // XXXbz On at least some platforms, the innerWidth is off by the scrollbar
+ // width for some reason. So just make sure it's the same for both popups.
+ var width = w.innerWidth;
+ w.close();
+
+ w = document.open(url, "", "width=100,height=100");
+ w.onload = function() {
+ is(w.innerHeight, 100, "Popup height should be 100 when opened with document.open");
+ is(w.innerWidth, width, "Popup width should be the same when opened with document.open");
+ w.close();
+ SimpleTest.finish();
+ };
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug742030.html b/dom/html/test/test_bug742030.html
new file mode 100644
index 000000000..c88f38879
--- /dev/null
+++ b/dom/html/test/test_bug742030.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=742030
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 742030</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=742030">Mozilla Bug 742030</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 742030 **/
+const str = " color: #ff0000 ";
+var span = document.createElement("span");
+span.setAttribute("style", str);
+is(span.getAttribute("style"), str, "Should have set properly");
+var span2 = span.cloneNode(false);
+is(span2.getAttribute("style"), str, "Should have cloned properly");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug742549.html b/dom/html/test/test_bug742549.html
new file mode 100644
index 000000000..5ab88f36e
--- /dev/null
+++ b/dom/html/test/test_bug742549.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=742549
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 742549</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=742549">Mozilla Bug 742549</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 742549 **/
+var els = [ document.createElement("script"),
+ document.createElementNS("http://www.w3.org/2000/svg", "script") ]
+
+for (var i = 0; i < els.length; ++i) {
+ reflectLimitedEnumerated({
+ element: els[i],
+ attribute: { content: "crossorigin", idl: "crossOrigin" },
+ // "" is a valid value per spec, but gets mapped to the "anonymous" state,
+ // just like invalid values, so just list it under invalidValues
+ validValues: [ "anonymous", "use-credentials" ],
+ invalidValues: [
+ "", " aNOnYmous ", " UsE-CreDEntIALS ", "foobar", "FOOBAR", " fOoBaR "
+ ],
+ defaultValue: { invalid: "anonymous", missing: null },
+ nullable: true,
+ })
+}
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug745685.html b/dom/html/test/test_bug745685.html
new file mode 100644
index 000000000..c4544441e
--- /dev/null
+++ b/dom/html/test/test_bug745685.html
@@ -0,0 +1,105 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=745685
+-->
+<title>Test for Bug 745685</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=745685">Mozilla Bug 745685</a>
+<font>Test text</font>
+<font size=1>1</font>
+<font size=2>2</font>
+<font size=3>3</font>
+<font size=4>4</font>
+<font size=5>5</font>
+<font size=6>6</font>
+<font size=7>7</font>
+<script>
+/** Test for Bug 745685 **/
+
+var referenceSizes = {};
+for (var i = 1; i <= 7; i++) {
+ referenceSizes[i] =
+ getComputedStyle(document.querySelector('[size="' + i + '"]'))
+ .fontSize;
+ if (i > 1) {
+ isnot(referenceSizes[i], referenceSizes[i - 1],
+ "Sanity check: different <font size>s give different .fontSize");
+ }
+}
+
+function testFontSize(input, expected) {
+ var font = document.querySelector("font");
+ font.setAttribute("size", input);
+ is(font.getAttribute("size"), input,
+ "Setting doesn't round-trip (.getAttribute)");
+ is(font.size, input,
+ "Setting doesn't round-trip (.size)");
+ is(getComputedStyle(font).fontSize, referenceSizes[expected],
+ 'Incorrect size for "' + input + '" : expected the same as ' + expected);
+}
+
+function testFontSizes(input, expected) {
+ testFontSize(input, expected);
+ // Leading whitespace
+ testFontSize(" " + input, expected);
+ testFontSize("\t" + input, expected);
+ testFontSize("\n" + input, expected);
+ testFontSize("\f" + input, expected);
+ testFontSize("\r" + input, expected);
+ // Trailing garbage
+ testFontSize(input + "abcd", expected);
+ testFontSize(input + ".5", expected);
+ testFontSize(input + "e2", expected);
+}
+
+// Parse error
+testFontSizes("", 3);
+
+// No sign
+testFontSizes("0", 1);
+testFontSizes("1", 1);
+testFontSizes("2", 2);
+testFontSizes("3", 3);
+testFontSizes("4", 4);
+testFontSizes("5", 5);
+testFontSizes("6", 6);
+testFontSizes("7", 7);
+testFontSizes("8", 7);
+testFontSizes("9", 7);
+testFontSizes("10", 7);
+testFontSizes("10000000000000000000000", 7);
+
+// Minus sign
+testFontSizes("-0", 3);
+testFontSizes("-1", 2);
+testFontSizes("-2", 1);
+testFontSizes("-3", 1);
+testFontSizes("-4", 1);
+testFontSizes("-5", 1);
+testFontSizes("-6", 1);
+testFontSizes("-7", 1);
+testFontSizes("-8", 1);
+testFontSizes("-9", 1);
+testFontSizes("-10", 1);
+testFontSizes("-10000000000000000000000", 1);
+
+// Plus sign
+testFontSizes("+0", 3);
+testFontSizes("+1", 4);
+testFontSizes("+2", 5);
+testFontSizes("+3", 6);
+testFontSizes("+4", 7);
+testFontSizes("+5", 7);
+testFontSizes("+6", 7);
+testFontSizes("+7", 7);
+testFontSizes("+8", 7);
+testFontSizes("+9", 7);
+testFontSizes("+10", 7);
+testFontSizes("+10000000000000000000000", 7);
+
+// Non-HTML5 whitespace
+testFontSize("\b1", 3);
+testFontSize("\v1", 3);
+testFontSize("\0u00a01", 3);
+</script>
diff --git a/dom/html/test/test_bug763626.html b/dom/html/test/test_bug763626.html
new file mode 100644
index 000000000..960712c39
--- /dev/null
+++ b/dom/html/test/test_bug763626.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=763626
+-->
+<head>
+<title>Test for Bug 763626</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 boom()
+{
+ var r = document.createElement("iframe").sandbox;
+ SpecialPowers.DOMWindowUtils.garbageCollect();
+ is("" + r, "", "ToString should return empty string when element is gone");
+ SimpleTest.finish();
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
+
diff --git a/dom/html/test/test_bug765780.html b/dom/html/test/test_bug765780.html
new file mode 100644
index 000000000..975353e2e
--- /dev/null
+++ b/dom/html/test/test_bug765780.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=765780
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 765780</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 765780 **/
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function() {
+ var f = $("f");
+ var doc = f.contentDocument;
+ doc.designMode = "on";
+ var s = doc.createElement("script");
+ s.textContent = "parent.called = true;";
+
+ window.called = false;
+ doc.body.appendChild(s);
+ ok(called, "Script in designMode iframe should have run");
+
+ doc = doc.querySelector("iframe").contentDocument;
+ var s = doc.createElement("script");
+ s.textContent = "parent.parent.called = true;";
+
+ window.called = false;
+ doc.body.appendChild(s);
+ ok(called, "Script in designMode iframe's child should have run");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=765780">Mozilla Bug 765780</a>
+<!-- Important: iframe needs to not be display: none -->
+<p id="display"><iframe id="f" src="data:text/html,<iframe></iframe>"></iframe> </p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug780993.html b/dom/html/test/test_bug780993.html
new file mode 100644
index 000000000..14324e8e4
--- /dev/null
+++ b/dom/html/test/test_bug780993.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test for bug 780993</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function() {
+ var select = document.createElement("select");
+ var option = document.createElement("option");
+ select.appendChild(option);
+ assert_equals(select[0], option);
+ select[0] = null;
+ assert_equals(option.parentNode, null);
+ assert_equals(select[0], undefined);
+}, "Should be able to set select[n] to null.");
+test(function() {
+ var select = document.createElement("select");
+ var option = document.createElement("option");
+ var option2 = document.createElement("option");
+ select.appendChild(option);
+ assert_equals(select[0], option);
+ select[0] = option2;
+ assert_equals(option.parentNode, null);
+ assert_equals(option2.parentNode, select);
+ assert_equals(select[0], option2);
+}, "Should be able to set select[n] to an option element");
+test(function() {
+ var select = document.createElement("select");
+ var option = document.createElement("option");
+ select.appendChild(option);
+ assert_equals(select[0], option);
+ assert_throws(null, function() {
+ select[0] = 42;
+ });
+ assert_equals(option.parentNode, select);
+ assert_equals(select[0], option);
+}, "Should not be able to set select[n] to a primitive.");
+</script>
diff --git a/dom/html/test/test_bug787134.html b/dom/html/test/test_bug787134.html
new file mode 100644
index 000000000..37daa8e33
--- /dev/null
+++ b/dom/html/test/test_bug787134.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=787134
+-->
+<head>
+ <title>Test for Bug 787134</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=787134">Mozilla Bug 787134</a>
+<p id="display"></p>
+<p><a id="link-test1" href="example link">example link</a></p>
+<pre id="test">
+<script>
+ var div = document.createElement('div');
+ div.innerHTML = '<a href=#></a>';
+ var a = div.firstChild;
+ ok(a.matches(':link'), "Should match a link not in a document");
+ is(div.querySelector(':link'), a, "Should find a link not in a document");
+ a = document.querySelector('#link-test1');
+ ok(a.matches(':link'), "Should match a link in a document with an invalid URL");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug797113.html b/dom/html/test/test_bug797113.html
new file mode 100644
index 000000000..6c246eb3c
--- /dev/null
+++ b/dom/html/test/test_bug797113.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test for bug 780993</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function() {
+ var select = document.createElement("select");
+ var option = document.createElement("option");
+ select.appendChild(option);
+ assert_equals(select.options[0], option);
+ select.options[0] = null;
+ assert_equals(option.parentNode, null);
+ assert_equals(select.options[0], undefined);
+}, "Should be able to set select.options[n] to null.");
+test(function() {
+ var select = document.createElement("select");
+ var option = document.createElement("option");
+ var option2 = document.createElement("option");
+ select.appendChild(option);
+ assert_equals(select.options[0], option);
+ select.options[0] = option2;
+ assert_equals(option.parentNode, null);
+ assert_equals(option2.parentNode, select);
+ assert_equals(select.options[0], option2);
+}, "Should be able to set select.options[n] to an option element");
+test(function() {
+ var select = document.createElement("select");
+ var option = document.createElement("option");
+ select.appendChild(option);
+ assert_equals(select.options[0], option);
+ assert_throws(null, function() {
+ select.options[0] = 42;
+ });
+ assert_equals(option.parentNode, select);
+ assert_equals(select.options[0], option);
+}, "Should not be able to set select.options[n] to a primitive.");
+</script>
diff --git a/dom/html/test/test_bug803677.html b/dom/html/test/test_bug803677.html
new file mode 100644
index 000000000..fd754ba00
--- /dev/null
+++ b/dom/html/test/test_bug803677.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=803677
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 803677</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<style>
+ .base { border:1px solid gray; }
+ .bad-table { display:table-cell; border:1px solid red; }
+</style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=803677">Mozilla Bug 803677</a>
+<p id="display"></p>
+<div id="content">
+ <p class="base">1</p>
+ <p class="base">2</p>
+ <p class="base">3</p>
+ <p class="base bad-table">4</p>
+ <p class="base">7</p>
+ <p class="base">8</p>
+ <p class="base">9</p>
+</div>
+<pre id="test">
+<script type="application/javascript">
+ var p = document.querySelectorAll(".base");
+ var parent = document.querySelector("body");
+ var prevOffset = 0;
+ for (var i = 0; i < p.length; i++) {
+ var t = 0, e = p[i];
+ is(e.offsetParent, parent, "Offset parent of all paragraphs should be the body.");
+ while (e) {
+ t += e.offsetTop;
+ e = e.offsetParent;
+ }
+ p[i].innerHTML = t;
+
+ ok(t > prevOffset, "Offset should increase down the page");
+ prevOffset = t;
+ }
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug821307.html b/dom/html/test/test_bug821307.html
new file mode 100644
index 000000000..46721fcca
--- /dev/null
+++ b/dom/html/test/test_bug821307.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=821307
+-->
+<head>
+ <title>Test for Bug 821307</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=821307">Mozilla Bug 821307</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<input id='dummy'></input>
+<input type="password" id='input' value='11111111111111111' style="width:40em; font-size:40px;"></input>
+
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ var dummy = document.getElementById('dummy');
+ dummy.focus();
+ is(document.activeElement, dummy, "Check dummy element is now focused");
+
+ var input = document.getElementById('input');
+ var rect = input.getBoundingClientRect();
+ synthesizeMouse(input, 100, rect.height/2, {});
+ is(document.activeElement, input, "Check input element is now focused");
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug827126.html b/dom/html/test/test_bug827126.html
new file mode 100644
index 000000000..3d8b1a597
--- /dev/null
+++ b/dom/html/test/test_bug827126.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=827126
+-->
+<head>
+ <title>Test for Bug 827126</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=827126">Mozilla Bug 827126</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test to ensure we reflect <img align> correctly **/
+reflectString({
+ element: new Image(),
+ attribute: "align",
+ otherValues: [ "left", "right", "middle", "justify" ]
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug838582.html b/dom/html/test/test_bug838582.html
new file mode 100644
index 000000000..5c227c2bd
--- /dev/null
+++ b/dom/html/test/test_bug838582.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=838582
+-->
+<head>
+ <title>Test for Bug 838582</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=838582">Mozilla Bug 838582</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<textarea id="t">abc</textarea>
+<script type="application/javascript">
+
+/** Test for Bug 838582 **/
+
+var textarea = document.getElementById("t");
+
+is(t.textLength, 3, "Correct textLength for defaultValue");
+t.value = "abcdef";
+is(t.textLength, 6, "Correct textLength for value");
+ok(!("controllers" in t), "Don't have web-visible controllers property");
+ok("controllers" in SpecialPowers.wrap(t), "Have chrome-visible controllers property");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug839371.html b/dom/html/test/test_bug839371.html
new file mode 100644
index 000000000..293c0c452
--- /dev/null
+++ b/dom/html/test/test_bug839371.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=839371
+-->
+<head>
+ <title>Test for Bug 839371</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=839371">Mozilla Bug 839371</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<div itemscope>
+ <data id="d1" itemprop="product-id" value="9678AOU879">The Instigator 2000</data>
+</div>
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 839371 **/
+
+var d1 = document.getElementById("d1"),
+ d2 = document.createElement("data");
+
+// .value IDL
+is(d1.value, "9678AOU879", "value property reflects content attribute");
+d1.value = "123";
+is(d1.value, "123", "value property can be set via setter");
+
+// .value reflects value attribute
+reflectString({
+ element: d2,
+ attribute: "value"
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug839913.html b/dom/html/test/test_bug839913.html
new file mode 100644
index 000000000..7397fa3b6
--- /dev/null
+++ b/dom/html/test/test_bug839913.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for HTMLAreaElement's stringifier</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ var area = document.createElement("area");
+ area.href = "http://example.org/";
+ assert_equals(area.href, "http://example.org/");
+ assert_equals(String(area), "http://example.org/");
+}, "Area elements should stringify to the href attribute");
+</script>
diff --git a/dom/html/test/test_bug841466.html b/dom/html/test/test_bug841466.html
new file mode 100644
index 000000000..260ecfcca
--- /dev/null
+++ b/dom/html/test/test_bug841466.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=841466
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 841466</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+ <script>
+ /** Test for Bug 841466 **/
+var els = ['button', 'fieldset', 'input', 'keygen', 'object', 'output', 'select', 'textarea'];
+var code = "try { is(foo, 'bar', 'expected value bar from expando on element ' + localName); } catch (e) { ok(false, String(e)); }";
+els.forEach(function(el) {
+ var f = document.createElement("form");
+ f.foo = "bar";
+ f.innerHTML = '<' + el + ' onclick="' + code + '">';
+ var e = f.firstChild
+ if (el === "keygen") {
+ todo_is(e.localName, "keygen", "Bug 101019");
+ return;
+ }
+ e.dispatchEvent(new Event("click"));
+})
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=841466">Mozilla Bug 841466</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug845057.html b/dom/html/test/test_bug845057.html
new file mode 100644
index 000000000..631cd2b5c
--- /dev/null
+++ b/dom/html/test/test_bug845057.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=845057
+-->
+<head>
+ <title>Test for Bug 845057</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=845057">Mozilla Bug 845057</a>
+<p id="display"></p>
+<div id="content">
+ <iframe id="iframe" sandbox="allow-scripts"></iframe>
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ var iframe = document.getElementById("iframe"),
+ attr = iframe.sandbox;
+ // Security enforcement tests for iframe sandbox are in test_iframe_*
+
+ function eq(a, b) {
+ // check if two attributes are qual modulo permutation
+ return ((a+'').split(" ").sort()+'') == ((b+'').split(" ").sort()+'');
+ }
+
+ ok(attr instanceof DOMTokenList,
+ "Iframe sandbox attribute is instace of DOMTokenList");
+ ok(eq(attr, "allow-scripts") &&
+ eq(iframe.getAttribute("sandbox"), "allow-scripts"),
+ "Stringyfied sandbox attribute is same as that of the DOM element");
+
+ ok(attr.contains("allow-scripts") && !attr.contains("allow-same-origin"),
+ "Set membership of attribute elements is ok");
+
+ attr.add("allow-same-origin");
+
+ ok(attr.contains("allow-scripts") && attr.contains("allow-same-origin"),
+ "Attribute contains added atom");
+ ok(eq(attr, "allow-scripts allow-same-origin") &&
+ eq(iframe.getAttribute("sandbox"), "allow-scripts allow-same-origin"),
+ "Stringyfied attribute with new atom is correct");
+
+ attr.add("allow-forms");
+ attr.remove("allow-scripts");
+
+ ok(!attr.contains("allow-scripts") && attr.contains("allow-forms") &&
+ attr.contains("allow-same-origin"),
+ "Attribute does not contain removed atom");
+ ok(eq(attr, "allow-forms allow-same-origin") &&
+ eq(iframe.getAttribute("sandbox"), "allow-forms allow-same-origin"),
+ "Stringyfied attribute with removed atom is correct");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_bug869040.html b/dom/html/test/test_bug869040.html
new file mode 100644
index 000000000..bfd75c7b7
--- /dev/null
+++ b/dom/html/test/test_bug869040.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=869040
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 869040</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=869040">Mozilla Bug 869040</a>
+<p id="display"></p>
+<div id="content" style="display: none" data-foo="present1" data-bar="present2">
+
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ /** Test for Bug 869040 **/
+ var foo = "default1";
+ var dataset = $("content").dataset;
+ for (var i = 0; i < 100000; ++i)
+ foo = dataset.foo;
+
+ var bar = "default2";
+ for (var j = 0; j < 100; ++j)
+ bar = dataset.bar;
+
+ is(foo, "present1", "Our IC should work");
+ is(bar, "present2", "Our non-IC case should work");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/test_bug870787.html b/dom/html/test/test_bug870787.html
new file mode 100644
index 000000000..92a871284
--- /dev/null
+++ b/dom/html/test/test_bug870787.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=870787
+-->
+<head>
+ <title>Test for Bug 870787</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=870787">Mozilla Bug 870787</a>
+
+<p id="msg"></p>
+
+<form id="form0"></form>
+<img name="img0" id="img0id">
+
+<img name="img1" id="img1id" />
+<form id="form1">
+ <img name="img2" id="img2id" />
+</form>
+<img name="img3" id="img3id" />
+
+<table>
+ <form id="form2">
+ <tr><td>
+ <button name="input1" id="input1id" />
+ <input name="input2" id="input2id" />
+ </form>
+</table>
+
+<table>
+ <form id="form3">
+ <tr><td>
+ <img name="img4" id="img4id" />
+ <img name="img5" id="img5id" />
+ </form>
+</table>
+
+<form id="form4"><img id="img6"></form>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 870787 **/
+
+var form0 = document.getElementById("form0");
+ok(form0, "Form0 exists");
+ok(!form0.img0, "Form0.img0 doesn't exist");
+ok(!form0.img0id, "Form0.img0id doesn't exist");
+
+var form1 = document.getElementById("form1");
+ok(form1, "Form1 exists");
+ok(!form1.img1, "Form1.img1 doesn't exist");
+ok(!form1.img1id, "Form1.img1id doesn't exist");
+is(form1.img2, document.getElementById("img2id"), "Form1.img2 exists");
+is(form1.img2id, document.getElementById("img2id"), "Form1.img2id exists");
+ok(!form1.img3, "Form1.img3 doesn't exist");
+ok(!form1.img3id, "Form1.img3id doesn't exist");
+
+var form2 = document.getElementById("form2");
+ok(form2, "Form2 exists");
+is(form2.input1, document.getElementById("input1id"), "Form2.input1 exists");
+is(form2.input1id, document.getElementById("input1id"), "Form2.input1id exists");
+is(form2.input2, document.getElementById("input2id"), "Form2.input2 exists");
+is(form2.input2id, document.getElementById("input2id"), "Form2.input2id exists");
+
+var form3 = document.getElementById("form3");
+ok(form3, "Form3 exists");
+is(form3.img4, document.getElementById("img4id"), "Form3.img4 doesn't exists");
+is(form3.img4id, document.getElementById("img4id"), "Form3.img4id doesn't exists");
+is(form3.img5, document.getElementById("img5id"), "Form3.img5 doesn't exists");
+is(form3.img5id, document.getElementById("img5id"), "Form3.img5id doesn't exists");
+
+var form4 = document.getElementById("form4");
+ok(form4, "Form4 exists");
+is(Object.getOwnPropertyNames(form4.elements).indexOf("img6"), -1, "Form4.elements should not contain img6");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug871161.html b/dom/html/test/test_bug871161.html
new file mode 100644
index 000000000..c2049b25b
--- /dev/null
+++ b/dom/html/test/test_bug871161.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=871161
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 871161</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 871161 **/
+ SimpleTest.waitForExplicitFinish();
+
+ window.onmessage = function(e) {
+ is(e.data, "windows-1252", "Wrong charset");
+ e.source.close();
+ SimpleTest.finish();
+ }
+
+ function run() {
+ window.open("file_bug871161-1.html");
+ }
+
+ </script>
+</head>
+<body onload="run();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=871161">Mozilla Bug 871161</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug874758.html b/dom/html/test/test_bug874758.html
new file mode 100644
index 000000000..b8bfe40f3
--- /dev/null
+++ b/dom/html/test/test_bug874758.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html data-expando-prop="xyz">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=874758
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 874758</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 874758 **/
+ Object.prototype.expandoProp = 5;
+ is({}.expandoProp, 5, "Should see this on random objects");
+
+ is(document.head.dataset.expandoProp, 5, "Should see this on dataset too");
+ is(document.documentElement.dataset.expandoProp, "xyz",
+ "But if the dataset has it, we should get it from there");
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=874758">Mozilla Bug 874758</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug879319.html b/dom/html/test/test_bug879319.html
new file mode 100644
index 000000000..a232461ba
--- /dev/null
+++ b/dom/html/test/test_bug879319.html
@@ -0,0 +1,92 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=879319
+-->
+<head>
+ <title>Test for Bug 879319</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=879319">Mozilla Bug 879319</a>
+
+<p id="msg"></p>
+
+<form id="form">
+ <img id="img0" name="bar0" />
+</form>
+<input id="input0" name="foo0" form="form" />
+<input id="input1" name="foo1" form="form" />
+<input id="input2" name="foo2" form="form" />
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 879319 **/
+
+var input0 = document.getElementById("input0");
+ok(input0, "input0 exists");
+
+var form = document.getElementById("form");
+ok(form, "form exists");
+is(form.foo0, input0, "Form.foo0 should exist");
+
+ok("foo0" in form.elements, "foo0 in form.elements");
+is(input0.form, form, "input0.form is form");
+
+input0.setAttribute("name", "tmp0");
+ok("tmp0" in form.elements, "tmp0 is in form.elements");
+ok(!("foo0" in form.elements), "foo0 is not in form.elements");
+is(form.tmp0, input0, "Form.tmp0 == input0");
+is(form.foo0, input0, "Form.foo0 is still here");
+
+input0.setAttribute("name", "tmp1");
+ok("tmp1" in form.elements, "tmp1 is in form.elements");
+ok(!("tmp0" in form.elements), "tmp0 is not in form.elements");
+ok(!("foo0" in form.elements), "foo0 is not in form.elements");
+is(form.tmp0, input0, "Form.tmp0 == input0");
+is(form.tmp1, input0, "Form.tmp1 == input0");
+is(form.foo0, input0, "Form.foo0 is still here");
+
+input0.setAttribute("form", "");
+ok(!("foo0" in form.elements), "foo0 is not in form.elements");
+is(form.foo0, undefined, "Form.foo0 should not still be here");
+is(form.tmp0, undefined, "Form.tmp0 should not still be here");
+is(form.tmp1, undefined, "Form.tmp1 should not still be here");
+
+var input1 = document.getElementById("input1");
+ok(input1, "input1 exists");
+is(form.foo1, input1, "Form.foo1 should exist");
+
+ok("foo1" in form.elements, "foo1 in form.elements");
+is(input1.form, form, "input1.form is form");
+
+input1.setAttribute("name", "foo0");
+ok("foo0" in form.elements, "foo0 is in form.elements");
+is(form.foo0, input1, "Form.foo0 should be input1");
+is(form.foo1, input1, "Form.foo1 should be input1");
+
+var input2 = document.getElementById("input2");
+ok(input2, "input2 exists");
+is(form.foo2, input2, "Form.foo2 should exist");
+input2.parentNode.removeChild(input2);
+ok(!("foo2" in form.elements), "foo2 is not in form.elements");
+is(form.foo2, undefined, "Form.foo2 should not longer be there");
+
+var img0 = document.getElementById("img0");
+ok(img0, "img0 exists");
+is(form.bar0, img0, "Form.bar0 should exist");
+
+img0.setAttribute("name", "old_bar0");
+is(form.old_bar0, img0, "Form.bar0 is still here");
+is(form.bar0, img0, "Form.bar0 is still here");
+
+img0.parentNode.removeChild(img0);
+is(form.bar0, undefined, "Form.bar0 should not be here");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug885024.html b/dom/html/test/test_bug885024.html
new file mode 100644
index 000000000..118c140bd
--- /dev/null
+++ b/dom/html/test/test_bug885024.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html data-expando-prop="xyz">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=885024
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 885024</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=885024">Mozilla Bug 885024</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<img form="t">
+
+<form id="form">
+ <div id="div"></div>
+</form>
+
+<pre id="test">
+ <script type="application/javascript">
+ var img = document.createElement('img');
+ img.setAttribute('id', 'img');
+
+ var div = document.getElementById('div');
+ div.appendChild(img);
+
+ var form = document.getElementById('form');
+ ok(form, "form exists");
+ ok(form.img, "form.img exists");
+
+ var img2 = document.createElement('img');
+ img2.setAttribute('id', 'img2');
+ img2.setAttribute('form', 'blabla');
+ ok(form, "form exists2");
+ div.appendChild(img2);
+ ok(form.img2, "form.img2 exists");
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug893537.html b/dom/html/test/test_bug893537.html
new file mode 100644
index 000000000..5935529d8
--- /dev/null
+++ b/dom/html/test/test_bug893537.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=893537
+-->
+ <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=893537">Mozilla Bug 893537</a>
+
+<iframe id="pframe" src="file_bug893537.html"></iframe>
+
+<pre id="test">
+<script>
+ <!-- Bug 895303 -->
+ SimpleTest.expectAssertions(0, 1);
+
+ SimpleTest.waitForExplicitFinish();
+ var pframe = $("pframe");
+
+ var loadState = 1;
+ pframe.contentWindow.addEventListener("load", function () {
+
+ if (loadState == 1) {
+ var iframe = pframe.contentDocument.getElementById("iframe");
+ iframe.removeAttribute("srcdoc");
+ loadState = 2;
+ }
+ if (loadState == 2) {
+ SimpleTest.executeSoon(function () { pframe.contentWindow.location.reload() });
+ loadState = 3;
+ }
+ if (loadState == 3) {
+ ok(true, "This is a mochitest implementation of a crashtest. To finish is to pass");
+ SimpleTest.finish();
+ }
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug95530.html b/dom/html/test/test_bug95530.html
new file mode 100644
index 000000000..05cbd709b
--- /dev/null
+++ b/dom/html/test/test_bug95530.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=95530
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 95530</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 95530 **/
+ function run() {
+ is(document.compatMode, "CSS1Compat", "Ensure we are in standards mode, not quirks mode.");
+
+ var body = document.getElementsByTagName("body");
+
+ is(computedStyle(body[0],"margin-top"), "100px", "Ensure margin-top matches topmargin");
+ is(computedStyle(body[0],"margin-bottom"), "150px", "Ensure margin-bottom matches bottommargin");
+ is(computedStyle(body[0],"margin-left"), "23px", "Ensure margin-left matches leftmargin");
+ is(computedStyle(body[0],"margin-right"), "64px", "Ensure margin-right matches rightmargin");
+ SimpleTest.finish();
+ }
+ SimpleTest.waitForExplicitFinish();
+ window.addEventListener("load", run, false);
+ </script>
+</head>
+<body topmargin="100" bottommargin="150" leftmargin="23" rightmargin="64">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=95530">Mozilla Bug 95530</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug969346.html b/dom/html/test/test_bug969346.html
new file mode 100644
index 000000000..5be76c46e
--- /dev/null
+++ b/dom/html/test/test_bug969346.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=969346
+-->
+<head>
+<title>Nesting of srcdoc iframes is permitted</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=969349">Mozilla Bug 969346</a>
+
+<iframe id="pframe" srcdoc="<iframe id='iframe' srcdoc='I am nested'></iframe"></iframe>
+
+<pre id="test">
+<script>
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function () {
+ var pframe = $("pframe");
+ var pframeDoc = pframe.contentDocument;
+ var iframe = pframeDoc.getElementById("iframe");
+ var innerDoc = iframe.contentDocument;
+
+ is(innerDoc.body.innerHTML, "I am nested", "Nesting not working?");
+ SimpleTest.finish();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_bug982039.html b/dom/html/test/test_bug982039.html
new file mode 100644
index 000000000..22833440f
--- /dev/null
+++ b/dom/html/test/test_bug982039.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=982039
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 982039</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 982039 **/
+ SimpleTest.waitForExplicitFinish();
+ function test() {
+ var f = document.getElementById("testform");
+ f.elements[0].disabled = true;
+ is(f.checkValidity(), false,
+ "Setting a radiobutton to disabled shouldn't make form valid.");
+
+ f.elements[1].checked = true;
+ ok(f.checkValidity(), "Form should be now valid.");
+
+ f.elements[0].required = false;
+ f.elements[1].required = false;
+ f.elements[2].required = false;
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=982039">Mozilla Bug 982039</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<form action="#" id="testform">
+ <input type="radio" name="radio" value="1" required>
+ <input type="radio" name="radio" value="2" required>
+ <input type="radio" name="radio" value="3" required>
+</form>
+</body>
+</html>
diff --git a/dom/html/test/test_change_crossorigin.html b/dom/html/test/test_change_crossorigin.html
new file mode 100644
index 000000000..01f31bc09
--- /dev/null
+++ b/dom/html/test/test_change_crossorigin.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=696451
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 696451</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 696451 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ var img = new Image,
+ canvas = document.createElement("canvas"),
+ ctx = canvas.getContext("2d"),
+ src = "http://example.com/tests/dom/html/test/image-allow-credentials.png",
+ imgDone = false,
+ imgNotAllowedToLoadDone = false;
+
+ img.src = src;
+ img.crossOrigin = "Anonymous";
+
+ img.addEventListener("load", function() {
+ canvas.width = img.width;
+ canvas.height = img.height;
+ ctx.drawImage( img, 0, 0 );
+ try {
+ canvas.toDataURL("image/png");
+ ok(true, "Image was refetched with setting crossOrigin.");
+ } catch (e) {
+ ok(false, "Image was not refetched after setting crossOrigin.");
+ }
+
+ imgDone = true;
+ if (imgDone && imgNotAllowedToLoadDone) {
+ SimpleTest.finish();
+ }
+ });
+
+ img.addEventListener("error", function (event) {
+ ok(false, "Should be able to load cross origin image with proper headers.");
+
+ imgDone = true;
+ if (imgDone && imgNotAllowedToLoadDone) {
+ SimpleTest.finish();
+ }
+ });
+
+ var imgNotAllowedToLoad = new Image;
+
+ imgNotAllowedToLoad.src = "http://example.com/tests/dom/html/test/image.png";
+
+ imgNotAllowedToLoad.crossOrigin = "Anonymous";
+
+ imgNotAllowedToLoad.addEventListener("load", function() {
+ ok(false, "Image should not be allowed to load without " +
+ "allow-cross-origin-access headers.");
+
+ imgNotAllowedToLoadDone = true;
+ if (imgDone && imgNotAllowedToLoadDone) {
+ SimpleTest.finish();
+ }
+ });
+
+ imgNotAllowedToLoad.addEventListener("error", function() {
+ ok(true, "Image should not be allowed to load without " +
+ "allow-cross-origin-access headers.");
+ imgNotAllowedToLoadDone = true;
+ if (imgDone && imgNotAllowedToLoadDone) {
+ SimpleTest.finish();
+ }
+ });
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=696451">Mozilla Bug 696451</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_checked.html b/dom/html/test/test_checked.html
new file mode 100644
index 000000000..9d40a6ded
--- /dev/null
+++ b/dom/html/test/test_checked.html
@@ -0,0 +1,358 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=418756
+https://bugzilla.mozilla.org/show_bug.cgi?id=617528
+-->
+<head>
+ <title>Test for Bug 418756 and 617528</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+Mozilla bug
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418756">418756</a>
+and
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=617528">617528</a>
+<p id="display"></p>
+<div id="content">
+ <form id="f1">
+ </form>
+ <form id="f2">
+ </form>
+ <menu id="m1">
+ </menu>
+ <menu id="m2">
+ </menu>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript; version=1.7">
+
+/** Test for Bug 418756 and 617528 **/
+var group1;
+var group2;
+var group3;
+
+var tags = ["input", "menuitem"];
+for (let tag of tags) {
+
+function bounce(node) {
+ let n = node.nextSibling;
+ let p = node.parentNode;
+ p.removeChild(node);
+ p.insertBefore(node, n);
+}
+
+var createdNodes = [];
+
+function cleanup() {
+ for (let node of createdNodes) {
+ if (node.parentNode) {
+ node.parentNode.removeChild(node);
+ }
+ }
+
+ createdNodes = [];
+}
+
+var typeMapper = {
+ 'c': 'checkbox',
+ 'r': 'radio'
+};
+
+var id = 0;
+
+// type can be 'c' for 'checkbox' and 'r' for 'radio'
+function createNode(type, name, checked) {
+ let node = document.createElement(tag);
+ node.setAttribute("type", typeMapper[type]);
+ if (checked) {
+ node.setAttribute("checked", "checked");
+ }
+ node.setAttribute("id", type + (++id));
+ node.setAttribute(tag == "input" ? "name" : "radiogroup", name);
+ createdNodes.push(node);
+ return node;
+}
+
+var types = ['c', 'r'];
+
+// First make sure that setting .checked makes .defaultChecked changes no
+// longer affect .checked.
+for (let type of types) {
+ let n = createNode(type, '', false);
+ is(n.defaultChecked, false, "Bogus defaultChecked on " + typeMapper[type]);
+ is(n.checked, false, "Bogus checked on " + typeMapper[type]);
+ n.defaultChecked = true;
+ is(n.defaultChecked, true, "Bogus defaultChecked on " + typeMapper[type] +
+ "after mutation");
+ is(n.checked, true, "Bogus checked on " + typeMapper[type] +
+ "after mutation");
+ n.checked = false;
+ is(n.defaultChecked, true, "Bogus defaultChecked on " + typeMapper[type] +
+ "after second mutation");
+ is(n.checked, false, "Bogus checked on " + typeMapper[type] +
+ "after second mutation");
+ n.defaultChecked = false;
+ is(n.defaultChecked, false, "Bogus defaultChecked on " + typeMapper[type] +
+ "after third mutation");
+ is(n.checked, false, "Bogus checked on " + typeMapper[type] +
+ "after third mutation");
+ n.defaultChecked = true;
+ is(n.defaultChecked, true, "Bogus defaultChecked on " + typeMapper[type] +
+ "after fourth mutation");
+ is(n.checked, false, "Bogus checked on " + typeMapper[type] +
+ "after fourth mutation");
+}
+
+cleanup();
+
+// Now check that bouncing a control that's the only one of its kind has no
+// effect
+for (let type of types) {
+ let n = createNode(type, 'test1', true);
+ $(tag == "input" ? "f1" : "m1").appendChild(n);
+ n.checked = false;
+ n.defaultChecked = false;
+ bounce(n);
+ n.defaultChecked = true;
+ is(n.checked, false, "We set .checked on this " + typeMapper[type]);
+}
+
+cleanup();
+
+// Now check that playing with a single radio in a group affects all
+// other radios in the group (but not radios not in that group)
+group1 = [ createNode('r', 'g1', false),
+ createNode('r', 'g1', false),
+ createNode('r', 'g1', false) ];
+group2 = [ createNode('r', 'g2', false),
+ createNode('r', 'g2', false),
+ createNode('r', 'g2', false) ];
+group3 = [ createNode('r', 'g1', false),
+ createNode('r', 'g1', false),
+ createNode('r', 'g1', false) ];
+for (let g of group1) {
+ $(tag == "input" ? "f1" : "m1").appendChild(g);
+}
+for (let g of group2) {
+ $(tag == "input" ? "f1" : "m1").appendChild(g);
+}
+for (let g of group3) {
+ $(tag == "input" ? "f2" : "m2").appendChild(g);
+}
+
+for (let n of [1, 2, 3]) {
+ for (let g of window["group"+n]) {
+ is(g.defaultChecked, false,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] defaultChecked wrong pass 1");
+ is(g.checked, false,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] checkedhecked wrong pass 1");
+ }
+}
+
+group1[1].defaultChecked = true;
+for (let n of [1, 2, 3]) {
+ for (let g of window["group"+n]) {
+ is(g.defaultChecked, n == 1 && group1.indexOf(g) == 1,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] defaultChecked wrong pass 2");
+ is(g.checked, n == 1 && group1.indexOf(g) == 1,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] checked wrong pass 2");
+ }
+}
+
+group1[0].defaultChecked = true;
+for (let n of [1, 2, 3]) {
+ for (let g of window["group"+n]) {
+ is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 1 ||
+ group1.indexOf(g) == 0),
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] defaultChecked wrong pass 3");
+ is(g.checked, n == 1 && group1.indexOf(g) == 0,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] checked wrong pass 3");
+ }
+}
+
+group1[2].defaultChecked = true;
+for (let n of [1, 2, 3]) {
+ for (let g of window["group"+n]) {
+ is(g.defaultChecked, n == 1,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] defaultChecked wrong pass 4");
+ is(g.checked, n == 1 && group1.indexOf(g) == 2,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] checked wrong pass 4");
+ }
+}
+
+var next = group1[1].nextSibling;
+var p = group1[1].parentNode;
+p.removeChild(group1[1]);
+group1[1].defaultChecked = false;
+group1[1].defaultChecked = true;
+p.insertBefore(group1[1], next);
+for (let n of [1, 2, 3]) {
+ for (let g of window["group"+n]) {
+ is(g.defaultChecked, n == 1,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] defaultChecked wrong pass 5");
+ is(g.checked, n == 1 && group1.indexOf(g) == 1,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] checked wrong pass 5");
+ }
+}
+
+for (let g of group1) {
+ g.defaultChecked = false;
+}
+for (let n of [1, 2, 3]) {
+ for (let g of window["group"+n]) {
+ is(g.defaultChecked, false,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] defaultChecked wrong pass 6");
+ is(g.checked, false,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] checkedhecked wrong pass 6");
+ }
+}
+
+group1[1].checked = true;
+for (let n of [1, 2, 3]) {
+ for (let g of window["group"+n]) {
+ is(g.defaultChecked, false,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] defaultChecked wrong pass 7");
+ is(g.checked, n == 1 && group1.indexOf(g) == 1,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] checked wrong pass 7");
+ }
+}
+
+group1[0].defaultChecked = true;
+for (let n of [1, 2, 3]) {
+ for (let g of window["group"+n]) {
+ is(g.defaultChecked, n == 1 && group1.indexOf(g) == 0,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] defaultChecked wrong pass 8");
+ is(g.checked, n == 1 && group1.indexOf(g) == 1,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] checked wrong pass 8");
+ }
+}
+
+group1[2].defaultChecked = true;
+for (let n of [1, 2, 3]) {
+ for (let g of window["group"+n]) {
+ is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
+ group1.indexOf(g) == 2),
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] defaultChecked wrong pass 9");
+ is(g.checked, n == 1 && group1.indexOf(g) == 1,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] checked wrong pass 9");
+ }
+}
+group1[1].parentNode.removeChild(group1[1]);
+for (let n of [1, 2, 3]) {
+ for (let g of window["group"+n]) {
+ is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
+ group1.indexOf(g) == 2),
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] defaultChecked wrong pass 10");
+ is(g.checked, n == 1 && group1.indexOf(g) == 1,
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] checked wrong pass 10");
+ }
+}
+
+group1[2].checked = true;
+for (let n of [1, 2, 3]) {
+ for (let g of window["group"+n]) {
+ is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
+ group1.indexOf(g) == 2),
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] defaultChecked wrong pass 11");
+ is(g.checked, n == 1 && (group1.indexOf(g) == 1 ||
+ group1.indexOf(g) == 2),
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] checked wrong pass 11");
+ }
+}
+
+group1[0].checked = true;
+for (let n of [1, 2, 3]) {
+ for (let g of window["group"+n]) {
+ is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
+ group1.indexOf(g) == 2),
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] defaultChecked wrong pass 12");
+ is(g.checked, n == 1 && (group1.indexOf(g) == 1 ||
+ group1.indexOf(g) == 0),
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] checked wrong pass 12");
+ }
+}
+
+next = group2[1].nextSibling;
+p = group2[1].parentNode;
+p.removeChild(group2[1]);
+p.insertBefore(group2[1], next);
+group2[0].checked = true;
+for (let n of [1, 2, 3]) {
+ for (let g of window["group"+n]) {
+ is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
+ group1.indexOf(g) == 2),
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] defaultChecked wrong pass 13");
+ is(g.checked, (n == 1 && (group1.indexOf(g) == 1 ||
+ group1.indexOf(g) == 0)) ||
+ (n == 2 && group2.indexOf(g) == 0),
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] checked wrong pass 13");
+ }
+}
+
+p.insertBefore(group2[1], next);
+for (let n of [1, 2, 3]) {
+ for (let g of window["group"+n]) {
+ is(g.defaultChecked, n == 1 && (group1.indexOf(g) == 0 ||
+ group1.indexOf(g) == 2),
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] defaultChecked wrong pass 14");
+ is(g.checked, (n == 1 && (group1.indexOf(g) == 1 ||
+ group1.indexOf(g) == 0)) ||
+ (n == 2 && group2.indexOf(g) == 0),
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] checked wrong pass 14");
+ }
+}
+
+group2[1].defaultChecked = true;
+for (let n of [1, 2, 3]) {
+ for (let g of window["group"+n]) {
+ is(g.defaultChecked, (n == 1 && (group1.indexOf(g) == 0 ||
+ group1.indexOf(g) == 2)) ||
+ (n == 2 && group2.indexOf(g) == 1),
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] defaultChecked wrong pass 15");
+ is(g.checked, (n == 1 && (group1.indexOf(g) == 1 ||
+ group1.indexOf(g) == 0)) ||
+ (n == 2 && group2.indexOf(g) == 0),
+ "group" + n + "[" + window["group"+n].indexOf(g) +
+ "] checked wrong pass 15");
+ }
+}
+
+cleanup();
+
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_dir_attributes_reflection.html b/dom/html/test/test_dir_attributes_reflection.html
new file mode 100644
index 000000000..c9c4022a3
--- /dev/null
+++ b/dom/html/test/test_dir_attributes_reflection.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLDirectoryElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLDirectoryElement attributes reflection **/
+
+// .name
+reflectBoolean({
+ element: document.createElement("dir"),
+ attribute: "compact",
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_dl_attributes_reflection.html b/dom/html/test/test_dl_attributes_reflection.html
new file mode 100644
index 000000000..1fbab51ab
--- /dev/null
+++ b/dom/html/test/test_dl_attributes_reflection.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLDListElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLDListElement attributes reflection **/
+
+// .compact
+reflectBoolean({
+ element: document.createElement("dl"),
+ attribute: "compact"
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_document-element-inserted.html b/dom/html/test/test_document-element-inserted.html
new file mode 100644
index 000000000..483d0cb0a
--- /dev/null
+++ b/dom/html/test/test_document-element-inserted.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: document-element-inserted</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe id = 'media'>
+</iframe>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+if (navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 4);
+}
+
+SimpleTest.waitForExplicitFinish();
+var loc;
+
+var observe = function(doc){
+ if (doc == media.contentDocument) {
+ ok(media.contentDocument.location.toString().indexOf(loc) != -1,
+ "The loaded media should be " + loc);
+ next();
+ }
+}
+
+var media = document.getElementById('media');
+var tests = [
+ "../../../media/test/short-video.ogv",
+ "../../../media/test/sound.ogg",
+ "../../content/test/image.png"
+]
+
+function next() {
+ if (tests.length > 0) {
+ var t = tests.shift();
+ loc = t.substring(t.indexOf("test"));
+ media.setAttribute("src",t);
+ }
+ else {
+ SpecialPowers.removeObserver(observe, "document-element-inserted");
+ SimpleTest.finish();
+ }
+}
+
+SpecialPowers.addObserver(observe, "document-element-inserted", false)
+next();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_document.watch.html b/dom/html/test/test_document.watch.html
new file mode 100644
index 000000000..54509823b
--- /dev/null
+++ b/dom/html/test/test_document.watch.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=903332
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 903332</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 903332 **/
+
+ var watch1Called;
+ function watch1(prop, oldValue, newValue)
+ {
+ is(watch1Called, false, "watch1Called not reset properly?");
+ watch1Called = true;
+
+ is(prop, "cookie", "wrong property name passed to watch1");
+ return newValue;
+ }
+
+ var watch2Called;
+ function watch2(prop, oldValue, newValue)
+ {
+ is(watch2Called, false, "watch2Called not reset properly?");
+ watch2Called = true;
+
+ is(prop, "cookie", "wrong property name passed to watch2");
+ return newValue;
+ }
+
+ // Just in case subsequent tests depend on a particular value...
+ var originalValue = document.cookie;
+ ok(true, "originalValue: " + originalValue);
+
+ var originalPrefix = originalValue.length > 0 ? originalValue + "; " : "";
+
+ try
+ {
+ // trial set (no watch) to verify things work
+ document.cookie = "first=set";
+ is(document.cookie, originalPrefix + "first=set",
+ "first value correct");
+
+ // add a watch
+ document.watch("cookie", watch1);
+
+ // set, check for watch invoked
+ watch1Called = false;
+ document.cookie = "second=set";
+ is(watch1Called, true, "watch1 function should be called");
+ is(document.cookie, originalPrefix + "first=set; second=set",
+ "second value correct");
+
+ // and a second time, just in case
+ watch1Called = false;
+ document.cookie = "third=set";
+ is(watch1Called, true, "watch1 function should be called");
+ is(document.cookie, originalPrefix + "first=set; second=set; third=set",
+ "third value correct");
+
+ // overwrite the current watch with a new one
+ document.watch("cookie", watch2);
+
+ // set, check for watch invoked
+ watch1Called = false;
+ watch2Called = false;
+ document.cookie = "fourth=set";
+ is(watch1Called, false, "watch1 invoked erroneously");
+ is(watch2Called, true, "watch2 function should be called");
+ is(document.cookie, originalPrefix + "first=set; second=set; third=set; fourth=set",
+ "fourth value correct");
+
+ // and a second time, just in case
+ watch1Called = false;
+ watch2Called = false;
+ document.cookie = "fifth=set";
+ is(watch1Called, false, "watch1 invoked erroneously");
+ is(watch2Called, true, "watch2 function should be called");
+ is(document.cookie, originalPrefix + "first=set; second=set; third=set; fourth=set; fifth=set",
+ "fifth value correct");
+
+ // remove the watch
+ document.unwatch("cookie");
+
+ // check for non-invocation now
+ watch1Called = false;
+ watch2Called = false;
+ document.cookie = "sixth=set";
+ is(watch1Called, false, "watch1 shouldn't be called");
+ is(watch2Called, false, "watch2 shouldn't be called");
+ is(document.cookie, originalPrefix + "first=set; second=set; third=set; fourth=set; fifth=set; sixth=set",
+ "sixth value correct");
+ }
+ finally
+ {
+ // reset
+ document.unwatch("cookie"); // harmless, should be no-op except if bugs
+
+ var d = new Date();
+ d.setTime(0);
+ var suffix = "=; expires=" + d.toGMTString();
+
+ document.cookie = "first" + suffix;
+ document.cookie = "second" + suffix;
+ document.cookie = "third" + suffix;
+ document.cookie = "fourth" + suffix;
+ document.cookie = "fifth" + suffix;
+ document.cookie = "sixth" + suffix;
+ }
+
+ is(document.cookie, originalValue,
+ "document.cookie isn't what it was initially! expect bustage further " +
+ "down the line");
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=903332">Mozilla Bug 903332</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_documentAll.html b/dom/html/test/test_documentAll.html
new file mode 100644
index 000000000..ec877acec
--- /dev/null
+++ b/dom/html/test/test_documentAll.html
@@ -0,0 +1,167 @@
+<html>
+<!--
+Tests for document.all
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Tests for document.all</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=259332">Mozilla Bug 259332</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=393629">Mozilla Bug 393629</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=448904">Mozilla Bug 448904</a>
+<p id="display">
+</p>
+<div id="content" style="display: none">
+ <a id="id1">A</a>
+ <a id="id2">B</a>
+ <a id="id2">C</a>
+ <a id="id3">D</a>
+ <a id="id3">E</a>
+ <a id="id3">F</a>
+</div>
+<iframe id="subframe" src="data:text/html,<span id='x'></span>"
+ style="display: none"></iframe>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+p = document.getElementById("content");
+
+// Test that several elements with the same id or name behave correctly
+function testNumSame() {
+ is(document.all.id0, undefined, "no ids");
+ is(document.all.namedItem("id0"), null, "no ids");
+ is(document.all.id1, p.children[0], "one id");
+ is(document.all.id2[0], p.children[1], "two ids");
+ is(document.all.id2[1], p.children[2], "two ids");
+ is(document.all.id2.length, 2, "two length");
+ is(document.all.id3[0], p.children[3], "three ids");
+ is(document.all.id3[1], p.children[4], "three ids");
+ is(document.all.id3[2], p.children[5], "three ids");
+ is(document.all.id3.length, 3, "three length");
+}
+testNumSame();
+p.innerHTML = p.innerHTML.replace(/id=/g, "name=");
+testNumSame();
+
+
+// Test that dynamic changes behave properly
+
+// Add two elements and check that they are added to the correct lists
+child = Array.prototype.slice.call(p.children);
+child[6] = document.createElement("a");
+child[6].id = "id0";
+p.appendChild(child[6]);
+child[7] = document.createElement("a");
+child[7].id = "id1";
+p.appendChild(child[7]);
+is(document.all.id0, child[6], "now one id");
+is(document.all.id1[0], child[0], "now two ids");
+is(document.all.id1[1], child[7], "now two ids");
+is(document.all.id1.length, 2, "now two length");
+
+// Remove and element and check that the list shrinks
+rC(child[1]);
+is(document.all.id2, child[2], "now just one id");
+
+// Change an id and check that its removed and added to the correct lists
+child[4].name = "id1";
+is(document.all.id1[0], child[0], "now three ids");
+is(document.all.id1[1], child[4], "now three ids");
+is(document.all.id1[2], child[7], "now three ids");
+is(document.all.id1.length, 3, "now three length");
+is(document.all.id3[1], child[5], "now just two ids");
+is(document.all.id3.length, 2, "now two length");
+
+// Remove all elements from a list and check that it goes empty
+id3list = document.all.id3;
+rC(child[3]);
+is(id3list.length, 1, "now one length");
+rC(child[5]);
+is(document.all.id3, undefined, "now none");
+is(document.all.namedItem("id3"), null, "now none (namedItem)");
+is(id3list.length, 0, "now none length");
+
+// Give an element both a name and id and check that it appears in two lists
+p.insertBefore(child[1], child[2]); // restore previously removed
+id1list = document.all.id1;
+id2list = document.all.id2;
+child[1].id = "id1";
+is(id1list[0], child[0], "now four ids");
+is(id1list[1], child[1], "now four ids");
+is(id1list[2], child[4], "now four ids");
+is(id1list[3], child[7], "now four ids");
+is(id1list.length, 4, "now four length");
+is(id2list[0], child[1], "still two ids");
+is(id2list[1], child[2], "still two ids");
+is(id2list.length, 2, "still two length");
+
+
+// Check that document.all behaves list a list of all elements
+allElems = document.getElementsByTagName("*");
+ok(testArraysSame(document.all, allElems), "arrays same");
+length = document.all.length;
+expectedLength = length + p.getElementsByTagName("*").length + 1;
+p.appendChild(p.cloneNode(true));
+ok(testArraysSame(document.all, allElems), "arrays still same");
+is(document.all.length, expectedLength, "grew correctly");
+
+// Check which elements the 'name' attribute works on
+var elementNames =
+ ['applet','abbr','acronym','address','area','a','b','base',
+ 'bgsound','big','blockquote','br','canvas','center','cite','code',
+ 'col','colgroup','dd','del','dfn','dir','div','dir','dl','dt','em','embed',
+ 'fieldset','font','form','frame','frameset','head','i','iframe','img',
+ 'input','ins','isindex','kbd','keygen','label','li','legend','link','menu',
+ 'multicol','noscript','noframes','object','spacer','table','td','td','th',
+ 'thead','tfoot','tr','textarea','select','option','spacer','param',
+ 'marquee','hr','title','hx','tt','u','ul','var','wbr','sub','sup','cite',
+ 'code','q','nobr','ol','p','pre','s','samp','small','body','html','map',
+ 'bdo','legend','listing','style','script','tbody','caption','meta',
+ 'optgroup','button','span','strike','strong','td'].sort();
+var hasName =
+ ['applet','a','embed','form','iframe','img','input','object','textarea',
+ 'select','map','meta','button','frame','frameset'].sort();
+
+elementNames.forEach(function (name) {
+ nameval = 'namefor' + name;
+
+ e = document.createElement(name);
+ p.appendChild(e);
+ e.setAttribute('name', nameval);
+
+ if (name == hasName[0]) {
+ is(document.all[nameval], e, "should have name");
+ hasName.shift();
+ }
+ else {
+ is(document.all[nameval], undefined, "shouldn't have name");
+ is(document.all.namedItem(nameval), null, "shouldn't have name (namedItem)");
+ }
+});
+is(hasName.length, 0, "found all names");
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var subdoc = $("subframe").contentDocument;
+ is(subdoc.all.x, subdoc.body.firstChild,
+ "document.all should work in a subdocument");
+ SimpleTest.finish();
+});
+
+// Utility functions
+function rC(node) {
+ node.parentNode.removeChild(node);
+}
+function testArraysSame(a1, a2) {
+ return Array.prototype.every.call(a1, function(e, index) {
+ return a2[index] === e;
+ }) && a1.length == a2.length;
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_element_prototype.html b/dom/html/test/test_element_prototype.html
new file mode 100644
index 000000000..44ef4edd6
--- /dev/null
+++ b/dom/html/test/test_element_prototype.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=844127
+-->
+<head>
+ <title>Test for Bug 844127</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=844127">Mozilla Bug 844127</a>
+
+<script type="text/javascript">
+
+/** Test for Bug 844127 **/
+
+var a1 = document.createElement('bgsound');
+var a2 = document.createElement('image');
+var a3 = document.createElement('multicol');
+var a4 = document.createElement('spacer');
+var a5 = document.createElement('isindex');
+
+is(Object.getPrototypeOf(a1), HTMLUnknownElement.prototype, "Prototype for bgsound should be correct");
+is(Object.getPrototypeOf(a2), HTMLElement.prototype, "Prototype for image should be correct");
+is(Object.getPrototypeOf(a3), HTMLUnknownElement.prototype, "Prototype for multicol should be correct");
+is(Object.getPrototypeOf(a4), HTMLUnknownElement.prototype, "Prototype for spacer should be correct");
+is(Object.getPrototypeOf(a5), HTMLUnknownElement.prototype, "Prototype for isindex should be correct");
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_embed_attributes_reflection.html b/dom/html/test/test_embed_attributes_reflection.html
new file mode 100644
index 000000000..85001598a
--- /dev/null
+++ b/dom/html/test/test_embed_attributes_reflection.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLEmbedElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLEmbedElement attributes reflection **/
+
+// .src (URL)
+reflectURL({
+ element: document.createElement("embed"),
+ attribute: "src",
+});
+
+// .type (String)
+reflectString({
+ element: document.createElement("embed"),
+ attribute: "type",
+});
+
+// .width (String)
+reflectString({
+ element: document.createElement("embed"),
+ attribute: "width",
+});
+
+// .height (String)
+reflectString({
+ element: document.createElement("embed"),
+ attribute: "height",
+});
+
+// .align (String)
+reflectString({
+ element: document.createElement("embed"),
+ attribute: "align",
+});
+
+// .name (String)
+reflectString({
+ element: document.createElement("embed"),
+ attribute: "name",
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_filepicker_default_directory.html b/dom/html/test/test_filepicker_default_directory.html
new file mode 100644
index 000000000..c2212baa0
--- /dev/null
+++ b/dom/html/test/test_filepicker_default_directory.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1194893
+-->
+<head>
+ <title>Test for filepicker default directory</title>
+ <script type="text/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=1194893">Mozilla Bug 1194893</a>
+<div id="content">
+ <input type="file" id="f">
+</div>
+<pre id="text">
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+const { Cc: Cc, Ci: Ci } = SpecialPowers;
+
+// Platform-independent directory names are #define'd in xpcom/io/nsDirectoryServiceDefs.h
+var defaultUploadDirectory = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryService)
+ .QueryInterface(Ci.nsIProperties)
+ .get("Desk", Ci.nsIFile);
+
+// When we want to test an upload directory other than the default, we need to
+// get a valid directory in a platform-independent way. Since NS_OS_DESKTOP_DIR
+// may fallback to NS_OS_HOME_DIR, let's use NS_OS_TMP_DIR.
+var customUploadDirectory = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryService)
+ .QueryInterface(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+
+// Useful for debugging
+//info("defaultUploadDirectory" + defaultUploadDirectory.path);
+//info("customUploadDirectory" + customUploadDirectory.path);
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+// need to show the MockFilePicker so .displayDirectory gets set
+var f = document.getElementById("f");
+f.focus();
+
+var testIndex = 0;
+var tests = [
+ ["", defaultUploadDirectory.path],
+ [customUploadDirectory.path, customUploadDirectory.path]
+]
+
+MockFilePicker.showCallback = function(filepicker) {
+ info(SpecialPowers.wrap(MockFilePicker).displayDirectory.path);
+
+ is(SpecialPowers.wrap(MockFilePicker).displayDirectory.path,
+ tests[testIndex][1]);
+
+ if (++testIndex == tests.length) {
+ MockFilePicker.cleanup();
+ SimpleTest.finish();
+ } else {
+ launchNextTest();
+ }
+}
+
+function launchNextTest() {
+ SpecialPowers.pushPrefEnv(
+ { 'set': [
+ ['dom.input.fallbackUploadDir', tests[testIndex][0]],
+ ]},
+ function () {
+ f.click();
+ });
+}
+
+launchNextTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_focusshift_button.html b/dom/html/test/test_focusshift_button.html
new file mode 100644
index 000000000..2ae4743be
--- /dev/null
+++ b/dom/html/test/test_focusshift_button.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for shifting focus while mouse clicking on button</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>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+
+<script class="testbody" type="application/javascript;version=1.7">
+
+var result = "";
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ synthesizeMouseAtCenter(document.getElementById("button"), { });
+ if (/Mac/.test(navigator.platform)) {
+ // Buttons don't focus when clicked on Mac.
+ is(result, "", "Focus button then input");
+ }
+ else {
+ is(result, "(focus button)(blur button)(focus input)", "Focus button then input");
+ }
+ SimpleTest.finish();
+});
+</script>
+
+
+<button id="button" onfocus="result += '(focus button)'; document.getElementById('input').focus()"
+ onblur="result += '(blur button)'">Focus</button>
+<input id="input" value="Test" onfocus="result += '(focus input)'"
+ onblur="result += '(blur input)'">
+
+</body>
+</html>
diff --git a/dom/html/test/test_form-parsing.html b/dom/html/test/test_form-parsing.html
new file mode 100644
index 000000000..6a4a4c54e
--- /dev/null
+++ b/dom/html/test/test_form-parsing.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Form parsing</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="content" style="display: none">
+ <div>
+ <form name="test" action="test" id="test">
+ <input type="text" name="text1" id="text1" />
+ <input type="text" name="text2" id="text2" />
+ </div>
+ <input type="text" name="text3" id="text3" />
+ <input type="text" name="text4" id="text4" />
+ <input type="text" name="text5" id="text5" />
+ <input type="text" name="text6" id="text6" />
+ </form>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var form1 = document.getElementById("test");
+ var elem1 = form1.getElementsByTagName("*");
+ var elem1Length = elem1.length;
+ var form1ElementsLength = form1.elements.length;
+
+ is(form1ElementsLength, 6, "form.elements must include mis-nested elements");
+ is(elem1Length, 2, "form must not include mis-nested elements");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_formData.html b/dom/html/test/test_formData.html
new file mode 100644
index 000000000..a8b1bc0f7
--- /dev/null
+++ b/dom/html/test/test_formData.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=690659
+-->
+<head>
+ <title>Test for Bug 690659 and 739173</title>
+ <script type="text/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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=690659">Mozilla Bug 690659 & 739173</a>
+<script type="text/javascript" src="./formData_test.js"></script>
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function runMainThreadAndWorker() {
+ var mt = new Promise(function(resolve) {
+ runTest(resolve);
+ });
+
+ var worker;
+ var w = new Promise(function(resolve) {
+ worker = new Worker("formData_worker.js");
+ worker.onmessage = function(event) {
+ if (event.data.type == 'finish') {
+ resolve();
+ } else if (event.data.type == 'status') {
+ ok(event.data.status, event.data.msg);
+ } else if (event.data.type == 'todo') {
+ todo(event.data.status, event.data.msg);
+ }
+ }
+
+ worker.onerror = function(event) {
+ ok(false, "Worker had an error: " + event.message + " at " + event.lineno);
+ resolve();
+ };
+
+ worker.postMessage(true);
+ });
+
+ return Promise.all([mt, w]);
+}
+
+runMainThreadAndWorker().then(SimpleTest.finish);
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_formSubmission.html b/dom/html/test/test_formSubmission.html
new file mode 100644
index 000000000..0edfeaeb8
--- /dev/null
+++ b/dom/html/test/test_formSubmission.html
@@ -0,0 +1,825 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=523771
+-->
+<head>
+ <title>Test for Bug 523771</title>
+ <script type="text/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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=523771">Mozilla Bug 523771</a>
+<p id="display"></p>
+<iframe name="target_iframe" id="target_iframe"></iframe>
+<form action="form_submit_server.sjs" target="target_iframe" id="form"
+ method="POST" enctype="multipart/form-data">
+ <table>
+ <tr>
+ <td>Control type</td>
+ <td>Name and value</td>
+ <td>Name, empty value</td>
+ <td>Name, no value</td>
+ <td>Empty name, with value</td>
+ <td>No name, with value</td>
+ <td>No name or value</td>
+ <td>Strange name/value</td>
+ </tr>
+ <tr>
+ <td>Default input</td>
+ <td><input name="n1_1" value="v1_1"></td>
+ <td><input name="n1_2" value=""></td>
+ <td><input name="n1_3"></td>
+ <td><input name="" value="v1_4"></td>
+ <td><input value="v1_5"></td>
+ <td><input></td>
+ <td><input name="n1_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v1_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></td>
+ </tr>
+ <tr>
+ <td>Text input</td>
+ <td><input type=text name="n2_1" value="v2_1"></td>
+ <td><input type=text name="n2_2" value=""></td>
+ <td><input type=text name="n2_3"></td>
+ <td><input type=text name="" value="v2_4"></td>
+ <td><input type=text value="v2_5"></td>
+ <td><input type=text></td>
+ <td><input type=text name="n2_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v2_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></td>
+ </tr>
+ <tr>
+ <td>Checkbox unchecked</td>
+ <td><input type=checkbox name="n3_1" value="v3_1"></td>
+ <td><input type=checkbox name="n3_2" value=""></td>
+ <td><input type=checkbox name="n3_3"></td>
+ <td><input type=checkbox name="" value="v3_4"></td>
+ <td><input type=checkbox value="v3_5"></td>
+ <td><input type=checkbox></td>
+ <td><input type=checkbox name="n3_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v3_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></td>
+ </tr>
+ <tr>
+ <td>Checkbox checked</td>
+ <td><input checked type=checkbox name="n4_1" value="v4_1"></td>
+ <td><input checked type=checkbox name="n4_2" value=""></td>
+ <td><input checked type=checkbox name="n4_3"></td>
+ <td><input checked type=checkbox name="" value="v4_4"></td>
+ <td><input checked type=checkbox value="v4_5"></td>
+ <td><input checked type=checkbox></td>
+ <td><input checked type=checkbox
+ name="n4_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v4_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></td>
+ </tr>
+ <tr>
+ <td>Radio unchecked</td>
+ <td><input type=radio name="n5_1" value="v5_1"></td>
+ <td><input type=radio name="n5_2" value=""></td>
+ <td><input type=radio name="n5_3"></td>
+ <td><input type=radio name="" value="v5_4"></td>
+ <td><input type=radio value="v5_5"></td>
+ <td><input type=radio></td>
+ <td><input type=radio name="n5_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v5_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></td>
+ </tr>
+ <tr>
+ <td>Radio checked</td>
+ <td><input checked type=radio name="n6_1" value="v6_1"></td>
+ <td><input checked type=radio name="n6_2" value=""></td>
+ <td><input checked type=radio name="n6_3"></td>
+ <td><input checked type=radio name="" value="v6_4"></td>
+ <td><input checked type=radio value="v6_5"></td>
+ <td><input checked type=radio></td>
+ <td><input checked type=radio
+ name="n6_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v6_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></td>
+ </tr>
+ <tr>
+ <td>Hidden input</td>
+ <td><input type=hidden name="n7_1" value="v7_1"></td>
+ <td><input type=hidden name="n7_2" value=""></td>
+ <td><input type=hidden name="n7_3"></td>
+ <td><input type=hidden nane="" value="v7_4"></td>
+ <td><input type=hidden value="v7_5"></td>
+ <td><input type=hidden></td>
+ <td><input type=hidden name="n7_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v7_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></td>
+ </tr>
+ <tr>
+ <td>Password input</td>
+ <td><input type=password name="n8_1" value="v8_1"></td>
+ <td><input type=password name="n8_2" value=""></td>
+ <td><input type=password name="n8_3"></td>
+ <td><input type=password name="" value="v8_4"></td>
+ <td><input type=password value="v8_5"></td>
+ <td><input type=password></td>
+ <td><input type=password name="n8_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v8_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></td>
+ </tr>
+ <tr>
+ <td>Submit input</td>
+ <td><input type=submit name="n9_1" value="v9_1"></td>
+ <td><input type=submit name="n9_2" value=""></td>
+ <td><input type=submit name="n9_3"></td>
+ <td><input type=submit name="" value="v9_4"></td>
+ <td><input type=submit value="v9_5"></td>
+ <td><input type=submit></td>
+ <td><input type=submit name="n9_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v9_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></td>
+ </tr>
+ <tr>
+ <td>Button input</td>
+ <td><input type=button name="n10_1" value="v10_1"></td>
+ <td><input type=button name="n10_2" value=""></td>
+ <td><input type=button name="n10_3"></td>
+ <td><input type=button name="" value="v10_4"></td>
+ <td><input type=button value="v10_5"></td>
+ <td><input type=button></td>
+ <td><input type=button name="n10_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v10_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></td>
+ </tr>
+ <tr>
+ <td>Image input</td>
+ <td><input type=image src="file_formSubmission_img.jpg" name="n11_1" value="v11_1"></td>
+ <td><input type=image src="file_formSubmission_img.jpg" name="n11_2" value=""></td>
+ <td><input type=image src="file_formSubmission_img.jpg" name="n11_3"></td>
+ <td><input type=image src="file_formSubmission_img.jpg" name="" value="v11_4"></td>
+ <td><input type=image src="file_formSubmission_img.jpg" value="v11_5"></td>
+ <td><input type=image src="file_formSubmission_img.jpg"></td>
+ <td><input type=image src="file_formSubmission_img.jpg"
+ name="n11_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v11_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></td>
+ </tr>
+ <tr>
+ <td>Reset input</td>
+ <td><input type=reset name="n12_1" value="v12_1"></td>
+ <td><input type=reset name="n12_2" value=""></td>
+ <td><input type=reset name="n12_3"></td>
+ <td><input type=reset name="" value="v12_4"></td>
+ <td><input type=reset value="v12_5"></td>
+ <td><input type=reset></td>
+ <td><input type=reset name="n12_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v12_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></td>
+ </tr>
+ <tr>
+ <td>Unknown input</td>
+ <td><input type=foobar name="n13_1" value="v13_1"></td>
+ <td><input type=foobar name="n13_2" value=""></td>
+ <td><input type=foobar name="n13_3"></td>
+ <td><input type=foobar name="" value="v13_4"></td>
+ <td><input type=foobar value="v13_5"></td>
+ <td><input type=foobar></td>
+ <td><input type=foobar name="n13_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v13_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></td>
+ </tr>
+ <tr>
+ <td>Default button</td>
+ <td><button name="n14_1" value="v14_1"></button></td>
+ <td><button name="n14_2" value=""></button></td>
+ <td><button name="n14_3"></button></td>
+ <td><button name="" value="v14_4"></button></td>
+ <td><button value="v14_5"></button></td>
+ <td><button></button></td>
+ <td><button name="n14_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v14_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></button></td>
+ </tr>
+ <tr>
+ <td>Submit button</td>
+ <td><button type=submit name="n15_1" value="v15_1"></button></td>
+ <td><button type=submit name="n15_2" value=""></button></td>
+ <td><button type=submit name="n15_3"></button></td>
+ <td><button type=submit name="" value="v15_4"></button></td>
+ <td><button type=submit value="v15_5"></button></td>
+ <td><button type=submit></button></td>
+ <td><button type=submit name="n15_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v15_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></button></td>
+ </tr>
+ <tr>
+ <td>Button button</td>
+ <td><button type=button name="n16_1" value="v16_1"></button></td>
+ <td><button type=button name="n16_2" value=""></button></td>
+ <td><button type=button name="n16_3"></button></td>
+ <td><button type=button name="" value="v16_4"></button></td>
+ <td><button type=button value="v16_5"></button></td>
+ <td><button type=button></button></td>
+ <td><button type=button name="n16_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v16_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></button></td>
+ </tr>
+ <tr>
+ <td>Reset button</td>
+ <td><button type=reset name="n17_1" value="v17_1"></button></td>
+ <td><button type=reset name="n17_2" value=""></button></td>
+ <td><button type=reset name="n17_3"></button></td>
+ <td><button type=reset name="" value="v17_4"></button></td>
+ <td><button type=reset value="v17_5"></button></td>
+ <td><button type=reset></button></td>
+ <td><button type=reset name="n17_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v17_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></button></td>
+ </tr>
+ <tr>
+ <td>Unknown button</td>
+ <td><button type=foobar name="n18_1" value="v18_1"></button></td>
+ <td><button type=foobar name="n18_2" value=""></button></td>
+ <td><button type=foobar name="n18_3"></button></td>
+ <td><button type=foobar name="" value="v18_4"></button></td>
+ <td><button type=foobar value="v18_5"></button></td>
+ <td><button type=foobar ></button></td>
+ <td><button type=foobar name="n18_7_&#13;_&#10;_&#13;&#10;_ _&quot;"
+ value="v18_7_&#13;_&#10;_&#13;&#10;_ _&quot;"></button></td>
+ </tr>
+ <tr>
+ <td>&lt;input type='url'&gt;</td>
+ <td><input type=url name="n19_1" value="http://v19_1.org"></td>
+ <td><input type=url name="n19_2" value=""></td>
+ <td><input type=url name="n19_3"></td>
+ <td><input type=url name="" value="http://v19_4.org"></td>
+ <td><input type=url value="http://v19_5.org"></td>
+ <td><input type=url ></td>
+ <td><input type=url name="n19_7_&#13;_&#10;_&#13;&#10;__&quot;"
+ value="http://v19_7_&#13;_&#10;_&#13;&#10;__&quot;">
+ <!-- Put UTF-8 value in the "strange" column. -->
+ <input type=url name="n19_8" value="http://m&#xf3;zill&auml;.&#xf3;rg"></td>
+ </tr>
+ <tr>
+ <td>&lt;input type='email'&gt;</td>
+ <td><input type=email name="n20_1" value="v20_1@bar"></td>
+ <td><input type=email name="n20_2" value=""></td>
+ <td><input type=email name="n20_3"></td>
+ <td><input type=email name="" value="v20_4@bar"></td>
+ <td><input type=email value="v20_5@bar"></td>
+ <td><input type=email ></td>
+ <td><input type=email name="n20_7_&#13;_&#10;_&#13;&#10;__&quot;"
+ value="v20_7_&#13;_&#10;_&#13;&#10;__&quot;@bar">
+ <!-- Put UTF-8 value is the "strange" column. -->
+ <input type=email name="n20_8" value="foo@mózillä.órg"></td>
+ </tr>
+ </table>
+
+ <p>
+ File input:
+ <input type=file name="file_1" class="setfile">
+ <input type=file name="file_2">
+ <input type=file name="" class="setfile">
+ <input type=file name="">
+ <input type=file class="setfile">
+ <input type=file>
+ </p>
+ <p>
+ Multifile input:
+ <input multiple type=file name="file_3" class="setfile">
+ <input multiple type=file name="file_4" class="setfile multi">
+ <input multiple type=file name="file_5">
+ <input multiple type=file name="" class="setfile">
+ <input multiple type=file name="" class="setfile multi">
+ <input multiple type=file name="">
+ <input multiple type=file class="setfile">
+ <input multiple type=file class="setfile multi">
+ <input multiple type=file>
+ </p>
+
+ <p>
+ Textarea:
+ <textarea name="t1">t_1_v</textarea>
+ <textarea name="t2"></textarea>
+ <textarea name="">t_3_v</textarea>
+ <textarea>t_4_v</textarea>
+ <textarea></textarea>
+ <textarea name="t6">
+t_6_v</textarea>
+ <textarea name="t7">t_7_v
+</textarea>
+ <textarea name="t8">
+
+ t_8_v
+</textarea>
+ <textarea name="t9_&#13;_&#10;_&#13;&#10;_ _&quot;">t_9_&#13;_&#10;_&#13;&#10;_ _&quot;_v</textarea>
+ <textarea name="t10" value="t_10_bogus">t_10_v</textarea>
+ </p>
+
+ <p>
+ Select one:
+
+ <select name="sel_1"></select>
+ <select name="sel_1b"><option></option></select>
+ <select name="sel_1c"><option selected></option></select>
+
+ <select name="sel_2"><option value="sel_2_v"></option></select>
+ <select name="sel_3"><option selected value="sel_3_v"></option></select>
+
+ <select name="sel_4"><option value="sel_4_v1"></option><option value="sel_4_v2"></option></select>
+ <select name="sel_5"><option selected value="sel_5_v1"></option><option value="sel_5_v2"></option></select>
+ <select name="sel_6"><option value="sel_6_v1"></option><option selected value="sel_6_v2"></option></select>
+
+ <select name="sel_7"><option>sel_7_v1</option><option>sel_7_v2</option></select>
+ <select name="sel_8"><option selected>sel_8_v1</option><option>sel_8_v2</option></select>
+ <select name="sel_9"><option>sel_9_v1</option><option selected>sel_9_v2</option></select>
+
+ <select name="sel_10"><option value="sel_10_v1">sel_10_v1_text</option><option value="sel_10_v2">sel_10_v2_text</option></select>
+ <select name="sel_11"><option selected value="sel_11_v1">sel_11_v1_text</option><option value="sel_11_v2">sel_11_v2_text</option></select>
+ <select name="sel_12"><option value="sel_12_v1">sel_12_v1_text</option><option selected value="sel_12_v2">sel_12_v2_text</option></select>
+
+ <select name="sel_13"><option disabled>sel_13_v1</option><option>sel_13_v2</option></select>
+ <select name="sel_14"><option disabled selected>sel_14_v1</option><option>sel_14_v2</option></select>
+ <select name="sel_15"><option disabled>sel_15_v1</option><option selected>sel_15_v2</option></select>
+
+ <select name="sel_16"><option>sel_16_v1</option><option disabled>sel_16_v2</option></select>
+ <select name="sel_17"><option selected>sel_17_v1</option><option disabled>sel_17_v2</option></select>
+ <select name="sel_18"><option>sel_18_v1</option><option disabled selected>sel_18_v2</option></select>
+
+ <select name=""><option selected value="sel_13_v1"></option><option value="sel_13_v2"></option></select>
+ <select name=""><option value="sel_14_v1"></option><option selected value="sel_14_v2"></option></select>
+ <select name=""><option selected>sel_15_v1</option><option>sel_15_v2</option></select>
+ <select name=""><option>sel_16_v1</option><option selected>sel_16_v2</option></select>
+
+ <select><option selected value="sel_17_v1"></option><option value="sel_17_v2"></option></select>
+ <select><option value="sel_18_v1"></option><option selected value="sel_18_v2"></option></select>
+ <select><option selected>sel_19_v1</option><option>sel_19_v2</option></select>
+ <select><option>sel_20_v1</option><option selected>sel_20_v2</option></select>
+ </p>
+
+ <p>
+ Select multiple:
+
+ <select multiple name="msel_1"></select>
+ <select multiple name="msel_1b"><option></option></select>
+ <select multiple name="msel_1c"><option selected></option></select>
+
+ <select multiple name="msel_2"><option value="msel_2_v"></option></select>
+ <select multiple name="msel_3"><option selected value="msel_3_v"></option></select>
+
+ <select multiple name="msel_4"><option value="msel_4_v1"></option><option value="msel_4_v2"></option></select>
+ <select multiple name="msel_5"><option selected value="msel_5_v1"></option><option value="msel_5_v2"></option></select>
+ <select multiple name="msel_6"><option value="msel_6_v1"></option><option selected value="msel_6_v2"></option></select>
+ <select multiple name="msel_7"><option selected value="msel_7_v1"></option><option selected value="msel_7_v2"></option></select>
+
+ <select multiple name="msel_8"><option>msel_8_v1</option><option>msel_8_v2</option></select>
+ <select multiple name="msel_9"><option selected>msel_9_v1</option><option>msel_9_v2</option></select>
+ <select multiple name="msel_10"><option>msel_10_v1</option><option selected>msel_10_v2</option></select>
+ <select multiple name="msel_11"><option selected>msel_11_v1</option><option selected>msel_11_v2</option></select>
+
+ <select multiple name="msel_12"><option value="msel_12_v1">msel_12_v1_text</option><option value="msel_12_v2">msel_12_v2_text</option></select>
+ <select multiple name="msel_13"><option selected value="msel_13_v1">msel_13_v1_text</option><option value="msel_13_v2">msel_13_v2_text</option></select>
+ <select multiple name="msel_14"><option value="msel_14_v1">msel_14_v1_text</option><option selected value="msel_14_v2">msel_14_v2_text</option></select>
+ <select multiple name="msel_15"><option selected value="msel_15_v1">msel_15_v1_text</option><option selected value="msel_15_v2">msel_15_v2_text</option></select>
+
+ <select multiple name="msel_16"><option>msel_16_v1</option><option>msel_16_v2</option><option>msel_16_v3</option></select>
+ <select multiple name="msel_17"><option selected>msel_17_v1</option><option>msel_17_v2</option><option>msel_17_v3</option></select>
+ <select multiple name="msel_18"><option>msel_18_v1</option><option selected>msel_18_v2</option><option>msel_18_v3</option></select>
+ <select multiple name="msel_19"><option selected>msel_19_v1</option><option selected>msel_19_v2</option><option>msel_19_v3</option></select>
+ <select multiple name="msel_20"><option>msel_20_v1</option><option>msel_20_v2</option><option selected>msel_20_v3</option></select>
+ <select multiple name="msel_21"><option selected>msel_21_v1</option><option>msel_21_v2</option><option selected>msel_21_v3</option></select>
+ <select multiple name="msel_22"><option>msel_22_v1</option><option selected>msel_22_v2</option><option selected>msel_22_v3</option></select>
+ <select multiple name="msel_23"><option selected>msel_23_v1</option><option selected>msel_23_v2</option><option selected>msel_23_v3</option></select>
+
+ <select multiple name="msel_24"><option disabled>msel_24_v1</option><option>msel_24_v2</option></select>
+ <select multiple name="msel_25"><option disabled selected>msel_25_v1</option><option>msel_25_v2</option></select>
+ <select multiple name="msel_26"><option disabled>msel_26_v1</option><option selected>msel_26_v2</option></select>
+ <select multiple name="msel_27"><option disabled selected>msel_27_v1</option><option selected>msel_27_v2</option></select>
+
+ <select multiple name="msel_28"><option>msel_28_v1</option><option disabled>msel_28_v2</option></select>
+ <select multiple name="msel_29"><option selected>msel_29_v1</option><option disabled>msel_29_v2</option></select>
+ <select multiple name="msel_30"><option>msel_30_v1</option><option disabled selected>msel_30_v2</option></select>
+ <select multiple name="msel_31"><option selected>msel_31_v1</option><option disabled selected>msel_31_v2</option></select>
+
+ <select multiple name="msel_32"><option disabled selected>msel_32_v1</option><option disabled selected>msel_32_v2</option></select>
+
+ <select multiple name=""><option>msel_33_v1</option><option>msel_33_v2</option></select>
+ <select multiple name=""><option selected>msel_34_v1</option><option>msel_34_v2</option></select>
+ <select multiple name=""><option>msel_35_v1</option><option selected>msel_35_v2</option></select>
+ <select multiple name=""><option selected>msel_36_v1</option><option selected>msel_36_v2</option></select>
+
+ <select multiple><option>msel_37_v1</option><option>msel_37_v2</option></select>
+ <select multiple><option selected>msel_38_v1</option><option>msel_38_v2</option></select>
+ <select multiple><option>msel_39_v1</option><option selected>msel_39_v2</option></select>
+ <select multiple><option selected>msel_40_v1</option><option selected>msel_40_v2</option></select>
+ </p>
+</form>
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.8">
+
+SimpleTest.waitForExplicitFinish();
+
+const placeholder_myFile1 = {};
+const placeholder_myFile2 = {};
+const placeholder_emptyFile = {};
+
+var myFile1, myFile2, emptyFile;
+let openerURL = SimpleTest.getTestFileURL("formSubmission_chrome.js");
+let opener = SpecialPowers.loadChromeScript(openerURL);
+
+{
+ let xhr = new XMLHttpRequest;
+ xhr.open("GET", "/dynamic/getMyDirectory.sjs", false);
+ xhr.send();
+ let basePath = xhr.responseText;
+
+ opener.addMessageListener("files.opened", onFilesOpened);
+ opener.sendAsyncMessage("files.open", [
+ basePath + "file_formSubmission_text.txt",
+ basePath + "file_formSubmission_img.jpg",
+ ]);
+}
+
+function onFilesOpened(files) {
+ let [textFile, imageFile] = files;
+ opener.destroy();
+
+ let singleFile = textFile;
+ let multiFile = [textFile, imageFile];
+
+ var addList = document.getElementsByClassName("setfile");
+ let i = 0;
+ var input;
+ while (input = addList[i++]) {
+ if (input.classList.contains("multi")) {
+ SpecialPowers.wrap(input).mozSetFileArray(multiFile);
+ } else {
+ SpecialPowers.wrap(input).mozSetFileArray([singleFile]);
+ }
+ }
+
+ input = document.createElement("input");
+ input.type = "file";
+ input.multiple = true;
+ SpecialPowers.wrap(input).mozSetFileArray(multiFile);
+ myFile1 = input.files[0];
+ myFile2 = input.files[1];
+ is(myFile1.size, 20, "File1 size");
+ is(myFile2.size, 2711, "File2 size");
+ emptyFile = { name: "", type: "application/octet-stream" };
+
+ // Now, actually run the tests; see below.
+ onFilesSet();
+};
+
+var expectedSub = [
+ // Default input
+ { name: "n1_1", value: "v1_1" },
+ { name: "n1_2", value: "" },
+ { name: "n1_3", value: "" },
+ { name: "n1_7_\r\n_\r\n_\r\n_ _\"", value: "v1_7____ _\"" },
+ // Text input
+ { name: "n2_1", value: "v2_1" },
+ { name: "n2_2", value: "" },
+ { name: "n2_3", value: "" },
+ { name: "n2_7_\r\n_\r\n_\r\n_ _\"", value: "v2_7____ _\"" },
+ // Checkbox unchecked
+ // Checkbox checked
+ { name: "n4_1", value: "v4_1" },
+ { name: "n4_2", value: "" },
+ { name: "n4_3", value: "on" },
+ { name: "n4_7_\r\n_\r\n_\r\n_ _\"", value: "v4_7_\r\n_\r\n_\r\n_ _\"" },
+ // Radio unchecked
+ // Radio checked
+ { name: "n6_1", value: "v6_1" },
+ { name: "n6_2", value: "" },
+ { name: "n6_3", value: "on" },
+ { name: "n6_7_\r\n_\r\n_\r\n_ _\"", value: "v6_7_\r\n_\r\n_\r\n_ _\"" },
+ // Hidden input
+ { name: "n7_1", value: "v7_1" },
+ { name: "n7_2", value: "" },
+ { name: "n7_3", value: "" },
+ { name: "n7_7_\r\n_\r\n_\r\n_ _\"", value: "v7_7_\r\n_\r\n_\r\n_ _\"" },
+ // Password input
+ { name: "n8_1", value: "v8_1" },
+ { name: "n8_2", value: "" },
+ { name: "n8_3", value: "" },
+ { name: "n8_7_\r\n_\r\n_\r\n_ _\"", value: "v8_7____ _\"" },
+ // Submit input
+ // Button input
+ // Image input
+ // Reset input
+ // Unknown input
+ { name: "n13_1", value: "v13_1" },
+ { name: "n13_2", value: "" },
+ { name: "n13_3", value: "" },
+ { name: "n13_7_\r\n_\r\n_\r\n_ _\"", value: "v13_7____ _\"" },
+ // <input type='url'>
+ { name: "n19_1", value: "http://v19_1.org" },
+ { name: "n19_2", value: "" },
+ { name: "n19_3", value: "" },
+ { name: "n19_7_\r\n_\r\n_\r\n__\"", value: "http://v19_7_____\"" },
+ { name: "n19_8", value: "http://m\xf3zill\xe4.\xf3rg" },
+ // <input type='email'>
+ { name: "n20_1", value: "v20_1@bar" },
+ { name: "n20_2", value: "" },
+ { name: "n20_3", value: "" },
+ { name: "n20_7_\r\n_\r\n_\r\n__\"", value: "v20_7_____\"@bar" },
+ { name: "n20_8", value: "foo@mózillä.órg" },
+ // Default button
+ // Submit button
+ // Button button
+ // Reset button
+ // Unknown button
+ // File
+ { name: "file_1", value: placeholder_myFile1 },
+ { name: "file_2", value: placeholder_emptyFile },
+ // Multiple file
+ { name: "file_3", value: placeholder_myFile1 },
+ { name: "file_4", value: placeholder_myFile1 },
+ { name: "file_4", value: placeholder_myFile2 },
+ { name: "file_5", value: placeholder_emptyFile },
+ // Textarea
+ { name: "t1", value: "t_1_v" },
+ { name: "t2", value: "" },
+ { name: "t6", value: "t_6_v" },
+ { name: "t7", value: "t_7_v\r\n" },
+ { name: "t8", value: "\r\n t_8_v \r\n" },
+ { name: "t9_\r\n_\r\n_\r\n_ _\"", value: "t_9_\r\n_\r\n_\r\n_ _\"_v" },
+ { name: "t10", value: "t_10_v" },
+
+ // Select one
+ { name: "sel_1b", value: "" },
+ { name: "sel_1c", value: "" },
+ { name: "sel_2", value: "sel_2_v" },
+ { name: "sel_3", value: "sel_3_v" },
+ { name: "sel_4", value: "sel_4_v1" },
+ { name: "sel_5", value: "sel_5_v1" },
+ { name: "sel_6", value: "sel_6_v2" },
+ { name: "sel_7", value: "sel_7_v1" },
+ { name: "sel_8", value: "sel_8_v1" },
+ { name: "sel_9", value: "sel_9_v2" },
+ { name: "sel_10", value: "sel_10_v1" },
+ { name: "sel_11", value: "sel_11_v1" },
+ { name: "sel_12", value: "sel_12_v2" },
+ { name: "sel_13", value: "sel_13_v2" },
+ { name: "sel_15", value: "sel_15_v2" },
+ { name: "sel_16", value: "sel_16_v1" },
+ { name: "sel_17", value: "sel_17_v1" },
+ // Select three
+ { name: "msel_1c", value: "" },
+ { name: "msel_3", value: "msel_3_v" },
+ { name: "msel_5", value: "msel_5_v1" },
+ { name: "msel_6", value: "msel_6_v2" },
+ { name: "msel_7", value: "msel_7_v1" },
+ { name: "msel_7", value: "msel_7_v2" },
+ { name: "msel_9", value: "msel_9_v1" },
+ { name: "msel_10", value: "msel_10_v2" },
+ { name: "msel_11", value: "msel_11_v1" },
+ { name: "msel_11", value: "msel_11_v2" },
+ { name: "msel_13", value: "msel_13_v1" },
+ { name: "msel_14", value: "msel_14_v2" },
+ { name: "msel_15", value: "msel_15_v1" },
+ { name: "msel_15", value: "msel_15_v2" },
+ { name: "msel_17", value: "msel_17_v1" },
+ { name: "msel_18", value: "msel_18_v2" },
+ { name: "msel_19", value: "msel_19_v1" },
+ { name: "msel_19", value: "msel_19_v2" },
+ { name: "msel_20", value: "msel_20_v3" },
+ { name: "msel_21", value: "msel_21_v1" },
+ { name: "msel_21", value: "msel_21_v3" },
+ { name: "msel_22", value: "msel_22_v2" },
+ { name: "msel_22", value: "msel_22_v3" },
+ { name: "msel_23", value: "msel_23_v1" },
+ { name: "msel_23", value: "msel_23_v2" },
+ { name: "msel_23", value: "msel_23_v3" },
+ { name: "msel_26", value: "msel_26_v2" },
+ { name: "msel_27", value: "msel_27_v2" },
+ { name: "msel_29", value: "msel_29_v1" },
+ { name: "msel_31", value: "msel_31_v1" },
+];
+
+var expectedAugment = [
+ { name: "aName", value: "aValue" },
+ //{ name: "aNameBool", value: "false" },
+ { name: "aNameNum", value: "9.2" },
+ { name: "aNameFile1", value: placeholder_myFile1 },
+ { name: "aNameFile2", value: placeholder_myFile2 },
+ //{ name: "aNameObj", value: "[object XMLHttpRequest]" },
+ //{ name: "aNameNull", value: "null" },
+ //{ name: "aNameUndef", value: "undefined" },
+];
+
+function checkMPSubmission(sub, expected, test) {
+ function getPropCount(o) {
+ var x, l = 0;
+ for (x in o) ++l;
+ return l;
+ }
+ function mpquote(s) {
+ return s.replace(/\r\n/g, " ")
+ .replace(/\r/g, " ")
+ .replace(/\n/g, " ")
+ .replace(/\"/g, "\\\"");
+ }
+
+ is(sub.length, expected.length,
+ "Correct number of multipart items in " + test);
+
+ if (sub.length != expected.length) {
+ alert(JSON.stringify(sub));
+ }
+
+ var i;
+ for (i = 0; i < expected.length; ++i) {
+ if (!("fileName" in expected[i])) {
+ is(sub[i].headers["Content-Disposition"],
+ "form-data; name=\"" + mpquote(expected[i].name) + "\"",
+ "Correct name in " + test);
+ is (getPropCount(sub[i].headers), 1,
+ "Wrong number of headers in " + test);
+ is(sub[i].body,
+ expected[i].value.replace(/\r\n|\r|\n/, "\r\n"),
+ "Correct value in " + test);
+ }
+ else {
+ is(sub[i].headers["Content-Disposition"],
+ "form-data; name=\"" + mpquote(expected[i].name) + "\"; filename=\"" +
+ mpquote(expected[i].fileName) + "\"",
+ "Correct name in " + test);
+ is(sub[i].headers["Content-Type"],
+ expected[i].contentType,
+ "Correct content type in " + test);
+ is (getPropCount(sub[i].headers), 2,
+ "Wrong number of headers in " + test);
+ is(sub[i].body,
+ expected[i].value,
+ "Correct value in " + test);
+ }
+ }
+}
+
+function utf8encode(s) {
+ return unescape(encodeURIComponent(s));
+}
+
+function checkURLSubmission(sub, expected) {
+ function urlEscape(s) {
+ return escape(utf8encode(s)).replace(/%20/g, "+")
+ .replace(/\//g, "%2F")
+ .replace(/@/g, "%40");
+ }
+
+ subItems = sub.split("&");
+ is(subItems.length, expected.length,
+ "Correct number of url items");
+ var i;
+ for (i = 0; i < expected.length; ++i) {
+ let expect = urlEscape(expected[i].name) + "=" +
+ urlEscape(("fileName" in expected[i]) ? expected[i].fileName : expected[i].value);
+ is (subItems[i], expect, "expected URL part");
+ }
+}
+
+function checkPlainSubmission(sub, expected) {
+
+ is(sub,
+ expected.map(function(v) {
+ return v.name + "=" +
+ (("fileName" in v) ? v.fileName : v.value) +
+ "\r\n";
+ }).join(""),
+ "Correct submission");
+}
+
+function setDisabled(list, state) {
+ Array.prototype.forEach.call(list, function(e) {
+ e.disabled = state;
+ });
+}
+
+var gen;
+function onFilesSet() {
+ gen = runTest();
+ addLoadEvent(function() {
+ gen.next();
+ });
+}
+
+function runTest() {
+ // Set up the expectedSub array
+ fileReader1 = new FileReader;
+ fileReader1.readAsBinaryString(myFile1);
+ fileReader2 = new FileReader;
+ fileReader2.readAsBinaryString(myFile2);
+ fileReader1.onload = fileReader2.onload = function() { gen.next(); };
+ yield undefined; // Wait for both FileReaders. We don't care which order they finish.
+ yield undefined;
+ function fileFixup(o) {
+ if (o.value === placeholder_myFile1) {
+ o.value = fileReader1.result;
+ o.fileName = myFile1.name;
+ o.contentType = myFile1.type;
+ }
+ else if (o.value === placeholder_myFile2) {
+ o.value = fileReader2.result;
+ o.fileName = myFile2.name;
+ o.contentType = myFile2.type;
+ }
+ else if (o.value === placeholder_emptyFile) {
+ o.value = "";
+ o.fileName = emptyFile.name;
+ o.contentType = emptyFile.type;
+ }
+ };
+ expectedSub.forEach(fileFixup);
+ expectedAugment.forEach(fileFixup);
+
+ var form = $("form");
+
+ // multipart/form-data
+
+ var iframe = $("target_iframe");
+ iframe.onload = function() { gen.next(); };
+
+ // Make normal submission
+ form.submit();
+ yield undefined; // Wait for iframe to load as a result of the submission
+ var submission = JSON.parse(iframe.contentDocument.documentElement.textContent);
+ checkMPSubmission(submission, expectedSub, "normal submission");
+
+ // Disabled controls
+ setDisabled(document.querySelectorAll("input, select, textarea"), true);
+ form.submit();
+ yield undefined;
+ submission = JSON.parse(iframe.contentDocument.documentElement.textContent);
+ checkMPSubmission(submission, [], "disabled controls");
+
+ // Reenabled controls
+ setDisabled(document.querySelectorAll("input, select, textarea"), false);
+ form.submit();
+ yield undefined;
+ submission = JSON.parse(iframe.contentDocument.documentElement.textContent);
+ checkMPSubmission(submission, expectedSub, "reenabled controls");
+
+ // text/plain
+ form.action = "form_submit_server.sjs?plain";
+ form.enctype = "text/plain";
+ form.submit();
+ yield undefined;
+ submission = JSON.parse(iframe.contentDocument.documentElement.textContent);
+ checkPlainSubmission(submission, expectedSub);
+
+ // application/x-www-form-urlencoded
+ form.action = "form_submit_server.sjs?url";
+ form.enctype = "application/x-www-form-urlencoded";
+ form.submit();
+ yield undefined;
+ submission = JSON.parse(iframe.contentDocument.documentElement.textContent);
+ checkURLSubmission(submission, expectedSub);
+
+ // application/x-www-form-urlencoded
+ form.action = "form_submit_server.sjs?xxyy";
+ form.method = "GET";
+ form.enctype = "";
+ form.submit();
+ yield undefined;
+ submission = JSON.parse(iframe.contentDocument.documentElement.textContent);
+ checkURLSubmission(submission, expectedSub);
+
+ // application/x-www-form-urlencoded
+ form.action = "form_submit_server.sjs";
+ form.method = "";
+ form.enctype = "";
+ form.submit();
+ yield undefined;
+ submission = JSON.parse(iframe.contentDocument.documentElement.textContent);
+ checkURLSubmission(submission, expectedSub);
+
+ // Send form using XHR and FormData
+ xhr = new XMLHttpRequest();
+ xhr.onload = function() { gen.next(); };
+ xhr.open("POST", "form_submit_server.sjs");
+ xhr.send(new FormData(form));
+ yield undefined; // Wait for XHR load
+ checkMPSubmission(JSON.parse(xhr.responseText), expectedSub, "send form using XHR and FormData");
+
+ // Send disabled form using XHR and FormData
+ setDisabled(document.querySelectorAll("input, select, textarea"), true);
+ xhr.open("POST", "form_submit_server.sjs");
+ xhr.send(new FormData(form));
+ yield undefined;
+ checkMPSubmission(JSON.parse(xhr.responseText), [], "send disabled form using XHR and FormData");
+ setDisabled(document.querySelectorAll("input, select, textarea"), false);
+
+ // Send FormData
+ function addToFormData(fd) {
+ fd.append("aName", "aValue");
+ fd.append("aNameNum", 9.2);
+ fd.append("aNameFile1", myFile1);
+ fd.append("aNameFile2", myFile2);
+ }
+ var fd = new FormData();
+ addToFormData(fd);
+ xhr.open("POST", "form_submit_server.sjs");
+ xhr.send(fd);
+ yield undefined;
+ checkMPSubmission(JSON.parse(xhr.responseText), expectedAugment, "send FormData");
+
+ // Augment <form> using FormData
+ fd = new FormData(form);
+ addToFormData(fd);
+ xhr.open("POST", "form_submit_server.sjs");
+ xhr.send(fd);
+ yield undefined;
+ checkMPSubmission(JSON.parse(xhr.responseText),
+ expectedSub.concat(expectedAugment), "send augmented FormData");
+
+ SimpleTest.finish();
+ yield undefined;
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_formSubmission2.html b/dom/html/test/test_formSubmission2.html
new file mode 100644
index 000000000..2d0eb42f9
--- /dev/null
+++ b/dom/html/test/test_formSubmission2.html
@@ -0,0 +1,221 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=523771
+-->
+<head>
+ <title>Test for Bug 523771</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=523771">Mozilla Bug 523771</a>
+<p id="display"></p>
+<iframe name="target_iframe" id="target_iframe"></iframe>
+<form action="form_submit_server.sjs" target="target_iframe" id="form"
+ method="POST" enctype="multipart/form-data">
+ <table>
+ <tr>
+ <td>Control type</td>
+ <td>Name and value</td>
+ <td>Name, empty value</td>
+ <td>Name, no value</td>
+ <td>Empty name, with value</td>
+ <td>No name, with value</td>
+ <td>No name or value</td>
+ </tr>
+ <tr>
+ <td>Submit input</td>
+ <td><input type=submit name="n1_1" value="v1_1"></td>
+ <td><input type=submit name="n1_2" value=""></td>
+ <td><input type=submit name="n1_3"></td>
+ <td><input type=submit name="" value="v1_4"></td>
+ <td><input type=submit value="v1_5"></td>
+ <td><input type=submit></td>
+ </tr>
+ <tr>
+ <td>Image input</td>
+ <td><input type=image src="file_formSubmission_img.jpg" name="n2_1" value="v2_1"></td>
+ <td><input type=image src="file_formSubmission_img.jpg" name="n2_2" value=""></td>
+ <td><input type=image src="file_formSubmission_img.jpg" name="n2_3"></td>
+ <td><input type=image src="file_formSubmission_img.jpg" name="" value="v2_4"></td>
+ <td><input type=image src="file_formSubmission_img.jpg" value="v2_5"></td>
+ <td><input type=image src="file_formSubmission_img.jpg"></td>
+ </tr>
+ <tr>
+ <td>Submit button</td>
+ <td><button type=submit name="n3_1" value="v3_1"></button></td>
+ <td><button type=submit name="n3_2" value=""></button></td>
+ <td><button type=submit name="n3_3"></button></td>
+ <td><button type=submit name="" value="v3_4"></button></td>
+ <td><button type=submit value="v3_5"></button></td>
+ <td><button type=submit ></button></td>
+ </tr>
+ <tr>
+ <td>Submit button with text</td>
+ <td><button type=submit name="n4_1" value="v4_1">text here</button></td>
+ <td><button type=submit name="n4_2" value="">text here</button></td>
+ <td><button type=submit name="n4_3">text here</button></td>
+ <td><button type=submit name="" value="v4_4">text here</button></td>
+ <td><button type=submit value="v4_5">text here</button></td>
+ <td><button type=submit>text here</button></td>
+ </tr>
+ </table>
+</form>
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.8">
+
+var gen = runTest();
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ gen.next();
+});
+
+var expectedSub = [
+ // Submit input
+ [{ name: "n1_1", value: "v1_1" }],
+ [{ name: "n1_2", value: "" }],
+ [{ name: "n1_3", value: "Submit Query" }],
+ [],
+ [],
+ [],
+ // Image input
+ [{ name: "n2_1.x", value: "10" },
+ { name: "n2_1.y", value: "7" }],
+ [{ name: "n2_2.x", value: "10" },
+ { name: "n2_2.y", value: "7" }],
+ [{ name: "n2_3.x", value: "10" },
+ { name: "n2_3.y", value: "7" }],
+ [{ name: "x", value: "10" },
+ { name: "y", value: "7" }],
+ [{ name: "x", value: "10" },
+ { name: "y", value: "7" }],
+ [{ name: "x", value: "10" },
+ { name: "y", value: "7" }],
+ // Submit button
+ [{ name: "n3_1", value: "v3_1" }],
+ [{ name: "n3_2", value: "" }],
+ [{ name: "n3_3", value: "" }],
+ [],
+ [],
+ [],
+ // Submit button with text
+ [{ name: "n4_1", value: "v4_1" }],
+ [{ name: "n4_2", value: "" }],
+ [{ name: "n4_3", value: "" }],
+ [],
+ [],
+ [],
+];
+
+function checkSubmission(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");
+ is (getPropCount(sub[i].headers), 1,
+ "Wrong number of headers");
+ }
+ else {
+ is(sub[i].headers["Content-Disposition"],
+ "form-data; name=\"" + expected[i].name + "\"; filename=\"" +
+ expected[i].fileName + "\"",
+ "Correct name");
+ is(sub[i].headers["Content-Type"],
+ expected[i].contentType,
+ "Correct content type");
+ is (getPropCount(sub[i].headers), 2,
+ "Wrong number of headers");
+ }
+ is(sub[i].body,
+ expected[i].value,
+ "Correct value");
+ }
+}
+
+function clickImage(aTarget, aX, aY)
+{
+ aTarget.style.position = "absolute";
+ aTarget.style.top = "0";
+ aTarget.style.left = "0";
+ aTarget.offsetTop;
+
+ var wu = SpecialPowers.getDOMWindowUtils(aTarget.ownerDocument.defaultView);
+
+ wu.sendMouseEvent('mousedown', aX, aY, 0, 1, 0);
+ wu.sendMouseEvent('mouseup', aX, aY, 0, 0, 0);
+
+ aTarget.style.position = "";
+ aTarget.style.top = "";
+ aTarget.style.left = "";
+}
+
+function runTest() {
+ // Make normal submission
+ var form = $("form");
+ var iframe = $("target_iframe");
+ iframe.onload = function() { gen.next(); };
+
+ var elements = form.querySelectorAll("input, button");
+
+ is(elements.length, expectedSub.length,
+ "tests vs. expected out of sync");
+
+ var i;
+ for (i = 0; i < elements.length && i < expectedSub.length; ++i) {
+ elem = elements[i];
+ if (elem.localName != "input" || elem.type != "image") {
+ elem.click();
+ }
+ else {
+ clickImage(elem, 10, 7);
+ }
+ yield undefined;
+
+ var submission = JSON.parse(iframe.contentDocument.documentElement.textContent);
+ checkSubmission(submission, expectedSub[i]);
+ }
+
+ // Disabled controls
+ var i;
+ for (i = 0; i < elements.length && i < expectedSub.length; ++i) {
+ elem = elements[i];
+ form.onsubmit = function() {
+ elem.disabled = true;
+ }
+ if (elem.localName != "input" || elem.type != "image") {
+ elem.click();
+ }
+ else {
+ clickImage(elem, 10, 7);
+ }
+ yield undefined;
+
+ is(elem.disabled, true, "didn't disable");
+ elem.disabled = false;
+ form.onsubmit = undefined;
+
+ var submission = JSON.parse(iframe.contentDocument.documentElement.textContent);
+ checkSubmission(submission, []);
+ }
+
+ SimpleTest.finish();
+ yield undefined;
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_formelements.html b/dom/html/test/test_formelements.html
new file mode 100644
index 000000000..c4714aa6b
--- /dev/null
+++ b/dom/html/test/test_formelements.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=772869
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 772869</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=772869">Mozilla Bug 772869</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <form id="f">
+ <input name="x">
+ <input type="image" name="a">
+ <input type="file" name="y">
+ <input type="submit" name="z">
+ <input id="w">
+ <input name="w">
+ </form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 772869 **/
+var x = $("f").elements;
+x.something = "another";
+names = [];
+for (var name in x) {
+ names.push(name);
+}
+is(names.length, 9, "Should have 9 enumerated names");
+is(names[0], "0", "Enum entry 1");
+is(names[1], "1", "Enum entry 2");
+is(names[2], "2", "Enum entry 3");
+is(names[3], "3", "Enum entry 4");
+is(names[4], "4", "Enum entry 5");
+is(names[5], "something", "Enum entry 6");
+is(names[6], "namedItem", "Enum entry 7");
+is(names[7], "item", "Enum entry 8");
+is(names[8], "length", "Enum entry 9");
+
+names = Object.getOwnPropertyNames(x);
+is(names.length, 10, "Should have 10 items");
+// Now sort entries 5 through 8, for comparison purposes. We don't sort the
+// whole array, because we want to make sure the ordering between the parts
+// is correct
+temp = names.slice(5, 9);
+temp.sort();
+names.splice.bind(names, 5, 4).apply(null, temp);
+is(names.length, 10, "Should still have 10 items");
+is(names[0], "0", "Entry 1")
+is(names[1], "1", "Entry 2")
+is(names[2], "2", "Entry 3")
+is(names[3], "3", "Entry 4")
+is(names[4], "4", "Entry 5")
+is(names[5], "w", "Entry 6")
+is(names[6], "x", "Entry 7")
+is(names[7], "y", "Entry 8")
+is(names[8], "z", "Entry 9")
+is(names[9], "something", "Entry 10")
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_fragment_form_pointer.html b/dom/html/test/test_fragment_form_pointer.html
new file mode 100644
index 000000000..c9ed9dbc5
--- /dev/null
+++ b/dom/html/test/test_fragment_form_pointer.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=946585
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 946585</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=946585">Mozilla Bug 946585</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<form><div id="formdiv"></div></form>
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+/** Test for Bug 946585 **/
+var formDiv = document.getElementById("formdiv");
+formDiv.innerHTML = '<form>';
+is(formDiv.firstChild, null, "InnerHTML should not produce form element because the div has a form pointer.");
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_fullscreen-api-race.html b/dom/html/test/test_fullscreen-api-race.html
new file mode 100644
index 000000000..03c6c6da3
--- /dev/null
+++ b/dom/html/test/test_fullscreen-api-race.html
@@ -0,0 +1,166 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for race conditions of Fullscreen API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script>
+
+function Deferred() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+}
+
+function checkIsChromeFullscreen(win, inFullscreen) {
+ return SimpleTest.promiseWaitForCondition(
+ () => win.fullScreen == inFullscreen,
+ "The window should exit fullscreen state");
+}
+
+SimpleTest.waitForExplicitFinish();
+// XXX This actually exposes a true race condition, but it could rarely
+// happen in real world, because it only happens when requestFullscreen
+// is called immediately after exiting fullscreen in certain condition,
+// and in real life, requestFullscreen can only be called inside a user
+// event handler. But we want to fix this race condition at some point,
+// via queuing all exiting request as well as entering request together
+// which we may eventually need to do for bug 1188256.
+SimpleTest.requestFlakyTimeout(
+ "Need to wait for potential fullscreen transition");
+addLoadEvent(function () {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["full-screen-api.unprefix.enabled", true],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ ]
+ }, next);
+});
+
+const OPEN_WINDOW_FUNCS = [
+ function openNewTab() {
+ return window.open("about:blank");
+ },
+ function openNewWindow() {
+ return window.open("about:blank", "", "width=300,height=200");
+ }
+];
+
+const ACTION_FUNCS = [
+ function navigate(win) {
+ info("About to navigate to another page");
+ var deferred = new Deferred();
+ win.location = "data:text/html,<html>";
+ setTimeout(() => {
+ SimpleTest.waitForFocus(() => {
+ checkIsChromeFullscreen(win, false).then(() => {
+ win.close();
+ deferred.resolve();
+ });
+ }, win);
+ }, 0);
+ return deferred.promise;
+ },
+ function closeWindow(win) {
+ info("About to close the window");
+ win.close();
+ return Promise.resolve();
+ },
+ function exitFullscreen(win) {
+ info("About to cancel fullscreen");
+ var deferred = new Deferred();
+ function listener() {
+ win.removeEventListener("fullscreenchange", listener);
+ ok(!win.document.fullscreenElement, "Should exit fullscreen");
+ checkIsChromeFullscreen(win, false).then(() => {
+ win.close();
+ deferred.resolve();
+ });
+ }
+ win.addEventListener("fullscreenchange", listener);
+ win.document.exitFullscreen();
+ return deferred.promise;
+ },
+ function exitAndClose(win) {
+ info("About to cancel fullscreen and close the window");
+ win.document.exitFullscreen();
+ win.close();
+ return Promise.resolve();
+ }
+];
+
+function* testGenerator() {
+ for (var openWinFunc of OPEN_WINDOW_FUNCS) {
+ for (var actionFunc of ACTION_FUNCS) {
+ info(`Testing ${openWinFunc.name}, ${actionFunc.name}`);
+ yield { openWinFunc: openWinFunc, actionFunc: actionFunc };
+ }
+ }
+}
+
+function runTest(test) {
+ var win = test.openWinFunc();
+ return new Promise(resolve => {
+ SimpleTest.waitForFocus(resolve, win, true);
+ }).then(() => {
+ return new Promise((resolve, reject) => {
+ var retried = false;
+ function listener(evt) {
+ if (!retried && evt.type == "fullscreenerror") {
+ todo(false, "Failed to enter fullscreen, but try again");
+ retried = true;
+ SimpleTest.waitForFocus(() => {
+ win.document.documentElement.requestFullscreen();
+ }, win, true);
+ return;
+ }
+ win.removeEventListener("fullscreenchange", listener);
+ win.removeEventListener("fullscreenerror", listener);
+ is(evt.type, "fullscreenchange", "Should get fullscreenchange");
+ ok(win.document.fullscreenElement, "Should have entered fullscreen");
+ ok(win.fullScreen, "The window should be in fullscreen");
+ test.actionFunc(win).then(resolve);
+ }
+ if (win.fullScreen) {
+ todo(false, "Should not open in fullscreen mode");
+ win.close();
+ reject();
+ return;
+ }
+ info("About to enter fullscreen");
+ win.addEventListener("fullscreenchange", listener);
+ win.addEventListener("fullscreenerror", listener);
+ win.document.documentElement.requestFullscreen();
+ });
+ }).then(() => {
+ ok(win.closed, "The window should have been closed");
+ });
+}
+
+var tests = testGenerator();
+
+function next() {
+ var test = tests.next().value;
+ if (test) {
+ runTest(test).catch(() => {
+ return new Promise(resolve => {
+ SimpleTest.waitForFocus(resolve);
+ }).then(() => runTest(test));
+ }).catch(() => {
+ ok(false, "Fail to run test " +
+ `${test.openWinFunc.name}, ${test.actionFunc.name}`);
+ }).then(() => {
+ setTimeout(() => SimpleTest.waitForFocus(next), 1000);
+ });
+ } else {
+ SimpleTest.finish();
+ return;
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/html/test/test_fullscreen-api.html b/dom/html/test/test_fullscreen-api.html
new file mode 100644
index 000000000..1ac5b3b51
--- /dev/null
+++ b/dom/html/test/test_fullscreen-api.html
@@ -0,0 +1,106 @@
+ <!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 545812</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"/>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <style>
+ body {
+ background-color: black;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=545812">Mozilla Bug 545812</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Tests for Bug 545812 **/
+SimpleTest.requestFlakyTimeout("untriaged");
+
+// Run the tests which go full-screen in new windows, as mochitests normally
+// run in an iframe, which by default will not have the allowfullscreen
+// attribute set, so full-screen won't work.
+var gTestWindows = [
+ "file_fullscreen-multiple.html",
+ "file_fullscreen-rollback.html",
+ "file_fullscreen-esc-exit.html",
+ "file_fullscreen-denied.html",
+ "file_fullscreen-api.html",
+ "file_fullscreen-plugins.html",
+ "file_fullscreen-hidden.html",
+ "file_fullscreen-svg-element.html",
+ "file_fullscreen-navigation.html",
+ "file_fullscreen-scrollbar.html",
+ "file_fullscreen-selector.html",
+ "file_fullscreen-top-layer.html",
+ "file_fullscreen-backdrop.html",
+ "file_fullscreen-nested.html",
+ "file_fullscreen-prefixed.html",
+ "file_fullscreen-unprefix-disabled.html",
+ "file_fullscreen-lenient-setters.html",
+];
+
+var testWindow = null;
+var gTestIndex = 0;
+
+function finish() {
+ SimpleTest.finish();
+}
+
+function nextTest() {
+ if (testWindow) {
+ testWindow.close();
+ }
+ SimpleTest.executeSoon(runNextTest);
+}
+
+function runNextTest() {
+ if (gTestIndex < gTestWindows.length) {
+ info("Run test " + gTestWindows[gTestIndex]);
+ testWindow = window.open(gTestWindows[gTestIndex], "", "width=500,height=500,scrollbars=yes");
+ // We'll wait for the window to load, then make sure our window is refocused
+ // before starting the test, which will get kicked off on "focus".
+ // This ensures that we're essentially back on the primary "desktop" on
+ // OS X Lion before we run the test.
+ testWindow.addEventListener("load", function onload() {
+ testWindow.removeEventListener("load", onload, false);
+ SimpleTest.waitForFocus(function() {
+ SimpleTest.waitForFocus(testWindow.begin, testWindow);
+ });
+ }, false);
+ gTestIndex++;
+ } else {
+ SimpleTest.finish();
+ }
+}
+
+try {
+ window.fullScreen = true;
+} catch (e) {
+}
+is(window.fullScreen, false, "Shouldn't be able to set window fullscreen from content");
+// Ensure the full-screen api is enabled, and will be disabled on test exit.
+// Disable the requirement for trusted contexts only, so the tests are easier
+// to write
+addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["full-screen-api.enabled", true],
+ ["full-screen-api.unprefix.enabled", true],
+ ["full-screen-api.allow-trusted-requests-only", false],
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"]
+ ]}, nextTest);
+});
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_hash_encoded.html b/dom/html/test/test_hash_encoded.html
new file mode 100644
index 000000000..f814c8ae9
--- /dev/null
+++ b/dom/html/test/test_hash_encoded.html
@@ -0,0 +1,118 @@
+<!doctype html>
+<html>
+<head>
+<title>Test link.hash attribute</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<pre id="test">
+
+<script>
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [['dom.url.encode_decode_hash', false]]}, runTest);
+
+function runTest() {
+ setupTest();
+ doTestEncoded();
+ SimpleTest.finish();
+}
+
+function setupTest() {
+ var target1 = document.createElement("a");
+ target1.id = "target1";
+ target1.href = "http://www.example.com/#q=♥â¥#hello";
+ document.body.appendChild(target1);
+
+ var target2 = document.createElement("a");
+ target2.id = "target2";
+ target2.href = "http://www.example.com/#q=%E2%99%A5%C3%A2%C2%A5";
+ document.body.appendChild(target2);
+
+ var target3 = document.createElement("a");
+ target3.id = "target3";
+ target3.href = "http://www.example.com/#/search/%23important";
+ document.body.appendChild(target3);
+
+ var target4 = document.createElement("a");
+ target4.id = "target4";
+ target4.href = 'http://www.example.com/#{"a":[13, 42], "b":{"key":"value"}}';
+ document.body.appendChild(target4);
+}
+
+function doTestEncoded() {
+ // Tests Link::GetHash
+
+ // Check that characters aren't being encoded
+ var target = document.getElementById("target1");
+ is(target.hash, '#q=♥â¥#hello', 'Unexpected link hash');
+
+ // Check that encoded characters aren't being decoded
+ target = document.getElementById("target2");
+ is(target.hash, '#q=%E2%99%A5%C3%A2%C2%A5', 'Unexpected link hash');
+
+ // A more regular use case
+ target = document.getElementById("target3");
+ is(target.hash, '#/search/%23important', 'Unexpected link hash');
+
+ // Some JSON
+ target = document.getElementById("target4");
+ is(target.hash, '#{"a":[13, 42], "b":{"key":"value"}}', 'Unexpected link hash');
+
+ // Tests URL::GetHash
+
+ var url = new URL("http://www.example.com/#q=♥â¥#hello")
+ is(url.hash, '#q=♥â¥#hello', 'Unexpected url hash');
+
+ url = new URL("http://www.example.com/#q=%E2%99%A5%C3%A2%C2%A5")
+ is(url.hash, '#q=%E2%99%A5%C3%A2%C2%A5', 'Unexpected url hash');
+
+ url = new URL("http://www.example.com/#/search/%23important")
+ is(url.hash, '#/search/%23important', 'Unexpected url hash');
+
+ // Test getters and setters
+
+ url = new URL("http://www.example.com/");
+ url.hash = "#q=♥â¥#hello%E2%99%A5%C3%A2%C2%A5#/search/%23important"
+ is(url.hash, '#q=♥â¥#hello%E2%99%A5%C3%A2%C2%A5#/search/%23important', 'Unexpected url hash');
+
+ // codepath in nsStandardUrl::SetRef is different if the path is non-empty
+ url = new URL("http://www.example.com/test/");
+ url.hash = "#q=♥â¥#hello%E2%99%A5%C3%A2%C2%A5#/search/%23important"
+ is(url.hash, '#q=♥â¥#hello%E2%99%A5%C3%A2%C2%A5#/search/%23important', 'Unexpected url hash');
+
+ url = new URL("http://www.example.com/");
+ url.hash = '#{"a":[13, 42], "b":{"key":"value"}}';
+ is(target.hash, '#{"a":[13, 42], "b":{"key":"value"}}', 'Unexpected url hash');
+ var parsed = JSON.parse(target.hash.substring(1));
+ is(parsed.b.key, 'value', 'JSON not parsed correctly');
+
+ url = new URL("http://www.example.com/test/");
+ url.hash = '#{"a":[13, 42], "b":{"key":"value"}}';
+ is(target.hash, '#{"a":[13, 42], "b":{"key":"value"}}', 'Unexpected url hash');
+ parsed = JSON.parse(target.hash.substring(1));
+ is(parsed.b.key, 'value', 'JSON not parsed correctly');
+
+ // Tests Location::GetHash
+
+ window.history.pushState(1, document.title, '#q=♥â¥#hello');
+ is(location.hash,'#q=♥â¥#hello', 'Unexpected location hash');
+
+ window.history.pushState(1, document.title, '#q=%E2%99%A5%C3%A2%C2%A5');
+ is(location.hash,'#q=%E2%99%A5%C3%A2%C2%A5', 'Unexpected location hash');
+
+ window.history.pushState(1, document.title, '#/search/%23important');
+ is(location.hash,'#/search/%23important', 'Unexpected location hash');
+
+ window.history.pushState(1, document.title, '#{"a":[13, 42], "b":{"key":"value"}}');
+ is(location.hash,'#{"a":[13, 42], "b":{"key":"value"}}', 'Unexpected location hash');
+
+}
+
+</script>
+
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_hidden.html b/dom/html/test/test_hidden.html
new file mode 100644
index 000000000..7b9d488c0
--- /dev/null
+++ b/dom/html/test/test_hidden.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=567663
+-->
+<head>
+ <title>Test for Bug 567663</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=567663">Mozilla Bug 567663</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <p></p>
+ <p hidden></p>
+</div>
+<pre id="test">
+<script>
+/** Test for Bug 567663 **/
+var ps = document.getElementById("content").getElementsByTagName("p");
+is(ps[0].hidden, false, "First p's IDL attribute was wrong.");
+is(ps[0].hasAttribute("hidden"), false, "First p had a content attribute.");
+is(ps[1].hidden, true, "Second p's IDL attribute was wrong.");
+is(ps[1].hasAttribute("hidden"), true,
+ "Second p didn't have a content attribute.");
+is(ps[1].getAttribute("hidden"), "",
+ "Second p's content attribute was wrong.");
+
+ps[0].hidden = true;
+is(ps[0].getAttribute("hidden"), "",
+ "Content attribute was set to an incorrect value.");
+ps[1].hidden = false;
+is(ps[1].hasAttribute("hidden"), false,
+ "Second p still had a content attribute.");
+
+ps[0].setAttribute("hidden", "banana");
+is(ps[0].hidden, true, "p's IDL attribute was wrong after setting.");
+is(ps[0].getAttribute("hidden"), "banana", "Content attribute changed.");
+
+ps[0].setAttribute("hidden", "false");
+is(ps[0].hidden, true, "p's IDL attribute was wrong after setting.");
+is(ps[0].getAttribute("hidden"), "false", "Content attribute changed.");
+
+ps[0].removeAttribute("hidden");
+is(ps[0].hidden, false,
+ "p's IDL attribute was wrong after removing the content attribute.");
+is(ps[0].hasAttribute("hidden"), false);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_html_attributes_reflection.html b/dom/html/test/test_html_attributes_reflection.html
new file mode 100644
index 000000000..372c2503a
--- /dev/null
+++ b/dom/html/test/test_html_attributes_reflection.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLHtmlElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLHtmlElement attributes reflection **/
+
+// .version
+reflectString({
+ element: document.createElement("html"),
+ attribute: "version",
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_htmlcollection.html b/dom/html/test/test_htmlcollection.html
new file mode 100644
index 000000000..1452f0c3c
--- /dev/null
+++ b/dom/html/test/test_htmlcollection.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=772869
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 772869</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=772869">Mozilla Bug 772869</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <a class="foo" name="x"></a>
+ <span class="foo" id="y"></span>
+ <span class="foo" name="x"></span>
+ <form class="foo" name="z" id="w"></form>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 772869 **/
+var x = document.getElementsByClassName("foo");
+x.something = "another";
+var names = [];
+for (var name in x) {
+ names.push(name);
+}
+is(names.length, 8, "Should have 8 enumerated names");
+is(names[0], "0", "Enum entry 1")
+is(names[1], "1", "Enum entry 2")
+is(names[2], "2", "Enum entry 3")
+is(names[3], "3", "Enum entry 4")
+is(names[4], "something", "Enum entry 5")
+is(names[5], "item", "Enum entry 6")
+is(names[6], "namedItem", "Enum entry 7")
+is(names[7], "length", "Enum entry 8");
+
+names = Object.getOwnPropertyNames(x);
+is(names.length, 9, "Should have 9 items");
+is(names[0], "0", "Entry 1")
+is(names[1], "1", "Entry 2")
+is(names[2], "2", "Entry 3")
+is(names[3], "3", "Entry 4")
+is(names[4], "x", "Entry 5")
+is(names[5], "y", "Entry 6")
+is(names[6], "w", "Entry 7")
+is(names[7], "z", "Entry 8")
+is(names[8], "something", "Entry 9")
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_iframe_sandbox_general.html b/dom/html/test/test_iframe_sandbox_general.html
new file mode 100644
index 000000000..6d3a190ee
--- /dev/null
+++ b/dom/html/test/test_iframe_sandbox_general.html
@@ -0,0 +1,283 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=341604
+Implement HTML5 sandbox attribute for IFRAMEs - general tests
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 341604 and Bug 766282</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>
+<script type="application/javascript">
+/** Test for Bug 341604 - Implement HTML5 sandbox attribute for IFRAMEs - general tests **/
+
+SimpleTest.expectAssertions(0, 1);
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestCompleteLog();
+
+// a postMessage handler that is used by sandboxed iframes without
+// 'allow-same-origin' to communicate pass/fail back to this main page.
+// it expects to be called with an object like {ok: true/false, desc:
+// <description of the test> which it then forwards to ok()
+window.addEventListener("message", receiveMessage, false);
+
+function receiveMessage(event)
+{
+ ok_wrapper(event.data.ok, event.data.desc);
+}
+
+var completedTests = 0;
+var passedTests = 0;
+
+function ok_wrapper(result, desc) {
+ ok(result, desc);
+
+ completedTests++;
+
+ if (result) {
+ passedTests++;
+ }
+
+ if (completedTests == 33) {
+ is(passedTests, completedTests, "There are " + completedTests + " general tests that should pass");
+ SimpleTest.finish();
+ }
+}
+
+function doTest() {
+ // passes twice if good
+ // 1) test that inline scripts (<script>) can run in an iframe sandboxed with "allow-scripts"
+ // (done in file_iframe_sandbox_c_if1.html which has 'allow-scripts')
+
+ // passes twice if good
+ // 2) test that <script src=...> can run in an iframe sandboxed with "allow-scripts"
+ // (done in file_iframe_sandbox_c_if1.html which has 'allow-scripts')
+
+ // passes twice if good
+ // 3) test that script in an event listener (body onload) can run in an iframe sandboxed with "allow-scripts"
+ // (done in file_iframe_sandbox_c_if1.html which has 'allow-scripts')
+
+ // passes twice if good
+ // 4) test that script in an javascript:url can run in an iframe sandboxed with "allow-scripts"
+ // (done in file_iframe_sandbox_c_if1.html which has 'allow-scripts')
+
+ // fails if bad
+ // 5) test that inline scripts cannot run in an iframe sandboxed without "allow-scripts"
+ // (done in file_iframe_sandbox_c_if2.html which has sandbox='')
+
+ // fails if bad
+ // 6) test that <script src=...> cannot run in an iframe sandboxed without "allow-scripts"
+ // (done in file_iframe_sandbox_c_if2.html which has sandbox='')
+
+ // fails if bad
+ // 7) test that script in an event listener (body onload) cannot run in an iframe sandboxed without "allow-scripts"
+ // (done in file_iframe_sandbox_c_if2.html which has sandbox='')
+
+ // fails if bad
+ // 8) test that script in an event listener (img onerror) cannot run in an iframe sandboxed without "allow-scripts"
+ // (done in file_iframe_sandbox_c_if2.html which has sandbox='')
+
+ // fails if bad
+ // 9) test that script in an javascript:url cannot run in an iframe sandboxed without "allow-scripts"
+ // (done in file_iframe_sandbox_c_if_5.html which has sandbox='allow-same-origin')
+ var if_w = document.getElementById('if_5').contentWindow;
+ sendMouseEvent({type:'click'}, 'a_link', if_w);
+
+ // passes if good
+ // 10) test that a new iframe has sandbox attribute
+ var ifr = document.createElement("iframe");
+ ok_wrapper("sandbox" in ifr, "a new iframe should have a sandbox attribute");
+
+ // passes if good
+ // 11) test that the sandbox attribute's default stringyfied value is an empty string
+ ok_wrapper(ifr.sandbox.length === 0 && ifr.sandbox == "", "default sandbox attribute should be an empty string");
+
+ // passes if good
+ // 12) test that a sandboxed iframe with 'allow-forms' can submit forms
+ // (done in file_iframe_sandbox_c_if3.html which has 'allow-forms' and 'allow-scripts')
+
+ // fails if bad
+ // 13) test that a sandboxed iframe without 'allow-forms' can NOT submit forms
+ // (done in file_iframe_sandbox_c_if1.html which only has 'allow-scripts')
+
+ // fails if bad
+ // 14) test that a sandboxed iframe can't open a new window using the target.attribute
+ // this is done via file_iframe_sandbox_c_if4.html which is sandboxed with "allow-scripts" and "allow-same-origin"
+ // the window it attempts to open calls window.opener.ok(false, ...) and file_iframe_c_if4.html has an ok()
+ // function that calls window.parent.ok_wrapper
+
+ // passes if good
+ // 15) test that a sandboxed iframe can't open a new window using window.open
+ // this is done via file_iframe_sandbox_c_if4.html which is sandboxed with "allow-scripts" and "allow-same-origin"
+ // the window it attempts to open calls window.opener.ok(false, ...) and file_iframe_c_if4.html has an ok()
+ // function that calls window.parent.ok_wrapper
+
+ // passes if good
+ // 16) test that a sandboxed iframe can't open a new window using window.ShowModalDialog
+ // this is done via file_iframe_sandbox_c_if4.html which is sandboxed with "allow-scripts" and "allow-same-origin"
+ // the window it attempts to open calls window.opener.ok(false, ...) and file_iframe_c_if4.html has an ok()
+ // function that calls window.parent.ok_wrapper
+
+ // passes twice if good
+ // 17) test that a sandboxed iframe can access same-origin documents and run scripts when its sandbox attribute
+ // is separated with two spaces
+ // done via file_iframe_sandbox_c_if6.html which is sandboxed with " allow-scripts allow-same-origin "
+
+ // passes twice if good
+ // 18) test that a sandboxed iframe can access same-origin documents and run scripts when its sandbox attribute
+ // is separated with tabs
+ // done via file_iframe_sandbox_c_if6.html which is sandboxed with "&#x09;allow-scripts&#x09;allow-same-origin&#x09;"
+
+ // passes twice if good
+ // 19) test that a sandboxed iframe can access same-origin documents and run scripts when its sandbox attribute
+ // is separated with line feeds
+ // done via file_iframe_sandbox_c_if6.html which is sandboxed with "&#x0a;allow-scripts&#x0a;allow-same-origin&#x0a;"
+
+ // passes twice if good
+ // 20) test that a sandboxed iframe can access same-origin documents and run scripts when its sandbox attribute
+ // is separated with form feeds
+ // done via file_iframe_sandbox_c_if6.html which is sandboxed with "&#x0c;allow-scripts&#x0c;allow-same-origin&#x0c;"
+
+ // passes twice if good
+ // 21) test that a sandboxed iframe can access same-origin documents and run scripts when its sandbox attribute
+ // is separated with carriage returns
+ // done via file_iframe_sandbox_c_if6.html which is sandboxed with "&#x0d;allow-scripts&#x0d;allow-same-origin&#x0d;"
+
+ // fails if bad
+ // 22) test that an iframe with sandbox="" does NOT have script in a src attribute created by a javascript:
+ // URL executed
+ // done by this page, see if_7
+
+ // passes if good
+ // 23) test that an iframe with sandbox="allow-scripts" DOES have script in a src attribute created by a javascript:
+ // URL executed
+ // done by this page, see if_8
+
+ // fails if bad
+ // 24) test that an iframe with sandbox="", starting out with a document already loaded, does NOT have script in a newly
+ // set src attribute created by a javascript: URL executed
+ // done by this page, see if_9
+
+ // passes if good
+ // 25) test that an iframe with sandbox="allow-scripts", starting out with a document already loaded, DOES have script
+ // in a newly set src attribute created by a javascript: URL executed
+ // done by this page, see if_10
+
+ // passes if good or fails if bad
+ // 26) test that an sandboxed document without 'allow-same-origin' can NOT access indexedDB
+ // done via file_iframe_sandbox_c_if7.html, which has sandbox='allow-scripts'
+
+ // passes if good or fails if bad
+ // 27) test that an sandboxed document with 'allow-same-origin' can access indexedDB
+ // done via file_iframe_sandbox_c_if8.html, which has sandbox='allow-scripts allow-same-origin'
+
+ // fails if bad
+ // 28) Test that a sandboxed iframe can't open a new window using the target.attribute for a
+ // non-existing browsing context (BC341604).
+ // This is done via file_iframe_sandbox_c_if4.html which is sandboxed with "allow-scripts" and "allow-same-origin"
+ // the window it attempts to open calls window.opener.ok(false, ...) and file_iframe_c_if4.html has an ok()
+ // function that calls window.parent.ok_wrapper.
+
+ // passes twice if good
+ // 29-32) Test that sandboxFlagsAsString returns the set flags.
+ // see if_14 and if_15
+
+ // passes once if good
+ // 33) Test that sandboxFlagsAsString returns null if iframe does not have sandbox flag set.
+ // see if_16
+}
+
+addLoadEvent(doTest);
+
+var started_if_9 = false;
+var started_if_10 = false;
+
+function start_if_9() {
+ if (started_if_9)
+ return;
+
+ started_if_9 = true;
+ sendMouseEvent({type:'click'}, 'a_button');
+}
+
+function start_if_10() {
+ if (started_if_10)
+ return;
+
+ started_if_10 = true;
+ sendMouseEvent({type:'click'}, 'a_button2');
+}
+
+function do_if_9() {
+ var if_9 = document.getElementById('if_9');
+ if_9.src = 'javascript:"<html><script>window.parent.ok_wrapper(false, \'an iframe sandboxed without allow-scripts should not execute script in a javascript URL in a newly set src attribute\');<\/script><\/html>"';
+}
+
+function do_if_10() {
+ var if_10 = document.getElementById('if_10');
+ if_10.src = 'javascript:"<html><script>window.parent.ok_wrapper(true, \'an iframe sandboxed with allow-scripts should execute script in a javascript URL in a newly set src attribute\');<\/script><\/html>"';
+}
+
+function eqFlags(a, b) {
+ // both a and b should be either null or have the array same flags
+ if (a === null && b === null) { return true; }
+ if (a === null || b === null) { return false; }
+ if (a.length !== b.length) { return false; }
+ var a_sorted = a.sort();
+ var b_sorted = b.sort();
+ for (var i in a_sorted) {
+ if (a_sorted[i] !== b_sorted[i]) { return false; }
+ }
+ return true;
+}
+
+function getSandboxFlags(doc) {
+ var flags = doc.sandboxFlagsAsString;
+ if (flags === null) { return null; }
+ return flags? flags.split(" "):[];
+}
+
+function test_sandboxFlagsAsString(name, expected) {
+ var ifr = document.getElementById(name);
+ try {
+ var flags = getSandboxFlags(SpecialPowers.wrap(ifr).contentDocument);
+ ok_wrapper(eqFlags(flags, expected), name + ' expected: "' + expected + '", got: "' + flags + '"');
+ } catch (e) {
+ ok_wrapper(false, name + ' expected "' + expected + ', but failed with ' + e);
+ }
+}
+
+</script>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=341604">Mozilla Bug 341604</a> - Implement HTML5 sandbox attribute for IFRAMEs
+<p id="display"></p>
+<div id="content">
+<iframe sandbox="allow-same-origin allow-scripts" id="if_1" src="file_iframe_sandbox_c_if1.html" height="10" width="10"></iframe>
+<iframe sandbox="aLlOw-SAME-oRiGin ALLOW-sCrIpTs" id="if_1_case_insensitive" src="file_iframe_sandbox_c_if1.html" height="10" width="10"></iframe>
+<iframe sandbox="" id="if_2" src="file_iframe_sandbox_c_if2.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-forms allow-scripts" id="if_3" src="file_iframe_sandbox_c_if3.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-same-origin allow-scripts" id="if_4" src="file_iframe_sandbox_c_if4.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-same-origin" id="if_5" src="file_iframe_sandbox_c_if5.html" height="10" width="10"></iframe>
+<iframe sandbox=" allow-same-origin allow-scripts " id="if_6_a" src="file_iframe_sandbox_c_if6.html" height="10" width="10"></iframe>
+<iframe sandbox="&#x09;allow-same-origin&#x09;allow-scripts&#x09;" id="if_6_b" src="file_iframe_sandbox_c_if6.html" height="10" width="10"></iframe>
+<iframe sandbox="&#x0a;allow-same-origin&#x0a;allow-scripts&#x0a;" id="if_6_c" src="file_iframe_sandbox_c_if6.html" height="10" width="10"></iframe>
+<iframe sandbox="&#x0c;allow-same-origin&#x0c;allow-scripts&#x0c;" id="if_6_d" src="file_iframe_sandbox_c_if6.html" height="10" width="10"></iframe>
+<iframe sandbox="&#x0d;allow-same-origin&#x0d;allow-scripts&#x0d;" id="if_6_e" src="file_iframe_sandbox_c_if6.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-same-origin" id='if_7' src="javascript:'<html><script>window.parent.ok_wrapper(false, \'an iframe sandboxed without allow-scripts should not execute script in a javascript URL in its src attribute\');<\/script><\/html>';" height="10" width="10"></iframe>
+<iframe sandbox="allow-same-origin allow-scripts" id='if_8' src="javascript:'<html><script>window.parent.ok_wrapper(true, \'an iframe sandboxed without allow-scripts should execute script in a javascript URL in its src attribute\');<\/script><\/html>';" height="10" width="10"></iframe>
+<iframe sandbox="allow-same-origin" onload='start_if_9()' id='if_9' src="about:blank" height="10" width="10"></iframe>
+<iframe sandbox="allow-same-origin allow-scripts" onload='start_if_10()' id='if_10' src="about:blank" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts" id='if_11' src="file_iframe_sandbox_c_if7.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-same-origin allow-scripts" id='if_12' src="file_iframe_sandbox_c_if8.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-top-navigation " id='if_13' src="file_iframe_sandbox_c_if9.html" height="10" width="10" onload='test_sandboxFlagsAsString("if_13",["allow-forms", "allow-pointer-lock", "allow-popups", "allow-same-origin", "allow-scripts", "allow-top-navigation"])'></iframe>
+<iframe sandbox="&#x09;allow-same-origin&#x09;allow-scripts&#x09;" id="if_14" src="file_iframe_sandbox_c_if6.html" height="10" width="10" onload='test_sandboxFlagsAsString("if_14",["allow-same-origin","allow-scripts"])'></iframe>
+<iframe sandbox="" id="if_15" src="file_iframe_sandbox_c_if9.html" height="10" width="10" onload='test_sandboxFlagsAsString("if_15",[])'></iframe>
+<iframe id="if_16" src="file_iframe_sandbox_c_if9.html" height="10" width="10" onload='test_sandboxFlagsAsString("if_16",null)'></iframe>
+<input type='button' id="a_button" onclick='do_if_9()'>
+<input type='button' id="a_button2" onclick='do_if_10()'>
+</div>
+</body>
+</html>
diff --git a/dom/html/test/test_iframe_sandbox_inheritance.html b/dom/html/test/test_iframe_sandbox_inheritance.html
new file mode 100644
index 000000000..ddf45f70b
--- /dev/null
+++ b/dom/html/test/test_iframe_sandbox_inheritance.html
@@ -0,0 +1,203 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=341604
+Implement HTML5 sandbox attribute for IFRAMEs - inheritance tests
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script type="application/javascript">
+/** Test for Bug 341604 - Implement HTML5 sandbox attribute for IFRAMEs **/
+/** Inheritance Tests **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+// A postMessage handler that is used by sandboxed iframes without
+// 'allow-same-origin' to communicate pass/fail back to this main page.
+// It expects to be called with an object like {ok: true/false, desc:
+// <description of the test> which it then forwards to ok().
+window.addEventListener("message", receiveMessage, false);
+
+function receiveMessage(event) {
+ switch (event.data.type) {
+ case "attempted":
+ testAttempted();
+ break;
+ case "ok":
+ ok_wrapper(event.data.ok, event.data.desc, event.data.addToAttempted);
+ break;
+ default:
+ // allow for old style message
+ if (event.data.ok != undefined) {
+ ok_wrapper(event.data.ok, event.data.desc, event.data.addToAttempted);
+ }
+ }
+}
+
+var attemptedTests = 0;
+var passedTests = 0;
+var totalTestsToPass = 15;
+var totalTestsToAttempt = 19;
+
+function ok_wrapper(result, desc, addToAttempted = true) {
+ ok(result, desc);
+
+ if (result) {
+ passedTests++;
+ }
+
+ if (addToAttempted) {
+ testAttempted();
+ }
+}
+
+// Added so that tests that don't register unless they fail,
+// can at least notify that they've attempted to run.
+function testAttempted() {
+ attemptedTests++;
+ if (attemptedTests == totalTestsToAttempt) {
+ // Make sure all tests have had a chance to complete.
+ setTimeout(function() {finish();}, 1000);
+ }
+}
+
+var finishCalled = false;
+
+function finish() {
+ if (!finishCalled) {
+ finishCalled = true;
+ is(passedTests, totalTestsToPass, "There are " + totalTestsToPass + " inheritance tests that should pass");
+
+ SimpleTest.finish();
+ }
+}
+
+function doTest() {
+ // fails if bad
+ // 1) an iframe with no sandbox attribute inside an iframe that has sandbox = ""
+ // should not be able to execute scripts (cannot ever loosen permissions)
+ // (done by file_iframe_sandbox_a_if2.html contained within file_iframe_sandbox_a_if1.html)
+ testAttempted();
+
+ // fails if bad
+ // 2) an iframe with sandbox = "allow-scripts" inside an iframe that has sandbox = ""
+ // should not be able to execute scripts (cannot ever loosen permissions)
+ // (done by file_iframe_sandbox_a_if2.html contained within file_iframe_sandbox_a_if1.html)
+ testAttempted();
+
+ // passes if good and fails if bad
+ // 3) an iframe with no sandbox attribute inside an iframe that has sandbox = "allow-scripts"
+ // should not be same origin with the top window
+ // (done by file_iframe_sandbox_a_if4.html contained within file_iframe_sandbox_a_if3.html)
+
+ // passes if good and fails if bad
+ // 4) an iframe with no sandbox attribute inside an iframe that has sandbox = "allow-scripts"
+ // should not be same origin with its parent
+ // (done by file_iframe_sandbox_a_if4.html contained within file_iframe_sandbox_a_if3.html)
+
+ // passes if good
+ // 5) an iframe with 'allow-same-origin' and 'allow-scripts' inside an iframe with 'allow-same-origin'
+ // and 'allow-scripts' should be same origin with the top window
+ // (done by file_iframe_sandbox_a_if6.html contained within file_iframe_sandbox_a_if5.html)
+
+ // passes if good
+ // 6) an iframe with 'allow-same-origin' and 'allow-scripts' inside an iframe with 'allow-same-origin'
+ // and 'allow-scripts' should be same origin with its parent
+ // (done by file_iframe_sandbox_a_if6.html contained within file_iframe_sandbox_a_if5.html)
+
+ // passes if good
+ // 7) an iframe with no sandbox attribute inside an iframe that has sandbox = "allow-scripts"
+ // should be able to execute scripts
+ // (done by file_iframe_sandbox_a_if7.html contained within file_iframe_sandbox_a_if3.html)
+
+ // fails if bad
+ // 8) an iframe with sandbox="" inside an iframe that has allow-scripts should not be able
+ // to execute scripts
+ // (done by file_iframe_sandbox_a_if2.html contained within file_iframe_sandbox_a_if3.html)
+ testAttempted();
+
+ // passes if good
+ // 9) make sure that changing the sandbox flags on an iframe (if_8) doesn't affect
+ // the sandboxing of subloads of content within that iframe
+ var if_8 = document.getElementById('if_8');
+ if_8.sandbox = 'allow-scripts';
+ if_8.contentWindow.doSubload();
+
+ // passes if good
+ // 10) a <frame> inside an <iframe> sandboxed with 'allow-scripts' should not be same
+ // origin with this document
+ // done by file_iframe_sandbox_a_if11.html which is contained with file_iframe_sandbox_a_if10.html
+
+ // passes if good
+ // 11) a <frame> inside a <frame> inside an <iframe> sandboxed with 'allow-scripts' should not be same
+ // origin with its parent frame or this document
+ // done by file_iframe_sandbox_a_if12.html which is contained with file_iframe_sandbox_a_if11.html
+
+ // passes if good, fails if bad
+ // 12) An <object> inside an <iframe> sandboxed with 'allow-scripts' should not be same
+ // origin with this document
+ // Done by file_iframe_sandbox_a_if14.html which is contained within file_iframe_sandbox_a_if13.html
+
+ // passes if good, fails if bad
+ // 13) An <object> inside an <object> inside an <iframe> sandboxed with 'allow-scripts' should not be same
+ // origin with its parent frame or this document
+ // Done by file_iframe_sandbox_a_if15.html which is contained within file_iframe_sandbox_a_if14.html
+
+ // passes if good, fails if bad
+ // 14) An <object> inside a <frame> inside an <iframe> sandboxed with 'allow-scripts' should not be same
+ // origin with its parent frame or this document
+ // Done by file_iframe_sandbox_a_if15.html which is contained within file_iframe_sandbox_a_if16.html
+ // which is contained within file_iframe_sandbox_a_if10.html
+
+ // passes if good
+ // 15) An <object> inside an <object> inside an <iframe> sandboxed with 'allow-scripts allow-forms'
+ // should be able to submit forms.
+ // Done by file_iframe_sandbox_a_if15.html which is contained within file_iframe_sandbox_a_if14.html
+
+ // passes if good
+ // 16) An <object> inside a <frame> inside an <iframe> sandboxed with 'allow-scripts allow-forms'
+ // should be able to submit forms.
+ // Done by file_iframe_sandbox_a_if15.html which is contained within file_iframe_sandbox_a_if16.html
+ // which is contained within file_iframe_sandbox_a_if10.html
+
+ // fails if bad
+ // 17) An <object> inside an <iframe> sandboxed with 'allow-same-origin'
+ // should not be able to run scripts.
+ // Done by iframe "if_no_scripts" using a data: load.
+ testAttempted();
+
+ // passes if good
+ // 18) An <object> inside an <iframe> sandboxed with 'allow-scripts allow-same-origin'
+ // should be able to run scripts and be same origin with this document.
+ // Done by iframe "if_scripts" using a data: load.
+
+ // passes if good, fails if bad
+ // 19) Make sure that the parent's document's sandboxing flags are copied when
+ // changing the sandbox flags on an iframe inside an iframe.
+ // Done in file_iframe_sandbox_a_if17.html and file_iframe_sandbox_a_if18.html
+}
+
+addLoadEvent(doTest);
+</script>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=341604">Mozilla Bug 341604</a> - Implement HTML5 sandbox attribute for IFRAMEs
+<p id="display"></p>
+<div id="content">
+<iframe sandbox="" id="if_1" src="file_iframe_sandbox_a_if1.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts" id="if_3" src="file_iframe_sandbox_a_if3.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts allow-same-origin" id="if_5" src="file_iframe_sandbox_a_if5.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts allow-same-origin" id="if_8" src="file_iframe_sandbox_a_if8.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts allow-forms" id="if_10" src="file_iframe_sandbox_a_if10.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts allow-forms" id="if_13" src="file_iframe_sandbox_a_if13.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-same-origin" id="if_no_scripts" src="data:text/html,<object%20data='data:text/html,<script>parent.parent.ok_wrapper(false, &quot;an object inside an iframe sandboxed with only allow-same-origin should not be able to run scripts&quot;)</script>'></object>" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts allow-same-origin" id="if_scripts" src="data:text/html,<object%20data='data:text/html,<script>parent.parent.ok_wrapper(true, &quot;an object inside an iframe sandboxed with allow-scripts allow-same-origin should be able to run scripts and call functions in the parent of the iframe&quot;)</script>'></object>" height="10" width="10"></iframe>
+<iframe sandbox="allow-same-origin" id="if_19" src="data:text/html,<iframe%20data='data:text/html,<script>parent.parent.ok_wrapper(true, &quot;an object inside an iframe sandboxed with allow-scripts allow-same-origin should be able to run scripts and call functions in the parent of the iframe&quot;)</script>'></object>" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts" id="if_17" src="file_iframe_sandbox_a_if17.html" height="10" width="10"></iframe>
+</div>
+</body>
+</html>
diff --git a/dom/html/test/test_iframe_sandbox_modal.html b/dom/html/test/test_iframe_sandbox_modal.html
new file mode 100644
index 000000000..1307ea9a5
--- /dev/null
+++ b/dom/html/test/test_iframe_sandbox_modal.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=766282
+implement allow-popups directive for iframe sandbox
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 766282</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script>
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+// A postMessage handler that is used by sandboxed iframes without
+// 'allow-same-origin' to communicate pass/fail back to this main page.
+window.addEventListener("message", receiveMessage, false);
+
+function receiveMessage(event) {
+ switch (event.data.type) {
+ case "attempted":
+ testAttempted();
+ break;
+ case "ok":
+ ok_wrapper(event.data.ok, event.data.desc, event.data.addToAttempted);
+ break;
+ default:
+ // allow for old style message
+ if (event.data.ok != undefined) {
+ ok_wrapper(event.data.ok, event.data.desc, event.data.addToAttempted);
+ }
+ }
+}
+
+var attemptedTests = 0;
+var passedTests = 0;
+var totalTestsToPass = 5;
+var totalTestsToAttempt = 5;
+
+function ok_wrapper(result, desc, addToAttempted = true) {
+ ok(result, desc);
+
+ if (result) {
+ passedTests++;
+ }
+
+ if (addToAttempted) {
+ testAttempted();
+ }
+}
+
+// Added so that tests that don't register unless they fail,
+// can at least notify that they've attempted to run.
+function testAttempted() {
+ attemptedTests++;
+ if (attemptedTests == totalTestsToAttempt) {
+ // Make sure all tests have had a chance to complete.
+ setTimeout(function() {finish();}, 1000);
+ }
+}
+
+var finishCalled = false;
+
+function finish() {
+ if (!finishCalled) {
+ finishCalled = true;
+ is(passedTests, totalTestsToPass, "There are " + totalTestsToPass + " modal tests that should pass");
+
+ SimpleTest.finish();
+ }
+}
+
+function doTest() {
+ // passes if good and fails if bad
+ // 1) A window opened from inside an iframe that has sandbox = "allow-scripts allow-popups
+ // allow-same-origin" should not have its origin sandbox flag set and be able to access
+ // document.cookie. (Done by file_iframe_sandbox_k_if5.html opened from
+ // file_iframe_sandbox_j_if1.html) using showModalDialog.)
+
+ // passes if good
+ // 2) A window opened from inside an iframe that has sandbox = "allow-scripts allow-popups
+ // allow-top-navigation" should not have its top-level navigation sandbox flag set and be able to
+ // navigate top. (Done by file_iframe_sandbox_k_if5.html (and if6) opened from
+ // file_iframe_sandbox_j_if1.html) using showModalDialog.)
+
+ // passes if good
+ // 3) A window opened from inside an iframe that has sandbox = "allow-scripts allow-popups
+ // all-forms" should not have its forms sandbox flag set and be able to submit forms.
+ // (Done by file_iframe_sandbox_k_if7.html opened from
+ // file_iframe_sandbox_j_if1.html) using showModalDialog.)
+
+ // passes if good
+ // 4) Make sure that the sandbox flags copied to a new browsing context are taken from the
+ // current active document not the browsing context (iframe / docShell).
+ // This is done by removing allow-same-origin and calling doSubOpens from file_iframe_sandbox_j_if2.html,
+ // which opens file_iframe_sandbox_k_if9.html using showModalDialog.
+ var if_2 = document.getElementById('if_2');
+ if_2.sandbox = 'allow-scripts allow-popups';
+ if_2.contentWindow.doSubOpens();
+
+ // passes if good
+ // 5) Test that a sandboxed iframe with "allow-popups" can open a new window using window.ShowModalDialog.
+ // This is done via file_iframe_sandbox_j_if3.html which is sandboxed with "allow-popups allow-scripts
+ // allow-same-origin". The window it attempts to open calls window.opener.ok(true, ...) and
+ // file_iframe_j_if3.html has an ok() function that calls window.parent.ok_wrapper.
+}
+
+addLoadEvent(doTest);
+</script>
+
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=766282">Mozilla Bug 766282</a> - implement allow-popups directive for iframe sandbox
+<p id="display"></p>
+<div id="content">
+<iframe sandbox="allow-scripts allow-popups allow-modals allow-same-origin allow-forms allow-top-navigation" id="if_1" src="file_iframe_sandbox_j_if1.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts allow-popups allow-modals allow-same-origin" id="if_2" src="file_iframe_sandbox_j_if2.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-popups allow-modals allow-same-origin allow-scripts" id="if_3" src="file_iframe_sandbox_j_if3.html" height="10" width="10"></iframe>
+</div>
diff --git a/dom/html/test/test_iframe_sandbox_navigation.html b/dom/html/test/test_iframe_sandbox_navigation.html
new file mode 100644
index 000000000..b60f715f3
--- /dev/null
+++ b/dom/html/test/test_iframe_sandbox_navigation.html
@@ -0,0 +1,281 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=341604
+Implement HTML5 sandbox attribute for IFRAMEs
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604 - navigation</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>
+<script type="application/javascript">
+/** Test for Bug 341604 - Implement HTML5 sandbox attribute for IFRAMEs **/
+/** Navigation tests Part 1**/
+
+SimpleTest.requestLongerTimeout(2); // slow on Android
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+// a postMessage handler that is used by sandboxed iframes without
+// 'allow-same-origin'/other windows to communicate pass/fail back to this main page.
+// it expects to be called with an object like {ok: true/false, desc:
+// <description of the test> which it then forwards to ok()
+window.addEventListener("message", receiveMessage, false);
+
+var testPassesReceived = 0;
+
+function receiveMessage(event) {
+ switch (event.data.type) {
+ case "attempted":
+ testAttempted();
+ break;
+ case "ok":
+ ok_wrapper(event.data.ok, event.data.desc, event.data.addToAttempted);
+ break;
+ case "if_10":
+ doIf10TestPart2();
+ break;
+ default:
+ // allow for old style message
+ if (event.data.ok != undefined) {
+ ok_wrapper(event.data.ok, event.data.desc, event.data.addToAttempted);
+ }
+ }
+}
+
+// Open windows for tests to attempt to navigate later.
+var windowsToClose = new Array();
+
+var attemptedTests = 0;
+var passedTests = 0;
+var totalTestsToPass = 7;
+var totalTestsToAttempt = 13;
+
+function ok_wrapper(result, desc, addToAttempted = true) {
+ ok(result, desc);
+
+ if (result) {
+ passedTests++;
+ }
+
+ if (addToAttempted) {
+ testAttempted();
+ }
+}
+
+// Added so that tests that don't register unless they fail,
+// can at least notify that they've attempted to run.
+function testAttempted() {
+ attemptedTests++;
+ if (attemptedTests == totalTestsToAttempt) {
+ // Make sure all tests have had a chance to complete.
+ setTimeout(function() {finish();}, 1000);
+ }
+}
+
+var finishCalled = false;
+
+function finish() {
+ if (!finishCalled) {
+ finishCalled = true;
+ is(passedTests, totalTestsToPass, "There are " + totalTestsToPass + " navigation tests that should pass");
+
+ closeWindows();
+
+ SimpleTest.finish();
+ }
+}
+
+function checkTestsFinished() {
+ // If our own finish() has not been called, probably failed due to a timeout, so close remaining windows.
+ if (!finishCalled) {
+ closeWindows();
+ }
+}
+
+function closeWindows() {
+ for (var i = 0; i < windowsToClose.length; i++) {
+ windowsToClose[i].close();
+ }
+}
+
+function doTest() {
+ // passes if good
+ // 1) A sandboxed iframe is allowed to navigate itself
+ // (done by file_iframe_sandbox_d_if1.html which has 'allow-scripts' and navigates to
+ // file_iframe_sandbox_navigation_pass.html).
+
+ // passes if good
+ // 2) A sandboxed iframe is allowed to navigate its children, even if they are sandboxed
+ // (done by file_iframe_sandbox_d_if2.html which has 'allow-scripts', it navigates a child
+ // iframe containing file_iframe_sandbox_navigation_start.html to file_iframe_sandbox_navigation_pass.html).
+
+ // fails if bad
+ // 3) A sandboxed iframe is not allowed to navigate its ancestor
+ // (done by file_iframe_sandbox_d_if4.html contained within file_iframe_sandbox_d_if3.html,
+ // it attempts to navigate file_iframe_sandbox_d_if3.html to file_iframe_sandbox_navigation_fail.html).
+
+ // fails if bad
+ // 4) A sandboxed iframe is not allowed to navigate its sibling
+ // (done by file_iframe_sandbox_d_if5.html which has 'allow scripts allow-same-origin'
+ // and attempts to navigate file_iframe_navigation_start.html contained in if_sibling on this
+ // page to file_iframe_sandbox_navigation_fail.html).
+
+ // passes if good, fails if bad
+ // 5) When a link is clicked in a sandboxed iframe, the document navigated to is sandboxed
+ // the same as the original document and is not same origin with parent document
+ // (done by file_iframe_sandbox_d_if6.html which simulates a link click and navigates
+ // to file_iframe_sandbox_d_if7.html which attempts to call back into its parent).
+
+ // fails if bad
+ // 6) An iframe (if_8) has sandbox="allow-same-origin allow-scripts", the sandboxed document
+ // (file_iframe_sandbox_d_if_8.html) that it contains accesses its parent (this file) and removes
+ // 'allow-same-origin' and then triggers a reload.
+ // The document should not be able to access its parent (this file).
+
+ // fails if bad
+ // 7) An iframe (if_9) has sandbox="allow-same-origin allow-scripts", the sandboxed document
+ // (file_iframe_sandbox_d_if_9.html) that it contains accesses its parent (this file) and removes
+ // 'allow-scripts' and then triggers a reload.
+ // The document should not be able to run a script and access its parent (this file).
+
+ // passes if good
+ // 8) a document in an iframe with sandbox='allow-scripts' should have a different null
+ // principal in its original document than a document to which it navigates itself
+ // file_iframe_sandbox_d_if_10.html does this, co-ordinating with this page via postMessage
+
+ // passes if good
+ // 9) a document (file_iframe_sandbox_d_if11.html in an iframe (if_11) with sandbox='allow-scripts'
+ // is navigated to file_iframe_sandbox_d_if12.html - when that document loads
+ // a message is sent back to this document, which adds 'allow-same-origin' to if_11 and then
+ // calls .back on it - file_iframe_sandbox_if12.html should be able to call back into this
+ // document - this is all contained in file_iframe_sandbox_d_if13.html which is opened in another
+ // tab so it has its own isolated session history
+ window.open("file_iframe_sandbox_d_if13.html");
+
+ // open up the top navigation tests
+
+ // fails if bad
+ // 10) iframe with sandbox='allow-scripts' can NOT navigate top
+ // file_iframe_sandbox_e_if1.html contains file_iframe_sandbox_e_if6.html which
+ // attempts to navigate top
+ windowsToClose.push(window.open("file_iframe_sandbox_e_if1.html"));
+
+ // fails if bad
+ // 11) iframe with sandbox='allow-scripts' nested inside iframe with
+ // 'allow-top-navigation allow-scripts' can NOT navigate top
+ // file_iframe_sandbox_e_if2.html contains file_iframe_sandbox_e_if1.html which
+ // contains file_iframe_sandbox_e_if6.html which attempts to navigate top
+ windowsToClose.push(window.open("file_iframe_sandbox_e_if2.html"));
+
+ // passes if good
+ // 12) iframe with sandbox='allow-top-navigation allow-scripts' can navigate top
+ // file_iframe_sandbox_e_if3.html contains file_iframe_sandbox_e_if5.html which navigates top
+ window.open("file_iframe_sandbox_e_if3.html");
+
+ // passes if good
+ // 13) iframe with sandbox='allow-top-navigation allow-scripts' nested inside an iframe with
+ // 'allow-top-navigation allow-scripts' can navigate top
+ // file_iframe_sandbox_e_if4.html contains file_iframe_sandbox_e_if3.html which contains
+ // file_iframe_sandbox_e_if5.html which navigates top
+ window.open("file_iframe_sandbox_e_if4.html");
+}
+
+addLoadEvent(doTest);
+
+window.modified_if_8 = false;
+
+function reload_if_8() {
+ var if_8 = document.getElementById('if_8');
+ if_8.src = 'file_iframe_sandbox_d_if8.html';
+}
+
+function modify_if_8() {
+ // If this is the second time this has been called
+ // that's a failed test (allow-same-origin was removed
+ // the first time).
+ if (window.modified_if_8) {
+ ok_wrapper(false, "a sandboxed iframe from which 'allow-same-origin' was removed should not be able to access its parent");
+
+ // need to return here since we end up in an infinite loop otherwise
+ return;
+ }
+
+ var if_8 = document.getElementById('if_8');
+ window.modified_if_8 = true;
+
+ if_8.sandbox = 'allow-scripts';
+ testAttempted();
+ sendMouseEvent({type:'click'}, 'a_button');
+}
+
+window.modified_if_9 = false;
+
+function reload_if_9() {
+ var if_9 = document.getElementById('if_9');
+ if_9.src = 'file_iframe_sandbox_d_if9.html';
+}
+
+function modify_if_9() {
+ // If this is the second time this has been called
+ // that's a failed test (allow-scripts was removed
+ // the first time).
+ if (window.modified_if_9) {
+ ok_wrapper(false, "an sandboxed iframe from which 'allow-scripts' should be removed should not be able to access its parent via a script", false);
+
+ // need to return here since we end up in an infinite loop otherwise
+ return;
+ }
+
+ var if_9 = document.getElementById('if_9');
+ window.modified_if_9 = true;
+
+ if_9.sandbox = 'allow-same-origin';
+
+ testAttempted();
+ sendMouseEvent({type:'click'}, 'a_button2');
+}
+
+var firstPrincipal = "";
+var secondPrincipal;
+
+function doIf10TestPart1() {
+ if (firstPrincipal != "")
+ return;
+
+ // use SpecialPowers to get the principal of if_10.
+ // NB: We stringify here and below because special-powers wrapping doesn't
+ // preserve identity.
+ var if_10 = document.getElementById('if_10');
+ firstPrincipal = SpecialPowers.wrap(if_10).contentDocument.nodePrincipal.origin;
+ if_10.src = 'file_iframe_sandbox_d_if10.html';
+}
+
+function doIf10TestPart2() {
+ var if_10 = document.getElementById('if_10');
+ // use SpecialPowers to get the principal of if_10
+ secondPrincipal = SpecialPowers.wrap(if_10).contentDocument.nodePrincipal.origin;
+ ok_wrapper(firstPrincipal != secondPrincipal, "documents should NOT have the same principal if they are sandboxed without" +
+ " allow-same-origin and the first document is navigated to the second");
+}
+</script>
+<body onunload="checkTestsFinished()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=341604">Mozilla Bug 341604</a> - Implement HTML5 sandbox attribute for IFRAMEs
+<p id="display"></p>
+<div id="content">
+<iframe sandbox="allow-scripts" id="if_1" src="file_iframe_sandbox_d_if1.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts" id="if_2" src="file_iframe_sandbox_d_if2.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts" id="if_3" src="file_iframe_sandbox_d_if3.html" height="10" width="10"></iframe>
+<iframe id="if_sibling" name="if_sibling" src="about:blank" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts allow-same-origin" id="if_5" src="file_iframe_sandbox_d_if5.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts" id="if_6" src="file_iframe_sandbox_d_if6.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-same-origin allow-scripts" id="if_8" src="file_iframe_sandbox_d_if8.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-same-origin allow-scripts" id="if_9" src="file_iframe_sandbox_d_if9.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts" id="if_10" src="file_iframe_sandbox_navigation_start.html" onload='doIf10TestPart1()' height="10" width="10"></iframe>
+</div>
+<input type='button' id="a_button" onclick='reload_if_8()'>
+<input type='button' id="a_button2" onclick='reload_if_9()'>
+</body>
+</html>
diff --git a/dom/html/test/test_iframe_sandbox_navigation2.html b/dom/html/test/test_iframe_sandbox_navigation2.html
new file mode 100644
index 000000000..7982d913f
--- /dev/null
+++ b/dom/html/test/test_iframe_sandbox_navigation2.html
@@ -0,0 +1,212 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=341604
+Implement HTML5 sandbox attribute for IFRAMEs
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604 - navigation</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>
+<script type="application/javascript">
+/** Test for Bug 341604 - Implement HTML5 sandbox attribute for IFRAMEs **/
+/** Navigation tests Part 2**/
+
+SimpleTest.expectAssertions(0);
+SimpleTest.requestLongerTimeout(2); // slow on Android
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+// a postMessage handler that is used by sandboxed iframes without
+// 'allow-same-origin'/other windows to communicate pass/fail back to this main page.
+// it expects to be called with an object like {ok: true/false, desc:
+// <description of the test> which it then forwards to ok()
+window.addEventListener("message", receiveMessage, false);
+
+var testPassesReceived = 0;
+
+function receiveMessage(event) {
+ switch (event.data.type) {
+ case "attempted":
+ testAttempted();
+ break;
+ case "ok":
+ ok_wrapper(event.data.ok, event.data.desc, event.data.addToAttempted);
+ break;
+ default:
+ // allow for old style message
+ if (event.data.ok != undefined) {
+ ok_wrapper(event.data.ok, event.data.desc, event.data.addToAttempted);
+ }
+ }
+}
+
+// Open windows for tests to attempt to navigate later.
+var windowsToClose = new Array();
+windowsToClose.push(window.open("about:blank", "window_to_navigate"));
+windowsToClose.push(window.open("about:blank", "window_to_navigate2"));
+var iframesWithWindowsToClose = new Array();
+
+var attemptedTests = 0;
+var passedTests = 0;
+var totalTestsToPass = 12;
+var totalTestsToAttempt = 15;
+
+function ok_wrapper(result, desc, addToAttempted = true) {
+ ok(result, desc);
+
+ if (result) {
+ passedTests++;
+ }
+
+ if (addToAttempted) {
+ testAttempted();
+ }
+}
+
+// Added so that tests that don't register unless they fail,
+// can at least notify that they've attempted to run.
+function testAttempted() {
+ attemptedTests++;
+ if (attemptedTests == totalTestsToAttempt) {
+ // Make sure all tests have had a chance to complete.
+ setTimeout(function() {finish();}, 1000);
+ }
+}
+
+var finishCalled = false;
+
+function finish() {
+ if (!finishCalled) {
+ finishCalled = true;
+ is(passedTests, totalTestsToPass, "There are " + totalTestsToPass + " navigation tests that should pass");
+
+ for (var i = 0; i < windowsToClose.length; i++) {
+ windowsToClose[i].close();
+ }
+
+ SimpleTest.finish();
+ }
+}
+
+function checkTestsFinished() {
+ // If our own finish() has not been called, probably failed due to a timeout, so close remaining windows.
+ if (!finishCalled) {
+ for (var i = 0; i < windowsToClose.length; i++) {
+ windowsToClose[i].close();
+ }
+ }
+}
+
+function doTest() {
+ // fails if bad
+ // 14) iframe with sandbox='allow-same-origin allow-scripts allow-top-navigation' should not
+ // be able to navigate another window (opened by another browsing context) using its name.
+ // file_iframe_sandbox_d_if14.html in if_14 attempts to navigate "window_to_navigate",
+ // which has been opened in preparation.
+
+ // fails if bad
+ // 15) iframe with sandbox='allow-scripts' should not be able to navigate top using its
+ // real name (instead of _top) as allow-top-navigation is not specified.
+ // file_iframe_sandbox_e_if7.html contains file_iframe_sandbox_e_if8.html, which
+ // attempts to navigate top by name.
+ windowsToClose.push(window.open("file_iframe_sandbox_e_if7.html"));
+
+ // fails if bad
+ // 16) iframe with sandbox='allow-same-origin allow-scripts allow-top-navigation' should not
+ // be able to use its parent's name (instead of _parent) to navigate it, when it is not top.
+ // (Note: this would apply to other ancestors that are not top as well.)
+ // file_iframe_sandbox_d_if15.html in if_15 contains file_iframe_sandbox_d_if16.html, which
+ // tries to navigate if_15 by its name (if_parent).
+
+ // passes if good, fails if bad
+ // 17) A sandboxed iframe is allowed to navigate itself using window.open().
+ // (Done by file_iframe_sandbox_d_if17.html which has 'allow-scripts' and navigates to
+ // file_iframe_sandbox_navigation_pass.html).
+
+ // passes if good, fails if bad
+ // 18) A sandboxed iframe is allowed to navigate its children with window.open(), even if
+ // they are sandboxed. (Done by file_iframe_sandbox_d_if18.html which has 'allow-scripts',
+ // it navigates a child iframe to file_iframe_sandbox_navigation_pass.html).
+
+ // passes if good, fails if bad
+ // 19) A sandboxed iframe is not allowed to navigate its ancestor with window.open().
+ // (Done by file_iframe_sandbox_d_if20.html contained within file_iframe_sandbox_d_if19.html,
+ // it attempts to navigate file_iframe_sandbox_d_if19.html to file_iframe_sandbox_navigation_fail.html).
+
+ // passes if good, fails if bad
+ // 20) iframe with sandbox='allow-same-origin allow-scripts allow-top-navigation' should not
+ // be able to navigate another window (opened by another browsing context) using window.open(..., "<name>").
+ // file_iframe_sandbox_d_if14.html in if_14 attempts to navigate "window_to_navigate2",
+ // which has been opened in preparation, using window.open(..., "window_to_navigate2").
+
+ // passes if good, fails if bad
+ // 21) iframe with sandbox='allow-same-origin allow-scripts allow-top-navigation' should not
+ // be able to use its parent's name (not _parent) to navigate it using window.open(), when it is not top.
+ // (Note: this would apply to other ancestors that are not top as well.)
+ // file_iframe_sandbox_d_if21.html in if_21 contains file_iframe_sandbox_d_if22.html, which
+ // tries to navigate if_21 by its name (if_parent2).
+
+ // passes if good, fails if bad
+ // 22) iframe with sandbox='allow-top-navigation allow-scripts' can navigate top with window.open().
+ // file_iframe_sandbox_e_if9.html contains file_iframe_sandbox_e_if11.html which navigates top.
+ window.open("file_iframe_sandbox_e_if9.html");
+
+ // passes if good, fails if bad
+ // 23) iframe with sandbox='allow-top-navigation allow-scripts' nested inside an iframe with
+ // 'allow-top-navigation allow-scripts' can navigate top, with window.open().
+ // file_iframe_sandbox_e_if10.html contains file_iframe_sandbox_e_if9.html which contains
+ // file_iframe_sandbox_e_if11.html which navigates top.
+ window.open("file_iframe_sandbox_e_if10.html");
+
+ // passes if good, fails if bad
+ // 24) iframe with sandbox='allow-scripts' can NOT navigate top with window.open().
+ // file_iframe_sandbox_e_if12.html contains file_iframe_sandbox_e_if14.html which navigates top.
+ window.open("file_iframe_sandbox_e_if12.html");
+
+ // passes if good, fails if bad
+ // 25) iframe with sandbox='allow-scripts' nested inside an iframe with
+ // 'allow-top-navigation allow-scripts' can NOT navigate top, with window.open(..., "_top").
+ // file_iframe_sandbox_e_if13.html contains file_iframe_sandbox_e_if12.html which contains
+ // file_iframe_sandbox_e_if14.html which navigates top.
+ window.open("file_iframe_sandbox_e_if13.html");
+
+ // passes if good, fails if bad
+ // 26) iframe with sandbox='allow-scripts' should not be able to navigate top using its real name
+ // (not with _top e.g. window.open(..., "topname")) as allow-top-navigation is not specified.
+ // file_iframe_sandbox_e_if15.html contains file_iframe_sandbox_e_if16.html, which
+ // attempts to navigate top by name using window.open().
+ window.open("file_iframe_sandbox_e_if15.html");
+
+ // passes if good
+ // 27) iframe with sandbox='allow-scripts allow-popups' should be able to
+ // navigate a window, that it has opened, using it's name.
+ // file_iframe_sandbox_d_if23.html in if_23 opens a window and then attempts
+ // to navigate it using it's name in the target of an anchor.
+ iframesWithWindowsToClose.push("if_23");
+
+ // passes if good, fails if bad
+ // 28) iframe with sandbox='allow-scripts allow-popups' should be able to
+ // navigate a window, that it has opened, using window.open(..., "<name>").
+ // file_iframe_sandbox_d_if23.html in if_23 opens a window and then attempts
+ // to navigate it using it's name in the target of window.open().
+}
+
+addLoadEvent(doTest);
+</script>
+<body onunload="checkTestsFinished()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=341604">Mozilla Bug 341604</a> - Implement HTML5 sandbox attribute for IFRAMEs
+<p id="display"></p>
+<div id="content">
+<iframe sandbox="allow-same-origin allow-scripts allow-top-navigation" id="if_14" src="file_iframe_sandbox_d_if14.html" height="10" width="10"></iframe>
+<iframe id="if_15" name="if_parent" src="file_iframe_sandbox_d_if15.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts" id="if_17" src="file_iframe_sandbox_d_if17.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts" id="if_18" src="file_iframe_sandbox_d_if18.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts" id="if_19" src="file_iframe_sandbox_d_if19.html" height="10" width="10"></iframe>
+<iframe id="if_21" name="if_parent2" src="file_iframe_sandbox_d_if21.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts allow-popups" id="if_23" src="file_iframe_sandbox_d_if23.html" height="10" width="10"></iframe>
+</div>
+</body>
+</html>
diff --git a/dom/html/test/test_iframe_sandbox_plugins.html b/dom/html/test/test_iframe_sandbox_plugins.html
new file mode 100644
index 000000000..a8c2bd21b
--- /dev/null
+++ b/dom/html/test/test_iframe_sandbox_plugins.html
@@ -0,0 +1,141 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=341604
+Implement HTML5 sandbox attribute for IFRAMEs
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604 - plugins</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="plugin-utils.js"></script>
+ <script type="application/javascript">
+ setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script type="application/javascript">
+/** Test for Bug 341604 - Implement HTML5 sandbox attribute for IFRAMEs **/
+/** Plugin tests **/
+SimpleTest.waitForExplicitFinish();
+
+function doTest() {
+ // 1) test that a plugin can't be loaded with <embed> inside a sandboxed <iframe>
+ // (done by file_iframe_sandbox_f_if1.html loaded in if1 below)
+ // 2) test that a plugin can't be loaded with <object> inside a sandboxed <iframe>
+ // (done by file_iframe_sandbox_f_if1.html loaded in if1 below)
+ // 3) test that plugin can't be loaded by a sandboxed <iframe> with src pointing to
+ // a document that is handled by a plugin (done by if_2 below)
+ // 4) test that when a plugin is loaded in an unsandboxed iframe, a sandbox attribute
+ // is then added to the iframe and the document containing the plugin is reloaded,
+ // the plugin does not load in the sandboxed iframe (done with if_3 below)
+ // 5) test that when when a sandboxed iframe's sandbox attribute is removed,
+ // and a new document is loaded into the iframe, the plugin loads
+ // (done with if_4 below)
+
+ // these are all handled by checking how many instances of the test plugin are loaded
+ // when this script runs as the onload handler - there should be two instances,
+ // initially the one loaded directly by this page itself, and the one loaded during
+ // test #4 above.
+ var p = document.getElementById('plugin1');
+ var if_1 = document.getElementById('if_1');
+ p.startWatchingInstanceCount();
+
+ if_1.src = 'file_iframe_sandbox_f_if1.html';
+}
+
+function if_1_load() {
+ var if_1 = document.getElementById('if_1');
+
+ if (if_1.src == "about:blank")
+ return;
+
+ // need to wait for plugin to load, if the test fails...
+ SimpleTest.executeSoon(if_1_continue);
+}
+
+function if_1_continue() {
+ // instance count should be 0 (tests #1 and #2 above)
+ var p = document.getElementById('plugin1');
+ is(p.getInstanceCount(), 0, "plugins should not be loaded via <object> or <embed> by a sandboxed iframe");
+
+ var if_2 = document.getElementById('if_2');
+ if_2.src = 'file_iframe_sandbox_f_if2.html';
+
+ SimpleTest.executeSoon(if_2_continue);
+}
+
+function if_2_continue() {
+ // instance count should be 0 (test #3 above)
+ var p = document.getElementById('plugin1');
+
+ is(p.getInstanceCount(), 0, "plugins should not be loaded via a document of a type that requires a plugin embedded in a sandboxed iframe");
+
+ SimpleTest.executeSoon(if_3_test);
+}
+
+function if_3_test() {
+ var if_3 = document.getElementById('if_3');
+ // add sandbox attribute
+ if_3.sandbox = '';
+ if_3.src = 'file_iframe_sandbox_f_if1.html';
+}
+
+function if_3_load() {
+ if (if_3.src == "about:blank")
+ return;
+
+ SimpleTest.executeSoon(if_3_continue);
+}
+
+function if_3_continue() {
+ var p = document.getElementById('plugin1');
+ is(p.getInstanceCount(), 0, "plugins should not be loaded when a sandbox attribute is added" +
+ "to an iframe and a document containing a plugin is then loaded into the iframe");
+
+ SimpleTest.executeSoon(if_4_test);
+}
+
+function if_4_test() {
+ var if_4 = document.getElementById('if_4');
+ // remove sandbox attribute
+ if_4.removeAttribute('sandbox');
+ if_4.src = 'file_iframe_sandbox_f_if1.html';
+}
+
+function if_4_load() {
+ if (if_4.src == "about:blank")
+ return;
+
+ SimpleTest.executeSoon(if_4_continue);
+}
+
+function if_4_continue() {
+ var p = document.getElementById('plugin1');
+ // there are 2 plugin instances in file_iframe_sandbox_if1.html loaded by
+ // if_1, they should successfully load.
+ is(p.getInstanceCount(), 2, "plugins should be loaded when a sandbox attribute is removed " +
+ "from an iframe and a document containing a plugin is then loaded into the iframe");
+
+ p.stopWatchingInstanceCount();
+ SimpleTest.executeSoon(finish_test);
+}
+
+function finish_test() {
+ SimpleTest.finish();
+}
+
+addLoadEvent(doTest);
+</script>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=341604">Mozilla Bug 341604</a> - Implement HTML5 sandbox attribute for IFRAMEs
+<p id="display"></p>
+<div id="content">
+<embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
+<iframe id="if_1" sandbox='allow-same-origin' onLoad='if_1_load()' src="about:blank" height="400" width="400"></iframe>
+<iframe id="if_2" sandbox='allow-same-origin' src="about:blank" height="400" width="400"></iframe>
+<iframe id="if_3" src="about:blank" onload='if_3_load()' height="400" width="400"></iframe>
+<iframe id="if_4" sandbox='allow-same-origin' onload='if_4_load()' src="about:blank" height="400" width="400"></iframe>
+</div>
+</body>
+</html>
diff --git a/dom/html/test/test_iframe_sandbox_popups.html b/dom/html/test/test_iframe_sandbox_popups.html
new file mode 100644
index 000000000..28094f2e6
--- /dev/null
+++ b/dom/html/test/test_iframe_sandbox_popups.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=766282
+implement allow-popups directive for iframe sandbox
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 766282</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>
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// a postMessage handler that is used by sandboxed iframes without
+// 'allow-same-origin' to communicate pass/fail back to this main page.
+// it expects to be called with an object like {ok: true/false, desc:
+// <description of the test> which it then forwards to ok()
+window.addEventListener("message", receiveMessage, false);
+
+function receiveMessage(event)
+{
+ ok_wrapper(event.data.ok, event.data.desc);
+}
+
+var completedTests = 0;
+var passedTests = 0;
+
+function ok_wrapper(result, desc) {
+ ok(result, desc);
+
+ completedTests++;
+
+ if (result) {
+ passedTests++;
+ }
+
+ if (completedTests == 3) {
+ is(passedTests, completedTests, "There are " + completedTests + " popups tests that should pass");
+ SimpleTest.finish();
+ }
+}
+
+function doTest() {
+ // passes if good
+ // 1) Test that a sandboxed iframe with "allow-popups" can open a new window using the target.attribute.
+ // This is done via file_iframe_sandbox_h_if1.html which is sandboxed with "allow-popups allow-scripts allow-same-origin".
+ // The window it attempts to open calls window.opener.ok(true, ...) and file_iframe_h_if1.html has an ok()
+ // function that calls window.parent.ok_wrapper.
+
+ // passes if good
+ // 2) Test that a sandboxed iframe with "allow-popups" can open a new window using window.open.
+ // This is done via file_iframe_sandbox_h_if1.html which is sandboxed with "allow-popups allow-scripts allow-same-origin".
+ // The window it attempts to open calls window.opener.ok(true, ...) and file_iframe_h_if1.html has an ok()
+ // function that calls window.parent.ok_wrapper.
+
+ // passes if good, fails if bad
+ // 3) Test that a sandboxed iframe with "allow-popups" can open a new window using the target.attribute
+ // for a non-existing browsing context (BC766282).
+ // This is done via file_iframe_sandbox_h_if1.html which is sandboxed with "allow-popups allow-scripts allow-same-origin".
+ // The window it attempts to open calls window.opener.ok(true, ...) and file_iframe_h_if1.html has an ok()
+ // function that calls window.parent.ok_wrapper.
+}
+
+addLoadEvent(doTest);
+
+</script>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=766282">Mozilla Bug 766282</a> - implement allow-popups directive for iframe sandbox
+<p id="display"></p>
+<div id="content">
+<iframe sandbox="allow-popups allow-same-origin allow-scripts" id="if1" src="file_iframe_sandbox_h_if1.html" height="10" width="10"></iframe>
+</div>
+</body>
+</html>
diff --git a/dom/html/test/test_iframe_sandbox_popups_inheritance.html b/dom/html/test/test_iframe_sandbox_popups_inheritance.html
new file mode 100644
index 000000000..54afb56b2
--- /dev/null
+++ b/dom/html/test/test_iframe_sandbox_popups_inheritance.html
@@ -0,0 +1,157 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=766282
+Implement HTML5 sandbox allow-popuos directive for IFRAMEs - inheritance tests
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 766282</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+
+<script type="application/javascript">
+
+SimpleTest.expectAssertions(0, 5);
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+// A postMessage handler that is used by sandboxed iframes without
+// 'allow-same-origin' to communicate pass/fail back to this main page.
+window.addEventListener("message", receiveMessage, false);
+
+function receiveMessage(event) {
+ switch (event.data.type) {
+ case "attempted":
+ testAttempted();
+ break;
+ case "ok":
+ ok_wrapper(event.data.ok, event.data.desc, event.data.addToAttempted);
+ break;
+ default:
+ // allow for old style message
+ if (event.data.ok != undefined) {
+ ok_wrapper(event.data.ok, event.data.desc, event.data.addToAttempted);
+ }
+ }
+}
+
+var iframesWithWindowsToClose = new Array();
+
+var attemptedTests = 0;
+var passedTests = 0;
+var totalTestsToPass = 15;
+var totalTestsToAttempt = 21;
+
+function ok_wrapper(result, desc, addToAttempted = true) {
+ ok(result, desc);
+
+ if (result) {
+ passedTests++;
+ }
+
+ if (addToAttempted) {
+ testAttempted();
+ }
+}
+
+// Added so that tests that don't register unless they fail,
+// can at least notify that they've attempted to run.
+function testAttempted() {
+ attemptedTests++;
+ if (attemptedTests == totalTestsToAttempt) {
+ // Make sure all tests have had a chance to complete.
+ setTimeout(function() {finish();}, 1000);
+ }
+}
+
+var finishCalled = false;
+
+function finish() {
+ if (!finishCalled) {
+ finishCalled = true;
+ is(passedTests, totalTestsToPass, "There are " + totalTestsToPass + " inheritance tests that should pass");
+
+ closeWindows();
+
+ SimpleTest.finish();
+ }
+}
+
+function checkTestsFinished() {
+ // If our own finish() has not been called, probably failed due to a timeout, so close remaining windows.
+ if (!finishCalled) {
+ closeWindows();
+ }
+}
+
+function closeWindows() {
+ for (var i = 0; i < iframesWithWindowsToClose.length; i++) {
+ document.getElementById(iframesWithWindowsToClose[i]).contentWindow.postMessage({type: "closeWindows"}, "*");
+ }
+}
+
+function doTest() {
+ // passes if good and fails if bad
+ // 1,2,3) A window opened from inside an iframe that has sandbox = "allow-scripts allow-popups
+ // allow-same-origin" should not have its origin sandbox flag set and be able to access document.cookie.
+ // (Done by file_iframe_sandbox_k_if5.html opened from file_iframe_sandbox_k_if4.html)
+ // This is repeated for 3 different ways of opening the window,
+ // see file_iframe_sandbox_k_if4.html for details.
+
+ // passes if good
+ // 4,5,6) A window opened from inside an iframe that has sandbox = "allow-scripts allow-popups
+ // allow-top-navigation" should not have its top-level navigation sandbox flag set and be able to
+ // navigate top. (Done by file_iframe_sandbox_k_if5.html (and if6) opened from
+ // file_iframe_sandbox_k_if4.html). This is repeated for 3 different ways of opening the window,
+ // see file_iframe_sandbox_k_if4.html for details.
+
+ // passes if good
+ // 7,8,9) A window opened from inside an iframe that has sandbox = "allow-scripts allow-popups
+ // all-forms" should not have its forms sandbox flag set and be able to submit forms.
+ // (Done by file_iframe_sandbox_k_if7.html opened from file_iframe_sandbox_k_if4.html)
+ // This is repeated for 3 different ways of opening the window,
+ // see file_iframe_sandbox_k_if4.html for details.
+
+ // passes if good
+ // 10,11,12) Make sure that the sandbox flags copied to a new browsing context are taken from the
+ // current active document not the browsing context (iframe / docShell).
+ // This is done by removing allow-same-origin and calling doSubOpens from file_iframe_sandbox_k_if8.html,
+ // which opens file_iframe_sandbox_k_if9.html in 3 different ways.
+ // It then navigates to file_iframe_sandbox_k_if1.html to run tests 13 - 21 below.
+ var if_8_1 = document.getElementById('if_8_1');
+ if_8_1.sandbox = 'allow-scripts allow-popups';
+ if_8_1.contentWindow.doSubOpens();
+
+ // passes if good and fails if bad
+ // 13,14,15) A window opened from inside an iframe that has sandbox = "allow-scripts allow-popups"
+ // should have its origin sandbox flag set and not be able to access document.cookie.
+ // This is done by file_iframe_sandbox_k_if8.html navigating to file_iframe_sandbox_k_if1.html
+ // after allow-same-origin has been removed from iframe if_8_1. file_iframe_sandbox_k_if1.html
+ // opens file_iframe_sandbox_k_if2.html in 3 different ways to perform the tests.
+ iframesWithWindowsToClose.push("if_8_1");
+
+ // fails if bad
+ // 16,17,18) A window opened from inside an iframe that has sandbox = "allow-scripts allow-popups"
+ // should have its forms sandbox flag set and not be able to submit forms.
+ // This is done by file_iframe_sandbox_k_if2.html, see test 10 for details of how this is opened.
+
+ // fails if bad
+ // 19,20,21) A window opened from inside an iframe that has sandbox = "allow-scripts allow-popups"
+ // should have its top-level navigation sandbox flag set and not be able to navigate top.
+ // This is done by file_iframe_sandbox_k_if2.html, see test 10 for details of how this is opened.
+}
+
+addLoadEvent(doTest);
+</script>
+
+<body onunload="checkTestsFinished()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=766282">Mozilla Bug 766282</a> - Implement HTML5 sandbox allow-popups directive for IFRAMEs
+<p id="display"></p>
+<div id="content">
+<iframe sandbox="allow-scripts allow-popups allow-same-origin allow-forms allow-top-navigation" id="if_4" src="file_iframe_sandbox_k_if4.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts allow-popups allow-same-origin" id="if_8_1" src="file_iframe_sandbox_k_if8.html" height="10" width="10"></iframe>
+</div>
+</body>
+</html>
diff --git a/dom/html/test/test_iframe_sandbox_redirect.html b/dom/html/test/test_iframe_sandbox_redirect.html
new file mode 100644
index 000000000..5476a6d44
--- /dev/null
+++ b/dom/html/test/test_iframe_sandbox_redirect.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=985135
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 985135</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 985135 **/
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ try {
+ var doc = frames[0].document;
+ ok(false, "Should not be able to get the document");
+ isnot(doc.body.textContent.slice(0, -1), "I have been redirected",
+ "Should not happen");
+ SimpleTest.finish();
+ } catch (e) {
+ // Check that we got the right document
+ window.onmessage = function(event) {
+ is(event.data, "who are you? redirect target",
+ "Should get the message we expect");
+ SimpleTest.finish();
+ }
+
+ frames[0].postMessage("who are you?", "*");
+ }
+ });
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=985135">Mozilla Bug 985135</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe src="file_iframe_sandbox_redirect.html" sandbox="allow-scripts"></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_iframe_sandbox_refresh.html b/dom/html/test/test_iframe_sandbox_refresh.html
new file mode 100644
index 000000000..770a4ba8f
--- /dev/null
+++ b/dom/html/test/test_iframe_sandbox_refresh.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1156059
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 1156059</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">
+
+ // Tests for Bug 1156059
+ // See ok messages in iframes for test cases.
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("We cannot detect when the sandbox blocks the META REFRESH, so we need to allow a reasonable amount of time for them to fail.");
+
+ var testCases = [
+ {
+ desc: "Meta refresh without allow-scripts should be ignored.",
+ numberOfLoads: 0,
+ numberOfLoadsExpected: 1
+ },
+ {
+ desc: "Meta refresh check should be case insensitive.",
+ numberOfLoads: 0,
+ numberOfLoadsExpected: 1
+ },
+ {
+ desc: "Meta refresh with allow-scripts should work.",
+ numberOfLoads: 0,
+ numberOfLoadsExpected: 2
+ },
+ {
+ desc: "Refresh HTTP headers should not be affected by sandbox.",
+ numberOfLoads: 0,
+ numberOfLoadsExpected: 2
+ }
+ ];
+
+ var totalLoads = 0;
+ var totalLoadsExpected = testCases.reduce(function(partialSum, testCase) {
+ return partialSum + testCase.numberOfLoadsExpected;
+ }, 0);
+
+ function processLoad(testCaseIndex) {
+ testCases[testCaseIndex].numberOfLoads++;
+
+ if (++totalLoads == totalLoadsExpected) {
+ // Give the tests that should block the refresh a bit of extra time to
+ // fail. The worst that could happen here is that we get a false pass.
+ window.setTimeout(processResults, 500);
+ }
+ }
+
+ function processResults() {
+ testCases.forEach(function(testCase, index) {
+ var msg = "Test Case " + index + ": " + testCase.desc;
+ is(testCase.numberOfLoads, testCase.numberOfLoadsExpected, msg);
+ });
+
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1156059">Mozilla Bug 1156059</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+<iframe
+ onload="processLoad(0)"
+ srcdoc="<meta http-equiv='refresh' content='0; url=data:text/html,Refreshed'>"
+ sandbox="allow-forms allow-pointer-lock allow-popups allow-same-origin allow-top-navigation"
+></iframe>
+
+<iframe
+ onload="processLoad(1)"
+ srcdoc="<meta http-equiv='rEfReSh' content='0; url=data:text/html,Refreshed'>"
+ sandbox="allow-forms allow-pointer-lock allow-popups allow-same-origin allow-top-navigation"
+></iframe>
+
+<iframe
+ onload="processLoad(2)"
+ srcdoc="<meta http-equiv='refresh' content='0; url=data:text/html,Refreshed'>"
+ sandbox="allow-scripts"
+></iframe>
+
+<iframe
+ onload="processLoad(3)"
+ src="file_iframe_sandbox_refresh.html"
+ sandbox
+></iframe>
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_iframe_sandbox_same_origin.html b/dom/html/test/test_iframe_sandbox_same_origin.html
new file mode 100644
index 000000000..b924b9f20
--- /dev/null
+++ b/dom/html/test/test_iframe_sandbox_same_origin.html
@@ -0,0 +1,108 @@
+\<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=341604
+Implement HTML5 sandbox attribute for IFRAMEs - same origin tests
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script type="application/javascript">
+/** Test for Bug 341604 - Implement HTML5 sandbox attribute for IFRAMEs **/
+/** Same Origin Tests **/
+
+SimpleTest.waitForExplicitFinish();
+
+var completedTests = 0;
+var passedTests = 0;
+
+function ok_wrapper(result, desc) {
+ ok(result, desc);
+
+ completedTests++;
+
+ if (result) {
+ passedTests++;
+ }
+
+ if (completedTests == 14) {
+ is(passedTests, completedTests, "There are " + completedTests + " same-origin tests that should pass");
+
+ SimpleTest.finish();
+ }
+}
+
+function receiveMessage(event)
+{
+ ok_wrapper(event.data.ok, event.data.desc);
+}
+
+// a postMessage handler that is used by sandboxed iframes without
+// 'allow-same-origin' to communicate pass/fail back to this main page.
+// it expects to be called with an object like {ok: true/false, desc:
+// <description of the test> which it then forwards to ok()
+window.addEventListener("message", receiveMessage, false);
+
+function doTest() {
+ // 1) test that we can't access an iframe sandboxed without "allow-same-origin"
+ var if_1 = document.getElementById("if_1");
+ try {
+ var b = if_1.contentDocument.body;
+ ok_wrapper(false, "accessing body of a sandboxed document should not be allowed");
+ } catch (err){
+ ok_wrapper(true, "accessing body of a sandboxed document should not be allowed");
+ }
+
+ // 2) test that we can access an iframe sandboxed with "allow-same-origin"
+ var if_2 = document.getElementById("if_2");
+
+ try {
+ var b = if_2.contentDocument.body;
+ ok_wrapper(true, "accessing body of a sandboxed document with allow-same-origin should be allowed");
+ } catch (err) {
+ ok_wrapper(false, "accessing body of a sandboxed document with allow-same-origin should be allowed");
+ }
+
+ // 3) test that a sandboxed iframe without 'allow-same-origin' cannot access its parent
+ // this is done by file_iframe_b_if3.html which has 'allow-scripts' but not 'allow-same-origin'
+
+ // 4) test that a sandboxed iframe with 'allow-same-origin' can access its parent
+ // this is done by file_iframe_b_if2.html which has 'allow-same-origin' and 'allow-scripts'
+
+ // 5) check that a sandboxed iframe with "allow-same-origin" can access document.cookie
+ // this is done by file_iframe_b_if2.html which has 'allow-same-origin' and 'allow-scripts'
+
+ // 6) check that a sandboxed iframe with "allow-same-origin" can access window.localStorage
+ // this is done by file_iframe_b_if2.html which has 'allow-same-origin' and 'allow-scripts'
+
+ // 7) check that a sandboxed iframe with "allow-same-origin" can access window.sessionStorage
+ // this is done by file_iframe_b_if2.html which has 'allow-same-origin' and 'allow-scripts'
+
+ // 8) check that a sandboxed iframe WITHOUT "allow-same-origin" can NOT access document.cookie
+ // this is done by file_iframe_b_if3.html which has 'allow-scripts' but not 'allow-same-origin'
+
+ // 9) check that a sandboxed iframe WITHOUT "allow-same-origin" can NOT access window.localStorage
+ // this is done by file_iframe_b_if3.html which has 'allow-scripts' but not 'allow-same-origin'
+
+ // 10) check that a sandboxed iframe WITHOUT "allow-same-origin" can NOT access window.sessionStorage
+ // this is done by file_iframe_b_if3.html which has 'allow-scripts' but not 'allow-same-origin'
+
+ // 11) check that XHR works normally in a sandboxed iframe with "allow-same-origin" and "allow-scripts"
+ // this is done by file_iframe_b_if2.html which has 'allow-same-origin' and 'allow-scripts'
+
+ // 12) check that XHR is blocked in a sandboxed iframe with "allow-scripts" but WITHOUT "allow-same-origin"
+ // this is done by file_iframe_b_if3.html which has 'allow-scripts' but not 'allow-same-origin'
+}
+addLoadEvent(doTest);
+</script>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=341604">Mozilla Bug 341604</a> - Implement HTML5 sandbox attribute for IFRAMEs
+<p id="display"></p>
+<div id="content">
+<iframe sandbox="" id="if_1" src="file_iframe_sandbox_b_if1.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-same-origin allow-scripts" id="if_2" src="file_iframe_sandbox_b_if2.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-scripts" id="if_3" src="file_iframe_sandbox_b_if3.html" height="10" width="10"></iframe>
+</div>
diff --git a/dom/html/test/test_iframe_sandbox_workers.html b/dom/html/test/test_iframe_sandbox_workers.html
new file mode 100644
index 000000000..0737729da
--- /dev/null
+++ b/dom/html/test/test_iframe_sandbox_workers.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=341604
+Implement HTML5 sandbox attribute for IFRAMEs - tests for workers
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 341604</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>
+<script type="application/javascript">
+/** Test for Bug 341604 - Implement HTML5 sandbox attribute for IFRAMEs - test for workers **/
+
+SimpleTest.waitForExplicitFinish();
+
+// a postMessage handler that is used by sandboxed iframes without
+// 'allow-same-origin' to communicate pass/fail back to this main page.
+// it expects to be called with an object like {ok: true/false, desc:
+// <description of the test> which it then forwards to ok()
+window.addEventListener("message", receiveMessage, false);
+
+function receiveMessage(event)
+{
+ ok_wrapper(event.data.ok, event.data.desc);
+}
+
+var completedTests = 0;
+var passedTests = 0;
+
+function ok_wrapper(result, desc) {
+ ok(result, desc);
+
+ completedTests++;
+
+ if (result) {
+ passedTests++;
+ }
+
+ if (completedTests == 3) {
+ is(passedTests, 3, "There are 3 worker tests that should pass");
+ SimpleTest.finish();
+ }
+}
+
+function doTest() {
+ // passes if good
+ // 1) test that a worker in a sandboxed iframe with 'allow-scripts' can be loaded
+ // from a data: URI
+ // (done by file_iframe_sandbox_g_if1.html)
+
+ // passes if good
+ // 2) test that a worker in a sandboxed iframe with 'allow-scripts' can be loaded
+ // from a blob URI created by the sandboxed document itself
+ // (done by file_iframe_sandbox_g_if1.html)
+
+ // passes if good
+ // 3) test that a worker in a sandboxed iframe with 'allow-scripts' without
+ // 'allow-same-origin' cannot load a script via a relative URI
+ // (done by file_iframe_sandbox_g_if1.html)
+}
+
+addLoadEvent(doTest);
+</script>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=341604">Mozilla Bug 341604</a> - Implement HTML5 sandbox attribute for IFRAMEs
+<p id="display"></p>
+<div id="content">
+<iframe sandbox="allow-scripts" id="if_1" src="file_iframe_sandbox_g_if1.html" height="10" width="10"></iframe>
+</div>
+</body>
+</html>
diff --git a/dom/html/test/test_ignoreuserfocus.html b/dom/html/test/test_ignoreuserfocus.html
new file mode 100644
index 000000000..629790182
--- /dev/null
+++ b/dom/html/test/test_ignoreuserfocus.html
@@ -0,0 +1,158 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <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.requestCompleteLog();
+ SimpleTest.waitForExplicitFinish();
+
+ // Copied from EventUtils.js, but we want *ToWindow methods.
+ function synthesizeMouseAtPoint(left, top, aEvent, aWindow) {
+ var utils = _getDOMWindowUtils(aWindow);
+ var defaultPrevented = false;
+ if (utils) {
+ var button = aEvent.button || 0;
+ var clickCount = aEvent.clickCount || 1;
+ var modifiers = _parseModifiers(aEvent);
+ var pressure = ("pressure" in aEvent) ? aEvent.pressure : 0;
+ var inputSource = ("inputSource" in aEvent) ? aEvent.inputSource : 0;
+
+ if (("type" in aEvent) && aEvent.type) {
+ defaultPrevented = utils.sendMouseEventToWindow(aEvent.type, left, top, button, clickCount, modifiers, false, pressure, inputSource);
+ }
+ else {
+ utils.sendMouseEventToWindow("mousedown", left, top, button, clickCount, modifiers, false, pressure, inputSource);
+ utils.sendMouseEventToWindow("mouseup", left, top, button, clickCount, modifiers, false, pressure, inputSource);
+ }
+ }
+ }
+
+ function runTest() {
+ var witness = document.createElement("input");
+ witness.setAttribute("type", "text");
+ var witness2 = document.createElement("input");
+ witness2.setAttribute("type", "text");
+
+ var iframe = document.createElement("iframe");
+ iframe.setAttribute("mozbrowser", "true");
+ iframe.setAttribute("ignoreuserfocus", "true");
+ iframe.setAttribute("height", "300px");
+
+ iframe.addEventListener('load', function (e) {
+ // Get privileged iframe because mozbrowser iframe is not same origin
+ // with the parent. We need to access its content through the wrapper.
+ var privilegedIframe = SpecialPowers.wrap(iframe);
+ privilegedIframe.contentWindow.addEventListener("MozAfterPaint", function afterPaint(e) {
+ privilegedIframe.contentWindow.removeEventListener("MozAfterPaint", afterPaint);
+
+ privilegedIframe.contentWindow.addEventListener("focus",
+ function(e) {
+ ok(!iframe.hasAttribute("ignoreuserfocus"), "Shouldn't get a focus event in ignoreuserfocus iframe!");
+ },
+ true);
+ privilegedIframe.contentWindow.addEventListener("blur",
+ function(e) {
+ ok(!iframe.hasAttribute("ignoreuserfocus"), "Shouldn't get a blur event in ignoreuserfocus iframe!");
+ },
+ true);
+
+ // Sanity check
+ witness.focus();
+ is(document.activeElement, witness, "witness should have the focus");
+
+ iframe.focus();
+ isnot(document.activeElement, iframe, "[explicit iframe.focus()] iframe should not get the focus");
+
+ iframe.removeAttribute("ignoreuserfocus");
+ iframe.focus();
+ is(document.activeElement, iframe, "[explicit iframe.focus()] iframe should get the focus");
+
+ iframe.setAttribute("ignoreuserfocus", "true");
+
+ // Test the case when iframe contains <input> and .focus()
+ // is called and explicit focus using mouse
+ witness.focus();
+
+ var innerInput = privilegedIframe.contentDocument.getElementsByTagName("input")[0];
+ innerInput.focus();
+ isnot(document.activeElement, iframe, "[explicit innerInput.focus()] iframe should not have the focus");
+
+ var iframeWindow = SpecialPowers.unwrap(privilegedIframe.contentWindow);
+ witness.focus();
+ synthesizeMouseAtCenter(innerInput, {}, iframeWindow);
+ is(document.activeElement, witness, "[synthesize mouse click] witness should have the focus");
+
+ // Test the case when iframe contains <iframe> and .focus()
+ // is called and explicit focus using mouse
+ witness.focus();
+
+ var innerIframe = privilegedIframe.contentDocument.getElementsByTagName("iframe")[0];
+ innerIframe.focus();
+ isnot(document.activeElement, iframe, "[explicit innerIframe.focus()] iframe should not have the focus");
+
+ witness.focus();
+ synthesizeMouseAtCenter(innerIframe, {}, iframeWindow);
+ is(document.activeElement, witness, "[synthesize mouse click inner iframe] witness should have the focus");
+
+ // Test the case when iframe contains <area> and .focus()
+ // is called and explicit focus using mouse
+
+ // Wait for paint to setup frame for area. Currently the area frame
+ // map is reset for each paint. If we are in the middle of a paint
+ // then the area will not be focusable.
+ privilegedIframe.contentWindow.addEventListener("MozAfterPaint", function afterPaintArea(e) {
+ privilegedIframe.contentWindow.removeEventListener("MozAfterPaint", afterPaintArea);
+ var innerArea = privilegedIframe.contentDocument.getElementsByTagName("area")[0];
+ innerArea.focus();
+ isnot(document.activeElement, iframe, "[explicit innerArea.focus()] iframe should not have the focus");
+
+ witness.focus();
+ synthesizeMouseAtCenter(innerArea, {}, iframeWindow);
+ is(document.activeElement, witness, "[synthesize mouse click] witness should have the focus");
+
+ // Test tabindex
+ witness.focus();
+ is(document.activeElement, witness, "witness should have the focus");
+ synthesizeKey("VK_TAB", {});
+ isnot(document.activeElement, iframe, "[synthesize tab key] iframe should not have the focus");
+ is(document.activeElement, witness2, "witness2 should have the focus");
+
+ SimpleTest.finish();
+ });
+ witness.focus();
+ // force reflow
+ iframe.setAttribute("height", "298px");
+ });
+ // force reflow
+ iframe.setAttribute("height", "299px");
+ });
+
+ document.body.appendChild(witness);
+ document.body.appendChild(iframe);
+ document.body.appendChild(witness2);
+
+ iframe.setAttribute("src", "file_ignoreuserfocus.html");
+ }
+ addEventListener("load", function() {
+ SpecialPowers.pushPermissions(
+ [{'type': 'browser', 'allow': true, 'context': document}],
+ function() {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true],
+ ]
+ }, function() {
+ SimpleTest.waitForFocus(runTest);
+ });
+ });
+ });
+ </script>
+ </body>
+</html>
diff --git a/dom/html/test/test_imageSrcSet.html b/dom/html/test/test_imageSrcSet.html
new file mode 100644
index 000000000..adbea1676
--- /dev/null
+++ b/dom/html/test/test_imageSrcSet.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=980243
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 980243</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 980243 **/
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ var img = document.querySelector("img");
+ img.onload = function() {
+ ok(true, "Reached here");
+ SimpleTest.finish();
+ }
+ // If ths spec ever changes to treat .src sets differently from
+ // setAttribute("src"), we'll need some sort of canonicalization step
+ // earlier to make the attr value an absolute URI.
+ img.setAttribute("src", img.getAttribute("src"));
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=980243">Mozilla Bug 980243</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <img src="file_formSubmission_img.jpg">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_image_clone_load.html b/dom/html/test/test_image_clone_load.html
new file mode 100644
index 000000000..e808c80a5
--- /dev/null
+++ b/dom/html/test/test_image_clone_load.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for image clones doing their load</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+var t = async_test("The clone of an image should do the load of the same image, and do it synchronously");
+t.step(function() {
+ var img = new Image();
+ img.onload = t.step_func(function() {
+ var clone = img.cloneNode();
+ assert_not_equals(img.naturalWidth, 0, "Should have a width");
+ assert_equals(clone.naturalWidth, img.naturalWidth,
+ "Clone should have a width too");
+ // And make sure the clone fires onload too, which happens async.
+ clone.onload = function() { t.done() }
+ });
+ img.src = "image.png";
+});
+</script>
diff --git a/dom/html/test/test_img_attributes_reflection.html b/dom/html/test/test_img_attributes_reflection.html
new file mode 100644
index 000000000..c40865a86
--- /dev/null
+++ b/dom/html/test/test_img_attributes_reflection.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLImageElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+/** Test for HTMLImageElement attributes reflection **/
+
+reflectString({
+ element: document.createElement("img"),
+ attribute: "alt",
+})
+
+reflectURL({
+ element: document.createElement("img"),
+ attribute: "src",
+})
+
+reflectString({
+ element: document.createElement("img"),
+ attribute: "srcset",
+})
+
+reflectLimitedEnumerated({
+ element: document.createElement("img"),
+ attribute: "crossOrigin",
+ // "" is a valid value per spec, but gets mapped to the "anonymous" state,
+ // just like invalid values, so just list it under invalidValues
+ validValues: [ "anonymous", "use-credentials" ],
+ invalidValues: [
+ "", " aNOnYmous ", " UsE-CreDEntIALS ", "foobar", "FOOBAR", " fOoBaR "
+ ],
+ defaultValue: { invalid: "anonymous", missing: null },
+ nullable: true,
+})
+
+reflectString({
+ element: document.createElement("img"),
+ attribute: "useMap",
+})
+
+reflectBoolean({
+ element: document.createElement("img"),
+ attribute: "isMap",
+})
+
+ok("width" in document.createElement("img"), "img.width is present")
+ok("height" in document.createElement("img"), "img.height is present")
+ok("naturalWidth" in document.createElement("img"), "img.naturalWidth is present")
+ok("naturalHeight" in document.createElement("img"), "img.naturalHeight is present")
+ok("complete" in document.createElement("img"), "img.complete is present")
+
+reflectString({
+ element: document.createElement("img"),
+ attribute: "name",
+})
+
+reflectString({
+ element: document.createElement("img"),
+ attribute: "align",
+})
+
+reflectUnsignedInt({
+ element: document.createElement("img"),
+ attribute: "hspace",
+})
+
+reflectUnsignedInt({
+ element: document.createElement("img"),
+ attribute: "vspace",
+})
+
+reflectURL({
+ element: document.createElement("img"),
+ attribute: "longDesc",
+})
+
+reflectString({
+ element: document.createElement("img"),
+ attribute: "border",
+ extendedAttributes: { TreatNullAs: "EmptyString" },
+})
+
+reflectURL({
+ element: document.createElement("img"),
+ attribute: "lowsrc",
+})
+
+ok("x" in document.createElement("img"), "img.x is present")
+ok("y" in document.createElement("img"), "img.y is present")
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_imports_basics.html b/dom/html/test/test_imports_basics.html
new file mode 100644
index 000000000..3bdd96152
--- /dev/null
+++ b/dom/html/test/test_imports_basics.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=877072
+-->
+<head>
+ <title>Test for Bug 877072</title>
+ <script type="text/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>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=877072">Mozilla Bug 877072</a>
+
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ var loadEventFired = false;
+ var errorEventFired = false;
+ var counter = 0;
+ function loaded() {
+ loadEventFired = true;
+ }
+ function failed() {
+ errorEventFired = true;
+ }
+ </script>
+
+ <link rel="import" href="file_imports_basics.html" id="import1" onload="loaded()" onerror="failed()"></link>
+
+ <script type="text/javascript">
+ ok(importDone, "Script of the imported document runned before this script");
+ ok(loadEventFired, "Load eventhandler works");
+ ok(!errorEventFired, "There were no error event fired");
+
+ var import1 = document.getElementById("import1").import;
+ is(import1.getElementById("foo").textContent, "bar",
+ "import property of link import works");
+
+ try{
+ import1.open();
+ import1.write("<h1>This should not show up!</h1>");
+ import1.close();
+ } catch (e) {
+ ok(true, "import.open/write should throw");
+ }
+
+ // Let's add dynamically a new link import with the same URI
+ var link = document.createElement("link");
+ link.setAttribute("id", "inserted");
+ link.setAttribute("rel", "import");
+ link.setAttribute("href", "file_imports_basics.html");
+ function loaded2() {
+ ok(true, "Load event is fired for link import that refers to an already loaded import");
+ is(counter, 1, "Adding another link referring to the same import, does not load it twice");
+ is(document.getElementById("inserted").import.getElementById("foo").textContent, "bar",
+ "import property of dynamic link import works");
+ SimpleTest.finish();
+ };
+ link.setAttribute("onload", "loaded2()");
+ function failed2() {
+ ok(false, "You should not reach this code");
+ SimpleTest.finish();
+ };
+ link.setAttribute("onerror", "failed2()");
+ document.body.appendChild(link);
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/html/test/test_imports_nested.html b/dom/html/test/test_imports_nested.html
new file mode 100644
index 000000000..518e5b741
--- /dev/null
+++ b/dom/html/test/test_imports_nested.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=877072
+-->
+<head>
+ <title>Test for Bug 877072</title>
+ <script type="text/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>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=877072">Mozilla Bug 877072</a>
+
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ var counter = 0;
+ var fcounter = 0;
+ var order = [];
+ function loaded() {
+ counter++;
+ }
+ function failed() {
+ fcounter++;
+ }
+ </script>
+
+ <link rel="import" href="imports/file_importA1.html" id="A1" onload="loaded()" onerror="failed()"></link>
+ <link rel="import" href="imports/file_importB1.html" id="B1" onload="loaded()" onerror="failed()"></link>
+ <link rel="import" href="imports/file_importB2.html" id="B2_2" onload="loaded()" onerror="failed()"></link>
+
+ <script type="text/javascript">
+ is(counter, 5, "Imports are loaded");
+ is(fcounter, 0, "No error in imports");
+ var expected = ["A2", "A1", "B2", "B1"];
+ for (i in expected)
+ is(order[i], expected[i], "import " + i + " should be " + expected[i]);
+ SimpleTest.finish();
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/test_imports_nested_2.html b/dom/html/test/test_imports_nested_2.html
new file mode 100644
index 000000000..8e8e57d68
--- /dev/null
+++ b/dom/html/test/test_imports_nested_2.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=877072
+-->
+<head>
+ <title>Test for Bug 877072</title>
+ <script type="text/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>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=877072">Mozilla Bug 877072</a>
+
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ var counter = 0;
+ var fcounter = 0;
+ var order = [];
+ function loaded() {
+ counter++;
+ }
+ function failed() {
+ fcounter++;
+ }
+ </script>
+
+<!--
+
+Master -> c1 -> ... -> C10
+| | |
+| - - -> D |
+| ^ |
+| | |
+ - - - -> E < - - - - - -
+
+This test is for testing ImportLoader::UpdateDependants.Because of the long
+chain to C10, it's very likely that C10->E will be the last edge the ImportLoaders
+find. At that point it won't only have to update E but also its subimport D.
+
+-->
+
+ <link rel="import" href="imports/file_importC1.html" onload="loaded()" onerror="failed()"></link>
+ <link rel="import" href="imports/file_importD.html" onload="loaded()" onerror="failed()"></link>
+ <link rel="import" href="imports/file_importE.html" onload="loaded()" onerror="failed()"></link>
+
+ <script type="text/javascript">
+ is(counter, 14, "Imports are loaded"); // 12 imports but 14 link imports...
+ is(fcounter, 0, "No error in imports");
+ var expected = ["D", "E", "C10", "C9", "C8", "C7", "C6", "C5", "C4", "C3", "C2", "C1"];
+ for (i in expected)
+ is(order[i], expected[i], "import " + i + " should be " + expected[i]);
+ SimpleTest.finish();
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/test_imports_nonhttp.html b/dom/html/test/test_imports_nonhttp.html
new file mode 100644
index 000000000..7a5620599
--- /dev/null
+++ b/dom/html/test/test_imports_nonhttp.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1016875
+-->
+<head>
+ <title>Test for Bug 1016875</title>
+ <script type="text/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>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1016875">Mozilla Bug 1016875</a>
+
+ <script type="text/javascript">
+ //<![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ var counter = 0;
+ function loaded() {
+ if ( counter++ == 1 ) {
+ ok(document.getElementById("import1").import.getElementById("div1"),
+ "import document was loaded");
+ ok(document.getElementById("import2").import.getElementById("div1"),
+ "import document was loaded");
+ SimpleTest.finish();
+ }
+ }
+ function failed() {
+ ok(false, "Import should not have failed")
+ SimpleTest.finish();
+ }
+ var link = document.createElement("link");
+ link.setAttribute("id", "import1");
+ link.setAttribute("rel", "import");
+ var stringDoc = "<!DOCTYPE html><html><body><div id='div1'></div></body></html>";
+ var encoded = btoa(stringDoc);
+ var dataurl = "data:text/html;base64," + encoded;
+ link.setAttribute("href", dataurl);
+ link.onload = loaded;
+ link.onerror = failed;
+ document.body.appendChild(link);
+
+ var link = document.createElement("link");
+ link.setAttribute("id", "import2");
+ link.setAttribute("rel", "import");
+ var blob = new Blob([stringDoc], {type : 'text/html'});
+ var objectURL = URL.createObjectURL(blob);
+ link.setAttribute("href", objectURL);
+ link.onload = loaded;
+ link.onerror = failed;
+ document.body.appendChild(link);
+ //]]>
+ </script>
+
+</body>
+</html>
diff --git a/dom/html/test/test_imports_redirect.html b/dom/html/test/test_imports_redirect.html
new file mode 100644
index 000000000..d780476cf
--- /dev/null
+++ b/dom/html/test/test_imports_redirect.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1016875
+-->
+<head>
+ <title>Test for Bug 1016875</title>
+ <script type="text/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>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1016875">Mozilla Bug 1016875</a>
+
+ <script type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ var loadEventFired = false;
+ var errorEventFired = false;
+ var counter = 0;
+ function loaded() {
+ loadEventFired = true;
+ }
+ function failed() {
+ errorEventFired = true;
+ }
+ </script>
+
+ <link rel="import" href="http://mochi.test:8888/tests/dom/html/test/file_imports_redirect.html" id="import" onload="loaded()" onerror="failed()"></link>
+
+ <script type="text/javascript">
+ ok(loadEventFired, "Load event was fired");
+ ok(!errorEventFired, "Error event was not fired, redirection worked");
+ ok(redirected, "Script of the target of redirection was executed");
+
+ SimpleTest.finish();
+ </script>
+
+</body>
+</html>
diff --git a/dom/html/test/test_input_files_not_nsIFile.html b/dom/html/test/test_input_files_not_nsIFile.html
new file mode 100644
index 000000000..a52afac7c
--- /dev/null
+++ b/dom/html/test/test_input_files_not_nsIFile.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for &lt;input type='file'&gt; handling when its "files" do not implement nsIFile</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>
+
+<div id="content">
+ <input id='a' type='file'>
+</div>
+<button id='b' onclick="document.getElementById('a').click();">Show Filepicker</button>
+
+<input type="file" id="file" />
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+SimpleTest.waitForFocus(function() {
+ MockFilePicker.useBlobFile();
+ MockFilePicker.returnValue = MockFilePicker.returnOK;
+
+ var b = document.getElementById('b');
+ b.focus(); // Be sure the element is visible.
+
+ document.getElementById('a').addEventListener("change", function(aEvent) {
+ ok(true, "change event correctly sent");
+
+ SimpleTest.executeSoon(function() {
+ MockFilePicker.cleanup();
+ SimpleTest.finish();
+ });
+ }, false);
+
+ b.click();
+});
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/html/test/test_li_attributes_reflection.html b/dom/html/test/test_li_attributes_reflection.html
new file mode 100644
index 000000000..b6fcd7581
--- /dev/null
+++ b/dom/html/test/test_li_attributes_reflection.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLLIElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLLIElement attributes reflection **/
+
+// .value
+reflectInt({
+ element: document.createElement("li"),
+ attribute: "value",
+ nonNegative: false,
+});
+
+// .type
+reflectString({
+ element: document.createElement("li"),
+ attribute: "type"
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_link_attributes_reflection.html b/dom/html/test/test_link_attributes_reflection.html
new file mode 100644
index 000000000..db092f0ef
--- /dev/null
+++ b/dom/html/test/test_link_attributes_reflection.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLLinkElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLLinkElement attributes reflection **/
+
+// .href (URL)
+reflectURL({
+ element: document.createElement("link"),
+ attribute: "href",
+});
+
+// .crossOrigin (String or null)
+reflectLimitedEnumerated({
+ element: document.createElement("link"),
+ attribute: "crossOrigin",
+ // "" is a valid value per spec, but gets mapped to the "anonymous" state,
+ // just like invalid values, so just list it under invalidValues
+ validValues: [ "anonymous", "use-credentials" ],
+ invalidValues: [
+ "", " aNOnYmous ", " UsE-CreDEntIALS ", "foobar", "FOOBAR", " fOoBaR "
+ ],
+ defaultValue: { invalid: "anonymous", missing: null },
+ nullable: true,
+})
+
+// .rel (String)
+reflectString({
+ element: document.createElement("link"),
+ attribute: "rel",
+});
+
+// .media (String)
+reflectString({
+ element: document.createElement("link"),
+ attribute: "media",
+});
+
+// .hreflang (String)
+reflectString({
+ element: document.createElement("link"),
+ attribute: "hreflang",
+});
+
+// .type (String)
+reflectString({
+ element: document.createElement("link"),
+ attribute: "type",
+});
+
+
+// .charset (String)
+reflectString({
+ element: document.createElement("link"),
+ attribute: "charset",
+});
+
+// .rev (String)
+reflectString({
+ element: document.createElement("link"),
+ attribute: "rev",
+});
+
+// .target (String)
+reflectString({
+ element: document.createElement("link"),
+ attribute: "target",
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_link_sizes.html b/dom/html/test/test_link_sizes.html
new file mode 100644
index 000000000..b24274888
--- /dev/null
+++ b/dom/html/test/test_link_sizes.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<html>
+<head>
+<title>Test link.sizes attribute</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" sizes="16x16 24x24 32x32 48x48">
+</head>
+<body>
+
+<pre id="test">
+<script>
+
+ var links = document.getElementsByTagName('link');
+ for (var i = 0; i < links.length; ++i) {
+ var link = links[i];
+ ok("sizes" in link, "link.sizes exists");
+
+ if (link.rel == 'shortcut icon') {
+ is(link.sizes.value, "16x16 24x24 32x32 48x48", 'link.sizes.value correct value');
+ is(link.sizes.length, 4, 'link.sizes.length correct value');
+ ok(link.sizes.contains('32x32'), 'link.sizes.contains() works');
+ link.sizes.add('64x64');
+ is(link.sizes.length, 5, 'link.sizes.length correct value');
+ link.sizes.remove('64x64');
+ is(link.sizes.length, 4, 'link.sizes.length correct value');
+ is(link.sizes + "", "16x16 24x24 32x32 48x48", 'link.sizes stringify correct value');
+ } else {
+ is(link.sizes.value, "", 'link.sizes correct value');
+ }
+ }
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_map_attributes_reflection.html b/dom/html/test/test_map_attributes_reflection.html
new file mode 100644
index 000000000..710dce669
--- /dev/null
+++ b/dom/html/test/test_map_attributes_reflection.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLMapElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLMapElement attributes reflection **/
+
+// .name (String)
+reflectString({
+ element: document.createElement("map"),
+ attribute: "name",
+})
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_meta_attributes_reflection.html b/dom/html/test/test_meta_attributes_reflection.html
new file mode 100644
index 000000000..c21d53587
--- /dev/null
+++ b/dom/html/test/test_meta_attributes_reflection.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLMetaElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLMetaElement attributes reflection **/
+
+// .name (String)
+reflectString({
+ element: document.createElement("meta"),
+ attribute: "name",
+})
+
+// .httpEquiv (String)
+reflectString({
+ element: document.createElement("meta"),
+ attribute: { content: "http-equiv", idl: "httpEquiv" },
+})
+
+// .content (String)
+reflectString({
+ element: document.createElement("meta"),
+ attribute: "content",
+})
+
+// .scheme (String)
+reflectString({
+ element: document.createElement("meta"),
+ attribute: "scheme",
+})
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_mod_attributes_reflection.html b/dom/html/test/test_mod_attributes_reflection.html
new file mode 100644
index 000000000..94313bc63
--- /dev/null
+++ b/dom/html/test/test_mod_attributes_reflection.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLModElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLModElement attributes reflection **/
+
+// .cite (URL)
+reflectURL({
+ element: document.createElement("ins"),
+ attribute: "cite",
+})
+reflectURL({
+ element: document.createElement("del"),
+ attribute: "cite",
+})
+
+// .dateTime (String)
+reflectString({
+ element: document.createElement("ins"),
+ attribute: "dateTime",
+})
+reflectString({
+ element: document.createElement("del"),
+ attribute: "dateTime",
+})
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_mozaudiochannel.html b/dom/html/test/test_mozaudiochannel.html
new file mode 100644
index 000000000..722e181bf
--- /dev/null
+++ b/dom/html/test/test_mozaudiochannel.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for mozaudiochannel</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>
+
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["media.useAudioChannelAPI", true]]}, function() {
+ var ifr = document.createElement('iframe');
+ ifr.src = 'file_mozaudiochannel.html';
+ onmessage = function(e) {
+ if ("finish" in e.data) {
+ SimpleTest.finish();
+ } else {
+ ok(e.data.status, e.data.msg);
+ }
+ }
+
+ document.body.appendChild(ifr);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_named_options.html b/dom/html/test/test_named_options.html
new file mode 100644
index 000000000..b6e84a9f2
--- /dev/null
+++ b/dom/html/test/test_named_options.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=772869
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 772869</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=772869">Mozilla Bug 772869</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <select id="s">
+ <option name="x"></option>
+ <option name="y" id="z"></option>
+ <option name="z" id="x"></option>
+ <option id="w"></option>
+ </select>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 772869 **/
+var opt = $("s").options;
+opt.loopy = "something"
+var names = Object.getOwnPropertyNames(opt);
+is(names.length, 9, "Should have nine entries");
+is(names[0], "0", "Entry 1")
+is(names[1], "1", "Entry 2")
+is(names[2], "2", "Entry 3")
+is(names[3], "3", "Entry 4")
+is(names[4], "x", "Entry 5")
+is(names[5], "y", "Entry 6")
+is(names[6], "z", "Entry 7")
+is(names[7], "w", "Entry 8")
+is(names[8], "loopy", "Entry 9")
+
+var names2 = [];
+for (var name in opt) {
+ names2.push(name);
+}
+is(names2.length, 11, "Should have eleven enumerated names");
+is(names2[0], "0", "Enum entry 1")
+is(names2[1], "1", "Enum entry 2")
+is(names2[2], "2", "Enum entry 3")
+is(names2[3], "3", "Enum entry 4")
+is(names2[4], "loopy", "Enum entry 5")
+is(names2[5], "add", "Enum entrry 6")
+is(names2[6], "remove", "Enum entry 7")
+is(names2[7], "length", "Enum entry 8")
+is(names2[8], "selectedIndex", "Enum entry 9")
+is(names2[9], "item", "Enum entry 10")
+is(names2[10], "namedItem", "Enum entry 11")
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_nested_invalid_fieldsets.html b/dom/html/test/test_nested_invalid_fieldsets.html
new file mode 100644
index 000000000..42413baa9
--- /dev/null
+++ b/dom/html/test/test_nested_invalid_fieldsets.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=914029
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 914029</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 914029 **/
+
+ var innerFieldset = document.createElement("fieldset");
+ var outerFieldset = document.createElement("fieldset");
+ var textarea = document.createElement("textarea");
+ textarea.setAttribute("required", "");
+ innerFieldset.appendChild(textarea);
+ outerFieldset.appendChild(innerFieldset);
+ SpecialPowers.forceGC();
+ ok(true, "This page did not crash - dynamically added nested invalid fieldsets" +
+ " work correctly.");
+ var innerFieldset = document.createElement("fieldset");
+ var outerFieldset = document.createElement("fieldset");
+ var textarea = document.createElement("textarea");
+ var textarea2 = document.createElement("textarea");
+ textarea.setAttribute("required", "");
+ innerFieldset.appendChild(textarea);
+ innerFieldset.appendChild(textarea2);
+ outerFieldset.appendChild(innerFieldset);
+ SpecialPowers.forceGC();
+ ok(true, "This page did not crash - dynamically added nested invalid fieldsets" +
+ " work correctly.");
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=914029">Mozilla Bug 914029</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_non-ascii-cookie.html b/dom/html/test/test_non-ascii-cookie.html
new file mode 100644
index 000000000..ab96ac07c
--- /dev/null
+++ b/dom/html/test/test_non-ascii-cookie.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=784367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for non-ASCII cookie values</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=784367">Mozilla Bug 784367</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for non-ASCII cookie values **/
+
+SimpleTest.waitForExplicitFinish();
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL("file_cookiemanager.js"));
+
+function getCookieFromManager() {
+ return new Promise(resolve => {
+ gScript.addMessageListener("getCookieFromManager:return", function gcfm({ cookie }) {
+ gScript.removeMessageListener("getCookieFromManager:return", gcfm);
+ resolve(cookie);
+ });
+ gScript.sendAsyncMessage("getCookieFromManager", { host: location.hostname, path: location.pathname });
+ });
+}
+
+var c = document.cookie;
+is(document.cookie, 'abc=012©ABC\ufffdDEF', "document.cookie should be decoded as UTF-8");
+
+var newCookie;
+
+getCookieFromManager().then((cookie) => {
+ is(cookie, document.cookie, "nsICookieManager should be consistent with document.cookie");
+ newCookie = 'def=∼≩≭≧∯≳≲≣∽≸≸∺≸∠≯≮≥≲≲≯≲∽≡≬≥≲≴∨∱∩∾';
+ document.cookie = newCookie;
+ is(document.cookie, c + '; ' + newCookie, "document.cookie should be encoded as UTF-8");
+
+ return getCookieFromManager();
+}).then((cookie) => {
+ is(cookie, document.cookie, "nsICookieManager should be consistent with document.cookie");
+ var date1 = new Date();
+ date1.setTime(0);
+ document.cookie = newCookie + 'def=;expires=' + date1.toGMTString();
+ gScript.destroy();
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_non-ascii-cookie.html^headers^ b/dom/html/test/test_non-ascii-cookie.html^headers^
new file mode 100644
index 000000000..54aa6c3e7
--- /dev/null
+++ b/dom/html/test/test_non-ascii-cookie.html^headers^
@@ -0,0 +1 @@
+Set-Cookie: abc=012©ABC©DEF
diff --git a/dom/html/test/test_object_attributes_reflection.html b/dom/html/test/test_object_attributes_reflection.html
new file mode 100644
index 000000000..40978e582
--- /dev/null
+++ b/dom/html/test/test_object_attributes_reflection.html
@@ -0,0 +1,117 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLObjectElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLObjectElement attributes reflection **/
+
+// .data (URL)
+reflectURL({
+ element: document.createElement("object"),
+ attribute: "data",
+});
+
+// .type (String)
+reflectString({
+ element: document.createElement("object"),
+ attribute: "type",
+});
+
+// .name (String)
+reflectString({
+ element: document.createElement("object"),
+ attribute: "name",
+});
+
+// .useMap (String)
+reflectString({
+ element: document.createElement("object"),
+ attribute: "useMap",
+});
+
+// .width (String)
+reflectString({
+ element: document.createElement("object"),
+ attribute: "width",
+});
+
+// .height (String)
+reflectString({
+ element: document.createElement("object"),
+ attribute: "height",
+});
+
+// .align (String)
+reflectString({
+ element: document.createElement("object"),
+ attribute: "align",
+});
+
+// .archive (String)
+reflectString({
+ element: document.createElement("object"),
+ attribute: "archive",
+});
+
+// .code (String)
+reflectString({
+ element: document.createElement("object"),
+ attribute: "code",
+});
+
+// .declare (String)
+reflectBoolean({
+ element: document.createElement("object"),
+ attribute: "declare",
+});
+
+// .hspace (unsigned int)
+reflectUnsignedInt({
+ element: document.createElement("object"),
+ attribute: "hspace",
+});
+
+// .standby (String)
+reflectString({
+ element: document.createElement("object"),
+ attribute: "standby",
+});
+
+// .vspace (unsigned int)
+reflectUnsignedInt({
+ element: document.createElement("object"),
+ attribute: "vspace",
+});
+
+// .codeBase (URL)
+reflectURL({
+ element: document.createElement("object"),
+ attribute: "codeBase",
+});
+
+// .codeType (String)
+reflectString({
+ element: document.createElement("object"),
+ attribute: "codeType",
+});
+
+// .border (String)
+reflectString({
+ element: document.createElement("object"),
+ attribute: "border",
+ extendedAttributes: { TreatNullAs: "EmptyString" },
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_object_plugin_nav.html b/dom/html/test/test_object_plugin_nav.html
new file mode 100644
index 000000000..91590e51a
--- /dev/null
+++ b/dom/html/test/test_object_plugin_nav.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=720130
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 720130</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="plugin-utils.js"></script>
+ <script type="application/javascript">
+ setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=720130">Mozilla Bug 720130</a>
+<p id="display"></p>
+<div id="content">
+ <input>
+ <object type="application/x-test"></object>
+ <button>foo</button>
+ <object tabindex='0' type="application/x-test"></object>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 720130 **/
+
+var gFocusCount = 0;
+var gFocusNb = 4;
+
+/**
+ * Check the focus navigation.
+ */
+function checkFocus() {
+ switch (gFocusCount) {
+ case 0:
+ is(document.activeElement, document.getElementsByTagName('a')[0],
+ "first focused element should be the link");
+ break;
+ case 1:
+ is(document.activeElement, document.getElementsByTagName('input')[0],
+ "second focused element should be the text field");
+ break;
+ case 2:
+ is(document.activeElement, document.getElementsByTagName('button')[0],
+ "third focused element should be the button");
+ break;
+ case 3:
+ is(document.activeElement, document.getElementsByTagName('object')[1],
+ "fourth focused element should be the object");
+ break;
+ }
+
+ gFocusCount++;
+}
+
+SimpleTest.waitForExplicitFinish();
+
+function doTest() {
+ is(document.activeElement, document.body);
+
+ // Preliminary check: tabindex should be -1 on the object.
+ is(document.getElementsByTagName('object')[0].tabIndex, -1,
+ "the plugin shouldn't get focus while navigating in the document");
+
+ document.addEventListener("focus", function() {
+ checkFocus();
+
+ if (gFocusCount != gFocusNb) {
+ synthesizeKey("VK_TAB", {});
+ return;
+ }
+
+ document.removeEventListener("focus", arguments.callee, true);
+
+ // Just make sure that .focus() still works.
+ var o = document.getElementsByTagName('object')[0];
+ o.onfocus = function() {
+ SimpleTest.finish();
+ o.onfocus = null;
+ };
+ o.focus();
+ }, true);
+
+ synthesizeKey("VK_TAB", {});
+}
+
+SimpleTest.waitForFocus(function () {
+ // Set the focus model so that links are focusable by the tab key even on Mac
+ SpecialPowers.pushPrefEnv({'set': [['accessibility.tabfocus', 7]]}, doTest);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_ol_attributes_reflection.html b/dom/html/test/test_ol_attributes_reflection.html
new file mode 100644
index 000000000..819ef60e4
--- /dev/null
+++ b/dom/html/test/test_ol_attributes_reflection.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLOLElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLOLElement attributes reflection **/
+
+// .reversed (boolean)
+reflectBoolean({
+ element: document.createElement("ol"),
+ attribute: "reversed",
+})
+
+// .start
+reflectInt({
+ element: document.createElement("ol"),
+ attribute: "start",
+ nonNegative: false,
+ defaultValue: 1,
+});
+
+// .type
+reflectString({
+ element: document.createElement("ol"),
+ attribute: "type"
+});
+
+// .compact
+reflectBoolean({
+ element: document.createElement("ol"),
+ attribute: "compact",
+})
+
+// Additional tests for ol.start behavior when li elements are added
+var ol = document.createElement("ol");
+var li = document.createElement("li");
+li.value = 42;
+ol.appendChild(li);
+is(ol.start, 1, "ol.start with one li child, li.value = 42:");
+li.value = -42;
+is(ol.start, 1, "ol.start with one li child, li.value = 42:");
+ol.removeAttribute("start");
+li.removeAttribute("value");
+ol.appendChild(document.createElement("li"));
+ol.reversed = true;
+todo_is(ol.start, 2, "ol.start with two li children, ol.reversed == true:");
+li.value = 42;
+todo_is(ol.start, 2, "ol.start with two li childern, ol.reversed == true:");
+ol.start = 42;
+is(ol.start, 42, "ol.start = 42:");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_option_defaultSelected.html b/dom/html/test/test_option_defaultSelected.html
new file mode 100644
index 000000000..f3994e784
--- /dev/null
+++ b/dom/html/test/test_option_defaultSelected.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=927796
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 927796</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=927796">Mozilla Bug 927796</a>
+<p id="display">
+<select id="s1">
+ <option selected>one</option>
+ <option>two</option>
+</select>
+<select id="s2" size="5">
+ <option selected>one</option>
+ <option>two</option>
+</select>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ /** Test for Bug 927796 **/
+ var s1 = $("s1");
+ s1.options[0].defaultSelected = false;
+ is(s1.options[0].selected, true,
+ "First option in combobox should still be selected");
+ is(s1.options[1].selected, false,
+ "Second option in combobox should not be selected");
+
+ var s2 = $("s2");
+ s2.options[0].defaultSelected = false;
+ is(s2.options[0].selected, false,
+ "First option in listbox should not be selected");
+ is(s2.options[1].selected, false,
+ "Second option in listbox should not be selected");
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/test_option_selected_state.html b/dom/html/test/test_option_selected_state.html
new file mode 100644
index 000000000..ba4a1a330
--- /dev/null
+++ b/dom/html/test/test_option_selected_state.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=942648
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 942648</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=942648">Mozilla Bug 942648</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <select>
+ <option value="1">1</option>
+ <option id="e1" value="2">2</option>
+ </select>
+ <select>
+ <option value="1">1</option>
+ <option id="e2" selected value="2">2</option>
+ </select>
+ <select>
+ <option value="1">1</option>
+ <option id="e3" selected="" value="2">2</option>
+ </select>
+ <select>
+ <option value="1">1</option>
+ <option id="e4" selected="selected" value="2">2</option>
+ </select>
+</pre>
+ <script type="application/javascript">
+
+ /** Test for Bug 942648 **/
+SimpleTest.waitForExplicitFinish();
+ window.onload = function() {
+ var e1 = document.getElementById('e1');
+ var e2 = document.getElementById('e2');
+ var e3 = document.getElementById('e3');
+ var e4 = document.getElementById('e4');
+ ok(!e1.selected, "e1 should not be selected");
+ ok(e2.selected, "e2 should be selected");
+ ok(e3.selected, "e3 should be selected");
+ ok(e4.selected, "e4 should be selected");
+ e1.setAttribute('selected', 'selected');
+ e2.setAttribute('selected', 'selected');
+ e3.setAttribute('selected', 'selected');
+ e4.setAttribute('selected', 'selected');
+ ok(e1.selected, "e1 should now be selected");
+ ok(e2.selected, "e2 should still be selected");
+ ok(e3.selected, "e3 should still be selected");
+ ok(e4.selected, "e4 should still be selected");
+ SimpleTest.finish();
+ };
+ </script>
+</body>
+</html>
diff --git a/dom/html/test/test_param_attributes_reflection.html b/dom/html/test/test_param_attributes_reflection.html
new file mode 100644
index 000000000..05d7949d5
--- /dev/null
+++ b/dom/html/test/test_param_attributes_reflection.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLParamElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLParamElement attributes reflection **/
+
+// .name
+reflectString({
+ element: document.createElement("param"),
+ attribute: "name",
+});
+
+// .value
+reflectString({
+ element: document.createElement("param"),
+ attribute: "value"
+});
+
+// .type
+reflectString({
+ element: document.createElement("param"),
+ attribute: "type"
+});
+
+// .valueType
+reflectString({
+ element: document.createElement("param"),
+ attribute: "valueType"
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_plugin.tst b/dom/html/test/test_plugin.tst
new file mode 100644
index 000000000..323fae03f
--- /dev/null
+++ b/dom/html/test/test_plugin.tst
@@ -0,0 +1 @@
+foobar
diff --git a/dom/html/test/test_q_attributes_reflection.html b/dom/html/test/test_q_attributes_reflection.html
new file mode 100644
index 000000000..ddb6c77d9
--- /dev/null
+++ b/dom/html/test/test_q_attributes_reflection.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLQuoteElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLQuoteElement attributes reflection **/
+
+// .cite
+reflectURL({
+ element: document.createElement("q"),
+ attribute: "cite",
+});
+
+reflectURL({
+ element: document.createElement("blockquote"),
+ attribute: "cite",
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_restore_from_parser_fragment.html b/dom/html/test/test_restore_from_parser_fragment.html
new file mode 100644
index 000000000..119ee2102
--- /dev/null
+++ b/dom/html/test/test_restore_from_parser_fragment.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=644959
+-->
+<head>
+ <title>Test for Bug 644959</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=644959">Mozilla Bug 644959</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 644959 **/
+
+var content = document.getElementById('content');
+
+function appendHTML(aParent, aElementString)
+{
+ aParent.innerHTML = "<form>" + aElementString + "</form>";
+}
+
+function clearHTML(aParent)
+{
+ aParent.innerHTML = "";
+}
+
+var tests = [
+ [ "button", "<button></button>" ],
+ [ "input", "<input>" ],
+ [ "textarea", "<textarea></textarea>" ],
+ [ "select", "<select></select>" ],
+];
+
+var element = null;
+
+for (var test of tests) {
+ appendHTML(content, test[1]);
+ element = content.getElementsByTagName(test[0])[0];
+ is(element.disabled, false, "element shouldn't be disabled");
+ element.disabled = true;
+ is(element.disabled, true, "element should be disabled");
+
+ clearHTML(content);
+
+ appendHTML(content, test[1]);
+ element = content.getElementsByTagName(test[0])[0];
+ is(element.disabled, false, "element shouldn't be disabled");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_rowscollection.html b/dom/html/test/test_rowscollection.html
new file mode 100644
index 000000000..7a726b8a8
--- /dev/null
+++ b/dom/html/test/test_rowscollection.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=772869
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 772869</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=772869">Mozilla Bug 772869</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <table id="f">
+ <thead>
+ <tr id="x"></tr>
+ </thead>
+ <tfoot>
+ <tr id="z"></tr>
+ <tr id="w"></tr>
+ </tfoot>
+ <tr id="x"></tr>
+ <tr id="y"></tr>
+ <tbody>
+ <tr id="z"></tr>
+ </tbody>
+ </table>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 772869 **/
+var x = $("f").rows;
+x.something = "another";
+var names = [];
+for (var name in x) {
+ names.push(name);
+}
+is(names.length, 10, "Should have 10 enumerated names");
+is(names[0], "0", "Enum entry 1")
+is(names[1], "1", "Enum entry 2")
+is(names[2], "2", "Enum entry 3")
+is(names[3], "3", "Enum entry 4")
+is(names[4], "4", "Enum entry 5")
+is(names[5], "5", "Enum entry 6")
+is(names[6], "something", "Enum entry 7")
+is(names[7], "item", "Enum entry 8")
+is(names[8], "namedItem", "Enum entry 9")
+is(names[9], "length", "Enum entry 10");
+
+names = Object.getOwnPropertyNames(x);
+is(names.length, 11, "Should have 11 items");
+is(names[0], "0", "Entry 1")
+is(names[1], "1", "Entry 2")
+is(names[2], "2", "Entry 3")
+is(names[3], "3", "Entry 4")
+is(names[4], "4", "Entry 5")
+is(names[5], "5", "Entry 6")
+is(names[6], "x", "Entry 7")
+is(names[7], "y", "Entry 8")
+is(names[8], "z", "Entry 9")
+is(names[9], "w", "Entry 10")
+is(names[10], "something", "Entry 11")
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_srcdoc-2.html b/dom/html/test/test_srcdoc-2.html
new file mode 100644
index 000000000..4e273473b
--- /dev/null
+++ b/dom/html/test/test_srcdoc-2.html
@@ -0,0 +1,57 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=802895
+-->
+ <head>
+<title>Test session history for srcdoc iframes introduced in bug 802895</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=802895">Mozilla Bug 802895</a>
+
+<iframe id="pframe" name="pframe" src="file_srcdoc-2.html"></iframe>
+<pre id="test">
+<script>
+
+ SimpleTest.waitForExplicitFinish();
+ var pframe = $("pframe");
+
+ //disable bfcache
+ pframe.contentWindow.addEventListener("unload", function () { }, false);
+
+ var loadState = 0;
+ pframe.onload = function () {
+ SimpleTest.executeSoon(function () {
+
+ var pDoc = pframe.contentDocument;
+
+ if (loadState == 0) {
+ var div = pDoc.createElement("div");
+ div.id = "modifyCheck";
+ div.innerHTML = "hello again";
+ pDoc.body.appendChild(div);
+ ok(pDoc.getElementById("modifyCheck"), "Child element not created");
+ pframe.src = "about:blank";
+ loadState = 1;
+ }
+ else if (loadState == 1) {
+ loadState = 2;
+ window.history.back();
+ }
+ else if (loadState == 2) {
+ ok(!pDoc.getElementById("modifyCheck"), "modifyCheck element shouldn't be present");
+ is(pDoc.getElementById("iframe").contentDocument.body.innerHTML,
+ "Hello World", "srcdoc iframe not present");
+ SimpleTest.finish();
+ }
+
+ })
+ };
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_srcdoc.html b/dom/html/test/test_srcdoc.html
new file mode 100644
index 000000000..f7e48fde9
--- /dev/null
+++ b/dom/html/test/test_srcdoc.html
@@ -0,0 +1,118 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=802895
+-->
+ <head>
+<title>Tests for srcdoc iframes introduced in bug 802895</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=802895">Mozilla Bug 802895</a>
+
+<iframe id="pframe" src="file_srcdoc.html"></iframe>
+
+<pre id="test">
+<script>
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+ var pframe = $("pframe");
+
+ var loadState = 0;
+ pframe.contentWindow.addEventListener("load", function () {
+
+ var pframeDoc = pframe.contentDocument;
+
+ var iframe = pframeDoc.getElementById("iframe");
+ var innerDoc = iframe.contentDocument;
+ var iframe1 = pframeDoc.getElementById("iframe1");
+ var innerDoc1 = iframe1.contentDocument;
+
+ var finish = false;
+ var finish1 = false;
+ var finish3 = false;
+
+
+
+ is(iframe.srcdoc, "Hello World", "Bad srcdoc attribute contents")
+
+ is(innerDoc.domain, document.domain, "Wrong domain");
+ is(innerDoc.referrer, pframeDoc.referrer, "Wrong referrer");
+ is(innerDoc.body.innerHTML, "Hello World", "Wrong body");
+ is(innerDoc.compatMode, "CSS1Compat", "Not standards compliant");
+
+ is(innerDoc1.domain, document.domain, "Wrong domain with src attribute");
+ is(innerDoc1.referrer, pframeDoc.referrer, "Wrong referrer with src attribute");
+ is(innerDoc1.body.innerHTML, "Goodbye World", "Wrong body with src attribute")
+ is(innerDoc1.compatMode, "CSS1Compat", "Not standards compliant with src attribute");
+
+ var iframe2 = pframeDoc.getElementById("iframe2");
+ var innerDoc2 = iframe2.contentDocument;
+ try {
+ innerDoc2.domain;
+ foundError = false;
+ }
+ catch (error) {
+ foundError = true;
+ }
+ ok(foundError, "srcdoc iframe not sandboxed");
+
+ //Test changed srcdoc attribute
+ iframe.onload = function () {
+
+ iframe = pframeDoc.getElementById("iframe");
+ innerDoc = iframe.contentDocument;
+
+ is(iframe.srcdoc, "Hello again", "Bad srcdoc attribute contents with srcdoc attribute changed");
+ is(innerDoc.domain, document.domain, "Wrong domain with srcdoc attribute changed");
+ is(innerDoc.referrer, pframeDoc.referrer, "Wrong referrer with srcdoc attribute changed");
+ is(innerDoc.body.innerHTML, "Hello again", "Wrong body with srcdoc attribute changed");
+ is(innerDoc.compatMode, "CSS1Compat", "Not standards compliant with srcdoc attribute changed");
+
+ finish = true;
+ if (finish && finish1 && finish3) {
+ SimpleTest.finish();
+ }
+ };
+
+ iframe.srcdoc = "Hello again";
+
+ var iframe3 = pframeDoc.getElementById("iframe3");
+
+ // Test srcdoc attribute removal
+ iframe3.onload = function () {
+ var innerDoc3 = iframe3.contentDocument;
+ is(innerDoc3.body.innerHTML, "Gone", "Bad srcdoc attribute removal");
+ finish3 = true;
+ if (finish && finish1 && finish3) {
+ SimpleTest.finish();
+ }
+ }
+
+ iframe3.removeAttribute("srcdoc");
+
+
+ var iframe1load = false;
+ iframe1.onload = function () {
+ iframe1load = true;
+ }
+
+ iframe1.src = "data:text/plain;charset=US-ASCII,Goodbyeeee";
+
+ // Need to test that changing the src doesn't change the iframe.
+ setTimeout(function () {
+ ok(!iframe1load, "Changing src attribute shouldn't cause a load when srcdoc is set");
+ finish1 = true;
+ if (finish && finish1 && finish3) {
+ SimpleTest.finish();
+ }
+ }, 2000);
+
+ }, false);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_style_attributes_reflection.html b/dom/html/test/test_style_attributes_reflection.html
new file mode 100644
index 000000000..c97bc8767
--- /dev/null
+++ b/dom/html/test/test_style_attributes_reflection.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for HTMLStyleElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLStyleElement attributes reflection **/
+
+var e = document.createElement("style");
+
+// .media
+reflectString({
+ element: e,
+ attribute: "media"
+});
+
+// .type
+reflectString({
+ element: e,
+ attribute: "type"
+});
+
+// .scoped
+reflectBoolean({
+ element: e,
+ attribute: "scoped"
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_track.html b/dom/html/test/test_track.html
new file mode 100644
index 000000000..bc23e4dae
--- /dev/null
+++ b/dom/html/test/test_track.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=833386
+-->
+<head>
+ <meta charset='utf-8'>
+ <title>Test for Bug 833386 - HTMLTrackElement</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/dom/html/test/reflect.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">
+reflectLimitedEnumerated({
+ element: document.createElement("track"),
+ attribute: "kind",
+ validValues: ["subtitles", "captions", "descriptions", "chapters",
+ "metadata"],
+ invalidValues: ["foo", "bar", "\u0000", "null", "", "subtitle", "caption",
+ "description", "chapter", "meta"],
+ defaultValue: { missing: "subtitles", invalid: "metadata" },
+});
+
+// Default attribute
+reflectBoolean({
+ element: document.createElement("track"),
+ attribute: "default"
+});
+
+// Label attribute
+reflectString({
+ element: document.createElement("track"),
+ attribute: "label",
+ otherValues: [ "foo", "BAR", "_FoO", "\u0000", "null", "white space" ]
+});
+
+// Source attribute
+reflectURL({
+ element: document.createElement("track"),
+ attribute: "src",
+ otherValues: ["foo", "bar", "\u0000", "null", ""]
+});
+
+// Source Language attribute
+reflectString({
+ element: document.createElement("track"),
+ attribute: "srclang",
+ otherValues: ["foo", "bar", "\u0000", "null", ""]
+});
+
+var track = document.createElement("track");
+is(track.readyState, 0, "Default ready state should be 0 (NONE).");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_ul_attributes_reflection.html b/dom/html/test/test_ul_attributes_reflection.html
new file mode 100644
index 000000000..7696a4f9f
--- /dev/null
+++ b/dom/html/test/test_ul_attributes_reflection.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for HTMLUListElement attributes reflection</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="reflect.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">
+
+/** Test for HTMLUListElement attributes reflection **/
+
+// .compact
+reflectBoolean({
+ element: document.createElement("ul"),
+ attribute: "compact"
+});
+
+// .type
+reflectString({
+ element: document.createElement("ul"),
+ attribute: "type"
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_video_wakelock.html b/dom/html/test/test_video_wakelock.html
new file mode 100644
index 000000000..52b05cda4
--- /dev/null
+++ b/dom/html/test/test_video_wakelock.html
@@ -0,0 +1,198 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=868943
+-->
+<head>
+ <title>Test for Bug 868943</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <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=868943">Mozilla Bug 868943</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 868943 **/
+
+function testVideoPlayPause() {
+ info("#1 testVideoPlayPause");
+
+ var lockState_cpu = true;
+ var lockState_screen = true;
+ var count_cpu = 0;
+ var count_screen = 0;
+
+ var content = document.getElementById('content');
+
+ var video = document.createElement('video');
+ ok(video.mozUseScreenWakeLock, "#1 Video element uses screen wake lock by default");
+ video.src = "wakelock.ogv";
+ content.appendChild(video);
+
+ var startDate;
+ function testVideoPlayPauseListener(topic, state) {
+ info("#1 topic=" + topic + ", state=" + state);
+
+ var locked = state == "locked-foreground" ||
+ state == "locked-background";
+
+ if (topic == "cpu") {
+ is(locked, lockState_cpu, "#1 Video element locked the cpu");
+ count_cpu++;
+ } else if (topic == "screen") {
+ is(locked, lockState_screen, "#1 Video element locked the screen");
+ count_screen++;
+ }
+
+ if (count_cpu == 1 && count_screen == 1) {
+ info("#1 Both cpu and screen are locked");
+ // The next step is to unlock the resource.
+ lockState_cpu = false;
+ lockState_screen = false;
+ video.pause();
+ startDate = new Date();
+ }
+
+ if (count_cpu == 2 && count_screen == 2) {
+ var diffDate = (new Date() - startDate);
+ ok(diffDate > 200, "#1 There was at least 200 milliseconds between the stop and the wakelock release");
+
+ content.removeChild(video);
+ navigator.mozPower.removeWakeLockListener(testVideoPlayPauseListener);
+ runTests();
+ }
+ }
+
+ navigator.mozPower.addWakeLockListener(testVideoPlayPauseListener);
+ video.play();
+}
+
+function testVideoPlay() {
+ info("#2 testVideoPlay");
+
+ var lockState_cpu = true;
+ var lockState_screen = true;
+ var count_cpu = 0;
+ var count_screen = 0;
+
+ var content = document.getElementById('content');
+
+ var video = document.createElement('video');
+ ok(video.mozUseScreenWakeLock, "#2 Video element uses screen wake lock by default");
+ video.src = "wakelock.ogv";
+ content.appendChild(video);
+
+ var startDate;
+ video.addEventListener('progress', function() {
+ startDate = new Date();
+ });
+
+ function testVideoPlayListener(topic, state) {
+ info("#2 topic=" + topic + ", state=" + state);
+
+ var locked = state == "locked-foreground" ||
+ state == "locked-background";
+
+ if (topic == "cpu") {
+ is(locked, lockState_cpu, "#2 Video element locked the cpu");
+ count_cpu++;
+ } else if (topic == "screen") {
+ is(locked, lockState_screen, "#2 Video element locked the screen");
+ count_screen++;
+ }
+
+ if (count_cpu == 1 && count_screen == 1) {
+ info("#2 Both cpu and screen are locked");
+ // The next step is to unlock the resource.
+ lockState_cpu = false;
+ lockState_screen = false;
+ } else if (count_cpu == 2 && count_screen == 2) {
+ var diffDate = (new Date() - startDate);
+ ok(diffDate > 200, "#2 There was at least milliseconds between the stop and the wakelock release");
+
+ content.removeChild(video);
+ navigator.mozPower.removeWakeLockListener(testVideoPlayListener);
+ runTests();
+ }
+ }
+
+ navigator.mozPower.addWakeLockListener(testVideoPlayListener);
+ video.play();
+}
+
+function testVideoNoScreenWakeLock() {
+ info("#3 testVideoNoScreenWakeLock");
+
+ var lockState_cpu = true;
+ var lockState_screen = false;
+ var count_cpu = 0;
+
+ var content = document.getElementById('content');
+
+ var video = document.createElement('video');
+ video.mozUseScreenWakeLock = false;
+ video.src = "wakelock.ogv";
+ content.appendChild(video);
+
+ var startDate;
+ function testVideoNoScreenWakeLockListener(topic, state) {
+ info("#3 topic=" + topic + ", state=" + state);
+
+ var locked = state == "locked-foreground" ||
+ state == "locked-background";
+
+ if (topic == "cpu") {
+ is(locked, lockState_cpu, "#3 Video element locked the cpu");
+ count_cpu++;
+ } else if (topic == "screen") {
+ is(locked, lockState_screen, "#3 Video element locked the screen");
+ }
+
+ if (count_cpu == 1) {
+ info("#3 Cpu is locked");
+ // The next step is to unlock the resource.
+ lockState_cpu = false;
+ video.pause();
+ startDate = new Date();
+ }
+
+ if (count_cpu == 2) {
+ var diffDate = (new Date() - startDate);
+ ok(diffDate > 200, "#3 There was at least 200 milliseconds between the stop and the wakelock release");
+
+ content.removeChild(video);
+ navigator.mozPower.removeWakeLockListener(testVideoNoScreenWakeLockListener);
+ runTests();
+ }
+ }
+
+ navigator.mozPower.addWakeLockListener(testVideoNoScreenWakeLockListener);
+ video.play();
+}
+
+var tests = [ testVideoPlayPause, testVideoPlay, testVideoNoScreenWakeLock ];
+function runTests() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.pop();
+ test();
+};
+
+SpecialPowers.pushPrefEnv({"set": [["media.wakelock_timeout", 500],
+ ["dom.wakelock.enabled", true]]}, runTests);
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_viewport.html b/dom/html/test/test_viewport.html
new file mode 100644
index 000000000..2299bef3d
--- /dev/null
+++ b/dom/html/test/test_viewport.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=436083
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta name="viewport" content="height=398, width=4224, minimum-scale=0.1,
+ initial-scale=2.3, maximum-scale=45.2, user-scalable=no">
+ <title>Test for Viewport META Tag Parsing</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=436083">Mozilla Bug 436083</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Viewport META Tag **/
+
+SimpleTest.waitForExplicitFinish();
+
+function testViewport() {
+
+ /* We need to access the document headers, which are chrome-only. */
+
+ /* Grab Viewport Metadata from the document header. */
+ var windowUtils = SpecialPowers.getDOMWindowUtils(window);
+ var vpWidth =
+ parseInt(windowUtils.getDocumentMetadata("viewport-width"));
+ var vpHeight =
+ parseInt(windowUtils.getDocumentMetadata("viewport-height"));
+ var vpInitialScale =
+ parseFloat(windowUtils.getDocumentMetadata("viewport-initial-scale"));
+ var vpMaxScale =
+ parseFloat(windowUtils.getDocumentMetadata("viewport-maximum-scale"));
+ var vpMinScale =
+ parseFloat(windowUtils.getDocumentMetadata("viewport-minimum-scale"));
+ var vpUserScalable =
+ windowUtils.getDocumentMetadata("viewport-user-scalable");
+
+ is(vpWidth, 4224, "Should get proper width");
+ is(vpHeight, 398, "Should get proper height");
+ is(vpInitialScale, 2.3, "Should get proper initial scale");
+ is(vpMaxScale, 45.2, "Should get proper max scale");
+ is(vpMinScale, 0.1, "Should get proper min scale");
+ is(vpUserScalable, "no", "Should get proper user scalable parameter");
+}
+
+addLoadEvent(testViewport);
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_viewport_resize.html b/dom/html/test/test_viewport_resize.html
new file mode 100644
index 000000000..711d2afa0
--- /dev/null
+++ b/dom/html/test/test_viewport_resize.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1135812
+-->
+<head>
+ <title>Test for Bug 1135812</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=1135812">Mozilla Bug 1135812</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+
+<iframe style="width: 50px;"
+ srcdoc='<picture><source srcset="data:,a" media="(min-width: 150px)" /><source srcset="data:,b" media="(min-width: 100px)" /><img src="data:,c" /></picture>'></iframe>
+<script>
+ SimpleTest.waitForExplicitFinish();
+ addEventListener('load', function() {
+ var iframe = document.querySelector('iframe');
+ var img = iframe.contentDocument.querySelector('img');
+ is(img.currentSrc, 'data:,c');
+
+ img.onload = function() {
+ is(img.currentSrc, 'data:,a');
+ img.onload = function() {
+ is(img.currentSrc, 'data:,b');
+ SimpleTest.finish();
+ }
+ img.onerror = img.onload;
+ iframe.style.width = '120px';
+ };
+ img.onerror = img.onload;
+
+ iframe.style.width = '200px';
+ }, true);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/html/test/test_window_open_close.html b/dom/html/test/test_window_open_close.html
new file mode 100644
index 000000000..c7e3b96f5
--- /dev/null
+++ b/dom/html/test/test_window_open_close.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <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>
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// Opens a popup. Link should load in main browser window. Popup should be closed when link clicked.
+function openWindow1() {
+ return window.open('file_window_open_close_outer.html','','width=300,height=200');
+}
+
+// Opens a new tab T1. Link opens in another new tab T2. T1 should close when link clicked.
+function openWindow2() {
+ return window.open('file_window_open_close_outer.html');
+}
+
+// Opens a new window. Link should open in a new tab of that window, but then both windows should close.
+function openWindow3() {
+ return window.open('file_window_open_close_outer.html', '', 'toolbar=1');
+}
+
+var TESTS = [openWindow1, openWindow2, openWindow3];
+
+function popupLoad(win)
+{
+ info("Sending click");
+ sendMouseEvent({type: "click"}, "link", win);
+ ok(true, "Didn't crash");
+
+ next();
+}
+
+function next()
+{
+ if (TESTS.length == 0) {
+ SimpleTest.finish();
+ } else {
+ var test = TESTS.shift();
+ var w = test();
+ w.addEventListener("load", (e) => popupLoad(w));
+ }
+}
+</script>
+
+<body onload="next()">
+</body>
+</html>
diff --git a/dom/html/test/wakelock.ogg b/dom/html/test/wakelock.ogg
new file mode 100644
index 000000000..d7f6a0ccf
--- /dev/null
+++ b/dom/html/test/wakelock.ogg
Binary files differ
diff --git a/dom/html/test/wakelock.ogv b/dom/html/test/wakelock.ogv
new file mode 100644
index 000000000..093158432
--- /dev/null
+++ b/dom/html/test/wakelock.ogv
Binary files differ