From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- layout/forms/crashtests/1102791.html | 33 + layout/forms/crashtests/1140216.html | 20 + layout/forms/crashtests/1182414.html | 17 + layout/forms/crashtests/1212688.html | 27 + layout/forms/crashtests/1228670.xhtml | 7 + layout/forms/crashtests/1279354.html | 21 + layout/forms/crashtests/166750-1.html | 14 + layout/forms/crashtests/200347-1.html | 8 + layout/forms/crashtests/203041-1.html | 24 + layout/forms/crashtests/213390-1.html | 23 + layout/forms/crashtests/258101-1.html | 18 + layout/forms/crashtests/266225-1.html | 7 + layout/forms/crashtests/310426-1.xhtml | 9 + layout/forms/crashtests/310520-1.xhtml | 19 + layout/forms/crashtests/315752-1.xhtml | 21 + layout/forms/crashtests/317502-1.xhtml | 13 + layout/forms/crashtests/321894.html | 17 + layout/forms/crashtests/323499-1.html | 21 + layout/forms/crashtests/343510-1.html | 4 + layout/forms/crashtests/363696-1.xul | 10 + layout/forms/crashtests/363696-2.html | 2 + layout/forms/crashtests/363696-3.html | 5 + layout/forms/crashtests/366205-1.html | 11 + layout/forms/crashtests/366537-1.xhtml | 32 + layout/forms/crashtests/367587-1.html | 37 + layout/forms/crashtests/370703-1.html | 30 + layout/forms/crashtests/370940-1.html | 28 + layout/forms/crashtests/370967.html | 13 + layout/forms/crashtests/373586-1.xhtml | 40 + layout/forms/crashtests/375299-binding.xml | 4 + layout/forms/crashtests/375299.html | 17 + layout/forms/crashtests/378369.html | 19 + layout/forms/crashtests/378413-1.xhtml | 16 + layout/forms/crashtests/380116-1.xhtml | 11 + layout/forms/crashtests/382212-1.xhtml | 7 + layout/forms/crashtests/382610-1.html | 11 + layout/forms/crashtests/383887-1.html | 20 + layout/forms/crashtests/386554-1.html | 14 + layout/forms/crashtests/388374-1.xhtml | 22 + layout/forms/crashtests/388374-2.html | 25 + layout/forms/crashtests/393656-1.xhtml | 13 + layout/forms/crashtests/393656-2.xhtml | 22 + layout/forms/crashtests/399262.html | 50 + layout/forms/crashtests/402852-1.html | 2 + layout/forms/crashtests/403148-1.html | 22 + layout/forms/crashtests/404118-1.html | 5 + layout/forms/crashtests/404123-1.html | 12 + layout/forms/crashtests/407066.html | 1 + layout/forms/crashtests/451316.html | 7 + layout/forms/crashtests/455451-1.html | 17 + layout/forms/crashtests/457537-1.html | 17 + layout/forms/crashtests/457537-2.html | 17 + layout/forms/crashtests/478219-1.xhtml | 7 + layout/forms/crashtests/498698-1.html | 6 + layout/forms/crashtests/513113-1.html | 6 + layout/forms/crashtests/538062-1.xhtml | 20 + layout/forms/crashtests/570624-1.html | 15 + layout/forms/crashtests/578604-1.html | 17 + layout/forms/crashtests/590302-1.xhtml | 4 + layout/forms/crashtests/626014.xhtml | 20 + layout/forms/crashtests/639733.xhtml | 26 + layout/forms/crashtests/669767.html | 14 + layout/forms/crashtests/682684-binding.xml | 4 + layout/forms/crashtests/682684.xhtml | 3 + layout/forms/crashtests/865602.html | 9 + layout/forms/crashtests/893332-1.html | 10 + layout/forms/crashtests/944198.html | 9 + layout/forms/crashtests/949891.xhtml | 5 + layout/forms/crashtests/959311.html | 17 + layout/forms/crashtests/960277-2.html | 14 + layout/forms/crashtests/997709-1.html | 5 + layout/forms/crashtests/crashtests.list | 69 + layout/forms/moz.build | 57 + layout/forms/nsButtonFrameRenderer.cpp | 516 ++++ layout/forms/nsButtonFrameRenderer.h | 88 + layout/forms/nsColorControlFrame.cpp | 147 ++ layout/forms/nsColorControlFrame.h | 65 + layout/forms/nsComboboxControlFrame.cpp | 1715 +++++++++++++ layout/forms/nsComboboxControlFrame.h | 328 +++ layout/forms/nsDateTimeControlFrame.cpp | 414 ++++ layout/forms/nsDateTimeControlFrame.h | 119 + layout/forms/nsFieldSetFrame.cpp | 703 ++++++ layout/forms/nsFieldSetFrame.h | 110 + layout/forms/nsFileControlFrame.cpp | 506 ++++ layout/forms/nsFileControlFrame.h | 167 ++ layout/forms/nsFormControlFrame.cpp | 225 ++ layout/forms/nsFormControlFrame.h | 129 + layout/forms/nsGfxButtonControlFrame.cpp | 237 ++ layout/forms/nsGfxButtonControlFrame.h | 67 + layout/forms/nsGfxCheckboxControlFrame.cpp | 147 ++ layout/forms/nsGfxCheckboxControlFrame.h | 40 + layout/forms/nsGfxRadioControlFrame.cpp | 93 + layout/forms/nsGfxRadioControlFrame.h | 32 + layout/forms/nsHTMLButtonControlFrame.cpp | 465 ++++ layout/forms/nsHTMLButtonControlFrame.h | 115 + layout/forms/nsIComboboxControlFrame.h | 86 + layout/forms/nsIFormControlFrame.h | 42 + layout/forms/nsIListControlFrame.h | 98 + layout/forms/nsISelectControlFrame.h | 50 + layout/forms/nsITextControlFrame.h | 53 + layout/forms/nsImageControlFrame.cpp | 206 ++ layout/forms/nsLegendFrame.cpp | 94 + layout/forms/nsLegendFrame.h | 37 + layout/forms/nsListControlFrame.cpp | 2537 ++++++++++++++++++++ layout/forms/nsListControlFrame.h | 477 ++++ layout/forms/nsMeterFrame.cpp | 297 +++ layout/forms/nsMeterFrame.h | 94 + layout/forms/nsNumberControlFrame.cpp | 799 ++++++ layout/forms/nsNumberControlFrame.h | 216 ++ layout/forms/nsProgressFrame.cpp | 308 +++ layout/forms/nsProgressFrame.h | 102 + layout/forms/nsRangeFrame.cpp | 949 ++++++++ layout/forms/nsRangeFrame.h | 215 ++ layout/forms/nsSelectsAreaFrame.cpp | 206 ++ layout/forms/nsSelectsAreaFrame.h | 49 + layout/forms/nsTextControlFrame.cpp | 1506 ++++++++++++ layout/forms/nsTextControlFrame.h | 319 +++ layout/forms/test/bug287446_subframe.html | 38 + layout/forms/test/bug477700_subframe.html | 39 + layout/forms/test/bug536567_iframe.html | 9 + layout/forms/test/bug536567_subframe.html | 14 + layout/forms/test/bug564115_window.html | 10 + layout/forms/test/bug665540_window.xul | 18 + layout/forms/test/chrome.ini | 11 + layout/forms/test/mochitest.ini | 66 + layout/forms/test/test_bug1111995.html | 60 + layout/forms/test/test_bug1301290.html | 49 + layout/forms/test/test_bug1305282.html | 59 + layout/forms/test/test_bug231389.html | 55 + layout/forms/test/test_bug287446.html | 75 + layout/forms/test/test_bug345267.html | 98 + layout/forms/test/test_bug346043.html | 65 + layout/forms/test/test_bug348236.html | 125 + layout/forms/test/test_bug353539.html | 52 + layout/forms/test/test_bug365410.html | 134 ++ layout/forms/test/test_bug378670.html | 55 + layout/forms/test/test_bug402198.html | 77 + layout/forms/test/test_bug411236.html | 80 + layout/forms/test/test_bug446663.html | 80 + layout/forms/test/test_bug476308.html | 31 + layout/forms/test/test_bug477531.html | 65 + layout/forms/test/test_bug477700.html | 60 + layout/forms/test/test_bug478219.xhtml | 38 + layout/forms/test/test_bug534785.html | 88 + layout/forms/test/test_bug536567_perwindowpb.html | 215 ++ layout/forms/test/test_bug542914.html | 115 + layout/forms/test/test_bug549170.html | 74 + layout/forms/test/test_bug562447.html | 62 + layout/forms/test/test_bug563642.html | 85 + layout/forms/test/test_bug564115.html | 55 + layout/forms/test/test_bug571352.html | 86 + layout/forms/test/test_bug572406.html | 48 + layout/forms/test/test_bug572649.html | 64 + layout/forms/test/test_bug595310.html | 69 + layout/forms/test/test_bug620936.html | 35 + layout/forms/test/test_bug644542.html | 63 + layout/forms/test/test_bug665540.html | 118 + layout/forms/test/test_bug672810.html | 120 + layout/forms/test/test_bug704049.html | 50 + layout/forms/test/test_bug717878_input_scroll.html | 82 + layout/forms/test/test_bug869314.html | 54 + layout/forms/test/test_bug903715.html | 81 + layout/forms/test/test_bug935876.html | 495 ++++ layout/forms/test/test_bug957562.html | 43 + layout/forms/test/test_bug960277.html | 29 + layout/forms/test/test_bug961363.html | 96 + layout/forms/test/test_listcontrol_search.html | 46 + layout/forms/test/test_select_prevent_default.html | 119 + layout/forms/test/test_select_vertical.html | 75 + layout/forms/test/test_textarea_resize.html | 102 + 170 files changed, 20419 insertions(+) create mode 100644 layout/forms/crashtests/1102791.html create mode 100644 layout/forms/crashtests/1140216.html create mode 100644 layout/forms/crashtests/1182414.html create mode 100644 layout/forms/crashtests/1212688.html create mode 100644 layout/forms/crashtests/1228670.xhtml create mode 100644 layout/forms/crashtests/1279354.html create mode 100644 layout/forms/crashtests/166750-1.html create mode 100644 layout/forms/crashtests/200347-1.html create mode 100644 layout/forms/crashtests/203041-1.html create mode 100644 layout/forms/crashtests/213390-1.html create mode 100644 layout/forms/crashtests/258101-1.html create mode 100644 layout/forms/crashtests/266225-1.html create mode 100644 layout/forms/crashtests/310426-1.xhtml create mode 100644 layout/forms/crashtests/310520-1.xhtml create mode 100644 layout/forms/crashtests/315752-1.xhtml create mode 100644 layout/forms/crashtests/317502-1.xhtml create mode 100644 layout/forms/crashtests/321894.html create mode 100644 layout/forms/crashtests/323499-1.html create mode 100644 layout/forms/crashtests/343510-1.html create mode 100644 layout/forms/crashtests/363696-1.xul create mode 100644 layout/forms/crashtests/363696-2.html create mode 100644 layout/forms/crashtests/363696-3.html create mode 100644 layout/forms/crashtests/366205-1.html create mode 100644 layout/forms/crashtests/366537-1.xhtml create mode 100644 layout/forms/crashtests/367587-1.html create mode 100644 layout/forms/crashtests/370703-1.html create mode 100644 layout/forms/crashtests/370940-1.html create mode 100644 layout/forms/crashtests/370967.html create mode 100644 layout/forms/crashtests/373586-1.xhtml create mode 100644 layout/forms/crashtests/375299-binding.xml create mode 100644 layout/forms/crashtests/375299.html create mode 100644 layout/forms/crashtests/378369.html create mode 100644 layout/forms/crashtests/378413-1.xhtml create mode 100644 layout/forms/crashtests/380116-1.xhtml create mode 100644 layout/forms/crashtests/382212-1.xhtml create mode 100644 layout/forms/crashtests/382610-1.html create mode 100644 layout/forms/crashtests/383887-1.html create mode 100644 layout/forms/crashtests/386554-1.html create mode 100644 layout/forms/crashtests/388374-1.xhtml create mode 100644 layout/forms/crashtests/388374-2.html create mode 100644 layout/forms/crashtests/393656-1.xhtml create mode 100644 layout/forms/crashtests/393656-2.xhtml create mode 100644 layout/forms/crashtests/399262.html create mode 100644 layout/forms/crashtests/402852-1.html create mode 100644 layout/forms/crashtests/403148-1.html create mode 100644 layout/forms/crashtests/404118-1.html create mode 100644 layout/forms/crashtests/404123-1.html create mode 100644 layout/forms/crashtests/407066.html create mode 100644 layout/forms/crashtests/451316.html create mode 100644 layout/forms/crashtests/455451-1.html create mode 100644 layout/forms/crashtests/457537-1.html create mode 100644 layout/forms/crashtests/457537-2.html create mode 100644 layout/forms/crashtests/478219-1.xhtml create mode 100644 layout/forms/crashtests/498698-1.html create mode 100644 layout/forms/crashtests/513113-1.html create mode 100644 layout/forms/crashtests/538062-1.xhtml create mode 100644 layout/forms/crashtests/570624-1.html create mode 100644 layout/forms/crashtests/578604-1.html create mode 100644 layout/forms/crashtests/590302-1.xhtml create mode 100644 layout/forms/crashtests/626014.xhtml create mode 100644 layout/forms/crashtests/639733.xhtml create mode 100644 layout/forms/crashtests/669767.html create mode 100644 layout/forms/crashtests/682684-binding.xml create mode 100644 layout/forms/crashtests/682684.xhtml create mode 100644 layout/forms/crashtests/865602.html create mode 100644 layout/forms/crashtests/893332-1.html create mode 100644 layout/forms/crashtests/944198.html create mode 100644 layout/forms/crashtests/949891.xhtml create mode 100644 layout/forms/crashtests/959311.html create mode 100644 layout/forms/crashtests/960277-2.html create mode 100644 layout/forms/crashtests/997709-1.html create mode 100644 layout/forms/crashtests/crashtests.list create mode 100644 layout/forms/moz.build create mode 100644 layout/forms/nsButtonFrameRenderer.cpp create mode 100644 layout/forms/nsButtonFrameRenderer.h create mode 100644 layout/forms/nsColorControlFrame.cpp create mode 100644 layout/forms/nsColorControlFrame.h create mode 100644 layout/forms/nsComboboxControlFrame.cpp create mode 100644 layout/forms/nsComboboxControlFrame.h create mode 100644 layout/forms/nsDateTimeControlFrame.cpp create mode 100644 layout/forms/nsDateTimeControlFrame.h create mode 100644 layout/forms/nsFieldSetFrame.cpp create mode 100644 layout/forms/nsFieldSetFrame.h create mode 100644 layout/forms/nsFileControlFrame.cpp create mode 100644 layout/forms/nsFileControlFrame.h create mode 100644 layout/forms/nsFormControlFrame.cpp create mode 100644 layout/forms/nsFormControlFrame.h create mode 100644 layout/forms/nsGfxButtonControlFrame.cpp create mode 100644 layout/forms/nsGfxButtonControlFrame.h create mode 100644 layout/forms/nsGfxCheckboxControlFrame.cpp create mode 100644 layout/forms/nsGfxCheckboxControlFrame.h create mode 100644 layout/forms/nsGfxRadioControlFrame.cpp create mode 100644 layout/forms/nsGfxRadioControlFrame.h create mode 100644 layout/forms/nsHTMLButtonControlFrame.cpp create mode 100644 layout/forms/nsHTMLButtonControlFrame.h create mode 100644 layout/forms/nsIComboboxControlFrame.h create mode 100644 layout/forms/nsIFormControlFrame.h create mode 100644 layout/forms/nsIListControlFrame.h create mode 100644 layout/forms/nsISelectControlFrame.h create mode 100644 layout/forms/nsITextControlFrame.h create mode 100644 layout/forms/nsImageControlFrame.cpp create mode 100644 layout/forms/nsLegendFrame.cpp create mode 100644 layout/forms/nsLegendFrame.h create mode 100644 layout/forms/nsListControlFrame.cpp create mode 100644 layout/forms/nsListControlFrame.h create mode 100644 layout/forms/nsMeterFrame.cpp create mode 100644 layout/forms/nsMeterFrame.h create mode 100644 layout/forms/nsNumberControlFrame.cpp create mode 100644 layout/forms/nsNumberControlFrame.h create mode 100644 layout/forms/nsProgressFrame.cpp create mode 100644 layout/forms/nsProgressFrame.h create mode 100644 layout/forms/nsRangeFrame.cpp create mode 100644 layout/forms/nsRangeFrame.h create mode 100644 layout/forms/nsSelectsAreaFrame.cpp create mode 100644 layout/forms/nsSelectsAreaFrame.h create mode 100644 layout/forms/nsTextControlFrame.cpp create mode 100644 layout/forms/nsTextControlFrame.h create mode 100644 layout/forms/test/bug287446_subframe.html create mode 100644 layout/forms/test/bug477700_subframe.html create mode 100644 layout/forms/test/bug536567_iframe.html create mode 100644 layout/forms/test/bug536567_subframe.html create mode 100644 layout/forms/test/bug564115_window.html create mode 100644 layout/forms/test/bug665540_window.xul create mode 100644 layout/forms/test/chrome.ini create mode 100644 layout/forms/test/mochitest.ini create mode 100644 layout/forms/test/test_bug1111995.html create mode 100644 layout/forms/test/test_bug1301290.html create mode 100644 layout/forms/test/test_bug1305282.html create mode 100644 layout/forms/test/test_bug231389.html create mode 100644 layout/forms/test/test_bug287446.html create mode 100644 layout/forms/test/test_bug345267.html create mode 100644 layout/forms/test/test_bug346043.html create mode 100644 layout/forms/test/test_bug348236.html create mode 100644 layout/forms/test/test_bug353539.html create mode 100644 layout/forms/test/test_bug365410.html create mode 100644 layout/forms/test/test_bug378670.html create mode 100644 layout/forms/test/test_bug402198.html create mode 100644 layout/forms/test/test_bug411236.html create mode 100644 layout/forms/test/test_bug446663.html create mode 100644 layout/forms/test/test_bug476308.html create mode 100644 layout/forms/test/test_bug477531.html create mode 100644 layout/forms/test/test_bug477700.html create mode 100644 layout/forms/test/test_bug478219.xhtml create mode 100644 layout/forms/test/test_bug534785.html create mode 100644 layout/forms/test/test_bug536567_perwindowpb.html create mode 100644 layout/forms/test/test_bug542914.html create mode 100644 layout/forms/test/test_bug549170.html create mode 100644 layout/forms/test/test_bug562447.html create mode 100644 layout/forms/test/test_bug563642.html create mode 100644 layout/forms/test/test_bug564115.html create mode 100644 layout/forms/test/test_bug571352.html create mode 100644 layout/forms/test/test_bug572406.html create mode 100644 layout/forms/test/test_bug572649.html create mode 100644 layout/forms/test/test_bug595310.html create mode 100644 layout/forms/test/test_bug620936.html create mode 100644 layout/forms/test/test_bug644542.html create mode 100644 layout/forms/test/test_bug665540.html create mode 100644 layout/forms/test/test_bug672810.html create mode 100644 layout/forms/test/test_bug704049.html create mode 100644 layout/forms/test/test_bug717878_input_scroll.html create mode 100644 layout/forms/test/test_bug869314.html create mode 100644 layout/forms/test/test_bug903715.html create mode 100644 layout/forms/test/test_bug935876.html create mode 100644 layout/forms/test/test_bug957562.html create mode 100644 layout/forms/test/test_bug960277.html create mode 100644 layout/forms/test/test_bug961363.html create mode 100644 layout/forms/test/test_listcontrol_search.html create mode 100644 layout/forms/test/test_select_prevent_default.html create mode 100644 layout/forms/test/test_select_vertical.html create mode 100644 layout/forms/test/test_textarea_resize.html (limited to 'layout/forms') 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 @@ + + + + Testcase for bug 1102791 + + + + + + + + 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 @@ + + + + + + + + + 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 @@ + + + + + + + + + + + + + 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 @@ + + 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 @@ + + + + + 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 @@ + + + + + Testcase for bug 1279354 + + + +
+1 +
+2 +
+3 +
+ + + 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 @@ + + + +Killer + +
+ +
+ + \ 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 @@ + + +
+ Crash test +
Hello, my name is Inigo Montoya.
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. +
+ + 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 @@ + + + + + +
+ +
+ +
+ +
+ + +
+ \ 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 @@ + + + + + + + + + + + +
+ 1 +
+ +
+
+ + + 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 @@ + + + + +
Test
+ + 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 @@ + + + + + + + + + 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 @@ + + + + + + + + + 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 @@ + + + + +Settings - Unclassified NewsBoard + + + + + + + + + \ 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 @@ + + + + + Testcase, bug 317502 + + + + + + + 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 @@ + + +Testcase bug 321894 - ASSERTION: RemovedAsPrimaryFrame called after PreDestroy: 'PR_FALSE' + + + +This shouldn't assert in a debug build + + +
+ + + +
+
+ + 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 @@ + + + + + + + + + + + + \ 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 @@ + + + + + \ 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 @@ +
+ \ 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 @@ + + + + + \ 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 @@ + + + + + +
Clicking the button below should not trigger an assertion
+ + + + + \ 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 @@ + + + + + + + + + +legend + +

H3

+ + + + + + 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 @@ + + + + + + + + + + + + + + + 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 @@ + + + + + + +
+ + + +
+ + + +
+ + + 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 @@ + + + + + +

العربي

+ + + 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 @@ + + + + +Untitled + + +Focus the input of the isindex, then do a reload, Mozilla should not crash + + + + + 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 @@ + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + 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 @@ + + + + \ 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 @@ + +Testcase bug - Crash [@ nsFileControlFrame::CreateAnonymousContent] when removing stylesheet with binding and removing file input + + + + + + 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 @@ + + +Testcase bug - Crash [@ nsEventListenerManager::FixContextMenuEvent] when firing contextmenu event in display: none iframe + + +This page should not crash Mozilla + + + + + 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 @@ + + + + +
+ Your name + +
+
+ Your E-mail address + +
+ + + + 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 @@ + + + + + + + +
+ + + 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 @@ + + +
+ +
+ + 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 @@ + + + + +
+ +
+ + + + 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 @@ + + + + + + + + + + +
+ +
+
+
+ + + 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 @@ + + + + + +
+ + + + + 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 @@ + + + + + + + + + + + + +
1
+ + + 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 @@ + + + + + + + + + + + + +
+ + + 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 @@ + + + + + + + + + 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 @@ + + + + + +
+
XXX
+ +
+
+
+
+
+
+
+ + + 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 @@ + + + + + + + + +
+ + + + + +
+ +
+ + + + + +
+ + + + + + + +
+ + + + + +
+ +
+ + + + + +
+ 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 @@ + +
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 @@ + + + + + + + + + + + + + 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 @@ + + + + + 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 @@ + + + + + + +
+ +
+ + + 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 @@ +
text 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 @@ + + + +
+
+ + 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 @@ + + + + + + + + + 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 @@ + + + + + + + + + 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 @@ + + + + + + + + + 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 @@ + +
+ + + +
+ 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 @@ + + + + + + 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 @@ + + + + + + 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 @@ + + + + + + + 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 @@ + + + + + + + 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 @@ + + + + + + + +

Crash Test Case

+
+ +
+ + + 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 @@ + +
+ + 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 @@ + + + + + + + + + + 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 @@ + + + + + +
+ + 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 @@ + + +Untitled + + + + + + + + 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 @@ + + + + \ 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 @@ + + + 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 @@ + + + +
+ + +
+ + 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 @@ + + + + + + + + + + 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 @@ + + + + + + + + + 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 @@ + + + + + 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 @@ + + + + + + + +
+ + + 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 @@ + + +
+
+
+ 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 @@ + +
+
+ +
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 *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(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(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 mInnerFocusStyle; + RefPtr 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(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& aElements) +{ + nsCOMPtr 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 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& 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 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 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& aElements) override; + virtual void AppendAnonymousContentTo(nsTArray& 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 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 +#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 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(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(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(mFrame.GetFrame()); + static_cast(combo->mDropdownFrame)-> + SetSuppressScrollbarUpdate(true); + nsCOMPtr shell = mFrame->PresContext()->PresShell(); + shell->FrameNeedsReflow(combo->mDropdownFrame, nsIPresShell::eResize, + NS_FRAME_IS_DIRTY); + shell->FlushPendingNotifications(Flush_Layout); + if (mFrame.IsAlive()) { + combo = static_cast(mFrame.GetFrame()); + static_cast(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(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(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 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 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 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(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(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& 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& 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 styleContext; + styleContext = styleSet-> + ResolveAnonymousBoxStyle(nsCSSAnonBoxes::mozDisplayComboboxControlFrame, + mStyleContext, + nsStyleSet::eSkipParentDisplayBasedStyleFixup); + + RefPtr 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(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* 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 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(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& aElements) override; + virtual void AppendAnonymousContentTo(nsTArray& 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* 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 *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 mDisplayContent; // Anonymous content used to display the current selection + nsCOMPtr 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 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 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 inputAreaContent = + do_QueryInterface(mInputAreaContent); + if (inputAreaContent) { + inputAreaContent->NotifyInputElementValueChanged(); + } +} + +void +nsDateTimeControlFrame::SetValueFromPicker(const DateTimeValue& aValue) +{ + nsCOMPtr 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 jsValue(jsapi.cx()); + if (!ToJSValue(jsapi.cx(), aValue, &jsValue)) { + return; + } + + inputAreaContent->SetValueFromPicker(jsValue); + } +} + +void +nsDateTimeControlFrame::SetPickerState(bool aOpen) +{ + nsCOMPtr inputAreaContent = + do_QueryInterface(mInputAreaContent); + if (inputAreaContent) { + inputAreaContent->SetPickerState(aOpen); + } +} + +void +nsDateTimeControlFrame::HandleFocusEvent() +{ + nsCOMPtr inputAreaContent = + do_QueryInterface(mInputAreaContent); + if (inputAreaContent) { + inputAreaContent->FocusInnerTextBox(); + } +} + +void +nsDateTimeControlFrame::HandleBlurEvent() +{ + nsCOMPtr 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& aElements) +{ + // Set up "datetimebox" XUL element which will be XBL-bound to the + // actual controls. + nsNodeInfoManager* nodeInfoManager = + mContent->GetComposedDoc()->NodeInfoManager(); + RefPtr 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& 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(mContent); + // If script changed the '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 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& aElements) override; + void AppendAnonymousContentTo(nsTArray& 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(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 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 +#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 *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 *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(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(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 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 + (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 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
. + */ + 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 +MakeAnonButton(nsIDocument* aDoc, const char* labelKey, + HTMLInputElement* aInputElement, + const nsAString& aAccessKey) +{ + RefPtr 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 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 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& aElements) +{ + nsCOMPtr doc = mContent->GetComposedDoc(); + + RefPtr 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 = 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& 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& 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 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::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 dragEvent = do_QueryInterface(aEvent); + if (!dragEvent) { + return NS_OK; + } + + nsCOMPtr dataTransfer; + dragEvent->GetDataTransfer(getter_AddRefs(dataTransfer)); + if (!IsValidDropData(dataTransfer)) { + return NS_OK; + } + + + nsCOMPtr 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 fileList; + dataTransfer->GetFiles(getter_AddRefs(fileList)); + + RefPtr webkitDir; + nsresult rv = + GetBlobImplForWebkitDirectory(fileList, getter_AddRefs(webkitDir)); + NS_ENSURE_SUCCESS(rv, NS_OK); + + nsTArray 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.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(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 retVal = impl; + retVal.swap(*aBlobImpl); + return NS_OK; + } + } + } + + return NS_ERROR_FAILURE; +} + +bool +nsFileControlFrame::DnDListener::IsValidDropData(nsIDOMDataTransfer* aDOMDataTransfer) +{ + nsCOMPtr dataTransfer = do_QueryInterface(aDOMDataTransfer); + NS_ENSURE_TRUE(dataTransfer, false); + + // We only support dropping files onto a file upload control + nsTArray types; + dataTransfer->GetTypes(types, *nsContentUtils::GetSystemPrincipal()); + + return types.Contains(NS_LITERAL_STRING("Files")); +} + +bool +nsFileControlFrame::DnDListener::CanDropTheseFiles(nsIDOMDataTransfer* aDOMDataTransfer, + bool aSupportsMultiple) +{ + nsCOMPtr dataTransfer = do_QueryInterface(aDOMDataTransfer); + NS_ENSURE_TRUE(dataTransfer, false); + + nsCOMPtr fileList; + dataTransfer->GetFiles(getter_AddRefs(fileList)); + + RefPtr 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& aElements) override; + virtual void AppendAnonymousContentTo(nsTArray& 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(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 mTextContent; + /** + * The button to open a file or directory picker. + * @see nsFileControlFrame::CreateAnonymousContent + */ + nsCOMPtr mBrowseFilesOrDirs; + + /** + * Drag and drop mouse listener. + * This makes sure we don't get used after destruction. + */ + RefPtr 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(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(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 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& 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& 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 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 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 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& aElements) override; + virtual void AppendAnonymousContentTo(nsTArray& 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 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 + +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 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 = 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 elem(do_QueryInterface(mContent)); + bool retval = false; + elem->GetChecked(&retval); + return retval; +} + +bool +nsGfxCheckboxControlFrame::IsIndeterminate() +{ + nsCOMPtr 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 builder = aDrawTarget->CreatePathBuilder(); + AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size()); + RefPtr 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 + +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(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 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(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 -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); +} + +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 + (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(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(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 + +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(this), false); + nsHTMLScrollFrame::DestroyFrom(aDestructRoot); +} + +void +nsListControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // We allow visibility:hidden . So if the mouse goes over an option just before + // he leaves the box and clicks, that's what the 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 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 textField = HTMLInputElement::FromContent(mTextField); + textField->Focus(); + } +} + +nsresult +nsNumberControlFrame::HandleSelectCall() +{ + RefPtr 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& 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 . + */ +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& aElements) override; + virtual void AppendAnonymousContentTo(nsTArray& 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& 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(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 mOuterWrapper; + nsCOMPtr mTextField; + nsCOMPtr mSpinBox; + nsCOMPtr mSpinUp; + nsCOMPtr 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 + +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(this), false); + nsContentUtils::DestroyAnonymousContent(&mBarDiv); + nsContainerFrame::DestroyFrom(aDestructRoot); +} + +nsIAtom* +nsProgressFrame::GetType() const +{ + return nsGkAtoms::progressFrame; +} + +nsresult +nsProgressFrame::CreateAnonymousContent(nsTArray& aElements) +{ + // Create the progress bar div. + nsCOMPtr doc = mContent->GetComposedDoc(); + mBarDiv = doc->CreateHTMLElement(nsGkAtoms::div); + + // Associate ::-moz-progress-bar pseudo-element to the anonymous child. + CSSPseudoElementType pseudoType = CSSPseudoElementType::mozProgressBar; + RefPtr 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& 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(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 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& aElements) override; + virtual void AppendAnonymousContentTo(nsTArray& 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 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(this), false); + nsContentUtils::DestroyAnonymousContent(&mTrackDiv); + nsContentUtils::DestroyAnonymousContent(&mProgressDiv); + nsContentUtils::DestroyAnonymousContent(&mThumbDiv); + nsContainerFrame::DestroyFrom(aDestructRoot); +} + +nsresult +nsRangeFrame::MakeAnonymousDiv(Element** aResult, + CSSPseudoElementType aPseudoType, + nsTArray& aElements) +{ + nsCOMPtr doc = mContent->GetComposedDoc(); + RefPtr resultElement = doc->CreateHTMLElement(nsGkAtoms::div); + + // Associate the pseudo-element with the anonymous child. + RefPtr 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& 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& 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(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(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(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(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(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(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, + ¬UsedCanOverride); + 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(mContent)->GetType() == + NS_FORM_INPUT_RANGE; + // If script changed the '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(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(mContent)->GetMinimum().toDouble(); +} + +double +nsRangeFrame::GetMax() const +{ + return static_cast(mContent)->GetMaximum().toDouble(); +} + +double +nsRangeFrame::GetValue() const +{ + return static_cast(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& aElements) override; + virtual void AppendAnonymousContentTo(nsTArray& 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& 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 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 mProgressDiv; + + /** + * The div used to show the ::-moz-range-thumb pseudo-element. + * @see nsRangeFrame::CreateAnonymousContent + */ + nsCOMPtr mThumbDiv; + + /** + * Cached style context for -moz-focus-outer CSS pseudo-element style. + */ + RefPtr 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 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 *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 *aOutFrames) +{ + nsTArray 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(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 +#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 txtCtrl = do_QueryInterface(GetContent()); + NS_ASSERTION(txtCtrl, "Content not a text control element"); + txtCtrl->UnbindFromFrame(this); + + nsFormControlFrame::RegUnRegAccessKey(static_cast(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 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
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 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& aElements) +{ + NS_ASSERTION(mContent, "We should have a content!"); + + mState |= NS_FRAME_INDEPENDENT_SELECTION; + + nsCOMPtr 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 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 txtCtrl = do_QueryInterface(GetContent()); + NS_ASSERTION(txtCtrl, "Content not a text control element"); + initEagerly = txtCtrl->HasCachedSelection(); + } + if (!initEagerly) { + nsCOMPtr 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& 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(content); + } else if (content->IsHTMLElement(nsGkAtoms::textarea)) { + txtCtrl = static_cast(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 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 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 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 ourSel; + selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(ourSel)); + if (!ourSel) return; + + nsIPresShell* presShell = PresContext()->GetPresShell(); + RefPtr 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 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 selcon = do_QueryInterface(presShell); + nsCOMPtr 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 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 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 start = do_QueryInterface(aStartNode); + nsCOMPtr 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 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 selection; + selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); + NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); + + nsCOMPtr 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 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 root; + GetRootNodeAndInitializeEditor(getter_AddRefs(root)); + nsCOMPtr rootElem = do_QueryInterface(root); + return rootElem; +} + +nsresult +nsTextControlFrame::GetRootNodeAndInitializeEditor(nsIDOMElement **aRootElement) +{ + NS_ENSURE_ARG_POINTER(aRootElement); + + nsCOMPtr editor; + GetEditor(getter_AddRefs(editor)); + if (!editor) + return NS_OK; + + return editor->GetRootElement(aRootElement); +} + +nsresult +nsTextControlFrame::SelectAllOrCollapseToEndOfText(bool aSelect) +{ + nsCOMPtr rootElement; + nsresult rv = GetRootNodeAndInitializeEditor(getter_AddRefs(rootElement)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr rootContent = do_QueryInterface(rootElement); + nsCOMPtr 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 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 rootElement; + nsresult rv = GetRootNodeAndInitializeEditor(getter_AddRefs(rootElement)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr rootNode(do_QueryInterface(rootElement)); + + NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE); + + nsCOMPtr 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 firstNode; + rv = nodeList->Item(0, getter_AddRefs(firstNode)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 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 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 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 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 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 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 textArea = do_QueryInterface(mContent); + if (textArea) { + rv = textArea->GetValue(aText); + } + } + return rv; +} + + +nsresult +nsTextControlFrame::GetPhonetic(nsAString& aPhonetic) +{ + aPhonetic.Truncate(0); + + nsCOMPtr editor; + nsresult rv = GetEditor(getter_AddRefs(editor)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr imeSupport = do_QueryInterface(editor); + if (imeSupport) { + nsCOMPtr 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 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 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 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 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 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 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 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 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 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 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 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& aElements) override; + virtual void AppendAnonymousContentTo(nsTArray& 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 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 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 + + + 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 @@ + + + + + Test for Bug 1305282 + + + + + +Mozilla Bug 1305282 +

+
+ +
+
+
+
+ + 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 @@ + + + + + Test for Bug 231389 + + + + +Mozilla Bug 231389 +

+ +

+ +
+
+
+ + 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 @@ + + + + + Test for Bug 287446 + + + + +Mozilla Bug 287446 +

+ +

+ +
+
+
+ + 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 @@ + + + + + Test for Bug 345267 + + + + + +Mozilla Bug 345267 +

+ + + + + +

+ +
+
+
+ + + 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 @@ + + + + + + Test for Bug 346043 + + + + + + + Mozilla Bug 346043 +

+
+ + + +
+

+
+
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 @@
+
+
+
+
+
+  Test for Bug 348236
+  
+  
+  
+
+
+Mozilla Bug 348236
+

+
+ + +
+
+
+
+ + 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 @@ + + + + + Test for Bug 353539 + + + + +Mozilla Bug 353539 +

+ +

+ +
+
+
+ + 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 @@ + + + + + Test for Bug 365410 + + + + + +Mozilla Bug 365410 +

+ + + + + +

+ +
+
+
+ + 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 @@ + + + + + Test for Bug 378670 + + + + + +Mozilla Bug 378670 +

+ +Clicking on the select should not crash Mozilla + + +
+
+
+
+ + 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 @@ + + + + Test for Bug 402198 + + + + + +Mozilla Bug 402198 +

+ + + + + + + + + + + + + + + + + + + +

+ +
+
+
+ + + + + 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 @@ + + + + + Test for Bug 411236 + + + + +Mozilla Bug 411236 +

+
+ +
+
+
+
+ + 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 @@ + + + + + Test for Bug 446663 + + + + + +Mozilla Bug 446663 +

+ +

+ +
+
+
+ + + 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 @@ + + + + + Test for Bug 345267 + + + + + + + + +
+
2
+ + + + + + 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 @@ + + + + + Test for Bug 477531 + + + + + + +Mozilla Bug 477531 +

+
+ + + +
+
+
+
+ + + 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 @@ + + + + + Test for Bug 477700 + + + + +Mozilla Bug 477700 +

+ +

+ +
+
+
+ + 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 @@ + + + + Test for Bug 478219 + + + + + + +Mozilla Bug 478219 + +
+
+
+ + + + + +
+ +
+ +
+
+
+ + 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 @@ + + + + + Test for Bug 534785 + + + + + +Mozilla Bug 534785 +

+ +
+ + + + +
+
+
+
+ + 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 @@ + + + + + Test for Bug 536567 + + + + + +Mozilla Bug 536567 +

+
+
+
+ + 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 @@ + + + + + Test for Bug 542914 + + + + + +Mozilla Bug 542914 +

+ + + +

+ +
+
+
+ + 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 @@ + + + + + Test for Bug 549170 + + + + + +Mozilla Bug 549170 +

+ + +
+ + +
+
+
+
+ + 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 @@ + + + + + Test for Bug 562447 + + + + + +

Mozilla Bug 562447 + + + +

+
+
+ + + 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 @@ + + + + + Test for Bug 563642 + + + + + +Mozilla Bug 563642 +

+ + + + +

+ +
+
+
+ + 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 @@ + + + + + Test for Bug 564115 + + + + + +

Mozilla Bug 564115 + +

+
+
+ + + 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 @@ + + + + + Test for Bug 571352 + + + + + +Mozilla Bug 571352 +

+ +
+
+
+ + 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 @@ + + + + + Test for Bug 572406 + + + + +Mozilla Bug 572406 +

+
+ +
+
+
+
+ + 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 @@ + + + + + Test for Bug 572649 + + + + + +Mozilla Bug 572649 +

+ +

+ +
+
+
+ + 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 @@ + + + + + Test for Bug 595310 + + + + + +Mozilla Bug 595310 +

+
+ + +
+
+
+
+ + 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 @@ + + + + + Test for Bug 620936 + + + + + +Mozilla Bug 620936 +

+
+ +
+
+
+
+ + 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 @@ + + + + + Test for Bug 644542 + + + + + + +Mozilla Bug 644542 +

+

+ +
+

+ +
+
+
+ + 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 @@ + + + + + Test for Bug 665540 Select dropdown position in fullscreen window + + + + + +Mozilla Bug 665540 +

+ +
+
+
+ + 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 @@ + + + + + Test for Bug 672810 + + + + + +Mozilla Bug 672810 +

+
+ + + +
+
+
+
+ + 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 @@ + + + + + Test for Bug 704049 + + + + + +Mozilla Bug 704049 +

+ + + + +
+
+
+ + 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 @@ + + + + + + Test for Bug 717878 + + + + +Mozilla Bug 717878 +

+ + + + + +
+
+
+ + 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 @@ + + + + + + Test for Bug 869314 + + + + + + + +Mozilla Bug 869314 +

+
+ + + + + + + +
+
+
+ + 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 @@ + + + + + + Test for Bug 903715 + + + + + +Mozilla Bug 903715 +

+
+
+ + + +
+
+
+
+ + + 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 @@ + + + + + + Test for Bug 935876 + + + + + +Mozilla Bug 935876 +

+
+ + + +
+
+
+ + + 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 @@ + + + + + + Test for Bug 903715 + + + + + +Mozilla Bug 957562 +

+ +
+
+ + + 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 @@ + + + + + + Test for Bug 903715 + + + + + +Mozilla Bug 960277 +

+
+ +
+
+
+
+
+ + + 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 @@ + + + + + +Test for Bug 961363 + + + + + + +Mozilla Bug 961363 +
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+
+ + 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 @@ + + + + + + Test for <select> list control search + + + + + + +Mozilla Bug 849438 +

+
+ +
+
+
+ + 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 @@ + + + + + +Test for Bug 291082 + + + + + + +Mozilla Bug 291082 +
+
    +
  • + + select onkeydown="event.preventDefault();" +
  • +
  • + + select onkeypress="event.preventDefault();" +
  • +
  • + + li onkeydown="event.preventDefault();" +
  • +
  • + + li onkeypress="event.preventDefault();" +
  • +
  • + + select.addEventListener("keydown", function(event) { event.preventDefault(); }); +
  • +
  • + + select.addEventListener("keypress", function(event) { event.preventDefault(); }); +
  • +
+
+
+
+ + 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 @@ + + +Test for select popup in vertical writing mode + + + + + +Mozilla Bug 1113206 +
+ + +
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 @@ + + + + Test for Bug 477700 + + + + + + + + + +
+
+
+ + -- cgit v1.2.3