Merge mozilla-central to mozilla-inbound on a CLOSED TREE
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 15 May 2015 17:41:01 +0200
changeset 244041 5943d32f35155feb6144f5b06d7e413888d9072e
parent 244000 ca67ae37b6113ae9a327eaacc7bd4c182d14faaf (current diff)
parent 244040 1a8343f8ed8336cbba1b236ab9725012c6c73179 (diff)
child 244042 2937420a763331605538d39e7b6d4caf1dd0cd10
push id59820
push usercbook@mozilla.com
push dateFri, 15 May 2015 15:41:47 +0000
treeherdermozilla-inbound@5943d32f3515 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone41.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
Merge mozilla-central to mozilla-inbound on a CLOSED TREE
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -986,17 +986,17 @@ pref("gfx.canvas.skiagl.dynamic-cache", 
 
 // Limit skia to canvases the size of the device screen or smaller
 pref("gfx.canvas.max-size-for-skia-gl", -1);
 
 // enable fence with readpixels for SurfaceStream
 pref("gfx.gralloc.fence-with-readpixels", true);
 
 // The url of the page used to display network error details.
-pref("b2g.neterror.url", "app://system.gaiamobile.org/net_error.html");
+pref("b2g.neterror.url", "net_error.html");
 
 // The origin used for the shared themes uri space.
 pref("b2g.theme.origin", "app://theme.gaiamobile.org");
 pref("dom.mozApps.themable", true);
 pref("dom.mozApps.selected_theme", "default_theme.gaiamobile.org");
 
 // Enable PAC generator for B2G.
 pref("network.proxy.pac_generator", true);
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -631,17 +631,35 @@ var shell = {
       Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
 
       SystemAppProxy.setIsReady();
       if ('pendingChromeEvents' in shell) {
         shell.pendingChromeEvents.forEach((shell.sendChromeEvent).bind(shell));
       }
       delete shell.pendingChromeEvents;
     });
-  }
+
+    shell.handleCmdLine();
+  },
+
+  handleCmdLine: function shell_handleCmdLine() {
+    let b2gcmds = Cc["@mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds"]
+                    .getService(Ci.nsISupports);
+    let args = b2gcmds.wrappedJSObject.cmdLine;
+    try {
+      // Returns null if -url is not present
+      let url = args.handleFlagWithParam("url", false);
+      if (url) {
+        this.sendChromeEvent({type: "mozbrowseropenwindow", url});
+        args.preventDefault = true;
+      }
+    } catch(e) {
+      // Throws if -url is present with no params
+    }
+  },
 };
 
 Services.obs.addObserver(function onFullscreenOriginChange(subject, topic, data) {
   shell.sendChromeEvent({ type: "fullscreenoriginchange",
                           fullscreenorigin: data });
 }, "fullscreen-origin-change", false);
 
 DOMApplicationRegistry.registryStarted.then(function () {
--- a/b2g/components/B2GAboutRedirector.js
+++ b/b2g/components/B2GAboutRedirector.js
@@ -7,21 +7,21 @@ const Ci = Components.interfaces;
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 function debug(msg) {
   //dump("B2GAboutRedirector: " + msg + "\n");
 }
 
 function netErrorURL() {
-  let uri = "app://system.gaiamobile.org/net_error.html";
-  try {
-    uri = Services.prefs.getCharPref("b2g.neterror.url");
-  } catch(e) {}
-  return uri;
+  let systemManifestURL = Services.prefs.getCharPref("b2g.system_manifest_url");
+  systemManifestURL = Services.io.newURI(systemManifestURL, null, null);
+  let netErrorURL = Services.prefs.getCharPref("b2g.neterror.url");
+  netErrorURL = Services.io.newURI(netErrorURL, null, systemManifestURL);
+  return netErrorURL.spec;
 }
 
 let modules = {
   certerror: {
     uri: "chrome://b2g/content/aboutCertError.xhtml",
     privileged: false,
     hide: true
   },
--- a/b2g/components/CommandLine.js
+++ b/b2g/components/CommandLine.js
@@ -10,16 +10,20 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 function CommandlineHandler() {
   this.wrappedJSObject = this;
 }
 
 CommandlineHandler.prototype = {
     handle: function(cmdLine) {
       this.cmdLine = cmdLine;
+      let win = Services.wm.getMostRecentWindow("navigator:browser");
+      if (win && win.shell) {
+        win.shell.handleCmdLine();
+      }
     },
 
     helpInfo: "",
     classID: Components.ID("{385993fe-8710-4621-9fb1-00a09d8bec37}"),
     QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CommandlineHandler]);
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1327,16 +1327,18 @@ pref("services.sync.prefs.sync.signon.re
 pref("services.sync.prefs.sync.spellchecker.dictionary", true);
 pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
 #endif
 
 // Developer edition preferences
 #ifdef MOZ_DEV_EDITION
 sticky_pref("lightweightThemes.selectedThemeID", "firefox-devedition@mozilla.org");
 sticky_pref("browser.devedition.theme.enabled", true);
+#else
+sticky_pref("lightweightThemes.selectedThemeID", "");
 #endif
 
 // Developer edition promo preferences
 pref("devtools.devedition.promo.shown", false);
 pref("devtools.devedition.promo.url", "https://www.mozilla.org/firefox/developer/?utm_source=firefox-dev-tools&utm_medium=firefox-browser&utm_content=betadoorhanger");
 
 // Only potentially show in beta release
 #if MOZ_UPDATE_CHANNEL == beta
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -74,16 +74,20 @@
       <menuitem id="context-sharelink"
                 label="&shareLink.label;"
                 accesskey="&shareLink.accesskey;"
                 oncommand="gContextMenu.shareLink();"/>
       <menuitem id="context-savelink"
                 label="&saveLinkCmd.label;"
                 accesskey="&saveLinkCmd.accesskey;"
                 oncommand="gContextMenu.saveLink();"/>
+      <menuitem id="context-savelinktopocket"
+                label="&saveLinkToPocketCmd.label;"
+                accesskey="&saveLinkToPocketCmd.accesskey;"
+                oncommand="gContextMenu.saveLinkToPocket();"/>
       <menu id="context-marklinkMenu" label="&social.marklinkMenu.label;"
             accesskey="&social.marklinkMenu.accesskey;">
         <menupopup/>
       </menu>
       <menuitem id="context-copyemail"
                 label="&copyEmailCmd.label;"
                 accesskey="&copyEmailCmd.accesskey;"
                 oncommand="gContextMenu.copyEmail();"/>
@@ -264,17 +268,17 @@
                 oncommand="SocialShare.sharePage();"/>
       <menuitem id="context-savepage"
                 label="&savePageCmd.label;"
                 accesskey="&savePageCmd.accesskey2;"
                 oncommand="gContextMenu.savePageAs();"/>
       <menuitem id="context-pocket"
                 label="&saveToPocketCmd.label;"
                 accesskey="&saveToPocketCmd.accesskey;"
-                oncommand="gContextMenu.saveToPocket();"/>
+                oncommand="gContextMenu.savePageToPocket();"/>
       <menu id="context-markpageMenu" label="&social.markpageMenu.label;"
             accesskey="&social.markpageMenu.accesskey;">
         <menupopup/>
       </menu>
       <menuseparator id="context-sep-viewbgimage"/>
       <menuitem id="context-viewbgimage"
                 label="&viewBGImageCmd.label;"
                 accesskey="&viewBGImageCmd.accesskey;"
--- a/browser/base/content/browser-pocket-de.properties
+++ b/browser/base/content/browser-pocket-de.properties
@@ -6,9 +6,11 @@
 # browser.properties in the usual L10N location.
 
 pocket-button.label = Pocket
 pocket-button.tooltiptext = Bei Pocket speichern
 
 # From browser-pocket.dtd
 saveToPocketCmd.label = Seite bei Pocket speichern
 saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Link in Pocket speichern
+saveLinkToPocketCmd.accesskey = o
 pocketMenuitem.label = Pocket-Liste anzeigen
--- a/browser/base/content/browser-pocket-es-ES.properties
+++ b/browser/base/content/browser-pocket-es-ES.properties
@@ -6,9 +6,11 @@
 # browser.properties in the usual L10N location.
 
 pocket-button.label = Pocket
 pocket-button.tooltiptext = Guardar en Pocket
 
 # From browser-pocket.dtd
 saveToPocketCmd.label = Guardar página en Pocket
 saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Guardar enlace en Pocket
+saveLinkToPocketCmd.accesskey = k
 pocketMenuitem.label = Ver lista de Pocket
--- a/browser/base/content/browser-pocket-ja.properties
+++ b/browser/base/content/browser-pocket-ja.properties
@@ -6,9 +6,11 @@
 # browser.properties in the usual L10N location.
 
 pocket-button.label = Pocket
 pocket-button.tooltiptext = Pocket に保存
 
 # From browser-pocket.dtd
 saveToPocketCmd.label = Pocket にページを保存
 saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Pocket にリンクを保存
+saveLinkToPocketCmd.accesskey = o
 pocketMenuitem.label = Pocket のマイリストを表示
--- a/browser/base/content/browser-pocket-ru.properties
+++ b/browser/base/content/browser-pocket-ru.properties
@@ -6,9 +6,11 @@
 # browser.properties in the usual L10N location.
 
 pocket-button.label = Pocket
 pocket-button.tooltiptext = Сохранить в Pocket
 
 # From browser-pocket.dtd
 saveToPocketCmd.label = Сохранить страницу в Pocket
 saveToPocketCmd.accesskey = х
+saveLinkToPocketCmd.label = Сохранить ссылку в Pocket
+saveLinkToPocketCmd.accesskey = P
 pocketMenuitem.label = Показать список Pocket
--- a/browser/base/content/browser-pocket.dtd
+++ b/browser/base/content/browser-pocket.dtd
@@ -2,9 +2,11 @@
    - 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/. -->
 
 <!-- This is a temporary file and not meant for localization; later versions
    - of Firefox include these strings in browser.dtd -->
 
 <!ENTITY saveToPocketCmd.label     "Save Page to Pocket">
 <!ENTITY saveToPocketCmd.accesskey "k">
+<!ENTITY saveLinkToPocketCmd.label     "Save Link to Pocket">
+<!ENTITY saveLinkToPocketCmd.accesskey "o">
 <!ENTITY pocketMenuitem.label      "View Pocket List">
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1,16 +1,22 @@
 /* vim: set ts=2 sw=2 sts=2 et tw=80: */
 # 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/.
 
 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm");
 Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+  "resource:///modules/CustomizableUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Pocket",
+  "resource:///modules/Pocket.jsm");
 
 var gContextMenuContentData = null;
 
 function nsContextMenu(aXulMenu, aIsShift) {
   this.shouldDisplay = true;
   this.initMenu(aXulMenu, aIsShift);
 }
 
@@ -173,46 +179,57 @@ nsContextMenu.prototype = {
     // to be already loaded, since we load it on startup in nsBrowserGlue,
     // and CastingApps isn't, so check SimpleServiceDiscovery.services first
     // to avoid needing to load CastingApps.jsm if we don't need to.
     shouldShowCast = shouldShowCast && this.mediaURL &&
                      SimpleServiceDiscovery.services.length > 0 &&
                      CastingApps.getServicesForVideo(this.target).length > 0;
     this.setItemAttr("context-castvideo", "disabled", !shouldShowCast);
 
-    let canPocket = false;
-    if (shouldShow && window.gBrowser &&
-        this.browser.getTabBrowser() == window.gBrowser) {
-      let uri = this.browser.currentURI;
-      canPocket =
-        CustomizableUI.getPlacementOfWidget("pocket-button") &&
-        (uri.schemeIs("http") || uri.schemeIs("https") ||
-         (uri.schemeIs("about") && ReaderMode.getOriginalUrl(uri.spec)));
-      if (canPocket) {
-        let locale = Cc["@mozilla.org/chrome/chrome-registry;1"].
-                     getService(Ci.nsIXULChromeRegistry).
-                     getSelectedLocale("browser");
-        if (locale != "en-US") {
-          if (locale == "ja-JP-mac")
-            locale = "ja";
-          let url = "chrome://browser/content/browser-pocket-" + locale + ".properties";
-          let bundle = Services.strings.createBundle(url);
-          let item = document.getElementById("context-pocket");
-          try {
-            item.setAttribute("label", bundle.GetStringFromName("saveToPocketCmd.label"));
-            item.setAttribute("accesskey", bundle.GetStringFromName("saveToPocketCmd.accesskey"));
-          } catch (err) {
-            // GetStringFromName throws when the bundle doesn't exist.  In that
-            // case, the item will retain the browser-pocket.dtd en-US string that
-            // it has in the markup.
-          }
+    this.initPocketItems();
+  },
+
+  initPocketItems: function CM_initPocketItems() {
+    var showSaveCurrentPageToPocket = !(this.onTextInput || this.onLink ||
+                                        this.isContentSelected || this.onImage ||
+                                        this.onCanvas || this.onVideo || this.onAudio);
+    let targetURI = (this.onSaveableLink || this.onPlainTextLink) ? this.linkURI : this.browser.currentURI;
+    let canPocket = CustomizableUI.getPlacementOfWidget("pocket-button") &&
+                    window.pktApi && window.pktApi.isUserLoggedIn();
+    canPocket = canPocket && (targetURI.schemeIs("http") || targetURI.schemeIs("https") ||
+                              (targetURI.schemeIs("about") && ReaderMode.getOriginalUrl(targetURI.spec)));
+    canPocket = canPocket && window.gBrowser && this.browser.getTabBrowser() == window.gBrowser;
+
+    if (canPocket) {
+      let locale = Cc["@mozilla.org/chrome/chrome-registry;1"].
+                   getService(Ci.nsIXULChromeRegistry).
+                   getSelectedLocale("browser");
+      if (locale != "en-US") {
+        if (locale == "ja-JP-mac")
+          locale = "ja";
+        let url = "chrome://browser/content/browser-pocket-" + locale + ".properties";
+        let bundle = Services.strings.createBundle(url);
+        let saveToPocketItem = document.getElementById("context-pocket");
+        let saveLinkToPocketItem = document.getElementById("context-savelinktopocket");
+        try {
+          saveToPocketItem.setAttribute("label", bundle.GetStringFromName("saveToPocketCmd.label"));
+          saveToPocketItem.setAttribute("accesskey", bundle.GetStringFromName("saveToPocketCmd.accesskey"));
+          saveLinkToPocketItem.setAttribute("label", bundle.GetStringFromName("saveLinkToPocketCmd.label"));
+          saveLinkToPocketItem.setAttribute("accesskey", bundle.GetStringFromName("saveLinkToPocketCmd.accesskey"));
+        } catch (err) {
+          // GetStringFromName throws when the bundle doesn't exist.  In that
+          // case, the item will retain the browser-pocket.dtd en-US string that
+          // it has in the markup.
         }
       }
     }
-    this.showItem("context-pocket", canPocket && window.pktApi && window.pktApi.isUserLoggedIn());
+    this.showItem("context-pocket", canPocket && showSaveCurrentPageToPocket);
+    let showSaveLinkToPocket = canPocket && !showSaveCurrentPageToPocket &&
+                               (this.onSaveableLink || this.onPlainTextLink);
+    this.showItem("context-savelinktopocket", showSaveLinkToPocket);
   },
 
   initViewItems: function CM_initViewItems() {
     // View source is always OK, unless in directory listing.
     this.showItem("context-viewpartialsource-selection",
                   this.isContentSelected);
     this.showItem("context-viewpartialsource-mathml",
                   this.onMathML && !this.isContentSelected);
@@ -1656,30 +1673,22 @@ nsContextMenu.prototype = {
   shareSelect: function CM_shareSelect() {
     SocialShare.sharePage(null, { url: this.browser.currentURI.spec, text: this.textSelected }, this.target);
   },
 
   savePageAs: function CM_savePageAs() {
     saveDocument(this.browser.contentDocumentAsCPOW);
   },
 
-  saveToPocket: function CM_saveToPocket() {
-    let pocketWidget = document.getElementById("pocket-button");
-    let placement = CustomizableUI.getPlacementOfWidget("pocket-button");
-    if (!placement)
-      return;
+  saveLinkToPocket: function CM_saveLinkToPocket() {
+    Pocket.savePage(this.browser, this.linkURL);
+  },
 
-    if (placement.area == CustomizableUI.AREA_PANEL) {
-      PanelUI.show().then(function() {
-        pocketWidget = document.getElementById("pocket-button");
-        pocketWidget.doCommand();
-      });
-    } else {
-      pocketWidget.doCommand();
-    }
+  savePageToPocket: function CM_saveToPocket() {
+    Pocket.savePage(this.browser, this.browser.currentURI.spec, this.browser.contentTitle);
   },
 
   printFrame: function CM_printFrame() {
     PrintUtils.print(this.target.ownerDocument.defaultView, this.browser);
   },
 
   switchPageDirection: function CM_switchPageDirection() {
     this.browser.messageManager.sendAsyncMessage("SwitchDocumentDirection");
--- a/browser/base/content/test/plugins/browser.ini
+++ b/browser/base/content/test/plugins/browser.ini
@@ -50,17 +50,16 @@ support-files =
 [browser_clearplugindata.js]
 skip-if = e10s # bug 1149253
 [browser_CTP_context_menu.js]
 skip-if = toolkit == "gtk2" || toolkit == "gtk3"   # fails intermittently on Linux (bug 909342)
 [browser_CTP_crashreporting.js]
 skip-if = !crashreporter
 [browser_CTP_data_urls.js]
 [browser_CTP_drag_drop.js]
-skip-if = e10s # misc. issues, bug 1156871
 [browser_CTP_hide_overlay.js]
 [browser_CTP_iframe.js]
 skip-if = os == 'linux' || os == 'mac' # Bug 984821
 [browser_CTP_multi_allow.js]
 [browser_CTP_nonplugins.js]
 [browser_CTP_notificationBar.js]
 [browser_CTP_outsideScrollArea.js]
 [browser_CTP_remove_navigate.js]
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -230,17 +230,16 @@
         </vbox>
         <button id="PanelUI-panic-view-button"
                 label="&panicButton.view.forgetButton;"/>
       </vbox>
     </panelview>
 
     <panelview id="PanelUI-pocketView" flex="1">
       <vbox class="panel-subview-body">
-        <iframe id="pocket-panel-iframe" type="content"/>
       </vbox>
     </panelview>
 
 
   </panelmultiview>
   <!-- These menupopups are located here to prevent flickering,
        see bug 492960 comment 20. -->
   <menupopup id="customizationPanelItemContextMenu">
--- a/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js
+++ b/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js
@@ -46,17 +46,17 @@ add_task(function () {
   ok(installedThemeId.startsWith(firstLWThemeId),
      "The second theme in the 'My Themes' section should be the newly installed theme: " +
      "Installed theme id: " + installedThemeId + "; First theme ID: " + firstLWThemeId);
   is(header.nextSibling.nextSibling.nextSibling, recommendedHeader,
      "There should be two themes in the 'My Themes' section");
 
   let defaultTheme = header.nextSibling;
   defaultTheme.doCommand();
-  is(Services.prefs.prefHasUserValue("lightweightThemes.selectedThemeID"), false, "No lwtheme should be selected");
+  is(Services.prefs.getCharPref("lightweightThemes.selectedThemeID"), "", "No lwtheme should be selected");
 });
 
 add_task(function asyncCleanup() {
   yield endCustomizing();
 
   Services.prefs.clearUserPref("lightweightThemes.usedThemes");
   Services.prefs.clearUserPref("lightweightThemes.recommendedThemes");
 })
\ No newline at end of file
--- a/browser/components/pocket/Pocket.jsm
+++ b/browser/components/pocket/Pocket.jsm
@@ -20,40 +20,47 @@ let Pocket = {
   get listURL() { return "https://" + Pocket.site + "/?src=ff_ext"; },
 
   /**
    * Functions related to the Pocket panel UI.
    */
   onPanelViewShowing(event) {
     let document = event.target.ownerDocument;
     let window = document.defaultView;
-    let iframe = document.getElementById('pocket-panel-iframe');
+    let iframe = window.pktUI.getPanelFrame();
 
+    let urlToSave = Pocket._urlToSave;
+    let titleToSave = Pocket._titleToSave;
+    Pocket._urlToSave = null;
+    Pocket._titleToSave = null;
     // ViewShowing fires immediately before it creates the contents,
     // in lieu of an AfterViewShowing event, just spin the event loop.
     window.setTimeout(function() {
-      window.pktUI.pocketButtonOnCommand();
+      if (urlToSave) {
+        window.pktUI.tryToSaveUrl(urlToSave, titleToSave);
+      } else {
+        window.pktUI.pocketButtonOnCommand();
+      }
 
       if (iframe.contentDocument &&
-          iframe.contentDocument.readyState == "complete")
-      {
+          iframe.contentDocument.readyState == "complete") {
         window.pktUI.pocketPanelDidShow();
       } else {
         // iframe didn't load yet. This seems to always be the case when in
         // the toolbar panel, but never the case for a subview.
         // XXX this only being fired when it's a _capturing_ listener!
         iframe.addEventListener("load", Pocket.onFrameLoaded, true);
       }
     }, 0);
   },
 
   onFrameLoaded(event) {
     let document = event.currentTarget.ownerDocument;
     let window = document.defaultView;
-    let iframe = document.getElementById('pocket-panel-iframe');
+    let iframe = window.pktUI.getPanelFrame();
 
     iframe.removeEventListener("load", Pocket.onFrameLoaded, true);
     window.pktUI.pocketPanelDidShow();
   },
 
   onPanelViewHiding(event) {
     let window = event.target.ownerDocument.defaultView;
     window.pktUI.pocketPanelDidHide(event);
@@ -77,9 +84,31 @@ let Pocket = {
         node.disabled = win.pktApi.isUserLoggedIn() &&
                         !locationURI.schemeIs("http") &&
                         !locationURI.schemeIs("https") &&
                         !(locationURI.schemeIs("about") &&
                           locationURI.spec.toLowerCase().startsWith("about:reader?url="));
       }
     }
   },
+
+  _urlToSave: null,
+  _titleToSave: null,
+  savePage(browser, url, title) {
+    let document = browser.ownerDocument;
+    let pocketWidget = document.getElementById("pocket-button");
+    let placement = CustomizableUI.getPlacementOfWidget("pocket-button");
+    if (!placement)
+      return;
+
+    this._urlToSave = url;
+    this._titleToSave = title;
+    if (placement.area == CustomizableUI.AREA_PANEL) {
+      let win = document.defaultView;
+      win.PanelUI.show().then(function() {
+        pocketWidget = document.getElementById("pocket-button");
+        pocketWidget.doCommand();
+      });
+    } else {
+      pocketWidget.doCommand();
+    }
+  },
 };
--- a/browser/components/pocket/main.js
+++ b/browser/components/pocket/main.js
@@ -639,17 +639,25 @@ var pktUI = (function() {
         var panel = frame;
         while (panel && panel.localName != "panel") {
             panel = panel.parentNode;
         }
     	return panel;
     }
 
     function getPanelFrame() {
-    	return document.getElementById('pocket-panel-iframe');
+        var frame = document.getElementById('pocket-panel-iframe');
+        if (!frame) {
+            var frameParent = document.getElementById("PanelUI-pocketView").firstChild;
+            frame = document.createElement("iframe");
+            frame.id = 'pocket-panel-iframe';
+            frame.setAttribute("type", "content");
+            frameParent.appendChild(frame);
+        }
+        return frame;
     }
 
     function getSubview() {
         var frame = getPanelFrame();
         var view = frame;
         while (view && view.localName != "panelview") {
             view = view.parentNode;
         }
@@ -774,16 +782,17 @@ var pktUI = (function() {
     }
     
     
 	/**
      * Public functions
      */
     return {
     	onLoad: onLoad,
+    	getPanelFrame: getPanelFrame,
 
     	pocketButtonOnCommand: pocketButtonOnCommand,
     	pocketPanelDidShow: pocketPanelDidShow,
     	pocketPanelDidHide: pocketPanelDidHide,
 
         pocketContextSaveLinkOnCommand,
         pocketContextSavePageOnCommand,
 
@@ -836,17 +845,17 @@ var pktUIMessaging = (function() {
 
     /**
      * Send a message to the panel's iframe
      */
     function sendMessageToPanel(panelId, messageId, payload) {
 
         if (!isPanelIdValid(panelId)) { return; };
 
-        var panelFrame = document.getElementById('pocket-panel-iframe');
+        var panelFrame = pktUI.getPanelFrame();
         if (!isPocketPanelFrameValid(panelFrame)) { return; }
 
         var doc = panelFrame.contentWindow.document;
         var documentElement = doc.documentElement;
 
         // Send message to panel
         var panelMessageId = prefixedMessageId(panelId + '_' + messageId);
 
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -10,16 +10,17 @@ support-files =
 # Commented out tests are profiler tests
 # that need to be moved over to performance tool
 
 [browser_perf-aaa-run-first-leaktest.js]
 [browser_markers-gc.js]
 [browser_markers-parse-html.js]
 [browser_markers-timestamp.js]
 [browser_perf-allocations-to-samples.js]
+[browser_perf-categories-js-calltree.js]
 [browser_perf-compatibility-01.js]
 [browser_perf-compatibility-02.js]
 [browser_perf-compatibility-03.js]
 [browser_perf-compatibility-04.js]
 [browser_perf-compatibility-05.js]
 [browser_perf-compatibility-06.js]
 [browser_perf-compatibility-07.js]
 [browser_perf-clear-01.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_perf-categories-js-calltree.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the categories are shown in the js call tree when platform data
+ * is enabled.
+ */
+function spawnTest () {
+  let { panel } = yield initPerformance(SIMPLE_URL);
+  let { EVENTS, $, DetailsView, JsCallTreeView } = panel.panelWin;
+
+  // Enable platform data to show the categories.
+  Services.prefs.setBoolPref(PLATFORM_DATA_PREF, true);
+
+  yield startRecording(panel);
+  yield busyWait(100);
+
+  let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
+  yield stopRecording(panel);
+  yield DetailsView.selectView("js-calltree");
+  yield rendered;
+
+  is($(".call-tree-cells-container").hasAttribute("categories-hidden"), false,
+    "The call tree cells container should show the categories now.");
+  ok($(".call-tree-category[value=Gecko]"),
+    "A category node with the label `Gecko` is displayed in the tree.");
+
+  // Disable platform data to show the categories.
+  Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false);
+
+  is($(".call-tree-cells-container").getAttribute("categories-hidden"), "",
+    "The call tree cells container should hide the categories now.");
+  ok(!$(".call-tree-category[value=Gecko]"),
+    "A category node with the label `Gecko` doesn't exist in the tree anymore.");
+
+  yield teardown(panel);
+  finish();
+}
--- a/browser/devtools/performance/views/details-js-call-tree.js
+++ b/browser/devtools/performance/views/details-js-call-tree.js
@@ -118,17 +118,17 @@ let JsCallTreeView = Heritage.extend(Det
     // tests and JITOptimizationsView.
     root.on("focus", (_, node) => this.emit("focus", node));
 
     // Clear out other call trees.
     this.container.innerHTML = "";
     root.attachTo(this.container);
 
     // When platform data isn't shown, hide the cateogry labels, since they're
-    // only available for C++ frames.
-    root.toggleCategories(options.contentOnly);
+    // only available for C++ frames. Pass *false* to make them invisible.
+    root.toggleCategories(!options.contentOnly);
 
     // Return the CallView for tests
     return root;
   },
 
   toString: () => "[object JsCallTreeView]"
 });
--- a/browser/devtools/shared/timeline/waterfall.js
+++ b/browser/devtools/shared/timeline/waterfall.js
@@ -17,17 +17,17 @@ loader.lazyImporter(this, "setNamedTimeo
   "resource:///modules/devtools/ViewHelpers.jsm");
 loader.lazyImporter(this, "clearNamedTimeout",
   "resource:///modules/devtools/ViewHelpers.jsm");
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/toolkit/event-emitter");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
-const WATERFALL_SIDEBAR_WIDTH = 150; // px
+const WATERFALL_SIDEBAR_WIDTH = 200; // px
 
 const WATERFALL_IMMEDIATE_DRAW_MARKERS_COUNT = 30;
 const WATERFALL_FLUSH_OUTSTANDING_MARKERS_DELAY = 75; // ms
 
 const FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS = 100;
 const WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
 const WATERFALL_HEADER_TICKS_SPACING_MIN = 50; // px
 const WATERFALL_HEADER_TEXT_PADDING = 3; // px
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -246,20 +246,21 @@ let DirectoryLinksProvider = {
   _removePrefsObserver: function DirectoryLinksProvider_removeObserver() {
     for (let pref in this._observedPrefs) {
       let prefName = this._observedPrefs[pref];
       Services.prefs.removeObserver(prefName, this);
     }
   },
 
   _cacheSuggestedLinks: function(link) {
-    if (!link.frecent_sites || "sponsored" == link.type) {
-      // Don't cache links that don't have the expected 'frecent_sites' or are sponsored.
+    // Don't cache links that don't have the expected 'frecent_sites'
+    if (!link.frecent_sites) {
       return;
     }
+
     for (let suggestedSite of link.frecent_sites) {
       let suggestedMap = this._suggestedLinks.get(suggestedSite) || new Map();
       suggestedMap.set(link.url, link);
       this._setupStartEndTime(link);
       this._suggestedLinks.set(suggestedSite, suggestedMap);
     }
   },
 
--- a/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
+++ b/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
@@ -404,16 +404,19 @@ add_task(function test_updateSuggestedTi
   yield promiseCleanDirectoryLinksProvider();
   DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   NewTabUtils.getProviderLinks = origGetProviderLinks;
   DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
 });
 
 add_task(function test_suggestedLinksMap() {
+  let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
+  DirectoryLinksProvider.getFrecentSitesName = () => "testing map";
+
   let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3, suggestedTile4], "directory": [someOtherSite]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
 
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
 
   // Ensure the suggested tiles were not considered directory tiles.
   do_check_eq(links.length, 1);
@@ -422,27 +425,34 @@ add_task(function test_suggestedLinksMap
 
   // Check for correctly saved suggested tiles data.
   expected_data = {
     "taxact.com": [suggestedTile1, suggestedTile2, suggestedTile3],
     "hrblock.com": [suggestedTile1, suggestedTile2],
     "1040.com": [suggestedTile1, suggestedTile3],
     "taxslayer.com": [suggestedTile1, suggestedTile2, suggestedTile3],
     "freetaxusa.com": [suggestedTile2, suggestedTile3],
+    "sponsoredtarget.com": [suggestedTile4],
   };
-  do_check_eq([...DirectoryLinksProvider._suggestedLinks.keys()].indexOf("sponsoredtarget.com"), -1);
+
+  let suggestedSites = [...DirectoryLinksProvider._suggestedLinks.keys()];
+  do_check_eq(suggestedSites.indexOf("sponsoredtarget.com"), 5);
+  do_check_eq(suggestedSites.length, Object.keys(expected_data).length);
 
   DirectoryLinksProvider._suggestedLinks.forEach((suggestedLinks, site) => {
     let suggestedLinksItr = suggestedLinks.values();
     for (let link of expected_data[site]) {
-      isIdentical(suggestedLinksItr.next().value, link);
+      let linkCopy = JSON.parse(JSON.stringify(link));
+      linkCopy.targetedName = "testing map";
+      isIdentical(suggestedLinksItr.next().value, linkCopy);
     }
   })
 
   yield promiseCleanDirectoryLinksProvider();
+  DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
 });
 
 add_task(function test_topSitesWithSuggestedLinks() {
   let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
   DirectoryLinksProvider.getFrecentSitesName = () => "";
 
   let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
   let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
--- a/dom/base/contentAreaDropListener.js
+++ b/dom/base/contentAreaDropListener.js
@@ -1,13 +1,14 @@
 /* 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/. */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/osfile.jsm");
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 // This component is used for handling dragover and drop of urls.
 //
 // It checks to see whether a drop of a url is allowed. For instance, a url
 // cannot be dropped if it is not a valid uri or the source of the drag cannot
@@ -37,23 +38,19 @@ ContentAreaDropListener.prototype =
         case "text/x-moz-url":
           return dt.getData(type).split("\n");
       }
     }
 
     // For shortcuts, we want to check for the file type last, so that the
     // url pointed to in one of the url types is found first before the file
     // type, which points to the actual file.
-    let file = dt.mozGetDataAt("application/x-moz-file", 0);
-    if (file instanceof Ci.nsIFile) {
-      let ioService = Cc["@mozilla.org/network/io-service;1"].
-                        getService(Ci.nsIIOService);
-      let fileHandler = ioService.getProtocolHandler("file")
-                                 .QueryInterface(Ci.nsIFileProtocolHandler);
-      return [fileHandler.getURLSpecFromFile(file), file.leafName];
+    let files = dt.files;
+    if (files && files.length) {
+      return [OS.Path.toFileURI(files[0].mozFullPath), files[0].name];
     }
 
     return [ ];
   },
 
   _validateURI: function(dataTransfer, uriString, disallowInherit)
   {
     if (!uriString)
--- a/dom/base/nsISelectionController.idl
+++ b/dom/base/nsISelectionController.idl
@@ -11,17 +11,17 @@ typedef short SelectionType;
 typedef short SelectionRegion;
 %}
 
 interface nsIContent;
 interface nsIDOMNode;
 interface nsISelection;
 interface nsISelectionDisplay;
 
-[scriptable, uuid(7835DE46-DB36-4BB7-8684-1049A0C13049)]
+[scriptable, uuid(82c3a9df-9bd6-4da2-b561-d85a9eec5caa)]
 interface nsISelectionController : nsISelectionDisplay
 {
    const short SELECTION_NONE=0;
    const short SELECTION_NORMAL=1;
    const short SELECTION_SPELLCHECK=2;
    const short SELECTION_IME_RAWINPUT=4;
    const short SELECTION_IME_SELECTEDRAWTEXT=8;
    const short SELECTION_IME_CONVERTEDTEXT=16;
@@ -261,14 +261,19 @@ interface nsISelectionController : nsISe
    *  @param aNode textNode to test
    *  @param aStartOffset  offset in dom to first char of textnode to test
    *  @param aEndOffset    offset in dom to last char of textnode to test
    *  @param aReturnBool   boolean returned TRUE if visible FALSE if not
    */
     boolean checkVisibility(in nsIDOMNode node, in short startOffset, in short endOffset);
     [noscript,nostdcall] boolean checkVisibilityContent(in nsIContent node, in short startOffset, in short endOffset);
 
+  /**
+   * Returns the current visibility status of the selection carets, and allows
+   * the visibility to be turned off, or on (if a selection exists).
+   */
+    attribute boolean selectionCaretsVisibility;
 };
 %{ C++
    #define NS_ISELECTIONCONTROLLER_CID \
    { 0x513b9460, 0xd56a, 0x4c4e, \
    { 0xb6, 0xf9, 0x0b, 0x8a, 0xe4, 0x37, 0x2a, 0x3b }}
 %}
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -240,16 +240,19 @@ public:
   NS_IMETHOD CompleteMove(bool aForward, bool aExtend) override;
   NS_IMETHOD ScrollPage(bool aForward) override;
   NS_IMETHOD ScrollLine(bool aForward) override;
   NS_IMETHOD ScrollCharacter(bool aRight) override;
   NS_IMETHOD SelectAll(void) override;
   NS_IMETHOD CheckVisibility(nsIDOMNode *node, int16_t startOffset, int16_t EndOffset, bool* _retval) override;
   virtual nsresult CheckVisibilityContent(nsIContent* aNode, int16_t aStartOffset, int16_t aEndOffset, bool* aRetval) override;
 
+  NS_IMETHOD GetSelectionCaretsVisibility(bool* aOutVisibility) override;
+  NS_IMETHOD SetSelectionCaretsVisibility(bool aVisibility) override;
+
 private:
   nsRefPtr<nsFrameSelection> mFrameSelection;
   nsCOMPtr<nsIContent>       mLimiter;
   nsIScrollableFrame        *mScrollFrame;
   nsWeakPtr mPresShellWeak;
 };
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTextInputSelectionImpl)
@@ -635,16 +638,46 @@ nsTextInputSelectionImpl::CheckVisibilit
   if (shell)
   {
     return shell->CheckVisibility(node,startOffset,EndOffset, _retval);
   }
   return NS_ERROR_FAILURE;
 
 }
 
+NS_IMETHODIMP
+nsTextInputSelectionImpl::GetSelectionCaretsVisibility(bool* aOutVisibility)
+{
+  if (!mPresShellWeak) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  nsresult result;
+  nsCOMPtr<nsISelectionController> shell = do_QueryReferent(mPresShellWeak, &result);
+  if (shell) {
+    return shell->GetSelectionCaretsVisibility(aOutVisibility);
+  }
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsTextInputSelectionImpl::SetSelectionCaretsVisibility(bool aVisibility)
+{
+  if (!mPresShellWeak) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  nsresult result;
+  nsCOMPtr<nsISelectionController> shell = do_QueryReferent(mPresShellWeak, &result);
+  if (shell) {
+    return shell->SetSelectionCaretsVisibility(aVisibility);
+  }
+  return NS_ERROR_FAILURE;
+}
+
 nsresult
 nsTextInputSelectionImpl::CheckVisibilityContent(nsIContent* aNode,
                                                  int16_t aStartOffset,
                                                  int16_t aEndOffset,
                                                  bool* aRetval)
 {
   if (!mPresShellWeak) {
     return NS_ERROR_NOT_INITIALIZED;
--- a/layout/base/SelectionCarets.cpp
+++ b/layout/base/SelectionCarets.cpp
@@ -52,54 +52,59 @@ static const char* kSelectionCaretsLogMo
 #define SELECTIONCARETS_LOG_STATIC(message, ...)                               \
   PR_LOG(gSelectionCaretsLog, PR_LOG_DEBUG,                                    \
          ("SelectionCarets: %s:%d : " message "\n", __FUNCTION__, __LINE__,    \
           ##__VA_ARGS__));
 
 // We treat mouse/touch move as "REAL" move event once its move distance
 // exceed this value, in CSS pixel.
 static const int32_t kMoveStartTolerancePx = 5;
-// Time for trigger scroll end event, in miliseconds.
-static const int32_t kScrollEndTimerDelay = 300;
 
 NS_IMPL_ISUPPORTS(SelectionCarets,
                   nsIReflowObserver,
                   nsISelectionListener,
                   nsIScrollObserver,
                   nsISupportsWeakReference)
 
 /*static*/ int32_t SelectionCarets::sSelectionCaretsInflateSize = 0;
 /*static*/ bool SelectionCarets::sSelectionCaretDetectsLongTap = true;
+/*static*/ bool SelectionCarets::sCaretManagesAndroidActionbar = false;
+/*static*/ bool SelectionCarets::sSelectionCaretObservesCompositions = false;
 
 SelectionCarets::SelectionCarets(nsIPresShell* aPresShell)
   : mPresShell(aPresShell)
   , mActiveTouchId(-1)
   , mCaretCenterToDownPointOffsetY(0)
   , mDragMode(NONE)
   , mUseAsyncPanZoom(false)
   , mInAsyncPanZoomGesture(false)
   , mEndCaretVisible(false)
   , mStartCaretVisible(false)
   , mSelectionVisibleInScrollFrames(true)
   , mVisible(false)
+  , mActionBarViewID(0)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!gSelectionCaretsLog) {
     gSelectionCaretsLog = PR_NewLogModule(kSelectionCaretsLogModuleName);
   }
 
   SELECTIONCARETS_LOG("Constructor, PresShell=%p", mPresShell);
 
   static bool addedPref = false;
   if (!addedPref) {
     Preferences::AddIntVarCache(&sSelectionCaretsInflateSize,
                                 "selectioncaret.inflatesize.threshold");
     Preferences::AddBoolVarCache(&sSelectionCaretDetectsLongTap,
                                  "selectioncaret.detects.longtap", true);
+    Preferences::AddBoolVarCache(&sCaretManagesAndroidActionbar,
+                                 "caret.manages-android-actionbar");
+    Preferences::AddBoolVarCache(&sSelectionCaretObservesCompositions,
+                                 "selectioncaret.observes.compositions");
     addedPref = true;
   }
 }
 
 void
 SelectionCarets::Init()
 {
   nsPresContext* presContext = mPresShell->GetPresContext();
@@ -315,16 +320,21 @@ SelectionCarets::SetVisibility(bool aVis
   mVisible = aVisible;
   SELECTIONCARETS_LOG("Set visibility %s", (mVisible ? "shown" : "hidden"));
 
   dom::Element* startElement = mPresShell->GetSelectionCaretsStartElement();
   SetElementVisibility(startElement, mVisible && mStartCaretVisible);
 
   dom::Element* endElement = mPresShell->GetSelectionCaretsEndElement();
   SetElementVisibility(endElement, mVisible && mEndCaretVisible);
+
+  // Update the Android Actionbar visibility if in use.
+  if (sCaretManagesAndroidActionbar) {
+    TouchCaret::UpdateAndroidActionBarVisibility(mVisible, mActionBarViewID);
+  }
 }
 
 void
 SelectionCarets::SetStartFrameVisibility(bool aVisible)
 {
   mStartCaretVisible = aVisible;
   SELECTIONCARETS_LOG("Set start frame visibility %s",
                       (mStartCaretVisible ? "shown" : "hidden"));
@@ -1122,22 +1132,56 @@ SelectionCarets::NotifySelectionChanged(
 {
   SELECTIONCARETS_LOG("aSel (%p), Reason=%d", aSel, aReason);
 
   if (aSel != GetSelection()) {
     SELECTIONCARETS_LOG("Return for selection mismatch!");
     return NS_OK;
   }
 
-  if (!aReason || (aReason & (nsISelectionListener::DRAG_REASON |
-                              nsISelectionListener::KEYPRESS_REASON |
-                              nsISelectionListener::MOUSEDOWN_REASON))) {
-    SetVisibility(false);
+  // Update SelectionCaret visibility.
+  if (sSelectionCaretObservesCompositions) {
+    // When observing selection change notifications generated for example
+    // by Android soft-keyboard compositions, we can only obtain visibility
+    // after mouse-up by long-tap, or final caret-drag.
+    if (!mVisible) {
+      if (aReason & nsISelectionListener::MOUSEUP_REASON) {
+        UpdateSelectionCarets();
+      }
+    } else {
+      // If already visible, we hide immediately for some known
+      // event-reasons: drag, keypress, or mouse down.
+      if (aReason & (nsISelectionListener::DRAG_REASON |
+                     nsISelectionListener::KEYPRESS_REASON |
+                     nsISelectionListener::MOUSEDOWN_REASON)) {
+        SetVisibility(false);
+      } else {
+        // Else we look further at the selection status, as currently
+        // style-composition changes don't provide reason codes.
+        UpdateSelectionCarets();
+      }
+    }
   } else {
-    UpdateSelectionCarets();
+    // Default logic, mainly employed by b2g, isn't aware of soft-keyboard
+    // selection change compositions.
+    if (!aReason || (aReason & (nsISelectionListener::DRAG_REASON |
+                                nsISelectionListener::KEYPRESS_REASON |
+                                nsISelectionListener::MOUSEDOWN_REASON))) {
+      SetVisibility(false);
+    } else {
+      UpdateSelectionCarets();
+    }
+  }
+
+  // Maybe trigger Android ActionBar updates.
+  if (mVisible && sCaretManagesAndroidActionbar) {
+    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+    if (os) {
+      os->NotifyObservers(nullptr, "ActionBar:UpdateState", nullptr);
+    }
   }
 
   DispatchSelectionStateChangedEvent(static_cast<Selection*>(aSel),
                                      GetSelectionStates(aReason));
   return NS_OK;
 }
 
 static void
@@ -1159,17 +1203,20 @@ DispatchScrollViewChangeEvent(nsIPresShe
   }
 }
 
 void
 SelectionCarets::AsyncPanZoomStarted()
 {
   if (mVisible) {
     mInAsyncPanZoomGesture = true;
-    SetVisibility(false);
+    // Hide selection carets if not using ActionBar.
+    if (!sCaretManagesAndroidActionbar) {
+      SetVisibility(false);
+    }
 
     SELECTIONCARETS_LOG("Dispatch scroll started");
     DispatchScrollViewChangeEvent(mPresShell, dom::ScrollState::Started);
   } else {
     nsRefPtr<dom::Selection> selection = GetSelection();
     if (selection && selection->RangeCount() && selection->IsCollapsed()) {
       mInAsyncPanZoomGesture = true;
       DispatchScrollViewChangeEvent(mPresShell, dom::ScrollState::Started);
@@ -1195,17 +1242,21 @@ SelectionCarets::AsyncPanZoomStopped()
   }
 }
 
 void
 SelectionCarets::ScrollPositionChanged()
 {
   if (mVisible) {
     if (!mUseAsyncPanZoom) {
-      SetVisibility(false);
+      // Hide selection carets if not using ActionBar.
+      if (!sCaretManagesAndroidActionbar) {
+        SetVisibility(false);
+      }
+
       //TODO: handling scrolling for selection bubble when APZ is off
       // Dispatch event to notify gaia to hide selection bubble.
       // Positions will be updated when scroll is end, so no need to calculate
       // and keep scroll positions here. An arbitrary (0, 0) is sent instead.
       DispatchScrollViewChangeEvent(mPresShell, dom::ScrollState::Started);
 
       SELECTIONCARETS_LOG("Launch scroll end detector");
       LaunchScrollEndDetector();
@@ -1281,20 +1332,21 @@ void
 SelectionCarets::LaunchScrollEndDetector()
 {
   if (!mScrollEndDetectorTimer) {
     mScrollEndDetectorTimer = do_CreateInstance("@mozilla.org/timer;1");
   }
 
   MOZ_ASSERT(mScrollEndDetectorTimer);
 
-  SELECTIONCARETS_LOG("Will fire scroll end after %d ms", kScrollEndTimerDelay);
+  SELECTIONCARETS_LOG("Will fire scroll end after %d ms",
+    TouchCaret::sScrollEndTimerDelay);
   mScrollEndDetectorTimer->InitWithFuncCallback(FireScrollEnd,
                                                 this,
-                                                kScrollEndTimerDelay,
+                                                TouchCaret::sScrollEndTimerDelay,
                                                 nsITimer::TYPE_ONE_SHOT);
 }
 
 void
 SelectionCarets::CancelScrollEndDetector()
 {
   if (!mScrollEndDetectorTimer) {
     return;
--- a/layout/base/SelectionCarets.h
+++ b/layout/base/SelectionCarets.h
@@ -96,31 +96,31 @@ public:
    * Get from pref "selectioncaret.inflatesize.threshold". This will inflate size of
    * caret frame when we checking if user click on caret or not. In app units.
    */
   static int32_t SelectionCaretsInflateSize()
   {
     return sSelectionCaretsInflateSize;
   }
 
-private:
-  virtual ~SelectionCarets();
-
-  SelectionCarets() = delete;
-
   /**
    * Set visibility for selection caret.
    */
   void SetVisibility(bool aVisible);
 
   /**
    * Update selection caret position base on current selection range.
    */
   void UpdateSelectionCarets();
 
+private:
+  virtual ~SelectionCarets();
+
+  SelectionCarets() = delete;
+
   /**
    * Select a word base on current position, which activates only if element is
    * selectable. Triggered by long tap event.
    */
   nsresult SelectWord();
 
   /**
    * Move selection base on current touch/mouse point
@@ -262,12 +262,17 @@ private:
   bool mEndCaretVisible;
   bool mStartCaretVisible;
   bool mSelectionVisibleInScrollFrames;
   bool mVisible;
 
   // Preference
   static int32_t sSelectionCaretsInflateSize;
   static bool sSelectionCaretDetectsLongTap;
+  static bool sCaretManagesAndroidActionbar;
+  static bool sSelectionCaretObservesCompositions;
+
+  // Unique ID of current Mobile ActionBar view.
+  uint32_t mActionBarViewID;
 };
 } // namespace mozilla
 
 #endif //SelectionCarets_h__
--- a/layout/base/TouchCaret.cpp
+++ b/layout/base/TouchCaret.cpp
@@ -51,50 +51,100 @@ static const char* kTouchCaretLogModuleN
          ("TouchCaret: %s:%d : " message "\n", __FUNCTION__, __LINE__,         \
           ##__VA_ARGS__));
 
 // Click on the boundary of input/textarea will place the caret at the
 // front/end of the content. To advoid this, we need to deflate the content
 // boundary by 61 app units (1 pixel + 1 app unit).
 static const int32_t kBoundaryAppUnits = 61;
 
-NS_IMPL_ISUPPORTS(TouchCaret, nsISelectionListener)
+NS_IMPL_ISUPPORTS(TouchCaret,
+                  nsISelectionListener,
+                  nsIScrollObserver,
+                  nsISupportsWeakReference)
 
 /*static*/ int32_t TouchCaret::sTouchCaretInflateSize = 0;
 /*static*/ int32_t TouchCaret::sTouchCaretExpirationTime = 0;
+/*static*/ bool TouchCaret::sCaretManagesAndroidActionbar = false;
+/*static*/ bool TouchCaret::sTouchcaretExtendedvisibility = false;
+
+/*static*/ uint32_t TouchCaret::sActionBarViewCount = 0;
 
 TouchCaret::TouchCaret(nsIPresShell* aPresShell)
   : mState(TOUCHCARET_NONE),
     mActiveTouchId(-1),
     mCaretCenterToDownPointOffsetY(0),
+    mInAsyncPanZoomGesture(false),
     mVisible(false),
-    mIsValidTap(false)
+    mIsValidTap(false),
+    mActionBarViewID(0)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!gTouchCaretLog) {
     gTouchCaretLog = PR_NewLogModule(kTouchCaretLogModuleName);
   }
 
   TOUCHCARET_LOG("Constructor, PresShell=%p", aPresShell);
 
   static bool addedTouchCaretPref = false;
   if (!addedTouchCaretPref) {
     Preferences::AddIntVarCache(&sTouchCaretInflateSize,
                                 "touchcaret.inflatesize.threshold");
     Preferences::AddIntVarCache(&sTouchCaretExpirationTime,
                                 "touchcaret.expiration.time");
+    Preferences::AddBoolVarCache(&sCaretManagesAndroidActionbar,
+                                 "caret.manages-android-actionbar");
+    Preferences::AddBoolVarCache(&sTouchcaretExtendedvisibility,
+                                 "touchcaret.extendedvisibility");
     addedTouchCaretPref = true;
   }
 
   // The presshell owns us, so no addref.
   mPresShell = do_GetWeakReference(aPresShell);
   MOZ_ASSERT(mPresShell, "Hey, pres shell should support weak refs");
 }
 
+void
+TouchCaret::Init()
+{
+  nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
+  if (!presShell) {
+    return;
+  }
+
+  nsPresContext* presContext = presShell->GetPresContext();
+  MOZ_ASSERT(presContext, "PresContext should be given in PresShell::Init()");
+
+  nsIDocShell* docShell = presContext->GetDocShell();
+  if (!docShell) {
+    return;
+  }
+
+  docShell->AddWeakScrollObserver(this);
+  mDocShell = static_cast<nsDocShell*>(docShell);
+}
+
+void
+TouchCaret::Terminate()
+{
+  nsRefPtr<nsDocShell> docShell(mDocShell.get());
+  if (docShell) {
+    docShell->RemoveWeakScrollObserver(this);
+  }
+
+  if (mScrollEndDetectorTimer) {
+    mScrollEndDetectorTimer->Cancel();
+    mScrollEndDetectorTimer = nullptr;
+  }
+
+  mDocShell = WeakPtr<nsDocShell>();
+  mPresShell = nullptr;
+}
+
 TouchCaret::~TouchCaret()
 {
   TOUCHCARET_LOG("Destructor");
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mTouchCaretExpirationTimer) {
     mTouchCaretExpirationTimer->Cancel();
     mTouchCaretExpirationTimer = nullptr;
@@ -169,16 +219,48 @@ TouchCaret::SetVisibility(bool aVisible)
   ErrorResult err;
   touchCaretElement->ClassList()->Toggle(NS_LITERAL_STRING("hidden"),
                                          dom::Optional<bool>(!mVisible),
                                          err);
   TOUCHCARET_LOG("Set visibility %s", (mVisible ? "shown" : "hidden"));
 
   // Set touch caret expiration time.
   mVisible ? LaunchExpirationTimer() : CancelExpirationTimer();
+
+  // If after a TouchCaret visibility change we become hidden, ensure
+  // the Android ActionBar handler is notified to close the current view.
+  if (!mVisible && sCaretManagesAndroidActionbar) {
+    UpdateAndroidActionBarVisibility(false, mActionBarViewID);
+  }
+}
+
+/**
+ * Open or close the Android TextSelection ActionBar, based on visibility.
+ * Each time we're called to open the actionbar, we increment / assign a
+ * unique view ID and return it to the caller. The ID is returned on calls
+ * to close the actionbar to ensure we don't close the shared view if it
+ * was already force closed by a subsequent callers open request.
+ */
+/* static */void
+TouchCaret::UpdateAndroidActionBarVisibility(bool aVisibility, uint32_t& aViewID)
+{
+  // Are we openning a new view?
+  if (aVisibility) {
+    // Assign a new view ID.
+    aViewID = ++sActionBarViewCount;
+  }
+
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  if (os) {
+    nsString topic = (aVisibility) ?
+      NS_LITERAL_STRING("ActionBar:OpenNew") : NS_LITERAL_STRING("ActionBar:Close");
+    nsAutoString viewCount;
+    viewCount.AppendInt(aViewID);
+    os->NotifyObservers(nullptr, NS_ConvertUTF16toUTF8(topic).get(), viewCount.get());
+  }
 }
 
 nsRect
 TouchCaret::GetTouchFrameRect()
 {
   nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
   if (!presShell) {
     return nsRect();
@@ -357,21 +439,110 @@ TouchCaret::NotifySelectionChanged(nsIDO
   // Also hide touch caret when gecko or javascript collapse the selection.
   if (aReason & nsISelectionListener::KEYPRESS_REASON ||
       aReason & nsISelectionListener::COLLAPSETOSTART_REASON ||
       aReason & nsISelectionListener::COLLAPSETOEND_REASON) {
     TOUCHCARET_LOG("KEYPRESS_REASON");
     SetVisibility(false);
   } else {
     SyncVisibilityWithCaret();
+
+    // Is the TouchCaret visible and we're showing/hiding the actionbar?
+    if (mVisible && sCaretManagesAndroidActionbar) {
+      // A selection change due to touch tap opens the actionbar.
+      if (aReason & nsISelectionListener::MOUSEUP_REASON) {
+        UpdateAndroidActionBarVisibility(true, mActionBarViewID);
+      } else {
+        // Update the ActionBar state for caret-specific selection changes.
+        // Ignore transient selection composition changes that occur while
+        // the TouchCaret is also visible.
+        bool isCollapsed;
+        if (NS_SUCCEEDED(aSel->GetIsCollapsed(&isCollapsed)) && isCollapsed) {
+          nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+          if (os) {
+            os->NotifyObservers(nullptr, "ActionBar:UpdateState", nullptr);
+          }
+        }
+      }
+    }
   }
 
   return NS_OK;
 }
 
+/**
+ * Used to update caret position after PanZoom stops for
+ * extended caret visibility. Never needed by MOZ_WIDGET_GONK.
+ */
+void
+TouchCaret::AsyncPanZoomStarted()
+{
+  if (mVisible) {
+    if (sTouchcaretExtendedvisibility) {
+      mInAsyncPanZoomGesture = true;
+    }
+  }
+}
+
+void
+TouchCaret::AsyncPanZoomStopped()
+{
+  if (mInAsyncPanZoomGesture) {
+    mInAsyncPanZoomGesture = false;
+    UpdatePosition();
+  }
+}
+
+/**
+ * Used to update caret position after Scroll stops for
+ * extended caret visibility. Never needed by MOZ_WIDGET_GONK.
+ */
+void
+TouchCaret::ScrollPositionChanged()
+{
+  if (mVisible) {
+    if (sTouchcaretExtendedvisibility) {
+      // Launch scroll end detector.
+      LaunchScrollEndDetector();
+    }
+  }
+}
+
+void
+TouchCaret::LaunchScrollEndDetector()
+{
+  if (!mScrollEndDetectorTimer) {
+    mScrollEndDetectorTimer = do_CreateInstance("@mozilla.org/timer;1");
+  }
+  MOZ_ASSERT(mScrollEndDetectorTimer);
+
+  mScrollEndDetectorTimer->InitWithFuncCallback(FireScrollEnd,
+                                                this,
+                                                sScrollEndTimerDelay,
+                                                nsITimer::TYPE_ONE_SHOT);
+}
+
+void
+TouchCaret::CancelScrollEndDetector()
+{
+  if (mScrollEndDetectorTimer) {
+    mScrollEndDetectorTimer->Cancel();
+  }
+}
+
+
+/* static */void
+TouchCaret::FireScrollEnd(nsITimer* aTimer, void* aTouchCaret)
+{
+  nsRefPtr<TouchCaret> self = static_cast<TouchCaret*>(aTouchCaret);
+  NS_PRECONDITION(aTimer == self->mScrollEndDetectorTimer,
+                  "Unexpected timer");
+  self->UpdatePosition();
+}
+
 void
 TouchCaret::SyncVisibilityWithCaret()
 {
   TOUCHCARET_LOG("SyncVisibilityWithCaret");
 
   if (!IsDisplayable()) {
     SetVisibility(false);
     return;
@@ -442,27 +613,33 @@ TouchCaret::IsDisplayable()
   }
 
   nsRect focusRect;
   nsIFrame* focusFrame = caret->GetGeometry(&focusRect);
   if (!focusFrame) {
     TOUCHCARET_LOG("Focus frame is not valid!");
     return false;
   }
-  if (focusRect.IsEmpty()) {
-    TOUCHCARET_LOG("Focus rect is empty!");
-    return false;
-  }
 
   dom::Element* editingHost = focusFrame->GetContent()->GetEditingHost();
   if (!editingHost) {
     TOUCHCARET_LOG("Cannot get editing host!");
     return false;
   }
 
+  // No further checks required if extended TouchCaret visibility.
+  if (sTouchcaretExtendedvisibility) {
+    return true;
+  }
+
+  if (focusRect.IsEmpty()) {
+    TOUCHCARET_LOG("Focus rect is empty!");
+    return false;
+  }
+
   if (!nsContentUtils::HasNonEmptyTextContent(
          editingHost, nsContentUtils::eRecurseIntoChildren)) {
     TOUCHCARET_LOG("The content is empty!");
     return false;
   }
 
   if (mState != TOUCHCARET_TOUCHDRAG_ACTIVE &&
         !nsLayoutUtils::IsRectVisibleInScrollFrames(focusFrame, focusRect)) {
@@ -832,16 +1009,22 @@ TouchCaret::HandleMouseDownEvent(WidgetM
           SetSelectionDragState(true);
           // Cache distence of the event point to the center of touch caret.
           mCaretCenterToDownPointOffsetY = GetCaretYCenterPosition() - point.y;
           // Enter TOUCHCARET_MOUSEDRAG_ACTIVE state and cancel the timer.
           SetState(TOUCHCARET_MOUSEDRAG_ACTIVE);
           CancelExpirationTimer();
           status = nsEventStatus_eConsumeNoDefault;
         } else {
+          // Mousedown events that miss HitTest can be caused by soft-keyboard
+          // auto-suggestions. If extended visibility, update the caret position.
+          if (sTouchcaretExtendedvisibility) {
+            UpdatePositionIfNeeded();
+            break;
+          }
           // Set touch caret invisible if HisTest fails. Bypass event.
           SetVisibility(false);
           status = nsEventStatus_eIgnore;
         }
       } else {
         // Set touch caret invisible if not left button down event.
         SetVisibility(false);
         status = nsEventStatus_eIgnore;
@@ -892,18 +1075,24 @@ TouchCaret::HandleTouchDownEvent(WidgetT
             CancelExpirationTimer();
             status = nsEventStatus_eConsumeNoDefault;
             break;
           }
         }
         // No touch is on the touch caret. Set touch caret invisible, and bypass
         // the event.
         if (mActiveTouchId == -1) {
-          SetVisibility(false);
-          status = nsEventStatus_eIgnore;
+          // Check touch caret visibility style.
+          if (sTouchcaretExtendedvisibility) {
+            // Update position on events associated with scroll and pan-zoom.
+            UpdatePositionIfNeeded();
+          } else {
+            SetVisibility(false);
+            status = nsEventStatus_eIgnore;
+          }
         }
       }
       break;
 
     case TOUCHCARET_MOUSEDRAG_ACTIVE:
     case TOUCHCARET_TOUCHDRAG_ACTIVE:
     case TOUCHCARET_TOUCHDRAG_INACTIVE:
       // Consume NS_TOUCH_START event.
--- a/layout/base/TouchCaret.h
+++ b/layout/base/TouchCaret.h
@@ -22,28 +22,35 @@ class nsIPresShell;
 namespace mozilla {
 
 /**
  * The TouchCaret places a touch caret according to caret position when the
  * caret is shown.
  * TouchCaret is also responsible for touch caret visibility. Touch caret
  * won't be shown when timer expires or while key event causes selection change.
  */
-class TouchCaret final : public nsISelectionListener
+class TouchCaret final : public nsISelectionListener,
+                         public nsIScrollObserver,
+                         public nsSupportsWeakReference
 {
 public:
   explicit TouchCaret(nsIPresShell* aPresShell);
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSISELECTIONLISTENER
 
-  void Terminate()
-  {
-    mPresShell = nullptr;
-  }
+  void Init();
+  void Terminate();
+
+  // nsIScrollObserver
+  virtual void ScrollPositionChanged() override;
+
+  // AsyncPanZoom started/stopped callbacks from nsIScrollObserver
+  virtual void AsyncPanZoomStarted() override;
+  virtual void AsyncPanZoomStopped() override;
 
   /**
    * Handle mouse and touch event only.
    * Depends on visibility and position of touch caret, HandleEvent may consume
    * that input event and return nsEventStatus_eConsumeNoDefault to the caller.
    * In that case, caller should stop bubble up that input event.
    */
   nsEventStatus HandleEvent(WidgetEvent* aEvent);
@@ -55,16 +62,21 @@ public:
   /**
    * GetVisibility will get the visibility of the touch caret.
    */
   bool GetVisibility() const
   {
     return mVisible;
   }
 
+  /**
+   * Open or close the Android TextSelection ActionBar based on visibility.
+   */
+  static void UpdateAndroidActionBarVisibility(bool aVisibility, uint32_t& aViewID);
+
 private:
   // Hide default constructor.
   TouchCaret() = delete;
 
   ~TouchCaret();
 
   bool IsDisplayable();
 
@@ -263,27 +275,49 @@ private:
    */
   static int32_t TouchCaretInflateSize() { return sTouchCaretInflateSize; }
 
   static int32_t TouchCaretExpirationTime()
   {
     return sTouchCaretExpirationTime;
   }
 
+  void LaunchScrollEndDetector();
+  void CancelScrollEndDetector();
+  static void FireScrollEnd(nsITimer* aTimer, void* aSelectionCarets);
+
+  // This timer is used for detecting scroll end. We don't have
+  // scroll end event now, so we will fire this event with a
+  // const time when we scroll. So when timer triggers, we treat it
+  // as scroll end event.
+  nsCOMPtr<nsITimer> mScrollEndDetectorTimer;
+
   nsWeakPtr mPresShell;
+  WeakPtr<nsDocShell> mDocShell;
+
+  // True if AsyncPanZoom is started
+  bool mInAsyncPanZoomGesture;
 
   // Touch caret visibility
   bool mVisible;
   // Use for detecting single tap on touch caret.
   bool mIsValidTap;
   // Touch caret timer
   nsCOMPtr<nsITimer> mTouchCaretExpirationTimer;
 
   // Preference
   static int32_t sTouchCaretInflateSize;
   static int32_t sTouchCaretExpirationTime;
+  static bool sCaretManagesAndroidActionbar;
+  static bool sTouchcaretExtendedvisibility;
 
   // The auto scroll timer's interval in miliseconds.
   friend class SelectionCarets;
   static const int32_t sAutoScrollTimerDelay = 30;
+  // Time for trigger scroll end event, in miliseconds.
+  static const int32_t sScrollEndTimerDelay = 300;
+
+  // Unique ID of current Mobile ActionBar view.
+  static uint32_t sActionBarViewCount;
+  uint32_t mActionBarViewID;
 };
 } //namespace mozilla
 #endif //mozilla_TouchCaret_h__
--- a/layout/base/nsCaret.cpp
+++ b/layout/base/nsCaret.cpp
@@ -41,16 +41,19 @@ using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 
 // The bidi indicator hangs off the caret to one side, to show which
 // direction the typing is in. It needs to be at least 2x2 to avoid looking like 
 // an insignificant dot
 static const int32_t kMinBidiIndicatorPixels = 2;
 
+/*static*/ bool nsCaret::sSelectionCaretEnabled = false;
+/*static*/ bool nsCaret::sSelectionCaretsAffectCaret = false;
+
 /**
  * Find the first frame in an in-order traversal of the frame subtree rooted
  * at aFrame which is either a text frame logically at the end of a line,
  * or which is aStopAtFrame. Return null if no such frame is found. We don't
  * descend into the children of non-eLineParticipant frames.
  */
 static nsIFrame*
 CheckForTrailingTextFrameRecursive(nsIFrame* aFrame, nsIFrame* aStopAtFrame)
@@ -139,16 +142,25 @@ nsresult nsCaret::Init(nsIPresShell *inP
 
   mPresShell = do_GetWeakReference(inPresShell);    // the presshell owns us, so no addref
   NS_ASSERTION(mPresShell, "Hey, pres shell should support weak refs");
 
   mShowDuringSelection =
     LookAndFeel::GetInt(LookAndFeel::eIntID_ShowCaretDuringSelection,
                         mShowDuringSelection ? 1 : 0) != 0;
 
+  static bool addedCaretPref = false;
+  if (!addedCaretPref) {
+    Preferences::AddBoolVarCache(&sSelectionCaretEnabled,
+      "selectioncaret.enabled");
+    Preferences::AddBoolVarCache(&sSelectionCaretsAffectCaret,
+      "selectioncaret.visibility.affectscaret");
+    addedCaretPref = true;
+  }
+
   // get the selection from the pres shell, and set ourselves up as a selection
   // listener
 
   nsCOMPtr<nsISelectionController> selCon = do_QueryReferent(mPresShell);
   if (!selCon)
     return NS_ERROR_FAILURE;
 
   nsCOMPtr<nsISelection> domSelection;
@@ -248,27 +260,42 @@ void nsCaret::SetVisible(bool inMakeVisi
 }
 
 bool nsCaret::IsVisible()
 {
   if (!mVisible) {
     return false;
   }
 
-  if (!mShowDuringSelection) {
+  if (!mShowDuringSelection &&
+      !(sSelectionCaretEnabled && sSelectionCaretsAffectCaret)) {
     Selection* selection = GetSelectionInternal();
     if (!selection) {
       return false;
     }
     bool isCollapsed;
     if (NS_FAILED(selection->GetIsCollapsed(&isCollapsed)) || !isCollapsed) {
       return false;
     }
   }
 
+  // The Android IME can have a visible caret when there is a composition
+  // selection, due to auto-suggest/auto-correct styling (underlining),
+  // but never when the SelectionCarets are visible.
+  if (sSelectionCaretEnabled && sSelectionCaretsAffectCaret) {
+    nsCOMPtr<nsISelectionController> selCon = do_QueryReferent(mPresShell);
+    if (selCon) {
+      bool visible = false;
+      selCon->GetSelectionCaretsVisibility(&visible);
+      if (visible) {
+        return false;
+      }
+    }
+  }
+
   if (IsMenuPopupHidingCaret()) {
     return false;
   }
 
   return true;
 }
 
 void nsCaret::SetCaretReadOnly(bool inMakeReadonly)
--- a/layout/base/nsCaret.h
+++ b/layout/base/nsCaret.h
@@ -225,11 +225,15 @@ protected:
      * the selection is not collapsed.
      */
     bool                  mShowDuringSelection;
     /**
      * mIgnoreUserModify is true when the caret should be shown even when
      * it's in non-user-modifiable content.
      */
     bool                  mIgnoreUserModify;
+
+    // Preference
+    static bool sSelectionCaretEnabled;
+    static bool sSelectionCaretsAffectCaret;
 };
 
 #endif //nsCaret_h__
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -894,16 +894,17 @@ PresShell::Init(nsIDocument* aDocument,
 
   // setup the preference style rules (no forced reflow), and do it
   // before creating any frames.
   SetPreferenceStyleRules(false);
 
   if (TouchCaretPrefEnabled() && !AccessibleCaretEnabled()) {
     // Create touch caret handle
     mTouchCaret = new TouchCaret(this);
+    mTouchCaret->Init();
   }
 
   if (SelectionCaretPrefEnabled() && !AccessibleCaretEnabled()) {
     // Create selection caret handle
     mSelectionCarets = new SelectionCarets(this);
     mSelectionCarets->Init();
   }
 
@@ -2549,16 +2550,36 @@ PresShell::CheckVisibilityContent(nsICon
     return NS_ERROR_INVALID_ARG;
   }
 
   *aRetval = false;
   DoCheckVisibility(mPresContext, aNode, aStartOffset, aEndOffset, aRetval);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+PresShell::GetSelectionCaretsVisibility(bool* aOutVisibility)
+{
+  *aOutVisibility = (SelectionCaretPrefEnabled() && mSelectionCarets->GetVisibility());
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresShell::SetSelectionCaretsVisibility(bool aVisibility)
+{
+  if (SelectionCaretPrefEnabled() && mSelectionCarets) {
+    if (aVisibility) {
+      mSelectionCarets->UpdateSelectionCarets();
+    } else {
+      mSelectionCarets->SetVisibility(false);
+    }
+  }
+  return NS_OK;
+}
+
 //end implementations nsISelectionController
 
 nsIFrame*
 nsIPresShell::GetRootFrameExternal() const
 {
   return mFrameConstructor->GetRootFrame();
 }
 
--- a/layout/base/nsPresShell.h
+++ b/layout/base/nsPresShell.h
@@ -274,16 +274,19 @@ public:
   NS_IMETHOD ScrollCharacter(bool aRight) override;
   NS_IMETHOD CompleteScroll(bool aForward) override;
   NS_IMETHOD CompleteMove(bool aForward, bool aExtend) override;
   NS_IMETHOD SelectAll() override;
   NS_IMETHOD CheckVisibility(nsIDOMNode *node, int16_t startOffset, int16_t EndOffset, bool *_retval) override;
   virtual nsresult CheckVisibilityContent(nsIContent* aNode, int16_t aStartOffset,
                                           int16_t aEndOffset, bool* aRetval) override;
 
+  NS_IMETHOD GetSelectionCaretsVisibility(bool* aOutVisibility) override;
+  NS_IMETHOD SetSelectionCaretsVisibility(bool aVisibility) override;
+
   // nsIDocumentObserver
   NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE
   NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE
   NS_DECL_NSIDOCUMENTOBSERVER_BEGINLOAD
   NS_DECL_NSIDOCUMENTOBSERVER_ENDLOAD
   NS_DECL_NSIDOCUMENTOBSERVER_CONTENTSTATECHANGED
   NS_DECL_NSIDOCUMENTOBSERVER_DOCUMENTSTATESCHANGED
   NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETADDED
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -218,16 +218,19 @@ pref("extensions.getLocales.get.url", ""
 pref("extensions.compatability.locales.buildid", "0");
 
 /* blocklist preferences */
 pref("extensions.blocklist.enabled", true);
 pref("extensions.blocklist.interval", 86400);
 pref("extensions.blocklist.url", "https://blocklist.addons.mozilla.org/blocklist/3/%APP_ID%/%APP_VERSION%/%PRODUCT%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%PING_COUNT%/%TOTAL_PING_COUNT%/%DAYS_SINCE_LAST_PING%/");
 pref("extensions.blocklist.detailsURL", "https://www.mozilla.com/%LOCALE%/blocklist/");
 
+/* Don't let XPIProvider install distribution add-ons; we do our own thing on mobile. */
+pref("extensions.installDistroAddons", false);
+
 /* block popups by default, and notify the user about blocked popups */
 pref("dom.disable_open_during_load", true);
 pref("privacy.popups.showBrowserMessage", true);
 
 /* disable opening windows with the dialog feature */
 pref("dom.disable_window_open_dialog_feature", true);
 pref("dom.disable_window_showModalDialog", true);
 pref("dom.disable_window_print", true);
@@ -869,10 +872,35 @@ pref("reader.toolbar.vertical", false);
 
 // Whether or not to display buttons related to reading list in reader view.
 pref("browser.readinglist.enabled", true);
 
 // Telemetry settings.
 // Whether to use the unified telemetry behavior, requires a restart.
 pref("toolkit.telemetry.unified", false);
 
+// Turn off selection caret by default
+pref("selectioncaret.enabled", false);
+
 // Selection carets never fall-back to internal LongTap detector.
 pref("selectioncaret.detects.longtap", false);
+
+// Selection carets override caret visibility.
+pref("selectioncaret.visibility.affectscaret", true);
+
+// Selection caret visibility observes composition
+// selections generated by soft keyboard managers.
+pref("selectioncaret.observes.compositions", true);
+
+// Turn off touch caret by default.
+pref("touchcaret.enabled", false);
+
+// TouchCaret never auto-hides.
+pref("touchcaret.expiration.time", 0);
+
+// Touch caret stays visible under a wider range of conditions
+// than the default b2g. We can display the caret in empty editables
+// for example, and do not auto-hide until loss of focus.
+pref("touchcaret.extendedvisibility", true);
+
+// The TouchCaret and the SelectionCarets will indicate when the
+// TextSelection actionbar is to be openned or closed.
+pref("caret.manages-android-actionbar", true);
--- a/mobile/android/base/Tab.java
+++ b/mobile/android/base/Tab.java
@@ -18,29 +18,31 @@ import org.json.JSONObject;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.URLMetadata;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.LoadFaviconTask;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.favicons.RemoteFavicon;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.Layer;
+import org.mozilla.gecko.mozglue.RobocopTarget;
 import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.ContentResolver;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.drawable.BitmapDrawable;
 import android.os.Build;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.widget.Toast;
+import org.mozilla.gecko.widget.SiteLogins;
 
 public class Tab {
     private static final String LOGTAG = "GeckoTab";
 
     private static Pattern sColorPattern;
     private final int mId;
     private final BrowserDB mDB;
     private long mLastUsed;
@@ -52,16 +54,17 @@ public class Tab {
     private String mFaviconUrl;
     private String mApplicationId; // Intended to be null after explicit user action.
 
     // The set of all available Favicons for this tab, sorted by attractiveness.
     final TreeSet<RemoteFavicon> mAvailableFavicons = new TreeSet<>();
     private boolean mHasFeeds;
     private boolean mHasOpenSearch;
     private final SiteIdentity mSiteIdentity;
+    private SiteLogins mSiteLogins;
     private BitmapDrawable mThumbnail;
     private final int mParentId;
     private final boolean mExternal;
     private boolean mBookmark;
     private boolean mIsInReadingList;
     private int mFaviconLoadId;
     private String mContentType;
     private boolean mHasTouchListeners;
@@ -139,16 +142,17 @@ public class Tab {
     private ContentResolver getContentResolver() {
         return mAppContext.getContentResolver();
     }
 
     public void onDestroy() {
         Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.CLOSED);
     }
 
+    @RobocopTarget
     public int getId() {
         return mId;
     }
 
     public synchronized void onChange() {
         mLastUsed = System.currentTimeMillis();
     }
 
@@ -275,16 +279,20 @@ public class Tab {
     public boolean hasOpenSearch() {
         return mHasOpenSearch;
     }
 
     public SiteIdentity getSiteIdentity() {
         return mSiteIdentity;
     }
 
+    public SiteLogins getSiteLogins() {
+        return mSiteLogins;
+    }
+
     public boolean isBookmark() {
         return mBookmark;
     }
 
     public boolean isInReadingList() {
         return mIsInReadingList;
     }
 
@@ -489,16 +497,20 @@ public class Tab {
     public void setHasOpenSearch(boolean hasOpenSearch) {
         mHasOpenSearch = hasOpenSearch;
     }
 
     public void updateIdentityData(JSONObject identityData) {
         mSiteIdentity.update(identityData);
     }
 
+    public void setSiteLogins(SiteLogins siteLogins) {
+        mSiteLogins = siteLogins;
+    }
+
     void updateBookmark() {
         if (getURL() == null) {
             return;
         }
 
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
@@ -686,16 +698,17 @@ public class Tab {
 
         setContentType(message.getString("contentType"));
         updateUserRequested(message.getString("userRequested"));
         mBaseDomain = message.optString("baseDomain");
 
         setHasFeeds(false);
         setHasOpenSearch(false);
         mSiteIdentity.reset();
+        setSiteLogins(null);
         setZoomConstraints(new ZoomConstraints(true));
         setHasTouchListeners(false);
         setBackgroundColor(DEFAULT_BACKGROUND_COLOR);
         setErrorType(ErrorType.NONE);
         setLoadProgressIfLoading(LOAD_PROGRESS_LOCATION_CHANGE);
 
         Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.LOCATION_CHANGE, oldUrl);
     }
--- a/mobile/android/base/TextSelection.java
+++ b/mobile/android/base/TextSelection.java
@@ -32,16 +32,17 @@ import org.json.JSONObject;
 import java.util.Timer;
 import java.util.TimerTask;
 
 import android.util.Log;
 import android.view.View;
 
 class TextSelection extends Layer implements GeckoEventListener {
     private static final String LOGTAG = "GeckoTextSelection";
+    private static final int SHUTDOWN_DELAY_MS = 250;
 
     private final TextSelectionHandle anchorHandle;
     private final TextSelectionHandle caretHandle;
     private final TextSelectionHandle focusHandle;
 
     private final DrawListener mDrawListener;
     private boolean mDraggingHandles;
 
@@ -86,26 +87,32 @@ class TextSelection extends Layer implem
             }
         };
 
         // Only register listeners if we have valid start/middle/end handles
         if (anchorHandle == null || caretHandle == null || focusHandle == null) {
             Log.e(LOGTAG, "Failed to initialize text selection because at least one handle is null");
         } else {
             EventDispatcher.getInstance().registerGeckoThreadListener(this,
+                "TextSelection:ActionbarInit",
+                "TextSelection:ActionbarStatus",
+                "TextSelection:ActionbarUninit",
                 "TextSelection:ShowHandles",
                 "TextSelection:HideHandles",
                 "TextSelection:PositionHandles",
                 "TextSelection:Update",
                 "TextSelection:DraggingHandle");
         }
     }
 
     void destroy() {
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+            "TextSelection:ActionbarInit",
+            "TextSelection:ActionbarStatus",
+            "TextSelection:ActionbarUninit",
             "TextSelection:ShowHandles",
             "TextSelection:HideHandles",
             "TextSelection:PositionHandles",
             "TextSelection:Update",
             "TextSelection:DraggingHandle");
     }
 
     private TextSelectionHandle getHandle(String name) {
@@ -162,17 +169,17 @@ class TextSelection extends Layer implem
                         // Remove draw-listener and text selection layer
                         LayerView layerView = GeckoAppShell.getLayerView();
                         if (layerView != null) {
                             layerView.removeDrawListener(mDrawListener);
                             layerView.removeLayer(TextSelection.this);
                         }
 
                         mActionModeTimerTask = new ActionModeTimerTask();
-                        mActionModeTimer.schedule(mActionModeTimerTask, 250);
+                        mActionModeTimer.schedule(mActionModeTimerTask, SHUTDOWN_DELAY_MS);
 
                         anchorHandle.setVisibility(View.GONE);
                         caretHandle.setVisibility(View.GONE);
                         focusHandle.setVisibility(View.GONE);
 
                     } else if (event.equals("TextSelection:PositionHandles")) {
                         final JSONArray positions = message.getJSONArray("positions");
                         for (int i=0; i < positions.length(); i++) {
@@ -180,17 +187,38 @@ class TextSelection extends Layer implem
                             final int left = position.getInt("left");
                             final int top = position.getInt("top");
                             final boolean rtl = position.getBoolean("rtl");
 
                             TextSelectionHandle handle = getHandle(position.getString("handle"));
                             handle.setVisibility(position.getBoolean("hidden") ? View.GONE : View.VISIBLE);
                             handle.positionFromGecko(left, top, rtl);
                         }
+
+                    } else if (event.equals("TextSelection:ActionbarInit")) {
+                        // Init / Open the action bar. Note the current selectionID,
+                        // cancel any pending actionBar close.
+                        selectionID = message.getString("selectionID");
+                        mCurrentItems = null;
+                        if (mActionModeTimerTask != null) {
+                            mActionModeTimerTask.cancel();
+                        }
+
+                    } else if (event.equals("TextSelection:ActionbarStatus")) {
+                        // Update the actionBar actions as provided by Gecko.
+                        showActionMode(message.getJSONArray("actions"));
+
+                    } else if (event.equals("TextSelection:ActionbarUninit")) {
+                        // Uninit the actionbar. Schedule a cancellable close
+                        // action to avoid UI jank. (During SelectionAll for ex).
+                        mCurrentItems = null;
+                        mActionModeTimerTask = new ActionModeTimerTask();
+                        mActionModeTimer.schedule(mActionModeTimerTask, SHUTDOWN_DELAY_MS);
                     }
+
                 } catch (JSONException e) {
                     Log.e(LOGTAG, "JSON exception", e);
                 }
             }
         });
     }
 
     private void showActionMode(final JSONArray items) {
--- a/mobile/android/base/home/HomeConfig.java
+++ b/mobile/android/base/home/HomeConfig.java
@@ -102,25 +102,27 @@ public final class HomeConfig {
     public static class PanelConfig implements Parcelable {
         private final PanelType mType;
         private final String mTitle;
         private final String mId;
         private final LayoutType mLayoutType;
         private final List<ViewConfig> mViews;
         private final AuthConfig mAuthConfig;
         private final EnumSet<Flags> mFlags;
+        private final int mPosition;
 
         static final String JSON_KEY_TYPE = "type";
         static final String JSON_KEY_TITLE = "title";
         static final String JSON_KEY_ID = "id";
         static final String JSON_KEY_LAYOUT = "layout";
         static final String JSON_KEY_VIEWS = "views";
         static final String JSON_KEY_AUTH_CONFIG = "authConfig";
         static final String JSON_KEY_DEFAULT = "default";
         static final String JSON_KEY_DISABLED = "disabled";
+        static final String JSON_KEY_POSITION = "position";
 
         public enum Flags {
             DEFAULT_PANEL,
             DISABLED_PANEL
         }
 
         public PanelConfig(JSONObject json) throws JSONException, IllegalArgumentException {
             final String panelType = json.optString(JSON_KEY_TYPE, null);
@@ -166,32 +168,35 @@ public final class HomeConfig {
             if (json.optBoolean(JSON_KEY_DEFAULT, false)) {
                 mFlags.add(Flags.DEFAULT_PANEL);
             }
 
             if (json.optBoolean(JSON_KEY_DISABLED, false)) {
                 mFlags.add(Flags.DISABLED_PANEL);
             }
 
+            mPosition = json.optInt(JSON_KEY_POSITION, -1);
+
             validate();
         }
 
         @SuppressWarnings("unchecked")
         public PanelConfig(Parcel in) {
             mType = (PanelType) in.readParcelable(getClass().getClassLoader());
             mTitle = in.readString();
             mId = in.readString();
             mLayoutType = (LayoutType) in.readParcelable(getClass().getClassLoader());
 
             mViews = new ArrayList<ViewConfig>();
             in.readTypedList(mViews, ViewConfig.CREATOR);
 
             mAuthConfig = (AuthConfig) in.readParcelable(getClass().getClassLoader());
 
             mFlags = (EnumSet<Flags>) in.readSerializable();
+            mPosition = in.readInt();
 
             validate();
         }
 
         public PanelConfig(PanelConfig panelConfig) {
             mType = panelConfig.mType;
             mTitle = panelConfig.mTitle;
             mId = panelConfig.mId;
@@ -202,37 +207,39 @@ public final class HomeConfig {
             if (viewConfigs != null) {
                 for (ViewConfig viewConfig : viewConfigs) {
                     mViews.add(new ViewConfig(viewConfig));
                 }
             }
 
             mAuthConfig = panelConfig.mAuthConfig;
             mFlags = panelConfig.mFlags.clone();
+            mPosition = panelConfig.mPosition;
 
             validate();
         }
 
         public PanelConfig(PanelType type, String title, String id) {
             this(type, title, id, EnumSet.noneOf(Flags.class));
         }
 
         public PanelConfig(PanelType type, String title, String id, EnumSet<Flags> flags) {
-            this(type, title, id, null, null, null, flags);
+            this(type, title, id, null, null, null, flags, -1);
         }
 
         public PanelConfig(PanelType type, String title, String id, LayoutType layoutType,
-                List<ViewConfig> views, AuthConfig authConfig, EnumSet<Flags> flags) {
+                List<ViewConfig> views, AuthConfig authConfig, EnumSet<Flags> flags, int position) {
             mType = type;
             mTitle = title;
             mId = id;
             mLayoutType = layoutType;
             mViews = views;
             mAuthConfig = authConfig;
             mFlags = flags;
+            mPosition = position;
 
             validate();
         }
 
         private void validate() {
             if (mType == null) {
                 throw new IllegalArgumentException("Can't create PanelConfig with null type");
             }
@@ -309,16 +316,20 @@ public final class HomeConfig {
                 mFlags.remove(Flags.DISABLED_PANEL);
             }
         }
 
         public AuthConfig getAuthConfig() {
             return mAuthConfig;
         }
 
+        public int getPosition() {
+            return mPosition;
+        }
+
         public JSONObject toJSON() throws JSONException {
             final JSONObject json = new JSONObject();
 
             json.put(JSON_KEY_TYPE, mType.toString());
             json.put(JSON_KEY_TITLE, mTitle);
             json.put(JSON_KEY_ID, mId);
 
             if (mLayoutType != null) {
@@ -345,16 +356,18 @@ public final class HomeConfig {
             if (mFlags.contains(Flags.DEFAULT_PANEL)) {
                 json.put(JSON_KEY_DEFAULT, true);
             }
 
             if (mFlags.contains(Flags.DISABLED_PANEL)) {
                 json.put(JSON_KEY_DISABLED, true);
             }
 
+            json.put(JSON_KEY_POSITION, mPosition);
+
             return json;
         }
 
         @Override
         public boolean equals(Object o) {
             if (o == null) {
                 return false;
             }
@@ -385,16 +398,17 @@ public final class HomeConfig {
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeParcelable(mType, 0);
             dest.writeString(mTitle);
             dest.writeString(mId);
             dest.writeParcelable(mLayoutType, 0);
             dest.writeTypedList(mViews);
             dest.writeParcelable(mAuthConfig, 0);
             dest.writeSerializable(mFlags);
+            dest.writeInt(mPosition);
         }
 
         public static final Creator<PanelConfig> CREATOR = new Creator<PanelConfig>() {
             @Override
             public PanelConfig createFromParcel(final Parcel in) {
                 return new PanelConfig(in);
             }
 
@@ -1267,17 +1281,23 @@ public final class HomeConfig {
                 throw new IllegalStateException("Can't install a disabled panel: " + panelConfig.getId());
             }
 
             boolean installed = false;
 
             final String id = panelConfig.getId();
             if (!mConfigMap.containsKey(id)) {
                 mConfigMap.put(id, panelConfig);
-                mConfigOrder.add(id);
+
+                final int position = panelConfig.getPosition();
+                if (position < 0 || position >= mConfigOrder.size()) {
+                    mConfigOrder.add(id);
+                } else {
+                    mConfigOrder.add(position, id);
+                }
 
                 mEnabledCount++;
                 if (mEnabledCount == 1 || panelConfig.isDefault()) {
                     setDefault(panelConfig.getId());
                 }
 
                 installed = true;
 
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -374,16 +374,21 @@ size. -->
      where normally a username would be displayed. In this case, no username was found, and this placeholder
      contains brackets to indicate this is not actually a username, but rather a placeholder -->
 <!ENTITY doorhanger_login_no_username "[No username]">
 <!ENTITY doorhanger_login_edit_title "Edit login">
 <!ENTITY doorhanger_login_edit_username_hint "Username">
 <!ENTITY doorhanger_login_edit_password_hint "Password">
 <!ENTITY doorhanger_login_edit_toggle "Show password">
 <!ENTITY doorhanger_login_edit_toast_error "Failed to save login">
+<!ENTITY doorhanger_login_select_message "Copy password from &formatS;?">
+<!ENTITY doorhanger_login_select_toast_copy "Password copied to clipboard">
+<!ENTITY doorhanger_login_select_toast_copy_error "Couldn\'t copy password">
+<!ENTITY doorhanger_login_select_action_text "Select another login">
+<!ENTITY doorhanger_login_select_title "Copy password from">
 
 <!ENTITY pref_titlebar_mode "Title bar">
 <!ENTITY pref_titlebar_mode_title "Show page title">
 <!ENTITY pref_titlebar_mode_url "Show page address">
 
 <!-- Localization note (pref_scroll_title_bar2): Label for setting that controls
      whether or not the dynamic toolbar is enabled. -->
 <!ENTITY pref_scroll_title_bar2 "Full-screen browsing">
@@ -434,16 +439,17 @@ size. -->
 <!ENTITY button_ok "OK">
 <!ENTITY button_cancel "Cancel">
 <!ENTITY button_yes "Yes">
 <!ENTITY button_no "No">
 <!ENTITY button_clear_data "Clear data">
 <!ENTITY button_set "Set">
 <!ENTITY button_clear "Clear">
 <!ENTITY button_remember "Remember">
+<!ENTITY button_copy "Copy">
 
 <!ENTITY firstrun_panel_title_welcome "Welcome">
 
 <!ENTITY home_top_sites_title "Top Sites">
 <!-- Localization note (home_top_sites_add): This string is used as placeholder
      text underneath empty thumbnails in the Top Sites page on about:home. -->
 <!ENTITY home_top_sites_add "Add a site">
 
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -513,16 +513,17 @@ gbjar.sources += [
     'widget/FlowLayout.java',
     'widget/GeckoActionProvider.java',
     'widget/GeckoPopupMenu.java',
     'widget/GeckoSwipeRefreshLayout.java',
     'widget/GeckoViewFlipper.java',
     'widget/IconTabWidget.java',
     'widget/LoginDoorHanger.java',
     'widget/ResizablePathDrawable.java',
+    'widget/SiteLogins.java',
     'widget/SquaredImageView.java',
     'widget/SwipeDismissListViewTouchListener.java',
     'widget/TabThumbnailWrapper.java',
     'widget/ThumbnailView.java',
     'widget/TwoWayView.java',
     'ZoomConstraints.java',
     'ZoomedView.java',
 ]
--- a/mobile/android/base/resources/layout/login_doorhanger.xml
+++ b/mobile/android/base/resources/layout/login_doorhanger.xml
@@ -30,17 +30,17 @@
 
             <TextView android:id="@+id/doorhanger_message"
                       android:focusable="true"
                       android:layout_width="match_parent"
                       android:layout_height="wrap_content"
                       android:paddingBottom="@dimen/doorhanger_section_padding_large"
                       android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/>
 
-            <TextView android:id="@+id/doorhanger_login"
+            <TextView android:id="@+id/doorhanger_link"
                       android:layout_width="match_parent"
                       android:layout_height="wrap_content"
                       android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
                       android:textColor="@color/link_blue"
                       android:paddingBottom="@dimen/doorhanger_section_padding_large"
                       android:visibility="gone"/>
 
         </LinearLayout>
--- a/mobile/android/base/resources/layout/site_identity.xml
+++ b/mobile/android/base/resources/layout/site_identity.xml
@@ -29,57 +29,52 @@
             <LinearLayout android:id="@+id/site_identity_known_container"
                           android:layout_width="match_parent"
                           android:layout_height="wrap_content"
                           android:visibility="gone"
                           android:orientation="vertical">
 
                 <TextView android:layout_width="wrap_content"
                           android:layout_height="wrap_content"
-                          android:textSize="14sp"
-                          android:textColor="@color/placeholder_active_grey"
+                          android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"
                           android:text="@string/identity_connected_to"/>
 
                 <TextView android:id="@+id/host"
                           android:layout_width="wrap_content"
                           android:layout_height="wrap_content"
-                          android:textSize="20sp"
-                          android:textColor="@color/placeholder_active_grey"
+                          android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
                           android:textStyle="bold"/>
 
                 <TextView android:id="@+id/owner_label"
                           android:layout_width="wrap_content"
                           android:layout_height="wrap_content"
-                          android:textSize="14sp"
-                          android:textColor="@color/placeholder_active_grey"
+                          android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"
                           android:text="@string/identity_run_by"
-                          android:paddingTop="12dip"/>
+                          android:paddingTop="@dimen/doorhanger_section_padding_small"/>
 
                 <TextView android:id="@+id/owner"
                           android:layout_width="wrap_content"
                           android:layout_height="wrap_content"
-                          android:textColor="@color/placeholder_active_grey"
-                          android:textSize="16sp"
+                          android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
                           android:textStyle="bold"/>
 
                 <TextView android:id="@+id/verifier"
                           android:layout_width="wrap_content"
                           android:layout_height="wrap_content"
-                          android:textSize="14sp"
-                          android:textColor="@color/placeholder_active_grey"
-                          android:paddingTop="12dip"/>
+                          android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"
+                          android:paddingTop="@dimen/doorhanger_section_padding_small"/>
 
             </LinearLayout>
             <TextView android:id="@+id/site_settings_link"
                       android:layout_width="match_parent"
                       android:layout_height="wrap_content"
-                      android:textAppearance="@style/TextAppearance.DoorHanger.Small"
+                      android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
                       android:textColor="@color/link_blue"
-                      android:paddingTop="@dimen/doorhanger_section_padding_small"
-                      android:paddingBottom="@dimen/doorhanger_section_padding_small"
+                      android:paddingTop="@dimen/doorhanger_section_padding_large"
+                      android:paddingBottom="@dimen/doorhanger_padding"
                       android:text="@string/contextmenu_site_settings"/>
          </LinearLayout>
     </LinearLayout>
 
     <View android:id="@+id/divider_doorhanger"
           android:layout_width="match_parent"
           android:layout_height="1dp"
           android:background="@color/divider_light"
--- a/mobile/android/base/resources/layout/site_identity_unknown.xml
+++ b/mobile/android/base/resources/layout/site_identity_unknown.xml
@@ -7,20 +7,18 @@
               android:id="@+id/site_identity_unknown_container"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:orientation="vertical"
               android:visibility="gone">
 
     <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
-              android:textSize="14sp"
-              android:textColor="@color/placeholder_active_grey"
+              android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
               android:text="@string/identity_no_info"/>
 
     <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
-              android:textSize="14sp"
-              android:textColor="@color/placeholder_active_grey"
+              android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
               android:text="@string/identity_not_encrypted"
-              android:paddingTop="12dip"/>
+              android:paddingTop="@dimen/doorhanger_section_padding_small"/>
 
 </LinearLayout>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -341,16 +341,21 @@
   <string name="contextmenu_add_search_engine">&contextmenu_add_search_engine;</string>
 
   <string name="doorhanger_login_no_username">&doorhanger_login_no_username;</string>
   <string name="doorhanger_login_edit_title">&doorhanger_login_edit_title;</string>
   <string name="doorhanger_login_edit_username_hint">&doorhanger_login_edit_username_hint;</string>
   <string name="doorhanger_login_edit_password_hint">&doorhanger_login_edit_password_hint;</string>
   <string name="doorhanger_login_edit_toggle">&doorhanger_login_edit_toggle;</string>
   <string name="doorhanger_login_edit_toast_error">&doorhanger_login_edit_toast_error;</string>
+  <string name="doorhanger_login_select_message">&doorhanger_login_select_message;</string>
+  <string name="doorhanger_login_select_toast_copy">&doorhanger_login_select_toast_copy;</string>
+  <string name="doorhanger_login_select_toast_copy_error">&doorhanger_login_select_toast_copy_error;</string>
+  <string name="doorhanger_login_select_action_text">&doorhanger_login_select_action_text;</string>
+  <string name="doorhanger_login_select_title">&doorhanger_login_select_title;</string>
 
   <string name="pref_titlebar_mode">&pref_titlebar_mode;</string>
   <string name="pref_titlebar_mode_title">&pref_titlebar_mode_title;</string>
   <string name="pref_titlebar_mode_url">&pref_titlebar_mode_url;</string>
 
   <string name="pref_scroll_title_bar2">&pref_scroll_title_bar2;</string>
   <string name="pref_scroll_title_bar_summary">&pref_scroll_title_bar_summary;</string>
 
@@ -370,16 +375,17 @@
   <string name="button_ok">&button_ok;</string>
   <string name="button_cancel">&button_cancel;</string>
   <string name="button_clear_data">&button_clear_data;</string>
   <string name="button_set">&button_set;</string>
   <string name="button_clear">&button_clear;</string>
   <string name="button_yes">&button_yes;</string>
   <string name="button_no">&button_no;</string>
   <string name="button_remember">&button_remember;</string>
+  <string name="button_copy">&button_copy;</string>
 
   <string name="firstrun_panel_title_welcome">&firstrun_panel_title_welcome;</string>
 
   <string name="home_title">&home_title;</string>
   <string name="home_top_sites_title">&home_top_sites_title;</string>
   <string name="home_top_sites_add">&home_top_sites_add;</string>
   <string name="home_history_title">&home_history_title;</string>
   <string name="home_clear_history_button">&home_clear_history_button;</string>
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -849,16 +849,17 @@ public abstract class BrowserToolbar ext
     }
 
     public View getDoorHangerAnchor() {
         return urlDisplayLayout;
     }
 
     public void onDestroy() {
         Tabs.unregisterOnTabsChangedListener(this);
+        urlDisplayLayout.destroy();
     }
 
     public boolean openOptionsMenu() {
         if (!hasSoftMenuButton) {
             return false;
         }
 
         // Initialize the popup.
--- a/mobile/android/base/toolbar/SiteIdentityPopup.java
+++ b/mobile/android/base/toolbar/SiteIdentityPopup.java
@@ -1,76 +1,92 @@
 /* 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.toolbar;
 
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.widget.Toast;
+import org.json.JSONException;
+import org.json.JSONArray;
 import org.mozilla.gecko.AboutPages;
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.SiteIdentity;
 import org.mozilla.gecko.SiteIdentity.SecurityMode;
 import org.mozilla.gecko.SiteIdentity.MixedMode;
 import org.mozilla.gecko.SiteIdentity.TrackingMode;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
+import org.mozilla.gecko.util.GeckoEventListener;
+import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.AnchoredPopup;
 import org.mozilla.gecko.widget.DoorHanger;
 import org.mozilla.gecko.widget.DoorHanger.OnButtonClickListener;
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 import org.mozilla.gecko.widget.DoorhangerConfig;
+import org.mozilla.gecko.widget.SiteLogins;
 
 /**
  * SiteIdentityPopup is a singleton class that displays site identity data in
  * an arrow panel popup hanging from the lock icon in the browser toolbar.
  */
-public class SiteIdentityPopup extends AnchoredPopup {
-    public static enum ButtonType { DISABLE, ENABLE, KEEP_BLOCKING };
+public class SiteIdentityPopup extends AnchoredPopup implements GeckoEventListener {
+
+    public static enum ButtonType { DISABLE, ENABLE, KEEP_BLOCKING, CANCEL, COPY };
 
     private static final String LOGTAG = "GeckoSiteIdentityPopup";
 
     private static final String MIXED_CONTENT_SUPPORT_URL =
         "https://support.mozilla.org/kb/how-does-insecure-content-affect-safety-android";
 
     private static final String TRACKING_CONTENT_SUPPORT_URL =
         "https://support.mozilla.org/kb/firefox-android-tracking-protection";
 
+    // Placeholder string.
+    private final static String FORMAT_S = "%s";
+
     private SiteIdentity mSiteIdentity;
 
     private LinearLayout mIdentity;
 
     private LinearLayout mIdentityKnownContainer;
     private LinearLayout mIdentityUnknownContainer;
 
     private TextView mHost;
     private TextView mOwnerLabel;
     private TextView mOwner;
     private TextView mVerifier;
 
     private View mDivider;
 
     private DoorHanger mMixedContentNotification;
     private DoorHanger mTrackingContentNotification;
+    private DoorHanger mSelectLoginDoorhanger;
 
-    private final OnButtonClickListener mButtonClickListener;
+    private final OnButtonClickListener mContentButtonClickListener;
 
     public SiteIdentityPopup(Context context) {
         super(context);
 
-        mButtonClickListener = new PopupButtonListener();
+        mContentButtonClickListener = new ContentNotificationButtonListener();
+        EventDispatcher.getInstance().registerGeckoThreadListener(this, "Doorhanger:Logins");
     }
 
     @Override
     protected void init() {
         super.init();
 
         // Make the popup focusable so it doesn't inadvertently trigger click events elsewhere
         // which may reshow the popup (see bug 785156)
@@ -109,16 +125,143 @@ public class SiteIdentityPopup extends A
         final boolean isIdentityKnown = (siteIdentity.getSecurityMode() != SecurityMode.UNKNOWN);
         toggleIdentityKnownContainerVisibility(isIdentityKnown);
 
         if (isIdentityKnown) {
             updateIdentityInformation(siteIdentity);
         }
     }
 
+    @Override
+    public void handleMessage(String event, JSONObject geckoObject) {
+        if ("Doorhanger:Logins".equals(event)) {
+            try {
+                final Tab selectedTab = Tabs.getInstance().getSelectedTab();
+                if (selectedTab != null) {
+                    final JSONObject data = geckoObject.getJSONObject("data");
+                    addLoginsToTab(data);
+                }
+                if (isShowing()) {
+                    addSelectLoginDoorhanger(selectedTab);
+                }
+            } catch (JSONException e) {
+                Log.e(LOGTAG, "Error accessing logins in Doorhanger:Logins message", e);
+            }
+        }
+    }
+
+    private void addLoginsToTab(JSONObject data) throws JSONException {
+        final JSONObject titleObj = data.getJSONObject("title");
+        final JSONArray logins = data.getJSONArray("logins");
+
+        final SiteLogins siteLogins = new SiteLogins(titleObj, logins);
+        Tabs.getInstance().getSelectedTab().setSiteLogins(siteLogins);
+    }
+
+    private void addSelectLoginDoorhanger(Tab tab) throws JSONException {
+        final SiteLogins siteLogins = tab.getSiteLogins();
+        if (siteLogins == null) {
+            return;
+        }
+
+        final JSONArray logins = siteLogins.getLogins();
+        if (logins.length() == 0) {
+            return;
+        }
+
+        final JSONObject login = (JSONObject) logins.get(0);
+
+        // Create button click listener for copying a password to the clipboard.
+        final OnButtonClickListener buttonClickListener = new OnButtonClickListener() {
+            @Override
+            public void onButtonClick(JSONObject response, DoorHanger doorhanger) {
+                try {
+                    final int buttonId = response.getInt("callback");
+                    if (buttonId == ButtonType.COPY.ordinal()) {
+                        final ClipboardManager manager = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
+                        String password;
+                        if (response.has("password")) {
+                            // Click listener being called from List Dialog.
+                            password = response.optString("password");
+                        } else {
+                            password = login.getString("password");
+                        }
+                        if (AppConstants.Versions.feature11Plus) {
+                            manager.setPrimaryClip(ClipData.newPlainText("password", password));
+                        } else {
+                            manager.setText(password);
+                        }
+                        Toast.makeText(mContext, R.string.doorhanger_login_select_toast_copy, Toast.LENGTH_SHORT).show();
+                    }
+                    dismiss();
+                } catch (JSONException e) {
+                    Log.e(LOGTAG, "Error handling Select login button click", e);
+                    Toast.makeText(mContext, R.string.doorhanger_login_select_toast_copy_error, Toast.LENGTH_SHORT).show();
+                }
+            }
+        };
+
+        final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.LOGIN, buttonClickListener);
+
+        // Set buttons.
+        config.appendButton(mContext.getString(R.string.button_cancel), ButtonType.CANCEL.ordinal());
+        config.appendButton(mContext.getString(R.string.button_copy), ButtonType.COPY.ordinal());
+
+        // Set message.
+        String username = ((JSONObject) logins.get(0)).getString("username");
+        if (TextUtils.isEmpty(username)) {
+            username = mContext.getString(R.string.doorhanger_login_no_username);
+        }
+
+        final String message = mContext.getString(R.string.doorhanger_login_select_message).replace(FORMAT_S, username);
+        config.setMessage(message);
+
+        // Set options.
+        final JSONObject options = new JSONObject();
+        final JSONObject titleObj = siteLogins.getTitle();
+        options.put("title", titleObj);
+
+        // Add action text only if there are other logins to select.
+        if (logins.length() > 1) {
+
+            final JSONObject actionText = new JSONObject();
+            actionText.put("type", "SELECT");
+
+            final JSONObject bundle = new JSONObject();
+            bundle.put("logins", logins);
+
+            actionText.put("bundle", bundle);
+            options.put("actionText", actionText);
+        }
+
+        config.setOptions(options);
+
+        ThreadUtils.postToUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (!mInflated) {
+                    init();
+                }
+
+                removeSelectLoginDoorhanger();
+
+                mSelectLoginDoorhanger = DoorHanger.Get(mContext, config);
+                mContent.addView(mSelectLoginDoorhanger);
+                mDivider.setVisibility(View.VISIBLE);
+            }
+        });
+    }
+
+    private void removeSelectLoginDoorhanger() {
+        if (mSelectLoginDoorhanger != null) {
+            mContent.removeView(mSelectLoginDoorhanger);
+            mSelectLoginDoorhanger = null;
+        }
+    }
+
     private void toggleIdentityKnownContainerVisibility(final boolean isIdentityKnown) {
         if (isIdentityKnown) {
             mIdentityKnownContainer.setVisibility(View.VISIBLE);
             mIdentityUnknownContainer.setVisibility(View.GONE);
         } else {
             mIdentityKnownContainer.setVisibility(View.GONE);
             mIdentityUnknownContainer.setVisibility(View.VISIBLE);
         }
@@ -147,50 +290,49 @@ public class SiteIdentityPopup extends A
         final String encrypted = siteIdentity.getEncrypted();
         mVerifier.setText(verifier + "\n" + encrypted);
     }
 
     private void addMixedContentNotification(boolean blocked) {
         // Remove any existing mixed content notification.
         removeMixedContentNotification();
 
-        final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.MIXED_CONTENT, mButtonClickListener);
+        final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.MIXED_CONTENT, mContentButtonClickListener);
         int icon;
         if (blocked) {
             icon = R.drawable.shield_enabled_doorhanger;
             config.setMessage(mContext.getString(R.string.blocked_mixed_content_message_top) + "\n\n" +
                       mContext.getString(R.string.blocked_mixed_content_message_bottom));
         } else {
             icon = R.drawable.shield_disabled_doorhanger;
             config.setMessage(mContext.getString(R.string.loaded_mixed_content_message));
         }
 
         config.setLink(mContext.getString(R.string.learn_more), MIXED_CONTENT_SUPPORT_URL, "\n\n");
         addNotificationButtons(config, blocked);
 
         mMixedContentNotification = DoorHanger.Get(mContext, config);
         mMixedContentNotification.setIcon(icon);
 
-
         mContent.addView(mMixedContentNotification);
         mDivider.setVisibility(View.VISIBLE);
     }
 
     private void removeMixedContentNotification() {
         if (mMixedContentNotification != null) {
             mContent.removeView(mMixedContentNotification);
             mMixedContentNotification = null;
         }
     }
 
     private void addTrackingContentNotification(boolean blocked) {
         // Remove any existing tracking content notification.
         removeTrackingContentNotification();
 
-        final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.TRACKING, mButtonClickListener);
+        final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.TRACKING, mContentButtonClickListener);
 
         int icon;
         if (blocked) {
             icon = R.drawable.shield_enabled_doorhanger;
             config.setMessage(mContext.getString(R.string.blocked_tracking_content_message_top) + "\n\n" +
                       mContext.getString(R.string.blocked_tracking_content_message_bottom));
         } else {
             icon = R.drawable.shield_disabled_doorhanger;
@@ -255,16 +397,22 @@ public class SiteIdentityPopup extends A
             addMixedContentNotification(mixedMode == MixedMode.MIXED_CONTENT_BLOCKED);
         }
 
         final TrackingMode trackingMode = mSiteIdentity.getTrackingMode();
         if (trackingMode != TrackingMode.UNKNOWN) {
             addTrackingContentNotification(trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED);
         }
 
+        try {
+            addSelectLoginDoorhanger(selectedTab);
+        } catch (JSONException e) {
+            Log.e(LOGTAG, "Error adding selectLogin doorhanger", e);
+        }
+
         showDividers();
 
         super.show();
     }
 
     // Show the right dividers
     private void showDividers() {
         final int count = mContent.getChildCount();
@@ -284,25 +432,30 @@ public class SiteIdentityPopup extends A
             }
         }
 
         if (lastVisibleDoorHanger != null) {
             lastVisibleDoorHanger.hideDivider();
         }
     }
 
+    void destroy() {
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Doorhanger:Logins");
+    }
+
     @Override
     public void dismiss() {
         super.dismiss();
         removeMixedContentNotification();
         removeTrackingContentNotification();
+        removeSelectLoginDoorhanger();
         mDivider.setVisibility(View.GONE);
     }
 
-    private class PopupButtonListener implements OnButtonClickListener {
+    private class ContentNotificationButtonListener implements OnButtonClickListener {
         @Override
         public void onButtonClick(JSONObject response, DoorHanger doorhanger) {
             GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", response.toString());
             GeckoAppShell.sendEventToGecko(e);
             dismiss();
         }
     }
 }
--- a/mobile/android/base/toolbar/ToolbarDisplayLayout.java
+++ b/mobile/android/base/toolbar/ToolbarDisplayLayout.java
@@ -611,9 +611,13 @@ public class ToolbarDisplayLayout extend
     boolean dismissSiteIdentityPopup() {
         if (mSiteIdentityPopup != null && mSiteIdentityPopup.isShowing()) {
             mSiteIdentityPopup.dismiss();
             return true;
         }
 
         return false;
     }
+
+    void destroy() {
+        mSiteIdentityPopup.destroy();
+    }
 }
--- a/mobile/android/base/widget/DefaultDoorHanger.java
+++ b/mobile/android/base/widget/DefaultDoorHanger.java
@@ -1,17 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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.widget;
 
 import android.util.Log;
-import android.view.LayoutInflater;
 import android.widget.Button;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.prompts.PromptInput;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
@@ -107,21 +106,18 @@ public class DefaultDoorHanger extends D
         if (!TextUtils.isEmpty(checkBoxText)) {
             mCheckBox = (CheckBox) findViewById(R.id.doorhanger_checkbox);
             mCheckBox.setText(checkBoxText);
             mCheckBox.setVisibility(VISIBLE);
         }
     }
 
     @Override
-    protected Button createButtonInstance(final String text, final int id) {
-        final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
-        button.setText(text);
-
-        button.setOnClickListener(new Button.OnClickListener() {
+    protected OnClickListener makeOnButtonClickListener(final int id) {
+        return new Button.OnClickListener() {
             @Override
             public void onClick(View v) {
                 final JSONObject response = new JSONObject();
                 try {
                     // TODO: Bug 1149359 - Split this into each Doorhanger Type class.
                     switch (mType) {
                         case MIXED_CONTENT:
                             response.put("allowContent", (id == SiteIdentityPopup.ButtonType.DISABLE.ordinal()));
@@ -144,24 +140,23 @@ public class DefaultDoorHanger extends D
                             if (doorHangerInputs != null) {
                                 JSONObject inputs = new JSONObject();
                                 for (PromptInput input : doorHangerInputs) {
                                     inputs.put(input.getId(), input.getValue());
                                 }
                                 response.put("inputs", inputs);
                             }
                     }
-                    mOnButtonClickListener.onButtonClick(response, DefaultDoorHanger.this);
                 } catch (JSONException e) {
                     Log.e(LOGTAG, "Error creating onClick response", e);
                 }
+
+                mOnButtonClickListener.onButtonClick(response, DefaultDoorHanger.this);
             }
-        });
-
-        return button;
+        };
     }
 
     private void styleInput(PromptInput input, View view) {
         if (input instanceof PromptInput.MenulistInput) {
             styleDropdownInputs(input, view);
         }
         view.setPadding(0, 0, 0, mResources.getDimensionPixelSize(R.dimen.doorhanger_padding));
     }
--- a/mobile/android/base/widget/DoorHanger.java
+++ b/mobile/android/base/widget/DoorHanger.java
@@ -95,21 +95,16 @@ public abstract class DoorHanger extends
                 resource = R.layout.doorhanger;
         }
 
         LayoutInflater.from(context).inflate(resource, this);
         mDivider = findViewById(R.id.divider_doorhanger);
         mIcon = (ImageView) findViewById(R.id.doorhanger_icon);
         mMessage = (TextView) findViewById(R.id.doorhanger_message);
 
-        // TODO: Bug 1149359 - split this into DoorHanger subclasses.
-        if (type == Type.TRACKING || type == Type.MIXED_CONTENT) {
-            mMessage.setTextAppearance(getContext(), R.style.TextAppearance_DoorHanger_Small);
-        }
-
         mType = type;
 
         mButtonsContainer = (LinearLayout) findViewById(R.id.doorhanger_buttons);
         mOnButtonClickListener = config.getButtonClickListener();
 
         mDividerColor = mResources.getColor(R.color.divider_light);
         setOrientation(VERTICAL);
     }
@@ -127,17 +122,16 @@ public abstract class DoorHanger extends
         final long timeout = options.optLong("timeout");
         if (timeout > 0) {
             mTimeout = timeout;
         }
     }
 
     protected void setButtons(DoorhangerConfig config) {
         final JSONArray buttons = config.getButtons();
-        final OnButtonClickListener listener = config.getButtonClickListener();
         for (int i = 0; i < buttons.length(); i++) {
             try {
                 final JSONObject buttonObject = buttons.getJSONObject(i);
                 final String label = buttonObject.getString("label");
                 final int callbackId = buttonObject.getInt("callback");
                 addButtonToLayout(label, callbackId);
             } catch (JSONException e) {
                 Log.e(LOGTAG, "Error creating doorhanger button", e);
@@ -209,17 +203,24 @@ public abstract class DoorHanger extends
             divider.setOrientation(Divider.Orientation.VERTICAL);
             divider.setBackgroundColor(mDividerColor);
             mButtonsContainer.addView(divider);
         }
 
         mButtonsContainer.addView(button, sButtonParams);
     }
 
-    protected abstract Button createButtonInstance(String text, int id);
+    protected Button createButtonInstance(String text, int id) {
+        final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
+        button.setText(text);
+        button.setOnClickListener(makeOnButtonClickListener(id));
+        return button;
+    }
+
+    protected abstract OnClickListener makeOnButtonClickListener(final int id);
 
     /*
      * Checks with persistence and timeout options to see if it's okay to remove a doorhanger.
      *
      * @param isShowing Whether or not this doorhanger is currently visible to the user.
      *                 (e.g. the DoorHanger view might be VISIBLE, but its parent could be hidden)
      */
     public boolean shouldRemove(boolean isShowing) {
--- a/mobile/android/base/widget/LoginDoorHanger.java
+++ b/mobile/android/base/widget/LoginDoorHanger.java
@@ -2,16 +2,18 @@
  * 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.widget;
 
 import android.app.AlertDialog;
 import android.app.Dialog;
+import android.content.ClipData;
+import android.content.ClipboardManager;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.text.method.PasswordTransformationMethod;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -19,33 +21,36 @@ import android.widget.Button;
 import android.widget.CheckBox;
 import android.widget.CompoundButton;
 import android.widget.EditText;
 import android.widget.TextView;
 import android.widget.Toast;
 import ch.boye.httpclientandroidlib.util.TextUtils;
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.json.JSONArray;
+import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
+import org.mozilla.gecko.toolbar.SiteIdentityPopup;
 
 public class LoginDoorHanger extends DoorHanger {
     private static final String LOGTAG = "LoginDoorHanger";
-    private enum ActionType { EDIT };
+    private enum ActionType { EDIT, SELECT }
 
     private final TextView mTitle;
-    private final TextView mLogin;
+    private final TextView mLink;
     private int mCallbackID;
 
     public LoginDoorHanger(Context context, DoorhangerConfig config) {
         super(context, config, Type.LOGIN);
 
         mTitle = (TextView) findViewById(R.id.doorhanger_title);
-        mLogin = (TextView) findViewById(R.id.doorhanger_login);
+        mLink = (TextView) findViewById(R.id.doorhanger_link);
 
         loadConfig(config);
     }
 
     @Override
     protected void loadConfig(DoorhangerConfig config) {
         setOptions(config.getOptions());
         setMessage(config.getMessage());
@@ -83,58 +88,48 @@ public class LoginDoorHanger extends Doo
         final JSONObject actionText = options.optJSONObject("actionText");
         addActionText(actionText);
     }
 
     @Override
     protected Button createButtonInstance(final String text, final int id) {
         // HACK: Confirm button will the the rightmost/last button added. Bug 1147064 should add differentiation of the two.
         mCallbackID = id;
+        return super.createButtonInstance(text, id);
+    }
 
-        final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
-        button.setText(text);
-
-        button.setOnClickListener(new Button.OnClickListener() {
+    @Override
+    protected OnClickListener makeOnButtonClickListener(final int id) {
+        return new Button.OnClickListener() {
             @Override
             public void onClick(View v) {
                 final JSONObject response = new JSONObject();
                 try {
                     response.put("callback", id);
                 } catch (JSONException e) {
                     Log.e(LOGTAG, "Error making doorhanger response message", e);
                 }
                 mOnButtonClickListener.onButtonClick(response, LoginDoorHanger.this);
             }
-        });
-
-        return button;
+        };
     }
 
     /**
      * Add sub-text to the doorhanger and add the click action.
      *
      * If the parsing the action from the JSON throws, the text is left visible, but there is no
      * click action.
      * @param actionTextObj JSONObject containing blob for making an action.
      */
     private void addActionText(JSONObject actionTextObj) {
         if (actionTextObj == null) {
-            mLogin.setVisibility(View.GONE);
+            mLink.setVisibility(View.GONE);
             return;
         }
 
-        boolean hasUsername = true;
-        String text = actionTextObj.optString("text");
-        if (TextUtils.isEmpty(text)) {
-            hasUsername = false;
-            text = mResources.getString(R.string.doorhanger_login_no_username);
-        }
-        mLogin.setText(text);
-        mLogin.setVisibility(View.VISIBLE);
-
         // Make action.
         try {
             final JSONObject bundle = actionTextObj.getJSONObject("bundle");
             final ActionType type = ActionType.valueOf(actionTextObj.getString("type"));
             final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
 
             switch (type) {
                 case EDIT:
@@ -177,27 +172,73 @@ public class LoginDoorHanger extends Doo
                         }
                     });
                     builder.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
                         @Override
                         public void onClick(DialogInterface dialog, int which) {
                             dialog.dismiss();
                         }
                     });
+                    String text = actionTextObj.optString("text");
+                    if (TextUtils.isEmpty(text)) {
+                        text = mResources.getString(R.string.doorhanger_login_no_username);
+                    }
+                    mLink.setText(text);
+                    mLink.setVisibility(View.VISIBLE);
+                    break;
+
+                case SELECT:
+                    try {
+                        builder.setTitle(mResources.getString(R.string.doorhanger_login_select_title));
+                        final JSONArray logins = bundle.getJSONArray("logins");
+                        final int numLogins = logins.length();
+                        final CharSequence[] usernames = new CharSequence[numLogins];
+                        final String[] passwords = new String[numLogins];
+                        final String noUser = mResources.getString(R.string.doorhanger_login_no_username);
+                        for (int i = 0; i < numLogins; i++) {
+                            final JSONObject login = (JSONObject) logins.get(i);
+                            String user = login.getString("username");
+                            if (TextUtils.isEmpty(user)) {
+                                user = noUser;
+                            }
+                            usernames[i] = user;
+                            passwords[i] = login.getString("password");
+                        }
+                        builder.setItems(usernames, new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                final JSONObject response = new JSONObject();
+                                try {
+                                    response.put("callback", SiteIdentityPopup.ButtonType.COPY.ordinal());
+                                    response.put("password", passwords[which]);
+                                } catch (JSONException e) {
+                                    Log.e(LOGTAG, "Error making login select dialog JSON", e);
+                                }
+                                mOnButtonClickListener.onButtonClick(response, LoginDoorHanger.this);
+                            }
+                        });
+                        builder.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                dialog.dismiss();
+                            }
+                        });
+                        mLink.setText(R.string.doorhanger_login_select_action_text);
+                        mLink.setVisibility(View.VISIBLE);
+                    } catch (JSONException e) {
+                        Log.e(LOGTAG, "Problem creating list of logins");
+                    }
+                    break;
             }
 
             final Dialog dialog = builder.create();
-            mLogin.setOnClickListener(new OnClickListener() {
+            mLink.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
                     dialog.show();
                 }
             });
 
         } catch (JSONException e) {
-            // Log an error, but leave the text visible if there was a username.
             Log.e(LOGTAG, "Error fetching actionText from JSON", e);
-            if (!hasUsername) {
-                mLogin.setVisibility(View.GONE);
-            }
         }
     }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/SiteLogins.java
@@ -0,0 +1,22 @@
+package org.mozilla.gecko.widget;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+public class SiteLogins {
+    private final JSONObject titleObj;
+    private final JSONArray logins;
+
+    public SiteLogins(JSONObject titleObj, JSONArray logins) {
+        this.logins = logins;
+        this.titleObj = titleObj;
+    }
+
+    public JSONArray getLogins() {
+        return logins;
+    }
+
+    public JSONObject getTitle() {
+        return titleObj;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/chrome/content/ActionBarHandler.js
@@ -0,0 +1,638 @@
+// -*- 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: "",
+  },
+
+  _selectionID: null, // Unique Selection ID, assigned each time we _init().
+  _actionBarActions: null, // Most-recent set of actions sent to ActionBar.
+
+  /**
+   * 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": {
+        for (let type in this.actions) {
+          let action = this.actions[type];
+          if (action.id == data) {
+            action.action(this._targetElement, this._contentWindow);
+            break;
+          }
+        }
+        break;
+      }
+
+      // Provide selected text to FindInPageBar on request.
+      case "TextSelection:Get": {
+        Messaging.sendRequest({
+          type: "TextSelection:Data",
+          requestId: data,
+          text: this._getSelectedText(),
+        });
+        break;
+      }
+
+      // User closed ActionBar by clicking "checkmark" button.
+      case "TextSelection:End": {
+        // End the requested selection only.
+        if (this._selectionID === JSON.parse(data).selectionID) {
+          this._uninit();
+        }
+        break;
+      }
+    }
+  },
+
+  /**
+   * Called when Gecko TouchCaret or SelectionCarets become visible.
+   */
+  _init: function(actionBarID) {
+    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._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.
+    Messaging.sendRequest({
+      type: "TextSelection:ActionbarInit",
+      selectionID: this._selectionID,
+    });
+    this._sendActionBarActions(true);
+
+    return this.START_TOUCH_ERROR.NONE;
+  },
+
+  /**
+   * Determines the window containing the selection, and its
+   * editable element if present.
+   */
+  _getSelectionTargets: function() {
+    let [element, win] = [Services.focus.focusedElement, Services.focus.focusedWindow];
+    if (!element) {
+      // No focused editable.
+      return [null, win];
+    }
+
+    // Return focused editable text element and its window.
+    if (((element instanceof HTMLInputElement) && element.mozIsTextField(false)) ||
+        (element instanceof HTMLTextAreaElement) ||
+        element.isContentEditable) {
+      return [element, win];
+    }
+
+    // Focused element can't contain text.
+    return [null, win];
+  },
+
+  /**
+   * Called when Gecko TouchCaret or SelectionCarets become hidden,
+   * ActionBar is closed by user "close" request, or as a result of object
+   * 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().
+    this._selectionID = null;
+
+    // Clear selection required if triggered by self, or TextSelection icon
+    // actions. If called by Gecko TouchCaret/SelectionCarets state change,
+    // visibility state is already correct.
+    if (clearSelection) {
+      this._clearSelection();
+    }
+  },
+
+  /**
+   * Final UI cleanup when Actionbar is closed by icon click, or where
+   * we terminate selection state after before/after actionbar actions
+   * (Cut, Copy, Paste, Search, Share, Call).
+   */
+  _clearSelection: function(element = this._targetElement, win = this._contentWindow) {
+    // Commit edit compositions, and clear focus from editables.
+    if (element) {
+      let imeSupport = this._getEditor(element, win).QueryInterface(Ci.nsIEditorIMESupport);
+      if (imeSupport.composing) {
+        imeSupport.forceCompositionEnd();
+      }
+      element.blur();
+    }
+
+    // Remove Selection from non-editables and now-unfocused contentEditables.
+    if (!element || element.isContentEditable) {
+      this._getSelection().removeAllRanges();
+    }
+  },
+
+  /**
+   * Called to determine current ActionBar actions and send to TextSelection
+   * handler. By default we only send if current action state differs from
+   * the previous.
+   * @param By default we only send an ActionBarStatus update message if
+   *        there is a change from the previous state. sendAlways can be
+   *        set by init() for example, where we want to always send the
+   *        current state.
+   */
+  _sendActionBarActions: function(sendAlways) {
+    let actions = this._getActionBarActions();
+
+    if (sendAlways || actions !== this._actionBarActions) {
+      Messaging.sendRequest({
+        type: "TextSelection:ActionbarStatus",
+        actions: actions,
+      });
+    }
+
+    this._actionBarActions = actions;
+  },
+
+  /**
+   * Determine and return current ActionBar state.
+   */
+  _getActionBarActions: function(element = this._targetElement, win = this._contentWindow) {
+    let actions = [];
+
+    for (let type in this.actions) {
+      let action = this.actions[type];
+      if (action.selector.matches(element, win)) {
+        let a = {
+          id: action.id,
+          label: this._getActionValue(action, "label", "", element),
+          icon: this._getActionValue(action, "icon", "drawable://ic_status_logo", element),
+          order: this._getActionValue(action, "order", 0, element),
+          showAsAction: this._getActionValue(action, "showAsAction", true, element),
+        };
+        actions.push(a);
+      }
+    }
+    actions.sort((a, b) => b.order - a.order);
+
+    return actions;
+  },
+
+  /**
+   * Provides a value from an action. If the action defines the value as a function,
+   * we return the result of calling the function. Otherwise, we return the value
+   * itself. If the value isn't defined for this action, will return a default.
+   */
+  _getActionValue: function(obj, name, defaultValue, element) {
+    if (!(name in obj))
+      return defaultValue;
+
+    if (typeof obj[name] == "function")
+      return obj[name](element);
+
+    return obj[name];
+  },
+
+  /**
+   * Actionbar callback methods.
+   */
+  actions: {
+
+    SELECT_ALL: {
+      id: "selectall_action",
+      label: Strings.browser.GetStringFromName("contextmenu.selectAll"),
+      icon: "drawable://ab_select_all",
+      order: 5,
+
+      selector: {
+        matches: function(element, win) {
+          // For editable, check its length. For default contentWindow, assume
+          // true, else there'd been nothing to long-press to open ActionBar.
+          return (element) ? element.textLength != 0 : true;
+        },
+      },
+
+      action: function(element, win) {
+        // Some Mobile keyboards such as SwiftKeyboard, provide auto-suggest
+        // style highlights via composition selections in editables.
+        if (element) {
+          // If we have an active composition string, commit it, and 
+          // ensure proper element focus.
+          let imeSupport = ActionBarHandler._getEditor(element, win).
+            QueryInterface(Ci.nsIEditorIMESupport);
+          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",
+      order: 4,
+
+      selector: {
+        matches: function(element, win) {
+          // Can't cut from non-editable.
+          if (!element) {
+            return false;
+          }
+          // Don't allow "cut" from password fields.
+          if (element instanceof Ci.nsIDOMHTMLInputElement &&
+              !element.mozIsTextField(true)) {
+            return false;
+          }
+          // Don't allow "cut" from disabled/readonly fields.
+          if (element.disabled || element.readOnly) {
+            return false;
+          }
+          // Allow if selected text exists.
+          return (ActionBarHandler._getSelectedText().length > 0);
+        },
+      },
+
+      action: function(element, win) {
+        // First copy the selection text to the clipboard.
+        let selectedText = ActionBarHandler._getSelectedText();
+        let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+          getService(Ci.nsIClipboardHelper);
+        clipboard.copyString(selectedText, win.document);
+
+        let msg = Strings.browser.GetStringFromName("selectionHelper.textCopied");
+        NativeWindow.toast.show(msg, "short");
+
+        // Then cut the selection text.
+        ActionBarHandler._getSelection(element, win).deleteFromDocument();
+
+        ActionBarHandler._uninit();
+        UITelemetry.addEvent("action.1", "actionbar", null, "cut");
+      },
+    },
+
+    COPY: {
+      id: "copy_action",
+      label: Strings.browser.GetStringFromName("contextmenu.copy"),
+      icon: "drawable://ab_copy",
+      order: 3,
+
+      selector: {
+        matches: function(element, win) {
+          // Don't allow "copy" from password fields.
+          if (element instanceof Ci.nsIDOMHTMLInputElement &&
+              !element.mozIsTextField(true)) {
+            return false;
+          }
+          // Allow if selected text exists.
+          return (ActionBarHandler._getSelectedText().length > 0);
+        },
+      },
+
+      action: function(element, win) {
+        let selectedText = ActionBarHandler._getSelectedText();
+        let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+          getService(Ci.nsIClipboardHelper);
+        clipboard.copyString(selectedText, win.document);
+
+        let msg = Strings.browser.GetStringFromName("selectionHelper.textCopied");
+        NativeWindow.toast.show(msg, "short");
+
+        ActionBarHandler._uninit();
+        UITelemetry.addEvent("action.1", "actionbar", null, "copy");
+      },
+    },
+
+    PASTE: {
+      id: "paste_action",
+      label: Strings.browser.GetStringFromName("contextmenu.paste"),
+      icon: "drawable://ab_paste",
+      order: 2,
+
+      selector: {
+        matches: function(element, win) {
+          // Can't paste into non-editable.
+          if (!element) {
+            return false;
+          }
+          // Can't paste into disabled/readonly fields.
+          if (element.disabled || element.readOnly) {
+            return false;
+          }
+          // Can't paste if Clipboard empty.
+          let flavors = ["text/unicode"];
+          return Services.clipboard.hasDataMatchingFlavors(flavors, flavors.length,
+            Ci.nsIClipboard.kGlobalClipboard);
+        },
+      },
+
+      action: function(element, win) {
+        // Paste the clipboard, then close the ActionBarHandler and ActionBar.
+        ActionBarHandler._getEditor(element, win).
+          paste(Ci.nsIClipboard.kGlobalClipboard);
+        ActionBarHandler._uninit();
+        UITelemetry.addEvent("action.1", "actionbar", null, "paste");
+      },
+    },
+
+    CALL: {
+      id: "call_action",
+      label: Strings.browser.GetStringFromName("contextmenu.call"),
+      icon: "drawable://phone",
+      order: 1,
+
+      selector: {
+        matches: function(element, win) {
+          return (ActionBarHandler._getSelectedPhoneNumber() != null);
+        },
+      },
+
+      action: function(element, win) {
+        BrowserApp.loadURI("tel:" +
+          ActionBarHandler._getSelectedPhoneNumber());
+
+        ActionBarHandler._uninit();
+        UITelemetry.addEvent("action.1", "actionbar", null, "call");
+      },
+    },
+
+    SEARCH: {
+      id: "search_action",
+      label: Strings.browser.formatStringFromName("contextmenu.search",
+        [Services.search.defaultEngine.name], 1),
+      icon: "drawable://ab_search",
+      order: 1,
+
+      selector: {
+        matches: function(element, win) {
+          // Allow if selected text exists.
+          return (ActionBarHandler._getSelectedText().length > 0);
+        },  
+      },
+
+      action: function(element, win) {
+        let selectedText = ActionBarHandler._getSelectedText();
+        ActionBarHandler._uninit();
+
+        // Set current tab as parent of new tab,
+        // and set new tab as private if the parent is.
+        let searchSubmission = Services.search.defaultEngine.getSubmission(selectedText);
+        let parent = BrowserApp.selectedTab;
+        let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(parent.browser);
+        BrowserApp.addTab(searchSubmission.uri.spec,
+          { parentId: parent.id,
+            selected: true,
+            isPrivate: isPrivate,
+          }
+        );
+
+        UITelemetry.addEvent("action.1", "actionbar", null, "search");
+      },
+    },
+
+    SEARCH_ADD: {
+      id: "search_add_action",
+      label: Strings.browser.GetStringFromName("contextmenu.addSearchEngine2"),
+      icon: "drawable://ab_add_search_engine",
+      order: 0,
+
+      selector: {
+        matches: function(element, win) {
+          if(!(element instanceof HTMLInputElement)) {
+            return false;
+          }
+          let form = element.form;
+          if (!form || element.type == "password") {
+            return false;
+          }
+          let method = form.method.toUpperCase();
+          return (method == "GET" || method == "") ||
+                 (form.enctype != "text/plain") && (form.enctype != "multipart/form-data");
+        },
+      },
+
+      action: function(element, win) {
+        UITelemetry.addEvent("action.1", "actionbar", null, "add_search_engine");
+        SearchEngines.addEngine(element);
+      },
+    },
+
+    SHARE: {
+      id: "share_action",
+      label: Strings.browser.GetStringFromName("contextmenu.share"),
+      icon: "drawable://ic_menu_share",
+      order: 0,
+
+      selector: {
+        matches: function(element, win) {
+          if (!ParentalControls.isAllowed(ParentalControls.SHARE)) {
+            return false;
+          }
+          // Allow if selected text exists.
+          return (ActionBarHandler._getSelectedText().length > 0);
+        },
+      },
+
+      action: function(element, win) {
+        Messaging.sendRequest({
+          type: "Share:Text",
+          text: ActionBarHandler._getSelectedText(),
+        });
+
+        ActionBarHandler._uninit();
+        UITelemetry.addEvent("action.1", "actionbar", null, "share");
+      },
+    },
+  },
+
+  /**
+   * Provides UUID service for generating action ID's.
+   */
+  get _idService() {
+    delete this._idService;
+    return this._idService = Cc["@mozilla.org/uuid-generator;1"].
+      getService(Ci.nsIUUIDGenerator);
+  },
+
+  /**
+   * The targetElement holds an editable element containing a
+   * selection or a caret.
+   */
+  get _targetElement() {
+    if (this._targetElementRef)
+      return this._targetElementRef.get();
+    return null;
+  },
+
+  set _targetElement(element) {
+    this._targetElementRef = Cu.getWeakReference(element);
+  },
+
+  /**
+   * The contentWindow holds the selection, or the targetElement
+   * if it's an editable.
+   */
+  get _contentWindow() {
+    if (this._contentWindowRef)
+      return this._contentWindowRef.get();
+    return null;
+  },
+
+  set _contentWindow(aContentWindow) {
+    this._contentWindowRef = Cu.getWeakReference(aContentWindow);
+  },
+
+  /**
+   * Provides the currently selected text, for either an editable,
+   * or for the default contentWindow.
+   */
+  _getSelectedText: function() {
+    // Can be called from FindInPageBar "TextSelection:Get", when there
+    // is no active selection.
+    if (!this._selectionID) {
+      return "";
+    }
+
+    let selection = this._getSelection();
+
+    // Textarea can contain LF, etc.
+    if (this._targetElement instanceof Ci.nsIDOMHTMLTextAreaElement) {
+      let flags = Ci.nsIDocumentEncoder.OutputPreformatted |
+        Ci.nsIDocumentEncoder.OutputRaw;
+      return selection.QueryInterface(Ci.nsISelectionPrivate).
+        toStringWithFormat("text/plain", flags, 0);
+    }
+
+    // Selection text gets trimmed up.
+    return selection.toString().trim();
+  },
+
+  /**
+   * Provides the nsISelection for either an editor, or from the
+   * default window.
+   */
+  _getSelection: function(element = this._targetElement, win = this._contentWindow) {
+    return (element instanceof Ci.nsIDOMNSEditableElement) ?
+      this._getEditor(element).selection :
+      win.getSelection();
+  },
+
+  /**
+   * Returns an nsEditor or nsHTMLEditor.
+   */
+  _getEditor: function(element = this._targetElement, win = this._contentWindow) {
+    if (element instanceof Ci.nsIDOMNSEditableElement) {
+      return element.QueryInterface(Ci.nsIDOMNSEditableElement).editor;
+    }
+
+    return win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation).
+               QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIEditingSession).
+               getEditorForWindow(win);
+  },
+
+  /**
+   * Returns a selection controller.
+   */
+  _getSelectionController: function(element = this._targetElement, win = this._contentWindow) {
+    if (element instanceof Ci.nsIDOMNSEditableElement) {
+      return this._getEditor(element, win).selectionController;
+    }
+
+    return win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation).
+               QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsISelectionDisplay).
+               QueryInterface(Ci.nsISelectionController);
+  },
+
+  /**
+   * For selectAll(), provides the editor, or the default window selection Controller.
+   */
+  _getSelectAllController: function(element = this._targetElement, win = this._contentWindow) {
+    let editor = this._getEditor(element, win);
+    return (editor) ?
+      editor : this._getSelectionController(element, win);
+  },
+
+  /**
+   * Call / Phone Helper methods.
+   */
+  _getSelectedPhoneNumber: function() {
+    let selectedText = this._getSelectedText().trim();
+    return this._isPhoneNumber(selectedText) ?
+      selectedText : null;
+  },
+
+  _isPhoneNumber: function(selectedText) {
+    return (PHONE_REGEX.test(selectedText));
+  },
+};
--- a/mobile/android/chrome/content/SelectionHandler.js
+++ b/mobile/android/chrome/content/SelectionHandler.js
@@ -3,44 +3,54 @@
  * 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";
+
 var SelectionHandler = {
 
   // Successful startSelection() or attachCaret().
   ERROR_NONE: "",
 
   // Error codes returned during startSelection().
   START_ERROR_INVALID_MODE: "Invalid selection mode requested.",
   START_ERROR_NONTEXT_INPUT: "Target element by definition contains no text.",
   START_ERROR_NO_WORD_SELECTED: "No word selected at point.",
   START_ERROR_SELECT_WORD_FAILED: "Word selection at point failed.",
   START_ERROR_SELECT_ALL_PARAGRAPH_FAILED: "Select-All Paragraph failed.",
   START_ERROR_NO_SELECTION: "Selection performed, but nothing resulted.",
   START_ERROR_PROXIMITY: "Selection target and result seem unrelated.",
+  START_ERROR_SELECTIONCARETS_ENABLED: "Native selectionCarets requested while Gecko enabled.",
 
   // Error codes returned during attachCaret().
   ATTACH_ERROR_INCOMPATIBLE: "Element disabled, handled natively, or not editable.",
+  ATTACH_ERROR_TOUCHCARET_ENABLED: "Native touchCaret requested while Gecko enabled.",
 
   HANDLE_TYPE_ANCHOR: "ANCHOR",
   HANDLE_TYPE_CARET: "CARET",
   HANDLE_TYPE_FOCUS: "FOCUS",
 
   TYPE_NONE: 0,
   TYPE_CURSOR: 1,
   TYPE_SELECTION: 2,
 
   SELECT_ALL: 0,
   SELECT_AT_POINT: 1,
 
+  // Gecko TouchCaret/SelectionCaret pref values.
+  _touchCaretEnabledValue: 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,
 
   _activeType: 0, // TYPE_NONE
@@ -86,16 +96,40 @@ 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);
+      }, 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;
+  },
+
   _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);
     Services.obs.addObserver(this, "TextSelection:Action", false);
     Services.obs.addObserver(this, "TextSelection:LayerReflow", false);
@@ -373,16 +407,21 @@ 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) {
+      return this.START_ERROR_SELECTIONCARETS_ENABLED;
+    }
+
     // Clear out any existing active selection
     this._closeSelection();
 
     if (this._isNonTextInputElement(aElement)) {
       return this.START_ERROR_NONTEXT_INPUT;
     }
 
     const focus = Services.focus.focusedWindow;
@@ -832,16 +871,21 @@ 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) {
+      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)) {
       return this.ATTACH_ERROR_INCOMPATIBLE;
     }
 
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -149,16 +149,22 @@ let lazilyLoadedObserverScripts = [
   ["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"],
   ["EmbedRT", ["GeckoView:ImportScript"], "chrome://browser/content/EmbedRT.js"],
   ["Reader", ["Reader:FetchContent", "Reader:Added", "Reader:Removed"], "chrome://browser/content/Reader.js"],
 ];
+if (AppConstants.NIGHTLY_BUILD) {
+  lazilyLoadedObserverScripts.push(
+    ["ActionBarHandler", ["ActionBar:OpenNew", "ActionBar:Close", "TextSelection:Get"],
+      "chrome://browser/content/ActionBarHandler.js"]
+  );
+}
 if (AppConstants.MOZ_WEBRTC) {
   lazilyLoadedObserverScripts.push(
     ["WebrtcUI", ["getUserMedia:request", "recording-device-events"], "chrome://browser/content/WebrtcUI.js"])
 }
 
 lazilyLoadedObserverScripts.forEach(function (aScript) {
   let [name, notifications, script] = aScript;
   XPCOMUtils.defineLazyGetter(window, name, function() {
@@ -4213,23 +4219,32 @@ Tab.prototype = {
 
           this.browser.addEventListener("pagehide", listener, true);
         }
 
         if (docURI.startsWith("about:reader")) {
           // Update the page action to show the "reader active" icon.
           Reader.updatePageAction(this);
         }
-
         break;
       }
 
       case "DOMFormHasPassword": {
         LoginManagerContent.onDOMFormHasPassword(aEvent,
                                                  this.browser.contentWindow);
+
+        // Send logins for this hostname to Java.
+        let hostname = aEvent.target.baseURIObject.prePath;
+        let foundLogins = Services.logins.findLogins({}, hostname, "", "");
+        if (foundLogins.length > 0) {
+          let displayHost = IdentityHandler.getEffectiveHost();
+          let title = { text: displayHost, resource: hostname };
+          let selectObj = { title: title, logins: foundLogins };
+          Messaging.sendRequest({ type: "Doorhanger:Logins", data: selectObj });
+        }
         break;
       }
 
       case "DOMMetaAdded":
         let target = aEvent.originalTarget;
         let browser = BrowserApp.getBrowserForDocument(target.ownerDocument);
 
         switch (target.name) {
@@ -6245,16 +6260,22 @@ var XPInstallObserver = {
           }];
         }
         NativeWindow.doorhanger.show(message, aTopic, buttons, tab.id);
         break;
     }
   },
 
   onInstallEnded: function(aInstall, aAddon) {
+    // Don't create a notification for distribution add-ons.
+    if (Distribution.pendingAddonInstalls.has(aInstall)) {
+      Distribution.pendingAddonInstalls.delete(aInstall);
+      return;
+    }
+
     let needsRestart = false;
     if (aInstall.existingAddon && (aInstall.existingAddon.pendingOperations & AddonManager.PENDING_UPGRADE))
       needsRestart = true;
     else if (aAddon.pendingOperations & AddonManager.PENDING_INSTALL)
       needsRestart = true;
 
     if (needsRestart) {
       this.showRestartPrompt();
@@ -6269,16 +6290,23 @@ var XPInstallObserver = {
             callback: () => { BrowserApp.addTab("about:addons#" + aAddon.id, { parentId: BrowserApp.selectedTab.id }); },
           }
         });
       }
     }
   },
 
   onInstallFailed: function(aInstall) {
+    // Don't create a notification for distribution add-ons.
+    if (Distribution.pendingAddonInstalls.has(aInstall)) {
+      Cu.reportError("Error installing distribution add-on: " + aInstall.addon.id);
+      Distribution.pendingAddonInstalls.delete(aInstall);
+      return;
+    }
+
     NativeWindow.toast.show(Strings.browser.GetStringFromName("alertAddonsFail"), "short");
   },
 
   onDownloadProgress: function xpidm_onDownloadProgress(aInstall) {},
 
   onDownloadFailed: function(aInstall) {
     this.onInstallFailed(aInstall);
   },
@@ -7678,16 +7706,17 @@ var Distribution = {
         } catch (e) {
           console.log("Unable to reinit search service.");
         }
         // Fall through.
 
       case "Distribution:Set":
         // Reload the default prefs so we can observe "prefservice:after-app-defaults"
         Services.prefs.QueryInterface(Ci.nsIObserver).observe(null, "reload-default-prefs", null);
+        this.installDistroAddons();
         break;
 
       case "prefservice:after-app-defaults":
         this.getPrefs();
         break;
 
       case "Campaign:Set": {
         // Update the prefs for this session
@@ -7800,17 +7829,71 @@ var Distribution = {
       } catch (e) {
         Cu.reportError("Distribution: Could not parse JSON: " + e);
       }
     }).then(null, function onError(reason) {
       if (!(reason instanceof OS.File.Error && reason.becauseNoSuchFile)) {
         Cu.reportError("Distribution: Could not read from " + aFile.leafName + " file");
       }
     });
-  }
+  },
+
+  // Track pending installs so we can avoid showing notifications for them.
+  pendingAddonInstalls: new Set(),
+
+  installDistroAddons: Task.async(function* () {
+    const PREF_ADDONS_INSTALLED = "distribution.addonsInstalled";
+    try {
+      let installed = Services.prefs.getBoolPref(PREF_ADDONS_INSTALLED);
+      if (installed) {
+        return;
+      }
+    } catch (e) {
+      Services.prefs.setBoolPref(PREF_ADDONS_INSTALLED, true);
+    }
+
+    let distroPath;
+    try {
+      distroPath = FileUtils.getDir("XREAppDist", ["extensions"]).path;
+
+      let info = yield OS.File.stat(distroPath);
+      if (!info.isDir) {
+        return;
+      }
+    } catch (e) {
+      return;
+    }
+
+    let it = new OS.File.DirectoryIterator(distroPath);
+    try {
+      yield it.forEach(entry => {
+        // Only support extensions that are zipped in .xpi files.
+        if (entry.isDir || !entry.name.endsWith(".xpi")) {
+          dump("Ignoring distribution add-on that isn't an XPI: " + entry.path);
+          return;
+        }
+
+        new Promise((resolve, reject) => {
+          AddonManager.getInstallForFile(new FileUtils.File(entry.path), resolve);
+        }).then(install => {
+          let id = entry.name.substring(0, entry.name.length - 4);
+          if (install.addon.id !== id) {
+            Cu.reportError("File entry " + entry.path + " contains an add-on with an incorrect ID");
+            return;
+          }
+          this.pendingAddonInstalls.add(install);
+          install.install();
+        }).catch(e => {
+          Cu.reportError("Error installing distribution add-on: " + entry.path + ": " + e);
+        });
+      });
+    } finally {
+      it.close();
+    }
+  })
 };
 
 var Tabs = {
   _enableTabExpiration: false,
   _domains: new Set(),
 
   init: function() {
     // On low-memory platforms, always allow tab expiration. On high-mem
--- a/mobile/android/chrome/jar.mn
+++ b/mobile/android/chrome/jar.mn
@@ -30,16 +30,17 @@ chrome.jar:
   content/languages.properties         (content/languages.properties)
   content/browser.xul                  (content/browser.xul)
   content/browser.js                   (content/browser.js)
   content/bindings/checkbox.xml        (content/bindings/checkbox.xml)
   content/bindings/settings.xml        (content/bindings/settings.xml)
   content/netError.xhtml               (content/netError.xhtml)
   content/SelectHelper.js              (content/SelectHelper.js)
   content/SelectionHandler.js          (content/SelectionHandler.js)
+  content/ActionBarHandler.js          (content/ActionBarHandler.js)
   content/WebappRT.js                  (content/WebappRT.js)
   content/EmbedRT.js                   (content/EmbedRT.js)
   content/InputWidgetHelper.js         (content/InputWidgetHelper.js)
   content/WebrtcUI.js                  (content/WebrtcUI.js)
   content/MemoryObserver.js            (content/MemoryObserver.js)
   content/ConsoleAPI.js                (content/ConsoleAPI.js)
   content/PluginHelper.js              (content/PluginHelper.js)
   content/OfflineApps.js               (content/OfflineApps.js)
--- a/mobile/android/confvars.sh
+++ b/mobile/android/confvars.sh
@@ -95,12 +95,14 @@ MOZ_ANDROID_SHARE_OVERLAY=1
 
 # Enable the Mozilla Location Service stumbler.
 MOZ_ANDROID_MLS_STUMBLER=1
 
 # Enable adding to the system downloads list in pre-release builds.
 MOZ_ANDROID_DOWNLOADS_INTEGRATION=1
 
 # Enable Tab Queue
-MOZ_ANDROID_TAB_QUEUE=1
+if test "$NIGHTLY_BUILD"; then
+  MOZ_ANDROID_TAB_QUEUE=1
+fi
 
 # Use the low-memory GC tuning.
 export JS_GC_SMALL_CHUNK_SIZE=1
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -541,16 +541,30 @@
 @BINPATH@/res/table-remove-row-active.gif
 @BINPATH@/res/table-remove-row-hover.gif
 @BINPATH@/res/table-remove-row.gif
 @BINPATH@/res/grabber.gif
 @BINPATH@/res/dtd/*
 @BINPATH@/res/html/*
 @BINPATH@/res/language.properties
 @BINPATH@/res/entityTables/*
+#ifdef NIGHTLY_BUILD
+@BINPATH@/res/text_caret.png
+@BINPATH@/res/text_caret@1.5x.png
+@BINPATH@/res/text_caret@2.25x.png
+@BINPATH@/res/text_caret@2x.png
+@BINPATH@/res/text_caret_tilt_left.png
+@BINPATH@/res/text_caret_tilt_left@1.5x.png
+@BINPATH@/res/text_caret_tilt_left@2.25x.png
+@BINPATH@/res/text_caret_tilt_left@2x.png
+@BINPATH@/res/text_caret_tilt_right.png
+@BINPATH@/res/text_caret_tilt_right@1.5x.png
+@BINPATH@/res/text_caret_tilt_right@2.25x.png
+@BINPATH@/res/text_caret_tilt_right@2x.png
+#endif
 
 #ifndef MOZ_ANDROID_EXCLUDE_FONTS
 @BINPATH@/res/fonts/*
 #endif
 
 ; svg
 @BINPATH@/res/svg.css
 @BINPATH@/components/dom_svg.xpt
--- a/mobile/android/modules/Home.jsm
+++ b/mobile/android/modules/Home.jsm
@@ -310,16 +310,17 @@ let HomePanels = (function () {
     INTENT: "intent"
   });
 
   function Panel(id, options) {
     this.id = id;
     this.title = options.title;
     this.layout = options.layout;
     this.views = options.views;
+    this.default = !!options.default;
 
     if (!this.id || !this.title) {
       throw "Home.panels: Can't create a home panel without an id and title!";
     }
 
     if (!this.layout) {
       // Use FRAME layout by default
       this.layout = Layout.FRAME;
@@ -373,16 +374,20 @@ let HomePanels = (function () {
         buttonText: options.auth.buttonText
       };
 
       // Include optional image URL if it is specified.
       if (options.auth.imageUrl) {
         this.authConfig.imageUrl = options.auth.imageUrl;
       }
     }
+
+    if (options.position && typeof options.position === "number") {
+      this.position = options.position;
+    }
   }
 
   let _generatePanel = function(id) {
     let options = _registeredPanels[id]();
     return new Panel(id, options);
   };
 
   // Helper function used to see if a value is in an object.
index 7c299823caeed7a27055cc121d79b5b9cbeadea2..9a0c5e6c95f5348cea461e4c60340c96a36a4b15
GIT binary patch
literal 5094
zc$}4&2{@E{`^PPjELkF1OSWWR5>jI+5@XFUvJEq1H=4m%!kl8r-q;C8q(x;ZS;pR2
zv&&wHgD7kE^>5C3k2CcCJH5|y{jPbQnd|qt@8^E5`QG<eUz_p>EgA7BzMi8&MshHa
zF_A$XQ0_<v8xMB}gsY^nDLt8am%cG^Et9j7>F;f2C8OCp2@s$f=~k}8TWt^sCl?6P
z2_@l(Lbwt^OiNq_#6<*!MUGSK(T>tw?X-8J+S^aLxAgZDt2>m~sl#-bmpjZAwReU3
zGe^{3eyAUE)I3B{{0W8hdkJ?K%3Z|;;p5=!43R(}?IgV19Ef3j_CjDg{|ZAl4~H{D
ztgT0^L)@%pJSU^)8r?Ohu`EK=)V$Oj*sQHB+$io5fuj7yXgiZG^#l()rCN?8RbV{G
zO;)Pl4%K<8udb;hiGKAezb|hC40hJxpH7r-%DEJMoM20OqdGoQGk)XAX-x&)uUaVu
zv8$85{ag36#f(2#`AGOzqP@YZo3&HucwHRup{2*U<=FWI-nqHs{<IG@)Wp*xy|`Dx
z!LEq<&Y7)(XXfsMk$QeNs#|7TYSS676t`4e-tOP-ea#rQ0zC1sYy{uhg6w6hmrS#)
zR@U2Uz6Z}Nh3QN+sZRG=(%r^#xTi9=74B^I>wMo?F;X<?O&;ny$$80Ez51i*Vve5p
z<&LZhehB=@4%TKUSrpmwcpCBe+7+Q8*4N(3>w{ZzU9;+y(>1;|cG=WV0Hw_pi|Cih
zb3s$b(X&)N7A0}~Ci&BM`lj9?cKu5ISJuCltK}BGTB>ZwHyB+(x1hs@W&D<}6t%-P
zQ(xfvwcDC2dgCsAh8mdo)IP`Na7^mR3+Q(CI=jp&rFgVtT<=&NJI$w^pOC<?nkwng
zypDEj+8y8IHLd`nD{kACZ<LkiM{K+=cPtp5I$FiAO?3*4@;wnZh>?zNYObnc2!GLB
zHi{!W<Su^{E9YiTWgC_Au`zNT&02Na17`OngG(`?(|uVsA#fwzV!&81z*lxsKx6i9
z!TX$@xo&TZQM^NIO46{4-J9WZe&1TY*}KLQ?1jq~8k<4<0QTLq!A_9;W?RcJR~K?+
zn0=Qgc}>G>xv)2TAU<mH<8xFce&bg4-COy3-3cW?pu)1<00etq&q%b5x`Ef6C+2Kp
z$;QN^UMHxdcp_)+`Ul_&Bi@yJcDn}~tms~CR8g`eBl|AJ1UR^9Gc}p#k{~IuHL@mx
zw%veKUK3025k)m#3y^DWrfu)5o7vT>f#=uCXGEbUkml}}eeh-?Ijdu;SBioWw<z8(
z-_+Yq=_(!bD~#>;)0zv^bB(B<Y~+?4Dt2iV?+6o2n;x7f^;hVrt*8ck9))^Gd}+IZ
zzqAtXD!=oQV@ZUs$q@26p|9XxbHJ|Iyo;j*czb)JSw{Lh;@dHkIqWwu6c5w=xO74i
zl`FF|qZDoBnJFf>G(3FQ5OKRZ_p7*7SSx@#;96VpNc}n|5Wg-P>M<!_-=w^be4DKa
zStUP_3gEp_+0SgyNEhBTc4umaPsQ>)6N(~JrlGr|w%1tlA{F-ovp)XLqWT9p)tKOM
zznPBgrOFoC&ExY3aL<6XJf!hnTiN%OvdeCDb9E7ksmfE5;vi!mu-&A;S$oowXJf|6
z(=jVYfvQJ$XCZVmj&JUJuVM{cZC{R9;m!4|X>Fl6+dAVj*QGFI(eLxJm<ej)yrz7$
zl*ytGfCQbw2BB9}hUXj-*>Ko}Qz0Cl4b&jAmnyVcpzGIdPSrP2ch=Acreov)bRkFo
z)WF*cYvP(m$jI2K$Vk-7KEwQg!txweFE=C%4nx9RZDIdtEcCpT0dWx(Dk56iiBg(A
zXmyjVE?Rt&Up>J&BUZx1C;DXRfSn+-0w~&#hjVdQRG8buz7rm^m9}(c-BjdpI;A4l
zk|EFI7~b*^O6pC^)yT*yAAf@vnSAUEz^mOB%nVr@<C&%)E_(aRB8<<+$RF}Vq*omi
z33cFOeBy0<MhVLxMd0ncIw}p*xCqTpp@7~vUBo|JyinA}OqD0due}1~szbKE3+ymN
zQ2K+}M|2;#C1GR?i#-)j#{*G4Aw?}O{QS%d^<F<K25zHFfQx$JJ9CdW0Pd9e{#L`j
zRfX;TN29Bj-T;D)pG<QR3n)-7)jF|PB6$kZCgPE1d+nz!3I&58ZSCEhJ?tF*(?<;U
z1YQ?tdh_o-^4~#ce-5%mxVZeMcOK^b^=3uPkO$tZ*e~&5u6uVd2`?9CB3Z&r(}1{$
z<Zh4)YE+(SC(Y`!3@X*rjDio-!iMCxRp(aAdv|LjB_9S}5Pj=Rd2w|ZUq>y0cA0~3
z$QTI~Sb8pho6U>>ePX=SHg3pWD>v^Om-5vo=v@VK;s@dKraSUaY2z5tCZ9xl_}_s(
z1?s2Y6Zij^(-$5z=Ts>6{#E&iiiJt#@w0|bAHL2E2+<at(kxsn&IL8{eLG{LR~kGA
zSAd)Ik=tTI@P%PZ&qY7?h1fS#J-$T9&wW#pF7Fc#l$bApFfp+wjHcrY+lnkb*Z?|-
z=2#RuJo}a}p5{iHu2h0SOuvc>zNSBsC55LRo!z>$D%blo=*qfy(V**2C=&}a?g9O9
z+3B;=DjaH*6kYy1s%AR|?OHpR0V1<cb^0LL1zEBoFRM>z4_zSi^L>*0J)rm0TCu}%
z-n_(aX;rDBcFF=K7B`{LHG9x`R%3X6q||Xpytpl$_~^|c!kPAYc^(@4F~+gcTtED<
z(xsSiZ(O64Fq9rI#xPHA6q_dmu{4$^=Qp$A7$e}wEeyt3*+Qn|djWr>^FJ~z&g1QG
zTaE`_?dV}8_qiI~S)FuK@{d2{!(|CVY5{f^G&x=n7#&L01_iil^&B!{<Ncq%43bUl
zeTH2#VelXWo*mfwGU=D`V6bPPr<fxuCC^HQK5En8k#o@myd`H<EiyjE1;EkXpEZQU
zl$glP>I2CIHcv@+2#7l%l<wftI7Zp;mbSP`TktAvfzCx(5K030=y>clpEm-9)s@c_
z*=N(RiuIWV$fQO#CzzBLePlpl?{tRf>5P*VrLvr3VL9nC&PK^yEr^NXxi!hgDadqj
zx@(;EQrP6>G?voj&{h0O7l-ILkH3u9%9qfRk>658l-~%2dk3GxoJCjV2z75%*I07C
zwiq7#pqY1(1}^L`aetvb<jDKxST0HRqBQD=Gu79SL&=&^i+7l3obAi?oswJ`gV+@n
z#mQyRn69^kWHBYp0aM{7Wg(^{%{mvx2n1i#$v1B^$4e%mHp8v6;I6IkZ7gQ0C0lQu
zYP!f$Zb)%{N6`1;!T=xEw9<o5(!9ZDBuDi^@JqYjlpHluUr)p2KE2X-=`9q|V?O|X
zq*=?cme6(X%grEb=&5%Z8OngGi?0`pIU8)(*!7@+%?UD-y8U2*a>2*%*k=sDF`C|5
zyu!J!JRsjkvGQ{tU|BU(kYg-{U)!UYlbZ!&WtW9=kqNlEY=99cDeKjJ0f`F^oEN&)
z#?){FoL7Y1gr7xwOSN2uDq<MeDI%GDl%~eLg6YseWEVnQ#-Xf9qb78I^W>$6i`KU@
zUukp_R7NraUkG_6`Ih231ne@(oFd;E4KS??y{|yDwSQAIlvitno;TIK#dbysHKgef
zZMz&6V#0J=wo>eU%3}Hko0nVTq=~%w?DvN8zAJ?CP+qLWg{3l?51!o$d`G5fbw;f$
z$EtGOc;TMbE9cd{LBA!B1-)vWFKLL|7(AlMTHpJJspqSRsvK21i)J_Q^pzvW?rn6w
zwhw%1y_z{2RLLPxyE7;_IO7-iXnlx*{!?vwq|@L~J%ufnUvlT>v+{-X&pbK(&8W(8
zN6RR{_gqXMMe}V7<0$v2?-@-i=LcyMrd=sAb6@ypXf1#*Ku#*tRDh0EynJLuUtqYU
zUf87N%Vczq4rTnibZCj=@;RQP!ELMT0w4wcd0<myF58J)KBH>lZKtWKuEvgBgJfit
zJ~vU&MvqCxYk#-m%<~wopXH*1umq{n_l=Xip#T7mF>W?0rZt4@9?!bB3T8^OQ=n@i
zEX+H(Z3H}c7Y*pf-JRxG1~fzjY+EmuDR4EOd##u%%12kJ;F$Vl%)WV@T$xGTSwE~M
zXI`&!5+6CrU33~Vwwo&_lJ@1^nZ?<iwKtz1_!*QawS->W{?y$)N9;|468h+*y~a*Q
zPuiPY{h<e13il5!z{AdN&xOFCD2F{avfoTFaPXn#r2O<?a}xL$k~6{<;!Hv$SR8J5
zZHIC@|BFPJtGKbzK3Coje5mam=3f4vxCejSA9#pi{tgv3VW_YFl$u%P_nT(1nb60v
ziLq8@HgT~ri3u+0IIy{4EaGVvr4cSR?O}S1)sdUo&Nk7G(KeX9;UB1@N5%EPBsqqX
zU7}H}QH$z1nw%pp>82SjDg6lsYNKUfgY<-3`oKeRaTyP!uNQ-Y&)IQ6JV0j2CEhk?
zy<25!X2nhz$>zN-wki2@sDp1dJ-fg6l+xtH_fAg1M*07FZu-A3Rc2I)ABP+`&AtQw
z3-gcvCiTu-B$%w<E*-`kcH=brZk#l?A8j{jZb^rs2TVB4z6mEylW4(7$6Lh%X%7E{
z_zQ-dX5WyLh9u6-qyr((fsm<GWdAlr)9g>tq_z2Rnk22w5H;D)XG@y>S@37jvj?EU
zM}G?4pED1$|CusL#YgNw9ninTCY?EH_GeDgC-_mwNT0xm;c%40vPQEnYoyd4MU9k7
z_!sIw5=XNyaisJg1&)+{^7yaO|CT$NeYqp8J~0NV1YAB)eb@gf#($MQntkch*QTN-
Qa_RR@%f0^@l971#f5Dgkz5oCK
--- a/mobile/android/tests/browser/robocop/robocop.ini
+++ b/mobile/android/tests/browser/robocop/robocop.ini
@@ -163,16 +163,17 @@ skip-if = android_version == "10" || and
 [testJavascriptBridge]
 [testNativeCrypto]
 [testReaderModeTitle]
 [testSessionHistory]
 # disabled on Android 4.3, bug 1144879
 skip-if = android_version == "18"
 [testStateWhileLoading]
 
+[testSelectionCarets]
 # testSelectionHandler disabled on Android 2.3 by trailing skip-if, due to bug 980074
 # also disabled on Android 4.3, bug 1144882
 [testSelectionHandler]
 skip-if = android_version == "10" || android_version == "18"
 
 # testInputSelections disabled on Android 2.3 by trailing skip-if, due to bug 980074
 [testInputSelections]
 skip-if = android_version == "10"
--- a/mobile/android/tests/browser/robocop/testDistribution.java
+++ b/mobile/android/tests/browser/robocop/testDistribution.java
@@ -48,16 +48,18 @@ import android.util.Log;
  *     bookmarks.json
  *     searchplugins/
  *       common/
  *         engine.xml
  *     suggestedsites/
  *       locales/
  *         en-US/
  *           suggestedsites.json
+ *     extensions/
+ *       distribution.test@mozilla.org.xpi
  */
 public class testDistribution extends ContentProviderTest {
     private static final String CLASS_REFERRER_RECEIVER = "org.mozilla.gecko.distribution.ReferrerReceiver";
     private static final String ACTION_INSTALL_REFERRER = "com.android.vending.INSTALL_REFERRER";
     private static final int WAIT_TIMEOUT_MSEC = 10000;
     public static final String LOGTAG = "GeckoTestDistribution";
 
     public static class TestableDistribution extends Distribution {
@@ -147,16 +149,17 @@ public class testDistribution extends Co
 
         // Pre-clear distribution pref, run basic preferences and en-US localized preferences Tests
         clearDistributionPref();
         setTestLocale("en-US");
         initDistribution(mockPackagePath);
         checkPreferences();
         checkLocalizedPreferences("en-US");
         checkSearchPlugin();
+        checkAddon();
 
         // Pre-clear distribution pref, and run es-MX localized preferences Test
         clearDistributionPref();
         setTestLocale("es-MX");
         initDistribution(mockPackagePath);
         checkLocalizedPreferences("es-MX");
 
         // Test the (stubbed) download interaction.
@@ -298,30 +301,17 @@ public class testDistribution extends Co
         try {
             final String[] prefNames = { prefID,
                                          prefAbout,
                                          prefVersion,
                                          prefTestBoolean,
                                          prefTestString,
                                          prefTestInt };
 
-            Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-            mActions.sendPreferencesGetEvent(PREF_REQUEST_ID, prefNames);
-
-            JSONObject data = null;
-            int requestId = -1;
-
-            // Wait until we get the correct "Preferences:Data" event
-            while (requestId != PREF_REQUEST_ID) {
-                data = new JSONObject(eventExpecter.blockForEventData());
-                requestId = data.getInt("requestId");
-            }
-            eventExpecter.unregisterListener();
-
-            JSONArray preferences = data.getJSONArray("preferences");
+            final JSONArray preferences = getPrefs(prefNames);
             for (int i = 0; i < preferences.length(); i++) {
                 JSONObject pref = (JSONObject) preferences.get(i);
                 String name = pref.getString("name");
 
                 if (name.equals(prefID)) {
                     mAsserter.is(pref.getString("value"), "test-partner", "check " + prefID);
                 } else if (name.equals(prefAbout)) {
                     mAsserter.is(pref.getString("value"), "Test Partner", "check " + prefAbout);
@@ -359,16 +349,44 @@ public class testDistribution extends Co
                 }
             }
             mAsserter.ok(foundEngine, "check search plugin", "found test search plugin");
         } catch (JSONException e) {
             mAsserter.ok(false, "exception getting search plugins", e.toString());
         }
     }
 
+    private void checkAddon() {
+        try {
+            final String[] prefNames = { "distribution.test.addonEnabled" };
+            final JSONArray preferences = getPrefs(prefNames);
+            final JSONObject pref = (JSONObject) preferences.get(0);
+            mAsserter.is(pref.getBoolean("value"), true, "check distribution add-on is enabled");
+        } catch (JSONException e) {
+            mAsserter.ok(false, "exception getting preferences", e.toString());
+        }
+    }
+
+    private JSONArray getPrefs(String[] prefNames) throws JSONException {
+        Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
+        mActions.sendPreferencesGetEvent(PREF_REQUEST_ID, prefNames);
+
+        JSONObject data = null;
+        int requestId = -1;
+
+        // Wait until we get the correct "Preferences:Data" event
+        while (requestId != PREF_REQUEST_ID) {
+            data = new JSONObject(eventExpecter.blockForEventData());
+            requestId = data.getInt("requestId");
+        }
+        eventExpecter.unregisterListener();
+
+        return data.getJSONArray("preferences"); 
+    }
+
     // Sets the distribution locale preference for the test.
     private void setTestLocale(String locale) {
         BrowserLocaleManager.getInstance().setSelectedLocale(mActivity, locale);
     }
 
     // Test localized distribution and preferences values stored in preferences.json
     private void checkLocalizedPreferences(String aLocale) {
         String prefAbout = "distribution.about";
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/browser/robocop/testSelectionCarets.html
@@ -0,0 +1,35 @@
+<html>
+  <head>
+    <title>ActionBar Handler and SelectionCarets tests</title>
+    <meta name="viewport"
+      content="initial-scale=1, allowZoom=no, maximum-scale=1,
+               user-scalable=no, width=device-width">
+  </head>
+
+  <body>
+    <div id="LTRcontenteditable"
+      style="direction: ltr; width: 10em; height: 2em; word-wrap: break-word;
+      overflow: auto; -moz-user-select:text"
+      contenteditable="true">Find my book</div>
+    <div id="RTLcontenteditable"
+      style="direction: rtl; width: 10em; height: 2em; word-wrap: break-word;
+      overflow: auto; -moz-user-select:text"
+      contenteditable="true">איפה האוטו שלי</div>
+
+    <div id="LTRtextContent"
+      style="direction: ltr; width: 10em; height: 2em; word-wrap: break-word;
+      overflow: auto; -moz-user-select:text">Open the door</div>
+    <div id="RTLtextContent"
+      style="direction: rtl; width: 10em; height: 2em; word-wrap: break-word;
+      overflow: auto; -moz-user-select:text">תן לי מים</div>
+
+    <input id="LTRinput" style="direction: ltr;" value="Type something">
+    <input id="RTLinput" style="direction: rtl;" value="לרוץ במעלה הגבעה">
+    <br>
+
+    <textarea id="LTRtextarea" style="direction: ltr;"
+      rows="3" cols="8">Words in a box</textarea>
+    <textarea id="RTLtextarea" style="direction: rtl;"
+      rows="3" cols="8">הספר הוא טוב</textarea>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/browser/robocop/testSelectionCarets.java
@@ -0,0 +1,106 @@
+/**
+ * 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.tests;
+
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoEvent;
+import org.mozilla.gecko.Tab;
+import org.mozilla.gecko.Tabs;
+import org.mozilla.gecko.util.GeckoEventListener;
+
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class testSelectionCarets extends JavascriptTest implements GeckoEventListener {
+    private static final String LOGTAG = "testSelectionCarets";
+
+    private static final String LONGPRESS_EVENT = "testSelectionCarets:Longpress";
+    private static final String TAB_CHANGE_EVENT = "testSelectionCarets:TabChange";
+
+    private final TabsListener tabsListener;
+
+    public testSelectionCarets() {
+        super("testSelectionCarets.js");
+
+        tabsListener = new TabsListener();
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        Tabs.registerOnTabsChangedListener(tabsListener);
+        EventDispatcher.getInstance().registerGeckoThreadListener(this, LONGPRESS_EVENT);
+    }
+
+    @Override
+    public void testJavascript() throws Exception {
+        // This feature is currently only available in Nightly.
+        if (!AppConstants.NIGHTLY_BUILD) {
+            mAsserter.dumpLog(LOGTAG + " is disabled on non-Nightly builds: returning");
+            return;
+        }
+        super.testJavascript();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        Tabs.unregisterOnTabsChangedListener(tabsListener);
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, LONGPRESS_EVENT);
+
+        super.tearDown();
+    }
+
+    /**
+     * The test script will request us to trigger Longpress AndroidGeckoEvents.
+    */
+    @Override
+    public void handleMessage(String event, final JSONObject message) {
+        switch(event) {
+            case LONGPRESS_EVENT: {
+                final long meTime = SystemClock.uptimeMillis();
+                final int meX = Math.round(message.optInt("x", 0));
+                final int meY = Math.round(message.optInt("y", 0));
+                final MotionEvent motionEvent =
+                    MotionEvent.obtain(meTime, meTime, MotionEvent.ACTION_DOWN, meX, meY, 0);
+
+                final GeckoEvent geckoEvent = GeckoEvent.createLongPressEvent(motionEvent);
+                GeckoAppShell.sendEventToGecko(geckoEvent);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Observes tab change events to broadcast to the test script.
+     */
+    private class TabsListener implements Tabs.OnTabsChangedListener {
+        @Override
+        public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
+            switch (msg) {
+                case STOP:
+                    final JSONObject args = new JSONObject();
+                    try {
+                        args.put("tabId", tab.getId());
+                        args.put("event", msg.toString());
+                    } catch (JSONException e) {
+                        Log.e(LOGTAG, "Error building JSON arguments for " + TAB_CHANGE_EVENT, e);
+                        return;
+                    }
+                    final GeckoEvent event =
+                        GeckoEvent.createBroadcastEvent(TAB_CHANGE_EVENT, args.toString());
+                    GeckoAppShell.sendEventToGecko(event);
+                    break;
+            }
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/browser/robocop/testSelectionCarets.js
@@ -0,0 +1,244 @@
+// -*- 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";
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Messaging.jsm");
+Cu.import('resource://gre/modules/Geometry.jsm');
+
+const SELECTION_CARETS_PREF = "selectioncaret.enabled";
+const TOUCH_CARET_PREF = "touchcaret.enabled";
+const TEST_URL = "http://mochi.test:8888/tests/robocop/testSelectionCarets.html";
+
+// After longpress, Gecko notifys ActionBarHandler to init then update state.
+// When it does, we'll peek over its shoulder and test status.
+const LONGPRESS_EVENT = "testSelectionCarets:Longpress";
+const STATUS_UPDATE_EVENT = "ActionBar:UpdateState";
+
+// Ensures Tabs are completely loaded, viewport and zoom constraints updated, etc.
+const TAB_CHANGE_EVENT = "testSelectionCarets:TabChange";
+const TAB_STOP_EVENT = "STOP";
+
+const gChromeWin = Services.wm.getMostRecentWindow("navigator:browser");
+
+/**
+ * Robocop test helpers.
+ */
+function ok(passed, text) {
+  do_report_result(passed, text, Components.stack.caller, false);
+}
+
+function is(lhs, rhs, text) {
+  do_report_result(lhs === rhs, "[ " + lhs + " === " + rhs + " ] " + text,
+    Components.stack.caller, false);
+}
+
+/**
+ * Wait for and return, when an expected tab change event occurs.
+ *
+ * @param tabId, The id of the target tab we're observing.
+ * @param eventType, The event type we expect.
+ * @return {Promise}
+ * @resolves The tab change object, including the matched tab id and event.
+ */
+function do_promiseTabChangeEvent(tabId, eventType) {
+  return new Promise(resolve => {
+    let observer = (subject, topic, data) => {
+      let message = JSON.parse(data);
+
+      if (message.event === eventType && message.tabId === tabId) {
+        Services.obs.removeObserver(observer, TAB_CHANGE_EVENT);
+        resolve(data);
+      }
+    }
+
+    Services.obs.addObserver(observer, TAB_CHANGE_EVENT, false);
+  });
+}
+
+/**
+ * Selection methods vary if we have an input / textarea element,
+ * or if we have basic content.
+ */
+function isInputOrTextarea(element) {
+  return ((element instanceof Ci.nsIDOMHTMLInputElement) ||
+          (element instanceof Ci.nsIDOMHTMLTextAreaElement));
+}
+
+/**
+ * Return the selection controller based on element.
+ */
+function elementSelection(element) {
+  return (isInputOrTextarea(element)) ?
+    element.editor.selection :
+    element.ownerDocument.defaultView.getSelection();
+}
+
+/**
+ * Select the first character of a target element, w/o affecting focus.
+ */
+function selectElementFirstChar(doc, element) {
+  if (isInputOrTextarea(element)) {
+    element.setSelectionRange(0, 1);
+    return;
+  }
+
+  // Simple test cases designed firstChild == #text node.
+  let range = doc.createRange();
+  range.setStart(element.firstChild, 0);
+  range.setEnd(element.firstChild, 1);
+
+  let selection = elementSelection(element);
+  selection.removeAllRanges();
+  selection.addRange(range);
+}
+
+/**
+ * Get longpress point. Determine the midpoint in the first character of
+ * the content in the element. X will be midpoint from left to right.
+ * Y will be 1/3 of the height up from the bottom to account for both
+ * LTR and smaller RTL characters. ie: |X| vs. |א|
+ */
+function getFirstCharPressPoint(doc, element, expected) {
+  // Select the first char in the element.
+  selectElementFirstChar(doc, element);
+
+  // Reality check selected char to expected.
+  let selection = elementSelection(element);
+  is(selection.toString(), expected, "Selected char should match expected char.");
+
+  // Return a point where long press should select entire word.
+  let rect = selection.getRangeAt(0).getBoundingClientRect();
+  let r = new Point(rect.left + (rect.width / 2), rect.bottom - (rect.height / 3));
+
+  return r;
+}
+
+/**
+ * Long press an element (RTL/LTR) at its calculated first character
+ * position, and return the result.
+ *
+ * @param midPoint, The screen coord for the longpress.
+ * @return {Promise}
+ * @resolves The ActionBar status, including its target focused element, and
+ *           the selected text that it sees.
+ */
+function do_promiseLongPressResult(midPoint) {
+  return new Promise(resolve => {
+    let observer = (subject, topic, data) => {
+      let ActionBarHandler = gChromeWin.ActionBarHandler;
+      if (topic === STATUS_UPDATE_EVENT) {
+        let text = ActionBarHandler._getSelectedText();
+        if (text !== "") {
+          // Remove notification observer, and resolve.
+          Services.obs.removeObserver(observer, STATUS_UPDATE_EVENT);
+          resolve({
+            focusedElement: ActionBarHandler._targetElement,
+            text: text,
+          });
+        }
+      }
+    };
+
+    // Add notification observer, trigger the longpress and wait.
+    Services.obs.addObserver(observer, STATUS_UPDATE_EVENT, false);
+    Messaging.sendRequestForResult({
+      type: LONGPRESS_EVENT,
+      x: midPoint.x,
+      y: midPoint.y,
+    });
+  });
+}
+
+/**
+ * Main test method.
+ */
+add_task(function* testSelectionCarets() {
+  // Wait to start loading our test page until after the initial browser tab is
+  // completely loaded. This allows each tab to complete its layer initialization,
+  // importantly, its viewport and zoomContraints info.
+  let BrowserApp = gChromeWin.BrowserApp;
+  yield do_promiseTabChangeEvent(BrowserApp.selectedTab.id, TAB_STOP_EVENT);
+
+  // Ensure Gecko Selection and Touch carets are enabled.
+  Services.prefs.setBoolPref(SELECTION_CARETS_PREF, true);
+  Services.prefs.setBoolPref(TOUCH_CARET_PREF, true);
+
+  // Load test page, wait for load completion, register cleanup.
+  let browser = BrowserApp.addTab(TEST_URL).browser;
+  let tab = BrowserApp.getTabForBrowser(browser);
+  yield do_promiseTabChangeEvent(tab.id, TAB_STOP_EVENT);
+
+  do_register_cleanup(function cleanup() {
+    Services.prefs.clearUserPref(SELECTION_CARETS_PREF);
+    Services.prefs.clearUserPref(TOUCH_CARET_PREF);
+    BrowserApp.closeTab(tab);
+  });
+
+  // References to test document elements.
+  let doc = browser.contentDocument;
+  let ce_LTR_elem = doc.getElementById("LTRcontenteditable");
+  let tc_LTR_elem = doc.getElementById("LTRtextContent");
+  let i_LTR_elem = doc.getElementById("LTRinput");
+  let ta_LTR_elem = doc.getElementById("LTRtextarea");
+
+  let ce_RTL_elem = doc.getElementById("RTLcontenteditable");
+  let tc_RTL_elem = doc.getElementById("RTLtextContent");
+  let i_RTL_elem = doc.getElementById("RTLinput");
+  let ta_RTL_elem = doc.getElementById("RTLtextarea");
+
+  // Locate longpress midpoints for test elements, ensure expactations.
+  let ce_LTR_midPoint = getFirstCharPressPoint(doc, ce_LTR_elem, "F");
+  let tc_LTR_midPoint = getFirstCharPressPoint(doc, tc_LTR_elem, "O");
+  let i_LTR_midPoint = getFirstCharPressPoint(doc, i_LTR_elem, "T");
+  let ta_LTR_midPoint = getFirstCharPressPoint(doc, ta_LTR_elem, "W");
+
+  let ce_RTL_midPoint = getFirstCharPressPoint(doc, ce_RTL_elem, "א");
+  let tc_RTL_midPoint = getFirstCharPressPoint(doc, tc_RTL_elem, "ת");
+  let i_RTL_midPoint = getFirstCharPressPoint(doc, i_RTL_elem, "ל");
+  let ta_RTL_midPoint = getFirstCharPressPoint(doc, ta_RTL_elem, "ה");
+
+
+  // Longpress various LTR content elements. Test focused element against
+  // expected, and selected text against expected.
+  let result = yield do_promiseLongPressResult(ce_LTR_midPoint);
+  is(result.focusedElement, ce_LTR_elem, "Focused element should match expected.");
+  is(result.text, "Find", "Selected text should match expected text.");
+
+  result = yield do_promiseLongPressResult(tc_LTR_midPoint);
+  is(result.focusedElement, null, "No focused element is expected.");
+  is(result.text, "Open", "Selected text should match expected text.");
+
+  result = yield do_promiseLongPressResult(i_LTR_midPoint);
+  is(result.focusedElement, i_LTR_elem, "Focused element should match expected.");
+  is(result.text, "Type", "Selected text should match expected text.");
+
+  result = yield do_promiseLongPressResult(ta_LTR_midPoint);
+  is(result.focusedElement, ta_LTR_elem, "Focused element should match expected.");
+  is(result.text, "Words", "Selected text should match expected text.");
+
+  // Longpress various RTL content elements. Test focused element against
+  // expected, and selected text against expected.
+  result = yield do_promiseLongPressResult(ce_RTL_midPoint);
+  is(result.focusedElement, ce_RTL_elem, "Focused element should match expected.");
+  is(result.text, "איפה", "Selected text should match expected text.");
+
+  result = yield do_promiseLongPressResult(tc_RTL_midPoint);
+  is(result.focusedElement, null, "No focused element is expected.");
+  is(result.text, "תן", "Selected text should match expected text.");
+
+  result = yield do_promiseLongPressResult(i_RTL_midPoint);
+  is(result.focusedElement, i_RTL_elem, "Focused element should match expected.");
+  is(result.text, "לרוץ", "Selected text should match expected text.");
+
+  result = yield do_promiseLongPressResult(ta_RTL_midPoint);
+  is(result.focusedElement, ta_RTL_elem, "Focused element should match expected.");
+  is(result.text, "הספר", "Selected text should match expected text.");
+
+  ok(true, "Finished all tests.");
+});
+
+run_next_test();
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4582,16 +4582,32 @@ pref("selectioncaret.enabled", false);
 
 // This will inflate size of selection caret frame when we checking if
 // user click on selection caret or not. In app units.
 pref("selectioncaret.inflatesize.threshold", 40);
 
 // Selection carets will fall-back to internal LongTap detector.
 pref("selectioncaret.detects.longtap", true);
 
+// Selection carets do not affect caret visibility.
+pref("selectioncaret.visibility.affectscaret", false);
+
+// Selection caret visibility does not observe composition
+// selections generated by soft keyboard managers.
+pref("selectioncaret.observes.compositions", false);
+
+// The Touch caret by default observes the b2g visibility rules, and
+// not the extended Android visibility rules that allow for touchcaret
+// display in empty editable fields, for example.
+pref("touchcaret.extendedvisibility", false);
+
+// Desktop and b2g don't need to open or close the Android
+// TextSelection (Actionbar) utility.
+pref("caret.manages-android-actionbar", false);
+
 // New implementation to unify touch-caret and selection-carets.
 pref("layout.accessiblecaret.enabled", false);
 
 // Timeout in milliseconds to hide the accessiblecaret under cursor mode while
 // no one touches it. Set the value to 0 to disable this feature.
 pref("layout.accessiblecaret.timeout_ms", 3000);
 
 // Wakelock is disabled by default.
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -13,16 +13,18 @@ const { DebuggerServer } = require("devt
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 const { dbg_assert, dumpn, update, fetch } = DevToolsUtils;
 const { dirname, joinURI } = require("devtools/toolkit/path");
 const promise = require("promise");
 const PromiseDebugging = require("PromiseDebugging");
 const xpcInspector = require("xpcInspector");
 const ScriptStore = require("./utils/ScriptStore");
 
+const { defer, resolve, reject, all } = require("devtools/toolkit/deprecated-sync-thenables");
+
 loader.lazyGetter(this, "Debugger", () => {
   let Debugger = require("Debugger");
   hackDebugger(Debugger);
   return Debugger;
 });
 loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true);
 loader.lazyRequireGetter(this, "SourceMapGenerator", "source-map", true);
 loader.lazyRequireGetter(this, "CssLogic", "devtools/styleinspector/css-logic", true);
@@ -759,17 +761,17 @@ ThreadActor.prototype = {
           return undefined;
         }
 
         packet.frame.where = {
           source: originalLocation.originalSourceActor.form(),
           line: originalLocation.originalLine,
           column: originalLocation.originalColumn
         };
-        Promise.resolve(onPacket(packet))
+        resolve(onPacket(packet))
           .then(null, error => {
             reportError(error);
             return {
               error: "unknownError",
               message: error.message + "\n" + error.stack
             };
           })
           .then(packet => {
@@ -934,18 +936,18 @@ ThreadActor.prototype = {
    * @param Object aRequest
    *        The request packet received over the RDP.
    * @returns A promise that resolves to true once the hooks are attached, or is
    *          rejected with an error packet.
    */
   _handleResumeLimit: function (aRequest) {
     let steppingType = aRequest.resumeLimit.type;
     if (["break", "step", "next", "finish"].indexOf(steppingType) == -1) {
-      return Promise.reject({ error: "badParameterType",
-                              message: "Unknown resumeLimit type" });
+      return reject({ error: "badParameterType",
+                      message: "Unknown resumeLimit type" });
     }
 
     const generatedLocation = this.sources.getFrameLocation(this.youngestFrame);
     return this.sources.getOriginalLocation(generatedLocation)
       .then(originalLocation => {
         const { onEnterFrame, onPop, onStep } = this._makeSteppingHooks(originalLocation,
                                                                         steppingType);
 
@@ -1048,17 +1050,17 @@ ThreadActor.prototype = {
       return this._forceCompletion(aRequest);
     }
 
     let resumeLimitHandled;
     if (aRequest && aRequest.resumeLimit) {
       resumeLimitHandled = this._handleResumeLimit(aRequest)
     } else {
       this._clearSteppingHooks(this.youngestFrame);
-      resumeLimitHandled = Promise.resolve(true);
+      resumeLimitHandled = resolve(true);
     }
 
     return resumeLimitHandled.then(() => {
       if (aRequest) {
         this._options.pauseOnExceptions = aRequest.pauseOnExceptions;
         this._options.ignoreCaughtExceptions = aRequest.ignoreCaughtExceptions;
         this.maybePauseOnExceptions();
         this._maybeListenToEvents(aRequest);
@@ -1310,17 +1312,17 @@ ThreadActor.prototype = {
           line: originalLocation.originalLine,
           column: originalLocation.originalColumn
         };
         form.source = sourceForm;
       });
       promises.push(promise);
     }
 
-    return Promise.all(promises).then(function () {
+    return all(promises).then(function () {
       return { frames: frames };
     });
   },
 
   onReleaseMany: function (aRequest) {
     if (!aRequest.actors) {
       return { error: "missingParameter",
                message: "no actors were specified" };
@@ -1350,18 +1352,18 @@ ThreadActor.prototype = {
     const scripts = this.scripts.getAllScripts();
     for (let i = 0, len = scripts.length; i < len; i++) {
       let s = scripts[i];
       if (s.source) {
         sourcesToScripts.set(s.source, s);
       }
     }
 
-    return Promise.all([this.sources.createSourceActors(script.source)
-                       for (script of sourcesToScripts.values())]);
+    return all([this.sources.createSourceActors(script.source)
+                for (script of sourcesToScripts.values())]);
   },
 
   onSources: function (aRequest) {
     return this._discoverSources().then(() => {
       return {
         sources: [s.form() for (s of this.sources.iter())]
       };
     });
@@ -2503,17 +2505,17 @@ SourceActor.prototype = {
 
     return offsets;
   },
 
   /**
    * Handler for the "source" packet.
    */
   onSource: function () {
-    return Promise.resolve(this._init)
+    return resolve(this._init)
       .then(this._getSourceText)
       .then(({ content, contentType }) => {
         return {
           from: this.actorID,
           source: this.threadActor.createValueGrip(
             content, this.threadActor.threadLifetimePool),
           contentType: contentType
         };
--- a/toolkit/mozapps/extensions/LightweightThemeManager.jsm
+++ b/toolkit/mozapps/extensions/LightweightThemeManager.jsm
@@ -297,17 +297,17 @@ this.LightweightThemeManager = {
           _notifyWindows(this.currentThemeForDisplay);
         }.bind(this));
       }
     }
 
     if (aData)
       _prefs.setCharPref("selectedThemeID", aData.id);
     else
-      _prefs.deleteBranch("selectedThemeID");
+      _prefs.setCharPref("selectedThemeID", "");
 
     _notifyWindows(aData);
     Services.obs.notifyObservers(null, "lightweight-theme-changed", null);
   },
 
   /**
    * Starts the Addons provider and enables the new lightweight theme if
    * necessary.
--- a/toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js
@@ -44,16 +44,23 @@ function resetPrefs() {
   Services.prefs.setIntPref("bootstraptest.install_reason", -1);
   Services.prefs.setIntPref("bootstraptest.uninstall_reason", -1);
   Services.prefs.setIntPref("bootstraptest.startup_oldversion", -1);
   Services.prefs.setIntPref("bootstraptest.shutdown_newversion", -1);
   Services.prefs.setIntPref("bootstraptest.install_oldversion", -1);
   Services.prefs.setIntPref("bootstraptest.uninstall_newversion", -1);
 }
 
+function clearCache(file) {
+  if (TEST_UNPACKED)
+    return;
+
+  Services.obs.notifyObservers(file, "flush-cache-entry", null);
+}
+
 function getActiveVersion() {
   return Services.prefs.getIntPref("bootstraptest.active_version");
 }
 
 function run_test() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4");
 
   // Start and stop the manager to initialise everything in the profile before
@@ -62,97 +69,101 @@ function run_test() {
   shutdownManager();
   resetPrefs();
 
   run_next_test();
 }
 
 // Injecting into profile (bootstrap)
 add_task(function*() {
-  manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.unsigned), profileDir, ID);
+  let file = manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.unsigned), profileDir, ID);
 
   startupManager();
 
   // Currently we leave the sideloaded add-on there but just don't run it
   let addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
   do_check_true(addon.appDisabled);
   do_check_false(addon.isActive);
   do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
   do_check_eq(getActiveVersion(), -1);
 
   addon.uninstall();
   yield promiseShutdownManager();
   resetPrefs();
 
-  do_check_false(getFileForAddon(profileDir, ID).exists());
+  do_check_false(file.exists());
+  clearCache(file);
 });
 
 add_task(function*() {
-  manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.signed), profileDir, ID);
-  breakAddon(getFileForAddon(profileDir, ID));
+  let file = manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.signed), profileDir, ID);
+  breakAddon(file);
 
   startupManager();
 
   // Currently we leave the sideloaded add-on there but just don't run it
   let addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
   do_check_true(addon.appDisabled);
   do_check_false(addon.isActive);
   do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
   do_check_eq(getActiveVersion(), -1);
 
   addon.uninstall();
   yield promiseShutdownManager();
   resetPrefs();
 
-  do_check_false(getFileForAddon(profileDir, ID).exists());
+  do_check_false(file.exists());
+  clearCache(file);
 });
 
 add_task(function*() {
-  manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.badid), profileDir, ID);
+  let file = manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.badid), profileDir, ID);
 
   startupManager();
 
   // Currently we leave the sideloaded add-on there but just don't run it
   let addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
   do_check_true(addon.appDisabled);
   do_check_false(addon.isActive);
   do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
   do_check_eq(getActiveVersion(), -1);
 
   addon.uninstall();
   yield promiseShutdownManager();
   resetPrefs();
 
-  do_check_false(getFileForAddon(profileDir, ID).exists());
+  do_check_false(file.exists());
+  clearCache(file);
 });
 
 // Installs a signed add-on then modifies it in place breaking its signing
 add_task(function*() {
-  manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.signed), profileDir, ID);
+  let file = manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.signed), profileDir, ID);
 
   // Make it appear to come from the past so when we modify it later it is
   // detected during startup. Obviously malware can bypass this method of
   // detection but the periodic scan will catch that
-  yield promiseSetExtensionModifiedTime(getFileForAddon(profileDir, ID).path, Date.now() - 600000);
+  yield promiseSetExtensionModifiedTime(file.path, Date.now() - 600000);
 
   startupManager();
   let addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
   do_check_false(addon.appDisabled);
   do_check_true(addon.isActive);
   do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SIGNED);
   do_check_eq(getActiveVersion(), 2);
 
   yield promiseShutdownManager();
   do_check_eq(getActiveVersion(), 0);
 
-  breakAddon(getFileForAddon(profileDir, ID));
+  clearCache(file);
+  breakAddon(file);
   resetPrefs();
 
   startupManager();
 
   addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
   do_check_true(addon.appDisabled);
   do_check_false(addon.isActive);
@@ -162,101 +173,106 @@ add_task(function*() {
   let ids = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED);
   do_check_eq(ids.length, 1);
   do_check_eq(ids[0], ID);
 
   addon.uninstall();
   yield promiseShutdownManager();
   resetPrefs();
 
-  do_check_false(getFileForAddon(profileDir, ID).exists());
+  do_check_false(file.exists());
+  clearCache(file);
 });
 
 // Injecting into profile (non-bootstrap)
 add_task(function*() {
-  manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.unsigned), profileDir, ID);
+  let file = manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.unsigned), profileDir, ID);
 
   startupManager();
 
   // Currently we leave the sideloaded add-on there but just don't run it
   let addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
   do_check_true(addon.appDisabled);
   do_check_false(addon.isActive);
   do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
   do_check_false(isExtensionInAddonsList(profileDir, ID));
 
   addon.uninstall();
   yield promiseRestartManager();
   yield promiseShutdownManager();
 
-  do_check_false(getFileForAddon(profileDir, ID).exists());
+  do_check_false(file.exists());
+  clearCache(file);
 });
 
 add_task(function*() {
-  manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.signed), profileDir, ID);
-  breakAddon(getFileForAddon(profileDir, ID));
+  let file = manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.signed), profileDir, ID);
+  breakAddon(file);
 
   startupManager();
 
   // Currently we leave the sideloaded add-on there but just don't run it
   let addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
   do_check_true(addon.appDisabled);
   do_check_false(addon.isActive);
   do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
   do_check_false(isExtensionInAddonsList(profileDir, ID));
 
   addon.uninstall();
   yield promiseRestartManager();
   yield promiseShutdownManager();
 
-  do_check_false(getFileForAddon(profileDir, ID).exists());
+  do_check_false(file.exists());
+  clearCache(file);
 });
 
 add_task(function*() {
-  manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.badid), profileDir, ID);
+  let file = manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.badid), profileDir, ID);
 
   startupManager();
 
   // Currently we leave the sideloaded add-on there but just don't run it
   let addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
   do_check_true(addon.appDisabled);
   do_check_false(addon.isActive);
   do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
   do_check_false(isExtensionInAddonsList(profileDir, ID));
 
   addon.uninstall();
   yield promiseRestartManager();
   yield promiseShutdownManager();
 
-  do_check_false(getFileForAddon(profileDir, ID).exists());
+  do_check_false(file.exists());
+  clearCache(file);
 });
 
 // Installs a signed add-on then modifies it in place breaking its signing
 add_task(function*() {
-  manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.signed), profileDir, ID);
+  let file = manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.signed), profileDir, ID);
 
   // Make it appear to come from the past so when we modify it later it is
   // detected during startup. Obviously malware can bypass this method of
   // detection but the periodic scan will catch that
-  yield promiseSetExtensionModifiedTime(getFileForAddon(profileDir, ID).path, Date.now() - 60000);
+  yield promiseSetExtensionModifiedTime(file.path, Date.now() - 60000);
 
   startupManager();
   let addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
   do_check_false(addon.appDisabled);
   do_check_true(addon.isActive);
   do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SIGNED);
   do_check_true(isExtensionInAddonsList(profileDir, ID));
 
   yield promiseShutdownManager();
 
-  breakAddon(getFileForAddon(profileDir, ID));
+  clearCache(file);
+  breakAddon(file);
 
   startupManager();
 
   addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
   do_check_true(addon.appDisabled);
   do_check_false(addon.isActive);
   do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
@@ -265,17 +281,18 @@ add_task(function*() {
   let ids = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED);
   do_check_eq(ids.length, 1);
   do_check_eq(ids[0], ID);
 
   addon.uninstall();
   yield promiseRestartManager();
   yield promiseShutdownManager();
 
-  do_check_false(getFileForAddon(profileDir, ID).exists());
+  do_check_false(file.exists());
+  clearCache(file);
 });
 
 // Stage install then modify before startup (non-bootstrap)
 add_task(function*() {
   startupManager();
   yield promiseInstallAllFiles([do_get_file(DATA + ADDONS.nonbootstrap.signed)]);
   yield promiseShutdownManager();
 
@@ -286,18 +303,17 @@ add_task(function*() {
 
   breakAddon(staged);
   startupManager();
 
   // Should have refused to install the broken staged version
   let addon = yield promiseAddonByID(ID);
   do_check_eq(addon, null);
 
-  let install = getFileForAddon(profileDir, ID);
-  do_check_false(install.exists());
+  clearCache(staged);
 
   yield promiseShutdownManager();
 });
 
 // Manufacture staged install (bootstrap)
 add_task(function*() {
   let stage = profileDir.clone();
   stage.append("staged");
@@ -307,14 +323,14 @@ add_task(function*() {
 
   startupManager();
 
   // Should have refused to install the broken staged version
   let addon = yield promiseAddonByID(ID);
   do_check_eq(addon, null);
   do_check_eq(getActiveVersion(), -1);
 
-  let install = getFileForAddon(profileDir, ID);
-  do_check_false(install.exists());
+  do_check_false(file.exists());
+  clearCache(file);
 
   yield promiseShutdownManager();
   resetPrefs();
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -233,17 +233,16 @@ skip-if = os == "android"
 [test_pluginBlocklistCtp.js]
 # Bug 676992: test consistently fails on Android
 fail-if = buildapp == "mulet" || os == "android"
 [test_pref_properties.js]
 [test_registry.js]
 [test_safemode.js]
 [test_signed_verify.js]
 [test_signed_inject.js]
-skip-if = true
 [test_signed_install.js]
 run-sequentially = Uses hardcoded ports in xpi files.
 [test_signed_migrate.js]
 [test_startup.js]
 # Bug 676992: test consistently fails on Android
 fail-if = os == "android"
 [test_syncGUID.js]
 [test_strictcompatibility.js]