Bug 1338961 - A mozinputrangeignorepreventdefault hack for input[type=range], r=smaug, sr=smaug
authorTimothy Guan-tin Chien <timdream@gmail.com>
Fri, 17 Feb 2017 17:05:27 +0800
changeset 343872 aab681ba77bff41401f2eff606b3c79837ea4e21
parent 343871 1163d72d876f94d5c6932e37af83ff6bfc91be44
child 343873 fb96b7e02bb279f2336869dc7770a69007f70915
push id37671
push userryanvm@gmail.com
push dateMon, 20 Feb 2017 19:41:44 +0000
treeherderautoland@aab681ba77bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, smaug
bugs1338961
milestone54.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
dom/base/nsGkAtomList.h
dom/html/HTMLInputElement.cpp
dom/html/HTMLInputElement.h
dom/html/test/forms/chrome.ini
dom/html/test/forms/mochitest.ini
dom/html/test/forms/test_input_range_mozinputrangeignorepreventdefault.html
dom/html/test/forms/test_input_range_mozinputrangeignorepreventdefault_chrome.html
toolkit/content/widgets/videocontrols.xml
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -1957,16 +1957,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>