Merge fx-team to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 28 Jan 2015 18:36:42 -0800
changeset 226432 6bfc0e1c4b29
parent 226413 ea54e6623f59 (current diff)
parent 226431 5885bbf7c2e4 (diff)
child 226433 408b00141b82
child 226486 b84a2086c457
child 226512 feb3c6c050a5
push id28195
push userkwierso@gmail.com
push dateThu, 29 Jan 2015 02:36:53 +0000
treeherdermozilla-central@6bfc0e1c4b29 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone38.0a1
first release with
nightly linux32
6bfc0e1c4b29 / 38.0a1 / 20150129030202 / files
nightly linux64
6bfc0e1c4b29 / 38.0a1 / 20150129030202 / files
nightly mac
6bfc0e1c4b29 / 38.0a1 / 20150129030202 / files
nightly win32
6bfc0e1c4b29 / 38.0a1 / 20150129030202 / files
nightly win64
6bfc0e1c4b29 / 38.0a1 / 20150129030202 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c a=merge
mobile/android/gradle/android.gradle
--- a/browser/base/content/browser-data-submission-info-bar.js
+++ b/browser/base/content/browser-data-submission-info-bar.js
@@ -20,21 +20,21 @@ let gDataNotificationInfoBar = {
 
   get _log() {
     let Log = Cu.import("resource://gre/modules/Log.jsm", {}).Log;
     delete this._log;
     return this._log = Log.repository.getLogger("Services.DataReporting.InfoBar");
   },
 
   init: function() {
-    window.addEventListener("unload", function onUnload() {
+    window.addEventListener("unload", () => {
       for (let o of this._OBSERVERS) {
         Services.obs.removeObserver(this, o);
       }
-    }.bind(this), false);
+    }, false);
 
     for (let o of this._OBSERVERS) {
       Services.obs.addObserver(this, o, true);
     }
   },
 
   _getDataReportingNotification: function (name=this._DATA_REPORTING_NOTIFICATION) {
     return this._notificationBox.getNotificationWithValue(name);
@@ -54,34 +54,34 @@ let gDataNotificationInfoBar = {
       [appName, vendorName]);
 
     this._actionTaken = false;
 
     let buttons = [{
       label: gNavigatorBundle.getString("dataReportingNotification.button.label"),
       accessKey: gNavigatorBundle.getString("dataReportingNotification.button.accessKey"),
       popup: null,
-      callback: function () {
+      callback: () => {
         this._actionTaken = true;
         window.openAdvancedPreferences("dataChoicesTab");
-      }.bind(this),
+      },
     }];
 
     this._log.info("Creating data reporting policy notification.");
     let notification = this._notificationBox.appendNotification(
       message,
       this._DATA_REPORTING_NOTIFICATION,
       null,
       this._notificationBox.PRIORITY_INFO_HIGH,
       buttons,
-      function onEvent(event) {
+      event => {
         if (event == "removed") {
           Services.obs.notifyObservers(null, "datareporting:notify-data-policy:close", null);
         }
-      }.bind(this)
+      }
     );
     // It is important to defer calling onUserNotifyComplete() until we're
     // actually sure the notification was displayed. If we ever called
     // onUserNotifyComplete() without showing anything to the user, that
     // would be very good for user choice. It may also have legal impact.
     request.onUserNotifyComplete();
   },
 
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -62,16 +62,17 @@ loop.panel = (function(_, mozL10n) {
           return;
         }
         var isSelected = (this.state.selectedTab == tabName);
         if (!tab.props.hidden) {
           tabButtons.push(
             React.createElement("li", {className: cx({selected: isSelected}), 
                 key: i, 
                 "data-tab-name": tabName, 
+                title: mozL10n.get(tabName + "_tab_button_tooltip"), 
                 onClick: this.handleSelectTab})
           );
         }
         tabs.push(
           React.createElement("div", {key: i, className: cx({tab: true, selected: isSelected})}, 
             tab.props.children
           )
         );
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -62,16 +62,17 @@ loop.panel = (function(_, mozL10n) {
           return;
         }
         var isSelected = (this.state.selectedTab == tabName);
         if (!tab.props.hidden) {
           tabButtons.push(
             <li className={cx({selected: isSelected})}
                 key={i}
                 data-tab-name={tabName}
+                title={mozL10n.get(tabName + "_tab_button_tooltip")}
                 onClick={this.handleSelectTab} />
           );
         }
         tabs.push(
           <div key={i} className={cx({tab: true, selected: isSelected})}>
             {tab.props.children}
           </div>
         );
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -610,17 +610,17 @@ let SessionStoreInternal = {
 
     // If we got here, that means we're dealing with a frame message
     // manager message, so the target will be a <xul:browser>.
     var browser = aMessage.target;
     var win = browser.ownerDocument.defaultView;
     let tab = win.gBrowser.getTabForBrowser(browser);
 
     // Ensure we receive only specific messages from <xul:browser>s that
-    // have no tab assigned, e.g. the ones that preload aobut:newtab pages.
+    // have no tab assigned, e.g. the ones that preload about:newtab pages.
     if (!tab && !FMM_NOTAB_MESSAGES.has(aMessage.name)) {
       throw new Error(`received unexpected message '${aMessage.name}' ` +
                       `from a browser that has no tab`);
     }
 
     switch (aMessage.name) {
       case "SessionStore:setupSyncHandler":
         TabState.setSyncHandler(browser, aMessage.objects.handler);
@@ -683,31 +683,28 @@ let SessionStoreInternal = {
       case "SessionStore:restoreTabContentComplete":
         if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
           // This callback is used exclusively by tests that want to
           // monitor the progress of network loads.
           if (gDebuggingEnabled) {
             Services.obs.notifyObservers(browser, NOTIFY_TAB_RESTORED, null);
           }
 
-          let tab = browser.__SS_restore_tab;
-
           delete browser.__SS_restore_data;
-          delete browser.__SS_restore_tab;
           delete browser.__SS_data;
 
           SessionStoreInternal._resetLocalTabRestoringState(tab);
           SessionStoreInternal.restoreNextTab();
 
           this._sendTabRestoredNotification(tab);
         }
         break;
       case "SessionStore:reloadPendingTab":
         if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
-          if (tab && browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
+          if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
             this.restoreTabContent(tab);
           }
         }
         break;
       default:
         throw new Error(`received unknown message '${aMessage.name}'`);
         break;
     }
@@ -2724,18 +2721,16 @@ let SessionStoreInternal = {
     if (tabData.entries.length) {
       // restore those aspects of the currently active documents which are not
       // preserved in the plain history entries (mainly scroll state and text data)
       browser.__SS_restore_data = tabData.entries[activeIndex] || {};
     } else {
       browser.__SS_restore_data = {};
     }
 
-    browser.__SS_restore_tab = aTab;
-
     browser.messageManager.sendAsyncMessage("SessionStore:restoreTabContent",
       {loadArguments: aLoadArguments});
   },
 
   /**
    * This _attempts_ to restore the next available tab. If the restore fails,
    * then we will attempt the next one.
    * There are conditions where this won't do anything:
--- a/browser/devtools/canvasdebugger/canvasdebugger.js
+++ b/browser/devtools/canvasdebugger/canvasdebugger.js
@@ -65,17 +65,18 @@ const EVENTS = {
   THUMBNAILS_DISPLAYED: "CanvasDebugger:ThumbnailsDisplayed",
 
   // When a source is shown in the JavaScript Debugger at a specific location.
   SOURCE_SHOWN_IN_JS_DEBUGGER: "CanvasDebugger:SourceShownInJsDebugger",
   SOURCE_NOT_FOUND_IN_JS_DEBUGGER: "CanvasDebugger:SourceNotFoundInJsDebugger"
 };
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
-const STRINGS_URI = "chrome://browser/locale/devtools/canvasdebugger.properties"
+const STRINGS_URI = "chrome://browser/locale/devtools/canvasdebugger.properties";
+const SHARED_STRINGS_URI = "chrome://browser/locale/devtools/shared.properties";
 
 const SNAPSHOT_START_RECORDING_DELAY = 10; // ms
 const SNAPSHOT_DATA_EXPORT_MAX_BLOCK = 1000; // ms
 const SNAPSHOT_DATA_DISPLAY_DELAY = 10; // ms
 const SCREENSHOT_DISPLAY_DELAY = 100; // ms
 const STACK_FUNC_INDENTATION = 14; // px
 
 // This identifier string is simply used to tentatively ascertain whether or not
@@ -696,17 +697,18 @@ let CallsListView = Heritage.extend(Widg
 
     let screenshotNode = $("#screenshot-image");
     screenshotNode.setAttribute("flipped", flipped);
     drawBackground("screenshot-rendering", width, height, pixels);
 
     let dimensionsNode = $("#screenshot-dimensions");
     let actualWidth = (width / scaling) | 0;
     let actualHeight = (height / scaling) | 0;
-    dimensionsNode.setAttribute("value", actualWidth + " \u00D7 " + actualHeight);
+    dimensionsNode.setAttribute("value",
+      SHARED_L10N.getFormatStr("dimensions", actualWidth, actualHeight));
 
     window.emit(EVENTS.CALL_SCREENSHOT_DISPLAYED);
   },
 
   /**
    * Populates this container's footer with a list of thumbnails, one generated
    * for each draw call in the recorded animation frame snapshot.
    *
@@ -1045,16 +1047,17 @@ let CallsListView = Heritage.extend(Widg
     this.selectedIndex = this.itemCount - 1;
   }
 });
 
 /**
  * Localization convenience methods.
  */
 let L10N = new ViewHelpers.L10N(STRINGS_URI);
+let SHARED_L10N = new ViewHelpers.L10N(SHARED_STRINGS_URI);
 
 /**
  * Convenient way of emitting events from the panel window.
  */
 EventEmitter.decorate(this);
 
 /**
  * DOM query helpers.
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-img-screenshots.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-img-screenshots.js
@@ -15,17 +15,17 @@ function ifTestingSupported() {
   let callListPopulated = once(window, EVENTS.CALL_LIST_POPULATED);
   let screenshotDisplayed = once(window, EVENTS.CALL_SCREENSHOT_DISPLAYED);
   SnapshotsListView._onRecordButtonClick();
   yield promise.all([recordingFinished, callListPopulated, screenshotDisplayed]);
 
   is($("#screenshot-container").hidden, false,
     "The screenshot container should now be visible.");
 
-  is($("#screenshot-dimensions").getAttribute("value"), "128" + " \u00D7 " + "128",
+  is($("#screenshot-dimensions").getAttribute("value"), "128" + "\u00D7" + "128",
     "The screenshot dimensions label has the expected value.");
 
   is($("#screenshot-image").getAttribute("flipped"), "false",
     "The screenshot element should not be flipped vertically.");
 
   ok(window.getComputedStyle($("#screenshot-image")).backgroundImage.contains("#screenshot-rendering"),
     "The screenshot element should have an offscreen canvas element as a background.");
 
--- a/browser/devtools/layoutview/view.js
+++ b/browser/devtools/layoutview/view.js
@@ -9,22 +9,24 @@
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/devtools/Loader.jsm");
 Cu.import("resource://gre/modules/devtools/Console.jsm");
+Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 
 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 const {InplaceEditor, editableItem} = devtools.require("devtools/shared/inplace-editor");
 const {parseDeclarations} = devtools.require("devtools/styleinspector/css-parsing-utils");
 const {ReflowFront} = devtools.require("devtools/server/actors/layout");
 
+const SHARED_L10N = new ViewHelpers.L10N("chrome://browser/locale/devtools/shared.properties");
 const NUMERIC = /^-?[\d\.]+$/;
 const LONG_TEXT_ROTATE_LIMIT = 3;
 
 /**
  * An instance of EditingSession tracks changes that have been made during the
  * modification of box model values. All of these changes can be reverted by
  * calling revert.
  *
@@ -396,17 +398,18 @@ LayoutView.prototype = {
       // If a subsequent request has been made, wait for that one instead.
       if (this._lastRequest != lastRequest) {
         return this._lastRequest;
       }
 
       this._lastRequest = null;
       let width = layout.width;
       let height = layout.height;
-      let newLabel = width + "\u00D7" + height;
+      let newLabel = SHARED_L10N.getFormatStr("dimensions", width, height);
+
       if (this.sizeHeadingLabel.textContent != newLabel) {
         this.sizeHeadingLabel.textContent = newLabel;
       }
 
       // If the view is dimmed, no need to do anything more.
       if (this.dimmed) {
         this.inspector.emit("layoutview-updated");
         return null;
--- a/browser/devtools/responsivedesign/responsivedesign.jsm
+++ b/browser/devtools/responsivedesign/responsivedesign.jsm
@@ -6,16 +6,17 @@
 
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/devtools/gDevTools.jsm");
 Cu.import("resource://gre/modules/devtools/event-emitter.js");
+Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
                                   "resource://gre/modules/SystemAppProxy.jsm");
 
 var require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 let {showDoorhanger} = require("devtools/shared/doorhanger");
 let {TouchEventHandler} = require("devtools/touch-events");
@@ -28,16 +29,18 @@ const MIN_HEIGHT = 50;
 const MAX_WIDTH = 10000;
 const MAX_HEIGHT = 10000;
 
 const SLOW_RATIO = 6;
 const ROUND_RATIO = 10;
 
 const INPUT_PARSER = /(\d+)[^\d]+(\d+)/;
 
+const SHARED_L10N = new ViewHelpers.L10N("chrome://browser/locale/devtools/shared.properties");
+
 let ActiveTabs = new Map();
 
 this.ResponsiveUIManager = {
   /**
    * Check if the a tab is in a responsive mode.
    * Leave the responsive mode if active,
    * active the responsive mode if not active.
    *
@@ -599,17 +602,18 @@ ResponsiveUI.prototype = {
 
   /**
    * Set the menuitem label of a preset.
    *
    * @param aMenuitem menuitem to edit.
    * @param aPreset associated preset.
    */
   setMenuLabel: function RUI_setMenuLabel(aMenuitem, aPreset) {
-    let size = Math.round(aPreset.width) + "\u00D7" + Math.round(aPreset.height);
+    let size = SHARED_L10N.getFormatStr("dimensions",
+      Math.round(aPreset.width), Math.round(aPreset.height));
 
     // .inputField might be not reachable yet (async XBL loading)
     if (this.menulist.inputField) {
       this.menulist.inputField.value = size;
     }
 
     if (aPreset.custom) {
       size = this.strings.formatStringFromName("responsiveUI.customResolution", [size], 1);
--- a/browser/devtools/webaudioeditor/test/browser.ini
+++ b/browser/devtools/webaudioeditor/test/browser.ini
@@ -19,19 +19,21 @@ support-files =
 [browser_audionode-actor-get-params-01.js]
 [browser_audionode-actor-get-params-02.js]
 [browser_audionode-actor-get-set-param.js]
 [browser_audionode-actor-get-type.js]
 [browser_audionode-actor-is-source.js]
 [browser_audionode-actor-bypass.js]
 [browser_audionode-actor-connectnode-disconnect.js]
 [browser_audionode-actor-connectparam.js]
+skip-if = true # bug 1092571
 [browser_audionode-actor-add-automation-event.js]
 [browser_audionode-actor-get-automation-data-01.js]
 [browser_audionode-actor-get-automation-data-02.js]
+[browser_audionode-actor-get-automation-data-03.js]
 [browser_webaudio-actor-simple.js]
 [browser_webaudio-actor-destroy-node.js]
 [browser_webaudio-actor-connect-param.js]
 [browser_webaudio-actor-automation-event.js]
 
 [browser_wa_destroy-node-01.js]
 
 [browser_wa_first-run.js]
@@ -44,16 +46,17 @@ support-files =
 
 [browser_wa_graph-click.js]
 [browser_wa_graph-markers.js]
 [browser_wa_graph-render-01.js]
 [browser_wa_graph-render-02.js]
 [browser_wa_graph-render-03.js]
 [browser_wa_graph-render-04.js]
 [browser_wa_graph-render-05.js]
+skip-if = true # bug 1092571
 [browser_wa_graph-selected.js]
 [browser_wa_graph-zoom.js]
 
 [browser_wa_inspector.js]
 [browser_wa_inspector-toggle.js]
 [browser_wa_inspector-bypass-01.js]
 
 [browser_wa_properties-view.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-get-automation-data-03.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that `cancelScheduledEvents` clears out events on and after
+ * its argument.
+ */
+
+add_task(function*() {
+  let { target, front } = yield initBackend(SIMPLE_CONTEXT_URL);
+  let [_, [destNode, oscNode, gainNode]] = yield Promise.all([
+    front.setup({ reload: true }),
+    get3(front, "create-node")
+  ]);
+
+  yield oscNode.addAutomationEvent("frequency", "setValueAtTime", [300, 0]);
+  yield oscNode.addAutomationEvent("frequency", "linearRampToValueAtTime", [500, 0.9]);
+  yield oscNode.addAutomationEvent("frequency", "setValueAtTime", [700, 1]);
+  yield oscNode.addAutomationEvent("frequency", "exponentialRampToValueAtTime", [1000, 2]);
+  yield oscNode.addAutomationEvent("frequency", "cancelScheduledValues", [1]);
+
+  var { events, values } = yield oscNode.getAutomationData("frequency");
+
+  is(events.length, 2, "2 recorded events returned.");
+  is(values.length, 2000, "2000 value points returned");
+
+  checkAutomationValue(values, 0, 300);
+  checkAutomationValue(values, 0.5, 411.15);
+  checkAutomationValue(values, 0.9, 499.9);
+  checkAutomationValue(values, 1, 499.9);
+  checkAutomationValue(values, 2, 499.9);
+
+  yield removeTab(target.tab);
+});
--- a/browser/devtools/webide/content/runtimedetails.js
+++ b/browser/devtools/webide/content/runtimedetails.js
@@ -5,21 +5,26 @@
 const Cu = Components.utils;
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
 const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
 const {AppManager} = require("devtools/webide/app-manager");
 const {Connection} = require("devtools/client/connection-manager");
 const {RuntimeTypes} = require("devtools/webide/runtimes");
 const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
 
+const UNRESTRICTED_HELP_URL = "https://developer.mozilla.org/docs/Tools/WebIDE#Unrestricted_app_debugging_%28including_certified_apps.2C_main_process.2C_etc.%29";
+
 window.addEventListener("load", function onLoad() {
   window.removeEventListener("load", onLoad);
   document.querySelector("#close").onclick = CloseUI;
-  document.querySelector("#certified-check button").onclick = EnableCertApps;
+  document.querySelector("#devtools-check button").onclick = EnableCertApps;
   document.querySelector("#adb-check button").onclick = RootADB;
+  document.querySelector("#unrestricted-privileges").onclick = function() {
+    window.parent.UI.openInBrowser(UNRESTRICTED_HELP_URL);
+  };
   AppManager.on("app-manager-update", OnAppManagerUpdate);
   BuildUI();
   CheckLockState();
 }, true);
 
 window.addEventListener("unload", function onUnload() {
   window.removeEventListener("unload", onUnload);
   AppManager.off("app-manager-update", OnAppManagerUpdate);
@@ -58,33 +63,33 @@ function BuildUI() {
     });
   } else {
     CloseUI();
   }
 }
 
 function CheckLockState() {
   let adbCheckResult = document.querySelector("#adb-check > .yesno");
-  let certCheckResult = document.querySelector("#certified-check > .yesno");
-  let flipCertPerfButton = document.querySelector("#certified-check button");
+  let devtoolsCheckResult = document.querySelector("#devtools-check > .yesno");
+  let flipCertPerfButton = document.querySelector("#devtools-check button");
   let adbRootButton = document.querySelector("#adb-check button");
-  let flipCertPerfAction = document.querySelector("#certified-check > .action");
+  let flipCertPerfAction = document.querySelector("#devtools-check > .action");
   let adbRootAction = document.querySelector("#adb-check > .action");
 
   let sYes = Strings.GetStringFromName("runtimedetails_checkyes");
   let sNo = Strings.GetStringFromName("runtimedetails_checkno");
   let sUnknown = Strings.GetStringFromName("runtimedetails_checkunknown");
   let sNotUSB = Strings.GetStringFromName("runtimedetails_notUSBDevice");
 
   flipCertPerfButton.setAttribute("disabled", "true");
   flipCertPerfAction.setAttribute("hidden", "true");
   adbRootAction.setAttribute("hidden", "true");
 
   adbCheckResult.textContent = sUnknown;
-  certCheckResult.textContent = sUnknown;
+  devtoolsCheckResult.textContent = sUnknown;
 
   if (AppManager.connection &&
       AppManager.connection.status == Connection.Status.CONNECTED) {
 
     // ADB check
     if (AppManager.selectedRuntime.type === RuntimeTypes.USB) {
       let device = AppManager.selectedRuntime.device;
       if (device && device.summonRoot) {
@@ -104,25 +109,25 @@ function CheckLockState() {
       adbCheckResult.textContent = sNotUSB;
     }
 
     // forbid-certified-apps check
     try {
       let prefFront = AppManager.preferenceFront;
       prefFront.getBoolPref("devtools.debugger.forbid-certified-apps").then(isForbidden => {
         if (isForbidden) {
-          certCheckResult.textContent = sYes;
+          devtoolsCheckResult.textContent = sNo;
           flipCertPerfAction.removeAttribute("hidden");
         } else {
-          certCheckResult.textContent = sNo;
+          devtoolsCheckResult.textContent = sYes;
         }
       }, e => console.error(e));
     } catch(e) {
       // Exception. pref actor is only accessible if forbird-certified-apps is false
-      certCheckResult.textContent = sYes;
+      devtoolsCheckResult.textContent = sYes;
       flipCertPerfAction.removeAttribute("hidden");
     }
 
   }
 
 }
 
 function EnableCertApps() {
--- a/browser/devtools/webide/content/runtimedetails.xhtml
+++ b/browser/devtools/webide/content/runtimedetails.xhtml
@@ -27,18 +27,18 @@
     <div id="devicePrivileges">
       <p id="adb-check">
         &runtimedetails_adbIsRoot;<span class="yesno"></span>
         <div class="action">
           <button>&runtimedetails_summonADBRoot;</button>
           <em>&runtimedetails_ADBRootWarning;</em>
         </div>
       </p>
-      <p id="certified-check">
-        &runtimedetails_restrictedPrivileges;<span class="yesno"></span>
+      <p id="devtools-check">
+        <a id="unrestricted-privileges">&runtimedetails_unrestrictedPrivileges;</a><span class="yesno"></span>
         <div class="action">
           <button>&runtimedetails_requestPrivileges;</button>
           <em>&runtimedetails_privilegesWarning;</em>
         </div>
       </p>
     </div>
 
     <table></table>
--- a/browser/devtools/webide/themes/runtimedetails.css
+++ b/browser/devtools/webide/themes/runtimedetails.css
@@ -6,15 +6,20 @@ html, body {
   background: white;
 }
 
 #devicePrivileges {
   font-family: monospace;
   padding-left: 6px;
 }
 
+#devtools-check > a {
+  color: #4C9ED9;
+  cursor: pointer;
+}
+
 .action {
   display: inline;
 }
 
 .action[hidden] {
   display: none;
 }
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/shared.properties
@@ -0,0 +1,7 @@
+# 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/.
+
+# LOCALIZATION NOTE (dimensions): This is used to display the dimensions
+# of a node or image, like 100×200.
+dimensions=%S\u00D7%S
--- a/browser/locales/en-US/chrome/browser/devtools/webide.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/webide.dtd
@@ -140,17 +140,17 @@
 <!ENTITY permissionstable_title "Permissions Table">
 <!ENTITY permissionstable_name_header "Name">
 
 <!-- Runtime Details -->
 <!ENTITY runtimedetails_title "Runtime Info">
 <!ENTITY runtimedetails_adbIsRoot "ADB is root: ">
 <!ENTITY runtimedetails_summonADBRoot "root device">
 <!ENTITY runtimedetails_ADBRootWarning "(requires unlocked bootloader)">
-<!ENTITY runtimedetails_restrictedPrivileges "DevTools restricted privileges: ">
+<!ENTITY runtimedetails_unrestrictedPrivileges "Unrestricted DevTools privileges: ">
 <!ENTITY runtimedetails_requestPrivileges "request higher privileges">
 <!ENTITY runtimedetails_privilegesWarning "(Will reboot device. Requires root access.)">
 
 <!-- Device Preferences and Settings -->
 <!ENTITY device_typeboolean "Boolean">
 <!ENTITY device_typenumber "Integer">
 <!ENTITY device_typestring "String">
 <!ENTITY device_typeobject "Object">
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -3,16 +3,19 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # Panel Strings
 
 ## LOCALIZATION NOTE(clientShortname2): This should not be localized and
 ## should remain "Firefox Hello" for all locales.
 clientShortname2=Firefox Hello
 
+rooms_tab_button_tooltip=Conversations
+contacts_tab_button_tooltip=Contacts
+
 ## LOCALIZATION_NOTE(first_time_experience.title): clientShortname will be
 ## replaced by the brand name
 first_time_experience_title={{clientShortname}} — Join the conversation
 first_time_experience_button_label=Get Started
 
 invite_header_text=Invite someone to join you.
 
 # Status text
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -40,16 +40,17 @@
     locale/browser/devtools/canvasdebugger.properties (%chrome/browser/devtools/canvasdebugger.properties)
     locale/browser/devtools/webaudioeditor.dtd          (%chrome/browser/devtools/webaudioeditor.dtd)
     locale/browser/devtools/webaudioeditor.properties   (%chrome/browser/devtools/webaudioeditor.properties)
     locale/browser/devtools/gcli.properties           (%chrome/browser/devtools/gcli.properties)
     locale/browser/devtools/gclicommands.properties   (%chrome/browser/devtools/gclicommands.properties)
     locale/browser/devtools/webconsole.properties     (%chrome/browser/devtools/webconsole.properties)
     locale/browser/devtools/inspector.properties      (%chrome/browser/devtools/inspector.properties)
     locale/browser/devtools/tilt.properties           (%chrome/browser/devtools/tilt.properties)
+    locale/browser/devtools/shared.properties         (%chrome/browser/devtools/shared.properties)
     locale/browser/devtools/scratchpad.properties     (%chrome/browser/devtools/scratchpad.properties)
     locale/browser/devtools/scratchpad.dtd            (%chrome/browser/devtools/scratchpad.dtd)
     locale/browser/devtools/storage.properties        (%chrome/browser/devtools/storage.properties)
     locale/browser/devtools/styleeditor.properties    (%chrome/browser/devtools/styleeditor.properties)
     locale/browser/devtools/styleeditor.dtd           (%chrome/browser/devtools/styleeditor.dtd)
     locale/browser/devtools/styleinspector.dtd        (%chrome/browser/devtools/styleinspector.dtd)
     locale/browser/devtools/webConsole.dtd            (%chrome/browser/devtools/webConsole.dtd)
     locale/browser/devtools/VariablesView.dtd         (%chrome/browser/devtools/VariablesView.dtd)
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -2,20 +2,21 @@
 /* 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/. */
 %endif
 
 :root {
   --tab-toolbar-navbar-overlap: 1px;
   --tab-min-height: 31px;
-  --tab-curve-width: 30px;
-  --tab-curve-half-width: 15px;
 }
 
+%define tabCurveWidth 30px
+%define tabCurveHalfWidth 15px
+
 /* image preloading hack */
 #tabbrowser-tabs::before {
   /* Because of bug 853415, we need to ordinal this to the first position: */
   -moz-box-ordinal-group: 0;
   content: '';
   display: block;
   background-image:
     url(chrome://browser/skin/tabbrowser/tab-background-end.png),
@@ -49,22 +50,19 @@
 .tabbrowser-tab[selected=true] {
   position: relative;
   z-index: 2;
 }
 
 .tab-background-middle {
   -moz-box-flex: 1;
   background-clip: padding-box;
-  border-left: var(--tab-curve-half-width) solid transparent;
-  border-right: var(--tab-curve-half-width) solid transparent;
-  /* XXX: Switch to `margin: 0 calc(-1 * var(--tab-curve-half-width))` after bug 1099448 */
-  margin: 0;
-  -moz-margin-start: calc(-1 * var(--tab-curve-half-width));
-  -moz-margin-end: calc(-1 * var(--tab-curve-half-width));
+  border-left: @tabCurveHalfWidth@ solid transparent;
+  border-right: @tabCurveHalfWidth@ solid transparent;
+  margin: 0 -@tabCurveHalfWidth@;
 }
 
 .tab-content {
   -moz-padding-end: 9px;
   -moz-padding-start: 9px;
 }
 
 .tab-throbber,
@@ -105,23 +103,23 @@
   -moz-margin-start: 4px;
   -moz-margin-end: -2px;
   padding: 0;
 }
 
 .tab-background,
 .tabs-newtab-button {
   /* overlap the tab curves */
-  -moz-margin-end: calc(-1 * var(--tab-curve-half-width));
-  -moz-margin-start: calc(-1 * var(--tab-curve-half-width));
+  -moz-margin-end: -@tabCurveHalfWidth@;
+  -moz-margin-start: -@tabCurveHalfWidth@;
 }
 
 .tabbrowser-arrowscrollbox > .arrowscrollbox-scrollbox {
-  -moz-padding-end: var(--tab-curve-half-width);
-  -moz-padding-start: var(--tab-curve-half-width);
+  -moz-padding-end: @tabCurveHalfWidth@;
+  -moz-padding-start: @tabCurveHalfWidth@;
 }
 
 /* Tab Overflow */
 .tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator:not([collapsed]),
 .tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator:not([collapsed]) {
   background-image: url(chrome://browser/skin/tabbrowser/tab-overflow-indicator.png);
   background-size: 100% 100%;
   width: 14px;
@@ -158,17 +156,17 @@
 
 .tab-background-start[selected=true]::after,
 .tab-background-start[selected=true]::before,
 .tab-background-start,
 .tab-background-end,
 .tab-background-end[selected=true]::after,
 .tab-background-end[selected=true]::before {
   min-height: var(--tab-min-height);
-  width: var(--tab-curve-width);
+  width: @tabCurveWidth@;
 }
 
 .tabbrowser-tab:not([selected=true]),
 .tabbrowser-tab:-moz-lwtheme {
   color: inherit;
 }
 
 /* Selected tab */
@@ -180,17 +178,17 @@
    - ::after  - provides the border/stroke of the tab curve and is overlayed above ::before.  Pointer
                 events go through to ::before to get the proper shape.
  */
 
 
 .tab-background-start[selected=true]::after,
 .tab-background-end[selected=true]::after {
   /* position ::after on top of its parent */
-  -moz-margin-start: calc(-1 * var(--tab-curve-width));
+  -moz-margin-start: -@tabCurveWidth@;
   background-size: 100% 100%;
   content: "";
   display: -moz-box;
   position: relative;
 }
 
 .tab-background-start[selected=true]::before,
 .tab-background-end[selected=true]::before {
@@ -271,19 +269,19 @@
 /* End selected tab */
 
 /* new tab button border and gradient on hover */
 .tabbrowser-tab:hover > .tab-stack > .tab-background:not([selected=true]),
 .tabs-newtab-button:hover {
   background-image: url(chrome://browser/skin/tabbrowser/tab-background-start.png),
                     url(chrome://browser/skin/tabbrowser/tab-background-middle.png),
                     url(chrome://browser/skin/tabbrowser/tab-background-end.png);
-  background-position: left bottom, var(--tab-curve-width) bottom, right bottom;
+  background-position: left bottom, @tabCurveWidth@ bottom, right bottom;
   background-repeat: no-repeat;
-  background-size: var(--tab-curve-width) 100%, calc(100% - (2 * var(--tab-curve-width))) 100%, var(--tab-curve-width) 100%;
+  background-size: @tabCurveWidth@ 100%, calc(100% - (2 * @tabCurveWidth@)) 100%, @tabCurveWidth@ 100%;
 }
 
 /* Tab pointer-events */
 .tabbrowser-tab {
   pointer-events: none;
 }
 
 .tab-background-middle,
@@ -329,10 +327,10 @@
 #TabsToolbar[currentset]:not([currentset*="tabbrowser-tabs,new-tab-button"]) #tabbrowser-tabs:not([overflow]) > .tabbrowser-tab[last-visible-tab]:not([selected]):not([beforehovered]):not(:hover)::after {
   -moz-margin-start: -3px;
   -moz-margin-end: 0;
 }
 
 /* New tab button */
 
 .tabs-newtab-button {
-  width: calc(36px + var(--tab-curve-width));
+  width: calc(36px + @tabCurveWidth@);
 }
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -423,16 +423,18 @@
                   android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
 
         <provider android:name="org.mozilla.gecko.db.HomeProvider"
                   android:authorities="@ANDROID_PACKAGE_NAME@.db.home"
                   android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
 
         <provider android:name="org.mozilla.gecko.db.ReadingListProvider"
                   android:authorities="@ANDROID_PACKAGE_NAME@.db.readinglist"
+                  android:exported="false"
+                  android:label="@string/reading_list_title"
                   android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
 
         <provider android:name="org.mozilla.gecko.db.SearchHistoryProvider"
                   android:authorities="@ANDROID_PACKAGE_NAME@.db.searchhistory"
                   android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
 
         <service
             android:exported="false"
deleted file mode 100644
--- a/mobile/android/gradle/android.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-// Configure shared Android settings.  This should be run (using "apply from")
-// immediately after applying the Android plugin.  Be aware that IntelliJ does
-// not parse these values correctly: the Android-Gradle facet panel manually
-// parses build.gradle rather than interrogating the Gradle model.
-
-android {
-    compileSdkVersion "android-${mozconfig.defines.ANDROID_TARGET_SDK}"
-    // Turn "android-sdk/build-tools/21.1.1" into "21.1.1".
-    buildToolsVersion (new File(mozconfig.substs.ANDROID_BUILD_TOOLS).getName())
-
-    defaultConfig {
-        targetSdkVersion mozconfig.defines.ANDROID_TARGET_SDK
-        minSdkVersion mozconfig.defines.MOZ_ANDROID_MIN_SDK_VERSION
-        if (mozconfig.defines.MOZ_ANDROID_MAX_SDK_VERSION) {
-            maxSdkVersion mozconfig.defines.MOZ_ANDROID_MAX_SDK_VERSION
-        }
-    }
-
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_7
-        targetCompatibility JavaVersion.VERSION_1_7
-    }
-
-    android {
-        lintOptions {
-            abortOnError false
-        }
-    }
-}
--- a/mobile/android/gradle/app/build.gradle
+++ b/mobile/android/gradle/app/build.gradle
@@ -1,20 +1,45 @@
 apply plugin: 'com.android.application'
 
-apply from: rootProject.file("${topsrcdir}/mobile/android/gradle/android.gradle")
+android {
+    compileSdkVersion 21
+    buildToolsVersion "21.1.1"
+
+    defaultConfig {
+        targetSdkVersion 21
+        minSdkVersion 9
+    }
 
-android {
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+
+    dexOptions {
+        incremental true
+        preDexLibraries true
+    }
+
+    lintOptions {
+        abortOnError false
+    }
+
     buildTypes {
         release {
             minifyEnabled true
             proguardFile "${topsrcdir}/mobile/android/config/proguard/proguard.cfg"
         }
     }
 
+    defaultConfig {
+        testApplicationId 'org.mozilla.roboexample.test'
+        testInstrumentationRunner 'org.mozilla.gecko.FennecInstrumentationTestRunner'
+    }
+
     sourceSets {
         androidTest {
             java {
                 srcDir "${topobjdir}/mobile/android/gradle/app/src/robocop_harness"
                 srcDir "${topobjdir}/mobile/android/gradle/app/src/robocop"
                 srcDir "${topobjdir}/mobile/android/gradle/app/src/background"
                 srcDir "${topobjdir}/mobile/android/gradle/app/src/browser"
             }
--- a/mobile/android/gradle/base/build.gradle
+++ b/mobile/android/gradle/base/build.gradle
@@ -1,13 +1,32 @@
 apply plugin: 'com.android.library'
 
-apply from: "${topsrcdir}/mobile/android/gradle/android.gradle"
+android {
+    compileSdkVersion 21
+    buildToolsVersion "21.1.1"
+
+    defaultConfig {
+        targetSdkVersion 21
+        minSdkVersion 9
+    }
 
-android {
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+
+    dexOptions {
+        incremental true
+    }
+
+    lintOptions {
+        abortOnError false
+    }
+
     buildTypes {
         release {
             minifyEnabled false
             proguardFile getDefaultProguardFile('proguard-android.txt')
         }
     }
 
     sourceSets {
@@ -26,36 +45,35 @@ android {
                 }
             }
         }
     }
 }
 
 dependencies {
     compile fileTree(dir: 'libs', include: ['*.jar'])
-    compile 'com.android.support:support-v4:19.1.+'
+    compile 'com.android.support:support-v4:21.+'
 
     if (mozconfig.substs.MOZ_NATIVE_DEVICES) {
-        compile 'com.android.support:appcompat-v7:19.1.+'
-        compile 'com.android.support:mediarouter-v7:19.1.+'
-        compile 'com.google.android.gms:play-services:5.+'
+        compile 'com.android.support:appcompat-v7:21.+'
+        compile 'com.android.support:mediarouter-v7:21.+'
+        compile 'com.google.android.gms:play-services-base:6.5.+'
+        compile 'com.google.android.gms:play-services-cast:6.5.+'
     }
 
     compile project(':branding')
     compile project(':preprocessed_code')
     compile project(':preprocessed_resources')
     compile project(':thirdparty')
 }
 
 android.libraryVariants.all { variant ->
     variant.checkManifest.dependsOn generateCodeAndResources
 }
 
 apply plugin: 'idea'
 
 idea {
     module {
-        // excludeDirs = []
-        excludeDirs += file('src/main/java/org/mozilla/gecko/tests')
+        excludeDirs += file('org/mozilla/gecko/resources')
         excludeDirs += file('org/mozilla/gecko/tests')
-        excludeDirs += file('tests')
     }
 }
--- a/mobile/android/gradle/branding/build.gradle
+++ b/mobile/android/gradle/branding/build.gradle
@@ -1,12 +1,24 @@
 apply plugin: 'com.android.library'
 
-apply from: "${topsrcdir}/mobile/android/gradle/android.gradle"
-
 android {
-    buildTypes {
-        release {
-            minifyEnabled false
-            proguardFile getDefaultProguardFile('proguard-android.txt')
-        }
+    compileSdkVersion 21
+    buildToolsVersion "21.1.1"
+
+    defaultConfig {
+        targetSdkVersion 21
+        minSdkVersion 9
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+
+    dexOptions {
+        incremental true
+    }
+
+    lintOptions {
+        abortOnError false
     }
 }
--- a/mobile/android/gradle/build.gradle
+++ b/mobile/android/gradle/build.gradle
@@ -28,10 +28,20 @@ repositories {
 
 subprojects {
     task generateCodeAndResources(type:Exec) {
         workingDir "${topobjdir}"
 
         commandLine "${topsrcdir}/mach"
         args 'build'
         args 'mobile/android/base/gradle-targets'
+
+        // Only show the output if something went wrong.
+        ignoreExitValue = true
+        standardOutput = new ByteArrayOutputStream()
+        errorOutput = standardOutput
+        doLast {
+            if (execResult.exitValue != 0) {
+                throw new GradleException("Process '${commandLine}' finished with non-zero exit value ${execResult.exitValue}:\n\n${standardOutput.toString()}")
+            }
+        }
     }
 }
--- a/mobile/android/gradle/omnijar/build.gradle
+++ b/mobile/android/gradle/omnijar/build.gradle
@@ -18,16 +18,26 @@ task buildOmnijar(type:Exec) {
 
     workingDir "${topobjdir}"
 
     commandLine "${topsrcdir}/mach"
     args 'build'
     args '-C'
     args 'mobile/android/base'
     args 'gradle-omnijar'
+
+    // Only show the output if something went wrong.
+    ignoreExitValue = true
+    standardOutput = new ByteArrayOutputStream()
+    errorOutput = standardOutput
+    doLast {
+        if (execResult.exitValue != 0) {
+            throw new GradleException("Process '${commandLine}' finished with non-zero exit value ${execResult.exitValue}:\n\n${standardOutput.toString()}")
+        }
+    }
 }
 
 apply plugin: 'idea'
 
 idea {
     module {
     }
 }
--- a/mobile/android/gradle/preprocessed_code/build.gradle
+++ b/mobile/android/gradle/preprocessed_code/build.gradle
@@ -1,16 +1,28 @@
 apply plugin: 'android-library'
 
-apply from: "${topsrcdir}/mobile/android/gradle/android.gradle"
-
 android {
-    buildTypes {
-        release {
-            minifyEnabled false
-            proguardFile getDefaultProguardFile('proguard-android.txt')
-        }
+    compileSdkVersion 21
+    buildToolsVersion "21.1.1"
+
+    defaultConfig {
+        targetSdkVersion 21
+        minSdkVersion 9
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+
+    dexOptions {
+        incremental true
+    }
+
+    lintOptions {
+        abortOnError false
     }
 }
 
 android.libraryVariants.all { variant ->
     variant.checkManifest.dependsOn generateCodeAndResources
 }
--- a/mobile/android/gradle/preprocessed_resources/build.gradle
+++ b/mobile/android/gradle/preprocessed_resources/build.gradle
@@ -1,18 +1,30 @@
 apply plugin: 'com.android.library'
 
-apply from: "${topsrcdir}/mobile/android/gradle/android.gradle"
-
 android {
-    buildTypes {
-        release {
-            minifyEnabled false
-            proguardFile getDefaultProguardFile('proguard-android.txt')
-        }
+    compileSdkVersion 21
+    buildToolsVersion "21.1.1"
+
+    defaultConfig {
+        targetSdkVersion 21
+        minSdkVersion 9
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+
+    dexOptions {
+        incremental true
+    }
+
+    lintOptions {
+        abortOnError false
     }
 }
 
 android.libraryVariants.all { variant ->
     variant.checkManifest.dependsOn generateCodeAndResources
 }
 
 dependencies {
--- a/mobile/android/gradle/settings.gradle
+++ b/mobile/android/gradle/settings.gradle
@@ -3,30 +3,30 @@
 // our root project is in the source directory, so we extract topsrcdir relative
 // to the location of this script.
 if (!hasProperty('topsrcdir')) {
     // In the source directory, we're not worried about links crossing directories.
     binding.variables['topsrcdir'] = new File("../../..").getCanonicalPath()
     logger.warn("topsrcdir is undefined: assuming source directory Gradle invocation with topsrcdir=${topsrcdir}.")
 }
 
-def command = ["${topsrcdir}/mach", "environment", "--format", "json", "--verbose"]
-def proc = command.execute(null, new File(topsrcdir))
-def sout = new StringBuffer()
-def serr = new StringBuffer()
-proc.consumeProcessOutput(sout, serr)
+def commandLine = ["${topsrcdir}/mach", "environment", "--format", "json", "--verbose"]
+def proc = commandLine.execute(null, new File(topsrcdir))
+def standardOutput = new ByteArrayOutputStream()
+proc.consumeProcessOutput(standardOutput, standardOutput)
 proc.waitFor()
 
+// Only show the output if something went wrong.
 if (proc.exitValue() != 0) {
-    throw new GradleException("Could not extract mozconfig/build environment from |${topsrcdir}/mach environment|!");
+    throw new GradleException("Process '${commandLine}' finished with non-zero exit value ${proc.exitValue()}:\n\n${standardOutput.toString()}")
 }
 
 import groovy.json.JsonSlurper
 def slurper = new JsonSlurper()
-def json = slurper.parseText(sout.toString())
+def json = slurper.parseText(standardOutput.toString())
 
 include ':app'
 include ':base'
 include ':branding'
 include ':omnijar'
 include ':preprocessed_code'
 include ':preprocessed_resources'
 include ':thirdparty'
--- a/mobile/android/gradle/thirdparty/build.gradle
+++ b/mobile/android/gradle/thirdparty/build.gradle
@@ -1,17 +1,29 @@
 apply plugin: 'com.android.library'
 
-apply from: "${topsrcdir}/mobile/android/gradle/android.gradle"
-
 android {
-    buildTypes {
-        release {
-            minifyEnabled false
-            proguardFile getDefaultProguardFile('proguard-android.txt')
-        }
+    compileSdkVersion 21
+    buildToolsVersion "21.1.1"
+
+    defaultConfig {
+        targetSdkVersion 21
+        minSdkVersion 9
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+
+    dexOptions {
+        incremental true
+    }
+
+    lintOptions {
+        abortOnError false
     }
 }
 
 dependencies {
     compile fileTree(dir: 'libs', include: ['*.jar'])
-    compile 'com.android.support:support-v4:19.1.+'
+    compile 'com.android.support:support-v4:21.+'
 }
--- a/mobile/android/mach_commands.py
+++ b/mobile/android/mach_commands.py
@@ -107,16 +107,18 @@ class MachCommands(MachCommandBase):
         srcdir('omnijar/src/main/java/locales', 'mobile/android/locales')
         srcdir('omnijar/src/main/java/chrome', 'mobile/android/chrome')
         srcdir('omnijar/src/main/java/components', 'mobile/android/components')
         srcdir('omnijar/src/main/java/modules', 'mobile/android/modules')
         srcdir('omnijar/src/main/java/themes', 'mobile/android/themes')
 
         srcdir('app/build.gradle', 'mobile/android/gradle/app/build.gradle')
         objdir('app/src/main/AndroidManifest.xml', 'mobile/android/base/AndroidManifest.xml')
+        srcdir('app/src/androidTest/res', 'build/mobile/robocop/res')
+        srcdir('app/src/androidTest/assets', 'mobile/android/base/tests/assets')
         objdir('app/src/debug/assets', 'dist/fennec/assets')
         objdir('app/src/debug/jniLibs', 'dist/fennec/lib')
         # Test code.
         srcdir('app/src/robocop_harness/org/mozilla/gecko', 'build/mobile/robocop')
         srcdir('app/src/robocop/org/mozilla/gecko/tests', 'mobile/android/base/tests')
         srcdir('app/src/background/org/mozilla/gecko', 'mobile/android/tests/background/junit3/src')
         srcdir('app/src/browser/org/mozilla/gecko', 'mobile/android/tests/browser/junit3/src')
         # Test libraries.
--- a/toolkit/devtools/server/actors/timeline.js
+++ b/toolkit/devtools/server/actors/timeline.js
@@ -246,17 +246,16 @@ let TimelineActor = exports.TimelineActo
     this._stackFrames.initFrames();
 
     for (let docShell of this.docShells) {
       docShell.recordProfileTimelineMarkers = true;
     }
 
     if (withMemory) {
       this._memoryActor = new MemoryActor(this.conn, this.tabActor, this._stackFrames);
-      events.emit(this, "memory", this._startTime, this._memoryActor.measure());
     }
 
     if (withTicks) {
       this._framerateActor = new FramerateActor(this.conn, this.tabActor);
       this._framerateActor.startRecording();
     }
 
     this._pullTimelineData();
--- a/toolkit/devtools/server/actors/webaudio.js
+++ b/toolkit/devtools/server/actors/webaudio.js
@@ -30,17 +30,17 @@ const NODE_CREATION_METHODS = [
   "createMediaStreamDestination", "createScriptProcessor", "createAnalyser",
   "createGain", "createDelay", "createBiquadFilter", "createWaveShaper",
   "createPanner", "createConvolver", "createChannelSplitter", "createChannelMerger",
   "createDynamicsCompressor", "createOscillator", "createStereoPanner"
 ];
 
 const AUTOMATION_METHODS = [
   "setValueAtTime", "linearRampToValueAtTime", "exponentialRampToValueAtTime",
-  "setTargetAtTime", "setValueCurveAtTime"
+  "setTargetAtTime", "setValueCurveAtTime", "cancelScheduledValues"
 ];
 
 const NODE_ROUTING_METHODS = [
   "connect", "disconnect"
 ];
 
 const NODE_PROPERTIES = {
   "OscillatorNode": {
--- a/toolkit/modules/sessionstore/FormData.jsm
+++ b/toolkit/modules/sessionstore/FormData.jsm
@@ -4,17 +4,16 @@
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["FormData"];
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 
-Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/XPathGenerator.jsm");
 
 /**
  * Returns whether the given URL very likely has input
  * fields that contain serialized session store data.
  */
 function isRestorationPage(url) {
   return url == "about:sessionrestore" || url == "about:welcomeback";
@@ -257,27 +256,20 @@ let FormDataInternal = {
     }
 
     if ("xpath" in data) {
       let retrieveNode = xpath => XPathGenerator.resolve(doc, xpath);
       this.restoreManyInputValues(data.xpath, retrieveNode);
     }
 
     if ("innerHTML" in data) {
-      // We know that the URL matches data.url right now, but the user
-      // may navigate away before the setTimeout handler runs. We do
-      // a simple comparison against savedURL to check for that.
-      let savedURL = doc.documentURI;
-
-      setTimeout(() => {
-        if (doc.body && doc.designMode == "on" && doc.documentURI == savedURL) {
-          doc.body.innerHTML = data.innerHTML;
-          this.fireEvent(doc.body, "input");
-        }
-      });
+      if (doc.body && doc.designMode == "on") {
+        doc.body.innerHTML = data.innerHTML;
+        this.fireEvent(doc.body, "input");
+      }
     }
   },
 
   /**
    * Iterates the given form data, retrieving nodes for all the keys and
    * restores their appropriate values.
    *
    * @param data (object)