merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 09 Oct 2015 11:36:38 +0200
changeset 266954 462074ffada4969135709b86de53210a277b11d3
parent 266912 16b558eac3e20d2d0c4c15f54b9105fa01ccc823 (current diff)
parent 266953 efea2819c5bc07fdafa534e27131cc02a9575bd5 (diff)
child 267023 d01dd42e654b8735d86f9e7c723cc869a3b56798
push id29503
push usercbook@mozilla.com
push dateFri, 09 Oct 2015 09:36:47 +0000
treeherdermozilla-central@462074ffada4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone44.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 fx-team to mozilla-central a=merge
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1212764 - Clobber needed for bug 957911 
+Bug 1212347 - Disable geckoview_example by default and forget build artifacts.
--- a/addon-sdk/source/lib/dev/theme.js
+++ b/addon-sdk/source/lib/dev/theme.js
@@ -118,18 +118,18 @@ onDisable.define(Theme, (theme, {window,
     theme.onDisable(window, newTheme);
   }
 });
 
 // Support for built-in themes
 
 const LightTheme = Theme({
   name: "theme-light",
-  styles: "chrome://browser/skin/devtools/light-theme.css",
+  styles: "chrome://devtools/skin/themes/light-theme.css",
 });
 
 const DarkTheme = Theme({
   name: "theme-dark",
-  styles: "chrome://browser/skin/devtools/dark-theme.css",
+  styles: "chrome://devtools/skin/themes/dark-theme.css",
 });
 
 exports.LightTheme = LightTheme;
 exports.DarkTheme = DarkTheme;
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -43,21 +43,27 @@ html[dir="rtl"] .conversation-toolbar {
 html[dir="rtl"] .conversation-toolbar > li {
   float: right;
   margin-left: .7rem;
   margin-right: auto;
 }
 
 .conversation-toolbar .btn {
   background-position: center;
-  background-size: 24px;
+  background-size: 28px;
   background-repeat: no-repeat;
   background-color: transparent;
-  height: 24px;
-  width: 24px;
+  height: 28px;
+  width: 33px;
+}
+
+.btn-hangup-entry > .btn {
+  /* Make the button the width of the background, so that we don't get an
+     extra gap which appears to push the toolbar in further than necessary */
+  width: 28px;
 }
 
 .conversation-toolbar-media-btn-group-box {
   background-position: center;
   background-repeat: no-repeat;
   background-color: transparent;
   background-image: url("../img/svg/media-group.svg");
   background-size: cover;
--- a/browser/components/loop/standalone/Makefile
+++ b/browser/components/loop/standalone/Makefile
@@ -21,17 +21,17 @@ install: npm_install
 npm_install:
 	@npm install
 
 # build the dist dir, which contains a production version of the code and
 # assets
 .PHONY: dist
 dist:
 	mkdir -p dist
-	cp -pR content dist
+	cp -pR content/* dist
 	NODE_ENV="production" $(NODE_LOCAL_BIN)/webpack \
 		-p -v --display-errors
 	sed 's#webappEntryPoint.js#js/standalone.js#' \
 		< content/index.html > dist/index.html
 
 .PHONY: distclean
 distclean:
 	rm -fr dist
--- a/browser/components/migration/ChromeProfileMigrator.js
+++ b/browser/components/migration/ChromeProfileMigrator.js
@@ -47,33 +47,41 @@ function chromeTimeToDate(aTime)
 
 /**
  * Insert bookmark items into specific folder.
  *
  * @param   parentGuid
  *          GUID of the folder where items will be inserted
  * @param   items
  *          bookmark items to be inserted
+ * @param   errorAccumulator
+ *          function that gets called with any errors thrown so we don't drop them on the floor.
  */
-function* insertBookmarkItems(parentGuid, items) {
+function* insertBookmarkItems(parentGuid, items, errorAccumulator) {
   for (let item of items) {
     try {
       if (item.type == "url") {
+        if (item.url.trim().startsWith("chrome:")) {
+          // Skip invalid chrome URIs. Creating an actual URI always reports
+          // messages to the console, so we avoid doing that.
+          continue;
+        }
         yield PlacesUtils.bookmarks.insert({
           parentGuid, url: item.url, title: item.name
         });
       } else if (item.type == "folder") {
         let newFolderGuid = (yield PlacesUtils.bookmarks.insert({
           parentGuid, type: PlacesUtils.bookmarks.TYPE_FOLDER, title: item.name
         })).guid;
 
-        yield insertBookmarkItems(newFolderGuid, item.children);
+        yield insertBookmarkItems(newFolderGuid, item.children, errorAccumulator);
       }
     } catch (e) {
       Cu.reportError(e);
+      errorAccumulator(e);
     }
   }
 }
 
 
 function ChromeProfileMigrator() {
   let chromeUserDataFolder = FileUtils.getDir(
 #ifdef XP_WIN
@@ -197,16 +205,18 @@ function GetBookmarksResource(aProfileFo
   if (!bookmarksFile.exists())
     return null;
 
   return {
     type: MigrationUtils.resourceTypes.BOOKMARKS,
 
     migrate: function(aCallback) {
       return Task.spawn(function* () {
+        let gotErrors = false;
+        let errorGatherer = () => gotErrors = true;
         let jsonStream = yield new Promise(resolve =>
           NetUtil.asyncFetch({ uri: NetUtil.newURI(bookmarksFile),
                                loadUsingSystemPrincipal: true
                              },
                              (inputStream, resultCode) => {
                                if (Components.isSuccessCode(resultCode)) {
                                  resolve(inputStream);
                                } else {
@@ -225,32 +235,35 @@ function GetBookmarksResource(aProfileFo
         if (roots.bookmark_bar.children &&
             roots.bookmark_bar.children.length > 0) {
           // Toolbar
           let parentGuid = PlacesUtils.bookmarks.toolbarGuid;
           if (!MigrationUtils.isStartupMigration) {
             parentGuid =
               yield MigrationUtils.createImportedBookmarksFolder("Chrome", parentGuid);
           }
-          yield insertBookmarkItems(parentGuid, roots.bookmark_bar.children);
+          yield insertBookmarkItems(parentGuid, roots.bookmark_bar.children, errorGatherer);
         }
 
         // Importing bookmark menu items
         if (roots.other.children &&
             roots.other.children.length > 0) {
           // Bookmark menu
           let parentGuid = PlacesUtils.bookmarks.menuGuid;
           if (!MigrationUtils.isStartupMigration) {
             parentGuid =
               yield MigrationUtils.createImportedBookmarksFolder("Chrome", parentGuid);
           }
-          yield insertBookmarkItems(parentGuid, roots.other.children);
+          yield insertBookmarkItems(parentGuid, roots.other.children, errorGatherer);
+        }
+        if (gotErrors) {
+          throw "The migration included errors.";
         }
       }.bind(this)).then(() => aCallback(true),
-                          e => { Cu.reportError(e); aCallback(false) });
+                          e => aCallback(false));
     }
   };
 }
 
 function GetHistoryResource(aProfileFolder) {
   let historyFile = aProfileFolder.clone();
   historyFile.append("History");
   if (!historyFile.exists())
--- a/browser/components/migration/MSMigrationUtils.jsm
+++ b/browser/components/migration/MSMigrationUtils.jsm
@@ -652,29 +652,45 @@ Cookies.prototype = {
    *  - host/path
    *  - flags
    *  - Expiration time most significant integer
    *  - Expiration time least significant integer
    *  - Creation time most significant integer
    *  - Creation time least significant integer
    *  - Record delimiter "*"
    *
+   * Unfortunately, "*" can also occur inside the value of the cookie, so we
+   * can't rely exclusively on it as a record separator.
+   *
    * @note All the times are in FILETIME format.
    */
   _parseCookieBuffer(aTextBuffer) {
-    // Note the last record is an empty string.
-    let records = [r for each (r in aTextBuffer.split("*\n")) if (r)];
+    // Note the last record is an empty string...
+    let records = [];
+    let lines = aTextBuffer.split("\n");
+    while (lines.length > 0) {
+      let record = lines.splice(0, 9);
+      // ... which means this is going to be a 1-element array for that record
+      if (record.length > 1) {
+        records.push(record);
+      }
+    }
     for (let record of records) {
       let [name, value, hostpath, flags,
-           expireTimeLo, expireTimeHi] = record.split("\n");
+           expireTimeLo, expireTimeHi] = record;
 
       // IE stores deleted cookies with a zero-length value, skip them.
       if (value.length == 0)
         continue;
 
+      // IE sometimes has cookies created by apps that use "~~local~~/local/file/path"
+      // as the hostpath, ignore those:
+      if (hostpath.startsWith("~~local~~"))
+        continue;
+
       let hostLen = hostpath.indexOf("/");
       let host = hostpath.substr(0, hostLen);
       let path = hostpath.substr(hostLen);
 
       // For a non-null domain, assume it's what Mozilla considers
       // a domain cookie.  See bug 222343.
       if (host.length > 0) {
         // Fist delete any possible extant matching host cookie.
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -269,17 +269,17 @@ PlacesController.prototype = {
       break;
     case "placesCmd_new:folder":
       this.newItem("folder");
       break;
     case "placesCmd_new:bookmark":
       this.newItem("bookmark");
       break;
     case "placesCmd_new:separator":
-      this.newSeparator().catch(Cu.reportError);
+      this.newSeparator().catch(Components.utils.reportError);
       break;
     case "placesCmd_show:info":
       this.showBookmarkPropertiesForSelection();
       break;
     case "placesCmd_moveBookmarks":
       this.moveSelectedBookmarks();
       break;
     case "placesCmd_reload":
@@ -1304,18 +1304,18 @@ PlacesController.prototype = {
 
           for (let item of items) {
             let doCopy = action == "copy";
 
             // If this is not a copy, check for safety that we can move the
             // source, otherwise report an error and fallback to a copy.
             if (!doCopy &&
                 !PlacesControllerDragHelper.canMoveUnwrappedNode(item)) {
-              Cu.reportError("Tried to move an unmovable Places node, " +
-                             "reverting to a copy operation.");
+              Components.utils.reportError("Tried to move an unmovable " +
+                             "Places node, reverting to a copy operation.");
               doCopy = true;
             }
             let guid = yield PlacesUIUtils.getTransactionForData(
               item, type, parent, insertionIndex, doCopy).transact();
             itemsToSelect.push(yield PlacesUtils.promiseItemId(guid));
 
             // Adjust index to make sure items are pasted in the correct
             // position.  If index is DEFAULT_INDEX, items are just appended.
@@ -1341,18 +1341,18 @@ PlacesController.prototype = {
         // Adjust index to make sure items are pasted in the correct position.
         // If index is DEFAULT_INDEX, items are just appended.
         if (ip.index != PlacesUtils.bookmarks.DEFAULT_INDEX)
           insertionIndex = ip.index + i;
 
         // If this is not a copy, check for safety that we can move the source,
         // otherwise report an error and fallback to a copy.
         if (action != "copy" && !PlacesControllerDragHelper.canMoveUnwrappedNode(items[i])) {
-          Components.utils.reportError("Tried to move an unmovable Places node, " +
-                                       "reverting to a copy operation.");
+          Components.utils.reportError("Tried to move an unmovable Places " +
+                                       "node, reverting to a copy operation.");
           action = "copy";
         }
         transactions.push(
           PlacesUIUtils.makeTransaction(items[i], type, ip.itemId,
                                         insertionIndex, action == "copy")
         );
       }
 
@@ -1620,18 +1620,18 @@ var PlacesControllerDragHelper = {
           transactions.push(PlacesTransactions.Tag({ uri: uri, tag: tagName }));
         else
           transactions.push(new PlacesTagURITransaction(uri, [tagItemId]));
       }
       else {
         // If this is not a copy, check for safety that we can move the source,
         // otherwise report an error and fallback to a copy.
         if (!doCopy && !PlacesControllerDragHelper.canMoveUnwrappedNode(unwrapped)) {
-          Components.utils.reportError("Tried to move an unmovable Places node, " +
-                                       "reverting to a copy operation.");
+          Components.utils.reportError("Tried to move an unmovable Places " +
+                                       "node, reverting to a copy operation.");
           doCopy = true;
         }
         if (PlacesUIUtils.useAsyncTransactions) {
           transactions.push(
             PlacesUIUtils.getTransactionForData(unwrapped,
                                                 flavor,
                                                 parentGuid,
                                                 index,
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -2485,17 +2485,17 @@ var SessionStoreInternal = {
     let state = {
       windows: total,
       selectedWindow: ix + 1,
       _closedWindows: lastClosedWindowsCopy,
       session: session,
       global: this._globalState.getState()
     };
 
-    if (Cu.isModuleLoaded("resource:///modules/devtools/scratchpad-manager.jsm")) {
+    if (Cu.isModuleLoaded("resource:///modules/devtools/client/scratchpad/scratchpad-manager.jsm")) {
       // get open Scratchpad window states too
       let scratchpads = ScratchpadManager.getSessionState();
       if (scratchpads && scratchpads.length) {
         state.scratchpads = scratchpads;
       }
     }
 
     // Persist the last session if we deferred restoring it
--- a/build/mobile/robocop/AndroidManifest.xml.in
+++ b/build/mobile/robocop/AndroidManifest.xml.in
@@ -7,17 +7,17 @@
 #endif
     android:versionCode="1"
     android:versionName="1.0" >
 
     <uses-sdk android:minSdkVersion="@MOZ_ANDROID_MIN_SDK_VERSION@"
 #ifdef MOZ_ANDROID_MAX_SDK_VERSION
               android:maxSdkVersion="@MOZ_ANDROID_MAX_SDK_VERSION@"
 #endif
-              android:targetSdkVersion="@ANDROID_TARGET_SDK@"/>
+              android:targetSdkVersion="22"/>
 
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
     <instrumentation
         android:name="org.mozilla.gecko.FennecInstrumentationTestRunner"
         android:targetPackage="@ANDROID_PACKAGE_NAME@" />
 
     <application
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -34,16 +34,17 @@ support-files =
 [browser_inspector_breadcrumbs_keybinding.js]
 [browser_inspector_breadcrumbs_menu.js]
 [browser_inspector_breadcrumbs_mutations.js]
 [browser_inspector_delete-selected-node-01.js]
 [browser_inspector_delete-selected-node-02.js]
 [browser_inspector_delete-selected-node-03.js]
 [browser_inspector_destroy-after-navigation.js]
 [browser_inspector_destroy-before-ready.js]
+[browser_inspector_expand-collapse.js]
 [browser_inspector_gcli-inspect-command.js]
 skip-if = e10s # GCLI isn't e10s compatible. See bug 1128988.
 [browser_inspector_highlighter-01.js]
 [browser_inspector_highlighter-02.js]
 [browser_inspector_highlighter-03.js]
 [browser_inspector_highlighter-04.js]
 [browser_inspector_highlighter-by-type.js]
 [browser_inspector_highlighter-comments.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_expand-collapse.js
@@ -0,0 +1,54 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that context menu items exapnd all and collapse are shown properly.
+
+const TEST_URL = "data:text/html;charset=utf-8,<div id='parent-node'><div id='child-node'></div></div>";
+
+add_task(function* () {
+
+    // Test is often exceeding time-out threshold, similar to Bug 1137765
+    requestLongerTimeout(2);
+
+    let {inspector, testActor} = yield openInspectorForURL(TEST_URL);
+
+    let nodeMenuCollapseElement = inspector.panelDoc.getElementById("node-menu-collapse");
+    let nodeMenuExpandElement = inspector.panelDoc.getElementById("node-menu-expand");
+
+    info("Selecting the parent node");
+
+    let front = yield getNodeFrontForSelector("#parent-node", inspector);
+
+    yield selectNode(front, inspector);
+
+    info("Simulating context menu click on the selected node container.");
+    contextMenuClick(getContainerForNodeFront(front, inspector).tagLine);
+
+    ok(nodeMenuCollapseElement.hasAttribute("disabled"), "Collapse option is disabled");
+
+    ok(!nodeMenuExpandElement.hasAttribute("disabled"), "ExpandAll option is enabled");
+
+    info("Testing whether expansion works properly");
+    dispatchCommandEvent(nodeMenuExpandElement);
+    info("Waiting for expansion to occur");
+    yield waitForMultipleChildrenUpdates(inspector);
+    let markUpContainer = getContainerForNodeFront(front, inspector);
+    ok(markUpContainer.expanded, "node has been successfully expanded");
+
+    //reslecting node after expansion
+    yield selectNode(front, inspector);
+
+    info("Testing whether collapse works properly");
+    info("Simulating context menu click on the selected node container.");
+    contextMenuClick(getContainerForNodeFront(front, inspector).tagLine);
+
+    ok(!nodeMenuCollapseElement.hasAttribute("disabled"), "Collapse option is enabled");
+
+    dispatchCommandEvent(nodeMenuCollapseElement);
+    info("Waiting for collapse to occur");
+    yield waitForMultipleChildrenUpdates(inspector);
+    ok(!markUpContainer.expanded, "node has been successfully collapsed");
+});
\ No newline at end of file
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -477,16 +477,45 @@ function dispatchCommandEvent(node) {
   info("Dispatching command event on " + node);
   let commandEvent = document.createEvent("XULCommandEvent");
   commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
                                 false, false, null);
   node.dispatchEvent(commandEvent);
 }
 
 /**
+ * A helper that simulates a contextmenu event on the given chrome DOM element.
+ */
+function contextMenuClick(element) {
+  let evt = element.ownerDocument.createEvent('MouseEvents');
+  let button = 2;  // right click
+
+  evt.initMouseEvent('contextmenu', true, true,
+       element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false,
+       false, false, false, button, null);
+
+  element.dispatchEvent(evt);
+}
+
+/**
+ * A helper that fetches a front for a node that matches the given selector or
+ * doctype node if the selector is falsy.
+ */
+function* getNodeFrontForSelector(selector, inspector) {
+  if (selector) {
+    info("Retrieving front for selector " + selector);
+    return getNodeFront(selector, inspector);
+  } else {
+    info("Retrieving front for doctype node");
+    let {nodes} = yield inspector.walker.children(inspector.walker.rootNode);
+    return nodes[0];
+  }
+}
+
+/**
  * Encapsulate some common operations for highlighter's tests, to have
  * the tests cleaner, without exposing directly `inspector`, `highlighter`, and
  * `testActor` if not needed.
  *
  * @param  {String}
  *    The highlighter's type
  * @return
  *    A generator function that takes an object with `inspector` and `testActor`
@@ -529,8 +558,38 @@ const getHighlighterHelperFor = (type) =
       },
 
       finalize: function*() {
         yield highlighter.finalize();
       }
     };
   }
 );
+
+// The expand all operation of the markup-view calls itself recursively and
+// there's not one event we can wait for to know when it's done
+// so use this helper function to wait until all recursive children updates are done.
+function* waitForMultipleChildrenUpdates(inspector) {
+// As long as child updates are queued up while we wait for an update already
+// wait again
+    if (inspector.markup._queuedChildUpdates &&
+        inspector.markup._queuedChildUpdates.size) {
+        yield waitForChildrenUpdated(inspector);
+        return yield waitForMultipleChildrenUpdates(inspector);
+    }
+}
+
+/**
+ * Using the markupview's _waitForChildren function, wait for all queued
+ * children updates to be handled.
+ * @param {InspectorPanel} inspector The instance of InspectorPanel currently
+ * loaded in the toolbox
+ * @return a promise that resolves when all queued children updates have been
+ * handled
+ */
+function waitForChildrenUpdated({markup}) {
+    info("Waiting for queued children updates to be handled");
+    let def = promise.defer();
+    markup._waitForChildren().then(() => {
+        executeSoon(def.resolve);
+    });
+    return def.promise;
+}
--- a/devtools/client/memory/memory.xhtml
+++ b/devtools/client/memory/memory.xhtml
@@ -8,19 +8,19 @@
 
 <!-- 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/. -->
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <link rel="stylesheet" href="chrome://browser/skin/" type="text/css"/>
     <link rel="stylesheet" href="chrome://browser/content/devtools/widgets.css" type="text/css"/>
-    <link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
-    <link rel="stylesheet" href="chrome://browser/skin/devtools/widgets.css" type="text/css"/>
-    <link rel="stylesheet" href="chrome://browser/skin/devtools/memory.css" type="text/css"/>
+    <link rel="stylesheet" href="chrome://devtools/skin/themes/common.css" type="text/css"/>
+    <link rel="stylesheet" href="chrome://devtools/skin/themes/widgets.css" type="text/css"/>
+    <link rel="stylesheet" href="chrome://devtools/skin/themes/memory.css" type="text/css"/>
 
     <script type="application/javascript;version=1.8"
             src="chrome://devtools/content/shared/theme-switching.js"/>
     <script type="application/javascript;version=1.8"
             src="initializer.js"></script>
   </head>
   <body class="theme-body">
     <div class="devtools-toolbar">
--- a/devtools/client/memory/test/mochitest/test_census-view-01.html
+++ b/devtools/client/memory/test/mochitest/test_census-view-01.html
@@ -3,20 +3,20 @@
 <!--
 Bug 1067491 - Test taking a census over the RDP.
 -->
 <head>
   <meta charset="utf-8">
   <title>Census Tree 01</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link href="chrome://browser/content/devtools/widgets.css" type="text/css" />
-  <link href="chrome://browser/skin/devtools/light-theme.css" type="text/css" />
-  <link href="chrome://browser/skin/devtools/common.css" type="text/css" />
-  <link href="chrome://browser/skin/devtools/widgets.css" type="text/css" />
-  <link href="chrome://browser/skin/devtools/memory.css" type="text/css" />
+  <link href="chrome://devtools/skin/themes/light-theme.css" type="text/css" />
+  <link href="chrome://devtools/skin/themes/common.css" type="text/css" />
+  <link href="chrome://devtools/skin/themes/widgets.css" type="text/css" />
+  <link href="chrome://devtools/skin/themes/memory.css" type="text/css" />
 </head>
 <body>
 <ul id="container" style="width:100%;height:300px;"></ul>
 <pre id="test">
 <script src="head.js" type="application/javascript;version=1.8"></script>
 <script>
 window.onload = function() {
   var { CensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
--- a/devtools/client/shared/widgets/Tooltip.js
+++ b/devtools/client/shared/widgets/Tooltip.js
@@ -1140,18 +1140,17 @@ SwatchColorPickerTooltip.prototype = Her
    * color.
    */
   show: function() {
     // Call then parent class' show function
     SwatchBasedEditorTooltip.prototype.show.call(this);
     // Then set spectrum's color and listen to color changes to preview them
     if (this.activeSwatch) {
       this.currentSwatchColor = this.activeSwatch.nextSibling;
-      this._colorUnit =
-        colorUtils.classifyColor(this.currentSwatchColor.textContent);
+      this._originalColor = this.currentSwatchColor.textContent;
       let color = this.activeSwatch.style.backgroundColor;
       this.spectrum.then(spectrum => {
         spectrum.off("changed", this._onSpectrumColorChange);
         spectrum.rgb = this._colorToRgba(color);
         spectrum.on("changed", this._onSpectrumColorChange);
         spectrum.updateUI();
       });
     }
@@ -1219,17 +1218,17 @@ SwatchColorPickerTooltip.prototype = Her
   _colorToRgba: function(color) {
     color = new colorUtils.CssColor(color);
     let rgba = color._getRGBATuple();
     return [rgba.r, rgba.g, rgba.b, rgba.a];
   },
 
   _toDefaultType: function(color) {
     let colorObj = new colorUtils.CssColor(color);
-    colorObj.colorUnit = this._colorUnit;
+    colorObj.setAuthoredUnitFromColor(this._originalColor);
     return colorObj.toString();
   },
 
   destroy: function() {
     SwatchBasedEditorTooltip.prototype.destroy.call(this);
     this.currentSwatchColor = null;
     this.spectrum.then(spectrum => {
       spectrum.off("changed", this._onSpectrumColorChange);
--- a/devtools/client/styleinspector/test/browser.ini
+++ b/devtools/client/styleinspector/test/browser.ini
@@ -67,16 +67,17 @@ support-files =
 [browser_ruleview_colorpicker-appears-on-swatch-click.js]
 [browser_ruleview_colorpicker-commit-on-ENTER.js]
 [browser_ruleview_colorpicker-edit-gradient.js]
 [browser_ruleview_colorpicker-hides-on-tooltip.js]
 [browser_ruleview_colorpicker-multiple-changes.js]
 [browser_ruleview_colorpicker-release-outside-frame.js]
 [browser_ruleview_colorpicker-revert-on-ESC.js]
 [browser_ruleview_colorpicker-swatch-displayed.js]
+[browser_ruleview_colorUnit.js]
 [browser_ruleview_completion-existing-property_01.js]
 [browser_ruleview_completion-existing-property_02.js]
 [browser_ruleview_completion-new-property_01.js]
 [browser_ruleview_completion-new-property_02.js]
 [browser_ruleview_computed-lists_01.js]
 [browser_ruleview_computed-lists_02.js]
 [browser_ruleview_completion-popup-hidden-after-navigation.js]
 [browser_ruleview_content_01.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/styleinspector/test/browser_ruleview_colorUnit.js
@@ -0,0 +1,59 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that color selection respects the user pref.
+
+const TEST_URI = `
+  <style type='text/css'>
+    #testid {
+      color: blue;
+    }
+  </style>
+  <div id='testid' class='testclass'>Styled Node</div>
+`;
+
+add_task(function*() {
+  let TESTS = [
+    {name: "hex", result: "#0F0"},
+    {name: "rgb", result: "rgb(0, 255, 0)"}
+  ];
+
+  for (let {name, result} of TESTS) {
+    info("starting test for " + name);
+    Services.prefs.setCharPref("devtools.defaultColorUnit", name);
+
+    yield addTab("data:text/html;charset=utf-8," +
+                 encodeURIComponent(TEST_URI));
+    let {inspector, view} = yield openRuleView();
+    yield selectNode("#testid", inspector);
+    yield basicTest(view, name, result);
+  }
+});
+
+function* basicTest(view, name, result) {
+  let cPicker = view.tooltips.colorPicker;
+  let swatch = getRuleViewProperty(view, "#testid", "color").valueSpan
+      .querySelector(".ruleview-colorswatch");
+  let onShown = cPicker.tooltip.once("shown");
+  swatch.click();
+  yield onShown;
+
+  let testNode = yield getNode("#testid");
+
+  yield simulateColorPickerChange(view, cPicker, [0, 255, 0, 1], {
+    element: testNode,
+    name: "color",
+    value: "rgb(0, 255, 0)"
+  });
+
+  let spectrum = yield cPicker.spectrum;
+  let onHidden = cPicker.tooltip.once("hidden");
+  EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
+  yield onHidden;
+
+  is(getRuleViewPropertyValue(view, "#testid", "color"), result,
+     "changing the color used the " + name + " unit");
+}
--- a/devtools/shared/css-color.js
+++ b/devtools/shared/css-color.js
@@ -87,16 +87,30 @@ CssColor.prototype = {
     }
     return this._colorUnit;
   },
 
   set colorUnit(unit) {
     this._colorUnit = unit;
   },
 
+  /**
+   * If the current color unit pref is "authored", then set the
+   * default color unit from the given color.  Otherwise, leave the
+   * color unit untouched.
+   *
+   * @param {String} color The color to use
+   */
+  setAuthoredUnitFromColor: function(color) {
+    if (Services.prefs.getCharPref(COLOR_UNIT_PREF) ===
+        CssColor.COLORUNIT.authored) {
+      this._colorUnit = classifyColor(color);
+    }
+  },
+
   get hasAlpha() {
     if (!this.valid) {
       return false;
     }
     return this._getRGBATuple().a !== 1;
   },
 
   get valid() {
--- a/devtools/shared/tests/unit/test_cssColor.js
+++ b/devtools/shared/tests/unit/test_cssColor.js
@@ -20,10 +20,15 @@ const CLASSIFY_TESTS = [
   { input: "blue", output: "name" },
   { input: "orange", output: "name" }
 ];
 
 function run_test() {
   for (let test of CLASSIFY_TESTS) {
     let result = colorUtils.classifyColor(test.input);
     equal(result, test.output, "test classifyColor(" + test.input + ")");
+
+    let obj = new colorUtils.CssColor("purple");
+    obj.setAuthoredUnitFromColor(test.input);
+    equal(obj.colorUnit, test.output,
+          "test setAuthoredUnitFromColor(" + test.input + ")");
   }
 }
--- a/mobile/android/base/ActivityHandlerHelper.java
+++ b/mobile/android/base/ActivityHandlerHelper.java
@@ -3,30 +3,53 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.ActivityResultHandlerMap;
 
 import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
 import android.content.Intent;
+import android.util.Log;
 
 public class ActivityHandlerHelper {
     private static final String LOGTAG = "GeckoActivityHandlerHelper";
     private static final ActivityResultHandlerMap mActivityResultHandlerMap = new ActivityResultHandlerMap();
 
     private static int makeRequestCode(ActivityResultHandler aHandler) {
         return mActivityResultHandlerMap.put(aHandler);
     }
 
     public static void startIntent(Intent intent, ActivityResultHandler activityResultHandler) {
         startIntentForActivity(GeckoAppShell.getGeckoInterface().getActivity(), intent, activityResultHandler);
     }
 
+    /**
+     * Starts the Activity, catching & logging if the Activity fails to start.
+     *
+     * We catch to prevent callers from passing in invalid Intents and crashing the browser.
+     *
+     * @return true if the Activity is successfully started, false otherwise.
+     */
+    public static boolean startIntentAndCatch(final String logtag, final Context context, final Intent intent) {
+        try {
+            context.startActivity(intent);
+            return true;
+        } catch (final ActivityNotFoundException e) {
+            Log.w(logtag, "Activity not found.", e);
+            return false;
+        } catch (final SecurityException e) {
+            Log.w(logtag, "Forbidden to launch activity.", e);
+            return false;
+        }
+    }
+
     public static void startIntentForActivity(Activity activity, Intent intent, ActivityResultHandler activityResultHandler) {
         activity.startActivityForResult(intent, mActivityResultHandlerMap.put(activityResultHandler));
     }
 
 
     public static boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
         ActivityResultHandler handler = mActivityResultHandlerMap.getAndRemove(requestCode);
         if (handler != null) {
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -8,17 +8,17 @@
 #ifdef MOZ_ANDROID_SHARED_ID
       android:sharedUserId="@MOZ_ANDROID_SHARED_ID@"
 #endif
       >
     <uses-sdk android:minSdkVersion="@MOZ_ANDROID_MIN_SDK_VERSION@"
 #ifdef MOZ_ANDROID_MAX_SDK_VERSION
               android:maxSdkVersion="@MOZ_ANDROID_MAX_SDK_VERSION@"
 #endif
-              android:targetSdkVersion="@ANDROID_TARGET_SDK@"/>
+              android:targetSdkVersion="22"/>
 
 #include ../services/manifests/FxAccountAndroidManifest_permissions.xml.in
 #include ../services/manifests/HealthReportAndroidManifest_permissions.xml.in
 #include ../services/manifests/SyncAndroidManifest_permissions.xml.in
 
 #ifdef MOZ_ANDROID_SEARCH_ACTIVITY
 #include ../search/manifests/SearchAndroidManifest_permissions.xml.in
 #endif
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -1537,17 +1537,17 @@ public class BrowserApp extends GeckoApp
             return;
         }
 
         if (AboutPages.isAboutReader(url)) {
             url = ReaderModeUtils.getUrlFromAboutReader(url);
         }
 
         GeckoAppShell.openUriExternal(url, "text/plain", "", "",
-                                      Intent.ACTION_SEND, tab.getDisplayTitle());
+                                      Intent.ACTION_SEND, tab.getDisplayTitle(), false);
 
         // Context: Sharing via chrome list (no explicit session is active)
         Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST);
     }
 
     private void setToolbarMargin(int margin) {
         ((RelativeLayout.LayoutParams) mGeckoLayout.getLayoutParams()).topMargin = margin;
         mGeckoLayout.requestLayout();
@@ -1778,17 +1778,17 @@ public class BrowserApp extends GeckoApp
                 public void run() {
                     removeAddonMenuItem(id);
                 }
             });
 
         } else if ("Reader:Share".equals(event)) {
             final String title = message.getString("title");
             final String url = message.getString("url");
-            GeckoAppShell.openUriExternal(url, "text/plain", "", "", Intent.ACTION_SEND, title);
+            GeckoAppShell.openUriExternal(url, "text/plain", "", "", Intent.ACTION_SEND, title, false);
         } else if ("Sanitize:ClearHistory".equals(event)) {
             handleClearHistory(message.optBoolean("clearSearchHistory", false));
             callback.sendSuccess(true);
         } else if ("Sanitize:ClearSyncedTabs".equals(event)) {
             handleClearSyncedTabs();
             callback.sendSuccess(true);
         } else if ("Settings:Show".equals(event)) {
             final String resource =
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -634,17 +634,17 @@ public abstract class GeckoApp
             String text = message.getString("text");
             final Tab tab = Tabs.getInstance().getSelectedTab();
             String title = "";
             if (tab != null) {
                 title = tab.getDisplayTitle();
                 final String url = ReaderModeUtils.stripAboutReaderUrl(tab.getURL());
                 text += "\n\n" + url;
             }
-            GeckoAppShell.openUriExternal(text, "text/plain", "", "", Intent.ACTION_SEND, title);
+            GeckoAppShell.openUriExternal(text, "text/plain", "", "", Intent.ACTION_SEND, title, false);
 
             // Context: Sharing via chrome list (no explicit session is active)
             Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST);
 
         } else if ("SystemUI:Visibility".equals(event)) {
             setSystemUiVisible(message.getBoolean("visible"));
 
         } else if ("Toast:Show".equals(event)) {
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -50,23 +50,23 @@ import org.mozilla.gecko.util.GeckoReque
 import org.mozilla.gecko.util.HardwareCodecCapabilityUtils;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.IOUtils;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSContainer;
 import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.ProxySelector;
 import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.widget.ExternalIntentDuringPrivateBrowsingPromptFragment;
 
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
-import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -101,16 +101,17 @@ import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.MessageQueue;
 import android.os.SystemClock;
 import android.os.Vibrator;
 import android.provider.Browser;
 import android.provider.Settings;
+import android.support.v4.app.FragmentActivity;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
@@ -1044,38 +1045,54 @@ public class GeckoAppShell
             final String number = aUri.getSchemeSpecificPart();
             if (number.contains("#") || number.contains("*") || aUri.getFragment() != null) {
                 return false;
             }
         }
         return true;
     }
 
+    @WrapForJNI
+    public static boolean openUriExternal(String targetURI,
+                                          String mimeType,
+                                          String packageName,
+                                          String className,
+                                          String action,
+                                          String title) {
+        // Default to showing prompt in private browsing to be safe.
+        return openUriExternal(targetURI, mimeType, packageName, className, action, title, true);
+    }
+
     /**
      * Given the inputs to <code>getOpenURIIntent</code>, plus an optional
      * package name and class name, create and fire an intent to open the
      * provided URI. If a class name is specified but a package name is not,
      * we will default to using the current fennec package.
      *
      * @param targetURI the string spec of the URI to open.
      * @param mimeType an optional MIME type string.
      * @param packageName an optional app package name.
      * @param className an optional intent class name.
      * @param action an Android action specifier, such as
      *               <code>Intent.ACTION_SEND</code>.
      * @param title the title to use in <code>ACTION_SEND</code> intents.
-     * @return true if the activity started successfully; false otherwise.
+     * @param showPromptInPrivateBrowsing whether or not the user should be prompted when opening
+     *                                    this uri from private browsing. This should be true
+     *                                    when the user doesn't explicitly choose to open an an
+     *                                    external app (e.g. just clicked a link).
+     * @return true if the activity started successfully or the user was prompted to open the
+     *              application; false otherwise.
      */
-    @WrapForJNI
     public static boolean openUriExternal(String targetURI,
                                           String mimeType,
                                           String packageName,
                                           String className,
                                           String action,
-                                          String title) {
+                                          String title,
+                                          final boolean showPromptInPrivateBrowsing) {
         final Context context = getContext();
         final Intent intent = getOpenURIIntent(context, targetURI,
                                                mimeType, action, title);
 
         if (intent == null) {
             return false;
         }
 
@@ -1084,25 +1101,26 @@ public class GeckoAppShell
                 intent.setClassName(packageName, className);
             } else {
                 // Default to using the fennec app context.
                 intent.setClassName(context, className);
             }
         }
 
         intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        try {
-            context.startActivity(intent);
-            return true;
-        } catch (ActivityNotFoundException e) {
-            Log.w(LOGTAG, "Activity not found.", e);
-            return false;
-        } catch (SecurityException e) {
-            Log.w(LOGTAG, "Forbidden to launch activity.", e);
-            return false;
+
+        if (!showPromptInPrivateBrowsing) {
+            return ActivityHandlerHelper.startIntentAndCatch(LOGTAG, context, intent);
+        } else {
+            // Ideally we retrieve the Activity from the calling args, rather than
+            // statically, but since this method is called from Gecko and I'm
+            // unfamiliar with that code, this is a simpler solution.
+            final FragmentActivity fragmentActivity = (FragmentActivity) getGeckoInterface().getActivity();
+            return ExternalIntentDuringPrivateBrowsingPromptFragment.showDialogOrAndroidChooser(
+                    context, fragmentActivity.getSupportFragmentManager(), intent);
         }
     }
 
     /**
      * Return a <code>Uri</code> instance which is equivalent to <code>u</code>,
      * but with a guaranteed-lowercase scheme as if the API level 16 method
      * <code>u.normalizeScheme</code> had been called.
      *
--- a/mobile/android/base/IntentHelper.java
+++ b/mobile/android/base/IntentHelper.java
@@ -7,24 +7,25 @@ package org.mozilla.gecko;
 
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.JSONUtils;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.WebActivityMapper;
+import org.mozilla.gecko.widget.ExternalIntentDuringPrivateBrowsingPromptFragment;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
-import android.app.Activity;
 import android.content.Intent;
 import android.net.Uri;
+import android.support.v4.app.FragmentActivity;
 import android.text.TextUtils;
 import android.util.Log;
 
 import java.io.UnsupportedEncodingException;
 import java.net.URISyntaxException;
 import java.net.URLEncoder;
 import java.util.Arrays;
 import java.util.List;
@@ -48,25 +49,25 @@ public final class IntentHelper implemen
     private static String MARKET_INTENT_URI_PACKAGE_PREFIX = "market://details?id=";
     private static String EXTRA_BROWSER_FALLBACK_URL = "browser_fallback_url";
 
     /** A partial URI to an error page - the encoded error URI should be appended before loading. */
     private static String UNKNOWN_PROTOCOL_URI_PREFIX = "about:neterror?e=unknownProtocolFound&u=";
 
     private static IntentHelper instance;
 
-    private final Activity activity;
+    private final FragmentActivity activity;
 
-    private IntentHelper(Activity activity) {
+    private IntentHelper(final FragmentActivity activity) {
         this.activity = activity;
         EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener) this, EVENTS);
         EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener) this, NATIVE_EVENTS);
     }
 
-    public static IntentHelper init(Activity activity) {
+    public static IntentHelper init(final FragmentActivity activity) {
         if (instance == null) {
             instance = new IntentHelper(activity);
         } else {
             Log.w(LOGTAG, "IntentHelper.init() called twice, ignoring.");
         }
 
         return instance;
     }
@@ -117,17 +118,17 @@ public final class IntentHelper implemen
     }
 
     private void open(JSONObject message) throws JSONException {
         GeckoAppShell.openUriExternal(message.optString("url"),
                                       message.optString("mime"),
                                       message.optString("packageName"),
                                       message.optString("className"),
                                       message.optString("action"),
-                                      message.optString("title"));
+                                      message.optString("title"), false);
     }
 
     private void openForResult(final JSONObject message) throws JSONException {
         Intent intent = GeckoAppShell.getOpenURIIntent(activity,
                                                        message.optString("url"),
                                                        message.optString("mime"),
                                                        message.optString("action"),
                                                        message.optString("title"));
@@ -196,17 +197,18 @@ public final class IntentHelper implemen
             // malicious software). Better to leave that one alone.
             final String marketUri = MARKET_INTENT_URI_PACKAGE_PREFIX + intent.getPackage();
             final Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(marketUri));
             marketIntent.addCategory(Intent.CATEGORY_BROWSABLE);
             marketIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
             // (Bug 1192436) We don't know if marketIntent matches any Activities (e.g. non-Play
             // Store devices). If it doesn't, clicking the link will cause no action to occur.
-            activity.startActivity(marketIntent);
+            ExternalIntentDuringPrivateBrowsingPromptFragment.showDialogOrAndroidChooser(
+                    activity, activity.getSupportFragmentManager(), marketIntent);
             callback.sendSuccess(null);
 
         }  else {
             // Don't log the URI to prevent leaking it.
             Log.w(LOGTAG, "Unable to open URI, default case - loading about:neterror");
             callback.sendError(getUnknownProtocolErrorPageUri(intent.getData().toString()));
         }
     }
--- a/mobile/android/base/ZoomedView.java
+++ b/mobile/android/base/ZoomedView.java
@@ -266,17 +266,17 @@ public class ZoomedView extends FrameLay
         closeButton = (ImageView) findViewById(R.id.dialog_close);
         changeZoomFactorButton = (TextView) findViewById(R.id.change_zoom_factor);
         zoomedImageView = (ImageView) findViewById(R.id.zoomed_image_view);
 
         setTextInZoomFactorButton(zoomFactor);
 
         toolbarHeight = getResources().getDimensionPixelSize(R.dimen.zoomed_view_toolbar_height);
         containterSize = getResources().getDimensionPixelSize(R.dimen.drawable_dropshadow_size);
-        cornerRadius = getResources().getDimensionPixelSize(R.dimen.button_corner_radius);
+        cornerRadius = getResources().getDimensionPixelSize(R.dimen.standard_corner_radius);
 
         moveToolbar(true);
     }
 
     private void setListeners() {
         closeButton.setOnClickListener(new View.OnClickListener() {
             public void onClick(View view) {
                 stopZoomDisplay(true);
--- a/mobile/android/base/home/HistoryPanel.java
+++ b/mobile/android/base/home/HistoryPanel.java
@@ -142,16 +142,17 @@ public class HistoryPanel extends HomeFr
                     final MostRecentSection rangeItem = (MostRecentSection) adapter.getItemAtPosition(position);
                     if (rangeItem != null) {
                         // Notify data has changed for both range and item adapter.
                         // This will update selected rangeItem item background and the tabs list.
                         // This will also update the selected range along with cursor start and end.
                         selected = rangeItem;
                         mRangeAdapter.notifyDataSetChanged();
                         getLoaderManager().getLoader(LOADER_ID_HISTORY).forceLoad();
+                        mList.smoothScrollToPosition(0);
                     }
                 }
             });
         }
 
         mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
             @Override
             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
--- a/mobile/android/base/home/HomeFragment.java
+++ b/mobile/android/base/home/HomeFragment.java
@@ -192,17 +192,17 @@ public abstract class HomeFragment exten
         }
 
         if (itemId == R.id.home_share) {
             if (info.url == null) {
                 Log.e(LOGTAG, "Can't share because URL is null");
                 return false;
             } else {
                 GeckoAppShell.openUriExternal(info.url, SHARE_MIME_TYPE, "", "",
-                                              Intent.ACTION_SEND, info.getDisplayTitle());
+                                              Intent.ACTION_SEND, info.getDisplayTitle(), false);
 
                 // Context: Sharing via chrome homepage contextmenu list (home session should be active)
                 Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST);
                 return true;
             }
         }
 
         if (itemId == R.id.home_add_to_launcher) {
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -700,16 +700,29 @@ just addresses the organization to follo
 <!ENTITY remote_tabs_never_synced "Last synced: never">
 
 <!-- Find-In-Page strings -->
 <!-- LOCALIZATION NOTE (find_matchcase): This is meant to appear as an icon that changes color
      if match-case is activated. i.e. No more than two letters, one uppercase, one lowercase. -->
 <!ENTITY find_matchcase "Aa">
 
 <!ENTITY intent_uri_cannot_open "Cannot open link">
+<!-- LOCALIZATION NOTE (intent_uri_private_browsing_prompt): This string will
+     appear in an alert when a user, who is currently in private browsing,
+     clicks a link that will open an external Android application. "&formatS;"
+     will be replaced with the name of the application that will be opened. -->
+<!ENTITY intent_uri_private_browsing_prompt "This link will open in &formatS;. Are you sure you want to exit Private Browsing?">
+<!-- LOCALIZATION NOTE (intent_uri_private_browsing_multiple_match_title): This
+     string will appear as the title of an alert when a user, who is currently
+     in private browsing, clicks a link that will open an external Android
+     application and more than one application is available to open that link.
+     We don't have control over the style of this dialog and it looks
+     unpolished when this string is longer than one line so ideally keep it
+     short! -->
+<!ENTITY intent_uri_private_browsing_multiple_match_title "Exit Private Browsing?">
 
 <!-- DevTools Authentication -->
 <!-- LOCALIZATION NOTE (devtools_auth_scan_header): This header text appears
      above a QR reader that is actively scanning for QR codes.  The expected QR
      code has already been displayed by the client trying to connect (such as
      desktop Firefox via WebIDE), so you just need to aim this device at the QR
      code. -->
 <!ENTITY devtools_auth_scan_header "Scanning for the QR code displayed on your other device">
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -544,16 +544,17 @@ gbjar.sources += [
     'widget/ContentSecurityDoorHanger.java',
     'widget/CropImageView.java',
     'widget/DateTimePicker.java',
     'widget/DefaultDoorHanger.java',
     'widget/Divider.java',
     'widget/DoorHanger.java',
     'widget/DoorhangerConfig.java',
     'widget/EllipsisTextView.java',
+    'widget/ExternalIntentDuringPrivateBrowsingPromptFragment.java',
     'widget/FadedMultiColorTextView.java',
     'widget/FadedSingleColorTextView.java',
     'widget/FadedTextView.java',
     'widget/FaviconView.java',
     'widget/FloatingHintEditText.java',
     'widget/FlowLayout.java',
     'widget/GeckoActionProvider.java',
     'widget/GeckoPopupMenu.java',
--- a/mobile/android/base/resources/drawable-large-v11/browser_toolbar_action_bar_button.xml
+++ b/mobile/android/base/resources/drawable-large-v11/browser_toolbar_action_bar_button.xml
@@ -11,63 +11,63 @@
          android:state_enabled="true">
 
         <inset android:insetTop="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
                android:insetBottom="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
                android:insetLeft="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal"
                android:insetRight="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal">
             <shape android:shape="rectangle">
                 <solid android:color="@color/text_and_tabs_tray_grey"/>
-                <corners android:radius="@dimen/tablet_browser_toolbar_menu_item_corner_radius"/>
+                <corners android:radius="@dimen/standard_corner_radius"/>
             </shape>
         </inset>
 
     </item>
 
     <item gecko:state_private="true"
           android:state_focused="true"
           android:state_pressed="false">
 
         <inset android:insetTop="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
                android:insetBottom="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
                android:insetLeft="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal"
                android:insetRight="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal">
             <shape android:shape="rectangle">
                 <solid android:color="@color/placeholder_active_grey"/>
-                <corners android:radius="@dimen/tablet_browser_toolbar_menu_item_corner_radius"/>
+                <corners android:radius="@dimen/standard_corner_radius"/>
             </shape>
         </inset>
 
     </item>
 
     <item android:state_pressed="true"
           android:state_enabled="true">
 
         <inset android:insetTop="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
                android:insetBottom="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
                android:insetLeft="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal"
                android:insetRight="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal">
             <shape android:shape="rectangle">
                 <solid android:color="@color/toolbar_grey_pressed"/>
-                <corners android:radius="@dimen/tablet_browser_toolbar_menu_item_corner_radius"/>
+                <corners android:radius="@dimen/standard_corner_radius"/>
             </shape>
         </inset>
 
     </item>
 
     <item android:state_focused="true"
           android:state_pressed="false">
 
         <inset android:insetTop="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
                android:insetBottom="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
                android:insetLeft="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal"
                android:insetRight="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal">
             <shape android:shape="rectangle">
                 <solid android:color="@color/tablet_highlight_focused"/>
-                <corners android:radius="@dimen/tablet_browser_toolbar_menu_item_corner_radius"/>
+                <corners android:radius="@dimen/standard_corner_radius"/>
             </shape>
         </inset>
 
     </item>
 
     <item>
         <shape android:shape="rectangle">
             <solid android:color="@android:color/transparent"/>
--- a/mobile/android/base/resources/drawable-large-v11/tab_strip_button.xml
+++ b/mobile/android/base/resources/drawable-large-v11/tab_strip_button.xml
@@ -10,32 +10,32 @@
           android:state_enabled="true">
 
         <inset android:insetTop="@dimen/tablet_tab_strip_button_inset"
                android:insetBottom="@dimen/tablet_tab_strip_button_inset"
                android:insetLeft="@dimen/tablet_tab_strip_button_inset"
                android:insetRight="@dimen/tablet_tab_strip_button_inset">
             <shape android:shape="rectangle">
                 <solid android:color="@color/highlight_dark"/>
-                <corners android:radius="@dimen/tablet_browser_toolbar_menu_item_corner_radius"/>
+                <corners android:radius="@dimen/standard_corner_radius"/>
             </shape>
         </inset>
 
     </item>
 
     <item android:state_focused="true"
           android:state_pressed="false">
 
         <inset android:insetTop="@dimen/tablet_tab_strip_button_inset"
                android:insetBottom="@dimen/tablet_tab_strip_button_inset"
                android:insetLeft="@dimen/tablet_tab_strip_button_inset"
                android:insetRight="@dimen/tablet_tab_strip_button_inset">
             <shape android:shape="rectangle">
                 <solid android:color="@color/tablet_highlight_focused"/>
-                <corners android:radius="@dimen/tablet_browser_toolbar_menu_item_corner_radius"/>
+                <corners android:radius="@dimen/standard_corner_radius"/>
             </shape>
         </inset>
 
     </item>
 
     <item>
         <shape android:shape="rectangle">
             <solid android:color="@android:color/transparent"/>
--- a/mobile/android/base/resources/drawable/button_enabled_action_blue_round.xml
+++ b/mobile/android/base/resources/drawable/button_enabled_action_blue_round.xml
@@ -2,10 +2,10 @@
 <!-- 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/. -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle" >
   <solid android:color="@color/link_blue_pressed" />
   <corners
-      android:radius="@dimen/button_corner_radius" />
+      android:radius="@dimen/standard_corner_radius" />
 </shape>
--- a/mobile/android/base/resources/drawable/button_enabled_action_orange_round.xml
+++ b/mobile/android/base/resources/drawable/button_enabled_action_orange_round.xml
@@ -2,10 +2,10 @@
 <!-- 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/. -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle" >
   <solid android:color="@color/action_orange" />
   <corners
-      android:radius="@dimen/button_corner_radius" />
+      android:radius="@dimen/standard_corner_radius" />
 </shape>
--- a/mobile/android/base/resources/drawable/button_pressed_action_blue_round.xml
+++ b/mobile/android/base/resources/drawable/button_pressed_action_blue_round.xml
@@ -2,10 +2,10 @@
 <!-- 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/. -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle" >
   <solid android:color="@color/link_blue" />
   <corners
-      android:radius="@dimen/button_corner_radius" />
+      android:radius="@dimen/standard_corner_radius" />
 </shape>
--- a/mobile/android/base/resources/drawable/button_pressed_action_orange_round.xml
+++ b/mobile/android/base/resources/drawable/button_pressed_action_orange_round.xml
@@ -2,10 +2,10 @@
 <!-- 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/. -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle" >
   <solid android:color="@color/action_orange_pressed" />
   <corners
-      android:radius="@dimen/button_corner_radius" />
+      android:radius="@dimen/standard_corner_radius" />
 </shape>
--- a/mobile/android/base/resources/drawable/overlay_share_button_background_first.xml
+++ b/mobile/android/base/resources/drawable/overlay_share_button_background_first.xml
@@ -8,22 +8,22 @@
 <!-- Should be kept in sync with overlay_share_button_background.xml
 
      This first item in the list has rounded corners. -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
 
     <item android:state_pressed="true">
         <shape>
             <solid android:color="@color/toolbar_grey_pressed"/>
-            <corners android:topLeftRadius="@dimen/button_corner_radius"
-                     android:topRightRadius="@dimen/button_corner_radius"/>
+            <corners android:topLeftRadius="@dimen/standard_corner_radius"
+                     android:topRightRadius="@dimen/standard_corner_radius"/>
         </shape>
     </item>
 
     <item>
         <shape>
             <solid android:color="@color/toolbar_grey"/>
-            <corners android:topLeftRadius="@dimen/button_corner_radius"
-                     android:topRightRadius="@dimen/button_corner_radius"/>
+            <corners android:topLeftRadius="@dimen/standard_corner_radius"
+                     android:topRightRadius="@dimen/standard_corner_radius"/>
         </shape>
     </item>
 
 </selector>
--- a/mobile/android/base/resources/drawable/remote_tabs_setup_button_background.xml
+++ b/mobile/android/base/resources/drawable/remote_tabs_setup_button_background.xml
@@ -2,19 +2,19 @@
 <!-- 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/. -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_pressed="true">
         <shape android:shape="rectangle">
             <solid android:color="@color/remote_tabs_setup_button_background_hit"/>
-            <corners android:radius="@dimen/button_corner_radius"/>
+            <corners android:radius="@dimen/standard_corner_radius"/>
         </shape>
     </item>
 
     <item>
         <shape android:shape="rectangle">
             <solid android:color="@color/action_orange"/>
-            <corners android:radius="@dimen/button_corner_radius"/>
+            <corners android:radius="@dimen/standard_corner_radius"/>
         </shape>
     </item>
 </selector>
--- a/mobile/android/base/resources/drawable/search_suggestion_button.xml
+++ b/mobile/android/base/resources/drawable/search_suggestion_button.xml
@@ -1,19 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_pressed="true">
         <shape>
             <solid android:color="@color/toolbar_grey_pressed"/>
-            <corners android:radius="4dp"/>
+            <corners android:radius="@dimen/standard_corner_radius"/>
         </shape>
     </item>
 
     <item>
         <shape>
             <solid android:color="@color/toolbar_grey"/>
-            <corners android:radius="4dp"/>
+            <corners android:radius="@dimen/standard_corner_radius"/>
         </shape>
     </item>
 </selector>
--- a/mobile/android/base/resources/drawable/tab_thumbnail.xml
+++ b/mobile/android/base/resources/drawable/tab_thumbnail.xml
@@ -6,82 +6,82 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android"
           xmlns:gecko="http://schemas.android.com/apk/res-auto">
 
     <item android:state_focused="true">
 
         <shape android:shape="rectangle">
             <!-- @color/fennec_ui_orange with alpha -->
             <solid android:color="#B3FF9500"/>
-            <corners android:radius="3dp"/>            
+            <corners android:radius="@dimen/standard_corner_radius"/>
         </shape>
 
     </item>
 
     <item android:state_focused="true"
           gecko:state_private="true">
 
         <shape android:shape="rectangle">
             <!-- @color/private_browsing_purple with alpha -->
             <solid android:color="#B3CF68FF"/>
-            <corners android:radius="3dp"/>
+            <corners android:radius="@dimen/standard_corner_radius"/>
         </shape>
 
     </item>
 
     <item android:state_pressed="true"
           gecko:state_private="true">
 
         <shape android:shape="rectangle">
             <!-- @color/private_browsing_purple with alpha -->
             <solid android:color="#B3CF68FF"/>
-            <corners android:radius="3dp"/>
+            <corners android:radius="@dimen/standard_corner_radius"/>
         </shape>
 
     </item>
 
     <item android:state_pressed="true">
 
         <shape android:shape="rectangle">
             <!-- @color/fennec_ui_orange with alpha -->
             <solid android:color="#B3FF9500"/>
-            <corners android:radius="3dp"/>
+            <corners android:radius="@dimen/standard_corner_radius"/>
         </shape>
 
     </item>
 
     <item gecko:state_recording="true">
 
         <shape android:shape="rectangle">
             <solid android:color="#FFFF0000"/>
-            <corners android:radius="3dp"/>            
+            <corners android:radius="@dimen/standard_corner_radius"/>
         </shape>
 
     </item>
 
     <item android:state_focused="false"
           android:state_pressed="false"
           android:state_checked="true"
           gecko:state_recording="false"
           gecko:state_private="true">
 
         <shape android:shape="rectangle">
             <solid android:color="@color/private_browsing_purple"/>
-            <corners android:radius="3dp"/>
+            <corners android:radius="@dimen/standard_corner_radius"/>
         </shape>
 
     </item>
 
     <item android:state_focused="false"
           android:state_pressed="false"
           android:state_checked="true"
           gecko:state_recording="false">
 
         <shape android:shape="rectangle">
             <solid android:color="@color/fennec_ui_orange"/>
-            <corners android:radius="3dp"/>            
+            <corners android:radius="@dimen/standard_corner_radius"/>
         </shape>
 
     </item>
 
     <item android:drawable="@android:color/transparent"/>
 
 </selector>
--- a/mobile/android/base/resources/drawable/toolbar_grey_round.xml
+++ b/mobile/android/base/resources/drawable/toolbar_grey_round.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
     <solid android:color="@color/toolbar_grey"/>
-    <corners android:radius="@dimen/button_corner_radius"/>
+    <corners android:radius="@dimen/standard_corner_radius"/>
 </shape>
 
--- a/mobile/android/base/resources/values-v11/themes.xml
+++ b/mobile/android/base/resources/values-v11/themes.xml
@@ -4,32 +4,33 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <!--
         Base application theme. This could be overridden by GeckoBaseTheme
         in other res/values-XXX/themes.xml.
     -->
-    <style name="GeckoBase" parent="@android:style/Theme.Holo.Light">
+    <style name="GeckoBase" parent="Theme.AppCompat.Light.DarkActionBar">
         <item name="android:windowContentOverlay">@null</item>
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
     </style>
 
     <style name="GeckoDialogBase" parent="@android:style/Theme.Holo.Light.Dialog">
         <item name="android:windowContentOverlay">@null</item>
         <item name="android:windowActionBar">false</item>
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowBackground">@android:color/transparent</item>
     </style>
 
     <style name="GeckoTitleDialogBase" parent="@android:style/Theme.Holo.Light.Dialog" />
 
-    <style name="GeckoPreferencesBase" parent="GeckoBase">
+    <style name="GeckoPreferencesBase" parent="@android:style/Theme.Holo.Light">
+        <item name="android:windowContentOverlay">@null</item>
         <item name="android:windowActionBar">true</item>
         <item name="android:windowNoTitle">false</item>
         <item name="android:actionBarStyle">@style/ActionBar.GeckoPreferences</item>
     </style>
 
     <!--
         Activity based themes for API 11+. This theme completely replaces
         GeckoAppBase from res/values/themes.xml on API 11+ devices.
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/values-v21/styles.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<resources>
+
+   <style name="ActionBarTitleTextStyle" parent="@android:style/TextAppearance.Material.Widget.ActionBar.Title">
+      <item name="android:textColor">#fff</item>
+   </style>
+
+   <style name="ActionBarTheme" parent="@style/ThemeOverlay.AppCompat.ActionBar">
+      <item name="android:colorControlNormal">#fff</item>
+
+      <!-- This color is the system default. -->
+      <item name="android:colorControlHighlight">#65696D</item>
+   </style>
+
+</resources>
--- a/mobile/android/base/resources/values-v21/themes.xml
+++ b/mobile/android/base/resources/values-v21/themes.xml
@@ -3,30 +3,39 @@
    - 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/. -->
 
 <resources>
 
     <!--
         Base application theme.
     -->
-    <style name="GeckoBase" parent="@android:style/Theme.Material.Light.DarkActionBar">
+    <style name="GeckoBase" parent="Theme.AppCompat.Light.DarkActionBar">
         <item name="android:colorPrimary">@color/text_and_tabs_tray_grey</item>
         <item name="android:colorPrimaryDark">@color/text_and_tabs_tray_grey</item>
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowContentOverlay">@null</item>
         <item name="android:actionBarStyle">@style/GeckoActionBar</item>
+        <item name="android:actionBarTheme">@style/ActionBarTheme</item>
         <item name="android:colorAccent">@color/action_orange</item>
     </style>
 
+    <style name="GeckoPreferencesBase" parent="GeckoBase">
+        <item name="android:windowActionBar">true</item>
+        <item name="android:windowNoTitle">false</item>
+        <item name="android:actionBarStyle">@style/ActionBar.GeckoPreferences</item>
+    </style>
+
     <style name="ActionBar.FxAccountStatusActivity" parent="@android:style/Widget.Material.ActionBar.Solid">
         <item name="android:displayOptions">homeAsUp|showTitle</item>
+        <item name="android:titleTextStyle">@style/ActionBarTitleTextStyle</item>
     </style>
 
     <style name="ActionBar.GeckoPreferences" parent="@android:style/Widget.Material.ActionBar.Solid">
+        <item name="android:titleTextStyle">@style/ActionBarTitleTextStyle</item>
     </style>
 
     <style name="GeckoAppBase" parent="Gecko">
         <item name="android:actionButtonStyle">@style/GeckoActionBar.Button</item>
         <item name="android:listViewStyle">@style/Widget.ListView</item>
         <item name="android:panelBackground">@drawable/menu_panel_bg</item>
         <item name="android:spinnerDropDownItemStyle">@style/Widget.DropDownItem.Spinner</item>
         <item name="android:spinnerItemStyle">@style/Widget.TextView.SpinnerItem</item>
--- a/mobile/android/base/resources/values/colors.xml
+++ b/mobile/android/base/resources/values/colors.xml
@@ -149,9 +149,12 @@
   <color name="canvas_delegate_paint">#FFFF0000</color>
 
   <!-- Top sites thumbnail colors -->
   <color name="top_site_default">#FFECF0F3</color>
   <color name="top_site_border">#FFCFD9E1</color>
 
   <color name="private_active_text">#FFFFFF</color>
 
+  <!-- This color is from a 2.3 Samsung device. -->
+  <color name="gingerbread_menu_background_color">#1b1b1b</color>
+
 </resources>
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -1,16 +1,16 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <resources>
 
-    <dimen name="button_corner_radius">3dp</dimen>
+    <dimen name="standard_corner_radius">4dp</dimen>
 
     <dimen name="autocomplete_min_width">200dp</dimen>
     <dimen name="autocomplete_row_height">32dp</dimen>
 
     <dimen name="browser_toolbar_height">48dp</dimen>
     <!-- This value is the height of the Tabs Panel header view
          (browser_toolbar_height) minus the height of the indicator
          (6dp). This value should change when the height of the view changes. -->
@@ -41,17 +41,16 @@
     <dimen name="tablet_tab_strip_item_width">208dp</dimen>
     <dimen name="tablet_tab_strip_item_margin">-28dp</dimen>
     <dimen name="tablet_tab_strip_fading_edge_size">15dp</dimen>
     <dimen name="tablet_browser_toolbar_menu_item_width">56dp</dimen>
     <!-- Padding combines with an 18dp image to form the menu item width and height. -->
     <dimen name="tablet_browser_toolbar_menu_item_padding_horizontal">19dp</dimen>
     <dimen name="tablet_browser_toolbar_menu_item_inset_vertical">5dp</dimen>
     <dimen name="tablet_browser_toolbar_menu_item_inset_horizontal">3dp</dimen>
-    <dimen name="tablet_browser_toolbar_menu_item_corner_radius">5dp</dimen>
     <dimen name="tablet_tab_strip_button_inset">5dp</dimen>
 
     <!-- Dimensions used by Favicons and FaviconView -->
     <dimen name="favicon_bg">32dp</dimen>
     <!-- Set the upper limit on the size of favicon that will be processed. Favicons larger than
          this will be downscaled to this value. If you need to use larger Favicons (Due to a UI
          redesign sometime after this is written) you should increase this value to the largest
          commonly-used size of favicon and, performance permitting, fetch the remainder from the
--- a/mobile/android/base/resources/values/themes.xml
+++ b/mobile/android/base/resources/values/themes.xml
@@ -4,30 +4,35 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <resources>
 
     <!--
         Base application theme. This could be overridden by GeckoBaseTheme
         in other res/values-XXX/themes.xml.
     -->
-    <style name="GeckoBase" parent="@android:style/Theme.Light">
+    <style name="GeckoBase" parent="Theme.AppCompat.Light.DarkActionBar">
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowContentOverlay">@null</item>
+
+        <!-- AppCompat sets this to transparent by default:
+             http://stackoverflow.com/a/31777488 -->
+        <item name="android:panelBackground">@color/gingerbread_menu_background_color</item>
     </style>
 
     <style name="GeckoDialogBase" parent="@android:style/Theme.Dialog">
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowContentOverlay">@null</item>
     </style>
 
     <style name="GeckoTitleDialogBase" parent="@android:style/Theme.Dialog" />
 
-    <style name="GeckoPreferencesBase" parent="GeckoBase">
+    <style name="GeckoPreferencesBase" parent="@android:style/Theme.Light">
         <item name="android:windowNoTitle">false</item>
+        <item name="android:windowContentOverlay">@null</item>
     </style>
 
     <!--
         Application Theme. All customizations that are not specific
         to a particular API level can go here.
     -->
     <style name="Gecko" parent="GeckoBase">
         <!-- Default colors -->
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -590,11 +590,13 @@
 
   <string name="colon">&colon;</string>
 
   <string name="percent">&percent;</string>
 
   <string name="remote_tabs_last_synced">&remote_tabs_last_synced;</string>
 
   <string name="intent_uri_cannot_open">&intent_uri_cannot_open;</string>
+  <string name="intent_uri_private_browsing_prompt">&intent_uri_private_browsing_prompt;</string>
+  <string name="intent_uri_private_browsing_multiple_match_title">&intent_uri_private_browsing_multiple_match_title;</string>
 
   <string name="devtools_auth_scan_header">&devtools_auth_scan_header;</string>
 </resources>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/ExternalIntentDuringPrivateBrowsingPromptFragment.java
@@ -0,0 +1,98 @@
+// 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 org.mozilla.gecko.ActivityHandlerHelper;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.Tab;
+import org.mozilla.gecko.Tabs;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v7.app.AlertDialog;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * A DialogFragment to contain a dialog that appears when the user clicks an Intent:// URI during private browsing. The
+ * dialog appears to notify the user that a clicked link will open in an external application, potentially leaking their
+ * browsing history.
+ */
+public class ExternalIntentDuringPrivateBrowsingPromptFragment extends DialogFragment {
+    private static final String LOGTAG = ExternalIntentDuringPrivateBrowsingPromptFragment.class.getSimpleName();
+    private static final String FRAGMENT_TAG = "ExternalIntentPB";
+
+    private static final String KEY_APPLICATION_NAME = "matchingApplicationName";
+    private static final String KEY_INTENT = "intent";
+
+    @Override
+    public Dialog onCreateDialog(final Bundle savedInstanceState) {
+        final Bundle args = getArguments();
+        final CharSequence matchingApplicationName = args.getCharSequence(KEY_APPLICATION_NAME);
+        final Intent intent = args.getParcelable(KEY_INTENT);
+
+        final Context context = getActivity();
+        final String promptMessage = context.getString(R.string.intent_uri_private_browsing_prompt, matchingApplicationName);
+
+        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setMessage(promptMessage)
+                .setTitle(intent.getDataString())
+                .setPositiveButton(R.string.button_yes, new DialogInterface.OnClickListener() {
+                    public void onClick(final DialogInterface dialog, final int id) {
+                        context.startActivity(intent);
+                    }
+                })
+                .setNegativeButton(R.string.button_no, null /* we do nothing if the user rejects */ );
+        return builder.create();
+    }
+
+    /**
+     * @return true if the Activity is started or a dialog is shown. false if the Activity fails to start.
+     */
+    public static boolean showDialogOrAndroidChooser(final Context context, final FragmentManager fragmentManager,
+            final Intent intent) {
+        final Tab selectedTab = Tabs.getInstance().getSelectedTab();
+        if (selectedTab == null || !selectedTab.isPrivate()) {
+            return ActivityHandlerHelper.startIntentAndCatch(LOGTAG, context, intent);
+        }
+
+        final PackageManager pm = context.getPackageManager();
+        final List<ResolveInfo> matchingActivities = pm.queryIntentActivities(intent, 0);
+        if (matchingActivities.size() == 1) {
+            final ExternalIntentDuringPrivateBrowsingPromptFragment fragment = new ExternalIntentDuringPrivateBrowsingPromptFragment();
+
+            final Bundle args = new Bundle(2);
+            args.putCharSequence(KEY_APPLICATION_NAME, matchingActivities.get(0).loadLabel(pm));
+            args.putParcelable(KEY_INTENT, intent);
+            fragment.setArguments(args);
+
+            fragment.show(fragmentManager, FRAGMENT_TAG);
+            // We don't know the results of the user interaction with the fragment so just return true.
+            return true;
+        } else if (matchingActivities.size() > 1) {
+            // We want to show the Android Intent Chooser. However, we have no way of distinguishing regular tabs from
+            // private tabs to the chooser. Thus, if a user chooses "Always" in regular browsing mode, the chooser will
+            // not be shown and the URL will be opened. Therefore we explicitly show the chooser (which notably does not
+            // have an "Always" option).
+            final String androidChooserTitle =
+                    context.getResources().getString(R.string.intent_uri_private_browsing_multiple_match_title);
+            final Intent chooserIntent = Intent.createChooser(intent, androidChooserTitle);
+            return ActivityHandlerHelper.startIntentAndCatch(LOGTAG, context, chooserIntent);
+        } else {
+            // Normally, we show about:neterror when an Intent does not resolve
+            // but we don't have the references here to do that so log instead.
+            Log.w(LOGTAG, "showDialogOrAndroidChooser unexpectedly called with Intent that does not resolve");
+            return false;
+        }
+    }
+}
--- a/mobile/android/confvars.sh
+++ b/mobile/android/confvars.sh
@@ -118,8 +118,11 @@ MOZ_ADDON_SIGNING=1
 if test "$NIGHTLY_BUILD"; then
   MOZ_SWITCHBOARD=1
 fi
 
 # Use native Firefox Accounts UI after Nightly.
 if ! test "$NIGHTLY_BUILD"; then
 MOZ_ANDROID_NATIVE_ACCOUNT_UI=1
 fi
+
+# Disable GeckoView by default.
+export MOZ_DISABLE_GECKOVIEW=1
--- a/mobile/android/search/java/org/mozilla/search/PostSearchFragment.java
+++ b/mobile/android/search/java/org/mozilla/search/PostSearchFragment.java
@@ -11,16 +11,17 @@ import java.net.URL;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.util.ColorUtils;
 import org.mozilla.search.providers.SearchEngine;
 
 import android.annotation.SuppressLint;
+import android.content.ActivityNotFoundException;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.support.v4.app.Fragment;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -136,20 +137,30 @@ public class PostSearchFragment extends 
                     i.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
                     Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL,
                             TelemetryContract.Method.CONTENT, "search-result");
                 } else {
                     Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH,
                             TelemetryContract.Method.INTENT, "search-result");
                 }
 
+                i.addCategory(Intent.CATEGORY_BROWSABLE);
+                i.setComponent(null);
+                if (AppConstants.Versions.feature15Plus) {
+                    i.setSelector(null);
+                }
+
                 startActivity(i);
                 return true;
             } catch (URISyntaxException e) {
                 Log.e(LOG_TAG, "Error parsing intent URI", e);
+            } catch (SecurityException e) {
+                Log.e(LOG_TAG, "SecurityException handling arbitrary intent content");
+            } catch (ActivityNotFoundException e) {
+                Log.e(LOG_TAG, "Intent not actionable");
             }
 
             return false;
         }
 
         // We are suppressing the 'deprecation' warning because the new method is only available starting with API
         // level 23 and that's much higher than our current minSdkLevel (1208580).
         @SuppressWarnings("deprecation")
--- a/modules/libmar/src/mar_read.c
+++ b/modules/libmar/src/mar_read.c
@@ -86,17 +86,18 @@ static int mar_consume_index(MarFile *ma
 
   offset = ntohl(offset);
   length = ntohl(length);
   flags = ntohl(flags);
 
   name = *buf;
   /* find namelen; must take care not to read beyond buf_end */
   while (**buf) {
-    if (*buf == buf_end)
+    /* buf_end points one byte past the end of buf's allocation */
+    if (*buf == (buf_end - 1))
       return -1;
     ++(*buf);
   }
   namelen = (*buf - name);
   /* must ensure that namelen is valid */
   if (namelen < 0) {
     return -1;
   }
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -655,27 +655,26 @@ function canRunInSafeMode(aAddon) {
  *         The add-on to check
  * @return true if the add-on should not be appDisabled
  */
 function isUsableAddon(aAddon) {
   // Hack to ensure the default theme is always usable
   if (aAddon.type == "theme" && aAddon.internalName == XPIProvider.defaultSkin)
     return true;
 
-  if (mustSign(aAddon.type)) {
+  if (aAddon._installLocation.name == KEY_APP_SYSTEM_ADDONS &&
+      aAddon.signedState != AddonManager.SIGNEDSTATE_SYSTEM) {
+    return false;
+  }
+
+  if (aAddon._installLocation.name != KEY_APP_SYSTEM_DEFAULTS && mustSign(aAddon.type)) {
     if (aAddon.signedState <= AddonManager.SIGNEDSTATE_MISSING)
       return false;
     if (aAddon.foreignInstall && aAddon.signedState < AddonManager.SIGNEDSTATE_SIGNED)
       return false;
-
-    if (aAddon._installLocation.name == KEY_APP_SYSTEM_ADDONS ||
-        aAddon._installLocation.name == KEY_APP_SYSTEM_DEFAULTS) {
-      if (aAddon.signedState != AddonManager.SIGNEDSTATE_SYSTEM)
-        return false;
-    }
   }
 
   if (aAddon.blocklistState == Blocklist.STATE_BLOCKED)
     return false;
 
   if (AddonManager.checkUpdateSecurity && !aAddon.providesUpdatesSecurely)
     return false;
 
@@ -2520,17 +2519,17 @@ this.XPIProvider = {
                                                           null);
       this.enabledAddons = "";
 
       Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this, false);
       Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this, false);
       if (!REQUIRE_SIGNING)
         Services.prefs.addObserver(PREF_XPI_SIGNATURES_REQUIRED, this, false);
       Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS, false);
-      if (Cu.isModuleLoaded("resource:///modules/devtools/ToolboxProcess.jsm")) {
+      if (Cu.isModuleLoaded("resource:///modules/devtools/client/framework/ToolboxProcess.jsm")) {
         // If BrowserToolboxProcess is already loaded, set the boolean to true
         // and do whatever is needed
         this._toolboxProcessLoaded = true;
         BrowserToolboxProcess.on("connectionchange",
                                  this.onDebugConnectionChange.bind(this));
       }
       else {
         // Else, wait for it to load
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
@@ -1,15 +1,12 @@
 // Tests that we reset to the default system add-ons correctly when switching
 // application versions
 const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet";
 
-// Enable signature checks for these tests
-Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
-
 BootstrapMonitor.init();
 
 const featureDir = FileUtils.getDir("ProfD", ["features"]);
 
 // Build the test sets
 var dir = FileUtils.getDir("ProfD", ["sysfeatures", "app1"], true);
 do_get_file("data/system_addons/system1_1.xpi").copyTo(dir, "system1@tests.mozilla.org.xpi");
 do_get_file("data/system_addons/system2_1.xpi").copyTo(dir, "system2@tests.mozilla.org.xpi");
@@ -55,17 +52,19 @@ function* check_installed(inProfile, ...
       file.append(id + ".xpi");
       do_check_true(file.exists());
       do_check_true(file.isFile());
 
       let uri = addon.getResourceURI(null);
       do_check_true(uri instanceof AM_Ci.nsIFileURL);
       do_check_eq(uri.file.path, file.path);
 
-      do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SYSTEM);
+      if (inProfile) {
+        do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SYSTEM);
+      }
 
       // Verify the add-on actually started
       BootstrapMonitor.checkAddonStarted(id, versions[i]);
     }
     else {
       if (inProfile) {
         // Add-on should not be installed
         do_check_eq(addon, null);
@@ -268,24 +267,23 @@ add_task(function* test_bad_profile_cert
 
   startupManager(false);
 
   yield check_installed(false, "1.0", "1.0", null);
 
   yield promiseShutdownManager();
 });
 
-// Switching to app defaults that contain a bad certificate should ignore the
-// bad add-on
+// Switching to app defaults that contain a bad certificate should still work
 add_task(function* test_bad_app_cert() {
   gAppInfo.version = "3";
   distroDir.leafName = "app3";
   startupManager();
 
-  // Add-on will still be present just not active
+  // Add-on will still be present
   let addon = yield promiseAddonByID("system1@tests.mozilla.org");
   do_check_neq(addon, null);
   do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
 
-  yield check_installed(false, null, null, "1.0");
+  yield check_installed(false, "1.0", null, "1.0");
 
   yield promiseShutdownManager();
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_update.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_update.js
@@ -3,19 +3,16 @@
 const PREF_SYSTEM_ADDON_SET           = "extensions.systemAddonSet";
 const PREF_SYSTEM_ADDON_UPDATE_URL    = "extensions.systemAddon.update.url";
 const PREF_XPI_STATE                  = "extensions.xpiState";
 const PREF_APP_UPDATE_ENABLED         = "app.update.enabled";
 
 Components.utils.import("resource://testing-common/httpd.js");
 const { computeHash } = Components.utils.import("resource://gre/modules/addons/ProductAddonChecker.jsm");
 
-// Enable signature checks for these tests
-Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
-
 BootstrapMonitor.init();
 
 const featureDir = FileUtils.getDir("ProfD", ["features"], false);
 
 function getCurrentFeatureDir() {
   let dir = featureDir.clone();
   let set = JSON.parse(Services.prefs.getCharPref(PREF_SYSTEM_ADDON_SET));
   dir.append(set.directory);
@@ -171,17 +168,19 @@ function* check_installed(inProfile, ...
       file.append(id + ".xpi");
       do_check_true(file.exists());
       do_check_true(file.isFile());
 
       let uri = addon.getResourceURI(null);
       do_check_true(uri instanceof AM_Ci.nsIFileURL);
       do_check_eq(uri.file.path, file.path);
 
-      do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SYSTEM);
+      if (inProfile) {
+        do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SYSTEM);
+      }
 
       // Verify the add-on actually started
       BootstrapMonitor.checkAddonStarted(id, versions[i]);
     }
     else {
       do_print(`Checking state of add-on ${id}, expecting it to be missing`);
 
       if (inProfile) {
@@ -347,16 +346,25 @@ const TESTS = {
   checkSizeHash: {
     updateList: [
       { id: "system2@tests.mozilla.org", version: "3.0", path: "system2_3.xpi", size: 4672 },
       { id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi", hashFunction: "sha1", hashValue: "2df604b37b13766c0e04f1b7f59800e038f46cd5" },
       { id: "system5@tests.mozilla.org", version: "1.0", path: "system5_1.xpi", size: 4671, hashFunction: "sha1", hashValue: "f13dcaa8bfacaa222189bcbb0074972c05ceb621" }
     ],
     finalState: [true, null, "3.0", "3.0", null, "1.0"]
   },
+
+  // A bad certificate should stop updates
+  badCert: {
+    fails: true,
+    updateList: [
+      { id: "system1@tests.mozilla.org", version: "1.0", path: "system1_1_badcert.xpi" },
+      { id: "system3@tests.mozilla.org", version: "1.0", path: "system3_1.xpi" }
+    ],
+  }
 }
 
 add_task(function* setup() {
   // Initialise the profile
   startupManager();
   yield promiseShutdownManager();
 })