Bug 1077574 - Add current and total counts to Find-in-page, r=wesj
authorMark Capella <markcapella@twcny.rr.com>
Thu, 16 Oct 2014 12:42:11 -0400
changeset 210713 dc0dec53a37cfceae69dc3e437b6fc3fe281225a
parent 210712 b0843f9cb54107dfe1fdb631ee6f926308c224ba
child 210714 93bf386d290e6151291f35d03c7dca8830e9eafc
push id9401
push usermarkcapella@twcny.rr.com
push dateThu, 16 Oct 2014 16:42:18 +0000
treeherderfx-team@dc0dec53a37c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswesj
bugs1077574
milestone36.0a1
Bug 1077574 - Add current and total counts to Find-in-page, r=wesj
mobile/android/base/FindInPageBar.java
mobile/android/base/resources/layout/find_in_page_content.xml
mobile/android/base/resources/values/colors.xml
mobile/android/base/resources/values/dimens.xml
mobile/android/chrome/content/FindHelper.js
mobile/android/chrome/content/browser.js
--- a/mobile/android/base/FindInPageBar.java
+++ b/mobile/android/base/FindInPageBar.java
@@ -1,35 +1,39 @@
 /* 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/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.util.GeckoEventListener;
+import org.mozilla.gecko.util.GeckoRequest;
+import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 
 public class FindInPageBar extends LinearLayout implements TextWatcher, View.OnClickListener, GeckoEventListener  {
     private static final String REQUEST_ID = "FindInPageBar";
 
     private final Context mContext;
     private CustomEditText mFindText;
+    private TextView mStatusText;
     private boolean mInflated;
 
     public FindInPageBar(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
         setFocusable(true);
     }
 
@@ -53,29 +57,32 @@ public class FindInPageBar extends Linea
                 if (keyCode == KeyEvent.KEYCODE_BACK) {
                     hide();
                     return true;
                 }
                 return false;
             }
         });
 
+        mStatusText = (TextView) content.findViewById(R.id.find_status);
+
         mInflated = true;
         EventDispatcher.getInstance().registerGeckoThreadListener(this, "TextSelection:Data");
     }
 
     public void show() {
         if (!mInflated)
             inflateContent();
 
         setVisibility(VISIBLE);
         mFindText.requestFocus();
 
         // handleMessage() receives response message and determines initial state of softInput
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("TextSelection:Get", REQUEST_ID));
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FindInPage:Opened", null));
     }
 
     public void hide() {
         setVisibility(GONE);
         getInputMethodManager(mFindText).hideSoftInputFromWindow(mFindText.getWindowToken(), 0);
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FindInPage:Closed", null));
     }
 
@@ -90,17 +97,17 @@ public class FindInPageBar extends Linea
         }
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "TextSelection:Data");
     }
 
     // TextWatcher implementation
 
     @Override
     public void afterTextChanged(Editable s) {
-        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FindInPage:Find", s.toString()));
+        sendRequestToFinderHelper("FindInPage:Find", s.toString());
     }
 
     @Override
     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
         // ignore
     }
 
     @Override
@@ -110,23 +117,23 @@ public class FindInPageBar extends Linea
 
     // View.OnClickListener implementation
 
     @Override
     public void onClick(View v) {
         final int viewId = v.getId();
 
         if (viewId == R.id.find_prev) {
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FindInPage:Prev", mFindText.getText().toString()));
+            sendRequestToFinderHelper("FindInPage:Prev", mFindText.getText().toString());
             getInputMethodManager(mFindText).hideSoftInputFromWindow(mFindText.getWindowToken(), 0);
             return;
         }
 
         if (viewId == R.id.find_next) {
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FindInPage:Next", mFindText.getText().toString()));
+            sendRequestToFinderHelper("FindInPage:Next", mFindText.getText().toString());
             getInputMethodManager(mFindText).hideSoftInputFromWindow(mFindText.getWindowToken(), 0);
             return;
         }
 
         if (viewId == R.id.find_close) {
             hide();
         }
     }
@@ -165,9 +172,33 @@ public class FindInPageBar extends Linea
                         return;
 
                     mFindText.setOnWindowFocusChangeListener(null);
                     getInputMethodManager(mFindText).showSoftInput(mFindText, 0);
                }
             });
         }
     }
+
+    /**
+     * Request find operation, and update matchCount results (current count and total).
+     */
+    private void sendRequestToFinderHelper(String request, String searchString) {
+        GeckoAppShell.sendRequestToGecko(new GeckoRequest(request, searchString) {
+            @Override
+            public void onResponse(NativeJSObject nativeJSObject) {
+                final int total = nativeJSObject.optInt("total", 0);
+                final int current = nativeJSObject.optInt("current", 0);
+
+                final Boolean statusVisibility = (total > 0);
+                final String statusText = current + "/" + total;
+
+                ThreadUtils.postToUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mStatusText.setVisibility(statusVisibility ? View.VISIBLE : View.GONE);
+                        mStatusText.setText(statusText);
+                    }
+                });
+            }
+        });
+    }
 }
--- a/mobile/android/base/resources/layout/find_in_page_content.xml
+++ b/mobile/android/base/resources/layout/find_in_page_content.xml
@@ -1,39 +1,49 @@
 <?xml version="1.0" encoding="utf-8"?>
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
 
     <view class="org.mozilla.gecko.CustomEditText"
               android:id="@+id/find_text"
               android:layout_width="0dip"
               android:layout_height="wrap_content"
               android:layout_weight="1.0"
-              android:layout_marginLeft="5dip"
-              android:layout_marginRight="5dip"
+              android:layout_marginLeft="@dimen/find_in_page_text_margin_left"
+              android:layout_marginRight="@dimen/find_in_page_text_margin_right"
               android:contentDescription="@string/find_text"
               android:background="@drawable/url_bar_entry"
               android:singleLine="true"
               android:textColor="#000000"
               android:textCursorDrawable="@null"
               android:inputType="text"
-              android:paddingLeft="15dip"
-              android:paddingRight="15dip"
+              android:paddingLeft="@dimen/find_in_page_text_padding_left"
+              android:paddingRight="@dimen/find_in_page_text_padding_right"
               android:textColorHighlight="@color/url_bar_text_highlight"
               android:imeOptions="actionSearch"
               android:selectAllOnFocus="true"
               android:gravity="center_vertical|left"/>
 
+    <TextView android:id="@+id/find_status"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:layout_marginRight="@dimen/find_in_page_status_margin_right"
+              android:textColor="@color/find_status_default"
+              android:visibility="gone"/>
+
     <ImageButton android:id="@+id/find_prev"
                  style="@style/FindBar.ImageButton"
                  android:contentDescription="@string/find_prev"
+                 android:layout_marginTop="@dimen/find_in_page_control_margin_top"
                  android:src="@drawable/find_prev"/>
 
     <ImageButton android:id="@+id/find_next"
                  style="@style/FindBar.ImageButton"
                  android:contentDescription="@string/find_next"
+                 android:layout_marginTop="@dimen/find_in_page_control_margin_top"
                  android:src="@drawable/find_next"/>
 
     <ImageButton android:id="@+id/find_close"
                  style="@style/FindBar.ImageButton"
                  android:contentDescription="@string/find_close"
+                 android:layout_marginTop="@dimen/find_in_page_control_margin_top"
                  android:src="@drawable/find_close"/>
 
 </merge>
--- a/mobile/android/base/resources/values/colors.xml
+++ b/mobile/android/base/resources/values/colors.xml
@@ -132,9 +132,13 @@
   <color name="remote_tabs_setup_button_background">#E66000</color>
   <color name="remote_tabs_setup_button_background_hit">#D95300</color>
 
   <!-- Button toast colors. -->
   <color name="toast_background">#DD363B40</color>
   <color name="toast_button_background">#00000000</color>
   <color name="toast_button_pressed">#DD2C3136</color>
   <color name="toast_button_text">#FFFFFFFF</color>
+
+  <!-- Colour used for Find-In-Page dialog -->
+  <color name="find_status_default">#AFB1B3</color>
+
 </resources>
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -139,9 +139,18 @@
 
     <!-- Button toast dimenstions. -->
     <dimen name="toast_button_corner_radius">2dp</dimen>
 
     <!-- ArrowPopup dimensions. -->
     <dimen name="arrow_popup_arrow_width">40dip</dimen>
     <dimen name="arrow_popup_arrow_height">12dip</dimen>
     <dimen name="arrow_popup_arrow_offset">8dp</dimen>
+
+    <!-- Find-In-Page dialog dimensions. -->
+    <dimen name="find_in_page_text_margin_left">5dip</dimen>
+    <dimen name="find_in_page_text_margin_right">12dip</dimen>
+    <dimen name="find_in_page_text_padding_left">10dip</dimen>
+    <dimen name="find_in_page_text_padding_right">10dip</dimen>
+    <dimen name="find_in_page_status_margin_right">5dip</dimen>
+    <dimen name="find_in_page_control_margin_top">2dip</dimen>
+
 </resources>
--- a/mobile/android/chrome/content/FindHelper.js
+++ b/mobile/android/chrome/content/FindHelper.js
@@ -3,73 +3,125 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 var FindHelper = {
   _finder: null,
   _targetTab: null,
   _initialViewport: null,
   _viewportChanged: false,
+  _matchesCountResult: null,
 
   observe: function(aMessage, aTopic, aData) {
     switch(aTopic) {
-      case "FindInPage:Find":
-        this.doFind(aData);
+      case "FindInPage:Opened": {
+        this._findOpened();
+        this._init();
         break;
-
-      case "FindInPage:Prev":
-        this.findAgain(aData, true);
-        break;
+      }
 
-      case "FindInPage:Next":
-        this.findAgain(aData, false);
+      case "Tab:Selected": {
+        // Allow for page switching.
+        this._uninit();
         break;
+      }
 
-      case "Tab:Selected":
       case "FindInPage:Closed":
-        this.findClosed();
+        this._uninit();
+        this._findClosed();
         break;
     }
   },
 
+  _findOpened: function() {
+    Messaging.addListener((data) => {
+      this.doFind(data);
+      return this._getMatchesCountResult(data);
+    }, "FindInPage:Find");
+
+    Messaging.addListener((data) => {
+      this.findAgain(data, false);
+      return this._getMatchesCountResult(data);
+    }, "FindInPage:Next");
+
+    Messaging.addListener((data) => {
+      this.findAgain(data, true);
+      return this._getMatchesCountResult(data);
+    }, "FindInPage:Prev");
+  },
+
+  _init: function() {
+    // If there's no find in progress, start one.
+    if (this._finder) {
+      return;
+    }
+
+    this._targetTab = BrowserApp.selectedTab;
+    this._finder = this._targetTab.browser.finder;
+    this._finder.addResultListener(this);
+    this._initialViewport = JSON.stringify(this._targetTab.getViewport());
+    this._viewportChanged = false;
+  },
+
+  _uninit: function() {
+    // If there's no find in progress, there's nothing to clean up.
+    if (!this._finder) {
+      return;
+    }
+
+    this._finder.removeSelection();
+    this._finder.removeResultListener(this);
+    this._finder = null;
+    this._targetTab = null;
+    this._initialViewport = null;
+    this._viewportChanged = false;
+  },
+
+  _findClosed: function() {
+    Messaging.removeListener("FindInPage:Find");
+    Messaging.removeListener("FindInPage:Next");
+    Messaging.removeListener("FindInPage:Prev");
+  },
+
+  /**
+   * Request, wait for, and return the current matchesCount results for a string.
+   */
+  _getMatchesCountResult: function(findString) {
+      // Sync call to Finder, results available immediately.
+      this._matchesCountResult = null;
+      this._finder.requestMatchesCount(findString);
+
+      return this._matchesCountResult;
+  },
+
+  /**
+   * Pass along the count results to FindInPageBar for display.
+   */
+  onMatchesCountResult: function(result) {
+    this._matchesCountResult = result;
+  },
+
   doFind: function(aSearchString) {
     if (!this._finder) {
-      this._targetTab = BrowserApp.selectedTab;
-      this._finder = this._targetTab.browser.finder;
-      this._finder.addResultListener(this);
-      this._initialViewport = JSON.stringify(this._targetTab.getViewport());
-      this._viewportChanged = false;
+      this._init();
     }
 
     this._finder.fastFind(aSearchString, false);
   },
 
   findAgain: function(aString, aFindBackwards) {
     // This can happen if the user taps next/previous after re-opening the search bar
     if (!this._finder) {
       this.doFind(aString);
       return;
     }
 
     this._finder.findAgain(aFindBackwards, false, false);
   },
 
-  findClosed: function() {
-    // If there's no find in progress, there's nothing to clean up
-    if (!this._finder)
-      return;
-
-    this._finder.removeSelection();
-    this._finder.removeResultListener(this);
-    this._finder = null;
-    this._targetTab = null;
-    this._initialViewport = null;
-    this._viewportChanged = false;
-  },
-
   onFindResult: function(aData) {
     if (aData.result == Ci.nsITypeAheadFind.FIND_NOTFOUND) {
       if (this._viewportChanged) {
         if (this._targetTab != BrowserApp.selectedTab) {
           // this should never happen
           Cu.reportError("Warning: selected tab changed during find!");
           // fall through and restore viewport on the initial tab anyway
         }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -122,17 +122,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 });
 
 [
 #ifdef MOZ_WEBRTC
   ["WebrtcUI", ["getUserMedia:request", "recording-device-events"], "chrome://browser/content/WebrtcUI.js"],
 #endif
   ["MemoryObserver", ["memory-pressure", "Memory:Dump"], "chrome://browser/content/MemoryObserver.js"],
   ["ConsoleAPI", ["console-api-log-event"], "chrome://browser/content/ConsoleAPI.js"],
-  ["FindHelper", ["FindInPage:Find", "FindInPage:Prev", "FindInPage:Next", "FindInPage:Closed", "Tab:Selected"], "chrome://browser/content/FindHelper.js"],
+  ["FindHelper", ["FindInPage:Opened", "FindInPage:Closed", "Tab:Selected"], "chrome://browser/content/FindHelper.js"],
   ["PermissionsHelper", ["Permissions:Get", "Permissions:Clear"], "chrome://browser/content/PermissionsHelper.js"],
   ["FeedHandler", ["Feeds:Subscribe"], "chrome://browser/content/FeedHandler.js"],
   ["Feedback", ["Feedback:Show"], "chrome://browser/content/Feedback.js"],
   ["SelectionHandler", ["TextSelection:Get"], "chrome://browser/content/SelectionHandler.js"],
   ["Notifications", ["Notification:Event"], "chrome://browser/content/Notifications.jsm"],
   ["EmbedRT", ["GeckoView:ImportScript"], "chrome://browser/content/EmbedRT.js"],
 ].forEach(function (aScript) {
   let [name, notifications, script] = aScript;