Bug 1309628 - Hide support for dispatching selection events on the contents of text controls behind a pref; r=mystor
authorEhsan Akhgari <ehsan@mozilla.com>
Wed, 12 Oct 2016 23:36:29 -0400
changeset 317919 4e664a1bdebd63417b0f75d0dac38a3e04ff063c
parent 317918 27509f16a0f2a011998b4dff8bba1fff6b48240a
child 317920 f799fa3ce5dcda8706169e24aeceabe97a36a917
push id33170
push usercbook@mozilla.com
push dateFri, 14 Oct 2016 10:37:07 +0000
treeherderautoland@0d101ebfd95c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmystor
bugs1309628
milestone52.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 1309628 - Hide support for dispatching selection events on the contents of text controls behind a pref; r=mystor This patch adds a pref to control whether we dispatch the selection events for changes in the contents of input and textarea text controls. The spec for this feature hasn't been written yet, and we need to exclude this part of the selection API from the part we want to ship.
dom/tests/mochitest/general/frameSelectEvents.html
layout/generic/nsFrameSelection.h
layout/generic/nsSelection.cpp
modules/libpref/init/all.js
--- a/dom/tests/mochitest/general/frameSelectEvents.html
+++ b/dom/tests/mochitest/general/frameSelectEvents.html
@@ -41,16 +41,21 @@
         var selectstart = 0;
         var selectionchange = 0;
         var inputSelectionchange = 0;
         var textareaSelectionchange = 0;
 
         var cancel = false;
         var selectstartTarget = null;
 
+        function* UpdateSelectEventsOntextControlsPref(aEnabled) {
+          yield SpecialPowers.pushPrefEnv({'set': [['dom.select_events.textcontrols.enabled', aEnabled]]});
+        }
+        yield* UpdateSelectEventsOntextControlsPref(false);
+
         document.addEventListener('selectstart', function(aEvent) {
           console.log("originaltarget", aEvent.originalTarget, "new", selectstartTarget);
           is(aEvent.originalTarget, selectstartTarget,
              "The original target of selectstart");
           selectstartTarget = null;
 
           console.log(selectstart);
           selectstart++;
@@ -314,17 +319,26 @@
 
 
         /*
            Selection events mouse move on input type=text
         */
 
         // Select a region
 
-        yield* mouseAction(elt("input"), 50, "mousedown", 0, 1, 1, 0);
+        // Without the dom.select_events.textcontrols.enabled pref,
+        // pressing the mouse shouldn't do anything.
+        yield* mouseAction(elt("input"), 50, "mousedown", 0, 1, 0, 0);
+
+        // Releasing the mouse shouldn't do anything
+        yield* mouseAction(elt("input"), 50, "mouseup", 0, 0, 0, 0);
+
+        yield* UpdateSelectEventsOntextControlsPref(true);
+
+        yield* mouseAction(elt("input"), 50, "mousedown", 0, 0, 0, 0);
 
         selectstartTarget = elt("input");
         yield* mouseAction(elt("input"), 100, "mousemove", 1, 0, 1, 0);
 
         // Moving it more shouldn't trigger a start (move back to empty)
         yield* mouseAction(elt("input"), 75, "mousemove", 0, 0, 1, 0);
         yield* mouseAction(elt("input"), 50, "mousemove", 0, 0, 1, 0);
 
@@ -355,20 +369,31 @@
         // selection remains collapsed
         yield* mouseAction(elt("input"), 75, "mousedown", 0, 0, 1, 0);
         cancel = true;
         selectstartTarget = elt("input");
         yield* mouseAction(elt("input"), 100, "mousemove", 1, 0, 1, 0);
         yield* mouseAction(elt("input"), 100, "mouseup", 0, 0, 0, 0);
 
 
-        // Select a region
+        yield* UpdateSelectEventsOntextControlsPref(false);
+
+        // Without the dom.select_events.textcontrols.enabled pref,
+        // pressing the mouse shouldn't do anything.
         // XXX For some reason we fire 2 selectchange events on the body
         // when switching from the input to the text area.
-        yield* mouseAction(elt("textarea"), 50, "mousedown", 0, 2, 0, 1);
+        yield* mouseAction(elt("textarea"), 50, "mousedown", 0, 2, 0, 0);
+
+        // Releasing the mouse shouldn't do anything
+        yield* mouseAction(elt("textarea"), 50, "mouseup", 0, 0, 0, 0);
+
+        yield* UpdateSelectEventsOntextControlsPref(true);
+
+        // Select a region
+        yield* mouseAction(elt("textarea"), 50, "mousedown", 0, 0, 0, 0);
 
         selectstartTarget = elt("textarea");
         yield* mouseAction(elt("textarea"), 100, "mousemove", 1, 0, 0, 1);
 
         // Moving it more shouldn't trigger a start (move back to empty)
         yield* mouseAction(elt("textarea"), 75, "mousemove", 0, 0, 0, 1);
         yield* mouseAction(elt("textarea"), 50, "mousemove", 0, 0, 0, 1);
 
--- a/layout/generic/nsFrameSelection.h
+++ b/layout/generic/nsFrameSelection.h
@@ -757,11 +757,12 @@ private:
   bool mDragSelectingCells;
   bool mDragState;   //for drag purposes
   bool mMouseDoubleDownState; //has the doubleclick down happened
   bool mDesiredPosSet;
 
   int8_t mCaretMovementStyle;
 
   static bool sSelectionEventsEnabled;
+  static bool sSelectionEventsOnTextControlsEnabled;
 };
 
 #endif /* nsFrameSelection_h___ */
--- a/layout/generic/nsSelection.cpp
+++ b/layout/generic/nsSelection.cpp
@@ -920,16 +920,18 @@ nsFrameSelection::Init(nsIPresShell *aSh
 
   // This should only ever be initialized on the main thread, so we are OK here.
   static bool prefCachesInitialized = false;
   if (!prefCachesInitialized) {
     prefCachesInitialized = true;
 
     Preferences::AddBoolVarCache(&sSelectionEventsEnabled,
                                  "dom.select_events.enabled", false);
+    Preferences::AddBoolVarCache(&sSelectionEventsOnTextControlsEnabled,
+                                 "dom.select_events.textcontrols.enabled", false);
   }
 
   RefPtr<AccessibleCaretEventHub> eventHub = mShell->GetAccessibleCaretEventHub();
   if (eventHub) {
     int8_t index = GetIndexFromSelectionType(SelectionType::eNormal);
     if (mDomSelections[index]) {
       mDomSelections[index]->AddSelectionListener(eventHub);
     }
@@ -944,16 +946,17 @@ nsFrameSelection::Init(nsIPresShell *aSh
       // so we don't have to worry about that!
       RefPtr<SelectionChangeListener> listener = new SelectionChangeListener;
       mDomSelections[index]->AddSelectionListener(listener);
     }
   }
 }
 
 bool nsFrameSelection::sSelectionEventsEnabled = false;
+bool nsFrameSelection::sSelectionEventsOnTextControlsEnabled = false;
 
 nsresult
 nsFrameSelection::MoveCaret(nsDirection       aDirection,
                             bool              aContinueSelection,
                             nsSelectionAmount aAmount,
                             CaretMovementStyle aMovementStyle)
 {
   bool visualMovement = aMovementStyle == eVisual ||
@@ -3855,35 +3858,50 @@ Selection::AddItem(nsRange* aItem, int32
 
       MOZ_ASSERT(!newRangesNonEmpty || nsContentUtils::IsSafeToRunScript());
       if (newRangesNonEmpty && nsContentUtils::IsSafeToRunScript()) {
         // We consider a selection to be starting if we are currently collapsed,
         // and the selection is becoming uncollapsed, and this is caused by a user
         // initiated event.
         bool defaultAction = true;
 
-        // Get the first element which isn't in a native anonymous subtree
+        // The spec currently doesn't say that we should dispatch this event
+        // on text controls, so for now we only support doing that under a
+        // pref, disabled by default.
+        // See https://github.com/w3c/selection-api/issues/53.
+        bool dispatchEvent = true;
         nsCOMPtr<nsINode> target = aItem->GetStartParent();
-        while (target && target->IsInNativeAnonymousSubtree()) {
-          target = target->GetParent();
+        if (nsFrameSelection::sSelectionEventsOnTextControlsEnabled) {
+          // Get the first element which isn't in a native anonymous subtree
+          while (target && target->IsInNativeAnonymousSubtree()) {
+            target = target->GetParent();
+          }
+        } else {
+          if (target->IsInNativeAnonymousSubtree()) {
+            // This is a selection under a text control, so don't dispatch the
+            // event.
+            dispatchEvent = false;
+          }
         }
 
-        nsContentUtils::DispatchTrustedEvent(GetParentObject(), target,
-                                             NS_LITERAL_STRING("selectstart"),
-                                             true, true, &defaultAction);
-
-        if (!defaultAction) {
-          return NS_OK;
-        }
-
-        // As we just dispatched an event to the DOM, something could have
-        // changed under our feet. Re-generate the rangesToAdd array, and ensure
-        // that the range we are about to add is still valid.
-        if (!aItem->IsPositioned()) {
-          return NS_ERROR_UNEXPECTED;
+        if (dispatchEvent) {
+          nsContentUtils::DispatchTrustedEvent(GetParentObject(), target,
+                                               NS_LITERAL_STRING("selectstart"),
+                                               true, true, &defaultAction);
+
+          if (!defaultAction) {
+            return NS_OK;
+          }
+
+          // As we just dispatched an event to the DOM, something could have
+          // changed under our feet. Re-generate the rangesToAdd array, and ensure
+          // that the range we are about to add is still valid.
+          if (!aItem->IsPositioned()) {
+            return NS_ERROR_UNEXPECTED;
+          }
         }
       }
 
       // The scratch ranges we generated may be invalid now, throw them out
       rangesToAdd.ClearAndRetainStorage();
     }
 
     // Generate the ranges to add
@@ -6715,39 +6733,60 @@ SelectionChangeListener::NotifySelection
 
   // If we are hiding changes, then don't do anything else. We do this after we
   // update mOldRanges so that changes after the changes stop being hidden don't
   // incorrectly trigger a change, even though they didn't change anything
   if (sel->IsBlockingSelectionChangeEvents()) {
     return NS_OK;
   }
 
-  nsCOMPtr<nsINode> target;
-
-  // Check if we should be firing this event to a different node than the
-  // document. The limiter of the nsFrameSelection will be within the native
-  // anonymous subtree of the node we want to fire the event on. We need to
-  // climb up the parent chain to escape the native anonymous subtree, and then
-  // fire the event.
-  if (nsFrameSelection* fs = sel->GetFrameSelection()) {
-    if (nsCOMPtr<nsIContent> root = fs->GetLimiter()) {
-      while (root && root->IsInNativeAnonymousSubtree()) {
-        root = root->GetParent();
+  // The spec currently doesn't say that we should dispatch this event on text
+  // controls, so for now we only support doing that under a pref, disabled by
+  // default.
+  // See https://github.com/w3c/selection-api/issues/53.
+  if (nsFrameSelection::sSelectionEventsOnTextControlsEnabled) {
+    nsCOMPtr<nsINode> target;
+
+    // Check if we should be firing this event to a different node than the
+    // document. The limiter of the nsFrameSelection will be within the native
+    // anonymous subtree of the node we want to fire the event on. We need to
+    // climb up the parent chain to escape the native anonymous subtree, and then
+    // fire the event.
+    if (nsFrameSelection* fs = sel->GetFrameSelection()) {
+      if (nsCOMPtr<nsIContent> root = fs->GetLimiter()) {
+        while (root && root->IsInNativeAnonymousSubtree()) {
+          root = root->GetParent();
+        }
+
+        target = root.forget();
       }
-
-      target = root.forget();
+    }
+
+    // If we didn't get a target before, we can instead fire the event at the document.
+    if (!target) {
+      nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc);
+      target = doc.forget();
     }
-  }
-
-  // If we didn't get a target before, we can instead fire the event at the document.
-  if (!target) {
+
+    if (target) {
+      RefPtr<AsyncEventDispatcher> asyncDispatcher =
+        new AsyncEventDispatcher(target, NS_LITERAL_STRING("selectionchange"), false);
+      asyncDispatcher->PostDOMEvent();
+    }
+  } else {
+    if (nsFrameSelection* fs = sel->GetFrameSelection()) {
+      if (nsCOMPtr<nsIContent> root = fs->GetLimiter()) {
+        if (root->IsInNativeAnonymousSubtree()) {
+          return NS_OK;
+        }
+      }
+    }
+
     nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc);
-    target = doc.forget();
-  }
-
-  if (target) {
-    RefPtr<AsyncEventDispatcher> asyncDispatcher =
-      new AsyncEventDispatcher(target, NS_LITERAL_STRING("selectionchange"), false);
-    asyncDispatcher->PostDOMEvent();
+    if (doc) {
+      RefPtr<AsyncEventDispatcher> asyncDispatcher =
+        new AsyncEventDispatcher(doc, NS_LITERAL_STRING("selectionchange"), false);
+      asyncDispatcher->PostDOMEvent();
+    }
   }
 
   return NS_OK;
 }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -137,16 +137,23 @@ pref("dom.manifest.onappinstalled", fals
 
 // Whether or not selection events are enabled
 #ifdef NIGHTLY_BUILD
 pref("dom.select_events.enabled", true);
 #else
 pref("dom.select_events.enabled", false);
 #endif
 
+// Whether or not selection events on text controls are enabled
+#ifdef NIGHTLY_BUILD
+pref("dom.select_events.textcontrols.enabled", true);
+#else
+pref("dom.select_events.textcontrols.enabled", false);
+#endif
+
 // Whether or not Web Workers are enabled.
 pref("dom.workers.enabled", true);
 
 // The number of workers per domain allowed to run concurrently.
 // We're going for effectively infinite, while preventing abuse.
 pref("dom.workers.maxPerDomain", 512);
 
 pref("dom.serviceWorkers.enabled", false);