Bug 1338961 - A mozinputrangeignorepreventdefault hack for input[type=range], r=smaug, sr=smaug draft
authorTimothy Guan-tin Chien <timdream@gmail.com>
Fri, 17 Feb 2017 17:05:27 +0800
changeset 486841 39f16b4e50267f36c7bd19f11d5226242d7445cd
parent 483291 195049fabb7ac5709e5f75614ba630ba3d1b5a9b
child 546329 99c5b70b2b1de05da46a645dc63602d8949e72ae
push id46074
push userbmo:timdream@gmail.com
push dateMon, 20 Feb 2017 03:28:58 +0000
reviewerssmaug, smaug
bugs1338961
milestone54.0a1
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
@@ -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>