Merge m-c to inbound
authorWes Kocher <wkocher@mozilla.com>
Tue, 10 Dec 2013 17:13:37 -0800
changeset 159814 5dc2bc88108d38dc3baaa0782c0978cab8a6d089
parent 159813 28672c5a8c37fe8cf70a16859a60c025897ad7c2 (current diff)
parent 159792 3ea3d3baa67bb33c82e46e0b37af61e5286d23f4 (diff)
child 159815 924d56cc2c07d4cfdf8938b2727c0c2ec2a655f3
push idunknown
push userunknown
push dateunknown
milestone29.0a1
Merge m-c to inbound
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -468,8 +468,16 @@ SettingsListener.observe("debug.fps.enab
   Services.prefs.setBoolPref("layers.acceleration.draw-fps", value);
 });
 SettingsListener.observe("debug.paint-flashing.enabled", false, function(value) {
   Services.prefs.setBoolPref("nglayout.debug.paint_flashing", value);
 });
 SettingsListener.observe("layers.draw-borders", false, function(value) {
   Services.prefs.setBoolPref("layers.draw-borders", value);
 });
+
+// ================ Accessibility ============
+SettingsListener.observe("accessibility.screenreader", false, function(value) {
+  if (value && !("AccessFu" in this)) {
+    Cu.import('resource://gre/modules/accessibility/AccessFu.jsm');
+    AccessFu.attach(window);
+  }
+});
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -7,17 +7,16 @@
 Cu.import('resource://gre/modules/ContactService.jsm');
 Cu.import('resource://gre/modules/SettingsChangeNotifier.jsm');
 Cu.import('resource://gre/modules/DataStoreChangeNotifier.jsm');
 Cu.import('resource://gre/modules/AlarmService.jsm');
 Cu.import('resource://gre/modules/ActivitiesService.jsm');
 Cu.import('resource://gre/modules/PermissionPromptHelper.jsm');
 Cu.import('resource://gre/modules/ObjectWrapper.jsm');
 Cu.import('resource://gre/modules/NotificationDB.jsm');
-Cu.import('resource://gre/modules/accessibility/AccessFu.jsm');
 Cu.import('resource://gre/modules/Payment.jsm');
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import('resource://gre/modules/UserAgentOverrides.jsm');
 Cu.import('resource://gre/modules/Keyboard.jsm');
 Cu.import('resource://gre/modules/ErrorPage.jsm');
 #ifdef MOZ_WIDGET_GONK
 Cu.import('resource://gre/modules/NetworkStatsService.jsm');
 #endif
@@ -305,17 +304,16 @@ var shell = {
     window.addEventListener('MozApplicationManifest', this);
     window.addEventListener('mozfullscreenchange', this);
     window.addEventListener('MozAfterPaint', this);
     window.addEventListener('sizemodechange', this);
     this.contentBrowser.addEventListener('mozbrowserloadstart', this, true);
 
     CustomEventManager.init();
     WebappsHelper.init();
-    AccessFu.attach(window);
     UserAgentOverrides.init();
     IndexedDBPromptHelper.init();
     CaptivePortalLoginHelper.init();
 
     this.contentBrowser.src = homeURL;
     this.isHomeLoaded = false;
 
     ppmm.addMessageListener("content-handler", this);
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "9f1117fde1d221d998c065a87e0614af6239b585", 
+    "revision": "2e4d09abb604dab914f1f29001012d872b57ef9e", 
     "repo_path": "/integration/gaia-central"
 }
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -74,16 +74,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/PlacesBackups.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
                                   "resource:///modules/sessionstore/SessionStore.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
+                                  "resource:///modules/BrowserUITelemetry.jsm");
+
 const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
 const PREF_PLUGINS_UPDATEURL  = "plugins.update.url";
 
 // We try to backup bookmarks at idle times, to avoid doing that at shutdown.
 // Number of idle seconds before trying to backup bookmarks.  10 minutes.
 const BOOKMARKS_BACKUP_IDLE_TIME = 10 * 60;
 // Minimum interval in milliseconds between backups.
 const BOOKMARKS_BACKUP_INTERVAL = 86400 * 1000;
@@ -471,16 +474,17 @@ BrowserGlue.prototype = {
     SignInToWebsiteUX.init();
     PdfJs.init();
 #ifdef NIGHTLY_BUILD
     ShumwayUtils.init();
 #endif
     webrtcUI.init();
     AboutHome.init();
     SessionStore.init();
+    BrowserUITelemetry.init();
 
     if (Services.prefs.getBoolPref("browser.tabs.remote"))
       ContentClick.init();
 
     Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
   },
 
   _checkForOldBuildUpdates: function () {
new file mode 100644
--- /dev/null
+++ b/browser/modules/BrowserUITelemetry.jsm
@@ -0,0 +1,161 @@
+// 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";
+
+this.EXPORTED_SYMBOLS = ["BrowserUITelemetry"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
+  "resource://gre/modules/UITelemetry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+  "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+  "resource:///modules/CustomizableUI.jsm");
+
+const ALL_BUILTIN_ITEMS = [
+  "fullscreen-button",
+  "switch-to-metro-button",
+];
+
+this.BrowserUITelemetry = {
+  init: function() {
+    UITelemetry.addSimpleMeasureFunction("toolbars",
+                                         this.getToolbarMeasures.bind(this));
+    Services.obs.addObserver(this, "browser-delayed-startup-finished", false);
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == "browser-delayed-startup-finished") {
+      this._registerWindow(aSubject);
+    }
+  },
+
+  /**
+   * For the _countableEvents object, constructs a chain of
+   * Javascript Objects with the keys in aKeys, with the final
+   * key getting the value in aEndWith. If the final key already
+   * exists in the final object, its value is not set. In either
+   * case, a reference to the second last object in the chain is
+   * returned.
+   *
+   * Example - suppose I want to store:
+   * _countableEvents: {
+   *   a: {
+   *     b: {
+   *       c: 0
+   *     }
+   *   }
+   * }
+   *
+   * And then increment the "c" value by 1, you could call this
+   * function like this:
+   *
+   * let example = this._ensureObjectChain([a, b, c], 0);
+   * example["c"]++;
+   *
+   * Subsequent repetitions of these last two lines would
+   * simply result in the c value being incremented again
+   * and again.
+   *
+   * @param aKeys the Array of keys to chain Objects together with.
+   * @param aEndWith the value to assign to the last key.
+   * @returns a reference to the second last object in the chain -
+   *          so in our example, that'd be "b".
+   */
+  _ensureObjectChain: function(aKeys, aEndWith) {
+    let current = this._countableEvents;
+    let parent = null;
+    for (let [i, key] of Iterator(aKeys)) {
+      if (!(key in current)) {
+        if (i == aKeys.length - 1) {
+          current[key] = aEndWith;
+        } else {
+          current[key] = {};
+        }
+      }
+      parent = current;
+      current = current[key];
+    }
+    return parent;
+  },
+
+  _countableEvents: {},
+  _countMouseUpEvent: function(aCategory, aAction, aMouseUpEvent) {
+    const BUTTONS = ["left", "middle", "right"];
+    let buttonKey = BUTTONS[aMouseUpEvent.button];
+    if (buttonKey) {
+      let countObject =
+        this._ensureObjectChain([aCategory, aAction, buttonKey], 0);
+      countObject[buttonKey]++;
+    }
+  },
+
+  _registerWindow: function(aWindow) {
+    aWindow.addEventListener("unload", this);
+    let document = aWindow.document;
+
+    for (let areaID of CustomizableUI.areas) {
+      let areaNode = document.getElementById(areaID);
+      (areaNode.customizationTarget || areaNode).addEventListener("mouseup", this);
+    }
+  },
+
+  _unregisterWindow: function(aWindow) {
+    aWindow.removeEventListener("unload", this);
+    let document = aWindow.document;
+
+    for (let areaID of CustomizableUI.areas) {
+      let areaNode = document.getElementById(areaID);
+      (areaNode.customizationTarget || areaNode).removeEventListener("mouseup", this);
+    }
+  },
+
+  handleEvent: function(aEvent) {
+    switch(aEvent.type) {
+      case "unload":
+        this._unregisterWindow(aEvent.currentTarget);
+        break;
+      case "mouseup":
+        this._handleMouseUp(aEvent);
+        break;
+    }
+  },
+
+  _handleMouseUp: function(aEvent) {
+    let item = aEvent.originalTarget;
+    // Perhaps we're seeing one of the default toolbar items
+    // being clicked.
+    if (ALL_BUILTIN_ITEMS.indexOf(item.id) != -1) {
+      // Base case - we clicked directly on one of our built-in items,
+      // and we can go ahead and register that click.
+      this._countMouseUpEvent("click-builtin-item", item.id, aEvent);
+    }
+  },
+
+  getToolbarMeasures: function() {
+    // Grab the most recent non-popup, non-private browser window for us to
+    // analyze the toolbars in...
+    let win = RecentWindow.getMostRecentBrowserWindow({
+      private: false,
+      allowPopups: false
+    });
+
+    // If there are no such windows, we're out of luck. :(
+    if (!win) {
+      return {};
+    }
+
+    let document = win.document;
+    let result = {};
+
+    result.countableEvents = this._countableEvents;
+
+    return result;
+  },
+};
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -3,16 +3,17 @@
 # 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/.
 
 TEST_DIRS += ['test']
 
 EXTRA_JS_MODULES += [
     'BrowserNewTabPreloader.jsm',
+    'BrowserUITelemetry.jsm',
     'CharsetMenu.jsm',
     'ContentClick.jsm',
     'NetworkPrioritizer.jsm',
     'offlineAppCache.jsm',
     'openLocationLastURL.jsm',
     'SharedFrame.jsm',
     'SignInToWebsite.jsm',
     'SitePermissions.jsm',
--- a/mobile/android/base/resources/values-v11/themes.xml
+++ b/mobile/android/base/resources/values-v11/themes.xml
@@ -44,11 +44,17 @@
         <item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
         <item name="menuItemShareActionButtonStyle">@style/Widget.MenuItemShareActionButton</item>
         <item name="bookmarksListViewStyle">@style/Widget.BookmarksListView</item>
         <item name="topSitesGridItemViewStyle">@style/Widget.TopSitesGridItemView</item>
         <item name="topSitesGridViewStyle">@style/Widget.TopSitesGridView</item>
         <item name="topSitesThumbnailViewStyle">@style/Widget.TopSitesThumbnailView</item>
         <item name="homeListViewStyle">@style/Widget.HomeListView</item>
         <item name="menuItemActionModeStyle">@style/GeckoActionBar.Button</item>
+        <item name="android:actionModeStyle">@style/GeckoActionBar</item>
+        <item name="android:actionButtonStyle">@style/GeckoActionBar.Button</item>
+        <item name="android:actionModeCutDrawable">@drawable/cut</item>
+        <item name="android:actionModeCopyDrawable">@drawable/copy</item>
+        <item name="android:actionModePasteDrawable">@drawable/paste</item>
+        <item name="android:actionModeSelectAllDrawable">@drawable/select_all</item>
     </style>
 
 </resources>
--- a/mobile/android/base/util/Clipboard.java
+++ b/mobile/android/base/util/Clipboard.java
@@ -10,25 +10,25 @@ import android.os.Build;
 import android.util.Log;
 
 import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
 
 import java.util.concurrent.SynchronousQueue;
 
 public final class Clipboard {
     private static Context mContext;
-    private final static String LOG_TAG = "Clipboard";
+    private final static String LOGTAG = "GeckoClipboard";
     private final static SynchronousQueue<String> sClipboardQueue = new SynchronousQueue<String>();
 
     private Clipboard() {
     }
 
     public static void init(Context c) {
         if (mContext != null) {
-            Log.w(LOG_TAG, "Clipboard.init() called twice!");
+            Log.w(LOGTAG, "Clipboard.init() called twice!");
             return;
         }
         mContext = c;
     }
 
     @WrapElementForJNI(stubName = "GetClipboardTextWrapper")
     public static String getText() {
         // If we're on the UI thread or the background thread, we have a looper on the thread
@@ -57,77 +57,82 @@ public final class Clipboard {
 
     @WrapElementForJNI(stubName = "SetClipboardText")
     public static void setText(final CharSequence text) {
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             @SuppressWarnings("deprecation")
             public void run() {
                 if (Build.VERSION.SDK_INT >= 11) {
-                    android.content.ClipboardManager cm = getClipboardManager11(mContext);
+                    android.content.ClipboardManager cm = getClipboardManager(mContext);
                     ClipData clip = ClipData.newPlainText("Text", text);
                     try {
                         cm.setPrimaryClip(clip);
                     } catch (NullPointerException e) {
                         // Bug 776223: This is a Samsung clipboard bug. setPrimaryClip() can throw
                         // a NullPointerException if Samsung's /data/clipboard directory is full.
                         // Fortunately, the text is still successfully copied to the clipboard.
                     }
                 } else {
-                    android.text.ClipboardManager cm = getClipboardManager(mContext);
+                    android.text.ClipboardManager cm = getDeprecatedClipboardManager(mContext);
                     cm.setText(text);
                 }
             }
         });
     }
 
     /**
      * Returns true if the clipboard is nonempty, false otherwise.
      *
      * @return true if the clipboard is nonempty, false otherwise.
      */
     @WrapElementForJNI
     public static boolean hasText() {
-        String text = getText();
-        return text != null;
+        if (Build.VERSION.SDK_INT >= 11) {
+            android.content.ClipboardManager cm = getClipboardManager(mContext);
+            return cm.hasPrimaryClip();
+        }
+
+        android.text.ClipboardManager cm = getDeprecatedClipboardManager(mContext);
+        return cm.hasText();
     }
 
     /**
      * Deletes all text from the clipboard.
      */
     @WrapElementForJNI
     public static void clearText() {
         setText(null);
     }
 
-    private static android.content.ClipboardManager getClipboardManager11(Context context) {
+    private static android.content.ClipboardManager getClipboardManager(Context context) {
         // In API Level 11 and above, CLIPBOARD_SERVICE returns android.content.ClipboardManager,
         // which is a subclass of android.text.ClipboardManager.
         return (android.content.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
     }
 
-    private static android.text.ClipboardManager getClipboardManager(Context context) {
+    private static android.text.ClipboardManager getDeprecatedClipboardManager(Context context) {
         return (android.text.ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
     }
 
     /* On some devices, access to the clipboard service needs to happen
      * on a thread with a looper, so this function requires a looper is
      * present on the thread. */
     @SuppressWarnings("deprecation")
     private static String getClipboardTextImpl() {
         if (Build.VERSION.SDK_INT >= 11) {
-            android.content.ClipboardManager cm = getClipboardManager11(mContext);
+            android.content.ClipboardManager cm = getClipboardManager(mContext);
             if (cm.hasPrimaryClip()) {
                 ClipData clip = cm.getPrimaryClip();
                 if (clip != null) {
                     ClipData.Item item = clip.getItemAt(0);
                     return item.coerceToText(mContext).toString();
                 }
             }
         } else {
-            android.text.ClipboardManager cm = getClipboardManager(mContext);
+            android.text.ClipboardManager cm = getDeprecatedClipboardManager(mContext);
             if (cm.hasText()) {
                 return cm.getText().toString();
             }
         }
         return null;
     }
 }
--- a/mobile/android/chrome/content/SelectionHandler.js
+++ b/mobile/android/chrome/content/SelectionHandler.js
@@ -310,21 +310,24 @@ var SelectionHandler = {
     for (let type in this.actions) {
       let action = this.actions[type];
       if (action.selector.matches(this._targetElement, aX, aY)) {
         let a = {
           id: action.id,
           label: this._getValue(action, "label", ""),
           icon: this._getValue(action, "icon", "drawable://ic_status_logo"),
           showAsAction: this._getValue(action, "showAsAction", true),
+          order: this._getValue(action, "order", 0)
         };
         actions.push(a);
       }
     }
 
+    actions.sort((a, b) => b.order - a.order);
+
     sendMessageToJava({
       type: type,
       handles: handles,
       actions: actions,
     });
   },
 
   _updateMenu: function() {
@@ -335,81 +338,77 @@ var SelectionHandler = {
     SELECT_ALL: {
       label: Strings.browser.GetStringFromName("contextmenu.selectAll"),
       id: "selectall_action",
       icon: "drawable://select_all",
       action: function(aElement) {
         SelectionHandler.selectAll(aElement);
       },
       selector: ClipboardHelper.selectAllContext,
+      order: 1,
     },
 
     CUT: {
       label: Strings.browser.GetStringFromName("contextmenu.cut"),
       id: "cut_action",
       icon: "drawable://cut",
       action: function(aElement) {
         let start = aElement.selectionStart;
         let end   = aElement.selectionEnd;
 
         SelectionHandler.copySelection();
         aElement.value = aElement.value.substring(0, start) + aElement.value.substring(end)
 
         // copySelection closes the selection. Show a caret where we just cut the text.
         SelectionHandler.attachCaret(aElement);
       },
+      order: 1,
       selector: ClipboardHelper.cutContext,
     },
 
     COPY: {
       label: Strings.browser.GetStringFromName("contextmenu.copy"),
       id: "copy_action",
       icon: "drawable://copy",
       action: function() {
         SelectionHandler.copySelection();
       },
+      order: 1,
       selector: ClipboardHelper.getCopyContext(false)
     },
 
     PASTE: {
       label: Strings.browser.GetStringFromName("contextmenu.paste"),
       id: "paste_action",
       icon: "drawable://paste",
       action: function(aElement) {
         ClipboardHelper.paste(aElement);
         SelectionHandler._positionHandles();
         SelectionHandler._updateMenu();
       },
+      order: 1,
       selector: ClipboardHelper.pasteContext,
     },
 
     SHARE: {
       label: Strings.browser.GetStringFromName("contextmenu.share"),
       id: "share_action",
       icon: "drawable://ic_menu_share",
       action: function() {
         SelectionHandler.shareSelection();
       },
-      showAsAction: function(aElement) {
-        return !((aElement instanceof HTMLInputElement && aElement.mozIsTextField(false)) ||
-                 (aElement instanceof HTMLTextAreaElement));
-      },
       selector: ClipboardHelper.shareContext,
     },
 
     SEARCH: {
       label: function() {
         return Strings.browser.formatStringFromName("contextmenu.search", [Services.search.defaultEngine.name], 1);
       },
       id: "search_action",
       icon: "drawable://ic_url_bar_search",
-      showAsAction: function(aElement) {
-        return !((aElement instanceof HTMLInputElement && aElement.mozIsTextField(false)) ||
-                 (aElement instanceof HTMLTextAreaElement));
-      },
       action: function() {
         SelectionHandler.searchSelection();
         SelectionHandler._closeSelection();
       },
       selector: ClipboardHelper.searchWithContext,
     },
 
   },