Bug 1338961 - A mozinputrangeignorepreventdefault hack for input[type=range], r=smaug, sr=smaug
This is a terrible hack, asking input[type=range] in our video control
xbl binding content continue to handle mouse/touch event, even if the
event is being defaultPrevented by the content.
MozReview-Commit-ID: G1huxbS7oeq
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -1955,16 +1955,19 @@ GK_ATOM(ondevicelight, "ondevicelight")
// Audio channel events
GK_ATOM(onmozinterruptbegin, "onmozinterruptbegin")
GK_ATOM(onmozinterruptend, "onmozinterruptend")
// MediaDevices device change event
GK_ATOM(ondevicechange, "ondevicechange")
+// HTML element attributes that only exposed to XBL and chrome content
+GK_ATOM(mozinputrangeignorepreventdefault, "mozinputrangeignorepreventdefault")
+
//---------------------------------------------------------------------------
// Special atoms
//---------------------------------------------------------------------------
// Node types
GK_ATOM(cdataTagName, "#cdata-section")
GK_ATOM(commentTagName, "#comment")
GK_ATOM(documentNodeName, "#document")
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -4919,17 +4919,18 @@ HTMLInputElement::PostHandleEvent(EventC
return MaybeInitPickers(aVisitor);
}
void
HTMLInputElement::PostHandleEventForRangeThumb(EventChainPostVisitor& aVisitor)
{
MOZ_ASSERT(mType == NS_FORM_INPUT_RANGE);
- if (nsEventStatus_eConsumeNoDefault == aVisitor.mEventStatus ||
+ if ((nsEventStatus_eConsumeNoDefault == aVisitor.mEventStatus &&
+ !MozInputRangeIgnorePreventDefault()) ||
!(aVisitor.mEvent->mClass == eMouseEventClass ||
aVisitor.mEvent->mClass == eTouchEventClass ||
aVisitor.mEvent->mClass == eKeyboardEventClass)) {
return;
}
nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
if (!rangeFrame && mIsDraggingRange) {
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -777,16 +777,22 @@ public:
int32_t InputTextLength(CallerType aCallerType);
void MozGetFileNameArray(nsTArray<nsString>& aFileNames, ErrorResult& aRv);
void MozSetFileNameArray(const Sequence< nsString >& aFileNames, ErrorResult& aRv);
void MozSetFileArray(const Sequence<OwningNonNull<File>>& aFiles);
void MozSetDirectory(const nsAString& aDirectoryPath, ErrorResult& aRv);
+ bool MozInputRangeIgnorePreventDefault() const
+ {
+ return (IsInChromeDocument() || IsInNativeAnonymousSubtree()) &&
+ GetBoolAttr(nsGkAtoms::mozinputrangeignorepreventdefault);
+ }
+
/*
* The following functions are called from datetime picker to let input box
* know the current state of the picker or to update the input box on changes.
*/
void GetDateTimeInputBoxValue(DateTimeValue& aValue);
void UpdateDateTimeInputBox(const DateTimeValue& aValue);
void SetDateTimePickerState(bool aOpen);
@@ -1463,17 +1469,17 @@ protected:
bool IsPopupBlocked() const;
GetFilesHelper* GetOrCreateGetFilesHelper(bool aRecursiveFlag,
ErrorResult& aRv);
void ClearGetFilesHelpers();
/**
- * nsINode::SetMayBeApzAware() will be invoked in this function if necessary
+ * nsINode::SetMayBeApzAware() will be invoked in this function if necessary
* to prevent default action of APZC so that we can increase/decrease the
* value of this InputElement when mouse wheel event comes without scrolling
* the page.
*
* SetMayBeApzAware() will set flag MayBeApzAware which is checked by apzc to
* decide whether to add this element into its dispatch-to-content region.
*/
void UpdateApzAwareFlag();
--- a/dom/html/test/forms/chrome.ini
+++ b/dom/html/test/forms/chrome.ini
@@ -1,5 +1,6 @@
[DEFAULT]
support-files =
submit_invalid_file.sjs
[test_autocompleteinfo.html]
[test_submit_invalid_file.html]
+[test_input_range_mozinputrangeignorepreventdefault_chrome.html]
--- a/dom/html/test/forms/mochitest.ini
+++ b/dom/html/test/forms/mochitest.ini
@@ -55,16 +55,17 @@ skip-if = os == "android"
skip-if = os == "android"
[test_input_number_validation.html]
# We don't build ICU for Firefox for Android:
skip-if = os == "android"
[test_input_number_focus.html]
[test_input_range_attr_order.html]
[test_input_range_key_events.html]
[test_input_range_mouse_and_touch_events.html]
+[test_input_range_mozinputrangeignorepreventdefault.html]
[test_input_range_rounding.html]
[test_input_sanitization.html]
[test_input_textarea_set_value_no_scroll.html]
[test_input_time_key_events.html]
skip-if = os == "android"
[test_input_types_pref.html]
[test_input_typing_sanitization.html]
[test_input_untrusted_key_events.html]
new file mode 100644
--- /dev/null
+++ b/dom/html/test/forms/test_input_range_mozinputrangeignorepreventdefault.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1338961
+-->
+<head>
+ <title>Test mouse and touch events for range</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <style>
+ /* synthesizeMouse and synthesizeFunc uses getBoundingClientRect. We set
+ * the following properties to avoid fractional values in the rect returned
+ * by getBoundingClientRect in order to avoid rounding that would occur
+ * when event coordinates are internally converted to be relative to the
+ * top-left of the element. (Such rounding would make it difficult to
+ * predict exactly what value the input should take on for events at
+ * certain coordinates.)
+ */
+ input { margin: 0 ! important; width: 200px ! important; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1338961">Mozilla Bug 1338961</a>
+<p id="display"></p>
+<div id="content">
+ <input id="range" type="range" mozinputrangeignorepreventdefault="true">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 1338961
+ * This test ensures mozinputrangeignorepreventdefault has no effect in
+ * content html.
+ **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ test(synthesizeMouse, "click", "mousedown", "mousemove", "mouseup");
+ test(synthesizeTouch, "tap", "touchstart", "touchmove", "touchend");
+ SimpleTest.finish();
+});
+
+function flush() {
+ // Flush style, specifically to flush the 'direction' property so that the
+ // browser uses the new value for thumb positioning.
+ var flush = document.body.clientWidth;
+}
+
+const QUARTER_OF_RANGE = "25";
+
+function test(synthesizeFunc, clickOrTap, startName, moveName, endName) {
+ var elem = document.getElementById("range");
+ elem.focus();
+ flush();
+
+ var width = parseFloat(window.getComputedStyle(elem).width);
+ var height = parseFloat(window.getComputedStyle(elem).height);
+ var borderLeft = parseFloat(window.getComputedStyle(elem).borderLeftWidth);
+ var borderTop = parseFloat(window.getComputedStyle(elem).borderTopWidth);
+ var paddingLeft = parseFloat(window.getComputedStyle(elem).paddingLeft);
+ var paddingTop = parseFloat(window.getComputedStyle(elem).paddingTop);
+
+ // Extrema for mouse/touch events:
+ var midY = height / 2 + borderTop + paddingTop;
+ var minX = borderLeft + paddingLeft;
+ var midX = minX + width / 2;
+ var maxX = minX + width;
+
+ function preventDefault(e) {
+ e.preventDefault();
+ }
+
+ // Test that preventDefault() works:
+ elem.value = QUARTER_OF_RANGE;
+ elem.addEventListener(startName, preventDefault);
+ synthesizeFunc(elem, midX, midY, {});
+ is(elem.value, QUARTER_OF_RANGE, "Test that preventDefault() works");
+ elem.removeEventListener(startName, preventDefault);
+
+ // Test that preventDefault() on the parent node works:
+ elem.value = QUARTER_OF_RANGE;
+ elem.parentNode.addEventListener(startName, preventDefault);
+ synthesizeFunc(elem, midX, midY, {});
+ is(elem.value, QUARTER_OF_RANGE, "Test that preventDefault() on the parent node works");
+ elem.parentNode.removeEventListener(startName, preventDefault);
+}
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/html/test/forms/test_input_range_mozinputrangeignorepreventdefault_chrome.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1338961
+-->
+<head>
+ <title>Test mouse and touch events for range</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <style>
+ /* synthesizeMouse and synthesizeFunc uses getBoundingClientRect. We set
+ * the following properties to avoid fractional values in the rect returned
+ * by getBoundingClientRect in order to avoid rounding that would occur
+ * when event coordinates are internally converted to be relative to the
+ * top-left of the element. (Such rounding would make it difficult to
+ * predict exactly what value the input should take on for events at
+ * certain coordinates.)
+ */
+ input { margin: 0 ! important; width: 200px ! important; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1338961">Mozilla Bug 1338961</a>
+<p id="display"></p>
+<div id="content">
+ <input id="range" type="range" mozinputrangeignorepreventdefault="true">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 1338961
+ * This test ensures mozinputrangeignorepreventdefault has it's desired effect in
+ * chrome html.
+ **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ test(synthesizeMouse, "click", "mousedown", "mousemove", "mouseup");
+ test(synthesizeTouch, "tap", "touchstart", "touchmove", "touchend");
+ SimpleTest.finish();
+});
+
+function flush() {
+ // Flush style, specifically to flush the 'direction' property so that the
+ // browser uses the new value for thumb positioning.
+ var flush = document.body.clientWidth;
+}
+
+const MIDDLE_OF_RANGE = "50";
+const QUARTER_OF_RANGE = "25";
+
+function test(synthesizeFunc, clickOrTap, startName, moveName, endName) {
+ var elem = document.getElementById("range");
+ elem.focus();
+ flush();
+
+ var width = parseFloat(window.getComputedStyle(elem).width);
+ var height = parseFloat(window.getComputedStyle(elem).height);
+ var borderLeft = parseFloat(window.getComputedStyle(elem).borderLeftWidth);
+ var borderTop = parseFloat(window.getComputedStyle(elem).borderTopWidth);
+ var paddingLeft = parseFloat(window.getComputedStyle(elem).paddingLeft);
+ var paddingTop = parseFloat(window.getComputedStyle(elem).paddingTop);
+
+ // Extrema for mouse/touch events:
+ var midY = height / 2 + borderTop + paddingTop;
+ var minX = borderLeft + paddingLeft;
+ var midX = minX + width / 2;
+ var maxX = minX + width;
+
+ function preventDefault(e) {
+ e.preventDefault();
+ }
+
+ // Test that preventDefault() is ignored:
+ elem.value = QUARTER_OF_RANGE;
+ elem.addEventListener(startName, preventDefault);
+ synthesizeFunc(elem, midX, midY, {});
+ is(elem.value, MIDDLE_OF_RANGE, "Test that preventDefault() is ignored");
+ elem.removeEventListener(startName, preventDefault);
+
+ // Test that preventDefault() on the parent node works:
+ elem.value = QUARTER_OF_RANGE;
+ elem.parentNode.addEventListener(startName, preventDefault);
+ synthesizeFunc(elem, midX, midY, {});
+ is(elem.value, MIDDLE_OF_RANGE, "Test that preventDefault() on the parent node is ignored");
+ elem.parentNode.removeEventListener(startName, preventDefault);
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -155,31 +155,31 @@
tabindex="-1"/>
<div anonid="scrubberStack" class="scrubberStack progressContainer" role="none">
<div class="progressBackgroundBar stackItem" role="none">
<div class="progressStack" role="none">
<progress anonid="bufferBar" class="bufferBar" value="0" max="100" tabindex="-1"></progress>
<progress anonid="progressBar" class="progressBar" value="0" max="100" tabindex="-1"></progress>
</div>
</div>
- <input type="range" anonid="scrubber" class="scrubber" tabindex="-1"/>
+ <input type="range" anonid="scrubber" class="scrubber" tabindex="-1" mozinputrangeignorepreventdefault="true" />
</div>
<span anonid="positionLabel" class="positionLabel" role="presentation"></span>
<span anonid="durationLabel" class="durationLabel" role="presentation"></span>
<span anonid="positionDurationBox" class="positionDurationBox" aria-hidden="true">
&positionAndDuration.nameFormat;
</span>
<div anonid="controlBarSpacer" class="controlBarSpacer" hidden="true" role="none"></div>
<button anonid="muteButton"
class="muteButton"
mutelabel="&muteButton.muteLabel;"
unmutelabel="&muteButton.unmuteLabel;"
tabindex="-1"/>
<div anonid="volumeStack" class="volumeStack progressContainer" role="none">
- <input type="range" anonid="volumeControl" class="volumeControl" min="0" max="100" step="1" tabindex="-1"/>
+ <input type="range" anonid="volumeControl" class="volumeControl" min="0" max="100" step="1" tabindex="-1" mozinputrangeignorepreventdefault="true" />
</div>
<button anonid="closedCaptionButton" class="closedCaptionButton"/>
<button anonid="fullscreenButton"
class="fullscreenButton"
enterfullscreenlabel="&fullscreenButton.enterfullscreenlabel;"
exitfullscreenlabel="&fullscreenButton.exitfullscreenlabel;"/>
</div>
<div anonid="textTrackList" class="textTrackList" hidden="true" offlabel="&closedCaption.off;"></div>