Merge mozilla-central to inbound. a=merge CLOSED TREE
authorshindli <shindli@mozilla.com>
Tue, 09 Oct 2018 19:30:34 +0300
changeset 496042 e1c02731e238c9ddc4c03d624b388563953be819
parent 496041 1fd19353d27cc6f3062306a95a28471a8af273e7 (current diff)
parent 495977 77491723486d3fb28d0001d2c603145696560ce7 (diff)
child 496043 13430c50d08491283e924ac2598b95528aebe642
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
devtools/client/shared/TelemetryStopwatch.jsm
--- a/browser/components/extensions/parent/ext-geckoProfiler.js
+++ b/browser/components/extensions/parent/ext-geckoProfiler.js
@@ -86,16 +86,17 @@ const spawnProcess = async function(name
 
   if (stdin) {
     const encoder = new TextEncoder("utf-8");
     proc.stdin.write(encoder.encode(stdin));
     proc.stdin.close();
   }
 
   await readAllData(proc.stdout, processData);
+  return proc.exitCode;
 };
 
 const runCommandAndGetOutputAsString = async function(command, cmdArgs) {
   const opts = {
     command,
     arguments: cmdArgs,
     stderr: "pipe",
   };
@@ -108,17 +109,23 @@ const runCommandAndGetOutputAsString = a
 const getSymbolsFromNM = async function(path, arch) {
   const parser = new NMParser();
 
   const args = [path];
   if (Services.appinfo.OS === "Darwin") {
     args.unshift("-arch", arch);
   }
 
-  await spawnProcess("nm", args, data => parser.consume(data));
+  const exitCode = await spawnProcess("nm", args, data => parser.consume(data));
+  if (exitCode === 69) {
+    throw new ExtensionError("Symbolication requires the Xcode command line tools to be installed " +
+                             "and the license accepted. Please run the following from the command " +
+                             "line to accept the xcode license:\n\n" +
+                             "sudo xcodebuild -license");
+  }
   if (Services.appinfo.OS !== "Darwin") {
     // Darwin nm does not support the -D option.
     await spawnProcess("nm", ["-D", ...args], data => parser.consume(data));
   }
   return parser.finish();
 };
 
 const getEnvVarCaseInsensitive = function(env, name) {
@@ -405,22 +412,26 @@ this.geckoProfiler = class extends Exten
                     previouslySuccessfulDumpSymsPath = dumpSymsPath;
                     return result;
                   }
                   break;
               }
             } catch (e) {
               // Each of our options can go wrong for a variety of reasons, so on failure
               // we will try the next one.
+              // But we should still throw the explicit ExtensionErrors if there are any.
               // "localBreakpad" will fail if this is not a local build that's running from the object
               // directory or if the user hasn't run `mach buildsymbols` on it.
               // "nm" will fail if `nm` is not available.
               // "dump_syms.exe" will fail if this is not a local build that's running from the object
               // directory, or if dump_syms.exe doesn't exist in the object directory, or if
               // dump_syms.exe failed for other reasons.
+              if (e instanceof ExtensionError) {
+                throw e;
+              }
             }
           }
 
           throw new Error(`Ran out of options to get symbols from library ${debugName} ${breakpadId}.`);
         },
 
         onRunning: new EventManager({
           context,
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -183,16 +183,17 @@ class UrlbarInput {
     return this.inputField.getAttribute("focused") == "true";
   }
 
   _set_value(val) {
     val = this.trimValue(val);
 
     this.valueIsTyped = false;
     this.inputField.value = val;
+    this.formatValue();
 
     return val;
   }
 
   // Private methods below.
 
   _updateTextOverflow() {
     if (!this._overflowing) {
--- a/devtools/client/aboutdebugging-new/src/actions/debug-targets.js
+++ b/devtools/client/aboutdebugging-new/src/actions/debug-targets.js
@@ -31,42 +31,41 @@ const {
   REQUEST_WORKERS_START,
   REQUEST_WORKERS_SUCCESS,
   RUNTIMES,
 } = require("../constants");
 
 function inspectDebugTarget(type, id) {
   return async (_, getState) => {
     const runtime = getCurrentRuntime(getState().runtimes);
-    const runtimeType = runtime.type;
-    const client = runtime.client;
+    const { connection, type: runtimeType } = runtime;
 
     switch (type) {
       case DEBUG_TARGETS.TAB: {
         // Open tab debugger in new window.
-        if (runtime.type === RUNTIMES.NETWORK || runtime.type === RUNTIMES.USB) {
-          const { host, port } = client.transport.connectionSettings;
+        if (runtimeType === RUNTIMES.NETWORK || runtimeType === RUNTIMES.USB) {
+          const { host, port } = connection.transportDetails;
           window.open(`about:devtools-toolbox?type=tab&id=${id}` +
                       `&host=${host}&port=${port}`);
         } else if (runtimeType === RUNTIMES.THIS_FIREFOX) {
           window.open(`about:devtools-toolbox?type=tab&id=${id}`);
         }
         break;
       }
       case DEBUG_TARGETS.EXTENSION: {
         if (runtimeType === RUNTIMES.NETWORK) {
-          await debugRemoteAddon(id, client);
+          await debugRemoteAddon(id, connection.client);
         } else if (runtimeType === RUNTIMES.THIS_FIREFOX) {
           debugLocalAddon(id);
         }
         break;
       }
       case DEBUG_TARGETS.WORKER: {
         // Open worker toolbox in new window.
-        gDevToolsBrowser.openWorkerToolbox(client, id);
+        gDevToolsBrowser.openWorkerToolbox(connection.client, id);
         break;
       }
 
       default: {
         console.error("Failed to inspect the debug target of " +
                       `type: ${ type } id: ${ id }`);
       }
     }
--- a/devtools/client/aboutdebugging-new/src/actions/runtimes.js
+++ b/devtools/client/aboutdebugging-new/src/actions/runtimes.js
@@ -33,41 +33,43 @@ const {
   WATCH_RUNTIME_SUCCESS,
 } = require("../constants");
 
 async function createLocalClient() {
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
   const client = new DebuggerClient(DebuggerServer.connectPipe());
   await client.connect();
-  return client;
+  return { client };
 }
 
 async function createNetworkClient(host, port) {
-  const transport = await DebuggerClient.socketConnect({ host, port });
+  const transportDetails = { host, port };
+  const transport = await DebuggerClient.socketConnect(transportDetails);
   const client = new DebuggerClient(transport);
   await client.connect();
-  return client;
+  return { client, transportDetails };
 }
 
 async function createUSBClient(socketPath) {
   const port = await ADB.prepareTCPConnection(socketPath);
   return createNetworkClient("localhost", port);
 }
 
 async function createClientForRuntime(runtime) {
-  const { id, type } = runtime;
+  const { connectionParameters, type } = runtime;
 
   if (type === RUNTIMES.THIS_FIREFOX) {
     return createLocalClient();
   } else if (type === RUNTIMES.NETWORK) {
-    const [host, port] = id.split(":");
+    const { host, port } = connectionParameters;
     return createNetworkClient(host, port);
   } else if (type === RUNTIMES.USB) {
-    return createUSBClient(runtime.socketPath);
+    const { socketPath } = connectionParameters;
+    return createUSBClient(socketPath);
   }
 
   return null;
 }
 
 async function getRuntimeInfo(runtime, client) {
   const { model, type } = runtime;
   const deviceFront = await client.mainRoot.getFront("device");
@@ -86,40 +88,40 @@ async function getRuntimeInfo(runtime, c
   };
 }
 
 function connectRuntime(id) {
   return async (dispatch, getState) => {
     dispatch({ type: CONNECT_RUNTIME_START });
     try {
       const runtime = findRuntimeById(id, getState().runtimes);
-      const client = await createClientForRuntime(runtime);
+      const { client, transportDetails } = await createClientForRuntime(runtime);
       const info = await getRuntimeInfo(runtime, client);
+      const connection = { client, info, transportDetails };
 
       dispatch({
         type: CONNECT_RUNTIME_SUCCESS,
         runtime: {
           id,
-          client,
-          info,
+          connection,
           type: runtime.type,
         }
       });
     } catch (e) {
       dispatch({ type: CONNECT_RUNTIME_FAILURE, error: e.message });
     }
   };
 }
 
 function disconnectRuntime(id) {
   return async (dispatch, getState) => {
     dispatch({ type: DISCONNECT_RUNTIME_START });
     try {
       const runtime = findRuntimeById(id, getState().runtimes);
-      const client = runtime.client;
+      const client = runtime.connection.client;
 
       await client.close();
       DebuggerServer.destroy();
 
       dispatch({
         type: DISCONNECT_RUNTIME_SUCCESS,
         runtime: {
           id,
--- a/devtools/client/aboutdebugging-new/src/components/sidebar/Sidebar.js
+++ b/devtools/client/aboutdebugging-new/src/components/sidebar/Sidebar.js
@@ -51,34 +51,34 @@ class Sidebar extends PureComponent {
     ];
   }
 
   renderSidebarItems(icon, runtimes) {
     const { dispatch, selectedPage } = this.props;
 
     return runtimes.map(runtime => {
       const pageId = runtime.type + "-" + runtime.id;
-      const runtimeHasClient = !!runtime.client;
+      const runtimeHasConnection = !!runtime.connection;
 
       const connectComponent = DeviceSidebarItemAction({
-        connected: runtimeHasClient,
+        connected: runtimeHasConnection,
         dispatch,
         runtimeId: runtime.id,
       });
 
       return SidebarItem({
         connectComponent,
         id: pageId,
         dispatch,
         icon,
         isSelected: selectedPage === pageId,
         key: pageId,
         name: runtime.name,
         runtimeId: runtime.id,
-        selectable: runtimeHasClient,
+        selectable: runtimeHasConnection,
       });
     });
   }
 
   render() {
     const { dispatch, selectedPage } = this.props;
 
     return dom.aside(
--- a/devtools/client/aboutdebugging-new/src/middleware/debug-target-listener.js
+++ b/devtools/client/aboutdebugging-new/src/middleware/debug-target-listener.js
@@ -49,28 +49,28 @@ function debugTargetListenerMiddleware(s
 
   const onWorkersUpdated = () => {
     store.dispatch(Actions.requestWorkers());
   };
 
   return next => action => {
     switch (action.type) {
       case WATCH_RUNTIME_SUCCESS: {
-        const { client } = action.runtime;
+        const { client } = action.runtime.connection;
         client.addListener("tabListChanged", onTabsUpdated);
         AddonManager.addAddonListener(extensionsListener);
         client.addListener("workerListChanged", onWorkersUpdated);
         client.addListener("serviceWorkerRegistrationListChanged", onWorkersUpdated);
         client.addListener("processListChanged", onWorkersUpdated);
         client.addListener("registration-changed", onWorkersUpdated);
         client.addListener("push-subscription-modified", onWorkersUpdated);
         break;
       }
       case UNWATCH_RUNTIME_START: {
-        const { client } = action.runtime;
+        const { client } = action.runtime.connection;
         client.removeListener("tabListChanged", onTabsUpdated);
         AddonManager.removeAddonListener(extensionsListener);
         client.removeListener("workerListChanged", onWorkersUpdated);
         client.removeListener("serviceWorkerRegistrationListChanged", onWorkersUpdated);
         client.removeListener("processListChanged", onWorkersUpdated);
         client.removeListener("registration-changed", onWorkersUpdated);
         client.removeListener("push-subscription-modified", onWorkersUpdated);
         break;
--- a/devtools/client/aboutdebugging-new/src/modules/runtimes-state-helper.js
+++ b/devtools/client/aboutdebugging-new/src/modules/runtimes-state-helper.js
@@ -6,27 +6,33 @@
 
 function getCurrentRuntime(runtimesState) {
   const selectedRuntimeId = runtimesState.selectedRuntimeId;
   return findRuntimeById(selectedRuntimeId, runtimesState);
 }
 exports.getCurrentRuntime = getCurrentRuntime;
 
 function getCurrentClient(runtimesState) {
-  return getCurrentRuntime(runtimesState).client;
+  const connection = getCurrentConnection(runtimesState);
+  return connection ? connection.client : null;
 }
 exports.getCurrentClient = getCurrentClient;
 
 function getCurrentRuntimeInfo(runtimesState) {
-  const runtime = getCurrentRuntime(runtimesState);
-  return runtime ? runtime.info : null;
+  const connection = getCurrentConnection(runtimesState);
+  return connection ? connection.info : null;
 }
 exports.getCurrentRuntimeInfo = getCurrentRuntimeInfo;
 
 function findRuntimeById(id, runtimesState) {
   const allRuntimes = [
     ...runtimesState.networkRuntimes,
     ...runtimesState.thisFirefoxRuntimes,
     ...runtimesState.usbRuntimes,
   ];
   return allRuntimes.find(r => r.id === id);
 }
 exports.findRuntimeById = findRuntimeById;
+
+function getCurrentConnection(runtimesState) {
+  const runtime = getCurrentRuntime(runtimesState);
+  return runtime ? runtime.connection : null;
+}
--- a/devtools/client/aboutdebugging-new/src/reducers/runtimes-state.js
+++ b/devtools/client/aboutdebugging-new/src/reducers/runtimes-state.js
@@ -63,49 +63,51 @@ function _updateRuntimeById(runtimeId, u
     return r;
   });
   return Object.assign({}, state, { [key]: updatedRuntimes });
 }
 
 function runtimesReducer(state = RuntimesState(), action) {
   switch (action.type) {
     case CONNECT_RUNTIME_SUCCESS: {
-      const { id, client, info } = action.runtime;
-      return _updateRuntimeById(id, { client, info }, state);
+      const { id, connection } = action.runtime;
+      return _updateRuntimeById(id, { connection }, state);
     }
 
     case DISCONNECT_RUNTIME_SUCCESS: {
       const { id } = action.runtime;
-      return _updateRuntimeById(id, { client: null, info: null }, state);
+      return _updateRuntimeById(id, { connection: null }, state);
     }
 
     case NETWORK_LOCATIONS_UPDATED: {
       const { locations } = action;
       const networkRuntimes = locations.map(location => {
+        const [ host, port ] = location.split(":");
         return {
+          connectionParameters: { host, port },
           id: location,
           name: location,
           type: RUNTIMES.NETWORK,
         };
       });
       return Object.assign({}, state, { networkRuntimes });
     }
 
     case UNWATCH_RUNTIME_SUCCESS: {
       return Object.assign({}, state, { selectedRuntimeId: null });
     }
 
     case USB_RUNTIMES_UPDATED: {
       const { runtimes } = action;
       const usbRuntimes = runtimes.map(runtime => {
         return {
+          connectionParameters: { socketPath: runtime._socketPath },
           id: runtime.id,
           model: runtime._model,
           name: runtime.name,
-          socketPath: runtime._socketPath,
           type: RUNTIMES.USB,
         };
       });
       return Object.assign({}, state, { usbRuntimes });
     }
 
     case WATCH_RUNTIME_SUCCESS: {
       const { id } = action.runtime;
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -224,18 +224,20 @@ function TabTarget({ form, client, chrom
   // target actor that doesn't have any valid browsing context.
   // (Once FF63 is no longer supported, we can remove the `else` branch and only look
   // for the traits)
   if (this._form.traits && ("isBrowsingContext" in this._form.traits)) {
     this._isBrowsingContext = this._form.traits.isBrowsingContext;
   } else {
     // browser content toolbox's form will be of the form:
     //   server0.conn0.content-process0/contentProcessTarget7
+    // while xpcshell debugging will be:
+    //   server1.conn0.contentProcessTarget7
     const isContentProcessTarget =
-      this._form.actor.match(/conn\d+\.content-process\d+\/contentProcessTarget\d+/);
+      this._form.actor.match(/conn\d+\.(content-process\d+\/)?contentProcessTarget\d+/);
     this._isBrowsingContext = !this.isLegacyAddon && !isContentProcessTarget;
   }
 
   // Cache of already created targed-scoped fronts
   // [typeName:string => Front instance]
   this.fronts = new Map();
   // Temporary fix for bug #1493131 - inspector has a different life cycle
   // than most other fronts because it is closely related to the toolbox.
--- a/devtools/client/inspector/boxmodel/components/BoxModelMain.js
+++ b/devtools/client/inspector/boxmodel/components/BoxModelMain.js
@@ -275,16 +275,18 @@ class BoxModelMain extends PureComponent
       this.props.onHideBoxModelHighlighter();
     }
 
     this.props.onShowBoxModelHighlighter({
       region,
       showOnly: region,
       onlyRegionArea: true,
     });
+
+    event.preventDefault();
   }
 
   /**
    * Handle keyboard navigation and focus for box model layouts.
    *
    * Updates active layout on arrow key navigation
    * Focuses next layout's editboxes on enter key
    * Unfocuses current layout's editboxes when active layout changes
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -1449,17 +1449,16 @@ Inspector.prototype = {
       this._highlighters = null;
     }
 
     if (this._search) {
       this._search.destroy();
       this._search = null;
     }
 
-    const cssPropertiesDestroyer = this._cssProperties.front.destroy();
     const sidebarDestroyer = this.sidebar.destroy();
     const ruleViewSideBarDestroyer = this.ruleViewSideBar ?
       this.ruleViewSideBar.destroy() : null;
     const markupDestroyer = this._destroyMarkup();
 
     this.teardownToolbar();
 
     this.breadcrumbs.destroy();
@@ -1482,17 +1481,16 @@ Inspector.prototype = {
     this.resultsLength = null;
     this.searchBox = null;
     this.show3PaneTooltip = null;
     this.sidebar = null;
     this.store = null;
     this.telemetry = null;
 
     this._panelDestroyer = promise.all([
-      cssPropertiesDestroyer,
       markupDestroyer,
       sidebarDestroyer,
       ruleViewSideBarDestroyer
     ]);
 
     return this._panelDestroyer;
   },
 
--- a/devtools/client/inspector/shared/reflow-tracker.js
+++ b/devtools/client/inspector/shared/reflow-tracker.js
@@ -1,18 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set 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 { ReflowFront } = require("devtools/shared/fronts/reflow");
-
 /**
  * Simple utility class that listens to reflows on a given target if and only if a
  * listener is actively listening to reflows.
  *
  * @param {Object} target
  *        The current target (as in toolbox target)
  */
 function ReflowTracker(target) {
@@ -22,32 +20,30 @@ function ReflowTracker(target) {
   this.listeners = new Map();
 
   this.reflowFront = null;
 
   this.onReflow = this.onReflow.bind(this);
 }
 
 ReflowTracker.prototype = {
-
   destroy() {
     if (this.reflowFront) {
       this.stopTracking();
       this.reflowFront.destroy();
       this.reflowFront = null;
     }
 
     this.listeners.clear();
   },
 
   startTracking() {
     // Initialize reflow front if necessary.
     if (!this.reflowFront && this.target.form.reflowActor) {
-      const { client, form } = this.target;
-      this.reflowFront = ReflowFront(client, form);
+      this.reflowFront = this.target.getFront("reflow");
     }
 
     if (this.reflowFront) {
       this.reflowFront.on("reflows", this.onReflow);
       this.reflowFront.start();
     }
   },
 
--- a/devtools/client/netmonitor/src/connector/firefox-connector.js
+++ b/devtools/client/netmonitor/src/connector/firefox-connector.js
@@ -9,17 +9,16 @@ const { ACTIVITY_TYPE, EVENTS } = requir
 const FirefoxDataProvider = require("./firefox-data-provider");
 const { getDisplayedTimingMarker } = require("../selectors/index");
 
 // To be removed once FF60 is deprecated
 loader.lazyRequireGetter(this, "TimelineFront", "devtools/shared/fronts/timeline", true);
 
 // Network throttling
 loader.lazyRequireGetter(this, "throttlingProfiles", "devtools/client/shared/components/throttling/profiles");
-loader.lazyRequireGetter(this, "EmulationFront", "devtools/shared/fronts/emulation", true);
 
 /**
  * Connector to Firefox backend.
  */
 class FirefoxConnector {
   constructor() {
     // Public methods
     this.connect = this.connect.bind(this);
@@ -75,18 +74,17 @@ class FirefoxConnector {
     // these are used to pause/resume the connector.
     // Paused network panel should be automatically resumed when page
     // reload, so `will-navigate` listener needs to be there all the time.
     if (this.tabTarget) {
       this.tabTarget.on("will-navigate", this.willNavigate);
       this.tabTarget.on("navigate", this.navigate);
 
       // Initialize Emulation front for network throttling.
-      const { tab } = await this.tabTarget.client.getTab();
-      this.emulationFront = EmulationFront(this.tabTarget.client, tab);
+      this.emulationFront = this.tabTarget.getFront("emulation");
     }
 
     // Displaying cache events is only intended for the UI panel.
     if (this.actions) {
       this.displayCachedEvents();
     }
   }
 
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -455,16 +455,20 @@ ResponsiveUI.prototype = {
     }
 
     this.destroyed = true;
 
     return true;
   },
 
   async connectToServer() {
+    // The client being instantiated here is separate from the toolbox. It is being used
+    // separately and has a life cycle that doesn't correspond to the toolbox. As a
+    // result, it does not have a target, so we are not using `target.getFront` here. See
+    // also the implementation for about:debugging
     DebuggerServer.init();
     DebuggerServer.registerAllActors();
     this.client = new DebuggerClient(DebuggerServer.connectPipe());
     await this.client.connect();
     const { tab } = await this.client.getTab();
     this.emulationFront = EmulationFront(this.client, tab);
   },
 
deleted file mode 100644
--- a/devtools/client/shared/TelemetryStopwatch.jsm
+++ /dev/null
@@ -1,417 +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/. */
-
-"use strict";
-
-var EXPORTED_SYMBOLS = ["TelemetryStopwatch"];
-
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.defineModuleGetter(this, "Log",
-  "resource://gre/modules/Log.jsm");
-
-// Weak map does not allow using null objects as keys. These objects are used
-// as 'null' placeholders.
-const NULL_OBJECT = {};
-const NULL_KEY = {};
-
-/**
- * Timers is a variation of a Map used for storing information about running
- * Stopwatches. Timers has the following data structure:
- *
- * {
- *    "HISTOGRAM_NAME": WeakMap {
- *      Object || NULL_OBJECT: Map {
- *        "KEY" || NULL_KEY: startTime
- *        ...
- *      }
- *      ...
- *    }
- *    ...
- * }
- *
- *
- * @example
- * // Stores current time for a keyed histogram "PLAYING_WITH_CUTE_ANIMALS".
- * Timers.put("PLAYING_WITH_CUTE_ANIMALS", null, "CATS", Date.now());
- *
- * @example
- * // Returns information about a simple Stopwatch.
- * let startTime = Timers.get("PLAYING_WITH_CUTE_ANIMALS", null, "CATS");
- */
-const Timers = {
-  _timers: new Map(),
-
-  _validTypes(histogram, obj, key) {
-    const nonEmptyString = value => {
-      return typeof value === "string" && value !== "" && value.length > 0;
-    };
-    return nonEmptyString(histogram) &&
-            typeof obj == "object" &&
-           (key === NULL_KEY || nonEmptyString(key));
-  },
-
-  get(histogram, obj, key) {
-    key = key === null ? NULL_KEY : key;
-    obj = obj || NULL_OBJECT;
-
-    if (!this.has(histogram, obj, key)) {
-      return null;
-    }
-
-    return this._timers.get(histogram).get(obj).get(key);
-  },
-
-  put(histogram, obj, key, startTime) {
-    key = key === null ? NULL_KEY : key;
-    obj = obj || NULL_OBJECT;
-
-    if (!this._validTypes(histogram, obj, key)) {
-      return false;
-    }
-
-    const objectMap = this._timers.get(histogram) || new WeakMap();
-    const keyedInfo = objectMap.get(obj) || new Map();
-    keyedInfo.set(key, startTime);
-    objectMap.set(obj, keyedInfo);
-    this._timers.set(histogram, objectMap);
-    return true;
-  },
-
-  has(histogram, obj, key) {
-    key = key === null ? NULL_KEY : key;
-    obj = obj || NULL_OBJECT;
-
-    return this._timers.has(histogram) &&
-      this._timers.get(histogram).has(obj) &&
-      this._timers.get(histogram).get(obj).has(key);
-  },
-
-  delete(histogram, obj, key) {
-    key = key === null ? NULL_KEY : key;
-    obj = obj || NULL_OBJECT;
-
-    if (!this.has(histogram, obj, key)) {
-      return false;
-    }
-    const objectMap = this._timers.get(histogram);
-    const keyedInfo = objectMap.get(obj);
-    if (keyedInfo.size > 1) {
-      keyedInfo.delete(key);
-      return true;
-    }
-    objectMap.delete(obj);
-    // NOTE:
-    // We never delete empty objecMaps from this._timers because there is no
-    // nice solution for tracking the number of objects in a WeakMap.
-    // WeakMap is not enumerable, so we can't deterministically say when it's
-    // empty. We accept that trade-off here, given that entries for short-lived
-    // objects will go away when they are no longer referenced
-    return true;
-  }
-};
-
-var TelemetryStopwatch = {
-  /**
-   * Starts a timer associated with a telemetry histogram. The timer can be
-   * directly associated with a histogram, or with a pair of a histogram and
-   * an object.
-   *
-   * @param {String} aHistogram - a string which must be a valid histogram name.
-   *
-   * @param {Object} aObj - Optional parameter. If specified, the timer is
-   *                        associated with this object, meaning that multiple
-   *                        timers for the same histogram may be run
-   *                        concurrently, as long as they are associated with
-   *                        different objects.
-   *
-   * @returns {Boolean} True if the timer was successfully started, false
-   *                    otherwise. If a timer already exists, it can't be
-   *                    started again, and the existing one will be cleared in
-   *                    order to avoid measurements errors.
-   */
-  start(aHistogram, aObj) {
-    return TelemetryStopwatchImpl.start(aHistogram, aObj, null);
-  },
-
-  /**
-   * Returns whether a timer associated with a telemetry histogram is currently
-   * running. The timer can be directly associated with a histogram, or with a
-   * pair of a histogram and an object.
-   *
-   * @param {String} aHistogram - a string which must be a valid histogram name.
-   *
-   * @param {Object} aObj - Optional parameter. If specified, the timer is
-   *                        associated with this object, meaning that multiple
-   *                        timers for the same histogram may be run
-   *                        concurrently, as long as they are associated with
-   *                        different objects.
-   *
-   * @returns {Boolean} True if the timer exists and is currently running.
-   */
-  running(aHistogram, aObj) {
-    return TelemetryStopwatchImpl.running(aHistogram, aObj, null);
-  },
-
-  /**
-   * Deletes the timer associated with a telemetry histogram. The timer can be
-   * directly associated with a histogram, or with a pair of a histogram and
-   * an object. Important: Only use this method when a legitimate cancellation
-   * should be done.
-   *
-   * @param {String} aHistogram - a string which must be a valid histogram name.
-   *
-   * @param {Object} aObj - Optional parameter. If specified, the timer is
-   *                        associated with this object, meaning that multiple
-   *                        timers or a same histogram may be run concurrently,
-   *                        as long as they are associated with different
-   *                        objects.
-   *
-   * @returns {Boolean} True if the timer exist and it was cleared, False
-   *                   otherwise.
-   */
-  cancel(aHistogram, aObj) {
-    return TelemetryStopwatchImpl.cancel(aHistogram, aObj, null);
-  },
-
-  /**
-   * Returns the elapsed time for a particular stopwatch. Primarily for
-   * debugging purposes. Must be called prior to finish.
-   *
-   * @param {String} aHistogram - a string which must be a valid histogram name.
-   *                              If an invalid name is given, the function will
-   *                              throw.
-   *
-   * @param (Object) aObj - Optional parameter which associates the histogram
-   *                        timer with the given object.
-   *
-   * @param {Boolean} aCanceledOkay - Optional parameter which will suppress any
-   *                                  warnings that normally fire when a stopwatch
-   *                                  is finished after being cancelled. Defaults
-   *                                  to false.
-   *
-   * @returns {Integer} time in milliseconds or -1 if the stopwatch was not
-   *                   found.
-   */
-  timeElapsed(aHistogram, aObj, aCanceledOkay) {
-    return TelemetryStopwatchImpl.timeElapsed(aHistogram, aObj, null,
-                                              aCanceledOkay);
-  },
-
-  /**
-   * Stops the timer associated with the given histogram (and object),
-   * calculates the time delta between start and finish, and adds the value
-   * to the histogram.
-   *
-   * @param {String} aHistogram - a string which must be a valid histogram name.
-   *
-   * @param {Object} aObj - Optional parameter which associates the histogram
-   *                        timer with the given object.
-   *
-   * @param {Boolean} aCanceledOkay - Optional parameter which will suppress any
-   *                                  warnings that normally fire when a stopwatch
-   *                                  is finished after being cancelled. Defaults
-   *                                  to false.
-   *
-   * @returns {Boolean} True if the timer was succesfully stopped and the data
-   *                    was added to the histogram, False otherwise.
-   */
-  finish(aHistogram, aObj, aCanceledOkay) {
-    return TelemetryStopwatchImpl.finish(aHistogram, aObj, null, aCanceledOkay);
-  },
-
-  /**
-   * Starts a timer associated with a keyed telemetry histogram. The timer can
-   * be directly associated with a histogram and its key. Similarly to
-   * @see{TelemetryStopwatch.stat} the histogram and its key can be associated
-   * with an object. Each key may have multiple associated objects and each
-   * object can be associated with multiple keys.
-   *
-   * @param {String} aHistogram - a string which must be a valid histogram name.
-   *
-   * @param {String} aKey - a string which must be a valid histgram key.
-   *
-   * @param {Object} aObj - Optional parameter. If specified, the timer is
-   *                        associated with this object, meaning that multiple
-   *                        timers for the same histogram may be run
-   *                        concurrently,as long as they are associated with
-   *                        different objects.
-   *
-   * @returns {Boolean} True if the timer was successfully started, false
-   *                    otherwise. If a timer already exists, it can't be
-   *                    started again, and the existing one will be cleared in
-   *                    order to avoid measurements errors.
-   */
-  startKeyed(aHistogram, aKey, aObj) {
-    return TelemetryStopwatchImpl.start(aHistogram, aObj, aKey);
-  },
-
-  /**
-   * Returns whether a timer associated with a telemetry histogram is currently
-   * running. Similarly to @see{TelemetryStopwatch.running} the timer and its
-   * key can be associated with an object. Each key may have multiple associated
-   * objects and each object can be associated with multiple keys.
-   *
-   * @param {String} aHistogram - a string which must be a valid histogram name.
-   *
-   * @param {String} aKey - a string which must be a valid histgram key.
-   *
-   * @param {Object} aObj - Optional parameter. If specified, the timer is
-   *                        associated with this object, meaning that multiple
-   *                        timers for the same histogram may be run
-   *                        concurrently, as long as they are associated with
-   *                        different objects.
-   *
-   * @returns {Boolean} True if the timer exists and is currently running.
-   */
-  runningKeyed(aHistogram, aKey, aObj) {
-    return TelemetryStopwatchImpl.running(aHistogram, aObj, aKey);
-  },
-
-  /**
-   * Deletes the timer associated with a keyed histogram. Important: Only use
-   * this method when a legitimate cancellation should be done.
-   *
-   * @param {String} aHistogram - a string which must be a valid histogram name.
-   *
-   * @param {String} aKey - a string which must be a valid histgram key.
-   *
-   * @param {Object} aObj - Optional parameter. If specified, the timer
-   *                        associated with this object is deleted.
-   *
-   * @return {Boolean} True if the timer exist and it was cleared, False
-   *                   otherwise.
-   */
-  cancelKeyed(aHistogram, aKey, aObj) {
-    return TelemetryStopwatchImpl.cancel(aHistogram, aObj, aKey);
-  },
-
-  /**
-   * Returns the elapsed time for a particular stopwatch. Primarily for
-   * debugging purposes. Must be called prior to finish.
-   *
-   * @param {String} aHistogram - a string which must be a valid histogram name.
-   *
-   * @param {String} aKey - a string which must be a valid histgram key.
-   *
-   * @param {Object} aObj - Optional parameter. If specified, the timer
-   *                        associated with this object is used to calculate
-   *                        the elapsed time.
-   *
-   * @return {Integer} time in milliseconds or -1 if the stopwatch was not
-   *                   found.
-   */
-  timeElapsedKeyed(aHistogram, aKey, aObj, aCanceledOkay) {
-    return TelemetryStopwatchImpl.timeElapsed(aHistogram, aObj, aKey,
-                                              aCanceledOkay);
-  },
-
-  /**
-   * Stops the timer associated with the given keyed histogram (and object),
-   * calculates the time delta between start and finish, and adds the value
-   * to the keyed histogram.
-   *
-   * @param {String} aHistogram - a string which must be a valid histogram name.
-   *
-   * @param {String} aKey - a string which must be a valid histgram key.
-   *
-   * @param {Object} aObj - optional parameter which associates the histogram
-   *                        timer with the given object.
-   *
-   * @param {Boolean} aCanceledOkay - Optional parameter which will suppress any
-   *                                  warnings that normally fire when a stopwatch
-   *                                  is finished after being cancelled. Defaults
-   *                                  to false.
-   *
-   * @returns {Boolean} True if the timer was succesfully stopped and the data
-   *                   was added to the histogram, False otherwise.
-   */
-  finishKeyed(aHistogram, aKey, aObj, aCanceledOkay) {
-    return TelemetryStopwatchImpl.finish(aHistogram, aObj, aKey, aCanceledOkay);
-  },
-
-  /**
-   * Set the testing mode. Used by tests.
-   */
-  setTestModeEnabled(testing) {
-    TelemetryStopwatchImpl.suppressErrors(true);
-  },
-};
-
-var TelemetryStopwatchImpl = {
-  // Suppress errors. Used when testing.
-  _suppressErrors: false,
-
-  suppressErrors(suppress) {
-    this._suppressErrors = suppress;
-  },
-
-  start(histogram, object, key) {
-    if (Timers.has(histogram, object, key)) {
-      Timers.delete(histogram, object, key);
-      if (!this._suppressErrors) {
-        Cu.reportError(`TelemetryStopwatch: key "${histogram}" was already ` +
-                       "initialized");
-      }
-      return false;
-    }
-
-    return Timers.put(histogram, object, key, Cu.now());
-  },
-
-  running(histogram, object, key) {
-    return Timers.has(histogram, object, key);
-  },
-
-  cancel(histogram, object, key) {
-    return Timers.delete(histogram, object, key);
-  },
-
-  timeElapsed(histogram, object, key, aCanceledOkay) {
-    const startTime = Timers.get(histogram, object, key);
-    if (startTime === null) {
-      if (!aCanceledOkay && !this._suppressErrors) {
-        Cu.reportError("TelemetryStopwatch: requesting elapsed time for " +
-                       `nonexisting stopwatch. Histogram: "${histogram}", ` +
-                       `key: "${key}"`);
-      }
-      return -1;
-    }
-
-    try {
-      const delta = Cu.now() - startTime;
-      return Math.round(delta / 1000);
-    } catch (e) {
-      if (!this._suppressErrors) {
-        Cu.reportError("TelemetryStopwatch: failed to calculate elapsed time " +
-                       `for Histogram: "${histogram}", key: "${key}", ` +
-                       `exception: ${Log.exceptionStr(e)}`);
-      }
-      return -1;
-    }
-  },
-
-  finish(histogram, object, key, aCanceledOkay) {
-    const delta = this.timeElapsed(histogram, object, key, aCanceledOkay);
-    if (delta == -1) {
-      return false;
-    }
-
-    try {
-      if (key) {
-        Services.telemetry.getKeyedHistogramById(histogram).add(key, delta);
-      } else {
-        Services.telemetry.getHistogramById(histogram).add(delta);
-      }
-    } catch (e) {
-      if (!this._suppressErrors) {
-        Cu.reportError("TelemetryStopwatch: failed to update the Histogram " +
-                       `"${histogram}", using key: "${key}", ` +
-                       `exception: ${Log.exceptionStr(e)}`);
-      }
-      return false;
-    }
-
-    return Timers.delete(histogram, object, key);
-  }
-};
--- a/devtools/client/shared/moz.build
+++ b/devtools/client/shared/moz.build
@@ -43,17 +43,16 @@ DevToolsModules(
     'prefs.js',
     'react-utils.js',
     'scroll.js',
     'source-utils.js',
     'SplitView.jsm',
     'stylesheet-utils.js',
     'suggestion-picker.js',
     'telemetry.js',
-    'TelemetryStopwatch.jsm',
     'theme.js',
     'undo.js',
     'unicode-url.js',
     'view-source.js',
     'WeakMapMap.js',
     'webgl-utils.js',
     'zoom-keys.js',
 )
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -6,17 +6,17 @@
  * This is the telemetry module to report metrics for tools.
  *
  * Comprehensive documentation is in docs/frontend/telemetry.md
  */
 
 "use strict";
 
 const Services = require("Services");
-const { TelemetryStopwatch } = require("devtools/client/shared/TelemetryStopwatch.jsm");
+const { TelemetryStopwatch } = require("resource://gre/modules/TelemetryStopwatch.jsm");
 const { getNthPathExcluding } = require("devtools/shared/platform/stack");
 const { TelemetryEnvironment } = require("resource://gre/modules/TelemetryEnvironment.jsm");
 const WeakMapMap = require("devtools/client/shared/WeakMapMap");
 
 const CATEGORY = "devtools.main";
 
 // Object to be shared among all instances.
 const PENDING_EVENT_PROPERTIES = new WeakMapMap();
@@ -79,23 +79,26 @@ class Telemetry {
    * an object.
    *
    * @param {String} histogramId
    *        A string which must be a valid histogram name.
    * @param {Object} obj
    *        The telemetry event or ping is associated with this object, meaning
    *        that multiple events or pings for the same histogram may be run
    *        concurrently, as long as they are associated with different objects.
+   * @param {Object}  [options.inSeconds=false]
+   *        Record elapsed time for this histogram in seconds instead of
+   *        milliseconds. Defaults to false.
    * @returns {Boolean}
    *          True if the timer was successfully started, false otherwise. If a
    *          timer already exists, it can't be started again, and the existing
    *          one will be cleared in order to avoid measurements errors.
    */
-  start(histogramId, obj) {
-    return TelemetryStopwatch.start(histogramId, obj);
+  start(histogramId, obj, {inSeconds} = {}) {
+    return TelemetryStopwatch.start(histogramId, obj, {inSeconds});
   }
 
   /**
    * Starts a timer associated with a keyed telemetry histogram. The timer can
    * be directly associated with a histogram and its key. Similarly to
    * TelemetryStopwatch.start the histogram and its key can be associated
    * with an object. Each key may have multiple associated objects and each
    * object can be associated with multiple keys.
@@ -103,24 +106,27 @@ class Telemetry {
    * @param {String} histogramId
    *        A string which must be a valid histogram name.
    * @param {String} key
    *        A string which must be a valid histgram key.
    * @param {Object} obj
    *        The telemetry event or ping is associated with this object, meaning
    *        that multiple events or pings for the same histogram may be run
    *        concurrently, as long as they are associated with different objects.
+   * @param {Object}  [options.inSeconds=false]
+   *        Record elapsed time for this histogram in seconds instead of
+   *        milliseconds. Defaults to false.
    *
    * @returns {Boolean}
    *          True if the timer was successfully started, false otherwise. If a
    *          timer already exists, it can't be started again, and the existing
    *          one will be cleared in order to avoid measurements errors.
    */
-  startKeyed(histogramId, key, obj) {
-    return TelemetryStopwatch.startKeyed(histogramId, key, obj);
+  startKeyed(histogramId, key, obj, {inSeconds} = {}) {
+    return TelemetryStopwatch.startKeyed(histogramId, key, obj, {inSeconds});
   }
 
   /**
    * Stops the timer associated with the given histogram (and object),
    * calculates the time delta between start and finish, and adds the value
    * to the histogram.
    *
    * @param {String} histogramId
@@ -541,27 +547,30 @@ class Telemetry {
    *          width: "1024"
    *        }
    */
   recordEvent(method, object, value = null, extra = null) {
     // Only string values are allowed so cast all values to strings.
     if (extra) {
       for (let [name, val] of Object.entries(extra)) {
         val = val + "";
-        extra[name] = val;
 
         if (val.length > 80) {
           const sig = `${method},${object},${value}`;
 
-          throw new Error(`The property "${name}" was added to a telemetry ` +
-                          `event with the signature ${sig} but it's value ` +
-                          `"${val}" is longer than the maximum allowed length ` +
-                          `of 80 characters\n` +
-                          `CALLER: ${getCaller()}`);
+          dump(`Warning: The property "${name}" was added to a telemetry ` +
+               `event with the signature ${sig} but it's value "${val}" is ` +
+               `longer than the maximum allowed length of 80 characters.\n` +
+               `The property value has been trimmed to 80 characters before ` +
+               `sending.\nCALLER: ${getCaller()}`);
+
+          val = val.substring(0, 80);
         }
+
+        extra[name] = val;
       }
     }
     Services.telemetry.recordEvent(CATEGORY, method, object, value, extra);
   }
 
   /**
    * Sends telemetry pings to indicate that a tool has been opened.
    *
@@ -595,17 +604,17 @@ class Telemetry {
         "os",
         "time_open",
         "session_id"
       ]);
       this.addEventProperty(obj, "tool_timer", id, null,
                             "time_open", this.msSystemNow());
     }
     if (charts.timerHist) {
-      this.start(charts.timerHist, obj);
+      this.start(charts.timerHist, obj, {inSeconds: true});
     }
     if (charts.countHist) {
       this.getHistogramById(charts.countHist).add(true);
     }
     if (charts.countScalar) {
       this.scalarAdd(charts.countScalar, 1);
     }
   }
--- a/devtools/server/actors/root.js
+++ b/devtools/server/actors/root.js
@@ -9,16 +9,20 @@
 const { Cu } = require("chrome");
 const Services = require("Services");
 const { Pool } = require("devtools/shared/protocol");
 const { LazyPool, createExtraActors } = require("devtools/shared/protocol/lazy-pool");
 const { DebuggerServer } = require("devtools/server/main");
 
 loader.lazyRequireGetter(this, "ChromeWindowTargetActor",
   "devtools/server/actors/targets/chrome-window", true);
+loader.lazyRequireGetter(this, "ContentProcessTargetActor",
+  "devtools/server/actors/targets/content-process", true);
+loader.lazyRequireGetter(this, "ParentProcessTargetActor",
+  "devtools/server/actors/targets/parent-process", true);
 
 /* Root actor for the remote debugging protocol. */
 
 /**
  * Create a remote debugging protocol root actor.
  *
  * @param connection
  *     The DebuggerServerConnection whose root actor we are constructing.
@@ -516,26 +520,40 @@ RootActor.prototype = {
     }
     if (("id" in request) && typeof (request.id) != "number") {
       return { error: "wrongParameter",
                message: "getProcess requires a valid `id` attribute." };
     }
     // If the request doesn't contains id parameter or id is 0
     // (id == 0, based on onListProcesses implementation)
     if ((!("id" in request)) || request.id === 0) {
-      if (this._parentProcessTargetActor && (!this._parentProcessTargetActor.docShell ||
-          this._parentProcessTargetActor.docShell.isBeingDestroyed)) {
+      // Check if we are running on xpcshell. hiddenDOMWindow is going to throw on it.
+      // When running on xpcshell, there is no valid browsing context to attach to
+      // and so ParentProcessTargetActor doesn't make sense as it inherits from
+      // BrowsingContextTargetActor. So instead use ContentProcessTargetActor, which
+      // matches xpcshell needs.
+      let isXpcshell = true;
+      try {
+        isXpcshell = !Services.wm.getMostRecentWindow(null) &&
+                     !Services.appShell.hiddenDOMWindow;
+      } catch (e) {}
+
+      if (!isXpcshell && this._parentProcessTargetActor &&
+          (!this._parentProcessTargetActor.docShell ||
+            this._parentProcessTargetActor.docShell.isBeingDestroyed)) {
         this._parentProcessTargetActor.destroy();
         this._parentProcessTargetActor = null;
       }
       if (!this._parentProcessTargetActor) {
-        // Create a ParentProcessTargetActor for the parent process
-        const { ParentProcessTargetActor } =
-          require("devtools/server/actors/targets/parent-process");
-        this._parentProcessTargetActor = new ParentProcessTargetActor(this.conn);
+        // Create the target actor for the parent process
+        if (isXpcshell) {
+          this._parentProcessTargetActor = new ContentProcessTargetActor(this.conn);
+        } else {
+          this._parentProcessTargetActor = new ParentProcessTargetActor(this.conn);
+        }
         this._globalActorPool.manage(this._parentProcessTargetActor);
       }
 
       return { form: this._parentProcessTargetActor.form() };
     }
 
     const { id } = request;
     const mm = Services.ppmm.getChildAt(id);
--- a/devtools/server/actors/targets/browsing-context.js
+++ b/devtools/server/actors/targets/browsing-context.js
@@ -331,40 +331,35 @@ const browsingContextTargetPrototype = {
   get docShells() {
     return getChildDocShells(this.docShell);
   },
 
   /**
    * Getter for the browsing context's current DOM window.
    */
   get window() {
-    // On xpcshell, there is no document
-    if (this.docShell) {
-      return this.docShell.domWindow;
-    }
-    return null;
+    return this.docShell.domWindow;
   },
 
   get outerWindowID() {
     if (this.window) {
       return this.window.windowUtils.outerWindowID;
     }
     return null;
   },
 
   /**
    * Getter for the WebExtensions ContentScript globals related to the
    * browsing context's current DOM window.
    */
   get webextensionsContentScriptGlobals() {
-    // Ignore xpcshell runtime which spawns target actors without a window
-    // and only retrieve the content scripts globals if the ExtensionContent JSM module
+    // Only retrieve the content scripts globals if the ExtensionContent JSM module
     // has been already loaded (which is true if the WebExtensions internals have already
     // been loaded in the same content process).
-    if (this.window && Cu.isModuleLoaded(EXTENSION_CONTENT_JSM)) {
+    if (Cu.isModuleLoaded(EXTENSION_CONTENT_JSM)) {
       return ExtensionContent.getContentScriptGlobals(this.window);
     }
 
     return [];
   },
 
   /**
    * Getter for the list of all content DOM windows in the browsing context.
@@ -455,27 +450,25 @@ const browsingContextTargetPrototype = {
     assert(!this.exited,
                "form() shouldn't be called on exited browser actor.");
     assert(this.actorID,
                "Actor should have an actorID.");
 
     const response = {
       actor: this.actorID,
       traits: {
-        // FF64+ exposes a new trait to help identify ParentProcessTargetActor used for
-        // xpcshell that isn't attached to any valid browsing context and so shouldn't
-        // be considered as a browsing context-inherited target on the client side.
-        isBrowsingContext: !!this.docShell,
+        // FF64+ exposes a new trait to help identify BrowsingContextActor's inherited
+        // actorss from the client side.
+        isBrowsingContext: true,
       },
     };
 
-    // We may try to access window while the document is closing, then
-    // accessing window throws. Also on xpcshell we are using this actor even if
-    // there is no valid document.
-    if (this.docShell && !this.docShell.isBeingDestroyed()) {
+    // We may try to access window while the document is closing, then accessing window
+    // throws.
+    if (!this.docShell.isBeingDestroyed()) {
       response.title = this.title;
       response.url = this.url;
       response.outerWindowID = this.outerWindowID;
     }
 
     // Always use the same ActorPool, so existing actor instances
     // (created in createExtraActors) are not lost.
     if (!this._targetScopedActorPool) {
@@ -570,27 +563,24 @@ const browsingContextTargetPrototype = {
   _attach() {
     if (this._attached) {
       return;
     }
 
     // Create a pool for context-lifetime actors.
     this._createThreadActor();
 
-    // on xpcshell, there is no document
-    if (this.window) {
-      this._progressListener = new DebuggerProgressListener(this);
+    this._progressListener = new DebuggerProgressListener(this);
 
-      // Save references to the original document we attached to
-      this._originalWindow = this.window;
+    // Save references to the original document we attached to
+    this._originalWindow = this.window;
 
-      // Ensure replying to attach() request first
-      // before notifying about new docshells.
-      DevToolsUtils.executeSoon(() => this._watchDocshells());
-    }
+    // Ensure replying to attach() request first
+    // before notifying about new docshells.
+    DevToolsUtils.executeSoon(() => this._watchDocshells());
 
     this._attached = true;
   },
 
   _watchDocshells() {
     // In child processes, we watch all docshells living in the process.
     if (this.listenForNewDocShells) {
       Services.obs.addObserver(this, "webnavigation-create");
--- a/devtools/server/actors/targets/content-process.js
+++ b/devtools/server/actors/targets/content-process.js
@@ -19,16 +19,17 @@ const { WebConsoleActor } = require("dev
 const makeDebugger = require("devtools/server/actors/utils/make-debugger");
 const { ActorPool } = require("devtools/server/actors/common");
 const { Pool } = require("devtools/shared/protocol");
 const { assert } = require("devtools/shared/DevToolsUtils");
 const { TabSources } = require("devtools/server/actors/utils/TabSources");
 
 loader.lazyRequireGetter(this, "WorkerTargetActorList", "devtools/server/actors/worker/worker-list", true);
 loader.lazyRequireGetter(this, "MemoryActor", "devtools/server/actors/memory", true);
+loader.lazyRequireGetter(this, "PromisesActor", "devtools/server/actors/promises", true);
 
 function ContentProcessTargetActor(connection) {
   this.conn = connection;
   this._contextPool = new ActorPool(this.conn);
   this.conn.addActorPool(this._contextPool);
   this.threadActor = null;
 
   // Use a see-everything debugger
@@ -96,23 +97,31 @@ ContentProcessTargetActor.prototype = {
     if (!this.threadActor) {
       this.threadActor = new ChromeDebuggerActor(this.conn, this);
       this._contextPool.addActor(this.threadActor);
     }
     if (!this.memoryActor) {
       this.memoryActor = new MemoryActor(this.conn, this);
       this._contextPool.addActor(this.memoryActor);
     }
+    // Promises actor is being tested by xpcshell test, which uses the content process
+    // target actor. But this actor isn't being used outside of tests yet.
+    if (!this._promisesActor) {
+      this._promisesActor = new PromisesActor(this.conn, this);
+      this._contextPool.addActor(this._promisesActor);
+    }
+
     return {
       actor: this.actorID,
       name: "Content process",
 
       consoleActor: this._consoleActor.actorID,
       chromeDebugger: this.threadActor.actorID,
       memoryActor: this.memoryActor.actorID,
+      promisesActor: this._promisesActor.actorID,
 
       traits: {
         highlightable: false,
         networkMonitor: false,
       },
     };
   },
 
--- a/devtools/server/actors/targets/parent-process.js
+++ b/devtools/server/actors/targets/parent-process.js
@@ -38,52 +38,51 @@ const parentProcessTargetPrototype = ext
  * Creates a target actor for debugging all the chrome content in the parent process.
  * Most of the implementation is inherited from BrowsingContextTargetActor.
  * ParentProcessTargetActor is a child of RootActor, it can be instantiated via
  * RootActor.getProcess request. ParentProcessTargetActor exposes all target-scoped actors
  * via its form() request, like BrowsingContextTargetActor.
  *
  * @param connection DebuggerServerConnection
  *        The connection to the client.
+ * @param window Window object (optional)
+ *        If the upper class already knows against which window the actor should attach,
+ *        it is passed as a constructor argument here.
  */
-parentProcessTargetPrototype.initialize = function(connection) {
+parentProcessTargetPrototype.initialize = function(connection, window) {
   BrowsingContextTargetActor.prototype.initialize.call(this, connection);
 
   // This creates a Debugger instance for chrome debugging all globals.
   this.makeDebugger = makeDebugger.bind(null, {
     findDebuggees: dbg => dbg.findAllGlobals(),
     shouldAddNewGlobalAsDebuggee: () => true
   });
 
   // Ensure catching the creation of any new content docshell
   this.listenForNewDocShells = true;
 
   // Defines the default docshell selected for the target actor
-  let window = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType);
+  if (!window) {
+    window = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType);
+  }
 
   // Default to any available top level window if there is no expected window
   // (for example when we open firefox with -webide argument)
   if (!window) {
     window = Services.wm.getMostRecentWindow(null);
   }
 
   // We really want _some_ window at least, so fallback to the hidden window if
   // there's nothing else (such as during early startup).
   if (!window) {
-    try {
-      window = Services.appShell.hiddenDOMWindow;
-    } catch (e) {
-      // On XPCShell, the above line will throw.
-    }
+    window = Services.appShell.hiddenDOMWindow;
   }
 
-  // On XPCShell, there is no window/docshell
-  const docShell = window ? window.docShell : null;
   Object.defineProperty(this, "docShell", {
-    value: docShell,
+    value: window.docShell,
     configurable: true
   });
 };
 
 parentProcessTargetPrototype.isRootActor = true;
 
 /**
  * Getter for the list of all docshells in this targetActor
--- a/devtools/server/actors/targets/webextension.js
+++ b/devtools/server/actors/targets/webextension.js
@@ -70,20 +70,29 @@ const webExtensionTargetPrototype = exte
  *        The chromeGlobal where this actor has been injected by the
  *        DebuggerServer.connectToFrame method.
  * @param {string} prefix
  *        the custom RDP prefix to use.
  * @param {string} addonId
  *        the addonId of the target WebExtension.
  */
 webExtensionTargetPrototype.initialize = function(conn, chromeGlobal, prefix, addonId) {
-  parentProcessTargetPrototype.initialize.call(this, conn);
+  this.id = addonId;
+
+  // Try to discovery an existent extension page to attach (which will provide the initial
+  // URL shown in the window tittle when the addon debugger is opened).
+  let extensionWindow = this._searchForExtensionWindow();
+  if (!extensionWindow) {
+    this._createFallbackWindow();
+    extensionWindow = this.fallbackWindow;
+  }
+
+  parentProcessTargetPrototype.initialize.call(this, conn, extensionWindow);
   this._chromeGlobal = chromeGlobal;
   this._prefix = prefix;
-  this.id = addonId;
 
   // Redefine the messageManager getter to return the chromeGlobal
   // as the messageManager for this actor (which is the browser XUL
   // element used by the parent actor running in the main process to
   // connect to the extension process).
   Object.defineProperty(this, "messageManager", {
     enumerable: true,
     configurable: true,
@@ -110,24 +119,16 @@ webExtensionTargetPrototype.initialize =
 
   // This creates a Debugger instance for debugging all the add-on globals.
   this.makeDebugger = makeDebugger.bind(null, {
     findDebuggees: dbg => {
       return dbg.findAllGlobals().filter(this._shouldAddNewGlobalAsDebuggee);
     },
     shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee.bind(this),
   });
-
-  // Try to discovery an existent extension page to attach (which will provide the initial
-  // URL shown in the window tittle when the addon debugger is opened).
-  const extensionWindow = this._searchForExtensionWindow();
-
-  if (extensionWindow) {
-    this._setWindow(extensionWindow);
-  }
 };
 
 // NOTE: This is needed to catch in the webextension webconsole all the
 // errors raised by the WebExtension internals that are not currently
 // associated with any window.
 webExtensionTargetPrototype.isRootActor = true;
 
 /**
--- a/devtools/server/tests/mochitest/test_css-properties.html
+++ b/devtools/server/tests/mochitest/test_css-properties.html
@@ -10,16 +10,18 @@ Bug 1265798 - Replace inIDOMUtils.cssPro
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript" src="inspector-helpers.js"></script>
   <script type="application/javascript">
 "use strict";
 
 window.onload = function() {
   const { initCssProperties, getCssProperties } =
     require("devtools/shared/fronts/css-properties");
+  const { getFront } =
+    require("devtools/shared/protocol");
 
   function promiseAttachUrl(url) {
     return new Promise((resolve, reject) => {
       attachURL(url, function(err, client, tab, doc) {
         if (err) {
           return reject(err);
         }
         return resolve({client, tab, doc});
@@ -36,26 +38,28 @@ window.onload = function() {
 
     const attachmentA = await promiseAttachUrl(url);
     const attachmentB = await promiseAttachUrl(url);
 
     const toolboxMockA = {
       target: {
         hasActor: () => useActor,
         client: attachmentA.client,
-        form: attachmentA.tab
+        form: attachmentA.tab,
+        getFront: typeName => getFront(attachmentA.client, typeName, attachmentA.tab)
       },
       // Fake the window for css-properties.js's getClientBrowserVersion to work
       win: window
     };
     const toolboxMockB = {
       target: {
         hasActor: () => useActor,
         client: attachmentB.client,
-        form: attachmentB.tab
+        form: attachmentB.tab,
+        getFront: typeName => getFront(attachmentB.client, typeName, attachmentB.tab)
       },
       win: window
     };
 
     await initCssProperties(toolboxMockA);
     await initCssProperties(toolboxMockB);
 
     const cssProperties = getCssProperties(toolboxMockA);
--- a/devtools/server/tests/unit/test_promises_actor_attach.js
+++ b/devtools/server/tests/unit/test_promises_actor_attach.js
@@ -10,24 +10,22 @@
 
 const { PromisesFront } = require("devtools/shared/fronts/promises");
 
 add_task(async function() {
   const client = await startTestDebuggerServer("promises-actor-test");
   const parentProcessActors = await getParentProcessActors(client);
 
   // We have to attach the chrome target actor before playing with the PromiseActor
-  await attachTarget(client, parentProcessActors);
   await testAttach(client, parentProcessActors);
 
   const response = await listTabs(client);
   const targetTab = findTab(response.tabs, "promises-actor-test");
   ok(targetTab, "Found our target tab.");
 
-  await attachTarget(client, targetTab);
   await testAttach(client, targetTab);
 
   await close(client);
 });
 
 async function testAttach(client, parent) {
   const promises = PromisesFront(client, parent);
 
--- a/devtools/server/tests/unit/test_promises_actor_list_promises.js
+++ b/devtools/server/tests/unit/test_promises_actor_list_promises.js
@@ -11,17 +11,16 @@
 const { PromisesFront } = require("devtools/shared/fronts/promises");
 const SECRET = "MyLittleSecret";
 
 add_task(async function() {
   const client = await startTestDebuggerServer("promises-actor-test");
   const parentProcessActors = await getParentProcessActors(client);
 
   // We have to attach the chrome target actor before playing with the PromiseActor
-  await attachTarget(client, parentProcessActors);
   await testListPromises(client, parentProcessActors, v =>
     new Promise(resolve => resolve(v)));
 
   const response = await listTabs(client);
   const targetTab = findTab(response.tabs, "promises-actor-test");
   ok(targetTab, "Found our target tab.");
 
   await testListPromises(client, targetTab, v => {
--- a/devtools/server/tests/unit/test_promises_actor_onnewpromise.js
+++ b/devtools/server/tests/unit/test_promises_actor_onnewpromise.js
@@ -14,17 +14,16 @@ var EventEmitter = require("devtools/sha
 
 add_task(async function() {
   const client = await startTestDebuggerServer("promises-actor-test");
   const parentProcessActors = await getParentProcessActors(client);
 
   ok(Promise.toString().includes("native code"), "Expect native DOM Promise");
 
   // We have to attach the chrome target actor before playing with the PromiseActor
-  await attachTarget(client, parentProcessActors);
   await testNewPromisesEvent(client, parentProcessActors,
     v => new Promise(resolve => resolve(v)));
 
   const response = await listTabs(client);
   const targetTab = findTab(response.tabs, "promises-actor-test");
   ok(targetTab, "Found our target tab.");
 
   await testNewPromisesEvent(client, targetTab, v => {
--- a/devtools/server/tests/unit/test_promises_actor_onpromisesettled.js
+++ b/devtools/server/tests/unit/test_promises_actor_onpromisesettled.js
@@ -16,17 +16,16 @@ var EventEmitter = require("devtools/sha
 
 add_task(async function() {
   const client = await startTestDebuggerServer("promises-actor-test");
   const parentProcessActors = await getParentProcessActors(client);
 
   ok(Promise.toString().includes("native code"), "Expect native DOM Promise");
 
   // We have to attach the chrome target actor before playing with the PromiseActor
-  await attachTarget(client, parentProcessActors);
   await testPromisesSettled(client, parentProcessActors,
     v => new Promise(resolve => resolve(v)),
     v => new Promise((resolve, reject) => reject(v)));
 
   const response = await listTabs(client);
   const targetTab = findTab(response.tabs, "promises-actor-test");
   ok(targetTab, "Found our target tab.");
 
--- a/devtools/server/tests/unit/test_promises_client_getdependentpromises.js
+++ b/devtools/server/tests/unit/test_promises_client_getdependentpromises.js
@@ -9,17 +9,16 @@
 
 const { PromisesFront } = require("devtools/shared/fronts/promises");
 
 var EventEmitter = require("devtools/shared/event-emitter");
 
 add_task(async function() {
   const client = await startTestDebuggerServer("test-promises-dependentpromises");
   const parentProcessActors = await getParentProcessActors(client);
-  await attachTarget(client, parentProcessActors);
 
   ok(Promise.toString().includes("native code"), "Expect native DOM Promise.");
 
   await testGetDependentPromises(client, parentProcessActors, () => {
     const p = new Promise(() => {});
     p.name = "p";
     const q = p.then();
     q.name = "q";
@@ -27,17 +26,16 @@ add_task(async function() {
     r.name = "r";
 
     return p;
   });
 
   const response = await listTabs(client);
   const targetTab = findTab(response.tabs, "test-promises-dependentpromises");
   ok(targetTab, "Found our target tab.");
-  await attachTarget(client, targetTab);
 
   await testGetDependentPromises(client, targetTab, () => {
     const debuggee =
       DebuggerServer.getTestGlobal("test-promises-dependentpromises");
 
     const p = new debuggee.Promise(() => {});
     p.name = "p";
     const q = p.then();
--- a/devtools/server/tests/unit/test_promises_object_creationtimestamp.js
+++ b/devtools/server/tests/unit/test_promises_object_creationtimestamp.js
@@ -23,17 +23,16 @@ add_task(async function() {
   });
 
   const client = await startTestDebuggerServer("promises-object-test");
   const parentProcessActors = await getParentProcessActors(client);
 
   ok(Promise.toString().includes("native code"), "Expect native DOM Promise.");
 
   // We have to attach the chrome target actor before playing with the PromiseActor
-  await attachTarget(client, parentProcessActors);
   await testPromiseCreationTimestamp(client, parentProcessActors, v => {
     return new Promise(resolve => resolve(v));
   });
 
   const response = await listTabs(client);
   const targetTab = findTab(response.tabs, "promises-object-test");
   ok(targetTab, "Found our target tab.");
 
--- a/devtools/server/tests/unit/test_promises_object_timetosettle-01.js
+++ b/devtools/server/tests/unit/test_promises_object_timetosettle-01.js
@@ -14,17 +14,16 @@ var EventEmitter = require("devtools/sha
 
 add_task(async function() {
   const client = await startTestDebuggerServer("test-promises-timetosettle");
   const parentProcessActors = await getParentProcessActors(client);
 
   ok(Promise.toString().includes("native code"), "Expect native DOM Promise.");
 
   // We have to attach the chrome target actor before playing with the PromiseActor
-  await attachTarget(client, parentProcessActors);
   await testGetTimeToSettle(client, parentProcessActors, () => {
     const p = new Promise(() => {});
     p.name = "p";
     const q = p.then();
     q.name = "q";
 
     return p;
   });
--- a/devtools/server/tests/unit/test_promises_object_timetosettle-02.js
+++ b/devtools/server/tests/unit/test_promises_object_timetosettle-02.js
@@ -11,29 +11,26 @@
 const { PromisesFront } = require("devtools/shared/fronts/promises");
 const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm", {});
 
 var EventEmitter = require("devtools/shared/event-emitter");
 
 add_task(async function() {
   const client = await startTestDebuggerServer("test-promises-timetosettle");
   const parentProcessActors = await getParentProcessActors(client);
-  await attachTarget(client, parentProcessActors);
 
   ok(Promise.toString().includes("native code"), "Expect native DOM Promise.");
 
   // We have to attach the chrome target actor before playing with the PromiseActor
-  await attachTarget(client, parentProcessActors);
   await testGetTimeToSettle(client, parentProcessActors,
     v => new Promise(resolve => setTimeout(() => resolve(v), 100)));
 
   const response = await listTabs(client);
   const targetTab = findTab(response.tabs, "test-promises-timetosettle");
   ok(targetTab, "Found our target tab.");
-  await attachTarget(client, targetTab);
 
   await testGetTimeToSettle(client, targetTab, v => {
     const debuggee =
       DebuggerServer.getTestGlobal("test-promises-timetosettle");
     return new debuggee.Promise(resolve => setTimeout(() => resolve(v), 100));
   });
 
   await close(client);
--- a/devtools/server/tests/unit/test_xpcshell_debugging.js
+++ b/devtools/server/tests/unit/test_xpcshell_debugging.js
@@ -23,19 +23,18 @@ add_task(async function() {
   // Ensure that global actors are available. Just test the device actor.
   const deviceFront = await client.mainRoot.getFront("device");
   const desc = await deviceFront.getDescription();
   equal(desc.geckobuildid, Services.appinfo.platformBuildID, "device actor works");
 
   // Even though we have no tabs, getProcess gives us the chromeDebugger.
   const response = await client.getProcess();
 
-  const actor = response.form.actor;
-  const [, tabClient] = await client.attachTarget(actor);
-  const [, threadClient] = await tabClient.attachThread(null);
+  const { chromeDebugger } = response.form;
+  const [, threadClient] = await client.attachThread(chromeDebugger);
   const onResumed = new Promise(resolve => {
     threadClient.addOneTimeListener("paused", (event, packet) => {
       equal(packet.why.type, "breakpoint",
           "yay - hit the breakpoint at the first line in our script");
       // Resume again - next stop should be our "debugger" statement.
       threadClient.addOneTimeListener("paused", (event, packet) => {
         equal(packet.why.type, "debuggerStatement",
               "yay - hit the 'debugger' statement in our script");
--- a/devtools/shared/fronts/css-properties.js
+++ b/devtools/shared/fronts/css-properties.js
@@ -232,17 +232,17 @@ const initCssProperties = async function
   if (cachedCssProperties.has(client)) {
     return cachedCssProperties.get(client);
   }
 
   let db, front;
 
   // Get the list dynamically if the cssProperties actor exists.
   if (toolbox.target.hasActor("cssProperties")) {
-    front = CssPropertiesFront(client, toolbox.target.form);
+    front = toolbox.target.getFront("cssProperties");
     db = await front.getCSSDatabase();
   } else {
     // The target does not support this actor, so require a static list of supported
     // properties.
     db = CSS_PROPERTIES_DB;
   }
 
   const cssProperties = new CssProperties(normalizeCssData(db));
--- a/gfx/2d/DrawTargetD2D1.cpp
+++ b/gfx/2d/DrawTargetD2D1.cpp
@@ -206,17 +206,17 @@ DrawTargetD2D1::DrawSurface(SourceSurfac
   RefPtr<ID2D1Image> image = GetImageForSurface(aSurface, transform, ExtendMode::CLAMP);
 
   if (!image) {
     gfxWarning() << *this << ": Unable to get D2D image for surface.";
     return;
   }
 
   RefPtr<ID2D1Bitmap> bitmap;
-  HRESULT hr;
+  HRESULT hr = E_FAIL;
   if (aSurface->GetType() == SurfaceType::D2D1_1_IMAGE) {
     // If this is called with a DataSourceSurface it might do a partial upload
     // that our DrawBitmap call doesn't support.
     hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap));
   }
 
   if (SUCCEEDED(hr) && bitmap && aSurfOptions.mSamplingBounds == SamplingBounds::UNBOUNDED) {
     mDC->DrawBitmap(bitmap, D2DRect(aDest), aOptions.mAlpha,
@@ -641,17 +641,17 @@ DrawTargetD2D1::FillGlyphs(ScaledFont *a
     aaMode = aOptions.mAntialiasMode;
   }
 
   PrepareForDrawing(aOptions.mCompositionOp, aPattern);
 
   bool forceClearType = false;
   if (!CurrentLayer().mIsOpaque && mPermitSubpixelAA &&
       aOptions.mCompositionOp == CompositionOp::OP_OVER && aaMode == AntialiasMode::SUBPIXEL) {
-    forceClearType = true;    
+    forceClearType = true;
   }
 
 
   D2D1_TEXT_ANTIALIAS_MODE d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
 
   switch (aaMode) {
   case AntialiasMode::NONE:
     d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
@@ -762,17 +762,17 @@ DrawTargetD2D1::Mask(const Pattern &aSou
                                        D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
                                        D2D1::IdentityMatrix(),
                                        1.0f, mask),
                  nullptr);
 
   Rect rect(0, 0, (Float)mSize.width, (Float)mSize.height);
   Matrix mat = mTransform;
   mat.Invert();
-  
+
   mDC->FillRectangle(D2DRect(mat.TransformBounds(rect)), source);
 
   mDC->PopLayer();
 
   FinalizeDrawing(aOptions.mCompositionOp, aSource);
 }
 
 void
@@ -1742,27 +1742,27 @@ DrawTargetD2D1::GetInverseClippedGeometr
   return inverseGeom.forget();
 }
 
 void
 DrawTargetD2D1::PopAllClips()
 {
   if (CurrentLayer().mClipsArePushed) {
     PopClipsFromDC(mDC);
-  
+
     CurrentLayer().mClipsArePushed = false;
   }
 }
 
 void
 DrawTargetD2D1::PushAllClips()
 {
   if (!CurrentLayer().mClipsArePushed) {
     PushClipsToDC(mDC);
-  
+
     CurrentLayer().mClipsArePushed = true;
   }
 }
 
 void
 DrawTargetD2D1::PushClipsToDC(ID2D1DeviceContext *aDC, bool aForceIgnoreAlpha, const D2D1_RECT_F& aMaxRect)
 {
   mDC->SetTransform(D2D1::IdentityMatrix());
@@ -2008,17 +2008,17 @@ DrawTargetD2D1::GetImageForSurface(Sourc
       // Sometimes we have a dual drawtarget but the underlying targets
       // are d2d surfaces. Let's not readback and reupload in those cases.
       SourceSurfaceDual* surface = static_cast<SourceSurfaceDual*>(aSurface);
       SourceSurface* first = surface->GetFirstSurface();
       if (first->GetType() == SurfaceType::D2D1_1_IMAGE) {
         MOZ_ASSERT(surface->SameSurfaceTypes());
         SourceSurfaceD2D1* d2dSurface = static_cast<SourceSurfaceD2D1*>(first);
         image = d2dSurface->GetImage();
-        AddDependencyOnSource(d2dSurface); 
+        AddDependencyOnSource(d2dSurface);
         break;
       }
       // Otherwise fall through
   }
   default:
     {
       RefPtr<DataSourceSurface> dataSurf = aSurface->GetDataSurface();
       if (!dataSurf) {
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -506,23 +506,26 @@ SpecialPowersAPI.prototype = {
   },
 
   /*
    * Load a privileged script that runs same-process. This is different from
    * |loadChromeScript|, which will run in the parent process in e10s mode.
    */
   loadPrivilegedScript(aFunction) {
     var str = "(" + aFunction.toString() + ")();";
-    var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
-    var sb = Cu.Sandbox(systemPrincipal);
+    let gGlobalObject = Cu.getGlobalForObject(this);
+    let sb = Cu.Sandbox(gGlobalObject);
     var window = this.window.get();
     var mc = new window.MessageChannel();
     sb.port = mc.port1;
     try {
-      sb.eval(str);
+      Cu.importGlobalProperties(["URL", "Blob"]);
+      let blob = new Blob([str], {type: "application/javascript"});
+      let blobUrl = URL.createObjectURL(blob);
+      Services.scriptloader.loadSubScript(blobUrl, sb);
     } catch (e) {
       throw wrapIfUnwrapped(e);
     }
 
     return mc.port2;
   },
 
   loadChromeScript(urlOrFunction, sandboxOptions) {
--- a/toolkit/components/telemetry/app/TelemetryStopwatch.jsm
+++ b/toolkit/components/telemetry/app/TelemetryStopwatch.jsm
@@ -1,11 +1,12 @@
 /* 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";
 
 var EXPORTED_SYMBOLS = ["TelemetryStopwatch"];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "Log",
   "resource://gre/modules/Log.jsm");
 
 // Weak map does not allow using null objects as keys. These objects are used
@@ -32,21 +33,23 @@ const NULL_KEY = {};
  * @example
  * // Stores current time for a keyed histogram "PLAYING_WITH_CUTE_ANIMALS".
  * Timers.put("PLAYING_WITH_CUTE_ANIMALS", null, "CATS", Date.now());
  *
  * @example
  * // Returns information about a simple Stopwatch.
  * let startTime = Timers.get("PLAYING_WITH_CUTE_ANIMALS", null, "CATS");
  */
-let Timers = {
+const Timers = {
   _timers: new Map(),
 
+  _inSeconds: new Set(),
+
   _validTypes(histogram, obj, key) {
-    let nonEmptyString = value => {
+    const nonEmptyString = value => {
       return typeof value === "string" && value !== "" && value.length > 0;
     };
     return nonEmptyString(histogram) &&
             typeof obj == "object" &&
            (key === NULL_KEY || nonEmptyString(key));
   },
 
   get(histogram, obj, key) {
@@ -55,26 +58,30 @@ let Timers = {
 
     if (!this.has(histogram, obj, key)) {
       return null;
     }
 
     return this._timers.get(histogram).get(obj).get(key);
   },
 
-  put(histogram, obj, key, startTime) {
+  put(histogram, obj, key, startTime, {inSeconds} = {}) {
     key = key === null ? NULL_KEY : key;
     obj = obj || NULL_OBJECT;
 
     if (!this._validTypes(histogram, obj, key)) {
       return false;
     }
 
-    let objectMap = this._timers.get(histogram) || new WeakMap();
-    let keyedInfo = objectMap.get(obj) || new Map();
+    if (inSeconds) {
+      this._inSeconds.add(histogram);
+    }
+
+    const objectMap = this._timers.get(histogram) || new WeakMap();
+    const keyedInfo = objectMap.get(obj) || new Map();
     keyedInfo.set(key, startTime);
     objectMap.set(obj, keyedInfo);
     this._timers.set(histogram, objectMap);
     return true;
   },
 
   has(histogram, obj, key) {
     key = key === null ? NULL_KEY : key;
@@ -87,18 +94,21 @@ let Timers = {
 
   delete(histogram, obj, key) {
     key = key === null ? NULL_KEY : key;
     obj = obj || NULL_OBJECT;
 
     if (!this.has(histogram, obj, key)) {
       return false;
     }
-    let objectMap = this._timers.get(histogram);
-    let keyedInfo = objectMap.get(obj);
+
+    this._inSeconds.delete(histogram);
+
+    const objectMap = this._timers.get(histogram);
+    const keyedInfo = objectMap.get(obj);
     if (keyedInfo.size > 1) {
       keyedInfo.delete(key);
       return true;
     }
     objectMap.delete(obj);
     // NOTE:
     // We never delete empty objecMaps from this._timers because there is no
     // nice solution for tracking the number of objects in a WeakMap.
@@ -117,24 +127,27 @@ var TelemetryStopwatch = {
    *
    * @param {String} aHistogram - a string which must be a valid histogram name.
    *
    * @param {Object} aObj - Optional parameter. If specified, the timer is
    *                        associated with this object, meaning that multiple
    *                        timers for the same histogram may be run
    *                        concurrently, as long as they are associated with
    *                        different objects.
+   * @param {Object}  [options.inSeconds=false] - Record elapsed time for this
+   *                  histogram in seconds instead of milliseconds. Defaults to
+   *                  false.
    *
    * @returns {Boolean} True if the timer was successfully started, false
    *                    otherwise. If a timer already exists, it can't be
    *                    started again, and the existing one will be cleared in
    *                    order to avoid measurements errors.
    */
-  start(aHistogram, aObj) {
-    return TelemetryStopwatchImpl.start(aHistogram, aObj, null);
+  start(aHistogram, aObj, {inSeconds} = {}) {
+    return TelemetryStopwatchImpl.start(aHistogram, aObj, null, {inSeconds});
   },
 
   /**
    * Returns whether a timer associated with a telemetry histogram is currently
    * running. The timer can be directly associated with a histogram, or with a
    * pair of a histogram and an object.
    *
    * @param {String} aHistogram - a string which must be a valid histogram name.
@@ -229,24 +242,27 @@ var TelemetryStopwatch = {
    *
    * @param {String} aKey - a string which must be a valid histgram key.
    *
    * @param {Object} aObj - Optional parameter. If specified, the timer is
    *                        associated with this object, meaning that multiple
    *                        timers for the same histogram may be run
    *                        concurrently,as long as they are associated with
    *                        different objects.
+   * @param {Object}  [options.inSeconds=false] - Record elapsed time for this
+   *                  histogram in seconds instead of milliseconds. Defaults to
+   *                  false.
    *
    * @returns {Boolean} True if the timer was successfully started, false
    *                    otherwise. If a timer already exists, it can't be
    *                    started again, and the existing one will be cleared in
    *                    order to avoid measurements errors.
    */
-  startKeyed(aHistogram, aKey, aObj) {
-    return TelemetryStopwatchImpl.start(aHistogram, aObj, aKey);
+  startKeyed(aHistogram, aKey, aObj, {inSeconds} = {}) {
+    return TelemetryStopwatchImpl.start(aHistogram, aObj, aKey, {inSeconds});
   },
 
   /**
    * Returns whether a timer associated with a telemetry histogram is currently
    * running. Similarly to @see{TelemetryStopwatch.running} the timer and its
    * key can be associated with an object. Each key may have multiple associated
    * objects and each object can be associated with multiple keys.
    *
@@ -339,63 +355,66 @@ var TelemetryStopwatch = {
 var TelemetryStopwatchImpl = {
   // Suppress errors. Used when testing.
   _suppressErrors: false,
 
   suppressErrors(suppress) {
     this._suppressErrors = suppress;
   },
 
-  start(histogram, object, key) {
+  start(histogram, object, key, {inSeconds} = {}) {
     if (Timers.has(histogram, object, key)) {
       Timers.delete(histogram, object, key);
       if (!this._suppressErrors) {
         Cu.reportError(`TelemetryStopwatch: key "${histogram}" was already ` +
                        "initialized");
       }
       return false;
     }
 
-    return Timers.put(histogram, object, key, Cu.now());
+    return Timers.put(histogram, object, key, Cu.now(), {inSeconds});
   },
 
   running(histogram, object, key) {
     return Timers.has(histogram, object, key);
   },
 
   cancel(histogram, object, key) {
     return Timers.delete(histogram, object, key);
   },
 
   timeElapsed(histogram, object, key, aCanceledOkay) {
-    let startTime = Timers.get(histogram, object, key);
+    const startTime = Timers.get(histogram, object, key);
     if (startTime === null) {
       if (!aCanceledOkay && !this._suppressErrors) {
         Cu.reportError("TelemetryStopwatch: requesting elapsed time for " +
                        `nonexisting stopwatch. Histogram: "${histogram}", ` +
                        `key: "${key}"`);
       }
       return -1;
     }
 
     try {
-      let delta = Cu.now() - startTime;
+      const delta = Cu.now() - startTime;
+      if (Timers._inSeconds.has(histogram)) {
+        return Math.round(delta / 1000);
+      }
       return Math.round(delta);
     } catch (e) {
       if (!this._suppressErrors) {
         Cu.reportError("TelemetryStopwatch: failed to calculate elapsed time " +
                        `for Histogram: "${histogram}", key: "${key}", ` +
                        `exception: ${Log.exceptionStr(e)}`);
       }
       return -1;
     }
   },
 
   finish(histogram, object, key, aCanceledOkay) {
-    let delta = this.timeElapsed(histogram, object, key, aCanceledOkay);
+    const delta = this.timeElapsed(histogram, object, key, aCanceledOkay);
     if (delta == -1) {
       return false;
     }
 
     try {
       if (key) {
         Services.telemetry.getKeyedHistogramById(histogram).add(key, delta);
       } else {
--- a/toolkit/components/telemetry/hybrid-content/package.json
+++ b/toolkit/components/telemetry/hybrid-content/package.json
@@ -1,11 +1,11 @@
 {
   "name": "mozilla-hybrid-content-telemetry",
-  "version": "1.2.0",
+  "version": "1.2.1",
   "description": "The content library for Mozilla's Hybrid Content Telemetry.",
   "main": "HybridContentTelemetry-lib.js",
   "author": "Alessio Placitelli <aplacitelli@mozilla.com>",
   "contributors": [
     "Chris Hutten-Czapski <chutten@mozilla.com>",
     "Georg Fritzsche <gfritzsche@mozilla.com>",
     "Jan-Erik Rediger <jrediger@mozilla.com>"
   ],
--- a/xpcom/base/nsIMacPreferencesReader.idl
+++ b/xpcom/base/nsIMacPreferencesReader.idl
@@ -9,17 +9,17 @@
 %}
 
 /**
  * This interface is designed to provide scriptable access to the macOS
  * preferences system.
  *
  * This interface is highly macOS specific.
  */
-[scriptable, uuid(06da64da-647f-4286-ac20-50ab4190cfe3)]
+[scriptable, uuid(b0f20595-88ce-4738-a1a4-24de78eb8051)]
 interface nsIMacPreferencesReader : nsISupports
 {
   /**
    * This method checks whether macOS policies are enabled.
    *
    * @return true if macOS policies are enabled, false otherwise.
    */
   bool policiesEnabled();
--- a/xpcom/base/nsMacPreferencesReader.h
+++ b/xpcom/base/nsMacPreferencesReader.h
@@ -5,18 +5,18 @@
 #ifndef MacPreferencesReader_h__
 #define MacPreferencesReader_h__
 
 //-----------------------------------------------------------------------------
 
 #include "nsIMacPreferencesReader.h"
 
 #define  NS_MACPREFERENCESREADER_CID \
-{ 0x95790842, 0x75a0, 0x430d, \
-  { 0x98, 0xbf, 0xf5, 0xce, 0x37, 0x88, 0xea, 0x6d } }
+{ 0xb0f20595, 0x88ce, 0x4738, \
+  { 0xa1, 0xa4, 0x24, 0xde, 0x78, 0xeb, 0x80, 0x51 } }
 #define NS_MACPREFERENCESREADER_CONTRACTID \
   "@mozilla.org/mac-preferences-reader;1"
 
 //-----------------------------------------------------------------------------
 
 class nsMacPreferencesReader : public nsIMacPreferencesReader
 {
 public: