Merge fx-team to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 08 Jul 2014 15:55:21 -0700
changeset 192867 d35c1bf0f084b965970ccf3b9a1393082012b8dd
parent 192866 e185bf9449f60b5483d1cfbee126967b70be4f57 (current diff)
parent 192828 2dc3d7c0defce75b9563a20a86fd406703403eb6 (diff)
child 192918 ff4e6d5629039b2d5fd6363d79c1f8187e75f2cd
push id7663
push userkwierso@gmail.com
push dateWed, 09 Jul 2014 03:08:08 +0000
treeherderfx-team@48de6f4f82af [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone33.0a1
Merge fx-team to m-c a=merge
mobile/android/tests/background/junit3/android-services-files.mk
mobile/android/tests/background/junit3/android-services.mozbuild
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,11 @@
 #
 # 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 940506: removing idl files appears to break the build without a clobber
+Bug 1033481 - Use a .mozbuild file rather than a .mk in
+m/a/tests/background/junit3 -- doesn't clean the classes* directory
+when moving from in APK to in JAR building.
--- a/addon-sdk/source/lib/sdk/base64.js
+++ b/addon-sdk/source/lib/sdk/base64.js
@@ -6,17 +6,19 @@
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const { Cu } = require("chrome");
 
 // Passing an empty object as second argument to avoid scope's pollution
-const { atob, btoa } = Cu.import("resource://gre/modules/Services.jsm", {});
+// (devtools loader injects these symbols as global and prevent using
+// const here)
+var { atob, btoa } = Cu.import("resource://gre/modules/Services.jsm", {});
 
 function isUTF8(charset) {
   let type = typeof charset;
 
   if (type === "undefined")
     return false;
 
   if (type === "string" && charset.toLowerCase() === "utf-8")
--- a/addon-sdk/source/lib/toolkit/loader.js
+++ b/addon-sdk/source/lib/toolkit/loader.js
@@ -278,27 +278,41 @@ const load = iced(function load(loader, 
     exports: module.exports,
     get Components() {
       // Expose `Components` property to throw error on usage with
       // additional information
       throw new ReferenceError(COMPONENT_ERROR);
     }
   });
 
-  let sandbox = sandboxes[module.uri] = Sandbox({
-    name: module.uri,
-    prototype: create(globals, descriptors),
-    wantXrays: false,
-    wantGlobalProperties: module.id == "sdk/indexed-db" ? ["indexedDB"] : [],
-    invisibleToDebugger: loader.invisibleToDebugger,
-    metadata: {
-      addonID: loader.id,
-      URI: module.uri
-    }
-  });
+  let sandbox;
+  if (loader.sharedGlobalSandbox &&
+      loader.sharedGlobalBlacklist.indexOf(module.id) == -1) {
+    // Create a new object in this sandbox, that will be used as
+    // the scope object for this particular module
+    sandbox = new loader.sharedGlobalSandbox.Object();
+    // Inject all expected globals in the scope object
+    getOwnPropertyNames(globals).forEach(function(name) {
+      descriptors[name] = getOwnPropertyDescriptor(globals, name)
+    });
+    define(sandbox, descriptors);
+  } else {
+    sandbox = Sandbox({
+      name: module.uri,
+      prototype: create(globals, descriptors),
+      wantXrays: false,
+      wantGlobalProperties: module.id == "sdk/indexed-db" ? ["indexedDB"] : [],
+      invisibleToDebugger: loader.invisibleToDebugger,
+      metadata: {
+        addonID: loader.id,
+        URI: module.uri
+      }
+    });
+  }
+  sandboxes[module.uri] = sandbox;
 
   try {
     evaluate(sandbox, module.uri);
   } catch (error) {
     let { message, fileName, lineNumber } = error;
     let stack = error.stack || Error().stack;
     let frames = parseStack(stack).filter(isntLoaderFrame);
     let toString = String(error);
@@ -686,27 +700,28 @@ exports.unload = unload;
 //   If `resolve` does not returns `uri` string exception will be thrown by
 //   an associated `require` call.
 const Loader = iced(function Loader(options) {
   let console = new ConsoleAPI({
     consoleID: options.id ? "addon/" + options.id : ""
   });
 
   let {
-    modules, globals, resolve, paths, rootURI,
-    manifest, requireMap, isNative, metadata
+    modules, globals, resolve, paths, rootURI, manifest, requireMap, isNative,
+    metadata, sharedGlobal, sharedGlobalBlacklist
   } = override({
     paths: {},
     modules: {},
     globals: {
       console: console
     },
     resolve: options.isNative ?
       exports.nodeResolve :
       exports.resolve,
+    sharedGlobalBlacklist: ["sdk/indexed-db"]
   }, options);
 
   // We create an identity object that will be dispatched on an unload
   // event as subject. This way unload listeners will be able to assert
   // which loader is unloaded. Please note that we intentionally don't
   // use `loader` as subject to prevent a loader access leakage through
   // observer notifications.
   let destructor = freeze(create(null));
@@ -733,26 +748,46 @@ const Loader = iced(function Loader(opti
     if (isNative && !uri)
       uri = id;
     let module = Module(id, uri);
     module.exports = freeze(modules[id]);
     result[uri] = freeze(module);
     return result;
   }, {});
 
+  let sharedGlobalSandbox;
+  if (sharedGlobal) {
+    // Create the unique sandbox we will be using for all modules,
+    // so that we prevent creating a new comportment per module.
+    // The side effect is that all modules will share the same
+    // global objects.
+    sharedGlobalSandbox = Sandbox({
+      name: "Addon-SDK",
+      wantXrays: false,
+      wantGlobalProperties: [],
+      invisibleToDebugger: options.invisibleToDebugger || false,
+      metadata: {
+        addonID: options.id,
+        URI: "Addon-SDK"
+      }
+    });
+  }
+
   // Loader object is just a representation of a environment
   // state. We freeze it and mark make it's properties non-enumerable
   // as they are pure implementation detail that no one should rely upon.
   let returnObj = {
     destructor: { enumerable: false, value: destructor },
     globals: { enumerable: false, value: globals },
     mapping: { enumerable: false, value: mapping },
     // Map of module objects indexed by module URIs.
     modules: { enumerable: false, value: modules },
     metadata: { enumerable: false, value: metadata },
+    sharedGlobalSandbox: { enumerable: false, value: sharedGlobalSandbox },
+    sharedGlobalBlacklist: { enumerable: false, value: sharedGlobalBlacklist },
     // Map of module sandboxes indexed by module URIs.
     sandboxes: { enumerable: false, value: {} },
     resolve: { enumerable: false, value: resolve },
     // ID of the addon, if provided.
     id: { enumerable: false, value: options.id },
     // Whether the modules loaded should be ignored by the debugger
     invisibleToDebugger: { enumerable: false,
                            value: options.invisibleToDebugger || false },
--- a/addon-sdk/source/test/test-loader.js
+++ b/addon-sdk/source/test/test-loader.js
@@ -338,9 +338,32 @@ exports['test console global by default'
   let loader2 = Loader({ paths: { '': uri }, globals: { console: fakeConsole }});
   let program2 = main(loader2, 'main');
 
   assert.equal(program2.console, fakeConsole,
     'global console can be overridden with Loader options');
   function fakeConsole () {};
 };
 
+exports['test shared globals'] = function(assert) {
+  let uri = root + '/fixtures/loader/cycles/';
+  let loader = Loader({ paths: { '': uri }, sharedGlobal: true,
+                        sharedGlobalBlacklist: ['b'] });
+
+  let program = main(loader, 'main');
+
+  // As it is hard to verify what is the global of an object
+  // (due to wrappers) we check that we see the `foo` symbol
+  // being manually injected into the shared global object
+  loader.sharedGlobalSandbox.foo = true;
+
+  let m = loader.sandboxes[uri + 'main.js'];
+  let a = loader.sandboxes[uri + 'a.js'];
+  let b = loader.sandboxes[uri + 'b.js'];
+
+  assert.ok(Cu.getGlobalForObject(m).foo, "main is shared");
+  assert.ok(Cu.getGlobalForObject(a).foo, "a is shared");
+  assert.ok(!Cu.getGlobalForObject(b).foo, "b isn't shared");
+
+  unload(loader);
+}
+
 require('test').run(exports);
--- a/browser/components/preferences/content.xul
+++ b/browser/components/preferences/content.xul
@@ -144,20 +144,21 @@
                     oncommand="gContentPane.showLanguages();"/>
           </row>
           <row id="translationBox" hidden="true">
             <hbox align="center">
               <checkbox id="translate" preference="browser.translation.detectLanguage"
                         label="&translateWebPages.label;." accesskey="&translateWebPages.accesskey;"
                         onsyncfrompreference="return gContentPane.updateButtons('translateButton',
                                               'browser.translation.detectLanguage');"/>
-              <label>Übersetzungen von</label>
+              <label>&translation.options.attribution.beforeLogo;</label>
               <image id="translationAttributionImage" aria-label="Microsoft Translator"
                      onclick="gContentPane.openTranslationProviderAttribution()"
                      src="chrome://browser/content/microsoft-translator-attribution.png"/>
+              <label>&translation.options.attribution.afterLogo;</label>
             </hbox>
             <button id="translateButton" label="&translateExceptions.label;"
                     oncommand="gContentPane.showTranslationExceptions();"
                     accesskey="&translateExceptions.accesskey;"/>
           </row>
         </rows>
       </grid>
 
--- a/browser/components/preferences/in-content/content.xul
+++ b/browser/components/preferences/in-content/content.xul
@@ -134,19 +134,21 @@
   </hbox>
 
   <hbox id="translationBox" hidden="true">
     <hbox align="center" flex="1">
       <checkbox id="translate" preference="browser.translation.detectLanguage"
                 label="&translateWebPages.label;." accesskey="&translateWebPages.accesskey;"
                 onsyncfrompreference="return gContentPane.updateButtons('translateButton',
                                               'browser.translation.detectLanguage');"/>
-      <label>Übersetzungen von</label>
+      <label>&translation.options.attribution.beforeLogo;</label>
       <separator orient="vertical" class="thin"/>
       <image id="translationAttributionImage" aria-label="Microsoft Translator"
              onclick="gContentPane.openTranslationProviderAttribution()"
              src="chrome://browser/content/microsoft-translator-attribution.png"/>
+      <separator orient="vertical" class="thin"/>
+      <label>&translation.options.attribution.afterLogo;</label>
     </hbox>
     <button id="translateButton" label="&translateExceptions.label;"
             oncommand="gContentPane.showTranslationExceptions();"
             accesskey="&translateExceptions.accesskey;"/>
   </hbox>
 </groupbox>
--- a/browser/components/translation/translation-infobar.xml
+++ b/browser/components/translation/translation-infobar.xml
@@ -108,19 +108,20 @@
                             label="&translation.options.neverForSite.label;"
                             accesskey="&translation.options.neverForSite.accesskey;"/>
               <xul:menuseparator/>
               <xul:menuitem oncommand="openPreferences('paneContent');"
                             label="&translation.options.preferences.label;"
                             accesskey="&translation.options.preferences.accesskey;"/>
               <xul:menuitem class="translation-attribution subviewbutton panel-subview-footer"
                             oncommand="document.getBindingParent(this).openProviderAttribution();">
-                <xul:label>Übersetzungen von</xul:label>
+                <xul:label>&translation.options.attribution.beforeLogo;</xul:label>
                 <xul:image src="chrome://browser/content/microsoft-translator-attribution.png"
                            aria-label="Microsoft Translator"/>
+                <xul:label>&translation.options.attribution.afterLogo;</xul:label>
               </xul:menuitem>
             </xul:menupopup>
           </xul:button>
 
         </xul:hbox>
         <xul:toolbarbutton ondblclick="event.stopPropagation();"
                            anonid="closeButton"
                            class="messageCloseButton close-icon tabbable"
--- a/browser/devtools/app-manager/app-projects.js
+++ b/browser/devtools/app-manager/app-projects.js
@@ -9,24 +9,23 @@ const { indexedDB } = require("sdk/index
 
 /**
  * IndexedDB wrapper that just save project objects
  *
  * The only constraint is that project objects have to have
  * a unique `location` object.
  */
 
-const global = this;
 const IDB = {
   _db: null,
 
   open: function () {
     let deferred = promise.defer();
 
-    let request = global.indexedDB.open("AppProjects", 5);
+    let request = indexedDB.open("AppProjects", 5);
     request.onerror = function(event) {
       deferred.reject("Unable to open AppProjects indexedDB. " +
                       "Error code: " + event.target.errorCode);
     };
     request.onupgradeneeded = function(event) {
       let db = event.target.result;
       db.createObjectStore("projects", { keyPath: "location" });
     };
--- a/browser/devtools/framework/toolbox-options.js
+++ b/browser/devtools/framework/toolbox-options.js
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Cu, Cc, Ci} = require("chrome");
 const Services = require("Services");
+const promise = require("promise");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
 
 exports.OptionsPanel = OptionsPanel;
 
 XPCOMUtils.defineLazyGetter(this, "l10n", function() {
   let bundle = Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
   let l10n = function(aName, ...aArgs) {
--- a/browser/devtools/projecteditor/lib/shells.js
+++ b/browser/devtools/projecteditor/lib/shells.js
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const { Cu } = require("chrome");
 const { Class } = require("sdk/core/heritage");
 const { EventTarget } = require("sdk/event/target");
 const { emit } = require("sdk/event/core");
 const { EditorTypeForResource } = require("projecteditor/editors");
 const NetworkHelper = require("devtools/toolkit/webconsole/network-helper");
+const promise = require("promise");
 
 /**
  * The Shell is the object that manages the editor for a single resource.
  * It is in charge of selecting the proper Editor (text/image/plugin-defined)
  * and instantiating / appending the editor.
  * This object is not exported, it is just used internally by the ShellDeck.
  *
  * This object has a promise `editorAppended`, that will resolve once the editor
--- a/browser/devtools/shared/test/browser_graphs-06.js
+++ b/browser/devtools/shared/test/browser_graphs-06.js
@@ -60,16 +60,24 @@ function testGraph(graph) {
 
   graph.setSelection({ start: graph.width, end: 0 });
   min = map(0, 0, graph.width, 112, 4180);
   max = map(graph.width, 0, graph.width, 112, 4180);
   is(graph.getMappedSelection().min, min,
     "The mapped selection's min value is correct (5).");
   is(graph.getMappedSelection().max, max,
     "The mapped selection's max value is correct (6).");
+
+  graph.setSelection({ start: graph.width + 100, end: -100 });
+  min = map(0, 0, graph.width, 112, 4180);
+  max = map(graph.width, 0, graph.width, 112, 4180);
+  is(graph.getMappedSelection().min, min,
+    "The mapped selection's min value is correct (7).");
+  is(graph.getMappedSelection().max, max,
+    "The mapped selection's max value is correct (8).");
 }
 
 /**
  * Maps a value from one range to another.
  */
 function map(value, istart, istop, ostart, ostop) {
   return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
 }
--- a/browser/devtools/shared/widgets/Graphs.jsm
+++ b/browser/devtools/shared/widgets/Graphs.jsm
@@ -131,19 +131,21 @@ GraphSelectionResizer.prototype = {
  *        The graph type, used for setting the correct class names.
  *        Currently supported: "line-graph" only.
  * @param number sharpness [optional]
  *        Defaults to the current device pixel ratio.
  */
 this.AbstractCanvasGraph = function(parent, name, sharpness) {
   EventEmitter.decorate(this);
 
+  this._parent = parent;
   this._ready = promise.defer();
-  this._parent = parent;
+
   this._uid = "canvas-graph-" + Date.now();
+  this._renderTargets = new Map();
 
   AbstractCanvasGraph.createIframe(GRAPH_SRC, parent, iframe => {
     this._iframe = iframe;
     this._window = iframe.contentWindow;
     this._document = iframe.contentDocument;
     this._pixelRatio = sharpness || this._window.devicePixelRatio;
 
     let container = this._container = this._document.getElementById("graph-container");
@@ -226,17 +228,19 @@ AbstractCanvasGraph.prototype = {
     let ownerWindow = this._parent.ownerDocument.defaultView;
     ownerWindow.removeEventListener("resize", this._onResize);
 
     this._window.cancelAnimationFrame(this._animationId);
     this._iframe.remove();
 
     this._data = null;
     this._regions = null;
+    this._cachedBackgroundImage = null;
     this._cachedGraphImage = null;
+    this._renderTargets.clear();
     gCachedStripePattern.clear();
   },
 
   /**
    * Rendering options. Subclasses should override these.
    */
   clipheadLineWidth: 1,
   clipheadLineColor: "transparent",
@@ -250,16 +254,24 @@ AbstractCanvasGraph.prototype = {
   /**
    * Makes sure the canvas graph is of the specified width or height, and
    * doesn't flex to fit all the available space.
    */
   fixedWidth: null,
   fixedHeight: null,
 
   /**
+   * Optionally builds and caches a background image for this graph.
+   * Inheriting classes may override this method.
+   */
+  buildBackgroundImage: function() {
+    return null;
+  },
+
+  /**
    * Builds and caches a graph image, based on the data source supplied
    * in `setData`. The graph image is not rebuilt on each frame, but
    * only when the data source changes.
    */
   buildGraphImage: function() {
     throw "This method needs to be implemented by inheriting classes.";
   },
 
@@ -273,16 +285,17 @@ AbstractCanvasGraph.prototype = {
   /**
    * Sets the data source for this graph.
    *
    * @param object data
    *        The data source. The actual format is specified by subclasses.
    */
   setData: function(data) {
     this._data = data;
+    this._cachedBackgroundImage = this.buildBackgroundImage();
     this._cachedGraphImage = this.buildGraphImage();
     this._shouldRedraw = true;
   },
 
   /**
    * Same as `setData`, but waits for this graph to finish initializing first.
    *
    * @param object data
@@ -387,27 +400,28 @@ AbstractCanvasGraph.prototype = {
    *        store this in a "delta" property for all entries, but in the future
    *        this may change as new graphs with different data source format
    *        requirements are implemented.
    * @return object
    *         The mapped selection's { min, max } values.
    */
   getMappedSelection: function(unpack = e => e.delta) {
     if (!this.hasData() || !this.hasSelection()) {
-      return { start: null, end: null };
+      return { min: null, max: null };
     }
     let selection = this.getSelection();
     let totalTicks = this._data.length;
     let firstTick = unpack(this._data[0]);
     let lastTick = unpack(this._data[totalTicks - 1]);
 
     // The selection's start and end values are not guaranteed to be ascending.
     // This can happen, for example, when click & dragging from right to left.
-    let min = Math.min(selection.start, selection.end);
-    let max = Math.max(selection.start, selection.end);
+    // Also make sure that the selection bounds fit inside the canvas bounds.
+    let min = Math.max(Math.min(selection.start, selection.end), 0);
+    let max = Math.min(Math.max(selection.start, selection.end), this._width);
     min = map(min, 0, this._width, firstTick, lastTick);
     max = map(max, 0, this._width, firstTick, lastTick);
 
     return { min: min, max: max };
   },
 
   /**
    * Removes the selection.
@@ -559,28 +573,61 @@ AbstractCanvasGraph.prototype = {
 
     bounds.width = newWidth;
     bounds.height = newHeight;
     this._iframe.setAttribute("width", bounds.width);
     this._iframe.setAttribute("height", bounds.height);
     this._width = this._canvas.width = bounds.width * this._pixelRatio;
     this._height = this._canvas.height = bounds.height * this._pixelRatio;
 
-    if (this._data) {
+    if (this.hasData()) {
+      this._cachedBackgroundImage = this.buildBackgroundImage();
       this._cachedGraphImage = this.buildGraphImage();
     }
-    if (this._regions) {
+    if (this.hasRegions()) {
       this._bakeRegions(this._regions, this._cachedGraphImage);
     }
 
     this._shouldRedraw = true;
     this.emit("refresh");
   },
 
   /**
+   * Gets a canvas with the specified name, for this graph.
+   *
+   * If it doesn't exist yet, it will be created, otherwise the cached instance
+   * will be cleared and returned.
+   *
+   * @param string name
+   *        The canvas name.
+   * @param number width, height [optional]
+   *        A custom width and height for the canvas. Defaults to this graph's
+   *        container canvas width and height.
+   */
+  _getNamedCanvas: function(name, width = this._width, height = this._height) {
+    let cachedRenderTarget = this._renderTargets.get(name);
+    if (cachedRenderTarget) {
+      let { canvas, ctx } = cachedRenderTarget;
+      canvas.width = width;
+      canvas.height = height;
+      ctx.clearRect(0, 0, width, height);
+      return cachedRenderTarget;
+    }
+
+    let canvas = this._document.createElementNS(HTML_NS, "canvas");
+    let ctx = canvas.getContext("2d");
+    canvas.width = width;
+    canvas.height = height;
+
+    let renderTarget = { canvas: canvas, ctx: ctx };
+    this._renderTargets.set(name, renderTarget);
+    return renderTarget;
+  },
+
+  /**
    * The contents of this graph are redrawn only when something changed,
    * like the data source, or the selection bounds etc. This flag tracks
    * if the rendering is "dirty" and needs to be refreshed.
    */
   _shouldRedraw: false,
 
   /**
    * Animation frame callback, invoked on each tick of the refresh driver.
@@ -593,24 +640,26 @@ AbstractCanvasGraph.prototype = {
   /**
    * Redraws the widget when necessary. The actual graph is not refreshed
    * every time this function is called, only the cliphead, selection etc.
    */
   _drawWidget: function() {
     if (!this._shouldRedraw) {
       return;
     }
-
     let ctx = this._ctx;
     ctx.clearRect(0, 0, this._width, this._height);
 
-    // Draw the graph underneath the cursor and selection.
-    if (this.hasData()) {
+    if (this._cachedBackgroundImage) {
+      ctx.drawImage(this._cachedBackgroundImage, 0, 0, this._width, this._height);
+    }
+    if (this._cachedGraphImage) {
       ctx.drawImage(this._cachedGraphImage, 0, 0, this._width, this._height);
     }
+
     if (this.hasCursor()) {
       this._drawCliphead();
     }
     if (this.hasSelection() || this.hasSelectionInProgress()) {
       this._drawSelection();
     }
 
     this._shouldRedraw = false;
@@ -1004,17 +1053,17 @@ AbstractCanvasGraph.prototype = {
     this._canvas.removeAttribute("input");
     this._shouldRedraw = true;
   },
 
   /**
    * Listener for the "resize" event on the graph's parent node.
    */
   _onResize: function() {
-    if (this._cachedGraphImage) {
+    if (this.hasData()) {
       setNamedTimeout(this._uid, GRAPH_RESIZE_EVENTS_DRAIN, this.refresh);
     }
   }
 };
 
 /**
  * A basic line graph, plotting values on a curve and adding helper lines
  * and tooltips for maximum, average and minimum values.
@@ -1069,20 +1118,19 @@ LineGraphWidget.prototype = Heritage.ext
    */
   minDistanceBetweenPoints: LINE_GRAPH_MIN_SQUARED_DISTANCE_BETWEEN_POINTS,
 
   /**
    * Renders the graph on a canvas.
    * @see AbstractCanvasGraph.prototype.buildGraphImage
    */
   buildGraphImage: function() {
-    let canvas = this._document.createElementNS(HTML_NS, "canvas");
-    let ctx = canvas.getContext("2d");
-    let width = canvas.width = this._width;
-    let height = canvas.height = this._height;
+    let { canvas, ctx } = this._getNamedCanvas("line-graph-data");
+    let width = this._width;
+    let height = this._height;
 
     let totalTicks = this._data.length;
     let firstTick = this._data[0].delta;
     let lastTick = this._data[totalTicks - 1].delta;
     let maxValue = Number.MIN_SAFE_INTEGER;
     let minValue = Number.MAX_SAFE_INTEGER;
     let sumValues = 0;
 
@@ -1330,28 +1378,44 @@ BarGraphWidget.prototype = Heritage.exte
 
   /**
    * Blocks in a bar that are too thin inside the bar will not be rendered.
    * This scalar specifies the required minimum height of each block.
    */
   minBlocksHeight: BAR_GRAPH_MIN_BLOCKS_HEIGHT,
 
   /**
+   * Renders the graph's background.
+   * @see AbstractCanvasGraph.prototype.buildBackgroundImage
+   */
+  buildBackgroundImage: function() {
+    let { canvas, ctx } = this._getNamedCanvas("bar-graph-background");
+    let width = this._width;
+    let height = this._height;
+
+    let gradient = ctx.createLinearGradient(0, 0, 0, height);
+    gradient.addColorStop(0, BAR_GRAPH_BACKGROUND_GRADIENT_START);
+    gradient.addColorStop(1, BAR_GRAPH_BACKGROUND_GRADIENT_END);
+    ctx.fillStyle = gradient;
+    ctx.fillRect(0, 0, width, height);
+
+    return canvas;
+  },
+
+  /**
    * Renders the graph on a canvas.
    * @see AbstractCanvasGraph.prototype.buildGraphImage
    */
   buildGraphImage: function() {
     if (!this.format || !this.format.length) {
       throw "The graph format traits are mandatory to style the data source.";
     }
-
-    let canvas = this._document.createElementNS(HTML_NS, "canvas");
-    let ctx = canvas.getContext("2d");
-    let width = canvas.width = this._width;
-    let height = canvas.height = this._height;
+    let { canvas, ctx } = this._getNamedCanvas("bar-graph-data");
+    let width = this._width;
+    let height = this._height;
 
     let totalTypes = this.format.length;
     let totalTicks = this._data.length;
     let firstTick = this._data[0].delta;
     let lastTick = this._data[totalTicks - 1].delta;
 
     let minBarsWidth = this.minBarsWidth * this._pixelRatio;
     let minBlocksHeight = this.minBlocksHeight * this._pixelRatio;
@@ -1359,24 +1423,16 @@ BarGraphWidget.prototype = Heritage.exte
     let dataScaleX = this.dataScaleX = width / (lastTick - firstTick);
     let dataScaleY = this.dataScaleY = height / this._calcMaxHeight({
       data: this._data,
       dataScaleX: dataScaleX,
       dataOffsetX: firstTick,
       minBarsWidth: minBarsWidth
     }) * BAR_GRAPH_DAMPEN_VALUES;
 
-    // Draw the background.
-
-    let gradient = ctx.createLinearGradient(0, 0, 0, height);
-    gradient.addColorStop(0, BAR_GRAPH_BACKGROUND_GRADIENT_START);
-    gradient.addColorStop(1, BAR_GRAPH_BACKGROUND_GRADIENT_END);
-    ctx.fillStyle = gradient;
-    ctx.fillRect(0, 0, width, height);
-
     // Draw the graph.
 
     // Iterate over the blocks, then the bars, to draw all rectangles of
     // the same color in a single pass. See the @constructor for more
     // information about the data source, and how a "bar" contains "blocks".
 
     let prevHeight = [];
     let scaledMarginEnd = BAR_GRAPH_BARS_MARGIN_END * this._pixelRatio;
--- a/browser/devtools/webide/modules/runtimes.js
+++ b/browser/devtools/webide/modules/runtimes.js
@@ -4,16 +4,17 @@
 
 const {Cu} = require("chrome");
 const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
 const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
 const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
 const {DebuggerServer} = require("resource://gre/modules/devtools/dbg-server.jsm");
 const discovery = require("devtools/toolkit/discovery/discovery");
+const promise = require("promise");
 
 const Strings = Services.strings.createBundle("chrome://webide/content/webide.properties");
 
 function USBRuntime(id) {
   this.id = id;
 }
 
 USBRuntime.prototype = {
--- a/browser/locales/en-US/chrome/browser/preferences/content.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/content.dtd
@@ -27,8 +27,18 @@
 <!ENTITY chooseLanguage.label         "Choose your preferred language for displaying pages">
 <!ENTITY chooseButton.label           "Choose…">
 <!ENTITY chooseButton.accesskey       "o">
 
 <!ENTITY translateWebPages.label      "Translate web content">
 <!ENTITY translateWebPages.accesskey  "T">
 <!ENTITY translateExceptions.label    "Exceptions…">
 <!ENTITY translateExceptions.accesskey "x">
+
+<!-- LOCALIZATION NOTE (translation.options.attribution.beforeLogo,
+  -                     translation.options.attribution.afterLogo):
+  -  These 2 strings are displayed before and after a 'Microsoft Translator'
+  -  logo.
+  -  The translations for these strings should match the translations in
+  -  browser/translation.dtd
+  -->
+<!ENTITY translation.options.attribution.beforeLogo "Translations by">
+<!ENTITY translation.options.attribution.afterLogo "">
--- a/browser/locales/en-US/chrome/browser/translation.dtd
+++ b/browser/locales/en-US/chrome/browser/translation.dtd
@@ -51,8 +51,16 @@
   -                     translation.options.preferences.accesskey):
   -  The accesskey values used here should not clash with the value used for
   -  translation.options.neverForLanguage.accesskey in translation.properties
   -->
 <!ENTITY translation.options.neverForSite.label "Never translate this site">
 <!ENTITY translation.options.neverForSite.accesskey "e">
 <!ENTITY translation.options.preferences.label  "Translation preferences">
 <!ENTITY translation.options.preferences.accesskey "T">
+
+<!-- LOCALIZATION NOTE (translation.options.attribution.beforeLogo,
+  -                     translation.options.attribution.afterLogo):
+  -  These 2 strings are displayed before and after a 'Microsoft Translator'
+  -  logo.
+  -->
+<!ENTITY translation.options.attribution.beforeLogo "Translations by">
+<!ENTITY translation.options.attribution.afterLogo "">
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3622,16 +3622,17 @@ notification[value="translation"] {
 }
 
 .translate-infobar-element {
   margin-top: 0 !important;
   margin-bottom: 0 !important;
 }
 
 button.translate-infobar-element {
+  background: linear-gradient(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1)) repeat scroll 0% 0% padding-box transparent;
   color: #333333;
   border: 1px solid;
   border-color: rgba(23, 51, 78, 0.15) rgba(23, 51, 78, 0.17) rgba(23, 51, 78, 0.2);
   box-shadow: 0px 0px 2px rgba(255, 255, 255, 0.5) inset, 0px 1px 0px rgba(255, 255, 255, 0.2);
   transition-property: background-color, border-color, box-shadow;
   transition-duration: 150ms;
   min-height: 22px;
   min-width: 0;
@@ -3645,96 +3646,102 @@ button.translate-infobar-element .button
   margin-right: 0 !important;
 }
 
 label.translate-infobar-element {
   padding-top: 2px;
 }
 
 button.translate-infobar-element:hover {
+  background: #f0f0f0;
   box-shadow: 0 1px 0 hsla(0,0%,100%,.1) inset,  0 0 0 1px hsla(0,0%,100%,.05) inset,  0 1px 0 hsla(210,54%,20%,.01),  0 0 4px hsla(206,100%,20%,.1);
 }
 
 button.translate-infobar-element:active {
   box-shadow: 0 1px 1px hsla(211,79%,6%,.1) inset,  0 0 1px hsla(211,79%,6%,.2) inset;
   transition-duration: 0ms;
 }
 
 button.translate-infobar-element[anonid="translate"] {
   color: #ffffff;
-  background-image: linear-gradient(#4cb1ff, #1793e5);
+  background: linear-gradient(#4cb1ff, #1793e5);
   box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,  0 0 0 1px hsla(0,0%,100%,.1) inset,  0 1px 0 hsla(210,54%,20%,.03);
   border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
   padding: 0 1.1em  !important;;
 }
 
 button.translate-infobar-element[anonid="translate"]:hover {
   background-image: linear-gradient(#66bdff, #0d9eff);
-}
-
-button.translate-infobar-element[anonid="notNow"] {
-  background: linear-gradient(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1)) repeat scroll 0% 0% padding-box transparent;
-}
-
-button.translate-infobar-element[anonid="notNow"]:hover {
-  background: #f0f0f0;
+  box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,  0 0 0 1px hsla(0,0%,100%,.1) inset,  0 1px 0 hsla(210,54%,20%,.03),  0 0 4px hsla(206,100%,20%,.2);
 }
 
 button.translate-infobar-element.options-menu-button {
   -moz-padding-start: 0.5em !important;
-  -moz-padding-end: 0.3em !important;
+  -moz-padding-end: 0em !important;
 }
 
 button.translate-infobar-element.options-menu-button > .button-box > .button-menu-dropmarker {
   display: -moz-box;
-  list-style-image: url("chrome://browser/skin/toolbarbutton-dropmarker.png");
+  list-style-image: url("chrome://global/skin/icons/glyph-dropdown.png");
   padding: 0 !important;
   margin: 0 !important;
 }
 
-@media (min-resolution: 1.25dppx) {
+@media (min-resolution: 2dppx) {
   button.translate-infobar-element.options-menu-button > .button-box > .button-menu-dropmarker {
-    list-style-image: url("chrome://browser/skin/toolbarbutton-dropmarker@2x.png");
+    list-style-image: url("chrome://global/skin/icons/glyph-dropdown@2x.png");
   }
 
   button.translate-infobar-element.options-menu-button > .button-box > .button-menu-dropmarker > .dropmarker-icon {
-    width: 7px;
+    width: 8px;
   }
 }
 
 menulist.translate-infobar-element {
   text-shadow: 0 1px 1px #FEFFFE;
   border: 1px solid;
   border-color: rgba(23, 51, 78, 0.15) rgba(23, 51, 78, 0.17) rgba(23, 51, 78, 0.2);
   box-shadow: 0 1px 1px 0 #FFFFFF, inset 0 2px 2px 0 #FFFFFF;
   background-color: #F1F1F1;
   background-image: linear-gradient(#FFFFFF, rgba(255,255,255,0.1));
   color: #333333;
   padding: 0;
+  min-height: 22px !important;
 }
 
 menulist.translate-infobar-element > .menulist-label-box {
   padding-top: 1px;
   -moz-padding-start: 0.3em;
   margin-top: 0;
   margin-bottom: 0;
 }
 
 menulist.translate-infobar-element:hover {
-  background-image: linear-gradient(#FFFFFF, rgba(255,255,255,0.6));
+  background: #f0f0f0;
+  box-shadow: 0 1px 0 hsla(0,0%,100%,.1) inset,  0 0 0 1px hsla(0,0%,100%,.05) inset,  0 1px 0 hsla(210,54%,20%,.01),  0 0 4px hsla(206,100%,20%,.1);
 }
 
 menulist.translate-infobar-element[open="true"] {
   background-image: linear-gradient(rgba(255,255,255,0.1),
                                     rgba(255,255,255,0.6));
 }
 
 menulist.translate-infobar-element > .menulist-dropmarker {
   display: -moz-box;
-  list-style-image: url("chrome://global/skin/icons/menulist-dropmarker.png");
+  list-style-image: url("chrome://global/skin/icons/glyph-dropdown.png");
+}
+
+@media (min-resolution: 2dppx) {
+  menulist.translate-infobar-element > .menulist-dropmarker {
+    list-style-image: url("chrome://global/skin/icons/glyph-dropdown@2x.png");
+  }
+
+  menulist.translate-infobar-element > .menulist-dropmarker > .dropmarker-icon {
+    width: 8px;
+  }
 }
 
 
 .popup-notification-icon {
   width: 64px;
   height: 64px;
   -moz-margin-end: 10px;
 }
--- a/configure.in
+++ b/configure.in
@@ -3890,16 +3890,17 @@ MOZ_PLACES=1
 MOZ_SOCIAL=1
 MOZ_PREF_EXTENSIONS=1
 MOZ_PROFILELOCKING=1
 MOZ_REFLOW_PERF=
 MOZ_SAFE_BROWSING=
 MOZ_HELP_VIEWER=
 MOZ_SPELLCHECK=1
 MOZ_ANDROID_OMTC=
+MOZ_NATIVE_CASTING=1
 MOZ_TOOLKIT_SEARCH=1
 MOZ_UI_LOCALE=en-US
 MOZ_UNIVERSALCHARDET=1
 MOZ_URL_CLASSIFIER=
 MOZ_XUL=1
 MOZ_ZIPWRITER=1
 NS_PRINTING=1
 MOZ_PDF_PRINTING=
@@ -7663,16 +7664,29 @@ if test "$MOZ_WIDGET_TOOLKIT" = "android
     dnl not the case on desktop: there are omnijars rooted at webrtc/,
     dnl etc). packager.mk handles changing the rooting of the single
     dnl omnijar.
     OMNIJAR_NAME=assets/omni.ja
 else
     OMNIJAR_NAME=omni.ja
 fi
 
+dnl ========================================================
+dnl = --disable-native-casting
+dnl ========================================================
+
+MOZ_ARG_DISABLE_BOOL(native-casting,
+[  --disable-native-casting      Disable native casting devices],
+    MOZ_NATIVE_CASTING=,
+    MOZ_NATIVE_CASTING=1)
+if test "$MOZ_NATIVE_CASTING"; then
+  AC_DEFINE(MOZ_NATIVE_CASTING)
+fi
+
+AC_SUBST(MOZ_NATIVE_CASTING)
 AC_SUBST(OMNIJAR_NAME)
 AC_SUBST(MOZ_OMNIJAR)
 AC_SUBST(MOZ_PACKAGER_FORMAT)
 
 dnl ========================================================
 dnl = Define default location for MOZILLA_FIVE_HOME
 dnl ========================================================
 MOZ_ARG_WITH_STRING(default-mozilla-five-home,
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -87,17 +87,17 @@
 #if !defined(MOZILLA_OFFICIAL) || defined(NIGHTLY_BUILD) && defined(MOZ_DEBUG)
                  android:debuggable="true">
 #else
                  android:debuggable="false">
 #endif
 
         <meta-data android:name="com.sec.android.support.multiwindow" android:value="true"/>
 
-#ifdef MOZ_NATIVE_DEVICES
+#ifdef MOZ_NATIVE_CASTING
         <!-- This resources comes from Google Play Services. Required for casting support. -->
         <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
 #endif
 
         <!-- If the windowSoftInputMode adjust* flag changes below, the
              setSoftInputMode call in BrowserSearch#onStop must also be updated. -->
         <activity android:name="org.mozilla.gecko.BrowserApp"
                   android:label="@string/moz_app_displayname"
--- a/mobile/android/base/AppConstants.java.in
+++ b/mobile/android/base/AppConstants.java.in
@@ -159,17 +159,17 @@ public class AppConstants {
     public static final boolean DEBUG_BUILD =
 #ifdef MOZ_DEBUG
     true;
 #else
     false;
 #endif
 
     public static final boolean MOZ_MEDIA_PLAYER =
-#ifdef MOZ_NATIVE_DEVICES
+#ifdef MOZ_NATIVE_CASTING
     true;
 #else
     false;
 #endif
 
     // Official corresponds, roughly, to whether this build is performed on
     // Mozilla's continuous integration infrastructure. You should disable
     // developer-only functionality when this flag is set.
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -545,29 +545,16 @@ public class BrowserApp extends GeckoApp
             "Reader:Removed",
             "Reader:Share",
             "Settings:Show",
             "Telemetry:Gather",
             "Updater:Launch");
 
         Distribution.init(this);
 
-        // Shipping Native casting is optional and dependent on whether you've downloaded the support
-        // and google play libraries
-        if (AppConstants.MOZ_MEDIA_PLAYER) {
-            try {
-                Class<?> mediaManagerClass = Class.forName("org.mozilla.gecko.MediaPlayerManager");
-                Method init = mediaManagerClass.getMethod("init", Context.class);
-                init.invoke(null, this);
-            } catch(Exception ex) {
-                // Ignore failures
-                Log.i(LOGTAG, "No native casting support", ex);
-            }
-        }
-
         JavaAddonManager.getInstance().init(getApplicationContext());
         mSharedPreferencesHelper = new SharedPreferencesHelper(getApplicationContext());
         mOrderedBroadcastHelper = new OrderedBroadcastHelper(getApplicationContext());
         mBrowserHealthReporter = new BrowserHealthReporter();
 
         if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
             NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
             if (nfc != null) {
--- a/mobile/android/base/ChromeCast.java
+++ b/mobile/android/base/ChromeCast.java
@@ -14,20 +14,22 @@ import com.google.android.gms.cast.Cast;
 import com.google.android.gms.cast.Cast.ApplicationConnectionResult;
 import com.google.android.gms.cast.CastDevice;
 import com.google.android.gms.cast.CastMediaControlIntent;
 import com.google.android.gms.cast.MediaInfo;
 import com.google.android.gms.cast.MediaMetadata;
 import com.google.android.gms.cast.MediaStatus;
 import com.google.android.gms.cast.RemoteMediaPlayer;
 import com.google.android.gms.cast.RemoteMediaPlayer.MediaChannelResult;
+import com.google.android.gms.common.ConnectionResult;
 import com.google.android.gms.common.api.GoogleApiClient;
 import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
 import com.google.android.gms.common.api.ResultCallback;
 import com.google.android.gms.common.api.Status;
+import com.google.android.gms.common.GooglePlayServicesUtil;
 
 import android.content.Context;
 import android.os.Bundle;
 import android.support.v7.media.MediaRouter.RouteInfo;
 import android.util.Log;
 
 /* Implementation of GeckoMediaPlayer for talking to ChromeCast devices */
 class ChromeCast implements GeckoMediaPlayer {
@@ -126,16 +128,21 @@ class ChromeCast implements GeckoMediaPl
                 debug("Problem opening media during loading", e);
             }
 
             callback.sendError(null);
         }
     }
 
     public ChromeCast(Context context, RouteInfo route) {
+        int status =  GooglePlayServicesUtil.isGooglePlayServicesAvailable(context);
+        if (status != ConnectionResult.SUCCESS) {
+            throw new IllegalStateException("Play services are required for Chromecast support (go status code " + status + ")");
+        }
+
         this.context = context;
         this.route = route;
     }
 
     // This dumps everything we can find about the device into JSON. This will hopefully make it
     // easier to filter out duplicate devices from different sources in js.
     public JSONObject toJSON() {
         final JSONObject obj = new JSONObject();
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -62,17 +62,17 @@ GARBAGE_DIRS += classes db jars res sync
 JAVA_BOOTCLASSPATH = \
     $(ANDROID_SDK)/android.jar \
     $(ANDROID_COMPAT_LIB) \
     $(NULL)
 
 JAVA_BOOTCLASSPATH := $(subst $(NULL) ,:,$(strip $(JAVA_BOOTCLASSPATH)))
 
 # If native devices are enabled, add Google Play Services and some of the v7 compat libraries
-ifdef MOZ_NATIVE_DEVICES
+ifdef MOZ_NATIVE_CASTING
     JAVA_CLASSPATH += \
         $(GOOGLE_PLAY_SERVICES_LIB) \
         $(ANDROID_MEDIAROUTER_LIB) \
         $(ANDROID_APPCOMPAT_LIB) \
         $(NULL)
 endif
 
 JAVA_CLASSPATH := $(subst $(NULL) ,:,$(strip $(JAVA_CLASSPATH)))
@@ -299,17 +299,17 @@ geckoview_resources.zip: $(all_resources
 
 generated/org/mozilla/gecko/R.java: .aapt.deps ;
 
 # If native devices are enabled, add Google Play Services, build their resources
 generated/android/support/v7/appcompat/R.java: .aapt.deps ;
 generated/android/support/v7/mediarouter/R.java: .aapt.deps ;
 generated/com/google/android/gms/R.java: .aapt.deps ;
 
-ifdef MOZ_NATIVE_DEVICES
+ifdef MOZ_NATIVE_CASTING
     extra_packages += android.support.v7.appcompat
     extra_res_dirs += $(ANDROID_APPCOMPAT_RES)
 
     extra_packages += android.support.v7.mediarouter
     extra_res_dirs += $(ANDROID_MEDIAROUTER_RES)
 
     extra_packages += com.google.android.gms
     extra_res_dirs += $(GOOGLE_PLAY_SERVICES_RES)
--- a/mobile/android/base/MediaPlayerManager.java
+++ b/mobile/android/base/MediaPlayerManager.java
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.mozglue.JNITarget;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
 import org.json.JSONException;
 
 import android.content.Context;
@@ -31,17 +32,17 @@ interface GeckoMediaPlayer {
     public void start(EventCallback callback);
     public void end(EventCallback callback);
 }
 
 /* Manages a list of GeckoMediaPlayers methods (i.e. Chromecast/Miracast). Routes messages
  * from Gecko to the correct caster based on the id of the display
  */
 class MediaPlayerManager implements NativeEventListener,
-                                      GeckoAppShell.AppStateListener {
+                                    GeckoAppShell.AppStateListener {
     private static final String LOGTAG = "GeckoMediaPlayerManager";
 
     private static final boolean SHOW_DEBUG = false;
     // Simplified debugging interfaces
     private static void debug(String msg, Exception e) {
         if (SHOW_DEBUG) {
             Log.e(LOGTAG, msg, e);
         }
@@ -53,16 +54,17 @@ class MediaPlayerManager implements Nati
         }
     }
 
     private final Context context;
     private final MediaRouter mediaRouter;
     private final HashMap<String, GeckoMediaPlayer> displays = new HashMap<String, GeckoMediaPlayer>();
     private static MediaPlayerManager instance;
 
+    @JNITarget
     public static void init(Context context) {
         if (instance != null) {
             debug("MediaPlayerManager initialized twice");
         }
 
         instance = new MediaPlayerManager(context);
     }
 
@@ -79,16 +81,17 @@ class MediaPlayerManager implements Nati
                                                                         "MediaPlayer:Start",
                                                                         "MediaPlayer:Stop",
                                                                         "MediaPlayer:Play",
                                                                         "MediaPlayer:Pause",
                                                                         "MediaPlayer:Get",
                                                                         "MediaPlayer:End");
     }
 
+    @JNITarget
     public static void onDestroy() {
         if (instance == null) {
             return;
         }
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener(instance, "MediaPlayer:Load",
                                                                               "MediaPlayer:Start",
                                                                               "MediaPlayer:Stop",
--- a/mobile/android/base/TelemetryContract.java
+++ b/mobile/android/base/TelemetryContract.java
@@ -180,19 +180,16 @@ public interface TelemetryContract {
         AWESOMESCREEN("awesomescreen.1"),
 
         // Started the very first time we believe the application has been launched.
         FIRSTRUN("firstrun.1"),
 
         // Awesomescreen frecency search is active.
         FRECENCY("frecency.1"),
 
-        // Started when a user enters about:home.
-        HOME("home.1"),
-
         // Started when a user enters a given home panel.
         // Session name is dynamic, encoded as "homepanel.1:<panel_id>"
         HOME_PANEL("homepanel.1"),
 
         // Started when a Reader viewer becomes active in the foreground.
         // Note: Only used in JavaScript for now, but here for completeness.
         READER("reader.1"),
 
--- a/mobile/android/base/home/HomePager.java
+++ b/mobile/android/base/home/HomePager.java
@@ -226,30 +226,28 @@ public class HomePager extends ViewPager
             });
 
             ViewHelper.setAlpha(this, 0.0f);
 
             animator.attach(this,
                             PropertyAnimator.Property.ALPHA,
                             1.0f);
         }
-        Telemetry.startUISession(TelemetryContract.Session.HOME);
     }
 
     /**
      * Removes all child fragments to free memory.
      */
     public void unload() {
         mVisible = false;
         setAdapter(null);
         mLoadState = LoadState.UNLOADED;
 
         // Stop UI Telemetry sessions.
         stopCurrentPanelTelemetrySession();
-        Telemetry.stopUISession(TelemetryContract.Session.HOME);
     }
 
     /**
      * Determines whether the pager is visible.
      *
      * Unlike getVisibility(), this method does not need to be called on the UI
      * thread.
      *
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -12,17 +12,17 @@ include('android-services.mozbuild')
 thirdparty_source_dir = TOPSRCDIR + '/mobile/android/thirdparty/'
 
 resjar = add_java_jar('gecko-R')
 resjar.sources = []
 resjar.generated_sources += [
     'org/mozilla/gecko/R.java',
 ]
 
-if CONFIG['MOZ_NATIVE_DEVICES']:
+if CONFIG['MOZ_NATIVE_CASTING']:
     resjar.generated_sources += ['com/google/android/gms/R.java']
     resjar.generated_sources += ['android/support/v7/appcompat/R.java']
     resjar.generated_sources += ['android/support/v7/mediarouter/R.java']
 
 resjar.javac_flags += ['-Xlint:all']
 
 mgjar = add_java_jar('gecko-mozglue')
 mgjar.sources += [
@@ -468,17 +468,17 @@ gbjar.extra_jars = [
     'gecko-R.jar',
     'gecko-mozglue.jar',
     'gecko-util.jar',
     'squareup-picasso.jar',
     'sync-thirdparty.jar',
     'websockets.jar',
 ]
 
-if CONFIG['MOZ_NATIVE_DEVICES']:
+if CONFIG['MOZ_NATIVE_CASTING']:
     gbjar.extra_jars += [CONFIG['ANDROID_APPCOMPAT_LIB']]
     gbjar.extra_jars += [CONFIG['ANDROID_MEDIAROUTER_LIB']]
     gbjar.extra_jars += [CONFIG['GOOGLE_PLAY_SERVICES_LIB']]
     gbjar.sources += ['ChromeCast.java']
     gbjar.sources += ['MediaPlayerManager.java']
 
 gbjar.javac_flags += ['-Xlint:all,-deprecation,-fallthrough']
 
@@ -525,17 +525,17 @@ ANDROID_RES_DIRS += [
 ]
 
 ANDROID_GENERATED_RESFILES += [
     'res/raw/suggestedsites.json',
     'res/values/strings.xml',
 ]
 
 for var in ('MOZ_ANDROID_ANR_REPORTER', 'MOZ_LINKER_EXTRACT', 'MOZILLA_OFFICIAL', 'MOZ_DEBUG',
-            'MOZ_ANDROID_SEARCH_ACTIVITY', 'MOZ_NATIVE_DEVICES'):
+            'MOZ_ANDROID_SEARCH_ACTIVITY', 'MOZ_NATIVE_CASTING'):
     if CONFIG[var]:
         DEFINES[var] = 1
 
 for var in ('MOZ_UPDATER', 'MOZ_PKG_SPECIAL'):
     if CONFIG[var]:
         DEFINES[var] = CONFIG[var]
 
 for var in ('ANDROID_PACKAGE_NAME', 'ANDROID_CPU_ARCH', 'CPU_ARCH',
@@ -608,17 +608,17 @@ main.libs = TOPOBJDIR + '/dist/' + CONFI
 main.res = None
 
 cpe = main.add_classpathentry('src', SRCDIR,
     dstdir='src/org/mozilla/gecko',
     exclude_patterns=['org/mozilla/gecko/tests/**',
         'org/mozilla/gecko/resources/**'])
 if not CONFIG['MOZ_CRASHREPORTER']:
     cpe.exclude_patterns += ['org/mozilla/gecko/CrashReporter.java']
-if not CONFIG['MOZ_NATIVE_DEVICES']:
+if not CONFIG['MOZ_NATIVE_CASTING']:
     cpe.exclude_patterns += ['org/mozilla/gecko/ChromeCast.java']
     cpe.exclude_patterns += ['org/mozilla/gecko/MediaPlayerManager.java']
 main.add_classpathentry('generated', OBJDIR + '/generated',
     dstdir='generated')
 main.add_classpathentry('thirdparty', TOPSRCDIR + '/mobile/android/thirdparty',
     dstdir='thirdparty',
     ignore_warnings=True)
 
--- a/mobile/android/chrome/content/aboutAddons.js
+++ b/mobile/android/chrome/content/aboutAddons.js
@@ -325,44 +325,47 @@ var Addons = {
 
     let box = document.querySelector("#addons-details > .addon-item .options-box");
     box.innerHTML = "";
 
     // Retrieve the extensions preferences
     try {
       let optionsURL = aListItem.getAttribute("optionsURL");
       let xhr = new XMLHttpRequest();
-      xhr.open("GET", optionsURL, false);
-      xhr.send();
-      if (xhr.responseXML) {
-        // Only allow <setting> for now
-        let settings = xhr.responseXML.querySelectorAll(":root > setting");
-        if (settings.length > 0) {
-          for (let i = 0; i < settings.length; i++) {
-            var setting = settings[i];
-            var desc = stripTextNodes(setting).trim();
-            if (!setting.hasAttribute("desc"))
-              setting.setAttribute("desc", desc);
-            box.appendChild(setting);
+      xhr.open("GET", optionsURL, true);
+      xhr.onload = function(e) {
+        if (xhr.responseXML) {
+          // Only allow <setting> for now
+          let settings = xhr.responseXML.querySelectorAll(":root > setting");
+          if (settings.length > 0) {
+            for (let i = 0; i < settings.length; i++) {
+              var setting = settings[i];
+              var desc = stripTextNodes(setting).trim();
+              if (!setting.hasAttribute("desc")) {
+                setting.setAttribute("desc", desc);
+              }
+              box.appendChild(setting);
+            }
+            // Send an event so add-ons can prepopulate any non-preference based
+            // settings
+            let event = document.createEvent("Events");
+            event.initEvent("AddonOptionsLoad", true, false);
+            window.dispatchEvent(event);
+  
+            // Also send a notification to match the behavior of desktop Firefox
+            let id = aListItem.getAttribute("addonID");
+            Services.obs.notifyObservers(document, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, id);
+          } else {
+            // No options, so hide the header and reset the list item
+            detailItem.setAttribute("optionsURL", "");
+            aListItem.setAttribute("optionsURL", "");
           }
-          // Send an event so add-ons can prepopulate any non-preference based
-          // settings
-          let event = document.createEvent("Events");
-          event.initEvent("AddonOptionsLoad", true, false);
-          window.dispatchEvent(event);
-  
-          // Also send a notification to match the behavior of desktop Firefox
-          let id = aListItem.getAttribute("addonID");
-          Services.obs.notifyObservers(document, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, id);
-        } else {
-          // No options, so hide the header and reset the list item
-          detailItem.setAttribute("optionsURL", "");
-          aListItem.setAttribute("optionsURL", "");
         }
       }
+      xhr.send(null);
     } catch (e) { }
 
     let list = document.querySelector("#addons-list");
     list.style.display = "none";
     let details = document.querySelector("#addons-details");
     details.style.display = "block";
   },
 
--- a/mobile/android/config/mozconfigs/common
+++ b/mobile/android/config/mozconfigs/common
@@ -27,16 +27,17 @@ else
   ac_add_options --with-android-ndk="/tools/android-ndk-$ANDROID_NDK_VERSION_32BIT"
   ac_add_options --with-android-sdk="/tools/android-sdk-r$ANDROID_SDK_VERSION/platforms/android-$ANDROID_SDK_VERSION"
 fi
 
 ac_add_options --with-android-gnu-compiler-version=4.7
 ac_add_options --with-android-version=9
 ac_add_options --with-system-zlib
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --disable-native-casting # Disable native casting support until the builders are updated
 
 # Treat warnings as errors in directories with FAIL_ON_WARNINGS.
 ac_add_options --enable-warnings-as-errors
 
 ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-api.key
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
--- a/mobile/android/confvars.sh
+++ b/mobile/android/confvars.sh
@@ -64,13 +64,10 @@ MOZ_DATA_REPORTING=1
 fi
 
 # Enable runtime locale switching.
 MOZ_LOCALE_SWITCHER=1
 
 # Enable second screen and casting support for external devices.
 MOZ_DEVICES=1
 
-# Enable second screen using native Android libraries
-MOZ_NATIVE_DEVICES=
-
 # Don't enable the Search Activity.
 # MOZ_ANDROID_SEARCH_ACTIVITY=1
--- a/mobile/android/tests/background/junit3/Makefile.in
+++ b/mobile/android/tests/background/junit3/Makefile.in
@@ -1,36 +1,35 @@
 # 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/.
 
 ANDROID_APK_NAME := background-junit3-debug
 
+ANDROID_EXTRA_JARS := \
+  background-junit3.jar \
+  $(NULL)
+
 PP_TARGETS        += manifest
 manifest          := $(srcdir)/AndroidManifest.xml.in
 manifest_TARGET   := AndroidManifest.xml
 manifest_FLAGS    += \
   -DANDROID_BACKGROUND_TARGET_PACKAGE_NAME='$(ANDROID_PACKAGE_NAME)' \
   -DANDROID_BACKGROUND_APP_DISPLAYNAME='$(MOZ_APP_DISPLAYNAME) Background Tests' \
   -DMOZ_ANDROID_SHARED_ID='$(ANDROID_PACKAGE_NAME).sharedID' \
   -DMOZ_ANDROID_SHARED_ACCOUNT_TYPE='$(ANDROID_PACKAGE_NAME)_sync' \
   $(NULL)
 ANDROID_MANIFEST_FILE := $(CURDIR)/AndroidManifest.xml
 
 GARBAGE += AndroidManifest.xml
 
-include $(srcdir)/android-services-files.mk
-
-# BACKGROUND_TESTS_{JAVA,RES}_FILES are defined in android-services-files.mk.
-JAVAFILES := $(BACKGROUND_TESTS_JAVA_FILES)
-
 # The test APK needs to know the contents of the target APK while not
 # being linked against them.  This is a best effort to avoid getting
 # out of sync with base's build config.
 JARS_DIR := $(DEPTH)/mobile/android/base
-JAVA_BOOTCLASSPATH := $(JAVA_BOOTCLASSPATH):$(subst $(NULL) ,:,$(wildcard $(JARS_DIR)/*.jar))
+JAVA_BOOTCLASSPATH := $(ANDROID_SDK)/android.jar:$(subst $(NULL) ,:,$(wildcard $(JARS_DIR)/*.jar))
 # We also want to re-compile classes.dex when the associated base
 # content changes.
 classes.dex: $(wildcard $(JARS_DIR)/*.jar)
 
 tools:: $(ANDROID_APK_NAME).apk
 
 include $(topsrcdir)/config/rules.mk
deleted file mode 100644
--- a/mobile/android/tests/background/junit3/android-services-files.mk
+++ /dev/null
@@ -1,110 +0,0 @@
-# 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/.
-
-# These files are managed in the android-sync repo. Do not modify directly, or your changes will be lost.
-BACKGROUND_TESTS_JAVA_FILES := \
-  src/announcements/TestAnnouncementsBroadcastService.java \
-  src/common/TestAndroidLogWriters.java \
-  src/common/TestBrowserContractHelpers.java \
-  src/common/TestDateUtils.java \
-  src/common/TestUtils.java \
-  src/common/TestWaitHelper.java \
-  src/db/AndroidBrowserRepositoryTestCase.java \
-  src/db/TestAndroidBrowserBookmarksRepository.java \
-  src/db/TestAndroidBrowserHistoryDataExtender.java \
-  src/db/TestAndroidBrowserHistoryRepository.java \
-  src/db/TestBookmarks.java \
-  src/db/TestCachedSQLiteOpenHelper.java \
-  src/db/TestClientsDatabase.java \
-  src/db/TestClientsDatabaseAccessor.java \
-  src/db/TestFennecTabsRepositorySession.java \
-  src/db/TestFennecTabsStorage.java \
-  src/db/TestFormHistoryRepositorySession.java \
-  src/db/TestPasswordsRepository.java \
-  src/fxa/authenticator/TestAccountPickler.java \
-  src/fxa/TestBrowserIDKeyPairGeneration.java \
-  src/fxa/TestFirefoxAccounts.java \
-  src/healthreport/MockDatabaseEnvironment.java \
-  src/healthreport/MockHealthReportDatabaseStorage.java \
-  src/healthreport/MockHealthReportSQLiteOpenHelper.java \
-  src/healthreport/MockProfileInformationCache.java \
-  src/healthreport/prune/TestHealthReportPruneService.java \
-  src/healthreport/prune/TestPrunePolicyDatabaseStorage.java \
-  src/healthreport/TestEnvironmentBuilder.java \
-  src/healthreport/TestEnvironmentV1HashAppender.java \
-  src/healthreport/TestHealthReportBroadcastService.java \
-  src/healthreport/TestHealthReportDatabaseStorage.java \
-  src/healthreport/TestHealthReportGenerator.java \
-  src/healthreport/TestHealthReportProvider.java \
-  src/healthreport/TestHealthReportSQLiteOpenHelper.java \
-  src/healthreport/TestProfileInformationCache.java \
-  src/healthreport/upload/TestAndroidSubmissionClient.java \
-  src/healthreport/upload/TestHealthReportUploadService.java \
-  src/helpers/AndroidSyncTestCase.java \
-  src/helpers/BackgroundServiceTestCase.java \
-  src/helpers/DBHelpers.java \
-  src/helpers/DBProviderTestCase.java \
-  src/helpers/FakeProfileTestCase.java \
-  src/nativecode/test/TestNativeCrypto.java \
-  src/sync/AndroidSyncTestCaseWithAccounts.java \
-  src/sync/helpers/BookmarkHelpers.java \
-  src/sync/helpers/DefaultBeginDelegate.java \
-  src/sync/helpers/DefaultCleanDelegate.java \
-  src/sync/helpers/DefaultDelegate.java \
-  src/sync/helpers/DefaultFetchDelegate.java \
-  src/sync/helpers/DefaultFinishDelegate.java \
-  src/sync/helpers/DefaultGuidsSinceDelegate.java \
-  src/sync/helpers/DefaultSessionCreationDelegate.java \
-  src/sync/helpers/DefaultStoreDelegate.java \
-  src/sync/helpers/ExpectBeginDelegate.java \
-  src/sync/helpers/ExpectBeginFailDelegate.java \
-  src/sync/helpers/ExpectFetchDelegate.java \
-  src/sync/helpers/ExpectFetchSinceDelegate.java \
-  src/sync/helpers/ExpectFinishDelegate.java \
-  src/sync/helpers/ExpectFinishFailDelegate.java \
-  src/sync/helpers/ExpectGuidsSinceDelegate.java \
-  src/sync/helpers/ExpectInvalidRequestFetchDelegate.java \
-  src/sync/helpers/ExpectInvalidTypeStoreDelegate.java \
-  src/sync/helpers/ExpectManyStoredDelegate.java \
-  src/sync/helpers/ExpectNoGUIDsSinceDelegate.java \
-  src/sync/helpers/ExpectStoreCompletedDelegate.java \
-  src/sync/helpers/ExpectStoredDelegate.java \
-  src/sync/helpers/HistoryHelpers.java \
-  src/sync/helpers/PasswordHelpers.java \
-  src/sync/helpers/SessionTestHelper.java \
-  src/sync/helpers/SimpleSuccessBeginDelegate.java \
-  src/sync/helpers/SimpleSuccessCreationDelegate.java \
-  src/sync/helpers/SimpleSuccessFetchDelegate.java \
-  src/sync/helpers/SimpleSuccessFinishDelegate.java \
-  src/sync/helpers/SimpleSuccessStoreDelegate.java \
-  src/sync/TestAccountPickler.java \
-  src/sync/TestClientsStage.java \
-  src/sync/TestConfigurationMigrator.java \
-  src/sync/TestResetting.java \
-  src/sync/TestSendTabData.java \
-  src/sync/TestStoreTracking.java \
-  src/sync/TestSyncAccounts.java \
-  src/sync/TestSyncAuthenticatorService.java \
-  src/sync/TestSyncConfiguration.java \
-  src/sync/TestTabsRecord.java \
-  src/sync/TestUpgradeRequired.java \
-  src/sync/TestWebURLFinder.java \
-  src/telemetry/TestTelemetryRecorder.java \
-  src/testhelpers/BaseMockServerSyncStage.java \
-  src/testhelpers/CommandHelpers.java \
-  src/testhelpers/DefaultGlobalSessionCallback.java \
-  src/testhelpers/JPakeNumGeneratorFixed.java \
-  src/testhelpers/MockAbstractNonRepositorySyncStage.java \
-  src/testhelpers/MockClientsDatabaseAccessor.java \
-  src/testhelpers/MockClientsDataDelegate.java \
-  src/testhelpers/MockGlobalSession.java \
-  src/testhelpers/MockPrefsGlobalSession.java \
-  src/testhelpers/MockRecord.java \
-  src/testhelpers/MockServerSyncStage.java \
-  src/testhelpers/MockSharedPreferences.java \
-  src/testhelpers/StubDelegate.java \
-  src/testhelpers/WaitHelper.java \
-  src/testhelpers/WBORepository.java \
-  $(NULL)
-
rename from mobile/android/tests/background/junit3/android-services.mozbuild
rename to mobile/android/tests/background/junit3/background_junit3_sources.mozbuild
--- a/mobile/android/tests/background/junit3/android-services.mozbuild
+++ b/mobile/android/tests/background/junit3/background_junit3_sources.mozbuild
@@ -1,6 +1,110 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+background_junit3_sources = [
+    'src/announcements/TestAnnouncementsBroadcastService.java',
+    'src/common/TestAndroidLogWriters.java',
+    'src/common/TestBrowserContractHelpers.java',
+    'src/common/TestDateUtils.java',
+    'src/common/TestUtils.java',
+    'src/common/TestWaitHelper.java',
+    'src/db/AndroidBrowserRepositoryTestCase.java',
+    'src/db/TestAndroidBrowserBookmarksRepository.java',
+    'src/db/TestAndroidBrowserHistoryDataExtender.java',
+    'src/db/TestAndroidBrowserHistoryRepository.java',
+    'src/db/TestBookmarks.java',
+    'src/db/TestCachedSQLiteOpenHelper.java',
+    'src/db/TestClientsDatabase.java',
+    'src/db/TestClientsDatabaseAccessor.java',
+    'src/db/TestFennecTabsRepositorySession.java',
+    'src/db/TestFennecTabsStorage.java',
+    'src/db/TestFormHistoryRepositorySession.java',
+    'src/db/TestPasswordsRepository.java',
+    'src/fxa/authenticator/TestAccountPickler.java',
+    'src/fxa/TestBrowserIDKeyPairGeneration.java',
+    'src/fxa/TestFirefoxAccounts.java',
+    'src/healthreport/MockDatabaseEnvironment.java',
+    'src/healthreport/MockHealthReportDatabaseStorage.java',
+    'src/healthreport/MockHealthReportSQLiteOpenHelper.java',
+    'src/healthreport/MockProfileInformationCache.java',
+    'src/healthreport/prune/TestHealthReportPruneService.java',
+    'src/healthreport/prune/TestPrunePolicyDatabaseStorage.java',
+    'src/healthreport/TestEnvironmentBuilder.java',
+    'src/healthreport/TestEnvironmentV1HashAppender.java',
+    'src/healthreport/TestHealthReportBroadcastService.java',
+    'src/healthreport/TestHealthReportDatabaseStorage.java',
+    'src/healthreport/TestHealthReportGenerator.java',
+    'src/healthreport/TestHealthReportProvider.java',
+    'src/healthreport/TestHealthReportSQLiteOpenHelper.java',
+    'src/healthreport/TestProfileInformationCache.java',
+    'src/healthreport/upload/TestAndroidSubmissionClient.java',
+    'src/healthreport/upload/TestHealthReportUploadService.java',
+    'src/helpers/AndroidSyncTestCase.java',
+    'src/helpers/BackgroundServiceTestCase.java',
+    'src/helpers/DBHelpers.java',
+    'src/helpers/DBProviderTestCase.java',
+    'src/helpers/FakeProfileTestCase.java',
+    'src/nativecode/test/TestNativeCrypto.java',
+    'src/sync/AndroidSyncTestCaseWithAccounts.java',
+    'src/sync/helpers/BookmarkHelpers.java',
+    'src/sync/helpers/DefaultBeginDelegate.java',
+    'src/sync/helpers/DefaultCleanDelegate.java',
+    'src/sync/helpers/DefaultDelegate.java',
+    'src/sync/helpers/DefaultFetchDelegate.java',
+    'src/sync/helpers/DefaultFinishDelegate.java',
+    'src/sync/helpers/DefaultGuidsSinceDelegate.java',
+    'src/sync/helpers/DefaultSessionCreationDelegate.java',
+    'src/sync/helpers/DefaultStoreDelegate.java',
+    'src/sync/helpers/ExpectBeginDelegate.java',
+    'src/sync/helpers/ExpectBeginFailDelegate.java',
+    'src/sync/helpers/ExpectFetchDelegate.java',
+    'src/sync/helpers/ExpectFetchSinceDelegate.java',
+    'src/sync/helpers/ExpectFinishDelegate.java',
+    'src/sync/helpers/ExpectFinishFailDelegate.java',
+    'src/sync/helpers/ExpectGuidsSinceDelegate.java',
+    'src/sync/helpers/ExpectInvalidRequestFetchDelegate.java',
+    'src/sync/helpers/ExpectInvalidTypeStoreDelegate.java',
+    'src/sync/helpers/ExpectManyStoredDelegate.java',
+    'src/sync/helpers/ExpectNoGUIDsSinceDelegate.java',
+    'src/sync/helpers/ExpectStoreCompletedDelegate.java',
+    'src/sync/helpers/ExpectStoredDelegate.java',
+    'src/sync/helpers/HistoryHelpers.java',
+    'src/sync/helpers/PasswordHelpers.java',
+    'src/sync/helpers/SessionTestHelper.java',
+    'src/sync/helpers/SimpleSuccessBeginDelegate.java',
+    'src/sync/helpers/SimpleSuccessCreationDelegate.java',
+    'src/sync/helpers/SimpleSuccessFetchDelegate.java',
+    'src/sync/helpers/SimpleSuccessFinishDelegate.java',
+    'src/sync/helpers/SimpleSuccessStoreDelegate.java',
+    'src/sync/TestAccountPickler.java',
+    'src/sync/TestClientsStage.java',
+    'src/sync/TestConfigurationMigrator.java',
+    'src/sync/TestResetting.java',
+    'src/sync/TestSendTabData.java',
+    'src/sync/TestStoreTracking.java',
+    'src/sync/TestSyncAccounts.java',
+    'src/sync/TestSyncAuthenticatorService.java',
+    'src/sync/TestSyncConfiguration.java',
+    'src/sync/TestTabsRecord.java',
+    'src/sync/TestUpgradeRequired.java',
+    'src/sync/TestWebURLFinder.java',
+    'src/telemetry/TestTelemetryRecorder.java',
+    'src/testhelpers/BaseMockServerSyncStage.java',
+    'src/testhelpers/CommandHelpers.java',
+    'src/testhelpers/DefaultGlobalSessionCallback.java',
+    'src/testhelpers/JPakeNumGeneratorFixed.java',
+    'src/testhelpers/MockAbstractNonRepositorySyncStage.java',
+    'src/testhelpers/MockClientsDatabaseAccessor.java',
+    'src/testhelpers/MockClientsDataDelegate.java',
+    'src/testhelpers/MockGlobalSession.java',
+    'src/testhelpers/MockPrefsGlobalSession.java',
+    'src/testhelpers/MockRecord.java',
+    'src/testhelpers/MockServerSyncStage.java',
+    'src/testhelpers/MockSharedPreferences.java',
+    'src/testhelpers/StubDelegate.java',
+    'src/testhelpers/WaitHelper.java',
+    'src/testhelpers/WBORepository.java',
+]
--- a/mobile/android/tests/background/junit3/moz.build
+++ b/mobile/android/tests/background/junit3/moz.build
@@ -1,17 +1,20 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 DEFINES['ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME']
 
-include('android-services.mozbuild')
+include('background_junit3_sources.mozbuild')
+
+jar = add_java_jar('background-junit3')
+jar.sources += background_junit3_sources
 
 main = add_android_eclipse_project('BackgroundInstrumentationTests', OBJDIR + '/AndroidManifest.xml')
 main.package_name = 'org.mozilla.gecko.background.tests'
 main.res = SRCDIR + '/res'
 main.recursive_make_targets += [
     OBJDIR + '/AndroidManifest.xml']
 main.referenced_projects += ['Fennec']
 
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -4167,24 +4167,54 @@ SearchService.prototype = {
   },
 
   _addObservers: function SRCH_SVC_addObservers() {
     Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC, false);
     Services.obs.addObserver(this, QUIT_APPLICATION_TOPIC, false);
     Services.prefs.addObserver(BROWSER_SEARCH_PREF + "defaultenginename", this, false);
     Services.prefs.addObserver(BROWSER_SEARCH_PREF + "selectedEngine", this, false);
 
-    AsyncShutdown.profileBeforeChange.addBlocker(
+    // The current stage of shutdown. Used to help analyze crash
+    // signatures in case of shutdown timeout.
+    let shutdownState = {
+      step: "Not started",
+      latestError: {
+        message: undefined,
+        stack: undefined
+      }
+    };
+    OS.File.profileBeforeChange.addBlocker(
       "Search service: shutting down",
-      () => Task.spawn(function () {
+      () => Task.spawn(function* () {
         if (this._batchTask) {
-          yield this._batchTask.finalize().then(null, Cu.reportError);
+          shutdownState.step = "Finalizing batched task";
+          try {
+            yield this._batchTask.finalize();
+            shutdownState.step = "Batched task finalized";
+          } catch (ex) {
+            shutdownState.step = "Batched task failed to finalize";
+
+            shutdownState.latestError.message = "" + ex;
+            if (ex && typeof ex == "object") {
+              shutdownState.latestError.stack = ex.stack || undefined;
+            }
+
+            // Ensure that error is reported and that it causes tests
+            // to fail.
+            Promise.reject(ex);
+          }
         }
+
+        shutdownState.step = "Finalizing engine metadata service";
         yield engineMetadataService.finalize();
-      }.bind(this))
+        shutdownState.step = "Engine metadata service finalized";
+
+      }.bind(this)),
+
+      () => shutdownState
     );
   },
 
   _removeObservers: function SRCH_SVC_removeObservers() {
     Services.obs.removeObserver(this, SEARCH_ENGINE_TOPIC);
     Services.obs.removeObserver(this, QUIT_APPLICATION_TOPIC);
     Services.prefs.removeObserver(BROWSER_SEARCH_PREF + "defaultenginename", this);
     Services.prefs.removeObserver(BROWSER_SEARCH_PREF + "selectedEngine", this);
--- a/toolkit/devtools/DevToolsUtils.js
+++ b/toolkit/devtools/DevToolsUtils.js
@@ -1,20 +1,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /* General utilities used throughout devtools. */
 
-// hasChrome is provided as a global by the loader. It is true if we are running
-// on the main thread, and false if we are running on a worker thread.
 var { Ci, Cu } = require("chrome");
 var Services = require("Services");
+var promise = require("promise");
+var { setTimeout } = require("Timer");
 
 /**
  * Turn the error |aError| into a string, without fail.
  */
 exports.safeErrorString = function safeErrorString(aError) {
   try {
     let errorString = aError.toString();
     if (typeof errorString == "string") {
--- a/toolkit/devtools/Loader.jsm
+++ b/toolkit/devtools/Loader.jsm
@@ -39,46 +39,52 @@ this.EXPORTED_SYMBOLS = ["DevToolsLoader
 /**
  * Providers are different strategies for loading the devtools.
  */
 
 let Timer = Cu.import("resource://gre/modules/Timer.jsm", {});
 
 let loaderGlobals = {
   isWorker: false,
-  Debugger: Debugger,
-  promise: promise,
   reportError: Cu.reportError,
-  setInterval: Timer.setInterval,
-  setTimeout: Timer.setTimeout,
-  clearInterval: Timer.clearInterval,
-  clearTimeout: Timer.clearTimeout,
-  xpcInspector: xpcInspector,
 
   btoa: btoa,
   console: console,
   _Iterator: Iterator,
-  ChromeWorker: ChromeWorker,
   loader: {
     lazyGetter: XPCOMUtils.defineLazyGetter.bind(XPCOMUtils),
     lazyImporter: XPCOMUtils.defineLazyModuleGetter.bind(XPCOMUtils),
     lazyServiceGetter: XPCOMUtils.defineLazyServiceGetter.bind(XPCOMUtils)
   },
 };
 
+let loaderModules = {
+  "Debugger": Debugger,
+  "Services": Object.create(Services),
+  "Timer": Object.create(Timer),
+  "toolkit/loader": loader,
+  "xpcInspector": xpcInspector,
+  "promise": promise,
+};
+try {
+  let { indexedDB } = Cu.Sandbox(this, {wantGlobalProperties:["indexedDB"]});
+  loaderModules.indexedDB = indexedDB;
+} catch(e) {
+  // On xpcshell, we can't instantiate indexedDB without crashing
+}
+
+let sharedGlobalBlacklist = ["sdk/indexed-db", "devtools/toolkit/qrcode/decoder/index"];
+
 // Used when the tools should be loaded from the Firefox package itself (the default)
 function BuiltinProvider() {}
 BuiltinProvider.prototype = {
   load: function() {
     this.loader = new loader.Loader({
       id: "fx-devtools",
-      modules: {
-        "Services": Object.create(Services),
-        "toolkit/loader": loader,
-      },
+      modules: loaderModules,
       paths: {
         // When you add a line to this mapping, don't forget to make a
         // corresponding addition to the SrcdirProvider mapping below as well.
         "": "resource://gre/modules/commonjs/",
         "main": "resource:///modules/devtools/main.js",
         "devtools": "resource:///modules/devtools",
         "devtools/toolkit": "resource://gre/modules/devtools",
         "devtools/server": "resource://gre/modules/devtools/server",
@@ -98,17 +104,19 @@ BuiltinProvider.prototype = {
         "acorn/util/walk": "resource://gre/modules/devtools/acorn/walk.js",
         "tern": "resource://gre/modules/devtools/tern",
         "source-map": "resource://gre/modules/devtools/SourceMap.jsm",
 
         // Allow access to xpcshell test items from the loader.
         "xpcshell-test": "resource://test"
       },
       globals: loaderGlobals,
-      invisibleToDebugger: this.invisibleToDebugger
+      invisibleToDebugger: this.invisibleToDebugger,
+      sharedGlobal: true,
+      sharedGlobalBlacklist: sharedGlobalBlacklist
     });
 
     return promise.resolve(undefined);
   },
 
   unload: function(reason) {
     loader.unload(this.loader, reason);
     delete this.loader;
@@ -148,20 +156,17 @@ SrcdirProvider.prototype = {
     let gcliURI = this.fileURI(OS.Path.join(toolkitDir, "gcli", "source", "lib", "gcli"));
     let projecteditorURI = this.fileURI(OS.Path.join(devtoolsDir, "projecteditor"));
     let acornURI = this.fileURI(OS.Path.join(toolkitDir, "acorn"));
     let acornWalkURI = OS.Path.join(acornURI, "walk.js");
     let ternURI = OS.Path.join(toolkitDir, "tern");
     let sourceMapURI = this.fileURI(OS.Path.join(toolkitDir), "SourceMap.jsm");
     this.loader = new loader.Loader({
       id: "fx-devtools",
-      modules: {
-        "Services": Object.create(Services),
-        "toolkit/loader": loader,
-      },
+      modules: loaderModules,
       paths: {
         "": "resource://gre/modules/commonjs/",
         "main": mainURI,
         "devtools": devtoolsURI,
         "devtools/toolkit": toolkitURI,
         "devtools/server": serverURI,
         "devtools/toolkit/webconsole": webconsoleURI,
         "devtools/app-actor-front": appActorURI,
@@ -176,17 +181,19 @@ SrcdirProvider.prototype = {
         "gcli": gcliURI,
         "projecteditor": projecteditorURI,
         "acorn": acornURI,
         "acorn/util/walk": acornWalkURI,
         "tern": ternURI,
         "source-map": sourceMapURI,
       },
       globals: loaderGlobals,
-      invisibleToDebugger: this.invisibleToDebugger
+      invisibleToDebugger: this.invisibleToDebugger,
+      sharedGlobal: true,
+      sharedGlobalBlacklist: sharedGlobalBlacklist
     });
 
     return this._writeManifest(devtoolsDir).then(null, Cu.reportError);
   },
 
   unload: function(reason) {
     loader.unload(this.loader, reason);
     delete this.loader;
--- a/toolkit/devtools/event-emitter.js
+++ b/toolkit/devtools/event-emitter.js
@@ -19,16 +19,17 @@
   }
 }).call(this, function (require, exports, module) {
 
 this.EventEmitter = function EventEmitter() {};
 module.exports = EventEmitter;
 
 const { Cu, components } = require("chrome");
 const Services = require("Services");
+const promise = require("promise");
 
 /**
  * Decorate an object with event emitter functionality.
  *
  * @param Object aObjectToDecorate
  *        Bind all public methods of EventEmitter to
  *        the aObjectToDecorate object.
  */
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -2,22 +2,26 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
-const { Cc, Ci, Cu, components } = require("chrome");
+const { Cc, Ci, Cu, components, ChromeWorker } = require("chrome");
 const { ActorPool } = require("devtools/server/actors/common");
 const { DebuggerServer } = require("devtools/server/main");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 const { dbg_assert, dumpn, update } = DevToolsUtils;
 const { SourceMapConsumer, SourceMapGenerator } = require("source-map");
+const promise = require("promise");
+const Debugger = require("Debugger");
+const xpcInspector = require("xpcInspector");
+
 const { defer, resolve, reject, all } = require("devtools/toolkit/deprecated-sync-thenables");
 const { CssLogic } = require("devtools/styleinspector/css-logic");
 
 DevToolsUtils.defineLazyGetter(this, "NetUtil", () => {
   return Cu.import("resource://gre/modules/NetUtil.jsm", {}).NetUtil;
 });
 
 let B2G_ID = "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}";
--- a/toolkit/devtools/server/actors/storage.js
+++ b/toolkit/devtools/server/actors/storage.js
@@ -1179,17 +1179,17 @@ StorageActors.createActor({
     if (/^(about:|chrome:)/.test(host)) {
       principal = Services.scriptSecurityManager.getSystemPrincipal();
     }
     else {
       let uri = Services.io.newURI(host, null, null);
       principal = Services.scriptSecurityManager.getCodebasePrincipal(uri);
     }
 
-    return indexedDB.openForPrincipal(principal, name);
+    return require("indexedDB").openForPrincipal(principal, name);
   },
 
   /**
    * Fetches and stores all the metadata information for the given database
    * `name` for the given `host`. The stored metadata information is of
    * `DatabaseMetadata` type.
    */
   getDBMetaData: function(host, name) {
--- a/toolkit/devtools/server/actors/tracer.js
+++ b/toolkit/devtools/server/actors/tracer.js
@@ -2,19 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cu } = require("chrome");
 const { DebuggerServer } = require("devtools/server/main");
 const { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
-
-Cu.import("resource://gre/modules/jsdebugger.jsm");
-addDebuggerToGlobal(this);
+const Debugger = require("Debugger");
 
 // TODO bug 943125: remove this polyfill and use Debugger.Frame.prototype.depth
 // once it is implemented.
 if (!Object.getOwnPropertyDescriptor(Debugger.Frame.prototype, "depth")) {
   Debugger.Frame.prototype._depth = null;
   Object.defineProperty(Debugger.Frame.prototype, "depth", {
     get: function () {
       if (this._depth === null) {
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
 const { DebuggerServer, ActorPool } = require("devtools/server/main");
 const { EnvironmentActor, LongStringActor, ObjectActor, ThreadActor } = require("devtools/server/actors/script");
 const { update } = require("devtools/toolkit/DevToolsUtils");
+const Debugger = require("Debugger");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyGetter(this, "NetworkMonitor", () => {
   return require("devtools/toolkit/webconsole/network-monitor")
          .NetworkMonitor;
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -14,16 +14,17 @@ let { Ci, Cc, CC, Cu, Cr } = require("ch
 let Services = require("Services");
 let { ActorPool } = require("devtools/server/actors/common");
 let { DebuggerTransport, LocalDebuggerTransport, ChildDebuggerTransport } =
   require("devtools/toolkit/transport/transport");
 let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 let { dumpn, dumpv, dbg_assert } = DevToolsUtils;
 let Services = require("Services");
 let EventEmitter = require("devtools/toolkit/event-emitter");
+let Debugger = require("Debugger");
 
 // Until all Debugger server code is converted to SDK modules,
 // imports Components.* alias from chrome module.
 var { Ci, Cc, CC, Cu, Cr } = require("chrome");
 // On B2G, `this` != Global scope, so `Ci` won't be binded on `this`
 // (i.e. this.Ci is undefined) Then later, when using loadSubScript,
 // Ci,... won't be defined for sub scripts.
 this.Ci = Ci;
@@ -838,16 +839,25 @@ var DebuggerServer = {
 EventEmitter.decorate(DebuggerServer);
 
 if (this.exports) {
   exports.DebuggerServer = DebuggerServer;
 }
 // Needed on B2G (See header note)
 this.DebuggerServer = DebuggerServer;
 
+// When using DebuggerServer.addActors, some symbols are expected to be in
+// the scope of the added actor even before the corresponding modules are
+// loaded, so let's explicitly bind the expected symbols here.
+let includes = ["Components", "Ci", "Cu", "require", "Services", "DebuggerServer",
+                "ActorPool", "DevToolsUtils"];
+includes.forEach(name => {
+  DebuggerServer[name] = this[name];
+});
+
 // Export ActorPool for requirers of main.js
 if (this.exports) {
   exports.ActorPool = ActorPool;
 }
 
 /**
  * Creates a DebuggerServerConnection.
  *
--- a/toolkit/devtools/server/tests/unit/testactors.js
+++ b/toolkit/devtools/server/tests/unit/testactors.js
@@ -1,15 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common");
 const { RootActor } = require("devtools/server/actors/root");
 const { ThreadActor } = require("devtools/server/actors/script");
 const { DebuggerServer } = require("devtools/server/main");
+const promise = require("promise");
 
 var gTestGlobals = [];
 DebuggerServer.addTestGlobal = function(aGlobal) {
   gTestGlobals.push(aGlobal);
 };
 
 // A mock tab list, for use by tests. This simply presents each global in
 // gTestGlobals as a tab, and the list is fixed: it never calls its
--- a/toolkit/devtools/tests/unit/test_invisible_loader.js
+++ b/toolkit/devtools/tests/unit/test_invisible_loader.js
@@ -16,33 +16,33 @@ function run_test() {
 }
 
 function visible_loader() {
   let loader = new DevToolsLoader();
   loader.invisibleToDebugger = false;
   loader.require("devtools/css-color");
 
   let dbg = new Debugger();
-  let sandbox = loader._provider.loader.sandboxes[COLOR_URI];
+  let sandbox = loader._provider.loader.sharedGlobalSandbox;
 
   try {
     dbg.addDebuggee(sandbox);
     do_check_true(true);
   } catch(e) {
     do_throw("debugger could not add visible value");
   }
 }
 
 function invisible_loader() {
   let loader = new DevToolsLoader();
   loader.invisibleToDebugger = true;
   loader.require("devtools/css-color");
 
   let dbg = new Debugger();
-  let sandbox = loader._provider.loader.sandboxes[COLOR_URI];
+  let sandbox = loader._provider.loader.sharedGlobalSandbox;
 
   try {
     dbg.addDebuggee(sandbox);
     do_throw("debugger added invisible value");
   } catch(e) {
     do_check_true(true);
   }
 }
--- a/toolkit/devtools/transport/packets.js
+++ b/toolkit/devtools/transport/packets.js
@@ -23,17 +23,17 @@
  *   * destroy()
  *     Called to clean up at the end of use
  */
 
 const { Cc, Ci, Cu } = require("chrome");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 const { dumpn, dumpv } = DevToolsUtils;
 const StreamUtils = require("devtools/toolkit/transport/stream-utils");
-const EventEmitter = require("devtools/toolkit/event-emitter");
+const promise = require("promise");
 
 DevToolsUtils.defineLazyGetter(this, "unicodeConverter", () => {
   const unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                            .createInstance(Ci.nsIScriptableUnicodeConverter);
   unicodeConverter.charset = "UTF-8";
   return unicodeConverter;
 });
 
--- a/toolkit/devtools/transport/stream-utils.js
+++ b/toolkit/devtools/transport/stream-utils.js
@@ -4,16 +4,17 @@
 
 "use strict";
 
 const { Ci, Cc, Cu, Cr, CC } = require("chrome");
 const Services = require("Services");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 const { dumpv } = DevToolsUtils;
 const EventEmitter = require("devtools/toolkit/event-emitter");
+const promise = require("promise");
 
 DevToolsUtils.defineLazyGetter(this, "IOUtil", () => {
   return Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil);
 });
 
 DevToolsUtils.defineLazyGetter(this, "ScriptableInputStream", () => {
   return CC("@mozilla.org/scriptableinputstream;1",
             "nsIScriptableInputStream", "init");
--- a/toolkit/devtools/transport/tests/unit/testactors.js
+++ b/toolkit/devtools/transport/tests/unit/testactors.js
@@ -1,16 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const { ActorPool, appendExtraActors, createExtraActors } =
   require("devtools/server/actors/common");
 const { RootActor } = require("devtools/server/actors/root");
 const { ThreadActor } = require("devtools/server/actors/script");
 const { DebuggerServer } = require("devtools/server/main");
+const promise = require("promise");
 
 var gTestGlobals = [];
 DebuggerServer.addTestGlobal = function(aGlobal) {
   gTestGlobals.push(aGlobal);
 };
 
 // A mock tab list, for use by tests. This simply presents each global in
 // gTestGlobals as a tab, and the list is fixed: it never calls its
--- a/toolkit/devtools/transport/transport.js
+++ b/toolkit/devtools/transport/transport.js
@@ -24,16 +24,17 @@
 
 const { Cc, Ci, Cr, Cu, CC } = require("chrome");
 const Services = require("Services");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 const { dumpn, dumpv } = DevToolsUtils;
 const StreamUtils = require("devtools/toolkit/transport/stream-utils");
 const { Packet, JSONPacket, BulkPacket } =
   require("devtools/toolkit/transport/packets");
+const promise = require("promise");
 
 DevToolsUtils.defineLazyGetter(this, "Pipe", () => {
   return CC("@mozilla.org/pipe;1", "nsIPipe", "init");
 });
 
 DevToolsUtils.defineLazyGetter(this, "ScriptableInputStream", () => {
   return CC("@mozilla.org/scriptableinputstream;1",
             "nsIScriptableInputStream", "init");
--- a/toolkit/devtools/worker-loader.js
+++ b/toolkit/devtools/worker-loader.js
@@ -306,28 +306,26 @@ if (typeof Components === "object") {
   const Timer = Cu.import("resource://gre/modules/Timer.jsm", {});
   const xpcInspector = Cc["@mozilla.org/jsinspector;1"].
                        getService(Ci.nsIJSInspector);
 
   this.worker = new WorkerDebuggerLoader({
     createSandbox: createSandbox,
     globals: {
       "isWorker": true,
-      "Debugger": Debugger,
-      "setInterval": Timer.setInterval,
-      "setTimeout": Timer.setTimeout,
-      "clearInterval": Timer.clearInterval,
-      "clearTimeout": Timer.clearTimeout,
-      "xpcInspector": xpcInspector,
       "reportError": Cu.reportError,
     },
     loadInSandbox: loadInSandbox,
     modules: {
       "Services": {},
       "chrome": chrome,
+      "promise": Promise,
+      "Debugger": Debugger,
+      "xpcInspector": xpcInspector,
+      "Timer": Object.create(Timer)
     },
     paths: {
       "": "resource://gre/modules/commonjs/",
       "devtools": "resource:///modules/devtools",
       "devtools/server": "resource://gre/modules/devtools/server",
       "devtools/toolkit": "resource://gre/modules/devtools",
       "source-map": "resource://gre/modules/devtools/source-map",
       "xpcshell-test": "resource://test",
--- a/toolkit/modules/AsyncShutdown.jsm
+++ b/toolkit/modules/AsyncShutdown.jsm
@@ -41,16 +41,18 @@
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/Services.jsm", this);
 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+  "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "gDebug",
   "@mozilla.org/xpcom/debug;1", "nsIDebug");
 Object.defineProperty(this, "gCrashReporter", {
   get: function() {
     delete this.gCrashReporter;
     try {
       let reporter = Cc["@mozilla.org/xre/app-info;1"].
             getService(Ci.nsICrashReporter);
@@ -436,25 +438,38 @@ function Barrier(name) {
       }
 
       // Determine the filename and line number of the caller.
       let leaf = Components.stack;
       let frame;
       for (frame = leaf; frame != null && frame.filename == leaf.filename; frame = frame.caller) {
         // Climb up the stack
       }
+      let filename = frame ? frame.filename : "?";
+      let lineNumber = frame ? frame.lineNumber : -1;
+
+      // Now build the rest of the stack as a string, using Task.jsm's rewriting
+      // to ensure that we do not lose information at each call to `Task.spawn`.
+      let frames = [];
+      while (frame != null) {
+        frames.push(frame.filename + ":" + frame.name + ":" + frame.lineNumber);
+        frame = frame.caller;
+      }
+      let stack = Task.Debugging.generateReadableStack(frames.join("\n")).split("\n");
+
       let set = this._conditions.get(condition);
       if (!set) {
         set = [];
         this._conditions.set(condition, set);
       }
       set.push({name: name,
                 fetchState: fetchState,
-                filename: frame ? frame.filename : "?",
-                lineNumber: frame ? frame.lineNumber : -1});
+                filename: filename,
+                lineNumber: lineNumber,
+                stack: stack});
     }.bind(this),
 
     /**
      * Remove the blocker for a condition.
      *
      * If several blockers have been registered for the same
      * condition, remove all these blockers. If no blocker has been
      * registered for this condition, this is a noop.
@@ -491,22 +506,23 @@ Barrier.prototype = Object.freeze({
   get state() {
     if (this._conditions) {
       return "Not started";
     }
     if (!this._monitors) {
       return "Complete";
     }
     let frozen = [];
-    for (let {name, isComplete, fetchState, filename, lineNumber} of this._monitors) {
+    for (let {name, isComplete, fetchState, stack, filename, lineNumber} of this._monitors) {
       if (!isComplete) {
         frozen.push({name: name,
                      state: safeGetState(fetchState),
                      filename: filename,
-                     lineNumber: lineNumber});
+                     lineNumber: lineNumber,
+                     stack: stack});
       }
     }
     return frozen;
   },
 
   /**
    * Wait until all currently registered blockers are complete.
    *
@@ -550,17 +566,17 @@ Barrier.prototype = Object.freeze({
 
     // Information to determine and report to the user which conditions
     // are not satisfied yet.
     this._monitors = [];
 
     for (let _condition of conditions.keys()) {
       for (let current of conditions.get(_condition)) {
         let condition = _condition; // Avoid capturing the wrong variable
-        let {name, fetchState, filename, lineNumber} = current;
+        let {name, fetchState, stack, filename, lineNumber} = current;
 
         // An indirection on top of condition, used to let clients
         // cancel a blocker through removeBlocker.
         let indirection = Promise.defer();
         this._indirections.set(condition, indirection);
 
         // Gather all completion conditions
 
@@ -580,16 +596,17 @@ Barrier.prototype = Object.freeze({
           // isn't going to be terribly interesting, but it will behave
           // as a promise.
           condition = Promise.resolve(condition);
 
           let monitor = {
             isComplete: false,
             name: name,
             fetchState: fetchState,
+            stack: stack,
             filename: filename,
             lineNumber: lineNumber
           };
 
 	  condition = condition.then(null, function onError(error) {
             let msg = "A completion condition encountered an error" +
               " while we were spinning the event loop." +
 	      " Condition: " + name +
--- a/toolkit/modules/PropertyListUtils.jsm
+++ b/toolkit/modules/PropertyListUtils.jsm
@@ -233,17 +233,17 @@ this.PropertyListUtils = Object.freeze({
 
 /**
  * Reader for binary-format property lists.
  *
  * @param aBuffer
  *        ArrayBuffer object from which the binary plist should be read.
  */
 function BinaryPropertyListReader(aBuffer) {
-  this._buffer = aBuffer;
+  this._dataView = new DataView(aBuffer);
 
   const JS_MAX_INT = Math.pow(2,53);
   this._JS_MAX_INT_SIGNED = ctypes.Int64(JS_MAX_INT);
   this._JS_MAX_INT_UNSIGNED = ctypes.UInt64(JS_MAX_INT);
   this._JS_MIN_INT = ctypes.Int64(-JS_MAX_INT);
 
   try {
     this._readTrailerInfo();
@@ -263,62 +263,46 @@ BinaryPropertyListReader.prototype = {
   canProcess: function BPLR_canProcess(aBuffer)
     [String.fromCharCode(c) for each (c in new Uint8Array(aBuffer, 0, 8))].
     join("") == "bplist00",
 
   get root() this._readObject(this._rootObjectIndex),
 
   _readTrailerInfo: function BPLR__readTrailer() {
     // The first 6 bytes of the 32-bytes trailer are unused
-    let trailerOffset = this._buffer.byteLength - 26;
+    let trailerOffset = this._dataView.byteLength - 26;
     [this._offsetTableIntegerSize, this._objectRefSize] =
       this._readUnsignedInts(trailerOffset, 1, 2);
 
     [this._numberOfObjects, this._rootObjectIndex, this._offsetTableOffset] =
       this._readUnsignedInts(trailerOffset + 2, 8, 3);
   },
 
   _readObjectsOffsets: function BPLR__readObjectsOffsets() {
     this._offsetTable = this._readUnsignedInts(this._offsetTableOffset,
                                                this._offsetTableIntegerSize,
                                                this._numberOfObjects);
   },
 
-  // TODO: This should be removed once DataView is implemented (Bug 575688).
-  _swapForBigEndian:
-  function BPLR__swapForBigEndian(aByteOffset, aIntSize, aNumberOfInts) {
-    let bytesCount = aIntSize * aNumberOfInts;
-    let bytes = new Uint8Array(this._buffer, aByteOffset, bytesCount);
-    let swapped = new Uint8Array(bytesCount);
-    for (let i = 0; i < aNumberOfInts; i++) {
-      for (let j = 0; j < aIntSize; j++) {
-        swapped[(i * aIntSize) + j] = bytes[(i * aIntSize) + (aIntSize - 1 - j)];
-      }
-    }
-    return swapped;
-  },
-
   _readSignedInt64: function BPLR__readSignedInt64(aByteOffset) {
-    let swapped = this._swapForBigEndian(aByteOffset, 8, 1);
-    let lo = new Uint32Array(swapped.buffer, 0, 1)[0];
-    let hi = new Int32Array(swapped.buffer, 4, 1)[0];
+    let lo = this._dataView.getUint32(aByteOffset + 4);
+    let hi = this._dataView.getInt32(aByteOffset);
     let int64 = ctypes.Int64.join(hi, lo);
     if (ctypes.Int64.compare(int64, this._JS_MAX_INT_SIGNED) == 1 ||
         ctypes.Int64.compare(int64, this._JS_MIN_INT) == -1)
       return PropertyListUtils.wrapInt64(int64.toString());
 
     return parseInt(int64.toString(), 10);
   },
 
   _readReal: function BPLR__readReal(aByteOffset, aRealSize) {
-    let swapped = this._swapForBigEndian(aByteOffset, aRealSize, 1);
     if (aRealSize == 4)
-      return new Float32Array(swapped.buffer, 0, 1)[0];
+      return this._dataView.getFloat32(aByteOffset);
     if (aRealSize == 8)
-      return new Float64Array(swapped.buffer, 0, 1)[0];
+      return this._dataView.getFloat64(aByteOffset);
 
     throw new Error("Unsupported real size: " + aRealSize);
   },
 
   OBJECT_TYPE_BITS: {
     SIMPLE:                  parseInt("0000", 2),
     INTEGER:                 parseInt("0001", 2),
     REAL:                    parseInt("0010", 2),
@@ -409,48 +393,49 @@ BinaryPropertyListReader.prototype = {
    *        numbers (±2^53) in the form of a String.
    * @return an array of integers (number primitive and/or Strings for large
    * numbers (see header)).
    * @throws if aBigIntAllowed is false and one of the integers in the array
    * cannot be represented by a primitive js number.
    */
   _readUnsignedInts:
   function BPLR__readUnsignedInts(aByteOffset, aIntSize, aLength, aBigIntAllowed) {
-    if (aIntSize == 1)
-      return new Uint8Array(this._buffer, aByteOffset, aLength);
-
-    // There are two reasons for the complexity you see here:
-    // (1) 64-bit integers - For which we use ctypes. When possible, the
-    //     number is converted back to js's default float-64 type.
-    // (2) The DataView object for ArrayBuffer, which takes care of swaping
-    //     bytes, is not yet implemented (bug 575688).
-    let swapped = this._swapForBigEndian(aByteOffset, aIntSize, aLength);
-    if (aIntSize == 2)
-      return new Uint16Array(swapped.buffer);
-    if (aIntSize == 4)
-      return new Uint32Array(swapped.buffer);
-    if (aIntSize == 8) {
-      let intsArray = [];
-      let lo_hi_view = new Uint32Array(swapped.buffer);
-      for (let i = 0; i < lo_hi_view.length; i += 2) {
-        let [lo, hi] = [lo_hi_view[i], lo_hi_view[i+1]];
+    let uints = [];
+    for (let offset = aByteOffset;
+         offset < aByteOffset + aIntSize * aLength;
+         offset += aIntSize) {
+      if (aIntSize == 1) {
+        uints.push(this._dataView.getUint8(offset));
+      }
+      else if (aIntSize == 2) {
+        uints.push(this._dataView.getUint16(offset));
+      }
+      else if (aIntSize == 4) {
+        uints.push(this._dataView.getUint32(offset));
+      }
+      else if (aIntSize == 8) {
+        let lo = this._dataView.getUint32(offset + 4);
+        let hi = this._dataView.getUint32(offset);
         let uint64 = ctypes.UInt64.join(hi, lo);
         if (ctypes.UInt64.compare(uint64, this._JS_MAX_INT_UNSIGNED) == 1) {
           if (aBigIntAllowed === true)
-            intsArray.push(PropertyListUtils.wrapInt64(uint64.toString()));
+            uints.push(PropertyListUtils.wrapInt64(uint64.toString()));
           else
             throw new Error("Integer too big to be read as float 64");
         }
         else {
-          intsArray.push(parseInt(uint64.toString(), 10));
+          uints.push(parseInt(uint64, 10));
         }
       }
-      return intsArray;
+      else {
+        throw new Error("Unsupported size: " + aIntSize);
+      }
     }
-    throw new Error("Unsupported size: " + aIntSize);
+
+    return uints;
   },
 
   /**
    * Reads from the buffer the data object-count and the offset at which the
    * first object starts.
    *
    * @param aObjectOffset
    *        the object's offset.
@@ -596,17 +581,17 @@ BinaryPropertyListReader.prototype = {
           throw new Error("Unexpected value");
 
         value = this._readDate(objOffset + 1);
         break;
       }
 
       case this.OBJECT_TYPE_BITS.DATA: {
         let [offset, bytesCount] = this._readDataOffsetAndCount(objOffset);
-        value = this._readUnsignedInts(offset, 1, bytesCount);
+        value = new Uint8Array(this._readUnsignedInts(offset, 1, bytesCount));
         break;
       }
 
       case this.OBJECT_TYPE_BITS.ASCII_STRING: {
         let [offset, charsCount] = this._readDataOffsetAndCount(objOffset);
         value = this._readString(offset, charsCount, false);
         break;
       }
--- a/toolkit/modules/tests/xpcshell/test_AsyncShutdown.js
+++ b/toolkit/modules/tests/xpcshell/test_AsyncShutdown.js
@@ -288,17 +288,19 @@ add_task(function* test_state() {
   let promiseDone = barrier.wait();
 
   // Now that we have called `wait()`, the state contains interesting things
   let state = barrier.state[0];
   do_print("State: " + JSON.stringify(barrier.state, null, "\t"));
   Assert.equal(state.filename, filename);
   Assert.equal(state.lineNumber, lineNumber + 1);
   Assert.equal(state.name, BLOCKER_NAME);
-  
+  Assert.ok(state.stack.some(x => x.contains("test_state")), "The stack contains the caller function's name");
+  Assert.ok(state.stack.some(x => x.contains(filename)), "The stack contains the calling file's name");
+
   deferred.resolve();
   yield promiseDone;
 });
 
 add_task(function*() {
   Services.prefs.clearUserPref("toolkit.asyncshutdown.testing");
 });
 
new file mode 100644
index 0000000000000000000000000000000000000000..fa08515836f986f26bef72f32002d6b28569a548
GIT binary patch
literal 99
zc%17D@N?(olHy`uVBq!ia0vp^96-#%!3HEZpRM}<q_jL;978y+CwFvpWidSZf1|*~
x#l@DH!^O?b?9r4K#uQhHNhi7*cJM{=Fv!WMy!DhlbPK49!PC{xWt~$(69C6-97g~E
new file mode 100644
index 0000000000000000000000000000000000000000..653039a3e6c335758d4da06c93b99b499551637a
GIT binary patch
literal 130
zc%17D@N?(olHy`uVBq!ia0vp^0zk~c!3HEhl+{lMQl6eJjv*T7r}k{*J)j`KY`g4j
zER)bp>xGLLKd!ewIrqK8ChuQ)oB!T-+r-S^(f2~)RJYfY3EG05yn=x$QzxHj=89gT
erk}HarVZok<nBnRj~(+FK&HC-xvX<aXaWHKqAb|}
--- a/toolkit/themes/osx/global/jar.mn
+++ b/toolkit/themes/osx/global/jar.mn
@@ -97,16 +97,18 @@ toolkit.jar:
   skin/classic/global/icons/blacklist_64.png                         (icons/blacklist_64.png)
   skin/classic/global/icons/chevron.png                              (icons/chevron.png)
   skin/classic/global/icons/chevron@2x.png                           (icons/chevron@2x.png)
   skin/classic/global/icons/checkbox.png                             (icons/checkbox.png)
   skin/classic/global/icons/checkbox@2x.png                          (icons/checkbox@2x.png)
   skin/classic/global/icons/close.png                                (icons/close.png)
   skin/classic/global/icons/close@2x.png                             (icons/close@2x.png)
   skin/classic/global/icons/find-arrows.png                          (icons/find-arrows.png)
+  skin/classic/global/icons/glyph-dropdown.png                       (icons/glyph-dropdown.png)
+  skin/classic/global/icons/glyph-dropdown@2x.png                    (icons/glyph-dropdown@2x.png)
   skin/classic/global/icons/information-16.png                       (icons/information-16.png)
   skin/classic/global/icons/information-24.png                       (icons/information-24.png)
   skin/classic/global/icons/information-32.png                       (icons/information-32.png)
   skin/classic/global/icons/information-64.png                       (icons/information-64.png)
   skin/classic/global/icons/information-large.png                    (icons/information-large.png)
   skin/classic/global/icons/loading_16.png                           (icons/loading_16.png)
   skin/classic/global/icons/menulist-dropmarker.png                  (icons/menulist-dropmarker.png)
   skin/classic/global/icons/notfound.png                             (icons/notfound.png)