Bug 994664 - Improve SelectionHandler UI performance, r=wesj
authorMark Capella <markcapella@twcny.rr.com>
Sat, 26 Apr 2014 00:49:50 -0400
changeset 198764 cf95a5315f6f80299e0e7bb571ec0fe1b4a84493
parent 198763 52cb8e937d121932c294a700e527b60a81dce37f
child 198765 ed6d2e76df4403ec5238a7eb19240bc0ab819bc2
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswesj
bugs994664
milestone31.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 994664 - Improve SelectionHandler UI performance, r=wesj
mobile/android/base/TextSelection.java
mobile/android/chrome/content/SelectionHandler.js
--- a/mobile/android/base/TextSelection.java
+++ b/mobile/android/base/TextSelection.java
@@ -118,20 +118,16 @@ class TextSelection extends Layer implem
                         mViewLeft = 0.0f;
                         mViewTop = 0.0f;
                         mViewZoom = 0.0f;
                         LayerView layerView = GeckoAppShell.getLayerView();
                         if (layerView != null) {
                             layerView.addLayer(TextSelection.this);
                         }
 
-                        if (mActionModeTimerTask != null)
-                            mActionModeTimerTask.cancel();
-                        showActionMode(message.getJSONArray("actions"));
-
                         if (handles.length() > 1)
                             GeckoAppShell.performHapticFeedback(true);
                     } else if (event.equals("TextSelection:Update")) {
                         if (mActionModeTimerTask != null)
                             mActionModeTimerTask.cancel();
                         showActionMode(message.getJSONArray("actions"));
                     } else if (event.equals("TextSelection:HideHandles")) {
                         LayerView layerView = GeckoAppShell.getLayerView();
--- a/mobile/android/chrome/content/SelectionHandler.js
+++ b/mobile/android/chrome/content/SelectionHandler.js
@@ -18,16 +18,19 @@ var SelectionHandler = {
 
   // Keeps track of data about the dimensions of the selection. Coordinates
   // stored here are relative to the _contentWindow window.
   _cache: null,
   _activeType: 0, // TYPE_NONE
   _draggingHandles: false, // True while user drags text selection handles
   _ignoreCompositionChanges: false, // Persist caret during IME composition updates
 
+  // TargetElement changes (text <--> no text) trigger actionbar UI update
+  _prevTargetElementHasText: null,
+
   // The window that holds the selection (can be a sub-frame)
   get _contentWindow() {
     if (this._contentWindowRef)
       return this._contentWindowRef.get();
     return null;
   },
 
   set _contentWindow(aContentWindow) {
@@ -155,16 +158,18 @@ var SelectionHandler = {
           } catch (e) {
             // User finished handle positioning with one end off the screen
             this._closeSelection();
             break;
           }
 
           this._stopDraggingHandles();
           this._positionHandles();
+          // Changes to handle position can affect selection context and actionbar display
+          this._updateMenu();
 
         } else if (this._activeType == this.TYPE_CURSOR) {
           // Act on IMM composition notifications after caret movement ends
           this._ignoreCompositionChanges = false;
           this._positionHandles();
 
         } else {
           Cu.reportError("Ignored \"TextSelection:Position\" message during invalid selection status");
@@ -349,18 +354,23 @@ var SelectionHandler = {
 
     if (aOptions.mode == this.SELECT_AT_POINT && !this._selectionNearClick(scroll.X + aOptions.x,
                                                                       scroll.Y + aOptions.y,
                                                                       positions)) {
         this._closeSelection();
         return false;
     }
 
+    // Determine position and show handles, open actionbar
     this._positionHandles(positions);
-    this._sendMessage("TextSelection:ShowHandles", [this.HANDLE_TYPE_START, this.HANDLE_TYPE_END]);
+    sendMessageToJava({
+      type: "TextSelection:ShowHandles",
+      handles: [this.HANDLE_TYPE_START, this.HANDLE_TYPE_END]
+    });
+    this._updateMenu();
     return true;
   },
 
   /*
    * Called to perform a selection operation, given a target element, selection method, starting point etc.
    */
   _performSelection: function sh_performSelection(aOptions) {
     if (aOptions.mode == this.SELECT_AT_POINT) {
@@ -428,17 +438,41 @@ var SelectionHandler = {
       return defaultValue;
 
     if (typeof obj[name] == "function")
       return obj[name](this._targetElement);
 
     return obj[name];
   },
 
-  _sendMessage: function(msgType, handles) {
+  addAction: function(action) {
+    if (!action.id)
+      action.id = uuidgen.generateUUID().toString()
+
+    if (this.actions[action.id])
+      throw "Action with id " + action.id + " already added";
+
+    // Update actions list and actionbar UI if active.
+    this.actions[action.id] = action;
+    this._updateMenu();
+    return action.id;
+  },
+
+  removeAction: function(id) {
+    // Update actions list and actionbar UI if active.
+    delete this.actions[id];
+    this._updateMenu();
+  },
+
+  _updateMenu: function() {
+    if (this._activeType == this.TYPE_NONE) {
+      return;
+    }
+
+    // Update actionbar UI.
     let actions = [];
     for (let type in this.actions) {
       let action = this.actions[type];
       if (action.selector.matches(this._targetElement)) {
         let a = {
           id: action.id,
           label: this._getValue(action, "label", ""),
           icon: this._getValue(action, "icon", "drawable://ic_status_logo"),
@@ -447,41 +481,21 @@ var SelectionHandler = {
         };
         actions.push(a);
       }
     }
 
     actions.sort((a, b) => b.order - a.order);
 
     sendMessageToJava({
-      type: msgType,
-      handles: handles,
-      actions: actions,
+      type: "TextSelection:Update",
+      actions: actions
     });
   },
 
-  _updateMenu: function() {
-    this._sendMessage("TextSelection:Update");
-  },
-
-  addAction: function(action) {
-    if (!action.id)
-      action.id = uuidgen.generateUUID().toString()
-
-    if (this.actions[action.id])
-      throw "Action with id " + action.id + " already added";
-
-    this.actions[action.id] = action;
-    return action.id;
-  },
-
-  removeAction: function(id) {
-    delete this.actions[id];
-  },
-
   /*
    * Actionbar methods.
    */
   actions: {
     SELECT_ALL: {
       label: Strings.browser.GetStringFromName("contextmenu.selectAll"),
       id: "selectall_action",
       icon: "drawable://ab_select_all",
@@ -627,19 +641,24 @@ var SelectionHandler = {
 
     // Caret-specific observer/listeners
     Services.obs.addObserver(this, "TextSelection:UpdateCaretPos", false);
     BrowserApp.deck.addEventListener("keyup", this, false);
     BrowserApp.deck.addEventListener("compositionupdate", this, false);
     BrowserApp.deck.addEventListener("compositionend", this, false);
 
     this._activeType = this.TYPE_CURSOR;
+
+    // Determine position and show caret, open actionbar
     this._positionHandles();
-
-    this._sendMessage("TextSelection:ShowHandles", [this.HANDLE_TYPE_MIDDLE]);
+    sendMessageToJava({
+      type: "TextSelection:ShowHandles",
+      handles: [this.HANDLE_TYPE_MIDDLE]
+    });
+    this._updateMenu();
   },
 
   // Target initialization for both TYPE_CURSOR and TYPE_SELECTION
   _initTargetInfo: function sh_initTargetInfo(aElement, aSelectionType) {
     this._targetElement = aElement;
     if (aElement instanceof Ci.nsIDOMNSEditableElement) {
       if (aSelectionType === this.TYPE_SELECTION) {
         // Blur the targetElement to force IME code to undo previous style compositions
@@ -900,16 +919,17 @@ var SelectionHandler = {
       if (selection.rangeCount != 0) {
         selection.collapseToStart();
       }
     }
   },
 
   _deactivate: function sh_deactivate() {
     this._stopDraggingHandles();
+    // Hide handle/caret, close actionbar
     sendMessageToJava({ type: "TextSelection:HideHandles" });
 
     this._removeObservers();
 
     // Only observed for caret positioning
     if (this._activeType == this.TYPE_CURSOR) {
       Services.obs.removeObserver(this, "TextSelection:UpdateCaretPos");
       BrowserApp.deck.removeEventListener("keyup", this);
@@ -917,16 +937,17 @@ var SelectionHandler = {
       BrowserApp.deck.removeEventListener("compositionend", this);
     }
 
     this._contentWindow = null;
     this._targetElement = null;
     this._isRTL = false;
     this._cache = null;
     this._ignoreCompositionChanges = false;
+    this._prevTargetElementHasText = null;
 
     this._activeType = this.TYPE_NONE;
   },
 
   _getViewOffset: function sh_getViewOffset() {
     let offset = { x: 0, y: 0 };
     let win = this._contentWindow;
 
@@ -1032,17 +1053,23 @@ var SelectionHandler = {
     if (!positions) {
       positions = this._getHandlePositions(this._getScrollPos());
     }
     sendMessageToJava({
       type: "TextSelection:PositionHandles",
       positions: positions,
       rtl: this._isRTL
     });
-    this._updateMenu();
+
+    // Text state transitions (text <--> no text) will affect selection context and actionbar display
+    let currTargetElementHasText = (this._targetElement.textLength > 0);
+    if (currTargetElementHasText != this._prevTargetElementHasText) {
+      this._prevTargetElementHasText = currTargetElementHasText;
+      this._updateMenu();
+    }
   },
 
   subdocumentScrolled: function sh_subdocumentScrolled(aElement) {
     if (this._activeType == this.TYPE_NONE) {
       return;
     }
     let scrollView = aElement.ownerDocument.defaultView;
     let view = this._contentWindow;