summaryrefslogtreecommitdiffstats
path: root/layout/forms
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 /layout/forms
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 'layout/forms')
-rw-r--r--layout/forms/crashtests/1102791.html33
-rw-r--r--layout/forms/crashtests/1140216.html20
-rw-r--r--layout/forms/crashtests/1182414.html17
-rw-r--r--layout/forms/crashtests/1212688.html27
-rw-r--r--layout/forms/crashtests/1228670.xhtml7
-rw-r--r--layout/forms/crashtests/1279354.html21
-rw-r--r--layout/forms/crashtests/166750-1.html14
-rw-r--r--layout/forms/crashtests/200347-1.html8
-rw-r--r--layout/forms/crashtests/203041-1.html24
-rw-r--r--layout/forms/crashtests/213390-1.html23
-rw-r--r--layout/forms/crashtests/258101-1.html18
-rw-r--r--layout/forms/crashtests/266225-1.html7
-rw-r--r--layout/forms/crashtests/310426-1.xhtml9
-rw-r--r--layout/forms/crashtests/310520-1.xhtml19
-rw-r--r--layout/forms/crashtests/315752-1.xhtml21
-rw-r--r--layout/forms/crashtests/317502-1.xhtml13
-rw-r--r--layout/forms/crashtests/321894.html17
-rw-r--r--layout/forms/crashtests/323499-1.html21
-rw-r--r--layout/forms/crashtests/343510-1.html4
-rw-r--r--layout/forms/crashtests/363696-1.xul10
-rw-r--r--layout/forms/crashtests/363696-2.html2
-rw-r--r--layout/forms/crashtests/363696-3.html5
-rw-r--r--layout/forms/crashtests/366205-1.html11
-rw-r--r--layout/forms/crashtests/366537-1.xhtml32
-rw-r--r--layout/forms/crashtests/367587-1.html37
-rw-r--r--layout/forms/crashtests/370703-1.html30
-rw-r--r--layout/forms/crashtests/370940-1.html28
-rw-r--r--layout/forms/crashtests/370967.html13
-rw-r--r--layout/forms/crashtests/373586-1.xhtml40
-rw-r--r--layout/forms/crashtests/375299-binding.xml4
-rw-r--r--layout/forms/crashtests/375299.html17
-rw-r--r--layout/forms/crashtests/378369.html19
-rw-r--r--layout/forms/crashtests/378413-1.xhtml16
-rw-r--r--layout/forms/crashtests/380116-1.xhtml11
-rw-r--r--layout/forms/crashtests/382212-1.xhtml7
-rw-r--r--layout/forms/crashtests/382610-1.html11
-rw-r--r--layout/forms/crashtests/383887-1.html20
-rw-r--r--layout/forms/crashtests/386554-1.html14
-rw-r--r--layout/forms/crashtests/388374-1.xhtml22
-rw-r--r--layout/forms/crashtests/388374-2.html25
-rw-r--r--layout/forms/crashtests/393656-1.xhtml13
-rw-r--r--layout/forms/crashtests/393656-2.xhtml22
-rw-r--r--layout/forms/crashtests/399262.html50
-rw-r--r--layout/forms/crashtests/402852-1.html2
-rw-r--r--layout/forms/crashtests/403148-1.html22
-rw-r--r--layout/forms/crashtests/404118-1.html5
-rw-r--r--layout/forms/crashtests/404123-1.html12
-rw-r--r--layout/forms/crashtests/407066.html1
-rw-r--r--layout/forms/crashtests/451316.html7
-rw-r--r--layout/forms/crashtests/455451-1.html17
-rw-r--r--layout/forms/crashtests/457537-1.html17
-rw-r--r--layout/forms/crashtests/457537-2.html17
-rw-r--r--layout/forms/crashtests/478219-1.xhtml7
-rw-r--r--layout/forms/crashtests/498698-1.html6
-rw-r--r--layout/forms/crashtests/513113-1.html6
-rw-r--r--layout/forms/crashtests/538062-1.xhtml20
-rw-r--r--layout/forms/crashtests/570624-1.html15
-rw-r--r--layout/forms/crashtests/578604-1.html17
-rw-r--r--layout/forms/crashtests/590302-1.xhtml4
-rw-r--r--layout/forms/crashtests/626014.xhtml20
-rw-r--r--layout/forms/crashtests/639733.xhtml26
-rw-r--r--layout/forms/crashtests/669767.html14
-rw-r--r--layout/forms/crashtests/682684-binding.xml4
-rw-r--r--layout/forms/crashtests/682684.xhtml3
-rw-r--r--layout/forms/crashtests/865602.html9
-rw-r--r--layout/forms/crashtests/893332-1.html10
-rw-r--r--layout/forms/crashtests/944198.html9
-rw-r--r--layout/forms/crashtests/949891.xhtml5
-rw-r--r--layout/forms/crashtests/959311.html17
-rw-r--r--layout/forms/crashtests/960277-2.html14
-rw-r--r--layout/forms/crashtests/997709-1.html5
-rw-r--r--layout/forms/crashtests/crashtests.list69
-rw-r--r--layout/forms/moz.build57
-rw-r--r--layout/forms/nsButtonFrameRenderer.cpp516
-rw-r--r--layout/forms/nsButtonFrameRenderer.h88
-rw-r--r--layout/forms/nsColorControlFrame.cpp147
-rw-r--r--layout/forms/nsColorControlFrame.h65
-rw-r--r--layout/forms/nsComboboxControlFrame.cpp1715
-rw-r--r--layout/forms/nsComboboxControlFrame.h328
-rw-r--r--layout/forms/nsDateTimeControlFrame.cpp414
-rw-r--r--layout/forms/nsDateTimeControlFrame.h119
-rw-r--r--layout/forms/nsFieldSetFrame.cpp703
-rw-r--r--layout/forms/nsFieldSetFrame.h110
-rw-r--r--layout/forms/nsFileControlFrame.cpp506
-rw-r--r--layout/forms/nsFileControlFrame.h167
-rw-r--r--layout/forms/nsFormControlFrame.cpp225
-rw-r--r--layout/forms/nsFormControlFrame.h129
-rw-r--r--layout/forms/nsGfxButtonControlFrame.cpp237
-rw-r--r--layout/forms/nsGfxButtonControlFrame.h67
-rw-r--r--layout/forms/nsGfxCheckboxControlFrame.cpp147
-rw-r--r--layout/forms/nsGfxCheckboxControlFrame.h40
-rw-r--r--layout/forms/nsGfxRadioControlFrame.cpp93
-rw-r--r--layout/forms/nsGfxRadioControlFrame.h32
-rw-r--r--layout/forms/nsHTMLButtonControlFrame.cpp465
-rw-r--r--layout/forms/nsHTMLButtonControlFrame.h115
-rw-r--r--layout/forms/nsIComboboxControlFrame.h86
-rw-r--r--layout/forms/nsIFormControlFrame.h42
-rw-r--r--layout/forms/nsIListControlFrame.h98
-rw-r--r--layout/forms/nsISelectControlFrame.h50
-rw-r--r--layout/forms/nsITextControlFrame.h53
-rw-r--r--layout/forms/nsImageControlFrame.cpp206
-rw-r--r--layout/forms/nsLegendFrame.cpp94
-rw-r--r--layout/forms/nsLegendFrame.h37
-rw-r--r--layout/forms/nsListControlFrame.cpp2537
-rw-r--r--layout/forms/nsListControlFrame.h477
-rw-r--r--layout/forms/nsMeterFrame.cpp297
-rw-r--r--layout/forms/nsMeterFrame.h94
-rw-r--r--layout/forms/nsNumberControlFrame.cpp799
-rw-r--r--layout/forms/nsNumberControlFrame.h216
-rw-r--r--layout/forms/nsProgressFrame.cpp308
-rw-r--r--layout/forms/nsProgressFrame.h102
-rw-r--r--layout/forms/nsRangeFrame.cpp949
-rw-r--r--layout/forms/nsRangeFrame.h215
-rw-r--r--layout/forms/nsSelectsAreaFrame.cpp206
-rw-r--r--layout/forms/nsSelectsAreaFrame.h49
-rw-r--r--layout/forms/nsTextControlFrame.cpp1506
-rw-r--r--layout/forms/nsTextControlFrame.h319
-rw-r--r--layout/forms/test/bug287446_subframe.html38
-rw-r--r--layout/forms/test/bug477700_subframe.html39
-rw-r--r--layout/forms/test/bug536567_iframe.html9
-rw-r--r--layout/forms/test/bug536567_subframe.html14
-rw-r--r--layout/forms/test/bug564115_window.html10
-rw-r--r--layout/forms/test/bug665540_window.xul18
-rw-r--r--layout/forms/test/chrome.ini11
-rw-r--r--layout/forms/test/mochitest.ini66
-rw-r--r--layout/forms/test/test_bug1111995.html60
-rw-r--r--layout/forms/test/test_bug1301290.html49
-rw-r--r--layout/forms/test/test_bug1305282.html59
-rw-r--r--layout/forms/test/test_bug231389.html55
-rw-r--r--layout/forms/test/test_bug287446.html75
-rw-r--r--layout/forms/test/test_bug345267.html98
-rw-r--r--layout/forms/test/test_bug346043.html65
-rw-r--r--layout/forms/test/test_bug348236.html125
-rw-r--r--layout/forms/test/test_bug353539.html52
-rw-r--r--layout/forms/test/test_bug365410.html134
-rw-r--r--layout/forms/test/test_bug378670.html55
-rw-r--r--layout/forms/test/test_bug402198.html77
-rw-r--r--layout/forms/test/test_bug411236.html80
-rw-r--r--layout/forms/test/test_bug446663.html80
-rw-r--r--layout/forms/test/test_bug476308.html31
-rw-r--r--layout/forms/test/test_bug477531.html65
-rw-r--r--layout/forms/test/test_bug477700.html60
-rw-r--r--layout/forms/test/test_bug478219.xhtml38
-rw-r--r--layout/forms/test/test_bug534785.html88
-rw-r--r--layout/forms/test/test_bug536567_perwindowpb.html215
-rw-r--r--layout/forms/test/test_bug542914.html115
-rw-r--r--layout/forms/test/test_bug549170.html74
-rw-r--r--layout/forms/test/test_bug562447.html62
-rw-r--r--layout/forms/test/test_bug563642.html85
-rw-r--r--layout/forms/test/test_bug564115.html55
-rw-r--r--layout/forms/test/test_bug571352.html86
-rw-r--r--layout/forms/test/test_bug572406.html48
-rw-r--r--layout/forms/test/test_bug572649.html64
-rw-r--r--layout/forms/test/test_bug595310.html69
-rw-r--r--layout/forms/test/test_bug620936.html35
-rw-r--r--layout/forms/test/test_bug644542.html63
-rw-r--r--layout/forms/test/test_bug665540.html118
-rw-r--r--layout/forms/test/test_bug672810.html120
-rw-r--r--layout/forms/test/test_bug704049.html50
-rw-r--r--layout/forms/test/test_bug717878_input_scroll.html82
-rw-r--r--layout/forms/test/test_bug869314.html54
-rw-r--r--layout/forms/test/test_bug903715.html81
-rw-r--r--layout/forms/test/test_bug935876.html495
-rw-r--r--layout/forms/test/test_bug957562.html43
-rw-r--r--layout/forms/test/test_bug960277.html29
-rw-r--r--layout/forms/test/test_bug961363.html96
-rw-r--r--layout/forms/test/test_listcontrol_search.html46
-rw-r--r--layout/forms/test/test_select_prevent_default.html119
-rw-r--r--layout/forms/test/test_select_vertical.html75
-rw-r--r--layout/forms/test/test_textarea_resize.html102
170 files changed, 20419 insertions, 0 deletions
diff --git a/layout/forms/crashtests/1102791.html b/layout/forms/crashtests/1102791.html
new file mode 100644
index 000000000..5bcdd80a6
--- /dev/null
+++ b/layout/forms/crashtests/1102791.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html class="reftest-print"><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 1102791</title>
+ <style type="text/css">
+
+html,body {
+ color:black; background-color:white; font-size:16px; padding:0; margin:0;
+}
+
+button {
+ position: absolute;
+ -moz-appearance: none;
+ background: transparent;
+ padding: 0;
+ border-style:none;
+}
+button::before {
+ position: absolute;
+ content: "::before";
+ width: 10px;
+ height: 200em;
+ border: 1px solid black;
+}
+
+ </style>
+</head>
+<body>
+
+<button></button>
+
+</body>
+</html>
diff --git a/layout/forms/crashtests/1140216.html b/layout/forms/crashtests/1140216.html
new file mode 100644
index 000000000..72612b8b3
--- /dev/null
+++ b/layout/forms/crashtests/1140216.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+
+input { -moz-appearance: textfield; }
+
+</style>
+<script>
+
+function boom()
+{
+ window.getComputedStyle(x, "::-moz-number-spin-down").getPropertyValue("color");
+}
+
+</script>
+<body onload="boom();">
+<input type="number" id="x">
+</body>
+</html>
diff --git a/layout/forms/crashtests/1182414.html b/layout/forms/crashtests/1182414.html
new file mode 100644
index 000000000..5d15f3905
--- /dev/null
+++ b/layout/forms/crashtests/1182414.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-print">
+<head>
+<meta charset="UTF-8">
+<style type="text/css">
+#menu { position: fixed; left: 0px; top: 0px; }
+</style>
+</head>
+<body>
+ <svg id="canvas" width="2427" height="2295.5" version="1.1" xmlns="http://www.w3.org/2000/svg"></svg>
+
+<div id="menu">
+ <input id="chooseSize" type="range">
+</div>
+</body>
+</html>
+
diff --git a/layout/forms/crashtests/1212688.html b/layout/forms/crashtests/1212688.html
new file mode 100644
index 000000000..68262d907
--- /dev/null
+++ b/layout/forms/crashtests/1212688.html
@@ -0,0 +1,27 @@
+<style type="text/css">
+ optgroup {overflow-x: hidden;}
+</style>
+<select>
+ <optgroup label="optgroup">
+ <option>1</option>
+ <option>2</option>
+ <option>3</option>
+ <option>4</option>
+ <option>5</option>
+ <option>6</option>
+ <option>7</option>
+ <option>8</option>
+ <option>9</option>
+ <option>10</option>
+ <option>11</option>
+ <option>12</option>
+ <option>13</option>
+ <option>14</option>
+ <option>15</option>
+ <option>16</option>
+ <option>17</option>
+ <option>18</option>
+ <option>19</option>
+ <option>20</option>
+</optgroup>
+</select>
diff --git a/layout/forms/crashtests/1228670.xhtml b/layout/forms/crashtests/1228670.xhtml
new file mode 100644
index 000000000..cc8d309c0
--- /dev/null
+++ b/layout/forms/crashtests/1228670.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <select>
+ <optgroup style="display: list-item; list-style: inside;"></optgroup>
+ </select>
+ </body>
+</html>
diff --git a/layout/forms/crashtests/1279354.html b/layout/forms/crashtests/1279354.html
new file mode 100644
index 000000000..1f4f40942
--- /dev/null
+++ b/layout/forms/crashtests/1279354.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html class="reftest-print"><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 1279354</title>
+</head>
+<body>
+
+<div style="position:fixed"><progress></progress></div>
+1
+<br style="page-break-after: always">
+2
+<br style="page-break-after: always">
+3
+<br style="page-break-after: always">
+
+</body>
+</html>
diff --git a/layout/forms/crashtests/166750-1.html b/layout/forms/crashtests/166750-1.html
new file mode 100644
index 000000000..d873be1b2
--- /dev/null
+++ b/layout/forms/crashtests/166750-1.html
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head><title>Killer</title></head>
+<body>
+ <form style="overflow: auto;">
+ <select style="position: fixed;">
+ <option>First</option>
+ <option>Second</option>
+ <option>Third</option>
+ </select>
+ </form>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/forms/crashtests/200347-1.html b/layout/forms/crashtests/200347-1.html
new file mode 100644
index 000000000..b41ec8365
--- /dev/null
+++ b/layout/forms/crashtests/200347-1.html
@@ -0,0 +1,8 @@
+<html>
+ <body>
+ <fieldset style="width: 300px; position: fixed">
+ <legend>Crash test</legend>
+ <div style="float: right; background-color: orange; border: 1px solid black">Hello, my name is Inigo Montoya.</div> hello world content is the best content around, I love hello world content to death, especially when it wraps; that just gives me the chills. Anything less than hello world content is uncivilized.
+ </fieldset>
+ </body>
+</html>
diff --git a/layout/forms/crashtests/203041-1.html b/layout/forms/crashtests/203041-1.html
new file mode 100644
index 000000000..32d95b40e
--- /dev/null
+++ b/layout/forms/crashtests/203041-1.html
@@ -0,0 +1,24 @@
+<html>
+
+<head></head>
+
+<body>
+<form method="post" action="#" enctype="multipart/form-data" name="content">
+
+ <div id="sshot" style="position:absolute; left:0; top:70; width:600;
+visibility:visible">
+ <input type="file" name="sshot-1" size="20">
+ </div>
+
+ <div id="comment" style="position:absolute; left:0; top:70; width:600;
+visibility:hidden"></div>
+
+ <script language="JavaScript">
+ <!--
+ document.documentElement.offsetHeight;
+ document.getElementById('sshot').style.display = 'none';
+ document.getElementById('comment').style.display = 'none';
+ // -->
+ </script>
+</form>
+</body></html> \ No newline at end of file
diff --git a/layout/forms/crashtests/213390-1.html b/layout/forms/crashtests/213390-1.html
new file mode 100644
index 000000000..0ff94ec54
--- /dev/null
+++ b/layout/forms/crashtests/213390-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3c.org/TR/1999/REC-html401-19991224/loose.dtd">
+
+<HTML>
+ <BODY>
+
+ <EMBED>
+
+ <TABLE>
+ <TR>
+ <TD STYLE="FONT: 10px Arial;">
+ <A STYLE="FONT: 11px Arial;">1</A>
+ </TD>
+ </TR>
+ </TABLE>
+
+ <DIV STYLE="POSITION: absolute;">
+ <FORM>
+ <SELECT STYLE="FONT: 11px Arial;">
+ </FORM>
+ </DIV>
+
+ </BODY>
+</HTML>
diff --git a/layout/forms/crashtests/258101-1.html b/layout/forms/crashtests/258101-1.html
new file mode 100644
index 000000000..245917cbf
--- /dev/null
+++ b/layout/forms/crashtests/258101-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+ <meta content="text/html; charset=ISO-8859-1"
+ http-equiv="content-type">
+ <title>Crash Testcase</title>
+ <script type="text/javascript">
+function crash()
+{
+ var inp = document.getElementById("theinp");
+ inp.type = "file";
+}
+ </script>
+</head>
+<body onload="crash();">
+<input id="theinp" type="text">
+</body>
+</html>
diff --git a/layout/forms/crashtests/266225-1.html b/layout/forms/crashtests/266225-1.html
new file mode 100644
index 000000000..c714125dd
--- /dev/null
+++ b/layout/forms/crashtests/266225-1.html
@@ -0,0 +1,7 @@
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<FIELDSET STYLE="float:right; text-indent:999px;">Test</FIELDSET>
+</BODY>
+</HTML>
diff --git a/layout/forms/crashtests/310426-1.xhtml b/layout/forms/crashtests/310426-1.xhtml
new file mode 100644
index 000000000..683dba679
--- /dev/null
+++ b/layout/forms/crashtests/310426-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<body>
+
+<select><span style="position: absolute;" /></select>
+
+</body>
+
+</html>
diff --git a/layout/forms/crashtests/310520-1.xhtml b/layout/forms/crashtests/310520-1.xhtml
new file mode 100644
index 000000000..5a76a6de1
--- /dev/null
+++ b/layout/forms/crashtests/310520-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script><![CDATA[
+
+function init()
+{
+ var select = document.getElementsByTagName("select")[0];
+ select.parentNode.removeChild(select);
+}
+
+window.addEventListener("load", init, 0);
+
+]]></script>
+
+
+</head>
+<body><select><input/></select></body>
+</html>
diff --git a/layout/forms/crashtests/315752-1.xhtml b/layout/forms/crashtests/315752-1.xhtml
new file mode 100644
index 000000000..66d3d793d
--- /dev/null
+++ b/layout/forms/crashtests/315752-1.xhtml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace">
+<head>
+<title>Settings - Unclassified NewsBoard</title>
+
+
+</head>
+<body>
+
+<select name="Language">
+ <option value="" style="clear: right;">
+ Automatic </option>
+ <option value="de" style="clear: right;">
+ <span alt="" style="float: right; margin-top: 1px;"> Deutsch</span> <small>(de)</small> </option>
+ <option value="en" selected="selected" style="clear: right;">
+ <span alt="" style="float: right; margin-top: 1px;"> English</span> <small>(en)</small> </option>
+</select>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/forms/crashtests/317502-1.xhtml b/layout/forms/crashtests/317502-1.xhtml
new file mode 100644
index 000000000..7ef5f0b4c
--- /dev/null
+++ b/layout/forms/crashtests/317502-1.xhtml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+ <title>Testcase, bug 317502</title>
+</head>
+<body onload="document.getElementsByTagName('input')[0].style.width='200px'">
+
+<input type="file" style="position: fixed" />
+
+</body>
+</html>
diff --git a/layout/forms/crashtests/321894.html b/layout/forms/crashtests/321894.html
new file mode 100644
index 000000000..0a82645ba
--- /dev/null
+++ b/layout/forms/crashtests/321894.html
@@ -0,0 +1,17 @@
+<html><head>
+<title>
+Testcase bug 321894 - ASSERTION: RemovedAsPrimaryFrame called after PreDestroy: 'PR_FALSE'
+</title>
+</head>
+<body>
+This shouldn't assert in a debug build
+<span>
+ <script>var x=document.body.offsetHeight;</script>
+ <div>
+ <span style="float:left;">
+ <input type="text">
+ </span>
+ </div>
+</span>
+</body>
+</html>
diff --git a/layout/forms/crashtests/323499-1.html b/layout/forms/crashtests/323499-1.html
new file mode 100644
index 000000000..bc015aa76
--- /dev/null
+++ b/layout/forms/crashtests/323499-1.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+
+<style>
+select {
+ visibility: collapse;
+}
+
+body {
+ display: -moz-grid-group;
+}
+
+</style>
+</head>
+
+<body>
+
+<select><option>a</option></select>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/forms/crashtests/343510-1.html b/layout/forms/crashtests/343510-1.html
new file mode 100644
index 000000000..00cb94f21
--- /dev/null
+++ b/layout/forms/crashtests/343510-1.html
@@ -0,0 +1,4 @@
+<select style="position: absolute;">
+<meta>
+<option style="position: absolute;">
+<body style="display: block;">
diff --git a/layout/forms/crashtests/363696-1.xul b/layout/forms/crashtests/363696-1.xul
new file mode 100644
index 000000000..19f938deb
--- /dev/null
+++ b/layout/forms/crashtests/363696-1.xul
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<body>
+
+<xul:hbox>
+ <input type="file" />
+</xul:hbox>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/forms/crashtests/363696-2.html b/layout/forms/crashtests/363696-2.html
new file mode 100644
index 000000000..40883b304
--- /dev/null
+++ b/layout/forms/crashtests/363696-2.html
@@ -0,0 +1,2 @@
+<div style="display: -moz-grid-line;">
+<input type="file"> \ No newline at end of file
diff --git a/layout/forms/crashtests/363696-3.html b/layout/forms/crashtests/363696-3.html
new file mode 100644
index 000000000..e1d38076e
--- /dev/null
+++ b/layout/forms/crashtests/363696-3.html
@@ -0,0 +1,5 @@
+<html><head></head><body>
+
+<span style="display: -moz-box;"><input type="file"><textarea></textarea></span>
+
+</body></html> \ No newline at end of file
diff --git a/layout/forms/crashtests/366205-1.html b/layout/forms/crashtests/366205-1.html
new file mode 100644
index 000000000..2a3786afe
--- /dev/null
+++ b/layout/forms/crashtests/366205-1.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+</head>
+<body onload='var yar=document.getElementById("yar"); yar.parentNode.removeChild(yar); document.getElementById("foo").removeAttribute("multiple");'>
+
+<div id="yar">Clicking the button below should not trigger an assertion</div>
+
+<select id="foo" multiple><option>A</option></select>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/forms/crashtests/366537-1.xhtml b/layout/forms/crashtests/366537-1.xhtml
new file mode 100644
index 000000000..b799cd2ca
--- /dev/null
+++ b/layout/forms/crashtests/366537-1.xhtml
@@ -0,0 +1,32 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<head>
+<script>
+
+function boom()
+{
+ var fieldset = document.getElementById("fieldset");
+ var h3 = document.getElementById("h3");
+ var legend = document.getElementById("legend");
+
+ fieldset.appendChild(legend);
+ fieldset.appendChild(h3);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+
+<body onload="setTimeout(boom, 30);">
+
+<legend id="legend">legend</legend>
+
+<h3 id="h3">H3</h3>
+
+<select><option>option<fieldset id="fieldset"></fieldset></option></select>
+
+</body>
+
+</html>
diff --git a/layout/forms/crashtests/367587-1.html b/layout/forms/crashtests/367587-1.html
new file mode 100644
index 000000000..8f55ec458
--- /dev/null
+++ b/layout/forms/crashtests/367587-1.html
@@ -0,0 +1,37 @@
+<html class="reftest-wait">
+<head>
+
+<style>
+
+#opt1 { display: table-footer-group; }
+#opt1 { visibility: collapse; }
+#opt1 { overflow: -moz-scrollbars-vertical; }
+
+#opt2 { display: table-cell; }
+#opt2 { clear: left; }
+
+select { width: 1px; }
+
+</style>
+
+<script>
+
+function boom()
+{
+ document.getElementById("opt2").style.clear = "none";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+<select multiple>
+<option id="opt1">A</option>
+<option id="opt2">B</option>
+</select>
+
+</body>
+</html>
diff --git a/layout/forms/crashtests/370703-1.html b/layout/forms/crashtests/370703-1.html
new file mode 100644
index 000000000..b447b180b
--- /dev/null
+++ b/layout/forms/crashtests/370703-1.html
@@ -0,0 +1,30 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("M").style.width = "6em";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+<body onload="setTimeout(boom, 30);">
+
+<div style="position:absolute; top: 50px; left: 50px; border: 2px solid orange;">
+
+ <select>
+ <option id="M">M</option>
+ </select>
+
+ <br>
+
+ <select style="width: 200%">
+ <option style="visibility: collapse; overflow: auto; display: table-footer-group;">X</option>
+ </select>
+
+</div>
+
+</body>
+</html>
diff --git a/layout/forms/crashtests/370940-1.html b/layout/forms/crashtests/370940-1.html
new file mode 100644
index 000000000..ccaeb9643
--- /dev/null
+++ b/layout/forms/crashtests/370940-1.html
@@ -0,0 +1,28 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+var HTML_NS = "http://www.w3.org/1999/xhtml";
+
+function boom1()
+{
+ var newSpan = document.createElementNS(HTML_NS, "span");
+ document.body.appendChild(newSpan);
+
+ setTimeout(boom2, 30);
+}
+
+function boom2()
+{
+ var newDiv = document.createElementNS(HTML_NS, "div");
+ document.getElementById("s").appendChild(newDiv);
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="setTimeout(boom1, 30);">
+ <p>العربي</p>
+ <span id="s"></span><input><select></select><isindex><span></span>
+</body>
+</html>
diff --git a/layout/forms/crashtests/370967.html b/layout/forms/crashtests/370967.html
new file mode 100644
index 000000000..be7854b41
--- /dev/null
+++ b/layout/forms/crashtests/370967.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html>
+<head>
+<title>Untitled</title>
+</head>
+<body>
+Focus the input of the isindex, then do a reload, Mozilla should not crash
+<isindex autofocus>
+</label>
+
+</body>
+</html>
diff --git a/layout/forms/crashtests/373586-1.xhtml b/layout/forms/crashtests/373586-1.xhtml
new file mode 100644
index 000000000..070315d15
--- /dev/null
+++ b/layout/forms/crashtests/373586-1.xhtml
@@ -0,0 +1,40 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="foo">
+ <content>
+ <children xmlns="http://www.mozilla.org/xbl" />
+ </content>
+ </binding>
+</bindings>
+
+<script>
+function boom()
+{
+ document.getElementById("div").style.MozBinding = "url('#foo')";
+
+ var opt1 = document.getElementById("opt1");
+ opt1.removeChild(opt1.firstChild);
+
+ document.getElementById("textarea").value += " ";
+
+ document.documentElement.removeAttribute("class")
+}
+</script>
+
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+<div id="div">
+ <textarea rows="3" cols="5" id="textarea"></textarea>
+</div>
+
+<select>
+ <option id="opt1">opt1</option>
+</select>
+
+</body>
+</html>
+
diff --git a/layout/forms/crashtests/375299-binding.xml b/layout/forms/crashtests/375299-binding.xml
new file mode 100644
index 000000000..ca76a3389
--- /dev/null
+++ b/layout/forms/crashtests/375299-binding.xml
@@ -0,0 +1,4 @@
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="a">
+<content><children/></content>
+</binding></bindings> \ No newline at end of file
diff --git a/layout/forms/crashtests/375299.html b/layout/forms/crashtests/375299.html
new file mode 100644
index 000000000..fd43d1c63
--- /dev/null
+++ b/layout/forms/crashtests/375299.html
@@ -0,0 +1,17 @@
+<html><head>
+<title>Testcase bug - Crash [@ nsFileControlFrame::CreateAnonymousContent] when removing stylesheet with binding and removing file input</title>
+<style>
+body {-moz-binding:url("375299-binding.xml#a"); }
+</style>
+</head><body onload="doe()">
+<input type="file">
+
+<script>
+function doe() {
+var x=document.getElementsByTagName('input')[0];
+x.parentNode.removeChild(x);
+var y=document.getElementsByTagName('style')[0];
+y.parentNode.removeChild(y);
+}
+</script>
+</body></html>
diff --git a/layout/forms/crashtests/378369.html b/layout/forms/crashtests/378369.html
new file mode 100644
index 000000000..90327735e
--- /dev/null
+++ b/layout/forms/crashtests/378369.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<title>Testcase bug - Crash [@ nsEventListenerManager::FixContextMenuEvent] when firing contextmenu event in display: none iframe</title>
+</head>
+<body>
+This page should not crash Mozilla
+<iframe style="display: none;"></iframe>
+
+<script>
+function docontextmenu(i){
+var doc = window.frames[0].document;
+ var ev = doc.createEvent ('MouseEvents');
+ ev.initMouseEvent('contextmenu', true,true, window, 3,5, 5, 400, 400, 0, 0, 0,0,0,null);
+ doc.dispatchEvent(ev);
+}
+setTimeout(docontextmenu,0);
+</script>
+</body>
+</html>
diff --git a/layout/forms/crashtests/378413-1.xhtml b/layout/forms/crashtests/378413-1.xhtml
new file mode 100644
index 000000000..c9ca6990a
--- /dev/null
+++ b/layout/forms/crashtests/378413-1.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body style="direction: rtl;">
+
+<form style="display: -moz-deck; width: 1em;">
+<fieldset>
+ <legend>Your name</legend>
+ <input type="text" name="name" size="20"/>
+</fieldset>
+<fieldset>
+ <legend>Your E-mail address</legend>
+ <input type="text" name="email" size="25"/>
+</fieldset>
+</form>
+
+</body>
+</html>
diff --git a/layout/forms/crashtests/380116-1.xhtml b/layout/forms/crashtests/380116-1.xhtml
new file mode 100644
index 000000000..cb37ff079
--- /dev/null
+++ b/layout/forms/crashtests/380116-1.xhtml
@@ -0,0 +1,11 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body onload="document.getElementById('t').style.display = 'block';">
+
+<table border="1" id="t">
+ <tr>
+ <td><select><option>foopy</option></select></td>
+ </tr>
+</table>
+
+</body>
+</html>
diff --git a/layout/forms/crashtests/382212-1.xhtml b/layout/forms/crashtests/382212-1.xhtml
new file mode 100644
index 000000000..46c957989
--- /dev/null
+++ b/layout/forms/crashtests/382212-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <body>
+ <div>
+ <xul:textbox><select><option>foo</option></select></xul:textbox>
+ </div>
+ </body>
+</html>
diff --git a/layout/forms/crashtests/382610-1.html b/layout/forms/crashtests/382610-1.html
new file mode 100644
index 000000000..9fe9c5b5c
--- /dev/null
+++ b/layout/forms/crashtests/382610-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+<div style="position: absolute">
+ <input type="file" style="position: absolute; -moz-appearance: menulist;">
+</div>
+
+</body>
+</html>
+
diff --git a/layout/forms/crashtests/383887-1.html b/layout/forms/crashtests/383887-1.html
new file mode 100644
index 000000000..65d4751f7
--- /dev/null
+++ b/layout/forms/crashtests/383887-1.html
@@ -0,0 +1,20 @@
+<html>
+<body>
+
+<table>
+ <tr>
+ <td>
+ <select>
+ <option style="padding: 200%;">
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <div style="padding: 200%"><span style="float: left; border: 1px solid red;"></span></div>
+ </td>
+ </tr>
+</table>
+
+</body>
+</html>
diff --git a/layout/forms/crashtests/386554-1.html b/layout/forms/crashtests/386554-1.html
new file mode 100644
index 000000000..585f1e5b5
--- /dev/null
+++ b/layout/forms/crashtests/386554-1.html
@@ -0,0 +1,14 @@
+<html>
+<head></head>
+
+<body>
+
+<div style="position: absolute"><input id="input" type="file" style="top: 100%"></div>
+
+<script>
+document.body.offsetHeight;
+document.getElementById("input").style.position = "absolute";
+</script>
+
+</body>
+</html>
diff --git a/layout/forms/crashtests/388374-1.xhtml b/layout/forms/crashtests/388374-1.xhtml
new file mode 100644
index 000000000..2a871f68b
--- /dev/null
+++ b/layout/forms/crashtests/388374-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function boom()
+{
+ var newText = document.createTextNode('x');
+ document.getElementById("a").parentNode.appendChild(newText);
+}
+</script>
+</head>
+
+<body onload="boom()">
+
+<table border="1">
+ <tr>
+ <td>1</td>
+ <td id="a" style="padding-left: 10%"><select style="padding-left: 20%; bottom: 10%;"><option style="padding: 0 0 10% 20%;">pppp</option></select></td>
+ </tr>
+</table>
+
+</body>
+</html>
diff --git a/layout/forms/crashtests/388374-2.html b/layout/forms/crashtests/388374-2.html
new file mode 100644
index 000000000..81918c37c
--- /dev/null
+++ b/layout/forms/crashtests/388374-2.html
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function boom()
+{
+ var newDiv = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ newDiv.style.width = "100px";
+ newDiv.style.height = "8px";
+ newDiv.style.background = "lightgreen";
+ document.getElementById("tr").appendChild(newDiv);
+}
+</script>
+</head>
+
+<body onload="boom()">
+
+<table border="1">
+ <tr id="tr">
+ <td></td>
+ <td style="padding-left: 10%"><select style="padding-left: 20%; bottom: 10%;"><option style="padding: 0 0 10% 20%; width: 25px;"></option></select></td>
+ </tr>
+</table>
+
+</body>
+</html>
diff --git a/layout/forms/crashtests/393656-1.xhtml b/layout/forms/crashtests/393656-1.xhtml
new file mode 100644
index 000000000..22a9326c5
--- /dev/null
+++ b/layout/forms/crashtests/393656-1.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+
+<body>
+ <select style="float: right; clear: right;">
+ <option><a style="float: right;"></a></option>
+ <option style="margin: 100%;"></option>
+ <option><div style="clear: both"></div></option>
+ </select>
+</body>
+
+</html>
diff --git a/layout/forms/crashtests/393656-2.xhtml b/layout/forms/crashtests/393656-2.xhtml
new file mode 100644
index 000000000..7d9adaf20
--- /dev/null
+++ b/layout/forms/crashtests/393656-2.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+
+<body>
+ <div style="overflow: hidden;">
+ <div style="float: right;"><span>XXX</span></div>
+ <select style="float: right; clear: right;">
+ <option><a style="float: right;"></a></option>
+ <option style="margin: 100%;"></option>
+ <option><div style="clear: both"></div></option>
+ </select>
+ <dl style="float: left;"></dl>
+ <div>
+ <div style="float: left;"></div>
+ <div style="display: block; clear: both"></div>
+ <dl style="float: left;"></dl>
+ </div>
+ </div>
+</body>
+
+</html>
diff --git a/layout/forms/crashtests/399262.html b/layout/forms/crashtests/399262.html
new file mode 100644
index 000000000..77bc0a16d
--- /dev/null
+++ b/layout/forms/crashtests/399262.html
@@ -0,0 +1,50 @@
+<style>
+select::first-letter, option::first-letter { color:green; }
+.block select, .block option{ display:block; }
+.scroll select, .scroll option{ display:block; overflow:hidden; }
+</style>
+
+<select></select>
+<select>F</select>
+<select>Ff</select>
+<select><option>F</select>
+<select><option>Ff</select>
+
+<div class="block">
+<select></select>
+<select>F</select>
+<select>Ff</select>
+<select><option>F</select>
+<select><option>Ff</select>
+</div>
+
+<div class="scroll">
+<select></select>
+<select>F</select>
+<select>Ff</select>
+<select><option>F</select>
+<select><option>Ff</select>
+</div>
+
+<select size=2></select>
+<select size=2>F</select>
+<select size=2>Ff</select>
+<select size=2><option>F</select>
+<select size=2><option>Ff</select>
+
+<div class="block">
+<select size=2></select>
+<select size=2>F</select>
+<select size=2>Ff</select>
+<select size=2><option>F</select>
+<select size=2><option>Ff</select>
+</div>
+
+<div class="scroll">
+<select size=2></select>
+<select size=2>F</select>
+<select size=2>Ff</select>
+<select size=2><option>F</select>
+<select size=2><option>Ff</select>
+</div>
+
diff --git a/layout/forms/crashtests/402852-1.html b/layout/forms/crashtests/402852-1.html
new file mode 100644
index 000000000..e5ba9adb1
--- /dev/null
+++ b/layout/forms/crashtests/402852-1.html
@@ -0,0 +1,2 @@
+<body style="text-indent: 99999999999999999999px;">
+<fieldset>
diff --git a/layout/forms/crashtests/403148-1.html b/layout/forms/crashtests/403148-1.html
new file mode 100644
index 000000000..cfb38fdb1
--- /dev/null
+++ b/layout/forms/crashtests/403148-1.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("ce").contentEditable = true;
+ document.documentElement.offsetHeight;
+ document.getElementById("finput").setAttribute("type", "");
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<input id="finput" type="file" disabled>
+
+<span id="ce"></span>
+
+</body>
+</html>
diff --git a/layout/forms/crashtests/404118-1.html b/layout/forms/crashtests/404118-1.html
new file mode 100644
index 000000000..40a2ac72b
--- /dev/null
+++ b/layout/forms/crashtests/404118-1.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<input type="file" style="position: fixed; height: 0; width: 10px; white-space: normal;">
+</body>
+</html>
diff --git a/layout/forms/crashtests/404123-1.html b/layout/forms/crashtests/404123-1.html
new file mode 100644
index 000000000..73fbaa363
--- /dev/null
+++ b/layout/forms/crashtests/404123-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+
+<fieldset>
+<legend style="padding: 0pt 100%;"></legend>
+</fieldset>
+
+</body>
+</html>
diff --git a/layout/forms/crashtests/407066.html b/layout/forms/crashtests/407066.html
new file mode 100644
index 000000000..64a3739ea
--- /dev/null
+++ b/layout/forms/crashtests/407066.html
@@ -0,0 +1 @@
+<div style="display: inline-block; -moz-column-count: 2;">text<input type="reset" style="float: right;">
diff --git a/layout/forms/crashtests/451316.html b/layout/forms/crashtests/451316.html
new file mode 100644
index 000000000..77f5d5517
--- /dev/null
+++ b/layout/forms/crashtests/451316.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="-moz-column-width: 1px;"><select style="height: 12em; display: block;"></select></div>
+<div style="-moz-column-width: 1px;"><select multiple style="height: 12em; display: block;"></select></div>
+</body>
+</html>
diff --git a/layout/forms/crashtests/455451-1.html b/layout/forms/crashtests/455451-1.html
new file mode 100644
index 000000000..09ee84aad
--- /dev/null
+++ b/layout/forms/crashtests/455451-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ window.addEventListener("DOMCharacterDataModified", function(){}, false);
+ document.body.appendChild(document.createElement("isindex"));
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+
+</html>
diff --git a/layout/forms/crashtests/457537-1.html b/layout/forms/crashtests/457537-1.html
new file mode 100644
index 000000000..f6fa2fb05
--- /dev/null
+++ b/layout/forms/crashtests/457537-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ window.addEventListener("DOMAttrModified", function(){}, false);
+ document.body.appendChild(document.createElement("input"));
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+
+</html>
diff --git a/layout/forms/crashtests/457537-2.html b/layout/forms/crashtests/457537-2.html
new file mode 100644
index 000000000..48bf1e73e
--- /dev/null
+++ b/layout/forms/crashtests/457537-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ window.addEventListener("DOMAttrModified", function(){}, false);
+ document.body.appendChild(document.createElement("isindex"));
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+
+</html>
diff --git a/layout/forms/crashtests/478219-1.xhtml b/layout/forms/crashtests/478219-1.xhtml
new file mode 100644
index 000000000..47144e649
--- /dev/null
+++ b/layout/forms/crashtests/478219-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:mathml="http://www.w3.org/1998/Math/MathML" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<div>
+<xul:textbox onDOMAttrModified="this.parentNode.removeChild(this)">
+<mathml:mathml/>
+</xul:textbox>
+</div>
+</html>
diff --git a/layout/forms/crashtests/498698-1.html b/layout/forms/crashtests/498698-1.html
new file mode 100644
index 000000000..fa8e2e17a
--- /dev/null
+++ b/layout/forms/crashtests/498698-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<select style="height: 3401091640591pc"></select>
+</body>
+</html>
diff --git a/layout/forms/crashtests/513113-1.html b/layout/forms/crashtests/513113-1.html
new file mode 100644
index 000000000..36f3974a1
--- /dev/null
+++ b/layout/forms/crashtests/513113-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html><body onload="document.body.style.display = 'table';" style="-moz-column-count: 1; white-space: pre-line; -moz-appearance: resizer; height: 23698324514pc;">
+
+
+
+<input></body></html>
diff --git a/layout/forms/crashtests/538062-1.xhtml b/layout/forms/crashtests/538062-1.xhtml
new file mode 100644
index 000000000..431f7ab84
--- /dev/null
+++ b/layout/forms/crashtests/538062-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ var option = document.getElementsByTagName("option")[0];
+ var b = option.firstChild;
+ option.appendChild(document.createTextNode("\u064A"));
+ document.documentElement.offsetHeight;
+ option.removeChild(b);
+}
+
+]]>
+</script>
+</head>
+
+<body onload="boom();"><select><option>B </option></select></body>
+</html>
diff --git a/layout/forms/crashtests/570624-1.html b/layout/forms/crashtests/570624-1.html
new file mode 100644
index 000000000..47a277728
--- /dev/null
+++ b/layout/forms/crashtests/570624-1.html
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function boom()
+{
+ var xt = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", 'textbox');
+ document.body.appendChild(xt);
+ xt.setAttribute('disabled', "true");
+ xt.setAttribute('value', "foo");
+}
+</script>
+</head>
+<body onload="boom();">
+</body>
+</html>
diff --git a/layout/forms/crashtests/578604-1.html b/layout/forms/crashtests/578604-1.html
new file mode 100644
index 000000000..d934c454d
--- /dev/null
+++ b/layout/forms/crashtests/578604-1.html
@@ -0,0 +1,17 @@
+<html class="reftest-print">
+<head>
+<style>
+ h2 { page-break-before: always; }
+ #reviewer { position: fixed;}
+</style>
+</head>
+
+<body>
+
+<h2>Crash Test Case</h2>
+<div>
+<input id='reviewer'>
+</div>
+
+</body>
+</html>
diff --git a/layout/forms/crashtests/590302-1.xhtml b/layout/forms/crashtests/590302-1.xhtml
new file mode 100644
index 000000000..339a424f7
--- /dev/null
+++ b/layout/forms/crashtests/590302-1.xhtml
@@ -0,0 +1,4 @@
+<html class="reftest-print" xmlns="http://www.w3.org/1999/xhtml" style=" float: left; white-space: pre; ">
+<div style="page-break-before: always;"></div>
+<textarea style="position: fixed;"></textarea>
+</html>
diff --git a/layout/forms/crashtests/626014.xhtml b/layout/forms/crashtests/626014.xhtml
new file mode 100644
index 000000000..05a2361d3
--- /dev/null
+++ b/layout/forms/crashtests/626014.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+<![CDATA[
+
+function boom()
+{
+ document.getElementById("i").selectionEnd;
+ document.getElementById("a").appendChild(document.getElementById("b"));
+}
+
+]]>
+</script>
+</head>
+<body onload="boom();">
+
+<mrow xmlns="http://www.w3.org/1998/Math/MathML" id="a"><mrow id="b"/><input xmlns="http://www.w3.org/1999/xhtml" id="i" /></mrow>
+
+</body>
+</html>
diff --git a/layout/forms/crashtests/639733.xhtml b/layout/forms/crashtests/639733.xhtml
new file mode 100644
index 000000000..51c46df6c
--- /dev/null
+++ b/layout/forms/crashtests/639733.xhtml
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+<![CDATA[
+
+function remove(n)
+{
+ n.parentNode.removeChild(n);
+}
+
+function boom()
+{
+
+ document.documentElement.offsetHeight;
+ remove(document.getElementById("s"));
+ remove(document.getElementById("e"));
+ document.documentElement.offsetHeight;
+}
+
+]]>
+</script>
+</head>
+<body onload="boom();">
+<embed id="e" src="data:text/html,A"></embed><span id="s"><div></div></span><isindex/>
+</body>
+</html>
diff --git a/layout/forms/crashtests/669767.html b/layout/forms/crashtests/669767.html
new file mode 100644
index 000000000..f41a9125c
--- /dev/null
+++ b/layout/forms/crashtests/669767.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<title>Untitled</title>
+
+
+</head>
+<body>
+<iframe src="data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3C/head%3E%3Cbody%3E%0A%3Ctextarea%3E%3C/textarea%3E%0A%0A%0A%3Cstyle%3E%0A@font-face%20%7B%0A%20%20%20%20%20%20font-family%3A%20%22cutabovetherest%22%3B%0A%20%20%20%20%20%20src%3A%20url%28%22http%3A//www.webpagepublicity.com/free-fonts/a/A%2520Cut%2520Above%2520The%2520Rest.ttf%22%29%3B%0A%7D%20%20%20%20%0A%0A%3C/style%3E%0A%0A%3Coptgroup%20contenteditable%3D%22true%22%20style%3D%22display%3A%20inline%3B%22%3E%3C/optgroup%3E%0A%0A%3C/body%3E%3C/html%3E"></iframe>
+<script>
+
+
+</script>
+</body>
+</html>
diff --git a/layout/forms/crashtests/682684-binding.xml b/layout/forms/crashtests/682684-binding.xml
new file mode 100644
index 000000000..910eec33c
--- /dev/null
+++ b/layout/forms/crashtests/682684-binding.xml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<style>html::before { content:"b"; }</style>
+<input contenteditable="true" spellcheck="true"/>
+</html> \ No newline at end of file
diff --git a/layout/forms/crashtests/682684.xhtml b/layout/forms/crashtests/682684.xhtml
new file mode 100644
index 000000000..2b7b2c83f
--- /dev/null
+++ b/layout/forms/crashtests/682684.xhtml
@@ -0,0 +1,3 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<span style="mask: url(682684-binding.xml#a);"/>
+</html>
diff --git a/layout/forms/crashtests/865602.html b/layout/forms/crashtests/865602.html
new file mode 100644
index 000000000..b7048d955
--- /dev/null
+++ b/layout/forms/crashtests/865602.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><body>
+
+<div style="-moz-column-count: 2;"><fieldset style="transform-style: preserve-3d;"><fieldset style="white-space: pre-line; position: fixed;"><div style="position: fixed;">
+
+
+</div></fieldset></fieldset></div>
+
+</body></html>
diff --git a/layout/forms/crashtests/893332-1.html b/layout/forms/crashtests/893332-1.html
new file mode 100644
index 000000000..1cd17c0b1
--- /dev/null
+++ b/layout/forms/crashtests/893332-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ </head>
+ <body>
+ <input min="-10" max="10" step="0.1" type="range" value="-5"/>
+ <input max="50" min="10" step="2" type="range" value="7"/>
+ </body>
+</html>
diff --git a/layout/forms/crashtests/944198.html b/layout/forms/crashtests/944198.html
new file mode 100644
index 000000000..0da7bc54d
--- /dev/null
+++ b/layout/forms/crashtests/944198.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body onload="c.removeAttribute('type'); c.removeAttribute('value');">
+<input id="c" type="color">
+</body>
+</html>
diff --git a/layout/forms/crashtests/949891.xhtml b/layout/forms/crashtests/949891.xhtml
new file mode 100644
index 000000000..8fbfe1e42
--- /dev/null
+++ b/layout/forms/crashtests/949891.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+<input type="number"> </input>
+</body>
+</html>
diff --git a/layout/forms/crashtests/959311.html b/layout/forms/crashtests/959311.html
new file mode 100644
index 000000000..c646b84f7
--- /dev/null
+++ b/layout/forms/crashtests/959311.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-print">
+<head>
+<meta charset="utf-8">
+<style type="text/css">
+@page { size:5in 3in; margin:0in; }
+div { height: 2.5in; }
+select { height: 0.5in; display:block; padding:20px; page-break-inside:initial; }
+</style>
+</head>
+<body>
+ <div></div>
+ <select>
+ <option>Text</option>
+ </select>
+ </body>
+</html>
diff --git a/layout/forms/crashtests/960277-2.html b/layout/forms/crashtests/960277-2.html
new file mode 100644
index 000000000..ea771ae79
--- /dev/null
+++ b/layout/forms/crashtests/960277-2.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<style>
+#d { overflow:scroll; width:200px; height:200px; background:yellow; }
+#d2 { right:0; width:100px; height:100px; background:purple; }
+</style>
+<fieldset id="d">
+ <div id="d2"></div>
+</fieldset>
+<script>
+d.getBoundingClientRect();
+d.style.transform = "translateX(100px)";
+d.getBoundingClientRect();
+d2.style.position = "absolute";
+</script>
diff --git a/layout/forms/crashtests/997709-1.html b/layout/forms/crashtests/997709-1.html
new file mode 100644
index 000000000..31d048407
--- /dev/null
+++ b/layout/forms/crashtests/997709-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<html class="reftest-print"><body><div style="position: fixed;">
+<div style="page-break-after: always"></div>
+<select style="display:flex; position: fixed;"><option>A</select>
+</div></body></html>
diff --git a/layout/forms/crashtests/crashtests.list b/layout/forms/crashtests/crashtests.list
new file mode 100644
index 000000000..462ff35cf
--- /dev/null
+++ b/layout/forms/crashtests/crashtests.list
@@ -0,0 +1,69 @@
+load 166750-1.html
+load 200347-1.html
+load 203041-1.html
+load 213390-1.html
+load 258101-1.html
+load 266225-1.html
+load 310426-1.xhtml
+load 310520-1.xhtml
+load 315752-1.xhtml
+load 317502-1.xhtml
+load 321894.html
+load 323499-1.html
+load 343510-1.html
+load 363696-1.xul
+load 363696-2.html
+load 363696-3.html
+load 366205-1.html
+load 366537-1.xhtml
+load 367587-1.html
+load 370703-1.html
+load 370940-1.html
+load 370967.html
+load 373586-1.xhtml
+load 375299.html
+load 378369.html
+asserts(4-10) load 378413-1.xhtml # bug 424225, bug 402850?
+load 380116-1.xhtml
+load 382212-1.xhtml
+load 382610-1.html
+load 383887-1.html
+load 386554-1.html
+load 388374-1.xhtml
+load 388374-2.html
+load 393656-1.xhtml
+load 393656-2.xhtml
+load 399262.html
+load 402852-1.html
+load 403148-1.html
+load 404118-1.html
+load 404123-1.html
+load 407066.html
+load 451316.html
+load 455451-1.html
+load 457537-1.html
+load 457537-2.html
+load 478219-1.xhtml
+load 498698-1.html
+load 513113-1.html
+load 538062-1.xhtml
+load 570624-1.html
+asserts(1) load 578604-1.html # bug 584564
+asserts(4-7) load 590302-1.xhtml # bug 584564
+load 626014.xhtml
+load 639733.xhtml
+load 669767.html
+load 682684.xhtml
+load 865602.html
+load 893332-1.html
+load 944198.html
+load 949891.xhtml
+load 959311.html
+load 960277-2.html
+load 997709-1.html
+load 1102791.html
+load 1140216.html
+load 1182414.html
+load 1212688.html
+load 1228670.xhtml
+load 1279354.html
diff --git a/layout/forms/moz.build b/layout/forms/moz.build
new file mode 100644
index 000000000..4fecbad38
--- /dev/null
+++ b/layout/forms/moz.build
@@ -0,0 +1,57 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files('**'):
+ BUG_COMPONENT = ('Core', 'Layout: Form Controls')
+
+MOCHITEST_MANIFESTS += ['test/mochitest.ini']
+MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
+
+EXPORTS += [
+ 'nsIComboboxControlFrame.h',
+ 'nsIFormControlFrame.h',
+ 'nsIListControlFrame.h',
+ 'nsISelectControlFrame.h',
+ 'nsITextControlFrame.h',
+]
+
+UNIFIED_SOURCES += [
+ 'nsButtonFrameRenderer.cpp',
+ 'nsColorControlFrame.cpp',
+ 'nsComboboxControlFrame.cpp',
+ 'nsDateTimeControlFrame.cpp',
+ 'nsFieldSetFrame.cpp',
+ 'nsFileControlFrame.cpp',
+ 'nsFormControlFrame.cpp',
+ 'nsGfxButtonControlFrame.cpp',
+ 'nsGfxCheckboxControlFrame.cpp',
+ 'nsGfxRadioControlFrame.cpp',
+ 'nsHTMLButtonControlFrame.cpp',
+ 'nsImageControlFrame.cpp',
+ 'nsLegendFrame.cpp',
+ 'nsListControlFrame.cpp',
+ 'nsMeterFrame.cpp',
+ 'nsNumberControlFrame.cpp',
+ 'nsProgressFrame.cpp',
+ 'nsRangeFrame.cpp',
+ 'nsSelectsAreaFrame.cpp',
+ 'nsTextControlFrame.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '../../editor/txmgr',
+ '../base',
+ '../generic',
+ '../style',
+ '../xul',
+ '/dom/base',
+ '/dom/html',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/layout/forms/nsButtonFrameRenderer.cpp b/layout/forms/nsButtonFrameRenderer.cpp
new file mode 100644
index 000000000..096031385
--- /dev/null
+++ b/layout/forms/nsButtonFrameRenderer.cpp
@@ -0,0 +1,516 @@
+/* -*- 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 "nsButtonFrameRenderer.h"
+#include "nsCSSRendering.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsCSSPseudoElements.h"
+#include "nsNameSpaceManager.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsDisplayList.h"
+#include "nsITheme.h"
+#include "nsFrame.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/Element.h"
+
+#define ACTIVE "active"
+#define HOVER "hover"
+#define FOCUS "focus"
+
+using namespace mozilla;
+using namespace mozilla::image;
+
+nsButtonFrameRenderer::nsButtonFrameRenderer()
+{
+ MOZ_COUNT_CTOR(nsButtonFrameRenderer);
+}
+
+nsButtonFrameRenderer::~nsButtonFrameRenderer()
+{
+ MOZ_COUNT_DTOR(nsButtonFrameRenderer);
+
+#ifdef DEBUG
+ if (mInnerFocusStyle) {
+ mInnerFocusStyle->FrameRelease();
+ }
+ if (mOuterFocusStyle) {
+ mOuterFocusStyle->FrameRelease();
+ }
+#endif
+}
+
+void
+nsButtonFrameRenderer::SetFrame(nsFrame* aFrame, nsPresContext* aPresContext)
+{
+ mFrame = aFrame;
+ ReResolveStyles(aPresContext);
+}
+
+nsIFrame*
+nsButtonFrameRenderer::GetFrame()
+{
+ return mFrame;
+}
+
+void
+nsButtonFrameRenderer::SetDisabled(bool aDisabled, bool notify)
+{
+ if (aDisabled)
+ mFrame->GetContent()->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, EmptyString(),
+ notify);
+ else
+ mFrame->GetContent()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, notify);
+}
+
+bool
+nsButtonFrameRenderer::isDisabled()
+{
+ return mFrame->GetContent()->AsElement()->
+ State().HasState(NS_EVENT_STATE_DISABLED);
+}
+
+class nsDisplayButtonBoxShadowOuter : public nsDisplayItem {
+public:
+ nsDisplayButtonBoxShadowOuter(nsDisplayListBuilder* aBuilder,
+ nsButtonFrameRenderer* aRenderer)
+ : nsDisplayItem(aBuilder, aRenderer->GetFrame()), mBFR(aRenderer) {
+ MOZ_COUNT_CTOR(nsDisplayButtonBoxShadowOuter);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayButtonBoxShadowOuter() {
+ MOZ_COUNT_DTOR(nsDisplayButtonBoxShadowOuter);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override;
+ NS_DISPLAY_DECL_NAME("ButtonBoxShadowOuter", TYPE_BUTTON_BOX_SHADOW_OUTER)
+private:
+ nsButtonFrameRenderer* mBFR;
+};
+
+nsRect
+nsDisplayButtonBoxShadowOuter::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+ *aSnap = false;
+ return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
+}
+
+void
+nsDisplayButtonBoxShadowOuter::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) {
+ nsRect frameRect = nsRect(ToReferenceFrame(), mFrame->GetSize());
+
+ nsRect buttonRect;
+ mBFR->GetButtonRect(frameRect, buttonRect);
+
+ nsCSSRendering::PaintBoxShadowOuter(mFrame->PresContext(), *aCtx, mFrame,
+ buttonRect, mVisibleRect);
+}
+
+class nsDisplayButtonBorder : public nsDisplayItem {
+public:
+ nsDisplayButtonBorder(nsDisplayListBuilder* aBuilder,
+ nsButtonFrameRenderer* aRenderer)
+ : nsDisplayItem(aBuilder, aRenderer->GetFrame()), mBFR(aRenderer) {
+ MOZ_COUNT_CTOR(nsDisplayButtonBorder);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayButtonBorder() {
+ MOZ_COUNT_DTOR(nsDisplayButtonBorder);
+ }
+#endif
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames) override {
+ aOutFrames->AppendElement(mFrame);
+ }
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override;
+ virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion *aInvalidRegion) override;
+ NS_DISPLAY_DECL_NAME("ButtonBorderBackground", TYPE_BUTTON_BORDER_BACKGROUND)
+private:
+ nsButtonFrameRenderer* mBFR;
+};
+
+nsDisplayItemGeometry*
+nsDisplayButtonBorder::AllocateGeometry(nsDisplayListBuilder* aBuilder)
+{
+ return new nsDisplayItemGenericImageGeometry(this, aBuilder);
+}
+
+void
+nsDisplayButtonBorder::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion *aInvalidRegion)
+{
+ auto geometry =
+ static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ bool snap;
+ aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
+ }
+
+ nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
+}
+
+void nsDisplayButtonBorder::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ NS_ASSERTION(mFrame, "No frame?");
+ nsPresContext* pc = mFrame->PresContext();
+ nsRect r = nsRect(ToReferenceFrame(), mFrame->GetSize());
+
+ // draw the border and background inside the focus and outline borders
+ DrawResult result =
+ mBFR->PaintBorder(aBuilder, pc, *aCtx, mVisibleRect, r);
+
+ nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
+}
+
+nsRect
+nsDisplayButtonBorder::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+ *aSnap = false;
+ return aBuilder->IsForEventDelivery() ? nsRect(ToReferenceFrame(), mFrame->GetSize())
+ : mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
+}
+
+class nsDisplayButtonForeground : public nsDisplayItem {
+public:
+ nsDisplayButtonForeground(nsDisplayListBuilder* aBuilder,
+ nsButtonFrameRenderer* aRenderer)
+ : nsDisplayItem(aBuilder, aRenderer->GetFrame()), mBFR(aRenderer) {
+ MOZ_COUNT_CTOR(nsDisplayButtonForeground);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayButtonForeground() {
+ MOZ_COUNT_DTOR(nsDisplayButtonForeground);
+ }
+#endif
+
+ nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion *aInvalidRegion) override;
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("ButtonForeground", TYPE_BUTTON_FOREGROUND)
+private:
+ nsButtonFrameRenderer* mBFR;
+};
+
+nsDisplayItemGeometry*
+nsDisplayButtonForeground::AllocateGeometry(nsDisplayListBuilder* aBuilder)
+{
+ return new nsDisplayItemGenericImageGeometry(this, aBuilder);
+}
+
+void
+nsDisplayButtonForeground::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+{
+ auto geometry =
+ static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ bool snap;
+ aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
+ }
+
+ nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
+}
+
+void nsDisplayButtonForeground::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ nsPresContext *presContext = mFrame->PresContext();
+ const nsStyleDisplay *disp = mFrame->StyleDisplay();
+ if (!mFrame->IsThemed(disp) ||
+ !presContext->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) {
+ nsRect r = nsRect(ToReferenceFrame(), mFrame->GetSize());
+
+ // Draw the focus and outline borders.
+ DrawResult result =
+ mBFR->PaintOutlineAndFocusBorders(aBuilder, presContext, *aCtx,
+ mVisibleRect, r);
+
+ nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
+ }
+}
+
+nsresult
+nsButtonFrameRenderer::DisplayButton(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aBackground,
+ nsDisplayList* aForeground)
+{
+ if (mFrame->StyleEffects()->mBoxShadow) {
+ aBackground->AppendNewToTop(new (aBuilder)
+ nsDisplayButtonBoxShadowOuter(aBuilder, this));
+ }
+
+ nsRect buttonRect;
+ GetButtonRect(mFrame->GetRectRelativeToSelf(), buttonRect);
+
+ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
+ aBuilder, mFrame, buttonRect, aBackground);
+
+ aBackground->AppendNewToTop(new (aBuilder)
+ nsDisplayButtonBorder(aBuilder, this));
+
+ // Only display focus rings if we actually have them. Since at most one
+ // button would normally display a focus ring, most buttons won't have them.
+ if ((mOuterFocusStyle && mOuterFocusStyle->StyleBorder()->HasBorder()) ||
+ (mInnerFocusStyle && mInnerFocusStyle->StyleBorder()->HasBorder())) {
+ aForeground->AppendNewToTop(new (aBuilder)
+ nsDisplayButtonForeground(aBuilder, this));
+ }
+ return NS_OK;
+}
+
+DrawResult
+nsButtonFrameRenderer::PaintOutlineAndFocusBorders(
+ nsDisplayListBuilder* aBuilder,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ const nsRect& aRect)
+{
+ // once we have all that we'll draw the focus if we have it. We will
+ // need to draw 2 focuses, the inner and the outer. This is so we
+ // can do any kind of look and feel. Some buttons have focus on the
+ // outside like mac and motif. While others like windows have it
+ // inside (dotted line). Usually only one will be specifed. But I
+ // guess you could have both if you wanted to.
+
+ nsRect rect;
+
+ PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages()
+ ? PaintBorderFlags::SYNC_DECODE_IMAGES
+ : PaintBorderFlags();
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ if (mOuterFocusStyle) {
+ // ---------- paint the outer focus border -------------
+
+ GetButtonOuterFocusRect(aRect, rect);
+
+ result &=
+ nsCSSRendering::PaintBorder(aPresContext, aRenderingContext, mFrame,
+ aDirtyRect, rect, mOuterFocusStyle, flags);
+ }
+
+ if (mInnerFocusStyle) {
+ // ---------- paint the inner focus border -------------
+
+ GetButtonInnerFocusRect(aRect, rect);
+
+ result &=
+ nsCSSRendering::PaintBorder(aPresContext, aRenderingContext, mFrame,
+ aDirtyRect, rect, mInnerFocusStyle, flags);
+ }
+
+ return result;
+}
+
+DrawResult
+nsButtonFrameRenderer::PaintBorder(
+ nsDisplayListBuilder* aBuilder,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ const nsRect& aRect)
+{
+ // get the button rect this is inside the focus and outline rects
+ nsRect buttonRect;
+ GetButtonRect(aRect, buttonRect);
+
+ nsStyleContext* context = mFrame->StyleContext();
+
+ PaintBorderFlags borderFlags = aBuilder->ShouldSyncDecodeImages()
+ ? PaintBorderFlags::SYNC_DECODE_IMAGES
+ : PaintBorderFlags();
+
+ nsCSSRendering::PaintBoxShadowInner(aPresContext, aRenderingContext,
+ mFrame, buttonRect);
+
+ DrawResult result =
+ nsCSSRendering::PaintBorder(aPresContext, aRenderingContext, mFrame,
+ aDirtyRect, buttonRect, context, borderFlags);
+
+ return result;
+}
+
+
+void
+nsButtonFrameRenderer::GetButtonOuterFocusRect(const nsRect& aRect, nsRect& focusRect)
+{
+ focusRect = aRect;
+}
+
+void
+nsButtonFrameRenderer::GetButtonRect(const nsRect& aRect, nsRect& r)
+{
+ r = aRect;
+ r.Deflate(GetButtonOuterFocusBorderAndPadding());
+}
+
+
+void
+nsButtonFrameRenderer::GetButtonInnerFocusRect(const nsRect& aRect, nsRect& focusRect)
+{
+ GetButtonRect(aRect, focusRect);
+ focusRect.Deflate(GetButtonBorderAndPadding());
+ focusRect.Deflate(GetButtonInnerFocusMargin());
+}
+
+
+nsMargin
+nsButtonFrameRenderer::GetButtonOuterFocusBorderAndPadding()
+{
+ nsMargin result(0,0,0,0);
+
+ if (mOuterFocusStyle) {
+ mOuterFocusStyle->StylePadding()->GetPadding(result);
+ result += mOuterFocusStyle->StyleBorder()->GetComputedBorder();
+ }
+
+ return result;
+}
+
+nsMargin
+nsButtonFrameRenderer::GetButtonBorderAndPadding()
+{
+ return mFrame->GetUsedBorderAndPadding();
+}
+
+/**
+ * Gets the size of the buttons border this is the union of the normal and disabled borders.
+ */
+nsMargin
+nsButtonFrameRenderer::GetButtonInnerFocusMargin()
+{
+ nsMargin innerFocusMargin(0,0,0,0);
+
+ if (mInnerFocusStyle) {
+ const nsStyleMargin* margin = mInnerFocusStyle->StyleMargin();
+ margin->GetMargin(innerFocusMargin);
+ }
+
+ return innerFocusMargin;
+}
+
+nsMargin
+nsButtonFrameRenderer::GetButtonInnerFocusBorderAndPadding()
+{
+ nsMargin result(0,0,0,0);
+
+ if (mInnerFocusStyle) {
+ mInnerFocusStyle->StylePadding()->GetPadding(result);
+ result += mInnerFocusStyle->StyleBorder()->GetComputedBorder();
+ }
+
+ return result;
+}
+
+// gets all the focus borders and padding that will be added to the regular border
+nsMargin
+nsButtonFrameRenderer::GetAddedButtonBorderAndPadding()
+{
+ return GetButtonOuterFocusBorderAndPadding() + GetButtonInnerFocusMargin() + GetButtonInnerFocusBorderAndPadding();
+}
+
+/**
+ * Call this when styles change
+ */
+void
+nsButtonFrameRenderer::ReResolveStyles(nsPresContext* aPresContext)
+{
+ // get all the styles
+ nsStyleContext* context = mFrame->StyleContext();
+ StyleSetHandle styleSet = aPresContext->StyleSet();
+
+#ifdef DEBUG
+ if (mInnerFocusStyle) {
+ mInnerFocusStyle->FrameRelease();
+ }
+ if (mOuterFocusStyle) {
+ mOuterFocusStyle->FrameRelease();
+ }
+#endif
+
+ // style for the inner such as a dotted line (Windows)
+ mInnerFocusStyle =
+ styleSet->ProbePseudoElementStyle(mFrame->GetContent()->AsElement(),
+ CSSPseudoElementType::mozFocusInner,
+ context);
+
+ // style for outer focus like a ridged border (MAC).
+ mOuterFocusStyle =
+ styleSet->ProbePseudoElementStyle(mFrame->GetContent()->AsElement(),
+ CSSPseudoElementType::mozFocusOuter,
+ context);
+
+#ifdef DEBUG
+ if (mInnerFocusStyle) {
+ mInnerFocusStyle->FrameAddRef();
+ }
+ if (mOuterFocusStyle) {
+ mOuterFocusStyle->FrameAddRef();
+ }
+#endif
+}
+
+nsStyleContext*
+nsButtonFrameRenderer::GetStyleContext(int32_t aIndex) const
+{
+ switch (aIndex) {
+ case NS_BUTTON_RENDERER_FOCUS_INNER_CONTEXT_INDEX:
+ return mInnerFocusStyle;
+ case NS_BUTTON_RENDERER_FOCUS_OUTER_CONTEXT_INDEX:
+ return mOuterFocusStyle;
+ default:
+ return nullptr;
+ }
+}
+
+void
+nsButtonFrameRenderer::SetStyleContext(int32_t aIndex, nsStyleContext* aStyleContext)
+{
+ switch (aIndex) {
+ case NS_BUTTON_RENDERER_FOCUS_INNER_CONTEXT_INDEX:
+#ifdef DEBUG
+ if (mInnerFocusStyle) {
+ mInnerFocusStyle->FrameRelease();
+ }
+#endif
+ mInnerFocusStyle = aStyleContext;
+ break;
+ case NS_BUTTON_RENDERER_FOCUS_OUTER_CONTEXT_INDEX:
+#ifdef DEBUG
+ if (mOuterFocusStyle) {
+ mOuterFocusStyle->FrameRelease();
+ }
+#endif
+ mOuterFocusStyle = aStyleContext;
+ break;
+ }
+#ifdef DEBUG
+ aStyleContext->FrameAddRef();
+#endif
+}
diff --git a/layout/forms/nsButtonFrameRenderer.h b/layout/forms/nsButtonFrameRenderer.h
new file mode 100644
index 000000000..8662c52e0
--- /dev/null
+++ b/layout/forms/nsButtonFrameRenderer.h
@@ -0,0 +1,88 @@
+/* -*- 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 nsButtonFrameRenderer_h___
+#define nsButtonFrameRenderer_h___
+
+#include "imgIContainer.h"
+#include "nsMargin.h"
+
+class nsIFrame;
+class nsFrame;
+class nsDisplayList;
+class nsDisplayListBuilder;
+class nsPresContext;
+class nsRenderingContext;
+struct nsRect;
+class nsStyleContext;
+
+
+#define NS_BUTTON_RENDERER_FOCUS_INNER_CONTEXT_INDEX 0
+#define NS_BUTTON_RENDERER_FOCUS_OUTER_CONTEXT_INDEX 1
+#define NS_BUTTON_RENDERER_LAST_CONTEXT_INDEX NS_BUTTON_RENDERER_FOCUS_OUTER_CONTEXT_INDEX
+
+class nsButtonFrameRenderer {
+ typedef mozilla::image::DrawResult DrawResult;
+
+public:
+
+ nsButtonFrameRenderer();
+ ~nsButtonFrameRenderer();
+
+ /**
+ * Create display list items for the button
+ */
+ nsresult DisplayButton(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aBackground, nsDisplayList* aForeground);
+
+
+ DrawResult PaintOutlineAndFocusBorders(nsDisplayListBuilder* aBuilder,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ const nsRect& aRect);
+
+ DrawResult PaintBorder(nsDisplayListBuilder* aBuilder,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ const nsRect& aRect);
+
+ void SetFrame(nsFrame* aFrame, nsPresContext* aPresContext);
+
+ void SetDisabled(bool aDisabled, bool notify);
+
+ bool isActive();
+ bool isDisabled();
+
+ void GetButtonOuterFocusRect(const nsRect& aRect, nsRect& aResult);
+ void GetButtonRect(const nsRect& aRect, nsRect& aResult);
+ void GetButtonInnerFocusRect(const nsRect& aRect, nsRect& aResult);
+ nsMargin GetButtonOuterFocusBorderAndPadding();
+ nsMargin GetButtonBorderAndPadding();
+ nsMargin GetButtonInnerFocusMargin();
+ nsMargin GetButtonInnerFocusBorderAndPadding();
+ nsMargin GetAddedButtonBorderAndPadding();
+
+ nsStyleContext* GetStyleContext(int32_t aIndex) const;
+ void SetStyleContext(int32_t aIndex, nsStyleContext* aStyleContext);
+ void ReResolveStyles(nsPresContext* aPresContext);
+
+ nsIFrame* GetFrame();
+
+protected:
+
+private:
+
+ // cached styles for focus and outline.
+ RefPtr<nsStyleContext> mInnerFocusStyle;
+ RefPtr<nsStyleContext> mOuterFocusStyle;
+
+ nsFrame* mFrame;
+};
+
+
+#endif
+
diff --git a/layout/forms/nsColorControlFrame.cpp b/layout/forms/nsColorControlFrame.cpp
new file mode 100644
index 000000000..e0bae43a9
--- /dev/null
+++ b/layout/forms/nsColorControlFrame.cpp
@@ -0,0 +1,147 @@
+/* -*- 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 "nsColorControlFrame.h"
+
+#include "nsContentCreatorFunctions.h"
+#include "nsContentList.h"
+#include "nsContentUtils.h"
+#include "nsCSSPseudoElements.h"
+#include "nsFormControlFrame.h"
+#include "nsGkAtoms.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsIDOMNode.h"
+#include "nsIFormControl.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsIDocument.h"
+
+using mozilla::dom::Element;
+
+nsColorControlFrame::nsColorControlFrame(nsStyleContext* aContext)
+ : nsHTMLButtonControlFrame(aContext)
+{
+}
+
+nsIFrame*
+NS_NewColorControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsColorControlFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsColorControlFrame)
+
+NS_QUERYFRAME_HEAD(nsColorControlFrame)
+ NS_QUERYFRAME_ENTRY(nsColorControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+NS_QUERYFRAME_TAIL_INHERITING(nsHTMLButtonControlFrame)
+
+
+void nsColorControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
+ nsContentUtils::DestroyAnonymousContent(&mColorContent);
+ nsHTMLButtonControlFrame::DestroyFrom(aDestructRoot);
+}
+
+nsIAtom*
+nsColorControlFrame::GetType() const
+{
+ return nsGkAtoms::colorControlFrame;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsColorControlFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("ColorControl"), aResult);
+}
+#endif
+
+// Create the color area for the button.
+// The frame will be generated by the frame constructor.
+nsresult
+nsColorControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+{
+ nsCOMPtr<nsIDocument> doc = mContent->GetComposedDoc();
+ mColorContent = doc->CreateHTMLElement(nsGkAtoms::div);
+
+ // Mark the element to be native anonymous before setting any attributes.
+ mColorContent->SetIsNativeAnonymousRoot();
+
+ nsresult rv = UpdateColor();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CSSPseudoElementType pseudoType = CSSPseudoElementType::mozColorSwatch;
+ RefPtr<nsStyleContext> newStyleContext = PresContext()->StyleSet()->
+ ResolvePseudoElementStyle(mContent->AsElement(), pseudoType,
+ StyleContext(), mColorContent->AsElement());
+ if (!aElements.AppendElement(ContentInfo(mColorContent, newStyleContext))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+void
+nsColorControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter)
+{
+ if (mColorContent) {
+ aElements.AppendElement(mColorContent);
+ }
+}
+
+nsresult
+nsColorControlFrame::UpdateColor()
+{
+ // Get the color from the "value" property of our content; it will return the
+ // default color (through the sanitization algorithm) if there is none.
+ nsAutoString color;
+ nsCOMPtr<nsIDOMHTMLInputElement> elt = do_QueryInterface(mContent);
+ elt->GetValue(color);
+ MOZ_ASSERT(!color.IsEmpty(),
+ "Content node's GetValue() should return a valid color string "
+ "(the default color, in case no valid color is set)");
+
+ // Set the background-color style property of the swatch element to this color
+ return mColorContent->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
+ NS_LITERAL_STRING("background-color:") + color, true);
+}
+
+nsresult
+nsColorControlFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ NS_ASSERTION(mColorContent, "The color div must exist");
+
+ // If the value attribute is set, update the color box, but only if we're
+ // still a color control, which might not be the case if the type attribute
+ // was removed/changed.
+ nsCOMPtr<nsIFormControl> fctrl = do_QueryInterface(GetContent());
+ if (fctrl->GetType() == NS_FORM_INPUT_COLOR &&
+ aNameSpaceID == kNameSpaceID_None && nsGkAtoms::value == aAttribute) {
+ UpdateColor();
+ }
+ return nsHTMLButtonControlFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+}
+
+nsContainerFrame*
+nsColorControlFrame::GetContentInsertionFrame()
+{
+ return this;
+}
+
+Element*
+nsColorControlFrame::GetPseudoElement(CSSPseudoElementType aType)
+{
+ if (aType == CSSPseudoElementType::mozColorSwatch) {
+ return mColorContent;
+ }
+
+ return nsContainerFrame::GetPseudoElement(aType);
+}
diff --git a/layout/forms/nsColorControlFrame.h b/layout/forms/nsColorControlFrame.h
new file mode 100644
index 000000000..238347ce3
--- /dev/null
+++ b/layout/forms/nsColorControlFrame.h
@@ -0,0 +1,65 @@
+/* -*- 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 nsColorControlFrame_h___
+#define nsColorControlFrame_h___
+
+#include "nsCOMPtr.h"
+#include "nsHTMLButtonControlFrame.h"
+#include "nsIAnonymousContentCreator.h"
+
+namespace mozilla {
+enum class CSSPseudoElementType : uint8_t;
+} // namespace mozilla
+
+// Class which implements the input type=color
+
+class nsColorControlFrame final : public nsHTMLButtonControlFrame,
+ public nsIAnonymousContentCreator
+{
+ typedef mozilla::CSSPseudoElementType CSSPseudoElementType;
+ typedef mozilla::dom::Element Element;
+
+public:
+ friend nsIFrame* NS_NewColorControlFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ NS_DECL_QUERYFRAME_TARGET(nsColorControlFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ // nsIAnonymousContentCreator
+ virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
+ virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter) override;
+
+ // nsIFrame
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+ virtual bool IsLeaf() const override { return true; }
+ virtual nsContainerFrame* GetContentInsertionFrame() override;
+
+ virtual Element* GetPseudoElement(CSSPseudoElementType aType) override;
+
+ // Refresh the color swatch, using associated input's value
+ nsresult UpdateColor();
+
+private:
+ explicit nsColorControlFrame(nsStyleContext* aContext);
+
+ nsCOMPtr<Element> mColorContent;
+};
+
+
+#endif // nsColorControlFrame_h___
diff --git a/layout/forms/nsComboboxControlFrame.cpp b/layout/forms/nsComboboxControlFrame.cpp
new file mode 100644
index 000000000..f69198cc7
--- /dev/null
+++ b/layout/forms/nsComboboxControlFrame.cpp
@@ -0,0 +1,1715 @@
+/* -*- 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 "nsComboboxControlFrame.h"
+
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "nsCOMPtr.h"
+#include "nsFocusManager.h"
+#include "nsFormControlFrame.h"
+#include "nsGkAtoms.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsHTMLParts.h"
+#include "nsIFormControl.h"
+#include "nsNameSpaceManager.h"
+#include "nsIListControlFrame.h"
+#include "nsPIDOMWindow.h"
+#include "nsIPresShell.h"
+#include "nsPresState.h"
+#include "nsContentList.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsIDOMEventListener.h"
+#include "nsIDOMNode.h"
+#include "nsISelectControlFrame.h"
+#include "nsContentUtils.h"
+#include "nsIDocument.h"
+#include "nsIScrollableFrame.h"
+#include "nsListControlFrame.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsLayoutUtils.h"
+#include "nsDisplayList.h"
+#include "nsITheme.h"
+#include "nsThemeConstants.h"
+#include "nsRenderingContext.h"
+#include "mozilla/Likely.h"
+#include <algorithm>
+#include "nsTextNode.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Unused.h"
+#include "gfx2DGlue.h"
+
+#ifdef XP_WIN
+#define COMBOBOX_ROLLUP_CONSUME_EVENT 0
+#else
+#define COMBOBOX_ROLLUP_CONSUME_EVENT 1
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+NS_IMETHODIMP
+nsComboboxControlFrame::RedisplayTextEvent::Run()
+{
+ if (mControlFrame)
+ mControlFrame->HandleRedisplayTextEvent();
+ return NS_OK;
+}
+
+class nsPresState;
+
+#define FIX_FOR_BUG_53259
+
+// Drop down list event management.
+// The combo box uses the following strategy for managing the drop-down list.
+// If the combo box or its arrow button is clicked on the drop-down list is displayed
+// If mouse exits the combo box with the drop-down list displayed the drop-down list
+// is asked to capture events
+// The drop-down list will capture all events including mouse down and up and will always
+// return with ListWasSelected method call regardless of whether an item in the list was
+// actually selected.
+// The ListWasSelected code will turn off mouse-capture for the drop-down list.
+// The drop-down list does not explicitly set capture when it is in the drop-down mode.
+
+
+/**
+ * Helper class that listens to the combo boxes button. If the button is pressed the
+ * combo box is toggled to open or close. this is used by Accessibility which presses
+ * that button Programmatically.
+ */
+class nsComboButtonListener : public nsIDOMEventListener
+{
+private:
+ virtual ~nsComboButtonListener() {}
+
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD HandleEvent(nsIDOMEvent*) override
+ {
+ mComboBox->ShowDropDown(!mComboBox->IsDroppedDown());
+ return NS_OK;
+ }
+
+ explicit nsComboButtonListener(nsComboboxControlFrame* aCombobox)
+ {
+ mComboBox = aCombobox;
+ }
+
+ nsComboboxControlFrame* mComboBox;
+};
+
+NS_IMPL_ISUPPORTS(nsComboButtonListener,
+ nsIDOMEventListener)
+
+// static class data member for Bug 32920
+nsComboboxControlFrame* nsComboboxControlFrame::sFocused = nullptr;
+
+nsContainerFrame*
+NS_NewComboboxControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, nsFrameState aStateFlags)
+{
+ nsComboboxControlFrame* it = new (aPresShell) nsComboboxControlFrame(aContext);
+
+ if (it) {
+ // set the state flags (if any are provided)
+ it->AddStateBits(aStateFlags);
+ }
+
+ return it;
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsComboboxControlFrame)
+
+//-----------------------------------------------------------
+// Reflow Debugging Macros
+// These let us "see" how many reflow counts are happening
+//-----------------------------------------------------------
+#ifdef DO_REFLOW_COUNTER
+
+#define MAX_REFLOW_CNT 1024
+static int32_t gTotalReqs = 0;;
+static int32_t gTotalReflows = 0;;
+static int32_t gReflowControlCntRQ[MAX_REFLOW_CNT];
+static int32_t gReflowControlCnt[MAX_REFLOW_CNT];
+static int32_t gReflowInx = -1;
+
+#define REFLOW_COUNTER() \
+ if (mReflowId > -1) \
+ gReflowControlCnt[mReflowId]++;
+
+#define REFLOW_COUNTER_REQUEST() \
+ if (mReflowId > -1) \
+ gReflowControlCntRQ[mReflowId]++;
+
+#define REFLOW_COUNTER_DUMP(__desc) \
+ if (mReflowId > -1) {\
+ gTotalReqs += gReflowControlCntRQ[mReflowId];\
+ gTotalReflows += gReflowControlCnt[mReflowId];\
+ printf("** Id:%5d %s RF: %d RQ: %d %d/%d %5.2f\n", \
+ mReflowId, (__desc), \
+ gReflowControlCnt[mReflowId], \
+ gReflowControlCntRQ[mReflowId],\
+ gTotalReflows, gTotalReqs, float(gTotalReflows)/float(gTotalReqs)*100.0f);\
+ }
+
+#define REFLOW_COUNTER_INIT() \
+ if (gReflowInx < MAX_REFLOW_CNT) { \
+ gReflowInx++; \
+ mReflowId = gReflowInx; \
+ gReflowControlCnt[mReflowId] = 0; \
+ gReflowControlCntRQ[mReflowId] = 0; \
+ } else { \
+ mReflowId = -1; \
+ }
+
+// reflow messages
+#define REFLOW_DEBUG_MSG(_msg1) printf((_msg1))
+#define REFLOW_DEBUG_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
+#define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
+#define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
+
+#else //-------------
+
+#define REFLOW_COUNTER_REQUEST()
+#define REFLOW_COUNTER()
+#define REFLOW_COUNTER_DUMP(__desc)
+#define REFLOW_COUNTER_INIT()
+
+#define REFLOW_DEBUG_MSG(_msg)
+#define REFLOW_DEBUG_MSG2(_msg1, _msg2)
+#define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3)
+#define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4)
+
+
+#endif
+
+//------------------------------------------
+// This is for being VERY noisy
+//------------------------------------------
+#ifdef DO_VERY_NOISY
+#define REFLOW_NOISY_MSG(_msg1) printf((_msg1))
+#define REFLOW_NOISY_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
+#define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
+#define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
+#else
+#define REFLOW_NOISY_MSG(_msg)
+#define REFLOW_NOISY_MSG2(_msg1, _msg2)
+#define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3)
+#define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4)
+#endif
+
+//------------------------------------------
+// Displays value in pixels or twips
+//------------------------------------------
+#ifdef DO_PIXELS
+#define PX(__v) __v / 15
+#else
+#define PX(__v) __v
+#endif
+
+//------------------------------------------------------
+//-- Done with macros
+//------------------------------------------------------
+
+nsComboboxControlFrame::nsComboboxControlFrame(nsStyleContext* aContext)
+ : nsBlockFrame(aContext)
+ , mDisplayFrame(nullptr)
+ , mButtonFrame(nullptr)
+ , mDropdownFrame(nullptr)
+ , mListControlFrame(nullptr)
+ , mDisplayISize(0)
+ , mRecentSelectedIndex(NS_SKIP_NOTIFY_INDEX)
+ , mDisplayedIndex(-1)
+ , mLastDropDownBeforeScreenBCoord(nscoord_MIN)
+ , mLastDropDownAfterScreenBCoord(nscoord_MIN)
+ , mDroppedDown(false)
+ , mInRedisplayText(false)
+ , mDelayedShowDropDown(false)
+ , mIsOpenInParentProcess(false)
+{
+ REFLOW_COUNTER_INIT()
+}
+
+//--------------------------------------------------------------
+nsComboboxControlFrame::~nsComboboxControlFrame()
+{
+ REFLOW_COUNTER_DUMP("nsCCF");
+}
+
+//--------------------------------------------------------------
+
+NS_QUERYFRAME_HEAD(nsComboboxControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIComboboxControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+ NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
+
+#ifdef ACCESSIBILITY
+a11y::AccType
+nsComboboxControlFrame::AccessibleType()
+{
+ return a11y::eHTMLComboboxType;
+}
+#endif
+
+void
+nsComboboxControlFrame::SetFocus(bool aOn, bool aRepaint)
+{
+ nsWeakFrame weakFrame(this);
+ if (aOn) {
+ nsListControlFrame::ComboboxFocusSet();
+ sFocused = this;
+ if (mDelayedShowDropDown) {
+ ShowDropDown(true); // might destroy us
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ }
+ } else {
+ sFocused = nullptr;
+ mDelayedShowDropDown = false;
+ if (mDroppedDown) {
+ mListControlFrame->ComboboxFinish(mDisplayedIndex); // might destroy us
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ }
+ // May delete |this|.
+ mListControlFrame->FireOnInputAndOnChange();
+ }
+
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+
+ // This is needed on a temporary basis. It causes the focus
+ // rect to be drawn. This is much faster than ReResolvingStyle
+ // Bug 32920
+ InvalidateFrame();
+}
+
+void
+nsComboboxControlFrame::ShowPopup(bool aShowPopup)
+{
+ nsView* view = mDropdownFrame->GetView();
+ nsViewManager* viewManager = view->GetViewManager();
+
+ if (aShowPopup) {
+ nsRect rect = mDropdownFrame->GetRect();
+ rect.x = rect.y = 0;
+ viewManager->ResizeView(view, rect);
+ viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
+ } else {
+ viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
+ nsRect emptyRect(0, 0, 0, 0);
+ viewManager->ResizeView(view, emptyRect);
+ }
+
+ // fire a popup dom event if it is safe to do so
+ nsCOMPtr<nsIPresShell> shell = PresContext()->GetPresShell();
+ if (shell && nsContentUtils::IsSafeToRunScript()) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, aShowPopup ? eXULPopupShowing : eXULPopupHiding,
+ nullptr, WidgetMouseEvent::eReal);
+
+ shell->HandleDOMEventWithTarget(mContent, &event, &status);
+ }
+}
+
+bool
+nsComboboxControlFrame::ShowList(bool aShowList)
+{
+ nsView* view = mDropdownFrame->GetView();
+ if (aShowList) {
+ NS_ASSERTION(!view->HasWidget(),
+ "We shouldn't have a widget before we need to display the popup");
+
+ // Create the widget for the drop-down list
+ view->GetViewManager()->SetViewFloating(view, true);
+
+ nsWidgetInitData widgetData;
+ widgetData.mWindowType = eWindowType_popup;
+ widgetData.mBorderStyle = eBorderStyle_default;
+ view->CreateWidgetForPopup(&widgetData);
+ } else {
+ nsIWidget* widget = view->GetWidget();
+ if (widget) {
+ // We must do this before ShowPopup in case it destroys us (bug 813442).
+ widget->CaptureRollupEvents(this, false);
+ }
+ }
+
+ nsWeakFrame weakFrame(this);
+ ShowPopup(aShowList); // might destroy us
+ if (!weakFrame.IsAlive()) {
+ return false;
+ }
+
+ mDroppedDown = aShowList;
+ nsIWidget* widget = view->GetWidget();
+ if (mDroppedDown) {
+ // The listcontrol frame will call back to the nsComboboxControlFrame's
+ // ListWasSelected which will stop the capture.
+ mListControlFrame->AboutToDropDown();
+ mListControlFrame->CaptureMouseEvents(true);
+ if (widget) {
+ widget->CaptureRollupEvents(this, true);
+ }
+ } else {
+ if (widget) {
+ view->DestroyWidget();
+ }
+ }
+
+ return weakFrame.IsAlive();
+}
+
+class nsResizeDropdownAtFinalPosition final
+ : public nsIReflowCallback, public Runnable
+{
+public:
+ explicit nsResizeDropdownAtFinalPosition(nsComboboxControlFrame* aFrame)
+ : mFrame(aFrame)
+ {
+ MOZ_COUNT_CTOR(nsResizeDropdownAtFinalPosition);
+ }
+
+protected:
+ ~nsResizeDropdownAtFinalPosition()
+ {
+ MOZ_COUNT_DTOR(nsResizeDropdownAtFinalPosition);
+ }
+
+public:
+ virtual bool ReflowFinished() override
+ {
+ Run();
+ NS_RELEASE_THIS();
+ return false;
+ }
+
+ virtual void ReflowCallbackCanceled() override
+ {
+ NS_RELEASE_THIS();
+ }
+
+ NS_IMETHOD Run() override
+ {
+ if (mFrame.IsAlive()) {
+ static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())->
+ AbsolutelyPositionDropDown();
+ }
+ return NS_OK;
+ }
+
+ nsWeakFrame mFrame;
+};
+
+void
+nsComboboxControlFrame::ReflowDropdown(nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput)
+{
+ // All we want out of it later on, really, is the block size of a row, so we
+ // don't even need to cache mDropdownFrame's ascent or anything. If we don't
+ // need to reflow it, just bail out here.
+ if (!aReflowInput.ShouldReflowAllKids() &&
+ !NS_SUBTREE_DIRTY(mDropdownFrame)) {
+ return;
+ }
+
+ // XXXbz this will, for small-block-size dropdowns, have extra space
+ // on the appropriate edge for the scrollbar we don't show... but
+ // that's the best we can do here for now.
+ WritingMode wm = mDropdownFrame->GetWritingMode();
+ LogicalSize availSize = aReflowInput.AvailableSize(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput kidReflowInput(aPresContext, aReflowInput, mDropdownFrame,
+ availSize);
+
+ // If the dropdown's intrinsic inline size is narrower than our
+ // specified inline size, then expand it out. We want our border-box
+ // inline size to end up the same as the dropdown's so account for
+ // both sets of mComputedBorderPadding.
+ nscoord forcedISize = aReflowInput.ComputedISize() +
+ aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm) -
+ kidReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm);
+ kidReflowInput.SetComputedISize(std::max(kidReflowInput.ComputedISize(),
+ forcedISize));
+
+ // ensure we start off hidden
+ if (!mDroppedDown && GetStateBits() & NS_FRAME_FIRST_REFLOW) {
+ nsView* view = mDropdownFrame->GetView();
+ nsViewManager* viewManager = view->GetViewManager();
+ viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
+ nsRect emptyRect(0, 0, 0, 0);
+ viewManager->ResizeView(view, emptyRect);
+ }
+
+ // Allow the child to move/size/change-visibility its view if it's currently
+ // dropped down
+ int32_t flags = mDroppedDown ? 0
+ : NS_FRAME_NO_MOVE_FRAME |
+ NS_FRAME_NO_VISIBILITY |
+ NS_FRAME_NO_SIZE_VIEW;
+
+ //XXX Can this be different from the dropdown's writing mode?
+ // That would be odd!
+ // Note that we don't need to pass the true frame position or container size
+ // to ReflowChild or FinishReflowChild here; it will be positioned as needed
+ // by AbsolutelyPositionDropDown().
+ WritingMode outerWM = GetWritingMode();
+ const nsSize dummyContainerSize;
+ ReflowOutput desiredSize(aReflowInput);
+ nsReflowStatus ignoredStatus;
+ ReflowChild(mDropdownFrame, aPresContext, desiredSize,
+ kidReflowInput, outerWM, LogicalPoint(outerWM),
+ dummyContainerSize, flags, ignoredStatus);
+
+ // Set the child's width and height to its desired size
+ FinishReflowChild(mDropdownFrame, aPresContext, desiredSize, &kidReflowInput,
+ outerWM, LogicalPoint(outerWM), dummyContainerSize, flags);
+}
+
+nsPoint
+nsComboboxControlFrame::GetCSSTransformTranslation()
+{
+ nsIFrame* frame = this;
+ bool is3DTransform = false;
+ Matrix transform;
+ while (frame) {
+ nsIFrame* parent;
+ Matrix4x4 ctm = frame->GetTransformMatrix(nullptr, &parent);
+ Matrix matrix;
+ if (ctm.Is2D(&matrix)) {
+ transform = transform * matrix;
+ } else {
+ is3DTransform = true;
+ break;
+ }
+ frame = parent;
+ }
+ nsPoint translation;
+ if (!is3DTransform && !transform.HasNonTranslation()) {
+ nsPresContext* pc = PresContext();
+ // To get the translation introduced only by transforms we subtract the
+ // regular non-transform translation.
+ nsRootPresContext* rootPC = pc->GetRootPresContext();
+ if (rootPC) {
+ int32_t apd = pc->AppUnitsPerDevPixel();
+ translation.x = NSFloatPixelsToAppUnits(transform._31, apd);
+ translation.y = NSFloatPixelsToAppUnits(transform._32, apd);
+ translation -= GetOffsetToCrossDoc(rootPC->PresShell()->GetRootFrame());
+ }
+ }
+ return translation;
+}
+
+class nsAsyncRollup : public Runnable
+{
+public:
+ explicit nsAsyncRollup(nsComboboxControlFrame* aFrame) : mFrame(aFrame) {}
+ NS_IMETHOD Run() override
+ {
+ if (mFrame.IsAlive()) {
+ static_cast<nsComboboxControlFrame*>(mFrame.GetFrame())
+ ->RollupFromList();
+ }
+ return NS_OK;
+ }
+ nsWeakFrame mFrame;
+};
+
+class nsAsyncResize : public Runnable
+{
+public:
+ explicit nsAsyncResize(nsComboboxControlFrame* aFrame) : mFrame(aFrame) {}
+ NS_IMETHOD Run() override
+ {
+ if (mFrame.IsAlive()) {
+ nsComboboxControlFrame* combo =
+ static_cast<nsComboboxControlFrame*>(mFrame.GetFrame());
+ static_cast<nsListControlFrame*>(combo->mDropdownFrame)->
+ SetSuppressScrollbarUpdate(true);
+ nsCOMPtr<nsIPresShell> shell = mFrame->PresContext()->PresShell();
+ shell->FrameNeedsReflow(combo->mDropdownFrame, nsIPresShell::eResize,
+ NS_FRAME_IS_DIRTY);
+ shell->FlushPendingNotifications(Flush_Layout);
+ if (mFrame.IsAlive()) {
+ combo = static_cast<nsComboboxControlFrame*>(mFrame.GetFrame());
+ static_cast<nsListControlFrame*>(combo->mDropdownFrame)->
+ SetSuppressScrollbarUpdate(false);
+ if (combo->mDelayedShowDropDown) {
+ combo->ShowDropDown(true);
+ }
+ }
+ }
+ return NS_OK;
+ }
+ nsWeakFrame mFrame;
+};
+
+void
+nsComboboxControlFrame::GetAvailableDropdownSpace(WritingMode aWM,
+ nscoord* aBefore,
+ nscoord* aAfter,
+ LogicalPoint* aTranslation)
+{
+ MOZ_ASSERT(!XRE_IsContentProcess());
+ // Note: At first glance, it appears that you could simply get the
+ // absolute bounding box for the dropdown list by first getting its
+ // view, then getting the view's nsIWidget, then asking the nsIWidget
+ // for its AbsoluteBounds.
+ // The problem with this approach, is that the dropdown list's bcoord
+ // location can change based on whether the dropdown is placed after
+ // or before the display frame. The approach taken here is to get the
+ // absolute position of the display frame and use its location to
+ // determine if the dropdown will go offscreen.
+
+ // Normal frame geometry (eg GetOffsetTo, mRect) doesn't include transforms.
+ // In the special case that our transform is only a 2D translation we
+ // introduce this hack so that the dropdown will show up in the right place.
+ // Use null container size when converting a vector from logical to physical.
+ const nsSize nullContainerSize;
+ *aTranslation = LogicalPoint(aWM, GetCSSTransformTranslation(),
+ nullContainerSize);
+ *aBefore = 0;
+ *aAfter = 0;
+
+ nsRect screen = nsFormControlFrame::GetUsableScreenRect(PresContext());
+ nsSize containerSize = screen.Size();
+ LogicalRect logicalScreen(aWM, screen, containerSize);
+ if (mLastDropDownAfterScreenBCoord == nscoord_MIN) {
+ LogicalRect thisScreenRect(aWM, GetScreenRectInAppUnits(),
+ containerSize);
+ mLastDropDownAfterScreenBCoord = thisScreenRect.BEnd(aWM) +
+ aTranslation->B(aWM);
+ mLastDropDownBeforeScreenBCoord = thisScreenRect.BStart(aWM) +
+ aTranslation->B(aWM);
+ }
+
+ nscoord minBCoord;
+ nsPresContext* pc = PresContext()->GetToplevelContentDocumentPresContext();
+ nsIFrame* root = pc ? pc->PresShell()->GetRootFrame() : nullptr;
+ if (root) {
+ minBCoord = LogicalRect(aWM,
+ root->GetScreenRectInAppUnits(),
+ containerSize).BStart(aWM);
+ if (mLastDropDownAfterScreenBCoord < minBCoord) {
+ // Don't allow the drop-down to be placed before the content area.
+ return;
+ }
+ } else {
+ minBCoord = logicalScreen.BStart(aWM);
+ }
+
+ nscoord after = logicalScreen.BEnd(aWM) - mLastDropDownAfterScreenBCoord;
+ nscoord before = mLastDropDownBeforeScreenBCoord - minBCoord;
+
+ // If the difference between the space before and after is less
+ // than a row-block-size, then we favor the space after.
+ if (before >= after) {
+ nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
+ nscoord rowBSize = lcf->GetBSizeOfARow();
+ if (before < after + rowBSize) {
+ before -= rowBSize;
+ }
+ }
+
+ *aAfter = after;
+ *aBefore = before;
+}
+
+nsComboboxControlFrame::DropDownPositionState
+nsComboboxControlFrame::AbsolutelyPositionDropDown()
+{
+ if (XRE_IsContentProcess()) {
+ return eDropDownPositionSuppressed;
+ }
+
+ WritingMode wm = GetWritingMode();
+ LogicalPoint translation(wm);
+ nscoord before, after;
+ mLastDropDownAfterScreenBCoord = nscoord_MIN;
+ GetAvailableDropdownSpace(wm, &before, &after, &translation);
+ if (before <= 0 && after <= 0) {
+ if (IsDroppedDown()) {
+ // Hide the view immediately to minimize flicker.
+ nsView* view = mDropdownFrame->GetView();
+ view->GetViewManager()->SetViewVisibility(view, nsViewVisibility_kHide);
+ NS_DispatchToCurrentThread(new nsAsyncRollup(this));
+ }
+ return eDropDownPositionSuppressed;
+ }
+
+ LogicalSize dropdownSize = mDropdownFrame->GetLogicalSize(wm);
+ nscoord bSize = std::max(before, after);
+ nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
+ if (bSize < dropdownSize.BSize(wm)) {
+ if (lcf->GetNumDisplayRows() > 1) {
+ // The drop-down doesn't fit and currently shows more than 1 row -
+ // schedule a resize to show fewer rows.
+ NS_DispatchToCurrentThread(new nsAsyncResize(this));
+ return eDropDownPositionPendingResize;
+ }
+ } else if (bSize > (dropdownSize.BSize(wm) + lcf->GetBSizeOfARow() * 1.5) &&
+ lcf->GetDropdownCanGrow()) {
+ // The drop-down fits but there is room for at least 1.5 more rows -
+ // schedule a resize to show more rows if it has more rows to show.
+ // (1.5 rows for good measure to avoid any rounding issues that would
+ // lead to a loop of reflow requests)
+ NS_DispatchToCurrentThread(new nsAsyncResize(this));
+ return eDropDownPositionPendingResize;
+ }
+
+ // Position the drop-down after if there is room, otherwise place it before
+ // if there is room. If there is no room for it on either side then place
+ // it after (to avoid overlapping UI like the URL bar).
+ bool b = dropdownSize.BSize(wm)<= after || dropdownSize.BSize(wm) > before;
+ LogicalPoint dropdownPosition(wm, 0, b ? BSize(wm) : -dropdownSize.BSize(wm));
+
+ // Don't position the view unless the position changed since it might cause
+ // a call to NotifyGeometryChange() and an infinite loop here.
+ nsSize containerSize = GetSize();
+ const LogicalPoint currentPos =
+ mDropdownFrame->GetLogicalPosition(containerSize);
+ const LogicalPoint newPos = dropdownPosition + translation;
+ if (currentPos != newPos) {
+ mDropdownFrame->SetPosition(wm, newPos, containerSize);
+ nsContainerFrame::PositionFrameView(mDropdownFrame);
+ }
+ return eDropDownPositionFinal;
+}
+
+void
+nsComboboxControlFrame::NotifyGeometryChange()
+{
+ if (XRE_IsContentProcess()) {
+ return;
+ }
+
+ // We don't need to resize if we're not dropped down since ShowDropDown
+ // does that, or if we're dirty then the reflow callback does it,
+ // or if we have a delayed ShowDropDown pending.
+ if (IsDroppedDown() &&
+ !(GetStateBits() & NS_FRAME_IS_DIRTY) &&
+ !mDelayedShowDropDown) {
+ // Async because we're likely in a middle of a scroll here so
+ // frame/view positions are in flux.
+ RefPtr<nsResizeDropdownAtFinalPosition> resize =
+ new nsResizeDropdownAtFinalPosition(this);
+ NS_DispatchToCurrentThread(resize);
+ }
+}
+
+//----------------------------------------------------------
+//
+//----------------------------------------------------------
+#ifdef DO_REFLOW_DEBUG
+static int myCounter = 0;
+
+static void printSize(char * aDesc, nscoord aSize)
+{
+ printf(" %s: ", aDesc);
+ if (aSize == NS_UNCONSTRAINEDSIZE) {
+ printf("UC");
+ } else {
+ printf("%d", PX(aSize));
+ }
+}
+#endif
+
+//-------------------------------------------------------------------
+//-- Main Reflow for the Combobox
+//-------------------------------------------------------------------
+
+nscoord
+nsComboboxControlFrame::GetIntrinsicISize(nsRenderingContext* aRenderingContext,
+ nsLayoutUtils::IntrinsicISizeType aType)
+{
+ // get the scrollbar width, we'll use this later
+ nscoord scrollbarWidth = 0;
+ nsPresContext* presContext = PresContext();
+ if (mListControlFrame) {
+ nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
+ NS_ASSERTION(scrollable, "List must be a scrollable frame");
+ scrollbarWidth = scrollable->GetNondisappearingScrollbarWidth(
+ presContext, aRenderingContext, GetWritingMode());
+ }
+
+ nscoord displayISize = 0;
+ if (MOZ_LIKELY(mDisplayFrame)) {
+ displayISize = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+ mDisplayFrame,
+ aType);
+ }
+
+ if (mDropdownFrame) {
+ nscoord dropdownContentISize;
+ bool isUsingOverlayScrollbars =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0;
+ if (aType == nsLayoutUtils::MIN_ISIZE) {
+ dropdownContentISize = mDropdownFrame->GetMinISize(aRenderingContext);
+ if (isUsingOverlayScrollbars) {
+ dropdownContentISize += scrollbarWidth;
+ }
+ } else {
+ NS_ASSERTION(aType == nsLayoutUtils::PREF_ISIZE, "Unexpected type");
+ dropdownContentISize = mDropdownFrame->GetPrefISize(aRenderingContext);
+ if (isUsingOverlayScrollbars) {
+ dropdownContentISize += scrollbarWidth;
+ }
+ }
+ dropdownContentISize = NSCoordSaturatingSubtract(dropdownContentISize,
+ scrollbarWidth,
+ nscoord_MAX);
+
+ displayISize = std::max(dropdownContentISize, displayISize);
+ }
+
+ // add room for the dropmarker button if there is one
+ if ((!IsThemed() ||
+ presContext->GetTheme()->ThemeNeedsComboboxDropmarker()) &&
+ StyleDisplay()->mAppearance != NS_THEME_NONE) {
+ displayISize += scrollbarWidth;
+ }
+
+ return displayISize;
+
+}
+
+nscoord
+nsComboboxControlFrame::GetMinISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord minISize;
+ DISPLAY_MIN_WIDTH(this, minISize);
+ minISize = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::MIN_ISIZE);
+ return minISize;
+}
+
+nscoord
+nsComboboxControlFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord prefISize;
+ DISPLAY_PREF_WIDTH(this, prefISize);
+ prefISize = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::PREF_ISIZE);
+ return prefISize;
+}
+
+void
+nsComboboxControlFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ // Constraints we try to satisfy:
+
+ // 1) Default inline size of button is the vertical scrollbar size
+ // 2) If the inline size of button is bigger than our inline size, set
+ // inline size of button to 0.
+ // 3) Default block size of button is block size of display area
+ // 4) Inline size of display area is whatever is left over from our
+ // inline size after allocating inline size for the button.
+ // 5) Block Size of display area is GetBSizeOfARow() on the
+ // mListControlFrame.
+
+ if (!mDisplayFrame || !mButtonFrame || !mDropdownFrame) {
+ NS_ERROR("Why did the frame constructor allow this to happen? Fix it!!");
+ return;
+ }
+
+ // Make sure the displayed text is the same as the selected option, bug 297389.
+ int32_t selectedIndex;
+ nsAutoString selectedOptionText;
+ if (!mDroppedDown) {
+ selectedIndex = mListControlFrame->GetSelectedIndex();
+ }
+ else {
+ // In dropped down mode the "selected index" is the hovered menu item,
+ // we want the last selected item which is |mDisplayedIndex| in this case.
+ selectedIndex = mDisplayedIndex;
+ }
+ if (selectedIndex != -1) {
+ mListControlFrame->GetOptionText(selectedIndex, selectedOptionText);
+ }
+ if (mDisplayedOptionText != selectedOptionText) {
+ RedisplayText(selectedIndex);
+ }
+
+ // First reflow our dropdown so that we know how tall we should be.
+ ReflowDropdown(aPresContext, aReflowInput);
+ RefPtr<nsResizeDropdownAtFinalPosition> resize =
+ new nsResizeDropdownAtFinalPosition(this);
+ if (NS_SUCCEEDED(aPresContext->PresShell()->PostReflowCallback(resize))) {
+ // The reflow callback queue doesn't AddRef so we keep it alive until
+ // it's released in its ReflowFinished / ReflowCallbackCanceled.
+ Unused << resize.forget();
+ }
+
+ // Get the width of the vertical scrollbar. That will be the inline
+ // size of the dropdown button.
+ WritingMode wm = aReflowInput.GetWritingMode();
+ nscoord buttonISize;
+ const nsStyleDisplay *disp = StyleDisplay();
+ if ((IsThemed(disp) && !aPresContext->GetTheme()->ThemeNeedsComboboxDropmarker()) ||
+ StyleDisplay()->mAppearance == NS_THEME_NONE) {
+ buttonISize = 0;
+ }
+ else {
+ nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
+ NS_ASSERTION(scrollable, "List must be a scrollable frame");
+ buttonISize = scrollable->GetNondisappearingScrollbarWidth(
+ PresContext(), aReflowInput.mRenderingContext, wm);
+ if (buttonISize > aReflowInput.ComputedISize()) {
+ buttonISize = 0;
+ }
+ }
+
+ mDisplayISize = aReflowInput.ComputedISize() - buttonISize;
+
+ nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
+
+ // The button should occupy the same space as a scrollbar
+ nsSize containerSize = aDesiredSize.PhysicalSize();
+ LogicalRect buttonRect = mButtonFrame->GetLogicalRect(containerSize);
+
+ buttonRect.IStart(wm) =
+ aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm) +
+ mDisplayISize -
+ (aReflowInput.ComputedLogicalBorderPadding().IEnd(wm) -
+ aReflowInput.ComputedLogicalPadding().IEnd(wm));
+ buttonRect.ISize(wm) = buttonISize;
+
+ buttonRect.BStart(wm) = this->GetLogicalUsedBorder(wm).BStart(wm);
+ buttonRect.BSize(wm) = mDisplayFrame->BSize(wm) +
+ this->GetLogicalUsedPadding(wm).BStartEnd(wm);
+
+ mButtonFrame->SetRect(buttonRect, containerSize);
+
+ if (!NS_INLINE_IS_BREAK_BEFORE(aStatus) &&
+ !NS_FRAME_IS_FULLY_COMPLETE(aStatus)) {
+ // This frame didn't fit inside a fragmentation container. Splitting
+ // a nsComboboxControlFrame makes no sense, so we override the status here.
+ aStatus = NS_FRAME_COMPLETE;
+ }
+}
+
+//--------------------------------------------------------------
+
+nsIAtom*
+nsComboboxControlFrame::GetType() const
+{
+ return nsGkAtoms::comboboxControlFrame;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsComboboxControlFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("ComboboxControl"), aResult);
+}
+#endif
+
+
+//----------------------------------------------------------------------
+// nsIComboboxControlFrame
+//----------------------------------------------------------------------
+void
+nsComboboxControlFrame::ShowDropDown(bool aDoDropDown)
+{
+ MOZ_ASSERT(!XRE_IsContentProcess());
+ mDelayedShowDropDown = false;
+ EventStates eventStates = mContent->AsElement()->State();
+ if (aDoDropDown && eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
+ return;
+ }
+
+ if (!mDroppedDown && aDoDropDown) {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm || fm->GetFocusedContent() == GetContent()) {
+ DropDownPositionState state = AbsolutelyPositionDropDown();
+ if (state == eDropDownPositionFinal) {
+ ShowList(aDoDropDown); // might destroy us
+ } else if (state == eDropDownPositionPendingResize) {
+ // Delay until after the resize reflow, see nsAsyncResize.
+ mDelayedShowDropDown = true;
+ }
+ } else {
+ // Delay until we get focus, see SetFocus().
+ mDelayedShowDropDown = true;
+ }
+ } else if (mDroppedDown && !aDoDropDown) {
+ ShowList(aDoDropDown); // might destroy us
+ }
+}
+
+void
+nsComboboxControlFrame::SetDropDown(nsIFrame* aDropDownFrame)
+{
+ mDropdownFrame = aDropDownFrame;
+ mListControlFrame = do_QueryFrame(mDropdownFrame);
+ if (!sFocused && nsContentUtils::IsFocusedContent(GetContent())) {
+ sFocused = this;
+ nsListControlFrame::ComboboxFocusSet();
+ }
+}
+
+nsIFrame*
+nsComboboxControlFrame::GetDropDown()
+{
+ return mDropdownFrame;
+}
+
+///////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsComboboxControlFrame::RedisplaySelectedText()
+{
+ nsAutoScriptBlocker scriptBlocker;
+ return RedisplayText(mListControlFrame->GetSelectedIndex());
+}
+
+nsresult
+nsComboboxControlFrame::RedisplayText(int32_t aIndex)
+{
+ // Get the text to display
+ if (aIndex != -1) {
+ mListControlFrame->GetOptionText(aIndex, mDisplayedOptionText);
+ } else {
+ mDisplayedOptionText.Truncate();
+ }
+ mDisplayedIndex = aIndex;
+
+ REFLOW_DEBUG_MSG2("RedisplayText \"%s\"\n",
+ NS_LossyConvertUTF16toASCII(mDisplayedOptionText).get());
+
+ // Send reflow command because the new text maybe larger
+ nsresult rv = NS_OK;
+ if (mDisplayContent) {
+ // Don't call ActuallyDisplayText(true) directly here since that
+ // could cause recursive frame construction. See bug 283117 and the comment in
+ // HandleRedisplayTextEvent() below.
+
+ // Revoke outstanding events to avoid out-of-order events which could mean
+ // displaying the wrong text.
+ mRedisplayTextEvent.Revoke();
+
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "If we happen to run our redisplay event now, we might kill "
+ "ourselves!");
+
+ RefPtr<RedisplayTextEvent> event = new RedisplayTextEvent(this);
+ mRedisplayTextEvent = event;
+ nsContentUtils::AddScriptRunner(event);
+ }
+ return rv;
+}
+
+void
+nsComboboxControlFrame::HandleRedisplayTextEvent()
+{
+ // First, make sure that the content model is up to date and we've
+ // constructed the frames for all our content in the right places.
+ // Otherwise they'll end up under the wrong insertion frame when we
+ // ActuallyDisplayText, since that flushes out the content sink by
+ // calling SetText on a DOM node with aNotify set to true. See bug
+ // 289730.
+ nsWeakFrame weakThis(this);
+ PresContext()->Document()->
+ FlushPendingNotifications(Flush_ContentAndNotify);
+ if (!weakThis.IsAlive())
+ return;
+
+ // Redirect frame insertions during this method (see GetContentInsertionFrame())
+ // so that any reframing that the frame constructor forces upon us is inserted
+ // into the correct parent (mDisplayFrame). See bug 282607.
+ NS_PRECONDITION(!mInRedisplayText, "Nested RedisplayText");
+ mInRedisplayText = true;
+ mRedisplayTextEvent.Forget();
+
+ ActuallyDisplayText(true);
+ // XXXbz This should perhaps be eResize. Check.
+ PresContext()->PresShell()->FrameNeedsReflow(mDisplayFrame,
+ nsIPresShell::eStyleChange,
+ NS_FRAME_IS_DIRTY);
+
+ mInRedisplayText = false;
+}
+
+void
+nsComboboxControlFrame::ActuallyDisplayText(bool aNotify)
+{
+ if (mDisplayedOptionText.IsEmpty()) {
+ // Have to use a non-breaking space for line-block-size calculations
+ // to be right
+ static const char16_t space = 0xA0;
+ mDisplayContent->SetText(&space, 1, aNotify);
+ } else {
+ mDisplayContent->SetText(mDisplayedOptionText, aNotify);
+ }
+}
+
+int32_t
+nsComboboxControlFrame::GetIndexOfDisplayArea()
+{
+ return mDisplayedIndex;
+}
+
+//----------------------------------------------------------------------
+// nsISelectControlFrame
+//----------------------------------------------------------------------
+NS_IMETHODIMP
+nsComboboxControlFrame::DoneAddingChildren(bool aIsDone)
+{
+ nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
+ if (!listFrame)
+ return NS_ERROR_FAILURE;
+
+ return listFrame->DoneAddingChildren(aIsDone);
+}
+
+NS_IMETHODIMP
+nsComboboxControlFrame::AddOption(int32_t aIndex)
+{
+ if (aIndex <= mDisplayedIndex) {
+ ++mDisplayedIndex;
+ }
+
+ nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
+ return lcf->AddOption(aIndex);
+}
+
+
+NS_IMETHODIMP
+nsComboboxControlFrame::RemoveOption(int32_t aIndex)
+{
+ nsWeakFrame weakThis(this);
+ if (mListControlFrame->GetNumberOfOptions() > 0) {
+ if (aIndex < mDisplayedIndex) {
+ --mDisplayedIndex;
+ } else if (aIndex == mDisplayedIndex) {
+ mDisplayedIndex = 0; // IE6 compat
+ RedisplayText(mDisplayedIndex);
+ }
+ }
+ else {
+ // If we removed the last option, we need to blank things out
+ RedisplayText(-1);
+ }
+
+ if (!weakThis.IsAlive())
+ return NS_OK;
+
+ nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
+ return lcf->RemoveOption(aIndex);
+}
+
+NS_IMETHODIMP
+nsComboboxControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex)
+{
+ nsAutoScriptBlocker scriptBlocker;
+ RedisplayText(aNewIndex);
+ NS_ASSERTION(mDropdownFrame, "No dropdown frame!");
+
+ nsISelectControlFrame* listFrame = do_QueryFrame(mDropdownFrame);
+ NS_ASSERTION(listFrame, "No list frame!");
+
+ return listFrame->OnSetSelectedIndex(aOldIndex, aNewIndex);
+}
+
+// End nsISelectControlFrame
+//----------------------------------------------------------------------
+
+nsresult
+nsComboboxControlFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+
+ if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
+ return NS_OK;
+ }
+
+ EventStates eventStates = mContent->AsElement()->State();
+ if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
+ return NS_OK;
+ }
+
+#if COMBOBOX_ROLLUP_CONSUME_EVENT == 0
+ if (aEvent->mMessage == eMouseDown) {
+ nsIWidget* widget = GetNearestWidget();
+ if (widget && GetContent() == widget->GetLastRollup()) {
+ // This event did a Rollup on this control - prevent it from opening
+ // the dropdown again!
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+ return NS_OK;
+ }
+ }
+#endif
+
+ // If we have style that affects how we are selected, feed event down to
+ // nsFrame::HandleEvent so that selection takes place when appropriate.
+ const nsStyleUserInterface* uiStyle = StyleUserInterface();
+ if (uiStyle->mUserInput == StyleUserInput::None ||
+ uiStyle->mUserInput == StyleUserInput::Disabled) {
+ return nsBlockFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+ }
+ return NS_OK;
+}
+
+
+nsresult
+nsComboboxControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
+{
+ nsIFormControlFrame* fcFrame = do_QueryFrame(mDropdownFrame);
+ if (!fcFrame) {
+ return NS_NOINTERFACE;
+ }
+
+ return fcFrame->SetFormProperty(aName, aValue);
+}
+
+nsContainerFrame*
+nsComboboxControlFrame::GetContentInsertionFrame() {
+ return mInRedisplayText ? mDisplayFrame : mDropdownFrame->GetContentInsertionFrame();
+}
+
+nsresult
+nsComboboxControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+{
+ // The frames used to display the combo box and the button used to popup the dropdown list
+ // are created through anonymous content. The dropdown list is not created through anonymous
+ // content because its frame is initialized specifically for the drop-down case and it is placed
+ // a special list referenced through NS_COMBO_FRAME_POPUP_LIST_INDEX to keep separate from the
+ // layout of the display and button.
+ //
+ // Note: The value attribute of the display content is set when an item is selected in the dropdown list.
+ // If the content specified below does not honor the value attribute than nothing will be displayed.
+
+ // For now the content that is created corresponds to two input buttons. It would be better to create the
+ // tag as something other than input, but then there isn't any way to create a button frame since it
+ // isn't possible to set the display type in CSS2 to create a button frame.
+
+ // create content used for display
+ //nsIAtom* tag = NS_Atomize("mozcombodisplay");
+
+ // Add a child text content node for the label
+
+ nsNodeInfoManager *nimgr = mContent->NodeInfo()->NodeInfoManager();
+
+ mDisplayContent = new nsTextNode(nimgr);
+
+ // set the value of the text node
+ mDisplayedIndex = mListControlFrame->GetSelectedIndex();
+ if (mDisplayedIndex != -1) {
+ mListControlFrame->GetOptionText(mDisplayedIndex, mDisplayedOptionText);
+ }
+ ActuallyDisplayText(false);
+
+ if (!aElements.AppendElement(mDisplayContent))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ mButtonContent = mContent->OwnerDoc()->CreateHTMLElement(nsGkAtoms::button);
+ if (!mButtonContent)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // make someone to listen to the button. If its pressed by someone like Accessibility
+ // then open or close the combo box.
+ mButtonListener = new nsComboButtonListener(this);
+ mButtonContent->AddEventListener(NS_LITERAL_STRING("click"), mButtonListener,
+ false, false);
+
+ mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
+ NS_LITERAL_STRING("button"), false);
+ // Set tabindex="-1" so that the button is not tabbable
+ mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
+ NS_LITERAL_STRING("-1"), false);
+
+ WritingMode wm = GetWritingMode();
+ if (wm.IsVertical()) {
+ mButtonContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orientation,
+ wm.IsVerticalRL() ? NS_LITERAL_STRING("left")
+ : NS_LITERAL_STRING("right"),
+ false);
+ }
+
+ if (!aElements.AppendElement(mButtonContent))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+void
+nsComboboxControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter)
+{
+ if (mDisplayContent) {
+ aElements.AppendElement(mDisplayContent);
+ }
+
+ if (mButtonContent) {
+ aElements.AppendElement(mButtonContent);
+ }
+}
+
+// XXXbz this is a for-now hack. Now that display:inline-block works,
+// need to revisit this.
+class nsComboboxDisplayFrame : public nsBlockFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ nsComboboxDisplayFrame (nsStyleContext* aContext,
+ nsComboboxControlFrame* aComboBox)
+ : nsBlockFrame(aContext),
+ mComboBox(aComboBox)
+ {}
+
+ // Need this so that line layout knows that this block's inline size
+ // depends on the available inline size.
+ virtual nsIAtom* GetType() const override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsBlockFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eReplacedContainsBlock));
+ }
+
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+protected:
+ nsComboboxControlFrame* mComboBox;
+};
+
+NS_IMPL_FRAMEARENA_HELPERS(nsComboboxDisplayFrame)
+
+nsIAtom*
+nsComboboxDisplayFrame::GetType() const
+{
+ return nsGkAtoms::comboboxDisplayFrame;
+}
+
+void
+nsComboboxDisplayFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ ReflowInput state(aReflowInput);
+ if (state.ComputedBSize() == NS_INTRINSICSIZE) {
+ // Note that the only way we can have a computed block size here is
+ // if the combobox had a specified block size. If it didn't, size
+ // based on what our rows look like, for lack of anything better.
+ state.SetComputedBSize(mComboBox->mListControlFrame->GetBSizeOfARow());
+ }
+ WritingMode wm = aReflowInput.GetWritingMode();
+ nscoord computedISize = mComboBox->mDisplayISize -
+ state.ComputedLogicalBorderPadding().IStartEnd(wm);
+ if (computedISize < 0) {
+ computedISize = 0;
+ }
+ state.SetComputedISize(computedISize);
+ nsBlockFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
+ aStatus = NS_FRAME_COMPLETE; // this type of frame can't be split
+}
+
+void
+nsComboboxDisplayFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ nsDisplayListCollection set;
+ nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, set);
+
+ // remove background items if parent frame is themed
+ if (mComboBox->IsThemed()) {
+ set.BorderBackground()->DeleteAll();
+ }
+
+ set.MoveTo(aLists);
+}
+
+nsIFrame*
+nsComboboxControlFrame::CreateFrameFor(nsIContent* aContent)
+{
+ NS_PRECONDITION(nullptr != aContent, "null ptr");
+
+ NS_ASSERTION(mDisplayContent, "mDisplayContent can't be null!");
+
+ if (mDisplayContent != aContent) {
+ // We only handle the frames for mDisplayContent here
+ return nullptr;
+ }
+
+ // Get PresShell
+ nsIPresShell *shell = PresContext()->PresShell();
+ StyleSetHandle styleSet = shell->StyleSet();
+
+ // create the style contexts for the anonymous block frame and text frame
+ RefPtr<nsStyleContext> styleContext;
+ styleContext = styleSet->
+ ResolveAnonymousBoxStyle(nsCSSAnonBoxes::mozDisplayComboboxControlFrame,
+ mStyleContext,
+ nsStyleSet::eSkipParentDisplayBasedStyleFixup);
+
+ RefPtr<nsStyleContext> textStyleContext;
+ textStyleContext =
+ styleSet->ResolveStyleForText(mDisplayContent, mStyleContext);
+
+ // Start by creating our anonymous block frame
+ mDisplayFrame = new (shell) nsComboboxDisplayFrame(styleContext, this);
+ mDisplayFrame->Init(mContent, this, nullptr);
+
+ // Create a text frame and put it inside the block frame
+ nsIFrame* textFrame = NS_NewTextFrame(shell, textStyleContext);
+
+ // initialize the text frame
+ textFrame->Init(aContent, mDisplayFrame, nullptr);
+ mDisplayContent->SetPrimaryFrame(textFrame);
+
+ nsFrameList textList(textFrame, textFrame);
+ mDisplayFrame->SetInitialChildList(kPrincipalList, textList);
+ return mDisplayFrame;
+}
+
+void
+nsComboboxControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ if (sFocused == this) {
+ sFocused = nullptr;
+ }
+
+ // Revoke any pending RedisplayTextEvent
+ mRedisplayTextEvent.Revoke();
+
+ nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
+
+ if (mDroppedDown) {
+ MOZ_ASSERT(mDropdownFrame, "mDroppedDown without frame");
+ nsView* view = mDropdownFrame->GetView();
+ MOZ_ASSERT(view);
+ nsIWidget* widget = view->GetWidget();
+ if (widget) {
+ widget->CaptureRollupEvents(this, false);
+ }
+ }
+
+ // Cleanup frames in popup child list
+ mPopupFrames.DestroyFramesFrom(aDestructRoot);
+ nsContentUtils::DestroyAnonymousContent(&mDisplayContent);
+ nsContentUtils::DestroyAnonymousContent(&mButtonContent);
+ nsBlockFrame::DestroyFrom(aDestructRoot);
+}
+
+const nsFrameList&
+nsComboboxControlFrame::GetChildList(ChildListID aListID) const
+{
+ if (kSelectPopupList == aListID) {
+ return mPopupFrames;
+ }
+ return nsBlockFrame::GetChildList(aListID);
+}
+
+void
+nsComboboxControlFrame::GetChildLists(nsTArray<ChildList>* aLists) const
+{
+ nsBlockFrame::GetChildLists(aLists);
+ mPopupFrames.AppendIfNonempty(aLists, kSelectPopupList);
+}
+
+void
+nsComboboxControlFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ if (kSelectPopupList == aListID) {
+ mPopupFrames.SetFrames(aChildList);
+ } else {
+ for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) {
+ nsCOMPtr<nsIFormControl> formControl =
+ do_QueryInterface(e.get()->GetContent());
+ if (formControl && formControl->GetType() == NS_FORM_BUTTON_BUTTON) {
+ mButtonFrame = e.get();
+ break;
+ }
+ }
+ NS_ASSERTION(mButtonFrame, "missing button frame in initial child list");
+ nsBlockFrame::SetInitialChildList(aListID, aChildList);
+ }
+}
+
+//----------------------------------------------------------------------
+ //nsIRollupListener
+//----------------------------------------------------------------------
+bool
+nsComboboxControlFrame::Rollup(uint32_t aCount, bool aFlush,
+ const nsIntPoint* pos, nsIContent** aLastRolledUp)
+{
+ if (!mDroppedDown) {
+ return false;
+ }
+
+ bool consume = !!COMBOBOX_ROLLUP_CONSUME_EVENT;
+ nsWeakFrame weakFrame(this);
+ mListControlFrame->AboutToRollup(); // might destroy us
+ if (!weakFrame.IsAlive()) {
+ return consume;
+ }
+ ShowDropDown(false); // might destroy us
+ if (weakFrame.IsAlive()) {
+ mListControlFrame->CaptureMouseEvents(false);
+ }
+
+ if (aFlush && weakFrame.IsAlive()) {
+ // The popup's visibility doesn't update until the minimize animation has
+ // finished, so call UpdateWidgetGeometry to update it right away.
+ nsViewManager* viewManager = mDropdownFrame->GetView()->GetViewManager();
+ viewManager->UpdateWidgetGeometry(); // might destroy us
+ }
+
+ if (!weakFrame.IsAlive()) {
+ return consume;
+ }
+
+ if (aLastRolledUp) {
+ *aLastRolledUp = GetContent();
+ }
+ return consume;
+}
+
+nsIWidget*
+nsComboboxControlFrame::GetRollupWidget()
+{
+ nsView* view = mDropdownFrame->GetView();
+ MOZ_ASSERT(view);
+ return view->GetWidget();
+}
+
+void
+nsComboboxControlFrame::RollupFromList()
+{
+ if (ShowList(false))
+ mListControlFrame->CaptureMouseEvents(false);
+}
+
+int32_t
+nsComboboxControlFrame::UpdateRecentIndex(int32_t aIndex)
+{
+ int32_t index = mRecentSelectedIndex;
+ if (mRecentSelectedIndex == NS_SKIP_NOTIFY_INDEX || aIndex == NS_SKIP_NOTIFY_INDEX)
+ mRecentSelectedIndex = aIndex;
+ return index;
+}
+
+class nsDisplayComboboxFocus : public nsDisplayItem {
+public:
+ nsDisplayComboboxFocus(nsDisplayListBuilder* aBuilder,
+ nsComboboxControlFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayComboboxFocus);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayComboboxFocus() {
+ MOZ_COUNT_DTOR(nsDisplayComboboxFocus);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("ComboboxFocus", TYPE_COMBOBOX_FOCUS)
+};
+
+void nsDisplayComboboxFocus::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ static_cast<nsComboboxControlFrame*>(mFrame)
+ ->PaintFocus(*aCtx->GetDrawTarget(), ToReferenceFrame());
+}
+
+void
+nsComboboxControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+#ifdef NOISY
+ printf("%p paint at (%d, %d, %d, %d)\n", this,
+ aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height);
+#endif
+
+ if (aBuilder->IsForEventDelivery()) {
+ // Don't allow children to receive events.
+ // REVIEW: following old GetFrameForPoint
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+ } else {
+ // REVIEW: Our in-flow child frames are inline-level so they will paint in our
+ // content list, so we don't need to mess with layers.
+ nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+ }
+
+ // draw a focus indicator only when focus rings should be drawn
+ nsIDocument* doc = mContent->GetComposedDoc();
+ if (doc) {
+ nsPIDOMWindowOuter* window = doc->GetWindow();
+ if (window && window->ShouldShowFocusRing()) {
+ nsPresContext *presContext = PresContext();
+ const nsStyleDisplay *disp = StyleDisplay();
+ if ((!IsThemed(disp) ||
+ !presContext->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) &&
+ mDisplayFrame && IsVisibleForPainting(aBuilder)) {
+ aLists.Content()->AppendNewToTop(
+ new (aBuilder) nsDisplayComboboxFocus(aBuilder, this));
+ }
+ }
+ }
+
+ DisplaySelectionOverlay(aBuilder, aLists.Content());
+}
+
+void nsComboboxControlFrame::PaintFocus(DrawTarget& aDrawTarget, nsPoint aPt)
+{
+ /* Do we need to do anything? */
+ EventStates eventStates = mContent->AsElement()->State();
+ if (eventStates.HasState(NS_EVENT_STATE_DISABLED) || sFocused != this)
+ return;
+
+ int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
+
+ nsRect clipRect = mDisplayFrame->GetRect() + aPt;
+ aDrawTarget.PushClipRect(NSRectToSnappedRect(clipRect,
+ appUnitsPerDevPixel,
+ aDrawTarget));
+
+ // REVIEW: Why does the old code paint mDisplayFrame again? We've
+ // already painted it in the children above. So clipping it here won't do
+ // us much good.
+
+ /////////////////////
+ // draw focus
+
+ StrokeOptions strokeOptions;
+ nsLayoutUtils::InitDashPattern(strokeOptions, NS_STYLE_BORDER_STYLE_DOTTED);
+ ColorPattern color(ToDeviceColor(StyleColor()->mColor));
+ nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
+ clipRect.width -= onePixel;
+ clipRect.height -= onePixel;
+ Rect r = ToRect(nsLayoutUtils::RectToGfxRect(clipRect, appUnitsPerDevPixel));
+ StrokeSnappedEdgesOfRect(r, aDrawTarget, color, strokeOptions);
+
+ aDrawTarget.PopClip();
+}
+
+//---------------------------------------------------------
+// gets the content (an option) by index and then set it as
+// being selected or not selected
+//---------------------------------------------------------
+NS_IMETHODIMP
+nsComboboxControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected)
+{
+ if (mDroppedDown) {
+ nsISelectControlFrame *selectFrame = do_QueryFrame(mListControlFrame);
+ if (selectFrame) {
+ selectFrame->OnOptionSelected(aIndex, aSelected);
+ }
+ } else {
+ if (aSelected) {
+ nsAutoScriptBlocker blocker;
+ RedisplayText(aIndex);
+ } else {
+ nsWeakFrame weakFrame(this);
+ RedisplaySelectedText();
+ if (weakFrame.IsAlive()) {
+ FireValueChangeEvent(); // Fire after old option is unselected
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsComboboxControlFrame::FireValueChangeEvent()
+{
+ // Fire ValueChange event to indicate data value of combo box has changed
+ nsContentUtils::AddScriptRunner(
+ new AsyncEventDispatcher(mContent, NS_LITERAL_STRING("ValueChange"), true,
+ false));
+}
+
+void
+nsComboboxControlFrame::OnContentReset()
+{
+ if (mListControlFrame) {
+ mListControlFrame->OnContentReset();
+ }
+}
+
+
+//--------------------------------------------------------
+// nsIStatefulFrame
+//--------------------------------------------------------
+NS_IMETHODIMP
+nsComboboxControlFrame::SaveState(nsPresState** aState)
+{
+ MOZ_ASSERT(!(*aState));
+ (*aState) = new nsPresState();
+ (*aState)->SetDroppedDown(mDroppedDown);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsComboboxControlFrame::RestoreState(nsPresState* aState)
+{
+ if (!aState) {
+ return NS_ERROR_FAILURE;
+ }
+ ShowList(aState->GetDroppedDown()); // might destroy us
+ return NS_OK;
+}
+
+// Append a suffix so that the state key for the combobox is different
+// from the state key the list control uses to sometimes save the scroll
+// position for the same Element
+NS_IMETHODIMP
+nsComboboxControlFrame::GenerateStateKey(nsIContent* aContent,
+ nsIDocument* aDocument,
+ nsACString& aKey)
+{
+ nsresult rv = nsContentUtils::GenerateStateKey(aContent, aDocument, aKey);
+ if (NS_FAILED(rv) || aKey.IsEmpty()) {
+ return rv;
+ }
+ aKey.Append("CCF");
+ return NS_OK;
+}
+
+// Fennec uses a custom combobox built-in widget.
+//
+
+/* static */
+bool
+nsComboboxControlFrame::ToolkitHasNativePopup()
+{
+#ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
+ return true;
+#else
+ return false;
+#endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
+}
+
diff --git a/layout/forms/nsComboboxControlFrame.h b/layout/forms/nsComboboxControlFrame.h
new file mode 100644
index 000000000..22849e8d1
--- /dev/null
+++ b/layout/forms/nsComboboxControlFrame.h
@@ -0,0 +1,328 @@
+/* -*- 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 nsComboboxControlFrame_h___
+#define nsComboboxControlFrame_h___
+
+#ifdef DEBUG_evaughan
+//#define DEBUG_rods
+#endif
+
+#ifdef DEBUG_rods
+//#define DO_REFLOW_DEBUG
+//#define DO_REFLOW_COUNTER
+//#define DO_UNCONSTRAINED_CHECK
+//#define DO_PIXELS
+//#define DO_NEW_REFLOW
+#endif
+
+//Mark used to indicate when onchange has been fired for current combobox item
+#define NS_SKIP_NOTIFY_INDEX -2
+
+#include "mozilla/Attributes.h"
+#include "nsBlockFrame.h"
+#include "nsIFormControlFrame.h"
+#include "nsIComboboxControlFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsISelectControlFrame.h"
+#include "nsIRollupListener.h"
+#include "nsIStatefulFrame.h"
+#include "nsThreadUtils.h"
+
+class nsStyleContext;
+class nsIListControlFrame;
+class nsComboboxDisplayFrame;
+class nsIDOMEventListener;
+class nsIScrollableFrame;
+
+namespace mozilla {
+namespace gfx {
+class DrawTarget;
+} // namespace gfx
+} // namespace mozilla
+
+class nsComboboxControlFrame final : public nsBlockFrame,
+ public nsIFormControlFrame,
+ public nsIComboboxControlFrame,
+ public nsIAnonymousContentCreator,
+ public nsISelectControlFrame,
+ public nsIRollupListener,
+ public nsIStatefulFrame
+{
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+public:
+ friend nsContainerFrame* NS_NewComboboxControlFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext,
+ nsFrameState aFlags);
+ friend class nsComboboxDisplayFrame;
+
+ explicit nsComboboxControlFrame(nsStyleContext* aContext);
+ ~nsComboboxControlFrame();
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIAnonymousContentCreator
+ virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
+ virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter) override;
+ virtual nsIFrame* CreateFrameFor(nsIContent* aContent) override;
+
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+ virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override;
+
+ virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override;
+
+ virtual void Reflow(nsPresContext* aCX,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ void PaintFocus(DrawTarget& aDrawTarget, nsPoint aPt);
+
+ // XXXbz this is only needed to prevent the quirk percent height stuff from
+ // leaking out of the combobox. We may be able to get rid of this as more
+ // things move to IsFrameOfType.
+ virtual nsIAtom* GetType() const override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsBlockFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
+ }
+
+ virtual nsIScrollableFrame* GetScrollTargetFrame() override {
+ return do_QueryFrame(mDropdownFrame);
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+ virtual void SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+ virtual const nsFrameList& GetChildList(ChildListID aListID) const override;
+ virtual void GetChildLists(nsTArray<ChildList>* aLists) const override;
+
+ virtual nsContainerFrame* GetContentInsertionFrame() override;
+
+ // nsIFormControlFrame
+ virtual nsresult SetFormProperty(nsIAtom* aName, const nsAString& aValue) override;
+ /**
+ * Inform the control that it got (or lost) focus.
+ * If it lost focus, the dropdown menu will be rolled up if needed,
+ * and FireOnChange() will be called.
+ * @param aOn true if got focus, false if lost focus.
+ * @param aRepaint if true then force repaint (NOTE: we always force repaint currently)
+ * @note This method might destroy |this|.
+ */
+ virtual void SetFocus(bool aOn, bool aRepaint) override;
+
+ //nsIComboboxControlFrame
+ virtual bool IsDroppedDown() override { return mDroppedDown; }
+ /**
+ * @note This method might destroy |this|.
+ */
+ virtual void ShowDropDown(bool aDoDropDown) override;
+ virtual nsIFrame* GetDropDown() override;
+ virtual void SetDropDown(nsIFrame* aDropDownFrame) override;
+ /**
+ * @note This method might destroy |this|.
+ */
+ virtual void RollupFromList() override;
+
+ /**
+ * Return the available space before and after this frame for
+ * placing the drop-down list, and the current 2D translation.
+ * Note that either or both can be less than or equal to zero,
+ * if both are then the drop-down should be closed.
+ */
+ void GetAvailableDropdownSpace(mozilla::WritingMode aWM,
+ nscoord* aBefore,
+ nscoord* aAfter,
+ mozilla::LogicalPoint* aTranslation);
+ virtual int32_t GetIndexOfDisplayArea() override;
+ /**
+ * @note This method might destroy |this|.
+ */
+ NS_IMETHOD RedisplaySelectedText() override;
+ virtual int32_t UpdateRecentIndex(int32_t aIndex) override;
+ virtual void OnContentReset() override;
+
+
+ bool IsOpenInParentProcess() override
+ {
+ return mIsOpenInParentProcess;
+ }
+
+ void SetOpenInParentProcess(bool aVal) override
+ {
+ mIsOpenInParentProcess = aVal;
+ }
+
+ // nsISelectControlFrame
+ NS_IMETHOD AddOption(int32_t index) override;
+ NS_IMETHOD RemoveOption(int32_t index) override;
+ NS_IMETHOD DoneAddingChildren(bool aIsDone) override;
+ NS_IMETHOD OnOptionSelected(int32_t aIndex, bool aSelected) override;
+ NS_IMETHOD OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex) override;
+
+ //nsIRollupListener
+ /**
+ * Hide the dropdown menu and stop capturing mouse events.
+ * @note This method might destroy |this|.
+ */
+ virtual bool Rollup(uint32_t aCount, bool aFlush,
+ const nsIntPoint* pos, nsIContent** aLastRolledUp) override;
+ virtual void NotifyGeometryChange() override;
+
+ /**
+ * A combobox should roll up if a mousewheel event happens outside of
+ * the popup area.
+ */
+ virtual bool ShouldRollupOnMouseWheelEvent() override
+ { return true; }
+
+ virtual bool ShouldConsumeOnMouseWheelEvent() override
+ { return false; }
+
+ /**
+ * A combobox should not roll up if activated by a mouse activate message
+ * (eg. X-mouse).
+ */
+ virtual bool ShouldRollupOnMouseActivate() override
+ { return false; }
+
+ virtual uint32_t GetSubmenuWidgetChain(nsTArray<nsIWidget*> *aWidgetChain) override
+ { return 0; }
+
+ virtual nsIWidget* GetRollupWidget() override;
+
+ //nsIStatefulFrame
+ NS_IMETHOD SaveState(nsPresState** aState) override;
+ NS_IMETHOD RestoreState(nsPresState* aState) override;
+ NS_IMETHOD GenerateStateKey(nsIContent* aContent,
+ nsIDocument* aDocument,
+ nsACString& aKey) override;
+
+ static bool ToolkitHasNativePopup();
+
+protected:
+ friend class RedisplayTextEvent;
+ friend class nsAsyncResize;
+ friend class nsResizeDropdownAtFinalPosition;
+
+ // Utilities
+ void ReflowDropdown(nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput);
+
+ enum DropDownPositionState {
+ // can't show the dropdown at its current position
+ eDropDownPositionSuppressed,
+ // a resize reflow is pending, don't show it yet
+ eDropDownPositionPendingResize,
+ // the dropdown has its final size and position and can be displayed here
+ eDropDownPositionFinal
+ };
+ DropDownPositionState AbsolutelyPositionDropDown();
+
+ // Helper for GetMinISize/GetPrefISize
+ nscoord GetIntrinsicISize(nsRenderingContext* aRenderingContext,
+ nsLayoutUtils::IntrinsicISizeType aType);
+
+ class RedisplayTextEvent : public mozilla::Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ explicit RedisplayTextEvent(nsComboboxControlFrame *c) : mControlFrame(c) {}
+ void Revoke() { mControlFrame = nullptr; }
+ private:
+ nsComboboxControlFrame *mControlFrame;
+ };
+
+ /**
+ * Show or hide the dropdown list.
+ * @note This method might destroy |this|.
+ */
+ void ShowPopup(bool aShowPopup);
+
+ /**
+ * Show or hide the dropdown list.
+ * @param aShowList true to show, false to hide the dropdown.
+ * @note This method might destroy |this|.
+ * @return false if this frame is destroyed, true if still alive.
+ */
+ bool ShowList(bool aShowList);
+ void CheckFireOnChange();
+ void FireValueChangeEvent();
+ nsresult RedisplayText(int32_t aIndex);
+ void HandleRedisplayTextEvent();
+ void ActuallyDisplayText(bool aNotify);
+
+private:
+ // If our total transform to the root frame of the root document is only a 2d
+ // translation then return that translation, otherwise returns (0,0).
+ nsPoint GetCSSTransformTranslation();
+
+protected:
+ nsFrameList mPopupFrames; // additional named child list
+ nsCOMPtr<nsIContent> mDisplayContent; // Anonymous content used to display the current selection
+ nsCOMPtr<nsIContent> mButtonContent; // Anonymous content for the button
+ nsContainerFrame* mDisplayFrame; // frame to display selection
+ nsIFrame* mButtonFrame; // button frame
+ nsIFrame* mDropdownFrame; // dropdown list frame
+ nsIListControlFrame * mListControlFrame; // ListControl Interface for the dropdown frame
+
+ // The inline size of our display area. Used by that frame's reflow
+ // to size to the full inline size except the drop-marker.
+ nscoord mDisplayISize;
+
+ nsRevocableEventPtr<RedisplayTextEvent> mRedisplayTextEvent;
+
+ int32_t mRecentSelectedIndex;
+ int32_t mDisplayedIndex;
+ nsString mDisplayedOptionText;
+
+ // make someone to listen to the button. If its programmatically pressed by someone like Accessibility
+ // then open or close the combo box.
+ nsCOMPtr<nsIDOMEventListener> mButtonListener;
+
+ // The last y-positions used for estimating available space before and
+ // after for the dropdown list in GetAvailableDropdownSpace. These are
+ // reset to nscoord_MIN in AbsolutelyPositionDropDown when placing the
+ // dropdown at its actual position. The GetAvailableDropdownSpace call
+ // from nsListControlFrame::ReflowAsDropdown use the last position.
+ nscoord mLastDropDownBeforeScreenBCoord;
+ nscoord mLastDropDownAfterScreenBCoord;
+ // Current state of the dropdown list, true is dropped down.
+ bool mDroppedDown;
+ // See comment in HandleRedisplayTextEvent().
+ bool mInRedisplayText;
+ // Acting on ShowDropDown(true) is delayed until we're focused.
+ bool mDelayedShowDropDown;
+
+ bool mIsOpenInParentProcess;
+
+ // static class data member for Bug 32920
+ // only one control can be focused at a time
+ static nsComboboxControlFrame* sFocused;
+
+#ifdef DO_REFLOW_COUNTER
+ int32_t mReflowId;
+#endif
+};
+
+#endif
diff --git a/layout/forms/nsDateTimeControlFrame.cpp b/layout/forms/nsDateTimeControlFrame.cpp
new file mode 100644
index 000000000..df2e43986
--- /dev/null
+++ b/layout/forms/nsDateTimeControlFrame.cpp
@@ -0,0 +1,414 @@
+/* -*- 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/. */
+
+/**
+ * This frame type is used for input type=date, time, month, week, and
+ * datetime-local.
+ */
+
+#include "nsDateTimeControlFrame.h"
+
+#include "nsContentUtils.h"
+#include "nsFormControlFrame.h"
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentList.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "nsNodeInfoManager.h"
+#include "nsIDateTimeInputArea.h"
+#include "nsIObserverService.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsIDOMMutationEvent.h"
+#include "jsapi.h"
+#include "nsJSUtils.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsIFrame*
+NS_NewDateTimeControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsDateTimeControlFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsDateTimeControlFrame)
+
+NS_QUERYFRAME_HEAD(nsDateTimeControlFrame)
+ NS_QUERYFRAME_ENTRY(nsDateTimeControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+nsDateTimeControlFrame::nsDateTimeControlFrame(nsStyleContext* aContext)
+ : nsContainerFrame(aContext)
+{
+}
+
+void
+nsDateTimeControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ nsContentUtils::DestroyAnonymousContent(&mInputAreaContent);
+ nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+void
+nsDateTimeControlFrame::UpdateInputBoxValue()
+{
+ nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+ do_QueryInterface(mInputAreaContent);
+ if (inputAreaContent) {
+ inputAreaContent->NotifyInputElementValueChanged();
+ }
+}
+
+void
+nsDateTimeControlFrame::SetValueFromPicker(const DateTimeValue& aValue)
+{
+ nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+ do_QueryInterface(mInputAreaContent);
+ if (inputAreaContent) {
+ AutoJSAPI api;
+ if (!api.Init(mContent->OwnerDoc()->GetScopeObject())) {
+ return;
+ }
+
+ JSObject* wrapper = mContent->GetWrapper();
+ if (!wrapper) {
+ return;
+ }
+
+ JSObject* scope = xpc::GetXBLScope(api.cx(), wrapper);
+ AutoJSAPI jsapi;
+ if (!scope || !jsapi.Init(scope)) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> jsValue(jsapi.cx());
+ if (!ToJSValue(jsapi.cx(), aValue, &jsValue)) {
+ return;
+ }
+
+ inputAreaContent->SetValueFromPicker(jsValue);
+ }
+}
+
+void
+nsDateTimeControlFrame::SetPickerState(bool aOpen)
+{
+ nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+ do_QueryInterface(mInputAreaContent);
+ if (inputAreaContent) {
+ inputAreaContent->SetPickerState(aOpen);
+ }
+}
+
+void
+nsDateTimeControlFrame::HandleFocusEvent()
+{
+ nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+ do_QueryInterface(mInputAreaContent);
+ if (inputAreaContent) {
+ inputAreaContent->FocusInnerTextBox();
+ }
+}
+
+void
+nsDateTimeControlFrame::HandleBlurEvent()
+{
+ nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+ do_QueryInterface(mInputAreaContent);
+ if (inputAreaContent) {
+ inputAreaContent->BlurInnerTextBox();
+ }
+}
+
+nscoord
+nsDateTimeControlFrame::GetMinISize(nsRenderingContext* aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_MIN_WIDTH(this, result);
+
+ nsIFrame* kid = mFrames.FirstChild();
+ if (kid) { // display:none?
+ result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+ kid,
+ nsLayoutUtils::MIN_ISIZE);
+ } else {
+ result = 0;
+ }
+
+ return result;
+}
+
+nscoord
+nsDateTimeControlFrame::GetPrefISize(nsRenderingContext* aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_PREF_WIDTH(this, result);
+
+ nsIFrame* kid = mFrames.FirstChild();
+ if (kid) { // display:none?
+ result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+ kid,
+ nsLayoutUtils::PREF_ISIZE);
+ } else {
+ result = 0;
+ }
+
+ return result;
+}
+
+void
+nsDateTimeControlFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+
+ DO_GLOBAL_REFLOW_COUNT("nsDateTimeControlFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
+ ("enter nsDateTimeControlFrame::Reflow: availSize=%d,%d",
+ aReflowInput.AvailableWidth(),
+ aReflowInput.AvailableHeight()));
+
+ NS_ASSERTION(mInputAreaContent, "The input area content must exist!");
+
+ const WritingMode myWM = aReflowInput.GetWritingMode();
+
+ // The ISize of our content box, which is the available ISize
+ // for our anonymous content:
+ const nscoord contentBoxISize = aReflowInput.ComputedISize();
+ nscoord contentBoxBSize = aReflowInput.ComputedBSize();
+
+ // Figure out our border-box sizes as well (by adding borderPadding to
+ // content-box sizes):
+ const nscoord borderBoxISize = contentBoxISize +
+ aReflowInput.ComputedLogicalBorderPadding().IStartEnd(myWM);
+
+ nscoord borderBoxBSize;
+ if (contentBoxBSize != NS_INTRINSICSIZE) {
+ borderBoxBSize = contentBoxBSize +
+ aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
+ } // else, we'll figure out borderBoxBSize after we resolve contentBoxBSize.
+
+ nsIFrame* inputAreaFrame = mFrames.FirstChild();
+ if (!inputAreaFrame) { // display:none?
+ if (contentBoxBSize == NS_INTRINSICSIZE) {
+ contentBoxBSize = 0;
+ borderBoxBSize =
+ aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
+ }
+ } else {
+ NS_ASSERTION(inputAreaFrame->GetContent() == mInputAreaContent,
+ "What is this child doing here?");
+
+ ReflowOutput childDesiredSize(aReflowInput);
+
+ WritingMode wm = inputAreaFrame->GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSize(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+
+ ReflowInput childReflowOuput(aPresContext, aReflowInput,
+ inputAreaFrame, availSize);
+
+ // Convert input area margin into my own writing-mode (in case it differs):
+ LogicalMargin childMargin =
+ childReflowOuput.ComputedLogicalMargin().ConvertTo(myWM, wm);
+
+ // offsets of input area frame within this frame:
+ LogicalPoint
+ childOffset(myWM,
+ aReflowInput.ComputedLogicalBorderPadding().IStart(myWM) +
+ childMargin.IStart(myWM),
+ aReflowInput.ComputedLogicalBorderPadding().BStart(myWM) +
+ childMargin.BStart(myWM));
+
+ nsReflowStatus childStatus;
+ // We initially reflow the child with a dummy containerSize; positioning
+ // will be fixed later.
+ const nsSize dummyContainerSize;
+ ReflowChild(inputAreaFrame, aPresContext, childDesiredSize,
+ childReflowOuput, myWM, childOffset, dummyContainerSize, 0,
+ childStatus);
+ MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(childStatus),
+ "We gave our child unconstrained available block-size, "
+ "so it should be complete");
+
+ nscoord childMarginBoxBSize =
+ childDesiredSize.BSize(myWM) + childMargin.BStartEnd(myWM);
+
+ if (contentBoxBSize == NS_INTRINSICSIZE) {
+ // We are intrinsically sized -- we should shrinkwrap the input area's
+ // block-size:
+ contentBoxBSize = childMarginBoxBSize;
+
+ // Make sure we obey min/max-bsize in the case when we're doing intrinsic
+ // sizing (we get it for free when we have a non-intrinsic
+ // aReflowInput.ComputedBSize()). Note that we do this before
+ // adjusting for borderpadding, since ComputedMaxBSize and
+ // ComputedMinBSize are content heights.
+ contentBoxBSize =
+ NS_CSS_MINMAX(contentBoxBSize,
+ aReflowInput.ComputedMinBSize(),
+ aReflowInput.ComputedMaxBSize());
+
+ borderBoxBSize = contentBoxBSize +
+ aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
+ }
+
+ // Center child in block axis
+ nscoord extraSpace = contentBoxBSize - childMarginBoxBSize;
+ childOffset.B(myWM) += std::max(0, extraSpace / 2);
+
+ // Needed in FinishReflowChild, for logical-to-physical conversion:
+ nsSize borderBoxSize = LogicalSize(myWM, borderBoxISize, borderBoxBSize).
+ GetPhysicalSize(myWM);
+
+ // Place the child
+ FinishReflowChild(inputAreaFrame, aPresContext, childDesiredSize,
+ &childReflowOuput, myWM, childOffset, borderBoxSize, 0);
+
+ nsSize contentBoxSize =
+ LogicalSize(myWM, contentBoxISize, contentBoxBSize).
+ GetPhysicalSize(myWM);
+ aDesiredSize.SetBlockStartAscent(
+ childDesiredSize.BlockStartAscent() +
+ inputAreaFrame->BStart(aReflowInput.GetWritingMode(),
+ contentBoxSize));
+ }
+
+ LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize);
+ aDesiredSize.SetSize(myWM, logicalDesiredSize);
+
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+
+ if (inputAreaFrame) {
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, inputAreaFrame);
+ }
+
+ FinishAndStoreOverflow(&aDesiredSize);
+
+ aStatus = NS_FRAME_COMPLETE;
+
+ NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
+ ("exit nsDateTimeControlFrame::Reflow: size=%d,%d",
+ aDesiredSize.Width(), aDesiredSize.Height()));
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+nsresult
+nsDateTimeControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+{
+ // Set up "datetimebox" XUL element which will be XBL-bound to the
+ // actual controls.
+ nsNodeInfoManager* nodeInfoManager =
+ mContent->GetComposedDoc()->NodeInfoManager();
+ RefPtr<NodeInfo> nodeInfo =
+ nodeInfoManager->GetNodeInfo(nsGkAtoms::datetimebox, nullptr,
+ kNameSpaceID_XUL, nsIDOMNode::ELEMENT_NODE);
+ NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
+
+ NS_TrustedNewXULElement(getter_AddRefs(mInputAreaContent), nodeInfo.forget());
+ aElements.AppendElement(mInputAreaContent);
+
+ // Propogate our tabindex.
+ nsAutoString tabIndexStr;
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr)) {
+ mInputAreaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
+ tabIndexStr, false);
+ }
+
+ // Propagate our readonly state.
+ nsAutoString readonly;
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly)) {
+ mInputAreaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly,
+ false);
+ }
+
+ SyncDisabledState();
+
+ return NS_OK;
+}
+
+void
+nsDateTimeControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter)
+{
+ if (mInputAreaContent) {
+ aElements.AppendElement(mInputAreaContent);
+ }
+}
+
+void
+nsDateTimeControlFrame::SyncDisabledState()
+{
+ EventStates eventStates = mContent->AsElement()->State();
+ if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
+ mInputAreaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
+ EmptyString(), true);
+ } else {
+ mInputAreaContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
+ }
+}
+
+nsresult
+nsDateTimeControlFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ NS_ASSERTION(mInputAreaContent, "The input area content must exist!");
+
+ // nsGkAtoms::disabled is handled by SyncDisabledState
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::value ||
+ aAttribute == nsGkAtoms::readonly ||
+ aAttribute == nsGkAtoms::tabindex) {
+ MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
+ auto contentAsInputElem = static_cast<dom::HTMLInputElement*>(mContent);
+ // If script changed the <input>'s type before setting these attributes
+ // then we don't need to do anything since we are going to be reframed.
+ if (contentAsInputElem->GetType() == NS_FORM_INPUT_TIME) {
+ if (aAttribute == nsGkAtoms::value) {
+ nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+ do_QueryInterface(mInputAreaContent);
+ if (inputAreaContent) {
+ nsContentUtils::AddScriptRunner(NewRunnableMethod(inputAreaContent,
+ &nsIDateTimeInputArea::NotifyInputElementValueChanged));
+ }
+ } else {
+ if (aModType == nsIDOMMutationEvent::REMOVAL) {
+ mInputAreaContent->UnsetAttr(aNameSpaceID, aAttribute, true);
+ } else {
+ MOZ_ASSERT(aModType == nsIDOMMutationEvent::ADDITION ||
+ aModType == nsIDOMMutationEvent::MODIFICATION);
+ nsAutoString value;
+ mContent->GetAttr(aNameSpaceID, aAttribute, value);
+ mInputAreaContent->SetAttr(aNameSpaceID, aAttribute, value, true);
+ }
+ }
+ }
+ }
+ }
+
+ return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+}
+
+void
+nsDateTimeControlFrame::ContentStatesChanged(EventStates aStates)
+{
+ if (aStates.HasState(NS_EVENT_STATE_DISABLED)) {
+ nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
+ }
+}
+
+nsIAtom*
+nsDateTimeControlFrame::GetType() const
+{
+ return nsGkAtoms::dateTimeControlFrame;
+}
diff --git a/layout/forms/nsDateTimeControlFrame.h b/layout/forms/nsDateTimeControlFrame.h
new file mode 100644
index 000000000..5ca599f6f
--- /dev/null
+++ b/layout/forms/nsDateTimeControlFrame.h
@@ -0,0 +1,119 @@
+/* -*- 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/. */
+
+/**
+ * This frame type is used for input type=date, time, month, week, and
+ * datetime-local.
+ *
+ * NOTE: some of the above-mentioned input types are still to-be-implemented.
+ * See nsCSSFrameConstructor::FindInputData, as well as bug 1286182 (date),
+ * bug 1306215 (month), bug 1306216 (week) and bug 1306217 (datetime-local).
+ */
+
+#ifndef nsDateTimeControlFrame_h__
+#define nsDateTimeControlFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace dom {
+struct DateTimeValue;
+} // namespace dom
+} // namespace mozilla
+
+class nsDateTimeControlFrame final : public nsContainerFrame,
+ public nsIAnonymousContentCreator
+{
+ typedef mozilla::dom::DateTimeValue DateTimeValue;
+
+ explicit nsDateTimeControlFrame(nsStyleContext* aContext);
+
+public:
+ friend nsIFrame* NS_NewDateTimeControlFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ void ContentStatesChanged(mozilla::EventStates aStates) override;
+ void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ NS_DECL_QUERYFRAME_TARGET(nsDateTimeControlFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(NS_LITERAL_STRING("DateTimeControl"), aResult);
+ }
+#endif
+
+ nsIAtom* GetType() const override;
+
+ bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsContainerFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
+ }
+
+ // Reflow
+ nscoord GetMinISize(nsRenderingContext* aRenderingContext) override;
+
+ nscoord GetPrefISize(nsRenderingContext* aRenderingContext) override;
+
+ void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowState,
+ nsReflowStatus& aStatus) override;
+
+ // nsIAnonymousContentCreator
+ nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
+ void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter) override;
+
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ void UpdateInputBoxValue();
+ void SetValueFromPicker(const DateTimeValue& aValue);
+ void HandleFocusEvent();
+ void HandleBlurEvent();
+ void SetPickerState(bool aOpen);
+
+private:
+ class SyncDisabledStateEvent;
+ friend class SyncDisabledStateEvent;
+ class SyncDisabledStateEvent : public mozilla::Runnable
+ {
+ public:
+ explicit SyncDisabledStateEvent(nsDateTimeControlFrame* aFrame)
+ : mFrame(aFrame)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ nsDateTimeControlFrame* frame =
+ static_cast<nsDateTimeControlFrame*>(mFrame.GetFrame());
+ NS_ENSURE_STATE(frame);
+
+ frame->SyncDisabledState();
+ return NS_OK;
+ }
+
+ private:
+ nsWeakFrame mFrame;
+ };
+
+ /**
+ * Sync the disabled state of the anonymous children up with our content's.
+ */
+ void SyncDisabledState();
+
+ // Anonymous child which is bound via XBL to an element that wraps the input
+ // area and reset button.
+ nsCOMPtr<nsIContent> mInputAreaContent;
+};
+
+#endif // nsDateTimeControlFrame_h__
diff --git a/layout/forms/nsFieldSetFrame.cpp b/layout/forms/nsFieldSetFrame.cpp
new file mode 100644
index 000000000..befd41ee2
--- /dev/null
+++ b/layout/forms/nsFieldSetFrame.cpp
@@ -0,0 +1,703 @@
+/* -*- 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 "nsFieldSetFrame.h"
+
+#include "mozilla/gfx/2D.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsLayoutUtils.h"
+#include "nsLegendFrame.h"
+#include "nsCSSRendering.h"
+#include <algorithm>
+#include "nsIFrame.h"
+#include "nsPresContext.h"
+#include "mozilla/RestyleManager.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsDisplayList.h"
+#include "nsRenderingContext.h"
+#include "nsIScrollableFrame.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Maybe.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+using namespace mozilla::layout;
+
+nsContainerFrame*
+NS_NewFieldSetFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsFieldSetFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsFieldSetFrame)
+
+nsFieldSetFrame::nsFieldSetFrame(nsStyleContext* aContext)
+ : nsContainerFrame(aContext)
+ , mLegendRect(GetWritingMode())
+{
+ mLegendSpace = 0;
+}
+
+nsIAtom*
+nsFieldSetFrame::GetType() const
+{
+ return nsGkAtoms::fieldSetFrame;
+}
+
+nsRect
+nsFieldSetFrame::VisualBorderRectRelativeToSelf() const
+{
+ WritingMode wm = GetWritingMode();
+ css::Side legendSide = wm.PhysicalSide(eLogicalSideBStart);
+ nscoord legendBorder = StyleBorder()->GetComputedBorderWidth(legendSide);
+ LogicalRect r(wm, LogicalPoint(wm, 0, 0), GetLogicalSize(wm));
+ nsSize containerSize = r.Size(wm).GetPhysicalSize(wm);
+ if (legendBorder < mLegendRect.BSize(wm)) {
+ nscoord off = (mLegendRect.BSize(wm) - legendBorder) / 2;
+ r.BStart(wm) += off;
+ r.BSize(wm) -= off;
+ }
+ return r.GetPhysicalRect(wm, containerSize);
+}
+
+nsIFrame*
+nsFieldSetFrame::GetInner() const
+{
+ nsIFrame* last = mFrames.LastChild();
+ if (last &&
+ last->StyleContext()->GetPseudo() == nsCSSAnonBoxes::fieldsetContent) {
+ return last;
+ }
+ MOZ_ASSERT(mFrames.LastChild() == mFrames.FirstChild());
+ return nullptr;
+}
+
+nsIFrame*
+nsFieldSetFrame::GetLegend() const
+{
+ if (mFrames.FirstChild() == GetInner()) {
+ MOZ_ASSERT(mFrames.LastChild() == mFrames.FirstChild());
+ return nullptr;
+ }
+ MOZ_ASSERT(mFrames.FirstChild() &&
+ mFrames.FirstChild()->GetContentInsertionFrame()->GetType() ==
+ nsGkAtoms::legendFrame);
+ return mFrames.FirstChild();
+}
+
+class nsDisplayFieldSetBorderBackground : public nsDisplayItem {
+public:
+ nsDisplayFieldSetBorderBackground(nsDisplayListBuilder* aBuilder,
+ nsFieldSetFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayFieldSetBorderBackground);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayFieldSetBorderBackground() {
+ MOZ_COUNT_DTOR(nsDisplayFieldSetBorderBackground);
+ }
+#endif
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames) override;
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion *aInvalidRegion) override;
+ NS_DISPLAY_DECL_NAME("FieldSetBorderBackground", TYPE_FIELDSET_BORDER_BACKGROUND)
+};
+
+void nsDisplayFieldSetBorderBackground::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
+{
+ // aPt is guaranteed to be in this item's bounds. We do the hit test based on the
+ // frame bounds even though our background doesn't cover the whole frame.
+ // It's not clear whether this is correct.
+ aOutFrames->AppendElement(mFrame);
+}
+
+void
+nsDisplayFieldSetBorderBackground::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ DrawResult result = static_cast<nsFieldSetFrame*>(mFrame)->
+ PaintBorder(aBuilder, *aCtx, ToReferenceFrame(), mVisibleRect);
+
+ nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
+}
+
+nsDisplayItemGeometry*
+nsDisplayFieldSetBorderBackground::AllocateGeometry(nsDisplayListBuilder* aBuilder)
+{
+ return new nsDisplayItemGenericImageGeometry(this, aBuilder);
+}
+
+void
+nsDisplayFieldSetBorderBackground::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion *aInvalidRegion)
+{
+ auto geometry =
+ static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ bool snap;
+ aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
+ }
+
+ nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
+}
+
+void
+nsFieldSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) {
+ // Paint our background and border in a special way.
+ // REVIEW: We don't really need to check frame emptiness here; if it's empty,
+ // the background/border display item won't do anything, and if it isn't empty,
+ // we need to paint the outline
+ if (!(GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) &&
+ IsVisibleForPainting(aBuilder)) {
+ if (StyleEffects()->mBoxShadow) {
+ aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
+ nsDisplayBoxShadowOuter(aBuilder, this));
+ }
+
+ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
+ aBuilder, this, VisualBorderRectRelativeToSelf(),
+ aLists.BorderBackground(),
+ /* aAllowWillPaintBorderOptimization = */ false);
+
+ aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
+ nsDisplayFieldSetBorderBackground(aBuilder, this));
+
+ DisplayOutlineUnconditional(aBuilder, aLists);
+
+ DO_GLOBAL_REFLOW_COUNT_DSP("nsFieldSetFrame");
+ }
+
+ if (GetPrevInFlow()) {
+ DisplayOverflowContainers(aBuilder, aDirtyRect, aLists);
+ }
+
+ nsDisplayListCollection contentDisplayItems;
+ if (nsIFrame* inner = GetInner()) {
+ // Collect the inner frame's display items into their own collection.
+ // We need to be calling BuildDisplayList on it before the legend in
+ // case it contains out-of-flow frames whose placeholders are in the
+ // legend. However, we want the inner frame's display items to be
+ // after the legend's display items in z-order, so we need to save them
+ // and append them later.
+ BuildDisplayListForChild(aBuilder, inner, aDirtyRect, contentDisplayItems);
+ }
+ if (nsIFrame* legend = GetLegend()) {
+ // The legend's background goes on our BlockBorderBackgrounds list because
+ // it's a block child.
+ nsDisplayListSet set(aLists, aLists.BlockBorderBackgrounds());
+ BuildDisplayListForChild(aBuilder, legend, aDirtyRect, set);
+ }
+ // Put the inner frame's display items on the master list. Note that this
+ // moves its border/background display items to our BorderBackground() list,
+ // which isn't really correct, but it's OK because the inner frame is
+ // anonymous and can't have its own border and background.
+ contentDisplayItems.MoveTo(aLists);
+}
+
+DrawResult
+nsFieldSetFrame::PaintBorder(
+ nsDisplayListBuilder* aBuilder,
+ nsRenderingContext& aRenderingContext,
+ nsPoint aPt,
+ const nsRect& aDirtyRect)
+{
+ // if the border is smaller than the legend. Move the border down
+ // to be centered on the legend.
+ // FIXME: This means border-radius clamping is incorrect; we should
+ // override nsIFrame::GetBorderRadii.
+ WritingMode wm = GetWritingMode();
+ nsRect rect = VisualBorderRectRelativeToSelf();
+ nscoord off = wm.IsVertical() ? rect.x : rect.y;
+ rect += aPt;
+ nsPresContext* presContext = PresContext();
+
+ PaintBorderFlags borderFlags = aBuilder->ShouldSyncDecodeImages()
+ ? PaintBorderFlags::SYNC_DECODE_IMAGES
+ : PaintBorderFlags();
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ nsCSSRendering::PaintBoxShadowInner(presContext, aRenderingContext,
+ this, rect);
+
+ if (nsIFrame* legend = GetLegend()) {
+ css::Side legendSide = wm.PhysicalSide(eLogicalSideBStart);
+ nscoord legendBorderWidth =
+ StyleBorder()->GetComputedBorderWidth(legendSide);
+
+ // Use the rect of the legend frame, not mLegendRect, so we draw our
+ // border under the legend's inline-start and -end margins.
+ LogicalRect legendRect(wm, legend->GetRect() + aPt, rect.Size());
+
+ // Compute clipRect using logical coordinates, so that the legend space
+ // will be clipped out of the appropriate physical side depending on mode.
+ LogicalRect clipRect = LogicalRect(wm, rect, rect.Size());
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+ gfxContext* gfx = aRenderingContext.ThebesContext();
+ int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
+
+ // draw inline-start portion of the block-start side of the border
+ clipRect.ISize(wm) = legendRect.IStart(wm) - clipRect.IStart(wm);
+ clipRect.BSize(wm) = legendBorderWidth;
+
+ gfx->Save();
+ gfx->Clip(NSRectToSnappedRect(clipRect.GetPhysicalRect(wm, rect.Size()),
+ appUnitsPerDevPixel, *drawTarget));
+ result &=
+ nsCSSRendering::PaintBorder(presContext, aRenderingContext, this,
+ aDirtyRect, rect, mStyleContext, borderFlags);
+ gfx->Restore();
+
+ // draw inline-end portion of the block-start side of the border
+ clipRect = LogicalRect(wm, rect, rect.Size());
+ clipRect.ISize(wm) = clipRect.IEnd(wm) - legendRect.IEnd(wm);
+ clipRect.IStart(wm) = legendRect.IEnd(wm);
+ clipRect.BSize(wm) = legendBorderWidth;
+
+ gfx->Save();
+ gfx->Clip(NSRectToSnappedRect(clipRect.GetPhysicalRect(wm, rect.Size()),
+ appUnitsPerDevPixel, *drawTarget));
+ result &=
+ nsCSSRendering::PaintBorder(presContext, aRenderingContext, this,
+ aDirtyRect, rect, mStyleContext, borderFlags);
+ gfx->Restore();
+
+ // draw remainder of the border (omitting the block-start side)
+ clipRect = LogicalRect(wm, rect, rect.Size());
+ clipRect.BStart(wm) += legendBorderWidth;
+ clipRect.BSize(wm) = BSize(wm) - (off + legendBorderWidth);
+
+ gfx->Save();
+ gfx->Clip(NSRectToSnappedRect(clipRect.GetPhysicalRect(wm, rect.Size()),
+ appUnitsPerDevPixel, *drawTarget));
+ result &=
+ nsCSSRendering::PaintBorder(presContext, aRenderingContext, this,
+ aDirtyRect, rect, mStyleContext, borderFlags);
+ gfx->Restore();
+ } else {
+ result &=
+ nsCSSRendering::PaintBorder(presContext, aRenderingContext, this,
+ aDirtyRect, nsRect(aPt, mRect.Size()),
+ mStyleContext, borderFlags);
+ }
+
+ return result;
+}
+
+nscoord
+nsFieldSetFrame::GetIntrinsicISize(nsRenderingContext* aRenderingContext,
+ nsLayoutUtils::IntrinsicISizeType aType)
+{
+ nscoord legendWidth = 0;
+ nscoord contentWidth = 0;
+ if (nsIFrame* legend = GetLegend()) {
+ legendWidth =
+ nsLayoutUtils::IntrinsicForContainer(aRenderingContext, legend, aType);
+ }
+
+ if (nsIFrame* inner = GetInner()) {
+ // Ignore padding on the inner, since the padding will be applied to the
+ // outer instead, and the padding computed for the inner is wrong
+ // for percentage padding.
+ contentWidth =
+ nsLayoutUtils::IntrinsicForContainer(aRenderingContext, inner, aType,
+ nsLayoutUtils::IGNORE_PADDING);
+ }
+
+ return std::max(legendWidth, contentWidth);
+}
+
+
+nscoord
+nsFieldSetFrame::GetMinISize(nsRenderingContext* aRenderingContext)
+{
+ nscoord result = 0;
+ DISPLAY_MIN_WIDTH(this, result);
+
+ result = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::MIN_ISIZE);
+ return result;
+}
+
+nscoord
+nsFieldSetFrame::GetPrefISize(nsRenderingContext* aRenderingContext)
+{
+ nscoord result = 0;
+ DISPLAY_PREF_WIDTH(this, result);
+
+ result = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::PREF_ISIZE);
+ return result;
+}
+
+/* virtual */
+LogicalSize
+nsFieldSetFrame::ComputeSize(nsRenderingContext *aRenderingContext,
+ WritingMode aWM,
+ const LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const LogicalSize& aMargin,
+ const LogicalSize& aBorder,
+ const LogicalSize& aPadding,
+ ComputeSizeFlags aFlags)
+{
+ LogicalSize result =
+ nsContainerFrame::ComputeSize(aRenderingContext, aWM,
+ aCBSize, aAvailableISize,
+ aMargin, aBorder, aPadding, aFlags);
+
+ // XXX The code below doesn't make sense if the caller's writing mode
+ // is orthogonal to this frame's. Not sure yet what should happen then;
+ // for now, just bail out.
+ if (aWM.IsVertical() != GetWritingMode().IsVertical()) {
+ return result;
+ }
+
+ // Fieldsets never shrink below their min width.
+
+ // If we're a container for font size inflation, then shrink
+ // wrapping inside of us should not apply font size inflation.
+ AutoMaybeDisableFontInflation an(this);
+
+ nscoord minISize = GetMinISize(aRenderingContext);
+ if (minISize > result.ISize(aWM)) {
+ result.ISize(aWM) = minISize;
+ }
+
+ return result;
+}
+
+void
+nsFieldSetFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsFieldSetFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+
+ NS_PRECONDITION(aReflowInput.ComputedISize() != NS_INTRINSICSIZE,
+ "Should have a precomputed inline-size!");
+
+ // Initialize OUT parameter
+ aStatus = NS_FRAME_COMPLETE;
+
+ nsOverflowAreas ocBounds;
+ nsReflowStatus ocStatus = NS_FRAME_COMPLETE;
+ if (GetPrevInFlow()) {
+ ReflowOverflowContainerChildren(aPresContext, aReflowInput, ocBounds, 0,
+ ocStatus);
+ }
+
+ //------------ Handle Incremental Reflow -----------------
+ bool reflowInner;
+ bool reflowLegend;
+ nsIFrame* legend = GetLegend();
+ nsIFrame* inner = GetInner();
+ if (aReflowInput.ShouldReflowAllKids()) {
+ reflowInner = inner != nullptr;
+ reflowLegend = legend != nullptr;
+ } else {
+ reflowInner = inner && NS_SUBTREE_DIRTY(inner);
+ reflowLegend = legend && NS_SUBTREE_DIRTY(legend);
+ }
+
+ // We don't allow fieldsets to break vertically. If we did, we'd
+ // need logic here to push and pull overflow frames.
+ // Since we're not applying our padding in this frame, we need to add it here
+ // to compute the available width for our children.
+ WritingMode wm = GetWritingMode();
+ WritingMode innerWM = inner ? inner->GetWritingMode() : wm;
+ WritingMode legendWM = legend ? legend->GetWritingMode() : wm;
+ LogicalSize innerAvailSize = aReflowInput.ComputedSizeWithPadding(innerWM);
+ LogicalSize legendAvailSize = aReflowInput.ComputedSizeWithPadding(legendWM);
+ innerAvailSize.BSize(innerWM) = legendAvailSize.BSize(legendWM) =
+ NS_UNCONSTRAINEDSIZE;
+ NS_ASSERTION(!inner ||
+ nsLayoutUtils::IntrinsicForContainer(aReflowInput.mRenderingContext,
+ inner,
+ nsLayoutUtils::MIN_ISIZE) <=
+ innerAvailSize.ISize(innerWM),
+ "Bogus availSize.ISize; should be bigger");
+ NS_ASSERTION(!legend ||
+ nsLayoutUtils::IntrinsicForContainer(aReflowInput.mRenderingContext,
+ legend,
+ nsLayoutUtils::MIN_ISIZE) <=
+ legendAvailSize.ISize(legendWM),
+ "Bogus availSize.ISize; should be bigger");
+
+ // get our border and padding
+ LogicalMargin border = aReflowInput.ComputedLogicalBorderPadding() -
+ aReflowInput.ComputedLogicalPadding();
+
+ // Figure out how big the legend is if there is one.
+ // get the legend's margin
+ LogicalMargin legendMargin(wm);
+ // reflow the legend only if needed
+ Maybe<ReflowInput> legendReflowInput;
+ if (legend) {
+ legendReflowInput.emplace(aPresContext, aReflowInput, legend,
+ legendAvailSize);
+ }
+ if (reflowLegend) {
+ ReflowOutput legendDesiredSize(aReflowInput);
+
+ // We'll move the legend to its proper place later, so the position
+ // and containerSize passed here are unimportant.
+ const nsSize dummyContainerSize;
+ ReflowChild(legend, aPresContext, legendDesiredSize, *legendReflowInput,
+ wm, LogicalPoint(wm), dummyContainerSize,
+ NS_FRAME_NO_MOVE_FRAME, aStatus);
+#ifdef NOISY_REFLOW
+ printf(" returned (%d, %d)\n",
+ legendDesiredSize.Width(), legendDesiredSize.Height());
+#endif
+ // figure out the legend's rectangle
+ legendMargin = legend->GetLogicalUsedMargin(wm);
+ mLegendRect =
+ LogicalRect(wm, 0, 0,
+ legendDesiredSize.ISize(wm) + legendMargin.IStartEnd(wm),
+ legendDesiredSize.BSize(wm) + legendMargin.BStartEnd(wm));
+ nscoord oldSpace = mLegendSpace;
+ mLegendSpace = 0;
+ if (mLegendRect.BSize(wm) > border.BStart(wm)) {
+ // center the border on the legend
+ mLegendSpace = mLegendRect.BSize(wm) - border.BStart(wm);
+ } else {
+ mLegendRect.BStart(wm) =
+ (border.BStart(wm) - mLegendRect.BSize(wm)) / 2;
+ }
+
+ // if the legend space changes then we need to reflow the
+ // content area as well.
+ if (mLegendSpace != oldSpace && inner) {
+ reflowInner = true;
+ }
+
+ FinishReflowChild(legend, aPresContext, legendDesiredSize,
+ legendReflowInput.ptr(), wm, LogicalPoint(wm),
+ dummyContainerSize, NS_FRAME_NO_MOVE_FRAME);
+ } else if (!legend) {
+ mLegendRect.SetEmpty();
+ mLegendSpace = 0;
+ } else {
+ // mLegendSpace and mLegendRect haven't changed, but we need
+ // the used margin when placing the legend.
+ legendMargin = legend->GetLogicalUsedMargin(wm);
+ }
+
+ // This containerSize is incomplete as yet: it does not include the size
+ // of the |inner| frame itself.
+ nsSize containerSize = (LogicalSize(wm, 0, mLegendSpace) +
+ border.Size(wm)).GetPhysicalSize(wm);
+ // reflow the content frame only if needed
+ if (reflowInner) {
+ ReflowInput kidReflowInput(aPresContext, aReflowInput, inner,
+ innerAvailSize, nullptr,
+ ReflowInput::CALLER_WILL_INIT);
+ // Override computed padding, in case it's percentage padding
+ kidReflowInput.Init(aPresContext, nullptr, nullptr,
+ &aReflowInput.ComputedPhysicalPadding());
+ // Our child is "height:100%" but we actually want its height to be reduced
+ // by the amount of content-height the legend is eating up, unless our
+ // height is unconstrained (in which case the child's will be too).
+ if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) {
+ kidReflowInput.SetComputedBSize(
+ std::max(0, aReflowInput.ComputedBSize() - mLegendSpace));
+ }
+
+ if (aReflowInput.ComputedMinBSize() > 0) {
+ kidReflowInput.ComputedMinBSize() =
+ std::max(0, aReflowInput.ComputedMinBSize() - mLegendSpace);
+ }
+
+ if (aReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) {
+ kidReflowInput.ComputedMaxBSize() =
+ std::max(0, aReflowInput.ComputedMaxBSize() - mLegendSpace);
+ }
+
+ ReflowOutput kidDesiredSize(kidReflowInput,
+ aDesiredSize.mFlags);
+ // Reflow the frame
+ NS_ASSERTION(kidReflowInput.ComputedPhysicalMargin() == nsMargin(0,0,0,0),
+ "Margins on anonymous fieldset child not supported!");
+ LogicalPoint pt(wm, border.IStart(wm), border.BStart(wm) + mLegendSpace);
+
+ // We don't know the correct containerSize until we have reflowed |inner|,
+ // so we use a dummy value for now; FinishReflowChild will fix the position
+ // if necessary.
+ const nsSize dummyContainerSize;
+ ReflowChild(inner, aPresContext, kidDesiredSize, kidReflowInput,
+ wm, pt, dummyContainerSize, 0, aStatus);
+
+ // Update containerSize to account for size of the inner frame, so that
+ // FinishReflowChild can position it correctly.
+ containerSize += kidDesiredSize.PhysicalSize();
+ FinishReflowChild(inner, aPresContext, kidDesiredSize,
+ &kidReflowInput, wm, pt, containerSize, 0);
+ NS_FRAME_TRACE_REFLOW_OUT("FieldSet::Reflow", aStatus);
+ } else if (inner) {
+ // |inner| didn't need to be reflowed but we do need to include its size
+ // in containerSize.
+ containerSize += inner->GetSize();
+ }
+
+ LogicalRect contentRect(wm);
+ if (inner) {
+ // We don't support margins on inner, so our content rect is just the
+ // inner's border-box. (We don't really care about container size at this
+ // point, as we'll figure out the actual positioning later.)
+ contentRect = inner->GetLogicalRect(wm, containerSize);
+ }
+
+ // Our content rect must fill up the available width
+ LogicalSize availSize = aReflowInput.ComputedSizeWithPadding(wm);
+ if (availSize.ISize(wm) > contentRect.ISize(wm)) {
+ contentRect.ISize(wm) = innerAvailSize.ISize(wm);
+ }
+
+ if (legend) {
+ // The legend is positioned inline-wards within the inner's content rect
+ // (so that padding on the fieldset affects the legend position).
+ LogicalRect innerContentRect = contentRect;
+ innerContentRect.Deflate(wm, aReflowInput.ComputedLogicalPadding());
+ // If the inner content rect is larger than the legend, we can align the
+ // legend.
+ if (innerContentRect.ISize(wm) > mLegendRect.ISize(wm)) {
+ // NOTE legend @align values are: left/right/center/top/bottom.
+ // GetLogicalAlign converts left/right to start/end for the given WM.
+ // @see HTMLLegendElement::ParseAttribute, nsLegendFrame::GetLogicalAlign
+ int32_t align = static_cast<nsLegendFrame*>
+ (legend->GetContentInsertionFrame())->GetLogicalAlign(wm);
+ switch (align) {
+ case NS_STYLE_TEXT_ALIGN_END:
+ mLegendRect.IStart(wm) =
+ innerContentRect.IEnd(wm) - mLegendRect.ISize(wm);
+ break;
+ case NS_STYLE_TEXT_ALIGN_CENTER:
+ // Note: rounding removed; there doesn't seem to be any need
+ mLegendRect.IStart(wm) = innerContentRect.IStart(wm) +
+ (innerContentRect.ISize(wm) - mLegendRect.ISize(wm)) / 2;
+ break;
+ case NS_STYLE_TEXT_ALIGN_START:
+ case NS_STYLE_VERTICAL_ALIGN_TOP:
+ case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
+ mLegendRect.IStart(wm) = innerContentRect.IStart(wm);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected GetLogicalAlign value");
+ }
+ } else {
+ // otherwise make place for the legend
+ mLegendRect.IStart(wm) = innerContentRect.IStart(wm);
+ innerContentRect.ISize(wm) = mLegendRect.ISize(wm);
+ contentRect.ISize(wm) = mLegendRect.ISize(wm) +
+ aReflowInput.ComputedLogicalPadding().IStartEnd(wm);
+ }
+
+ // place the legend
+ LogicalRect actualLegendRect = mLegendRect;
+ actualLegendRect.Deflate(wm, legendMargin);
+ LogicalPoint actualLegendPos(actualLegendRect.Origin(wm));
+
+ // Note that legend's writing mode may be different from the fieldset's,
+ // so we need to convert offsets before applying them to it (bug 1134534).
+ LogicalMargin offsets =
+ legendReflowInput->ComputedLogicalOffsets().
+ ConvertTo(wm, legendReflowInput->GetWritingMode());
+ ReflowInput::ApplyRelativePositioning(legend, wm, offsets,
+ &actualLegendPos,
+ containerSize);
+
+ legend->SetPosition(wm, actualLegendPos, containerSize);
+ nsContainerFrame::PositionFrameView(legend);
+ nsContainerFrame::PositionChildViews(legend);
+ }
+
+ // Return our size and our result.
+ LogicalSize finalSize(wm, contentRect.ISize(wm) + border.IStartEnd(wm),
+ mLegendSpace + border.BStartEnd(wm) +
+ (inner ? inner->BSize(wm) : 0));
+ aDesiredSize.SetSize(wm, finalSize);
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+
+ if (legend) {
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, legend);
+ }
+ if (inner) {
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, inner);
+ }
+
+ // Merge overflow container bounds and status.
+ aDesiredSize.mOverflowAreas.UnionWith(ocBounds);
+ NS_MergeReflowStatusInto(&aStatus, ocStatus);
+
+ FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, aStatus);
+
+ InvalidateFrame();
+
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+#ifdef DEBUG
+void
+nsFieldSetFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ nsContainerFrame::SetInitialChildList(aListID, aChildList);
+ MOZ_ASSERT(aListID != kPrincipalList || GetInner(),
+ "Setting principal child list should populate our inner frame");
+}
+void
+nsFieldSetFrame::AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList)
+{
+ MOZ_CRASH("nsFieldSetFrame::AppendFrames not supported");
+}
+
+void
+nsFieldSetFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ MOZ_CRASH("nsFieldSetFrame::InsertFrames not supported");
+}
+
+void
+nsFieldSetFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ MOZ_CRASH("nsFieldSetFrame::RemoveFrame not supported");
+}
+#endif
+
+#ifdef ACCESSIBILITY
+a11y::AccType
+nsFieldSetFrame::AccessibleType()
+{
+ return a11y::eHTMLGroupboxType;
+}
+#endif
+
+nscoord
+nsFieldSetFrame::GetLogicalBaseline(WritingMode aWritingMode) const
+{
+ nsIFrame* inner = GetInner();
+ return inner->BStart(aWritingMode, GetParent()->GetSize()) +
+ inner->GetLogicalBaseline(aWritingMode);
+}
diff --git a/layout/forms/nsFieldSetFrame.h b/layout/forms/nsFieldSetFrame.h
new file mode 100644
index 000000000..54eaf678f
--- /dev/null
+++ b/layout/forms/nsFieldSetFrame.h
@@ -0,0 +1,110 @@
+/* -*- 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 nsFieldSetFrame_h___
+#define nsFieldSetFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "imgIContainer.h"
+#include "nsContainerFrame.h"
+
+class nsFieldSetFrame final : public nsContainerFrame
+{
+ typedef mozilla::image::DrawResult DrawResult;
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ explicit nsFieldSetFrame(nsStyleContext* aContext);
+
+ nscoord
+ GetIntrinsicISize(nsRenderingContext* aRenderingContext,
+ nsLayoutUtils::IntrinsicISizeType);
+ virtual nscoord GetMinISize(nsRenderingContext* aRenderingContext) override;
+ virtual nscoord GetPrefISize(nsRenderingContext* aRenderingContext) override;
+ virtual mozilla::LogicalSize
+ ComputeSize(nsRenderingContext *aRenderingContext,
+ mozilla::WritingMode aWritingMode,
+ const mozilla::LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorder,
+ const mozilla::LogicalSize& aPadding,
+ ComputeSizeFlags aFlags) override;
+ virtual nscoord GetLogicalBaseline(mozilla::WritingMode aWritingMode) const override;
+
+ /**
+ * The area to paint box-shadows around. It's the border rect except
+ * when there's a <legend> we offset the y-position to the center of it.
+ */
+ virtual nsRect VisualBorderRectRelativeToSelf() const override;
+
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ DrawResult PaintBorder(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext& aRenderingContext,
+ nsPoint aPt, const nsRect& aDirtyRect);
+
+#ifdef DEBUG
+ virtual void SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+ virtual void AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override;
+ virtual void InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override;
+ virtual void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+#endif
+
+ virtual nsIAtom* GetType() const override;
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsContainerFrame::IsFrameOfType(aFlags &
+ ~nsIFrame::eCanContainOverflowContainers);
+ }
+ virtual nsIScrollableFrame* GetScrollTargetFrame() override
+ {
+ return do_QueryFrame(GetInner());
+ }
+
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(NS_LITERAL_STRING("FieldSet"), aResult);
+ }
+#endif
+
+ /**
+ * Return the anonymous frame that contains all descendants except
+ * the legend frame. This is currently always a block frame with
+ * pseudo nsCSSAnonBoxes::fieldsetContent -- this may change in the
+ * future when we add support for CSS overflow for <fieldset>.
+ */
+ nsIFrame* GetInner() const;
+
+ /**
+ * Return the frame that represents the legend if any. This may be
+ * a nsLegendFrame or a nsHTMLScrollFrame with the nsLegendFrame as the
+ * scrolled frame (aka content insertion frame).
+ */
+ nsIFrame* GetLegend() const;
+
+protected:
+ mozilla::LogicalRect mLegendRect;
+ nscoord mLegendSpace;
+};
+
+#endif // nsFieldSetFrame_h___
diff --git a/layout/forms/nsFileControlFrame.cpp b/layout/forms/nsFileControlFrame.cpp
new file mode 100644
index 000000000..659371615
--- /dev/null
+++ b/layout/forms/nsFileControlFrame.cpp
@@ -0,0 +1,506 @@
+/* -*- 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 "nsFileControlFrame.h"
+
+#include "nsGkAtoms.h"
+#include "nsCOMPtr.h"
+#include "nsIDocument.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/DataTransfer.h"
+#include "mozilla/dom/HTMLButtonElement.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/Preferences.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentUtils.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/DOMStringList.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/FileList.h"
+#include "nsIDOMDragEvent.h"
+#include "nsIDOMFileList.h"
+#include "nsContentList.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsTextNode.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsIFrame*
+NS_NewFileControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsFileControlFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsFileControlFrame)
+
+nsFileControlFrame::nsFileControlFrame(nsStyleContext* aContext)
+ : nsBlockFrame(aContext)
+{
+ AddStateBits(NS_BLOCK_FLOAT_MGR);
+}
+
+
+void
+nsFileControlFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsBlockFrame::Init(aContent, aParent, aPrevInFlow);
+
+ mMouseListener = new DnDListener(this);
+}
+
+void
+nsFileControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ ENSURE_TRUE(mContent);
+
+ // Remove the events.
+ if (mContent) {
+ mContent->RemoveSystemEventListener(NS_LITERAL_STRING("drop"),
+ mMouseListener, false);
+ mContent->RemoveSystemEventListener(NS_LITERAL_STRING("dragover"),
+ mMouseListener, false);
+ }
+
+ nsContentUtils::DestroyAnonymousContent(&mTextContent);
+ nsContentUtils::DestroyAnonymousContent(&mBrowseFilesOrDirs);
+
+ mMouseListener->ForgetFrame();
+ nsBlockFrame::DestroyFrom(aDestructRoot);
+}
+
+static already_AddRefed<Element>
+MakeAnonButton(nsIDocument* aDoc, const char* labelKey,
+ HTMLInputElement* aInputElement,
+ const nsAString& aAccessKey)
+{
+ RefPtr<Element> button = aDoc->CreateHTMLElement(nsGkAtoms::button);
+ // NOTE: SetIsNativeAnonymousRoot() has to be called before setting any
+ // attribute.
+ button->SetIsNativeAnonymousRoot();
+ button->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
+ NS_LITERAL_STRING("button"), false);
+
+ // Set the file picking button text depending on the current locale.
+ nsXPIDLString buttonTxt;
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ labelKey, buttonTxt);
+
+ // Set the browse button text. It's a bit of a pain to do because we want to
+ // make sure we are not notifying.
+ RefPtr<nsTextNode> textContent =
+ new nsTextNode(button->NodeInfo()->NodeInfoManager());
+
+ textContent->SetText(buttonTxt, false);
+
+ nsresult rv = button->AppendChildTo(textContent, false);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ // Make sure access key and tab order for the element actually redirect to the
+ // file picking button.
+ RefPtr<HTMLButtonElement> buttonElement =
+ HTMLButtonElement::FromContentOrNull(button);
+
+ if (!aAccessKey.IsEmpty()) {
+ buttonElement->SetAccessKey(aAccessKey);
+ }
+
+ // Both elements are given the same tab index so that the user can tab
+ // to the file control at the correct index, and then between the two
+ // buttons.
+ int32_t tabIndex;
+ aInputElement->GetTabIndex(&tabIndex);
+ buttonElement->SetTabIndex(tabIndex);
+
+ return button.forget();
+}
+
+nsresult
+nsFileControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+{
+ nsCOMPtr<nsIDocument> doc = mContent->GetComposedDoc();
+
+ RefPtr<HTMLInputElement> fileContent = HTMLInputElement::FromContentOrNull(mContent);
+
+ // The access key is transferred to the "Choose files..." button only. In
+ // effect that access key allows access to the control via that button, then
+ // the user can tab between the two buttons.
+ nsAutoString accessKey;
+ fileContent->GetAccessKey(accessKey);
+
+ mBrowseFilesOrDirs = MakeAnonButton(doc, "Browse", fileContent, accessKey);
+ if (!mBrowseFilesOrDirs || !aElements.AppendElement(mBrowseFilesOrDirs)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Create and setup the text showing the selected files.
+ RefPtr<NodeInfo> nodeInfo;
+ nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::label, nullptr,
+ kNameSpaceID_XUL,
+ nsIDOMNode::ELEMENT_NODE);
+ NS_TrustedNewXULElement(getter_AddRefs(mTextContent), nodeInfo.forget());
+ // NOTE: SetIsNativeAnonymousRoot() has to be called before setting any
+ // attribute.
+ mTextContent->SetIsNativeAnonymousRoot();
+ mTextContent->SetAttr(kNameSpaceID_None, nsGkAtoms::crop,
+ NS_LITERAL_STRING("center"), false);
+
+ // Update the displayed text to reflect the current element's value.
+ nsAutoString value;
+ HTMLInputElement::FromContent(mContent)->GetDisplayFileName(value);
+ UpdateDisplayedValue(value, false);
+
+ if (!aElements.AppendElement(mTextContent)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // We should be able to interact with the element by doing drag and drop.
+ mContent->AddSystemEventListener(NS_LITERAL_STRING("drop"),
+ mMouseListener, false);
+ mContent->AddSystemEventListener(NS_LITERAL_STRING("dragover"),
+ mMouseListener, false);
+
+ SyncDisabledState();
+
+ return NS_OK;
+}
+
+void
+nsFileControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter)
+{
+ if (mBrowseFilesOrDirs) {
+ aElements.AppendElement(mBrowseFilesOrDirs);
+ }
+
+ if (mTextContent) {
+ aElements.AppendElement(mTextContent);
+ }
+}
+
+NS_QUERYFRAME_HEAD(nsFileControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+ NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
+
+void
+nsFileControlFrame::SetFocus(bool aOn, bool aRepaint)
+{
+}
+
+static void
+AppendBlobImplAsDirectory(nsTArray<OwningFileOrDirectory>& aArray,
+ BlobImpl* aBlobImpl,
+ nsIContent* aContent)
+{
+ MOZ_ASSERT(aBlobImpl);
+ MOZ_ASSERT(aBlobImpl->IsDirectory());
+
+ nsAutoString fullpath;
+ ErrorResult err;
+ aBlobImpl->GetMozFullPath(fullpath, err);
+ if (err.Failed()) {
+ err.SuppressException();
+ return;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ NS_ConvertUTF16toUTF8 path(fullpath);
+ nsresult rv = NS_NewNativeLocalFile(path, true, getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsPIDOMWindowInner* inner = aContent->OwnerDoc()->GetInnerWindow();
+ if (!inner || !inner->IsCurrentInnerWindow()) {
+ return;
+ }
+
+ RefPtr<Directory> directory =
+ Directory::Create(inner, file);
+ MOZ_ASSERT(directory);
+
+ OwningFileOrDirectory* element = aArray.AppendElement();
+ element->SetAsDirectory() = directory;
+}
+
+/**
+ * This is called when we receive a drop or a dragover.
+ */
+NS_IMETHODIMP
+nsFileControlFrame::DnDListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+ NS_ASSERTION(mFrame, "We should have been unregistered");
+
+ bool defaultPrevented = false;
+ aEvent->GetDefaultPrevented(&defaultPrevented);
+ if (defaultPrevented) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMDragEvent> dragEvent = do_QueryInterface(aEvent);
+ if (!dragEvent) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMDataTransfer> dataTransfer;
+ dragEvent->GetDataTransfer(getter_AddRefs(dataTransfer));
+ if (!IsValidDropData(dataTransfer)) {
+ return NS_OK;
+ }
+
+
+ nsCOMPtr<nsIContent> content = mFrame->GetContent();
+ bool supportsMultiple = content && content->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple);
+ if (!CanDropTheseFiles(dataTransfer, supportsMultiple)) {
+ dataTransfer->SetDropEffect(NS_LITERAL_STRING("none"));
+ aEvent->StopPropagation();
+ return NS_OK;
+ }
+
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ if (eventType.EqualsLiteral("dragover")) {
+ // Prevent default if we can accept this drag data
+ aEvent->PreventDefault();
+ return NS_OK;
+ }
+
+ if (eventType.EqualsLiteral("drop")) {
+ aEvent->StopPropagation();
+ aEvent->PreventDefault();
+
+ NS_ASSERTION(content, "The frame has no content???");
+
+ HTMLInputElement* inputElement = HTMLInputElement::FromContent(content);
+ NS_ASSERTION(inputElement, "No input element for this file upload control frame!");
+
+ nsCOMPtr<nsIDOMFileList> fileList;
+ dataTransfer->GetFiles(getter_AddRefs(fileList));
+
+ RefPtr<BlobImpl> webkitDir;
+ nsresult rv =
+ GetBlobImplForWebkitDirectory(fileList, getter_AddRefs(webkitDir));
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ nsTArray<OwningFileOrDirectory> array;
+ if (webkitDir) {
+ AppendBlobImplAsDirectory(array, webkitDir, content);
+ inputElement->MozSetDndFilesAndDirectories(array);
+ } else {
+ bool blinkFileSystemEnabled =
+ Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false);
+ if (blinkFileSystemEnabled) {
+ FileList* files = static_cast<FileList*>(fileList.get());
+ if (files) {
+ for (uint32_t i = 0; i < files->Length(); ++i) {
+ File* file = files->Item(i);
+ if (file) {
+ if (file->Impl() && file->Impl()->IsDirectory()) {
+ AppendBlobImplAsDirectory(array, file->Impl(), content);
+ } else {
+ OwningFileOrDirectory* element = array.AppendElement();
+ element->SetAsFile() = file;
+ }
+ }
+ }
+ }
+ }
+
+ // This is rather ugly. Pass the directories as Files using SetFiles,
+ // but then if blink filesystem API is enabled, it wants
+ // FileOrDirectory array.
+ inputElement->SetFiles(fileList, true);
+ if (blinkFileSystemEnabled) {
+ inputElement->UpdateEntries(array);
+ }
+ nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
+ NS_LITERAL_STRING("input"), true,
+ false);
+ nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
+ NS_LITERAL_STRING("change"), true,
+ false);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsFileControlFrame::DnDListener::GetBlobImplForWebkitDirectory(nsIDOMFileList* aFileList,
+ BlobImpl** aBlobImpl)
+{
+ *aBlobImpl = nullptr;
+
+ HTMLInputElement* inputElement =
+ HTMLInputElement::FromContent(mFrame->GetContent());
+ bool webkitDirPicker =
+ Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+ inputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory);
+ if (!webkitDirPicker) {
+ return NS_OK;
+ }
+
+ if (!aFileList) {
+ return NS_ERROR_FAILURE;
+ }
+
+ FileList* files = static_cast<FileList*>(aFileList);
+ // webkitdirectory doesn't care about the length of the file list but
+ // only about the first item on it.
+ uint32_t len = files->Length();
+ if (len) {
+ File* file = files->Item(0);
+ if (file) {
+ BlobImpl* impl = file->Impl();
+ if (impl && impl->IsDirectory()) {
+ RefPtr<BlobImpl> retVal = impl;
+ retVal.swap(*aBlobImpl);
+ return NS_OK;
+ }
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+bool
+nsFileControlFrame::DnDListener::IsValidDropData(nsIDOMDataTransfer* aDOMDataTransfer)
+{
+ nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(aDOMDataTransfer);
+ NS_ENSURE_TRUE(dataTransfer, false);
+
+ // We only support dropping files onto a file upload control
+ nsTArray<nsString> types;
+ dataTransfer->GetTypes(types, *nsContentUtils::GetSystemPrincipal());
+
+ return types.Contains(NS_LITERAL_STRING("Files"));
+}
+
+bool
+nsFileControlFrame::DnDListener::CanDropTheseFiles(nsIDOMDataTransfer* aDOMDataTransfer,
+ bool aSupportsMultiple)
+{
+ nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(aDOMDataTransfer);
+ NS_ENSURE_TRUE(dataTransfer, false);
+
+ nsCOMPtr<nsIDOMFileList> fileList;
+ dataTransfer->GetFiles(getter_AddRefs(fileList));
+
+ RefPtr<BlobImpl> webkitDir;
+ nsresult rv =
+ GetBlobImplForWebkitDirectory(fileList, getter_AddRefs(webkitDir));
+ // Just check if either there isn't webkitdirectory attribute, or
+ // fileList has a directory which can be dropped to the element.
+ // No need to use webkitDir for anything here.
+ NS_ENSURE_SUCCESS(rv, false);
+
+ uint32_t listLength = 0;
+ if (fileList) {
+ fileList->GetLength(&listLength);
+ }
+ return listLength <= 1 || aSupportsMultiple;
+}
+
+nscoord
+nsFileControlFrame::GetMinISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_MIN_WIDTH(this, result);
+
+ // Our min width is our pref width
+ result = GetPrefISize(aRenderingContext);
+ return result;
+}
+
+void
+nsFileControlFrame::SyncDisabledState()
+{
+ EventStates eventStates = mContent->AsElement()->State();
+ if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
+ mBrowseFilesOrDirs->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
+ EmptyString(), true);
+ } else {
+ mBrowseFilesOrDirs->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
+ }
+}
+
+nsresult
+nsFileControlFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::tabindex) {
+ if (aModType == nsIDOMMutationEvent::REMOVAL) {
+ mBrowseFilesOrDirs->UnsetAttr(aNameSpaceID, aAttribute, true);
+ } else {
+ nsAutoString value;
+ mContent->GetAttr(aNameSpaceID, aAttribute, value);
+ mBrowseFilesOrDirs->SetAttr(aNameSpaceID, aAttribute, value, true);
+ }
+ }
+
+ return nsBlockFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+void
+nsFileControlFrame::ContentStatesChanged(EventStates aStates)
+{
+ if (aStates.HasState(NS_EVENT_STATE_DISABLED)) {
+ nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
+ }
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsFileControlFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("FileControl"), aResult);
+}
+#endif
+
+void
+nsFileControlFrame::UpdateDisplayedValue(const nsAString& aValue, bool aNotify)
+{
+ mTextContent->SetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue, aNotify);
+}
+
+nsresult
+nsFileControlFrame::SetFormProperty(nsIAtom* aName,
+ const nsAString& aValue)
+{
+ if (nsGkAtoms::value == aName) {
+ UpdateDisplayedValue(aValue, true);
+ }
+ return NS_OK;
+}
+
+void
+nsFileControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ BuildDisplayListForInline(aBuilder, aDirtyRect, aLists);
+}
+
+#ifdef ACCESSIBILITY
+a11y::AccType
+nsFileControlFrame::AccessibleType()
+{
+ return a11y::eHTMLFileInputType;
+}
+#endif
+
+////////////////////////////////////////////////////////////
+// Mouse listener implementation
+
+NS_IMPL_ISUPPORTS(nsFileControlFrame::MouseListener,
+ nsIDOMEventListener)
diff --git a/layout/forms/nsFileControlFrame.h b/layout/forms/nsFileControlFrame.h
new file mode 100644
index 000000000..55c51d426
--- /dev/null
+++ b/layout/forms/nsFileControlFrame.h
@@ -0,0 +1,167 @@
+/* -*- 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 nsFileControlFrame_h___
+#define nsFileControlFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsBlockFrame.h"
+#include "nsIFormControlFrame.h"
+#include "nsIDOMEventListener.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsCOMPtr.h"
+
+class nsIDOMDataTransfer;
+class nsIDOMFileList;
+namespace mozilla {
+namespace dom {
+class BlobImpl;
+} // namespace dom
+} // namespace mozilla
+
+class nsFileControlFrame : public nsBlockFrame,
+ public nsIFormControlFrame,
+ public nsIAnonymousContentCreator
+{
+public:
+ explicit nsFileControlFrame(nsStyleContext* aContext);
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIFormControlFrame
+ virtual nsresult SetFormProperty(nsIAtom* aName, const nsAString& aValue) override;
+ virtual void SetFocus(bool aOn, bool aRepaint) override;
+
+ virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override;
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+ virtual void ContentStatesChanged(mozilla::EventStates aStates) override;
+ virtual bool IsLeaf() const override
+ {
+ return true;
+ }
+
+ // nsIAnonymousContentCreator
+ virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
+ virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter) override;
+
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+ typedef bool (*AcceptAttrCallback)(const nsAString&, void*);
+
+protected:
+
+ class MouseListener;
+ friend class MouseListener;
+ class MouseListener : public nsIDOMEventListener {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit MouseListener(nsFileControlFrame* aFrame)
+ : mFrame(aFrame)
+ {}
+
+ void ForgetFrame() {
+ mFrame = nullptr;
+ }
+
+ protected:
+ virtual ~MouseListener() {}
+
+ nsFileControlFrame* mFrame;
+ };
+
+ class SyncDisabledStateEvent;
+ friend class SyncDisabledStateEvent;
+ class SyncDisabledStateEvent : public mozilla::Runnable
+ {
+ public:
+ explicit SyncDisabledStateEvent(nsFileControlFrame* aFrame)
+ : mFrame(aFrame)
+ {}
+
+ NS_IMETHOD Run() override {
+ nsFileControlFrame* frame = static_cast<nsFileControlFrame*>(mFrame.GetFrame());
+ NS_ENSURE_STATE(frame);
+
+ frame->SyncDisabledState();
+ return NS_OK;
+ }
+
+ private:
+ nsWeakFrame mFrame;
+ };
+
+ class DnDListener: public MouseListener {
+ public:
+ explicit DnDListener(nsFileControlFrame* aFrame)
+ : MouseListener(aFrame)
+ {}
+
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ nsresult GetBlobImplForWebkitDirectory(nsIDOMFileList* aFileList,
+ mozilla::dom::BlobImpl** aBlobImpl);
+
+ bool IsValidDropData(nsIDOMDataTransfer* aDOMDataTransfer);
+ bool CanDropTheseFiles(nsIDOMDataTransfer* aDOMDataTransfer, bool aSupportsMultiple);
+ };
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsBlockFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
+ }
+
+ /**
+ * The text box input.
+ * @see nsFileControlFrame::CreateAnonymousContent
+ */
+ nsCOMPtr<nsIContent> mTextContent;
+ /**
+ * The button to open a file or directory picker.
+ * @see nsFileControlFrame::CreateAnonymousContent
+ */
+ nsCOMPtr<nsIContent> mBrowseFilesOrDirs;
+
+ /**
+ * Drag and drop mouse listener.
+ * This makes sure we don't get used after destruction.
+ */
+ RefPtr<DnDListener> mMouseListener;
+
+protected:
+ /**
+ * Sync the disabled state of the content with anonymous children.
+ */
+ void SyncDisabledState();
+
+ /**
+ * Updates the displayed value by using aValue.
+ */
+ void UpdateDisplayedValue(const nsAString& aValue, bool aNotify);
+};
+
+#endif // nsFileControlFrame_h___
diff --git a/layout/forms/nsFormControlFrame.cpp b/layout/forms/nsFormControlFrame.cpp
new file mode 100644
index 000000000..4ee62acbf
--- /dev/null
+++ b/layout/forms/nsFormControlFrame.cpp
@@ -0,0 +1,225 @@
+/* -*- 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 "nsFormControlFrame.h"
+
+#include "nsGkAtoms.h"
+#include "nsLayoutUtils.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/LookAndFeel.h"
+#include "nsDeviceContext.h"
+#include "nsIContent.h"
+
+using namespace mozilla;
+
+//#define FCF_NOISY
+
+nsFormControlFrame::nsFormControlFrame(nsStyleContext* aContext)
+ : nsAtomicContainerFrame(aContext)
+{
+}
+
+nsFormControlFrame::~nsFormControlFrame()
+{
+}
+
+nsIAtom*
+nsFormControlFrame::GetType() const
+{
+ return nsGkAtoms::formControlFrame;
+}
+
+void
+nsFormControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ // Unregister the access key registered in reflow
+ nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
+ nsAtomicContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+NS_QUERYFRAME_HEAD(nsFormControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsAtomicContainerFrame)
+
+/* virtual */ nscoord
+nsFormControlFrame::GetMinISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_MIN_WIDTH(this, result);
+ result = GetIntrinsicISize();
+ return result;
+}
+
+/* virtual */ nscoord
+nsFormControlFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_PREF_WIDTH(this, result);
+ result = GetIntrinsicISize();
+ return result;
+}
+
+/* virtual */
+LogicalSize
+nsFormControlFrame::ComputeAutoSize(nsRenderingContext* aRenderingContext,
+ WritingMode aWM,
+ const LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const LogicalSize& aMargin,
+ const LogicalSize& aBorder,
+ const LogicalSize& aPadding,
+ ComputeSizeFlags aFlags)
+{
+ const WritingMode wm = GetWritingMode();
+ LogicalSize result(wm, GetIntrinsicISize(), GetIntrinsicBSize());
+ return result.ConvertTo(aWM, wm);
+}
+
+nscoord
+nsFormControlFrame::GetIntrinsicISize()
+{
+ // Provide a reasonable default for sites that use an "auto" height.
+ // Note that if you change this, you should change the values in forms.css
+ // as well. This is the 13px default width minus the 2px default border.
+ return nsPresContext::CSSPixelsToAppUnits(13 - 2 * 2);
+}
+
+nscoord
+nsFormControlFrame::GetIntrinsicBSize()
+{
+ // Provide a reasonable default for sites that use an "auto" height.
+ // Note that if you change this, you should change the values in forms.css
+ // as well. This is the 13px default width minus the 2px default border.
+ return nsPresContext::CSSPixelsToAppUnits(13 - 2 * 2);
+}
+
+nscoord
+nsFormControlFrame::GetLogicalBaseline(WritingMode aWritingMode) const
+{
+ NS_ASSERTION(!NS_SUBTREE_DIRTY(this),
+ "frame must not be dirty");
+ // Treat radio buttons and checkboxes as having an intrinsic baseline
+ // at the block-end of the control (use the block-end content edge rather
+ // than the margin edge).
+ // For "inverted" lines (typically in writing-mode:vertical-lr), use the
+ // block-start end instead.
+ return aWritingMode.IsLineInverted()
+ ? GetLogicalUsedBorderAndPadding(aWritingMode).BStart(aWritingMode)
+ : BSize(aWritingMode) -
+ GetLogicalUsedBorderAndPadding(aWritingMode).BEnd(aWritingMode);
+}
+
+void
+nsFormControlFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsFormControlFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
+ ("enter nsFormControlFrame::Reflow: aMaxSize=%d,%d",
+ aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ RegUnRegAccessKey(static_cast<nsIFrame*>(this), true);
+ }
+
+ aStatus = NS_FRAME_COMPLETE;
+ aDesiredSize.SetSize(aReflowInput.GetWritingMode(),
+ aReflowInput.ComputedSizeWithBorderPadding());
+
+ if (nsLayoutUtils::FontSizeInflationEnabled(aPresContext)) {
+ float inflation = nsLayoutUtils::FontSizeInflationFor(this);
+ aDesiredSize.Width() *= inflation;
+ aDesiredSize.Height() *= inflation;
+ }
+
+ NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
+ ("exit nsFormControlFrame::Reflow: size=%d,%d",
+ aDesiredSize.Width(), aDesiredSize.Height()));
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+ FinishAndStoreOverflow(&aDesiredSize);
+}
+
+nsresult
+nsFormControlFrame::RegUnRegAccessKey(nsIFrame * aFrame, bool aDoReg)
+{
+ NS_ENSURE_ARG_POINTER(aFrame);
+
+ nsPresContext* presContext = aFrame->PresContext();
+
+ NS_ASSERTION(presContext, "aPresContext is NULL in RegUnRegAccessKey!");
+
+ nsAutoString accessKey;
+
+ nsIContent* content = aFrame->GetContent();
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey);
+ if (!accessKey.IsEmpty()) {
+ EventStateManager* stateManager = presContext->EventStateManager();
+ if (aDoReg) {
+ stateManager->RegisterAccessKey(content, (uint32_t)accessKey.First());
+ } else {
+ stateManager->UnregisterAccessKey(content, (uint32_t)accessKey.First());
+ }
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+void
+nsFormControlFrame::SetFocus(bool aOn, bool aRepaint)
+{
+}
+
+nsresult
+nsFormControlFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ // Check for user-input:none style
+ const nsStyleUserInterface* uiStyle = StyleUserInterface();
+ if (uiStyle->mUserInput == StyleUserInput::None ||
+ uiStyle->mUserInput == StyleUserInput::Disabled) {
+ return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+ }
+ return NS_OK;
+}
+
+void
+nsFormControlFrame::GetCurrentCheckState(bool *aState)
+{
+ nsCOMPtr<nsIDOMHTMLInputElement> inputElement = do_QueryInterface(mContent);
+ if (inputElement) {
+ inputElement->GetChecked(aState);
+ }
+}
+
+nsresult
+nsFormControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
+{
+ return NS_OK;
+}
+
+// static
+nsRect
+nsFormControlFrame::GetUsableScreenRect(nsPresContext* aPresContext)
+{
+ nsRect screen;
+
+ nsDeviceContext *context = aPresContext->DeviceContext();
+ int32_t dropdownCanOverlapOSBar =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_MenusCanOverlapOSBar, 0);
+ if ( dropdownCanOverlapOSBar )
+ context->GetRect(screen);
+ else
+ context->GetClientRect(screen);
+
+ return screen;
+}
diff --git a/layout/forms/nsFormControlFrame.h b/layout/forms/nsFormControlFrame.h
new file mode 100644
index 000000000..fd3e95d93
--- /dev/null
+++ b/layout/forms/nsFormControlFrame.h
@@ -0,0 +1,129 @@
+/* -*- 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 nsFormControlFrame_h___
+#define nsFormControlFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsIFormControlFrame.h"
+#include "nsAtomicContainerFrame.h"
+#include "nsDisplayList.h"
+
+/**
+ * nsFormControlFrame is the base class for radio buttons and
+ * checkboxes. It also has two static methods (RegUnRegAccessKey and
+ * GetScreenHeight) that are used by other form controls.
+ */
+class nsFormControlFrame : public nsAtomicContainerFrame,
+ public nsIFormControlFrame
+{
+public:
+ /**
+ * Main constructor
+ * @param aContent the content representing this frame
+ * @param aParentFrame the parent frame
+ */
+ explicit nsFormControlFrame(nsStyleContext*);
+
+ virtual nsIAtom* GetType() const override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsAtomicContainerFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
+ }
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_ABSTRACT_FRAME(nsFormControlFrame)
+
+ // nsIFrame replacements
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override {
+ DO_GLOBAL_REFLOW_COUNT_DSP("nsFormControlFrame");
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+ }
+
+ /**
+ * Both GetMinISize and GetPrefISize will return whatever GetIntrinsicISize
+ * returns.
+ */
+ virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override;
+ virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override;
+
+ /**
+ * Our auto size is just intrinsic width and intrinsic height.
+ */
+ virtual mozilla::LogicalSize
+ ComputeAutoSize(nsRenderingContext* aRenderingContext,
+ mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorder,
+ const mozilla::LogicalSize& aPadding,
+ ComputeSizeFlags aFlags) override;
+
+ /**
+ * Respond to a gui event
+ * @see nsIFrame::HandleEvent
+ */
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual nscoord GetLogicalBaseline(mozilla::WritingMode aWritingMode)
+ const override;
+
+ /**
+ * Respond to the request to resize and/or reflow
+ * @see nsIFrame::Reflow
+ */
+ virtual void Reflow(nsPresContext* aCX,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ // new behavior
+
+ virtual void SetFocus(bool aOn = true, bool aRepaint = false) override;
+
+ // nsIFormControlFrame
+ virtual nsresult SetFormProperty(nsIAtom* aName, const nsAString& aValue) override;
+
+ // AccessKey Helper function
+ static nsresult RegUnRegAccessKey(nsIFrame * aFrame, bool aDoReg);
+
+ /**
+ * Returns the usable screen rect in app units, eg the rect where we can
+ * draw dropdowns.
+ */
+ static nsRect GetUsableScreenRect(nsPresContext* aPresContext);
+
+protected:
+
+ virtual ~nsFormControlFrame();
+
+ nscoord GetIntrinsicISize();
+ nscoord GetIntrinsicBSize();
+
+//
+//-------------------------------------------------------------------------------------
+// Utility methods for managing checkboxes and radiobuttons
+//-------------------------------------------------------------------------------------
+//
+ /**
+ * Get the state of the checked attribute.
+ * @param aState set to true if the checked attribute is set,
+ * false if the checked attribute has been removed
+ */
+
+ void GetCurrentCheckState(bool* aState);
+};
+
+#endif
+
diff --git a/layout/forms/nsGfxButtonControlFrame.cpp b/layout/forms/nsGfxButtonControlFrame.cpp
new file mode 100644
index 000000000..90da437f7
--- /dev/null
+++ b/layout/forms/nsGfxButtonControlFrame.cpp
@@ -0,0 +1,237 @@
+/* -*- 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 "nsGfxButtonControlFrame.h"
+#include "nsIFormControl.h"
+#include "nsGkAtoms.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsContentUtils.h"
+// MouseEvent suppression in PP
+#include "nsContentList.h"
+
+#include "nsIDOMHTMLInputElement.h"
+#include "nsTextNode.h"
+
+using namespace mozilla;
+
+nsGfxButtonControlFrame::nsGfxButtonControlFrame(nsStyleContext* aContext):
+ nsHTMLButtonControlFrame(aContext)
+{
+}
+
+nsContainerFrame*
+NS_NewGfxButtonControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsGfxButtonControlFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsGfxButtonControlFrame)
+
+void nsGfxButtonControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ nsContentUtils::DestroyAnonymousContent(&mTextContent);
+ nsHTMLButtonControlFrame::DestroyFrom(aDestructRoot);
+}
+
+nsIAtom*
+nsGfxButtonControlFrame::GetType() const
+{
+ return nsGkAtoms::gfxButtonControlFrame;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsGfxButtonControlFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("ButtonControl"), aResult);
+}
+#endif
+
+// Create the text content used as label for the button.
+// The frame will be generated by the frame constructor.
+nsresult
+nsGfxButtonControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+{
+ nsXPIDLString label;
+ GetLabel(label);
+
+ // Add a child text content node for the label
+ mTextContent = new nsTextNode(mContent->NodeInfo()->NodeInfoManager());
+
+ // set the value of the text node and add it to the child list
+ mTextContent->SetText(label, false);
+ aElements.AppendElement(mTextContent);
+
+ return NS_OK;
+}
+
+void
+nsGfxButtonControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter)
+{
+ if (mTextContent) {
+ aElements.AppendElement(mTextContent);
+ }
+}
+
+// Create the text content used as label for the button.
+// The frame will be generated by the frame constructor.
+nsIFrame*
+nsGfxButtonControlFrame::CreateFrameFor(nsIContent* aContent)
+{
+ nsIFrame * newFrame = nullptr;
+
+ if (aContent == mTextContent) {
+ nsContainerFrame* parentFrame = do_QueryFrame(mFrames.FirstChild());
+
+ nsPresContext* presContext = PresContext();
+ RefPtr<nsStyleContext> textStyleContext;
+ textStyleContext = presContext->StyleSet()->
+ ResolveStyleForText(mTextContent, mStyleContext);
+
+ newFrame = NS_NewTextFrame(presContext->PresShell(), textStyleContext);
+ // initialize the text frame
+ newFrame->Init(mTextContent, parentFrame, nullptr);
+ mTextContent->SetPrimaryFrame(newFrame);
+ }
+
+ return newFrame;
+}
+
+NS_QUERYFRAME_HEAD(nsGfxButtonControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+NS_QUERYFRAME_TAIL_INHERITING(nsHTMLButtonControlFrame)
+
+// Initially we hardcoded the default strings here.
+// Next, we used html.css to store the default label for various types
+// of buttons. (nsGfxButtonControlFrame::DoNavQuirksReflow rev 1.20)
+// However, since html.css is not internationalized, we now grab the default
+// label from a string bundle as is done for all other UI strings.
+// See bug 16999 for further details.
+nsresult
+nsGfxButtonControlFrame::GetDefaultLabel(nsXPIDLString& aString) const
+{
+ nsCOMPtr<nsIFormControl> form = do_QueryInterface(mContent);
+ NS_ENSURE_TRUE(form, NS_ERROR_UNEXPECTED);
+
+ int32_t type = form->GetType();
+ const char *prop;
+ if (type == NS_FORM_INPUT_RESET) {
+ prop = "Reset";
+ }
+ else if (type == NS_FORM_INPUT_SUBMIT) {
+ prop = "Submit";
+ }
+ else {
+ aString.Truncate();
+ return NS_OK;
+ }
+
+ return nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ prop, aString);
+}
+
+nsresult
+nsGfxButtonControlFrame::GetLabel(nsXPIDLString& aLabel)
+{
+ // Get the text from the "value" property on our content if there is
+ // one; otherwise set it to a default value (localized).
+ nsresult rv;
+ nsCOMPtr<nsIDOMHTMLInputElement> elt = do_QueryInterface(mContent);
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::value) && elt) {
+ rv = elt->GetValue(aLabel);
+ } else {
+ // Generate localized label.
+ // We can't make any assumption as to what the default would be
+ // because the value is localized for non-english platforms, thus
+ // it might not be the string "Reset", "Submit Query", or "Browse..."
+ rv = GetDefaultLabel(aLabel);
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Compress whitespace out of label if needed.
+ if (!StyleText()->WhiteSpaceIsSignificant()) {
+ aLabel.CompressWhitespace();
+ } else if (aLabel.Length() > 2 && aLabel.First() == ' ' &&
+ aLabel.CharAt(aLabel.Length() - 1) == ' ') {
+ // This is a bit of a hack. The reason this is here is as follows: we now
+ // have default padding on our buttons to make them non-ugly.
+ // Unfortunately, IE-windows does not have such padding, so people will
+ // stick values like " ok " (with the spaces) in the buttons in an attempt
+ // to make them look decent. Unfortunately, if they do this the button
+ // looks way too big in Mozilla. Worse yet, if they do this _and_ set a
+ // fixed width for the button we run into trouble because our focus-rect
+ // border/padding and outer border take up 10px of the horizontal button
+ // space or so; the result is that the text is misaligned, even with the
+ // recentering we do in nsHTMLButtonControlFrame::Reflow. So to solve
+ // this, even if the whitespace is significant, single leading and trailing
+ // _spaces_ (and not other whitespace) are removed. The proper solution,
+ // of course, is to not have the focus rect painting taking up 6px of
+ // horizontal space. We should do that instead (via XBL form controls or
+ // changing the renderer) and remove this.
+ aLabel.Cut(0, 1);
+ aLabel.Truncate(aLabel.Length() - 1);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsGfxButtonControlFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ nsresult rv = NS_OK;
+
+ // If the value attribute is set, update the text of the label
+ if (nsGkAtoms::value == aAttribute) {
+ if (mTextContent && mContent) {
+ nsXPIDLString label;
+ rv = GetLabel(label);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mTextContent->SetText(label, true);
+ } else {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+
+ // defer to HTMLButtonControlFrame
+ } else {
+ rv = nsHTMLButtonControlFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+ }
+ return rv;
+}
+
+bool
+nsGfxButtonControlFrame::IsLeaf() const
+{
+ return true;
+}
+
+nsContainerFrame*
+nsGfxButtonControlFrame::GetContentInsertionFrame()
+{
+ return this;
+}
+
+nsresult
+nsGfxButtonControlFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ // Override the HandleEvent to prevent the nsFrame::HandleEvent
+ // from being called. The nsFrame::HandleEvent causes the button label
+ // to be selected (Drawn with an XOR rectangle over the label)
+
+ // do we have user-input style?
+ const nsStyleUserInterface* uiStyle = StyleUserInterface();
+ if (uiStyle->mUserInput == StyleUserInput::None ||
+ uiStyle->mUserInput == StyleUserInput::Disabled) {
+ return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+ }
+ return NS_OK;
+}
diff --git a/layout/forms/nsGfxButtonControlFrame.h b/layout/forms/nsGfxButtonControlFrame.h
new file mode 100644
index 000000000..d91fe3695
--- /dev/null
+++ b/layout/forms/nsGfxButtonControlFrame.h
@@ -0,0 +1,67 @@
+/* -*- 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 nsGfxButtonControlFrame_h___
+#define nsGfxButtonControlFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsHTMLButtonControlFrame.h"
+#include "nsCOMPtr.h"
+#include "nsIAnonymousContentCreator.h"
+
+// Class which implements the input[type=button, reset, submit] and
+// browse button for input[type=file].
+// The label for button is specified through generated content
+// in the ua.css file.
+
+class nsGfxButtonControlFrame : public nsHTMLButtonControlFrame,
+ public nsIAnonymousContentCreator
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ explicit nsGfxButtonControlFrame(nsStyleContext* aContext);
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ NS_DECL_QUERYFRAME
+
+ // nsIAnonymousContentCreator
+ virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
+ virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter) override;
+ virtual nsIFrame* CreateFrameFor(nsIContent* aContent) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual bool IsLeaf() const override;
+
+ virtual nsContainerFrame* GetContentInsertionFrame() override;
+
+protected:
+ nsresult GetDefaultLabel(nsXPIDLString& aLabel) const;
+
+ nsresult GetLabel(nsXPIDLString& aLabel);
+
+ virtual bool IsInput() override { return true; }
+private:
+ nsCOMPtr<nsIContent> mTextContent;
+};
+
+
+#endif
+
diff --git a/layout/forms/nsGfxCheckboxControlFrame.cpp b/layout/forms/nsGfxCheckboxControlFrame.cpp
new file mode 100644
index 000000000..061c92349
--- /dev/null
+++ b/layout/forms/nsGfxCheckboxControlFrame.cpp
@@ -0,0 +1,147 @@
+/* -*- 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 "nsGfxCheckboxControlFrame.h"
+
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "nsIContent.h"
+#include "nsCOMPtr.h"
+#include "nsLayoutUtils.h"
+#include "nsRenderingContext.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsDisplayList.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+static void
+PaintCheckMark(nsIFrame* aFrame,
+ DrawTarget* aDrawTarget,
+ const nsRect& aDirtyRect,
+ nsPoint aPt)
+{
+ nsRect rect(aPt, aFrame->GetSize());
+ rect.Deflate(aFrame->GetUsedBorderAndPadding());
+
+ // Points come from the coordinates on a 7X7 unit box centered at 0,0
+ const int32_t checkPolygonX[] = { -3, -1, 3, 3, -1, -3 };
+ const int32_t checkPolygonY[] = { -1, 1, -3, -1, 3, 1 };
+ const int32_t checkNumPoints = sizeof(checkPolygonX) / sizeof(int32_t);
+ const int32_t checkSize = 9; // 2 units of padding on either side
+ // of the 7x7 unit checkmark
+
+ // Scale the checkmark based on the smallest dimension
+ nscoord paintScale = std::min(rect.width, rect.height) / checkSize;
+ nsPoint paintCenter(rect.x + rect.width / 2,
+ rect.y + rect.height / 2);
+
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ nsPoint p = paintCenter + nsPoint(checkPolygonX[0] * paintScale,
+ checkPolygonY[0] * paintScale);
+
+ int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+ builder->MoveTo(NSPointToPoint(p, appUnitsPerDevPixel));
+ for (int32_t polyIndex = 1; polyIndex < checkNumPoints; polyIndex++) {
+ p = paintCenter + nsPoint(checkPolygonX[polyIndex] * paintScale,
+ checkPolygonY[polyIndex] * paintScale);
+ builder->LineTo(NSPointToPoint(p, appUnitsPerDevPixel));
+ }
+ RefPtr<Path> path = builder->Finish();
+ aDrawTarget->Fill(path,
+ ColorPattern(ToDeviceColor(aFrame->StyleColor()->mColor)));
+}
+
+static void
+PaintIndeterminateMark(nsIFrame* aFrame,
+ DrawTarget* aDrawTarget,
+ const nsRect& aDirtyRect,
+ nsPoint aPt)
+{
+ int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+ nsRect rect(aPt, aFrame->GetSize());
+ rect.Deflate(aFrame->GetUsedBorderAndPadding());
+ rect.y += (rect.height - rect.height/4) / 2;
+ rect.height /= 4;
+
+ Rect devPxRect = NSRectToSnappedRect(rect, appUnitsPerDevPixel, *aDrawTarget);
+
+ aDrawTarget->FillRect(
+ devPxRect, ColorPattern(ToDeviceColor(aFrame->StyleColor()->mColor)));
+}
+
+//------------------------------------------------------------
+nsIFrame*
+NS_NewGfxCheckboxControlFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext)
+{
+ return new (aPresShell) nsGfxCheckboxControlFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsGfxCheckboxControlFrame)
+
+
+//------------------------------------------------------------
+// Initialize GFX-rendered state
+nsGfxCheckboxControlFrame::nsGfxCheckboxControlFrame(nsStyleContext* aContext)
+: nsFormControlFrame(aContext)
+{
+}
+
+nsGfxCheckboxControlFrame::~nsGfxCheckboxControlFrame()
+{
+}
+
+#ifdef ACCESSIBILITY
+a11y::AccType
+nsGfxCheckboxControlFrame::AccessibleType()
+{
+ return a11y::eHTMLCheckboxType;
+}
+#endif
+
+//------------------------------------------------------------
+void
+nsGfxCheckboxControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ nsFormControlFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+
+ // Get current checked state through content model.
+ if ((!IsChecked() && !IsIndeterminate()) || !IsVisibleForPainting(aBuilder))
+ return; // we're not checked or not visible, nothing to paint.
+
+ if (IsThemed())
+ return; // No need to paint the checkmark. The theme will do it.
+
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayGeneric(aBuilder, this,
+ IsIndeterminate()
+ ? PaintIndeterminateMark : PaintCheckMark,
+ "CheckedCheckbox",
+ nsDisplayItem::TYPE_CHECKED_CHECKBOX));
+}
+
+//------------------------------------------------------------
+bool
+nsGfxCheckboxControlFrame::IsChecked()
+{
+ nsCOMPtr<nsIDOMHTMLInputElement> elem(do_QueryInterface(mContent));
+ bool retval = false;
+ elem->GetChecked(&retval);
+ return retval;
+}
+
+bool
+nsGfxCheckboxControlFrame::IsIndeterminate()
+{
+ nsCOMPtr<nsIDOMHTMLInputElement> elem(do_QueryInterface(mContent));
+ bool retval = false;
+ elem->GetIndeterminate(&retval);
+ return retval;
+}
diff --git a/layout/forms/nsGfxCheckboxControlFrame.h b/layout/forms/nsGfxCheckboxControlFrame.h
new file mode 100644
index 000000000..70b8d8d6a
--- /dev/null
+++ b/layout/forms/nsGfxCheckboxControlFrame.h
@@ -0,0 +1,40 @@
+/* -*- 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 nsGfxCheckboxControlFrame_h___
+#define nsGfxCheckboxControlFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsFormControlFrame.h"
+
+class nsGfxCheckboxControlFrame : public nsFormControlFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ explicit nsGfxCheckboxControlFrame(nsStyleContext* aContext);
+ virtual ~nsGfxCheckboxControlFrame();
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(NS_LITERAL_STRING("CheckboxControl"), aResult);
+ }
+#endif
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+protected:
+
+ bool IsChecked();
+ bool IsIndeterminate();
+};
+
+#endif
+
diff --git a/layout/forms/nsGfxRadioControlFrame.cpp b/layout/forms/nsGfxRadioControlFrame.cpp
new file mode 100644
index 000000000..e4a35a998
--- /dev/null
+++ b/layout/forms/nsGfxRadioControlFrame.cpp
@@ -0,0 +1,93 @@
+/* -*- 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 "nsGfxRadioControlFrame.h"
+
+#include "gfx2DGlue.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "nsLayoutUtils.h"
+#include "nsRenderingContext.h"
+#include "nsDisplayList.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+nsIFrame*
+NS_NewGfxRadioControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsGfxRadioControlFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsGfxRadioControlFrame)
+
+nsGfxRadioControlFrame::nsGfxRadioControlFrame(nsStyleContext* aContext):
+ nsFormControlFrame(aContext)
+{
+}
+
+nsGfxRadioControlFrame::~nsGfxRadioControlFrame()
+{
+}
+
+#ifdef ACCESSIBILITY
+a11y::AccType
+nsGfxRadioControlFrame::AccessibleType()
+{
+ return a11y::eHTMLRadioButtonType;
+}
+#endif
+
+//--------------------------------------------------------------
+// Draw the dot for a non-native radio button in the checked state.
+static void
+PaintCheckedRadioButton(nsIFrame* aFrame,
+ DrawTarget* aDrawTarget,
+ const nsRect& aDirtyRect,
+ nsPoint aPt)
+{
+ // The dot is an ellipse 2px on all sides smaller than the content-box,
+ // drawn in the foreground color.
+ nsRect rect(aPt, aFrame->GetSize());
+ rect.Deflate(aFrame->GetUsedBorderAndPadding());
+ rect.Deflate(nsPresContext::CSSPixelsToAppUnits(2),
+ nsPresContext::CSSPixelsToAppUnits(2));
+
+ Rect devPxRect =
+ ToRect(nsLayoutUtils::RectToGfxRect(rect,
+ aFrame->PresContext()->AppUnitsPerDevPixel()));
+
+ ColorPattern color(ToDeviceColor(aFrame->StyleColor()->mColor));
+
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size());
+ RefPtr<Path> ellipse = builder->Finish();
+ aDrawTarget->Fill(ellipse, color);
+}
+
+void
+nsGfxRadioControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ nsFormControlFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+
+ if (!IsVisibleForPainting(aBuilder))
+ return;
+
+ if (IsThemed())
+ return; // The theme will paint the check, if any.
+
+ bool checked = true;
+ GetCurrentCheckState(&checked); // Get check state from the content model
+ if (!checked)
+ return;
+
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayGeneric(aBuilder, this, PaintCheckedRadioButton,
+ "CheckedRadioButton",
+ nsDisplayItem::TYPE_CHECKED_RADIOBUTTON));
+}
diff --git a/layout/forms/nsGfxRadioControlFrame.h b/layout/forms/nsGfxRadioControlFrame.h
new file mode 100644
index 000000000..f91e6b94c
--- /dev/null
+++ b/layout/forms/nsGfxRadioControlFrame.h
@@ -0,0 +1,32 @@
+/* -*- 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 nsGfxRadioControlFrame_h___
+#define nsGfxRadioControlFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsFormControlFrame.h"
+
+
+// nsGfxRadioControlFrame
+
+class nsGfxRadioControlFrame : public nsFormControlFrame
+{
+public:
+ explicit nsGfxRadioControlFrame(nsStyleContext* aContext);
+ ~nsGfxRadioControlFrame();
+
+ NS_DECL_FRAMEARENA_HELPERS
+
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+};
+
+#endif
diff --git a/layout/forms/nsHTMLButtonControlFrame.cpp b/layout/forms/nsHTMLButtonControlFrame.cpp
new file mode 100644
index 000000000..ef9459ef2
--- /dev/null
+++ b/layout/forms/nsHTMLButtonControlFrame.cpp
@@ -0,0 +1,465 @@
+/* -*- 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 "nsHTMLButtonControlFrame.h"
+
+#include "nsContainerFrame.h"
+#include "nsIFormControlFrame.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsButtonFrameRenderer.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsFormControlFrame.h"
+#include "nsNameSpaceManager.h"
+#include "nsDisplayList.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+nsContainerFrame*
+NS_NewHTMLButtonControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsHTMLButtonControlFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsHTMLButtonControlFrame)
+
+nsHTMLButtonControlFrame::nsHTMLButtonControlFrame(nsStyleContext* aContext)
+ : nsContainerFrame(aContext)
+{
+}
+
+nsHTMLButtonControlFrame::~nsHTMLButtonControlFrame()
+{
+}
+
+void
+nsHTMLButtonControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
+ nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+void
+nsHTMLButtonControlFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+ mRenderer.SetFrame(this, PresContext());
+}
+
+NS_QUERYFRAME_HEAD(nsHTMLButtonControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+#ifdef ACCESSIBILITY
+a11y::AccType
+nsHTMLButtonControlFrame::AccessibleType()
+{
+ return a11y::eHTMLButtonType;
+}
+#endif
+
+nsIAtom*
+nsHTMLButtonControlFrame::GetType() const
+{
+ return nsGkAtoms::HTMLButtonControlFrame;
+}
+
+void
+nsHTMLButtonControlFrame::SetFocus(bool aOn, bool aRepaint)
+{
+}
+
+nsresult
+nsHTMLButtonControlFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ // if disabled do nothing
+ if (mRenderer.isDisabled()) {
+ return NS_OK;
+ }
+
+ // mouse clicks are handled by content
+ // we don't want our children to get any events. So just pass it to frame.
+ return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+}
+
+bool
+nsHTMLButtonControlFrame::ShouldClipPaintingToBorderBox()
+{
+ return IsInput() || StyleDisplay()->mOverflowX != NS_STYLE_OVERFLOW_VISIBLE;
+}
+
+void
+nsHTMLButtonControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // Clip to our border area for event hit testing.
+ Maybe<DisplayListClipState::AutoSaveRestore> eventClipState;
+ const bool isForEventDelivery = aBuilder->IsForEventDelivery();
+ if (isForEventDelivery) {
+ eventClipState.emplace(aBuilder);
+ nsRect rect(aBuilder->ToReferenceFrame(this), GetSize());
+ nscoord radii[8];
+ bool hasRadii = GetBorderRadii(radii);
+ eventClipState->ClipContainingBlockDescendants(rect, hasRadii ? radii : nullptr);
+ }
+
+ nsDisplayList onTop;
+ if (IsVisibleForPainting(aBuilder)) {
+ mRenderer.DisplayButton(aBuilder, aLists.BorderBackground(), &onTop);
+ }
+
+ nsDisplayListCollection set;
+
+ // Do not allow the child subtree to receive events.
+ if (!isForEventDelivery) {
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+
+ if (ShouldClipPaintingToBorderBox()) {
+ nsMargin border = StyleBorder()->GetComputedBorder();
+ nsRect rect(aBuilder->ToReferenceFrame(this), GetSize());
+ rect.Deflate(border);
+ nscoord radii[8];
+ bool hasRadii = GetPaddingBoxBorderRadii(radii);
+ clipState.ClipContainingBlockDescendants(rect, hasRadii ? radii : nullptr);
+ }
+
+ BuildDisplayListForChild(aBuilder, mFrames.FirstChild(), aDirtyRect, set,
+ DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT);
+ // That should put the display items in set.Content()
+ }
+
+ // Put the foreground outline and focus rects on top of the children
+ set.Content()->AppendToTop(&onTop);
+ set.MoveTo(aLists);
+
+ DisplayOutline(aBuilder, aLists);
+
+ // to draw border when selected in editor
+ DisplaySelectionOverlay(aBuilder, aLists.Content());
+}
+
+nscoord
+nsHTMLButtonControlFrame::GetMinISize(nsRenderingContext* aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_MIN_WIDTH(this, result);
+
+ nsIFrame* kid = mFrames.FirstChild();
+ result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+ kid,
+ nsLayoutUtils::MIN_ISIZE);
+
+ result += GetWritingMode().IsVertical()
+ ? mRenderer.GetAddedButtonBorderAndPadding().TopBottom()
+ : mRenderer.GetAddedButtonBorderAndPadding().LeftRight();
+
+ return result;
+}
+
+nscoord
+nsHTMLButtonControlFrame::GetPrefISize(nsRenderingContext* aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_PREF_WIDTH(this, result);
+
+ nsIFrame* kid = mFrames.FirstChild();
+ result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+ kid,
+ nsLayoutUtils::PREF_ISIZE);
+
+ result += GetWritingMode().IsVertical()
+ ? mRenderer.GetAddedButtonBorderAndPadding().TopBottom()
+ : mRenderer.GetAddedButtonBorderAndPadding().LeftRight();
+
+ return result;
+}
+
+void
+nsHTMLButtonControlFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsHTMLButtonControlFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+
+ NS_PRECONDITION(aReflowInput.ComputedISize() != NS_INTRINSICSIZE,
+ "Should have real computed inline-size by now");
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), true);
+ }
+
+ // Reflow the child
+ nsIFrame* firstKid = mFrames.FirstChild();
+
+ MOZ_ASSERT(firstKid, "Button should have a child frame for its contents");
+ MOZ_ASSERT(!firstKid->GetNextSibling(),
+ "Button should have exactly one child frame");
+ MOZ_ASSERT(firstKid->StyleContext()->GetPseudo() ==
+ nsCSSAnonBoxes::buttonContent,
+ "Button's child frame has unexpected pseudo type!");
+
+ // XXXbz Eventually we may want to check-and-bail if
+ // !aReflowInput.ShouldReflowAllKids() &&
+ // !NS_SUBTREE_DIRTY(firstKid).
+ // We'd need to cache our ascent for that, of course.
+
+ // Reflow the contents of the button.
+ // (This populates our aDesiredSize, too.)
+ ReflowButtonContents(aPresContext, aDesiredSize,
+ aReflowInput, firstKid);
+
+ if (!ShouldClipPaintingToBorderBox()) {
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, firstKid);
+ }
+ // else, we ignore child overflow -- anything that overflows beyond our
+ // own border-box will get clipped when painting.
+
+ aStatus = NS_FRAME_COMPLETE;
+ FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize,
+ aReflowInput, aStatus);
+
+ // We're always complete and we don't support overflow containers
+ // so we shouldn't have a next-in-flow ever.
+ aStatus = NS_FRAME_COMPLETE;
+ MOZ_ASSERT(!GetNextInFlow());
+
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+// Helper-function that lets us clone the button's reflow state, but with its
+// ComputedWidth and ComputedHeight reduced by the amount of renderer-specific
+// focus border and padding that we're using. (This lets us provide a more
+// appropriate content-box size for descendents' percent sizes to resolve
+// against.)
+static ReflowInput
+CloneReflowInputWithReducedContentBox(
+ const ReflowInput& aButtonReflowInput,
+ const nsMargin& aFocusPadding)
+{
+ nscoord adjustedWidth =
+ aButtonReflowInput.ComputedWidth() - aFocusPadding.LeftRight();
+ adjustedWidth = std::max(0, adjustedWidth);
+
+ // (Only adjust height if it's an actual length.)
+ nscoord adjustedHeight = aButtonReflowInput.ComputedHeight();
+ if (adjustedHeight != NS_INTRINSICSIZE) {
+ adjustedHeight -= aFocusPadding.TopBottom();
+ adjustedHeight = std::max(0, adjustedHeight);
+ }
+
+ ReflowInput clone(aButtonReflowInput);
+ clone.SetComputedWidth(adjustedWidth);
+ clone.SetComputedHeight(adjustedHeight);
+
+ return clone;
+}
+
+void
+nsHTMLButtonControlFrame::ReflowButtonContents(nsPresContext* aPresContext,
+ ReflowOutput& aButtonDesiredSize,
+ const ReflowInput& aButtonReflowInput,
+ nsIFrame* aFirstKid)
+{
+ WritingMode wm = GetWritingMode();
+ LogicalSize availSize = aButtonReflowInput.ComputedSize(wm);
+ availSize.BSize(wm) = NS_INTRINSICSIZE;
+
+ // Buttons have some bonus renderer-determined border/padding,
+ // which occupies part of the button's content-box area:
+ LogicalMargin focusPadding =
+ LogicalMargin(wm, mRenderer.GetAddedButtonBorderAndPadding());
+
+ // See whether out availSize's inline-size is big enough. If it's
+ // smaller than our intrinsic min iSize, that means that the kid
+ // wouldn't really fit. In that case, we overflow into our internal
+ // focuspadding (which other browsers don't have) so that there's a
+ // little more space for it.
+ // Note that GetMinISize includes the focusPadding.
+ nscoord IOverflow = GetMinISize(aButtonReflowInput.mRenderingContext) -
+ aButtonReflowInput.ComputedISize();
+ nscoord IFocusPadding = focusPadding.IStartEnd(wm);
+ nscoord focusPaddingReduction = std::min(IFocusPadding,
+ std::max(IOverflow, 0));
+ if (focusPaddingReduction > 0) {
+ nscoord startReduction = focusPadding.IStart(wm);
+ if (focusPaddingReduction != IFocusPadding) {
+ startReduction = NSToCoordRound(startReduction *
+ (float(focusPaddingReduction) /
+ float(IFocusPadding)));
+ }
+ focusPadding.IStart(wm) -= startReduction;
+ focusPadding.IEnd(wm) -= focusPaddingReduction - startReduction;
+ }
+
+ // shorthand for a value we need to use in a bunch of places
+ const LogicalMargin& clbp = aButtonReflowInput.ComputedLogicalBorderPadding();
+
+ // Indent the child inside us by the focus border. We must do this separate
+ // from the regular border.
+ availSize.ISize(wm) -= focusPadding.IStartEnd(wm);
+
+ LogicalPoint childPos(wm);
+ childPos.I(wm) = focusPadding.IStart(wm) + clbp.IStart(wm);
+ availSize.ISize(wm) = std::max(availSize.ISize(wm), 0);
+
+ // Give child a clone of the button's reflow state, with height/width reduced
+ // by focusPadding, so that descendants with height:100% don't protrude.
+ ReflowInput adjustedButtonReflowInput =
+ CloneReflowInputWithReducedContentBox(aButtonReflowInput,
+ focusPadding.GetPhysicalMargin(wm));
+
+ ReflowInput contentsReflowInput(aPresContext,
+ adjustedButtonReflowInput,
+ aFirstKid, availSize);
+
+ nsReflowStatus contentsReflowStatus;
+ ReflowOutput contentsDesiredSize(aButtonReflowInput);
+ childPos.B(wm) = 0; // This will be set properly later, after reflowing the
+ // child to determine its size.
+
+ // We just pass a dummy containerSize here, as the child will be
+ // repositioned later by FinishReflowChild.
+ nsSize dummyContainerSize;
+ ReflowChild(aFirstKid, aPresContext,
+ contentsDesiredSize, contentsReflowInput,
+ wm, childPos, dummyContainerSize, 0, contentsReflowStatus);
+ MOZ_ASSERT(NS_FRAME_IS_COMPLETE(contentsReflowStatus),
+ "We gave button-contents frame unconstrained available height, "
+ "so it should be complete");
+
+ // Compute the button's content-box size:
+ LogicalSize buttonContentBox(wm);
+ if (aButtonReflowInput.ComputedBSize() != NS_INTRINSICSIZE) {
+ // Button has a fixed block-size -- that's its content-box bSize.
+ buttonContentBox.BSize(wm) = aButtonReflowInput.ComputedBSize();
+ } else {
+ // Button is intrinsically sized -- it should shrinkwrap the
+ // button-contents' bSize, plus any focus-padding space:
+ buttonContentBox.BSize(wm) =
+ contentsDesiredSize.BSize(wm) + focusPadding.BStartEnd(wm);
+
+ // Make sure we obey min/max-bSize in the case when we're doing intrinsic
+ // sizing (we get it for free when we have a non-intrinsic
+ // aButtonReflowInput.ComputedBSize()). Note that we do this before
+ // adjusting for borderpadding, since mComputedMaxBSize and
+ // mComputedMinBSize are content bSizes.
+ buttonContentBox.BSize(wm) =
+ NS_CSS_MINMAX(buttonContentBox.BSize(wm),
+ aButtonReflowInput.ComputedMinBSize(),
+ aButtonReflowInput.ComputedMaxBSize());
+ }
+ if (aButtonReflowInput.ComputedISize() != NS_INTRINSICSIZE) {
+ buttonContentBox.ISize(wm) = aButtonReflowInput.ComputedISize();
+ } else {
+ buttonContentBox.ISize(wm) =
+ contentsDesiredSize.ISize(wm) + focusPadding.IStartEnd(wm);
+ buttonContentBox.ISize(wm) =
+ NS_CSS_MINMAX(buttonContentBox.ISize(wm),
+ aButtonReflowInput.ComputedMinISize(),
+ aButtonReflowInput.ComputedMaxISize());
+ }
+
+ // Center child in the block-direction in the button
+ // (technically, inside of the button's focus-padding area)
+ nscoord extraSpace =
+ buttonContentBox.BSize(wm) - focusPadding.BStartEnd(wm) -
+ contentsDesiredSize.BSize(wm);
+
+ childPos.B(wm) = std::max(0, extraSpace / 2);
+
+ // Adjust childPos.B() to be in terms of the button's frame-rect, instead of
+ // its focus-padding rect:
+ childPos.B(wm) += focusPadding.BStart(wm) + clbp.BStart(wm);
+
+ nsSize containerSize =
+ (buttonContentBox + clbp.Size(wm)).GetPhysicalSize(wm);
+
+ // Place the child
+ FinishReflowChild(aFirstKid, aPresContext,
+ contentsDesiredSize, &contentsReflowInput,
+ wm, childPos, containerSize, 0);
+
+ // Make sure we have a useful 'ascent' value for the child
+ if (contentsDesiredSize.BlockStartAscent() ==
+ ReflowOutput::ASK_FOR_BASELINE) {
+ WritingMode wm = aButtonReflowInput.GetWritingMode();
+ contentsDesiredSize.SetBlockStartAscent(aFirstKid->GetLogicalBaseline(wm));
+ }
+
+ // OK, we're done with the child frame.
+ // Use what we learned to populate the button frame's reflow metrics.
+ // * Button's height & width are content-box size + border-box contribution:
+ aButtonDesiredSize.SetSize(wm,
+ LogicalSize(wm, aButtonReflowInput.ComputedISize() + clbp.IStartEnd(wm),
+ buttonContentBox.BSize(wm) + clbp.BStartEnd(wm)));
+
+ // * Button's ascent is its child's ascent, plus the child's block-offset
+ // within our frame... unless it's orthogonal, in which case we'll use the
+ // contents inline-size as an approximation for now.
+ // XXX is there a better strategy? should we include border-padding?
+ if (aButtonDesiredSize.GetWritingMode().IsOrthogonalTo(wm)) {
+ aButtonDesiredSize.SetBlockStartAscent(contentsDesiredSize.ISize(wm));
+ } else {
+ aButtonDesiredSize.SetBlockStartAscent(contentsDesiredSize.BlockStartAscent() +
+ childPos.B(wm));
+ }
+
+ aButtonDesiredSize.SetOverflowAreasToDesiredBounds();
+}
+
+nsresult nsHTMLButtonControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
+{
+ if (nsGkAtoms::value == aName) {
+ return mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::value,
+ aValue, true);
+ }
+ return NS_OK;
+}
+
+nsStyleContext*
+nsHTMLButtonControlFrame::GetAdditionalStyleContext(int32_t aIndex) const
+{
+ return mRenderer.GetStyleContext(aIndex);
+}
+
+void
+nsHTMLButtonControlFrame::SetAdditionalStyleContext(int32_t aIndex,
+ nsStyleContext* aStyleContext)
+{
+ mRenderer.SetStyleContext(aIndex, aStyleContext);
+}
+
+#ifdef DEBUG
+void
+nsHTMLButtonControlFrame::AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList)
+{
+ MOZ_CRASH("unsupported operation");
+}
+
+void
+nsHTMLButtonControlFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ MOZ_CRASH("unsupported operation");
+}
+
+void
+nsHTMLButtonControlFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ MOZ_CRASH("unsupported operation");
+}
+#endif
diff --git a/layout/forms/nsHTMLButtonControlFrame.h b/layout/forms/nsHTMLButtonControlFrame.h
new file mode 100644
index 000000000..96ad0f366
--- /dev/null
+++ b/layout/forms/nsHTMLButtonControlFrame.h
@@ -0,0 +1,115 @@
+/* -*- 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 nsHTMLButtonControlFrame_h___
+#define nsHTMLButtonControlFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+#include "nsIFormControlFrame.h"
+#include "nsButtonFrameRenderer.h"
+
+class nsRenderingContext;
+class nsPresContext;
+
+class nsHTMLButtonControlFrame : public nsContainerFrame,
+ public nsIFormControlFrame
+{
+public:
+ explicit nsHTMLButtonControlFrame(nsStyleContext* aContext);
+ ~nsHTMLButtonControlFrame();
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override;
+
+ virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override;
+
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual nsStyleContext* GetAdditionalStyleContext(int32_t aIndex) const override;
+ virtual void SetAdditionalStyleContext(int32_t aIndex,
+ nsStyleContext* aStyleContext) override;
+
+#ifdef DEBUG
+ virtual void AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override;
+ virtual void InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override;
+ virtual void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+#endif
+
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(NS_LITERAL_STRING("HTMLButtonControl"), aResult);
+ }
+#endif
+
+ virtual bool HonorPrintBackgroundSettings() override { return false; }
+
+ // nsIFormControlFrame
+ void SetFocus(bool aOn, bool aRepaint) override;
+ virtual nsresult SetFormProperty(nsIAtom* aName, const nsAString& aValue) override;
+
+ // Inserted child content gets its frames parented by our child block
+ virtual nsContainerFrame* GetContentInsertionFrame() override {
+ return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
+ }
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsContainerFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
+ }
+
+protected:
+ virtual bool IsInput() { return false; }
+
+ // Indicates whether we should clip our children's painting to our
+ // border-box (either because of "overflow" or because of legacy reasons
+ // about how <input>-flavored buttons work).
+ bool ShouldClipPaintingToBorderBox();
+
+ // Reflows the button's sole child frame, and computes the desired size
+ // of the button itself from the results.
+ void ReflowButtonContents(nsPresContext* aPresContext,
+ ReflowOutput& aButtonDesiredSize,
+ const ReflowInput& aButtonReflowInput,
+ nsIFrame* aFirstKid);
+
+ nsButtonFrameRenderer mRenderer;
+};
+
+#endif
+
+
+
+
diff --git a/layout/forms/nsIComboboxControlFrame.h b/layout/forms/nsIComboboxControlFrame.h
new file mode 100644
index 000000000..9ac430ed1
--- /dev/null
+++ b/layout/forms/nsIComboboxControlFrame.h
@@ -0,0 +1,86 @@
+/* -*- 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 nsIComboboxControlFrame_h___
+#define nsIComboboxControlFrame_h___
+
+#include "nsQueryFrame.h"
+
+/**
+ * nsIComboboxControlFrame is the interface for comboboxes.
+ */
+class nsIComboboxControlFrame : public nsQueryFrame
+{
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsIComboboxControlFrame)
+
+ /**
+ * Indicates whether the list is dropped down
+ */
+ virtual bool IsDroppedDown() = 0;
+
+ virtual bool IsOpenInParentProcess() = 0;
+ virtual void SetOpenInParentProcess(bool aVal) = 0;
+
+ bool IsDroppedDownOrHasParentPopup() { return IsDroppedDown() || IsOpenInParentProcess(); }
+
+ /**
+ * Shows or hides the drop down
+ */
+ virtual void ShowDropDown(bool aDoDropDown) = 0;
+
+ /**
+ * Gets the Drop Down List
+ */
+ virtual nsIFrame* GetDropDown() = 0;
+
+ /**
+ * Sets the Drop Down List
+ */
+ virtual void SetDropDown(nsIFrame* aDropDownFrame) = 0;
+
+ /**
+ * Tells the combobox to roll up
+ */
+ virtual void RollupFromList() = 0;
+
+ /**
+ * Redisplay the selected text (will do nothing if text has not changed).
+ * This method might destroy this frame or any others that happen to be
+ * around. It might even run script.
+ */
+ NS_IMETHOD RedisplaySelectedText() = 0;
+
+ /**
+ * Method for the listbox to set and get the recent index
+ */
+ virtual int32_t UpdateRecentIndex(int32_t aIndex) = 0;
+
+ /**
+ * Notification that the content has been reset
+ */
+ virtual void OnContentReset() = 0;
+
+ /**
+ * This returns the index of the item that is currently being displayed
+ * in the display area. It may differ from what the currently Selected index
+ * is in in the dropdown.
+ *
+ * Detailed explanation:
+ * When the dropdown is dropped down via a mouse click and the user moves the mouse
+ * up and down without clicking, the currently selected item is being tracking inside
+ * the dropdown, but the combobox is not being updated. When the user selects items
+ * with the arrow keys, the combobox is being updated. So when the user clicks outside
+ * the dropdown and it needs to roll up it has to decide whether to keep the current
+ * selection or not. This method is used to get the current index in the combobox to
+ * compare it to the current index in the dropdown to see if the combox has been updated
+ * and that way it knows whether to "cancel" the current selection residing in the
+ * dropdown. Or whether to leave the selection alone.
+ */
+ virtual int32_t GetIndexOfDisplayArea() = 0;
+};
+
+#endif
+
diff --git a/layout/forms/nsIFormControlFrame.h b/layout/forms/nsIFormControlFrame.h
new file mode 100644
index 000000000..9c4baa8fc
--- /dev/null
+++ b/layout/forms/nsIFormControlFrame.h
@@ -0,0 +1,42 @@
+/* -*- 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 nsIFormControlFrame_h___
+#define nsIFormControlFrame_h___
+
+#include "nsQueryFrame.h"
+
+class nsAString;
+class nsIAtom;
+
+/**
+ * nsIFormControlFrame is the common interface for frames of form controls. It
+ * provides a uniform way of creating widgets, resizing, and painting.
+ * @see nsLeafFrame and its base classes for more info
+ */
+class nsIFormControlFrame : public nsQueryFrame
+{
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsIFormControlFrame)
+
+ /**
+ *
+ * @param aOn
+ * @param aRepaint
+ */
+ virtual void SetFocus(bool aOn = true, bool aRepaint = false) = 0;
+
+ /**
+ * Set a property on the form control frame.
+ *
+ * @param aName name of the property to set
+ * @param aValue value of the property
+ * @returns NS_OK if the property name is valid, otherwise an error code
+ */
+ virtual nsresult SetFormProperty(nsIAtom* aName, const nsAString& aValue) = 0;
+};
+
+#endif
+
diff --git a/layout/forms/nsIListControlFrame.h b/layout/forms/nsIListControlFrame.h
new file mode 100644
index 000000000..03c6ce8b3
--- /dev/null
+++ b/layout/forms/nsIListControlFrame.h
@@ -0,0 +1,98 @@
+/* -*- 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 nsIListControlFrame_h___
+#define nsIListControlFrame_h___
+
+#include "nsQueryFrame.h"
+
+class nsAString;
+
+namespace mozilla {
+namespace dom {
+class HTMLOptionElement;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * nsIListControlFrame is the interface for frame-based listboxes.
+ */
+class nsIListControlFrame : public nsQueryFrame
+{
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsIListControlFrame)
+
+ /**
+ * Sets the ComboBoxFrame
+ *
+ */
+ virtual void SetComboboxFrame(nsIFrame* aComboboxFrame) = 0;
+
+ /**
+ * Get the display string for an item
+ */
+ virtual void GetOptionText(uint32_t aIndex, nsAString& aStr) = 0;
+
+ /**
+ * Get the Selected Item's index
+ *
+ */
+ virtual int32_t GetSelectedIndex() = 0;
+
+ /**
+ * Return current option. The current option is the option displaying
+ * the focus ring when the listbox is focused.
+ */
+ virtual mozilla::dom::HTMLOptionElement* GetCurrentOption() = 0;
+
+ /**
+ * Initiates mouse capture for the listbox
+ *
+ */
+ virtual void CaptureMouseEvents(bool aGrabMouseEvents) = 0;
+
+ /**
+ * Returns the block size of a single row in the list. This is the
+ * maximum of the block sizes of all the options/optgroups.
+ */
+ virtual nscoord GetBSizeOfARow() = 0;
+
+ /**
+ * Returns the number of options in the listbox
+ */
+
+ virtual uint32_t GetNumberOfOptions() = 0;
+
+ /**
+ * Called by combobox when it's about to drop down
+ */
+ virtual void AboutToDropDown() = 0;
+
+ /**
+ * Called by combobox when it's about to roll up
+ */
+ virtual void AboutToRollup() = 0;
+
+ /**
+ * Fire on input and on change (used by combobox)
+ */
+ virtual void FireOnInputAndOnChange() = 0;
+
+ /**
+ * Tell the selected list to roll up and ensure that the proper index is
+ * selected, possibly firing onChange if the index has changed
+ *
+ * @param aIndex the index to actually select
+ */
+ virtual void ComboboxFinish(int32_t aIndex) = 0;
+
+ /**
+ * Notification that the content has been reset
+ */
+ virtual void OnContentReset() = 0;
+};
+
+#endif
+
diff --git a/layout/forms/nsISelectControlFrame.h b/layout/forms/nsISelectControlFrame.h
new file mode 100644
index 000000000..3e82d7f5b
--- /dev/null
+++ b/layout/forms/nsISelectControlFrame.h
@@ -0,0 +1,50 @@
+/* -*- 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 nsISelectControlFrame_h___
+#define nsISelectControlFrame_h___
+
+#include "nsQueryFrame.h"
+
+/**
+ * nsISelectControlFrame is the interface for combo boxes and listboxes
+ */
+class nsISelectControlFrame : public nsQueryFrame
+{
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsISelectControlFrame)
+
+ /**
+ * Adds an option to the list at index
+ */
+
+ NS_IMETHOD AddOption(int32_t index) = 0;
+
+ /**
+ * Removes the option at index. The caller must have a live script
+ * blocker while calling this method.
+ */
+ NS_IMETHOD RemoveOption(int32_t index) = 0;
+
+ /**
+ * Sets whether the parser is done adding children
+ * @param aIsDone whether the parser is done adding children
+ */
+ NS_IMETHOD DoneAddingChildren(bool aIsDone) = 0;
+
+ /**
+ * Notify the frame when an option is selected
+ */
+ NS_IMETHOD OnOptionSelected(int32_t aIndex, bool aSelected) = 0;
+
+ /**
+ * Notify the frame when selectedIndex was changed. This might
+ * destroy the frame.
+ */
+ NS_IMETHOD OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex) = 0;
+
+};
+
+#endif
diff --git a/layout/forms/nsITextControlFrame.h b/layout/forms/nsITextControlFrame.h
new file mode 100644
index 000000000..ce46b66ff
--- /dev/null
+++ b/layout/forms/nsITextControlFrame.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsITextControlFrame_h___
+#define nsITextControlFrame_h___
+
+#include "nsIFormControlFrame.h"
+
+class nsIEditor;
+class nsISelectionController;
+class nsFrameSelection;
+
+class nsITextControlFrame : public nsIFormControlFrame
+{
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsITextControlFrame)
+
+ enum SelectionDirection {
+ eNone,
+ eForward,
+ eBackward
+ };
+
+ NS_IMETHOD GetEditor(nsIEditor **aEditor) = 0;
+
+ NS_IMETHOD SetSelectionStart(int32_t aSelectionStart) = 0;
+ NS_IMETHOD SetSelectionEnd(int32_t aSelectionEnd) = 0;
+
+ NS_IMETHOD SetSelectionRange(int32_t aSelectionStart,
+ int32_t aSelectionEnd,
+ SelectionDirection aDirection = eNone) = 0;
+ NS_IMETHOD GetSelectionRange(int32_t* aSelectionStart,
+ int32_t* aSelectionEnd,
+ SelectionDirection* aDirection = nullptr) = 0;
+
+ NS_IMETHOD GetOwnedSelectionController(nsISelectionController** aSelCon) = 0;
+ virtual nsFrameSelection* GetOwnedFrameSelection() = 0;
+
+ virtual nsresult GetPhonetic(nsAString& aPhonetic) = 0;
+
+ /**
+ * Ensure editor is initialized with the proper flags and the default value.
+ * @throws NS_ERROR_NOT_INITIALIZED if mEditor has not been created
+ * @throws various and sundry other things
+ */
+ virtual nsresult EnsureEditorInitialized() = 0;
+
+ virtual nsresult ScrollSelectionIntoView() = 0;
+};
+
+#endif
diff --git a/layout/forms/nsImageControlFrame.cpp b/layout/forms/nsImageControlFrame.cpp
new file mode 100644
index 000000000..212fa9356
--- /dev/null
+++ b/layout/forms/nsImageControlFrame.cpp
@@ -0,0 +1,206 @@
+/* -*- 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 "nsImageFrame.h"
+#include "nsIFormControlFrame.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsFormControlFrame.h"
+#include "nsLayoutUtils.h"
+#include "mozilla/MouseEvents.h"
+#include "nsIContent.h"
+
+using namespace mozilla;
+
+class nsImageControlFrame : public nsImageFrame,
+ public nsIFormControlFrame
+{
+public:
+ explicit nsImageControlFrame(nsStyleContext* aContext);
+ ~nsImageControlFrame();
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual nsIAtom* GetType() const override;
+
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(NS_LITERAL_STRING("ImageControl"), aResult);
+ }
+#endif
+
+ virtual nsresult GetCursor(const nsPoint& aPoint,
+ nsIFrame::Cursor& aCursor) override;
+ // nsIFormContromFrame
+ virtual void SetFocus(bool aOn, bool aRepaint) override;
+ virtual nsresult SetFormProperty(nsIAtom* aName,
+ const nsAString& aValue) override;
+};
+
+
+nsImageControlFrame::nsImageControlFrame(nsStyleContext* aContext)
+ : nsImageFrame(aContext)
+{
+}
+
+nsImageControlFrame::~nsImageControlFrame()
+{
+}
+
+void
+nsImageControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ if (!GetPrevInFlow()) {
+ nsFormControlFrame::RegUnRegAccessKey(this, false);
+ }
+ nsImageFrame::DestroyFrom(aDestructRoot);
+}
+
+nsIFrame*
+NS_NewImageControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsImageControlFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsImageControlFrame)
+
+void
+nsImageControlFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsImageFrame::Init(aContent, aParent, aPrevInFlow);
+
+ if (aPrevInFlow) {
+ return;
+ }
+
+ mContent->SetProperty(nsGkAtoms::imageClickedPoint,
+ new nsIntPoint(0, 0),
+ nsINode::DeleteProperty<nsIntPoint>);
+}
+
+NS_QUERYFRAME_HEAD(nsImageControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsImageFrame)
+
+#ifdef ACCESSIBILITY
+a11y::AccType
+nsImageControlFrame::AccessibleType()
+{
+ if (mContent->IsAnyOfHTMLElements(nsGkAtoms::button, nsGkAtoms::input)) {
+ return a11y::eHTMLButtonType;
+ }
+
+ return a11y::eNoType;
+}
+#endif
+
+nsIAtom*
+nsImageControlFrame::GetType() const
+{
+ return nsGkAtoms::imageControlFrame;
+}
+
+void
+nsImageControlFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ DO_GLOBAL_REFLOW_COUNT("nsImageControlFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ if (!GetPrevInFlow() && (mState & NS_FRAME_FIRST_REFLOW)) {
+ nsFormControlFrame::RegUnRegAccessKey(this, true);
+ }
+ return nsImageFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
+}
+
+nsresult
+nsImageControlFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+
+ // Don't do anything if the event has already been handled by someone
+ if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
+ return NS_OK;
+ }
+
+ // do we have user-input style?
+ const nsStyleUserInterface* uiStyle = StyleUserInterface();
+ if (uiStyle->mUserInput == StyleUserInput::None ||
+ uiStyle->mUserInput == StyleUserInput::Disabled) {
+ return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+ }
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) { // XXX cache disabled
+ return NS_OK;
+ }
+
+ *aEventStatus = nsEventStatus_eIgnore;
+
+ if (aEvent->mMessage == eMouseUp &&
+ aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) {
+ // Store click point for HTMLInputElement::SubmitNamesValues
+ // Do this on MouseUp because the specs don't say and that's what IE does
+ nsIntPoint* lastClickPoint =
+ static_cast<nsIntPoint*>
+ (mContent->GetProperty(nsGkAtoms::imageClickedPoint));
+ if (lastClickPoint) {
+ // normally lastClickedPoint is not null, as it's allocated in Init()
+ nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this);
+ TranslateEventCoords(pt, *lastClickPoint);
+ }
+ }
+ return nsImageFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+}
+
+void
+nsImageControlFrame::SetFocus(bool aOn, bool aRepaint)
+{
+}
+
+nsresult
+nsImageControlFrame::GetCursor(const nsPoint& aPoint,
+ nsIFrame::Cursor& aCursor)
+{
+ // Use style defined cursor if one is provided, otherwise when
+ // the cursor style is "auto" we use the pointer cursor.
+ FillCursorInformationFromStyle(StyleUserInterface(), aCursor);
+
+ if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) {
+ aCursor.mCursor = NS_STYLE_CURSOR_POINTER;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsImageControlFrame::SetFormProperty(nsIAtom* aName,
+ const nsAString& aValue)
+{
+ return NS_OK;
+}
diff --git a/layout/forms/nsLegendFrame.cpp b/layout/forms/nsLegendFrame.cpp
new file mode 100644
index 000000000..60056f15d
--- /dev/null
+++ b/layout/forms/nsLegendFrame.cpp
@@ -0,0 +1,94 @@
+/* -*- 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 "nsLegendFrame.h"
+#include "nsIContent.h"
+#include "nsGenericHTMLElement.h"
+#include "nsAttrValueInlines.h"
+#include "nsHTMLParts.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsFormControlFrame.h"
+
+nsIFrame*
+NS_NewLegendFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+#ifdef DEBUG
+ const nsStyleDisplay* disp = aContext->StyleDisplay();
+ NS_ASSERTION(!disp->IsAbsolutelyPositionedStyle() && !disp->IsFloatingStyle(),
+ "Legends should not be positioned and should not float");
+#endif
+
+ nsIFrame* f = new (aPresShell) nsLegendFrame(aContext);
+ if (f) {
+ f->AddStateBits(NS_BLOCK_FLOAT_MGR | NS_BLOCK_MARGIN_ROOT);
+ }
+ return f;
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsLegendFrame)
+
+nsIAtom*
+nsLegendFrame::GetType() const
+{
+ return nsGkAtoms::legendFrame;
+}
+
+void
+nsLegendFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
+ nsBlockFrame::DestroyFrom(aDestructRoot);
+}
+
+NS_QUERYFRAME_HEAD(nsLegendFrame)
+ NS_QUERYFRAME_ENTRY(nsLegendFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
+
+void
+nsLegendFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ DO_GLOBAL_REFLOW_COUNT("nsLegendFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), true);
+ }
+ return nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
+}
+
+int32_t
+nsLegendFrame::GetLogicalAlign(WritingMode aCBWM)
+{
+ int32_t intValue = NS_STYLE_TEXT_ALIGN_START;
+ nsGenericHTMLElement* content = nsGenericHTMLElement::FromContent(mContent);
+ if (content) {
+ const nsAttrValue* attr = content->GetParsedAttr(nsGkAtoms::align);
+ if (attr && attr->Type() == nsAttrValue::eEnum) {
+ intValue = attr->GetEnumValue();
+ switch (intValue) {
+ case NS_STYLE_TEXT_ALIGN_LEFT:
+ intValue = aCBWM.IsBidiLTR() ? NS_STYLE_TEXT_ALIGN_START
+ : NS_STYLE_TEXT_ALIGN_END;
+ break;
+ case NS_STYLE_TEXT_ALIGN_RIGHT:
+ intValue = aCBWM.IsBidiLTR() ? NS_STYLE_TEXT_ALIGN_END
+ : NS_STYLE_TEXT_ALIGN_START;
+ break;
+ }
+ }
+ }
+ return intValue;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsLegendFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("Legend"), aResult);
+}
+#endif
diff --git a/layout/forms/nsLegendFrame.h b/layout/forms/nsLegendFrame.h
new file mode 100644
index 000000000..5f5e1e03e
--- /dev/null
+++ b/layout/forms/nsLegendFrame.h
@@ -0,0 +1,37 @@
+/* -*- 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 nsLegendFrame_h___
+#define nsLegendFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsBlockFrame.h"
+
+class nsLegendFrame : public nsBlockFrame {
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsLegendFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ explicit nsLegendFrame(nsStyleContext* aContext) : nsBlockFrame(aContext) {}
+
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ int32_t GetLogicalAlign(WritingMode aCBWM);
+};
+
+
+#endif // guard
diff --git a/layout/forms/nsListControlFrame.cpp b/layout/forms/nsListControlFrame.cpp
new file mode 100644
index 000000000..cc5f37f9a
--- /dev/null
+++ b/layout/forms/nsListControlFrame.cpp
@@ -0,0 +1,2537 @@
+/* -*- 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 "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsUnicharUtils.h"
+#include "nsListControlFrame.h"
+#include "nsFormControlFrame.h" // for COMPARE macro
+#include "nsGkAtoms.h"
+#include "nsIDOMHTMLSelectElement.h"
+#include "nsIDOMHTMLOptionElement.h"
+#include "nsComboboxControlFrame.h"
+#include "nsIDOMHTMLOptGroupElement.h"
+#include "nsIPresShell.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsIXULRuntime.h"
+#include "nsFontMetrics.h"
+#include "nsIScrollableFrame.h"
+#include "nsCSSRendering.h"
+#include "nsIDOMEventListener.h"
+#include "nsLayoutUtils.h"
+#include "nsDisplayList.h"
+#include "nsContentUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/HTMLOptionsCollection.h"
+#include "mozilla/dom/HTMLSelectElement.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEvents.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+// Constants
+const uint32_t kMaxDropDownRows = 20; // This matches the setting for 4.x browsers
+const int32_t kNothingSelected = -1;
+
+// Static members
+nsListControlFrame * nsListControlFrame::mFocused = nullptr;
+nsString * nsListControlFrame::sIncrementalString = nullptr;
+
+// Using for incremental typing navigation
+#define INCREMENTAL_SEARCH_KEYPRESS_TIME 1000
+// XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose:
+// nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml
+// need to find a good place to put them together.
+// if someone changes one, please also change the other.
+
+DOMTimeStamp nsListControlFrame::gLastKeyTime = 0;
+
+/******************************************************************************
+ * nsListEventListener
+ * This class is responsible for propagating events to the nsListControlFrame.
+ * Frames are not refcounted so they can't be used as event listeners.
+ *****************************************************************************/
+
+class nsListEventListener final : public nsIDOMEventListener
+{
+public:
+ explicit nsListEventListener(nsListControlFrame *aFrame)
+ : mFrame(aFrame) { }
+
+ void SetFrame(nsListControlFrame *aFrame) { mFrame = aFrame; }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+private:
+ ~nsListEventListener() {}
+
+ nsListControlFrame *mFrame;
+};
+
+//---------------------------------------------------------
+nsContainerFrame*
+NS_NewListControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ nsListControlFrame* it =
+ new (aPresShell) nsListControlFrame(aContext);
+
+ it->AddStateBits(NS_FRAME_INDEPENDENT_SELECTION);
+
+ return it;
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsListControlFrame)
+
+//---------------------------------------------------------
+nsListControlFrame::nsListControlFrame(nsStyleContext* aContext)
+ : nsHTMLScrollFrame(aContext, false),
+ mMightNeedSecondPass(false),
+ mHasPendingInterruptAtStartOfReflow(false),
+ mDropdownCanGrow(false),
+ mForceSelection(false),
+ mLastDropdownComputedBSize(NS_UNCONSTRAINEDSIZE)
+{
+ mComboboxFrame = nullptr;
+ mChangesSinceDragStart = false;
+ mButtonDown = false;
+
+ mIsAllContentHere = false;
+ mIsAllFramesHere = false;
+ mHasBeenInitialized = false;
+ mNeedToReset = true;
+ mPostChildrenLoadedReset = false;
+
+ mControlSelectMode = false;
+}
+
+//---------------------------------------------------------
+nsListControlFrame::~nsListControlFrame()
+{
+ mComboboxFrame = nullptr;
+}
+
+// for Bug 47302 (remove this comment later)
+void
+nsListControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ // get the receiver interface from the browser button's content node
+ ENSURE_TRUE(mContent);
+
+ // Clear the frame pointer on our event listener, just in case the
+ // event listener can outlive the frame.
+
+ mEventListener->SetFrame(nullptr);
+
+ mContent->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"),
+ mEventListener, false);
+ mContent->RemoveSystemEventListener(NS_LITERAL_STRING("keypress"),
+ mEventListener, false);
+ mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"),
+ mEventListener, false);
+ mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mouseup"),
+ mEventListener, false);
+ mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mousemove"),
+ mEventListener, false);
+
+ if (XRE_IsContentProcess() &&
+ Preferences::GetBool("browser.tabs.remote.desktopbehavior", false)) {
+ nsContentUtils::AddScriptRunner(
+ new AsyncEventDispatcher(mContent,
+ NS_LITERAL_STRING("mozhidedropdown"), true,
+ true));
+ }
+
+ nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
+ nsHTMLScrollFrame::DestroyFrom(aDestructRoot);
+}
+
+void
+nsListControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // We allow visibility:hidden <select>s to contain visible options.
+
+ // Don't allow painting of list controls when painting is suppressed.
+ // XXX why do we need this here? we should never reach this. Maybe
+ // because these can have widgets? Hmm
+ if (aBuilder->IsBackgroundOnly())
+ return;
+
+ DO_GLOBAL_REFLOW_COUNT_DSP("nsListControlFrame");
+
+ if (IsInDropDownMode()) {
+ NS_ASSERTION(NS_GET_A(mLastDropdownBackstopColor) == 255,
+ "need an opaque backstop color");
+ // XXX Because we have an opaque widget and we get called to paint with
+ // this frame as the root of a stacking context we need make sure to draw
+ // some opaque color over the whole widget. (Bug 511323)
+ aLists.BorderBackground()->AppendNewToBottom(
+ new (aBuilder) nsDisplaySolidColor(aBuilder,
+ this, nsRect(aBuilder->ToReferenceFrame(this), GetSize()),
+ mLastDropdownBackstopColor));
+ }
+
+ nsHTMLScrollFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+}
+
+/**
+ * This is called by the SelectsAreaFrame, which is the same
+ * as the frame returned by GetOptionsContainer. It's the frame which is
+ * scrolled by us.
+ * @param aPt the offset of this frame, relative to the rendering reference
+ * frame
+ */
+void nsListControlFrame::PaintFocus(DrawTarget* aDrawTarget, nsPoint aPt)
+{
+ if (mFocused != this) return;
+
+ nsPresContext* presContext = PresContext();
+
+ nsIFrame* containerFrame = GetOptionsContainer();
+ if (!containerFrame) return;
+
+ nsIFrame* childframe = nullptr;
+ nsCOMPtr<nsIContent> focusedContent = GetCurrentOption();
+ if (focusedContent) {
+ childframe = focusedContent->GetPrimaryFrame();
+ }
+
+ nsRect fRect;
+ if (childframe) {
+ // get the child rect
+ fRect = childframe->GetRect();
+ // get it into our coordinates
+ fRect.MoveBy(childframe->GetParent()->GetOffsetTo(this));
+ } else {
+ float inflation = nsLayoutUtils::FontSizeInflationFor(this);
+ fRect.x = fRect.y = 0;
+ if (GetWritingMode().IsVertical()) {
+ fRect.width = GetScrollPortRect().width;
+ fRect.height = CalcFallbackRowBSize(inflation);
+ } else {
+ fRect.width = CalcFallbackRowBSize(inflation);
+ fRect.height = GetScrollPortRect().height;
+ }
+ fRect.MoveBy(containerFrame->GetOffsetTo(this));
+ }
+ fRect += aPt;
+
+ bool lastItemIsSelected = false;
+ if (focusedContent) {
+ nsCOMPtr<nsIDOMHTMLOptionElement> domOpt =
+ do_QueryInterface(focusedContent);
+ if (domOpt) {
+ domOpt->GetSelected(&lastItemIsSelected);
+ }
+ }
+
+ // set up back stop colors and then ask L&F service for the real colors
+ nscolor color =
+ LookAndFeel::GetColor(lastItemIsSelected ?
+ LookAndFeel::eColorID_WidgetSelectForeground :
+ LookAndFeel::eColorID_WidgetSelectBackground);
+
+ nsCSSRendering::PaintFocus(presContext, aDrawTarget, fRect, color);
+}
+
+void
+nsListControlFrame::InvalidateFocus()
+{
+ if (mFocused != this)
+ return;
+
+ nsIFrame* containerFrame = GetOptionsContainer();
+ if (containerFrame) {
+ containerFrame->InvalidateFrame();
+ }
+}
+
+NS_QUERYFRAME_HEAD(nsListControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIListControlFrame)
+ NS_QUERYFRAME_ENTRY(nsISelectControlFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsHTMLScrollFrame)
+
+#ifdef ACCESSIBILITY
+a11y::AccType
+nsListControlFrame::AccessibleType()
+{
+ return a11y::eHTMLSelectListType;
+}
+#endif
+
+static nscoord
+GetMaxOptionBSize(nsIFrame* aContainer, WritingMode aWM)
+{
+ nscoord result = 0;
+ for (nsIFrame* option : aContainer->PrincipalChildList()) {
+ nscoord optionBSize;
+ if (nsCOMPtr<nsIDOMHTMLOptGroupElement>
+ (do_QueryInterface(option->GetContent()))) {
+ // An optgroup; drill through any scroll frame and recurse. |frame| might
+ // be null here though if |option| is an anonymous leaf frame of some sort.
+ auto frame = option->GetContentInsertionFrame();
+ optionBSize = frame ? GetMaxOptionBSize(frame, aWM) : 0;
+ } else {
+ // an option
+ optionBSize = option->BSize(aWM);
+ }
+ if (result < optionBSize)
+ result = optionBSize;
+ }
+ return result;
+}
+
+//-----------------------------------------------------------------
+// Main Reflow for ListBox/Dropdown
+//-----------------------------------------------------------------
+
+nscoord
+nsListControlFrame::CalcBSizeOfARow()
+{
+ // Calculate the block size in our writing mode of a single row in the
+ // listbox or dropdown list by using the tallest thing in the subtree,
+ // since there may be option groups in addition to option elements,
+ // either of which may be visible or invisible, may use different
+ // fonts, etc.
+ int32_t blockSizeOfARow = GetMaxOptionBSize(GetOptionsContainer(),
+ GetWritingMode());
+
+ // Check to see if we have zero items (and optimize by checking
+ // blockSizeOfARow first)
+ if (blockSizeOfARow == 0 && GetNumberOfOptions() == 0) {
+ float inflation = nsLayoutUtils::FontSizeInflationFor(this);
+ blockSizeOfARow = CalcFallbackRowBSize(inflation);
+ }
+
+ return blockSizeOfARow;
+}
+
+nscoord
+nsListControlFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_PREF_WIDTH(this, result);
+
+ // Always add scrollbar inline sizes to the pref-inline-size of the
+ // scrolled content. Combobox frames depend on this happening in the
+ // dropdown, and standalone listboxes are overflow:scroll so they need
+ // it too.
+ WritingMode wm = GetWritingMode();
+ result = GetScrolledFrame()->GetPrefISize(aRenderingContext);
+ LogicalMargin scrollbarSize(wm, GetDesiredScrollbarSizes(PresContext(),
+ aRenderingContext));
+ result = NSCoordSaturatingAdd(result, scrollbarSize.IStartEnd(wm));
+ return result;
+}
+
+nscoord
+nsListControlFrame::GetMinISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_MIN_WIDTH(this, result);
+
+ // Always add scrollbar inline sizes to the min-inline-size of the
+ // scrolled content. Combobox frames depend on this happening in the
+ // dropdown, and standalone listboxes are overflow:scroll so they need
+ // it too.
+ WritingMode wm = GetWritingMode();
+ result = GetScrolledFrame()->GetMinISize(aRenderingContext);
+ LogicalMargin scrollbarSize(wm, GetDesiredScrollbarSizes(PresContext(),
+ aRenderingContext));
+ result += scrollbarSize.IStartEnd(wm);
+
+ return result;
+}
+
+void
+nsListControlFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ NS_PRECONDITION(aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE,
+ "Must have a computed inline size");
+
+ SchedulePaint();
+
+ mHasPendingInterruptAtStartOfReflow = aPresContext->HasPendingInterrupt();
+
+ // If all the content and frames are here
+ // then initialize it before reflow
+ if (mIsAllContentHere && !mHasBeenInitialized) {
+ if (false == mIsAllFramesHere) {
+ CheckIfAllFramesHere();
+ }
+ if (mIsAllFramesHere && !mHasBeenInitialized) {
+ mHasBeenInitialized = true;
+ }
+ }
+
+ if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
+ nsFormControlFrame::RegUnRegAccessKey(this, true);
+ }
+
+ if (IsInDropDownMode()) {
+ ReflowAsDropdown(aPresContext, aDesiredSize, aReflowInput, aStatus);
+ return;
+ }
+
+ MarkInReflow();
+ /*
+ * Due to the fact that our intrinsic block size depends on the block
+ * sizes of our kids, we end up having to do two-pass reflow, in
+ * general -- the first pass to find the intrinsic block size and a
+ * second pass to reflow the scrollframe at that block size (which
+ * will size the scrollbars correctly, etc).
+ *
+ * Naturally, we want to avoid doing the second reflow as much as
+ * possible.
+ * We can skip it in the following cases (in all of which the first
+ * reflow is already happening at the right block size):
+ *
+ * - We're reflowing with a constrained computed block size -- just
+ * use that block size.
+ * - We're not dirty and have no dirty kids and shouldn't be reflowing
+ * all kids. In this case, our cached max block size of a child is
+ * not going to change.
+ * - We do our first reflow using our cached max block size of a
+ * child, then compute the new max block size and it's the same as
+ * the old one.
+ */
+
+ bool autoBSize = (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE);
+
+ mMightNeedSecondPass = autoBSize &&
+ (NS_SUBTREE_DIRTY(this) || aReflowInput.ShouldReflowAllKids());
+
+ ReflowInput state(aReflowInput);
+ int32_t length = GetNumberOfRows();
+
+ nscoord oldBSizeOfARow = BSizeOfARow();
+
+ if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW) && autoBSize) {
+ // When not doing an initial reflow, and when the block size is
+ // auto, start off with our computed block size set to what we'd
+ // expect our block size to be.
+ nscoord computedBSize = CalcIntrinsicBSize(oldBSizeOfARow, length);
+ computedBSize = state.ApplyMinMaxBSize(computedBSize);
+ state.SetComputedBSize(computedBSize);
+ }
+
+ nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
+
+ if (!mMightNeedSecondPass) {
+ NS_ASSERTION(!autoBSize || BSizeOfARow() == oldBSizeOfARow,
+ "How did our BSize of a row change if nothing was dirty?");
+ NS_ASSERTION(!autoBSize ||
+ !(GetStateBits() & NS_FRAME_FIRST_REFLOW),
+ "How do we not need a second pass during initial reflow at "
+ "auto BSize?");
+ NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
+ "Shouldn't be suppressing if we don't need a second pass!");
+ if (!autoBSize) {
+ // Update our mNumDisplayRows based on our new row block size now
+ // that we know it. Note that if autoBSize and we landed in this
+ // code then we already set mNumDisplayRows in CalcIntrinsicBSize.
+ // Also note that we can't use BSizeOfARow() here because that
+ // just uses a cached value that we didn't compute.
+ nscoord rowBSize = CalcBSizeOfARow();
+ if (rowBSize == 0) {
+ // Just pick something
+ mNumDisplayRows = 1;
+ } else {
+ mNumDisplayRows = std::max(1, state.ComputedBSize() / rowBSize);
+ }
+ }
+
+ return;
+ }
+
+ mMightNeedSecondPass = false;
+
+ // Now see whether we need a second pass. If we do, our
+ // nsSelectsAreaFrame will have suppressed the scrollbar update.
+ if (!IsScrollbarUpdateSuppressed()) {
+ // All done. No need to do more reflow.
+ NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
+ "Shouldn't be suppressing if the block size of a row has not "
+ "changed!");
+ return;
+ }
+
+ SetSuppressScrollbarUpdate(false);
+
+ // Gotta reflow again.
+ // XXXbz We're just changing the block size here; do we need to dirty
+ // ourselves or anything like that? We might need to, per the letter
+ // of the reflow protocol, but things seem to work fine without it...
+ // Is that just an implementation detail of nsHTMLScrollFrame that
+ // we're depending on?
+ nsHTMLScrollFrame::DidReflow(aPresContext, &state,
+ nsDidReflowStatus::FINISHED);
+
+ // Now compute the block size we want to have
+ nscoord computedBSize = CalcIntrinsicBSize(BSizeOfARow(), length);
+ computedBSize = state.ApplyMinMaxBSize(computedBSize);
+ state.SetComputedBSize(computedBSize);
+
+ // XXXbz to make the ascent really correct, we should add our
+ // mComputedPadding.top to it (and subtract it from descent). Need that
+ // because nsGfxScrollFrame just adds in the border....
+ nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
+}
+
+void
+nsListControlFrame::ReflowAsDropdown(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ NS_PRECONDITION(aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE,
+ "We should not have a computed block size here!");
+
+ mMightNeedSecondPass = NS_SUBTREE_DIRTY(this) ||
+ aReflowInput.ShouldReflowAllKids();
+
+ WritingMode wm = aReflowInput.GetWritingMode();
+#ifdef DEBUG
+ nscoord oldBSizeOfARow = BSizeOfARow();
+ nscoord oldVisibleBSize = (GetStateBits() & NS_FRAME_FIRST_REFLOW) ?
+ NS_UNCONSTRAINEDSIZE : GetScrolledFrame()->BSize(wm);
+#endif
+
+ ReflowInput state(aReflowInput);
+
+ if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
+ // When not doing an initial reflow, and when the block size is
+ // auto, start off with our computed block size set to what we'd
+ // expect our block size to be.
+ // Note: At this point, mLastDropdownComputedBSize can be
+ // NS_UNCONSTRAINEDSIZE in cases when last time we didn't have to
+ // constrain the block size. That's fine; just do the same thing as
+ // last time.
+ state.SetComputedBSize(mLastDropdownComputedBSize);
+ }
+
+ nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
+
+ if (!mMightNeedSecondPass) {
+ NS_ASSERTION(oldVisibleBSize == GetScrolledFrame()->BSize(wm),
+ "How did our kid's BSize change if nothing was dirty?");
+ NS_ASSERTION(BSizeOfARow() == oldBSizeOfARow,
+ "How did our BSize of a row change if nothing was dirty?");
+ NS_ASSERTION(!IsScrollbarUpdateSuppressed(),
+ "Shouldn't be suppressing if we don't need a second pass!");
+ NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW),
+ "How can we avoid a second pass during first reflow?");
+ return;
+ }
+
+ mMightNeedSecondPass = false;
+
+ // Now see whether we need a second pass. If we do, our nsSelectsAreaFrame
+ // will have suppressed the scrollbar update.
+ if (!IsScrollbarUpdateSuppressed()) {
+ // All done. No need to do more reflow.
+ NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW),
+ "How can we avoid a second pass during first reflow?");
+ return;
+ }
+
+ SetSuppressScrollbarUpdate(false);
+
+ nscoord visibleBSize = GetScrolledFrame()->BSize(wm);
+ nscoord blockSizeOfARow = BSizeOfARow();
+
+ // Gotta reflow again.
+ // XXXbz We're just changing the block size here; do we need to dirty
+ // ourselves or anything like that? We might need to, per the letter
+ // of the reflow protocol, but things seem to work fine without it...
+ // Is that just an implementation detail of nsHTMLScrollFrame that
+ // we're depending on?
+ nsHTMLScrollFrame::DidReflow(aPresContext, &state,
+ nsDidReflowStatus::FINISHED);
+
+ // Now compute the block size we want to have.
+ // Note: no need to apply min/max constraints, since we have no such
+ // rules applied to the combobox dropdown.
+
+ mDropdownCanGrow = false;
+ if (visibleBSize <= 0 || blockSizeOfARow <= 0 || XRE_IsContentProcess()) {
+ // Looks like we have no options. Just size us to a single row
+ // block size.
+ state.SetComputedBSize(blockSizeOfARow);
+ mNumDisplayRows = 1;
+ } else {
+ nsComboboxControlFrame* combobox =
+ static_cast<nsComboboxControlFrame*>(mComboboxFrame);
+ LogicalPoint translation(wm);
+ nscoord before, after;
+ combobox->GetAvailableDropdownSpace(wm, &before, &after, &translation);
+ if (before <= 0 && after <= 0) {
+ state.SetComputedBSize(blockSizeOfARow);
+ mNumDisplayRows = 1;
+ mDropdownCanGrow = GetNumberOfRows() > 1;
+ } else {
+ nscoord bp = aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm);
+ nscoord availableBSize = std::max(before, after) - bp;
+ nscoord newBSize;
+ uint32_t rows;
+ if (visibleBSize <= availableBSize) {
+ // The dropdown fits in the available block size.
+ rows = GetNumberOfRows();
+ mNumDisplayRows = clamped<uint32_t>(rows, 1, kMaxDropDownRows);
+ if (mNumDisplayRows == rows) {
+ newBSize = visibleBSize; // use the exact block size
+ } else {
+ newBSize = mNumDisplayRows * blockSizeOfARow; // approximate
+ // The approximation here might actually be too big (bug 1208978);
+ // don't let it exceed the actual block-size of the list.
+ newBSize = std::min(newBSize, visibleBSize);
+ }
+ } else {
+ rows = availableBSize / blockSizeOfARow;
+ mNumDisplayRows = clamped<uint32_t>(rows, 1, kMaxDropDownRows);
+ newBSize = mNumDisplayRows * blockSizeOfARow; // approximate
+ }
+ state.SetComputedBSize(newBSize);
+ mDropdownCanGrow = visibleBSize - newBSize >= blockSizeOfARow &&
+ mNumDisplayRows != kMaxDropDownRows;
+ }
+ }
+
+ mLastDropdownComputedBSize = state.ComputedBSize();
+
+ nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, state, aStatus);
+}
+
+ScrollbarStyles
+nsListControlFrame::GetScrollbarStyles() const
+{
+ // We can't express this in the style system yet; when we can, this can go away
+ // and GetScrollbarStyles can be devirtualized
+ int32_t style = IsInDropDownMode() ? NS_STYLE_OVERFLOW_AUTO
+ : NS_STYLE_OVERFLOW_SCROLL;
+ if (GetWritingMode().IsVertical()) {
+ return ScrollbarStyles(style, NS_STYLE_OVERFLOW_HIDDEN);
+ } else {
+ return ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, style);
+ }
+}
+
+bool
+nsListControlFrame::ShouldPropagateComputedBSizeToScrolledContent() const
+{
+ return !IsInDropDownMode();
+}
+
+//---------------------------------------------------------
+nsContainerFrame*
+nsListControlFrame::GetContentInsertionFrame() {
+ return GetOptionsContainer()->GetContentInsertionFrame();
+}
+
+//---------------------------------------------------------
+bool
+nsListControlFrame::ExtendedSelection(int32_t aStartIndex,
+ int32_t aEndIndex,
+ bool aClearAll)
+{
+ return SetOptionsSelectedFromFrame(aStartIndex, aEndIndex,
+ true, aClearAll);
+}
+
+//---------------------------------------------------------
+bool
+nsListControlFrame::SingleSelection(int32_t aClickedIndex, bool aDoToggle)
+{
+ if (mComboboxFrame) {
+ mComboboxFrame->UpdateRecentIndex(GetSelectedIndex());
+ }
+
+ bool wasChanged = false;
+ // Get Current selection
+ if (aDoToggle) {
+ wasChanged = ToggleOptionSelectedFromFrame(aClickedIndex);
+ } else {
+ wasChanged = SetOptionsSelectedFromFrame(aClickedIndex, aClickedIndex,
+ true, true);
+ }
+ nsWeakFrame weakFrame(this);
+ ScrollToIndex(aClickedIndex);
+ if (!weakFrame.IsAlive()) {
+ return wasChanged;
+ }
+
+#ifdef ACCESSIBILITY
+ bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex;
+#endif
+ mStartSelectionIndex = aClickedIndex;
+ mEndSelectionIndex = aClickedIndex;
+ InvalidateFocus();
+
+#ifdef ACCESSIBILITY
+ if (isCurrentOptionChanged) {
+ FireMenuItemActiveEvent();
+ }
+#endif
+
+ return wasChanged;
+}
+
+void
+nsListControlFrame::InitSelectionRange(int32_t aClickedIndex)
+{
+ //
+ // If nothing is selected, set the start selection depending on where
+ // the user clicked and what the initial selection is:
+ // - if the user clicked *before* selectedIndex, set the start index to
+ // the end of the first contiguous selection.
+ // - if the user clicked *after* the end of the first contiguous
+ // selection, set the start index to selectedIndex.
+ // - if the user clicked *within* the first contiguous selection, set the
+ // start index to selectedIndex.
+ // The last two rules, of course, boil down to the same thing: if the user
+ // clicked >= selectedIndex, return selectedIndex.
+ //
+ // This makes it so that shift click works properly when you first click
+ // in a multiple select.
+ //
+ int32_t selectedIndex = GetSelectedIndex();
+ if (selectedIndex >= 0) {
+ // Get the end of the contiguous selection
+ RefPtr<dom::HTMLOptionsCollection> options = GetOptions();
+ NS_ASSERTION(options, "Collection of options is null!");
+ uint32_t numOptions = options->Length();
+ // Push i to one past the last selected index in the group.
+ uint32_t i;
+ for (i = selectedIndex + 1; i < numOptions; i++) {
+ if (!options->ItemAsOption(i)->Selected()) {
+ break;
+ }
+ }
+
+ if (aClickedIndex < selectedIndex) {
+ // User clicked before selection, so start selection at end of
+ // contiguous selection
+ mStartSelectionIndex = i-1;
+ mEndSelectionIndex = selectedIndex;
+ } else {
+ // User clicked after selection, so start selection at start of
+ // contiguous selection
+ mStartSelectionIndex = selectedIndex;
+ mEndSelectionIndex = i-1;
+ }
+ }
+}
+
+static uint32_t
+CountOptionsAndOptgroups(nsIFrame* aFrame)
+{
+ uint32_t count = 0;
+ nsFrameList::Enumerator e(aFrame->PrincipalChildList());
+ for (; !e.AtEnd(); e.Next()) {
+ nsIFrame* child = e.get();
+ nsIContent* content = child->GetContent();
+ if (content) {
+ if (content->IsHTMLElement(nsGkAtoms::option)) {
+ ++count;
+ } else {
+ nsCOMPtr<nsIDOMHTMLOptGroupElement> optgroup = do_QueryInterface(content);
+ if (optgroup) {
+ nsAutoString label;
+ optgroup->GetLabel(label);
+ if (label.Length() > 0) {
+ ++count;
+ }
+ count += CountOptionsAndOptgroups(child);
+ }
+ }
+ }
+ }
+ return count;
+}
+
+uint32_t
+nsListControlFrame::GetNumberOfRows()
+{
+ return ::CountOptionsAndOptgroups(GetContentInsertionFrame());
+}
+
+//---------------------------------------------------------
+bool
+nsListControlFrame::PerformSelection(int32_t aClickedIndex,
+ bool aIsShift,
+ bool aIsControl)
+{
+ bool wasChanged = false;
+
+ if (aClickedIndex == kNothingSelected && !mForceSelection) {
+ // Ignore kNothingSelected unless the selection is forced
+ } else if (GetMultiple()) {
+ if (aIsShift) {
+ // Make sure shift+click actually does something expected when
+ // the user has never clicked on the select
+ if (mStartSelectionIndex == kNothingSelected) {
+ InitSelectionRange(aClickedIndex);
+ }
+
+ // Get the range from beginning (low) to end (high)
+ // Shift *always* works, even if the current option is disabled
+ int32_t startIndex;
+ int32_t endIndex;
+ if (mStartSelectionIndex == kNothingSelected) {
+ startIndex = aClickedIndex;
+ endIndex = aClickedIndex;
+ } else if (mStartSelectionIndex <= aClickedIndex) {
+ startIndex = mStartSelectionIndex;
+ endIndex = aClickedIndex;
+ } else {
+ startIndex = aClickedIndex;
+ endIndex = mStartSelectionIndex;
+ }
+
+ // Clear only if control was not pressed
+ wasChanged = ExtendedSelection(startIndex, endIndex, !aIsControl);
+ nsWeakFrame weakFrame(this);
+ ScrollToIndex(aClickedIndex);
+ if (!weakFrame.IsAlive()) {
+ return wasChanged;
+ }
+
+ if (mStartSelectionIndex == kNothingSelected) {
+ mStartSelectionIndex = aClickedIndex;
+ }
+#ifdef ACCESSIBILITY
+ bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex;
+#endif
+ mEndSelectionIndex = aClickedIndex;
+ InvalidateFocus();
+
+#ifdef ACCESSIBILITY
+ if (isCurrentOptionChanged) {
+ FireMenuItemActiveEvent();
+ }
+#endif
+ } else if (aIsControl) {
+ wasChanged = SingleSelection(aClickedIndex, true); // might destroy us
+ } else {
+ wasChanged = SingleSelection(aClickedIndex, false); // might destroy us
+ }
+ } else {
+ wasChanged = SingleSelection(aClickedIndex, false); // might destroy us
+ }
+
+ return wasChanged;
+}
+
+//---------------------------------------------------------
+bool
+nsListControlFrame::HandleListSelection(nsIDOMEvent* aEvent,
+ int32_t aClickedIndex)
+{
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
+ bool isShift;
+ bool isControl;
+#ifdef XP_MACOSX
+ mouseEvent->GetMetaKey(&isControl);
+#else
+ mouseEvent->GetCtrlKey(&isControl);
+#endif
+ mouseEvent->GetShiftKey(&isShift);
+ return PerformSelection(aClickedIndex, isShift, isControl); // might destroy us
+}
+
+//---------------------------------------------------------
+void
+nsListControlFrame::CaptureMouseEvents(bool aGrabMouseEvents)
+{
+ // Currently cocoa widgets use a native popup widget which tracks clicks synchronously,
+ // so we never want to do mouse capturing. Note that we only bail if the list
+ // is in drop-down mode, and the caller is requesting capture (we let release capture
+ // requests go through to ensure that we can release capture requested via other
+ // code paths, if any exist).
+ if (aGrabMouseEvents && IsInDropDownMode() && nsComboboxControlFrame::ToolkitHasNativePopup())
+ return;
+
+ if (aGrabMouseEvents) {
+ nsIPresShell::SetCapturingContent(mContent, CAPTURE_IGNOREALLOWED);
+ } else {
+ nsIContent* capturingContent = nsIPresShell::GetCapturingContent();
+
+ bool dropDownIsHidden = false;
+ if (IsInDropDownMode()) {
+ dropDownIsHidden = !mComboboxFrame->IsDroppedDown();
+ }
+ if (capturingContent == mContent || dropDownIsHidden) {
+ // only clear the capturing content if *we* are the ones doing the
+ // capturing (or if the dropdown is hidden, in which case NO-ONE should
+ // be capturing anything - it could be a scrollbar inside this listbox
+ // which is actually grabbing
+ // This shouldn't be necessary. We should simply ensure that events targeting
+ // scrollbars are never visible to DOM consumers.
+ nsIPresShell::SetCapturingContent(nullptr, 0);
+ }
+ }
+}
+
+//---------------------------------------------------------
+nsresult
+nsListControlFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus)
+{
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+
+ /*const char * desc[] = {"eMouseMove",
+ "NS_MOUSE_LEFT_BUTTON_UP",
+ "NS_MOUSE_LEFT_BUTTON_DOWN",
+ "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
+ "NS_MOUSE_MIDDLE_BUTTON_UP",
+ "NS_MOUSE_MIDDLE_BUTTON_DOWN",
+ "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
+ "NS_MOUSE_RIGHT_BUTTON_UP",
+ "NS_MOUSE_RIGHT_BUTTON_DOWN",
+ "eMouseOver",
+ "eMouseOut",
+ "NS_MOUSE_LEFT_DOUBLECLICK",
+ "NS_MOUSE_MIDDLE_DOUBLECLICK",
+ "NS_MOUSE_RIGHT_DOUBLECLICK",
+ "NS_MOUSE_LEFT_CLICK",
+ "NS_MOUSE_MIDDLE_CLICK",
+ "NS_MOUSE_RIGHT_CLICK"};
+ int inx = aEvent->mMessage - eMouseEventFirst;
+ if (inx >= 0 && inx <= (NS_MOUSE_RIGHT_CLICK - eMouseEventFirst)) {
+ printf("Mouse in ListFrame %s [%d]\n", desc[inx], aEvent->mMessage);
+ } else {
+ printf("Mouse in ListFrame <UNKNOWN> [%d]\n", aEvent->mMessage);
+ }*/
+
+ if (nsEventStatus_eConsumeNoDefault == *aEventStatus)
+ return NS_OK;
+
+ // do we have style that affects how we are selected?
+ // do we have user-input style?
+ const nsStyleUserInterface* uiStyle = StyleUserInterface();
+ if (uiStyle->mUserInput == StyleUserInput::None ||
+ uiStyle->mUserInput == StyleUserInput::Disabled) {
+ return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+ }
+ EventStates eventStates = mContent->AsElement()->State();
+ if (eventStates.HasState(NS_EVENT_STATE_DISABLED))
+ return NS_OK;
+
+ return nsHTMLScrollFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
+}
+
+
+//---------------------------------------------------------
+void
+nsListControlFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ if (aListID == kPrincipalList) {
+ // First check to see if all the content has been added
+ mIsAllContentHere = mContent->IsDoneAddingChildren();
+ if (!mIsAllContentHere) {
+ mIsAllFramesHere = false;
+ mHasBeenInitialized = false;
+ }
+ }
+ nsHTMLScrollFrame::SetInitialChildList(aListID, aChildList);
+
+ // If all the content is here now check
+ // to see if all the frames have been created
+ /*if (mIsAllContentHere) {
+ // If all content and frames are here
+ // the reset/initialize
+ if (CheckIfAllFramesHere()) {
+ ResetList(aPresContext);
+ mHasBeenInitialized = true;
+ }
+ }*/
+}
+
+//---------------------------------------------------------
+void
+nsListControlFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ nsHTMLScrollFrame::Init(aContent, aParent, aPrevInFlow);
+
+ // we shouldn't have to unregister this listener because when
+ // our frame goes away all these content node go away as well
+ // because our frame is the only one who references them.
+ // we need to hook up our listeners before the editor is initialized
+ mEventListener = new nsListEventListener(this);
+
+ mContent->AddSystemEventListener(NS_LITERAL_STRING("keydown"),
+ mEventListener, false, false);
+ mContent->AddSystemEventListener(NS_LITERAL_STRING("keypress"),
+ mEventListener, false, false);
+ mContent->AddSystemEventListener(NS_LITERAL_STRING("mousedown"),
+ mEventListener, false, false);
+ mContent->AddSystemEventListener(NS_LITERAL_STRING("mouseup"),
+ mEventListener, false, false);
+ mContent->AddSystemEventListener(NS_LITERAL_STRING("mousemove"),
+ mEventListener, false, false);
+
+ mStartSelectionIndex = kNothingSelected;
+ mEndSelectionIndex = kNothingSelected;
+
+ mLastDropdownBackstopColor = PresContext()->DefaultBackgroundColor();
+
+ if (IsInDropDownMode()) {
+ AddStateBits(NS_FRAME_IN_POPUP);
+ }
+}
+
+dom::HTMLOptionsCollection*
+nsListControlFrame::GetOptions() const
+{
+ dom::HTMLSelectElement* select =
+ dom::HTMLSelectElement::FromContentOrNull(mContent);
+ NS_ENSURE_TRUE(select, nullptr);
+
+ return select->Options();
+}
+
+dom::HTMLOptionElement*
+nsListControlFrame::GetOption(uint32_t aIndex) const
+{
+ dom::HTMLSelectElement* select =
+ dom::HTMLSelectElement::FromContentOrNull(mContent);
+ NS_ENSURE_TRUE(select, nullptr);
+
+ return select->Item(aIndex);
+}
+
+NS_IMETHODIMP
+nsListControlFrame::OnOptionSelected(int32_t aIndex, bool aSelected)
+{
+ if (aSelected) {
+ ScrollToIndex(aIndex);
+ }
+ return NS_OK;
+}
+
+void
+nsListControlFrame::OnContentReset()
+{
+ ResetList(true);
+}
+
+void
+nsListControlFrame::ResetList(bool aAllowScrolling)
+{
+ // if all the frames aren't here
+ // don't bother reseting
+ if (!mIsAllFramesHere) {
+ return;
+ }
+
+ if (aAllowScrolling) {
+ mPostChildrenLoadedReset = true;
+
+ // Scroll to the selected index
+ int32_t indexToSelect = kNothingSelected;
+
+ nsCOMPtr<nsIDOMHTMLSelectElement> selectElement(do_QueryInterface(mContent));
+ NS_ASSERTION(selectElement, "No select element!");
+ if (selectElement) {
+ selectElement->GetSelectedIndex(&indexToSelect);
+ nsWeakFrame weakFrame(this);
+ ScrollToIndex(indexToSelect);
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ }
+ }
+
+ mStartSelectionIndex = kNothingSelected;
+ mEndSelectionIndex = kNothingSelected;
+ InvalidateFocus();
+ // Combobox will redisplay itself with the OnOptionSelected event
+}
+
+void
+nsListControlFrame::SetFocus(bool aOn, bool aRepaint)
+{
+ InvalidateFocus();
+
+ if (aOn) {
+ ComboboxFocusSet();
+ mFocused = this;
+ } else {
+ mFocused = nullptr;
+ }
+
+ InvalidateFocus();
+}
+
+void nsListControlFrame::ComboboxFocusSet()
+{
+ gLastKeyTime = 0;
+}
+
+void
+nsListControlFrame::SetComboboxFrame(nsIFrame* aComboboxFrame)
+{
+ if (nullptr != aComboboxFrame) {
+ mComboboxFrame = do_QueryFrame(aComboboxFrame);
+ }
+}
+
+void
+nsListControlFrame::GetOptionText(uint32_t aIndex, nsAString& aStr)
+{
+ aStr.Truncate();
+ if (dom::HTMLOptionElement* optionElement = GetOption(aIndex)) {
+ optionElement->GetText(aStr);
+ }
+}
+
+int32_t
+nsListControlFrame::GetSelectedIndex()
+{
+ dom::HTMLSelectElement* select =
+ dom::HTMLSelectElement::FromContentOrNull(mContent);
+ return select->SelectedIndex();
+}
+
+dom::HTMLOptionElement*
+nsListControlFrame::GetCurrentOption()
+{
+ // The mEndSelectionIndex is what is currently being selected. Use
+ // the selected index if this is kNothingSelected.
+ int32_t focusedIndex = (mEndSelectionIndex == kNothingSelected) ?
+ GetSelectedIndex() : mEndSelectionIndex;
+
+ if (focusedIndex != kNothingSelected) {
+ return GetOption(AssertedCast<uint32_t>(focusedIndex));
+ }
+
+ // There is no selected item. Return the first non-disabled item.
+ RefPtr<dom::HTMLSelectElement> selectElement =
+ dom::HTMLSelectElement::FromContent(mContent);
+
+ for (uint32_t i = 0, length = selectElement->Length(); i < length; ++i) {
+ dom::HTMLOptionElement* node = selectElement->Item(i);
+ if (!node) {
+ return nullptr;
+ }
+
+ if (!selectElement->IsOptionDisabled(node)) {
+ return node;
+ }
+ }
+
+ return nullptr;
+}
+
+bool
+nsListControlFrame::IsInDropDownMode() const
+{
+ return (mComboboxFrame != nullptr);
+}
+
+uint32_t
+nsListControlFrame::GetNumberOfOptions()
+{
+ dom::HTMLOptionsCollection* options = GetOptions();
+ if (!options) {
+ return 0;
+ }
+
+ return options->Length();
+}
+
+//----------------------------------------------------------------------
+// nsISelectControlFrame
+//----------------------------------------------------------------------
+bool nsListControlFrame::CheckIfAllFramesHere()
+{
+ // Get the number of optgroups and options
+ //int32_t numContentItems = 0;
+ nsCOMPtr<nsIDOMNode> node(do_QueryInterface(mContent));
+ if (node) {
+ // XXX Need to find a fail proff way to determine that
+ // all the frames are there
+ mIsAllFramesHere = true;//NS_OK == CountAllChild(node, numContentItems);
+ }
+ // now make sure we have a frame each piece of content
+
+ return mIsAllFramesHere;
+}
+
+NS_IMETHODIMP
+nsListControlFrame::DoneAddingChildren(bool aIsDone)
+{
+ mIsAllContentHere = aIsDone;
+ if (mIsAllContentHere) {
+ // Here we check to see if all the frames have been created
+ // for all the content.
+ // If so, then we can initialize;
+ if (!mIsAllFramesHere) {
+ // if all the frames are now present we can initialize
+ if (CheckIfAllFramesHere()) {
+ mHasBeenInitialized = true;
+ ResetList(true);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsListControlFrame::AddOption(int32_t aIndex)
+{
+#ifdef DO_REFLOW_DEBUG
+ printf("---- Id: %d nsLCF %p Added Option %d\n", mReflowId, this, aIndex);
+#endif
+
+ if (!mIsAllContentHere) {
+ mIsAllContentHere = mContent->IsDoneAddingChildren();
+ if (!mIsAllContentHere) {
+ mIsAllFramesHere = false;
+ mHasBeenInitialized = false;
+ } else {
+ mIsAllFramesHere = (aIndex == static_cast<int32_t>(GetNumberOfOptions()-1));
+ }
+ }
+
+ // Make sure we scroll to the selected option as needed
+ mNeedToReset = true;
+
+ if (!mHasBeenInitialized) {
+ return NS_OK;
+ }
+
+ mPostChildrenLoadedReset = mIsAllContentHere;
+ return NS_OK;
+}
+
+static int32_t
+DecrementAndClamp(int32_t aSelectionIndex, int32_t aLength)
+{
+ return aLength == 0 ? kNothingSelected : std::max(0, aSelectionIndex - 1);
+}
+
+NS_IMETHODIMP
+nsListControlFrame::RemoveOption(int32_t aIndex)
+{
+ NS_PRECONDITION(aIndex >= 0, "negative <option> index");
+
+ // Need to reset if we're a dropdown
+ if (IsInDropDownMode()) {
+ mNeedToReset = true;
+ mPostChildrenLoadedReset = mIsAllContentHere;
+ }
+
+ if (mStartSelectionIndex != kNothingSelected) {
+ NS_ASSERTION(mEndSelectionIndex != kNothingSelected, "");
+ int32_t numOptions = GetNumberOfOptions();
+ // NOTE: numOptions is the new number of options whereas aIndex is the
+ // unadjusted index of the removed option (hence the <= below).
+ NS_ASSERTION(aIndex <= numOptions, "out-of-bounds <option> index");
+
+ int32_t forward = mEndSelectionIndex - mStartSelectionIndex;
+ int32_t* low = forward >= 0 ? &mStartSelectionIndex : &mEndSelectionIndex;
+ int32_t* high = forward >= 0 ? &mEndSelectionIndex : &mStartSelectionIndex;
+ if (aIndex < *low)
+ *low = ::DecrementAndClamp(*low, numOptions);
+ if (aIndex <= *high)
+ *high = ::DecrementAndClamp(*high, numOptions);
+ if (forward == 0)
+ *low = *high;
+ }
+ else
+ NS_ASSERTION(mEndSelectionIndex == kNothingSelected, "");
+
+ InvalidateFocus();
+ return NS_OK;
+}
+
+//---------------------------------------------------------
+// Set the option selected in the DOM. This method is named
+// as it is because it indicates that the frame is the source
+// of this event rather than the receiver.
+bool
+nsListControlFrame::SetOptionsSelectedFromFrame(int32_t aStartIndex,
+ int32_t aEndIndex,
+ bool aValue,
+ bool aClearAll)
+{
+ RefPtr<dom::HTMLSelectElement> selectElement =
+ dom::HTMLSelectElement::FromContent(mContent);
+
+ uint32_t mask = dom::HTMLSelectElement::NOTIFY;
+ if (mForceSelection) {
+ mask |= dom::HTMLSelectElement::SET_DISABLED;
+ }
+ if (aValue) {
+ mask |= dom::HTMLSelectElement::IS_SELECTED;
+ }
+ if (aClearAll) {
+ mask |= dom::HTMLSelectElement::CLEAR_ALL;
+ }
+
+ return selectElement->SetOptionsSelectedByIndex(aStartIndex, aEndIndex, mask);
+}
+
+bool
+nsListControlFrame::ToggleOptionSelectedFromFrame(int32_t aIndex)
+{
+ RefPtr<dom::HTMLOptionElement> option =
+ GetOption(static_cast<uint32_t>(aIndex));
+ NS_ENSURE_TRUE(option, false);
+
+ RefPtr<dom::HTMLSelectElement> selectElement =
+ dom::HTMLSelectElement::FromContent(mContent);
+
+ uint32_t mask = dom::HTMLSelectElement::NOTIFY;
+ if (!option->Selected()) {
+ mask |= dom::HTMLSelectElement::IS_SELECTED;
+ }
+
+ return selectElement->SetOptionsSelectedByIndex(aIndex, aIndex, mask);
+}
+
+
+// Dispatch event and such
+bool
+nsListControlFrame::UpdateSelection()
+{
+ if (mIsAllFramesHere) {
+ // if it's a combobox, display the new text
+ nsWeakFrame weakFrame(this);
+ if (mComboboxFrame) {
+ mComboboxFrame->RedisplaySelectedText();
+
+ // When dropdown list is open, onchange event will be fired when Enter key
+ // is hit or when dropdown list is dismissed.
+ if (mComboboxFrame->IsDroppedDown()) {
+ return weakFrame.IsAlive();
+ }
+ }
+ if (mIsAllContentHere) {
+ FireOnInputAndOnChange();
+ }
+ return weakFrame.IsAlive();
+ }
+ return true;
+}
+
+void
+nsListControlFrame::ComboboxFinish(int32_t aIndex)
+{
+ gLastKeyTime = 0;
+
+ if (mComboboxFrame) {
+ int32_t displayIndex = mComboboxFrame->GetIndexOfDisplayArea();
+ // Make sure we can always reset to the displayed index
+ mForceSelection = displayIndex == aIndex;
+
+ nsWeakFrame weakFrame(this);
+ PerformSelection(aIndex, false, false); // might destroy us
+ if (!weakFrame.IsAlive() || !mComboboxFrame) {
+ return;
+ }
+
+ if (displayIndex != aIndex) {
+ mComboboxFrame->RedisplaySelectedText(); // might destroy us
+ }
+
+ if (weakFrame.IsAlive() && mComboboxFrame) {
+ mComboboxFrame->RollupFromList(); // might destroy us
+ }
+ }
+}
+
+// Send out an onInput and onChange notification.
+void
+nsListControlFrame::FireOnInputAndOnChange()
+{
+ if (mComboboxFrame) {
+ // Return hit without changing anything
+ int32_t index = mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX);
+ if (index == NS_SKIP_NOTIFY_INDEX) {
+ return;
+ }
+
+ // See if the selection actually changed
+ if (index == GetSelectedIndex()) {
+ return;
+ }
+ }
+
+ nsCOMPtr<nsIContent> content = mContent;
+ // Dispatch the input event.
+ nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
+ NS_LITERAL_STRING("input"), true,
+ false);
+ // Dispatch the change event.
+ nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
+ NS_LITERAL_STRING("change"), true,
+ false);
+}
+
+NS_IMETHODIMP
+nsListControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex)
+{
+ if (mComboboxFrame) {
+ // UpdateRecentIndex with NS_SKIP_NOTIFY_INDEX, so that we won't fire an onchange
+ // event for this setting of selectedIndex.
+ mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX);
+ }
+
+ nsWeakFrame weakFrame(this);
+ ScrollToIndex(aNewIndex);
+ if (!weakFrame.IsAlive()) {
+ return NS_OK;
+ }
+ mStartSelectionIndex = aNewIndex;
+ mEndSelectionIndex = aNewIndex;
+ InvalidateFocus();
+
+#ifdef ACCESSIBILITY
+ FireMenuItemActiveEvent();
+#endif
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+// End nsISelectControlFrame
+//----------------------------------------------------------------------
+
+nsresult
+nsListControlFrame::SetFormProperty(nsIAtom* aName,
+ const nsAString& aValue)
+{
+ if (nsGkAtoms::selected == aName) {
+ return NS_ERROR_INVALID_ARG; // Selected is readonly according to spec.
+ } else if (nsGkAtoms::selectedindex == aName) {
+ // You shouldn't be calling me for this!!!
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // We should be told about selectedIndex by the DOM element through
+ // OnOptionSelected
+
+ return NS_OK;
+}
+
+void
+nsListControlFrame::AboutToDropDown()
+{
+ NS_ASSERTION(IsInDropDownMode(),
+ "AboutToDropDown called without being in dropdown mode");
+
+ // Our widget doesn't get invalidated on changes to the rest of the document,
+ // so compute and store this color at the start of a dropdown so we don't
+ // get weird painting behaviour.
+ // We start looking for backgrounds above the combobox frame to avoid
+ // duplicating the combobox frame's background and compose each background
+ // color we find underneath until we have an opaque color, or run out of
+ // backgrounds. We compose with the PresContext default background color,
+ // which is always opaque, in case we don't end up with an opaque color.
+ // This gives us a very poor approximation of translucency.
+ nsIFrame* comboboxFrame = do_QueryFrame(mComboboxFrame);
+ nsStyleContext* context = comboboxFrame->StyleContext()->GetParent();
+ mLastDropdownBackstopColor = NS_RGBA(0,0,0,0);
+ while (NS_GET_A(mLastDropdownBackstopColor) < 255 && context) {
+ mLastDropdownBackstopColor =
+ NS_ComposeColors(context->StyleBackground()->mBackgroundColor,
+ mLastDropdownBackstopColor);
+ context = context->GetParent();
+ }
+ mLastDropdownBackstopColor =
+ NS_ComposeColors(PresContext()->DefaultBackgroundColor(),
+ mLastDropdownBackstopColor);
+
+ if (mIsAllContentHere && mIsAllFramesHere && mHasBeenInitialized) {
+ nsWeakFrame weakFrame(this);
+ ScrollToIndex(GetSelectedIndex());
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+#ifdef ACCESSIBILITY
+ FireMenuItemActiveEvent(); // Inform assistive tech what got focus
+#endif
+ }
+ mItemSelectionStarted = false;
+ mForceSelection = false;
+}
+
+// We are about to be rolledup from the outside (ComboboxFrame)
+void
+nsListControlFrame::AboutToRollup()
+{
+ // We've been updating the combobox with the keyboard up until now, but not
+ // with the mouse. The problem is, even with mouse selection, we are
+ // updating the <select>. So if the mouse goes over an option just before
+ // he leaves the box and clicks, that's what the <select> will show.
+ //
+ // To deal with this we say "whatever is in the combobox is canonical."
+ // - IF the combobox is different from the current selected index, we
+ // reset the index.
+
+ if (IsInDropDownMode()) {
+ ComboboxFinish(mComboboxFrame->GetIndexOfDisplayArea()); // might destroy us
+ }
+}
+
+void
+nsListControlFrame::DidReflow(nsPresContext* aPresContext,
+ const ReflowInput* aReflowInput,
+ nsDidReflowStatus aStatus)
+{
+ bool wasInterrupted = !mHasPendingInterruptAtStartOfReflow &&
+ aPresContext->HasPendingInterrupt();
+
+ nsHTMLScrollFrame::DidReflow(aPresContext, aReflowInput, aStatus);
+
+ if (mNeedToReset && !wasInterrupted) {
+ mNeedToReset = false;
+ // Suppress scrolling to the selected element if we restored
+ // scroll history state AND the list contents have not changed
+ // since we loaded all the children AND nothing else forced us
+ // to scroll by calling ResetList(true). The latter two conditions
+ // are folded into mPostChildrenLoadedReset.
+ //
+ // The idea is that we want scroll history restoration to trump ResetList
+ // scrolling to the selected element, when the ResetList was probably only
+ // caused by content loading normally.
+ ResetList(!DidHistoryRestore() || mPostChildrenLoadedReset);
+ }
+
+ mHasPendingInterruptAtStartOfReflow = false;
+}
+
+nsIAtom*
+nsListControlFrame::GetType() const
+{
+ return nsGkAtoms::listControlFrame;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsListControlFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("ListControl"), aResult);
+}
+#endif
+
+nscoord
+nsListControlFrame::GetBSizeOfARow()
+{
+ return BSizeOfARow();
+}
+
+nsresult
+nsListControlFrame::IsOptionDisabled(int32_t anIndex, bool &aIsDisabled)
+{
+ RefPtr<dom::HTMLSelectElement> sel =
+ dom::HTMLSelectElement::FromContent(mContent);
+ if (sel) {
+ sel->IsOptionDisabled(anIndex, &aIsDisabled);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+//----------------------------------------------------------------------
+// helper
+//----------------------------------------------------------------------
+bool
+nsListControlFrame::IsLeftButton(nsIDOMEvent* aMouseEvent)
+{
+ // only allow selection with the left button
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
+ if (mouseEvent) {
+ int16_t whichButton;
+ if (NS_SUCCEEDED(mouseEvent->GetButton(&whichButton))) {
+ return whichButton != 0?false:true;
+ }
+ }
+ return false;
+}
+
+nscoord
+nsListControlFrame::CalcFallbackRowBSize(float aFontSizeInflation)
+{
+ RefPtr<nsFontMetrics> fontMet =
+ nsLayoutUtils::GetFontMetricsForFrame(this, aFontSizeInflation);
+ return fontMet->MaxHeight();
+}
+
+nscoord
+nsListControlFrame::CalcIntrinsicBSize(nscoord aBSizeOfARow,
+ int32_t aNumberOfOptions)
+{
+ NS_PRECONDITION(!IsInDropDownMode(),
+ "Shouldn't be in dropdown mode when we call this");
+
+ dom::HTMLSelectElement* select =
+ dom::HTMLSelectElement::FromContentOrNull(mContent);
+ if (select) {
+ mNumDisplayRows = select->Size();
+ } else {
+ mNumDisplayRows = 1;
+ }
+
+ if (mNumDisplayRows < 1) {
+ mNumDisplayRows = 4;
+ }
+
+ return mNumDisplayRows * aBSizeOfARow;
+}
+
+//----------------------------------------------------------------------
+// nsIDOMMouseListener
+//----------------------------------------------------------------------
+nsresult
+nsListControlFrame::MouseUp(nsIDOMEvent* aMouseEvent)
+{
+ NS_ASSERTION(aMouseEvent != nullptr, "aMouseEvent is null.");
+
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
+ NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
+
+ UpdateInListState(aMouseEvent);
+
+ mButtonDown = false;
+
+ EventStates eventStates = mContent->AsElement()->State();
+ if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
+ return NS_OK;
+ }
+
+ // only allow selection with the left button
+ // if a right button click is on the combobox itself
+ // or on the select when in listbox mode, then let the click through
+ if (!IsLeftButton(aMouseEvent)) {
+ if (IsInDropDownMode()) {
+ if (!IgnoreMouseEventForSelection(aMouseEvent)) {
+ aMouseEvent->PreventDefault();
+ aMouseEvent->StopPropagation();
+ } else {
+ CaptureMouseEvents(false);
+ return NS_OK;
+ }
+ CaptureMouseEvents(false);
+ return NS_ERROR_FAILURE; // means consume event
+ } else {
+ CaptureMouseEvents(false);
+ return NS_OK;
+ }
+ }
+
+ const nsStyleVisibility* vis = StyleVisibility();
+
+ if (!vis->IsVisible()) {
+ return NS_OK;
+ }
+
+ if (IsInDropDownMode()) {
+ // XXX This is a bit of a hack, but.....
+ // But the idea here is to make sure you get an "onclick" event when you mouse
+ // down on the select and the drag over an option and let go
+ // And then NOT get an "onclick" event when when you click down on the select
+ // and then up outside of the select
+ // the EventStateManager tracks the content of the mouse down and the mouse up
+ // to make sure they are the same, and the onclick is sent in the PostHandleEvent
+ // depeneding on whether the clickCount is non-zero.
+ // So we cheat here by either setting or unsetting the clcikCount in the native event
+ // so the right thing happens for the onclick event
+ WidgetMouseEvent* mouseEvent =
+ aMouseEvent->WidgetEventPtr()->AsMouseEvent();
+
+ int32_t selectedIndex;
+ if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
+ // If it's disabled, disallow the click and leave.
+ bool isDisabled = false;
+ IsOptionDisabled(selectedIndex, isDisabled);
+ if (isDisabled) {
+ aMouseEvent->PreventDefault();
+ aMouseEvent->StopPropagation();
+ CaptureMouseEvents(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (kNothingSelected != selectedIndex) {
+ nsWeakFrame weakFrame(this);
+ ComboboxFinish(selectedIndex);
+ if (!weakFrame.IsAlive()) {
+ return NS_OK;
+ }
+
+ FireOnInputAndOnChange();
+ }
+
+ mouseEvent->mClickCount = 1;
+ } else {
+ // the click was out side of the select or its dropdown
+ mouseEvent->mClickCount =
+ IgnoreMouseEventForSelection(aMouseEvent) ? 1 : 0;
+ }
+ } else {
+ CaptureMouseEvents(false);
+ // Notify
+ if (mChangesSinceDragStart) {
+ // reset this so that future MouseUps without a prior MouseDown
+ // won't fire onchange
+ mChangesSinceDragStart = false;
+ FireOnInputAndOnChange();
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsListControlFrame::UpdateInListState(nsIDOMEvent* aEvent)
+{
+ if (!mComboboxFrame || !mComboboxFrame->IsDroppedDown())
+ return;
+
+ nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aEvent, this);
+ nsRect borderInnerEdge = GetScrollPortRect();
+ if (pt.y >= borderInnerEdge.y && pt.y < borderInnerEdge.YMost()) {
+ mItemSelectionStarted = true;
+ }
+}
+
+bool nsListControlFrame::IgnoreMouseEventForSelection(nsIDOMEvent* aEvent)
+{
+ if (!mComboboxFrame)
+ return false;
+
+ // Our DOM listener does get called when the dropdown is not
+ // showing, because it listens to events on the SELECT element
+ if (!mComboboxFrame->IsDroppedDown())
+ return true;
+
+ return !mItemSelectionStarted;
+}
+
+#ifdef ACCESSIBILITY
+void
+nsListControlFrame::FireMenuItemActiveEvent()
+{
+ if (mFocused != this && !IsInDropDownMode()) {
+ return;
+ }
+
+ nsCOMPtr<nsIContent> optionContent = GetCurrentOption();
+ if (!optionContent) {
+ return;
+ }
+
+ FireDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), optionContent);
+}
+#endif
+
+nsresult
+nsListControlFrame::GetIndexFromDOMEvent(nsIDOMEvent* aMouseEvent,
+ int32_t& aCurIndex)
+{
+ if (IgnoreMouseEventForSelection(aMouseEvent))
+ return NS_ERROR_FAILURE;
+
+ if (nsIPresShell::GetCapturingContent() != mContent) {
+ // If we're not capturing, then ignore movement in the border
+ nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent, this);
+ nsRect borderInnerEdge = GetScrollPortRect();
+ if (!borderInnerEdge.Contains(pt)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ RefPtr<dom::HTMLOptionElement> option;
+ for (nsCOMPtr<nsIContent> content =
+ PresContext()->EventStateManager()->GetEventTargetContent(nullptr);
+ content && !option;
+ content = content->GetParent()) {
+ option = dom::HTMLOptionElement::FromContent(content);
+ }
+
+ if (option) {
+ aCurIndex = option->Index();
+ MOZ_ASSERT(aCurIndex >= 0);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+static bool
+FireShowDropDownEvent(nsIContent* aContent, bool aShow, bool aIsSourceTouchEvent)
+{
+ if (XRE_IsContentProcess() &&
+ Preferences::GetBool("browser.tabs.remote.desktopbehavior", false)) {
+ nsString eventName;
+ if (aShow) {
+ eventName = aIsSourceTouchEvent ? NS_LITERAL_STRING("mozshowdropdown-sourcetouch") :
+ NS_LITERAL_STRING("mozshowdropdown");
+ } else {
+ eventName = NS_LITERAL_STRING("mozhidedropdown");
+ }
+ nsContentUtils::DispatchChromeEvent(aContent->OwnerDoc(), aContent,
+ eventName, true, false);
+ return true;
+ }
+
+ return false;
+}
+
+nsresult
+nsListControlFrame::MouseDown(nsIDOMEvent* aMouseEvent)
+{
+ NS_ASSERTION(aMouseEvent != nullptr, "aMouseEvent is null.");
+
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
+ NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
+
+ UpdateInListState(aMouseEvent);
+
+ EventStates eventStates = mContent->AsElement()->State();
+ if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
+ return NS_OK;
+ }
+
+ // only allow selection with the left button
+ // if a right button click is on the combobox itself
+ // or on the select when in listbox mode, then let the click through
+ if (!IsLeftButton(aMouseEvent)) {
+ if (IsInDropDownMode()) {
+ if (!IgnoreMouseEventForSelection(aMouseEvent)) {
+ aMouseEvent->PreventDefault();
+ aMouseEvent->StopPropagation();
+ } else {
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE; // means consume event
+ } else {
+ return NS_OK;
+ }
+ }
+
+ int32_t selectedIndex;
+ if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
+ // Handle Like List
+ mButtonDown = true;
+ CaptureMouseEvents(true);
+ nsWeakFrame weakFrame(this);
+ bool change =
+ HandleListSelection(aMouseEvent, selectedIndex); // might destroy us
+ if (!weakFrame.IsAlive()) {
+ return NS_OK;
+ }
+ mChangesSinceDragStart = change;
+ } else {
+ // NOTE: the combo box is responsible for dropping it down
+ if (mComboboxFrame) {
+ // Ignore the click that occurs on the option element when one is
+ // selected from the parent process popup.
+ if (mComboboxFrame->IsOpenInParentProcess()) {
+ nsCOMPtr<nsIDOMEventTarget> etarget;
+ aMouseEvent->GetTarget(getter_AddRefs(etarget));
+ nsCOMPtr<nsIDOMHTMLOptionElement> option = do_QueryInterface(etarget);
+ if (option) {
+ return NS_OK;
+ }
+ }
+
+ uint16_t inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
+ if (NS_FAILED(mouseEvent->GetMozInputSource(&inputSource))) {
+ return NS_ERROR_FAILURE;
+ }
+ bool isSourceTouchEvent = inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
+ if (FireShowDropDownEvent(mContent, !mComboboxFrame->IsDroppedDownOrHasParentPopup(),
+ isSourceTouchEvent)) {
+ return NS_OK;
+ }
+
+ if (!IgnoreMouseEventForSelection(aMouseEvent)) {
+ return NS_OK;
+ }
+
+ if (!nsComboboxControlFrame::ToolkitHasNativePopup())
+ {
+ bool isDroppedDown = mComboboxFrame->IsDroppedDown();
+ nsIFrame* comboFrame = do_QueryFrame(mComboboxFrame);
+ nsWeakFrame weakFrame(comboFrame);
+ mComboboxFrame->ShowDropDown(!isDroppedDown);
+ if (!weakFrame.IsAlive())
+ return NS_OK;
+ if (isDroppedDown) {
+ CaptureMouseEvents(false);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+// nsIDOMMouseMotionListener
+//----------------------------------------------------------------------
+nsresult
+nsListControlFrame::MouseMove(nsIDOMEvent* aMouseEvent)
+{
+ NS_ASSERTION(aMouseEvent, "aMouseEvent is null.");
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
+ NS_ENSURE_TRUE(mouseEvent, NS_ERROR_FAILURE);
+
+ UpdateInListState(aMouseEvent);
+
+ if (IsInDropDownMode()) {
+ if (mComboboxFrame->IsDroppedDown()) {
+ int32_t selectedIndex;
+ if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
+ PerformSelection(selectedIndex, false, false); // might destroy us
+ }
+ }
+ } else {// XXX - temporary until we get drag events
+ if (mButtonDown) {
+ return DragMove(aMouseEvent); // might destroy us
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsListControlFrame::DragMove(nsIDOMEvent* aMouseEvent)
+{
+ NS_ASSERTION(aMouseEvent, "aMouseEvent is null.");
+
+ UpdateInListState(aMouseEvent);
+
+ if (!IsInDropDownMode()) {
+ int32_t selectedIndex;
+ if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
+ // Don't waste cycles if we already dragged over this item
+ if (selectedIndex == mEndSelectionIndex) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
+ NS_ASSERTION(mouseEvent, "aMouseEvent is not an nsIDOMMouseEvent!");
+ bool isControl;
+#ifdef XP_MACOSX
+ mouseEvent->GetMetaKey(&isControl);
+#else
+ mouseEvent->GetCtrlKey(&isControl);
+#endif
+ nsWeakFrame weakFrame(this);
+ // Turn SHIFT on when you are dragging, unless control is on.
+ bool wasChanged = PerformSelection(selectedIndex,
+ !isControl, isControl);
+ if (!weakFrame.IsAlive()) {
+ return NS_OK;
+ }
+ mChangesSinceDragStart = mChangesSinceDragStart || wasChanged;
+ }
+ }
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+// Scroll helpers.
+//----------------------------------------------------------------------
+void
+nsListControlFrame::ScrollToIndex(int32_t aIndex)
+{
+ if (aIndex < 0) {
+ // XXX shouldn't we just do nothing if we're asked to scroll to
+ // kNothingSelected?
+ ScrollTo(nsPoint(0, 0), nsIScrollableFrame::INSTANT);
+ } else {
+ RefPtr<dom::HTMLOptionElement> option =
+ GetOption(AssertedCast<uint32_t>(aIndex));
+ if (option) {
+ ScrollToFrame(*option);
+ }
+ }
+}
+
+void
+nsListControlFrame::ScrollToFrame(dom::HTMLOptionElement& aOptElement)
+{
+ // otherwise we find the content's frame and scroll to it
+ nsIFrame* childFrame = aOptElement.GetPrimaryFrame();
+ if (childFrame) {
+ PresContext()->PresShell()->
+ ScrollFrameRectIntoView(childFrame,
+ nsRect(nsPoint(0, 0), childFrame->GetSize()),
+ nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(),
+ nsIPresShell::SCROLL_OVERFLOW_HIDDEN |
+ nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY);
+ }
+}
+
+//---------------------------------------------------------------------
+// Ok, the entire idea of this routine is to move to the next item that
+// is suppose to be selected. If the item is disabled then we search in
+// the same direction looking for the next item to select. If we run off
+// the end of the list then we start at the end of the list and search
+// backwards until we get back to the original item or an enabled option
+//
+// aStartIndex - the index to start searching from
+// aNewIndex - will get set to the new index if it finds one
+// aNumOptions - the total number of options in the list
+// aDoAdjustInc - the initial increment 1-n
+// aDoAdjustIncNext - the increment used to search for the next enabled option
+//
+// the aDoAdjustInc could be a "1" for a single item or
+// any number greater representing a page of items
+//
+void
+nsListControlFrame::AdjustIndexForDisabledOpt(int32_t aStartIndex,
+ int32_t &aNewIndex,
+ int32_t aNumOptions,
+ int32_t aDoAdjustInc,
+ int32_t aDoAdjustIncNext)
+{
+ // Cannot select anything if there is nothing to select
+ if (aNumOptions == 0) {
+ aNewIndex = kNothingSelected;
+ return;
+ }
+
+ // means we reached the end of the list and now we are searching backwards
+ bool doingReverse = false;
+ // lowest index in the search range
+ int32_t bottom = 0;
+ // highest index in the search range
+ int32_t top = aNumOptions;
+
+ // Start off keyboard options at selectedIndex if nothing else is defaulted to
+ //
+ // XXX Perhaps this should happen for mouse too, to start off shift click
+ // automatically in multiple ... to do this, we'd need to override
+ // OnOptionSelected and set mStartSelectedIndex if nothing is selected. Not
+ // sure of the effects, though, so I'm not doing it just yet.
+ int32_t startIndex = aStartIndex;
+ if (startIndex < bottom) {
+ startIndex = GetSelectedIndex();
+ }
+ int32_t newIndex = startIndex + aDoAdjustInc;
+
+ // make sure we start off in the range
+ if (newIndex < bottom) {
+ newIndex = 0;
+ } else if (newIndex >= top) {
+ newIndex = aNumOptions-1;
+ }
+
+ while (1) {
+ // if the newIndex isn't disabled, we are golden, bail out
+ bool isDisabled = true;
+ if (NS_SUCCEEDED(IsOptionDisabled(newIndex, isDisabled)) && !isDisabled) {
+ break;
+ }
+
+ // it WAS disabled, so sart looking ahead for the next enabled option
+ newIndex += aDoAdjustIncNext;
+
+ // well, if we reach end reverse the search
+ if (newIndex < bottom) {
+ if (doingReverse) {
+ return; // if we are in reverse mode and reach the end bail out
+ } else {
+ // reset the newIndex to the end of the list we hit
+ // reverse the incrementer
+ // set the other end of the list to our original starting index
+ newIndex = bottom;
+ aDoAdjustIncNext = 1;
+ doingReverse = true;
+ top = startIndex;
+ }
+ } else if (newIndex >= top) {
+ if (doingReverse) {
+ return; // if we are in reverse mode and reach the end bail out
+ } else {
+ // reset the newIndex to the end of the list we hit
+ // reverse the incrementer
+ // set the other end of the list to our original starting index
+ newIndex = top - 1;
+ aDoAdjustIncNext = -1;
+ doingReverse = true;
+ bottom = startIndex;
+ }
+ }
+ }
+
+ // Looks like we found one
+ aNewIndex = newIndex;
+}
+
+nsAString&
+nsListControlFrame::GetIncrementalString()
+{
+ if (sIncrementalString == nullptr)
+ sIncrementalString = new nsString();
+
+ return *sIncrementalString;
+}
+
+void
+nsListControlFrame::Shutdown()
+{
+ delete sIncrementalString;
+ sIncrementalString = nullptr;
+}
+
+void
+nsListControlFrame::DropDownToggleKey(nsIDOMEvent* aKeyEvent)
+{
+ // Cocoa widgets do native popups, so don't try to show
+ // dropdowns there.
+ if (IsInDropDownMode() && !nsComboboxControlFrame::ToolkitHasNativePopup()) {
+ aKeyEvent->PreventDefault();
+ if (!mComboboxFrame->IsDroppedDown()) {
+ if (!FireShowDropDownEvent(mContent, true, false)) {
+ mComboboxFrame->ShowDropDown(true);
+ }
+ } else {
+ nsWeakFrame weakFrame(this);
+ // mEndSelectionIndex is the last item that got selected.
+ ComboboxFinish(mEndSelectionIndex);
+ if (weakFrame.IsAlive()) {
+ FireOnInputAndOnChange();
+ }
+ }
+ }
+}
+
+nsresult
+nsListControlFrame::KeyDown(nsIDOMEvent* aKeyEvent)
+{
+ MOZ_ASSERT(aKeyEvent, "aKeyEvent is null.");
+
+ EventStates eventStates = mContent->AsElement()->State();
+ if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
+ return NS_OK;
+ }
+
+ AutoIncrementalSearchResetter incrementalSearchResetter;
+
+ // Don't check defaultPrevented value because other browsers don't prevent
+ // the key navigation of list control even if preventDefault() is called.
+ // XXXmats 2015-04-16: the above is not true anymore, Chrome prevents all
+ // XXXmats keyboard events, even tabbing, when preventDefault() is called
+ // XXXmats in onkeydown. That seems sub-optimal though.
+
+ const WidgetKeyboardEvent* keyEvent =
+ aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
+ MOZ_ASSERT(keyEvent,
+ "DOM event must have WidgetKeyboardEvent for its internal event");
+
+ bool dropDownMenuOnUpDown;
+ bool dropDownMenuOnSpace;
+#ifdef XP_MACOSX
+ dropDownMenuOnUpDown = IsInDropDownMode() && !mComboboxFrame->IsDroppedDown();
+ dropDownMenuOnSpace = !keyEvent->IsAlt() && !keyEvent->IsControl() &&
+ !keyEvent->IsMeta();
+#else
+ dropDownMenuOnUpDown = keyEvent->IsAlt();
+ dropDownMenuOnSpace = IsInDropDownMode() && !mComboboxFrame->IsDroppedDown();
+#endif
+ bool withinIncrementalSearchTime =
+ keyEvent->mTime - gLastKeyTime <= INCREMENTAL_SEARCH_KEYPRESS_TIME;
+ if ((dropDownMenuOnUpDown &&
+ (keyEvent->mKeyCode == NS_VK_UP || keyEvent->mKeyCode == NS_VK_DOWN)) ||
+ (dropDownMenuOnSpace && keyEvent->mKeyCode == NS_VK_SPACE &&
+ !withinIncrementalSearchTime)) {
+ DropDownToggleKey(aKeyEvent);
+ if (keyEvent->DefaultPrevented()) {
+ return NS_OK;
+ }
+ }
+ if (keyEvent->IsAlt()) {
+ return NS_OK;
+ }
+
+ // now make sure there are options or we are wasting our time
+ RefPtr<dom::HTMLOptionsCollection> options = GetOptions();
+ NS_ENSURE_TRUE(options, NS_ERROR_FAILURE);
+
+ uint32_t numOptions = options->Length();
+
+ // this is the new index to set
+ int32_t newIndex = kNothingSelected;
+
+ bool isControlOrMeta = (keyEvent->IsControl() || keyEvent->IsMeta());
+ // Don't try to handle multiple-select pgUp/pgDown in single-select lists.
+ if (isControlOrMeta && !GetMultiple() &&
+ (keyEvent->mKeyCode == NS_VK_PAGE_UP ||
+ keyEvent->mKeyCode == NS_VK_PAGE_DOWN)) {
+ return NS_OK;
+ }
+ if (isControlOrMeta && (keyEvent->mKeyCode == NS_VK_UP ||
+ keyEvent->mKeyCode == NS_VK_LEFT ||
+ keyEvent->mKeyCode == NS_VK_DOWN ||
+ keyEvent->mKeyCode == NS_VK_RIGHT ||
+ keyEvent->mKeyCode == NS_VK_HOME ||
+ keyEvent->mKeyCode == NS_VK_END)) {
+ // Don't go into multiple-select mode unless this list can handle it.
+ isControlOrMeta = mControlSelectMode = GetMultiple();
+ } else if (keyEvent->mKeyCode != NS_VK_SPACE) {
+ mControlSelectMode = false;
+ }
+
+ switch (keyEvent->mKeyCode) {
+ case NS_VK_UP:
+ case NS_VK_LEFT:
+ AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
+ static_cast<int32_t>(numOptions),
+ -1, -1);
+ break;
+ case NS_VK_DOWN:
+ case NS_VK_RIGHT:
+ AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
+ static_cast<int32_t>(numOptions),
+ 1, 1);
+ break;
+ case NS_VK_RETURN:
+ if (IsInDropDownMode()) {
+ if (mComboboxFrame->IsDroppedDown()) {
+ // If the select element is a dropdown style, Enter key should be
+ // consumed while the dropdown is open for security.
+ aKeyEvent->PreventDefault();
+
+ nsWeakFrame weakFrame(this);
+ ComboboxFinish(mEndSelectionIndex);
+ if (!weakFrame.IsAlive()) {
+ return NS_OK;
+ }
+ }
+ FireOnInputAndOnChange();
+ return NS_OK;
+ }
+
+ // If this is single select listbox, Enter key doesn't cause anything.
+ if (!GetMultiple()) {
+ return NS_OK;
+ }
+
+ newIndex = mEndSelectionIndex;
+ break;
+ case NS_VK_ESCAPE: {
+ // If the select element is a listbox style, Escape key causes nothing.
+ if (!IsInDropDownMode()) {
+ return NS_OK;
+ }
+
+ AboutToRollup();
+ // If the select element is a dropdown style, Enter key should be
+ // consumed everytime since Escape key may be pressed accidentally after
+ // the dropdown is closed by Escepe key.
+ aKeyEvent->PreventDefault();
+ return NS_OK;
+ }
+ case NS_VK_PAGE_UP: {
+ int32_t itemsPerPage =
+ std::max(1, static_cast<int32_t>(mNumDisplayRows - 1));
+ AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
+ static_cast<int32_t>(numOptions),
+ -itemsPerPage, -1);
+ break;
+ }
+ case NS_VK_PAGE_DOWN: {
+ int32_t itemsPerPage =
+ std::max(1, static_cast<int32_t>(mNumDisplayRows - 1));
+ AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
+ static_cast<int32_t>(numOptions),
+ itemsPerPage, 1);
+ break;
+ }
+ case NS_VK_HOME:
+ AdjustIndexForDisabledOpt(0, newIndex,
+ static_cast<int32_t>(numOptions),
+ 0, 1);
+ break;
+ case NS_VK_END:
+ AdjustIndexForDisabledOpt(static_cast<int32_t>(numOptions) - 1, newIndex,
+ static_cast<int32_t>(numOptions),
+ 0, -1);
+ break;
+
+#if defined(XP_WIN)
+ case NS_VK_F4:
+ if (!isControlOrMeta) {
+ DropDownToggleKey(aKeyEvent);
+ }
+ return NS_OK;
+#endif
+
+ default: // printable key will be handled by keypress event.
+ incrementalSearchResetter.Cancel();
+ return NS_OK;
+ }
+
+ aKeyEvent->PreventDefault();
+
+ // Actually process the new index and let the selection code
+ // do the scrolling for us
+ PostHandleKeyEvent(newIndex, 0, keyEvent->IsShift(), isControlOrMeta);
+ return NS_OK;
+}
+
+nsresult
+nsListControlFrame::KeyPress(nsIDOMEvent* aKeyEvent)
+{
+ MOZ_ASSERT(aKeyEvent, "aKeyEvent is null.");
+
+ EventStates eventStates = mContent->AsElement()->State();
+ if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
+ return NS_OK;
+ }
+
+ AutoIncrementalSearchResetter incrementalSearchResetter;
+
+ const WidgetKeyboardEvent* keyEvent =
+ aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
+ MOZ_ASSERT(keyEvent,
+ "DOM event must have WidgetKeyboardEvent for its internal event");
+
+ // Select option with this as the first character
+ // XXX Not I18N compliant
+
+ // Don't do incremental search if the key event has already consumed.
+ if (keyEvent->DefaultPrevented()) {
+ return NS_OK;
+ }
+
+ if (keyEvent->IsAlt()) {
+ return NS_OK;
+ }
+
+ // With some keyboard layout, space key causes non-ASCII space.
+ // So, the check in keydown event handler isn't enough, we need to check it
+ // again with keypress event.
+ if (keyEvent->mCharCode != ' ') {
+ mControlSelectMode = false;
+ }
+
+ bool isControlOrMeta = (keyEvent->IsControl() || keyEvent->IsMeta());
+ if (isControlOrMeta && keyEvent->mCharCode != ' ') {
+ return NS_OK;
+ }
+
+ // NOTE: If mKeyCode of keypress event is not 0, mCharCode is always 0.
+ // Therefore, all non-printable keys are not handled after this block.
+ if (!keyEvent->mCharCode) {
+ // Backspace key will delete the last char in the string. Otherwise,
+ // non-printable keypress should reset incremental search.
+ if (keyEvent->mKeyCode == NS_VK_BACK) {
+ incrementalSearchResetter.Cancel();
+ if (!GetIncrementalString().IsEmpty()) {
+ GetIncrementalString().Truncate(GetIncrementalString().Length() - 1);
+ }
+ aKeyEvent->PreventDefault();
+ } else {
+ // XXX When a select element has focus, even if the key causes nothing,
+ // it might be better to call preventDefault() here because nobody
+ // should expect one of other elements including chrome handles the
+ // key event.
+ }
+ return NS_OK;
+ }
+
+ incrementalSearchResetter.Cancel();
+
+ // We ate the key if we got this far.
+ aKeyEvent->PreventDefault();
+
+ // XXX Why don't we check/modify timestamp first?
+
+ // Incremental Search: if time elapsed is below
+ // INCREMENTAL_SEARCH_KEYPRESS_TIME, append this keystroke to the search
+ // string we will use to find options and start searching at the current
+ // keystroke. Otherwise, Truncate the string if it's been a long time
+ // since our last keypress.
+ if (keyEvent->mTime - gLastKeyTime > INCREMENTAL_SEARCH_KEYPRESS_TIME) {
+ // If this is ' ' and we are at the beginning of the string, treat it as
+ // "select this option" (bug 191543)
+ if (keyEvent->mCharCode == ' ') {
+ // Actually process the new index and let the selection code
+ // do the scrolling for us
+ PostHandleKeyEvent(mEndSelectionIndex, keyEvent->mCharCode,
+ keyEvent->IsShift(), isControlOrMeta);
+
+ return NS_OK;
+ }
+
+ GetIncrementalString().Truncate();
+ }
+
+ gLastKeyTime = keyEvent->mTime;
+
+ // Append this keystroke to the search string.
+ char16_t uniChar = ToLowerCase(static_cast<char16_t>(keyEvent->mCharCode));
+ GetIncrementalString().Append(uniChar);
+
+ // See bug 188199, if all letters in incremental string are same, just try to
+ // match the first one
+ nsAutoString incrementalString(GetIncrementalString());
+ uint32_t charIndex = 1, stringLength = incrementalString.Length();
+ while (charIndex < stringLength &&
+ incrementalString[charIndex] == incrementalString[charIndex - 1]) {
+ charIndex++;
+ }
+ if (charIndex == stringLength) {
+ incrementalString.Truncate(1);
+ stringLength = 1;
+ }
+
+ // Determine where we're going to start reading the string
+ // If we have multiple characters to look for, we start looking *at* the
+ // current option. If we have only one character to look for, we start
+ // looking *after* the current option.
+ // Exception: if there is no option selected to start at, we always start
+ // *at* 0.
+ int32_t startIndex = GetSelectedIndex();
+ if (startIndex == kNothingSelected) {
+ startIndex = 0;
+ } else if (stringLength == 1) {
+ startIndex++;
+ }
+
+ // now make sure there are options or we are wasting our time
+ RefPtr<dom::HTMLOptionsCollection> options = GetOptions();
+ NS_ENSURE_TRUE(options, NS_ERROR_FAILURE);
+
+ uint32_t numOptions = options->Length();
+
+ nsWeakFrame weakFrame(this);
+ for (uint32_t i = 0; i < numOptions; ++i) {
+ uint32_t index = (i + startIndex) % numOptions;
+ RefPtr<dom::HTMLOptionElement> optionElement =
+ options->ItemAsOption(index);
+ if (!optionElement || !optionElement->GetPrimaryFrame()) {
+ continue;
+ }
+
+ nsAutoString text;
+ if (NS_FAILED(optionElement->GetText(text)) ||
+ !StringBeginsWith(
+ nsContentUtils::TrimWhitespace<
+ nsContentUtils::IsHTMLWhitespaceOrNBSP>(text, false),
+ incrementalString, nsCaseInsensitiveStringComparator())) {
+ continue;
+ }
+
+ bool wasChanged = PerformSelection(index, keyEvent->IsShift(), isControlOrMeta);
+ if (!weakFrame.IsAlive()) {
+ return NS_OK;
+ }
+ if (!wasChanged) {
+ break;
+ }
+
+ // If UpdateSelection() returns false, that means the frame is no longer
+ // alive. We should stop doing anything.
+ if (!UpdateSelection()) {
+ return NS_OK;
+ }
+ break;
+ }
+
+ return NS_OK;
+}
+
+void
+nsListControlFrame::PostHandleKeyEvent(int32_t aNewIndex,
+ uint32_t aCharCode,
+ bool aIsShift,
+ bool aIsControlOrMeta)
+{
+ if (aNewIndex == kNothingSelected) {
+ return;
+ }
+
+ // If you hold control, but not shift, no key will actually do anything
+ // except space.
+ nsWeakFrame weakFrame(this);
+ bool wasChanged = false;
+ if (aIsControlOrMeta && !aIsShift && aCharCode != ' ') {
+ mStartSelectionIndex = aNewIndex;
+ mEndSelectionIndex = aNewIndex;
+ InvalidateFocus();
+ ScrollToIndex(aNewIndex);
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+
+#ifdef ACCESSIBILITY
+ FireMenuItemActiveEvent();
+#endif
+ } else if (mControlSelectMode && aCharCode == ' ') {
+ wasChanged = SingleSelection(aNewIndex, true);
+ } else {
+ wasChanged = PerformSelection(aNewIndex, aIsShift, aIsControlOrMeta);
+ }
+ if (wasChanged && weakFrame.IsAlive()) {
+ // dispatch event, update combobox, etc.
+ UpdateSelection();
+ }
+}
+
+
+/******************************************************************************
+ * nsListEventListener
+ *****************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsListEventListener, nsIDOMEventListener)
+
+NS_IMETHODIMP
+nsListEventListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+ if (!mFrame)
+ return NS_OK;
+
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ if (eventType.EqualsLiteral("keydown")) {
+ return mFrame->nsListControlFrame::KeyDown(aEvent);
+ }
+ if (eventType.EqualsLiteral("keypress")) {
+ return mFrame->nsListControlFrame::KeyPress(aEvent);
+ }
+ if (eventType.EqualsLiteral("mousedown")) {
+ bool defaultPrevented = false;
+ aEvent->GetDefaultPrevented(&defaultPrevented);
+ if (defaultPrevented) {
+ return NS_OK;
+ }
+ return mFrame->nsListControlFrame::MouseDown(aEvent);
+ }
+ if (eventType.EqualsLiteral("mouseup")) {
+ // Don't try to honor defaultPrevented here - it's not web compatible.
+ // (bug 1194733)
+ return mFrame->nsListControlFrame::MouseUp(aEvent);
+ }
+ if (eventType.EqualsLiteral("mousemove")) {
+ // I don't think we want to honor defaultPrevented on mousemove
+ // in general, and it would only prevent highlighting here.
+ return mFrame->nsListControlFrame::MouseMove(aEvent);
+ }
+
+ NS_ABORT();
+ return NS_OK;
+}
diff --git a/layout/forms/nsListControlFrame.h b/layout/forms/nsListControlFrame.h
new file mode 100644
index 000000000..47f033a9b
--- /dev/null
+++ b/layout/forms/nsListControlFrame.h
@@ -0,0 +1,477 @@
+/* -*- 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 nsListControlFrame_h___
+#define nsListControlFrame_h___
+
+#ifdef DEBUG_evaughan
+//#define DEBUG_rods
+#endif
+
+#ifdef DEBUG_rods
+//#define DO_REFLOW_DEBUG
+//#define DO_REFLOW_COUNTER
+//#define DO_UNCONSTRAINED_CHECK
+//#define DO_PIXELS
+#endif
+
+#include "mozilla/Attributes.h"
+#include "nsGfxScrollFrame.h"
+#include "nsIFormControlFrame.h"
+#include "nsIListControlFrame.h"
+#include "nsISelectControlFrame.h"
+#include "nsSelectsAreaFrame.h"
+
+// X.h defines KeyPress
+#ifdef KeyPress
+#undef KeyPress
+#endif
+
+class nsIComboboxControlFrame;
+class nsPresContext;
+class nsListEventListener;
+
+namespace mozilla {
+namespace dom {
+class HTMLOptionElement;
+class HTMLOptionsCollection;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * Frame-based listbox.
+ */
+
+class nsListControlFrame final : public nsHTMLScrollFrame,
+ public nsIFormControlFrame,
+ public nsIListControlFrame,
+ public nsISelectControlFrame
+{
+public:
+ friend nsContainerFrame* NS_NewListControlFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIFrame
+ virtual nsresult HandleEvent(nsPresContext* aPresContext,
+ mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) override;
+
+ virtual void SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+
+ virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override;
+ virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override;
+
+ virtual void Reflow(nsPresContext* aCX,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual void DidReflow(nsPresContext* aPresContext,
+ const ReflowInput* aReflowInput,
+ nsDidReflowStatus aStatus) override;
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual nsContainerFrame* GetContentInsertionFrame() override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::scrollFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsHTMLScrollFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ // nsIFormControlFrame
+ virtual nsresult SetFormProperty(nsIAtom* aName, const nsAString& aValue) override;
+ virtual void SetFocus(bool aOn = true, bool aRepaint = false) override;
+
+ virtual mozilla::ScrollbarStyles GetScrollbarStyles() const override;
+ virtual bool ShouldPropagateComputedBSizeToScrolledContent() const override;
+
+ // for accessibility purposes
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+ // nsIListControlFrame
+ virtual void SetComboboxFrame(nsIFrame* aComboboxFrame) override;
+ virtual int32_t GetSelectedIndex() override;
+ virtual mozilla::dom::HTMLOptionElement* GetCurrentOption() override;
+
+ /**
+ * Gets the text of the currently selected item.
+ * If the there are zero items then an empty string is returned
+ * If there is nothing selected, then the 0th item's text is returned.
+ */
+ virtual void GetOptionText(uint32_t aIndex, nsAString& aStr) override;
+
+ virtual void CaptureMouseEvents(bool aGrabMouseEvents) override;
+ virtual nscoord GetBSizeOfARow() override;
+ virtual uint32_t GetNumberOfOptions() override;
+ virtual void AboutToDropDown() override;
+
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ virtual void AboutToRollup() override;
+
+ /**
+ * Dispatch a DOM oninput and onchange event synchroniously.
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ virtual void FireOnInputAndOnChange() override;
+
+ /**
+ * Makes aIndex the selected option of a combobox list.
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ virtual void ComboboxFinish(int32_t aIndex) override;
+ virtual void OnContentReset() override;
+
+ // nsISelectControlFrame
+ NS_IMETHOD AddOption(int32_t index) override;
+ NS_IMETHOD RemoveOption(int32_t index) override;
+ NS_IMETHOD DoneAddingChildren(bool aIsDone) override;
+
+ /**
+ * Gets the content (an option) by index and then set it as
+ * being selected or not selected.
+ */
+ NS_IMETHOD OnOptionSelected(int32_t aIndex, bool aSelected) override;
+ NS_IMETHOD OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex) override;
+
+ /**
+ * Mouse event listeners.
+ * @note These methods might destroy the frame, pres shell and other objects.
+ */
+ nsresult MouseDown(nsIDOMEvent* aMouseEvent);
+ nsresult MouseUp(nsIDOMEvent* aMouseEvent);
+ nsresult MouseMove(nsIDOMEvent* aMouseEvent);
+ nsresult DragMove(nsIDOMEvent* aMouseEvent);
+ nsresult KeyDown(nsIDOMEvent* aKeyEvent);
+ nsresult KeyPress(nsIDOMEvent* aKeyEvent);
+
+ /**
+ * Returns the options collection for mContent, if any.
+ */
+ mozilla::dom::HTMLOptionsCollection* GetOptions() const;
+ /**
+ * Returns the HTMLOptionElement for a given index in mContent's collection.
+ */
+ mozilla::dom::HTMLOptionElement* GetOption(uint32_t aIndex) const;
+
+ static void ComboboxFocusSet();
+
+ // Helper
+ bool IsFocused() { return this == mFocused; }
+
+ /**
+ * Function to paint the focus rect when our nsSelectsAreaFrame is painting.
+ * @param aPt the offset of this frame, relative to the rendering reference
+ * frame
+ */
+ void PaintFocus(mozilla::gfx::DrawTarget* aDrawTarget, nsPoint aPt);
+
+ /**
+ * If this frame IsFocused(), invalidates an area that includes anything
+ * that PaintFocus will or could have painted --- basically the whole
+ * GetOptionsContainer, plus some extra stuff if there are no options. This
+ * must be called every time mEndSelectionIndex changes.
+ */
+ void InvalidateFocus();
+
+ /**
+ * Function to calculate the block size of a row, for use with the
+ * "size" attribute.
+ * Can't be const because GetNumberOfOptions() isn't const.
+ */
+ nscoord CalcBSizeOfARow();
+
+ /**
+ * Function to ask whether we're currently in what might be the
+ * first pass of a two-pass reflow.
+ */
+ bool MightNeedSecondPass() const {
+ return mMightNeedSecondPass;
+ }
+
+ void SetSuppressScrollbarUpdate(bool aSuppress) {
+ nsHTMLScrollFrame::SetSuppressScrollbarUpdate(aSuppress);
+ }
+
+ /**
+ * Return whether the list is in dropdown mode.
+ */
+ bool IsInDropDownMode() const;
+
+ /**
+ * Return the number of displayed rows in the list.
+ */
+ uint32_t GetNumDisplayRows() const { return mNumDisplayRows; }
+
+ /**
+ * Return true if the drop-down list can display more rows.
+ * (always false if not in drop-down mode)
+ */
+ bool GetDropdownCanGrow() const { return mDropdownCanGrow; }
+
+ /**
+ * Dropdowns need views
+ */
+ virtual bool NeedsView() override { return IsInDropDownMode(); }
+
+ /**
+ * Frees statics owned by this class.
+ */
+ static void Shutdown();
+
+#ifdef ACCESSIBILITY
+ /**
+ * Post a custom DOM event for the change, so that accessibility can
+ * fire a native focus event for accessibility
+ * (Some 3rd party products need to track our focus)
+ */
+ void FireMenuItemActiveEvent(); // Inform assistive tech what got focused
+#endif
+
+protected:
+ /**
+ * Updates the selected text in a combobox and then calls FireOnChange().
+ * @note This method might destroy the frame, pres shell and other objects.
+ * Returns false if calling it destroyed |this|.
+ */
+ bool UpdateSelection();
+
+ /**
+ * Returns whether mContent supports multiple selection.
+ */
+ bool GetMultiple() const {
+ return mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple);
+ }
+
+
+ /**
+ * Toggles (show/hide) the combobox dropdown menu.
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ void DropDownToggleKey(nsIDOMEvent* aKeyEvent);
+
+ nsresult IsOptionDisabled(int32_t anIndex, bool &aIsDisabled);
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ void ScrollToFrame(mozilla::dom::HTMLOptionElement& aOptElement);
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ void ScrollToIndex(int32_t anIndex);
+
+ /**
+ * When the user clicks on the comboboxframe to show the dropdown
+ * listbox, they then have to move the mouse into the list. We don't
+ * want to process those mouse events as selection events (i.e., to
+ * scroll list items into view). So we ignore the events until
+ * the mouse moves below our border-inner-edge, when
+ * mItemSelectionStarted is set.
+ *
+ * @param aPoint relative to this frame
+ */
+ bool IgnoreMouseEventForSelection(nsIDOMEvent* aEvent);
+
+ /**
+ * If the dropdown is showing and the mouse has moved below our
+ * border-inner-edge, then set mItemSelectionStarted.
+ */
+ void UpdateInListState(nsIDOMEvent* aEvent);
+ void AdjustIndexForDisabledOpt(int32_t aStartIndex, int32_t &anNewIndex,
+ int32_t aNumOptions, int32_t aDoAdjustInc, int32_t aDoAdjustIncNext);
+
+ /**
+ * Resets the select back to it's original default values;
+ * those values as determined by the original HTML
+ */
+ virtual void ResetList(bool aAllowScrolling);
+
+ explicit nsListControlFrame(nsStyleContext* aContext);
+ virtual ~nsListControlFrame();
+
+ /**
+ * Sets the mSelectedIndex and mOldSelectedIndex from figuring out what
+ * item was selected using content
+ * @param aPoint the event point, in listcontrolframe coordinates
+ * @return NS_OK if it successfully found the selection
+ */
+ nsresult GetIndexFromDOMEvent(nsIDOMEvent* aMouseEvent, int32_t& aCurIndex);
+
+ bool CheckIfAllFramesHere();
+ bool IsLeftButton(nsIDOMEvent* aMouseEvent);
+
+ // guess at a row block size based on our own style.
+ nscoord CalcFallbackRowBSize(float aFontSizeInflation);
+
+ // CalcIntrinsicBSize computes our intrinsic block size (taking the
+ // "size" attribute into account). This should only be called in
+ // non-dropdown mode.
+ nscoord CalcIntrinsicBSize(nscoord aBSizeOfARow, int32_t aNumberOfOptions);
+
+ // Dropped down stuff
+ void SetComboboxItem(int32_t aIndex);
+
+ /**
+ * Method to reflow ourselves as a dropdown list. This differs from
+ * reflow as a listbox because the criteria for needing a second
+ * pass are different. This will be called from Reflow() as needed.
+ */
+ void ReflowAsDropdown(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus);
+
+ // Selection
+ bool SetOptionsSelectedFromFrame(int32_t aStartIndex,
+ int32_t aEndIndex,
+ bool aValue,
+ bool aClearAll);
+ bool ToggleOptionSelectedFromFrame(int32_t aIndex);
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ bool SingleSelection(int32_t aClickedIndex, bool aDoToggle);
+ bool ExtendedSelection(int32_t aStartIndex, int32_t aEndIndex,
+ bool aClearAll);
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ bool PerformSelection(int32_t aClickedIndex, bool aIsShift,
+ bool aIsControl);
+ /**
+ * @note This method might destroy the frame, pres shell and other objects.
+ */
+ bool HandleListSelection(nsIDOMEvent * aDOMEvent, int32_t selectedIndex);
+ void InitSelectionRange(int32_t aClickedIndex);
+ void PostHandleKeyEvent(int32_t aNewIndex, uint32_t aCharCode,
+ bool aIsShift, bool aIsControlOrMeta);
+
+public:
+ nsSelectsAreaFrame* GetOptionsContainer() const {
+ return static_cast<nsSelectsAreaFrame*>(GetScrolledFrame());
+ }
+
+protected:
+ nscoord BSizeOfARow() {
+ return GetOptionsContainer()->BSizeOfARow();
+ }
+
+ /**
+ * @return how many displayable options/optgroups this frame has.
+ */
+ uint32_t GetNumberOfRows();
+
+ // Data Members
+ int32_t mStartSelectionIndex;
+ int32_t mEndSelectionIndex;
+
+ nsIComboboxControlFrame *mComboboxFrame;
+ uint32_t mNumDisplayRows;
+ bool mChangesSinceDragStart:1;
+ bool mButtonDown:1;
+ // Has the user selected a visible item since we showed the
+ // dropdown?
+ bool mItemSelectionStarted:1;
+
+ bool mIsAllContentHere:1;
+ bool mIsAllFramesHere:1;
+ bool mHasBeenInitialized:1;
+ bool mNeedToReset:1;
+ bool mPostChildrenLoadedReset:1;
+
+ //bool value for multiple discontiguous selection
+ bool mControlSelectMode:1;
+
+ // True if we're in the middle of a reflow and might need a second
+ // pass. This only happens for auto heights.
+ bool mMightNeedSecondPass:1;
+
+ /**
+ * Set to aPresContext->HasPendingInterrupt() at the start of Reflow.
+ * Set to false at the end of DidReflow.
+ */
+ bool mHasPendingInterruptAtStartOfReflow:1;
+
+ // True if the drop-down can show more rows. Always false if this list
+ // is not in drop-down mode.
+ bool mDropdownCanGrow:1;
+
+ // True if the selection can be set to nothing or disabled options.
+ bool mForceSelection:1;
+
+ // The last computed block size we reflowed at if we're a combobox
+ // dropdown.
+ // XXXbz should we be using a subclass here? Or just not worry
+ // about the extra member on listboxes?
+ nscoord mLastDropdownComputedBSize;
+
+ // At the time of our last dropdown, the backstop color to draw in case we
+ // are translucent.
+ nscolor mLastDropdownBackstopColor;
+
+ RefPtr<nsListEventListener> mEventListener;
+
+ static nsListControlFrame * mFocused;
+ static nsString * sIncrementalString;
+
+#ifdef DO_REFLOW_COUNTER
+ int32_t mReflowId;
+#endif
+
+private:
+ // for incremental typing navigation
+ static nsAString& GetIncrementalString ();
+ static DOMTimeStamp gLastKeyTime;
+
+ class MOZ_RAII AutoIncrementalSearchResetter
+ {
+ public:
+ AutoIncrementalSearchResetter() :
+ mCancelled(false)
+ {
+ }
+ ~AutoIncrementalSearchResetter()
+ {
+ if (!mCancelled) {
+ nsListControlFrame::GetIncrementalString().Truncate();
+ }
+ }
+ void Cancel()
+ {
+ mCancelled = true;
+ }
+ private:
+ bool mCancelled;
+ };
+};
+
+#endif /* nsListControlFrame_h___ */
+
diff --git a/layout/forms/nsMeterFrame.cpp b/layout/forms/nsMeterFrame.cpp
new file mode 100644
index 000000000..1f3bd8022
--- /dev/null
+++ b/layout/forms/nsMeterFrame.cpp
@@ -0,0 +1,297 @@
+/* -*- 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 "nsMeterFrame.h"
+
+#include "nsIContent.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+#include "nsIDocument.h"
+#include "nsIPresShell.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentUtils.h"
+#include "nsFormControlFrame.h"
+#include "nsFontMetrics.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLMeterElement.h"
+#include "nsContentList.h"
+#include "nsCSSPseudoElements.h"
+#include "nsStyleSet.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsThemeConstants.h"
+#include <algorithm>
+
+using namespace mozilla;
+using mozilla::dom::Element;
+using mozilla::dom::HTMLMeterElement;
+
+nsIFrame*
+NS_NewMeterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsMeterFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsMeterFrame)
+
+nsMeterFrame::nsMeterFrame(nsStyleContext* aContext)
+ : nsContainerFrame(aContext)
+ , mBarDiv(nullptr)
+{
+}
+
+nsMeterFrame::~nsMeterFrame()
+{
+}
+
+void
+nsMeterFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ NS_ASSERTION(!GetPrevContinuation(),
+ "nsMeterFrame should not have continuations; if it does we "
+ "need to call RegUnregAccessKey only for the first.");
+ nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
+ nsContentUtils::DestroyAnonymousContent(&mBarDiv);
+ nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+nsIAtom*
+nsMeterFrame::GetType() const
+{
+ return nsGkAtoms::meterFrame;
+}
+
+nsresult
+nsMeterFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+{
+ // Get the NodeInfoManager and tag necessary to create the meter bar div.
+ nsCOMPtr<nsIDocument> doc = mContent->GetComposedDoc();
+
+ // Create the div.
+ mBarDiv = doc->CreateHTMLElement(nsGkAtoms::div);
+
+ // Associate ::-moz-meter-bar pseudo-element to the anonymous child.
+ CSSPseudoElementType pseudoType = CSSPseudoElementType::mozMeterBar;
+ RefPtr<nsStyleContext> newStyleContext = PresContext()->StyleSet()->
+ ResolvePseudoElementStyle(mContent->AsElement(), pseudoType,
+ StyleContext(), mBarDiv->AsElement());
+
+ if (!aElements.AppendElement(ContentInfo(mBarDiv, newStyleContext))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+void
+nsMeterFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter)
+{
+ if (mBarDiv) {
+ aElements.AppendElement(mBarDiv);
+ }
+}
+
+NS_QUERYFRAME_HEAD(nsMeterFrame)
+ NS_QUERYFRAME_ENTRY(nsMeterFrame)
+ NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+
+void
+nsMeterFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsMeterFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+
+ NS_ASSERTION(mBarDiv, "Meter bar div must exist!");
+ NS_ASSERTION(!GetPrevContinuation(),
+ "nsMeterFrame should not have continuations; if it does we "
+ "need to call RegUnregAccessKey only for the first.");
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ nsFormControlFrame::RegUnRegAccessKey(this, true);
+ }
+
+ nsIFrame* barFrame = mBarDiv->GetPrimaryFrame();
+ NS_ASSERTION(barFrame, "The meter frame should have a child with a frame!");
+
+ ReflowBarFrame(barFrame, aPresContext, aReflowInput, aStatus);
+
+ aDesiredSize.SetSize(aReflowInput.GetWritingMode(),
+ aReflowInput.ComputedSizeWithBorderPadding());
+
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, barFrame);
+ FinishAndStoreOverflow(&aDesiredSize);
+
+ aStatus = NS_FRAME_COMPLETE;
+
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+void
+nsMeterFrame::ReflowBarFrame(nsIFrame* aBarFrame,
+ nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ bool vertical = ResolvedOrientationIsVertical();
+ WritingMode wm = aBarFrame->GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSize(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput reflowInput(aPresContext, aReflowInput,
+ aBarFrame, availSize);
+ nscoord size = vertical ? aReflowInput.ComputedHeight()
+ : aReflowInput.ComputedWidth();
+ nscoord xoffset = aReflowInput.ComputedPhysicalBorderPadding().left;
+ nscoord yoffset = aReflowInput.ComputedPhysicalBorderPadding().top;
+
+ // NOTE: Introduce a new function getPosition in the content part ?
+ HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(mContent);
+
+ double max = meterElement->Max();
+ double min = meterElement->Min();
+ double value = meterElement->Value();
+
+ double position = max - min;
+ position = position != 0 ? (value - min) / position : 1;
+
+ size = NSToCoordRound(size * position);
+
+ if (!vertical && (wm.IsVertical() ? wm.IsVerticalRL() : !wm.IsBidiLTR())) {
+ xoffset += aReflowInput.ComputedWidth() - size;
+ }
+
+ // The bar position is *always* constrained.
+ if (vertical) {
+ // We want the bar to begin at the bottom.
+ yoffset += aReflowInput.ComputedHeight() - size;
+
+ size -= reflowInput.ComputedPhysicalMargin().TopBottom() +
+ reflowInput.ComputedPhysicalBorderPadding().TopBottom();
+ size = std::max(size, 0);
+ reflowInput.SetComputedHeight(size);
+ } else {
+ size -= reflowInput.ComputedPhysicalMargin().LeftRight() +
+ reflowInput.ComputedPhysicalBorderPadding().LeftRight();
+ size = std::max(size, 0);
+ reflowInput.SetComputedWidth(size);
+ }
+
+ xoffset += reflowInput.ComputedPhysicalMargin().left;
+ yoffset += reflowInput.ComputedPhysicalMargin().top;
+
+ ReflowOutput barDesiredSize(reflowInput);
+ ReflowChild(aBarFrame, aPresContext, barDesiredSize, reflowInput, xoffset,
+ yoffset, 0, aStatus);
+ FinishReflowChild(aBarFrame, aPresContext, barDesiredSize, &reflowInput,
+ xoffset, yoffset, 0);
+}
+
+nsresult
+nsMeterFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ NS_ASSERTION(mBarDiv, "Meter bar div must exist!");
+
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::value ||
+ aAttribute == nsGkAtoms::max ||
+ aAttribute == nsGkAtoms::min )) {
+ nsIFrame* barFrame = mBarDiv->GetPrimaryFrame();
+ NS_ASSERTION(barFrame, "The meter frame should have a child with a frame!");
+ PresContext()->PresShell()->FrameNeedsReflow(barFrame,
+ nsIPresShell::eResize,
+ NS_FRAME_IS_DIRTY);
+ InvalidateFrame();
+ }
+
+ return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+}
+
+LogicalSize
+nsMeterFrame::ComputeAutoSize(nsRenderingContext* aRenderingContext,
+ WritingMode aWM,
+ const LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const LogicalSize& aMargin,
+ const LogicalSize& aBorder,
+ const LogicalSize& aPadding,
+ ComputeSizeFlags aFlags)
+{
+ RefPtr<nsFontMetrics> fontMet =
+ nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
+
+ const WritingMode wm = GetWritingMode();
+ LogicalSize autoSize(wm);
+ autoSize.BSize(wm) = autoSize.ISize(wm) = fontMet->Font().size; // 1em
+
+ if (ResolvedOrientationIsVertical() == wm.IsVertical()) {
+ autoSize.ISize(wm) *= 5; // 5em
+ } else {
+ autoSize.BSize(wm) *= 5; // 5em
+ }
+
+ return autoSize.ConvertTo(aWM, wm);
+}
+
+nscoord
+nsMeterFrame::GetMinISize(nsRenderingContext *aRenderingContext)
+{
+ RefPtr<nsFontMetrics> fontMet =
+ nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
+
+ nscoord minISize = fontMet->Font().size; // 1em
+
+ if (ResolvedOrientationIsVertical() == GetWritingMode().IsVertical()) {
+ // The orientation is inline
+ minISize *= 5; // 5em
+ }
+
+ return minISize;
+}
+
+nscoord
+nsMeterFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
+{
+ return GetMinISize(aRenderingContext);
+}
+
+bool
+nsMeterFrame::ShouldUseNativeStyle() const
+{
+ nsIFrame* barFrame = mBarDiv->GetPrimaryFrame();
+
+ // Use the native style if these conditions are satisfied:
+ // - both frames use the native appearance;
+ // - neither frame has author specified rules setting the border or the
+ // background.
+ return StyleDisplay()->mAppearance == NS_THEME_METERBAR &&
+ !PresContext()->HasAuthorSpecifiedRules(this,
+ NS_AUTHOR_SPECIFIED_BORDER | NS_AUTHOR_SPECIFIED_BACKGROUND) &&
+ barFrame &&
+ barFrame->StyleDisplay()->mAppearance == NS_THEME_METERCHUNK &&
+ !PresContext()->HasAuthorSpecifiedRules(barFrame,
+ NS_AUTHOR_SPECIFIED_BORDER | NS_AUTHOR_SPECIFIED_BACKGROUND);
+}
+
+Element*
+nsMeterFrame::GetPseudoElement(CSSPseudoElementType aType)
+{
+ if (aType == CSSPseudoElementType::mozMeterBar) {
+ return mBarDiv;
+ }
+
+ return nsContainerFrame::GetPseudoElement(aType);
+}
diff --git a/layout/forms/nsMeterFrame.h b/layout/forms/nsMeterFrame.h
new file mode 100644
index 000000000..9f24b7656
--- /dev/null
+++ b/layout/forms/nsMeterFrame.h
@@ -0,0 +1,94 @@
+/* -*- 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 nsMeterFrame_h___
+#define nsMeterFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsCOMPtr.h"
+#include "nsCSSPseudoElements.h"
+
+class nsMeterFrame : public nsContainerFrame,
+ public nsIAnonymousContentCreator
+
+{
+ typedef mozilla::dom::Element Element;
+
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsMeterFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ explicit nsMeterFrame(nsStyleContext* aContext);
+ virtual ~nsMeterFrame();
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual void Reflow(nsPresContext* aCX,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(NS_LITERAL_STRING("Meter"), aResult);
+ }
+#endif
+
+ virtual bool IsLeaf() const override { return true; }
+
+ // nsIAnonymousContentCreator
+ virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
+ virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual mozilla::LogicalSize
+ ComputeAutoSize(nsRenderingContext* aRenderingContext,
+ mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorder,
+ const mozilla::LogicalSize& aPadding,
+ ComputeSizeFlags aFlags) override;
+
+ virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override;
+ virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsContainerFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
+ }
+
+ /**
+ * Returns whether the frame and its child should use the native style.
+ */
+ bool ShouldUseNativeStyle() const;
+
+ virtual Element* GetPseudoElement(mozilla::CSSPseudoElementType aType) override;
+
+protected:
+ // Helper function which reflow the anonymous div frame.
+ void ReflowBarFrame(nsIFrame* aBarFrame,
+ nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus);
+ /**
+ * The div used to show the meter bar.
+ * @see nsMeterFrame::CreateAnonymousContent
+ */
+ nsCOMPtr<Element> mBarDiv;
+};
+
+#endif
diff --git a/layout/forms/nsNumberControlFrame.cpp b/layout/forms/nsNumberControlFrame.cpp
new file mode 100644
index 000000000..58421ecb9
--- /dev/null
+++ b/layout/forms/nsNumberControlFrame.cpp
@@ -0,0 +1,799 @@
+/* -*- 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 "nsNumberControlFrame.h"
+
+#include "HTMLInputElement.h"
+#include "ICUUtils.h"
+#include "nsIFocusManager.h"
+#include "nsIPresShell.h"
+#include "nsFocusManager.h"
+#include "nsFontMetrics.h"
+#include "nsFormControlFrame.h"
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+#include "nsThemeConstants.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventStates.h"
+#include "nsContentUtils.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentList.h"
+#include "nsCSSPseudoElements.h"
+#include "nsStyleSet.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsThreadUtils.h"
+#include "mozilla/FloatingPoint.h"
+
+#ifdef ACCESSIBILITY
+#include "mozilla/a11y/AccTypes.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsIFrame*
+NS_NewNumberControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsNumberControlFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsNumberControlFrame)
+
+NS_QUERYFRAME_HEAD(nsNumberControlFrame)
+ NS_QUERYFRAME_ENTRY(nsNumberControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+ NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+nsNumberControlFrame::nsNumberControlFrame(nsStyleContext* aContext)
+ : nsContainerFrame(aContext)
+ , mHandlingInputEvent(false)
+{
+}
+
+void
+nsNumberControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
+ "nsNumberControlFrame should not have continuations; if it does we "
+ "need to call RegUnregAccessKey only for the first");
+ nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
+ nsContentUtils::DestroyAnonymousContent(&mOuterWrapper);
+ nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+nscoord
+nsNumberControlFrame::GetMinISize(nsRenderingContext* aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_MIN_WIDTH(this, result);
+
+ nsIFrame* kid = mFrames.FirstChild();
+ if (kid) { // display:none?
+ result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+ kid,
+ nsLayoutUtils::MIN_ISIZE);
+ } else {
+ result = 0;
+ }
+
+ return result;
+}
+
+nscoord
+nsNumberControlFrame::GetPrefISize(nsRenderingContext* aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_PREF_WIDTH(this, result);
+
+ nsIFrame* kid = mFrames.FirstChild();
+ if (kid) { // display:none?
+ result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+ kid,
+ nsLayoutUtils::PREF_ISIZE);
+ } else {
+ result = 0;
+ }
+
+ return result;
+}
+
+void
+nsNumberControlFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsNumberControlFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+
+ NS_ASSERTION(mOuterWrapper, "Outer wrapper div must exist!");
+
+ NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
+ "nsNumberControlFrame should not have continuations; if it does we "
+ "need to call RegUnregAccessKey only for the first");
+
+ NS_ASSERTION(!mFrames.FirstChild() ||
+ !mFrames.FirstChild()->GetNextSibling(),
+ "We expect at most one direct child frame");
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ nsFormControlFrame::RegUnRegAccessKey(this, true);
+ }
+
+ const WritingMode myWM = aReflowInput.GetWritingMode();
+
+ // The ISize of our content box, which is the available ISize
+ // for our anonymous content:
+ const nscoord contentBoxISize = aReflowInput.ComputedISize();
+ nscoord contentBoxBSize = aReflowInput.ComputedBSize();
+
+ // Figure out our border-box sizes as well (by adding borderPadding to
+ // content-box sizes):
+ const nscoord borderBoxISize = contentBoxISize +
+ aReflowInput.ComputedLogicalBorderPadding().IStartEnd(myWM);
+
+ nscoord borderBoxBSize;
+ if (contentBoxBSize != NS_INTRINSICSIZE) {
+ borderBoxBSize = contentBoxBSize +
+ aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
+ } // else, we'll figure out borderBoxBSize after we resolve contentBoxBSize.
+
+ nsIFrame* outerWrapperFrame = mOuterWrapper->GetPrimaryFrame();
+
+ if (!outerWrapperFrame) { // display:none?
+ if (contentBoxBSize == NS_INTRINSICSIZE) {
+ contentBoxBSize = 0;
+ borderBoxBSize =
+ aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
+ }
+ } else {
+ NS_ASSERTION(outerWrapperFrame == mFrames.FirstChild(), "huh?");
+
+ ReflowOutput wrappersDesiredSize(aReflowInput);
+
+ WritingMode wrapperWM = outerWrapperFrame->GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSize(wrapperWM);
+ availSize.BSize(wrapperWM) = NS_UNCONSTRAINEDSIZE;
+
+ ReflowInput wrapperReflowInput(aPresContext, aReflowInput,
+ outerWrapperFrame, availSize);
+
+ // Convert wrapper margin into my own writing-mode (in case it differs):
+ LogicalMargin wrapperMargin =
+ wrapperReflowInput.ComputedLogicalMargin().ConvertTo(myWM, wrapperWM);
+
+ // offsets of wrapper frame within this frame:
+ LogicalPoint
+ wrapperOffset(myWM,
+ aReflowInput.ComputedLogicalBorderPadding().IStart(myWM) +
+ wrapperMargin.IStart(myWM),
+ aReflowInput.ComputedLogicalBorderPadding().BStart(myWM) +
+ wrapperMargin.BStart(myWM));
+
+ nsReflowStatus childStatus;
+ // We initially reflow the child with a dummy containerSize; positioning
+ // will be fixed later.
+ const nsSize dummyContainerSize;
+ ReflowChild(outerWrapperFrame, aPresContext, wrappersDesiredSize,
+ wrapperReflowInput, myWM, wrapperOffset, dummyContainerSize, 0,
+ childStatus);
+ MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(childStatus),
+ "We gave our child unconstrained available block-size, "
+ "so it should be complete");
+
+ nscoord wrappersMarginBoxBSize =
+ wrappersDesiredSize.BSize(myWM) + wrapperMargin.BStartEnd(myWM);
+
+ if (contentBoxBSize == NS_INTRINSICSIZE) {
+ // We are intrinsically sized -- we should shrinkwrap the outer wrapper's
+ // block-size:
+ contentBoxBSize = wrappersMarginBoxBSize;
+
+ // Make sure we obey min/max-bsize in the case when we're doing intrinsic
+ // sizing (we get it for free when we have a non-intrinsic
+ // aReflowInput.ComputedBSize()). Note that we do this before
+ // adjusting for borderpadding, since ComputedMaxBSize and
+ // ComputedMinBSize are content heights.
+ contentBoxBSize =
+ NS_CSS_MINMAX(contentBoxBSize,
+ aReflowInput.ComputedMinBSize(),
+ aReflowInput.ComputedMaxBSize());
+
+ borderBoxBSize = contentBoxBSize +
+ aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
+ }
+
+ // Center child in block axis
+ nscoord extraSpace = contentBoxBSize - wrappersMarginBoxBSize;
+ wrapperOffset.B(myWM) += std::max(0, extraSpace / 2);
+
+ // Needed in FinishReflowChild, for logical-to-physical conversion:
+ nsSize borderBoxSize = LogicalSize(myWM, borderBoxISize, borderBoxBSize).
+ GetPhysicalSize(myWM);
+
+ // Place the child
+ FinishReflowChild(outerWrapperFrame, aPresContext, wrappersDesiredSize,
+ &wrapperReflowInput, myWM, wrapperOffset,
+ borderBoxSize, 0);
+
+ nsSize contentBoxSize =
+ LogicalSize(myWM, contentBoxISize, contentBoxBSize).
+ GetPhysicalSize(myWM);
+ aDesiredSize.SetBlockStartAscent(
+ wrappersDesiredSize.BlockStartAscent() +
+ outerWrapperFrame->BStart(aReflowInput.GetWritingMode(),
+ contentBoxSize));
+ }
+
+ LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize);
+ aDesiredSize.SetSize(myWM, logicalDesiredSize);
+
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+
+ if (outerWrapperFrame) {
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, outerWrapperFrame);
+ }
+
+ FinishAndStoreOverflow(&aDesiredSize);
+
+ aStatus = NS_FRAME_COMPLETE;
+
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+void
+nsNumberControlFrame::SyncDisabledState()
+{
+ EventStates eventStates = mContent->AsElement()->State();
+ if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
+ mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, EmptyString(),
+ true);
+ } else {
+ mTextField->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
+ }
+}
+
+nsresult
+nsNumberControlFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ // nsGkAtoms::disabled is handled by SyncDisabledState
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::placeholder ||
+ aAttribute == nsGkAtoms::readonly ||
+ aAttribute == nsGkAtoms::tabindex) {
+ if (aModType == nsIDOMMutationEvent::REMOVAL) {
+ mTextField->UnsetAttr(aNameSpaceID, aAttribute, true);
+ } else {
+ MOZ_ASSERT(aModType == nsIDOMMutationEvent::ADDITION ||
+ aModType == nsIDOMMutationEvent::MODIFICATION);
+ nsAutoString value;
+ mContent->GetAttr(aNameSpaceID, aAttribute, value);
+ mTextField->SetAttr(aNameSpaceID, aAttribute, value, true);
+ }
+ }
+ }
+
+ return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+}
+
+void
+nsNumberControlFrame::ContentStatesChanged(EventStates aStates)
+{
+ if (aStates.HasState(NS_EVENT_STATE_DISABLED)) {
+ nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
+ }
+}
+
+nsITextControlFrame*
+nsNumberControlFrame::GetTextFieldFrame()
+{
+ return do_QueryFrame(GetAnonTextControl()->GetPrimaryFrame());
+}
+
+class FocusTextField : public Runnable
+{
+public:
+ FocusTextField(nsIContent* aNumber, nsIContent* aTextField)
+ : mNumber(aNumber),
+ mTextField(aTextField)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ if (mNumber->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS)) {
+ HTMLInputElement::FromContent(mTextField)->Focus();
+ }
+
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIContent> mNumber;
+ nsCOMPtr<nsIContent> mTextField;
+};
+
+nsresult
+nsNumberControlFrame::MakeAnonymousElement(Element** aResult,
+ nsTArray<ContentInfo>& aElements,
+ nsIAtom* aTagName,
+ CSSPseudoElementType aPseudoType,
+ nsStyleContext* aParentContext)
+{
+ // Get the NodeInfoManager and tag necessary to create the anonymous divs.
+ nsCOMPtr<nsIDocument> doc = mContent->GetComposedDoc();
+ RefPtr<Element> resultElement = doc->CreateHTMLElement(aTagName);
+
+ // If we legitimately fail this assertion and need to allow
+ // non-pseudo-element anonymous children, then we'll need to add a branch
+ // that calls ResolveStyleFor((*aResult)->AsElement(), aParentContext)") to
+ // set newStyleContext.
+ NS_ASSERTION(aPseudoType != CSSPseudoElementType::NotPseudo,
+ "Expecting anonymous children to all be pseudo-elements");
+ // Associate the pseudo-element with the anonymous child
+ RefPtr<nsStyleContext> newStyleContext =
+ PresContext()->StyleSet()->ResolvePseudoElementStyle(mContent->AsElement(),
+ aPseudoType,
+ aParentContext,
+ resultElement);
+
+ if (!aElements.AppendElement(ContentInfo(resultElement, newStyleContext))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (aPseudoType == CSSPseudoElementType::mozNumberSpinDown ||
+ aPseudoType == CSSPseudoElementType::mozNumberSpinUp) {
+ resultElement->SetAttr(kNameSpaceID_None, nsGkAtoms::role,
+ NS_LITERAL_STRING("button"), false);
+ }
+
+ resultElement.forget(aResult);
+ return NS_OK;
+}
+
+nsresult
+nsNumberControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+{
+ nsresult rv;
+
+ // We create an anonymous tree for our input element that is structured as
+ // follows:
+ //
+ // input
+ // div - outer wrapper with "display:flex" by default
+ // input - text input field
+ // div - spin box wrapping up/down arrow buttons
+ // div - spin up (up arrow button)
+ // div - spin down (down arrow button)
+ //
+ // If you change this, be careful to change the destruction order in
+ // nsNumberControlFrame::DestroyFrom.
+
+
+ // Create the anonymous outer wrapper:
+ rv = MakeAnonymousElement(getter_AddRefs(mOuterWrapper),
+ aElements,
+ nsGkAtoms::div,
+ CSSPseudoElementType::mozNumberWrapper,
+ mStyleContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ContentInfo& outerWrapperCI = aElements.LastElement();
+
+ // Create the ::-moz-number-text pseudo-element:
+ rv = MakeAnonymousElement(getter_AddRefs(mTextField),
+ outerWrapperCI.mChildren,
+ nsGkAtoms::input,
+ CSSPseudoElementType::mozNumberText,
+ outerWrapperCI.mStyleContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
+ NS_LITERAL_STRING("text"), PR_FALSE);
+
+ HTMLInputElement* content = HTMLInputElement::FromContent(mContent);
+ HTMLInputElement* textField = HTMLInputElement::FromContent(mTextField);
+
+ // Initialize the text field value:
+ nsAutoString value;
+ content->GetValue(value);
+ SetValueOfAnonTextControl(value);
+
+ // If we're readonly, make sure our anonymous text control is too:
+ nsAutoString readonly;
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly)) {
+ mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly, false);
+ }
+
+ // Propogate our tabindex:
+ int32_t tabIndex;
+ content->GetTabIndex(&tabIndex);
+ textField->SetTabIndex(tabIndex);
+
+ // Initialize the text field's placeholder, if ours is set:
+ nsAutoString placeholder;
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholder)) {
+ mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholder, false);
+ }
+
+ if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS)) {
+ // We don't want to focus the frame but the text field.
+ RefPtr<FocusTextField> focusJob = new FocusTextField(mContent, mTextField);
+ nsContentUtils::AddScriptRunner(focusJob);
+ }
+
+ if (StyleDisplay()->mAppearance == NS_THEME_TEXTFIELD) {
+ // The author has elected to hide the spinner by setting this
+ // -moz-appearance. We will reframe if it changes.
+ return rv;
+ }
+
+ // Create the ::-moz-number-spin-box pseudo-element:
+ rv = MakeAnonymousElement(getter_AddRefs(mSpinBox),
+ outerWrapperCI.mChildren,
+ nsGkAtoms::div,
+ CSSPseudoElementType::mozNumberSpinBox,
+ outerWrapperCI.mStyleContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ContentInfo& spinBoxCI = outerWrapperCI.mChildren.LastElement();
+
+ // Create the ::-moz-number-spin-up pseudo-element:
+ rv = MakeAnonymousElement(getter_AddRefs(mSpinUp),
+ spinBoxCI.mChildren,
+ nsGkAtoms::div,
+ CSSPseudoElementType::mozNumberSpinUp,
+ spinBoxCI.mStyleContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create the ::-moz-number-spin-down pseudo-element:
+ rv = MakeAnonymousElement(getter_AddRefs(mSpinDown),
+ spinBoxCI.mChildren,
+ nsGkAtoms::div,
+ CSSPseudoElementType::mozNumberSpinDown,
+ spinBoxCI.mStyleContext);
+
+ SyncDisabledState();
+
+ return rv;
+}
+
+nsIAtom*
+nsNumberControlFrame::GetType() const
+{
+ return nsGkAtoms::numberControlFrame;
+}
+
+void
+nsNumberControlFrame::SetFocus(bool aOn, bool aRepaint)
+{
+ GetTextFieldFrame()->SetFocus(aOn, aRepaint);
+}
+
+nsresult
+nsNumberControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
+{
+ return GetTextFieldFrame()->SetFormProperty(aName, aValue);
+}
+
+HTMLInputElement*
+nsNumberControlFrame::GetAnonTextControl()
+{
+ return mTextField ? HTMLInputElement::FromContent(mTextField) : nullptr;
+}
+
+/* static */ nsNumberControlFrame*
+nsNumberControlFrame::GetNumberControlFrameForTextField(nsIFrame* aFrame)
+{
+ // If aFrame is the anon text field for an <input type=number> then we expect
+ // the frame of its mContent's grandparent to be that input's frame. We
+ // have to check for this via the content tree because we don't know whether
+ // extra frames will be wrapped around any of the elements between aFrame and
+ // the nsNumberControlFrame that we're looking for (e.g. flex wrappers).
+ nsIContent* content = aFrame->GetContent();
+ if (content->IsInNativeAnonymousSubtree() &&
+ content->GetParent() && content->GetParent()->GetParent()) {
+ nsIContent* grandparent = content->GetParent()->GetParent();
+ if (grandparent->IsHTMLElement(nsGkAtoms::input) &&
+ grandparent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::number, eCaseMatters)) {
+ return do_QueryFrame(grandparent->GetPrimaryFrame());
+ }
+ }
+ return nullptr;
+}
+
+/* static */ nsNumberControlFrame*
+nsNumberControlFrame::GetNumberControlFrameForSpinButton(nsIFrame* aFrame)
+{
+ // If aFrame is a spin button for an <input type=number> then we expect the
+ // frame of its mContent's great-grandparent to be that input's frame. We
+ // have to check for this via the content tree because we don't know whether
+ // extra frames will be wrapped around any of the elements between aFrame and
+ // the nsNumberControlFrame that we're looking for (e.g. flex wrappers).
+ nsIContent* content = aFrame->GetContent();
+ if (content->IsInNativeAnonymousSubtree() &&
+ content->GetParent() && content->GetParent()->GetParent() &&
+ content->GetParent()->GetParent()->GetParent()) {
+ nsIContent* greatgrandparent = content->GetParent()->GetParent()->GetParent();
+ if (greatgrandparent->IsHTMLElement(nsGkAtoms::input) &&
+ greatgrandparent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::number, eCaseMatters)) {
+ return do_QueryFrame(greatgrandparent->GetPrimaryFrame());
+ }
+ }
+ return nullptr;
+}
+
+int32_t
+nsNumberControlFrame::GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const
+{
+ MOZ_ASSERT(aEvent->mClass == eMouseEventClass, "Unexpected event type");
+
+ if (!mSpinBox) {
+ // we don't have a spinner
+ return eSpinButtonNone;
+ }
+ if (aEvent->mOriginalTarget == mSpinUp) {
+ return eSpinButtonUp;
+ }
+ if (aEvent->mOriginalTarget == mSpinDown) {
+ return eSpinButtonDown;
+ }
+ if (aEvent->mOriginalTarget == mSpinBox) {
+ // In the case that the up/down buttons are hidden (display:none) we use
+ // just the spin box element, spinning up if the pointer is over the top
+ // half of the element, or down if it's over the bottom half. This is
+ // important to handle since this is the state things are in for the
+ // default UA style sheet. See the comment in forms.css for why.
+ LayoutDeviceIntPoint absPoint = aEvent->mRefPoint;
+ nsPoint point =
+ nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
+ absPoint, mSpinBox->GetPrimaryFrame());
+ if (point != nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
+ if (point.y < mSpinBox->GetPrimaryFrame()->GetSize().height / 2) {
+ return eSpinButtonUp;
+ }
+ return eSpinButtonDown;
+ }
+ }
+ return eSpinButtonNone;
+}
+
+void
+nsNumberControlFrame::SpinnerStateChanged() const
+{
+ MOZ_ASSERT(mSpinUp && mSpinDown,
+ "We should not be called when we have no spinner");
+
+ nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame();
+ if (spinUpFrame && spinUpFrame->IsThemed()) {
+ spinUpFrame->InvalidateFrame();
+ }
+ nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame();
+ if (spinDownFrame && spinDownFrame->IsThemed()) {
+ spinDownFrame->InvalidateFrame();
+ }
+}
+
+bool
+nsNumberControlFrame::SpinnerUpButtonIsDepressed() const
+{
+ return HTMLInputElement::FromContent(mContent)->
+ NumberSpinnerUpButtonIsDepressed();
+}
+
+bool
+nsNumberControlFrame::SpinnerDownButtonIsDepressed() const
+{
+ return HTMLInputElement::FromContent(mContent)->
+ NumberSpinnerDownButtonIsDepressed();
+}
+
+bool
+nsNumberControlFrame::IsFocused() const
+{
+ // Normally this depends on the state of our anonymous text control (which
+ // takes focus for us), but in the case that it does not have a frame we will
+ // have focus ourself.
+ return mTextField->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS) ||
+ mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS);
+}
+
+void
+nsNumberControlFrame::HandleFocusEvent(WidgetEvent* aEvent)
+{
+ if (aEvent->mOriginalTarget != mTextField) {
+ // Move focus to our text field
+ RefPtr<HTMLInputElement> textField = HTMLInputElement::FromContent(mTextField);
+ textField->Focus();
+ }
+}
+
+nsresult
+nsNumberControlFrame::HandleSelectCall()
+{
+ RefPtr<HTMLInputElement> textField = HTMLInputElement::FromContent(mTextField);
+ return textField->Select();
+}
+
+#define STYLES_DISABLING_NATIVE_THEMING \
+ NS_AUTHOR_SPECIFIED_BACKGROUND | \
+ NS_AUTHOR_SPECIFIED_PADDING | \
+ NS_AUTHOR_SPECIFIED_BORDER
+
+bool
+nsNumberControlFrame::ShouldUseNativeStyleForSpinner() const
+{
+ MOZ_ASSERT(mSpinUp && mSpinDown,
+ "We should not be called when we have no spinner");
+
+ nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame();
+ nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame();
+
+ return spinUpFrame &&
+ spinUpFrame->StyleDisplay()->mAppearance == NS_THEME_SPINNER_UPBUTTON &&
+ !PresContext()->HasAuthorSpecifiedRules(spinUpFrame,
+ STYLES_DISABLING_NATIVE_THEMING) &&
+ spinDownFrame &&
+ spinDownFrame->StyleDisplay()->mAppearance == NS_THEME_SPINNER_DOWNBUTTON &&
+ !PresContext()->HasAuthorSpecifiedRules(spinDownFrame,
+ STYLES_DISABLING_NATIVE_THEMING);
+}
+
+void
+nsNumberControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter)
+{
+ // Only one direct anonymous child:
+ if (mOuterWrapper) {
+ aElements.AppendElement(mOuterWrapper);
+ }
+}
+
+void
+nsNumberControlFrame::SetValueOfAnonTextControl(const nsAString& aValue)
+{
+ if (mHandlingInputEvent) {
+ // We have been called while our HTMLInputElement is processing a DOM
+ // 'input' event targeted at our anonymous text control. Our
+ // HTMLInputElement has taken the value of our anon text control and
+ // called SetValueInternal on itself to keep its own value in sync. As a
+ // result SetValueInternal has called us. In this one case we do not want
+ // to update our anon text control, especially since aValue will be the
+ // sanitized value, and only the internal value should be sanitized (not
+ // the value shown to the user, and certainly we shouldn't change it as
+ // they type).
+ return;
+ }
+
+ // Init to aValue so that we set aValue as the value of our text control if
+ // aValue isn't a valid number (in which case the HTMLInputElement's validity
+ // state will be set to invalid) or if aValue can't be localized:
+ nsAutoString localizedValue(aValue);
+
+#ifdef ENABLE_INTL_API
+ // Try and localize the value we will set:
+ Decimal val = HTMLInputElement::StringToDecimal(aValue);
+ if (val.isFinite()) {
+ ICUUtils::LanguageTagIterForContent langTagIter(mContent);
+ ICUUtils::LocalizeNumber(val.toDouble(), langTagIter, localizedValue);
+ }
+#endif
+
+ // We need to update the value of our anonymous text control here. Note that
+ // this must be its value, and not its 'value' attribute (the default value),
+ // since the default value is ignored once a user types into the text
+ // control.
+ HTMLInputElement::FromContent(mTextField)->SetValue(localizedValue);
+}
+
+void
+nsNumberControlFrame::GetValueOfAnonTextControl(nsAString& aValue)
+{
+ if (!mTextField) {
+ aValue.Truncate();
+ return;
+ }
+
+ HTMLInputElement::FromContent(mTextField)->GetValue(aValue);
+
+#ifdef ENABLE_INTL_API
+ // Here we need to de-localize any number typed in by the user. That is, we
+ // need to convert it from the number format of the user's language, region,
+ // etc. to the format that the HTML 5 spec defines to be a "valid
+ // floating-point number":
+ //
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#floating-point-numbers
+ //
+ // This is necessary to allow the number that we return to be parsed by
+ // functions like HTMLInputElement::StringToDecimal (the HTML-5-conforming
+ // parsing function) which don't know how to handle numbers that are
+ // formatted differently (for example, with non-ASCII digits, with grouping
+ // separator characters or with a decimal separator character other than
+ // '.').
+
+ ICUUtils::LanguageTagIterForContent langTagIter(mContent);
+ double value = ICUUtils::ParseNumber(aValue, langTagIter);
+ if (!IsFinite(value)) {
+ aValue.Truncate();
+ return;
+ }
+ if (value == HTMLInputElement::StringToDecimal(aValue).toDouble()) {
+ // We want to preserve the formatting of the number as typed in by the user
+ // whenever possible. Since the localized serialization parses to the same
+ // number as the de-localized serialization, we can do that. This helps
+ // prevent normalization of input such as "2e2" (which would otherwise be
+ // converted to "200"). Content relies on this.
+ //
+ // Typically we will only get here for locales in which numbers are
+ // formatted in the same way as they are for HTML5's "valid floating-point
+ // number" format.
+ return;
+ }
+ // We can't preserve the formatting, otherwise functions such as
+ // HTMLInputElement::StringToDecimal would incorrectly process the number
+ // input by the user. For example, "12.345" with lang=de de-localizes as
+ // 12345, but HTMLInputElement::StringToDecimal would mistakenly parse it as
+ // 12.345. Another example would be "12,345" with lang=de which de-localizes
+ // as 12.345, but HTMLInputElement::StringToDecimal would parse it to NaN.
+ aValue.Truncate();
+ aValue.AppendFloat(value);
+#endif
+}
+
+bool
+nsNumberControlFrame::AnonTextControlIsEmpty()
+{
+ if (!mTextField) {
+ return true;
+ }
+ nsAutoString value;
+ HTMLInputElement::FromContent(mTextField)->GetValue(value);
+ return value.IsEmpty();
+}
+
+Element*
+nsNumberControlFrame::GetPseudoElement(CSSPseudoElementType aType)
+{
+ if (aType == CSSPseudoElementType::mozNumberWrapper) {
+ return mOuterWrapper;
+ }
+
+ if (aType == CSSPseudoElementType::mozNumberText) {
+ return mTextField;
+ }
+
+ if (aType == CSSPseudoElementType::mozNumberSpinBox) {
+ // Might be null.
+ return mSpinBox;
+ }
+
+ if (aType == CSSPseudoElementType::mozNumberSpinUp) {
+ // Might be null.
+ return mSpinUp;
+ }
+
+ if (aType == CSSPseudoElementType::mozNumberSpinDown) {
+ // Might be null.
+ return mSpinDown;
+ }
+
+ return nsContainerFrame::GetPseudoElement(aType);
+}
+
+#ifdef ACCESSIBILITY
+a11y::AccType
+nsNumberControlFrame::AccessibleType()
+{
+ return a11y::eHTMLSpinnerType;
+}
+#endif
diff --git a/layout/forms/nsNumberControlFrame.h b/layout/forms/nsNumberControlFrame.h
new file mode 100644
index 000000000..47e32ad65
--- /dev/null
+++ b/layout/forms/nsNumberControlFrame.h
@@ -0,0 +1,216 @@
+/* -*- 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 nsNumberControlFrame_h__
+#define nsNumberControlFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+#include "nsIFormControlFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsCOMPtr.h"
+
+class nsITextControlFrame;
+class nsPresContext;
+
+namespace mozilla {
+enum class CSSPseudoElementType : uint8_t;
+class WidgetEvent;
+class WidgetGUIEvent;
+namespace dom {
+class HTMLInputElement;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * This frame type is used for <input type=number>.
+ */
+class nsNumberControlFrame final : public nsContainerFrame
+ , public nsIAnonymousContentCreator
+ , public nsIFormControlFrame
+{
+ friend nsIFrame*
+ NS_NewNumberControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ typedef mozilla::CSSPseudoElementType CSSPseudoElementType;
+ typedef mozilla::dom::Element Element;
+ typedef mozilla::dom::HTMLInputElement HTMLInputElement;
+ typedef mozilla::WidgetEvent WidgetEvent;
+ typedef mozilla::WidgetGUIEvent WidgetGUIEvent;
+
+ explicit nsNumberControlFrame(nsStyleContext* aContext);
+
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsNumberControlFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+ virtual void ContentStatesChanged(mozilla::EventStates aStates) override;
+ virtual bool IsLeaf() const override { return true; }
+
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+ virtual nscoord GetMinISize(nsRenderingContext* aRenderingContext) override;
+
+ virtual nscoord GetPrefISize(nsRenderingContext* aRenderingContext) override;
+
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ // nsIAnonymousContentCreator
+ virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
+ virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(NS_LITERAL_STRING("NumberControl"), aResult);
+ }
+#endif
+
+ virtual nsIAtom* GetType() const override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsContainerFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
+ }
+
+ // nsIFormControlFrame
+ virtual void SetFocus(bool aOn, bool aRepaint) override;
+ virtual nsresult SetFormProperty(nsIAtom* aName, const nsAString& aValue) override;
+
+ /**
+ * This method attempts to localizes aValue and then sets the result as the
+ * value of our anonymous text control. It's called when our
+ * HTMLInputElement's value changes, when we need to sync up the value
+ * displayed in our anonymous text control.
+ */
+ void SetValueOfAnonTextControl(const nsAString& aValue);
+
+ /**
+ * This method gets the string value of our anonymous text control,
+ * attempts to normalizes (de-localizes) it, then sets the outparam aValue to
+ * the result. It's called when user input changes the text value of our
+ * anonymous text control so that we can sync up the internal value of our
+ * HTMLInputElement.
+ */
+ void GetValueOfAnonTextControl(nsAString& aValue);
+
+ bool AnonTextControlIsEmpty();
+
+ /**
+ * Called to notify this frame that its HTMLInputElement is currently
+ * processing a DOM 'input' event.
+ */
+ void HandlingInputEvent(bool aHandlingEvent)
+ {
+ mHandlingInputEvent = aHandlingEvent;
+ }
+
+ HTMLInputElement* GetAnonTextControl();
+
+ /**
+ * If the frame is the frame for an nsNumberControlFrame's anonymous text
+ * field, returns the nsNumberControlFrame. Else returns nullptr.
+ */
+ static nsNumberControlFrame* GetNumberControlFrameForTextField(nsIFrame* aFrame);
+
+ /**
+ * If the frame is the frame for an nsNumberControlFrame's up or down spin
+ * button, returns the nsNumberControlFrame. Else returns nullptr.
+ */
+ static nsNumberControlFrame* GetNumberControlFrameForSpinButton(nsIFrame* aFrame);
+
+ enum SpinButtonEnum {
+ eSpinButtonNone,
+ eSpinButtonUp,
+ eSpinButtonDown
+ };
+
+ /**
+ * Returns one of the SpinButtonEnum values to depending on whether the
+ * pointer event is over the spin-up button, the spin-down button, or
+ * neither.
+ */
+ int32_t GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const;
+
+ void SpinnerStateChanged() const;
+
+ bool SpinnerUpButtonIsDepressed() const;
+ bool SpinnerDownButtonIsDepressed() const;
+
+ bool IsFocused() const;
+
+ void HandleFocusEvent(WidgetEvent* aEvent);
+
+ /**
+ * Our element had HTMLInputElement::Select() called on it.
+ */
+ nsresult HandleSelectCall();
+
+ virtual Element* GetPseudoElement(CSSPseudoElementType aType) override;
+
+ bool ShouldUseNativeStyleForSpinner() const;
+
+private:
+
+ nsITextControlFrame* GetTextFieldFrame();
+ nsresult MakeAnonymousElement(Element** aResult,
+ nsTArray<ContentInfo>& aElements,
+ nsIAtom* aTagName,
+ CSSPseudoElementType aPseudoType,
+ nsStyleContext* aParentContext);
+
+ class SyncDisabledStateEvent;
+ friend class SyncDisabledStateEvent;
+ class SyncDisabledStateEvent : public mozilla::Runnable
+ {
+ public:
+ explicit SyncDisabledStateEvent(nsNumberControlFrame* aFrame)
+ : mFrame(aFrame)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ nsNumberControlFrame* frame =
+ static_cast<nsNumberControlFrame*>(mFrame.GetFrame());
+ NS_ENSURE_STATE(frame);
+
+ frame->SyncDisabledState();
+ return NS_OK;
+ }
+
+ private:
+ nsWeakFrame mFrame;
+ };
+
+ /**
+ * Sync the disabled state of the anonymous children up with our content's.
+ */
+ void SyncDisabledState();
+
+ /**
+ * The text field used to edit and show the number.
+ * @see nsNumberControlFrame::CreateAnonymousContent.
+ */
+ nsCOMPtr<Element> mOuterWrapper;
+ nsCOMPtr<Element> mTextField;
+ nsCOMPtr<Element> mSpinBox;
+ nsCOMPtr<Element> mSpinUp;
+ nsCOMPtr<Element> mSpinDown;
+ bool mHandlingInputEvent;
+};
+
+#endif // nsNumberControlFrame_h__
diff --git a/layout/forms/nsProgressFrame.cpp b/layout/forms/nsProgressFrame.cpp
new file mode 100644
index 000000000..2445defd3
--- /dev/null
+++ b/layout/forms/nsProgressFrame.cpp
@@ -0,0 +1,308 @@
+/* -*- 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 "nsProgressFrame.h"
+
+#include "nsIContent.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+#include "nsIDocument.h"
+#include "nsIPresShell.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentUtils.h"
+#include "nsFormControlFrame.h"
+#include "nsFontMetrics.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLProgressElement.h"
+#include "nsContentList.h"
+#include "nsCSSPseudoElements.h"
+#include "nsStyleSet.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsThemeConstants.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsIFrame*
+NS_NewProgressFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsProgressFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsProgressFrame)
+
+nsProgressFrame::nsProgressFrame(nsStyleContext* aContext)
+ : nsContainerFrame(aContext)
+ , mBarDiv(nullptr)
+{
+}
+
+nsProgressFrame::~nsProgressFrame()
+{
+}
+
+void
+nsProgressFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ NS_ASSERTION(!GetPrevContinuation(),
+ "nsProgressFrame should not have continuations; if it does we "
+ "need to call RegUnregAccessKey only for the first.");
+ nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
+ nsContentUtils::DestroyAnonymousContent(&mBarDiv);
+ nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+nsIAtom*
+nsProgressFrame::GetType() const
+{
+ return nsGkAtoms::progressFrame;
+}
+
+nsresult
+nsProgressFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+{
+ // Create the progress bar div.
+ nsCOMPtr<nsIDocument> doc = mContent->GetComposedDoc();
+ mBarDiv = doc->CreateHTMLElement(nsGkAtoms::div);
+
+ // Associate ::-moz-progress-bar pseudo-element to the anonymous child.
+ CSSPseudoElementType pseudoType = CSSPseudoElementType::mozProgressBar;
+ RefPtr<nsStyleContext> newStyleContext = PresContext()->StyleSet()->
+ ResolvePseudoElementStyle(mContent->AsElement(), pseudoType,
+ StyleContext(), mBarDiv->AsElement());
+
+ if (!aElements.AppendElement(ContentInfo(mBarDiv, newStyleContext))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+void
+nsProgressFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter)
+{
+ if (mBarDiv) {
+ aElements.AppendElement(mBarDiv);
+ }
+}
+
+NS_QUERYFRAME_HEAD(nsProgressFrame)
+ NS_QUERYFRAME_ENTRY(nsProgressFrame)
+ NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+
+void
+nsProgressFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ BuildDisplayListForInline(aBuilder, aDirtyRect, aLists);
+}
+
+void
+nsProgressFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsProgressFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+
+ NS_ASSERTION(mBarDiv, "Progress bar div must exist!");
+ NS_ASSERTION(PrincipalChildList().GetLength() == 1 &&
+ PrincipalChildList().FirstChild() == mBarDiv->GetPrimaryFrame(),
+ "unexpected child frames");
+ NS_ASSERTION(!GetPrevContinuation(),
+ "nsProgressFrame should not have continuations; if it does we "
+ "need to call RegUnregAccessKey only for the first.");
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ nsFormControlFrame::RegUnRegAccessKey(this, true);
+ }
+
+ aDesiredSize.SetSize(aReflowInput.GetWritingMode(),
+ aReflowInput.ComputedSizeWithBorderPadding());
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+
+ for (auto childFrame : PrincipalChildList()) {
+ ReflowChildFrame(childFrame, aPresContext, aReflowInput, aStatus);
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, childFrame);
+ }
+
+ FinishAndStoreOverflow(&aDesiredSize);
+
+ aStatus = NS_FRAME_COMPLETE;
+
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+void
+nsProgressFrame::ReflowChildFrame(nsIFrame* aChild,
+ nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ bool vertical = ResolvedOrientationIsVertical();
+ WritingMode wm = aChild->GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSize(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput reflowInput(aPresContext, aReflowInput, aChild, availSize);
+ nscoord size = vertical ? aReflowInput.ComputedHeight()
+ : aReflowInput.ComputedWidth();
+ nscoord xoffset = aReflowInput.ComputedPhysicalBorderPadding().left;
+ nscoord yoffset = aReflowInput.ComputedPhysicalBorderPadding().top;
+
+ double position = static_cast<HTMLProgressElement*>(mContent)->Position();
+
+ // Force the bar's size to match the current progress.
+ // When indeterminate, the progress' size will be 100%.
+ if (position >= 0.0) {
+ size *= position;
+ }
+
+ if (!vertical && (wm.IsVertical() ? wm.IsVerticalRL() : !wm.IsBidiLTR())) {
+ xoffset += aReflowInput.ComputedWidth() - size;
+ }
+
+ // The bar size is fixed in these cases:
+ // - the progress position is determined: the bar size is fixed according
+ // to it's value.
+ // - the progress position is indeterminate and the bar appearance should be
+ // shown as native: the bar size is forced to 100%.
+ // Otherwise (when the progress is indeterminate and the bar appearance isn't
+ // native), the bar size isn't fixed and can be set by the author.
+ if (position != -1 || ShouldUseNativeStyle()) {
+ if (vertical) {
+ // We want the bar to begin at the bottom.
+ yoffset += aReflowInput.ComputedHeight() - size;
+
+ size -= reflowInput.ComputedPhysicalMargin().TopBottom() +
+ reflowInput.ComputedPhysicalBorderPadding().TopBottom();
+ size = std::max(size, 0);
+ reflowInput.SetComputedHeight(size);
+ } else {
+ size -= reflowInput.ComputedPhysicalMargin().LeftRight() +
+ reflowInput.ComputedPhysicalBorderPadding().LeftRight();
+ size = std::max(size, 0);
+ reflowInput.SetComputedWidth(size);
+ }
+ } else if (vertical) {
+ // For vertical progress bars, we need to position the bar specificly when
+ // the width isn't constrained (position == -1 and !ShouldUseNativeStyle())
+ // because aReflowInput.ComputedHeight() - size == 0.
+ yoffset += aReflowInput.ComputedHeight() - reflowInput.ComputedHeight();
+ }
+
+ xoffset += reflowInput.ComputedPhysicalMargin().left;
+ yoffset += reflowInput.ComputedPhysicalMargin().top;
+
+ ReflowOutput barDesiredSize(aReflowInput);
+ ReflowChild(aChild, aPresContext, barDesiredSize, reflowInput, xoffset,
+ yoffset, 0, aStatus);
+ FinishReflowChild(aChild, aPresContext, barDesiredSize, &reflowInput,
+ xoffset, yoffset, 0);
+}
+
+nsresult
+nsProgressFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ NS_ASSERTION(mBarDiv, "Progress bar div must exist!");
+
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::value || aAttribute == nsGkAtoms::max)) {
+ auto shell = PresContext()->PresShell();
+ for (auto childFrame : PrincipalChildList()) {
+ shell->FrameNeedsReflow(childFrame, nsIPresShell::eResize,
+ NS_FRAME_IS_DIRTY);
+ }
+ InvalidateFrame();
+ }
+
+ return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+LogicalSize
+nsProgressFrame::ComputeAutoSize(nsRenderingContext* aRenderingContext,
+ WritingMode aWM,
+ const LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const LogicalSize& aMargin,
+ const LogicalSize& aBorder,
+ const LogicalSize& aPadding,
+ ComputeSizeFlags aFlags)
+{
+ const WritingMode wm = GetWritingMode();
+ LogicalSize autoSize(wm);
+ autoSize.BSize(wm) = autoSize.ISize(wm) =
+ NSToCoordRound(StyleFont()->mFont.size *
+ nsLayoutUtils::FontSizeInflationFor(this)); // 1em
+
+ if (ResolvedOrientationIsVertical() == wm.IsVertical()) {
+ autoSize.ISize(wm) *= 10; // 10em
+ } else {
+ autoSize.BSize(wm) *= 10; // 10em
+ }
+
+ return autoSize.ConvertTo(aWM, wm);
+}
+
+nscoord
+nsProgressFrame::GetMinISize(nsRenderingContext *aRenderingContext)
+{
+ RefPtr<nsFontMetrics> fontMet =
+ nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
+
+ nscoord minISize = fontMet->Font().size; // 1em
+
+ if (ResolvedOrientationIsVertical() == GetWritingMode().IsVertical()) {
+ // The orientation is inline
+ minISize *= 10; // 10em
+ }
+
+ return minISize;
+}
+
+nscoord
+nsProgressFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
+{
+ return GetMinISize(aRenderingContext);
+}
+
+bool
+nsProgressFrame::ShouldUseNativeStyle() const
+{
+ nsIFrame* barFrame = PrincipalChildList().FirstChild();
+
+ // Use the native style if these conditions are satisfied:
+ // - both frames use the native appearance;
+ // - neither frame has author specified rules setting the border or the
+ // background.
+ return StyleDisplay()->mAppearance == NS_THEME_PROGRESSBAR &&
+ !PresContext()->HasAuthorSpecifiedRules(this,
+ NS_AUTHOR_SPECIFIED_BORDER | NS_AUTHOR_SPECIFIED_BACKGROUND) &&
+ barFrame &&
+ barFrame->StyleDisplay()->mAppearance == NS_THEME_PROGRESSCHUNK &&
+ !PresContext()->HasAuthorSpecifiedRules(barFrame,
+ NS_AUTHOR_SPECIFIED_BORDER | NS_AUTHOR_SPECIFIED_BACKGROUND);
+}
+
+Element*
+nsProgressFrame::GetPseudoElement(CSSPseudoElementType aType)
+{
+ if (aType == CSSPseudoElementType::mozProgressBar) {
+ return mBarDiv;
+ }
+
+ return nsContainerFrame::GetPseudoElement(aType);
+}
diff --git a/layout/forms/nsProgressFrame.h b/layout/forms/nsProgressFrame.h
new file mode 100644
index 000000000..01465ff7c
--- /dev/null
+++ b/layout/forms/nsProgressFrame.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsProgressFrame_h___
+#define nsProgressFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+enum class CSSPseudoElementType : uint8_t;
+} // namespace mozilla
+
+class nsProgressFrame : public nsContainerFrame,
+ public nsIAnonymousContentCreator
+{
+ typedef mozilla::CSSPseudoElementType CSSPseudoElementType;
+ typedef mozilla::dom::Element Element;
+
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsProgressFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ explicit nsProgressFrame(nsStyleContext* aContext);
+ virtual ~nsProgressFrame();
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual void Reflow(nsPresContext* aCX,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(NS_LITERAL_STRING("Progress"), aResult);
+ }
+#endif
+
+ virtual bool IsLeaf() const override { return true; }
+
+ // nsIAnonymousContentCreator
+ virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
+ virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual mozilla::LogicalSize
+ ComputeAutoSize(nsRenderingContext* aRenderingContext,
+ mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorder,
+ const mozilla::LogicalSize& aPadding,
+ ComputeSizeFlags aFlags) override;
+
+ virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override;
+ virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsContainerFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
+ }
+
+ /**
+ * Returns whether the frame and its child should use the native style.
+ */
+ bool ShouldUseNativeStyle() const;
+
+ virtual Element* GetPseudoElement(CSSPseudoElementType aType) override;
+
+protected:
+ // Helper function to reflow a child frame.
+ void ReflowChildFrame(nsIFrame* aChild,
+ nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus);
+
+ /**
+ * The div used to show the progress bar.
+ * @see nsProgressFrame::CreateAnonymousContent
+ */
+ nsCOMPtr<Element> mBarDiv;
+};
+
+#endif
diff --git a/layout/forms/nsRangeFrame.cpp b/layout/forms/nsRangeFrame.cpp
new file mode 100644
index 000000000..7590da066
--- /dev/null
+++ b/layout/forms/nsRangeFrame.cpp
@@ -0,0 +1,949 @@
+/* -*- 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 "nsRangeFrame.h"
+
+#include "mozilla/EventStates.h"
+#include "mozilla/TouchEvents.h"
+
+#include "nsContentCreatorFunctions.h"
+#include "nsContentList.h"
+#include "nsContentUtils.h"
+#include "nsCSSPseudoElements.h"
+#include "nsCSSRendering.h"
+#include "nsFormControlFrame.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsNameSpaceManager.h"
+#include "nsIPresShell.h"
+#include "nsGkAtoms.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "nsPresContext.h"
+#include "nsNodeInfoManager.h"
+#include "nsRenderingContext.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsThemeConstants.h"
+
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#endif
+
+#define LONG_SIDE_TO_SHORT_SIDE_RATIO 10
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::image;
+
+NS_IMPL_ISUPPORTS(nsRangeFrame::DummyTouchListener, nsIDOMEventListener)
+
+nsIFrame*
+NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsRangeFrame(aContext);
+}
+
+nsRangeFrame::nsRangeFrame(nsStyleContext* aContext)
+ : nsContainerFrame(aContext)
+{
+}
+
+nsRangeFrame::~nsRangeFrame()
+{
+#ifdef DEBUG
+ if (mOuterFocusStyle) {
+ mOuterFocusStyle->FrameRelease();
+ }
+#endif
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsRangeFrame)
+
+NS_QUERYFRAME_HEAD(nsRangeFrame)
+ NS_QUERYFRAME_ENTRY(nsRangeFrame)
+ NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+void
+nsRangeFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ // With APZ enabled, touch events may be handled directly by the APZC code
+ // if the APZ knows that there is no content interested in the touch event.
+ // The range input element *is* interested in touch events, but doesn't use
+ // the usual mechanism (i.e. registering an event listener) to handle touch
+ // input. Instead, we do it here so that the APZ finds out about it, and
+ // makes sure to wait for content to run handlers before handling the touch
+ // input itself.
+ if (!mDummyTouchListener) {
+ mDummyTouchListener = new DummyTouchListener();
+ }
+ aContent->AddEventListener(NS_LITERAL_STRING("touchstart"), mDummyTouchListener, false);
+
+ StyleSetHandle styleSet = PresContext()->StyleSet();
+
+ mOuterFocusStyle =
+ styleSet->ProbePseudoElementStyle(aContent->AsElement(),
+ CSSPseudoElementType::mozFocusOuter,
+ StyleContext());
+
+ return nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+
+void
+nsRangeFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
+ "nsRangeFrame should not have continuations; if it does we "
+ "need to call RegUnregAccessKey only for the first.");
+
+ mContent->RemoveEventListener(NS_LITERAL_STRING("touchstart"), mDummyTouchListener, false);
+
+ nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
+ nsContentUtils::DestroyAnonymousContent(&mTrackDiv);
+ nsContentUtils::DestroyAnonymousContent(&mProgressDiv);
+ nsContentUtils::DestroyAnonymousContent(&mThumbDiv);
+ nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+nsresult
+nsRangeFrame::MakeAnonymousDiv(Element** aResult,
+ CSSPseudoElementType aPseudoType,
+ nsTArray<ContentInfo>& aElements)
+{
+ nsCOMPtr<nsIDocument> doc = mContent->GetComposedDoc();
+ RefPtr<Element> resultElement = doc->CreateHTMLElement(nsGkAtoms::div);
+
+ // Associate the pseudo-element with the anonymous child.
+ RefPtr<nsStyleContext> newStyleContext =
+ PresContext()->StyleSet()->ResolvePseudoElementStyle(mContent->AsElement(),
+ aPseudoType,
+ StyleContext(),
+ resultElement);
+
+ if (!aElements.AppendElement(ContentInfo(resultElement, newStyleContext))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ resultElement.forget(aResult);
+ return NS_OK;
+}
+
+nsresult
+nsRangeFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+{
+ nsresult rv;
+
+ // Create the ::-moz-range-track pseuto-element (a div):
+ rv = MakeAnonymousDiv(getter_AddRefs(mTrackDiv),
+ CSSPseudoElementType::mozRangeTrack,
+ aElements);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create the ::-moz-range-progress pseudo-element (a div):
+ rv = MakeAnonymousDiv(getter_AddRefs(mProgressDiv),
+ CSSPseudoElementType::mozRangeProgress,
+ aElements);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create the ::-moz-range-thumb pseudo-element (a div):
+ rv = MakeAnonymousDiv(getter_AddRefs(mThumbDiv),
+ CSSPseudoElementType::mozRangeThumb,
+ aElements);
+ return rv;
+}
+
+void
+nsRangeFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter)
+{
+ if (mTrackDiv) {
+ aElements.AppendElement(mTrackDiv);
+ }
+
+ if (mProgressDiv) {
+ aElements.AppendElement(mProgressDiv);
+ }
+
+ if (mThumbDiv) {
+ aElements.AppendElement(mThumbDiv);
+ }
+}
+
+class nsDisplayRangeFocusRing : public nsDisplayItem
+{
+public:
+ nsDisplayRangeFocusRing(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayRangeFocusRing);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayRangeFocusRing() {
+ MOZ_COUNT_DTOR(nsDisplayRangeFocusRing);
+ }
+#endif
+
+ nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion *aInvalidRegion) override;
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+ virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("RangeFocusRing", TYPE_RANGE_FOCUS_RING)
+};
+
+nsDisplayItemGeometry*
+nsDisplayRangeFocusRing::AllocateGeometry(nsDisplayListBuilder* aBuilder)
+{
+ return new nsDisplayItemGenericImageGeometry(this, aBuilder);
+}
+
+void
+nsDisplayRangeFocusRing::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+{
+ auto geometry =
+ static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ bool snap;
+ aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
+ }
+
+ nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
+}
+
+nsRect
+nsDisplayRangeFocusRing::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
+{
+ *aSnap = false;
+ nsRect rect(ToReferenceFrame(), Frame()->GetSize());
+
+ // We want to paint as if specifying a border for ::-moz-focus-outer
+ // specifies an outline for our frame, so inflate by the border widths:
+ nsStyleContext* styleContext =
+ static_cast<nsRangeFrame*>(mFrame)->mOuterFocusStyle;
+ MOZ_ASSERT(styleContext, "We only exist if mOuterFocusStyle is non-null");
+ rect.Inflate(styleContext->StyleBorder()->GetComputedBorder());
+
+ return rect;
+}
+
+void
+nsDisplayRangeFocusRing::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ bool unused;
+ nsStyleContext* styleContext =
+ static_cast<nsRangeFrame*>(mFrame)->mOuterFocusStyle;
+ MOZ_ASSERT(styleContext, "We only exist if mOuterFocusStyle is non-null");
+
+ PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages()
+ ? PaintBorderFlags::SYNC_DECODE_IMAGES
+ : PaintBorderFlags();
+
+ DrawResult result =
+ nsCSSRendering::PaintBorder(mFrame->PresContext(), *aCtx, mFrame,
+ mVisibleRect, GetBounds(aBuilder, &unused),
+ styleContext, flags);
+
+ nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
+}
+
+void
+nsRangeFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ if (IsThemed()) {
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+ // Only create items for the thumb. Specifically, we do not want
+ // the track to paint, since *our* background is used to paint
+ // the track, and we don't want the unthemed track painting over
+ // the top of the themed track.
+ // This logic is copied from
+ // nsContainerFrame::BuildDisplayListForNonBlockChildren as
+ // called by BuildDisplayListForInline.
+ nsIFrame* thumb = mThumbDiv->GetPrimaryFrame();
+ if (thumb) {
+ nsDisplayListSet set(aLists, aLists.Content());
+ BuildDisplayListForChild(aBuilder, thumb, aDirtyRect, set, DISPLAY_CHILD_INLINE);
+ }
+ } else {
+ BuildDisplayListForInline(aBuilder, aDirtyRect, aLists);
+ }
+
+ // Draw a focus outline if appropriate:
+
+ if (!aBuilder->IsForPainting() ||
+ !IsVisibleForPainting(aBuilder)) {
+ // we don't want the focus ring item for hit-testing or if the item isn't
+ // in the area being [re]painted
+ return;
+ }
+
+ EventStates eventStates = mContent->AsElement()->State();
+ if (eventStates.HasState(NS_EVENT_STATE_DISABLED) ||
+ !eventStates.HasState(NS_EVENT_STATE_FOCUSRING)) {
+ return; // can't have focus or doesn't match :-moz-focusring
+ }
+
+ if (!mOuterFocusStyle ||
+ !mOuterFocusStyle->StyleBorder()->HasBorder()) {
+ // no ::-moz-focus-outer specified border (how style specifies a focus ring
+ // for range)
+ return;
+ }
+
+ const nsStyleDisplay *disp = StyleDisplay();
+ if (IsThemed(disp) &&
+ PresContext()->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) {
+ return; // the native theme displays its own visual indication of focus
+ }
+
+ aLists.Content()->AppendNewToTop(
+ new (aBuilder) nsDisplayRangeFocusRing(aBuilder, this));
+}
+
+void
+nsRangeFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsRangeFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+
+ NS_ASSERTION(mTrackDiv, "::-moz-range-track div must exist!");
+ NS_ASSERTION(mProgressDiv, "::-moz-range-progress div must exist!");
+ NS_ASSERTION(mThumbDiv, "::-moz-range-thumb div must exist!");
+ NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
+ "nsRangeFrame should not have continuations; if it does we "
+ "need to call RegUnregAccessKey only for the first.");
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ nsFormControlFrame::RegUnRegAccessKey(this, true);
+ }
+
+ WritingMode wm = aReflowInput.GetWritingMode();
+ nscoord computedBSize = aReflowInput.ComputedBSize();
+ if (computedBSize == NS_AUTOHEIGHT) {
+ computedBSize = 0;
+ }
+ LogicalSize
+ finalSize(wm,
+ aReflowInput.ComputedISize() +
+ aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm),
+ computedBSize +
+ aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm));
+ aDesiredSize.SetSize(wm, finalSize);
+
+ ReflowAnonymousContent(aPresContext, aDesiredSize, aReflowInput);
+
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+
+ nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
+ if (trackFrame) {
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, trackFrame);
+ }
+
+ nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
+ if (rangeProgressFrame) {
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, rangeProgressFrame);
+ }
+
+ nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
+ if (thumbFrame) {
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, thumbFrame);
+ }
+
+ FinishAndStoreOverflow(&aDesiredSize);
+
+ aStatus = NS_FRAME_COMPLETE;
+
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+void
+nsRangeFrame::ReflowAnonymousContent(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput)
+{
+ // The width/height of our content box, which is the available width/height
+ // for our anonymous content:
+ nscoord rangeFrameContentBoxWidth = aReflowInput.ComputedWidth();
+ nscoord rangeFrameContentBoxHeight = aReflowInput.ComputedHeight();
+ if (rangeFrameContentBoxHeight == NS_AUTOHEIGHT) {
+ rangeFrameContentBoxHeight = 0;
+ }
+
+ nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
+
+ if (trackFrame) { // display:none?
+
+ // Position the track:
+ // The idea here is that we allow content authors to style the width,
+ // height, border and padding of the track, but we ignore margin and
+ // positioning properties and do the positioning ourself to keep the center
+ // of the track's border box on the center of the nsRangeFrame's content
+ // box.
+
+ WritingMode wm = trackFrame->GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSize(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput trackReflowInput(aPresContext, aReflowInput,
+ trackFrame, availSize);
+
+ // Find the x/y position of the track frame such that it will be positioned
+ // as described above. These coordinates are with respect to the
+ // nsRangeFrame's border-box.
+ nscoord trackX = rangeFrameContentBoxWidth / 2;
+ nscoord trackY = rangeFrameContentBoxHeight / 2;
+
+ // Account for the track's border and padding (we ignore its margin):
+ trackX -= trackReflowInput.ComputedPhysicalBorderPadding().left +
+ trackReflowInput.ComputedWidth() / 2;
+ trackY -= trackReflowInput.ComputedPhysicalBorderPadding().top +
+ trackReflowInput.ComputedHeight() / 2;
+
+ // Make relative to our border box instead of our content box:
+ trackX += aReflowInput.ComputedPhysicalBorderPadding().left;
+ trackY += aReflowInput.ComputedPhysicalBorderPadding().top;
+
+ nsReflowStatus frameStatus;
+ ReflowOutput trackDesiredSize(aReflowInput);
+ ReflowChild(trackFrame, aPresContext, trackDesiredSize,
+ trackReflowInput, trackX, trackY, 0, frameStatus);
+ MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(frameStatus),
+ "We gave our child unconstrained height, so it should be complete");
+ FinishReflowChild(trackFrame, aPresContext, trackDesiredSize,
+ &trackReflowInput, trackX, trackY, 0);
+ }
+
+ nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
+
+ if (thumbFrame) { // display:none?
+ WritingMode wm = thumbFrame->GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSize(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput thumbReflowInput(aPresContext, aReflowInput,
+ thumbFrame, availSize);
+
+ // Where we position the thumb depends on its size, so we first reflow
+ // the thumb at {0,0} to obtain its size, then position it afterwards.
+
+ nsReflowStatus frameStatus;
+ ReflowOutput thumbDesiredSize(aReflowInput);
+ ReflowChild(thumbFrame, aPresContext, thumbDesiredSize,
+ thumbReflowInput, 0, 0, 0, frameStatus);
+ MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(frameStatus),
+ "We gave our child unconstrained height, so it should be complete");
+ FinishReflowChild(thumbFrame, aPresContext, thumbDesiredSize,
+ &thumbReflowInput, 0, 0, 0);
+ DoUpdateThumbPosition(thumbFrame, nsSize(aDesiredSize.Width(),
+ aDesiredSize.Height()));
+ }
+
+ nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
+
+ if (rangeProgressFrame) { // display:none?
+ WritingMode wm = rangeProgressFrame->GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSize(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput progressReflowInput(aPresContext, aReflowInput,
+ rangeProgressFrame, availSize);
+
+ // We first reflow the range-progress frame at {0,0} to obtain its
+ // unadjusted dimensions, then we adjust it to so that the appropriate edge
+ // ends at the thumb.
+
+ nsReflowStatus frameStatus;
+ ReflowOutput progressDesiredSize(aReflowInput);
+ ReflowChild(rangeProgressFrame, aPresContext,
+ progressDesiredSize, progressReflowInput, 0, 0,
+ 0, frameStatus);
+ MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(frameStatus),
+ "We gave our child unconstrained height, so it should be complete");
+ FinishReflowChild(rangeProgressFrame, aPresContext,
+ progressDesiredSize, &progressReflowInput, 0, 0, 0);
+ DoUpdateRangeProgressFrame(rangeProgressFrame, nsSize(aDesiredSize.Width(),
+ aDesiredSize.Height()));
+ }
+}
+
+#ifdef ACCESSIBILITY
+a11y::AccType
+nsRangeFrame::AccessibleType()
+{
+ return a11y::eHTMLRangeType;
+}
+#endif
+
+double
+nsRangeFrame::GetValueAsFractionOfRange()
+{
+ MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
+ dom::HTMLInputElement* input = static_cast<dom::HTMLInputElement*>(mContent);
+
+ MOZ_ASSERT(input->GetType() == NS_FORM_INPUT_RANGE);
+
+ Decimal value = input->GetValueAsDecimal();
+ Decimal minimum = input->GetMinimum();
+ Decimal maximum = input->GetMaximum();
+
+ MOZ_ASSERT(value.isFinite() && minimum.isFinite() && maximum.isFinite(),
+ "type=range should have a default maximum/minimum");
+
+ if (maximum <= minimum) {
+ MOZ_ASSERT(value == minimum, "Unsanitized value");
+ return 0.0;
+ }
+
+ MOZ_ASSERT(value >= minimum && value <= maximum, "Unsanitized value");
+
+ return ((value - minimum) / (maximum - minimum)).toDouble();
+}
+
+Decimal
+nsRangeFrame::GetValueAtEventPoint(WidgetGUIEvent* aEvent)
+{
+ MOZ_ASSERT(aEvent->mClass == eMouseEventClass ||
+ aEvent->mClass == eTouchEventClass,
+ "Unexpected event type - aEvent->mRefPoint may be meaningless");
+
+ MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
+ dom::HTMLInputElement* input = static_cast<dom::HTMLInputElement*>(mContent);
+
+ MOZ_ASSERT(input->GetType() == NS_FORM_INPUT_RANGE);
+
+ Decimal minimum = input->GetMinimum();
+ Decimal maximum = input->GetMaximum();
+ MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(),
+ "type=range should have a default maximum/minimum");
+ if (maximum <= minimum) {
+ return minimum;
+ }
+ Decimal range = maximum - minimum;
+
+ LayoutDeviceIntPoint absPoint;
+ if (aEvent->mClass == eTouchEventClass) {
+ MOZ_ASSERT(aEvent->AsTouchEvent()->mTouches.Length() == 1,
+ "Unexpected number of mTouches");
+ absPoint = aEvent->AsTouchEvent()->mTouches[0]->mRefPoint;
+ } else {
+ absPoint = aEvent->mRefPoint;
+ }
+ nsPoint point =
+ nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, absPoint, this);
+
+ if (point == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
+ // We don't want to change the current value for this error state.
+ return static_cast<dom::HTMLInputElement*>(mContent)->GetValueAsDecimal();
+ }
+
+ nsRect rangeContentRect = GetContentRectRelativeToSelf();
+ nsSize thumbSize;
+
+ if (IsThemed()) {
+ // We need to get the size of the thumb from the theme.
+ nsPresContext *presContext = PresContext();
+ bool notUsedCanOverride;
+ LayoutDeviceIntSize size;
+ presContext->GetTheme()->
+ GetMinimumWidgetSize(presContext, this, NS_THEME_RANGE_THUMB, &size,
+ &notUsedCanOverride);
+ thumbSize.width = presContext->DevPixelsToAppUnits(size.width);
+ thumbSize.height = presContext->DevPixelsToAppUnits(size.height);
+ MOZ_ASSERT(thumbSize.width > 0 && thumbSize.height > 0);
+ } else {
+ nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
+ if (thumbFrame) { // diplay:none?
+ thumbSize = thumbFrame->GetSize();
+ }
+ }
+
+ Decimal fraction;
+ if (IsHorizontal()) {
+ nscoord traversableDistance = rangeContentRect.width - thumbSize.width;
+ if (traversableDistance <= 0) {
+ return minimum;
+ }
+ nscoord posAtStart = rangeContentRect.x + thumbSize.width/2;
+ nscoord posAtEnd = posAtStart + traversableDistance;
+ nscoord posOfPoint = mozilla::clamped(point.x, posAtStart, posAtEnd);
+ fraction = Decimal(posOfPoint - posAtStart) / Decimal(traversableDistance);
+ if (IsRightToLeft()) {
+ fraction = Decimal(1) - fraction;
+ }
+ } else {
+ nscoord traversableDistance = rangeContentRect.height - thumbSize.height;
+ if (traversableDistance <= 0) {
+ return minimum;
+ }
+ nscoord posAtStart = rangeContentRect.y + thumbSize.height/2;
+ nscoord posAtEnd = posAtStart + traversableDistance;
+ nscoord posOfPoint = mozilla::clamped(point.y, posAtStart, posAtEnd);
+ // For a vertical range, the top (posAtStart) is the highest value, so we
+ // subtract the fraction from 1.0 to get that polarity correct.
+ fraction = Decimal(1) - Decimal(posOfPoint - posAtStart) / Decimal(traversableDistance);
+ }
+
+ MOZ_ASSERT(fraction >= Decimal(0) && fraction <= Decimal(1));
+ return minimum + fraction * range;
+}
+
+void
+nsRangeFrame::UpdateForValueChange()
+{
+ if (NS_SUBTREE_DIRTY(this)) {
+ return; // we're going to be updated when we reflow
+ }
+ nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
+ nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
+ if (!rangeProgressFrame && !thumbFrame) {
+ return; // diplay:none?
+ }
+ if (rangeProgressFrame) {
+ DoUpdateRangeProgressFrame(rangeProgressFrame, GetSize());
+ }
+ if (thumbFrame) {
+ DoUpdateThumbPosition(thumbFrame, GetSize());
+ }
+ if (IsThemed()) {
+ // We don't know the exact dimensions or location of the thumb when native
+ // theming is applied, so we just repaint the entire range.
+ InvalidateFrame();
+ }
+
+#ifdef ACCESSIBILITY
+ nsAccessibilityService* accService = nsIPresShell::AccService();
+ if (accService) {
+ accService->RangeValueChanged(PresContext()->PresShell(), mContent);
+ }
+#endif
+
+ SchedulePaint();
+}
+
+void
+nsRangeFrame::DoUpdateThumbPosition(nsIFrame* aThumbFrame,
+ const nsSize& aRangeSize)
+{
+ MOZ_ASSERT(aThumbFrame);
+
+ // The idea here is that we want to position the thumb so that the center
+ // of the thumb is on an imaginary line drawn from the middle of one edge
+ // of the range frame's content box to the middle of the opposite edge of
+ // its content box (the opposite edges being the left/right edge if the
+ // range is horizontal, or else the top/bottom edges if the range is
+ // vertical). How far along this line the center of the thumb is placed
+ // depends on the value of the range.
+
+ nsMargin borderAndPadding = GetUsedBorderAndPadding();
+ nsPoint newPosition(borderAndPadding.left, borderAndPadding.top);
+
+ nsSize rangeContentBoxSize(aRangeSize);
+ rangeContentBoxSize.width -= borderAndPadding.LeftRight();
+ rangeContentBoxSize.height -= borderAndPadding.TopBottom();
+
+ nsSize thumbSize = aThumbFrame->GetSize();
+ double fraction = GetValueAsFractionOfRange();
+ MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0);
+
+ if (IsHorizontal()) {
+ if (thumbSize.width < rangeContentBoxSize.width) {
+ nscoord traversableDistance =
+ rangeContentBoxSize.width - thumbSize.width;
+ if (IsRightToLeft()) {
+ newPosition.x += NSToCoordRound((1.0 - fraction) * traversableDistance);
+ } else {
+ newPosition.x += NSToCoordRound(fraction * traversableDistance);
+ }
+ newPosition.y += (rangeContentBoxSize.height - thumbSize.height)/2;
+ }
+ } else {
+ if (thumbSize.height < rangeContentBoxSize.height) {
+ nscoord traversableDistance =
+ rangeContentBoxSize.height - thumbSize.height;
+ newPosition.x += (rangeContentBoxSize.width - thumbSize.width)/2;
+ newPosition.y += NSToCoordRound((1.0 - fraction) * traversableDistance);
+ }
+ }
+ aThumbFrame->SetPosition(newPosition);
+}
+
+void
+nsRangeFrame::DoUpdateRangeProgressFrame(nsIFrame* aRangeProgressFrame,
+ const nsSize& aRangeSize)
+{
+ MOZ_ASSERT(aRangeProgressFrame);
+
+ // The idea here is that we want to position the ::-moz-range-progress
+ // pseudo-element so that the center line running along its length is on the
+ // corresponding center line of the nsRangeFrame's content box. In the other
+ // dimension, we align the "start" edge of the ::-moz-range-progress
+ // pseudo-element's border-box with the corresponding edge of the
+ // nsRangeFrame's content box, and we size the progress element's border-box
+ // to have a length of GetValueAsFractionOfRange() times the nsRangeFrame's
+ // content-box size.
+
+ nsMargin borderAndPadding = GetUsedBorderAndPadding();
+ nsSize progSize = aRangeProgressFrame->GetSize();
+ nsRect progRect(borderAndPadding.left, borderAndPadding.top,
+ progSize.width, progSize.height);
+
+ nsSize rangeContentBoxSize(aRangeSize);
+ rangeContentBoxSize.width -= borderAndPadding.LeftRight();
+ rangeContentBoxSize.height -= borderAndPadding.TopBottom();
+
+ double fraction = GetValueAsFractionOfRange();
+ MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0);
+
+ if (IsHorizontal()) {
+ nscoord progLength = NSToCoordRound(fraction * rangeContentBoxSize.width);
+ if (IsRightToLeft()) {
+ progRect.x += rangeContentBoxSize.width - progLength;
+ }
+ progRect.y += (rangeContentBoxSize.height - progSize.height)/2;
+ progRect.width = progLength;
+ } else {
+ nscoord progLength = NSToCoordRound(fraction * rangeContentBoxSize.height);
+ progRect.x += (rangeContentBoxSize.width - progSize.width)/2;
+ progRect.y += rangeContentBoxSize.height - progLength;
+ progRect.height = progLength;
+ }
+ aRangeProgressFrame->SetRect(progRect);
+}
+
+nsresult
+nsRangeFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ NS_ASSERTION(mTrackDiv, "The track div must exist!");
+ NS_ASSERTION(mThumbDiv, "The thumb div must exist!");
+
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::value ||
+ aAttribute == nsGkAtoms::min ||
+ aAttribute == nsGkAtoms::max ||
+ aAttribute == nsGkAtoms::step) {
+ // We want to update the position of the thumb, except in one special
+ // case: If the value attribute is being set, it is possible that we are
+ // in the middle of a type change away from type=range, under the
+ // SetAttr(..., nsGkAtoms::value, ...) call in HTMLInputElement::
+ // HandleTypeChange. In that case the HTMLInputElement's type will
+ // already have changed, and if we call UpdateForValueChange()
+ // we'll fail the asserts under that call that check the type of our
+ // HTMLInputElement. Given that we're changing away from being a range
+ // and this frame will shortly be destroyed, there's no point in calling
+ // UpdateForValueChange() anyway.
+ MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
+ bool typeIsRange = static_cast<dom::HTMLInputElement*>(mContent)->GetType() ==
+ NS_FORM_INPUT_RANGE;
+ // If script changed the <input>'s type before setting these attributes
+ // then we don't need to do anything since we are going to be reframed.
+ if (typeIsRange) {
+ UpdateForValueChange();
+ }
+ } else if (aAttribute == nsGkAtoms::orient) {
+ PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eResize,
+ NS_FRAME_IS_DIRTY);
+ }
+ }
+
+ return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+LogicalSize
+nsRangeFrame::ComputeAutoSize(nsRenderingContext* aRenderingContext,
+ WritingMode aWM,
+ const LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const LogicalSize& aMargin,
+ const LogicalSize& aBorder,
+ const LogicalSize& aPadding,
+ ComputeSizeFlags aFlags)
+{
+ nscoord oneEm = NSToCoordRound(StyleFont()->mFont.size *
+ nsLayoutUtils::FontSizeInflationFor(this)); // 1em
+
+ bool isInlineOriented = IsInlineOriented();
+
+ const WritingMode wm = GetWritingMode();
+ LogicalSize autoSize(wm);
+
+ // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
+ // given too small a size when we're natively themed. If we're themed, we set
+ // our "thickness" dimension to zero below and rely on that
+ // GetMinimumWidgetSize check to correct that dimension to the natural
+ // thickness of a slider in the current theme.
+
+ if (isInlineOriented) {
+ autoSize.ISize(wm) = LONG_SIDE_TO_SHORT_SIDE_RATIO * oneEm;
+ autoSize.BSize(wm) = IsThemed() ? 0 : oneEm;
+ } else {
+ autoSize.ISize(wm) = IsThemed() ? 0 : oneEm;
+ autoSize.BSize(wm) = LONG_SIDE_TO_SHORT_SIDE_RATIO * oneEm;
+ }
+
+ return autoSize.ConvertTo(aWM, wm);
+}
+
+nscoord
+nsRangeFrame::GetMinISize(nsRenderingContext *aRenderingContext)
+{
+ // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
+ // given too small a size when we're natively themed. If we aren't native
+ // themed, we don't mind how small we're sized.
+ return nscoord(0);
+}
+
+nscoord
+nsRangeFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
+{
+ bool isInline = IsInlineOriented();
+
+ if (!isInline && IsThemed()) {
+ // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
+ // given too small a size when we're natively themed. We return zero and
+ // depend on that correction to get our "natural" width when we're a
+ // vertical slider.
+ return 0;
+ }
+
+ nscoord prefISize = NSToCoordRound(StyleFont()->mFont.size *
+ nsLayoutUtils::FontSizeInflationFor(this)); // 1em
+
+ if (isInline) {
+ prefISize *= LONG_SIDE_TO_SHORT_SIDE_RATIO;
+ }
+
+ return prefISize;
+}
+
+bool
+nsRangeFrame::IsHorizontal() const
+{
+ dom::HTMLInputElement* element =
+ static_cast<dom::HTMLInputElement*>(mContent);
+ return element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
+ nsGkAtoms::horizontal, eCaseMatters) ||
+ (!element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
+ nsGkAtoms::vertical, eCaseMatters) &&
+ GetWritingMode().IsVertical() ==
+ element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
+ nsGkAtoms::block, eCaseMatters));
+}
+
+double
+nsRangeFrame::GetMin() const
+{
+ return static_cast<dom::HTMLInputElement*>(mContent)->GetMinimum().toDouble();
+}
+
+double
+nsRangeFrame::GetMax() const
+{
+ return static_cast<dom::HTMLInputElement*>(mContent)->GetMaximum().toDouble();
+}
+
+double
+nsRangeFrame::GetValue() const
+{
+ return static_cast<dom::HTMLInputElement*>(mContent)->GetValueAsDecimal().toDouble();
+}
+
+nsIAtom*
+nsRangeFrame::GetType() const
+{
+ return nsGkAtoms::rangeFrame;
+}
+
+#define STYLES_DISABLING_NATIVE_THEMING \
+ NS_AUTHOR_SPECIFIED_BACKGROUND | \
+ NS_AUTHOR_SPECIFIED_PADDING | \
+ NS_AUTHOR_SPECIFIED_BORDER
+
+bool
+nsRangeFrame::ShouldUseNativeStyle() const
+{
+ nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
+ nsIFrame* progressFrame = mProgressDiv->GetPrimaryFrame();
+ nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
+
+ return (StyleDisplay()->mAppearance == NS_THEME_RANGE) &&
+ !PresContext()->HasAuthorSpecifiedRules(this,
+ (NS_AUTHOR_SPECIFIED_BORDER |
+ NS_AUTHOR_SPECIFIED_BACKGROUND)) &&
+ trackFrame &&
+ !PresContext()->HasAuthorSpecifiedRules(trackFrame,
+ STYLES_DISABLING_NATIVE_THEMING) &&
+ progressFrame &&
+ !PresContext()->HasAuthorSpecifiedRules(progressFrame,
+ STYLES_DISABLING_NATIVE_THEMING) &&
+ thumbFrame &&
+ !PresContext()->HasAuthorSpecifiedRules(thumbFrame,
+ STYLES_DISABLING_NATIVE_THEMING);
+}
+
+Element*
+nsRangeFrame::GetPseudoElement(CSSPseudoElementType aType)
+{
+ if (aType == CSSPseudoElementType::mozRangeTrack) {
+ return mTrackDiv;
+ }
+
+ if (aType == CSSPseudoElementType::mozRangeThumb) {
+ return mThumbDiv;
+ }
+
+ if (aType == CSSPseudoElementType::mozRangeProgress) {
+ return mProgressDiv;
+ }
+
+ return nsContainerFrame::GetPseudoElement(aType);
+}
+
+nsStyleContext*
+nsRangeFrame::GetAdditionalStyleContext(int32_t aIndex) const
+{
+ // We only implement this so that SetAdditionalStyleContext will be
+ // called if style changes that would change the -moz-focus-outer
+ // pseudo-element have occurred.
+ if (aIndex != 0) {
+ return nullptr;
+ }
+ return mOuterFocusStyle;
+}
+
+void
+nsRangeFrame::SetAdditionalStyleContext(int32_t aIndex,
+ nsStyleContext* aStyleContext)
+{
+ MOZ_ASSERT(aIndex == 0,
+ "GetAdditionalStyleContext is handling other indexes?");
+
+#ifdef DEBUG
+ if (mOuterFocusStyle) {
+ mOuterFocusStyle->FrameRelease();
+ }
+#endif
+
+ // The -moz-focus-outer pseudo-element's style has changed.
+ mOuterFocusStyle = aStyleContext;
+
+#ifdef DEBUG
+ if (mOuterFocusStyle) {
+ mOuterFocusStyle->FrameAddRef();
+ }
+#endif
+}
diff --git a/layout/forms/nsRangeFrame.h b/layout/forms/nsRangeFrame.h
new file mode 100644
index 000000000..8a2d34a40
--- /dev/null
+++ b/layout/forms/nsRangeFrame.h
@@ -0,0 +1,215 @@
+/* -*- 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 nsRangeFrame_h___
+#define nsRangeFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Decimal.h"
+#include "mozilla/EventForwards.h"
+#include "nsContainerFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsIDOMEventListener.h"
+#include "nsCOMPtr.h"
+
+class nsDisplayRangeFocusRing;
+
+class nsRangeFrame : public nsContainerFrame,
+ public nsIAnonymousContentCreator
+{
+ friend nsIFrame*
+ NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ friend class nsDisplayRangeFocusRing;
+
+ explicit nsRangeFrame(nsStyleContext* aContext);
+ virtual ~nsRangeFrame();
+
+ typedef mozilla::CSSPseudoElementType CSSPseudoElementType;
+ typedef mozilla::dom::Element Element;
+
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsRangeFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIFrame overrides
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(NS_LITERAL_STRING("Range"), aResult);
+ }
+#endif
+
+ virtual bool IsLeaf() const override { return true; }
+
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+ // nsIAnonymousContentCreator
+ virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
+ virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual mozilla::LogicalSize
+ ComputeAutoSize(nsRenderingContext* aRenderingContext,
+ mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorder,
+ const mozilla::LogicalSize& aPadding,
+ ComputeSizeFlags aFlags) override;
+
+ virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override;
+ virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override;
+
+ virtual nsIAtom* GetType() const override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsContainerFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
+ }
+
+ nsStyleContext* GetAdditionalStyleContext(int32_t aIndex) const override;
+ void SetAdditionalStyleContext(int32_t aIndex,
+ nsStyleContext* aStyleContext) override;
+
+ /**
+ * Returns true if the slider's thumb moves horizontally, or else false if it
+ * moves vertically.
+ */
+ bool IsHorizontal() const;
+
+ /**
+ * Returns true if the slider is oriented along the inline axis.
+ */
+ bool IsInlineOriented() const {
+ return IsHorizontal() != GetWritingMode().IsVertical();
+ }
+
+ /**
+ * Returns true if the slider's thumb moves right-to-left for increasing
+ * values; only relevant when IsHorizontal() is true.
+ */
+ bool IsRightToLeft() const {
+ MOZ_ASSERT(IsHorizontal());
+ mozilla::WritingMode wm = GetWritingMode();
+ return wm.IsVertical() ? wm.IsVerticalRL() : !wm.IsBidiLTR();
+ }
+
+ double GetMin() const;
+ double GetMax() const;
+ double GetValue() const;
+
+ /**
+ * Returns the input element's value as a fraction of the difference between
+ * the input's minimum and its maximum (i.e. returns 0.0 when the value is
+ * the same as the minimum, and returns 1.0 when the value is the same as the
+ * maximum).
+ */
+ double GetValueAsFractionOfRange();
+
+ /**
+ * Returns whether the frame and its child should use the native style.
+ */
+ bool ShouldUseNativeStyle() const;
+
+ mozilla::Decimal GetValueAtEventPoint(mozilla::WidgetGUIEvent* aEvent);
+
+ /**
+ * Helper that's used when the value of the range changes to reposition the
+ * thumb, resize the range-progress element, and schedule a repaint. (This
+ * does not reflow, since the position and size of the thumb and
+ * range-progress element do not affect the position or size of any other
+ * frames.)
+ */
+ void UpdateForValueChange();
+
+ virtual Element* GetPseudoElement(CSSPseudoElementType aType) override;
+
+private:
+
+ nsresult MakeAnonymousDiv(Element** aResult,
+ CSSPseudoElementType aPseudoType,
+ nsTArray<ContentInfo>& aElements);
+
+ // Helper function which reflows the anonymous div frames.
+ void ReflowAnonymousContent(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput);
+
+ void DoUpdateThumbPosition(nsIFrame* aThumbFrame,
+ const nsSize& aRangeSize);
+
+ void DoUpdateRangeProgressFrame(nsIFrame* aProgressFrame,
+ const nsSize& aRangeSize);
+
+ /**
+ * The div used to show the ::-moz-range-track pseudo-element.
+ * @see nsRangeFrame::CreateAnonymousContent
+ */
+ nsCOMPtr<Element> mTrackDiv;
+
+ /**
+ * The div used to show the ::-moz-range-progress pseudo-element, which is
+ * used to (optionally) style the specific chunk of track leading up to the
+ * thumb's current position.
+ * @see nsRangeFrame::CreateAnonymousContent
+ */
+ nsCOMPtr<Element> mProgressDiv;
+
+ /**
+ * The div used to show the ::-moz-range-thumb pseudo-element.
+ * @see nsRangeFrame::CreateAnonymousContent
+ */
+ nsCOMPtr<Element> mThumbDiv;
+
+ /**
+ * Cached style context for -moz-focus-outer CSS pseudo-element style.
+ */
+ RefPtr<nsStyleContext> mOuterFocusStyle;
+
+ class DummyTouchListener final : public nsIDOMEventListener
+ {
+ private:
+ ~DummyTouchListener() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override
+ {
+ return NS_OK;
+ }
+ };
+
+ /**
+ * A no-op touch-listener used for APZ purposes (see nsRangeFrame::Init).
+ */
+ RefPtr<DummyTouchListener> mDummyTouchListener;
+};
+
+#endif
diff --git a/layout/forms/nsSelectsAreaFrame.cpp b/layout/forms/nsSelectsAreaFrame.cpp
new file mode 100644
index 000000000..dd613ae9f
--- /dev/null
+++ b/layout/forms/nsSelectsAreaFrame.cpp
@@ -0,0 +1,206 @@
+/* -*- 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 "nsSelectsAreaFrame.h"
+#include "nsIContent.h"
+#include "nsListControlFrame.h"
+#include "nsDisplayList.h"
+#include "WritingModes.h"
+
+using namespace mozilla;
+
+nsContainerFrame*
+NS_NewSelectsAreaFrame(nsIPresShell* aShell, nsStyleContext* aContext, nsFrameState aFlags)
+{
+ nsSelectsAreaFrame* it = new (aShell) nsSelectsAreaFrame(aContext);
+
+ // We need NS_BLOCK_FLOAT_MGR to ensure that the options inside the select
+ // aren't expanded by right floats outside the select.
+ it->AddStateBits(aFlags | NS_BLOCK_FLOAT_MGR);
+
+ return it;
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSelectsAreaFrame)
+
+//---------------------------------------------------------
+/**
+ * This wrapper class lets us redirect mouse hits from the child frame of
+ * an option element to the element's own frame.
+ * REVIEW: This is what nsSelectsAreaFrame::GetFrameForPoint used to do
+ */
+class nsDisplayOptionEventGrabber : public nsDisplayWrapList {
+public:
+ nsDisplayOptionEventGrabber(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayItem* aItem)
+ : nsDisplayWrapList(aBuilder, aFrame, aItem) {}
+ nsDisplayOptionEventGrabber(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList)
+ : nsDisplayWrapList(aBuilder, aFrame, aList) {}
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
+ virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+ NS_DISPLAY_DECL_NAME("OptionEventGrabber", TYPE_OPTION_EVENT_GRABBER)
+};
+
+void nsDisplayOptionEventGrabber::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect, HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
+{
+ nsTArray<nsIFrame*> outFrames;
+ mList.HitTest(aBuilder, aRect, aState, &outFrames);
+
+ for (uint32_t i = 0; i < outFrames.Length(); i++) {
+ nsIFrame* selectedFrame = outFrames.ElementAt(i);
+ while (selectedFrame &&
+ !(selectedFrame->GetContent() &&
+ selectedFrame->GetContent()->IsHTMLElement(nsGkAtoms::option))) {
+ selectedFrame = selectedFrame->GetParent();
+ }
+ if (selectedFrame) {
+ aOutFrames->AppendElement(selectedFrame);
+ } else {
+ // keep the original result, which could be this frame
+ aOutFrames->AppendElement(outFrames.ElementAt(i));
+ }
+ }
+}
+
+class nsOptionEventGrabberWrapper : public nsDisplayWrapper
+{
+public:
+ nsOptionEventGrabberWrapper() {}
+ virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame, nsDisplayList* aList) {
+ return new (aBuilder) nsDisplayOptionEventGrabber(aBuilder, aFrame, aList);
+ }
+ virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem) {
+ return new (aBuilder) nsDisplayOptionEventGrabber(aBuilder, aItem->Frame(), aItem);
+ }
+};
+
+static nsListControlFrame* GetEnclosingListFrame(nsIFrame* aSelectsAreaFrame)
+{
+ nsIFrame* frame = aSelectsAreaFrame->GetParent();
+ while (frame) {
+ if (frame->GetType() == nsGkAtoms::listControlFrame)
+ return static_cast<nsListControlFrame*>(frame);
+ frame = frame->GetParent();
+ }
+ return nullptr;
+}
+
+class nsDisplayListFocus : public nsDisplayItem {
+public:
+ nsDisplayListFocus(nsDisplayListBuilder* aBuilder,
+ nsSelectsAreaFrame* aFrame) :
+ nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayListFocus);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayListFocus() {
+ MOZ_COUNT_DTOR(nsDisplayListFocus);
+ }
+#endif
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override {
+ *aSnap = false;
+ // override bounds because the list item focus ring may extend outside
+ // the nsSelectsAreaFrame
+ nsListControlFrame* listFrame = GetEnclosingListFrame(Frame());
+ return listFrame->GetVisualOverflowRectRelativeToSelf() +
+ listFrame->GetOffsetToCrossDoc(ReferenceFrame());
+ }
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override {
+ nsListControlFrame* listFrame = GetEnclosingListFrame(Frame());
+ // listFrame must be non-null or we wouldn't get called.
+ listFrame->PaintFocus(aCtx->GetDrawTarget(),
+ aBuilder->ToReferenceFrame(listFrame));
+ }
+ NS_DISPLAY_DECL_NAME("ListFocus", TYPE_LIST_FOCUS)
+};
+
+void
+nsSelectsAreaFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ if (!aBuilder->IsForEventDelivery()) {
+ BuildDisplayListInternal(aBuilder, aDirtyRect, aLists);
+ return;
+ }
+
+ nsDisplayListCollection set;
+ BuildDisplayListInternal(aBuilder, aDirtyRect, set);
+
+ nsOptionEventGrabberWrapper wrapper;
+ wrapper.WrapLists(aBuilder, this, set, aLists);
+}
+
+void
+nsSelectsAreaFrame::BuildDisplayListInternal(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+
+ nsListControlFrame* listFrame = GetEnclosingListFrame(this);
+ if (listFrame && listFrame->IsFocused()) {
+ // we can't just associate the display item with the list frame,
+ // because then the list's scrollframe won't clip it (the scrollframe
+ // only clips contained descendants).
+ aLists.Outlines()->AppendNewToTop(new (aBuilder)
+ nsDisplayListFocus(aBuilder, this));
+ }
+}
+
+void
+nsSelectsAreaFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ nsListControlFrame* list = GetEnclosingListFrame(this);
+ NS_ASSERTION(list,
+ "Must have an nsListControlFrame! Frame constructor is "
+ "broken");
+
+ bool isInDropdownMode = list->IsInDropDownMode();
+
+ // See similar logic in nsListControlFrame::Reflow and
+ // nsListControlFrame::ReflowAsDropdown. We need to match it here.
+ WritingMode wm = aReflowInput.GetWritingMode();
+ nscoord oldBSize;
+ if (isInDropdownMode) {
+ // Store the block size now in case it changes during
+ // nsBlockFrame::Reflow for some odd reason.
+ if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
+ oldBSize = BSize(wm);
+ } else {
+ oldBSize = NS_UNCONSTRAINEDSIZE;
+ }
+ }
+
+ nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
+
+ // Check whether we need to suppress scrollbar updates. We want to do
+ // that if we're in a possible first pass and our block size of a row
+ // has changed.
+ if (list->MightNeedSecondPass()) {
+ nscoord newBSizeOfARow = list->CalcBSizeOfARow();
+ // We'll need a second pass if our block size of a row changed. For
+ // comboboxes, we'll also need it if our block size changed. If
+ // we're going to do a second pass, suppress scrollbar updates for
+ // this pass.
+ if (newBSizeOfARow != mBSizeOfARow ||
+ (isInDropdownMode && (oldBSize != aDesiredSize.BSize(wm) ||
+ oldBSize != BSize(wm)))) {
+ mBSizeOfARow = newBSizeOfARow;
+ list->SetSuppressScrollbarUpdate(true);
+ }
+ }
+}
diff --git a/layout/forms/nsSelectsAreaFrame.h b/layout/forms/nsSelectsAreaFrame.h
new file mode 100644
index 000000000..3aac8a837
--- /dev/null
+++ b/layout/forms/nsSelectsAreaFrame.h
@@ -0,0 +1,49 @@
+/* -*- 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 nsSelectsAreaFrame_h___
+#define nsSelectsAreaFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsBlockFrame.h"
+
+class nsSelectsAreaFrame : public nsBlockFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsContainerFrame* NS_NewSelectsAreaFrame(nsIPresShell* aShell,
+ nsStyleContext* aContext,
+ nsFrameState aFlags);
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ void BuildDisplayListInternal(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists);
+
+ virtual void Reflow(nsPresContext* aCX,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ nscoord BSizeOfARow() const { return mBSizeOfARow; }
+
+protected:
+ explicit nsSelectsAreaFrame(nsStyleContext* aContext) :
+ nsBlockFrame(aContext),
+ mBSizeOfARow(0)
+ {}
+
+ // We cache the block size of a single row so that changes to the
+ // "size" attribute, padding, etc. can all be handled with only one
+ // reflow. We'll have to reflow twice if someone changes our font
+ // size or something like that, so that the block size of our options
+ // will change.
+ nscoord mBSizeOfARow;
+};
+
+#endif /* nsSelectsAreaFrame_h___ */
diff --git a/layout/forms/nsTextControlFrame.cpp b/layout/forms/nsTextControlFrame.cpp
new file mode 100644
index 000000000..f85bc2a80
--- /dev/null
+++ b/layout/forms/nsTextControlFrame.cpp
@@ -0,0 +1,1506 @@
+/* -*- 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/DebugOnly.h"
+
+#include "nsCOMPtr.h"
+#include "nsFontMetrics.h"
+#include "nsTextControlFrame.h"
+#include "nsIPlaintextEditor.h"
+#include "nsCaret.h"
+#include "nsCSSPseudoElements.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIEditor.h"
+#include "nsIEditorIMESupport.h"
+#include "nsIPhonetic.h"
+#include "nsTextFragment.h"
+#include "nsIDOMHTMLTextAreaElement.h"
+#include "nsNameSpaceManager.h"
+#include "nsFormControlFrame.h" //for registering accesskeys
+
+#include "nsIContent.h"
+#include "nsPresContext.h"
+#include "nsRenderingContext.h"
+#include "nsGkAtoms.h"
+#include "nsLayoutUtils.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIPresShell.h"
+
+#include <algorithm>
+#include "nsIDOMNodeList.h" //for selection setting helper func
+#include "nsIDOMRange.h" //for selection setting helper func
+#include "nsPIDOMWindow.h" //needed for notify selection changed to update the menus ect.
+#include "nsIDOMNode.h"
+
+#include "nsIDOMText.h" //for multiline getselection
+#include "nsFocusManager.h"
+#include "nsPresState.h"
+#include "nsContentList.h"
+#include "nsAttrValueInlines.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/TextEditRules.h"
+#include "nsContentUtils.h"
+#include "nsTextNode.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/HTMLTextAreaElement.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/MathAlgorithms.h"
+#include "nsFrameSelection.h"
+
+#define DEFAULT_COLUMN_WIDTH 20
+
+using namespace mozilla;
+
+nsIFrame*
+NS_NewTextControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsTextControlFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTextControlFrame)
+
+NS_QUERYFRAME_HEAD(nsTextControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+ NS_QUERYFRAME_ENTRY(nsITextControlFrame)
+ NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+#ifdef ACCESSIBILITY
+a11y::AccType
+nsTextControlFrame::AccessibleType()
+{
+ return a11y::eHTMLTextFieldType;
+}
+#endif
+
+#ifdef DEBUG
+class EditorInitializerEntryTracker {
+public:
+ explicit EditorInitializerEntryTracker(nsTextControlFrame &frame)
+ : mFrame(frame)
+ , mFirstEntry(false)
+ {
+ if (!mFrame.mInEditorInitialization) {
+ mFrame.mInEditorInitialization = true;
+ mFirstEntry = true;
+ }
+ }
+ ~EditorInitializerEntryTracker()
+ {
+ if (mFirstEntry) {
+ mFrame.mInEditorInitialization = false;
+ }
+ }
+ bool EnteredMoreThanOnce() const { return !mFirstEntry; }
+private:
+ nsTextControlFrame &mFrame;
+ bool mFirstEntry;
+};
+#endif
+
+nsTextControlFrame::nsTextControlFrame(nsStyleContext* aContext)
+ : nsContainerFrame(aContext)
+ , mEditorHasBeenInitialized(false)
+ , mIsProcessing(false)
+#ifdef DEBUG
+ , mInEditorInitialization(false)
+#endif
+{
+}
+
+nsTextControlFrame::~nsTextControlFrame()
+{
+}
+
+void
+nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ mScrollEvent.Revoke();
+
+ EditorInitializer* initializer = Properties().Get(TextControlInitializer());
+ if (initializer) {
+ initializer->Revoke();
+ Properties().Delete(TextControlInitializer());
+ }
+
+ // Unbind the text editor state object from the frame. The editor will live
+ // on, but things like controllers will be released.
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+ txtCtrl->UnbindFromFrame(this);
+
+ nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
+
+ nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+nsIAtom*
+nsTextControlFrame::GetType() const
+{
+ return nsGkAtoms::textInputFrame;
+}
+
+LogicalSize
+nsTextControlFrame::CalcIntrinsicSize(nsRenderingContext* aRenderingContext,
+ WritingMode aWM,
+ float aFontSizeInflation) const
+{
+ LogicalSize intrinsicSize(aWM);
+ // Get leading and the Average/MaxAdvance char width
+ nscoord lineHeight = 0;
+ nscoord charWidth = 0;
+ nscoord charMaxAdvance = 0;
+
+ RefPtr<nsFontMetrics> fontMet =
+ nsLayoutUtils::GetFontMetricsForFrame(this, aFontSizeInflation);
+
+ lineHeight =
+ ReflowInput::CalcLineHeight(GetContent(), StyleContext(),
+ NS_AUTOHEIGHT, aFontSizeInflation);
+ charWidth = fontMet->AveCharWidth();
+ charMaxAdvance = fontMet->MaxAdvance();
+
+ // Set the width equal to the width in characters
+ int32_t cols = GetCols();
+ intrinsicSize.ISize(aWM) = cols * charWidth;
+
+ // To better match IE, take the maximum character width(in twips) and remove
+ // 4 pixels add this on as additional padding(internalPadding). But only do
+ // this if we think we have a fixed-width font.
+ if (mozilla::Abs(charWidth - charMaxAdvance) > (unsigned)nsPresContext::CSSPixelsToAppUnits(1)) {
+ nscoord internalPadding =
+ std::max(0, charMaxAdvance - nsPresContext::CSSPixelsToAppUnits(4));
+ nscoord t = nsPresContext::CSSPixelsToAppUnits(1);
+ // Round to a multiple of t
+ nscoord rest = internalPadding % t;
+ if (rest < t - rest) {
+ internalPadding -= rest;
+ } else {
+ internalPadding += t - rest;
+ }
+ // Now add the extra padding on (so that small input sizes work well)
+ intrinsicSize.ISize(aWM) += internalPadding;
+ } else {
+ // This is to account for the anonymous <br> having a 1 twip width
+ // in Full Standards mode, see BRFrame::Reflow and bug 228752.
+ if (PresContext()->CompatibilityMode() == eCompatibility_FullStandards) {
+ intrinsicSize.ISize(aWM) += 1;
+ }
+ }
+
+ // Increment width with cols * letter-spacing.
+ {
+ const nsStyleCoord& lsCoord = StyleText()->mLetterSpacing;
+ if (eStyleUnit_Coord == lsCoord.GetUnit()) {
+ nscoord letterSpacing = lsCoord.GetCoordValue();
+ if (letterSpacing != 0) {
+ intrinsicSize.ISize(aWM) += cols * letterSpacing;
+ }
+ }
+ }
+
+ // Set the height equal to total number of rows (times the height of each
+ // line, of course)
+ intrinsicSize.BSize(aWM) = lineHeight * GetRows();
+
+ // Add in the size of the scrollbars for textarea
+ if (IsTextArea()) {
+ nsIFrame* first = PrincipalChildList().FirstChild();
+
+ nsIScrollableFrame *scrollableFrame = do_QueryFrame(first);
+ NS_ASSERTION(scrollableFrame, "Child must be scrollable");
+
+ if (scrollableFrame) {
+ LogicalMargin scrollbarSizes(aWM,
+ scrollableFrame->GetDesiredScrollbarSizes(PresContext(),
+ aRenderingContext));
+
+ intrinsicSize.ISize(aWM) += scrollbarSizes.IStartEnd(aWM);
+ intrinsicSize.BSize(aWM) += scrollbarSizes.BStartEnd(aWM);
+ }
+ }
+ return intrinsicSize;
+}
+
+nsresult
+nsTextControlFrame::EnsureEditorInitialized()
+{
+ // This method initializes our editor, if needed.
+
+ // This code used to be called from CreateAnonymousContent(), but
+ // when the editor set the initial string, it would trigger a
+ // PresShell listener which called FlushPendingNotifications()
+ // during frame construction. This was causing other form controls
+ // to display wrong values. Additionally, calling this every time
+ // a text frame control is instantiated means that we're effectively
+ // instantiating the editor for all text fields, even if they
+ // never get used. So, now this method is being called lazily only
+ // when we actually need an editor.
+
+ if (mEditorHasBeenInitialized)
+ return NS_OK;
+
+ nsIDocument* doc = mContent->GetComposedDoc();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ nsWeakFrame weakFrame(this);
+
+ // Flush out content on our document. Have to do this, because script
+ // blockers don't prevent the sink flushing out content and notifying in the
+ // process, which can destroy frames.
+ doc->FlushPendingNotifications(Flush_ContentAndNotify);
+ NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_ERROR_FAILURE);
+
+ // Make sure that editor init doesn't do things that would kill us off
+ // (especially off the script blockers it'll create for its DOM mutations).
+ {
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ MOZ_ASSERT(txtCtrl, "Content not a text control element");
+
+ // Hide selection changes during the initialization, as webpages should not
+ // be aware of these initializations
+ AutoHideSelectionChanges hideSelectionChanges(txtCtrl->GetConstFrameSelection());
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ // Time to mess with our security context... See comments in GetValue()
+ // for why this is needed.
+ mozilla::dom::AutoNoJSAPI nojsapi;
+
+ // Make sure that we try to focus the content even if the method fails
+ class EnsureSetFocus {
+ public:
+ explicit EnsureSetFocus(nsTextControlFrame* aFrame)
+ : mFrame(aFrame) {}
+ ~EnsureSetFocus() {
+ if (nsContentUtils::IsFocusedContent(mFrame->GetContent()))
+ mFrame->SetFocus(true, false);
+ }
+ private:
+ nsTextControlFrame *mFrame;
+ };
+ EnsureSetFocus makeSureSetFocusHappens(this);
+
+#ifdef DEBUG
+ // Make sure we are not being called again until we're finished.
+ // If reentrancy happens, just pretend that we don't have an editor.
+ const EditorInitializerEntryTracker tracker(*this);
+ NS_ASSERTION(!tracker.EnteredMoreThanOnce(),
+ "EnsureEditorInitialized has been called while a previous call was in progress");
+#endif
+
+ // Create an editor for the frame, if one doesn't already exist
+ nsresult rv = txtCtrl->CreateEditor();
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(weakFrame.IsAlive());
+
+ // Set mEditorHasBeenInitialized so that subsequent calls will use the
+ // editor.
+ mEditorHasBeenInitialized = true;
+
+ if (weakFrame.IsAlive()) {
+ int32_t position = 0;
+
+ // Set the selection to the end of the text field (bug 1287655),
+ // but only if the contents has changed (bug 1337392).
+ if (txtCtrl->ValueChanged()) {
+ nsAutoString val;
+ txtCtrl->GetTextEditorValue(val, true);
+ position = val.Length();
+ }
+
+ SetSelectionEndPoints(position, position);
+ }
+ }
+ NS_ENSURE_STATE(weakFrame.IsAlive());
+ return NS_OK;
+}
+
+nsresult
+nsTextControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+{
+ NS_ASSERTION(mContent, "We should have a content!");
+
+ mState |= NS_FRAME_INDEPENDENT_SELECTION;
+
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+
+ // Bind the frame to its text control
+ nsresult rv = txtCtrl->BindToFrame(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIContent* rootNode = txtCtrl->GetRootEditorNode();
+ NS_ENSURE_TRUE(rootNode, NS_ERROR_OUT_OF_MEMORY);
+
+ if (!aElements.AppendElement(rootNode))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Do we need a placeholder node?
+ nsAutoString placeholderTxt;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder,
+ placeholderTxt);
+ nsContentUtils::RemoveNewlines(placeholderTxt);
+ mUsePlaceholder = !placeholderTxt.IsEmpty();
+
+ // Create the placeholder anonymous content if needed.
+ if (mUsePlaceholder) {
+ nsIContent* placeholderNode = txtCtrl->CreatePlaceholderNode();
+ NS_ENSURE_TRUE(placeholderNode, NS_ERROR_OUT_OF_MEMORY);
+
+ // Associate ::placeholder pseudo-element with the placeholder node.
+ CSSPseudoElementType pseudoType = CSSPseudoElementType::placeholder;
+
+ // If this is a text input inside a number input then we want to use the
+ // main number input as the source of style for the placeholder frame.
+ nsIFrame* mainInputFrame = this;
+ if (StyleContext()->GetPseudoType() == CSSPseudoElementType::mozNumberText) {
+ do {
+ mainInputFrame = mainInputFrame->GetParent();
+ } while (mainInputFrame &&
+ mainInputFrame->GetType() != nsGkAtoms::numberControlFrame);
+ MOZ_ASSERT(mainInputFrame);
+ }
+
+ RefPtr<nsStyleContext> placeholderStyleContext =
+ PresContext()->StyleSet()->ResolvePseudoElementStyle(
+ mainInputFrame->GetContent()->AsElement(), pseudoType, StyleContext(),
+ placeholderNode->AsElement());
+
+ if (!aElements.AppendElement(ContentInfo(placeholderNode,
+ placeholderStyleContext))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!IsSingleLineTextControl()) {
+ // For textareas, UpdateValueDisplay doesn't initialize the visibility
+ // status of the placeholder because it returns early, so we have to
+ // do that manually here.
+ txtCtrl->UpdatePlaceholderVisibility(true);
+ }
+ }
+
+ rv = UpdateValueDisplay(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // textareas are eagerly initialized
+ bool initEagerly = !IsSingleLineTextControl();
+ if (!initEagerly) {
+ // Also, input elements which have a cached selection should get eager
+ // editor initialization.
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+ initEagerly = txtCtrl->HasCachedSelection();
+ }
+ if (!initEagerly) {
+ nsCOMPtr<nsIDOMHTMLElement> element = do_QueryInterface(txtCtrl);
+ if (element) {
+ // so are input text controls with spellcheck=true
+ element->GetSpellcheck(&initEagerly);
+ }
+ }
+
+ if (initEagerly) {
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "Someone forgot a script blocker?");
+ EditorInitializer* initializer = Properties().Get(TextControlInitializer());
+ if (initializer) {
+ initializer->Revoke();
+ }
+ initializer = new EditorInitializer(this);
+ Properties().Set(TextControlInitializer(),initializer);
+ nsContentUtils::AddScriptRunner(initializer);
+ }
+
+ return NS_OK;
+}
+
+void
+nsTextControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter)
+{
+ // This can be called off-main-thread during Servo traversal, so we take care
+ // to avoid QI-ing the DOM node.
+ nsITextControlElement* txtCtrl = nullptr;
+ nsIContent* content = GetContent();
+ if (content->IsHTMLElement(nsGkAtoms::input)) {
+ txtCtrl = static_cast<HTMLInputElement*>(content);
+ } else if (content->IsHTMLElement(nsGkAtoms::textarea)) {
+ txtCtrl = static_cast<HTMLTextAreaElement*>(content);
+ } else {
+ MOZ_CRASH("Unexpected content type for nsTextControlFrame");
+ }
+
+ nsIContent* root = txtCtrl->GetRootEditorNode();
+ if (root) {
+ aElements.AppendElement(root);
+ }
+
+ nsIContent* placeholder = txtCtrl->GetPlaceholderNode();
+ if (placeholder && !(aFilter & nsIContent::eSkipPlaceholderContent))
+ aElements.AppendElement(placeholder);
+
+}
+
+nscoord
+nsTextControlFrame::GetPrefISize(nsRenderingContext* aRenderingContext)
+{
+ nscoord result = 0;
+ DISPLAY_PREF_WIDTH(this, result);
+ float inflation = nsLayoutUtils::FontSizeInflationFor(this);
+ WritingMode wm = GetWritingMode();
+ result = CalcIntrinsicSize(aRenderingContext, wm, inflation).ISize(wm);
+ return result;
+}
+
+nscoord
+nsTextControlFrame::GetMinISize(nsRenderingContext* aRenderingContext)
+{
+ // Our min width is just our preferred width if we have auto width.
+ nscoord result;
+ DISPLAY_MIN_WIDTH(this, result);
+ result = GetPrefISize(aRenderingContext);
+ return result;
+}
+
+LogicalSize
+nsTextControlFrame::ComputeAutoSize(nsRenderingContext* aRenderingContext,
+ WritingMode aWM,
+ const LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const LogicalSize& aMargin,
+ const LogicalSize& aBorder,
+ const LogicalSize& aPadding,
+ ComputeSizeFlags aFlags)
+{
+ float inflation = nsLayoutUtils::FontSizeInflationFor(this);
+ LogicalSize autoSize = CalcIntrinsicSize(aRenderingContext, aWM, inflation);
+
+ // Note: nsContainerFrame::ComputeAutoSize only computes the inline-size (and
+ // only for 'auto'), the block-size it returns is always NS_UNCONSTRAINEDSIZE.
+ const nsStyleCoord& iSizeCoord = StylePosition()->ISize(aWM);
+ if (iSizeCoord.GetUnit() == eStyleUnit_Auto) {
+ if (aFlags & ComputeSizeFlags::eIClampMarginBoxMinSize) {
+ // CalcIntrinsicSize isn't aware of grid-item margin-box clamping, so we
+ // fall back to nsContainerFrame's ComputeAutoSize to handle that.
+ // XXX maybe a font-inflation issue here? (per the assertion below).
+ autoSize.ISize(aWM) =
+ nsContainerFrame::ComputeAutoSize(aRenderingContext, aWM,
+ aCBSize, aAvailableISize,
+ aMargin, aBorder,
+ aPadding, aFlags).ISize(aWM);
+ }
+#ifdef DEBUG
+ else {
+ LogicalSize ancestorAutoSize =
+ nsContainerFrame::ComputeAutoSize(aRenderingContext, aWM,
+ aCBSize, aAvailableISize,
+ aMargin, aBorder,
+ aPadding, aFlags);
+ // Disabled when there's inflation; see comment in GetXULPrefSize.
+ MOZ_ASSERT(inflation != 1.0f ||
+ ancestorAutoSize.ISize(aWM) == autoSize.ISize(aWM),
+ "Incorrect size computed by ComputeAutoSize?");
+ }
+#endif
+ }
+ return autoSize;
+}
+
+void
+nsTextControlFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsTextControlFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+
+ // make sure that the form registers itself on the initial/first reflow
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ nsFormControlFrame::RegUnRegAccessKey(this, true);
+ }
+
+ // set values of reflow's out parameters
+ WritingMode wm = aReflowInput.GetWritingMode();
+ LogicalSize
+ finalSize(wm,
+ aReflowInput.ComputedISize() +
+ aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm),
+ aReflowInput.ComputedBSize() +
+ aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm));
+ aDesiredSize.SetSize(wm, finalSize);
+
+ // computation of the ascent wrt the input height
+ nscoord lineHeight = aReflowInput.ComputedBSize();
+ float inflation = nsLayoutUtils::FontSizeInflationFor(this);
+ if (!IsSingleLineTextControl()) {
+ lineHeight = ReflowInput::CalcLineHeight(GetContent(), StyleContext(),
+ NS_AUTOHEIGHT, inflation);
+ }
+ RefPtr<nsFontMetrics> fontMet =
+ nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
+ // now adjust for our borders and padding
+ aDesiredSize.SetBlockStartAscent(
+ nsLayoutUtils::GetCenteredFontBaseline(fontMet, lineHeight,
+ wm.IsLineInverted()) +
+ aReflowInput.ComputedLogicalBorderPadding().BStart(wm));
+
+ // overflow handling
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+ // perform reflow on all kids
+ nsIFrame* kid = mFrames.FirstChild();
+ while (kid) {
+ ReflowTextControlChild(kid, aPresContext, aReflowInput, aStatus, aDesiredSize);
+ kid = kid->GetNextSibling();
+ }
+
+ // take into account css properties that affect overflow handling
+ FinishAndStoreOverflow(&aDesiredSize);
+
+ aStatus = NS_FRAME_COMPLETE;
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+void
+nsTextControlFrame::ReflowTextControlChild(nsIFrame* aKid,
+ nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus,
+ ReflowOutput& aParentDesiredSize)
+{
+ // compute available size and frame offsets for child
+ WritingMode wm = aKid->GetWritingMode();
+ LogicalSize availSize = aReflowInput.ComputedSizeWithPadding(wm);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+
+ ReflowInput kidReflowInput(aPresContext, aReflowInput,
+ aKid, availSize, nullptr,
+ ReflowInput::CALLER_WILL_INIT);
+ // Override padding with our computed padding in case we got it from theming or percentage
+ kidReflowInput.Init(aPresContext, nullptr, nullptr, &aReflowInput.ComputedPhysicalPadding());
+
+ // Set computed width and computed height for the child
+ kidReflowInput.SetComputedWidth(aReflowInput.ComputedWidth());
+ kidReflowInput.SetComputedHeight(aReflowInput.ComputedHeight());
+
+ // Offset the frame by the size of the parent's border
+ nscoord xOffset = aReflowInput.ComputedPhysicalBorderPadding().left -
+ aReflowInput.ComputedPhysicalPadding().left;
+ nscoord yOffset = aReflowInput.ComputedPhysicalBorderPadding().top -
+ aReflowInput.ComputedPhysicalPadding().top;
+
+ // reflow the child
+ ReflowOutput desiredSize(aReflowInput);
+ ReflowChild(aKid, aPresContext, desiredSize, kidReflowInput,
+ xOffset, yOffset, 0, aStatus);
+
+ // place the child
+ FinishReflowChild(aKid, aPresContext, desiredSize,
+ &kidReflowInput, xOffset, yOffset, 0);
+
+ // consider the overflow
+ aParentDesiredSize.mOverflowAreas.UnionWith(desiredSize.mOverflowAreas);
+}
+
+nsSize
+nsTextControlFrame::GetXULMinSize(nsBoxLayoutState& aState)
+{
+ // XXXbz why? Why not the nsBoxFrame sizes?
+ return nsBox::GetXULMinSize(aState);
+}
+
+bool
+nsTextControlFrame::IsXULCollapsed()
+{
+ // We're never collapsed in the box sense.
+ return false;
+}
+
+bool
+nsTextControlFrame::IsLeaf() const
+{
+ return true;
+}
+
+NS_IMETHODIMP
+nsTextControlFrame::ScrollOnFocusEvent::Run()
+{
+ if (mFrame) {
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(mFrame->GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+ nsISelectionController* selCon = txtCtrl->GetSelectionController();
+ if (selCon) {
+ mFrame->mScrollEvent.Forget();
+ selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
+ nsISelectionController::SELECTION_FOCUS_REGION,
+ nsISelectionController::SCROLL_SYNCHRONOUS);
+ }
+ }
+ return NS_OK;
+}
+
+//IMPLEMENTING NS_IFORMCONTROLFRAME
+void nsTextControlFrame::SetFocus(bool aOn, bool aRepaint)
+{
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+
+ // Revoke the previous scroll event if one exists
+ mScrollEvent.Revoke();
+
+ // If 'dom.placeholeder.show_on_focus' preference is 'false', focusing or
+ // blurring the frame can have an impact on the placeholder visibility.
+ if (mUsePlaceholder) {
+ txtCtrl->UpdatePlaceholderVisibility(true);
+ }
+
+ if (!aOn) {
+ return;
+ }
+
+ nsISelectionController* selCon = txtCtrl->GetSelectionController();
+ if (!selCon)
+ return;
+
+ nsCOMPtr<nsISelection> ourSel;
+ selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
+ getter_AddRefs(ourSel));
+ if (!ourSel) return;
+
+ nsIPresShell* presShell = PresContext()->GetPresShell();
+ RefPtr<nsCaret> caret = presShell->GetCaret();
+ if (!caret) return;
+
+ // Scroll the current selection into view
+ nsISelection *caretSelection = caret->GetSelection();
+ const bool isFocusedRightNow = ourSel == caretSelection;
+ if (!isFocusedRightNow) {
+ // Don't scroll the current selection if we've been focused using the mouse.
+ uint32_t lastFocusMethod = 0;
+ nsIDocument* doc = GetContent()->GetComposedDoc();
+ if (doc) {
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ fm->GetLastFocusMethod(doc->GetWindow(), &lastFocusMethod);
+ }
+ }
+ if (!(lastFocusMethod & nsIFocusManager::FLAG_BYMOUSE)) {
+ RefPtr<ScrollOnFocusEvent> event = new ScrollOnFocusEvent(this);
+ nsresult rv = NS_DispatchToCurrentThread(event);
+ if (NS_SUCCEEDED(rv)) {
+ mScrollEvent = event;
+ }
+ }
+ }
+
+ // tell the caret to use our selection
+ caret->SetSelection(ourSel);
+
+ // mutual-exclusion: the selection is either controlled by the
+ // document or by the text input/area. Clear any selection in the
+ // document since the focus is now on our independent selection.
+
+ nsCOMPtr<nsISelectionController> selcon = do_QueryInterface(presShell);
+ nsCOMPtr<nsISelection> docSel;
+ selcon->GetSelection(nsISelectionController::SELECTION_NORMAL,
+ getter_AddRefs(docSel));
+ if (!docSel) return;
+
+ bool isCollapsed = false;
+ docSel->GetIsCollapsed(&isCollapsed);
+ if (!isCollapsed)
+ docSel->RemoveAllRanges();
+}
+
+nsresult nsTextControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
+{
+ if (!mIsProcessing)//some kind of lock.
+ {
+ mIsProcessing = true;
+ if (nsGkAtoms::select == aName)
+ {
+ // Select all the text.
+ //
+ // XXX: This is lame, we can't call editor's SelectAll method
+ // because that triggers AutoCopies in unix builds.
+ // Instead, we have to call our own homegrown version
+ // of select all which merely builds a range that selects
+ // all of the content and adds that to the selection.
+
+ nsWeakFrame weakThis = this;
+ SelectAllOrCollapseToEndOfText(true); // NOTE: can destroy the world
+ if (!weakThis.IsAlive()) {
+ return NS_OK;
+ }
+ }
+ mIsProcessing = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTextControlFrame::GetEditor(nsIEditor **aEditor)
+{
+ NS_ENSURE_ARG_POINTER(aEditor);
+
+ nsresult rv = EnsureEditorInitialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+ *aEditor = txtCtrl->GetTextEditor();
+ NS_IF_ADDREF(*aEditor);
+ return NS_OK;
+}
+
+nsresult
+nsTextControlFrame::SetSelectionInternal(nsIDOMNode *aStartNode,
+ int32_t aStartOffset,
+ nsIDOMNode *aEndNode,
+ int32_t aEndOffset,
+ nsITextControlFrame::SelectionDirection aDirection)
+{
+ // Create a new range to represent the new selection.
+ // Note that we use a new range to avoid having to do
+ // isIncreasing checks to avoid possible errors.
+
+ RefPtr<nsRange> range = new nsRange(mContent);
+ // Be careful to use internal nsRange methods which do not check to make sure
+ // we have access to the node.
+ nsCOMPtr<nsINode> start = do_QueryInterface(aStartNode);
+ nsCOMPtr<nsINode> end = do_QueryInterface(aEndNode);
+ nsresult rv = range->Set(start, aStartOffset, end, aEndOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the selection, clear it and add the new range to it!
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+ nsISelectionController* selCon = txtCtrl->GetSelectionController();
+ NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsISelection> selection;
+ selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
+ NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(selection, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsDirection direction;
+ if (aDirection == eNone) {
+ // Preserve the direction
+ direction = selPriv->GetSelectionDirection();
+ } else {
+ direction = (aDirection == eBackward) ? eDirPrevious : eDirNext;
+ }
+
+ rv = selection->RemoveAllRanges();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = selection->AddRange(range); // NOTE: can destroy the world
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ selPriv->SetSelectionDirection(direction);
+ return rv;
+}
+
+nsresult
+nsTextControlFrame::ScrollSelectionIntoView()
+{
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+ nsISelectionController* selCon = txtCtrl->GetSelectionController();
+ if (selCon) {
+ // Scroll the selection into view (see bug 231389).
+ return selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
+ nsISelectionController::SELECTION_FOCUS_REGION,
+ nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+mozilla::dom::Element*
+nsTextControlFrame::GetRootNodeAndInitializeEditor()
+{
+ nsCOMPtr<nsIDOMElement> root;
+ GetRootNodeAndInitializeEditor(getter_AddRefs(root));
+ nsCOMPtr<mozilla::dom::Element> rootElem = do_QueryInterface(root);
+ return rootElem;
+}
+
+nsresult
+nsTextControlFrame::GetRootNodeAndInitializeEditor(nsIDOMElement **aRootElement)
+{
+ NS_ENSURE_ARG_POINTER(aRootElement);
+
+ nsCOMPtr<nsIEditor> editor;
+ GetEditor(getter_AddRefs(editor));
+ if (!editor)
+ return NS_OK;
+
+ return editor->GetRootElement(aRootElement);
+}
+
+nsresult
+nsTextControlFrame::SelectAllOrCollapseToEndOfText(bool aSelect)
+{
+ nsCOMPtr<nsIDOMElement> rootElement;
+ nsresult rv = GetRootNodeAndInitializeEditor(getter_AddRefs(rootElement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIContent> rootContent = do_QueryInterface(rootElement);
+ nsCOMPtr<nsIDOMNode> rootNode(do_QueryInterface(rootElement));
+
+ NS_ENSURE_TRUE(rootNode && rootContent, NS_ERROR_FAILURE);
+
+ int32_t numChildren = rootContent->GetChildCount();
+
+ if (numChildren > 0) {
+ // We never want to place the selection after the last
+ // br under the root node!
+ nsIContent *child = rootContent->GetChildAt(numChildren - 1);
+ if (child) {
+ if (child->IsHTMLElement(nsGkAtoms::br))
+ --numChildren;
+ }
+ if (!aSelect && numChildren) {
+ child = rootContent->GetChildAt(numChildren - 1);
+ if (child && child->IsNodeOfType(nsINode::eTEXT)) {
+ rootNode = do_QueryInterface(child);
+ const nsTextFragment* fragment = child->GetText();
+ numChildren = fragment ? fragment->GetLength() : 0;
+ }
+ }
+ }
+
+ rv = SetSelectionInternal(rootNode, aSelect ? 0 : numChildren,
+ rootNode, numChildren);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return ScrollSelectionIntoView();
+}
+
+nsresult
+nsTextControlFrame::SetSelectionEndPoints(int32_t aSelStart, int32_t aSelEnd,
+ nsITextControlFrame::SelectionDirection aDirection)
+{
+ NS_ASSERTION(aSelStart <= aSelEnd, "Invalid selection offsets!");
+
+ if (aSelStart > aSelEnd)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIDOMNode> startNode, endNode;
+ int32_t startOffset, endOffset;
+
+ // Calculate the selection start point.
+
+ nsresult rv = OffsetToDOMPoint(aSelStart, getter_AddRefs(startNode), &startOffset);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aSelStart == aSelEnd) {
+ // Collapsed selection, so start and end are the same!
+ endNode = startNode;
+ endOffset = startOffset;
+ }
+ else {
+ // Selection isn't collapsed so we have to calculate
+ // the end point too.
+
+ rv = OffsetToDOMPoint(aSelEnd, getter_AddRefs(endNode), &endOffset);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return SetSelectionInternal(startNode, startOffset, endNode, endOffset, aDirection);
+}
+
+NS_IMETHODIMP
+nsTextControlFrame::SetSelectionRange(int32_t aSelStart, int32_t aSelEnd,
+ nsITextControlFrame::SelectionDirection aDirection)
+{
+ nsresult rv = EnsureEditorInitialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aSelStart > aSelEnd) {
+ // Simulate what we'd see SetSelectionStart() was called, followed
+ // by a SetSelectionEnd().
+
+ aSelStart = aSelEnd;
+ }
+
+ return SetSelectionEndPoints(aSelStart, aSelEnd, aDirection);
+}
+
+
+NS_IMETHODIMP
+nsTextControlFrame::SetSelectionStart(int32_t aSelectionStart)
+{
+ nsresult rv = EnsureEditorInitialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t selStart = 0, selEnd = 0;
+
+ rv = GetSelectionRange(&selStart, &selEnd);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aSelectionStart > selEnd) {
+ // Collapse to the new start point.
+ selEnd = aSelectionStart;
+ }
+
+ selStart = aSelectionStart;
+
+ return SetSelectionEndPoints(selStart, selEnd);
+}
+
+NS_IMETHODIMP
+nsTextControlFrame::SetSelectionEnd(int32_t aSelectionEnd)
+{
+ nsresult rv = EnsureEditorInitialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t selStart = 0, selEnd = 0;
+
+ rv = GetSelectionRange(&selStart, &selEnd);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aSelectionEnd < selStart) {
+ // Collapse to the new end point.
+ selStart = aSelectionEnd;
+ }
+
+ selEnd = aSelectionEnd;
+
+ return SetSelectionEndPoints(selStart, selEnd);
+}
+
+nsresult
+nsTextControlFrame::OffsetToDOMPoint(int32_t aOffset,
+ nsIDOMNode** aResult,
+ int32_t* aPosition)
+{
+ NS_ENSURE_ARG_POINTER(aResult && aPosition);
+
+ *aResult = nullptr;
+ *aPosition = 0;
+
+ nsCOMPtr<nsIDOMElement> rootElement;
+ nsresult rv = GetRootNodeAndInitializeEditor(getter_AddRefs(rootElement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDOMNode> rootNode(do_QueryInterface(rootElement));
+
+ NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDOMNodeList> nodeList;
+
+ rv = rootNode->GetChildNodes(getter_AddRefs(nodeList));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(nodeList, NS_ERROR_FAILURE);
+
+ uint32_t length = 0;
+
+ rv = nodeList->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(length <= 2, "We should have one text node and one mozBR at most");
+
+ nsCOMPtr<nsIDOMNode> firstNode;
+ rv = nodeList->Item(0, getter_AddRefs(firstNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(firstNode);
+
+ if (length == 0 || aOffset < 0) {
+ NS_IF_ADDREF(*aResult = rootNode);
+ *aPosition = 0;
+ } else if (textNode) {
+ uint32_t textLength = 0;
+ textNode->GetLength(&textLength);
+ if (length == 2 && uint32_t(aOffset) == textLength) {
+ // If we're at the end of the text node and we have a trailing BR node,
+ // set the selection on the BR node.
+ NS_IF_ADDREF(*aResult = rootNode);
+ *aPosition = 1;
+ } else {
+ // Otherwise, set the selection on the textnode itself.
+ NS_IF_ADDREF(*aResult = firstNode);
+ *aPosition = std::min(aOffset, int32_t(textLength));
+ }
+ } else {
+ NS_IF_ADDREF(*aResult = rootNode);
+ *aPosition = 0;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTextControlFrame::GetSelectionRange(int32_t* aSelectionStart,
+ int32_t* aSelectionEnd,
+ SelectionDirection* aDirection)
+{
+ // make sure we have an editor
+ nsresult rv = EnsureEditorInitialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aSelectionStart) {
+ *aSelectionStart = 0;
+ }
+ if (aSelectionEnd) {
+ *aSelectionEnd = 0;
+ }
+ if (aDirection) {
+ *aDirection = eNone;
+ }
+
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+ nsISelectionController* selCon = txtCtrl->GetSelectionController();
+ NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
+ nsCOMPtr<nsISelection> selection;
+ rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
+
+ dom::Selection* sel = selection->AsSelection();
+ if (aDirection) {
+ nsDirection direction = sel->GetSelectionDirection();
+ if (direction == eDirNext) {
+ *aDirection = eForward;
+ } else if (direction == eDirPrevious) {
+ *aDirection = eBackward;
+ } else {
+ NS_NOTREACHED("Invalid nsDirection enum value");
+ }
+ }
+
+ if (!aSelectionStart || !aSelectionEnd) {
+ return NS_OK;
+ }
+
+ mozilla::dom::Element* root = GetRootNodeAndInitializeEditor();
+ NS_ENSURE_STATE(root);
+ nsContentUtils::GetSelectionInTextControl(sel, root,
+ *aSelectionStart, *aSelectionEnd);
+
+ return NS_OK;
+}
+
+/////END INTERFACE IMPLEMENTATIONS
+
+////NSIFRAME
+nsresult
+nsTextControlFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+ nsISelectionController* selCon = txtCtrl->GetSelectionController();
+ const bool needEditor = nsGkAtoms::maxlength == aAttribute ||
+ nsGkAtoms::readonly == aAttribute ||
+ nsGkAtoms::disabled == aAttribute ||
+ nsGkAtoms::spellcheck == aAttribute;
+ nsCOMPtr<nsIEditor> editor;
+ if (needEditor) {
+ GetEditor(getter_AddRefs(editor));
+ }
+ if ((needEditor && !editor) || !selCon) {
+ return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+ }
+
+ if (nsGkAtoms::maxlength == aAttribute) {
+ int32_t maxLength;
+ bool maxDefined = GetMaxLength(&maxLength);
+ nsCOMPtr<nsIPlaintextEditor> textEditor = do_QueryInterface(editor);
+ if (textEditor) {
+ if (maxDefined) { // set the maxLength attribute
+ textEditor->SetMaxTextLength(maxLength);
+ // if maxLength>docLength, we need to truncate the doc content
+ } else { // unset the maxLength attribute
+ textEditor->SetMaxTextLength(-1);
+ }
+ }
+ return NS_OK;
+ }
+
+ if (nsGkAtoms::readonly == aAttribute) {
+ uint32_t flags;
+ editor->GetFlags(&flags);
+ if (AttributeExists(nsGkAtoms::readonly)) { // set readonly
+ flags |= nsIPlaintextEditor::eEditorReadonlyMask;
+ if (nsContentUtils::IsFocusedContent(mContent)) {
+ selCon->SetCaretEnabled(false);
+ }
+ } else { // unset readonly
+ flags &= ~(nsIPlaintextEditor::eEditorReadonlyMask);
+ if (!(flags & nsIPlaintextEditor::eEditorDisabledMask) &&
+ nsContentUtils::IsFocusedContent(mContent)) {
+ selCon->SetCaretEnabled(true);
+ }
+ }
+ editor->SetFlags(flags);
+ return NS_OK;
+ }
+
+ if (nsGkAtoms::disabled == aAttribute) {
+ uint32_t flags;
+ editor->GetFlags(&flags);
+ int16_t displaySelection = nsISelectionController::SELECTION_OFF;
+ const bool focused = nsContentUtils::IsFocusedContent(mContent);
+ const bool hasAttr = AttributeExists(nsGkAtoms::disabled);
+ if (hasAttr) { // set disabled
+ flags |= nsIPlaintextEditor::eEditorDisabledMask;
+ } else { // unset disabled
+ flags &= ~(nsIPlaintextEditor::eEditorDisabledMask);
+ displaySelection = focused ? nsISelectionController::SELECTION_ON
+ : nsISelectionController::SELECTION_HIDDEN;
+ }
+ selCon->SetDisplaySelection(displaySelection);
+ if (focused) {
+ selCon->SetCaretEnabled(!hasAttr);
+ }
+ editor->SetFlags(flags);
+ return NS_OK;
+ }
+
+ if (!mEditorHasBeenInitialized && nsGkAtoms::value == aAttribute) {
+ UpdateValueDisplay(true);
+ return NS_OK;
+ }
+
+ // Allow the base class to handle common attributes supported by all form
+ // elements...
+ return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+
+nsresult
+nsTextControlFrame::GetText(nsString& aText)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+ if (IsSingleLineTextControl()) {
+ // There will be no line breaks so we can ignore the wrap property.
+ txtCtrl->GetTextEditorValue(aText, true);
+ } else {
+ nsCOMPtr<nsIDOMHTMLTextAreaElement> textArea = do_QueryInterface(mContent);
+ if (textArea) {
+ rv = textArea->GetValue(aText);
+ }
+ }
+ return rv;
+}
+
+
+nsresult
+nsTextControlFrame::GetPhonetic(nsAString& aPhonetic)
+{
+ aPhonetic.Truncate(0);
+
+ nsCOMPtr<nsIEditor> editor;
+ nsresult rv = GetEditor(getter_AddRefs(editor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIEditorIMESupport> imeSupport = do_QueryInterface(editor);
+ if (imeSupport) {
+ nsCOMPtr<nsIPhonetic> phonetic = do_QueryInterface(imeSupport);
+ if (phonetic)
+ phonetic->GetPhonetic(aPhonetic);
+ }
+ return NS_OK;
+}
+
+///END NSIFRAME OVERLOADS
+/////BEGIN PROTECTED METHODS
+
+bool
+nsTextControlFrame::GetMaxLength(int32_t* aSize)
+{
+ *aSize = -1;
+
+ nsGenericHTMLElement *content = nsGenericHTMLElement::FromContent(mContent);
+ if (content) {
+ const nsAttrValue* attr = content->GetParsedAttr(nsGkAtoms::maxlength);
+ if (attr && attr->Type() == nsAttrValue::eInteger) {
+ *aSize = attr->GetIntegerValue();
+
+ return true;
+ }
+ }
+ return false;
+}
+
+// END IMPLEMENTING NS_IFORMCONTROLFRAME
+
+void
+nsTextControlFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ nsContainerFrame::SetInitialChildList(aListID, aChildList);
+ if (aListID != kPrincipalList) {
+ return;
+ }
+
+ // Mark the scroll frame as being a reflow root. This will allow
+ // incremental reflows to be initiated at the scroll frame, rather
+ // than descending from the root frame of the frame hierarchy.
+ if (nsIFrame* first = PrincipalChildList().FirstChild()) {
+ first->AddStateBits(NS_FRAME_REFLOW_ROOT);
+
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+ txtCtrl->InitializeKeyboardEventListeners();
+
+ nsPoint* contentScrollPos = Properties().Get(ContentScrollPos());
+ if (contentScrollPos) {
+ // If we have a scroll pos stored to be passed to our anonymous
+ // div, do it here!
+ nsIStatefulFrame* statefulFrame = do_QueryFrame(first);
+ NS_ASSERTION(statefulFrame, "unexpected type of frame for the anonymous div");
+ nsPresState fakePresState;
+ fakePresState.SetScrollState(*contentScrollPos);
+ statefulFrame->RestoreState(&fakePresState);
+ Properties().Remove(ContentScrollPos());
+ delete contentScrollPos;
+ }
+ }
+}
+
+void
+nsTextControlFrame::SetValueChanged(bool aValueChanged)
+{
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+
+ if (mUsePlaceholder) {
+ nsWeakFrame weakFrame(this);
+ txtCtrl->UpdatePlaceholderVisibility(true);
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+ }
+
+ txtCtrl->SetValueChanged(aValueChanged);
+}
+
+
+nsresult
+nsTextControlFrame::UpdateValueDisplay(bool aNotify,
+ bool aBeforeEditorInit,
+ const nsAString *aValue)
+{
+ if (!IsSingleLineTextControl()) // textareas don't use this
+ return NS_OK;
+
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+ nsIContent* rootNode = txtCtrl->GetRootEditorNode();
+
+ NS_PRECONDITION(rootNode, "Must have a div content\n");
+ NS_PRECONDITION(!mEditorHasBeenInitialized,
+ "Do not call this after editor has been initialized");
+ NS_ASSERTION(!mUsePlaceholder || txtCtrl->GetPlaceholderNode(),
+ "A placeholder div must exist");
+
+ nsIContent *textContent = rootNode->GetChildAt(0);
+ if (!textContent) {
+ // Set up a textnode with our value
+ RefPtr<nsTextNode> textNode =
+ new nsTextNode(mContent->NodeInfo()->NodeInfoManager());
+
+ NS_ASSERTION(textNode, "Must have textcontent!\n");
+
+ rootNode->AppendChildTo(textNode, aNotify);
+ textContent = textNode;
+ }
+
+ NS_ENSURE_TRUE(textContent, NS_ERROR_UNEXPECTED);
+
+ // Get the current value of the textfield from the content.
+ nsAutoString value;
+ if (aValue) {
+ value = *aValue;
+ } else {
+ txtCtrl->GetTextEditorValue(value, true);
+ }
+
+ // Update the display of the placeholder value if needed.
+ // We don't need to do this if we're about to initialize the
+ // editor, since EnsureEditorInitialized takes care of this.
+ if (mUsePlaceholder && !aBeforeEditorInit)
+ {
+ nsWeakFrame weakFrame(this);
+ txtCtrl->UpdatePlaceholderVisibility(aNotify);
+ NS_ENSURE_STATE(weakFrame.IsAlive());
+ }
+
+ if (aBeforeEditorInit && value.IsEmpty()) {
+ rootNode->RemoveChildAt(0, true);
+ return NS_OK;
+ }
+
+ if (!value.IsEmpty() && IsPasswordTextControl()) {
+ TextEditRules::FillBufWithPWChars(&value, value.Length());
+ }
+ return textContent->SetText(value, aNotify);
+}
+
+NS_IMETHODIMP
+nsTextControlFrame::GetOwnedSelectionController(nsISelectionController** aSelCon)
+{
+ NS_ENSURE_ARG_POINTER(aSelCon);
+
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+
+ *aSelCon = txtCtrl->GetSelectionController();
+ NS_IF_ADDREF(*aSelCon);
+
+ return NS_OK;
+}
+
+nsFrameSelection*
+nsTextControlFrame::GetOwnedFrameSelection()
+{
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+
+ return txtCtrl->GetConstFrameSelection();
+}
+
+NS_IMETHODIMP
+nsTextControlFrame::SaveState(nsPresState** aState)
+{
+ NS_ENSURE_ARG_POINTER(aState);
+
+ *aState = nullptr;
+
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+
+ nsIContent* rootNode = txtCtrl->GetRootEditorNode();
+ if (rootNode) {
+ // Query the nsIStatefulFrame from the HTMLScrollFrame
+ nsIStatefulFrame* scrollStateFrame = do_QueryFrame(rootNode->GetPrimaryFrame());
+ if (scrollStateFrame) {
+ return scrollStateFrame->SaveState(aState);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTextControlFrame::RestoreState(nsPresState* aState)
+{
+ NS_ENSURE_ARG_POINTER(aState);
+
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element");
+
+ nsIContent* rootNode = txtCtrl->GetRootEditorNode();
+ if (rootNode) {
+ // Query the nsIStatefulFrame from the HTMLScrollFrame
+ nsIStatefulFrame* scrollStateFrame = do_QueryFrame(rootNode->GetPrimaryFrame());
+ if (scrollStateFrame) {
+ return scrollStateFrame->RestoreState(aState);
+ }
+ }
+
+ // Most likely, we don't have our anonymous content constructed yet, which
+ // would cause us to end up here. In this case, we'll just store the scroll
+ // pos ourselves, and forward it to the scroll frame later when it's created.
+ Properties().Set(ContentScrollPos(), new nsPoint(aState->GetScrollPosition()));
+ return NS_OK;
+}
+
+nsresult
+nsTextControlFrame::PeekOffset(nsPeekOffsetStruct *aPos)
+{
+ return NS_ERROR_FAILURE;
+}
+
+void
+nsTextControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ /*
+ * The implementation of this method is equivalent as:
+ * nsContainerFrame::BuildDisplayList()
+ * with the difference that we filter-out the placeholder frame when it
+ * should not be visible.
+ */
+ DO_GLOBAL_REFLOW_COUNT_DSP("nsTextControlFrame");
+
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ NS_ASSERTION(txtCtrl, "Content not a text control element!");
+
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+
+ nsIFrame* kid = mFrames.FirstChild();
+ // Redirect all lists to the Content list so that nothing can escape, ie
+ // opacity creating stacking contexts that then get sorted with stacking
+ // contexts external to us.
+ nsDisplayList* content = aLists.Content();
+ nsDisplayListSet set(content, content, content, content, content, content);
+
+ while (kid) {
+ // If the frame is the placeholder frame, we should only show it if the
+ // placeholder has to be visible.
+ if (kid->GetContent() != txtCtrl->GetPlaceholderNode() ||
+ txtCtrl->GetPlaceholderVisibility()) {
+ BuildDisplayListForChild(aBuilder, kid, aDirtyRect, set, 0);
+ }
+ kid = kid->GetNextSibling();
+ }
+}
+
+mozilla::dom::Element*
+nsTextControlFrame::GetPseudoElement(CSSPseudoElementType aType)
+{
+ if (aType == CSSPseudoElementType::placeholder) {
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+ return txtCtrl->GetPlaceholderNode();
+ }
+
+ return nsContainerFrame::GetPseudoElement(aType);
+}
+
+NS_IMETHODIMP
+nsTextControlFrame::EditorInitializer::Run()
+{
+ if (!mFrame) {
+ return NS_OK;
+ }
+
+ // Need to block script to avoid bug 669767.
+ nsAutoScriptBlocker scriptBlocker;
+
+ nsCOMPtr<nsIPresShell> shell =
+ mFrame->PresContext()->GetPresShell();
+ bool observes = shell->ObservesNativeAnonMutationsForPrint();
+ shell->ObserveNativeAnonMutationsForPrint(true);
+ // This can cause the frame to be destroyed (and call Revoke()).
+ mFrame->EnsureEditorInitialized();
+ shell->ObserveNativeAnonMutationsForPrint(observes);
+
+ // The frame can *still* be destroyed even though we have a scriptblocker,
+ // bug 682684.
+ if (!mFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mFrame->FinishedInitializer();
+ return NS_OK;
+}
diff --git a/layout/forms/nsTextControlFrame.h b/layout/forms/nsTextControlFrame.h
new file mode 100644
index 000000000..a76cba514
--- /dev/null
+++ b/layout/forms/nsTextControlFrame.h
@@ -0,0 +1,319 @@
+/* -*- 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 nsTextControlFrame_h___
+#define nsTextControlFrame_h___
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsIContent.h"
+#include "nsITextControlFrame.h"
+#include "nsITextControlElement.h"
+#include "nsIStatefulFrame.h"
+
+class nsISelectionController;
+class EditorInitializerEntryTracker;
+class nsTextEditorState;
+class nsIEditor;
+namespace mozilla {
+enum class CSSPseudoElementType : uint8_t;
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+class nsTextControlFrame final : public nsContainerFrame,
+ public nsIAnonymousContentCreator,
+ public nsITextControlFrame,
+ public nsIStatefulFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(ContentScrollPos, nsPoint)
+
+ explicit nsTextControlFrame(nsStyleContext* aContext);
+ virtual ~nsTextControlFrame();
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual nsIScrollableFrame* GetScrollTargetFrame() override {
+ return do_QueryFrame(PrincipalChildList().FirstChild());
+ }
+
+ virtual nscoord GetMinISize(nsRenderingContext* aRenderingContext) override;
+ virtual nscoord GetPrefISize(nsRenderingContext* aRenderingContext) override;
+
+ virtual mozilla::LogicalSize
+ ComputeAutoSize(nsRenderingContext* aRenderingContext,
+ mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorder,
+ const mozilla::LogicalSize& aPadding,
+ ComputeSizeFlags aFlags) override;
+
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override;
+ virtual bool IsXULCollapsed() override;
+
+ virtual bool IsLeaf() const override;
+
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ aResult.AssignLiteral("nsTextControlFrame");
+ return NS_OK;
+ }
+#endif
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ // nsStackFrame is already both of these, but that's somewhat bogus,
+ // and we really mean it.
+ return nsContainerFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
+ }
+
+ // nsIAnonymousContentCreator
+ virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
+ virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter) override;
+
+ virtual void SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual mozilla::dom::Element*
+ GetPseudoElement(mozilla::CSSPseudoElementType aType) override;
+
+//==== BEGIN NSIFORMCONTROLFRAME
+ virtual void SetFocus(bool aOn , bool aRepaint) override;
+ virtual nsresult SetFormProperty(nsIAtom* aName, const nsAString& aValue) override;
+
+//==== END NSIFORMCONTROLFRAME
+
+//==== NSITEXTCONTROLFRAME
+
+ NS_IMETHOD GetEditor(nsIEditor **aEditor) override;
+ NS_IMETHOD SetSelectionStart(int32_t aSelectionStart) override;
+ NS_IMETHOD SetSelectionEnd(int32_t aSelectionEnd) override;
+ NS_IMETHOD SetSelectionRange(int32_t aSelectionStart,
+ int32_t aSelectionEnd,
+ SelectionDirection aDirection = eNone) override;
+ NS_IMETHOD GetSelectionRange(int32_t* aSelectionStart,
+ int32_t* aSelectionEnd,
+ SelectionDirection* aDirection = nullptr) override;
+ NS_IMETHOD GetOwnedSelectionController(nsISelectionController** aSelCon) override;
+ virtual nsFrameSelection* GetOwnedFrameSelection() override;
+
+ nsresult GetPhonetic(nsAString& aPhonetic) override;
+
+ /**
+ * Ensure mEditor is initialized with the proper flags and the default value.
+ * @throws NS_ERROR_NOT_INITIALIZED if mEditor has not been created
+ * @throws various and sundry other things
+ */
+ virtual nsresult EnsureEditorInitialized() override;
+
+//==== END NSITEXTCONTROLFRAME
+
+//==== NSISTATEFULFRAME
+
+ NS_IMETHOD SaveState(nsPresState** aState) override;
+ NS_IMETHOD RestoreState(nsPresState* aState) override;
+
+//=== END NSISTATEFULFRAME
+
+//==== OVERLOAD of nsIFrame
+ virtual nsIAtom* GetType() const override;
+
+ /** handler for attribute changes to mContent */
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ nsresult GetText(nsString& aText);
+
+ virtual nsresult PeekOffset(nsPeekOffsetStruct *aPos) override;
+
+ NS_DECL_QUERYFRAME
+
+protected:
+ /**
+ * Launch the reflow on the child frames - see nsTextControlFrame::Reflow()
+ */
+ void ReflowTextControlChild(nsIFrame* aFrame,
+ nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus,
+ ReflowOutput& aParentDesiredSize);
+
+public: //for methods who access nsTextControlFrame directly
+ void SetValueChanged(bool aValueChanged);
+
+ // called by the focus listener
+ nsresult MaybeBeginSecureKeyboardInput();
+ void MaybeEndSecureKeyboardInput();
+
+#define DEFINE_TEXTCTRL_FORWARDER(type, name) \
+ type name() { \
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); \
+ NS_ASSERTION(txtCtrl, "Content not a text control element"); \
+ return txtCtrl->name(); \
+ }
+#define DEFINE_TEXTCTRL_CONST_FORWARDER(type, name) \
+ type name() const { \
+ nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); \
+ NS_ASSERTION(txtCtrl, "Content not a text control element"); \
+ return txtCtrl->name(); \
+ }
+
+ DEFINE_TEXTCTRL_CONST_FORWARDER(bool, IsSingleLineTextControl)
+ DEFINE_TEXTCTRL_CONST_FORWARDER(bool, IsTextArea)
+ DEFINE_TEXTCTRL_CONST_FORWARDER(bool, IsPlainTextControl)
+ DEFINE_TEXTCTRL_CONST_FORWARDER(bool, IsPasswordTextControl)
+ DEFINE_TEXTCTRL_CONST_FORWARDER(int32_t, GetCols)
+ DEFINE_TEXTCTRL_CONST_FORWARDER(int32_t, GetWrapCols)
+ DEFINE_TEXTCTRL_CONST_FORWARDER(int32_t, GetRows)
+
+#undef DEFINE_TEXTCTRL_CONST_FORWARDER
+#undef DEFINE_TEXTCTRL_FORWARDER
+
+protected:
+ class EditorInitializer;
+ friend class EditorInitializer;
+ friend class nsTextEditorState; // needs access to UpdateValueDisplay
+
+ // Temp reference to scriptrunner
+ // We could make these auto-Revoking via the "delete" entry for safety
+ NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(TextControlInitializer,
+ EditorInitializer)
+
+ class EditorInitializer : public mozilla::Runnable {
+ public:
+ explicit EditorInitializer(nsTextControlFrame* aFrame) :
+ mFrame(aFrame) {}
+
+ NS_IMETHOD Run() override;
+
+ // avoids use of nsWeakFrame
+ void Revoke() {
+ mFrame = nullptr;
+ }
+
+ private:
+ nsTextControlFrame* mFrame;
+ };
+
+ class ScrollOnFocusEvent;
+ friend class ScrollOnFocusEvent;
+
+ class ScrollOnFocusEvent : public mozilla::Runnable {
+ public:
+ explicit ScrollOnFocusEvent(nsTextControlFrame* aFrame) :
+ mFrame(aFrame) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ void Revoke() {
+ mFrame = nullptr;
+ }
+
+ private:
+ nsTextControlFrame* mFrame;
+ };
+
+ nsresult OffsetToDOMPoint(int32_t aOffset, nsIDOMNode** aResult, int32_t* aPosition);
+
+ /**
+ * Update the textnode under our anonymous div to show the new
+ * value. This should only be called when we have no editor yet.
+ * @throws NS_ERROR_UNEXPECTED if the div has no text content
+ */
+ nsresult UpdateValueDisplay(bool aNotify,
+ bool aBeforeEditorInit = false,
+ const nsAString *aValue = nullptr);
+
+ /**
+ * Get the maxlength attribute
+ * @param aMaxLength the value of the max length attr
+ * @returns false if attr not defined
+ */
+ bool GetMaxLength(int32_t* aMaxLength);
+
+ /**
+ * Find out whether an attribute exists on the content or not.
+ * @param aAtt the attribute to determine the existence of
+ * @returns false if it does not exist
+ */
+ bool AttributeExists(nsIAtom *aAtt) const
+ { return mContent && mContent->HasAttr(kNameSpaceID_None, aAtt); }
+
+ /**
+ * We call this when we are being destroyed or removed from the PFM.
+ * @param aPresContext the current pres context
+ */
+ void PreDestroy();
+
+ // Compute our intrinsic size. This does not include any borders, paddings,
+ // etc. Just the size of our actual area for the text (and the scrollbars,
+ // for <textarea>).
+ mozilla::LogicalSize CalcIntrinsicSize(nsRenderingContext* aRenderingContext,
+ mozilla::WritingMode aWM,
+ float aFontSizeInflation) const;
+
+ nsresult ScrollSelectionIntoView() override;
+
+private:
+ //helper methods
+ nsresult SetSelectionInternal(nsIDOMNode *aStartNode, int32_t aStartOffset,
+ nsIDOMNode *aEndNode, int32_t aEndOffset,
+ SelectionDirection aDirection = eNone);
+ nsresult SelectAllOrCollapseToEndOfText(bool aSelect);
+ nsresult SetSelectionEndPoints(int32_t aSelStart, int32_t aSelEnd,
+ SelectionDirection aDirection = eNone);
+
+ /**
+ * Return the root DOM element, and implicitly initialize the editor if needed.
+ */
+ mozilla::dom::Element* GetRootNodeAndInitializeEditor();
+ nsresult GetRootNodeAndInitializeEditor(nsIDOMElement **aRootElement);
+
+ void FinishedInitializer() {
+ Properties().Delete(TextControlInitializer());
+ }
+
+private:
+ // these packed bools could instead use the high order bits on mState, saving 4 bytes
+ bool mEditorHasBeenInitialized;
+ bool mIsProcessing;
+ // Keep track if we have asked a placeholder node creation.
+ bool mUsePlaceholder;
+
+#ifdef DEBUG
+ bool mInEditorInitialization;
+ friend class EditorInitializerEntryTracker;
+#endif
+
+ nsRevocableEventPtr<ScrollOnFocusEvent> mScrollEvent;
+};
+
+#endif
+
+
diff --git a/layout/forms/test/bug287446_subframe.html b/layout/forms/test/bug287446_subframe.html
new file mode 100644
index 000000000..626430268
--- /dev/null
+++ b/layout/forms/test/bug287446_subframe.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function doIs(arg1, arg2, arg3) {
+ window.parent.postMessage("t " + encodeURIComponent(arg1) + " " +
+ encodeURIComponent(arg2) + " " +
+ encodeURIComponent(arg3), "*");
+ }
+
+ function $(arg) { return document.getElementById(arg); }
+
+ window.addEventListener("message",
+ function(evt) {
+ var t = $("target");
+ if (evt.data == "start") {
+ doIs(t.value, "Test", "Shouldn't have lost our initial value");
+ t.focus();
+ sendString("Foo");
+ doIs(t.value, "FooTest", "Typing should work");
+ window.parent.postMessage("c", "*");
+ } else {
+ doIs(evt.data, "continue", "Unexpected message");
+ doIs(t.value, "FooTest", "Shouldn't have lost our typed value");
+ sendString("Bar");
+ doIs(t.value, "FooBarTest", "Typing should still work");
+ window.parent.postMessage("f", "*");
+ }
+ },
+ "false");
+
+ </script>
+ </head>
+ <body>
+ <input id="target" value="Test">
+ </body>
+</html>
diff --git a/layout/forms/test/bug477700_subframe.html b/layout/forms/test/bug477700_subframe.html
new file mode 100644
index 000000000..067ce68c7
--- /dev/null
+++ b/layout/forms/test/bug477700_subframe.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function doIs(arg1, arg2, arg3) {
+ window.parent.postMessage("t " + encodeURIComponent(arg1) + " " +
+ encodeURIComponent(arg2) + " " +
+ encodeURIComponent(arg3), "*");
+ }
+
+ function $(arg) { return document.getElementById(arg); }
+
+ window.addEventListener("message",
+ function(evt) {
+ doIs(evt.data, "start", "Unexpected message");
+ $("target").focus();
+ sendString("Test");
+ var t = $("target");
+ doIs(t.value, "Test", "Typing should work");
+ (function() {
+ SpecialPowers.wrap(t).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor.undo(1);
+ })()
+ doIs(t.value, "", "Undo should work");
+ (function() {
+ SpecialPowers.wrap(t).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor.redo(1);
+ })()
+ doIs(t.value, "Test", "Redo should work");
+ window.parent.postMessage("f", "*");
+ },
+ "false");
+
+ </script>
+ </head>
+ <body>
+ <input id="target">
+ </body>
+</html>
+
diff --git a/layout/forms/test/bug536567_iframe.html b/layout/forms/test/bug536567_iframe.html
new file mode 100644
index 000000000..b2b2ca60a
--- /dev/null
+++ b/layout/forms/test/bug536567_iframe.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ </head>
+ <body>
+ <iframe id="content"></iframe>
+ </body>
+</html>
+
diff --git a/layout/forms/test/bug536567_subframe.html b/layout/forms/test/bug536567_subframe.html
new file mode 100644
index 000000000..c1271becf
--- /dev/null
+++ b/layout/forms/test/bug536567_subframe.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<body>
+<input id="target" type="file" />
+<script type="text/javascript">
+
+window.onload = function() {
+ var fileInput = document.getElementById("target");
+ fileInput.click();
+};
+
+</script>
+</body>
+</html>
diff --git a/layout/forms/test/bug564115_window.html b/layout/forms/test/bug564115_window.html
new file mode 100644
index 000000000..b55cc0400
--- /dev/null
+++ b/layout/forms/test/bug564115_window.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Window for Bug 564115</title>
+</head>
+<body>
+<input type="text">
+<div style="height: 10000px;"></div>
+</body>
+</html>
diff --git a/layout/forms/test/bug665540_window.xul b/layout/forms/test/bug665540_window.xul
new file mode 100644
index 000000000..f0225ef93
--- /dev/null
+++ b/layout/forms/test/bug665540_window.xul
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window title="Test Select Dropdown Positioning in Fullscreen Window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ sizemode="fullscreen">
+
+ <script>
+ opener.SimpleTest.waitForFocus(opener.childFocused, window);
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <select id="select" style="-moz-appearance:none">
+ <option id="optiona">a</option>
+ <option>b</option>
+ </select>
+ </body>
+</window>
diff --git a/layout/forms/test/chrome.ini b/layout/forms/test/chrome.ini
new file mode 100644
index 000000000..32d862eec
--- /dev/null
+++ b/layout/forms/test/chrome.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ bug536567_iframe.html
+ bug536567_subframe.html
+ bug665540_window.xul
+
+[test_bug536567_perwindowpb.html]
+[test_bug665540.html]
+tags = fullscreen
+skip-if = (os == 'linux' && bits == 64) # Bug 888164
diff --git a/layout/forms/test/mochitest.ini b/layout/forms/test/mochitest.ini
new file mode 100644
index 000000000..cc8519db7
--- /dev/null
+++ b/layout/forms/test/mochitest.ini
@@ -0,0 +1,66 @@
+[DEFAULT]
+support-files =
+ bug287446_subframe.html
+ bug477700_subframe.html
+ bug564115_window.html
+
+[test_bug231389.html]
+[test_bug287446.html]
+[test_bug345267.html]
+[test_bug346043.html]
+[test_bug348236.html]
+skip-if = toolkit == 'android' || e10s || os == 'mac' # mac(select form control popup behavior is different)
+[test_bug353539.html]
+[test_bug365410.html]
+[test_bug378670.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_bug402198.html]
+[test_bug411236.html]
+[test_bug446663.html]
+skip-if = toolkit == 'android'
+[test_bug476308.html]
+[test_bug477531.html]
+[test_bug477700.html]
+[test_bug478219.xhtml]
+skip-if = toolkit == 'android'
+[test_bug534785.html]
+[test_bug542914.html]
+[test_bug549170.html]
+[test_bug562447.html]
+[test_bug563642.html]
+[test_bug564115.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_bug571352.html]
+skip-if = (os == 'mac' && os_version == '10.10') || toolkit == 'android' #TIMED_OUT # OS X 10.10 - bug 947690
+[test_bug572406.html]
+[test_bug572649.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_bug595310.html]
+[test_bug620936.html]
+[test_bug644542.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_bug672810.html]
+skip-if = toolkit == 'android'
+[test_bug704049.html]
+[test_bug717878_input_scroll.html]
+[test_bug869314.html]
+[test_bug903715.html]
+skip-if = toolkit == 'android' || e10s #select elements don't use an in-page popup on Android
+[test_bug935876.html]
+# Bug 1023472 - Fails when pushed into a different chunk on Android
+skip-if = toolkit == 'android'
+[test_bug957562.html]
+[test_bug960277.html]
+[test_bug961363.html]
+skip-if = toolkit == 'android' # Bug 1021644 - Fails when pushed into a different chunk on Android
+[test_bug1111995.html]
+[test_bug1301290.html]
+skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # b2g(resizing textarea not available in b2g) b2g-debug(resizing textarea not available in b2g) b2g-desktop(resizing textarea not available in b2g)
+[test_bug1305282.html]
+[test_listcontrol_search.html]
+skip-if = toolkit == 'android' #select elements don't use an in-page popup on Android
+[test_select_prevent_default.html]
+[test_select_vertical.html]
+skip-if = e10s || toolkit == 'android' # Bug 1170129 - vertical <select> popup not implemented for e10s # <select> elements don't use an in-page popup on Android
+[test_textarea_resize.html]
+skip-if = toolkit == 'android'
diff --git a/layout/forms/test/test_bug1111995.html b/layout/forms/test/test_bug1111995.html
new file mode 100644
index 000000000..74ab76b06
--- /dev/null
+++ b/layout/forms/test/test_bug1111995.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1111995
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1111995</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 1111995 **/
+
+ function doTest() {
+ SimpleTest.waitForExplicitFinish();
+
+ var clicks = 0;
+ var elms = document.querySelectorAll('.click');
+ for (var i = 0; i < elms.length; ++i) {
+ var e = elms[i];
+ e.addEventListener('click', function(event) {
+ ++clicks;
+ }, false);
+ }
+
+ for (var i = 0; i < elms.length; ++i) {
+ var e = elms[i];
+ synthesizeMouse(e, 3, 3, {});
+ }
+ is(clicks, 0, "click events outside border with radius");
+
+ clicks = 0;
+ synthesizeMouse($("t3"), 17, 17, {});
+ synthesizeMouse($("t4"), 17, 17, {});
+ is(clicks, 2, "click events on border with radius");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="SimpleTest.waitForFocus(doTest, window)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1111995">Mozilla Bug 1111995</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<input class="click" id="t1" type=button style="width:100px; height:100px; padding:20px; border-radius:50%" value="Round button">
+<button class="click" id="t2" style="width:100px; height:100px; padding:20px; border-radius:50%">Round button</button>
+<input class="click" id="t3" type=button style="width:100px; height:100px; border-width:20px; border-radius:50%" value="Round button">
+<button class="click" id="t4" style="width:100px; height:100px; border-width:20px; border-radius:50%">Round button</button>
+<input class="click" id="t5" type=button style="width:100px; height:100px; border-radius:50%;overflow:hidden" value="Round button">
+<button class="click" id="t6" style="width:100px; height:100px; border-radius:50%;overflow:hidden">Round button</button>
+
+</body>
+</html>
diff --git a/layout/forms/test/test_bug1301290.html b/layout/forms/test/test_bug1301290.html
new file mode 100644
index 000000000..5f226c453
--- /dev/null
+++ b/layout/forms/test/test_bug1301290.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test for Bug 1301290</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 type="text/css">
+ .blue, .green {
+ border: none;
+ box-sizing: border-box;
+ display: block;
+ width: 200px;
+ height: 100px;
+ overflow: scroll;
+ resize: both;
+ }
+
+ .blue {
+ background: blue;
+ }
+
+ .green {
+ background: green;
+ margin-top: -100px;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="blue"></div>
+ <textarea class="green" id="textarea"></textarea>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(() => SimpleTest.executeSoon(function() {
+ var textarea = $("textarea");
+ var rect = textarea.getBoundingClientRect();
+
+ synthesizeMouse(textarea, rect.width - 10, rect.height - 10, { type: "mousedown" });
+ synthesizeMouse(textarea, rect.width + 40, rect.height + 40, { type: "mousemove" });
+ synthesizeMouse(textarea, rect.width + 40, rect.height + 40, { type: "mouseup" });
+
+ var newrect = textarea.getBoundingClientRect();
+ ok(newrect.width > rect.width, "width did not increase");
+ ok(newrect.height > rect.height, "height did not increase");
+ SimpleTest.finish();
+ }));
+ </script>
+ </body>
+</html>
diff --git a/layout/forms/test/test_bug1305282.html b/layout/forms/test/test_bug1305282.html
new file mode 100644
index 000000000..b43901e6c
--- /dev/null
+++ b/layout/forms/test/test_bug1305282.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=615697
+-->
+<head>
+ <title>Test for Bug 1305282</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=1305282">Mozilla Bug 1305282</a>
+<p id="display"></p>
+<div id="content">
+ <select>
+ <option>f o o</option>
+ <option>b a r</option>
+ <option>b o o</option>
+ </select>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1305282 **/
+
+var select = document.getElementsByTagName('select')[0];
+
+select.addEventListener("change", function(aEvent) {
+ select.removeEventListener("change", arguments.callee, false);
+ is(select.selectedIndex, 1, "'b a r' option is selected");
+ SimpleTest.finish();
+}, false);
+
+select.addEventListener("focus", function() {
+ select.removeEventListener("focus", arguments.callee, false);
+ SimpleTest.executeSoon(function () {
+ synthesizeKey("VK_DOWN", {});
+ SimpleTest.executeSoon(function () {
+ synthesizeKey('b', {});
+ SimpleTest.executeSoon(function () {
+ synthesizeKey(' ', {});
+ SimpleTest.executeSoon(function () {
+ synthesizeKey("VK_RETURN", {});
+ });
+ });
+ });
+ });
+}, false);
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ select.focus();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug231389.html b/layout/forms/test/test_bug231389.html
new file mode 100644
index 000000000..6a6cc9e50
--- /dev/null
+++ b/layout/forms/test/test_bug231389.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=231389
+-->
+<head>
+ <title>Test for Bug 231389</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=231389">Mozilla Bug 231389</a>
+<p id="display">
+ <textarea id="area" rows="5">
+ Here
+ is
+ some
+ very
+ long
+ text
+ that
+ we're
+ using
+ for
+ testing
+ purposes
+ </textarea>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 231389 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var area = document.getElementById("area");
+ var val = area.value;
+ var pos = val.indexOf("purposes");
+
+ is(area.scrollTop, 0, "The textarea should not be scrolled initially");
+ area.selectionStart = pos;
+ area.selectionEnd = pos;
+ setTimeout(function() {
+ isnot(area.scrollTop, 0, "The textarea's insertion point should be scrolled into view");
+
+ SimpleTest.finish();
+ }, 0);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug287446.html b/layout/forms/test/test_bug287446.html
new file mode 100644
index 000000000..e551ba998
--- /dev/null
+++ b/layout/forms/test/test_bug287446.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=287446
+-->
+<head>
+ <title>Test for Bug 287446</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=287446">Mozilla Bug 287446</a>
+<p id="display">
+ <iframe id="i"
+ src="http://example.com/tests/layout/forms/test/bug287446_subframe.html"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 287446 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ isnot(window.location.host, "example.com", "test is not testing cross-site");
+ var accessed = false;
+ try {
+ $("i").contentDocument.documentElement;
+ accessed = true;
+ } catch(e) {}
+ is(accessed, false, "Shouldn't be able to access cross-site");
+
+ $("i").style.display = "none";
+ document.body.offsetWidth;
+ is(document.defaultView.getComputedStyle($("i"), "").display, "none",
+ "toggling display failed");
+ $("i").style.display = "";
+ document.body.offsetWidth;
+ is(document.defaultView.getComputedStyle($("i"), "").display, "inline",
+ "toggling display back failed");
+
+ $("i").contentWindow.postMessage("start", "*");
+});
+
+function continueTest() {
+ $("i").style.display = "none";
+ document.body.offsetWidth;
+ is(document.defaultView.getComputedStyle($("i"), "").display, "none",
+ "toggling display second time failed");
+ $("i").style.display = "";
+ document.body.offsetWidth;
+ is(document.defaultView.getComputedStyle($("i"), "").display, "inline",
+ "toggling display back second time failed");
+
+$("i").contentWindow.postMessage("continue", "*");
+}
+
+window.addEventListener("message",
+ function(evt) {
+ var arr = evt.data.split(/ /).map(decodeURIComponent);
+ if (arr[0] == 't') {
+ is(arr[1], arr[2], arr[3]);
+ } else if (arr[0] == 'c') {
+ continueTest();
+ } else if (arr[0] == 'f') {
+ SimpleTest.finish();
+ }
+ },
+ false);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug345267.html b/layout/forms/test/test_bug345267.html
new file mode 100644
index 000000000..0c1c4c9a4
--- /dev/null
+++ b/layout/forms/test/test_bug345267.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=345267
+-->
+<head>
+ <title>Test for Bug 345267</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=345267">Mozilla Bug 345267</a>
+<p id="display">
+ <input id="d1" maxlength="3" value="abcde">
+ <input id="d2" maxlength="3">
+ <input id="d3" maxlength="3">
+ <input id="d4" value="abcdefghijk">
+ <input id="target" value="abcdefghijklm" maxlength="3">
+</p>
+<div id="content" style="display: none">
+ <input id="u1" maxlength="3" value="abcdef">
+ <input id="u2" maxlength="3">
+ <input id="u3" maxlength="3">
+ <input id="u4" value="abcdefghijkl">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 345267 **/
+SimpleTest.waitForExplicitFinish();
+// Turn off Spatial Navigation to stop if from hijacking "left" keypress event.
+SpecialPowers.pushPrefEnv({"set":[['snav.enabled', false]]}, runTest);
+
+function runTest() {
+ is($("d1").value, "abcde",
+ "Displayed initial value should not be truncated by maxlength");
+ is($("u1").value, "abcdef",
+ "Undisplayed initial value should not be truncated by maxlength");
+
+ $("d2").value = "abcdefg";
+ is($("d2").value, "abcdefg",
+ "Displayed set value should not be truncated by maxlength");
+
+ $("u2").value = "abcdefgh";
+ is($("u2").value, "abcdefgh",
+ "Undisplayed set value should not be truncated by maxlength");
+
+ $("d3").defaultValue = "abcdefghi";
+ is($("d3").value, "abcdefghi",
+ "Displayed set defaultValue should not be truncated by maxlength");
+
+ $("u3").defaultValue = "abcdefghij";
+ is($("u3").value, "abcdefghij",
+ "Undisplayed set defaultValue should not be truncated by maxlength");
+
+ $("d4").maxLength = "3";
+ is($("d4").value, "abcdefghijk",
+ "Displayed: setting maxLength should not truncate existing value");
+
+ $("u4").maxLength = "3";
+ is($("u4").value, "abcdefghijkl",
+ "Undisplayed: setting maxLength should not truncate existing value");
+
+ // Now start the editing tests
+ is($("target").value, "abcdefghijklm", "Test starting state incorrect");
+ $("target").focus();
+ $("target").selectionStart = $("target").selectionEnd = 13;
+ sendKey("back_space");
+ is($("target").value, "abcdefghijkl", "Should only delete one char");
+ sendKey("back_space");
+ is($("target").value, "abcdefghijk", "Should only delete one char again");
+ (function () {
+ SpecialPowers.wrap($("target")).controllers.getControllerForCommand('cmd_undo')
+ .doCommand('cmd_undo');
+ })();
+ is($("target").value, "abcdefghijklm",
+ "Should be able to undo deletion in the face of maxlength");
+ sendString("nopq");
+ is($("target").value, "abcdefghijklm",
+ "Typing should have no effect when already past maxlength");
+
+ $("target").value = "";
+ sendString("abcde");
+ is($("target").value, "abc", "Typing should be limited by maxlength");
+
+ $("target").value = "";
+ sendString("ad");
+ sendKey("left");
+ sendString("bc");
+ is($("target").value, "abd", "Typing should be limited by maxlength again");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/forms/test/test_bug346043.html b/layout/forms/test/test_bug346043.html
new file mode 100644
index 000000000..f509ae1fc
--- /dev/null
+++ b/layout/forms/test/test_bug346043.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=346043
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 346043</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript">
+ /** Test for Bug 346043 **/
+ function test(select, index, useEscape) {
+ select.selectedIndex = index;
+ is(select.selectedIndex, index, "Selected index is broken");
+
+ // Open the select dropdown.
+ //
+ // Using Alt+Down (instead of a click) seems necessary to make subsequent
+ // mouse events work with the dropdown itself, instead of the window below
+ // it.
+ select.focus();
+ synthesizeKey("VK_DOWN", {altKey: true});
+
+ var options = select.getElementsByTagName("option");
+ synthesizeMouseAtCenter(options[1],
+ {type: "mousemove", clickCount: 0});
+
+ // Close the select dropdown.
+ if (useEscape)
+ synthesizeKey("VK_ESCAPE", {}); // Tests a different code path.
+ select.blur();
+ is(select.selectedIndex, index, "Selected index shouldn't change");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(function() {
+ test(document.getElementById("test-unselected"), -1, true);
+ test(document.getElementById("test-unselected"), -1, false);
+ test(document.getElementById("test-disabled"), 0, true);
+ test(document.getElementById("test-disabled"), 0, false);
+ SimpleTest.finish();
+ });
+ </script>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=346043">Mozilla Bug 346043</a>
+ <p id="display"></p>
+ <div id="content">
+ <select id="test-unselected">
+ <option>A</option>
+ <option>B</option>
+ <option>C</option>
+ </select>
+
+ <select id="test-disabled">
+ <option disabled>A</option>
+ <option>B</option>
+ <option>C</option>
+ </select>
+ </div>
+ <pre id="test"></pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug348236.html b/layout/forms/test/test_bug348236.html
new file mode 100644
index 000000000..ff89c3bd9
--- /dev/null
+++ b/layout/forms/test/test_bug348236.html
@@ -0,0 +1,125 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=348236
+-->
+<head>
+
+ <title>Test for Bug 348236</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">
+ #eSelect {
+ position: fixed; top:0; left: 350px; font-size: 24px; width: 100px
+ }
+ #eSelect option {
+ margin: 0; padding: 0; height: 24px
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=348236">Mozilla Bug 348236</a>
+<p id="display"></p>
+<div id="content">
+
+ <select id="eSelect" size="1" onchange="++this.onchangeCount">
+ <option selected>1</option>
+ <option>2</option>
+ <option>3</option>
+ </select>
+</div>
+<pre id="test">
+<script type="text/javascript">
+
+ /** Test for Bug 348236 **/
+
+SimpleTest.waitForExplicitFinish()
+addLoadEvent(function test() {
+
+ var
+ CI = SpecialPowers.Components.interfaces,
+ WinUtils = SpecialPowers.getDOMWindowUtils(window),
+ sec = netscape.security,
+ eSelect = $("eSelect"),
+ IDOMEvent = CI.nsIDOMEvent,
+ IDOMKeyEvent = CI.nsIDOMKeyEvent,
+ timeout = 0 // Choose a larger value like 500 ms if you want to see what's happening.
+
+ function keypressOnSelect(key, modifiers) {
+ WinUtils.focus(eSelect)
+ WinUtils.sendKeyEvent("keyup", key, 0, modifiers)
+ WinUtils.sendKeyEvent("keypress", key, 0, modifiers)
+ WinUtils.sendKeyEvent("keydown", key, 0, modifiers)
+ }
+
+ function testKey(key, modifiers, keyString, functionToContinue) {
+ var selectGotClick
+ function clickListener() { selectGotClick = true }
+ eSelect.selectedIndex = 0
+ eSelect.onchangeCount = 0
+
+ // Drop the SELECT down.
+ keypressOnSelect(key, modifiers)
+ // This timeout and the following are necessary to let the sent events take effect.
+ setTimeout(cont1, timeout)
+ function cont1() {
+ // Move the mouse over option 3 (90 = 3 (rows) * 24 (row height) + 18).
+ WinUtils.sendMouseEvent("mousemove", 355, 90, 0, 0, 0, true)
+ setTimeout(cont2, timeout)
+ }
+ function cont2() {
+ // Close the select.
+ keypressOnSelect(key, modifiers)
+ setTimeout(cont3, timeout)
+ }
+ function cont3() {
+ is(eSelect.value, "3", "Select's value should be 3 after hovering over option 3 and pressing " + keyString + ".")
+ is(eSelect.onchangeCount, 1, "Onchange should have fired once.")
+
+ // Simulate click on area to the left of the select.
+ eSelect.addEventListener("click", clickListener, true)
+ selectGotClick = false
+ WinUtils.sendMouseEvent("mousedown", 320, 0, 0, 0, 0, true)
+ WinUtils.sendMouseEvent("mouseup", 320, 0, 0, 0, 0, true)
+ setTimeout(cont4, timeout)
+ }
+ function cont4() {
+ eSelect.removeEventListener("click", clickListener, true)
+ ok(!selectGotClick, "SELECT must not capture mouse events after closing it with " + keyString + ".")
+ functionToContinue()
+ }
+ }
+
+
+ // Quick sanity checks.
+ is(eSelect.value, "1", "SELECT value should be 1 after load.")
+ is(eSelect.selectedIndex, 0, "SELECT selectedIndex should be 0 after load.")
+
+ // Check if sending key events works.
+ keypressOnSelect(IDOMKeyEvent.DOM_VK_DOWN, 0)
+ is(eSelect.value, "2", "SELECT value should be 2 after pressing Down.")
+
+ // Test ALT-Down.
+ testKey(IDOMKeyEvent.DOM_VK_DOWN, IDOMEvent.ALT_MASK, "ALT-Down", nextKey1)
+ function nextKey1() {
+ // Test ALT-Up.
+ testKey(IDOMKeyEvent.DOM_VK_UP, IDOMEvent.ALT_MASK, "ALT-Up", nextKey2)
+ }
+ function nextKey2() {
+ // Test the F4 key on OS/2 and Windows.
+ if (/OS\/2|Win/i.test(navigator.platform))
+ testKey(IDOMKeyEvent.DOM_VK_F4, 0, "F4", finished)
+ else
+ finished()
+ }
+ function finished() {
+ // Reset value to get the expected value if we reload the page.
+ eSelect.selectedIndex = 0
+ SimpleTest.finish()
+ }
+})
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug353539.html b/layout/forms/test/test_bug353539.html
new file mode 100644
index 000000000..6ada2b115
--- /dev/null
+++ b/layout/forms/test/test_bug353539.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=353539
+-->
+<head>
+ <title>Test for Bug 353539</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=353539">Mozilla Bug 353539</a>
+<p id="display">
+ <textarea id="area" rows="5">
+ Here
+ is
+ some
+ very
+ long
+ text
+ that
+ we're
+ using
+ for
+ testing
+ purposes
+ </textarea>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 353539 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var area = document.getElementById("area");
+
+ is(area.scrollTop, 0, "The textarea should not be scrolled initially");
+ area.focus();
+ setTimeout(function() {
+ is(area.scrollTop, 0, "The textarea's insertion point should not be scrolled into view");
+
+ SimpleTest.finish();
+ }, 0);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug365410.html b/layout/forms/test/test_bug365410.html
new file mode 100644
index 000000000..93a55eac6
--- /dev/null
+++ b/layout/forms/test/test_bug365410.html
@@ -0,0 +1,134 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=365410
+-->
+<head>
+ <title>Test for Bug 365410</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=365410">Mozilla Bug 365410</a>
+<p id="display">
+<select id="test0" multiple="multiple">
+ <option id="option">Item 1</option>
+ <option>Item 2</option>
+ <option>Item 3</option>
+ <option>Item 4</option>
+ <option>Item 5</option>
+ <option>Item 6</option>
+ <option>Item 7</option>
+ <option>Item 8</option>
+ <option>Item 9</option>
+ <option>Item 10</option>
+ <option>Item 11</option>
+ <option>Item 12</option>
+ <option>Item 13</option>
+ <option>Item 14</option>
+ <option>Item 15</option>
+</select>
+<select id="test1" multiple="multiple" size="1">
+ <option>Item 1</option>
+ <option>Item 2</option>
+ <option>Item 3</option>
+ <option>Item 4</option>
+ <option>Item 5</option>
+ <option>Item 6</option>
+ <option>Item 7</option>
+ <option>Item 8</option>
+ <option>Item 9</option>
+ <option>Item 10</option>
+ <option>Item 11</option>
+ <option>Item 12</option>
+ <option>Item 13</option>
+ <option>Item 14</option>
+ <option>Item 15</option>
+</select>
+<select id="test2" multiple="multiple" size="1" style="height:0.9em">
+ <option>Item 1</option>
+ <option>Item 2</option>
+ <option>Item 3</option>
+ <option>Item 4</option>
+ <option>Item 5</option>
+ <option>Item 6</option>
+ <option>Item 7</option>
+ <option>Item 8</option>
+ <option>Item 9</option>
+ <option>Item 10</option>
+ <option>Item 11</option>
+ <option>Item 12</option>
+ <option>Item 13</option>
+ <option>Item 14</option>
+ <option>Item 15</option>
+</select>
+<select id="test3" multiple="multiple" size="1"></select>
+<select id="test4" multiple="multiple" size="1" style="height:0.9em"></select>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 365410 **/
+
+function pageUpDownTest(id,index) {
+ var elm = document.getElementById(id);
+ elm.focus();
+ elm.selectedIndex = 0;
+ sendKey("page_down");
+ sendKey("page_down");
+ sendKey("page_up");
+ sendKey("page_down");
+ is(elm.selectedIndex, index, "pageUpDownTest: selectedIndex for " + id + " is " + index);
+}
+
+function upDownTest(id,index) {
+ var elm = document.getElementById(id);
+ elm.focus();
+ elm.selectedIndex = 0;
+ sendKey("down");
+ sendKey("down");
+ sendKey("up");
+ sendKey("down");
+ is(elm.selectedIndex, index, "upDownTest: selectedIndex for " + id + " is " + index);
+}
+
+function setHeight(id, h) {
+ var elm = document.getElementById(id);
+ elm.style.height = h + 'px';
+}
+
+function runTest() {
+ var h = document.getElementById("option").clientHeight;
+ var list5itemsHeight = h * 5.5;
+ setHeight("test0", list5itemsHeight);
+ setHeight("test1", list5itemsHeight);
+ setHeight("test3", list5itemsHeight);
+
+ pageUpDownTest("test0",8);
+ pageUpDownTest("test1",8);
+ pageUpDownTest("test2",2);
+ pageUpDownTest("test3",-1);
+ pageUpDownTest("test4",-1);
+ upDownTest("test0",2);
+ upDownTest("test1",2);
+ upDownTest("test2",2);
+ upDownTest("test3",-1);
+ upDownTest("test4",-1);
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+// Turn off spatial nav so that it does not hijack the up and down events.
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[["snav.enabled", false]]}, runTest);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug378670.html b/layout/forms/test/test_bug378670.html
new file mode 100644
index 000000000..098ff75be
--- /dev/null
+++ b/layout/forms/test/test_bug378670.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=378670
+-->
+<head>
+ <title>Test for Bug 378670</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=378670">Mozilla Bug 378670</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+Clicking on the select should not crash Mozilla
+<select id="select">
+<option>1</option>
+<option>2</option>
+</select>
+
+<pre id="test">
+<script>
+document.body.addEventListener('popupshowing', function(e) {e.target.parentNode.removeChild(e.target) }, true);
+</script>
+<script type="application/javascript">
+
+/** Test for Bug 378670 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+function clickit() {
+ var select = document.getElementById('select');
+
+ sendMouseEvent({type:'mousedown'}, select);
+ sendMouseEvent({type:'mouseup'}, select);
+ sendMouseEvent({type:'click'}, select);
+
+ setTimeout(finish, 200);
+}
+
+window.addEventListener('load', clickit, false);
+
+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/layout/forms/test/test_bug402198.html b/layout/forms/test/test_bug402198.html
new file mode 100644
index 000000000..647a892fb
--- /dev/null
+++ b/layout/forms/test/test_bug402198.html
@@ -0,0 +1,77 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=402198
+-->
+<head>
+ <title>Test for Bug 402198</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=402198">Mozilla Bug 402198</a>
+<p id="display">
+ <select></select>
+ <select><optgroup></optgroup></select>
+ <legend style="overflow: scroll;">
+ <select></select>
+ </legend>
+ <span></span>
+ <span style="display: -moz-box;">
+ <select></select>
+ </span>
+ <legend style=" ">
+ <label style="overflow: scroll; display: -moz-box;">
+ <select></select>
+ </label>
+ </legend>
+ <legend>
+ <label style=" display: table;">
+ <select id="a">
+ <option>High Grade</option>
+ <option>Medium Grade</option>
+ </select>
+ </label>
+ </legend>
+
+ <input>
+ <select multiple="multiple"></select>
+ <select style="overflow: scroll; display: -moz-box;">
+ <optgroup></optgroup>
+ <optgroup style="display: table-cell;"></optgroup>
+ </select>
+</p>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+function doe3() {
+ document.documentElement.style.display = 'none';
+ document.body.offsetHeight;
+ document.documentElement.style.display = '';
+ document.body.offsetHeight;
+
+ document.getElementById('a').focus();
+ document.body.style.display = 'none';
+
+ synthesizeKey('KEY_Tab', { code: "Tab", shiftKey: true });
+
+ is(0, 0, "this is a crash/assertion test, so we're ok if we survived this far");
+ setTimeout(function() {document.body.style.display = ''; SimpleTest.finish();}, 0);
+}
+
+function do_test() {
+ setTimeout(doe3,300);
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+addLoadEvent(do_test);
+</script>
+</pre>
+
+<style>
+* {quotes: "quote" "quote" !important;}
+</style>
+
+</body>
+</html>
diff --git a/layout/forms/test/test_bug411236.html b/layout/forms/test/test_bug411236.html
new file mode 100644
index 000000000..a5369b832
--- /dev/null
+++ b/layout/forms/test/test_bug411236.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=411236
+-->
+<head>
+ <title>Test for Bug 411236</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=411236">Mozilla Bug 411236</a>
+<p id="display"></p>
+<div id="content">
+ <input type="file" onfocus="window.oTarget = event.originalTarget;"
+ onclick="window.fileInputGotClick = true; return false;"
+ id="fileinput">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 411236 **/
+
+window.oTarget = null;
+window.fileInputGotClick = false;
+
+function tab() {
+ var utils = SpecialPowers.DOMWindowUtils;
+ // Send tab key events.
+ var key = SpecialPowers.Ci.nsIDOMKeyEvent.DOM_VK_TAB;
+ utils.sendKeyEvent("keydown", key, 0, 0);
+ utils.sendKeyEvent("keypress", key, 0, 0);
+ utils.sendKeyEvent("keyup", key, 0, 0);
+}
+
+function test() {
+ // Try to find the 'Browse...' using tabbing.
+ var i = 0;
+ while (!window.oTarget && i < 100) {
+ ++i;
+ tab();
+ }
+
+ if (i >= 100) {
+ ok(false, "Couldn't find an input element!");
+ SimpleTest.finish();
+ return;
+ }
+
+ ok(window.oTarget instanceof HTMLButtonElement, "Should have focused an input element!")
+ ok(SpecialPowers.wrap(window.oTarget).type == "button", "Should have focused 'Browse...' button!");
+ var e = document.createEvent("mouseevents");
+ e.initMouseEvent("click", true, true, window, 0, 1, 1, 1, 1,
+ false, false, false, false, 0, null);
+ SpecialPowers.wrap(window.oTarget).dispatchEvent(e);
+ ok(window.fileInputGotClick,
+ "File input should have got a click event, but not open the file dialog.");
+ SimpleTest.finish();
+}
+
+function beginTest() {
+ // accessibility.tabfocus must be set to value 7 before running test also
+ // on a mac.
+ SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]}, do_test);
+}
+
+function do_test() {
+ window.focus();
+ document.getElementById('fileinput').focus();
+ setTimeout(test, 100);
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+addLoadEvent(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug446663.html b/layout/forms/test/test_bug446663.html
new file mode 100644
index 000000000..f12a47248
--- /dev/null
+++ b/layout/forms/test/test_bug446663.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=446663
+-->
+<head>
+ <title>Test for Bug 446663</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=446663">Mozilla Bug 446663</a>
+<p id="display">
+<style>#bug446663_a:focus{overflow:hidden}</style>
+<input id="bug446663_a"><input id="bug446663_b"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 446663 **/
+
+function test_edit_cmds(id) {
+
+ var elm = document.getElementById(id);
+ elm.focus();
+ elm.select();
+ SpecialPowers.wrap(elm).controllers.getControllerForCommand('cmd_cut')
+ .doCommand('cmd_cut');
+ is(elm.value, '', id + " cut");
+
+ SpecialPowers.wrap(elm).controllers.getControllerForCommand('cmd_undo')
+ .doCommand('cmd_undo');
+ is(elm.value, '123', id + " undo");
+}
+
+var inputHappened = false;
+function inputListener() {
+ inputHappened = true;
+ $(id).removeEventListener("input", inputListener, false);
+}
+
+var id = 'bug446663_a'
+var elm = document.getElementById(id);
+elm.focus();
+var x = document.body.offsetHeight;
+$(id).addEventListener("input", inputListener, false);
+sendChar('1');
+is(inputHappened, true, "How come no input?");
+sendChar('3');
+sendKey('LEFT')
+sendChar('2');
+elm.blur();
+x = document.body.offsetHeight;
+is(elm.value, '123', id + " edit");
+test_edit_cmds(id)
+
+id = 'bug446663_b'
+elm = document.getElementById(id);
+elm.focus();
+sendChar('1');
+elm.style.display = 'none'
+var x = document.body.offsetHeight;
+elm.style.display = 'inline'
+x = document.body.offsetHeight;
+sendChar('3');
+sendKey('LEFT')
+sendChar('2');
+elm.blur();
+x = document.body.offsetHeight;
+is(elm.value, '123', id + " edit");
+test_edit_cmds(id)
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/forms/test/test_bug476308.html b/layout/forms/test/test_bug476308.html
new file mode 100644
index 000000000..d8f572803
--- /dev/null
+++ b/layout/forms/test/test_bug476308.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=476308
+-->
+<head>
+ <title>Test for Bug 345267</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>
+
+<button style="-moz-appearance: none; width: 100px; height: 60px; background-color: red; border: 2px solid green; box-shadow: 30px 0px 3.5px black; position: absolute; top: 300px; left: 20px;"
+ id="button1">1</button>
+
+<br />
+<div style="width: 100px; height: 100px; background-color: green; border: 3px dotted blue; box-shadow: -30px -20px 0px black; position: absolute; top: 500px; left: 70px;"
+ id="div1">2</div>
+
+<script type="text/javascript">
+ var elem = document.elementFromPoint(130, 310);
+ isnot(elem, document.getElementById("button1"), "button1's box-shadow is receiving events when it shouldn't");
+
+ elem = document.elementFromPoint(50, 500);
+ isnot(elem, document.getElementById("div1"), "div1's box-shadow is receiving events when it shouldn't");
+</script>
+
+</body>
+</html>
+
diff --git a/layout/forms/test/test_bug477531.html b/layout/forms/test/test_bug477531.html
new file mode 100644
index 000000000..d766d3f7d
--- /dev/null
+++ b/layout/forms/test/test_bug477531.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=477531
+-->
+<head>
+ <title>Test for Bug 477531</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">
+ #s {
+ margin-left: 10px;
+ }
+
+ #s:indeterminate {
+ margin-left: 30px;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=477531">Mozilla Bug 477531</a>
+<p id="display"></p>
+<div id="content">
+
+<input type="checkbox" id="s" />
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 477531 **/
+is(document.defaultView.getComputedStyle($("s"), null).getPropertyValue("margin-left"),
+ "10px",
+ "Non-indeterminate checkbox should have a margin of 10px");
+
+$("s").indeterminate = true;
+
+is(document.defaultView.getComputedStyle($("s"), null).getPropertyValue("margin-left"),
+ "30px",
+ "Indeterminate checkbox should have a margin of 30px");
+
+$("s").setAttribute("type", "radio");
+
+is(document.defaultView.getComputedStyle($("s"), null).getPropertyValue("margin-left"),
+ "30px",
+ "Setting an indeterminate element to type radio should give it indeterminate styles");
+
+$("s").setAttribute("type", "checkbox");
+
+is(document.defaultView.getComputedStyle($("s"), null).getPropertyValue("margin-left"),
+ "30px",
+ "Setting an indeterminate element to type checkbox should give it indeterminate styles");
+
+$("s").indeterminate = false;
+
+is(document.defaultView.getComputedStyle($("s"), null).getPropertyValue("margin-left"),
+ "10px",
+ "Newly non-indeterminate checkbox should have a margin of 10px");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/forms/test/test_bug477700.html b/layout/forms/test/test_bug477700.html
new file mode 100644
index 000000000..45537257b
--- /dev/null
+++ b/layout/forms/test/test_bug477700.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=477700
+-->
+<head>
+ <title>Test for Bug 477700</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=477700">Mozilla Bug 477700</a>
+<p id="display">
+ <iframe id="i"
+ src="http://example.com/tests/layout/forms/test/bug477700_subframe.html"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 477700 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ isnot(window.location.host, "example.com", "test is not testing cross-site");
+ var accessed = false;
+ try {
+ $("i").contentDocument.documentElement;
+ accessed = true;
+ } catch(e) {}
+ is(accessed, false, "Shouldn't be able to access cross-site");
+
+ $("i").style.display = "none";
+ document.body.offsetWidth;
+ is(document.defaultView.getComputedStyle($("i"), "").display, "none",
+ "toggling display failed");
+ $("i").style.display = "";
+ document.body.offsetWidth;
+ is(document.defaultView.getComputedStyle($("i"), "").display, "inline",
+ "toggling display back failed");
+
+ $("i").contentWindow.postMessage("start", "*");
+});
+
+window.addEventListener("message",
+ function(evt) {
+ var arr = evt.data.split(/ /).map(decodeURIComponent);
+ if (arr[0] == 't') {
+ is(arr[1], arr[2], arr[3]);
+ } else if (arr[0] == 'f') {
+ SimpleTest.finish();
+ }
+ },
+ false);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug478219.xhtml b/layout/forms/test/test_bug478219.xhtml
new file mode 100644
index 000000000..42e5f07b7
--- /dev/null
+++ b/layout/forms/test/test_bug478219.xhtml
@@ -0,0 +1,38 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=478219
+-->
+<head>
+ <title>Test for Bug 478219</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ </style>
+</head>
+
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=478219">Mozilla Bug 478219</a>
+<input style="overflow: scroll;-moz-binding:url(#xbl);" type="image"/>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ var win = window.open('', '', 'width=100,height=100');
+ win.close();
+ is(win.closed, true, "Window should have been opened");
+ is(win.opener, window, "Opened window should have as opener this window");
+</script>
+</pre>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="xbl" inheritstyle="false">
+<content>
+
+<div xmlns="http://www.w3.org/1999/xhtml" contenteditable="true">
+<style>style {-moz-binding:url(#xbl);</style>
+</div>
+<textarea xmlns="http://www.w3.org/1999/xhtml"></textarea>
+</content>
+</binding>
+</bindings>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug534785.html b/layout/forms/test/test_bug534785.html
new file mode 100644
index 000000000..76590798f
--- /dev/null
+++ b/layout/forms/test/test_bug534785.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=534785
+-->
+<head>
+ <title>Test for Bug 534785</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=534785">Mozilla Bug 534785</a>
+<p id="display"></p>
+<input type="text" value="test">
+<div id="reframe">
+<textarea></textarea>
+<textarea>test</textarea>
+<input type="text">
+<input type="text" value="test">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 534785 **/
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function() {
+ var i = document.querySelector("input");
+ i.addEventListener("focus", function() {
+ is(i.value, "test", "Sanity check");
+
+ is(document.activeElement, i, "Should be focused before frame reconstruction");
+ synthesizeKey("1", {});
+ is(i.value, "1test", "Can accept keyboard events before frame reconstruction");
+
+ // force frame reconstruction
+ i.style.display = "none";
+ document.offsetHeight;
+ i.style.display = "";
+ document.offsetHeight;
+
+ is(document.activeElement, i, "Should be focused after frame reconstruction");
+ synthesizeKey("2", {});
+ is(i.value, "12test", "Can accept keyboard events after frame reconstruction");
+
+ // Make sure reframing happens gracefully
+ var reframeDiv = document.getElementById("reframe");
+ var textAreaWithoutValue = reframeDiv.querySelectorAll("textarea")[0];
+ var textAreaWithValue = reframeDiv.querySelectorAll("textarea")[1];
+ var inputWithoutValue = reframeDiv.querySelectorAll("input")[0];
+ var inputWithValue = reframeDiv.querySelectorAll("input")[1];
+ reframeDiv.style.display = "none";
+ document.body.offsetWidth;
+ reframeDiv.style.display = "";
+ document.body.offsetWidth;
+ [textAreaWithoutValue, inputWithoutValue].forEach(function (elem) {
+ is(elem.value, "", "Value should persist correctly");
+ });
+ [textAreaWithValue, inputWithValue].forEach(function (elem) {
+ is(elem.value, "test", "Value should persist correctly");
+ });
+ [inputWithoutValue, inputWithValue].forEach(elem => elem.type = "submit");
+ document.body.offsetWidth;
+ is(inputWithoutValue.value, "", "Value should persist correctly");
+ is(inputWithValue.value, "test", "Value should persist correctly");
+ [inputWithoutValue, inputWithValue].forEach(elem => elem.type = "text");
+ document.body.offsetWidth;
+ is(inputWithoutValue.value, "", "Value should persist correctly");
+ is(inputWithValue.value, "test", "Value should persist correctly");
+ [inputWithoutValue, inputWithValue].forEach(elem => elem.focus()); // initialze the editor
+ reframeDiv.style.display = "none";
+ document.body.offsetWidth;
+ reframeDiv.style.display = "";
+ document.body.offsetWidth;
+ is(inputWithoutValue.value, "", "Value should persist correctly with editor");
+ is(inputWithValue.value, "test", "Value should persist correctly with editor");
+
+ SimpleTest.finish();
+ }, false);
+ i.focus();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug536567_perwindowpb.html b/layout/forms/test/test_bug536567_perwindowpb.html
new file mode 100644
index 000000000..8bb2f68ce
--- /dev/null
+++ b/layout/forms/test/test_bug536567_perwindowpb.html
@@ -0,0 +1,215 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=536567
+-->
+<head>
+ <title>Test for Bug 536567</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=536567">Mozilla Bug 536567</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 536567 **/
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cm = Components.manager;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+var tmpDir = Services.dirsvc.get("TmpD", Ci.nsILocalFile);
+var homeDir = Services.dirsvc.get("Desk", Ci.nsILocalFile);
+
+function newDir() {
+ var dir = tmpDir.clone();
+ dir.append("testdir" + Math.floor(Math.random() * 10000));
+ dir.QueryInterface(Ci.nsILocalFile);
+ dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0700);
+ return dir;
+}
+
+var dirs = [];
+for(var i = 0; i < 6; i++) {
+ dirs.push(newDir());
+}
+dirs.push(homeDir);
+var domains = ['http://mochi.test:8888', 'http://example.org:80', 'http://example.com:80'];
+/*
+ * These tests take 3 args each:
+ * - which domain to load
+ * - the filePicker displayDirectory we expect to be set
+ * - the file to pick (in most cases this will show up in the next test,
+ * as indicated by the comments)
+ */
+var tests = [
+ "clear history",
+ [0, 6, 0], // 0 -> 3
+ [1, 6, 1], // 1 -> 4
+ [2, 6, 2], // 2 -> 5
+ [0, 0, 3], // 3 -> 6
+ [1, 1, 1], // 4 -> 8
+ [2, 2, 2], // 5 -> 9
+ [0, 3, 1], // 6 -> 7
+ [0, 1, 0], // 7 -> x
+ [1, 1, 1], // 8 -> x
+ [2, 2, 2], // 9 -> x
+ "clear history",
+ [0, 6, 0], // 11 -> 15
+ [1, 6, 1], // 12 -> 16
+ [2, 6, 2], // 13 -> 17
+ "pb on",
+ [0, 0, 3], // 15 -> 18
+ [1, 1, 4], // 16 -> 19
+ [2, 2, 5], // 17 -> 20
+ [0, 3, 3], // 18 -> x
+ [1, 4, 4], // 19 -> x
+ [2, 5, 5], // 20 -> x
+ "pb off",
+ [0, 0, 5], // 22 -> 26
+ [1, 1, 4], // 23 -> 27
+ [2, 2, 3], // 24 -> 28
+ "pb on",
+ [0, 3, 5], // 26 -> x
+ [1, 4, 4], // 27 -> x
+ [2, 5, 3], // 28 -> x
+ "clear history",
+ // Not checking after clear history because browser.download.lastDir content
+ // pref is not being clear properly in private windows.
+ //[0, 6, 0], // 30 -> x
+ //[1, 6, 1], // 31 -> x
+ //[2, 6, 2], // 32 -> x
+ "pb off"
+];
+
+var testIndex = 0;
+var content;
+var normalWindow;
+var privateWindow;
+var normalWindowIframe;
+var privateWindowIframe;
+
+function runTest() {
+ var test = tests[testIndex];
+ if (test == undefined) {
+ endTest();
+ } else if (test == "pb on") {
+ content = privateWindowIframe;
+ testIndex++;
+ runTest();
+ } else if (test == "pb off") {
+ content = normalWindowIframe;
+ testIndex++;
+ runTest();
+ } else if (test == "clear history") {
+ Services.obs.notifyObservers(null, "browser:purge-session-history", "");
+ testIndex++;
+ runTest();
+ } else {
+ var file = dirs[test[2]].clone();
+ file.append("file.file");
+ MockFilePicker.returnFiles = [file];
+ content.setAttribute('src', domains[test[0]] + '/chrome/layout/forms/test/bug536567_subframe.html');
+ }
+}
+
+function endTest() {
+ for(var i = 0; i < dirs.length - 1; i++) {
+ dirs[i].remove(true);
+ }
+
+ normalWindow.close();
+ privateWindow.close();
+ MockFilePicker.cleanup();
+ SimpleTest.finish();
+}
+
+var mainWindow =
+ window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShellTreeItem).
+ rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindow);
+var contentPage = "http://mochi.test:8888/chrome/layout/forms/test/bug536567_iframe.html";
+
+function whenDelayedStartupFinished(aWindow, aCallback) {
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ setTimeout(aCallback, 0);
+ }
+ }, "browser-delayed-startup-finished", false);
+}
+
+function testOnWindow(aIsPrivate, aCallback) {
+ var win = mainWindow.OpenBrowserWindow({private: aIsPrivate});
+ whenDelayedStartupFinished(win, function() {
+ win.addEventListener("DOMContentLoaded", function onInnerLoad() {
+ if (win.content.location.href != contentPage) {
+ win.gBrowser.loadURI(contentPage);
+ return;
+ }
+ win.removeEventListener("DOMContentLoaded", onInnerLoad, true);
+ win.gBrowser.selectedBrowser.focus();
+ SimpleTest.info("DOMContentLoaded's window: " + win.location + " vs. " + window.location);
+ win.setTimeout(function() { aCallback(win); }, 0);
+ }, true);
+ SimpleTest.info("load's window: " + win.location + " vs. " + window.location);
+ win.setTimeout(function() { win.gBrowser.loadURI(contentPage); }, 0);
+ });
+}
+
+MockFilePicker.showCallback = function(filepicker) {
+ var test = tests[testIndex];
+ var returned = -1;
+ for (var i = 0; i < dirs.length; i++) {
+ if (dirs[i].path == MockFilePicker.displayDirectory.path) {
+ returned = i;
+ break;
+ }
+ }
+ if (test[1] == -1) {
+ ok(false, "We should never get an unknown directory back");
+ } else {
+ is(returned, test[1], 'test ' + testIndex);
+ }
+
+ filepicker.window.setTimeout(function() {
+ testIndex++;
+ runTest();
+ }, 0);
+};
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ testOnWindow(false, function(aWin) {
+ var selectedBrowser = aWin.gBrowser.selectedBrowser;
+
+ normalWindow = aWin;
+ normalWindowIframe =
+ selectedBrowser.contentDocument.getElementById("content");
+
+ testOnWindow(true, function(aPrivateWin) {
+ selectedBrowser = aPrivateWin.gBrowser.selectedBrowser;
+
+ privateWindow = aPrivateWin;
+ privateWindowIframe =
+ selectedBrowser.contentDocument.getElementById("content");
+
+ content = normalWindowIframe;
+ selectedBrowser.contentWindow.setTimeout(runTest, 0);
+ });
+ });
+};
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug542914.html b/layout/forms/test/test_bug542914.html
new file mode 100644
index 000000000..dc2fadb3a
--- /dev/null
+++ b/layout/forms/test/test_bug542914.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=542914
+-->
+<head>
+ <title>Test for Bug 542914</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=542914">Mozilla Bug 542914</a>
+<p id="display">
+ <input type="text" id="a" value="test">
+ <input type="text" id="b">
+ <input type="text" id="c">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 542914 **/
+SimpleTest.waitForExplicitFinish();
+function runTests(callback, type) {
+ var a = $("a");
+
+ // Test that the initial value of the control is available to script
+ // without initilization of the editor
+ is(a.value, "test", "The value is available before initialization");
+ // Initialize the editor
+ a.focus();
+ // Test that the value does not change after initialization
+ is(a.value, "test", "The value does not change after initializtion");
+
+ var b = $("b");
+
+ // Test that the initial value is empty before initialization.
+ is(b.value, "", "The value is empty before initialization");
+ // Make sure that the value can be changed before initialization
+ b.value ="some value";
+ is(b.value, "some value", "The value can be changed before initialization");
+ // Initialize the editor
+ b.focus();
+ // Make sure that the value does not change after initialization
+ is(b.value, "some value", "The value does not change after initialization");
+ // Make sure that the value does not change if the element is hidden
+ b.style.display = "none";
+ document.body.offsetHeight;
+ is(b.value, "some value", "The value does not change while hidden");
+ b.style.display = "";
+ document.body.offsetHeight;
+ b.focus();
+ is(b.value, "some value", "The value does not change after being shown");
+
+ var c = $("c");
+
+ // Make sure that the control accepts input events without explicit initialization
+ is(c.value, "", "Control is empty initially");
+ c.focus();
+ sendChar("a");
+ is(c.value, "a", "Control accepts input without explicit initialization");
+ // Make sure that the control retains its caret position
+ c.focus();
+ c.blur();
+ c.focus();
+ sendChar("b");
+ is(c.value, "ab", "Control retains caret position after being re-focused");
+
+ var d = document.createElement("input");
+ d.setAttribute("type", type);
+ $("display").appendChild(d);
+ document.body.offsetHeight;
+
+ // Make sure dynamically injected inputs work as expected
+ is(d.value, "", "Dynamic control's initial value should be empty");
+ d.value = "new";
+ d.focus();
+ is(d.value, "new", "Dynamic control's value can be set before initialization");
+ sendChar("x");
+ is(d.value, "newx", "Dynamic control accepts keyboard input without explicit initialization");
+ $("display").removeChild(d);
+ is(d.value, "newx", "Dynamic control retains value after being removed from the document");
+
+ callback();
+}
+
+var gPreviousType = "text";
+function setTypes(aType) {
+ var content = document.getElementById("display");
+ content.innerHTML = content.innerHTML.replace(gPreviousType, aType);
+ gPreviousType = aType;
+}
+
+addLoadEvent(function() {
+ ok(true, "Running tests on <input type=text>");
+ runTests(function() {
+ ok(true, "Running tests on <input type=password>");
+ setTypes("password");
+ runTests(function() {
+ ok(true, "Running tests on <input type=tel>");
+ setTypes("tel");
+ runTests(function() {
+ SimpleTest.finish();
+ }, "tel");
+ }, "password");
+ }, "text");
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug549170.html b/layout/forms/test/test_bug549170.html
new file mode 100644
index 000000000..37c5bb336
--- /dev/null
+++ b/layout/forms/test/test_bug549170.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=549170
+-->
+<head>
+ <title>Test for Bug 549170</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="window.setTimeout(runTests, 0);">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=549170">Mozilla Bug 549170</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<input id='i'
+ onmouseup="mouseHandler(event);"
+ onmousedown="mouseHandler(event);">
+<textarea id='t'
+ onmouseup="mouseHandler(event);"
+ onmousedown="mouseHandler(event);"></textarea><br>
+<input id='ip' placeholder='foo'
+ onmouseup="mouseHandler(event);"
+ onmousedown="mouseHandler(event);">
+<textarea id='tp' placeholder='foo'
+ onmouseup="mouseHandler(event);"
+ onmousedown="mouseHandler(event);"></textarea>
+<pre id="test">
+
+<script type="application/javascript">
+
+/** Test for Bug 549170 **/
+
+var gNumberOfMouseEventsCatched = 0;
+
+SimpleTest.waitForExplicitFinish();
+
+function mouseHandler(aEvent)
+{
+ gNumberOfMouseEventsCatched++;
+ is(SpecialPowers.wrap(aEvent).originalTarget.nodeName, "DIV", "An inner div should be the target of the event");
+ ok(SpecialPowers.wrap(aEvent).originalTarget.classList.contains("anonymous-div"), "the target div should be the editor div");
+}
+
+function checkMouseEvents(element)
+{
+ gNumberOfMouseEventsCatched = 0;
+
+ synthesizeMouse(element, 5, 5, {type: "mousedown", button: 0});
+ synthesizeMouse(element, 5, 5, {type: "mouseup", button: 0});
+ synthesizeMouse(element, 5, 5, {type: "mousedown", button: 1});
+ // NOTE: this event is going to copy the buffer on linux, this should not be a problem
+ synthesizeMouse(element, 5, 5, {type: "mouseup", button: 1});
+ synthesizeMouse(element, 5, 5, {type: "mousedown", button: 2});
+ synthesizeMouse(element, 5, 5, {type: "mouseup", button: 2});
+
+ is(gNumberOfMouseEventsCatched, 6, "Some mouse events have not been catched");
+}
+
+function runTests()
+{
+ checkMouseEvents(document.getElementById('i'));
+ checkMouseEvents(document.getElementById('t'));
+ checkMouseEvents(document.getElementById('ip'));
+ checkMouseEvents(document.getElementById('tp'));
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug562447.html b/layout/forms/test/test_bug562447.html
new file mode 100644
index 000000000..53f84428e
--- /dev/null
+++ b/layout/forms/test/test_bug562447.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=562447
+-->
+<head>
+ <title>Test for Bug 562447</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+</head>
+<body>
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug?id=562447">Mozilla Bug 562447</a>
+
+<input id="WhyDoYouFocusMe"
+ style="position: absolute; left: -50px; top: 10000px;">
+
+<pre id="test">
+<script>
+addLoadEvent(function() {
+ // Scroll down a bit
+ window.scrollTo(0, 5000);
+
+ setTimeout(function() {
+ // Make sure that we're scrolled by 5000px
+ is(window.pageYOffset, 5000, "Make sure we're scrolled correctly");
+
+ // Scroll back up, and mess with the input box along the way
+ var input = document.getElementById("WhyDoYouFocusMe");
+ input.focus();
+ input.blur();
+ window.scrollTo(0, 0);
+
+ setTimeout(function() {
+ is(window.pageYOffset, 0, "Make sure we're scrolled back up correctly");
+
+ // Scroll back up
+ window.scrollTo(0, 5000);
+
+ setTimeout(function() {
+ is(window.pageYOffset, 5000, "Sanity check");
+
+ window.scrollTo(0, 0);
+ input.focus();
+ input.blur();
+
+ setTimeout(function() {
+ isnot(window.pageYOffset, 0, "This time we shouldn't be scrolled up");
+
+ SimpleTest.finish();
+ }, 0);
+ }, 0);
+ }, 0);
+ }, 0);
+});
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/layout/forms/test/test_bug563642.html b/layout/forms/test/test_bug563642.html
new file mode 100644
index 000000000..9c5171d70
--- /dev/null
+++ b/layout/forms/test/test_bug563642.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=563642
+-->
+<head>
+ <title>Test for Bug 563642</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=563642">Mozilla Bug 563642</a>
+<p id="display">
+<select id="test1" multiple="multiple" size="1">
+ <option>Item 1</option>
+ <option>Item 2</option>
+ <option>Item 3</option>
+ <option>Item 4</option>
+ <option>Item 5</option>
+</select>
+<select id="test2" multiple="multiple" size="1">
+ <option>Item 1</option>
+ <option disabled>Item 2</option>
+ <option>Item 3</option>
+ <option disabled>Item 4</option>
+ <option>Item 5</option>
+</select>
+<select id="test3" multiple="multiple"></select>
+<select id="test4" multiple="multiple" size="1"></select>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 563642 **/
+
+function pageUpDownTest(id,index) {
+ var elm = document.getElementById(id);
+ elm.focus();
+ elm.selectedIndex = 0;
+ sendKey("page_down");
+ sendKey("page_down");
+ sendKey("page_down");
+ sendKey("page_up");
+ sendKey("page_down");
+ is(elm.selectedIndex, index, "pageUpDownTest: selectedIndex for " + id + " is " + index);
+}
+
+function upDownTest(id,index) {
+ var elm = document.getElementById(id);
+ elm.focus();
+ elm.selectedIndex = 0;
+ sendKey("down");
+ sendKey("down");
+ sendKey("down");
+ sendKey("up");
+ sendKey("down");
+ is(elm.selectedIndex, index, "upDownTest: selectedIndex for " + id + " is " + index);
+}
+
+function runTest() {
+ pageUpDownTest("test1",3);
+ pageUpDownTest("test2",4);
+ pageUpDownTest("test3",-1);
+ pageUpDownTest("test4",-1);
+ upDownTest("test1",3);
+ upDownTest("test2",4);
+ upDownTest("test3",-1);
+ upDownTest("test4",-1);
+
+ SimpleTest.finish();
+}
+SimpleTest.waitForExplicitFinish();
+// Turn off Spatial Navigation because it hijacks down and up key events.
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set":[["snav.enabled", false]]}, runTest);
+})
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug564115.html b/layout/forms/test/test_bug564115.html
new file mode 100644
index 000000000..5723b55d5
--- /dev/null
+++ b/layout/forms/test/test_bug564115.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=564115
+-->
+<head>
+ <title>Test for Bug 564115</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+</head>
+<body>
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug?id=564115">Mozilla Bug 564115</a>
+
+<pre id="test">
+<script>
+
+const TEST_URL = "/tests/layout/forms/test/bug564115_window.html";
+
+addLoadEvent(function() {
+ var win = open(TEST_URL, "", "width=600,height=600");
+ SimpleTest.waitForFocus(function() {
+ var doc = win.document;
+ var input = doc.querySelector("input");
+
+ // Focus the input box, and wait for the focus to actually happen
+ input.focus();
+ setTimeout(function() {
+ // Scroll down a bit
+ win.scrollTo(0, 5000);
+
+ setTimeout(function() {
+ is(win.pageYOffset, 5000, "Page should be scrolled correctly");
+
+ // Refocus the window
+ SimpleTest.waitForFocus(function() {
+ SimpleTest.waitForFocus(function() {
+ is(win.pageYOffset, 5000,
+ "The page's scroll offset should not have been changed");
+
+ win.close();
+ SimpleTest.finish();
+ }, win);
+ });
+ }, 0);
+ }, 0);
+ }, win);
+});
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/layout/forms/test/test_bug571352.html b/layout/forms/test/test_bug571352.html
new file mode 100644
index 000000000..547ab4007
--- /dev/null
+++ b/layout/forms/test/test_bug571352.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=571352
+-->
+<head>
+ <title>Test for Bug 571352</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=571352">Mozilla Bug 571352</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 571352 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function test() {
+ function createList() {
+ $('display').innerHTML = '<select multiple><option>0<option>1<option>2<option>3<option>4<option>5</select>';
+ $('display').firstChild.focus();
+ }
+ function option(index) {
+ return $('display').firstChild.childNodes[index];
+ }
+ function remove(index) {
+ var sel = $('display').firstChild;
+ sel.removeChild(sel.childNodes[index]);
+ }
+ function up() { synthesizeKey("VK_UP", {}); }
+ function shiftUp() { synthesizeKey("VK_UP", {shiftKey:true}); }
+ function down() { synthesizeKey("VK_DOWN", {}); }
+ function shiftDown() { synthesizeKey("VK_DOWN", {shiftKey:true}); }
+ function mouseEvent(index,event) {
+ synthesizeMouse(option(index), 5, 5, event);
+ }
+
+ const click = {};
+ const shiftClick = {shiftKey:true};
+ const CtrlClick = {CtrlKey:true};
+ createList();
+ mouseEvent(0,click)
+ is(document.activeElement,$('display').firstChild,"<select> is focused");
+ mouseEvent(2,shiftClick)
+ remove(0);
+ ok(option(0).selected && option(1).selected,"first two options are selected");
+ mouseEvent(2,shiftClick)
+ ok(option(0).selected && option(1).selected && option(2).selected,"first three options are selected");
+ shiftUp();
+ ok(option(0).selected && option(1).selected,"first two options are selected");
+ remove(1);
+ ok(option(0).selected,"first option is selected");
+ shiftDown();
+ ok(option(0).selected && option(1).selected,"first two options are selected");
+ down();
+ ok(option(2).selected,"third option is selected");
+ shiftDown();
+ ok(option(2).selected && option(3).selected,"third & fourth option are selected");
+ remove(2);
+ shiftUp();
+ ok(option(1).selected && option(2).selected,"2nd & third option are selected");
+ remove(0);
+ mouseEvent(0,shiftClick)
+ ok(option(0).selected && option(1).selected,"all remaining 2 options are selected");
+ shiftDown();
+ remove(1);
+ ok(!option(0).selected,"first option is unselected");
+ remove(0); // select is now empty
+ ok($('display').firstChild.firstChild==null,"all options were removed");
+
+ SimpleTest.finish();
+});
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug572406.html b/layout/forms/test/test_bug572406.html
new file mode 100644
index 000000000..3f2693c75
--- /dev/null
+++ b/layout/forms/test/test_bug572406.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=572406
+-->
+<head>
+ <title>Test for Bug 572406</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload='runTests();'>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=572406">Mozilla Bug 572406</a>
+<p id="display"></p>
+<div id='content'>
+ <textarea id='i'>foo</textarea>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 572406 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function runTests()
+{
+ var textarea = document.getElementById('i');
+
+ textarea.firstChild.nodeValue = "bar";
+ is(textarea.value, textarea.firstChild.nodeValue,
+ "textarea value should be firstChild.nodeValue");
+ is(textarea.defaultValue, textarea.firstChild.nodeValue,
+ "textarea defaultValue should be firstChild.nodeValue");
+
+ textarea.style.display = 'none';
+ SimpleTest.executeSoon(function() {
+ textarea.firstChild.nodeValue = "tulip";
+ is(textarea.value, textarea.firstChild.nodeValue,
+ "textarea value should be firstChild.nodeValue");
+ is(textarea.defaultValue, textarea.firstChild.nodeValue,
+ "textarea defaultValue should be firstChild.nodeValue");
+ SimpleTest.finish();
+ });
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug572649.html b/layout/forms/test/test_bug572649.html
new file mode 100644
index 000000000..c6f00f3bb
--- /dev/null
+++ b/layout/forms/test/test_bug572649.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=572649
+-->
+<head>
+ <title>Test for Bug 572649</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=572649">Mozilla Bug 572649</a>
+<p id="display">
+ <textarea id="area" rows="5">
+ Here
+ is
+ some
+ very
+ long
+ text
+ that
+ we're
+ using
+ for
+ testing
+ purposes
+ </textarea>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 572649 **/
+SimpleTest.waitForExplicitFinish();
+
+// We intermittently trigger two "Wrong parent style context" assertions
+// on B2G emulator builds (bug XXXXXXX). The two frames that get incorrect
+// style context parents are scroll bar parts in the <textarea>.
+SimpleTest.expectAssertions(0, 2);
+
+addLoadEvent(function() {
+ var area = document.getElementById("area");
+
+ is(area.scrollTop, 0, "The textarea should not be scrolled initially");
+ area.addEventListener("focus", function() {
+ area.removeEventListener("focus", arguments.callee, false);
+ setTimeout(function() {
+ is(area.scrollTop, 0, "The textarea's insertion point should not be scrolled into view");
+
+ SimpleTest.finish();
+ }, 0);
+ }, false);
+ setTimeout(function() {
+ var rect = area.getBoundingClientRect();
+ synthesizeMouse(area, rect.width - 5, 5, {});
+ }, 0);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug595310.html b/layout/forms/test/test_bug595310.html
new file mode 100644
index 000000000..295da1170
--- /dev/null
+++ b/layout/forms/test/test_bug595310.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=595310
+-->
+<head>
+ <title>Test for Bug 595310</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/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=595310">Mozilla Bug 595310</a>
+<p id="display"></p>
+<div id="content">
+ <input id='i' value="bar">
+ <textarea id='t'>bar</textarea>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 595310 **/
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // We want to compare to value="bar" and no placeholder shown.
+ // That is what is currently showed.
+ var s1 = snapshotWindow(window, false);
+
+ var content = document.getElementById('content');
+ var i = document.getElementById('i');
+ var t = SpecialPowers.wrap(document.getElementById('t'));
+ i.value = ""; i.placeholder = "foo";
+ t.value = ""; t.placeholder = "foo";
+
+ // Flushing.
+ // Note: one call would have been enough actually but I didn't want to favour
+ // one element... ;)
+ i.getBoundingClientRect();
+ t.getBoundingClientRect();
+
+ function synthesizeDropText(aElement, aText)
+ {
+ var editor = SpecialPowers.wrap(aElement)
+ .QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement)
+ .editor
+ .QueryInterface(SpecialPowers.Ci.nsIPlaintextEditor);
+
+ editor.insertText(aText);
+ }
+
+ // We insert "bar" and we should only see "bar" now.
+ synthesizeDropText(i, "bar");
+ synthesizeDropText(t, "bar");
+
+ var s2 = snapshotWindow(window, false);
+
+ ok(compareSnapshots(s1, s2, true)[0],
+ "When setting the value, the placeholder should disappear.");
+
+ SimpleTest.finish();
+});
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug620936.html b/layout/forms/test/test_bug620936.html
new file mode 100644
index 000000000..5f02129ac
--- /dev/null
+++ b/layout/forms/test/test_bug620936.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=620936
+-->
+<head>
+ <title>Test for Bug 620936</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=620936">Mozilla Bug 620936</a>
+<p id="display"></p>
+<div id="content">
+ <input value="foo">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 620936 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var i = document.querySelector("input");
+ i.focus();
+ i.setSelectionRange(100, 100);
+ is(i.selectionStart, 3, "The selection should be set to the end of the text");
+ is(i.selectionEnd, 3, "The selection should be set to the end of the text");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug644542.html b/layout/forms/test/test_bug644542.html
new file mode 100644
index 000000000..dca37c390
--- /dev/null
+++ b/layout/forms/test/test_bug644542.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=644542
+-->
+<head>
+ <title>Test for Bug 644542</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 type="text/css">
+ select:after {
+ content: ' appended string';
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=644542">Mozilla Bug 644542</a>
+<p id="display">
+ <form method="post" action="">
+ <select id="select">
+ <option value="1">1</option>
+ <option value="2">2</option>
+ </select>
+ </form>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 644542 **/
+
+var select = document.getElementById("select");
+
+var clicks = 0;
+
+function click() {
+ synthesizeMouseAtCenter(select, { });
+ ++clicks;
+
+ // At least two clicks were required for bug 644542, sometimes more;
+ // delay is long enough that this doesn't look like a double
+ // click, and also allows time for popup to show and for painting.
+ setTimeout(clicks < 4 ? click : done, 500);
+}
+
+function done() {
+ ok(true, "No crash on opening dropdown");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+// waitForFocus is most likely not the right thing to wait for, but
+// without this the first click is ineffective (even with a reflow forced
+// before synthesizeMouse).
+SimpleTest.waitForFocus(click);
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug665540.html b/layout/forms/test/test_bug665540.html
new file mode 100644
index 000000000..51f8f568a
--- /dev/null
+++ b/layout/forms/test/test_bug665540.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=665540
+-->
+<head>
+ <title>Test for Bug 665540 Select dropdown position in fullscreen window</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body onload="openFullscreenWindow()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=665540">Mozilla Bug 665540</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 665540 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var win;
+var select;
+var optiona;
+var eventType = "mouseover";
+var timeoutID;
+var eventOffsetX = 2;
+var eventOffsetY = 2;
+
+function openFullscreenWindow() {
+ win = open("bug665540_window.xul", "_blank", "resizable=yes,chrome");
+ win.addEventListener("sizemodechange",
+ function() {
+ info("sizemodechange. windowState = " + win.windowState + " fullScreen = " + win.fullScreen);
+ }, false);
+ win.addEventListener("fullscreen",
+ function() {
+ info("fullscreen event. windowState = " + win.windowState + " fullScreen = " + win.fullScreen);
+ }, false);
+ info("win.windowState = " + win.windowState);
+ info("win.fullScreen = " + win.fullScreen);
+
+ // Close our window if the test times out so that it doesn't interfere
+ // with later tests.
+ timeoutID = setTimeout(function () {
+ ok(false, "Test timed out.");
+ // Provide some time for a screenshot
+ setTimeout(finish, 1000);
+ }, 20000);
+}
+
+function childFocused() {
+ ok(win.fullScreen, "window should be fullscreen");
+ is(win.windowState, win.STATE_FULLSCREEN,
+ "window state should be fullscreen");
+
+ // The select doesn't open if the mouse click is fired too soon
+ // (on X11 at least).
+ setTimeout(openSelect, 1000);
+}
+
+function openSelect() {
+ select = win.document.getElementById("select");
+ synthesizeMouseAtCenter(select, {}, win);
+ // A yield was required on X11 tinderbox machines.
+ // (Wasn't required on other platforms nor on an X11 system with kwin.)
+ setTimeout(checkPosition, 1000);
+}
+
+function checkPosition() {
+ optiona = win.document.getElementById("optiona");
+ optiona.addEventListener(eventType, eventReceived, false);
+
+ // If the select dropdown is opened in the position where
+ // getBoundingClientRect() predicts, then optiona will receive the event.
+ // The event is received asynchronously (I don't know why), so the handler
+ // is removed later.
+ synthesizeMouse(optiona, eventOffsetX, eventOffsetY,
+ { type: eventType }, win);
+}
+
+function eventReceived(event) {
+ clearTimeout(timeoutID);
+ optiona.removeEventListener(eventType, eventReceived, false);
+
+ var rect = optiona.getBoundingClientRect();
+
+ // Note that fullscreen only fully covers one monitor, so win.screenX can
+ // be non-zero.
+ is(event.screenX, win.screenX + rect.left + eventOffsetX,
+ "event.screenX should match sent event");
+ is(event.screenY, win.screenY + rect.top + eventOffsetY,
+ "event.screenY should match sent event");
+
+ finish();
+}
+
+function finish() {
+ if (select && navigator.platform.indexOf("Win") >= 0) {
+ todo(false,
+ "Should not have to close select before closing its window");
+ // This avoids mochitest "Unable to restore focus" errors (bug 670053).
+ synthesizeMouseAtCenter(select, {}, win);
+ }
+
+ is(win.windowState, win.STATE_FULLSCREEN,
+ "window state should still be fullscreen");
+
+ win.close();
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug672810.html b/layout/forms/test/test_bug672810.html
new file mode 100644
index 000000000..4a3f74134
--- /dev/null
+++ b/layout/forms/test/test_bug672810.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=672810
+-->
+<head>
+ <title>Test for Bug 672810</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=672810">Mozilla Bug 672810</a>
+<p id="display"></p>
+<div id="content">
+ <select id="s1" multiple size="10"><option>x<option>x<option>x<option>x<option>x<option>x<option>x<option>x<option>x<option>x</select>
+ <select id="s2" size="10"><option>x<option>x<option>x<option>x<option>x<option>x<option>x<option>x<option>x<option>x</select>
+ <select id="s3" size="1"><option>x<option>x<option>x<option>x<option>x<option>x<option>x<option>x<option>x<option>x</select>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 672810 **/
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function() {
+ var sel = document.getElementsByTagName('select');
+
+ sel[0].addEventListener('focus', function() {
+ s = sel[0];
+ s.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey('VK_DOWN', {});
+ is(s.selectedIndex,0, s.id + ": initial DOWN selects first option");
+ synthesizeKey('VK_DOWN', {ctrlKey: true});
+ is(s.selectedIndex,0, s.id + ": first option is still selected");
+ ok(!s[1].selected,s.id + ": CTRL+DOWN did not select 2nd option");
+ synthesizeKey('VK_DOWN', {ctrlKey: true});
+ synthesizeKey('VK_DOWN', {ctrlKey: true, shiftKey: true});
+ is(s.selectedIndex,0, s.id + ": first option is still selected");
+ ok(!s[1].selected,s.id + ": 2nd option is still unselected");
+ ok(s[2].selected,s.id + ": 3rd option is selected");
+ ok(s[3].selected,s.id + ": 4th option is selected");
+ ok(!s[4].selected,s.id + ": 5th option is unselected");
+ synthesizeKey('VK_DOWN', {ctrlKey: true, shiftKey: true});
+ is(s.selectedIndex,0, s.id + ": first option is still selected");
+ ok(!s[1].selected,s.id + ": 2nd option is still unselected");
+ ok(s[2].selected,s.id + ": 3rd option is still selected");
+ ok(s[3].selected,s.id + ": 4th option is still selected");
+ ok(s[4].selected,s.id + ": 5th option is selected");
+ ok(!s[5].selected,s.id + ": 6th option is unselected");
+ synthesizeKey('VK_UP', {ctrlKey: true, shiftKey: true});
+ is(s.selectedIndex,0, s.id + ": first option is still selected");
+ ok(!s[1].selected,s.id + ": 2nd option is still unselected");
+ ok(s[2].selected,s.id + ": 3rd option is still selected");
+ ok(s[3].selected,s.id + ": 4th option is still selected");
+ ok(s[4].selected,s.id + ": 5th option is still selected");
+ ok(!s[5].selected,s.id + ": 6th option is still unselected");
+ synthesizeKey(' ', {ctrlKey: true, shiftKey: true});
+ is(s.selectedIndex,0, s.id + ": first option is still selected");
+ ok(!s[1].selected,s.id + ": 2nd option is still unselected");
+ ok(s[2].selected,s.id + ": 3rd option is still selected");
+ ok(!s[3].selected,s.id + ": 4th option is unselected");
+ ok(s[4].selected,s.id + ": 5th option is still selected");
+ ok(!s[5].selected,s.id + ": 6th option is still unselected");
+ synthesizeKey(' ', {ctrlKey: true, shiftKey: true});
+ is(s.selectedIndex,0, s.id + ": first option is still selected");
+ ok(!s[1].selected,s.id + ": 2nd option is still unselected");
+ ok(s[2].selected,s.id + ": 3rd option is still selected");
+ ok(s[3].selected,s.id + ": 4th option is selected");
+ ok(s[4].selected,s.id + ": 5th option is still selected");
+ ok(!s[5].selected,s.id + ": 6th option is still unselected");
+ setTimeout(function(){sel[1].focus()},0);
+ }, false);
+ sel[1].addEventListener('focus', function() {
+ s = sel[1];
+ s.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey('VK_DOWN', {});
+ is(s.selectedIndex,0, s.id + ": initial DOWN selects first option");
+ synthesizeKey('VK_DOWN', {ctrlKey: true});
+ is(s.selectedIndex,1, s.id + ": 2nd option is selected");
+ ok(!s[0].selected,s.id + ": CTRL+DOWN deselected first option");
+ synthesizeKey('VK_DOWN', {ctrlKey: true});
+ synthesizeKey('VK_DOWN', {ctrlKey: true, shiftKey: true});
+ is(s.selectedIndex,3, s.id + ": 4th option is selected");
+ ok(!s[1].selected,s.id + ": CTRL+SHIFT+DOWN deselected 2nd option");
+ synthesizeKey(' ', {ctrlKey: true, shiftKey: true});
+ is(s.selectedIndex,3, s.id + ": 4th option is still selected");
+ synthesizeKey(' ', {ctrlKey: true, shiftKey: true});
+ is(s.selectedIndex,3, s.id + ": 4th option is still selected");
+ setTimeout(function(){sel[2].focus()},0);
+ }, false);
+ sel[2].addEventListener('focus', function() {
+ if (navigator.platform.indexOf("Mac") == -1) {
+ s = sel[2];
+ s.removeEventListener('focus', arguments.callee, false);
+ synthesizeKey('VK_DOWN', {});
+ is(s.selectedIndex,1, s.id + ": initial DOWN selects 2nd option");
+ synthesizeKey('VK_DOWN', {ctrlKey: true});
+ is(s.selectedIndex,2, s.id + ": 3rd option is selected");
+ ok(!s[1].selected,s.id + ": CTRL+DOWN deselected 2nd option");
+ synthesizeKey('VK_DOWN', {ctrlKey: true, shiftKey: true});
+ is(s.selectedIndex,3, s.id + ": 4th option is selected");
+ ok(!s[2].selected,s.id + ": CTRL+SHIFT+DOWN deselected 3rd option");
+ synthesizeKey(' ', {ctrlKey: true, shiftKey: true});
+ is(s.selectedIndex,3, s.id + ": 4th option is still selected");
+ synthesizeKey(' ', {ctrlKey: true, shiftKey: true});
+ is(s.selectedIndex,3, s.id + ": 4th option is still selected");
+ } else {
+ todo(false, "Make this test work on OSX");
+ }
+ setTimeout(function(){SimpleTest.finish()},0);
+ }, false);
+ sel[0].focus();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug704049.html b/layout/forms/test/test_bug704049.html
new file mode 100644
index 000000000..a779abfcd
--- /dev/null
+++ b/layout/forms/test/test_bug704049.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=704049
+-->
+<head>
+ <title>Test for Bug 704049</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=704049">Mozilla Bug 704049</a>
+<p id="display"></p>
+<input type="radio" id="radio11" name="group1">
+<input type="radio" id="radio12" name="group1">
+<input type="radio" id="radio21" name="group2" checked>
+<input type="radio" id="radio22" name="group2">
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 704049 **/
+
+window.addEventListener("click", function (e) { e.preventDefault(); }, false);
+
+function doTest()
+{
+ var target = document.getElementById("radio11");
+ synthesizeMouseAtCenter(target, {});
+ is(target.checked, false, "radio11 is checked");
+ target = document.getElementById("radio12");
+ synthesizeMouseAtCenter(target, {});
+ is(target.checked, false, "radio12 is checked");
+ target = document.getElementById("radio21");
+ synthesizeMouseAtCenter(target, {});
+ is(target.checked, true, "radio21 is not checked");
+ target = document.getElementById("radio22");
+ synthesizeMouseAtCenter(target, {});
+ is(target.checked, false, "radio22 is checked");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(doTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug717878_input_scroll.html b/layout/forms/test/test_bug717878_input_scroll.html
new file mode 100644
index 000000000..89b431b34
--- /dev/null
+++ b/layout/forms/test/test_bug717878_input_scroll.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=717878
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 717878</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=717878">Mozilla Bug 717878</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<!-- size=10 and monospace font ensure there's no overflow in either direction -->
+<input id="no-overflow" type="text"
+ size="10"
+ style="
+ font-family: monospace;
+ font-size: 1em;"
+ value="Short">
+<!-- size=10, monospace font, and height=0.5em ensure overflow in both directions -->
+<input id="overflow" type="text"
+ size="10"
+ style="
+ font-family: monospace;
+ font-size: 1em;
+ height: 0.5em;"
+ value="This is a long string">
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 717878 **/
+
+/**
+ * Test an element's scroll properties for correctness
+ *
+ * @param element Element to test
+ * @param prop Specify the property to test,
+ * i.e. "scrollLeft" or "scrollTop"
+ * @param propMax Specify the scrollMax property to test,
+ * i.e. "scrollLeftMax" or "scrollTopMax"
+ * @param is_overflow Specify whether the element is
+ * scrollable in the above direction
+ */
+function test_scroll(element, scroll, scrollMax, is_overflow) {
+
+ is(element[scroll], 0, element.id + " initial " + scroll + " != 0");
+ if (is_overflow) {
+ isnot(element[scrollMax], 0, element.id + " " + scrollMax + " == 0");
+ } else {
+ is(element[scrollMax], 0, element.id + " " + scrollMax + " != 0");
+ }
+
+ element[scroll] = 10;
+ if (is_overflow) {
+ isnot(element[scroll], 0, element.id + " unable to scroll " + scroll);
+ } else {
+ is(element[scroll], 0, element.id + " able to scroll " + scroll);
+ }
+
+ element[scroll] = element[scrollMax];
+ is(element[scroll], element[scrollMax], element.id + " did not scroll to " + scrollMax);
+
+ element[scroll] = element[scrollMax] + 10;
+ is(element[scroll], element[scrollMax], element.id + " scrolled past " + scrollMax);
+}
+
+var no_overflow = document.getElementById("no-overflow");
+test_scroll(no_overflow, "scrollLeft", "scrollLeftMax", /* is_overflow */ false);
+test_scroll(no_overflow, "scrollTop", "scrollTopMax", /* is_overflow */ false);
+
+var overflow = document.getElementById("overflow");
+test_scroll(overflow, "scrollLeft", "scrollLeftMax", /* is_overflow */ true);
+test_scroll(overflow, "scrollTop", "scrollTopMax", /* is_overflow */ true);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug869314.html b/layout/forms/test/test_bug869314.html
new file mode 100644
index 000000000..9116884d6
--- /dev/null
+++ b/layout/forms/test/test_bug869314.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=869314
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 869314</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">
+ .selectbox {
+ background-color: #00FF00;
+ }
+ </style>
+
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=869314">Mozilla Bug 869314</a>
+<p id="display"></p>
+<div id="content">
+
+ <select id="selectbox1" name="non-native selectbox" class="selectbox">
+ <option value="item">test item</option>
+ </select>
+
+ <select id="selectbox2" name="native selectbox">
+ <option value="item">test item</option>
+ </select>
+
+ <script type="application/javascript">
+ var Cc = SpecialPowers.Cc;
+ var Ci = SpecialPowers.Ci;
+ var sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
+ var osName = sysInfo.getProperty("name");
+ if (osName == "Darwin") { // Mac OS X.
+ // This test is for Mac only. See bug for more info.
+ ok(document.getElementById("selectbox1").clientWidth >
+ document.getElementById("selectbox2").clientWidth,
+ "Non-native styled combobox does not have enough space for a " +
+ "dropmarker!");
+ } else {
+ // We need to call at least one test function to make the test harness
+ // happy.
+ ok(true, "Test wasn't ignored but should have been.");
+ }
+ </script>
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug903715.html b/layout/forms/test/test_bug903715.html
new file mode 100644
index 000000000..37d49762e
--- /dev/null
+++ b/layout/forms/test/test_bug903715.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=903715
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 903715</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=903715">Mozilla Bug 903715</a>
+<p id="display"></p>
+<div id="content">
+ <form id="form" action="/">
+ <select id="select" name="select">
+ <option>1</option>
+ <option>2</option>
+ <option>3</option>
+ <option>4</option>
+ <option>5</option>
+ <option>6</option>
+ <option>7</option>
+ <option>8</option>
+ <option>9</option>
+ </select>
+ <input id="input-text" name="text" value="some text">
+ <input id="input-submit" type="submit">
+ </form>
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+SimpleTest.waitForFocus(runTests, window);
+
+function runTests()
+{
+ var form = document.getElementById("form");
+ form.addEventListener("keypress", function (aEvent) {
+ ok(false, "keypress event shouldn't be fired when the preceding keydown event caused closing the dropdown of the select element");
+ }, true);
+ form.addEventListener("submit", function (aEvent) {
+ ok(false, "submit shouldn't be performed by the Enter key press on the select element");
+ aEvent.preventDefault();
+ }, true);
+ var select = document.getElementById("select");
+ select.addEventListener("change", function (aEvent) {
+ var input = document.getElementById("input-text");
+ input.focus();
+ input.select();
+ }, false);
+
+ select.focus();
+
+ select.addEventListener("popupshowing", function (aEvent) {
+ setTimeout(function () {
+ synthesizeKey("VK_DOWN", { });
+ select.addEventListener("popuphiding", function (aEvent) {
+ setTimeout(function () {
+ // Enter key should cause closing the dropdown of the select element
+ // and keypress event shouldn't be fired on the input element because
+ // which shouldn't cause sumbmitting the form contents.
+ ok(true, "Test passes if there is no error");
+ SimpleTest.finish();
+ }, 100);
+ }, false);
+ // Close dropdown.
+ synthesizeKey("VK_RETURN", { });
+ }, 100);
+ }, false);
+
+ // Open dropdown.
+ synthesizeKey("VK_DOWN", { altKey: true });
+}
+</script>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug935876.html b/layout/forms/test/test_bug935876.html
new file mode 100644
index 000000000..bb4090211
--- /dev/null
+++ b/layout/forms/test/test_bug935876.html
@@ -0,0 +1,495 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=935876
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 935876</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=935876">Mozilla Bug 935876</a>
+<p id="display"></p>
+<div>
+<select id="listbox" size="3">
+ <option selected>1</option>
+ <option>2</option>
+ <option>3</option>
+ <option>4</option>
+ <option>5</option>
+ <option>6</option>
+ <option>7</option>
+</select>
+<select id="multipleListbox" size="3" multiple>
+ <option selected>1</option>
+ <option>2</option>
+ <option>3</option>
+ <option>4</option>
+ <option>5</option>
+ <option>6</option>
+ <option>7</option>
+</select>
+<select id="combobox">
+ <option selected>1</option>
+ <option>2</option>
+ <option>3</option>
+ <option>4</option>
+ <option>5</option>
+ <option>6</option>
+ <option>7</option>
+</select>
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+const kIsWin = navigator.platform.indexOf("Win") == 0;
+const kIsMac = navigator.platform.indexOf("Mac") == 0;
+const kIsAndroid = navigator.appVersion.indexOf("Android") != 0;
+
+function runTests()
+{
+ var doPreventDefault = false;
+ function onKeydown(aEvent)
+ {
+ if (doPreventDefault) {
+ aEvent.preventDefault();
+ }
+ }
+
+ var keyPressEventFired = false;
+ function onKeypress(aEvent)
+ {
+ keyPressEventFired = true;
+ }
+
+ var keyDownEventConsumedByJS = false;
+ var keyDownEventConsumed = false;
+ function onkeydownInSystemEventGroup(aEvent)
+ {
+ keyDownEventConsumedByJS = aEvent.defaultPrevented;
+ keyDownEventConsumed = aEvent.getPreventDefault();
+ }
+
+ function reset()
+ {
+ keyPressEventFired = false;
+ keyDownEventConsumedByJS = false;
+ keyDownEventConsumed = false;
+ }
+
+ function check(aExpectingKeydownConsumed, aDescription)
+ {
+ if (doPreventDefault) {
+ ok(!keyPressEventFired, "keypress event shouldn't be fired for " + aDescription +
+ " if preventDefault() of keydown event was called");
+ ok(keyDownEventConsumedByJS, "keydown event of " + aDescription +
+ " should be consumed in content level if preventDefault() of keydown event is called");
+ ok(keyDownEventConsumed, "keydown event of " + aDescription +
+ " should be consumed in system level if preventDefault() of keydown event is called");
+ } else if (aExpectingKeydownConsumed) {
+ ok(!keyPressEventFired, "keypress event shouldn't be fired for " + aDescription);
+ ok(!keyDownEventConsumedByJS, "keydown event of " + aDescription + " shouldn't be consumed in content level");
+ ok(keyDownEventConsumed, "keydown event of " + aDescription + " should be consumed in system level");
+ } else {
+ ok(keyPressEventFired, "keypress event should be fired for " + aDescription);
+ ok(!keyDownEventConsumedByJS, "keydown event of " + aDescription + " shouldn't be consumed in content level");
+ ok(!keyDownEventConsumed, "keydown event of " + aDescription + " should be consumed in system level");
+ }
+ }
+
+ var listbox = document.getElementById("listbox");
+ listbox.addEventListener("keydown", onKeydown, false);
+ listbox.addEventListener("keypress", onKeypress, false);
+ SpecialPowers.addSystemEventListener(listbox, "keydown", onkeydownInSystemEventGroup, false);
+
+ listbox.focus();
+
+ [ false, true ].forEach(function (consume) {
+ doPreventDefault = consume;
+ for (var i = 0; i < listbox.options.length + 1; i++) {
+ reset()
+ synthesizeKey("VK_DOWN", {});
+ check(true, "DownArrow key on listbox #" + i);
+ }
+
+ for (var i = 0; i < listbox.options.length + 1; i++) {
+ reset()
+ synthesizeKey("VK_UP", {});
+ check(true, "UpArrow key on listbox #" + i);
+ }
+
+ for (var i = 0; i < listbox.options.length + 1; i++) {
+ reset()
+ synthesizeKey("VK_RIGHT", {});
+ check(true, "RightArrow key on listbox #" + i);
+ }
+
+ for (var i = 0; i < listbox.options.length + 1; i++) {
+ reset()
+ synthesizeKey("VK_LEFT", {});
+ check(true, "LeftArrow key on listbox #" + i);
+ }
+
+ for (var i = 0; i < 4; i++) {
+ reset()
+ synthesizeKey("VK_PAGE_DOWN", {});
+ check(true, "PageDown key on listbox #" + i);
+ }
+
+ for (var i = 0; i < 4; i++) {
+ reset()
+ synthesizeKey("VK_PAGE_UP", {});
+ check(true, "PageUp key on listbox #" + i);
+ }
+
+ for (var i = 0; i < 2; i++) {
+ reset()
+ synthesizeKey("VK_END", {});
+ check(true, "End key on listbox #" + i);
+ }
+
+ for (var i = 0; i < 2; i++) {
+ reset()
+ synthesizeKey("VK_HOME", {});
+ check(true, "Home key on listbox #" + i);
+ }
+
+ reset()
+ synthesizeKey("VK_RETURN", {});
+ check(false, "Enter key on listbox");
+
+ reset()
+ synthesizeKey("VK_ESCAPE", {});
+ check(false, "Esc key on listbox");
+
+ reset()
+ synthesizeKey("VK_F4", {});
+ check(false, "F4 key on listbox");
+
+ reset()
+ synthesizeKey("a", {});
+ check(false, "'A' key on listbox");
+ });
+
+ listbox.removeEventListener("keydown", onKeydown, false);
+ listbox.removeEventListener("keypress", onKeypress, false);
+ SpecialPowers.removeSystemEventListener(listbox, "keydown", onkeydownInSystemEventGroup, false);
+
+
+
+ var multipleListbox = document.getElementById("multipleListbox");
+ multipleListbox.addEventListener("keydown", onKeydown, false);
+ multipleListbox.addEventListener("keypress", onKeypress, false);
+ SpecialPowers.addSystemEventListener(multipleListbox, "keydown", onkeydownInSystemEventGroup, false);
+
+ multipleListbox.focus();
+
+ [ false, true ].forEach(function (consume) {
+ doPreventDefault = consume;
+ for (var i = 0; i < multipleListbox.options.length + 1; i++) {
+ reset()
+ synthesizeKey("VK_DOWN", {});
+ check(true, "DownArrow key on multiple listbox #" + i);
+ }
+
+ for (var i = 0; i < multipleListbox.options.length + 1; i++) {
+ reset()
+ synthesizeKey("VK_UP", {});
+ check(true, "UpArrow key on multiple listbox #" + i);
+ }
+
+ for (var i = 0; i < multipleListbox.options.length + 1; i++) {
+ reset()
+ synthesizeKey("VK_RIGHT", {});
+ check(true, "RightArrow key on multiple listbox #" + i);
+ }
+
+ for (var i = 0; i < multipleListbox.options.length + 1; i++) {
+ reset()
+ synthesizeKey("VK_LEFT", {});
+ check(true, "LeftArrow key on multiple listbox #" + i);
+ }
+
+ for (var i = 0; i < 4; i++) {
+ reset()
+ synthesizeKey("VK_PAGE_DOWN", {});
+ check(true, "PageDown key on multiple listbox #" + i);
+ }
+
+ for (var i = 0; i < 4; i++) {
+ reset()
+ synthesizeKey("VK_PAGE_UP", {});
+ check(true, "PageUp key on multiple listbox #" + i);
+ }
+
+ for (var i = 0; i < 2; i++) {
+ reset()
+ synthesizeKey("VK_END", {});
+ check(true, "End key on multiple listbox #" + i);
+ }
+
+ for (var i = 0; i < 2; i++) {
+ reset()
+ synthesizeKey("VK_HOME", {});
+ check(true, "Home key on multiple listbox #" + i);
+ }
+
+ reset()
+ synthesizeKey("VK_RETURN", {});
+ check(true, "Enter key on multiple listbox");
+
+ reset()
+ synthesizeKey("VK_ESCAPE", {});
+ check(false, "Esc key on multiple listbox");
+
+ reset()
+ synthesizeKey("VK_F4", {});
+ check(false, "F4 key on multiple listbox");
+
+ reset()
+ synthesizeKey("a", {});
+ check(false, "'A' key on multiple listbox");
+ });
+
+ multipleListbox.removeEventListener("keydown", onKeydown, false);
+ multipleListbox.removeEventListener("keypress", onKeypress, false);
+ SpecialPowers.removeSystemEventListener(multipleListbox, "keydown", onkeydownInSystemEventGroup, false);
+
+
+
+ var combobox = document.getElementById("combobox");
+ combobox.addEventListener("keydown", onKeydown, false);
+ combobox.addEventListener("keypress", onKeypress, false);
+ SpecialPowers.addSystemEventListener(combobox, "keydown", onkeydownInSystemEventGroup, false);
+
+ combobox.focus();
+
+ [ false, true ].forEach(function (consume) {
+ doPreventDefault = consume;
+ if (!kIsMac) {
+ for (var i = 0; i < combobox.options.length + 1; i++) {
+ reset()
+ synthesizeKey("VK_DOWN", {});
+ check(true, "DownArrow key on combobox #" + i);
+ }
+
+ for (var i = 0; i < combobox.options.length + 1; i++) {
+ reset()
+ synthesizeKey("VK_UP", {});
+ check(true, "UpArrow key on combobox #" + i);
+ }
+ } else {
+ todo(false, "Make this test work on OSX");
+ }
+
+ for (var i = 0; i < combobox.options.length + 1; i++) {
+ reset()
+ synthesizeKey("VK_RIGHT", {});
+ check(true, "RightArrow key on combobox #" + i);
+ }
+
+ for (var i = 0; i < combobox.options.length + 1; i++) {
+ reset()
+ synthesizeKey("VK_LEFT", {});
+ check(true, "LeftArrow key on combobox #" + i);
+ }
+
+ for (var i = 0; i < 4; i++) {
+ reset()
+ synthesizeKey("VK_PAGE_DOWN", {});
+ check(true, "PageDown key on combobox #" + i);
+ }
+
+ for (var i = 0; i < 4; i++) {
+ reset()
+ synthesizeKey("VK_PAGE_UP", {});
+ check(true, "PageUp key on combobox #" + i);
+ }
+
+ for (var i = 0; i < 2; i++) {
+ reset()
+ synthesizeKey("VK_END", {});
+ check(true, "End key on combobox #" + i);
+ }
+
+ for (var i = 0; i < 2; i++) {
+ reset()
+ synthesizeKey("VK_HOME", {});
+ check(true, "Home key on combobox #" + i);
+ }
+
+ reset()
+ synthesizeKey("VK_RETURN", {});
+ check(false, "Enter key on combobox");
+
+ reset()
+ synthesizeKey("VK_ESCAPE", {});
+ check(true, "Esc key on combobox");
+
+ if (!kIsWin) {
+ reset()
+ synthesizeKey("VK_F4", {});
+ check(false, "F4 key on combobox");
+ }
+
+ reset()
+ synthesizeKey("a", {});
+ check(false, "'A' key on combobox");
+ });
+
+ function finish()
+ {
+ combobox.removeEventListener("keydown", onKeydown, false);
+ combobox.removeEventListener("keypress", onKeypress, false);
+ SpecialPowers.removeSystemEventListener(combobox, "keydown", onkeydownInSystemEventGroup, false);
+ SimpleTest.finish();
+ }
+
+ // Mac uses native popup for dropdown. Let's skip the tests for popup
+ // since it's not handled in nsListControlFrame.
+ // Similarly, Android doesn't use popup for dropdown.
+ if (kIsMac || kIsAndroid) {
+ finish();
+ return;
+ }
+
+ function testDropDown(aCallback)
+ {
+ testOpenDropDown(function () {
+ reset()
+ synthesizeKey("VK_DOWN", { altKey: true });
+ }, function () {
+ check(true, "Alt + DownArrow key on combobox at opening dropdown");
+
+ for (var i = 0; i < combobox.options.length + 1; i++) {
+ reset()
+ synthesizeKey("VK_DOWN", {});
+ check(true, "DownArrow key on combobox during dropdown open #" + i);
+ }
+
+ for (var i = 0; i < combobox.options.length + 1; i++) {
+ reset()
+ synthesizeKey("VK_UP", {});
+ check(true, "UpArrow key on combobox during dropdown open #" + i);
+ }
+
+ for (var i = 0; i < combobox.options.length + 1; i++) {
+ reset()
+ synthesizeKey("VK_RIGHT", {});
+ check(true, "RightArrow key on combobox during dropdown open #" + i);
+ }
+
+ for (var i = 0; i < combobox.options.length + 1; i++) {
+ reset()
+ synthesizeKey("VK_LEFT", {});
+ check(true, "LeftArrow key on combobox during dropdown open #" + i);
+ }
+
+ for (var i = 0; i < 4; i++) {
+ reset()
+ synthesizeKey("VK_PAGE_DOWN", {});
+ check(true, "PageDown key on combobox during dropdown open #" + i);
+ }
+
+ for (var i = 0; i < 4; i++) {
+ reset()
+ synthesizeKey("VK_PAGE_UP", {});
+ check(true, "PageUp key on combobox during dropdown open #" + i);
+ }
+
+ for (var i = 0; i < 2; i++) {
+ reset()
+ synthesizeKey("VK_END", {});
+ check(true, "End key on combobox during dropdown open #" + i);
+ }
+
+ for (var i = 0; i < 2; i++) {
+ reset()
+ synthesizeKey("VK_HOME", {});
+ check(true, "Home key on combobox during dropdown open #" + i);
+ }
+
+ testCloseDropDown(function () {
+ reset()
+ synthesizeKey("VK_RETURN", {});
+ }, function () {
+ testOpenDropDown(function () {
+ check(true, "Enter key on combobox at closing dropdown");
+
+ synthesizeKey("VK_UP", { altKey: true });
+ }, function () {
+ check(true, "Alt + UpArrow key on combobox at opening dropdown");
+
+ testCloseDropDown(function () {
+ reset()
+ synthesizeKey("VK_ESCAPE", {});
+ }, function () {
+ check(true, "Esc key on combobox at closing dropdown");
+
+ // F4 key opens/closes dropdown only on Windows. So, other platforms
+ // don't need to do anymore.
+ if (!kIsWin) {
+ aCallback();
+ return;
+ }
+
+ testOpenDropDown(function () {
+ reset()
+ synthesizeKey("VK_F4", {});
+ }, function () {
+ check(true, "F4 key on combobox at opening dropdown on Windows");
+
+ testCloseDropDown(function () {
+ reset()
+ synthesizeKey("VK_F4", {});
+ }, function () {
+ check(true, "F4 key on combobox at closing dropdown on Windows");
+
+ aCallback();
+ return;
+ });
+ });
+ });
+ });
+ });
+ });
+ }
+
+ doPreventDefault = false;
+ testDropDown(function () {
+ // Even if keydown event is consumed by JS, opening/closing dropdown
+ // should work for a11y and security (e.g., cannot close dropdown causes
+ // staying top-most window on the screen). If it's blocked by JS, this
+ // test would cause permanent timeout.
+ doPreventDefault = true;
+ testDropDown(finish);
+ });
+}
+
+function testOpenDropDown(aTest, aOnOpenDropDown)
+{
+ document.addEventListener("popupshowing", function (aEvent) {
+ document.removeEventListener(aEvent.type, arguments.callee, false);
+ setTimeout(aOnOpenDropDown, 0);
+ }, false);
+ aTest();
+}
+
+function testCloseDropDown(aTest, aOnCloseDropDown)
+{
+ document.addEventListener("popuphiding", function (aEvent) {
+ document.removeEventListener(aEvent.type, arguments.callee, false);
+ setTimeout(aOnCloseDropDown, 0)
+ }, false);
+ aTest();
+}
+
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug957562.html b/layout/forms/test/test_bug957562.html
new file mode 100644
index 000000000..6c52dc5e0
--- /dev/null
+++ b/layout/forms/test/test_bug957562.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=957562
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 903715</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=957562">Mozilla Bug 957562</a>
+<p id="display"></p>
+<input id="n" onfocus="kill()" type="number" style="border:20px solid black">
+<pre id="test">
+</pre>
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests, window);
+
+var killed = false;
+function kill() {
+ if (killed) {
+ return;
+ }
+ killed = true;
+ n.style.display = 'none';
+ r = n.getBoundingClientRect();
+ setTimeout(function() {
+ ok(true, "Didn't crash");
+ SimpleTest.finish();
+ }, 0);
+}
+
+function runTests()
+{
+ synthesizeMouse(n, 2, 2, {});
+}
+</script>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug960277.html b/layout/forms/test/test_bug960277.html
new file mode 100644
index 000000000..d95e0f7cb
--- /dev/null
+++ b/layout/forms/test/test_bug960277.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=960277
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 903715</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=960277">Mozilla Bug 960277</a>
+<p id="display"></p>
+<fieldset style="position:relative; height:100px; margin:50px; background:blue;">
+<legend style="background:purple">
+ <div id="d" style="position:absolute; background:yellow; top:0; left:0; width:50px; height:50px;"></div>
+</legend>
+</fieldset>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+var rect = d.getBoundingClientRect();
+is(document.elementFromPoint(rect.left + 10, rect.top + 10), d,
+ "Hit testing yellow div");
+</script>
+</body>
+</html>
diff --git a/layout/forms/test/test_bug961363.html b/layout/forms/test/test_bug961363.html
new file mode 100644
index 000000000..a3c45cd9a
--- /dev/null
+++ b/layout/forms/test/test_bug961363.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=961363
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 961363</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 961363 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function test() {
+
+ SimpleTest.waitForFocus(function() {
+ var one = [{k:"DOWN",s:[false,true,true,false]}, {k:"UP",s:[false,false,true,false]},
+ {k:"RIGHT",s:[false,false,false,false]}, {k:"LEFT",s:[false,true,false,false]},
+ {k:"PAGE_DOWN",s:[false,true,false,true]}, {k:"PAGE_UP",s:[false,false,false,true]},
+ {k:"END",s:[false,false,false,false]}, {k:"HOME",s:[true,false,false,false]} ];
+ var two_1 = [{k:"DOWN",s:[false,false,true,false]}, {k:"UP",s:[false,true,false,false]},
+ {k:"RIGHT",s:[false,false,true,false]}, {k:"LEFT",s:[false,true,false,false]},
+ {k:"END",s:[false,false,false,true]}, {k:"HOME",s:[true,false,false,false]} ];
+ var two_2 = [{k:"PAGE_DOWN",s:[true,false,false,false]}, {k:"PAGE_UP",s:[true,false,false,false]}];
+ var three_1 = [{k:"DOWN",s:[false,false,true,false]}, {k:"UP",s:[false,true,false,false]},
+ {k:"RIGHT",s:[false,false,true,false]}, {k:"LEFT",s:[false,true,false,false]},
+ {k:"END",s:[false,false,false,true]}, {k:"HOME",s:[true,false,false,false]} ];
+ var three_2 = [{k:"PAGE_DOWN",s:[true,false,false,false]}, {k:"PAGE_UP",s:[true,false,false,false]} ];
+
+ function select_test(id, tests, ctrl_change) {
+ var element = document.getElementById(id);
+ element.focus();
+ var previousValue = element.value;
+ tests.forEach(function(data) {
+ var key = data.k;
+ synthesizeKey("VK_"+key, { shiftKey:false, metaKey:false, ctrlKey:true });
+ (ctrl_change ? isnot : is)(element.value, previousValue, "value should " +
+ (ctrl_change?"":"not ") + "have changed while testing CTRL+key " + key + " (id: " + id + ")");
+ previousValue = element.value;
+ synthesizeKey(" ", { shiftKey:false, metaKey:false, ctrlKey:true });
+ var sel = Array.prototype.slice.call(element.options).map(function(o){return o.selected})
+ is(""+sel, ""+data.s, "selected options match after CTRL+SPACE (after testing CTRL+key " + key + ") for (id: " + id + ")");
+ previousValue = element.value;
+ });
+ };
+ select_test("one", one, false);
+ select_test("two", two_1, true);
+ select_test("two", two_2, false);
+ if (navigator.platform.indexOf("Mac") == -1) {
+ select_test("three", three_1, true);
+ select_test("three", three_2, false);
+ } else {
+ todo(false, "Make these tests work on OSX");
+ }
+ SimpleTest.finish();
+ });
+ }
+</script>
+</head>
+<body onload="test();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=961363">Mozilla Bug 961363</a>
+<div>
+ <ul>
+ <li>
+ <select id="one" multiple size="3">
+ <option>0</option>
+ <option selected>1</option>
+ <option>2</option>
+ <option>3</option>
+ </select>
+ </li>
+ <li>
+ <select id="two" size="3">
+ <option>0</option>
+ <option selected>1</option>
+ <option>2</option>
+ <option>3</option>
+ </select>
+ </li>
+ <li>
+ <select id="three" size="1">
+ <option>0</option>
+ <option selected>1</option>
+ <option>2</option>
+ <option>3</option>
+ </select>
+ </li>
+ </ul>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_listcontrol_search.html b/layout/forms/test/test_listcontrol_search.html
new file mode 100644
index 000000000..7f73c5f75
--- /dev/null
+++ b/layout/forms/test/test_listcontrol_search.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=849438
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for &lt;select&gt; list control search</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">
+
+ /** This test will focus a select element and press a key that matches the
+ first non-space character of an entry. **/
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(function() {
+ var select = document.getElementsByTagName('select')[0];
+ select.focus();
+ synthesizeKey('a', {});
+
+ is(select.options[0].selected, false, "the first option isn't selected");
+ is(select.options[1].selected, true, "the second option is selected");
+
+ select.blur();
+
+ SimpleTest.finish();
+ });
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=849438">Mozilla Bug 849438</a>
+<p id="display"></p>
+<div id="content">
+ <select>
+ <option>Please select an entry</option>
+ <option>&nbsp;a</option>
+ <option>&nbsp;b</option>
+ </select>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_select_prevent_default.html b/layout/forms/test/test_select_prevent_default.html
new file mode 100644
index 000000000..85066e3c2
--- /dev/null
+++ b/layout/forms/test/test_select_prevent_default.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=291082
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 291082</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 291082 **/
+
+
+ SimpleTest.waitForExplicitFinish();
+
+ function preventDefault(event) {
+ event.preventDefault();
+ }
+
+ function test() {
+ // Turn off Spatial Navigation because it hijacks arrow keydown events.
+ SpecialPowers.pushPrefEnv({"set":[["snav.enabled", false]]}, runTest);
+ }
+
+ function runTest() {
+ document.getElementById("keydown").addEventListener("keydown", preventDefault);
+ document.getElementById("keypress").addEventListener("keypress", preventDefault);
+
+ SimpleTest.waitForFocus(function() {
+ if (navigator.platform.indexOf("Mac") == 0) {
+ todo(false, "Make this test work on OSX");
+ SimpleTest.finish();
+ return;
+ }
+ var testData = [ "one", "two", "three", "four", "keydown", "keypress" ];
+
+ // The order of the keys in otherKeys is important for the test to function properly.
+ var otherKeys = [ "DOWN", "UP", "RIGHT", "LEFT", "PAGE_DOWN", "PAGE_UP",
+ "END", "HOME" ];
+
+ testData.forEach(function(id) {
+ var element = document.getElementById(id);
+ element.focus();
+ var previousValue = element.value;
+ sendChar('2');
+ is(element.value, previousValue, "value should not have changed (id: " + id + ")");
+ previousValue = element.value;
+ otherKeys.forEach(function(key) {
+ sendKey(key);
+ isnot(element.value, previousValue, "value should have changed while testing key " + key + " (id: " + id + ")");
+ previousValue = element.value;
+ });
+ });
+ SimpleTest.finish();
+ });
+ }
+</script>
+</head>
+<body onload="test();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=291082">Mozilla Bug 291082</a>
+<div>
+ <ul>
+ <li>
+ <select id="one" onkeydown="event.preventDefault();">
+ <option>0</option>
+ <option>1</option>
+ <option>2</option>
+ </select>
+ select onkeydown="event.preventDefault();"
+ </li>
+ <li>
+ <select id="two" onkeypress="event.preventDefault();">
+ <option>0</option>
+ <option>1</option>
+ <option>2</option>
+ </select>
+ select onkeypress="event.preventDefault();"
+ </li>
+ <li onkeydown="event.preventDefault();">
+ <select id="three">
+ <option>0</option>
+ <option>1</option>
+ <option>2</option>
+ </select>
+ li onkeydown="event.preventDefault();"
+ </li>
+ <li onkeypress="event.preventDefault();">
+ <select id="four">
+ <option>0</option>
+ <option>1</option>
+ <option>2</option>
+ </select>
+ li onkeypress="event.preventDefault();"
+ </li>
+ <li>
+ <select id="keydown">
+ <option>0</option>
+ <option>1</option>
+ <option>2</option>
+ </select>
+ select.addEventListener("keydown", function(event) { event.preventDefault(); });
+ </li>
+ <li>
+ <select id="keypress">
+ <option>0</option>
+ <option>1</option>
+ <option>2</option>
+ <option>9</option>
+ </select>
+ select.addEventListener("keypress", function(event) { event.preventDefault(); });
+ </li>
+ </ul>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/forms/test/test_select_vertical.html b/layout/forms/test/test_select_vertical.html
new file mode 100644
index 000000000..570735641
--- /dev/null
+++ b/layout/forms/test/test_select_vertical.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for select popup in vertical writing mode</title>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ SimpleTest.waitForFocus(function() {
+ var vlr = document.getElementById("vlr");
+ var selWidth = vlr.offsetWidth;
+ var optWidth = vlr.children[0].offsetWidth;
+
+ // We should be able to choose options from the vertical-lr <select>
+ // at positions increasingly to the right of the element itself.
+ is(vlr.value, "1", "(vertical-lr) initial value should be 1");
+
+ synthesizeMouse(vlr, 5, 5, { type : "mousedown", button: 0 });
+ synthesizeMouse(vlr, selWidth + 1.5 * optWidth, 5, { type : "mouseup", button: 0 });
+
+ is(vlr.value, "2", "(vertical-lr) new value should be 2");
+
+ synthesizeMouse(vlr, 5, 5, { type : "mousedown", button: 0 });
+ synthesizeMouse(vlr, selWidth + 2.5 * optWidth, 5, { type : "mouseup", button: 0 });
+
+ is(vlr.value, "3", "(vertical-lr) new value should be 3");
+
+ synthesizeMouse(vlr, 5, 5, { type : "mousedown", button: 0 });
+ synthesizeMouse(vlr, selWidth + 0.5 * optWidth, 5, { type : "mouseup", button: 0 });
+
+ is(vlr.value, "1", "(vertical-lr) value should be back to 1");
+
+ var vrl = document.getElementById("vrl");
+
+ // We should be able to choose options from the vertical-rl <select>
+ // at positions increasingly to the left of the element itself.
+ is(vrl.value, "1", "(vertical-rl) initial value should be 1");
+
+ synthesizeMouse(vrl, 5, 5, { type : "mousedown", button: 0 });
+ synthesizeMouse(vrl, -1.5 * optWidth, 5, { type : "mouseup", button: 0 });
+
+ is(vrl.value, "2", "(vertical-rl) new value should be 2");
+
+ synthesizeMouse(vrl, 5, 5, { type : "mousedown", button: 0 });
+ synthesizeMouse(vrl, -2.5 * optWidth, 5, { type : "mouseup", button: 0 });
+
+ is(vrl.value, "3", "(vertical-rl) new value should be 3");
+
+ synthesizeMouse(vrl, 5, 5, { type : "mousedown", button: 0 });
+ synthesizeMouse(vrl, -0.5 * optWidth, 5, { type : "mouseup", button: 0 });
+
+ is(vrl.value, "1", "(vertical-rl) value should be back to 1");
+
+ SimpleTest.finish();
+ });
+}
+</script>
+
+<body onload="test();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1113206">Mozilla Bug 1113206</a>
+<div>
+ <select id="vlr" style="writing-mode: vertical-lr; margin: 80px;">
+ <option value="1">one
+ <option value="2">two
+ <option value="3">three
+ <option value="4">four
+ </select>
+ <select id="vrl" style="writing-mode: vertical-rl; margin: 80px;">
+ <option value="1">one
+ <option value="2">two
+ <option value="3">three
+ <option value="4">four
+ </select>
+</div>
diff --git a/layout/forms/test/test_textarea_resize.html b/layout/forms/test/test_textarea_resize.html
new file mode 100644
index 000000000..c71f554e0
--- /dev/null
+++ b/layout/forms/test/test_textarea_resize.html
@@ -0,0 +1,102 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 477700</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" style="display: none">
+</div>
+
+<textarea id="textarea" style="-moz-appearance: none; border: 2px solid black; padding: 3px; box-sizing: border-box; min-width: 15px; min-height: 15px;">Text</textarea>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for textbox resizing **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(() => SimpleTest.executeSoon(doTheTest));
+
+// -1 means use the default value which is 'both', then test explicitly
+// setting each possible value.
+var currentResize = -1;
+var currentBoxSizing = 0;
+var currentPointer = 0;
+var resizeTypes = [ "horizontal", "vertical", "none", "inherit", "both" ];
+var boxSizingTypes = [ "", "border-box" ];
+var pointerTypes = [ synthesizeMouse, synthesizeTouch]
+
+function doTheTest() {
+ runTest(pointerTypes[currentPointer]);
+}
+
+function runTest(aPointerFunc) {
+ var boxSizingText = " with box sizing " + (currentBoxSizing ? boxSizingTypes[currentBoxSizing] : "content-box");
+
+ var textarea = $("textarea");
+ var rect = textarea.getBoundingClientRect();
+ var touch = aPointerFunc.name.match(/Touch/);
+ // -1 means use the default value of resize, i.e. "both"
+ var type = (currentResize == -1) ? "both" : resizeTypes[currentResize];
+ // assume that the resizer is in the lower right corner
+
+ aPointerFunc(textarea, rect.width - 10, rect.height - 10, { type: touch ? "touchstart" : "mousedown" });
+ aPointerFunc(textarea, rect.width + 40, rect.height + 40, { type: touch ? "touchmove" : "mousemove" });
+
+ var newrect = textarea.getBoundingClientRect();
+ var hchange = (type == "both" || type == "horizontal");
+ var vchange = (type == "both" || type == "vertical");
+
+ is(Math.round(newrect.width), Math.round(rect.width + (hchange ? 50 : 0)),
+ type + " width has increased" + boxSizingText + " using " + aPointerFunc.name);
+ is(Math.round(newrect.height), Math.round(rect.height + (vchange ? 50 : 0)),
+ type + " height has increased" + boxSizingText + " using " + aPointerFunc.name);
+
+ aPointerFunc(textarea, rect.width - 20, rect.height - 20, { type: touch ? "touchmove" : "mousemove" });
+
+ newrect = textarea.getBoundingClientRect();
+
+ is(Math.round(newrect.width), Math.round(rect.width - (hchange ? 10 : 0)),
+ type + " width has decreased" + boxSizingText + " using " + aPointerFunc.name);
+ is(Math.round(newrect.height), Math.round(rect.height - (vchange ? 10 : 0)),
+ type + " height has decreased" + boxSizingText + " using " + aPointerFunc.name);
+
+ aPointerFunc(textarea, rect.width - 220, rect.height - 220, { type: touch ? "touchmove" : "mousemove" });
+
+ newrect = textarea.getBoundingClientRect();
+ ok(hchange ? newrect.width >= 15 : Math.round(newrect.width) == Math.round(rect.width),
+ type + " width decreased below minimum" + boxSizingText + " using " + newrect.width);
+ ok(vchange ? newrect.height >= 15 : Math.round(newrect.height) == Math.round(rect.height),
+ type + " height decreased below minimum" + boxSizingText + " using " + aPointerFunc.name);
+
+ aPointerFunc(textarea, rect.width - 8, rect.height - 8, { type: touch ? "touchend" : "mouseup" });
+
+ textarea.style.width = "auto";
+ textarea.style.height = "auto";
+
+ if (currentBoxSizing++ <= boxSizingTypes.length) {
+ textarea.style.MozBoxSizing = boxSizingTypes[currentBoxSizing];
+ SimpleTest.executeSoon(doTheTest);
+ } else {
+ currentBoxSizing = 0;
+ if (++currentResize < resizeTypes.length) {
+ textarea.style.resize = resizeTypes[currentResize];
+ SimpleTest.executeSoon(doTheTest);
+ } else {
+ currentResize = -1;
+ textarea.style.resize = "";
+ if (++currentPointer < pointerTypes.length) {
+ SimpleTest.executeSoon(doTheTest);
+ } else {
+ SimpleTest.finish();
+ }
+ }
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>