Bug 1215959 - (GeckoCaret2) Upgrade mobile-side for new AccessibleCaret front-end, r=snorp
authorMark Capella <markcapella@twcny.rr.com>
Tue, 01 Dec 2015 15:25:06 -0500
changeset 309160 2d4cfc1c9e97ddab80895ef4e3f5570baea65520
parent 309159 f6e63dd4fd9c31388e25c64f1c4e11ccdd9cbe88
child 309161 716f4df6fad5bc5b6fcbddd4385676467c97bb6c
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1215959
milestone45.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 1215959 - (GeckoCaret2) Upgrade mobile-side for new AccessibleCaret front-end, r=snorp
mobile/android/chrome/content/ActionBarHandler.js
mobile/android/chrome/content/SelectionHandler.js
mobile/android/chrome/content/browser.js
--- a/mobile/android/chrome/content/ActionBarHandler.js
+++ b/mobile/android/chrome/content/ActionBarHandler.js
@@ -1,69 +1,74 @@
 // -*- indent-tabs-mode: nil; js-indent-level: 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/. */
 "use strict";
 
-// Notifications we observe.
-const NOTIFICATIONS = [
-    "ActionBar:UpdateState",
-    "TextSelection:Action",
-    "TextSelection:End",
-];
-
-const DEFER_INIT_DELAY_MS = 50; // Delay period before _init() begins.
 const PHONE_REGEX = /^\+?[0-9\s,-.\(\)*#pw]{1,30}$/; // Are we a phone #?
 
 
 /**
  * ActionBarHandler Object and methods. Interface between Gecko Text Selection code
  * (TouchCaret, SelectionCarets, etc) and the Mobile ActionBar UI.
  */
 var ActionBarHandler = {
   // Error codes returned from _init().
   START_TOUCH_ERROR: {
     NO_CONTENT_WINDOW: "No valid content Window found.",
     NONE: "",
   },
 
+  _nextSelectionID: 1, // Next available.
   _selectionID: null, // Unique Selection ID, assigned each time we _init().
   _actionBarActions: null, // Most-recent set of actions sent to ActionBar.
 
   /**
+   * Receive and act on AccessibleCarets caret state-change
+   * (mozcaretstatechanged) events.
+   */
+  caretStateChangedHandler: function(e) {
+    // Close an open ActionBar, if carets no longer logically visible.
+    if (this._selectionID && !e.caretVisible) {
+      this._uninit(false);
+      return;
+    }
+
+    // Open a closed ActionBar if carets actually visible.
+    if (!this._selectionID && e.caretVisuallyVisible) {
+      this._init();
+      return;
+    }
+
+    // Else, update an open ActionBar.
+    if (this._selectionID) {
+      if ([this._targetElement, this._contentWindow] ===
+          [Services.focus.focusedElement, Services.focus.focusedWindow]) {
+        // We have the same focused window/element as before. Trigger "TextSelection:ActionbarStatus"
+        // message only if available actions differ from when last we checked.
+        this._sendActionBarActions();
+      } else {
+        // We have a new focused window/element pair.
+        this._uninit(false);
+        this._init();
+      }
+    }
+  },
+
+  /**
    * ActionBarHandler notification observers.
    */
   observe: function(subject, topic, data) {
     switch (topic) {
-
-      // Gecko opens the ActionBarHandler.
-      case "ActionBar:OpenNew": {
-        // Always close, then re-open.
-        this._uninit(false);
-        this._init(data);
-        break;
-      }
-
-      // Gecko closes the ActionBarHandler.
-      case "ActionBar:Close": {
-        if (this._selectionID === data) {
-          this._uninit(false);
-        }
-        break;
-      }
-
-      // Update ActionBar when text selection changes.
-      case "ActionBar:UpdateState": {
-        this._sendActionBarActions();
-        break;
-      }
-
       // User click an ActionBar button.
       case "TextSelection:Action": {
+        if (!this._selectionID) {
+          break;
+        }
         for (let type in this.actions) {
           let action = this.actions[type];
           if (action.id == data) {
             action.action(this._targetElement, this._contentWindow);
             break;
           }
         }
         break;
@@ -71,49 +76,46 @@ var ActionBarHandler = {
 
       // Provide selected text to FindInPageBar on request.
       case "TextSelection:Get": {
         Messaging.sendRequest({
           type: "TextSelection:Data",
           requestId: data,
           text: this._getSelectedText(),
         });
+
+        this._uninit();
         break;
       }
 
       // User closed ActionBar by clicking "checkmark" button.
       case "TextSelection:End": {
         // End the requested selection only.
-        if (this._selectionID === JSON.parse(data).selectionID) {
+        if (this._selectionID == JSON.parse(data).selectionID) {
           this._uninit();
         }
         break;
       }
     }
   },
 
   /**
    * Called when Gecko TouchCaret or SelectionCarets become visible.
    */
-  _init: function(actionBarID) {
+  _init: function() {
     let [element, win] = this._getSelectionTargets();
     if (!win) {
       return this.START_TOUCH_ERROR.NO_CONTENT_WINDOW;
     }
 
     // Hold the ActionBar ID provided by Gecko.
-    this._selectionID = actionBarID;
+    this._selectionID = this._nextSelectionID++;
     [this._targetElement, this._contentWindow] = [element, win];
 
-    // Add notification observers.
-    NOTIFICATIONS.forEach(notification => {
-      Services.obs.addObserver(this, notification, false);
-    });
-
-    // Open the ActionBar, send it's initial actions list.
+    // Open the ActionBar, send it's actions list.
     Messaging.sendRequest({
       type: "TextSelection:ActionbarInit",
       selectionID: this._selectionID,
     });
     this._sendActionBarActions(true);
 
     return this.START_TOUCH_ERROR.NONE;
   },
@@ -146,21 +148,16 @@ var ActionBarHandler = {
    * methods such as SELECT_ALL, PASTE, etc.
    */
   _uninit: function(clearSelection = true) {
     // Bail if there's no active selection.
     if (!this._selectionID) {
       return;
     }
 
-    // Remove notification observers.
-    NOTIFICATIONS.forEach(notification => {
-      Services.obs.removeObserver(this, notification);
-    });
-
     // Close the ActionBar.
     Messaging.sendRequest({
       type: "TextSelection:ActionbarUninit",
     });
 
     // Clear the selection ID to complete the uninit(), but leave our reference
     // to selectionTargets (_targetElement, _contentWindow) in case we need
     // a final clearSelection().
@@ -286,19 +283,16 @@ var ActionBarHandler = {
           if (imeSupport.composing) {
             element.blur();
             element.focus();
           }
         }
 
         // Close ActionBarHandler, then selectAll, and display handles.
         ActionBarHandler._getSelectAllController(element, win).selectAll();
-        ActionBarHandler._getSelectionController(element, win).
-          selectionCaretsVisibility = true;
-
         UITelemetry.addEvent("action.1", "actionbar", null, "select_all");
       },
     },
 
     CUT: {
       id: "cut_action",
       label: Strings.browser.GetStringFromName("contextmenu.cut"),
       icon: "drawable://ab_cut",
--- a/mobile/android/chrome/content/SelectionHandler.js
+++ b/mobile/android/chrome/content/SelectionHandler.js
@@ -3,19 +3,18 @@
  * 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/. */
 "use strict";
 
 // Define elements that bound phone number containers.
 const PHONE_NUMBER_CONTAINERS = "td,div";
 const DEFER_CLOSE_TRIGGER_MS = 125; // Grace period delay before deferred _closeSelection()
 
-// Gecko TouchCaret/SelectionCaret pref names.
-const PREF_GECKO_TOUCHCARET_ENABLED = "touchcaret.enabled";
-const PREF_GECKO_SELECTIONCARETS_ENABLED = "selectioncaret.enabled";
+// Gecko AccessibleCaret pref names.
+const PREF_GECKO_ACCESSIBLECARET_ENABLED = "layout.accessiblecaret.enabled";
 
 var SelectionHandler = {
 
   // Successful startSelection() or attachCaret().
   ERROR_NONE: "",
 
   // Error codes returned during startSelection().
   START_ERROR_INVALID_MODE: "Invalid selection mode requested.",
@@ -38,17 +37,17 @@ var SelectionHandler = {
   TYPE_NONE: 0,
   TYPE_CURSOR: 1,
   TYPE_SELECTION: 2,
 
   SELECT_ALL: 0,
   SELECT_AT_POINT: 1,
 
   // Gecko TouchCaret/SelectionCaret pref values.
-  _touchCaretEnabledValue: null,
+  _accessibleCaretEnabledValue: null,
   _selectionCaretEnabledValue: null,
 
   // Keeps track of data about the dimensions of the selection. Coordinates
   // stored here are relative to the _contentWindow window.
   _cache: { anchorPt: {}, focusPt: {} },
   _targetIsRTL: false,
   _anchorIsRTL: false,
   _focusIsRTL: false,
@@ -96,38 +95,28 @@ var SelectionHandler = {
 
   // Provides UUID service for selection ID's.
   get _idService() {
     delete this._idService;
     return this._idService = Cc["@mozilla.org/uuid-generator;1"].
       getService(Ci.nsIUUIDGenerator);
   },
 
-  // Are we supporting Gecko or Native touchCarets?
-  get _touchCaretEnabled() {
-    if (this._touchCaretEnabledValue == null) {
-      this._touchCaretEnabledValue = Services.prefs.getBoolPref(PREF_GECKO_TOUCHCARET_ENABLED);
-      Services.prefs.addObserver(PREF_GECKO_TOUCHCARET_ENABLED, function() {
-        SelectionHandler._touchCaretEnabledValue =
-          Services.prefs.getBoolPref(PREF_GECKO_TOUCHCARET_ENABLED);
+  // Are we supporting Accessible-core or native-Java carets?
+  get _accessibleCaretEnabled() {
+    if (this._accessibleCaretEnabledValue == null) {
+      try {
+        this._accessibleCaretEnabledValue = Services.prefs.getBoolPref(PREF_GECKO_ACCESSIBLECARET_ENABLED);
+      } catch (unused) { }
+      Services.prefs.addObserver(PREF_GECKO_ACCESSIBLECARET_ENABLED, function() {
+        SelectionHandler._accessibleCaretEnabledValue =
+          Services.prefs.getBoolPref(PREF_GECKO_ACCESSIBLECARET_ENABLED);
       }, false);
     }
-    return this._touchCaretEnabledValue;
-  },
-
-  // Are we supporting Gecko or Native selectionCarets?
-  get _selectionCaretEnabled() {
-    if (this._selectionCaretEnabledValue == null) {
-      this._selectionCaretEnabledValue = Services.prefs.getBoolPref(PREF_GECKO_SELECTIONCARETS_ENABLED);
-      Services.prefs.addObserver(PREF_GECKO_SELECTIONCARETS_ENABLED, function() {
-        SelectionHandler._selectionCaretEnabledValue =
-          Services.prefs.getBoolPref(PREF_GECKO_SELECTIONCARETS_ENABLED);
-      }, false);
-    }
-    return this._selectionCaretEnabledValue;
+    return this._accessibleCaretEnabledValue;
   },
 
   _addObservers: function sh_addObservers() {
     Services.obs.addObserver(this, "Gesture:SingleTap", false);
     Services.obs.addObserver(this, "Tab:Selected", false);
     Services.obs.addObserver(this, "TextSelection:Move", false);
     Services.obs.addObserver(this, "TextSelection:Position", false);
     Services.obs.addObserver(this, "TextSelection:End", false);
@@ -407,18 +396,18 @@ var SelectionHandler = {
    * @param aOptions list of options describing how to start selection
    *                 Options include:
    *                   mode - SELECT_ALL to select everything in the target
    *                          element, or SELECT_AT_POINT to select a word.
    *                   x    - The x-coordinate for SELECT_AT_POINT.
    *                   y    - The y-coordinate for SELECT_AT_POINT.
    */
   startSelection: function sh_startSelection(aElement, aOptions = { mode: SelectionHandler.SELECT_ALL }) {
-    // Disable Native touchCarets if Gecko enabled.
-    if (this._selectionCaretEnabled) {
+    // Disable Native touchCarets if Gecko AccessibleCaret enabled.
+    if (this._accessibleCaretEnabled) {
       return this.START_ERROR_SELECTIONCARETS_ENABLED;
     }
 
     // Clear out any existing active selection
     this._closeSelection();
 
     if (this._isNonTextInputElement(aElement)) {
       return this.START_ERROR_NONTEXT_INPUT;
@@ -871,18 +860,18 @@ var SelectionHandler = {
 
   /*
    * Called by BrowserEventHandler when the user taps in a form input.
    * Initializes SelectionHandler and positions the caret handle.
    *
    * @param aX, aY tap location in client coordinates.
    */
   attachCaret: function sh_attachCaret(aElement) {
-    // Disable Native attachCaret() if Gecko touchCarets are enabled.
-    if (this._touchCaretEnabled) {
+    // Disable Native touchCarets if Gecko AccessibleCaret enabled.
+    if (this._accessibleCaretEnabled) {
       return this.ATTACH_ERROR_TOUCHCARET_ENABLED;
     }
 
     // Clear out any existing active selection
     this._closeSelection();
 
     // Ensure it isn't disabled, isn't handled by Android native dialog, and is editable text element
     if (aElement.disabled || InputWidgetHelper.hasInputWidget(aElement) || !this.isElementEditableText(aElement)) {
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -154,17 +154,17 @@ var lazilyLoadedObserverScripts = [
   ["Feedback", ["Feedback:Show"], "chrome://browser/content/Feedback.js"],
   ["SelectionHandler", ["TextSelection:Get"], "chrome://browser/content/SelectionHandler.js"],
   ["EmbedRT", ["GeckoView:ImportScript"], "chrome://browser/content/EmbedRT.js"],
   ["Reader", ["Reader:FetchContent", "Reader:Added", "Reader:Removed"], "chrome://browser/content/Reader.js"],
   ["PrintHelper", ["Print:PDF"], "chrome://browser/content/PrintHelper.js"],
 ];
 if (AppConstants.NIGHTLY_BUILD) {
   lazilyLoadedObserverScripts.push(
-    ["ActionBarHandler", ["ActionBar:OpenNew", "ActionBar:Close", "TextSelection:Get"],
+    ["ActionBarHandler", ["TextSelection:Get", "TextSelection:Action", "TextSelection:End"],
       "chrome://browser/content/ActionBarHandler.js"]
   );
 }
 if (AppConstants.MOZ_WEBRTC) {
   lazilyLoadedObserverScripts.push(
     ["WebrtcUI", ["getUserMedia:request",
                   "PeerConnection:request",
                   "recording-device-events"], "chrome://browser/content/WebrtcUI.js"])
@@ -610,16 +610,23 @@ var BrowserApp = {
         // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
         InitLater(() => SafeBrowsing.init(), window, "SafeBrowsing");
       }
 
       InitLater(() => Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager));
       InitLater(() => LoginManagerParent.init(), window, "LoginManagerParent");
 
     }, false);
+
+    // Pass caret StateChanged events to ActionBarHandler.
+    if (AppConstants.NIGHTLY_BUILD) {
+      window.addEventListener("mozcaretstatechanged", e => {
+        ActionBarHandler.caretStateChangedHandler(e);
+      }, /* useCapture = */ true, /* wantsUntrusted = */ false);
+    }
   },
 
   get _startupStatus() {
     delete this._startupStatus;
 
     let savedMilestone = null;
     try {
       savedMilestone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone");