Merge autoland to mozilla-central. a=merge
authorshindli <shindli@mozilla.com>
Tue, 26 Feb 2019 18:41:24 +0200
changeset 518957 2e02ffdb852e490a3467743fb91b2320721ff38c
parent 518912 4a87b6ac14c030bea86c2ccd5a9a48c0470a29dc (current diff)
parent 518956 9b8bdd92d93ea62e1508c9f8ae18feaed9e8634c (diff)
child 519026 d326a9d5f77bdbb3f43581b2ba3d268881698cdc
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.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 autoland to mozilla-central. a=merge
devtools/client/themes/images/aboutdebugging-collapse-icon.svg
--- a/browser/app/permissions
+++ b/browser/app/permissions
@@ -8,18 +8,15 @@
 
 # UITour
 origin	uitour	1	https://www.mozilla.org
 origin	uitour	1	https://screenshots.firefox.com
 origin	uitour	1	https://support.mozilla.org
 origin	uitour	1	about:home
 origin	uitour	1	about:newtab
 
-# XPInstall
-origin	install	1	https://addons.mozilla.org
-
 # Remote troubleshooting
 origin	remote-troubleshooting	1	https://input.mozilla.org
 origin	remote-troubleshooting	1	https://support.mozilla.org
 
 # Hybrid Content Telemetry - https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/collection/hybrid-content.html
 # Adding hc_telemetry permission to a new domain requires Data Collection Review: https://wiki.mozilla.org/Firefox/Data_Collection
 origin	hc_telemetry	1	https://discovery.addons.mozilla.org
--- a/browser/base/content/test/general/browser_bug1015721.js
+++ b/browser/base/content/test/general/browser_bug1015721.js
@@ -27,17 +27,17 @@ function zoomTab1() {
     FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1");
 
     let browser1 = gBrowser.getBrowserForTab(gTab1);
     await BrowserTestUtils.synthesizeMouse(null, 10, 10, {
       wheel: true, ctrlKey: true, deltaY: -1, deltaMode: WheelEvent.DOM_DELTA_LINE,
     }, browser1);
 
     info("Waiting for tab 1 to be zoomed");
-    await promiseWaitForCondition(() => {
+    await TestUtils.waitForCondition(() => {
       gLevel1 = ZoomManager.getZoomForBrowser(browser1);
       return gLevel1 > 1;
     });
 
     await FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
     FullZoomHelper.zoomTest(gTab2, gLevel1, "Tab 2 should have zoomed along with tab 1");
   })().then(finishTest, FullZoomHelper.failAndContinue(finish));
 }
--- a/devtools/client/aboutdebugging-new/src/base.css
+++ b/devtools/client/aboutdebugging-new/src/base.css
@@ -5,17 +5,16 @@
 :root {
   /* Colors from common.css */
   --bg-color: #f9f9fa; /* --in-content-background-color */
   --text-color: #0c0c0d; /* --in-content-text-color */
 
   --border-color: #d7d7db; /* --in-content-border-color */
 
   --box-background: #fff;
-  --box-background-hover: #ebebeb;
   --box-border-color: #d7d7db;
 
   --category-background-hover: rgba(12,12,13,0.1);
   --category-text: rgba(12,12,13);
   --category-text-selected: #0a84ff;
 
   /* Dimensions from common.css #categories > .category */
   /* TODO: Values are not based on photon's 4px base distance, see bug 1501638 */
@@ -31,56 +30,74 @@
   --success-50: #30e60b;
   --warning-50: #ffe900;
   --warning-90: #3e2800;
   --error-50: #ff0039;
   --error-60: #d70022;
   --highlight-50: #0a84ff;
   --grey-20: #ededf0;
   --grey-30: #d7d7db; /* for ui, no special semantic */
+  --grey-90: #0c0c0d;
+  --grey-90-a10: rgba(12, 12, 13, 0.1);
+  --grey-90-a20: rgba(12, 12, 13, 0.2);
+  --grey-90-a30: rgba(12, 12, 13, 0.3);
   --grey-90-a60: rgba(12, 12, 13, 0.6);
   --grey-90-a80: rgba(12, 12, 13, 0.8);
   --white-100: #fff; /* for ui, no special semantic */
 
+  /* Typography from Photon */
+  --body-10-font-size: 13px;
+  --body-10-font-weight: 400;
+  --body-20-font-size: 15px;
+  --body-20-font-weight: 700;
+  --caption-20-font-size: 13px;
+  --caption-20-font-weight: 400;
+  --title-20-font-size: 17px;
+  --title-20-font-weight: 600;
+
   /* Global layout vars */
   --page-width: 664px;
   --base-unit: 4px;
 
   /* Global styles */
   --base-font-style: message-box;
-  --base-font-size: 15px; /* root font of 11px * 1.36em = 15px */
+  --base-font-size: var(--body-10-font-size);
+  --base-font-weight: var(--body-10-font-weight);
   --base-line-height: 1.8;
+  --button-font-size: var(--base-font-size);
   --micro-font-size: 11px;
 
   --monospace-font-family: monospace;
 
   /*
   * Variables particular to about:debugging
   */
   --alt-heading-icon-size: calc(var(--base-unit) * 6);
   --alt-heading-icon-gap: var(--base-unit);
   --main-heading-icon-size: calc(var(--base-unit) * 16);
   --main-heading-icon-gap: calc(var(--base-unit) * 3);
-  --main-subheading-icon-size: calc(var(--base-unit) * 5);
+  --main-subheading-icon-size: calc(var(--base-unit) * 4);
   --main-subheading-heading-icon-gap: calc(var(--base-unit) * 2);
 }
 
 /*
 * Reset some tags
 */
 
 html {
   font: var(--base-font-style);
 }
 
 body {
   margin: 0;
   padding: 0;
   color: var(--text-color);
   font-size: var(--base-font-size);
+  font-weight: var(--base-font-weight);
+  line-height: var(--base-line-height);
   background: var(--bg-color);
 }
 
 dd {
   margin: 0;
   padding: 0;
 }
 
@@ -156,20 +173,19 @@ p {
 }
 
 /* Main style for a subheading (i.e. h2). It features an icon */
 /* +--------+-------------+
 *  | [Icon] | Lorem ipsum |
 *  +--------+-------------+
 */
 .main-subheading {
-  margin-block-start: calc(var(--base-unit) * 4);
-  font-weight: 600;
-  font-size: 1.46em; /* import from .header-name in common.inc.css */
-  line-height: 1.3em; /* import from .header-name in common.inc.css */
+  margin-block: calc(var(--base-unit) * 4);
+  font-size: var(--title-20-font-size); /* Note: this is from Photon Title 20 */
+  font-weight: var(--title-20-font-weight); /* Note: this is from Photon Title 20 */
 
   display: grid;
   grid-template-columns: var(--main-subheading-icon-size) 1fr;
   grid-column-gap: var(--main-subheading-heading-icon-gap);
   align-items: center;
 }
 
 .main-subheading__icon {
@@ -236,37 +252,41 @@ p {
 Form controls
 */
 .default-button, .default-input {
   box-sizing: border-box;
   font-size: 1em;
 }
 
 /* standard, normal button */
-.default-button, .default-select {
+.default-button {
   -moz-appearance: none;
-  font-size: 1em;
-  color: var(--text-color);
-  background-color: var(--page-background);
+  color: var(--grey-90); /* Note: this is from Photon Default button */
+  background-color: var(--grey-90-a10); /* Note: this is from Photon Default button */
+  font-size: var(--button-font-size); /* Note: this is from Photon Default button */
 
   margin: 0;
   height: calc(var(--base-unit) * 8); /* Note: this is from Photon, not common.css */
   padding-inline-start: calc(var(--base-unit) * 5);
   padding-inline-end: calc(var(--base-unit) * 5);
 
-  border: 1px solid var(--box-border-color);
+  border: none;
   border-radius: calc(var(--base-unit) / 2);
 }
 
 .default-button:enabled:hover {
-  background: var(--box-background-hover)
+  background: var(--grey-90-a20); /* Note: this is from Photon Default button */
+}
+
+.default-button:enabled:active {
+  background: var(--grey-90-a30); /* Note: this is from Photon Default button */
 }
 
 .default-button:disabled {
-  opacity: 0.4;
+  opacity: 0.4; /* Note: this is from Photon Default button */
 }
 
 /* smaller size for a default button */
 .default-button--micro {
   padding-inline-start: calc(2 * var(--base-unit));
   padding-inline-end: calc(2 * var(--base-unit));
   font-size: var(--micro-font-size);
   height: calc(var(--base-unit) * 6);
@@ -337,13 +357,24 @@ Form controls
 .badge--warning {
   background: var(--warning-50);
 }
 
 .badge--error {
   background: var(--error-50);
 }
 
+/*
+ * Card style which is used in debug target item and so on.
+ */
+.card {
+  background-color: var(--white-100); /* from common.inc.css */
+  border-radius: var(--base-unit); /* from common.inc.css */
+  box-shadow: 0 1px 4px var(--grey-90-a10); /* from common.inc.css */
+  padding-block: calc(var(--base-unit) * 3) calc(var(--base-unit) * 2);
+  padding-inline: calc(var(--base-unit) * 3) calc(var(--base-unit) * 2);
+}
+
 .undecorated-link,
 .undecorated-link:hover {
   text-decoration: none;
   color: currentColor;
 }
--- a/devtools/client/aboutdebugging-new/src/components/App.css
+++ b/devtools/client/aboutdebugging-new/src/components/App.css
@@ -27,16 +27,17 @@
   height: 100vh;
   overflow: hidden; /* we don't want the sidebar to scroll, only the main content */
 
   display: grid;
   grid-column-gap: 40px;
   grid-template-columns: var(--sidebar-width) auto;
 
   font-size: var(--base-font-size);
+  font-weight: var(--base-font-weight);
   line-height: var(--base-line-height);
 }
 
 .app__sidebar {
   padding-block-start: var(--app-top-padding);
   padding-block-end: var(--app-bottom-padding);
   padding-inline-start: var(--app-left-padding);
 }
--- a/devtools/client/aboutdebugging-new/src/components/RuntimePage.js
+++ b/devtools/client/aboutdebugging-new/src/components/RuntimePage.js
@@ -24,17 +24,18 @@ const RuntimeInfo = createFactory(requir
 const ServiceWorkerAction = createFactory(require("./debugtarget/ServiceWorkerAction"));
 const ServiceWorkersWarning = createFactory(require("./ServiceWorkersWarning"));
 const TabDetail = createFactory(require("./debugtarget/TabDetail"));
 const TemporaryExtensionAction = createFactory(require("./debugtarget/TemporaryExtensionAction"));
 const TemporaryExtensionDetail = createFactory(require("./debugtarget/TemporaryExtensionDetail"));
 const WorkerDetail = createFactory(require("./debugtarget/WorkerDetail"));
 
 const Actions = require("../actions/index");
-const { DEBUG_TARGET_PANE, MESSAGE_LEVEL, PAGE_TYPES } = require("../constants");
+const { DEBUG_TARGETS, DEBUG_TARGET_PANE, MESSAGE_LEVEL, PAGE_TYPES } =
+  require("../constants");
 const Types = require("../types/index");
 
 const { getCurrentRuntimeDetails } = require("../modules/runtimes-state-helper");
 const { isSupportedDebugTargetPane } = require("../modules/debug-target-support");
 
 class RuntimePage extends PureComponent {
   static get propTypes() {
     return {
@@ -55,17 +56,30 @@ class RuntimePage extends PureComponent 
 
   // TODO: avoid the use of this method
   // https://bugzilla.mozilla.org/show_bug.cgi?id=1508688
   componentWillMount() {
     const { dispatch, runtimeId } = this.props;
     dispatch(Actions.selectPage(PAGE_TYPES.RUNTIME, runtimeId));
   }
 
-  renderDebugTargetPane(name, targets, actionComponent,
+  getIconByType(type) {
+    switch (type) {
+      case DEBUG_TARGETS.EXTENSION:
+        return "chrome://devtools/skin/images/debugging-addons.svg";
+      case DEBUG_TARGETS.TAB:
+        return "chrome://devtools/skin/images/debugging-tabs.svg";
+      case DEBUG_TARGETS.WORKER:
+        return "chrome://devtools/skin/images/debugging-workers.svg";
+    }
+
+    throw new Error(`Unsupported type [${ type }]`);
+  }
+
+  renderDebugTargetPane(name, icon, targets, actionComponent,
                         detailComponent, paneKey, localizationId) {
     const { collapsibilities, dispatch, runtimeDetails } = this.props;
 
     if (!isSupportedDebugTargetPane(runtimeDetails.info.type, paneKey)) {
       return null;
     }
 
     return Localized(
@@ -73,16 +87,17 @@ class RuntimePage extends PureComponent 
         id: localizationId,
         attrs: { name: true },
       },
       DebugTargetPane({
         actionComponent,
         collapsibilityKey: paneKey,
         detailComponent,
         dispatch,
+        icon,
         isCollapsed: collapsibilities.get(paneKey),
         name,
         targets,
       })
     );
   }
 
   renderTemporaryExtensionInstallError() {
@@ -143,46 +158,52 @@ class RuntimePage extends PureComponent 
         className: "page js-runtime-page",
       },
       RuntimeInfo(runtimeDetails.info),
       RuntimeActions({ dispatch, runtimeId, runtimeDetails }),
       runtimeDetails.serviceWorkersAvailable ? null : ServiceWorkersWarning(),
       CompatibilityWarning({ compatibilityReport }),
       this.renderTemporaryExtensionInstallError(),
       this.renderDebugTargetPane("Temporary Extensions",
+                                 this.getIconByType(DEBUG_TARGETS.EXTENSION),
                                  temporaryExtensions,
                                  TemporaryExtensionAction,
                                  TemporaryExtensionDetail,
                                  DEBUG_TARGET_PANE.TEMPORARY_EXTENSION,
                                  "about-debugging-runtime-temporary-extensions"),
       this.renderDebugTargetPane("Extensions",
+                                 this.getIconByType(DEBUG_TARGETS.EXTENSION),
                                  installedExtensions,
                                  ExtensionAction,
                                  ExtensionDetail,
                                  DEBUG_TARGET_PANE.INSTALLED_EXTENSION,
                                  "about-debugging-runtime-extensions"),
       this.renderDebugTargetPane("Tabs",
+                                 this.getIconByType(DEBUG_TARGETS.TAB),
                                  tabs,
                                  InspectAction,
                                  TabDetail,
                                  DEBUG_TARGET_PANE.TAB,
                                  "about-debugging-runtime-tabs"),
       this.renderDebugTargetPane("Service Workers",
+                                 this.getIconByType(DEBUG_TARGETS.WORKER),
                                  serviceWorkers,
                                  ServiceWorkerAction,
                                  WorkerDetail,
                                  DEBUG_TARGET_PANE.SERVICE_WORKER,
                                  "about-debugging-runtime-service-workers"),
       this.renderDebugTargetPane("Shared Workers",
+                                 this.getIconByType(DEBUG_TARGETS.WORKER),
                                  sharedWorkers,
                                  InspectAction,
                                  WorkerDetail,
                                  DEBUG_TARGET_PANE.SHARED_WORKER,
                                  "about-debugging-runtime-shared-workers"),
       this.renderDebugTargetPane("Other Workers",
+                                 this.getIconByType(DEBUG_TARGETS.WORKER),
                                  otherWorkers,
                                  InspectAction,
                                  WorkerDetail,
                                  DEBUG_TARGET_PANE.OTHER_WORKER,
                                  "about-debugging-runtime-other-workers"),
 
       showProfilerDialog ? ProfilerDialog({ dispatch, runtimeDetails }) : null,
     );
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.css
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.css
@@ -9,35 +9,36 @@
  *  | [Icon] | Name                        | Action button  |
  *  +--------+-----------------------------+----------------+
  *  |        | Detail                                       |
  *  |        |                                              |
  *  +--------+----------------------------------------------+
  */
 .debug-target-item {
   display: grid;
-  grid-template-columns: calc(var(--base-unit) * 9) 1fr max-content;
+  grid-template-columns: calc(var(--base-unit) * 8) 1fr max-content;
   grid-column-gap: calc(var(--base-unit) * 2);
   grid-template-areas: "icon name   action"
                        ".    detail detail";
   margin-block-end: calc(var(--base-unit) * 4);
 }
 
 .debug-target-item__icon {
   grid-area: icon;
   width: 100%;
 }
 
 .debug-target-item__name {
   grid-area: name;
-  /* so as to ellipsis */
-  min-width: 0;
-  font-size: calc(var(--base-unit) * 5);
+  font-size: var(--body-20-font-size);
+  font-weight: var(--body-20-font-weight);
 }
 
 .debug-target-item__action {
   grid-area: action;
   align-self: center;
 }
 
 .debug-target-item__detail {
   grid-area: detail;
+  font-size: var(--caption-20-font-size);
+  font-weight: var(--caption-20-font-weight);
 }
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.js
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.js
@@ -58,17 +58,17 @@ class DebugTargetItem extends PureCompon
       },
       this.props.target.name,
     );
   }
 
   render() {
     return dom.li(
       {
-        className: "debug-target-item js-debug-target-item",
+        className: "card debug-target-item js-debug-target-item",
       },
       this.renderIcon(),
       this.renderName(),
       this.renderAction(),
       this.renderDetail(),
     );
   }
 }
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.css
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.css
@@ -1,12 +1,11 @@
 /* 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/. */
 
 .debug-target-list {
-  margin-inline-start: calc(var(--base-unit) * 6);
   overflow: hidden;
 }
 
 .debug-target-list--collapsed {
   max-height: 0;
 }
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetPane.css
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetPane.css
@@ -1,15 +1,27 @@
 /* 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/. */
 
+/*
+ *  Style for the heading of a debug target pane
+ *  +-----------------+---------------+-----------------+
+ *  | [Category icon] | Category name | [Collapse icon] |
+ *  +-----------------+---------------+-----------------+
+ */
+.debug-target-pane__heading {
+  grid-template-columns: var(--main-subheading-icon-size) max-content calc(var(--base-unit) * 3);
+  -moz-user-select: none;
+}
+
 .debug-target-pane__icon {
   transition: transform 150ms cubic-bezier(.07, .95, 0, 1);
+  transform: rotate(90deg);
 }
 
 .debug-target-pane__icon--collapsed {
-  transform: rotate(-90deg);
+  transform: rotate(0deg);
 }
 
 .debug-target-pane__title {
   cursor: pointer;
-}
\ No newline at end of file
+}
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetPane.js
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetPane.js
@@ -3,78 +3,94 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
+const FluentReact = require("devtools/client/shared/vendor/fluent-react");
+
 const DebugTargetList = createFactory(require("./DebugTargetList"));
 
 const Actions = require("../../actions/index");
 const Types = require("../../types/index");
 
 /**
  * This component provides list for debug target and name area.
  */
 class DebugTargetPane extends PureComponent {
   static get propTypes() {
     return {
       actionComponent: PropTypes.any.isRequired,
       collapsibilityKey: PropTypes.string.isRequired,
       detailComponent: PropTypes.any.isRequired,
       dispatch: PropTypes.func.isRequired,
+      // Provided by wrapping the component with FluentReact.withLocalization.
+      getString: PropTypes.func.isRequired,
+      icon: PropTypes.string.isRequired,
       isCollapsed: PropTypes.bool.isRequired,
       name: PropTypes.string.isRequired,
       targets: PropTypes.arrayOf(Types.debugTarget).isRequired,
     };
   }
 
   toggleCollapsibility() {
     const { collapsibilityKey, dispatch, isCollapsed } = this.props;
     dispatch(Actions.updateDebugTargetCollapsibility(collapsibilityKey, !isCollapsed));
   }
 
   render() {
     const {
       actionComponent,
       detailComponent,
       dispatch,
+      getString,
+      icon,
       isCollapsed,
       name,
       targets,
     } = this.props;
 
+    const title = getString("about-debugging-collapse-expand-debug-targets");
+
     return dom.section(
       {
         className: "js-debug-target-pane",
       },
       dom.a(
         {
           className: "undecorated-link debug-target-pane__title " +
-            "js-debug-target-pane-title",
+                     "js-debug-target-pane-title",
+          title,
           onClick: e => this.toggleCollapsibility(),
         },
         dom.h2(
-          { className: "main-subheading" },
+          { className: "main-subheading debug-target-pane__heading" },
+          dom.img(
+            {
+              className: "main-subheading__icon",
+              src: icon,
+            }
+          ),
+          `${ name } (${ targets.length })`,
           dom.img(
             {
               className: "main-subheading__icon debug-target-pane__icon" +
-                         (isCollapsed ? " debug-target-pane__icon--collapsed" : ""),
-              src: "chrome://devtools/skin/images/aboutdebugging-collapse-icon.svg",
+                            (isCollapsed ? " debug-target-pane__icon--collapsed" : ""),
+              src: "chrome://devtools/skin/images/arrow-e.svg",
             }
           ),
-          name + (isCollapsed ? ` (${ targets.length })` : ""),
         )
       ),
       DebugTargetList({
         actionComponent,
         detailComponent,
         dispatch,
         isCollapsed,
         targets,
       }),
     );
   }
 }
 
-module.exports = DebugTargetPane;
+module.exports = FluentReact.withLocalization(DebugTargetPane);
--- a/devtools/client/aboutdebugging-new/src/middleware/event-recording.js
+++ b/devtools/client/aboutdebugging-new/src/middleware/event-recording.js
@@ -10,22 +10,25 @@ loader.lazyGetter(this, "telemetry", () 
 loader.lazyGetter(this, "sessionId", () => parseInt(telemetry.msSinceProcessStart(), 10));
 
 const {
   CONNECT_RUNTIME_SUCCESS,
   DISCONNECT_RUNTIME_SUCCESS,
   REMOTE_RUNTIMES_UPDATED,
   RUNTIMES,
   SELECT_PAGE_SUCCESS,
+  SHOW_PROFILER_DIALOG,
   TELEMETRY_RECORD,
+  UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS,
 } = require("../constants");
 
 const {
   findRuntimeById,
   getAllRuntimes,
+  getCurrentRuntime,
 } = require("../modules/runtimes-state-helper");
 
 function recordEvent(method, details) {
   // Add the session id to the event details.
   const eventDetails = Object.assign({}, details, { "session_id": sessionId });
   telemetry.recordEvent(method, "aboutdebugging", null, eventDetails);
 }
 
@@ -35,16 +38,21 @@ const telemetryRuntimeIds = new Map();
 function getTelemetryRuntimeId(id) {
   if (!telemetryRuntimeIds.has(id)) {
     const randomId = (Math.random() * 100000) | 0;
     telemetryRuntimeIds.set(id, "runtime-" + randomId);
   }
   return telemetryRuntimeIds.get(id);
 }
 
+function getCurrentRuntimeIdForTelemetry(store) {
+  const id = getCurrentRuntime(store.getState().runtimes).id;
+  return getTelemetryRuntimeId(id);
+}
+
 function getRuntimeEventExtras(runtime) {
   const { extra, runtimeDetails } = runtime;
 
   // deviceName can be undefined for non-usb devices, but we should not log "undefined".
   const deviceName = extra && extra.deviceName || "";
   const runtimeShortName = runtime.type === RUNTIMES.USB ? runtime.name : "";
   const runtimeName = runtimeDetails && runtimeDetails.info.name || "";
   return {
@@ -140,23 +148,34 @@ function eventRecordingMiddleware(store)
         onDisconnectRuntimeSuccess(action, store);
         break;
       case REMOTE_RUNTIMES_UPDATED:
         onRemoteRuntimesUpdated(action, store);
         break;
       case SELECT_PAGE_SUCCESS:
         recordEvent("select_page", { "page_type": action.page });
         break;
+      case SHOW_PROFILER_DIALOG:
+        recordEvent("show_profiler", {
+          "runtime_id": getCurrentRuntimeIdForTelemetry(store),
+        });
+        break;
       case TELEMETRY_RECORD:
         const { method, details } = action;
         if (method) {
           recordEvent(method, details);
         } else {
           console.error(`[RECORD EVENT FAILED] ${action.type}: no "method" property`);
         }
         break;
+      case UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS:
+        recordEvent("update_conn_prompt", {
+          "prompt_enabled": `${action.connectionPromptEnabled}`,
+          "runtime_id": getCurrentRuntimeIdForTelemetry(store),
+        });
+        break;
     }
 
     return next(action);
   };
 }
 
 module.exports = eventRecordingMiddleware;
--- a/devtools/client/aboutdebugging-new/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging-new/test/browser/browser.ini
@@ -92,15 +92,16 @@ skip-if = (os == 'linux' && bits == 32) 
 [browser_aboutdebugging_sidebar_usb_unknown_runtime.js]
 [browser_aboutdebugging_stop_adb.js]
 skip-if = (os == 'linux' && bits == 32) # ADB start() fails on linux 32, see Bug 1499638
 [browser_aboutdebugging_system_addons.js]
 [browser_aboutdebugging_tab_favicons.js]
 [browser_aboutdebugging_telemetry_basic.js]
 [browser_aboutdebugging_telemetry_inspect.js]
 [browser_aboutdebugging_telemetry_navigate.js]
+[browser_aboutdebugging_telemetry_runtime_actions.js]
 [browser_aboutdebugging_telemetry_runtime_updates.js]
 [browser_aboutdebugging_telemetry_runtime_updates_multi.js]
 [browser_aboutdebugging_telemetry_runtime_updates_network.js]
 [browser_aboutdebugging_thisfirefox.js]
 [browser_aboutdebugging_thisfirefox_runtime_info.js]
 [browser_aboutdebugging_thisfirefox_worker_inspection.js]
 [browser_aboutdebugging_workers_remote_runtime.js]
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_debug-target-pane_collapsibilities_interaction.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_debug-target-pane_collapsibilities_interaction.js
@@ -46,10 +46,12 @@ async function assertDebugTargetExpanded
   info("Check debug target is expanded");
 
   // check list height
   const listEl = paneEl.querySelector(".js-debug-target-list");
   await waitUntil(() => listEl.clientHeight > 0);
   ok(true, "Height of list element is greater than zero");
   // check title
   const titleEl = paneEl.querySelector(".js-debug-target-pane-title");
-  is(titleEl.textContent, title, "Expanded title is correct");
+  const expectedTitle =
+    `${ title } (${ listEl.querySelectorAll(".js-debug-target-item").length })`;
+  is(titleEl.textContent, expectedTitle, "Expanded title is correct");
 }
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_profiler_dialog.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_profiler_dialog.js
@@ -60,21 +60,8 @@ function assertDialogVisible(doc) {
   ok(doc.querySelector(".js-profiler-dialog"), "Dialog is displayed");
   ok(doc.querySelector(".js-profiler-dialog-mask"), "Dialog mask is displayed");
 }
 
 function assertDialogHidden(doc) {
   ok(!document.querySelector(".js-profiler-dialog"), "Dialog is removed");
   ok(!document.querySelector(".js-profiler-dialog-mask"), "Dialog mask is removed");
 }
-
-function openProfilerDialog(client, doc) {
-  const onProfilerLoaded = new Promise(r => {
-    client.loadPerformanceProfiler = r;
-  });
-
-  info("Click on the Profile Runtime button");
-  const profileButton = doc.querySelector(".js-profile-runtime-button");
-  profileButton.click();
-
-  info("Wait for the loadPerformanceProfiler callback to be executed on client-wrapper");
-  return onProfilerLoaded;
-}
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_telemetry_runtime_actions.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from helper-telemetry.js */
+Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-telemetry.js", this);
+
+const RUNTIME_ID = "test-runtime-id";
+const RUNTIME_NAME = "Test Runtime";
+const RUNTIME_DEVICE_NAME = "Test Device";
+
+/**
+ * Test that runtime specific actions are logged as telemetry events with the expected
+ * runtime id and action type.
+ */
+add_task(async function testUsbRuntimeUpdates() {
+  // enable USB devices mocks
+  const mocks = new Mocks();
+  setupTelemetryTest();
+
+  const { tab, document } = await openAboutDebugging();
+
+  const sessionId = getOpenEventSessionId();
+  ok(!isNaN(sessionId), "Open event has a valid session id");
+
+  const usbClient = mocks.createUSBRuntime(RUNTIME_ID, {
+    deviceName: RUNTIME_DEVICE_NAME,
+    name: RUNTIME_NAME,
+    shortName: RUNTIME_NAME,
+  });
+  mocks.emitUSBUpdate();
+
+  info("Wait for the runtime to appear in the sidebar");
+  await waitUntil(() => findSidebarItemByText(RUNTIME_NAME, document));
+  await connectToRuntime(RUNTIME_DEVICE_NAME, document);
+  await selectRuntime(RUNTIME_DEVICE_NAME, RUNTIME_NAME, document);
+
+  info("Read telemetry events to flush unrelated events");
+  const evts = readAboutDebuggingEvents();
+  const runtimeAddedEvent = evts.filter(e => e.method === "runtime_added")[0];
+  const telemetryRuntimeId = runtimeAddedEvent.extras.runtime_id;
+
+  info("Click on the toggle button and wait until the text is updated");
+  const promptButton = document.querySelector(".js-connection-prompt-toggle-button");
+  promptButton.click();
+  await waitUntil(() => promptButton.textContent.includes("Enable"));
+
+  checkTelemetryEvents([{
+    method: "update_conn_prompt",
+    extras: { "prompt_enabled": "false", "runtime_id": telemetryRuntimeId },
+  }], sessionId);
+
+  info("Click on the toggle button again and check we log the correct value");
+  promptButton.click();
+  await waitUntil(() => promptButton.textContent.includes("Disable"));
+
+  checkTelemetryEvents([{
+    method: "update_conn_prompt",
+    extras: { "prompt_enabled": "true", "runtime_id": telemetryRuntimeId },
+  }], sessionId);
+
+  info("Open the profiler dialog");
+  await openProfilerDialog(usbClient, document);
+
+  checkTelemetryEvents([{
+    method: "show_profiler",
+    extras: { "runtime_id": telemetryRuntimeId },
+  }], sessionId);
+
+  info("Remove runtime");
+  mocks.removeRuntime(RUNTIME_ID);
+  mocks.emitUSBUpdate();
+  await waitUntil(() => !findSidebarItemByText(RUNTIME_NAME, document));
+
+  await removeTab(tab);
+});
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_thisfirefox.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_thisfirefox.js
@@ -28,15 +28,17 @@ add_task(async function() {
     "The selected sidebar item is This Firefox");
 
   const paneTitlesEls = document.querySelectorAll(".js-debug-target-pane-title");
   is(paneTitlesEls.length, EXPECTED_TARGET_PANES.length,
     "This Firefox has the expecte number of debug target categories");
 
   const paneTitles = [...paneTitlesEls].map(el => el.textContent);
 
-  EXPECTED_TARGET_PANES.forEach(expectedPane => {
-    ok(paneTitles.includes(expectedPane),
-      "Expected debug target category found: " + expectedPane);
-  });
+  for (let i = 0; i < EXPECTED_TARGET_PANES.length; i++) {
+    const expectedPaneTitle = EXPECTED_TARGET_PANES[i];
+    const actualPaneTitle = paneTitles[i];
+    ok(actualPaneTitle.startsWith(expectedPaneTitle),
+       `Expected debug target category found: ${ expectedPaneTitle }`);
+  }
 
   await removeTab(tab);
 });
--- a/devtools/client/aboutdebugging-new/test/browser/head.js
+++ b/devtools/client/aboutdebugging-new/test/browser/head.js
@@ -262,8 +262,25 @@ async function selectRuntime(deviceName,
     const runtimeInfo = document.querySelector(".js-runtime-info");
     return runtimeInfo && runtimeInfo.textContent.includes(name);
   });
 }
 
 function getToolbox(win) {
   return gDevTools.getToolboxes().find(toolbox => toolbox.win === win);
 }
+
+/**
+ * Open the performance profiler dialog. Assumes the client is a mocked remote runtime
+ * client.
+ */
+async function openProfilerDialog(client, doc) {
+  const onProfilerLoaded = new Promise(r => {
+    client.loadPerformanceProfiler = r;
+  });
+
+  info("Click on the Profile Runtime button");
+  const profileButton = doc.querySelector(".js-profile-runtime-button");
+  profileButton.click();
+
+  info("Wait for the loadPerformanceProfiler callback to be executed on client-wrapper");
+  return onProfilerLoaded;
+}
--- a/devtools/client/aboutdebugging-new/tmp-locale/en-US/aboutdebugging.notftl
+++ b/devtools/client/aboutdebugging-new/tmp-locale/en-US/aboutdebugging.notftl
@@ -301,8 +301,13 @@ about-debugging-page-title = { -applicat
 # Title of a modal dialog displayed on remote runtime pages after clicking on the Profile Runtime button.
 about-debugging-profiler-dialog-title = Performance Profiler
 
 # Label of a checkbox displayed in the runtime page for "This Firefox".
 # This checkbox will toggle preferences that enable local addon debugging.
 # The "Learn more" link points to MDN.
 # https://developer.mozilla.org/docs/Tools/about:debugging#Enabling_add-on_debugging
 about-debugging-extension-debug-setting-label = Enable extension debugging <a>Learn more</a>
+
+# Clicking on the header of a debug target category will expand or collapse the debug
+# target items in the category. This text is used as ’title’ attribute of the header,
+# to describe this feature.
+about-debugging-collapse-expand-debug-targets = Collapse / expand
--- a/devtools/client/inspector/changes/ChangesView.js
+++ b/devtools/client/inspector/changes/ChangesView.js
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
 
 loader.lazyRequireGetter(this, "ChangesContextMenu", "devtools/client/inspector/changes/ChangesContextMenu");
-loader.lazyRequireGetter(this, "prettifyCSS", "devtools/shared/inspector/css-logic", true);
 loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
 
 const ChangesApp = createFactory(require("./components/ChangesApp"));
 const { getChangesStylesheet } = require("./selectors/changes");
 
 const {
   TELEMETRY_SCALAR_CONTEXTMENU,
   TELEMETRY_SCALAR_CONTEXTMENU_COPY,
@@ -153,18 +152,17 @@ class ChangesView {
    * and copies it to the clipboard.
    *
    * @param {String} ruleId
    *        Rule id of the target CSS rule.
    */
   async copyRule(ruleId) {
     const rule = await this.inspector.pageStyle.getRule(ruleId);
     const text = await rule.getRuleText();
-    const prettyCSS = prettifyCSS(text);
-    clipboardHelper.copyString(prettyCSS);
+    clipboardHelper.copyString(text);
   }
 
   /**
    * Handler for the "Copy" option from the context menu.
    * Copies the current text selection to the clipboard.
    */
   copySelection() {
     clipboardHelper.copyString(this.window.getSelection().toString());
--- a/devtools/client/inspector/markup/markup-context-menu.js
+++ b/devtools/client/inspector/markup/markup-context-menu.js
@@ -7,17 +7,16 @@
 "use strict";
 
 const Services = require("Services");
 const promise = require("promise");
 const { LocalizationHelper } = require("devtools/shared/l10n");
 
 loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu");
 loader.lazyRequireGetter(this, "MenuItem", "devtools/client/framework/menu-item");
-loader.lazyRequireGetter(this, "copyLongString", "devtools/client/inspector/shared/utils", true);
 loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
 
 loader.lazyGetter(this, "TOOLBOX_L10N", function() {
   return new LocalizationHelper("devtools/client/locales/toolbox.properties");
 });
 
 const INSPECTOR_L10N =
   new LocalizationHelper("devtools/client/locales/inspector.properties");
@@ -96,31 +95,23 @@ class MarkupContextMenu {
       container.copyImageDataUri();
     }
   }
 
   /**
    * Copy the innerHTML of the selected Node to the clipboard.
    */
   _copyInnerHTML() {
-    if (!this.selection.isNode()) {
-      return;
-    }
-
-    copyLongString(this.walker.innerHTML(this.selection.nodeFront));
+    this.markup.copyInnerHTML();
   }
 
   /**
    * Copy the outerHTML of the selected Node to the clipboard.
    */
   _copyOuterHTML() {
-    if (!this.selection.isNode()) {
-      return;
-    }
-
     this.markup.copyOuterHTML();
   }
 
   /**
    * Copy a unique selector of the selected Node to the clipboard.
    */
   _copyUniqueSelector() {
     if (!this.selection.isNode()) {
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -18,22 +18,23 @@ const {scrollIntoViewIfNeeded} = require
 const {PrefObserver} = require("devtools/client/shared/prefs");
 const MarkupElementContainer = require("devtools/client/inspector/markup/views/element-container");
 const MarkupReadOnlyContainer = require("devtools/client/inspector/markup/views/read-only-container");
 const MarkupTextContainer = require("devtools/client/inspector/markup/views/text-container");
 const RootContainer = require("devtools/client/inspector/markup/views/root-container");
 
 loader.lazyRequireGetter(this, "MarkupContextMenu", "devtools/client/inspector/markup/markup-context-menu");
 loader.lazyRequireGetter(this, "SlottedNodeContainer", "devtools/client/inspector/markup/views/slotted-node-container");
-loader.lazyRequireGetter(this, "copyLongString", "devtools/client/inspector/shared/utils", true);
 loader.lazyRequireGetter(this, "getLongString", "devtools/client/inspector/shared/utils", true);
 loader.lazyRequireGetter(this, "openContentLink", "devtools/client/shared/link", true);
 loader.lazyRequireGetter(this, "HTMLTooltip", "devtools/client/shared/widgets/tooltip/HTMLTooltip", true);
 loader.lazyRequireGetter(this, "UndoStack", "devtools/client/shared/undo", true);
 loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
+loader.lazyRequireGetter(this, "beautify", "devtools/shared/jsbeautify/beautify");
+loader.lazyRequireGetter(this, "getTabPrefs", "devtools/shared/indentation", true);
 
 const INSPECTOR_L10N =
   new LocalizationHelper("devtools/client/locales/inspector.properties");
 
 // Page size for pageup/pagedown
 const PAGE_SIZE = 10;
 const DEFAULT_MAX_CHILDREN = 100;
 const NEW_SELECTION_HIGHLIGHTER_TIMER = 1000;
@@ -41,16 +42,17 @@ const DRAG_DROP_AUTOSCROLL_EDGE_MAX_DIST
 const DRAG_DROP_AUTOSCROLL_EDGE_RATIO = 0.1;
 const DRAG_DROP_MIN_AUTOSCROLL_SPEED = 2;
 const DRAG_DROP_MAX_AUTOSCROLL_SPEED = 8;
 const DRAG_DROP_HEIGHT_TO_SPEED = 500;
 const DRAG_DROP_HEIGHT_TO_SPEED_MIN = 0.5;
 const DRAG_DROP_HEIGHT_TO_SPEED_MAX = 1;
 const ATTR_COLLAPSE_ENABLED_PREF = "devtools.markup.collapseAttributes";
 const ATTR_COLLAPSE_LENGTH_PREF = "devtools.markup.collapseAttributeLength";
+const BEAUTIFY_HTML_ON_COPY_PREF = "devtools.markup.beautifyOnCopy";
 
 /**
  * Vocabulary for the purposes of this file:
  *
  * MarkupContainer - the structure that holds an editor and its
  *  immediate children in the markup panel.
  *  - MarkupElementContainer: markup container for element nodes
  *  - MarkupTextContainer: markup container for text / comment nodes
@@ -797,30 +799,41 @@ MarkupView.prototype = {
   copyOuterHTML: function() {
     if (!this.inspector.selection.isNode()) {
       return;
     }
     const node = this.inspector.selection.nodeFront;
 
     switch (node.nodeType) {
       case nodeConstants.ELEMENT_NODE :
-        copyLongString(this.walker.outerHTML(node));
+        copyLongHTMLString(this.walker.outerHTML(node));
         break;
       case nodeConstants.COMMENT_NODE :
         getLongString(node.getNodeValue()).then(comment => {
           clipboardHelper.copyString("<!--" + comment + "-->");
         });
         break;
       case nodeConstants.DOCUMENT_TYPE_NODE :
         clipboardHelper.copyString(node.doctypeString);
         break;
     }
   },
 
   /**
+   * Copy the innerHTML of the selected Node to the clipboard.
+   */
+  copyInnerHTML: function() {
+    if (!this.inspector.selection.isNode()) {
+      return;
+    }
+
+    copyLongHTMLString(this.walker.innerHTML(this.inspector.selection.nodeFront));
+  },
+
+  /**
    * Given a type and link found in a node's attribute in the markup-view,
    * attempt to follow that link (which may result in opening a new tab, the
    * style editor or debugger).
    */
   followAttributeLink: function(type, link) {
     if (!type || !link) {
       return;
     }
@@ -1462,22 +1475,17 @@ MarkupView.prototype = {
     let walkerPromise = null;
 
     if (isOuter) {
       walkerPromise = this.walker.outerHTML(node);
     } else {
       walkerPromise = this.walker.innerHTML(node);
     }
 
-    return walkerPromise.then(longstr => {
-      return longstr.string().then(html => {
-        longstr.release().catch(console.error);
-        return html;
-      });
-    });
+    return getLongString(walkerPromise);
   },
 
   /**
    * Retrieve the outerHTML for a remote node.
    *
    * @param  {NodeFront} node
    *         The NodeFront to get the outerHTML for.
    * @return {Promise} that will be resolved with the outerHTML.
@@ -2184,16 +2192,43 @@ MarkupView.prototype = {
       return null;
     }
 
     return {parent, nextSibling};
   },
 };
 
 /**
+ * Copy the content of a longString containing HTML code to the clipboard.
+ * The string is retrieved, and possibly beautified if the user has the right pref set and
+ * then placed in the clipboard.
+ *
+ * @param  {Promise} longStringActorPromise
+ *         The promise expected to resolve a LongStringActor instance
+ */
+async function copyLongHTMLString(longStringActorPromise) {
+  let string = await getLongString(longStringActorPromise);
+
+  if (Services.prefs.getBoolPref(BEAUTIFY_HTML_ON_COPY_PREF)) {
+    const { indentUnit, indentWithTabs } = getTabPrefs();
+    string = beautify.html(string, {
+      // eslint-disable-next-line camelcase
+      preserve_newlines: false,
+      // eslint-disable-next-line camelcase
+      indent_size: indentWithTabs ? 1 : indentUnit,
+      // eslint-disable-next-line camelcase
+      indent_char: indentWithTabs ? "\t" : " ",
+      unformatted: [],
+    });
+  }
+
+  clipboardHelper.copyString(string);
+}
+
+/**
  * Map a number from one range to another.
  */
 function map(value, oldMin, oldMax, newMin, newMax) {
   const ratio = oldMax - oldMin;
   if (ratio == 0) {
     return value;
   }
   return newMin + (newMax - newMin) * ((value - oldMin) / ratio);
--- a/devtools/client/inspector/markup/test/browser.ini
+++ b/devtools/client/inspector/markup/test/browser.ini
@@ -94,16 +94,18 @@ skip-if = os == "mac" # Full keyboard na
 [browser_markup_accessibility_new_selection.js]
 [browser_markup_accessibility_navigation_after_edit.js]
 skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
 [browser_markup_accessibility_semantics.js]
 [browser_markup_anonymous_01.js]
 [browser_markup_anonymous_02.js]
 [browser_markup_anonymous_03.js]
 [browser_markup_anonymous_04.js]
+[browser_markup_copy_html.js]
+subsuite = clipboard
 [browser_markup_copy_image_data.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_markup_css_completion_style_attribute_01.js]
 [browser_markup_css_completion_style_attribute_02.js]
 [browser_markup_css_completion_style_attribute_03.js]
 [browser_markup_display_node_01.js]
 [browser_markup_display_node_02.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/test/browser_markup_copy_html.js
@@ -0,0 +1,83 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the copy inner and outer html menu options.
+
+// The nicely formatted HTML code.
+const FORMATTED_HTML =
+`<body>
+  <style>
+    div {
+      color: red;
+    }
+    span {
+      text-decoration: underline;
+    }
+  </style>
+  <div>
+    <span>
+      <em>Hello</em>
+    </span>
+  </div>
+  <script>
+    console.log("Hello!");
+  </script>
+</body>`;
+
+// The inner HTML of the body node from the code above.
+const FORMATTED_INNER_HTML = FORMATTED_HTML.replace(/<\/*body>/g, "").trim()
+                                           .replace(/^  /gm, "");
+
+// The formatted outer HTML, using tabs rather than spaces.
+const TABS_FORMATTED_HTML = FORMATTED_HTML.replace(/[ ]{2}/g, "\t");
+
+// The formatted outer HTML, using 3 spaces instead of 2.
+const THREE_SPACES_FORMATTED_HTML = FORMATTED_HTML.replace(/[ ]{2}/g, "   ");
+
+// Uglify the formatted code by removing all spaces and line breaks.
+const UGLY_HTML = FORMATTED_HTML.replace(/[\r\n\s]+/g, "");
+
+// And here is the inner html of the body node from the ugly code above.
+const UGLY_INNER_HTML = UGLY_HTML.replace(/<\/*body>/g, "");
+
+add_task(async function() {
+  // Load the ugly code in a new tab and open the inspector.
+  const { inspector } = await openInspectorForURL(
+    "data:text/html;charset=utf-8," + encodeURIComponent(UGLY_HTML));
+
+  info("Get the inner and outer html copy menu items");
+  const allMenuItems = openContextMenuAndGetAllItems(inspector);
+  const outerHtmlMenu = allMenuItems.find(({ id }) => id === "node-menu-copyouter");
+  const innerHtmlMenu = allMenuItems.find(({ id }) => id === "node-menu-copyinner");
+
+  info("Try to copy the outer html");
+  await waitForClipboardPromise(() => outerHtmlMenu.click(), UGLY_HTML);
+
+  info("Try to copy the inner html");
+  await waitForClipboardPromise(() => innerHtmlMenu.click(), UGLY_INNER_HTML);
+
+  info("Set the pref for beautifying html on copy");
+  await pushPref("devtools.markup.beautifyOnCopy", true);
+
+  info("Try to copy the beautified outer html");
+  await waitForClipboardPromise(() => outerHtmlMenu.click(), FORMATTED_HTML);
+
+  info("Try to copy the beautified inner html");
+  await waitForClipboardPromise(() => innerHtmlMenu.click(), FORMATTED_INNER_HTML);
+
+  info("Set the pref to stop expanding tabs into spaces");
+  await pushPref("devtools.editor.expandtab", false);
+
+  info("Check that the beautified outer html uses tabs");
+  await waitForClipboardPromise(() => outerHtmlMenu.click(), TABS_FORMATTED_HTML);
+
+  info("Set the pref to expand tabs to 3 spaces");
+  await pushPref("devtools.editor.expandtab", true);
+  await pushPref("devtools.editor.tabsize", 3);
+
+  info("Try to copy the beautified outer html");
+  await waitForClipboardPromise(() => outerHtmlMenu.click(), THREE_SPACES_FORMATTED_HTML);
+});
--- a/devtools/client/inspector/markup/views/text-editor.js
+++ b/devtools/client/inspector/markup/views/text-editor.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { editableField } = require("devtools/client/shared/inplace-editor");
 const { LocalizationHelper } = require("devtools/shared/l10n");
 
 loader.lazyRequireGetter(this, "getAutocompleteMaxWidth", "devtools/client/inspector/markup/utils", true);
+loader.lazyRequireGetter(this, "getLongString", "devtools/client/inspector/shared/utils", true);
 
 const INSPECTOR_L10N =
   new LocalizationHelper("devtools/client/locales/inspector.properties");
 
 /**
  * Creates a simple text editor node, used for TEXT and COMMENT
  * nodes.
  *
@@ -37,25 +38,21 @@ function TextEditor(container, node, typ
     trigger: "dblclick",
     multiline: true,
     maxWidth: () => getAutocompleteMaxWidth(this.value, this.container.elt),
     trimOutput: false,
     done: (val, commit) => {
       if (!commit) {
         return;
       }
-      this.node.getNodeValue().then(longstr => {
-        longstr.string().then(oldValue => {
-          longstr.release().catch(console.error);
-
-          this.container.undo.do(() => {
-            this.node.setNodeValue(val);
-          }, () => {
-            this.node.setNodeValue(oldValue);
-          });
+      getLongString(this.node.getNodeValue()).then(oldValue => {
+        this.container.undo.do(() => {
+          this.node.setNodeValue(val);
+        }, () => {
+          this.node.setNodeValue(oldValue);
         });
       });
     },
     cssProperties: this.markup.inspector.cssProperties,
   });
 
   this.update();
 }
@@ -93,22 +90,17 @@ TextEditor.prototype = {
     if (value === this._selected) {
       return;
     }
     this._selected = value;
     this.update();
   },
 
   update: function() {
-    let longstr = null;
-    this.node.getNodeValue().then(ret => {
-      longstr = ret;
-      return longstr.string();
-    }).then(str => {
-      longstr.release().catch(console.error);
+    getLongString(this.node.getNodeValue()).then(str => {
       this.value.textContent = str;
 
       const isWhitespace = !/[^\s]/.exec(str);
       this.value.classList.toggle("whitespace", isWhitespace);
 
       const chars = str.replace(/\n/g, "⏎")
                      .replace(/\t/g, "⇥")
                      .replace(/ /g, "◦");
--- a/devtools/client/inspector/shared/utils.js
+++ b/devtools/client/inspector/shared/utils.js
@@ -6,17 +6,16 @@
 
 "use strict";
 
 const promise = require("promise");
 
 loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true);
 loader.lazyRequireGetter(this, "getCSSLexer", "devtools/shared/css/lexer", true);
 loader.lazyRequireGetter(this, "parseDeclarations", "devtools/shared/css/parsing-utils", true);
-loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 /**
  * Called when a character is typed in a value editor.  This decides
  * whether to advance or not, first by checking to see if ";" was
  * typed, and then by lexing the input and seeing whether the ";"
  * would be a terminator at this point.
@@ -79,31 +78,16 @@ function blurOnMultipleProperties(cssPro
       if (props.length > 1) {
         e.target.blur();
       }
     }, 0);
   };
 }
 
 /**
- * Copy the content of a longString (via a promise resolving a
- * LongStringActor) to the clipboard.
- *
- * @param  {Promise} longStringActorPromise
- *         promise expected to resolve a LongStringActor instance
- * @return {Promise} promise resolving (with no argument) when the
- *         string is sent to the clipboard
- */
-function copyLongString(longStringActorPromise) {
-  return getLongString(longStringActorPromise).then(string => {
-    clipboardHelper.copyString(string);
-  }).catch(console.error);
-}
-
-/**
  * Create a child element with a set of attributes.
  *
  * @param {Element} parent
  *        The parent node.
  * @param {string} tagName
  *        The tag name.
  * @param {object} attributes
  *        A set of attributes to set on the node.
@@ -223,14 +207,13 @@ function translateNodeFrontToGrip(nodeFr
       nodeType: nodeFront.nodeType,
     },
   };
 }
 
 exports.advanceValidate = advanceValidate;
 exports.appendText = appendText;
 exports.blurOnMultipleProperties = blurOnMultipleProperties;
-exports.copyLongString = copyLongString;
 exports.createChild = createChild;
 exports.getLongString = getLongString;
 exports.getSelectorFromGrip = getSelectorFromGrip;
 exports.promiseWarn = promiseWarn;
 exports.translateNodeFrontToGrip = translateNodeFrontToGrip;
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -84,17 +84,16 @@ devtools.jar:
     skin/images/arrowhead-left.svg (themes/images/arrowhead-left.svg)
     skin/images/arrowhead-right.svg (themes/images/arrowhead-right.svg)
     skin/images/arrowhead-down.svg (themes/images/arrowhead-down.svg)
     skin/images/arrowhead-up.svg (themes/images/arrowhead-up.svg)
     skin/images/breadcrumbs-divider.svg (themes/images/breadcrumbs-divider.svg)
     skin/images/checkbox.svg (themes/images/checkbox.svg)
     skin/images/filters.svg (themes/images/filters.svg)
     skin/images/filter-swatch.svg (themes/images/filter-swatch.svg)
-    skin/images/aboutdebugging-collapse-icon.svg (themes/images/aboutdebugging-collapse-icon.svg)
     skin/images/aboutdebugging-connect-icon.svg (themes/images/aboutdebugging-connect-icon.svg)
     skin/images/aboutdebugging-firefox-aurora.svg (themes/images/aboutdebugging-firefox-aurora.svg)
     skin/images/aboutdebugging-firefox-beta.svg (themes/images/aboutdebugging-firefox-beta.svg)
     skin/images/aboutdebugging-firefox-logo.svg (themes/images/aboutdebugging-firefox-logo.svg)
     skin/images/aboutdebugging-firefox-nightly.svg (themes/images/aboutdebugging-firefox-nightly.svg)
     skin/images/aboutdebugging-firefox-release.svg (themes/images/aboutdebugging-firefox-release.svg)
     skin/images/aboutdebugging-globe-icon.svg (themes/images/aboutdebugging-globe-icon.svg)
     skin/images/aboutdebugging-usb-icon.svg (themes/images/aboutdebugging-usb-icon.svg)
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -88,16 +88,19 @@ pref("devtools.layout.boxmodel.highlight
 pref("devtools.eyedropper.zoom", 6);
 
 // Enable to collapse attributes that are too long.
 pref("devtools.markup.collapseAttributes", true);
 
 // Length to collapse attributes
 pref("devtools.markup.collapseAttributeLength", 120);
 
+// Whether to auto-beautify the HTML on copy.
+pref("devtools.markup.beautifyOnCopy", false);
+
 // DevTools default color unit
 pref("devtools.defaultColorUnit", "authored");
 
 // Enable the Memory tools
 pref("devtools.memory.enabled", true);
 
 pref("devtools.memory.custom-census-displays", "{}");
 pref("devtools.memory.custom-label-displays", "{}");
deleted file mode 100644
--- a/devtools/client/themes/images/aboutdebugging-collapse-icon.svg
+++ /dev/null
@@ -1,8 +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/. -->
-<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="context-fill #0b0b0b">
-  <path d="M4 12.5l8-5-8-5v10zm-1 0v-10a1 1 0 0 1 1.53-.848l8 5a1 1 0 0 1 0 1.696l-8 5A1 1 0 0 1 3 12.5z"
-        transform="rotate(90, 7.5, 8)"
-        fill-rule="evenodd"/>
-</svg>
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -18,16 +18,18 @@ const {pageStyleSpec, styleRuleSpec, ELE
 loader.lazyRequireGetter(this, "CssLogic", "devtools/server/actors/inspector/css-logic", true);
 loader.lazyRequireGetter(this, "SharedCssLogic", "devtools/shared/inspector/css-logic");
 loader.lazyRequireGetter(this, "getDefinedGeometryProperties",
   "devtools/server/actors/highlighters/geometry-editor", true);
 loader.lazyRequireGetter(this, "isCssPropertyKnown",
   "devtools/server/actors/css-properties", true);
 loader.lazyRequireGetter(this, "parseNamedDeclarations",
   "devtools/shared/css/parsing-utils", true);
+loader.lazyRequireGetter(this, "prettifyCSS",
+  "devtools/shared/inspector/css-logic", true);
 loader.lazyRequireGetter(this, "UPDATE_PRESERVING_RULES",
   "devtools/server/actors/stylesheets", true);
 loader.lazyRequireGetter(this, "UPDATE_GENERAL",
   "devtools/server/actors/stylesheets", true);
 loader.lazyRequireGetter(this, "findCssSelector",
   "devtools/shared/inspector/css-logic", true);
 loader.lazyRequireGetter(this, "CSSRuleTypeName",
   "devtools/shared/inspector/css-logic", true);
@@ -1392,23 +1394,28 @@ var StyleRuleActor = protocol.ActorClass
 
   /**
    * Return a promise that resolves to the authored form of a rule's
    * text, if available.  If the authored form is not available, the
    * returned promise simply resolves to the empty string.  If the
    * authored form is available, this also sets |this.authoredText|.
    * The authored text will include invalid and otherwise ignored
    * properties.
+   *
+   * @param {Boolean} skipCache
+   *        If a value for authoredText was previously found and cached,
+   *        ignore it and parse the stylehseet again. The authoredText
+   *        may be outdated if a descendant of this rule has changed.
    */
-  getAuthoredCssText: function() {
+  getAuthoredCssText: function(skipCache = false) {
     if (!this.canSetRuleText || !SUPPORTED_RULE_TYPES.includes(this.type)) {
       return Promise.resolve("");
     }
 
-    if (typeof this.authoredText === "string") {
+    if (typeof this.authoredText === "string" && !skipCache) {
       return Promise.resolve(this.authoredText);
     }
 
     return this.sheetActor.getText().then((longStr) => {
       const cssText = longStr.str;
       const {text} = getRuleText(cssText, this.line, this.column);
 
       // Cache the result on the rule actor to avoid parsing again next time
@@ -1419,48 +1426,57 @@ var StyleRuleActor = protocol.ActorClass
 
   /**
    * Return a promise that resolves to the complete cssText of the rule as authored.
    *
    * Unlike |getAuthoredCssText()|, which only returns the contents of the rule, this
    * method includes the CSS selectors and at-rules (@media, @supports, @keyframes, etc.)
    *
    * If the rule type is unrecongized, the promise resolves to an empty string.
-   * If the rule is an element inline style, the promise resolves to the text content of
+   * If the rule is an element inline style, the promise resolves with the generated
+   * selector that uniquely identifies the element and with the rule body consisting of
    * the element's style attribute.
    *
    * @return {String}
    */
   getRuleText: async function() {
-    if (this.type === ELEMENT_STYLE) {
-      return Promise.resolve(this.rawNode.getAttribute("style"));
-    }
-
-    if (!SUPPORTED_RULE_TYPES.includes(this.type)) {
+    // Bail out if the rule is not supported or not an element inline style.
+    if (![...SUPPORTED_RULE_TYPES, ELEMENT_STYLE].includes(this.type)) {
       return Promise.resolve("");
     }
 
-    const ruleBodyText = await this.getAuthoredCssText();
-    const { str: stylesheetText } = await this.sheetActor.getText();
-    const [start, end] = getSelectorOffsets(stylesheetText, this.line, this.column);
-    const selectorText = stylesheetText.substring(start, end);
+    let ruleBodyText;
+    let selectorText;
+    let text;
+
+    // For element inline styles, use the style attribute and generated unique selector.
+    if (this.type === ELEMENT_STYLE) {
+      ruleBodyText = this.rawNode.getAttribute("style");
+      selectorText = this.metadata.selector;
+    } else {
+      // Get the rule's authored text and skip any cached value.
+      ruleBodyText = await this.getAuthoredCssText(true);
+      const { str: stylesheetText } = await this.sheetActor.getText();
+      const [start, end] = getSelectorOffsets(stylesheetText, this.line, this.column);
+      selectorText = stylesheetText.substring(start, end);
+    }
 
     // CSS rule type as a string "@media", "@supports", "@keyframes", etc.
     const typeName = CSSRuleTypeName[this.type];
 
-    let text;
     // When dealing with at-rules, getSelectorOffsets() will not return the rule type.
     // We prepend it ourselves.
     if (typeName) {
       text = `${typeName}${selectorText} {${ruleBodyText}}`;
     } else {
       text = `${selectorText} {${ruleBodyText}}`;
     }
 
-    return text;
+    const prettyCSS = prettifyCSS(text);
+    return Promise.resolve(prettyCSS);
   },
 
   /**
    * Set the contents of the rule.  This rewrites the rule in the
    * stylesheet and causes it to be re-evaluated.
    *
    * @param {String} newText
    *        The new text of the rule
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -1336,17 +1336,19 @@ Document::Document(const char* aContentT
       mBoxObjectTable(nullptr),
       mCurrentOrientationAngle(0),
       mCurrentOrientationType(OrientationType::Portrait_primary),
       mServoRestyleRootDirtyBits(0),
       mThrowOnDynamicMarkupInsertionCounter(0),
       mIgnoreOpensDuringUnloadCounter(0),
       mDocLWTheme(Doc_Theme_Uninitialized),
       mSavedResolution(1.0f),
-      mPendingInitialTranslation(false) {
+      mPendingInitialTranslation(false),
+      mGeneration(0),
+      mCachedTabSizeGeneration(0) {
   MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
 
   SetIsInDocument();
   SetIsConnected(true);
 
   if (StaticPrefs::layout_css_use_counters_enabled()) {
     mStyleUseCounters.reset(Servo_UseCounters_Create());
   }
@@ -12544,16 +12546,34 @@ void Document::ReportShadowDOMUsage() {
   mHasReportedShadowDOMUsage = true;
 }
 
 bool Document::StorageAccessSandboxed() const {
   return StaticPrefs::dom_storage_access_enabled() &&
          (GetSandboxFlags() & SANDBOXED_STORAGE_ACCESS) != 0;
 }
 
+bool Document::GetCachedSizes(nsTabSizes* aSizes) {
+  if (mCachedTabSizeGeneration == 0 ||
+      GetGeneration() != mCachedTabSizeGeneration) {
+    return false;
+  }
+  aSizes->mDom += mCachedTabSizes.mDom;
+  aSizes->mStyle += mCachedTabSizes.mStyle;
+  aSizes->mOther += mCachedTabSizes.mOther;
+  return true;
+}
+
+void Document::SetCachedSizes(nsTabSizes* aSizes) {
+  mCachedTabSizes.mDom = aSizes->mDom;
+  mCachedTabSizes.mStyle = aSizes->mStyle;
+  mCachedTabSizes.mOther = aSizes->mOther;
+  mCachedTabSizeGeneration = GetGeneration();
+}
+
 already_AddRefed<nsAtom> Document::GetContentLanguageAsAtomForStyle() const {
   nsAutoString contentLang;
   GetContentLanguage(contentLang);
   contentLang.StripWhitespace();
 
   // Content-Language may be a comma-separated list of language codes,
   // in which case the HTML5 spec says to treat it as unknown
   if (!contentLang.IsEmpty() && !contentLang.Contains(char16_t(','))) {
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -1485,16 +1485,31 @@ class Document : public nsINode,
   // canceled by the URL classifier (Safebrowsing).
   //
   already_AddRefed<nsSimpleContentList> BlockedNodesByClassifier() const;
 
   // Helper method that returns true if the document has storage-access sandbox
   // flag.
   bool StorageAccessSandboxed() const;
 
+  // Increments the document generation.
+  inline void Changed() { ++mGeneration; }
+
+  // Returns the current generation.
+  inline int32_t GetGeneration() const { return mGeneration; }
+
+  // Adds cached sizes values to aSizes if there's any
+  // cached value and if the document generation hasn't
+  // changed since the cache was created.
+  // Returns true if sizes were added.
+  bool GetCachedSizes(nsTabSizes* aSizes);
+
+  // Sets the cache sizes for the current generation.
+  void SetCachedSizes(nsTabSizes* aSizes);
+
  protected:
   friend class nsUnblockOnloadEvent;
 
   nsresult InitCSP(nsIChannel* aChannel);
 
   nsresult InitFeaturePolicy(nsIChannel* aChannel);
 
   void PostUnblockOnloadEvent();
@@ -4636,16 +4651,23 @@ class Document : public nsINode,
   // :-moz-lwtheme-brighttext and :-moz-lwtheme-darktext
   DocumentTheme mDocLWTheme;
 
   // Pres shell resolution saved before entering fullscreen mode.
   float mSavedResolution;
 
   bool mPendingInitialTranslation;
 
+  // Document generation. Gets incremented everytime it changes.
+  int32_t mGeneration;
+
+  // Cached TabSizes values for the document.
+  int32_t mCachedTabSizeGeneration;
+  nsTabSizes mCachedTabSizes;
+
  public:
   // Needs to be public because the bindings code pokes at it.
   js::ExpandoAndGeneration mExpandoAndGeneration;
 
   bool HasPendingInitialTranslation() { return mPendingInitialTranslation; }
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(Document, NS_IDOCUMENT_IID)
--- a/dom/base/nsNodeUtils.cpp
+++ b/dom/base/nsNodeUtils.cpp
@@ -131,32 +131,34 @@ void nsNodeUtils::CharacterDataWillChang
   Document* doc = aContent->OwnerDoc();
   IMPL_MUTATION_NOTIFICATION(CharacterDataWillChange, aContent,
                              (aContent, aInfo), IsRemoveNotification::No);
 }
 
 void nsNodeUtils::CharacterDataChanged(nsIContent* aContent,
                                        const CharacterDataChangeInfo& aInfo) {
   Document* doc = aContent->OwnerDoc();
+  doc->Changed();
   IMPL_MUTATION_NOTIFICATION(CharacterDataChanged, aContent, (aContent, aInfo),
                              IsRemoveNotification::No);
 }
 
 void nsNodeUtils::AttributeWillChange(Element* aElement, int32_t aNameSpaceID,
                                       nsAtom* aAttribute, int32_t aModType) {
   Document* doc = aElement->OwnerDoc();
   IMPL_MUTATION_NOTIFICATION(AttributeWillChange, aElement,
                              (aElement, aNameSpaceID, aAttribute, aModType),
                              IsRemoveNotification::No);
 }
 
 void nsNodeUtils::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
                                    nsAtom* aAttribute, int32_t aModType,
                                    const nsAttrValue* aOldValue) {
   Document* doc = aElement->OwnerDoc();
+  doc->Changed();
   IMPL_MUTATION_NOTIFICATION(
       AttributeChanged, aElement,
       (aElement, aNameSpaceID, aAttribute, aModType, aOldValue),
       IsRemoveNotification::No);
 }
 
 void nsNodeUtils::AttributeSetToCurrentValue(Element* aElement,
                                              int32_t aNameSpaceID,
@@ -165,17 +167,17 @@ void nsNodeUtils::AttributeSetToCurrentV
   IMPL_MUTATION_NOTIFICATION(AttributeSetToCurrentValue, aElement,
                              (aElement, aNameSpaceID, aAttribute),
                              IsRemoveNotification::No);
 }
 
 void nsNodeUtils::ContentAppended(nsIContent* aContainer,
                                   nsIContent* aFirstNewContent) {
   Document* doc = aContainer->OwnerDoc();
-
+  doc->Changed();
   IMPL_MUTATION_NOTIFICATION(ContentAppended, aContainer, (aFirstNewContent),
                              IsRemoveNotification::No);
 }
 
 void nsNodeUtils::NativeAnonymousChildListChange(nsIContent* aContent,
                                                  bool aIsRemove) {
   Document* doc = aContent->OwnerDoc();
   auto isRemove =
@@ -183,25 +185,27 @@ void nsNodeUtils::NativeAnonymousChildLi
   IMPL_MUTATION_NOTIFICATION(NativeAnonymousChildListChange, aContent,
                              (aContent, aIsRemove), isRemove);
 }
 
 void nsNodeUtils::ContentInserted(nsINode* aContainer, nsIContent* aChild) {
   MOZ_ASSERT(aContainer->IsContent() || aContainer->IsDocument(),
              "container must be an nsIContent or an Document");
   Document* doc = aContainer->OwnerDoc();
+  doc->Changed();
   IMPL_MUTATION_NOTIFICATION(ContentInserted, aContainer, (aChild),
                              IsRemoveNotification::No);
 }
 
 void nsNodeUtils::ContentRemoved(nsINode* aContainer, nsIContent* aChild,
                                  nsIContent* aPreviousSibling) {
   MOZ_ASSERT(aContainer->IsContent() || aContainer->IsDocument(),
              "container must be an nsIContent or an Document");
   Document* doc = aContainer->OwnerDoc();
+  doc->Changed();
   MOZ_ASSERT(aChild->GetParentNode() == aContainer,
              "We expect the parent link to be still around at this point");
   IMPL_MUTATION_NOTIFICATION(ContentRemoved, aContainer,
                              (aChild, aPreviousSibling),
                              IsRemoveNotification::Yes);
 }
 
 Maybe<NonOwningAnimationTarget> nsNodeUtils::GetTargetForAnimation(
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -1580,17 +1580,19 @@ class WebGLContext : public nsICanvasRen
   void EnableExtension(WebGLExtensionID ext);
 
   // Enable an extension if it's supported. Return the extension on success.
   WebGLExtensionBase* EnableSupportedExtension(dom::CallerType callerType,
                                                WebGLExtensionID ext);
 
  public:
   // returns true if the extension has been enabled by calling getExtension.
-  bool IsExtensionEnabled(WebGLExtensionID ext) const;
+  bool IsExtensionEnabled(const WebGLExtensionID ext) const {
+    return mExtensions[ext];
+  }
 
  protected:
   // returns true if the extension is supported for this caller type (this
   // decides what getSupportedExtensions exposes)
   bool IsExtensionSupported(dom::CallerType callerType,
                             WebGLExtensionID ext) const;
   bool IsExtensionSupported(WebGLExtensionID ext) const;
 
--- a/dom/canvas/WebGLContextExtensions.cpp
+++ b/dom/canvas/WebGLContextExtensions.cpp
@@ -65,20 +65,16 @@ namespace mozilla {
     WEBGL_EXTENSION_IDENTIFIER(WEBGL_lose_context)
 
 #undef WEBGL_EXTENSION_IDENTIFIER
   }
 
   return sExtensionNamesEnumeratedArray[ext];
 }
 
-bool WebGLContext::IsExtensionEnabled(WebGLExtensionID ext) const {
-  return mExtensions[ext];
-}
-
 bool WebGLContext::IsExtensionSupported(dom::CallerType callerType,
                                         WebGLExtensionID ext) const {
   bool allowPrivilegedExts = false;
 
   // Chrome contexts need access to debug information even when
   // webgl.disable-extensions is set. This is used in the graphics
   // section of about:support
   if (callerType == dom::CallerType::System) {
--- a/dom/media/Intervals.h
+++ b/dom/media/Intervals.h
@@ -40,25 +40,25 @@ class Interval {
 
   Interval() : mStart(T()), mEnd(T()), mFuzz(T()) {}
 
   template <typename StartArg, typename EndArg>
   Interval(StartArg&& aStart, EndArg&& aEnd)
       : mStart(std::forward<StartArg>(aStart)),
         mEnd(std::forward<EndArg>(aEnd)),
         mFuzz() {
-    MOZ_ASSERT(aStart <= aEnd);
+    MOZ_DIAGNOSTIC_ASSERT(mStart <= mEnd, "Invalid Interval");
   }
 
   template <typename StartArg, typename EndArg, typename FuzzArg>
   Interval(StartArg&& aStart, EndArg&& aEnd, FuzzArg&& aFuzz)
       : mStart(std::forward<StartArg>(aStart)),
         mEnd(std::forward<EndArg>(aEnd)),
         mFuzz(std::forward<FuzzArg>(aFuzz)) {
-    MOZ_ASSERT(aStart <= aEnd);
+    MOZ_DIAGNOSTIC_ASSERT(mStart <= mEnd, "Invalid Interval");
   }
 
   Interval(const SelfType& aOther)
       : mStart(aOther.mStart), mEnd(aOther.mEnd), mFuzz(aOther.mFuzz) {}
 
   Interval(SelfType&& aOther)
       : mStart(std::move(aOther.mStart)),
         mEnd(std::move(aOther.mEnd)),
--- a/dom/media/MediaData.cpp
+++ b/dom/media/MediaData.cpp
@@ -81,24 +81,30 @@ bool AudioData::SetTrimWindow(const medi
   auto trimBefore = TimeUnitToFrames(aTrim.mStart - mOriginalTime, mRate);
   auto trimAfter = aTrim.mEnd == GetEndTime()
                        ? originalFrames
                        : TimeUnitToFrames(aTrim.mEnd - mOriginalTime, mRate);
   if (!trimBefore.isValid() || !trimAfter.isValid()) {
     // Overflow.
     return false;
   }
+  MOZ_DIAGNOSTIC_ASSERT(trimAfter.value() >= trimBefore.value(),
+                        "Something went wrong with trimming value");
   if (!mTrimWindow && trimBefore == 0 && trimAfter == originalFrames) {
     // Nothing to change, abort early to prevent rounding errors.
     return true;
   }
 
   mTrimWindow = Some(aTrim);
   mDataOffset = trimBefore.value() * mChannels;
+  MOZ_DIAGNOSTIC_ASSERT(mDataOffset <= mAudioData.Length(),
+                        "Data offset outside original buffer");
   mFrames = (trimAfter - trimBefore).value();
+  MOZ_DIAGNOSTIC_ASSERT(mFrames <= originalFrames,
+                        "More frames than found in container");
   mTime = mOriginalTime + FramesToTimeUnit(trimBefore.value(), mRate);
   mDuration = FramesToTimeUnit(mFrames, mRate);
 
   return true;
 }
 
 AudioDataValue* AudioData::GetAdjustedData() const {
   if (!mAudioData) {
--- a/dom/media/MediaData.h
+++ b/dom/media/MediaData.h
@@ -177,17 +177,17 @@ class AlignedBuffer {
   // For backward compatibility with UniquePtr<Type[]>
   Type* get() const { return mData; }
   explicit operator bool() const { return mData != nullptr; }
 
   // Size in bytes of extra space allocated for padding.
   static size_t AlignmentPaddingSize() { return AlignmentOffset() * 2; }
 
   void PopFront(size_t aSize) {
-    MOZ_ASSERT(mLength >= aSize);
+    MOZ_DIAGNOSTIC_ASSERT(mLength >= aSize, "Popping too many frames");
     PodMove(mData, mData + aSize, mLength - aSize);
     mLength -= aSize;
   }
 
  private:
   static size_t AlignmentOffset() { return Alignment ? Alignment - 1 : 0; }
 
   // Ensure that the backend buffer can hold aLength data. Will update mData.
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1286,51 +1286,36 @@ class MediaDecoderStateMachine::Accurate
       // in this case, we'll just decode forward. Bug 1026330.
       mSeekJob.mTarget->SetType(SeekTarget::Accurate);
     }
   }
 
   nsresult DropAudioUpToSeekTarget(AudioData* aAudio) {
     MOZ_ASSERT(aAudio && mSeekJob.mTarget->IsAccurate());
 
-    auto sampleDuration =
-        FramesToTimeUnit(aAudio->Frames(), Info().mAudio.mRate);
-    if (!sampleDuration.IsValid()) {
-      return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
-    }
-
-    auto audioTime = aAudio->mTime;
-    if (audioTime + sampleDuration <= mSeekJob.mTarget->GetTime()) {
+    if (mSeekJob.mTarget->GetTime() >= aAudio->GetEndTime()) {
       // Our seek target lies after the frames in this AudioData. Don't
       // push it onto the audio queue, and keep decoding forwards.
       return NS_OK;
     }
 
-    if (audioTime > mSeekJob.mTarget->GetTime()) {
+    if (aAudio->mTime > mSeekJob.mTarget->GetTime()) {
       // The seek target doesn't lie in the audio block just after the last
       // audio frames we've seen which were before the seek target. This
       // could have been the first audio data we've seen after seek, i.e. the
       // seek terminated after the seek target in the audio stream. Just
       // abort the audio decode-to-target, the state machine will play
       // silence to cover the gap. Typically this happens in poorly muxed
       // files.
       SLOGW("Audio not synced after seek, maybe a poorly muxed file?");
       mMaster->PushAudio(aAudio);
       mDoneAudioSeeking = true;
       return NS_OK;
     }
 
-    // The seek target lies somewhere in this AudioData's frames, strip off
-    // any frames which lie before the seek target, so we'll begin playback
-    // exactly at the seek target.
-    NS_ASSERTION(mSeekJob.mTarget->GetTime() >= audioTime,
-                 "Target must at or be after data start.");
-    NS_ASSERTION(mSeekJob.mTarget->GetTime() < audioTime + sampleDuration,
-                 "Data must end after target.");
-
     bool ok = aAudio->SetTrimWindow(
         {mSeekJob.mTarget->GetTime(), aAudio->GetEndTime()});
     if (!ok) {
       return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
     }
 
     MOZ_ASSERT(AudioQueue().GetSize() == 0,
                "Should be the 1st sample after seeking");
--- a/layout/reftests/image-element/reftest.list
+++ b/layout/reftests/image-element/reftest.list
@@ -1,16 +1,16 @@
 random == bug-364968.html bug-364968-ref.html
 == bug-463204.html bug-463204-ref.html
 == canvas-outside-document.html canvas-inside-document.html
 == mozsetimageelement-01.html mozsetimageelement-01-ref.html
 == mozsetimageelement-02.html about:blank
 == image-outside-document-invalidate.html about:blank
 == canvas-outside-document-invalidate-01.html about:blank
-fails-if(azureSkia) fails-if(cocoaWidget) == canvas-outside-document-invalidate-02.html about:blank # See bug 666800
+fails-if(azureSkia&&!(webrender&&winWidget)) fails-if(cocoaWidget) == canvas-outside-document-invalidate-02.html about:blank # See bug 666800
 #fails with Skia due to Skia bug http://code.google.com/p/skia/issues/detail?id=568
 fuzzy-if(webrender&&!gtkWidget,117-129,47-54) == element-paint-simple.html element-paint-simple-ref.html
 == element-paint-repeated.html element-paint-repeated-ref.html
 == element-paint-recursion.html element-paint-recursion-ref.html
 == element-paint-continuation.html element-paint-continuation-ref.html
 == element-paint-transform-01.html element-paint-transform-01-ref.html
 random-if(d2d) == element-paint-transform-02.html element-paint-transform-02-ref.html # bug 587133
 fuzzy-if(d2d&&/^Windows\x20NT\x206\.1/.test(http.oscpu),0-16,0-90) == element-paint-background-size-01.html element-paint-background-size-01-ref.html
--- a/layout/style/ServoCSSPropList.mako.py
+++ b/layout/style/ServoCSSPropList.mako.py
@@ -55,120 +55,119 @@ def is_internal(prop):
 
 def method(prop):
     if prop.name == "float":
         return "CssFloat"
     if prop.name.startswith("-x-"):
         return prop.camel_case[1:]
     return prop.camel_case
 
-# Colors, integers and lengths are easy as well.
-#
-# TODO(emilio): This will go away once the rest of the longhands have been
-# moved or perhaps using a blacklist for the ones with non-layout-dependence
-# but other non-trivial dependence like scrollbar colors.
-SERIALIZED_PREDEFINED_TYPES = [
-    "Appearance",
-    "AlignContent",
-    "AlignItems",
-    "AlignSelf",
-    "BackgroundRepeat",
-    "BackgroundSize",
-    "BorderImageRepeat",
-    "BorderStyle",
-    "BreakBetween",
-    "BreakWithin",
-    "Clear",
-    "ClipRectOrAuto",
-    "Color",
-    "Content",
-    "CounterIncrement",
-    "CounterReset",
-    "Cursor",
-    "FillRule",
-    "Float",
-    "FontFamily",
-    "FontFeatureSettings",
-    "FontLanguageOverride",
-    "FontSize",
-    "FontSizeAdjust",
-    "FontStretch",
-    "FontStyle",
-    "FontSynthesis",
-    "FontVariant",
-    "FontVariantAlternates",
-    "FontVariantEastAsian",
-    "FontVariantLigatures",
-    "FontVariantNumeric",
-    "FontVariationSettings",
-    "FontWeight",
-    "Integer",
-    "ImageLayer",
-    "JustifyContent",
-    "JustifyItems",
-    "JustifySelf",
-    "Length",
-    "LengthPercentage",
-    "NonNegativeLength",
-    "NonNegativeLengthPercentage",
-    "NonNegativeLengthPercentageOrAuto",
-    "ListStyleType",
-    "OffsetPath",
-    "Opacity",
-    "OutlineStyle",
-    "OverflowWrap",
-    "Position",
-    "Quotes",
-    "Resize",
-    "Rotate",
-    "Scale",
-    "TextAlign",
-    "Translate",
-    "TimingFunction",
-    "TransformOrigin",
-    "TransformStyle",
-    "UserSelect",
-    "background::BackgroundSize",
-    "basic_shape::ClippingShape",
-    "basic_shape::FloatAreaShape",
-    "position::HorizontalPosition",
-    "position::VerticalPosition",
-    "url::ImageUrlOrNone",
-    "Appearance",
-    "OverscrollBehavior",
-    "OverflowAnchor",
-    "OverflowClipBox",
-    "ScrollSnapAlign",
-    "ScrollSnapType",
-    "Float",
-    "Overflow",
-    "BorderImageSlice",
-    "NonNegativeLengthOrNumberRect",
-    "NonNegativeLengthOrNumber",
-    "ZIndex",
-    "Perspective",
+# TODO(emilio): Get this to zero.
+LONGHANDS_NOT_SERIALIZED_WITH_SERVO = [
+    "animation-delay",
+    "animation-duration",
+    "animation-iteration-count",
+    "animation-name",
+    "border-bottom-left-radius",
+    "border-bottom-right-radius",
+    "border-end-end-radius",
+    "border-end-start-radius",
+    "border-image-width",
+    "border-spacing",
+    "border-start-end-radius",
+    "border-start-start-radius",
+    "border-top-left-radius",
+    "border-top-right-radius",
+    "border-image-width",
+    "border-spacing",
+    "box-shadow",
+    "caret-color",
+    "color",
+    "column-count",
+    "column-gap",
+    "column-rule-width",
+    "column-width",
+    "contain",
+    "display",
+    "fill",
+    "fill-opacity",
+    "filter",
+    "flex-basis",
+    "flex-grow",
+    "flex-shrink",
+    "grid-auto-columns",
+    "grid-auto-flow",
+    "grid-auto-rows",
+    "grid-column-end",
+    "grid-column-start",
+    "grid-row-end",
+    "grid-row-start",
+    "grid-template-areas",
+    "initial-letter",
+    "letter-spacing",
+    "marker-end",
+    "marker-mid",
+    "marker-start",
+    "max-block-size",
+    "max-height",
+    "max-inline-size",
+    "max-width",
+    "min-block-size",
+    "min-height",
+    "min-inline-size",
+    "min-width",
+    "-moz-binding",
+    "-moz-box-flex",
+    "-moz-force-broken-image-icon",
+    "-moz-osx-font-smoothing",
+    "-moz-outline-radius-bottomleft",
+    "-moz-outline-radius-bottomright",
+    "-moz-outline-radius-topleft",
+    "-moz-outline-radius-topright",
+    "outline-width",
+    "paint-order",
+    "row-gap",
+    "scrollbar-color",
+    "scroll-snap-points-x",
+    "scroll-snap-points-y",
+    "stroke",
+    "stroke-dasharray",
+    "stroke-dashoffset",
+    "stroke-miterlimit",
+    "stroke-opacity",
+    "stroke-width",
+    "text-decoration-line",
+    "text-emphasis-position",
+    "text-emphasis-style",
+    "text-overflow",
+    "text-shadow",
+    "touch-action",
+    "transition-delay",
+    "transition-duration",
+    "transition-property",
+    "vertical-align",
+    "-webkit-text-stroke-width",
+    "will-change",
+    "word-spacing",
 ]
 
 def serialized_by_servo(prop):
     # If the property requires layout information, no such luck.
     if "GETCS_NEEDS_LAYOUT_FLUSH" in prop.flags:
         return False
     if prop.type() == "shorthand":
         # FIXME: Need to serialize a value interpolated with currentcolor
         # properly to be able to use text-decoration, and figure out what to do
         # with relative mask urls.
         return prop.name != "text-decoration" and prop.name != "mask"
     # Keywords are all fine, except -moz-osx-font-smoothing, which does
     # resistfingerprinting stuff.
     if prop.keyword and prop.name != "-moz-osx-font-smoothing":
         return True
-    if prop.predefined_type in SERIALIZED_PREDEFINED_TYPES:
-        return True
-    # TODO(emilio): Enable the rest of the longhands.
-    return False
+    return prop.name not in LONGHANDS_NOT_SERIALIZED_WITH_SERVO
 
 def exposed_on_getcs(prop):
     if prop.type() == "longhand":
         return not is_internal(prop)
     # TODO: bug 137688 / https://github.com/w3c/csswg-drafts/issues/2529
     if prop.type() == "shorthand":
         return "SHORTHAND_IN_GETCS" in prop.flags
 
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -7703,16 +7703,256 @@ if (IsCSSPropertyPrefEnabled("layout.css
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "none" ],
     other_values: ["mandatory", "proximity"],
     invalid_values: [ "auto",  "1px" ]
   };
 }
 
+if (IsCSSPropertyPrefEnabled("layout.css.scroll-snap-v1.enabled")) {
+  gCSSProperties["scroll-snap-align"] = {
+    domProp: "scrollSnapAlign",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "none" ],
+    other_values: [ "start", "end", "center", "start none", "center end",
+                    "start start" ],
+    invalid_values: [ "auto", "start invalid", "start end center" ]
+  };
+  gCSSProperties["scroll-margin"] = {
+    domProp: "scrollMargin",
+    inherited: false,
+    type: CSS_TYPE_TRUE_SHORTHAND,
+    subproperties: [ "scroll-margin-top",
+                     "scroll-margin-right",
+                     "scroll-margin-bottom",
+                     "scroll-margin-left" ],
+    initial_values: [ "0" ],
+    other_values: [ "-10px", "calc(2em + 3ex)", "1px 2px", "1px 2px 3px",
+                    "1px 2px 3px 4px" ],
+    invalid_values: [ "auto", "20%", "-30%", "1px 2px 3px 4px 5px" ],
+  };
+  gCSSProperties["scroll-margin-top"] = {
+    domProp: "scrollMarginTop",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    applies_to_first_letter: true,
+    applies_to_first_line: true,
+    initial_values: [ "0" ],
+    other_values: [ "-10px", "calc(2em + 3ex)" ],
+    invalid_values: [ "auto", "20%", "-30%", "1px 2px" ],
+  };
+  gCSSProperties["scroll-margin-right"] = {
+    domProp: "scrollMarginRight",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    applies_to_first_letter: true,
+    applies_to_first_line: true,
+    initial_values: [ "0" ],
+    other_values: [ "-10px", "calc(2em + 3ex)" ],
+    invalid_values: [ "auto", "20%", "-30%", "1px 2px" ],
+  };
+  gCSSProperties["scroll-margin-bottom"] = {
+    domProp: "scrollMarginBottom",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    applies_to_first_letter: true,
+    applies_to_first_line: true,
+    initial_values: [ "0" ],
+    other_values: [ "-10px", "calc(2em + 3ex)" ],
+    invalid_values: [ "auto", "20%", "-30%", "1px 2px" ],
+  };
+  gCSSProperties["scroll-margin-left"] = {
+    domProp: "scrollMarginLeft",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    applies_to_first_letter: true,
+    applies_to_first_line: true,
+    initial_values: [ "0" ],
+    other_values: [ "-10px", "calc(2em + 3ex)" ],
+    invalid_values: [ "auto", "20%", "-30%", "1px 2px" ],
+  };
+  gCSSProperties["scroll-margin-inline"] = {
+    domProp: "scrollMarginInline",
+    inherited: false,
+    type: CSS_TYPE_TRUE_SHORTHAND,
+    subproperties: [ "scroll-margin-inline-start",
+                     "scroll-margin-inline-end" ],
+    initial_values: [ "0" ],
+    other_values: [ "-10px", "calc(2em + 3ex)", "1px 2px" ],
+    invalid_values: [ "auto", "20%", "-30%", "1px 2px 3px" ],
+  };
+  gCSSProperties["scroll-margin-inline-start"] = {
+    domProp: "scrollMarginInlineStart",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    logical: true,
+    applies_to_first_letter: true,
+    applies_to_first_line: true,
+    initial_values: [ "0" ],
+    other_values: [ "-10px", "calc(2em + 3ex)" ],
+    invalid_values: [ "auto", "20%", "-30%", "1px 2px" ],
+  };
+  gCSSProperties["scroll-margin-inline-end"] = {
+    domProp: "scrollMarginInlineEnd",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    logical: true,
+    applies_to_first_letter: true,
+    applies_to_first_line: true,
+    initial_values: [ "0" ],
+    other_values: [ "-10px", "calc(2em + 3ex)" ],
+    invalid_values: [ "auto", "20%", "-30%", "1px 2px" ],
+  };
+  gCSSProperties["scroll-margin-block"] = {
+    domProp: "scrollMarginBlock",
+    inherited: false,
+    type: CSS_TYPE_TRUE_SHORTHAND,
+    subproperties: [ "scroll-margin-block-start",
+                     "scroll-margin-block-end" ],
+    initial_values: [ "0" ],
+    other_values: [ "-10px", "calc(2em + 3ex)", "1px 2px" ],
+    invalid_values: [ "auto", "20%", "-30%", "1px 2px 3px" ],
+  };
+  gCSSProperties["scroll-margin-block-start"] = {
+    domProp: "scrollMarginBlockStart",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    logical: true,
+    applies_to_first_letter: true,
+    applies_to_first_line: true,
+    initial_values: [ "0" ],
+    other_values: [ "-10px", "calc(2em + 3ex)" ],
+    invalid_values: [ "auto", "20%", "-30%", "1px 2px" ],
+  };
+  gCSSProperties["scroll-margin-block-end"] = {
+    domProp: "scrollMarginBlockEnd",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    logical: true,
+    applies_to_first_letter: true,
+    applies_to_first_line: true,
+    initial_values: [ "0" ],
+    other_values: [ "-10px", "calc(2em + 3ex)" ],
+    invalid_values: [ "auto", "20%", "-30%", "1px 2px" ],
+  };
+  gCSSProperties["scroll-padding"] = {
+    domProp: "scrollPadding",
+    inherited: false,
+    type: CSS_TYPE_TRUE_SHORTHAND,
+    subproperties: [ "scroll-padding-top",
+                     "scroll-padding-right",
+                     "scroll-padding-bottom",
+                     "scroll-padding-left" ],
+    initial_values: [ "auto" ],
+    other_values: [ "10px", "0", "20%", "calc(2em + 3ex)", "1px 2px",
+                    "1px 2px 3%", "1px 2px 3% 4px", "1px auto" ],
+    invalid_values: [ "20", "-20px" ]
+  };
+  gCSSProperties["scroll-padding-top"] = {
+    domProp: "scrollPaddingTop",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "auto" ],
+    other_values: [ "0", "10px", "20%", "calc(2em + 3ex)", "calc(50% + 60px)",
+                    "calc(-50px)" ],
+    invalid_values: [ "20", "-20px" ],
+  };
+  gCSSProperties["scroll-padding-right"] = {
+    domProp: "scrollPaddingRight",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "auto" ],
+    other_values: [ "0", "10px", "20%", "calc(2em + 3ex)", "calc(50% + 60px)",
+                    "calc(-50px)" ],
+    invalid_values: [ "20", "-20px" ],
+  };
+  gCSSProperties["scroll-padding-bottom"] = {
+    domProp: "scrollPaddingBottom",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "auto" ],
+    other_values: [ "0", "10px", "20%", "calc(2em + 3ex)", "calc(50% + 60px)",
+                    "calc(-50px)" ],
+    invalid_values: [ "20", "-20px" ],
+  };
+  gCSSProperties["scroll-padding-left"] = {
+    domProp: "scrollPaddingLeft",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "auto" ],
+    other_values: [ "0", "10px", "20%", "calc(2em + 3ex)", "calc(50% + 60px)",
+                    "calc(-50px)" ],
+    invalid_values: [ "20", "-20px" ],
+  };
+  gCSSProperties["scroll-padding-inline"] = {
+    domProp: "scrollPaddingInline",
+    inherited: false,
+    type: CSS_TYPE_TRUE_SHORTHAND,
+    subproperties: [ "scroll-padding-inline-start",
+                     "scroll-padding-inline-end" ],
+    initial_values: [ "auto", "auto auto" ],
+    other_values: [ "10px", "0", "20%", "calc(2em + 3ex)", "1px 2px",
+                    "1px auto" ],
+    invalid_values: [ "20", "-20px" ]
+  };
+  gCSSProperties["scroll-padding-inline-start"] = {
+    domProp: "scrollPaddingInlineStart",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    logical: true,
+    initial_values: [ "auto" ],
+    other_values: [ "0", "10px", "20%", "calc(2em + 3ex)", "calc(50% + 60px)",
+                    "calc(-50px)" ],
+    invalid_values: [ "20", "-20px" ],
+  };
+  gCSSProperties["scroll-padding-inline-end"] = {
+    domProp: "scrollPaddingInlineEnd",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    logical: true,
+    initial_values: [ "auto" ],
+    other_values: [ "0", "10px", "20%", "calc(2em + 3ex)", "calc(50% + 60px)",
+                    "calc(-50px)" ],
+    invalid_values: [ "20", "-20px" ],
+  };
+  gCSSProperties["scroll-padding-block"] = {
+    domProp: "scrollPaddingBlock",
+    inherited: false,
+    type: CSS_TYPE_TRUE_SHORTHAND,
+    subproperties: [ "scroll-padding-block-start",
+                     "scroll-padding-block-end" ],
+    initial_values: [ "auto", "auto auto" ],
+    other_values: [ "10px", "0", "20%", "calc(2em + 3ex)", "1px 2px",
+                    "1px auto" ],
+    invalid_values: [ "20", "-20px" ]
+  };
+  gCSSProperties["scroll-padding-block-start"] = {
+    domProp: "scrollPaddingBlockStart",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    logical: true,
+    initial_values: [ "auto" ],
+    other_values: [ "0", "10px", "20%", "calc(2em + 3ex)", "calc(50% + 60px)",
+                    "calc(-50px)" ],
+    invalid_values: [ "20", "-20px" ],
+  };
+  gCSSProperties["scroll-padding-block-end"] = {
+    domProp: "scrollPaddingBlockEnd",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    logical: true,
+    initial_values: [ "auto" ],
+    other_values: [ "0", "10px", "20%", "calc(2em + 3ex)", "calc(50% + 60px)",
+                    "calc(-50px)" ],
+    invalid_values: [ "20", "-20px" ],
+  };
+}
+
 if (IsCSSPropertyPrefEnabled("layout.css.prefixes.webkit")) {
   gCSSProperties["-webkit-animation"] = {
     domProp: "webkitAnimation",
     inherited: false,
     type: CSS_TYPE_TRUE_SHORTHAND,
     alias_for: "animation",
     subproperties: [ "animation-name", "animation-duration", "animation-timing-function", "animation-delay", "animation-direction", "animation-fill-mode", "animation-iteration-count", "animation-play-state" ],
   };
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -326,30 +326,65 @@ if (IsCSSPropertyPrefEnabled("layout.css
 }
 
 if (IsCSSPropertyPrefEnabled("layout.css.scrollbar-color.enabled")) {
   supported_properties["scrollbar-color"] = [
     test_scrollbar_color_transition,
   ];
 }
 
+if (IsCSSPropertyPrefEnabled("layout.css.scroll-snap-v1.enabled")) {
+  supported_properties["scroll-margin-top"] = [
+    test_length_transition,
+  ];
+  supported_properties["scroll-margin-right"] = [
+    test_length_transition,
+  ];
+  supported_properties["scroll-margin-bottom"] = [
+    test_length_transition,
+  ];
+  supported_properties["scroll-margin-left"] = [
+    test_length_transition,
+  ];
+  supported_properties["scroll-padding-top"] = [
+    test_length_transition, test_percent_transition,
+    test_length_clamped, test_percent_clamped,
+  ];
+  supported_properties["scroll-padding-right"] = [
+    test_length_transition, test_percent_transition,
+    test_length_clamped, test_percent_clamped,
+  ];
+  supported_properties["scroll-padding-bottom"] = [
+    test_length_transition, test_percent_transition,
+    test_length_clamped, test_percent_clamped,
+  ];
+  supported_properties["scroll-padding-left"] = [
+    test_length_transition, test_percent_transition,
+    test_length_clamped, test_percent_clamped,
+  ];
+}
+
 // For properties which are well-tested by web-platform-tests, we don't need to
 // test animations/transitions again on them.
 var skipped_transitionable_properties = [
   "grid-template-columns",
   "grid-template-rows",
 ]
 
 // Logical properties.
 for (const logical_side of ["inline-start", "inline-end", "block-start", "block-end"]) {
   supported_properties["border-" + logical_side + "-color"] = supported_properties["border-top-color"];
   supported_properties["border-" + logical_side + "-width"] = supported_properties["border-top-width"];
   supported_properties["margin-" + logical_side] = supported_properties["margin-top"];
   supported_properties["padding-" + logical_side] = supported_properties["padding-top"];
   supported_properties["inset-" + logical_side] = supported_properties["top"];
+  if (IsCSSPropertyPrefEnabled("layout.css.scroll-snap-v1.enabled")) {
+    supported_properties["scroll-margin-" + logical_side] = supported_properties["scroll-margin-top"];
+    supported_properties["scroll-padding-" + logical_side] = supported_properties["scroll-padding-top"];
+  }
 }
 
 for (const logical_size of ["inline", "block"]) {
   supported_properties[logical_size + "-size"] = supported_properties["width"];
   supported_properties["min-" + logical_size + "-size"] = supported_properties["min-width"];
   supported_properties["max-" + logical_size + "-size"] = supported_properties["max-width"];
 }
 
--- a/mobile/android/base/java/org/mozilla/gecko/FilePicker.java
+++ b/mobile/android/base/java/org/mozilla/gecko/FilePicker.java
@@ -109,23 +109,23 @@ public class FilePicker implements Bundl
                         }, tabId);
                     }
                 });
         }
     }
 
     private static String[] getPermissionsForMimeType(final String mimeType) {
         if (mimeType.startsWith("audio/")) {
-            return new String[] { Manifest.permission.READ_EXTERNAL_STORAGE };
+            return new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE };
         } else if (mimeType.startsWith("image/")) {
-            return new String[] { Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE };
+            return new String[] { Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE };
         } else if (mimeType.startsWith("video/")) {
-            return new String[] { Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE };
+            return new String[] { Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE };
         }
-        return new String[] { Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE };
+        return new String[] { Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE };
     }
 
     private static boolean hasPermissionsForMimeType(final String mimeType, final String[] availPermissions) {
         return Arrays.asList(availPermissions)
                 .containsAll(Arrays.asList(getPermissionsForMimeType(mimeType)));
     }
 
     private void addActivities(Intent intent, HashMap<String, Intent> intents, HashMap<String, Intent> filters) {
--- a/python/mozboot/bin/bootstrap.py
+++ b/python/mozboot/bin/bootstrap.py
@@ -128,16 +128,20 @@ def ensure_environment(repo_url=None, re
             # This should always work.
             sys.path.append(TEMPDIR)
             from mozboot.bootstrap import Bootstrapper
             return Bootstrapper
 
 
 def main(args):
     parser = OptionParser()
+    parser.add_option('--vcs', dest='vcs',
+                      default='hg',
+                      help='VCS (hg or git) to use for downloading the source code. '
+                      'Uses hg if omitted.')
     parser.add_option('-r', '--repo-url', dest='repo_url',
                       default='https://hg.mozilla.org/mozilla-central/',
                       help='Base URL of source control repository where bootstrap files can '
                       'be downloaded.')
     parser.add_option('--repo-rev', dest='repo_rev',
                       default='default',
                       help='Revision of files in repository to fetch')
     parser.add_option('--repo-type', dest='repo_type',
@@ -158,17 +162,18 @@ def main(args):
             cls = ensure_environment(options.repo_url, options.repo_rev,
                                      options.repo_type)
         except Exception as e:
             print('Could not load the bootstrap Python environment.\n')
             print('This should never happen. Consider filing a bug.\n')
             print('\n')
             print(e)
             return 1
-        dasboot = cls(choice=options.application_choice, no_interactive=options.no_interactive)
+        dasboot = cls(choice=options.application_choice, no_interactive=options.no_interactive,
+                      vcs=options.vcs)
         dasboot.bootstrap()
 
         return 0
     finally:
         if TEMPDIR is not None:
             shutil.rmtree(TEMPDIR)
 
 
--- a/python/mozboot/mozboot/bootstrap.py
+++ b/python/mozboot/mozboot/bootstrap.py
@@ -230,23 +230,25 @@ def update_or_create_build_telemetry_con
         config.write(f)
     return True
 
 
 class Bootstrapper(object):
     """Main class that performs system bootstrap."""
 
     def __init__(self, finished=FINISHED, choice=None, no_interactive=False,
-                 hg_configure=False, no_system_changes=False, mach_context=None):
+                 hg_configure=False, no_system_changes=False, mach_context=None,
+                 vcs=None):
         self.instance = None
         self.finished = finished
         self.choice = choice
         self.hg_configure = hg_configure
         self.no_system_changes = no_system_changes
         self.mach_context = mach_context
+        self.vcs = vcs
         cls = None
         args = {'no_interactive': no_interactive,
                 'no_system_changes': no_system_changes}
 
         if sys.platform.startswith('linux'):
             distro, version, dist_id = platform.linux_distribution()
 
             if distro in ('CentOS', 'CentOS Linux', 'Fedora'):
@@ -447,32 +449,33 @@ class Bootstrapper(object):
 
         # We need to enable the loading of hgrc in case extensions are
         # required to open the repo.
         r = current_firefox_checkout(check_output=self.instance.check_output,
                                      env=self.instance._hg_cleanenv(load_hgrc=True),
                                      hg=self.instance.which('hg'))
         (checkout_type, checkout_root) = r
 
-        # Possibly configure Mercurial, but not if the current checkout is Git.
-        if hg_installed and state_dir_available and checkout_type != 'git':
+        # Possibly configure Mercurial, but not if the current checkout or repo
+        # type is Git.
+        if hg_installed and state_dir_available and (checkout_type == 'hg' or self.vcs == 'hg'):
             configure_hg = False
             if not self.instance.no_interactive:
                 choice = self.instance.prompt_int(prompt=CONFIGURE_MERCURIAL,
                                                   low=1, high=2)
                 if choice == 1:
                     configure_hg = True
             else:
                 configure_hg = self.hg_configure
 
             if configure_hg:
                 configure_mercurial(self.instance.which('hg'), state_dir)
 
-        # Offer to configure Git, if the current checkout is Git.
-        elif self.instance.which('git') and checkout_type == 'git':
+        # Offer to configure Git, if the current checkout or repo type is Git.
+        elif self.instance.which('git') and (checkout_type == 'git' or self.vcs == 'git'):
             should_configure_git = False
             if not self.instance.no_interactive:
                 choice = self.instance.prompt_int(prompt=CONFIGURE_GIT,
                                                   low=1, high=2)
                 if choice == 1:
                     should_configure_git = True
             else:
                 # Assuming default configuration setting applies to all VCS.
@@ -482,22 +485,22 @@ class Bootstrapper(object):
                 configure_git(self.instance.which('git'), state_dir,
                               checkout_root)
 
         # Offer to clone if we're not inside a clone.
         have_clone = False
 
         if checkout_type:
             have_clone = True
-        elif hg_installed and not self.instance.no_interactive:
+        elif hg_installed and not self.instance.no_interactive and self.vcs == 'hg':
             dest = self.input_clone_dest()
             if dest:
                 have_clone = hg_clone_firefox(self.instance.which('hg'), dest)
                 checkout_root = dest
-        elif self.instance.which('git') and checkout_type == 'git':
+        elif self.instance.which('git') and self.vcs == 'git':
             dest = self.input_clone_dest(False)
             if dest:
                 git = self.instance.which('git')
                 watchman = self.instance.which('watchman')
                 have_clone = git_clone_firefox(git, dest, watchman)
                 checkout_root = dest
 
         if not have_clone:
@@ -680,25 +683,26 @@ def current_firefox_checkout(check_outpu
 
     return (None, None)
 
 
 def update_git_tools(git, root_state_dir, top_src_dir):
     """Update git tools, hooks and extensions"""
     # Bug 1481425 - delete the git-mozreview
     # commit message hook in .git/hooks dir
-    mozreview_commit_hook = os.path.join(top_src_dir, '.git/hooks/commit-msg')
-    if os.path.exists(mozreview_commit_hook):
-        with open(mozreview_commit_hook, 'rb') as f:
-            contents = f.read()
+    if top_src_dir:
+        mozreview_commit_hook = os.path.join(top_src_dir, '.git/hooks/commit-msg')
+        if os.path.exists(mozreview_commit_hook):
+            with open(mozreview_commit_hook, 'rb') as f:
+                contents = f.read()
 
-        if b'MozReview' in contents:
-            print('removing git-mozreview commit message hook...')
-            os.remove(mozreview_commit_hook)
-            print('git-mozreview commit message hook removed.')
+            if b'MozReview' in contents:
+                print('removing git-mozreview commit message hook...')
+                os.remove(mozreview_commit_hook)
+                print('git-mozreview commit message hook removed.')
 
     # Ensure git-cinnabar is up to date.
     cinnabar_dir = os.path.join(root_state_dir, 'git-cinnabar')
 
     # Ensure the latest revision of git-cinnabar is present.
     update_git_repo(git, 'https://github.com/glandium/git-cinnabar.git',
                     cinnabar_dir)
 
--- a/servo/components/style/properties/longhands/padding.mako.rs
+++ b/servo/components/style/properties/longhands/padding.mako.rs
@@ -34,11 +34,11 @@
         "scroll-padding-%s" % side[0],
         "NonNegativeLengthPercentageOrAuto",
         "computed::NonNegativeLengthPercentageOrAuto::auto()",
         products="gecko",
         gecko_pref="layout.css.scroll-snap-v1.enabled",
         logical=side[1],
         logical_group="scroll-padding",
         spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-padding-%s" % side[0],
-        animation_value_type="ComputedValue",
+        animation_value_type="NonNegativeLengthPercentageOrAuto",
     )}
 % endfor
--- a/testing/raptor/raptor/tests/raptor-unity-webgl.ini
+++ b/testing/raptor/raptor/tests/raptor-unity-webgl.ini
@@ -14,13 +14,14 @@ apps = firefox
 
 [raptor-unity-webgl-chrome]
 apps = chrome
 
 [raptor-unity-webgl-geckoview]
 page_timeout = 900000  # 15 min
 page_cycles = 1
 apps = geckoview
+disabled = Bug 1524495
 
 [raptor-unity-webgl-refbrow]
 page_timeout = 900000  # 15 min
 page_cycles = 1
 apps = refbrow
--- a/testing/raptor/test/test_manifest.py
+++ b/testing/raptor/test/test_manifest.py
@@ -121,23 +121,27 @@ def test_get_raptor_test_list_chrome(cre
     args = create_args(app="chrome",
                        test="raptor-speedometer")
 
     test_list = get_raptor_test_list(args, mozinfo.os)
     assert len(test_list) == 1
     assert test_list[0]['name'] == 'raptor-speedometer-chrome'
 
 
-def test_get_raptor_test_list_geckoview(create_args):
+# Commented test_get_raptor_test_list_geckoview
+# in order to disable raptor-unity-webgl-geckoview - Bug 1524495
+"""
+ def test_get_raptor_test_list_geckoview(create_args):
     args = create_args(app="geckoview",
-                       test="raptor-unity-webgl")
+                         test="raptor-unity-webgl")
 
     test_list = get_raptor_test_list(args, mozinfo.os)
     assert len(test_list) == 1
     assert test_list[0]['name'] == 'raptor-unity-webgl-geckoview'
+"""
 
 
 def test_get_raptor_test_list_gecko_profiling(create_args):
     args = create_args(test="raptor-tp6-google-firefox",
                        gecko_profile=True)
 
     test_list = get_raptor_test_list(args, mozinfo.os)
     assert len(test_list) == 1
--- a/testing/web-platform/meta/service-workers/service-worker/fetch-csp.https.html.ini
+++ b/testing/web-platform/meta/service-workers/service-worker/fetch-csp.https.html.ini
@@ -1,11 +1,8 @@
 [fetch-csp.https.html]
   [Verify CSP control of fetch() in a Service Worker]
     expected:
-      if (os == "linux") and debug and not webrender and e10s and not sw-e10s: FAIL
-      if (os == "linux") and debug and not webrender and not e10s: FAIL
-      if (os == "linux") and debug and webrender: FAIL
-      if (os == "linux") and not debug: FAIL
+      if (os == "linux"): FAIL
       if (os == "win"): FAIL
       if (os == "mac"): FAIL
       if (os == "android"): FAIL
 
--- a/toolkit/components/perfmonitoring/PerformanceUtils.cpp
+++ b/toolkit/components/perfmonitoring/PerformanceUtils.cpp
@@ -55,32 +55,51 @@ nsTArray<RefPtr<PerformanceInfoPromise>>
     }
     for (DocGroup* docGroup : docGroups) {
       promises.AppendElement(docGroup->ReportPerformanceInfo());
     }
   }
   return promises;
 }
 
-nsresult GetTabSizes(nsGlobalWindowOuter* aWindow, nsTabSizes* aSizes) {
+void AddWindowTabSizes(nsGlobalWindowOuter* aWindow, nsTabSizes* aSizes) {
+  Document* document = aWindow->GetDocument();
+  if (document && document->GetCachedSizes(aSizes)) {
+    // We got a cached version
+    return;
+  }
+  // We measure the sizes on a fresh nsTabSizes instance
+  // because we want to cache the value and aSizes might
+  // already have some values from other windows.
+  nsTabSizes sizes;
+
   // Measure the window.
   SizeOfState state(moz_malloc_size_of);
   nsWindowSizes windowSizes(state);
   aWindow->AddSizeOfIncludingThis(windowSizes);
-
   // Measure the inner window, if there is one.
   nsGlobalWindowInner* inner = aWindow->GetCurrentInnerWindowInternal();
   if (inner != nullptr) {
     inner->AddSizeOfIncludingThis(windowSizes);
   }
+  windowSizes.addToTabSizes(&sizes);
+  if (document) {
+    document->SetCachedSizes(&sizes);
+  }
+  aSizes->mDom += sizes.mDom;
+  aSizes->mStyle += sizes.mStyle;
+  aSizes->mOther += sizes.mOther;
+}
 
-  windowSizes.addToTabSizes(aSizes);
+nsresult GetTabSizes(nsGlobalWindowOuter* aWindow, nsTabSizes* aSizes) {
+  // Add the window (and inner window) sizes. Might be cached.
+  AddWindowTabSizes(aWindow, aSizes);
+
   nsDOMWindowList* frames = aWindow->GetFrames();
   uint32_t length = frames->GetLength();
-
   // Measure this window's descendents.
   for (uint32_t i = 0; i < length; i++) {
     nsCOMPtr<nsPIDOMWindowOuter> child = frames->IndexedGetter(i);
     NS_ENSURE_STATE(child);
     nsGlobalWindowOuter* childWin = nsGlobalWindowOuter::Cast(child);
     nsresult rv = GetTabSizes(childWin, aSizes);
     NS_ENSURE_SUCCESS(rv, rv);
   }
--- a/toolkit/components/telemetry/Events.yaml
+++ b/toolkit/components/telemetry/Events.yaml
@@ -791,16 +791,27 @@ devtools.main:
     notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
     record_in_processes: ["main"]
     description: User navigates to a new page of an application such as about:debugging
     release_channel_collection: opt-out
     expiry_version: never
     extra_keys:
       page_type: Type of page the user navigates to (this-firefox, connect, runtime)
       session_id: The start time of the session in milliseconds since epoch (Unix Timestamp) e.g. 1396381378123.
+  show_profiler:
+    objects: ["aboutdebugging"]
+    bug_numbers: [1521511]
+    notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
+    record_in_processes: ["main"]
+    description: User has clicked on the "Open Profiler" button in a runtime page of about:debugging
+    release_channel_collection: opt-out
+    expiry_version: never
+    extra_keys:
+      runtime_id: Random id generated to track events related to a single runtime
+      session_id: The start time of the session in milliseconds since epoch (Unix Timestamp) e.g. 1396381378123.
   sidepanel_changed:
     objects: ["inspector", "netmonitor"]
     bug_numbers: [1463083, 1463169]
     notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
     record_in_processes: ["main"]
     description: User has switched sidepanel tabs.
     release_channel_collection: opt-out
     expiry_version: never
@@ -827,16 +838,28 @@ devtools.main:
     record_in_processes: ["main"]
     description: The amount of time a tool was opened for.
     release_channel_collection: opt-out
     expiry_version: never
     extra_keys:
       time_open: Time open.
       os: The OS name and version e.g. "Linux 4.4.0-1014-aws", "Darwin 14.5.0", "Windows_NT 6.1.7601" or "Windows_NT 10.0.15063." This can be used to make sense of data when a feature is only available from a particular operating system build number.
       session_id: The start time of the session in milliseconds since epoch (Unix Timestamp) e.g. 1396381378123.
+  update_conn_prompt:
+    objects: ["aboutdebugging"]
+    bug_numbers: [1521511]
+    notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
+    record_in_processes: ["main"]
+    description: User has clicked on the "Enable/Disable connection prompt" button in a runtime page of about:debugging
+    release_channel_collection: opt-out
+    expiry_version: never
+    extra_keys:
+      prompt_enabled: True if the user enables the prompt, false otherwise.
+      runtime_id: Random id generated to track events related to a single runtime
+      session_id: The start time of the session in milliseconds since epoch (Unix Timestamp) e.g. 1396381378123.
 
 security.ui.certerror:
   load:
     objects: ["aboutcerterror"]
     bug_numbers:
       - 1484255
       - 1505310
     description: >
--- a/widget/BasicEvents.h
+++ b/widget/BasicEvents.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_BasicEvents_h__
 #define mozilla_BasicEvents_h__
 
 #include <stdint.h>
 
 #include "mozilla/dom/EventTarget.h"
+#include "mozilla/layers/LayersTypes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/TimeStamp.h"
 #include "nsCOMPtr.h"
 #include "nsAtom.h"
 #include "nsISupportsImpl.h"
 #include "nsIWidget.h"
 #include "nsString.h"
 #include "Units.h"
@@ -473,17 +474,18 @@ class WidgetEvent : public WidgetEventTi
               EventClassID aEventClassID)
       : WidgetEventTime(),
         mClass(aEventClassID),
         mMessage(aMessage),
         mRefPoint(0, 0),
         mLastRefPoint(0, 0),
         mFocusSequenceNumber(0),
         mSpecifiedEventType(nullptr),
-        mPath(nullptr) {
+        mPath(nullptr),
+        mLayersId(layers::LayersId{0}) {
     MOZ_COUNT_CTOR(WidgetEvent);
     mFlags.Clear();
     mFlags.mIsTrusted = aIsTrusted;
     SetDefaultCancelableAndBubbles();
     SetDefaultComposed();
     SetDefaultComposedInNativeAnonymousContent();
   }
 
@@ -565,26 +567,32 @@ class WidgetEvent : public WidgetEventTi
   nsCOMPtr<dom::EventTarget> mOriginalTarget;
 
   /// The possible related target
   nsCOMPtr<dom::EventTarget> mRelatedTarget;
   nsCOMPtr<dom::EventTarget> mOriginalRelatedTarget;
 
   nsTArray<EventTargetChainItem>* mPath;
 
+  // The LayersId of the content process that this event should be
+  // dispatched to. This field is only used in the chrome process
+  // and doesn't get remoted to child processes.
+  layers::LayersId mLayersId;
+
   dom::EventTarget* GetDOMEventTarget() const;
   dom::EventTarget* GetCurrentDOMEventTarget() const;
   dom::EventTarget* GetOriginalDOMEventTarget() const;
 
   void AssignEventData(const WidgetEvent& aEvent, bool aCopyTargets) {
     // mClass should be initialized with the constructor.
     // mMessage should be initialized with the constructor.
     mRefPoint = aEvent.mRefPoint;
     // mLastRefPoint doesn't need to be copied.
     mFocusSequenceNumber = aEvent.mFocusSequenceNumber;
+    // mLayersId intentionally not copied, since it's not used within content
     AssignEventTime(aEvent);
     // mFlags should be copied manually if it's necessary.
     mSpecifiedEventType = aEvent.mSpecifiedEventType;
     // mSpecifiedEventTypeString should be copied manually if it's necessary.
     mTarget = aCopyTargets ? aEvent.mTarget : nullptr;
     mCurrentTarget = aCopyTargets ? aEvent.mCurrentTarget : nullptr;
     mOriginalTarget = aCopyTargets ? aEvent.mOriginalTarget : nullptr;
     mRelatedTarget = aCopyTargets ? aEvent.mRelatedTarget : nullptr;
--- a/widget/InputData.cpp
+++ b/widget/InputData.cpp
@@ -18,24 +18,29 @@
 
 namespace mozilla {
 
 using namespace dom;
 
 InputData::~InputData() {}
 
 InputData::InputData(InputType aInputType)
-    : mInputType(aInputType), mTime(0), mFocusSequenceNumber(0), modifiers(0) {}
+    : mInputType(aInputType),
+      mTime(0),
+      mFocusSequenceNumber(0),
+      mLayersId{0},
+      modifiers(0) {}
 
 InputData::InputData(InputType aInputType, uint32_t aTime, TimeStamp aTimeStamp,
                      Modifiers aModifiers)
     : mInputType(aInputType),
       mTime(aTime),
       mTimeStamp(aTimeStamp),
       mFocusSequenceNumber(0),
+      mLayersId{0},
       modifiers(aModifiers) {}
 
 SingleTouchData::SingleTouchData(int32_t aIdentifier,
                                  ScreenIntPoint aScreenPoint,
                                  ScreenSize aRadius, float aRotationAngle,
                                  float aForce)
     : mIdentifier(aIdentifier),
       mScreenPoint(aScreenPoint),
--- a/widget/InputData.h
+++ b/widget/InputData.h
@@ -84,16 +84,20 @@ class InputData {
   // Set in parallel to mTime until we determine it is safe to drop
   // platform-specific event times (see bug 77992).
   TimeStamp mTimeStamp;
   // The sequence number of the last potentially focus changing event handled
   // by APZ. This is used to track when that event has been processed by
   // content, and focus can be reconfirmed for async keyboard scrolling.
   uint64_t mFocusSequenceNumber;
 
+  // The LayersId of the content process that the corresponding WidgetEvent
+  // should be dispatched to.
+  layers::LayersId mLayersId;
+
   Modifiers modifiers;
 
   INPUTDATA_AS_CHILD_TYPE(MultiTouchInput, MULTITOUCH_INPUT)
   INPUTDATA_AS_CHILD_TYPE(MouseInput, MOUSE_INPUT)
   INPUTDATA_AS_CHILD_TYPE(PanGestureInput, PANGESTURE_INPUT)
   INPUTDATA_AS_CHILD_TYPE(PinchGestureInput, PINCHGESTURE_INPUT)
   INPUTDATA_AS_CHILD_TYPE(TapGestureInput, TAPGESTURE_INPUT)
   INPUTDATA_AS_CHILD_TYPE(ScrollWheelInput, SCROLLWHEEL_INPUT)
--- a/widget/nsGUIEventIPC.h
+++ b/widget/nsGUIEventIPC.h
@@ -1087,25 +1087,27 @@ struct ParamTraits<mozilla::InputData> {
   typedef mozilla::InputData paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
     WriteParam(aMsg, aParam.mInputType);
     WriteParam(aMsg, aParam.mTime);
     WriteParam(aMsg, aParam.mTimeStamp);
     WriteParam(aMsg, aParam.modifiers);
     WriteParam(aMsg, aParam.mFocusSequenceNumber);
+    WriteParam(aMsg, aParam.mLayersId);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter,
                    paramType* aResult) {
     return ReadParam(aMsg, aIter, &aResult->mInputType) &&
            ReadParam(aMsg, aIter, &aResult->mTime) &&
            ReadParam(aMsg, aIter, &aResult->mTimeStamp) &&
            ReadParam(aMsg, aIter, &aResult->modifiers) &&
-           ReadParam(aMsg, aIter, &aResult->mFocusSequenceNumber);
+           ReadParam(aMsg, aIter, &aResult->mFocusSequenceNumber) &&
+           ReadParam(aMsg, aIter, &aResult->mLayersId);
   }
 };
 
 template <>
 struct ParamTraits<mozilla::SingleTouchData> {
   typedef mozilla::SingleTouchData paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {