Merge autoland to mozilla-central. a=merge
authorCsoregi Natalia <ncsoregi@mozilla.com>
Thu, 17 Oct 2019 12:34:15 +0300
changeset 559294 273c3db836e1a2ffdb7ce46d28d85adbbf2b70ba
parent 559293 9e3ef2b6a8898c813666bd2e6c5f302dfde87653 (current diff)
parent 559290 20c215afd65de16e3b0427cc98ace7aba69e84c2 (diff)
child 559295 d333b6ef1fd3ccb2f492a8ac291ea983e5aeb366
child 559315 69becc01bb7e5a31d72ed4441d3f9972ae72b99c
child 559519 aad40aa64ebf88b84f03a93d7625f9f5c2591d77
push id12175
push userccoroiu@mozilla.com
push dateThu, 17 Oct 2019 19:29:09 +0000
treeherdermozilla-beta@d333b6ef1fd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone71.0a1
first release with
nightly linux32
273c3db836e1 / 71.0a1 / 20191017093524 / files
nightly linux64
273c3db836e1 / 71.0a1 / 20191017093524 / files
nightly mac
273c3db836e1 / 71.0a1 / 20191017093524 / files
nightly win32
273c3db836e1 / 71.0a1 / 20191017093524 / files
nightly win64
273c3db836e1 / 71.0a1 / 20191017093524 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central. a=merge
devtools/client/debugger/src/components/Editor/Highlight.css
testing/web-platform/meta/css/css-shadow-parts/multiple-parts.html.ini
--- a/browser/components/urlbar/UrlbarMuxerUnifiedComplete.jsm
+++ b/browser/components/urlbar/UrlbarMuxerUnifiedComplete.jsm
@@ -48,26 +48,29 @@ class MuxerUnifiedComplete extends Urlba
   }
 
   /**
    * Sorts results in the given UrlbarQueryContext.
    * @param {UrlbarQueryContext} context The query context.
    */
   sort(context) {
     // A Search in a Private Window result should only be shown when there are
-    // other results, and all of them are searches.
+    // other results, and all of them are searches. It should also not be shown
+    // if the user typed an alias, because it's an explicit search engine choice.
     let searchInPrivateWindowIndex = context.results.findIndex(
       r => r.type == UrlbarUtils.RESULT_TYPE.SEARCH && r.payload.inPrivateWindow
     );
     if (
       searchInPrivateWindowIndex != -1 &&
       (context.results.length == 1 ||
         context.results.some(
           r =>
-            r.type != UrlbarUtils.RESULT_TYPE.SEARCH || r.payload.keywordOffer
+            r.type != UrlbarUtils.RESULT_TYPE.SEARCH ||
+            r.payload.keywordOffer ||
+            (r.heuristic && r.payload.keyword)
         ))
     ) {
       // Remove the result.
       context.results.splice(searchInPrivateWindowIndex, 1);
     }
 
     if (!context.results.length) {
       return;
--- a/browser/components/urlbar/tests/browser/browser_urlbar_separatePrivateDefault.js
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_separatePrivateDefault.js
@@ -40,19 +40,27 @@ add_task(async function setup() {
   await Services.search.setDefaultPrivate(engine);
 
   // Add another engine in the first one-off position.
   let engine2 = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + "POSTSearchEngine.xml"
   );
   await Services.search.moveEngine(engine2, 0);
 
+  // Add an engine with an alias.
+  let aliasEngine = await Services.search.addEngineWithDetails("MozSearch", {
+    alias: "alias",
+    method: "GET",
+    template: "http://example.com/?q={searchTerms}",
+  });
+
   registerCleanupFunction(async () => {
     await Services.search.setDefault(oldDefaultEngine);
     await Services.search.setDefaultPrivate(oldDefaultPrivateEngine);
+    await Services.search.removeEngine(aliasEngine);
     await PlacesUtils.history.clear();
   });
 });
 
 async function AssertNoPrivateResult(win) {
   let count = await UrlbarTestUtils.getResultCount(win);
   Assert.ok(count > 0, "Sanity check result count");
   for (let i = 0; i < count; ++i) {
@@ -326,8 +334,27 @@ add_task(async function test_oneoff_sele
     let win = await promiseWindow;
     Assert.ok(
       PrivateBrowsingUtils.isWindowPrivate(win),
       "Should open a private window"
     );
     await BrowserTestUtils.closeWindow(win);
   });
 });
+
+add_task(async function test_alias() {
+  info(
+    "Test that 'Search in a Private Window' doesn's appear if an alias is typed"
+  );
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    waitForFocus,
+    value: "alias",
+  });
+  await AssertNoPrivateResult(window);
+
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    waitForFocus,
+    value: "alias something",
+  });
+  await AssertNoPrivateResult(window);
+});
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -614,26 +614,27 @@ option('--target', nargs=1,
 @imports(_from='mozbuild.configure.constants', _import='CPU')
 @imports(_from='mozbuild.configure.constants', _import='CPU_bitness')
 @imports(_from='mozbuild.configure.constants', _import='Endianness')
 @imports(_from='mozbuild.configure.constants', _import='Kernel')
 @imports(_from='mozbuild.configure.constants', _import='OS')
 @imports(_from='__builtin__', _import='ValueError')
 def split_triplet(triplet):
     # The standard triplet is defined as
-    #   CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
+    #   CPU_TYPE-VENDOR-OPERATING_SYSTEM
     # There is also a quartet form:
-    #   CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
+    #   CPU_TYPE-VENDOR-KERNEL-OPERATING_SYSTEM
     # But we can consider the "KERNEL-OPERATING_SYSTEM" as one.
-    # Additionally, some may omit "unknown" when the manufacturer
+    # Additionally, some may omit "unknown" when the vendor
     # is not specified and emit
     #   CPU_TYPE-OPERATING_SYSTEM
+    vendor = 'unknown'
     parts = triplet.split('-', 2)
     if len(parts) == 3:
-        cpu, _, os = parts
+        cpu, vendor, os = parts
     elif len(parts) == 2:
         cpu, os = parts
     else:
         raise ValueError("Unexpected triplet string: %s" % triplet)
 
     # Autoconf uses config.sub to validate and canonicalize those triplets,
     # but the granularity of its results has never been satisfying to our
     # use, so we've had our own, different, canonicalization. We've also
@@ -739,16 +740,17 @@ def split_triplet(triplet):
         cpu=CPU(canonical_cpu),
         bitness=CPU_bitness[canonical_cpu],
         kernel=Kernel(canonical_kernel),
         os=OS(canonical_os),
         endianness=Endianness(endianness),
         raw_cpu=cpu,
         raw_os=os,
         toolchain=toolchain,
+        vendor=vendor,
     )
 
 
 # This defines a fake target/host namespace for when running with --help
 # If either --host or --target is passed on the command line, then fall
 # back to the real deal.
 @depends('--help', '--host', '--target')
 def help_host_target(help, host, target):
--- a/build/moz.configure/rust.configure
+++ b/build/moz.configure/rust.configure
@@ -257,16 +257,22 @@ def rust_triple_alias(host_or_target, ho
                 else:
                     suffix = 'windows-msvc'
                 narrowed = [c for c in candidates if c.rust_target.endswith('-{}'.format(suffix))]
                 if len(narrowed) == 1:
                     return narrowed[0].rust_target
                 elif narrowed:
                     candidates = narrowed
 
+                vendor_aliases = {'pc': 'w64'}
+                narrowed = [c for c in candidates
+                            if vendor_aliases.get(c.target.vendor) == host_or_target.vendor]
+                if len(narrowed) == 1:
+                    return narrowed[0].rust_target
+
             # - For arm targets, correlate with arm_target
             #   we could be more thorough with the supported rust targets, but they
             #   don't support OSes that are supported to build Gecko anyways.
             #   Also, sadly, the only interface to check the rust target cpu features
             #   is --print target-spec-json, and it's unstable, so we have to rely on
             #   our own knowledge of what each arm target means.
             if host_or_target.cpu == 'arm' and host_or_target.endianness == 'little':
                 prefixes = []
@@ -321,16 +327,24 @@ def rust_triple_alias(host_or_target, ho
             narrowed = [
                 c for c in candidates
                 if c.target.raw_os == host_or_target.raw_os and
                    c.target.raw_cpu == host_or_target.raw_cpu
             ]
             if len(narrowed) == 1:
                 return narrowed[0].rust_target
 
+            # Finally, see if the vendor can be used to disambiguate.
+            narrowed = [
+                c for c in candidates
+                if c.target.vendor == host_or_target.vendor
+            ]
+            if len(narrowed) == 1:
+                return narrowed[0].rust_target
+
             return None
 
         rustc_target = find_candidate(candidates)
 
         if rustc_target is None:
             die("Don't know how to translate {} for rustc".format(
                 host_or_target.alias))
 
--- a/devtools/client/debugger/src/actions/sources/prettyPrint.js
+++ b/devtools/client/debugger/src/actions/sources/prettyPrint.js
@@ -11,26 +11,29 @@ import { remapBreakpoints } from "../bre
 
 import { setSymbols } from "./symbols";
 import { prettyPrint } from "../../workers/pretty-print";
 import {
   getPrettySourceURL,
   isGenerated,
   isJavaScript,
 } from "../../utils/source";
+import { isFulfilled } from "../../utils/async-value";
 import { loadSourceText } from "./loadSourceText";
 import { mapFrames } from "../pause";
 import { selectSpecificLocation } from "../sources";
 
 import {
   getSource,
+  getSourceContent,
   getSourceFromId,
   getSourceByURL,
   getSelectedLocation,
   getThreadContext,
+  getSourceActorsForSource,
 } from "../../selectors";
 
 import type { Action, ThunkArgs } from "../types";
 import { selectSource } from "./select";
 import type { Source, SourceContent, SourceActor, Context } from "../../types";
 
 export async function prettyPrintSource(
   sourceMaps: typeof SourceMaps,
@@ -75,28 +78,36 @@ export function createPrettySource(cx: C
       isWasm: false,
       introductionUrl: null,
       introductionType: undefined,
       isExtension: false,
       extensionName: null,
     };
 
     dispatch(({ type: "ADD_SOURCE", cx, source: prettySource }: Action));
-    await dispatch(selectSource(cx, prettySource.id));
+
+    const actors = getSourceActorsForSource(getState(), sourceId);
+    const content = getSourceContent(getState(), sourceId);
+    if (!content || !isFulfilled(content)) {
+      throw new Error("Cannot pretty-print a file that has not loaded");
+    }
+    await prettyPrintSource(sourceMaps, source, content.value, actors);
+    await dispatch(loadSourceText({ cx, source: prettySource }));
 
     return prettySource;
   };
 }
 
 function selectPrettyLocation(cx: Context, prettySource: Source) {
   return async ({ dispatch, sourceMaps, getState }: ThunkArgs) => {
     let location = getSelectedLocation(getState());
 
     if (location && location.line >= 1) {
       location = await sourceMaps.getOriginalLocation(location);
+
       return dispatch(
         selectSpecificLocation(cx, { ...location, sourceId: prettySource.id })
       );
     }
 
     return dispatch(selectSource(cx, prettySource.id));
   };
 }
@@ -120,30 +131,30 @@ export function togglePrettyPrint(cx: Co
       return {};
     }
 
     if (!source.isPrettyPrinted) {
       recordEvent("pretty_print");
     }
 
     await dispatch(loadSourceText({ cx, source }));
-
     assert(
       isGenerated(source),
       "Pretty-printing only allowed on generated sources"
     );
 
     const url = getPrettySourceURL(source.url);
     const prettySource = getSourceByURL(getState(), url);
 
     if (prettySource) {
       return dispatch(selectPrettyLocation(cx, prettySource));
     }
 
     const newPrettySource = await dispatch(createPrettySource(cx, sourceId));
+
     await dispatch(selectPrettyLocation(cx, newPrettySource));
 
     const threadcx = getThreadContext(getState());
     await dispatch(mapFrames(threadcx));
 
     await dispatch(setSymbols({ cx, source: newPrettySource }));
 
     await dispatch(remapBreakpoints(cx, sourceId));
deleted file mode 100644
--- a/devtools/client/debugger/src/components/Editor/Highlight.css
+++ /dev/null
@@ -1,29 +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/>. */
-
-.cm-highlight {
-  position: relative;
-}
-
-.cm-highlight::before {
-  position: absolute;
-  border-top-style: solid;
-  border-bottom-style: solid;
-  border-top-color: var(--theme-text-color-inactive);
-  border-bottom-color: var(--theme-text-color-inactive);
-  border-top-width: 1px;
-  border-bottom-width: 1px;
-  top: -1px;
-  bottom: 0;
-  left: 0;
-  right: 0;
-  content: "";
-  margin-bottom: -1px;
-}
-
-.cm-highlight-full::before {
-  border: 1px solid var(--theme-text-color-inactive);
-  border-radius: 2px;
-  margin: 0 -1px -1px -1px;
-}
--- a/devtools/client/debugger/src/components/Editor/index.js
+++ b/devtools/client/debugger/src/components/Editor/index.js
@@ -75,17 +75,16 @@ import {
   startOperation,
   endOperation,
 } from "../../utils/editor";
 
 import { resizeToggleButton, resizeBreakpointGutter } from "../../utils/ui";
 
 import "./Editor.css";
 import "./Breakpoints.css";
-import "./Highlight.css";
 import "./InlinePreview.css";
 
 import type SourceEditor from "../../utils/editor/source-editor";
 import type { SymbolDeclarations } from "../../workers/parser";
 import type {
   SourceLocation,
   SourceWithContent,
   ThreadContext,
--- a/devtools/client/debugger/src/components/Editor/moz.build
+++ b/devtools/client/debugger/src/components/Editor/moz.build
@@ -29,14 +29,13 @@ CompiledModules(
     'Tabs.js',
 )
 
 DevToolsModules(
     'Breakpoints.css',
     'ConditionalPanel.css',
     'Editor.css',
     'Footer.css',
-    'Highlight.css',
     'InlinePreview.css',
     'Preview.css',
     'SearchBar.css',
     'Tabs.css',
 )
--- a/devtools/client/debugger/src/debugger.css
+++ b/devtools/client/debugger/src/debugger.css
@@ -20,17 +20,16 @@
 @import url("./components/shared/ResultList.css");
 @import url("./components/shared/SearchInput.css");
 @import url("./components/shared/SourceIcon.css");
 
 @import url("./components/Editor/Breakpoints.css");
 @import url("./components/Editor/ConditionalPanel.css");
 @import url("./components/Editor/Editor.css");
 @import url("./components/Editor/Footer.css");
-@import url("./components/Editor/Highlight.css");
 @import url("./components/Editor/InlinePreview.css");
 @import url("./components/Editor/Preview.css");
 @import url("./components/Editor/Preview/Popup.css");
 @import url("./components/Editor/SearchBar.css");
 @import url("./components/Editor/Tabs.css");
 @import url("./components/PrimaryPanes/Outline.css");
 @import url("./components/PrimaryPanes/OutlineFilter.css");
 @import url("./components/PrimaryPanes/Sources.css");
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -87,16 +87,17 @@ skip-if = os == "linux" # bug 1351952
 skip-if = verify
 [browser_dbg-outline-pretty.js]
 [browser_dbg-outline-filter.js]
 [browser_dbg-pause-exceptions.js]
 skip-if = !debug && (os == "win" && os_version == "6.1") # Bug 1456441
 [browser_dbg-pause-on-next.js]
 [browser_dbg-pause-ux.js]
 skip-if = os == "win"
+[browser_dbg-navigation-when-paused.js]
 [browser_dbg-navigation.js]
 skip-if = (verify && debug && (os == 'mac')) || (os == 'linux' && debug && bits == 64) || (os == 'mac' && debug) # Bug 1307249
 [browser_dbg-minified.js]
 [browser_dbg-pretty-print.js]
 [browser_dbg-pretty-print-breakpoints.js]
 [browser_dbg-pretty-print-console.js]
 [browser_dbg-pretty-print-paused.js]
 [browser_dbg-preview.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-navigation-when-paused.js
@@ -0,0 +1,33 @@
+/* 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/>. */
+
+add_task(async function() {
+  const dbg = await initDebugger("doc-navigation-when-paused.html");
+
+  await togglePauseOnExceptions(dbg, true, true);
+
+  clickElementInTab("body");
+
+  await waitForPaused(dbg, "doc-navigation-when-paused.html");
+
+  assertPausedLocation(dbg);
+  assertDebugLine(dbg, 12);
+
+  await navigate(
+    dbg,
+    "doc-navigation-when-paused.html",
+    "doc-navigation-when-paused.html"
+  );
+
+  clickElementInTab("body");
+
+  await waitForPaused(dbg, "doc-navigation-when-paused.html");
+
+  // If breakpoints aren't properly ignored after navigation, this could
+  // potentially pause at line 9. This helper also ensures that the file
+  // source itself has loaded, which may not be the case if navigation cleared
+  // the source and nothing has sent it to the debugger client yet, as was
+  // the case in Bug 1581530.
+  assertDebugLine(dbg, 12);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/examples/doc-navigation-when-paused.html
@@ -0,0 +1,17 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<html>
+  <head>
+    <script>
+      window.onclick = () => {
+        Promise.resolve().then(() => {
+          throw new Error("Second");
+        });
+
+        throw new Error("First");
+      };
+    </script>
+  </head>
+  <body></body>
+</html>
--- a/devtools/client/inspector/markup/test/browser_markup_events_01.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_01.js
@@ -23,44 +23,44 @@ const TEST_DATA = [ // eslint-disable-li
       },
     ],
   },
   {
     selector: "#container",
     expected: [
       {
         type: "mouseover",
-        filename: TEST_URL + ":45",
+        filename: TEST_URL + ":45:31",
         attributes: ["Capturing", "DOM2"],
         handler:
           "function mouseoverHandler(event) {\n" +
           '  if (event.target.id !== "container") {\n' +
           '    let output = document.getElementById("output");\n' +
           "    output.textContent = event.target.textContent;\n" +
           "  }\n" +
           "}",
       },
     ],
   },
   {
     selector: "#multiple",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":52",
+        filename: TEST_URL + ":52:27",
         attributes: ["Bubbling", "DOM2"],
         handler:
           "function clickHandler(event) {\n" +
           '  let output = document.getElementById("output");\n' +
           '  output.textContent = "click";\n' +
           "}",
       },
       {
         type: "mouseup",
-        filename: TEST_URL + ":57",
+        filename: TEST_URL + ":57:29",
         attributes: ["Bubbling", "DOM2"],
         handler:
           "function mouseupHandler(event) {\n" +
           '  let output = document.getElementById("output");\n' +
           '  output.textContent = "mouseup";\n' +
           "}",
       },
     ],
@@ -76,17 +76,17 @@ const TEST_DATA = [ // eslint-disable-li
     beforeTest: async function(inspector, testActor) {
       const nodeMutated = inspector.once("markupmutation");
       await testActor.eval("window.wrappedJSObject.addNoeventsClickHandler();");
       await nodeMutated;
     },
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":72",
+        filename: TEST_URL + ":72:35",
         attributes: ["Bubbling", "DOM2"],
         handler:
           "function noeventsClickHandler(event) {\n" +
           '  alert("noevents has an event listener");\n' +
           "}",
       },
     ],
   },
@@ -112,17 +112,17 @@ const TEST_DATA = [ // eslint-disable-li
       },
     ],
   },
   {
     selector: "#handleevent",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":67",
+        filename: TEST_URL + ":67:29",
         attributes: ["Bubbling", "DOM2"],
         handler: "function(blah) {\n" + '  alert("handleEvent");\n' + "}",
       },
     ],
   },
 ];
 
 add_task(async function() {
--- a/devtools/client/inspector/markup/test/browser_markup_events_02.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_02.js
@@ -12,110 +12,110 @@ const TEST_URL = URL_ROOT + "doc_markup_
 loadHelperScript("helper_events_test_runner.js");
 
 const TEST_DATA = [ // eslint-disable-line
   {
     selector: "#fatarrow",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":39",
+        filename: TEST_URL + ":39:43",
         attributes: ["Bubbling", "DOM2"],
         handler: "() => {\n" + '  alert("Fat arrow without params!");\n' + "}",
       },
       {
         type: "click",
-        filename: TEST_URL + ":43",
+        filename: TEST_URL + ":43:43",
         attributes: ["Bubbling", "DOM2"],
         handler: "event => {\n" + '  alert("Fat arrow with 1 param!");\n' + "}",
       },
       {
         type: "click",
-        filename: TEST_URL + ":47",
+        filename: TEST_URL + ":47:43",
         attributes: ["Bubbling", "DOM2"],
         handler:
           "(event, foo, bar) => {\n" +
           '  alert("Fat arrow with 3 params!");\n' +
           "}",
       },
       {
         type: "click",
-        filename: TEST_URL + ":51",
+        filename: TEST_URL + ":51:43",
         attributes: ["Bubbling", "DOM2"],
         handler: "b => b",
       },
     ],
   },
   {
     selector: "#bound",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":62",
+        filename: TEST_URL + ":62:32",
         attributes: ["Bubbling", "DOM2"],
         handler: "function(event) {\n" + '  alert("Bound event");\n' + "}",
       },
     ],
   },
   {
     selector: "#boundhe",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":85",
+        filename: TEST_URL + ":85:29",
         attributes: ["Bubbling", "DOM2"],
         handler: "function() {\n" + '  alert("boundHandleEvent");\n' + "}",
       },
     ],
   },
   {
     selector: "#comment-inline",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":91",
+        filename: TEST_URL + ":91:47",
         attributes: ["Bubbling", "DOM2"],
         handler:
           "function functionProceededByInlineComment() {\n" +
           '  alert("comment-inline");\n' +
           "}",
       },
     ],
   },
   {
     selector: "#comment-streaming",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":96",
+        filename: TEST_URL + ":96:50",
         attributes: ["Bubbling", "DOM2"],
         handler:
           "function functionProceededByStreamingComment() {\n" +
           '  alert("comment-streaming");\n' +
           "}",
       },
     ],
   },
   {
     selector: "#anon-object-method",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":71",
+        filename: TEST_URL + ":71:34",
         attributes: ["Bubbling", "DOM2"],
         handler: "function() {\n" + '  alert("obj.anonObjectMethod");\n' + "}",
       },
     ],
   },
   {
     selector: "#object-method",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":75",
+        filename: TEST_URL + ":75:34",
         attributes: ["Bubbling", "DOM2"],
         handler: "function kay() {\n" + '  alert("obj.objectMethod");\n' + "}",
       },
     ],
   },
 ];
 
 add_task(async function() {
--- a/devtools/client/inspector/markup/test/browser_markup_events_03.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_03.js
@@ -12,75 +12,75 @@ const TEST_URL = URL_ROOT + "doc_markup_
 loadHelperScript("helper_events_test_runner.js");
 
 const TEST_DATA = [ // eslint-disable-line
   {
     selector: "#es6-method",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":66",
+        filename: TEST_URL + ":66:17",
         attributes: ["Bubbling", "DOM2"],
         handler:
           "es6Method(foo, bar) {\n" + '  alert("obj.es6Method");\n' + "}",
       },
     ],
   },
   {
     selector: "#generator",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":85",
+        filename: TEST_URL + ":85:25",
         attributes: ["Bubbling", "DOM2"],
         handler: "function* generator() {\n" + '  alert("generator");\n' + "}",
       },
     ],
   },
   {
     selector: "#anon-generator",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":43",
+        filename: TEST_URL + ":43:58",
         attributes: ["Bubbling", "DOM2"],
         handler: "function*() {\n" + '  alert("anonGenerator");\n' + "}",
       },
     ],
   },
   {
     selector: "#named-function-expression",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":20",
+        filename: TEST_URL + ":20:18",
         attributes: ["Bubbling", "DOM2"],
         handler:
           "function foo() {\n" + '  alert("namedFunctionExpression");\n' + "}",
       },
     ],
   },
   {
     selector: "#anon-function-expression",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":24",
+        filename: TEST_URL + ":24:43",
         attributes: ["Bubbling", "DOM2"],
         handler:
           "function() {\n" + '  alert("anonFunctionExpression");\n' + "}",
       },
     ],
   },
   {
     selector: "#returned-function",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":29",
+        filename: TEST_URL + ":29:27",
         attributes: ["Bubbling", "DOM2"],
         handler: "function bar() {\n" + '  alert("returnedFunction");\n' + "}",
       },
     ],
   },
 ];
 
 add_task(async function() {
--- a/devtools/client/inspector/markup/test/browser_markup_events_04.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_04.js
@@ -12,26 +12,26 @@ const TEST_URL = URL_ROOT + "doc_markup_
 loadHelperScript("helper_events_test_runner.js");
 
 const TEST_DATA = [ // eslint-disable-line
   {
     selector: "html",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":56",
+        filename: TEST_URL + ":56:67",
         attributes: ["Bubbling", "DOM2"],
         handler:
           "function(foo2, bar2) {\n" +
           '  alert("documentElement event listener clicked");\n' +
           "}",
       },
       {
         type: "click",
-        filename: TEST_URL + ":52",
+        filename: TEST_URL + ":52:51",
         attributes: ["Bubbling", "DOM2"],
         handler:
           "function(foo, bar) {\n" +
           '  alert("document event listener clicked");\n' +
           "}",
       },
       {
         type: "load",
@@ -41,42 +41,42 @@ const TEST_DATA = [ // eslint-disable-li
       },
     ],
   },
   {
     selector: "#constructed-function",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":1",
+        filename: TEST_URL + ":1:0",
         attributes: ["Bubbling", "DOM2"],
         handler: "function anonymous() {\n" + "\n" + "}",
       },
     ],
   },
   {
     selector: "#constructed-function-with-body-string",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":1",
+        filename: TEST_URL + ":1:0",
         attributes: ["Bubbling", "DOM2"],
         handler:
           "function anonymous(a, b, c) {\n" +
           '  alert("constructedFuncWithBodyString");\n' +
           "}",
       },
     ],
   },
   {
     selector: "#multiple-assignment",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":24",
+        filename: TEST_URL + ":24:57",
         attributes: ["Bubbling", "DOM2"],
         handler:
           "function multi() {\n" + '  alert("multipleAssignment");\n' + "}",
       },
     ],
   },
   {
     selector: "#promise",
@@ -100,17 +100,17 @@ const TEST_DATA = [ // eslint-disable-li
       },
     ],
   },
   {
     selector: "#handleEvent",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":77",
+        filename: TEST_URL + ":77:29",
         attributes: ["Bubbling", "DOM2"],
         handler:
           "function(event) {\n" +
           "  switch (event.type) {\n" +
           '    case "click":\n' +
           '      alert("handleEvent click");\n' +
           "  }\n" +
           "}",
--- a/devtools/client/inspector/markup/test/browser_markup_events_chrome_not_blocked.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_chrome_not_blocked.js
@@ -13,17 +13,17 @@ const FRAMESCRIPT_URL = `data:,(${frameS
 loadHelperScript("helper_events_test_runner.js");
 
 const TEST_DATA = [
   {
     selector: "div",
     expected: [
       {
         type: "click",
-        filename: `${FRAMESCRIPT_URL}:1`,
+        filename: `${FRAMESCRIPT_URL}:1:109`,
         attributes: ["Bubbling", "DOM2"],
         handler: `() => { /* Do nothing */ }`,
       },
     ],
   },
 ];
 
 add_task(async function() {
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.0.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.0.js
@@ -13,17 +13,17 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "html",
     expected: [
       {
         type: "DOMContentLoaded",
-        filename: URL_ROOT + TEST_LIB + ":1117",
+        filename: URL_ROOT + TEST_LIB + ":1117:16",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           function() {
             // Make sure that the DOM is not already loaded
             if (!jQuery.isReady) {
@@ -39,17 +39,17 @@ const TEST_DATA = [
                 // Reset the list of functions
                 jQuery.readyList = null;
               }
             }
           }`
       },
       {
         type: "load",
-        filename: TEST_URL + ":27",
+        filename: TEST_URL + ":27:38",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           () => {
             var handler1 = function liveDivDblClick() {
               alert(1);
@@ -101,17 +101,17 @@ const TEST_DATA = [
             var div = $("div")[0];
             $(div).click(handler7);
             $(div).click(handler8);
             $(div).keydown(handler9);
           }`
       },
       {
         type: "load",
-        filename: URL_ROOT + TEST_LIB + ":894",
+        filename: URL_ROOT + TEST_LIB + ":894:18",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           function(event) {
             if (typeof jQuery == "undefined") return;
 
@@ -137,39 +137,39 @@ const TEST_DATA = [
       }
     ]
   },
   {
     selector: "#testdiv",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":34",
+        filename: TEST_URL + ":34:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick1() {
             alert(7);
           }`
       },
       {
         type: "click",
-        filename: TEST_URL + ":35",
+        filename: TEST_URL + ":35:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick2() {
             alert(8);
           }`
       },
       {
         type: "click",
-        filename: URL_ROOT + TEST_LIB + ":894",
+        filename: URL_ROOT + TEST_LIB + ":894:18",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           function(event) {
             if (typeof jQuery == "undefined") return;
 
@@ -190,28 +190,28 @@ const TEST_DATA = [
               }
             }
 
             return returnValue;
           }`
       },
       {
         type: "keydown",
-        filename: TEST_URL + ":36",
+        filename: TEST_URL + ":36:42",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divKeyDown() {
             alert(9);
           }`
       },
       {
         type: "keydown",
-        filename: URL_ROOT + TEST_LIB + ":894",
+        filename: URL_ROOT + TEST_LIB + ":894:18",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           function(event) {
             if (typeof jQuery == "undefined") return;
 
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.1.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.1.js
@@ -13,17 +13,17 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "html",
     expected: [
       {
         type: "load",
-        filename: TEST_URL + ":27",
+        filename: TEST_URL + ":27:38",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           () => {
             var handler1 = function liveDivDblClick() {
               alert(1);
@@ -75,17 +75,17 @@ const TEST_DATA = [
             var div = $("div")[0];
             $(div).click(handler7);
             $(div).click(handler8);
             $(div).keydown(handler9);
           }`
       },
       {
         type: "load",
-        filename: URL_ROOT + TEST_LIB + ":1224",
+        filename: URL_ROOT + TEST_LIB + ":1224:17",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           function(event) {
             if (typeof jQuery == "undefined") return false;
 
@@ -121,39 +121,39 @@ const TEST_DATA = [
       }
     ]
   },
   {
     selector: "#testdiv",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":34",
+        filename: TEST_URL + ":34:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick1() {
             alert(7);
           }`
       },
       {
         type: "click",
-        filename: TEST_URL + ":35",
+        filename: TEST_URL + ":35:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick2() {
             alert(8);
           }`
       },
       {
         type: "click",
-        filename: URL_ROOT + TEST_LIB + ":1224",
+        filename: URL_ROOT + TEST_LIB + ":1224:17",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           function(event) {
             if (typeof jQuery == "undefined") return false;
 
@@ -184,28 +184,28 @@ const TEST_DATA = [
             // Clean up added properties in IE to prevent memory leak
             if (jQuery.browser.msie) event.target = event.preventDefault = event.stopPropagation = event.handler = event.data = null;
 
             return returnValue;
           }`
       },
       {
         type: "keydown",
-        filename: TEST_URL + ":36",
+        filename: TEST_URL + ":36:42",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divKeyDown() {
             alert(9);
           }`
       },
       {
         type: "keydown",
-        filename: URL_ROOT + TEST_LIB + ":1224",
+        filename: URL_ROOT + TEST_LIB + ":1224:17",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           function(event) {
             if (typeof jQuery == "undefined") return false;
 
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.11.1.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.11.1.js
@@ -13,17 +13,17 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "html",
     expected: [
       {
         type: "load",
-        filename: TEST_URL + ":27",
+        filename: TEST_URL + ":27:38",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           () => {
             var handler1 = function liveDivDblClick() {
               alert(1);
@@ -81,92 +81,92 @@ const TEST_DATA = [
     ]
   },
 
   {
     selector: "#testdiv",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":34",
+        filename: TEST_URL + ":34:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick1() {
             alert(7);
           }`
       },
       {
         type: "click",
-        filename: TEST_URL + ":35",
+        filename: TEST_URL + ":35:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick2() {
             alert(8);
           }`
       },
       {
         type: "keydown",
-        filename: TEST_URL + ":36",
+        filename: TEST_URL + ":36:42",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divKeyDown() {
             alert(9);
           }`
       }
     ]
   },
 
   {
     selector: "#livediv",
     expected: [
       {
         type: "dragend",
-        filename: TEST_URL + ":31",
+        filename: TEST_URL + ":31:46",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDragEnd() {
             alert(4);
           }`
       },
       {
         type: "dragleave",
-        filename: TEST_URL + ":30",
+        filename: TEST_URL + ":30:48",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDragLeave() {
             alert(3);
           }`
       },
       {
         type: "dragover",
-        filename: TEST_URL + ":33",
+        filename: TEST_URL + ":33:47",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDragOver() {
             alert(6);
           }`
       },
       {
         type: "drop",
-        filename: TEST_URL + ":32",
+        filename: TEST_URL + ":32:43",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDrop() {
             alert(5);
           }`
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.2.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.2.js
@@ -13,17 +13,17 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "html",
     expected: [
       {
         type: "load",
-        filename: TEST_URL + ":27",
+        filename: TEST_URL + ":27:38",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           () => {
             var handler1 = function liveDivDblClick() {
               alert(1);
@@ -80,65 +80,65 @@ const TEST_DATA = [
       },
     ]
   },
   {
     selector: "#testdiv",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":34",
+        filename: TEST_URL + ":34:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick1() {
             alert(7);
           }`
       },
       {
         type: "click",
-        filename: TEST_URL + ":35",
+        filename: TEST_URL + ":35:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick2() {
             alert(8);
           }`
       },
       {
         type: "click",
-        filename: URL_ROOT + TEST_LIB + ":24",
+        filename: URL_ROOT + TEST_LIB + ":24:10040",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           function() {
             var val;
             if (typeof jQuery == "undefined" || jQuery.event.triggered) return val;
             val = jQuery.event.handle.apply(element, arguments);
             return val;
           }`
       },
       {
         type: "keydown",
-        filename: TEST_URL + ":36",
+        filename: TEST_URL + ":36:42",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divKeyDown() {
             alert(9);
           }`
       },
       {
         type: "keydown",
-        filename: URL_ROOT + TEST_LIB + ":24",
+        filename: URL_ROOT + TEST_LIB + ":24:10040",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           function() {
             var val;
             if (typeof jQuery == "undefined" || jQuery.event.triggered) return val;
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.3.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.3.js
@@ -13,30 +13,30 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "html",
     expected: [
       {
         type: "DOMContentLoaded",
-        filename: URL_ROOT + TEST_LIB + ":19",
+        filename: URL_ROOT + TEST_LIB + ":19:18937",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           function() {
             document.removeEventListener("DOMContentLoaded", arguments.callee, false);
             n.ready()
           }`
       },
       {
         type: "load",
-        filename: TEST_URL + ":27",
+        filename: TEST_URL + ":27:38",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           () => {
             var handler1 = function liveDivDblClick() {
               alert(1);
@@ -93,67 +93,67 @@ const TEST_DATA = [
       }
     ]
   },
   {
     selector: "#testdiv",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":34",
+        filename: TEST_URL + ":34:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick1() {
             alert(7);
           }`
       },
       {
         type: "click",
-        filename: TEST_URL + ":35",
+        filename: TEST_URL + ":35:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick2() {
             alert(8);
           }`
       },
       {
         type: "keydown",
-        filename: TEST_URL + ":36",
+        filename: TEST_URL + ":36:42",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divKeyDown() {
             alert(9);
           }`
       }
     ]
   },
   {
     selector: "#livediv",
     expected: [
       {
         type: "dblclick",
-        filename: TEST_URL + ":28",
+        filename: TEST_URL + ":28:47",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function() {
             return E.apply(this, arguments)
           }`
       },
       {
         type: "dragstart",
-        filename: TEST_URL + ":29",
+        filename: TEST_URL + ":29:48",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function() {
             return E.apply(this, arguments)
           }`
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.4.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.4.js
@@ -13,30 +13,30 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "html",
     expected: [
       {
         type: "DOMContentLoaded",
-        filename: URL_ROOT + TEST_LIB + ":32",
+        filename: URL_ROOT + TEST_LIB + ":32:355",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           function() {
             s.removeEventListener(\"DOMContentLoaded\", M, false);
             c.ready()
           }`
       },
       {
         type: "load",
-        filename: TEST_URL + ":27",
+        filename: TEST_URL + ":27:38",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           () => {
             var handler1 = function liveDivDblClick() {
               alert(1);
@@ -88,17 +88,17 @@ const TEST_DATA = [
             var div = $("div")[0];
             $(div).click(handler7);
             $(div).click(handler8);
             $(div).keydown(handler9);
           }`
       },
       {
         type: "load",
-        filename: URL_ROOT + TEST_LIB + ":26",
+        filename: URL_ROOT + TEST_LIB + ":26:107",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           function() {
             if (!c.isReady) {
               if (!s.body) return setTimeout(c.ready, 13);
@@ -113,91 +113,91 @@ const TEST_DATA = [
       }
     ]
   },
   {
     selector: "#testdiv",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":34",
+        filename: TEST_URL + ":34:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick1() {
             alert(7);
           }`
       },
       {
         type: "click",
-        filename: TEST_URL + ":35",
+        filename: TEST_URL + ":35:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick2() {
             alert(8);
           }`
       },
       {
         type: "keydown",
-        filename: TEST_URL + ":36",
+        filename: TEST_URL + ":36:42",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divKeyDown() {
             alert(9);
           }`
       }
     ]
   },
   {
     selector: "#livediv",
     expected: [
       {
         type: "dblclick",
-        filename: TEST_URL + ":28",
+        filename: TEST_URL + ":28:47",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function() {
             return a.apply(d || this, arguments)
           }`
       },
       {
         type: "dblclick",
-        filename: URL_ROOT + TEST_LIB + ":17",
+        filename: URL_ROOT + TEST_LIB + ":17:183",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function() {
             return a.apply(d || this, arguments)
           }`
       },
       {
         type: "dragstart",
-        filename: TEST_URL + ":29",
+        filename: TEST_URL + ":29:48",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function() {
             return a.apply(d || this, arguments)
           }`
       },
       {
         type: "dragstart",
-        filename: URL_ROOT + TEST_LIB + ":17",
+        filename: URL_ROOT + TEST_LIB + ":17:183",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function() {
             return a.apply(d || this, arguments)
           }`
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.6.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.6.js
@@ -15,29 +15,29 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "html",
     expected: [
       {
         type: "DOMContentLoaded",
-        filename: URL_ROOT + TEST_LIB + ":16",
+        filename: URL_ROOT + TEST_LIB + ":16:14483",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           function() {
             c.removeEventListener("DOMContentLoaded", z, !1), e.ready()
           }`
       },
       {
         type: "load",
-        filename: TEST_URL + ":27",
+        filename: TEST_URL + ":27:38",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           () => {
             var handler1 = function liveDivDblClick() {
               alert(1);
@@ -89,17 +89,17 @@ const TEST_DATA = [
             var div = $("div")[0];
             $(div).click(handler7);
             $(div).click(handler8);
             $(div).keydown(handler9);
           }`
       },
       {
         type: "load",
-        filename: URL_ROOT + TEST_LIB + ":16",
+        filename: URL_ROOT + TEST_LIB + ":16:10001",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           function(a) {
             if (a === !0 && !--e.readyWait || a !== !0 && !e.isReady) {
               if (!c.body) return setTimeout(e.ready, 1);
@@ -111,67 +111,67 @@ const TEST_DATA = [
       }
     ]
   },
   {
     selector: "#testdiv",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":34",
+        filename: TEST_URL + ":34:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick1() {
             alert(7);
           }`
       },
       {
         type: "click",
-        filename: TEST_URL + ":35",
+        filename: TEST_URL + ":35:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick2() {
             alert(8);
           }`
       },
       {
         type: "keydown",
-        filename: TEST_URL + ":36",
+        filename: TEST_URL + ":36:42",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divKeyDown() {
             alert(9);
           }`
       }
     ]
   },
   {
     selector: "#livediv",
     expected: [
       {
         type: "dblclick",
-        filename: TEST_URL + ":28",
+        filename: TEST_URL + ":28:47",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDblClick() {
             alert(1);
           }`
       },
       {
         type: "dblclick",
-        filename: URL_ROOT + TEST_LIB + ":16",
+        filename: URL_ROOT + TEST_LIB + ":16:4732",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function M(a) {
             var b, c, d, e, g, h, i, j, k, l, m, n, o, p = [],
               q = [],
@@ -206,29 +206,29 @@ const TEST_DATA = [
                 }
               }
               return b
             }
           }`
       },
       {
         type: "dragend",
-        filename: TEST_URL + ":31",
+        filename: TEST_URL + ":31:46",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDragEnd() {
             alert(4);
           }`
       },
       {
         type: "dragend",
-        filename: URL_ROOT + TEST_LIB + ":16",
+        filename: URL_ROOT + TEST_LIB + ":16:4732",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function M(a) {
             var b, c, d, e, g, h, i, j, k, l, m, n, o, p = [],
               q = [],
@@ -263,29 +263,29 @@ const TEST_DATA = [
                 }
               }
               return b
             }
           }`
       },
       {
         type: "dragleave",
-        filename: TEST_URL + ":30",
+        filename: TEST_URL + ":30:48",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDragLeave() {
             alert(3);
           }`
       },
       {
         type: "dragleave",
-        filename: URL_ROOT + TEST_LIB + ":16",
+        filename: URL_ROOT + TEST_LIB + ":16:4732",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function M(a) {
             var b, c, d, e, g, h, i, j, k, l, m, n, o, p = [],
               q = [],
@@ -320,29 +320,29 @@ const TEST_DATA = [
                 }
               }
               return b
             }
           }`
       },
       {
         type: "dragstart",
-        filename: TEST_URL + ":29",
+        filename: TEST_URL + ":29:48",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDragStart() {
             alert(2);
           }`
       },
       {
         type: "dragstart",
-        filename: URL_ROOT + TEST_LIB + ":16",
+        filename: URL_ROOT + TEST_LIB + ":16:4732",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function M(a) {
             var b, c, d, e, g, h, i, j, k, l, m, n, o, p = [],
               q = [],
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.7.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.7.js
@@ -15,29 +15,29 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "html",
     expected: [
       {
         type: "DOMContentLoaded",
-        filename: URL_ROOT + TEST_LIB + ":2",
+        filename: URL_ROOT + TEST_LIB + ":2:14177",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           function() {
             c.removeEventListener("DOMContentLoaded", C, !1), e.ready()
           }`
       },
       {
         type: "load",
-        filename: TEST_URL + ":27",
+        filename: TEST_URL + ":27:38",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           () => {
             var handler1 = function liveDivDblClick() {
               alert(1);
@@ -89,17 +89,17 @@ const TEST_DATA = [
             var div = $("div")[0];
             $(div).click(handler7);
             $(div).click(handler8);
             $(div).keydown(handler9);
           }`
       },
       {
         type: "load",
-        filename: URL_ROOT + TEST_LIB + ":2",
+        filename: URL_ROOT + TEST_LIB + ":2:9526",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           function(a) {
             if (a === !0 && !--e.readyWait || a !== !0 && !e.isReady) {
               if (!c.body) return setTimeout(e.ready, 1);
@@ -111,115 +111,115 @@ const TEST_DATA = [
       }
     ]
   },
   {
     selector: "#testdiv",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":34",
+        filename: TEST_URL + ":34:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick1() {
             alert(7);
           }`
       },
       {
         type: "click",
-        filename: TEST_URL + ":35",
+        filename: TEST_URL + ":35:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick2() {
             alert(8);
           }`
       },
       {
         type: "keydown",
-        filename: TEST_URL + ":36",
+        filename: TEST_URL + ":36:42",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divKeyDown() {
             alert(9);
           }`
       }
     ]
   },
   {
     selector: "#livediv",
     expected: [
       {
         type: "dblclick",
-        filename: TEST_URL + ":28",
+        filename: TEST_URL + ":28:47",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDblClick() {
             alert(1);
           }`
       },
       {
         type: "dragend",
-        filename: TEST_URL + ":31",
+        filename: TEST_URL + ":31:46",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDragEnd() {
             alert(4);
           }`
       },
       {
         type: "dragleave",
-        filename: TEST_URL + ":30",
+        filename: TEST_URL + ":30:48",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDragLeave() {
             alert(3);
           }`
       },
       {
         type: "dragover",
-        filename: TEST_URL + ":33",
+        filename: TEST_URL + ":33:47",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDragOver() {
             alert(6);
           }`
       },
       {
         type: "dragstart",
-        filename: TEST_URL + ":29",
+        filename: TEST_URL + ":29:48",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDragStart() {
             alert(2);
           }`
       },
       {
         type: "drop",
-        filename: TEST_URL + ":32",
+        filename: TEST_URL + ":32:43",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDrop() {
             alert(5);
           }`
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_2.1.1.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_2.1.1.js
@@ -15,17 +15,17 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "html",
     expected: [
       {
         type: "load",
-        filename: TEST_URL + ":27",
+        filename: TEST_URL + ":27:38",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `
           () => {
             var handler1 = function liveDivDblClick() {
               alert(1);
@@ -82,91 +82,91 @@ const TEST_DATA = [
       }
     ]
   },
   {
     selector: "#testdiv",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":34",
+        filename: TEST_URL + ":34:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick1() {
             alert(7);
           }`
       },
       {
         type: "click",
-        filename: TEST_URL + ":35",
+        filename: TEST_URL + ":35:41",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divClick2() {
             alert(8);
           }`
       },
       {
         type: "keydown",
-        filename: TEST_URL + ":36",
+        filename: TEST_URL + ":36:42",
         attributes: [
           "jQuery"
         ],
         handler: `
           function divKeyDown() {
             alert(9);
           }`
       }
     ]
   },
   {
     selector: "#livediv",
     expected: [
       {
         type: "dragend",
-        filename: TEST_URL + ":31",
+        filename: TEST_URL + ":31:46",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDragEnd() {
             alert(4);
           }`
       },
       {
         type: "dragleave",
-        filename: TEST_URL + ":30",
+        filename: TEST_URL + ":30:48",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDragLeave() {
             alert(3);
           }`
       },
       {
         type: "dragover",
-        filename: TEST_URL + ":33",
+        filename: TEST_URL + ":33:47",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDragOver() {
             alert(6);
           }`
       },
       {
         type: "drop",
-        filename: TEST_URL + ":32",
+        filename: TEST_URL + ":32:43",
         attributes: [
           "jQuery",
           "Live"
         ],
         handler: `
           function liveDivDrop() {
             alert(5);
           }`
--- a/devtools/client/inspector/markup/test/browser_markup_events_object_listener.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_object_listener.js
@@ -13,28 +13,28 @@ const TEST_URL = URL_ROOT + "doc_markup_
 loadHelperScript("helper_events_test_runner.js");
 
 const TEST_DATA = [ // eslint-disable-line
   {
     selector: "#valid-object-listener",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":17",
+        filename: TEST_URL + ":17:23",
         attributes: ["Bubbling", "DOM2"],
         handler: `() => {\n` + `  console.log("handleEvent");\n` + `}`,
       },
     ],
   },
   {
     selector: "#valid-invalid-object-listeners",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":24",
+        filename: TEST_URL + ":24:23",
         attributes: ["Bubbling", "DOM2"],
         handler: `() => {\n` + `  console.log("handleEvent");\n` + `}`,
       },
     ],
   },
 ];
 
 add_task(async function() {
--- a/devtools/client/inspector/markup/test/browser_markup_events_react_development_15.4.1.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_react_development_15.4.1.js
@@ -16,107 +16,107 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "#inline",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":17530",
+        filename: TEST_LIB + ":17530:42",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function emptyFunction() {}`
       },
       {
         type: "onClick",
-        filename: TEST_URL + ":21",
+        filename: TEST_URL + ":21:33",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function() {
             alert("inlineFunction");
           }`
       }
     ]
   },
   {
     selector: "#external",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":17530",
+        filename: TEST_LIB + ":17530:42",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function emptyFunction() {}`
       },
       {
         type: "onClick",
-        filename: TEST_EXTERNAL_LISTENERS + ":4",
+        filename: TEST_EXTERNAL_LISTENERS + ":4:25",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function externalFunction() {
             alert("externalFunction");
           }`
       }
     ]
   },
   {
     selector: "#externalinline",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":17530",
+        filename: TEST_LIB + ":17530:42",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function emptyFunction() {}`
       },
       {
         type: "onClick",
-        filename: TEST_EXTERNAL_LISTENERS + ":4",
+        filename: TEST_EXTERNAL_LISTENERS + ":4:25",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function externalFunction() {
             alert("externalFunction");
           }`
       },
       {
         type: "onMouseUp",
-        filename: TEST_URL + ":21",
+        filename: TEST_URL + ":21:33",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function() {
             alert("inlineFunction");
           }`
       }
     ]
   },
   {
     selector: "#externalcapturing",
     expected: [
       {
         type: "onClickCapture",
-        filename: TEST_EXTERNAL_LISTENERS + ":8",
+        filename: TEST_EXTERNAL_LISTENERS + ":8:34",
         attributes: [
           "Capturing",
           "React"
         ],
         handler: `
           function externalCapturingFunction() {
             alert("externalCapturingFunction");
           }`
--- a/devtools/client/inspector/markup/test/browser_markup_events_react_development_15.4.1_jsx.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_react_development_15.4.1_jsx.js
@@ -18,107 +18,107 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "#inlinejsx",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":17530",
+        filename: TEST_LIB + ":17530:42",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function emptyFunction() {}`
       },
       {
         type: "onClick",
-        filename: TEST_LIB_BABEL + ":10",
+        filename: TEST_LIB_BABEL + ":10:41",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function inlineFunction() {
             alert("inlineFunction");
           }`
       }
     ]
   },
   {
     selector: "#externaljsx",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":17530",
+        filename: TEST_LIB + ":17530:42",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function emptyFunction() {}`
       },
       {
         type: "onClick",
-        filename: TEST_EXTERNAL_LISTENERS + ":4",
+        filename: TEST_EXTERNAL_LISTENERS + ":4:25",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function externalFunction() {
             alert("externalFunction");
           }`
       }
     ]
   },
   {
     selector: "#externalinlinejsx",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":17530",
+        filename: TEST_LIB + ":17530:42",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function emptyFunction() {}`
       },
       {
         type: "onClick",
-        filename: TEST_EXTERNAL_LISTENERS + ":4",
+        filename: TEST_EXTERNAL_LISTENERS + ":4:25",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function externalFunction() {
             alert("externalFunction");
           }`
       },
       {
         type: "onMouseUp",
-        filename: TEST_LIB_BABEL + ":10",
+        filename: TEST_LIB_BABEL + ":10:41",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function inlineFunction() {
             alert("inlineFunction");
           }`
       }
     ]
   },
   {
     selector: "#externalcapturingjsx",
     expected: [
       {
         type: "onClickCapture",
-        filename: TEST_EXTERNAL_LISTENERS + ":8",
+        filename: TEST_EXTERNAL_LISTENERS + ":8:34",
         attributes: [
           "Capturing",
           "React"
         ],
         handler: `
           function externalCapturingFunction() {
             alert("externalCapturingFunction");
           }`
--- a/devtools/client/inspector/markup/test/browser_markup_events_react_production_15.3.1.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_react_production_15.3.1.js
@@ -16,107 +16,107 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "#inline",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":16",
+        filename: TEST_LIB + ":16:27180",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function() {}`
       },
       {
         type: "onClick",
-        filename: TEST_URL + ":21",
+        filename: TEST_URL + ":21:33",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function() {
             alert("inlineFunction");
           }`
       }
     ]
   },
   {
     selector: "#external",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":16",
+        filename: TEST_LIB + ":16:27180",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function() {}`
       },
       {
         type: "onClick",
-        filename: TEST_EXTERNAL_LISTENERS + ":4",
+        filename: TEST_EXTERNAL_LISTENERS + ":4:25",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function externalFunction() {
             alert("externalFunction");
           }`
       }
     ]
   },
   {
     selector: "#externalinline",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":16",
+        filename: TEST_LIB + ":16:27180",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function() {}`
       },
       {
         type: "onClick",
-        filename: TEST_EXTERNAL_LISTENERS + ":4",
+        filename: TEST_EXTERNAL_LISTENERS + ":4:25",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function externalFunction() {
             alert("externalFunction");
           }`
       },
       {
         type: "onMouseUp",
-        filename: TEST_URL + ":21",
+        filename: TEST_URL + ":21:33",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function() {
             alert("inlineFunction");
           }`
       }
     ]
   },
   {
     selector: "#externalcapturing",
     expected: [
       {
         type: "onClickCapture",
-        filename: TEST_EXTERNAL_LISTENERS + ":8",
+        filename: TEST_EXTERNAL_LISTENERS + ":8:34",
         attributes: [
           "Capturing",
           "React"
         ],
         handler: `
           function externalCapturingFunction() {
             alert("externalCapturingFunction");
           }`
--- a/devtools/client/inspector/markup/test/browser_markup_events_react_production_15.3.1_jsx.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_react_production_15.3.1_jsx.js
@@ -18,107 +18,107 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "#inlinejsx",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":16",
+        filename: TEST_LIB + ":16:27180",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function() {}`
       },
       {
         type: "onClick",
-        filename: TEST_LIB_BABEL + ":10",
+        filename: TEST_LIB_BABEL + ":10:41",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function() {
             alert("inlineFunction");
           }`
       }
     ]
   },
   {
     selector: "#externaljsx",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":16",
+        filename: TEST_LIB + ":16:27180",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function() {}`
       },
       {
         type: "onClick",
-        filename: TEST_EXTERNAL_LISTENERS + ":4",
+        filename: TEST_EXTERNAL_LISTENERS + ":4:25",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function externalFunction() {
             alert("externalFunction");
           }`
       }
     ]
   },
   {
     selector: "#externalinlinejsx",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":16",
+        filename: TEST_LIB + ":16:27180",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function() {}`
       },
       {
         type: "onClick",
-        filename: TEST_EXTERNAL_LISTENERS + ":4",
+        filename: TEST_EXTERNAL_LISTENERS + ":4:25",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function externalFunction() {
             alert("externalFunction");
           }`
       },
       {
         type: "onMouseUp",
-        filename: TEST_LIB_BABEL + ":10",
+        filename: TEST_LIB_BABEL + ":10:41",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function() {
             alert("inlineFunction");
           }`
       }
     ]
   },
   {
     selector: "#externalcapturingjsx",
     expected: [
       {
         type: "onClickCapture",
-        filename: TEST_EXTERNAL_LISTENERS + ":8",
+        filename: TEST_EXTERNAL_LISTENERS + ":8:34",
         attributes: [
           "Capturing",
           "React"
         ],
         handler: `
           function externalCapturingFunction() {
             alert("externalCapturingFunction");
           }`
--- a/devtools/client/inspector/markup/test/browser_markup_events_react_production_16.2.0.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_react_production_16.2.0.js
@@ -16,107 +16,107 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "#inline",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":93",
+        filename: TEST_LIB + ":93:417",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function() {}`
       },
       {
         type: "onClick",
-        filename: TEST_URL + ":21",
+        filename: TEST_URL + ":21:22",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           inlineFunction() {
             alert("inlineFunction");
           }`
       }
     ]
   },
   {
     selector: "#external",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":93",
+        filename: TEST_LIB + ":93:417",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function() {}`
       },
       {
         type: "onClick",
-        filename: TEST_EXTERNAL_LISTENERS + ":4",
+        filename: TEST_EXTERNAL_LISTENERS + ":4:25",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function externalFunction() {
             alert("externalFunction");
           }`
       }
     ]
   },
   {
     selector: "#externalinline",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":93",
+        filename: TEST_LIB + ":93:417",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function() {}`
       },
       {
         type: "onClick",
-        filename: TEST_EXTERNAL_LISTENERS + ":4",
+        filename: TEST_EXTERNAL_LISTENERS + ":4:25",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function externalFunction() {
             alert("externalFunction");
           }`
       },
       {
         type: "onMouseUp",
-        filename: TEST_URL + ":21",
+        filename: TEST_URL + ":21:22",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           inlineFunction() {
             alert("inlineFunction");
           }`
       }
     ]
   },
   {
     selector: "#externalcapturing",
     expected: [
       {
         type: "onClickCapture",
-        filename: TEST_EXTERNAL_LISTENERS + ":8",
+        filename: TEST_EXTERNAL_LISTENERS + ":8:34",
         attributes: [
           "Capturing",
           "React"
         ],
         handler: `
           function externalCapturingFunction() {
             alert("externalCapturingFunction");
           }`
--- a/devtools/client/inspector/markup/test/browser_markup_events_react_production_16.2.0_jsx.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_react_production_16.2.0_jsx.js
@@ -18,107 +18,107 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "#inlinejsx",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":93",
+        filename: TEST_LIB + ":93:417",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function() {}`
       },
       {
         type: "onClick",
-        filename: TEST_LIB_BABEL + ":26",
+        filename: TEST_LIB_BABEL + ":26:34",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function inlineFunction() {
             alert("inlineFunction");
           }`
       }
     ]
   },
   {
     selector: "#externaljsx",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":93",
+        filename: TEST_LIB + ":93:417",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function() {}`
       },
       {
         type: "onClick",
-        filename: TEST_EXTERNAL_LISTENERS + ":4",
+        filename: TEST_EXTERNAL_LISTENERS + ":4:25",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function externalFunction() {
             alert("externalFunction");
           }`
       }
     ]
   },
   {
     selector: "#externalinlinejsx",
     expected: [
       {
         type: "click",
-        filename: TEST_LIB + ":93",
+        filename: TEST_LIB + ":93:417",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: `function() {}`
       },
       {
         type: "onClick",
-        filename: TEST_EXTERNAL_LISTENERS + ":4",
+        filename: TEST_EXTERNAL_LISTENERS + ":4:25",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function externalFunction() {
             alert("externalFunction");
           }`
       },
       {
         type: "onMouseUp",
-        filename: TEST_LIB_BABEL + ":26",
+        filename: TEST_LIB_BABEL + ":26:34",
         attributes: [
           "Bubbling",
           "React"
         ],
         handler: `
           function inlineFunction() {
             alert("inlineFunction");
           }`
       }
     ]
   },
   {
     selector: "#externalcapturingjsx",
     expected: [
       {
         type: "onClickCapture",
-        filename: TEST_EXTERNAL_LISTENERS + ":8",
+        filename: TEST_EXTERNAL_LISTENERS + ":8:34",
         attributes: [
           "Capturing",
           "React"
         ],
         handler: `
           function externalCapturingFunction() {
             alert("externalCapturingFunction");
           }`
--- a/devtools/client/shared/sourceeditor/codemirror/mozilla.css
+++ b/devtools/client/shared/sourceeditor/codemirror/mozilla.css
@@ -91,56 +91,24 @@
 
 .cm-trailingspace {
   background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg==");
   opacity: 0.75;
   background-position: left bottom;
   background-repeat: repeat-x;
 }
 
+/* Search highlight style
+ * cm-searching is used in Style Editor, and cm-highlight in Debugger */
+.cm-searching,
 .cm-highlight {
-  position: relative;
-}
-
-.cm-highlight:before {
-  position: absolute;
-  border-top-style: solid;
-  border-bottom-style: solid;
-  border-top-color: var(--theme-text-color-inactive);
-  border-bottom-color: var(--theme-text-color-inactive);
-  border-top-width: 1px;
-  border-bottom-width: 1px;
-  top: -1px;
-  bottom: 0;
-  left: 0;
-  right: 0;
-  content: "";
-  margin-bottom: -1px;
-}
-
-.cm-highlight-full:before {
-  border: 1px solid var(--theme-text-color-inactive);
-}
-
-.cm-highlight-start:before {
-  border-left-width: 1px;
-  border-left-style: solid;
-  border-left-color: var(--theme-text-color-inactive);
-  margin: 0 0 -1px -1px;
-  border-top-left-radius: 2px;
-  border-bottom-left-radius: 2px;
-}
-
-.cm-highlight-end:before {
-  border-right-width: 1px;
-  border-right-style: solid;
-  border-right-color: var(--theme-text-color-inactive);
-  margin: 0 -1px -1px 0;
-  border-top-right-radius: 2px;
-  border-bottom-right-radius: 2px;
+  border-bottom: 1px solid var(--theme-contrast-border);
+  /* Use the translucent yellow background, because we want the text selection
+     background (CodeMirror-selected) to show through this. */
+  background-color: var(--theme-contrast-background-alpha);
 }
 
 /* CodeMirror dialogs styling */
 
 .CodeMirror-dialog {
   padding: 4px 3px;
 }
 
--- a/devtools/client/shared/widgets/tooltip/EventTooltipHelper.js
+++ b/devtools/client/shared/widgets/tooltip/EventTooltipHelper.js
@@ -122,22 +122,23 @@ EventTooltip.prototype = {
               // This is emitted for testing.
               this._tooltip.emit("event-tooltip-source-map-ready");
             }
           };
 
           sourceMapService.subscribe(
             location.url,
             location.line,
-            null,
+            location.column,
             callback
           );
           this._subscriptions.push({
             url: location.url,
             line: location.line,
+            column: location.column,
             callback,
           });
         }
       }
 
       filename.textContent = text;
       filename.setAttribute("title", title);
       header.appendChild(filename);
@@ -280,17 +281,16 @@ EventTooltip.prototype = {
     }
   },
 
   _debugClicked: function(event) {
     const header = event.currentTarget;
     const content = header.nextElementSibling;
 
     const { sourceActor, uri } = this._eventEditors.get(content);
-
     const location = this._parseLocation(uri);
     if (location) {
       // Save a copy of toolbox as it will be set to null when we hide the tooltip.
       const toolbox = this._toolbox;
 
       this._tooltip.hide();
 
       toolbox.viewSourceInDebugger(
@@ -298,31 +298,38 @@ EventTooltip.prototype = {
         location.line,
         location.column,
         sourceActor
       );
     }
   },
 
   /**
-   * Parse URI and return {url, line}; or return null if it can't be parsed.
+   * Parse URI and return {url, line, column}; or return null if it can't be parsed.
    */
   _parseLocation: function(uri) {
     if (uri && uri !== "?") {
       uri = uri.replace(/"/g, "");
 
-      const matches = uri.match(/(.*):(\d+$)/);
+      let matches = uri.match(/(.*):(\d+):(\d+$)/);
 
       if (matches) {
         return {
           url: matches[1],
           line: parseInt(matches[2], 10),
+          column: parseInt(matches[3], 10),
+        };
+      } else if ((matches = uri.match(/(.*):(\d+$)/))) {
+        return {
+          url: matches[1],
+          line: parseInt(matches[2], 10),
+          column: null,
         };
       }
-      return { url: uri, line: 1 };
+      return { url: uri, line: 1, column: null };
     }
     return null;
   },
 
   destroy: function() {
     if (this._tooltip) {
       this._tooltip.off("hidden", this.destroy);
 
@@ -352,17 +359,17 @@ EventTooltip.prototype = {
       node.removeEventListener("click", this._debugClicked);
     }
 
     const sourceMapService = this._toolbox.sourceMapURLService;
     for (const subscription of this._subscriptions) {
       sourceMapService.unsubscribe(
         subscription.url,
         subscription.line,
-        null,
+        subscription.column,
         subscription.callback
       );
     }
 
     this._eventListenerInfos = this._toolbox = this._tooltip = null;
   },
 };
 
--- a/devtools/client/themes/light-theme.css
+++ b/devtools/client/themes/light-theme.css
@@ -190,17 +190,17 @@ body {
   border-left: solid 1px black;
 }
 
 .cm-s-mozilla.CodeMirror-focused .CodeMirror-selected { /* selected text (focused) */
   background: rgb(185, 215, 253);
 }
 
 .cm-s-mozilla .CodeMirror-selected { /* selected text (unfocused) */
-  background: rgb(176, 176, 176);
+  background: rgb(210, 210, 210);
 }
 
 .cm-s-mozilla .CodeMirror-activeline-background { /* selected color with alpha */
   background: rgba(185, 215, 253, .35);
 }
 
 div.cm-s-mozilla span.CodeMirror-matchingbracket { /* highlight brackets */
   outline: solid 1px rgba(0, 0, 0, .25);
--- a/devtools/client/themes/variables.css
+++ b/devtools/client/themes/variables.css
@@ -197,17 +197,18 @@
   --theme-toolbarbutton-active-color: var(--grey-90);
 
   /* Warning colors */
   --theme-warning-background: hsl(54, 100%, 92%);
   --theme-warning-border: rgba(215, 182, 0, 0.28); /* Yellow 60 + opacity */
   --theme-warning-color: var(--yellow-80);
 
   /* Flashing colors used to highlight updates */
-  --theme-contrast-background: #fff697;
+  --theme-contrast-background: #fff699; /* = Yellow 50-a40 on white */
+  --theme-contrast-background-alpha: rgba(255, 233, 0, 0.4); /* Yellow 50-a40 */
   --theme-contrast-color: black;
   --theme-contrast-border: var(--yellow-60);
 }
 
 /*
  * For doorhangers elsewhere in Firefox, Mac uses fixed colors rather than
  * system colors.
  */
@@ -237,17 +238,17 @@
   --toolbarbutton-hover-background: var(--grey-70);
 
   /* Buttons */
   --theme-button-background: rgba(249, 249, 250, 0.1);
   --theme-button-active-background: rgba(249, 249, 250, 0.15);
 
   /* Selection */
   --theme-selection-background: #204e8a;
-  --theme-selection-background-hover: #303844;
+  --theme-selection-background-hover: #353b48;
   --theme-selection-focus-background: var(--grey-60);
   --theme-selection-focus-color: var(--grey-30);
   --theme-selection-color: #ffffff;
 
   /* Border color that splits the toolbars/panels/headers. */
   --theme-splitter-color: var(--grey-70);
   --theme-emphasized-splitter-color: var(--grey-60);
   --theme-emphasized-splitter-color-hover: var(--grey-50);
@@ -313,12 +314,13 @@
   --theme-toolbarbutton-active-color: var(--grey-30);
 
   /* Warning colors */
   --theme-warning-background: hsl(42, 37%, 19%);
   --theme-warning-border: hsl(60, 30%, 26%);
   --theme-warning-color: hsl(43, 94%, 81%);
 
   /* Flashing colors used to highlight updates */
-  --theme-contrast-background: #605913; /* slightly desaturated Yellow 80 */
+  --theme-contrast-background: #4f4b1f; /* = Yellow 50-a20 on body background */
+  --theme-contrast-background-alpha: rgba(255, 233, 0, 0.15); /* Yellow 50-a15 */
   --theme-contrast-color: white;
   --theme-contrast-border: var(--yellow-65);
 }
--- a/devtools/server/actors/inspector/event-collector.js
+++ b/devtools/server/actors/inspector/event-collector.js
@@ -931,16 +931,17 @@ class EventCollector {
 
       const hide = listener.hide || {};
       const override = listener.override || {};
       const tags = listener.tags || "";
       const type = listener.type || "";
       let dom0 = false;
       let functionSource = handler.toString();
       let line = 0;
+      let column = null;
       let native = false;
       let url = "";
       let sourceActor = "";
 
       // If the listener is an object with a 'handleEvent' method, use that.
       if (
         listenerDO.class === "Object" ||
         /^XUL\w*Element$/.test(listenerDO.class)
@@ -970,18 +971,18 @@ class EventCollector {
 
         // Scripts are provided via script tags. If it wasn't provided by a
         // script tag it must be a DOM0 event.
         if (script.source.element) {
           dom0 = script.source.element.class !== "HTMLScriptElement";
         } else {
           dom0 = false;
         }
-
         line = script.startLine;
+        column = script.startColumn;
         url = script.url;
         const actor = this.targetActor.sources.getOrCreateSourceActor(
           script.source
         );
         sourceActor = actor ? actor.actorID : null;
 
         // Checking for the string "[native code]" is the only way at this point
         // to check for native code. Even if this provides a false positive then
@@ -1025,17 +1026,21 @@ class EventCollector {
       }
 
       // If the listener is native code we display the filename "[native code]."
       // This is the official string and should *not* be translated.
       let origin;
       if (native) {
         origin = "[native code]";
       } else {
-        origin = url + (dom0 || line === 0 ? "" : ":" + line);
+        origin =
+          url +
+          (dom0 || line === 0
+            ? ""
+            : ":" + line + (column === null ? "" : ":" + column));
       }
 
       const eventObj = {
         type: override.type || type,
         handler: override.handler || functionSource.trim(),
         origin: override.origin || origin,
         tags: override.tags || tags,
         DOM0: typeof override.dom0 !== "undefined" ? override.dom0 : dom0,
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -162,17 +162,21 @@ const ThreadActor = ActorClassWithSpec(t
 
   // Used by the ObjectActor to keep track of the depth of grip() calls.
   _gripDepth: null,
 
   get dbg() {
     if (!this._dbg) {
       this._dbg = this._parent.dbg;
       // Keep the debugger disabled until a client attaches.
-      this._dbg.enabled = this._state != "detached";
+      if (this._state === "detached") {
+        this._dbg.disable();
+      } else {
+        this._dbg.enable();
+      }
     }
     return this._dbg;
   },
 
   get globalDebugObject() {
     if (!this._parent.window || this.dbg.replaying) {
       return null;
     }
@@ -1719,17 +1723,17 @@ const ThreadActor = ActorClassWithSpec(t
     this.threadLifetimePool.addActor(actor);
     this.threadLifetimePool.objectActors.set(actor.obj, actor);
   },
 
   _onWindowReady: function({ isTopLevel, isBFCache, window }) {
     if (isTopLevel && this.state != "detached") {
       this.sources.reset();
       this.clearDebuggees();
-      this.dbg.enabled = true;
+      this.dbg.enable();
       this.maybePauseOnExceptions();
       // Update the global no matter if the debugger is on or off,
       // otherwise the global will be wrong when enabled later.
       this.global = window;
     }
 
     // Refresh the debuggee list when a new window object appears (top window or
     // iframe).
@@ -1747,24 +1751,24 @@ const ThreadActor = ActorClassWithSpec(t
   _onWillNavigate: function({ isTopLevel }) {
     if (!isTopLevel) {
       return;
     }
 
     // Proceed normally only if the debuggee is not paused.
     if (this.state == "paused") {
       this.unsafeSynchronize(Promise.resolve(this.doResume()));
-      this.dbg.enabled = false;
+      this.dbg.disable();
     }
     this.disableAllBreakpoints();
   },
 
   _onNavigate: function() {
     if (this.state == "running") {
-      this.dbg.enabled = true;
+      this.dbg.enable();
     }
   },
 
   // JS Debugger API hooks.
   pauseForMutationBreakpoint: function(mutationType) {
     if (
       !["subtreeModified", "nodeRemoved", "attributeModified"].includes(
         mutationType
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -127,16 +127,19 @@
 #    include "mozilla/Sandbox.h"
 #    include "mozilla/SandboxInfo.h"
 #    include "CubebUtils.h"
 #  elif defined(XP_MACOSX)
 #    include "mozilla/Sandbox.h"
 #  elif defined(__OpenBSD__)
 #    include <unistd.h>
 #  endif
+#  if defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+#    include "mozilla/SandboxTestingChild.h"
+#  endif
 #endif
 
 #include "mozilla/Unused.h"
 
 #include "mozInlineSpellChecker.h"
 #include "nsDocShell.h"
 #include "nsDocShellLoadState.h"
 #include "nsIDocShellTreeOwner.h"
@@ -4052,16 +4055,27 @@ mozilla::ipc::IPCResult ContentChild::Re
                                 aColNumber, aFlags, aCategory, aInnerWindowId,
                                 aFromChromeContext);
   rv = consoleService->LogMessage(scriptError);
   NS_ENSURE_SUCCESS(rv, IPC_FAIL(this, "Failed to log script error"));
 
   return IPC_OK();
 }
 
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+mozilla::ipc::IPCResult ContentChild::RecvInitSandboxTesting(
+    Endpoint<PSandboxTestingChild>&& aEndpoint) {
+  if (!SandboxTestingChild::Initialize(std::move(aEndpoint))) {
+    return IPC_FAIL(
+        this, "InitSandboxTesting failed to initialise the child process.");
+  }
+  return IPC_OK();
+}
+#endif
+
 }  // namespace dom
 
 #if defined(__OpenBSD__) && defined(MOZ_SANDBOX)
 #  include <unistd.h>
 
 static LazyLogModule sPledgeLog("SandboxPledge");
 
 bool StartOpenBSDSandbox(GeckoProcessType type) {
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -685,16 +685,21 @@ class ContentChild final : public PConte
 #ifdef NIGHTLY_BUILD
   // Fetch the current number of pending input events.
   //
   // NOTE: This method performs an atomic read, and is safe to call from all
   // threads.
   uint32_t GetPendingInputEvents() { return mPendingInputEvents; }
 #endif
 
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+  mozilla::ipc::IPCResult RecvInitSandboxTesting(
+      Endpoint<PSandboxTestingChild>&& aEndpoint);
+#endif
+
  private:
   static void ForceKillTimerCallback(nsITimer* aTimer, void* aClosure);
   void StartForceKillTimer();
 
   void ShutdownInternal();
 
   mozilla::ipc::IPCResult GetResultForRenderingInitFailure(
       base::ProcessId aOtherPid);
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -60,16 +60,20 @@ include PContentPermission;
 include ServiceWorkerConfiguration;
 include GraphicsMessages;
 include MemoryReportTypes;
 include ClientIPCTypes;
 include HangTypes;
 include PrefsTypes;
 include NeckoChannelParams;
 
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+include protocol PSandboxTesting;
+#endif
+
 using refcounted class nsIDOMGeoPosition from "nsGeoPositionIPCSerialiser.h";
 using refcounted class nsIAlertNotification from "mozilla/AlertNotificationIPCSerializer.h";
 
 using struct ChromePackage from "mozilla/chrome/RegistryMessageUtils.h";
 using struct SubstitutionMapping from "mozilla/chrome/RegistryMessageUtils.h";
 using struct OverrideMapping from "mozilla/chrome/RegistryMessageUtils.h";
 using base::ProcessId from "base/process.h";
 using struct IPC::Permission from "mozilla/net/NeckoMessageUtils.h";
@@ -815,16 +819,21 @@ child:
      * content process, such as play, pause, stop ..e.t.c.
      */
     async UpdateMediaAction(BrowsingContext aContext, MediaControlActions aAction);
 
     // Begin subscribing to a new BrowsingContextGroup, sending down the current
     // value for every individual BrowsingContext.
     async RegisterBrowsingContextGroup(BrowsingContextInitializer[] aInits);
 
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+    // Initialize top-level actor for testing content process sandbox.
+    async InitSandboxTesting(Endpoint<PSandboxTestingChild> aEndpoint);
+#endif
+
 parent:
     async InitBackground(Endpoint<PBackgroundParent> aEndpoint);
 
     sync OpenRecordReplayChannel(uint32_t channelId)
         returns (FileDescriptor connection);
     async CreateReplayingProcess(uint32_t channelId);
     async GenerateReplayCrashReport(uint32_t channelId);
 
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -125,24 +125,27 @@ UNIFIED_SOURCES += [
 ]
 
 # ContentChild.cpp cannot be compiled in unified mode on  linux due to Time conflict
 SOURCES += [
     'ContentChild.cpp',
     'ProcessHangMonitor.cpp',
 ]
 
+PREPROCESSED_IPDL_SOURCES += [
+    'PContent.ipdl',
+]
+
 IPDL_SOURCES += [
     'DOMTypes.ipdlh',
     'MemoryReportTypes.ipdlh',
     'PBrowser.ipdl',
     'PBrowserBridge.ipdl',
     'PBrowserOrId.ipdlh',
     'PColorPicker.ipdl',
-    'PContent.ipdl',
     'PContentPermission.ipdlh',
     'PContentPermissionRequest.ipdl',
     'PCycleCollectWithLogs.ipdl',
     'PFilePicker.ipdl',
     'PLoginReputation.ipdl',
     'PPluginWidget.ipdl',
     'PProcessHangMonitor.ipdl',
     'PrefsTypes.ipdlh',
--- a/dom/plugins/base/nsNPAPIPluginInstance.h
+++ b/dom/plugins/base/nsNPAPIPluginInstance.h
@@ -48,16 +48,21 @@ const NPDrawingModel kDefaultDrawingMode
     NPDrawingModelQuickDraw;  // Not supported
 #  else
 const NPDrawingModel kDefaultDrawingModel = NPDrawingModelCoreGraphics;
 #  endif
 #else
 const NPDrawingModel kDefaultDrawingModel = static_cast<NPDrawingModel>(0);
 #endif
 
+#if defined(OS_WIN)
+static const DWORD NPAPI_INVALID_WPARAM = 0xffffffff;
+#endif
+
+
 /**
  * Used to indicate whether it's OK to reenter Gecko and repaint, flush frames,
  * run scripts, etc, during this plugin call.
  * When NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO is set, we try to avoid dangerous
  * Gecko activities when the plugin spins a nested event loop, on a best-effort
  * basis.
  */
 enum NSPluginCallReentry {
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -269,16 +269,19 @@ nsPluginInstanceOwner::nsPluginInstanceO
 #endif
 
   mWaitingForPaint = false;
 
 #ifdef XP_WIN
   mGotCompositionData = false;
   mSentStartComposition = false;
   mPluginDidNotHandleIMEComposition = false;
+  // 3 is the Windows default for these values.
+  mWheelScrollLines = 3;
+  mWheelScrollChars = 3;
 #endif
 }
 
 nsPluginInstanceOwner::~nsPluginInstanceOwner() {
   if (mWaitingForPaint) {
     nsCOMPtr<nsIContent> content = do_QueryReferent(mContent);
     if (content) {
       // We don't care when the event is dispatched as long as it's "soon",
@@ -2078,23 +2081,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
           int32_t delta = 0;
           if (wheelEvent->mLineOrPageDeltaY) {
             switch (wheelEvent->mDeltaMode) {
               case WheelEvent_Binding::DOM_DELTA_PAGE:
                 pluginEvent.event = WM_MOUSEWHEEL;
                 delta = -WHEEL_DELTA * wheelEvent->mLineOrPageDeltaY;
                 break;
               case WheelEvent_Binding::DOM_DELTA_LINE: {
-                UINT linesPerWheelDelta = 0;
-                if (NS_WARN_IF(!::SystemParametersInfo(
-                        SPI_GETWHEELSCROLLLINES, 0, &linesPerWheelDelta, 0))) {
-                  // Use system default scroll amount, 3, when
-                  // SPI_GETWHEELSCROLLLINES isn't available.
-                  linesPerWheelDelta = 3;
-                }
+                UINT linesPerWheelDelta = mWheelScrollLines;
                 if (!linesPerWheelDelta) {
                   break;
                 }
                 pluginEvent.event = WM_MOUSEWHEEL;
                 delta = -WHEEL_DELTA / linesPerWheelDelta;
                 delta *= wheelEvent->mLineOrPageDeltaY;
                 break;
               }
@@ -2107,24 +2104,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
           } else if (wheelEvent->mLineOrPageDeltaX) {
             switch (wheelEvent->mDeltaMode) {
               case WheelEvent_Binding::DOM_DELTA_PAGE:
                 pluginEvent.event = WM_MOUSEHWHEEL;
                 delta = -WHEEL_DELTA * wheelEvent->mLineOrPageDeltaX;
                 break;
               case WheelEvent_Binding::DOM_DELTA_LINE: {
                 pluginEvent.event = WM_MOUSEHWHEEL;
-                UINT charsPerWheelDelta = 0;
-                // FYI: SPI_GETWHEELSCROLLCHARS is available on Vista or later.
-                if (::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0,
-                                           &charsPerWheelDelta, 0)) {
-                  // Use system default scroll amount, 3, when
-                  // SPI_GETWHEELSCROLLCHARS isn't available.
-                  charsPerWheelDelta = 3;
-                }
+                UINT charsPerWheelDelta = mWheelScrollChars;
                 if (!charsPerWheelDelta) {
                   break;
                 }
                 delta = WHEEL_DELTA / charsPerWheelDelta;
                 delta *= wheelEvent->mLineOrPageDeltaX;
                 break;
               }
               case WheelEvent_Binding::DOM_DELTA_PIXEL:
@@ -2154,24 +2144,21 @@ nsEventStatus nsPluginInstanceOwner::Pro
         }
         // don't synthesize anything for eMouseDoubleClick, since that
         // is a synthetic event generated on mouse-up, and Windows WM_*DBLCLK
         // messages are sent on mouse-down
         default:
           break;
       }
       if (pluginEvent.event && initWParamWithCurrentState) {
+        // We created one of the messages caught above but didn't fill in
+        // wParam. Mark it with an invalid wParam value so that HandleEvent can
+        // figure it out.
         pPluginEvent = &pluginEvent;
-        pluginEvent.wParam = (::GetKeyState(VK_CONTROL) ? MK_CONTROL : 0) |
-                             (::GetKeyState(VK_SHIFT) ? MK_SHIFT : 0) |
-                             (::GetKeyState(VK_LBUTTON) ? MK_LBUTTON : 0) |
-                             (::GetKeyState(VK_MBUTTON) ? MK_MBUTTON : 0) |
-                             (::GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0) |
-                             (::GetKeyState(VK_XBUTTON1) ? MK_XBUTTON1 : 0) |
-                             (::GetKeyState(VK_XBUTTON2) ? MK_XBUTTON2 : 0);
+        pluginEvent.wParam = NPAPI_INVALID_WPARAM;
       }
     }
     if (pPluginEvent) {
       // Make event coordinates relative to our enclosing widget,
       // not the widget they were received on.
       // See use of NPEvent in widget/windows/nsWindow.cpp
       // for why this assert should be safe
       NS_ASSERTION(
@@ -2213,16 +2200,33 @@ nsEventStatus nsPluginInstanceOwner::Pro
 
   if (pPluginEvent && !pPluginEvent->event) {
     // Don't send null events to plugins.
     NS_WARNING(
         "nsPluginFrame ProcessEvent: trying to send null event to plugin.");
     return rv;
   }
 
+  // We don't need to tell the plugin about changes to the scroll wheel
+  // settings but we do need to remember them for future mouse move
+  // calculations.  We put the scroll wheel setting in the lParam field.
+  if (pPluginEvent && pPluginEvent->event == WM_SETTINGCHANGE) {
+    switch (pPluginEvent->wParam) {
+      case SPI_SETWHEELSCROLLLINES:
+        mWheelScrollLines = static_cast<uint32_t>(pPluginEvent->lParam);
+        break;
+      case SPI_SETWHEELSCROLLCHARS:
+        mWheelScrollChars = static_cast<uint32_t>(pPluginEvent->lParam);
+        break;
+      default:
+        break;
+    }
+    return nsEventStatus_eConsumeNoDefault;
+  }
+
   if (pPluginEvent) {
     int16_t response = kNPEventNotHandled;
     mInstance->HandleEvent(const_cast<NPEvent*>(pPluginEvent), &response,
                            NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO);
     if (response == kNPEventHandled) rv = nsEventStatus_eConsumeNoDefault;
   }
 #endif
 
--- a/dom/plugins/base/nsPluginInstanceOwner.h
+++ b/dom/plugins/base/nsPluginInstanceOwner.h
@@ -290,16 +290,18 @@ class nsPluginInstanceOwner final : publ
   nsIWidget* GetContainingWidgetIfOffset();
   already_AddRefed<mozilla::TextComposition> GetTextComposition();
   void HandleNoConsumedCompositionMessage(
       mozilla::WidgetCompositionEvent* aCompositionEvent,
       const NPEvent* aPluginEvent);
   bool mGotCompositionData;
   bool mSentStartComposition;
   bool mPluginDidNotHandleIMEComposition;
+  uint32_t mWheelScrollLines;
+  uint32_t mWheelScrollChars;
 #endif
 
   nsPluginNativeWindow* mPluginWindow;
   RefPtr<nsNPAPIPluginInstance> mInstance;
   nsPluginFrame* mPluginFrame;
   nsWeakPtr mContent;  // WEAK, content owns us
   nsCString mDocumentBase;
   bool mWidgetCreationComplete;
--- a/dom/plugins/base/nsPluginNativeWindowWin.cpp
+++ b/dom/plugins/base/nsPluginNativeWindowWin.cpp
@@ -449,20 +449,16 @@ nsPluginNativeWindowWin::nsPluginNativeW
   type = NPWindowTypeWindow;
 
   mPrevWinProc = nullptr;
   mPluginWinProc = nullptr;
   mPluginType = nsPluginHost::eSpecialType_None;
 
   mParentWnd = nullptr;
   mParentProc = 0;
-
-  if (!sWM_FLASHBOUNCEMSG) {
-    sWM_FLASHBOUNCEMSG = ::RegisterWindowMessage(NS_PLUGIN_CUSTOM_MSG_ID);
-  }
 }
 
 WNDPROC nsPluginNativeWindowWin::GetPrevWindowProc() { return mPrevWinProc; }
 
 WNDPROC nsPluginNativeWindowWin::GetWindowProc() { return mPluginWinProc; }
 
 NS_IMETHODIMP PluginWindowEvent::Run() {
   nsPluginNativeWindowWin* win = mPluginWindowRef;
@@ -540,16 +536,20 @@ nsresult nsPluginNativeWindowWin::CallSe
   // With e10s we execute in the content process and as such we don't
   // have access to native widgets. CallSetWindow and skip native widget
   // subclassing.
   if (!XRE_IsParentProcess()) {
     nsPluginNativeWindow::CallSetWindow(aPluginInstance);
     return NS_OK;
   }
 
+  if (!sWM_FLASHBOUNCEMSG) {
+    sWM_FLASHBOUNCEMSG = ::RegisterWindowMessage(NS_PLUGIN_CUSTOM_MSG_ID);
+  }
+
   if (window) {
     // grab the widget procedure before the plug-in does a subclass in
     // setwindow. We'll use this in PluginWndProc for forwarding focus
     // events to the widget.
     WNDPROC currentWndProc =
         (WNDPROC)::GetWindowLongPtr((HWND)window, GWLP_WNDPROC);
     if (!mPrevWinProc && currentWndProc != PluginWndProc)
       mPrevWinProc = currentWndProc;
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -801,16 +801,42 @@ NPError PluginInstanceChild::DefaultAudi
 NPError PluginInstanceChild::AudioDeviceStateChanged(
     NPAudioDeviceStateChanged& aDeviceState) {
   if (!mPluginIface->setvalue) {
     return NPERR_GENERIC_ERROR;
   }
   return mPluginIface->setvalue(GetNPP(), NPNVaudioDeviceStateChanged,
                                 (void*)&aDeviceState);
 }
+
+void SetMouseEventWParam(NPEvent* aEvent) {
+  // Fill in potentially missing key state info.  See
+  // nsPluginInstanceOwner::ProcessEvent for circumstances where this happens.
+  const auto kMouseMessages =
+      mozilla::Array<int,9>(WM_LBUTTONDOWN, WM_MBUTTONDOWN, WM_RBUTTONDOWN,
+                            WM_LBUTTONUP, WM_MBUTTONUP, WM_RBUTTONUP,
+                            WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_MOUSEHWHEEL);
+
+  bool isInvalidWParam =
+      (aEvent->wParam == NPAPI_INVALID_WPARAM) &&
+      (std::find(kMouseMessages.begin(), kMouseMessages.end(),
+                 static_cast<int>(aEvent->event)) != kMouseMessages.end());
+
+  if (!isInvalidWParam) {
+    return;
+  }
+
+  aEvent->wParam = (::GetKeyState(VK_CONTROL) ? MK_CONTROL : 0) |
+                   (::GetKeyState(VK_SHIFT) ? MK_SHIFT : 0) |
+                   (::GetKeyState(VK_LBUTTON) ? MK_LBUTTON : 0) |
+                   (::GetKeyState(VK_MBUTTON) ? MK_MBUTTON : 0) |
+                   (::GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0) |
+                   (::GetKeyState(VK_XBUTTON1) ? MK_XBUTTON1 : 0) |
+                   (::GetKeyState(VK_XBUTTON2) ? MK_XBUTTON2 : 0);
+}
 #endif
 
 mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_HandleEvent(
     const NPRemoteEvent& event, int16_t* handled) {
   PLUGIN_LOG_DEBUG_FUNCTION;
   AssertPluginThread();
   AutoStackHelper guard(this);
 
@@ -843,16 +869,17 @@ mozilla::ipc::IPCResult PluginInstanceCh
     mContentsScaleFactor = event.contentsScaleFactor;
   }
 #endif
 
 #ifdef OS_WIN
   // FIXME/bug 567645: temporarily drop the "dummy event" on the floor
   if (WM_NULL == evcopy.event) return IPC_OK();
 
+  SetMouseEventWParam(&evcopy);
   *handled = WinlessHandleEvent(evcopy);
   return IPC_OK();
 #endif
 
   // XXX A previous call to mPluginIface->event might block, e.g. right click
   // for context menu. Still, we might get here again, calling into the plugin
   // a second time while it's in the previous call.
   if (!mPluginIface->event)
--- a/dom/plugins/ipc/PluginInstanceParent.cpp
+++ b/dom/plugins/ipc/PluginInstanceParent.cpp
@@ -1421,22 +1421,25 @@ int16_t PluginInstanceParent::NPP_Handle
     switch (npevent->event) {
       case WM_KILLFOCUS: {
         // When the user selects fullscreen mode in Flash video players,
         // WM_KILLFOCUS will be delayed by deferred event processing:
         // WM_LBUTTONUP results in a call to CreateWindow within Flash,
         // which fires WM_KILLFOCUS. Delayed delivery causes Flash to
         // misinterpret the event, dropping back out of fullscreen. Trap
         // this event and drop it.
-        wchar_t szClass[26];
-        HWND hwnd = GetForegroundWindow();
-        if (hwnd && hwnd != mPluginHWND &&
-            GetClassNameW(hwnd, szClass, sizeof(szClass) / sizeof(char16_t)) &&
-            !wcscmp(szClass, kFlashFullscreenClass)) {
-          return 0;
+        // mPluginHWND is always NULL for non-windowed plugins.
+        if (mPluginHWND) {
+          wchar_t szClass[26];
+          HWND hwnd = GetForegroundWindow();
+          if (hwnd && hwnd != mPluginHWND &&
+              GetClassNameW(hwnd, szClass, sizeof(szClass) / sizeof(char16_t)) &&
+              !wcscmp(szClass, kFlashFullscreenClass)) {
+            return 0;
+          }
         }
       } break;
 
       case WM_WINDOWPOSCHANGED: {
         // We send this in nsPluginFrame just before painting
         return SendWindowPosChanged(npremoteevent);
       }
 
--- a/gfx/ipc/GPUParent.cpp
+++ b/gfx/ipc/GPUParent.cpp
@@ -64,16 +64,20 @@
 #  include <gtk/gtk.h>
 #  include "skia/include/ports/SkTypeface_cairo.h"
 #endif
 #ifdef MOZ_GECKO_PROFILER
 #  include "ChildProfilerController.h"
 #endif
 #include "nsAppRunner.h"
 
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+#  include "mozilla/SandboxTestingChild.h"
+#endif
+
 namespace mozilla {
 namespace gfx {
 
 using namespace ipc;
 using namespace layers;
 
 static GPUParent* sGPUParent;
 
@@ -277,16 +281,27 @@ mozilla::ipc::IPCResult GPUParent::RecvI
   RecvGetDeviceStatus(&data);
   Unused << SendInitComplete(data);
 
   Telemetry::AccumulateTimeDelta(Telemetry::GPU_PROCESS_INITIALIZATION_TIME_MS,
                                  mLaunchTime);
   return IPC_OK();
 }
 
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+mozilla::ipc::IPCResult GPUParent::RecvInitSandboxTesting(
+    Endpoint<PSandboxTestingChild>&& aEndpoint) {
+  if (!SandboxTestingChild::Initialize(std::move(aEndpoint))) {
+    return IPC_FAIL(
+        this, "InitSandboxTesting failed to initialise the child process.");
+  }
+  return IPC_OK();
+}
+#endif
+
 mozilla::ipc::IPCResult GPUParent::RecvInitCompositorManager(
     Endpoint<PCompositorManagerParent>&& aEndpoint) {
   CompositorManagerParent::Create(std::move(aEndpoint), /* aIsRoot */ true);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult GPUParent::RecvInitVsyncBridge(
     Endpoint<PVsyncBridgeParent>&& aVsyncEndpoint) {
--- a/gfx/ipc/GPUParent.h
+++ b/gfx/ipc/GPUParent.h
@@ -77,16 +77,21 @@ class GPUParent final : public PGPUParen
       const Maybe<ipc::FileDescriptor>& DMDFile);
   mozilla::ipc::IPCResult RecvShutdownVR();
 
   mozilla::ipc::IPCResult RecvUpdatePerfStatsCollectionMask(
       const uint64_t& aMask);
   mozilla::ipc::IPCResult RecvCollectPerfStatsJSON(
       CollectPerfStatsJSONResolver&& aResolver);
 
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+  mozilla::ipc::IPCResult RecvInitSandboxTesting(
+      Endpoint<PSandboxTestingChild>&& aEndpoint);
+#endif
+
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
  private:
   const TimeStamp mLaunchTime;
   RefPtr<VsyncBridgeParent> mVsyncBridge;
 #ifdef MOZ_GECKO_PROFILER
   RefPtr<ChildProfilerController> mProfilerController;
 #endif
--- a/gfx/ipc/PGPU.ipdl
+++ b/gfx/ipc/PGPU.ipdl
@@ -12,16 +12,20 @@ include protocol PCompositorManager;
 include protocol PImageBridge;
 include protocol PProfiler;
 include protocol PVRGPU;
 include protocol PVRManager;
 include protocol PVsyncBridge;
 include protocol PUiCompositorController;
 include protocol PRemoteDecoderManager;
 
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+include protocol PSandboxTesting;
+#endif
+
 using base::ProcessId from "base/process.h";
 using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
 using mozilla::Telemetry::HistogramAccumulation from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::KeyedHistogramAccumulation from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::ChildEventData from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::DiscardedData from "mozilla/TelemetryComms.h";
@@ -94,16 +98,20 @@ parent:
                             bool minimizeMemoryUsage,
                             FileDescriptor? DMDFile);
   async ShutdownVR();
 
   // Functions supporting PerfStats data collection.
   async UpdatePerfStatsCollectionMask(uint64_t aMask);
   async CollectPerfStatsJSON() returns (nsCString aStats);
 
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+  async InitSandboxTesting(Endpoint<PSandboxTestingChild> aEndpoint);
+#endif
+
 child:
   // Sent when the GPU process has initialized devices. This occurs once, after
   // Init().
   async InitComplete(GPUDeviceData data);
 
   // Sent when APZ detects checkerboarding and apz checkerboard reporting is enabled.
   async ReportCheckerboard(uint32_t severity, nsCString log);
 
--- a/gfx/ipc/moz.build
+++ b/gfx/ipc/moz.build
@@ -65,18 +65,21 @@ UNIFIED_SOURCES += [
 ]
 
 SOURCES += [
     'GPUParent.cpp',
 ]
 
 IPDL_SOURCES = [
     'GraphicsMessages.ipdlh',
+    'PVsyncBridge.ipdl',
+]
+
+PREPROCESSED_IPDL_SOURCES += [
     'PGPU.ipdl',
-    'PVsyncBridge.ipdl',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/ipc',
     '/toolkit/crashreporter',
     '/xpcom/threads',
 ]
 
--- a/gfx/vr/service/binding/OpenVRCosmosBinding.h
+++ b/gfx/vr/service/binding/OpenVRCosmosBinding.h
@@ -64,29 +64,29 @@ struct OpenVRCosmosBinding {
     "               \"click\" : { \n"
     "                \"output\" : \"/actions/firefox/in/LHand_grip_pressed\" \n"
     "              }, \n"
     "               \"touch\" : { \n"
     "                \"output\" : \"/actions/firefox/in/LHand_grip_touched\" \n"
     "              } \n"
     "             }, \n"
     "             \"mode\" : \"button\", \n"
-    "             \"path\" : \"/user/hand/left/input/paddle_heavy\" \n"
+    "             \"path\" : \"/user/hand/left/input/grip\" \n"
     "           }, \n"
     "           {\n"
     "             \"inputs\" : { \n"
     "               \"click\" : { \n"
     "                \"output\" : \"/actions/firefox/in/RHand_grip_pressed\" \n"
     "              }, \n"
     "               \"touch\" : { \n"
     "                 \"output\" : \"/actions/firefox/in/RHand_grip_touched\" \n"
     "              } \n"
     "           }, \n"
     "             \"mode\" : \"button\", \n"
-    "             \"path\" : \"/user/hand/right/input/paddle_heavy\" \n"
+    "             \"path\" : \"/user/hand/right/input/grip\" \n"
     "           }, \n"
     "           {\n"
     "             \"inputs\" : { \n"
     "                 \"click\" : { \n"
     "                    \"output\" : \"/actions/firefox/in/LHand_system_pressed\"  \n"
     "                   }, \n"
     "                 \"touch\" : { \n"
     "                    \"output\" : \"/actions/firefox/in/LHand_system_touched\"  \n"
@@ -109,17 +109,17 @@ struct OpenVRCosmosBinding {
     "           }, \n"
     "           {\n"
     "             \"inputs\" : { \n"
     "                 \"click\" : { \n"
     "                    \"output\" : \"/actions/firefox/in/LHand_a_pressed\"  \n"
     "                   } \n"
     "              },\n"
     "             \"mode\" : \"button\", \n"
-    "             \"path\" : \"/user/hand/left/input/a\" \n"
+    "             \"path\" : \"/user/hand/left/input/x\" \n"
     "           }, \n"
     "           {\n"
     "             \"inputs\" : { \n"
     "                 \"click\" : { \n"
     "                    \"output\" : \"/actions/firefox/in/RHand_a_pressed\"  \n"
     "                   } \n"
     "              },\n"
     "             \"mode\" : \"button\", \n"
@@ -127,17 +127,17 @@ struct OpenVRCosmosBinding {
     "           }, \n"
     "           {\n"
     "             \"inputs\" : { \n"
     "                 \"click\" : { \n"
     "                    \"output\" : \"/actions/firefox/in/LHand_b_pressed\"  \n"
     "                   } \n"
     "              },\n"
     "             \"mode\" : \"button\", \n"
-    "             \"path\" : \"/user/hand/left/input/b\" \n"
+    "             \"path\" : \"/user/hand/left/input/y\" \n"
     "           }, \n"
     "           {\n"
     "             \"inputs\" : { \n"
     "                 \"click\" : { \n"
     "                    \"output\" : \"/actions/firefox/in/RHand_b_pressed\"  \n"
     "                   } \n"
     "              },\n"
     "             \"mode\" : \"button\", \n"
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -9252,17 +9252,17 @@ Maybe<ScrollMetadata> nsLayoutUtils::Get
   if (ensureMetricsForRootId && content) {
     ViewID scrollId = nsLayoutUtils::FindOrCreateIDFor(content);
     if (aCallback(scrollId)) {
       ensureMetricsForRootId = false;
     }
   }
 
   if (addMetrics || ensureMetricsForRootId) {
-    bool isRootContent = presContext->IsRootContentDocument();
+    bool isRootContent = presContext->IsRootContentDocumentCrossProcess();
 
     nsRect viewport(aBuilder->ToReferenceFrame(frame), frame->GetSize());
     if (isRootContent && rootScrollFrame) {
       nsIScrollableFrame* scrollableFrame =
           rootScrollFrame->GetScrollTargetFrame();
       viewport.SizeTo(scrollableFrame->GetScrollPortRect().Size());
     }
     return Some(nsLayoutUtils::ComputeScrollMetadata(
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -3550,19 +3550,20 @@ void ScrollFrameHelper::BuildDisplayList
             nsMargin(caretRect.height / 2, caretRect.width, 0, 0));
         contentBoxClip = Some(inflatedClip);
       }
     }
   }
 
   nsDisplayListCollection set(aBuilder);
 
+  bool isRootContent =
+      mIsRoot && mOuter->PresContext()->IsRootContentDocumentCrossProcess();
   bool willBuildAsyncZoomContainer =
-      aBuilder->ShouldBuildAsyncZoomContainer() && mIsRoot &&
-      mOuter->PresContext()->IsRootContentDocument();
+      aBuilder->ShouldBuildAsyncZoomContainer() && isRootContent;
 
   nsRect scrollPortClip = mScrollPort + aBuilder->ToReferenceFrame(mOuter);
   nsRect clipRect = scrollPortClip;
   // Our override of GetBorderRadii ensures we never have a radius at
   // the corners where we have a scrollbar.
   nscoord radii[8];
   bool haveRadii = mOuter->GetPaddingBoxBorderRadii(radii);
   if (mIsRoot) {
@@ -4024,17 +4025,17 @@ Maybe<ScrollMetadata> ScrollFrameHelper:
       mOuter->GetOffsetToCrossDoc(aContainerReferenceFrame);
 
   Maybe<nsRect> parentLayerClip;
   if (aClip && mAddClipRectToLayer) {
     parentLayerClip = Some(aClip->GetClipRect());
   }
 
   bool isRootContent =
-      mIsRoot && mOuter->PresContext()->IsRootContentDocument();
+      mIsRoot && mOuter->PresContext()->IsRootContentDocumentCrossProcess();
 
   MOZ_ASSERT(mScrolledFrame->GetContent());
 
   nsRect scrollport = mScrollPort + toReferenceFrame;
 
   return Some(nsLayoutUtils::ComputeScrollMetadata(
       mScrolledFrame, mOuter, mOuter->GetContent(), aContainerReferenceFrame,
       aLayerManager, mScrollParentID, scrollport, parentLayerClip,
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -7174,17 +7174,17 @@ UniquePtr<ScrollMetadata> nsDisplaySubDo
     LayerManager* aLayerManager,
     const ContainerLayerParameters& aContainerParameters) {
   if (!(mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer)) {
     return UniquePtr<ScrollMetadata>(nullptr);
   }
 
   nsPresContext* presContext = mFrame->PresContext();
   nsIFrame* rootScrollFrame = presContext->PresShell()->GetRootScrollFrame();
-  bool isRootContentDocument = presContext->IsRootContentDocument();
+  bool isRootContentDocument = presContext->IsRootContentDocumentCrossProcess();
   PresShell* presShell = presContext->PresShell();
   ContainerLayerParameters params(
       aContainerParameters.mXScale * presShell->GetResolution(),
       aContainerParameters.mYScale * presShell->GetResolution(), nsIntPoint(),
       aContainerParameters);
 
   nsRect viewport = mFrame->GetRect() - mFrame->GetPosition() +
                     mFrame->GetOffsetToCrossDoc(ReferenceFrame());
--- a/python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_toolchain_configure.py
@@ -1585,28 +1585,52 @@ def gen_invoke_rustc(version, rustup_wra
                 ]
             # Additional targets from 1.36
             if Version(version) >= '1.36.0':
                 rust_targets += [
                     'wasm32-wasi',
                 ]
                 rust_targets.remove('wasm32-unknown-wasi')
                 rust_targets.remove('x86_64-unknown-bitrig')
+            # Additional targets from 1.37
+            if Version(version) >= '1.37.0':
+                rust_targets += [
+                    'x86_64-pc-solaris',
+                ]
+            # Additional targets from 1.38
+            if Version(version) >= '1.38.0':
+                rust_targets += [
+                    'aarch64-unknown-redox',
+                    'aarch64-wrs-vxworks',
+                    'armv7-unknown-linux-gnueabi',
+                    'armv7-unknown-linux-musleabi',
+                    'armv7-wrs-vxworks',
+                    'hexagon-unknown-linux-musl',
+                    'i586-wrs-vxworks',
+                    'i686-uwp-windows-gnu',
+                    'i686-wrs-vxworks',
+                    'powerpc-wrs-vxworks',
+                    'powerpc-wrs-vxworks-spe',
+                    'powerpc64-wrs-vxworks',
+                    'riscv32i-unknown-none-elf',
+                    'x86_64-uwp-windows-gnu',
+                    'x86_64-wrs-vxworks',
+                ]
             return 0, '\n'.join(sorted(rust_targets)), ''
         if (len(args) == 6 and args[:2] == ('--crate-type', 'staticlib') and
             args[2].startswith('--target=') and args[3] == '-o'):
             with open(args[4], 'w') as fh:
                 fh.write('foo')
             return 0, '', ''
         raise NotImplementedError('unsupported arguments')
     return invoke_rustc
 
 
 class RustTest(BaseConfigureTest):
-    def get_rust_target(self, target, compiler_type='gcc', version='1.36.0',
+    def get_rust_target(self, target, compiler_type='gcc', version='1.38.0',
                         arm_target=None):
         environ = {
             'PATH': os.pathsep.join(
                 mozpath.abspath(p) for p in ('/bin', '/usr/bin')),
         }
 
         paths = {
             mozpath.abspath('/usr/bin/cargo'): gen_invoke_cargo(version),
@@ -1638,17 +1662,16 @@ class RustTest(BaseConfigureTest):
             'i686-unknown-freebsd',
             'x86_64-unknown-freebsd',
             'sparc64-unknown-netbsd',
             'i686-unknown-netbsd',
             'x86_64-unknown-netbsd',
             'i686-unknown-openbsd',
             'x86_64-unknown-openbsd',
             'aarch64-unknown-linux-gnu',
-            'armv7-unknown-linux-gnueabihf',
             'sparc64-unknown-linux-gnu',
             'i686-unknown-linux-gnu',
             'i686-apple-darwin',
             'x86_64-apple-darwin',
             'aarch64-apple-ios',
             'armv7s-apple-ios',
             'i386-apple-ios',
             'x86_64-apple-ios',
@@ -1663,31 +1686,34 @@ class RustTest(BaseConfigureTest):
 
         # Cases where the output of config.sub is different
         for autoconf, rust in (
             ('aarch64-unknown-linux-android', 'aarch64-linux-android'),
             ('arm-unknown-linux-androideabi', 'armv7-linux-androideabi'),
             ('armv7-unknown-linux-androideabi', 'armv7-linux-androideabi'),
             ('i386-unknown-linux-android', 'i686-linux-android'),
             ('i686-unknown-linux-android', 'i686-linux-android'),
+            ('i686-pc-linux-gnu', 'i686-unknown-linux-gnu'),
             ('x86_64-unknown-linux-android', 'x86_64-linux-android'),
             ('x86_64-pc-linux-gnu', 'x86_64-unknown-linux-gnu'),
             ('sparcv9-sun-solaris2', 'sparcv9-sun-solaris'),
             ('x86_64-sun-solaris2', 'x86_64-sun-solaris'),
         ):
             self.assertEqual(self.get_rust_target(autoconf), rust)
 
         # Windows
         for autoconf, building_with_gcc, rust in (
             ('i686-pc-mingw32', 'cl', 'i686-pc-windows-msvc'),
             ('x86_64-pc-mingw32', 'cl', 'x86_64-pc-windows-msvc'),
             ('i686-pc-mingw32', 'gcc', 'i686-pc-windows-gnu'),
             ('x86_64-pc-mingw32', 'gcc', 'x86_64-pc-windows-gnu'),
             ('i686-pc-mingw32', 'clang', 'i686-pc-windows-gnu'),
             ('x86_64-pc-mingw32', 'clang', 'x86_64-pc-windows-gnu'),
+            ('i686-w64-mingw32', 'clang', 'i686-pc-windows-gnu'),
+            ('x86_64-w64-mingw32', 'clang', 'x86_64-pc-windows-gnu'),
         ):
             self.assertEqual(self.get_rust_target(autoconf, building_with_gcc), rust)
 
         # Arm special cases
         self.assertEqual(
             self.get_rust_target('arm-unknown-linux-androideabi',
                                  arm_target=ReadOnlyNamespace(
                                      arm_arch=7, fpu='neon', thumb2=True, float_abi='softfp')),
--- a/security/sandbox/common/components.conf
+++ b/security/sandbox/common/components.conf
@@ -6,8 +6,18 @@
 
 Classes = [
     {
         'cid': '{5516303d-9007-45a0-94b9-940ef134a6e2}',
         'contract_ids': ['@mozilla.org/sandbox/sandbox-settings;1'],
         'type': 'mozISandboxSettings',
     },
 ]
+
+if defined('MOZ_SANDBOX') and defined('MOZ_DEBUG') and defined('ENABLE_TESTS'):
+    Classes += [
+        {
+        'cid':
+        '{2306c118-3544-4674-9222-670b88dc07a9}',
+        'contract_ids': ['@mozilla.org/sandbox/sandbox-test;1'],
+        'type': 'mozISandboxTest',
+    },
+]
--- a/security/sandbox/common/moz.build
+++ b/security/sandbox/common/moz.build
@@ -16,13 +16,35 @@ XPCOM_MANIFESTS += [
 ]
 
 XPIDL_SOURCES += [
     'mozISandboxSettings.idl',
 ]
 
 XPIDL_MODULE = 'sandbox'
 
+if CONFIG['MOZ_SANDBOX'] and CONFIG['MOZ_DEBUG'] and CONFIG['ENABLE_TESTS']:
+    UNIFIED_SOURCES += [
+        'test/SandboxTest.cpp',
+        'test/SandboxTestingChild.cpp',
+        'test/SandboxTestingParent.cpp',
+    ]
+
+    EXPORTS.mozilla += [
+        'test/SandboxTestingChild.h',
+        'test/SandboxTestingParent.h',
+    ]
+
+    IPDL_SOURCES += [
+        "test/PSandboxTesting.ipdl",
+    ]
+    
+    XPIDL_SOURCES += [
+        'test/mozISandboxTest.idl',
+    ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
 FINAL_LIBRARY = 'xul'
 
 EXPORTS.mozilla += [
     'SandboxSettings.h',
 ]
new file mode 100644
--- /dev/null
+++ b/security/sandbox/common/test/PSandboxTesting.ipdl
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+namespace mozilla {
+
+async protocol PSandboxTesting {
+parent:
+  async ReportTestResults(nsCString testName, bool shouldSucceed, bool didSucceed, nsCString resultMessage);
+  async TestCompleted();
+
+child:
+  async ShutDown();
+};
+
+} //namespace mozilla
new file mode 100644
--- /dev/null
+++ b/security/sandbox/common/test/SandboxTest.cpp
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "SandboxTest.h"
+
+#include "mozilla/Components.h"
+#include "SandboxTestingParent.h"
+#include "SandboxTestingChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/gfx/GPUChild.h"
+
+using namespace mozilla;
+using namespace mozilla::ipc;
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(SandboxTest, mozISandboxTest)
+
+GeckoProcessType GeckoProcessStringToType(const nsCString& aString) {
+  for (GeckoProcessType type = GeckoProcessType(0);
+       type < GeckoProcessType::GeckoProcessType_End;
+       type = GeckoProcessType(type + 1)) {
+    if (aString == kGeckoProcessTypeString[type]) {
+      return type;
+    }
+  }
+  return GeckoProcessType::GeckoProcessType_Invalid;
+}
+
+// Set up tests on remote process connected to the given actor.
+// The actor must handle the InitSandboxTesting message.
+template <typename Actor>
+SandboxTestingParent* InitializeSandboxTestingActors(Actor* aActor) {
+  Endpoint<PSandboxTestingParent> sandboxTestingParentEnd;
+  Endpoint<PSandboxTestingChild> sandboxTestingChildEnd;
+  nsresult rv = PSandboxTesting::CreateEndpoints(
+      base::GetCurrentProcId(), aActor->OtherPid(), &sandboxTestingParentEnd,
+      &sandboxTestingChildEnd);
+  if (NS_FAILED(rv)) {
+    return nullptr;
+  }
+
+  Unused << aActor->SendInitSandboxTesting(std::move(sandboxTestingChildEnd));
+  return SandboxTestingParent::Create(std::move(sandboxTestingParentEnd));
+}
+
+NS_IMETHODIMP
+SandboxTest::StartTests(const nsTArray<nsCString>& aProcessesList) {
+  for (auto processTypeName : aProcessesList) {
+    GeckoProcessType type = GeckoProcessStringToType(processTypeName);
+    if (type == GeckoProcessType::GeckoProcessType_Invalid) {
+      return NS_ERROR_ILLEGAL_VALUE;
+    }
+
+    switch (type) {
+      case GeckoProcessType_Content: {
+        nsTArray<ContentParent*> parents;
+        ContentParent::GetAll(parents);
+        MOZ_ASSERT(parents.Length() > 0);
+        mSandboxTestingParents[type] =
+            InitializeSandboxTestingActors(parents[0]);
+        break;
+      }
+
+      case GeckoProcessType_GPU: {
+        gfx::GPUProcessManager* gpuProc = gfx::GPUProcessManager::Get();
+        gfx::GPUChild* gpuChild = gpuProc ? gpuProc->GetGPUChild() : nullptr;
+        if (!gpuChild) {
+          // There is no GPU process for this OS.  Report test done.
+          nsCOMPtr<nsIObserverService> observerService =
+              mozilla::services::GetObserverService();
+          MOZ_RELEASE_ASSERT(observerService);
+          observerService->NotifyObservers(nullptr, "sandbox-test-done", 0);
+          return NS_OK;
+        }
+
+        mSandboxTestingParents[type] = InitializeSandboxTestingActors(gpuChild);
+        break;
+      }
+
+      default:
+        MOZ_ASSERT_UNREACHABLE(
+            "SandboxTest does not yet support this process type");
+        return NS_ERROR_ILLEGAL_VALUE;
+    }
+
+    if (!mSandboxTestingParents[type]) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SandboxTest::FinishTests() {
+  for (SandboxTestingParent* stp : mSandboxTestingParents) {
+    SandboxTestingParent::Destroy(stp);
+  }
+  return NS_OK;
+}
+
+}  // namespace mozilla
+
+NS_IMPL_COMPONENT_FACTORY(mozISandboxTest) {
+  return MakeAndAddRef<SandboxTest>().downcast<nsISupports>();
+}
new file mode 100644
--- /dev/null
+++ b/security/sandbox/common/test/SandboxTest.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+#ifndef mozilla_SandboxTest_h
+#define mozilla_SandboxTest_h
+
+#include "SandboxTestingParent.h"
+#include "mozISandboxTest.h"
+
+#if !defined(MOZ_DEBUG) || !defined(ENABLE_TESTS)
+#  error "This file should not be used outside of debug with tests"
+#endif
+
+namespace mozilla {
+class SandboxTest : public mozISandboxTest {
+ public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_MOZISANDBOXTEST
+
+  SandboxTest() : mSandboxTestingParents{nullptr} {};
+
+ private:
+  virtual ~SandboxTest() = default;
+  static constexpr size_t NumProcessTypes =
+      static_cast<size_t>(GeckoProcessType_End);
+  SandboxTestingParent* mSandboxTestingParents[NumProcessTypes];
+};
+
+}  // namespace mozilla
+#endif  // mozilla_SandboxTest_h
new file mode 100644
--- /dev/null
+++ b/security/sandbox/common/test/SandboxTestingChild.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+#include "SandboxTestingChild.h"
+#include "SandboxTestingThread.h"
+
+namespace mozilla {
+
+SandboxTestingChild* SandboxTestingChild::sInstance = nullptr;
+
+bool SandboxTestingChild::IsTestThread() { return mThread->IsOnThread(); }
+
+void SandboxTestingChild::PostToTestThread(
+    already_AddRefed<nsIRunnable>&& runnable) {
+  mThread->Dispatch(std::move(runnable));
+}
+
+/* static */
+bool SandboxTestingChild::Initialize(
+    Endpoint<PSandboxTestingChild>&& aSandboxTestingEndpoint) {
+  MOZ_ASSERT(!sInstance);
+  SandboxTestingThread* thread = SandboxTestingThread::Create();
+  if (!thread) {
+    return false;
+  }
+  sInstance =
+      new SandboxTestingChild(thread, std::move(aSandboxTestingEndpoint));
+  return true;
+}
+
+/* static */
+SandboxTestingChild* SandboxTestingChild::GetInstance() {
+  MOZ_ASSERT(sInstance, "Must initialize SandboxTestingChild before using it");
+  return sInstance;
+}
+
+SandboxTestingChild::SandboxTestingChild(
+    SandboxTestingThread* aThread, Endpoint<PSandboxTestingChild>&& aEndpoint)
+    : mThread(aThread) {
+  MOZ_ASSERT(aThread);
+  PostToTestThread(NewNonOwningRunnableMethod<Endpoint<PSandboxTestingChild>&&>(
+      "SandboxTestingChild::Bind", this, &SandboxTestingChild::Bind,
+      std::move(aEndpoint)));
+}
+
+void SandboxTestingChild::Bind(Endpoint<PSandboxTestingChild>&& aEndpoint) {
+  MOZ_RELEASE_ASSERT(mThread->IsOnThread());
+  DebugOnly<bool> ok = aEndpoint.Bind(this);
+  MOZ_ASSERT(ok);
+
+  // Placeholder usage of the APIs needed to report test results.
+  // This will be fleshed out with tests that are OS and process type dependent.
+  SendReportTestResults(nsCString("testId1"), true /* shouldSucceed */,
+                        true /* didSucceed*/,
+                        nsCString("These are some test results!"));
+  // Tell SandboxTest that this process is done with all tests.
+  SendTestCompleted();
+}
+
+void SandboxTestingChild::ActorDestroy(ActorDestroyReason aWhy) {
+  MOZ_ASSERT(mThread->IsOnThread());
+  NS_DispatchToMainThread(NS_NewRunnableFunction(
+      "SandboxChildDestroyer", []() { SandboxTestingChild::Destroy(); }));
+}
+
+void SandboxTestingChild::Destroy() {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(sInstance);
+  delete sInstance;
+  sInstance = nullptr;
+}
+
+bool SandboxTestingChild::RecvShutDown() {
+  Close();
+  return true;
+}
+
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/security/sandbox/common/test/SandboxTestingChild.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxTestingChild_h
+#define mozilla_SandboxTestingChild_h
+
+#include "mozilla/PSandboxTestingChild.h"
+#include "mozilla/Monitor.h"
+
+#if !defined(MOZ_SANDBOX) || !defined(MOZ_DEBUG) || !defined(ENABLE_TESTS)
+#  error "This file should not be used outside of debug with tests"
+#endif
+
+namespace mozilla {
+
+class SandboxTestingThread;
+
+/**
+ * Runs tests that check sandbox in child process, depending on process type.
+ */
+class SandboxTestingChild : public PSandboxTestingChild {
+ public:
+  static bool Initialize(
+      Endpoint<PSandboxTestingChild>&& aSandboxTestingEndpoint);
+  static SandboxTestingChild* GetInstance();
+  static void Destroy();
+
+  bool IsTestThread();
+  void PostToTestThread(already_AddRefed<nsIRunnable>&& runnable);
+
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+
+  virtual bool RecvShutDown();
+
+ private:
+  explicit SandboxTestingChild(SandboxTestingThread* aThread,
+                               Endpoint<PSandboxTestingChild>&& aEndpoint);
+  void Bind(Endpoint<PSandboxTestingChild>&& aEndpoint);
+
+  nsAutoPtr<SandboxTestingThread> mThread;
+
+  static SandboxTestingChild* sInstance;
+};
+
+}  // namespace mozilla
+
+#endif  // mozilla_SandboxTestingChild_h
new file mode 100644
--- /dev/null
+++ b/security/sandbox/common/test/SandboxTestingParent.cpp
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "SandboxTestingParent.h"
+#include "SandboxTestingThread.h"
+
+namespace mozilla {
+
+/* static */
+SandboxTestingParent* SandboxTestingParent::Create(
+    Endpoint<PSandboxTestingParent>&& aParentEnd) {
+  SandboxTestingThread* thread = SandboxTestingThread::Create();
+  if (!thread) {
+    return nullptr;
+  }
+  return new SandboxTestingParent(thread, std::move(aParentEnd));
+}
+
+SandboxTestingParent::SandboxTestingParent(
+    SandboxTestingThread* aThread, Endpoint<PSandboxTestingParent>&& aParentEnd)
+    : mThread(aThread),
+      mMonitor("SandboxTestingParent Lock"),
+      mShutdownDone(false) {
+  MOZ_ASSERT(mThread);
+  mThread->Dispatch(
+      NewNonOwningRunnableMethod<Endpoint<PSandboxTestingParent>&&>(
+          "SandboxTestingParent::Bind", this, &SandboxTestingParent::Bind,
+          std::move(aParentEnd)));
+}
+
+void SandboxTestingParent::Bind(Endpoint<PSandboxTestingParent>&& aEnd) {
+  MOZ_RELEASE_ASSERT(mThread->IsOnThread());
+  DebugOnly<bool> ok = aEnd.Bind(this);
+  MOZ_ASSERT(ok);
+}
+
+void SandboxTestingParent::ShutdownSandboxTestThread() {
+  MOZ_ASSERT(mThread->IsOnThread());
+  Close();
+  // Notify waiting thread that we are done.
+  MonitorAutoLock lock(mMonitor);
+  mShutdownDone = true;
+  mMonitor.Notify();
+}
+
+void SandboxTestingParent::Destroy(SandboxTestingParent* aInstance) {
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!aInstance) {
+    return;
+  }
+
+  {
+    // Hold the lock while we destroy the actor on the test thread.
+    MonitorAutoLock lock(aInstance->mMonitor);
+    aInstance->mThread->Dispatch(NewNonOwningRunnableMethod(
+        "SandboxTestingParent::ShutdownSandboxTestThread", aInstance,
+        &SandboxTestingParent::ShutdownSandboxTestThread));
+
+    // Wait for test thread to complete destruction.
+    while (!aInstance->mShutdownDone) {
+      aInstance->mMonitor.Wait();
+    }
+  }
+
+  delete aInstance;
+}
+
+void SandboxTestingParent::ActorDestroy(ActorDestroyReason aWhy) {
+  MOZ_RELEASE_ASSERT(mThread->IsOnThread());
+}
+
+mozilla::ipc::IPCResult SandboxTestingParent::RecvReportTestResults(
+    const nsCString& testName, bool shouldSucceed, bool didSucceed,
+    const nsCString& resultMessage) {
+  NS_DispatchToMainThread(
+      NS_NewRunnableFunction("SandboxReportTestResults", [=]() {
+        nsCOMPtr<nsIObserverService> observerService =
+            mozilla::services::GetObserverService();
+        MOZ_RELEASE_ASSERT(observerService);
+        const char* kFmt =
+            "{ \"testid\" : \"%s\", \"shouldPermit\" : %s, "
+            "\"wasPermitted\" : %s, \"message\" : \"%s\" }";
+        nsString json;
+        json.AppendPrintf(
+            kFmt, testName.BeginReading(), shouldSucceed ? "true" : "false",
+            didSucceed ? "true" : "false", resultMessage.BeginReading());
+        observerService->NotifyObservers(nullptr, "sandbox-test-result",
+                                         json.BeginReading());
+      }));
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SandboxTestingParent::RecvTestCompleted() {
+  Unused << SendShutDown();
+  NS_DispatchToMainThread(
+      NS_NewRunnableFunction("SandboxReportTestResults", []() {
+        nsCOMPtr<nsIObserverService> observerService =
+            mozilla::services::GetObserverService();
+        MOZ_RELEASE_ASSERT(observerService);
+        observerService->NotifyObservers(nullptr, "sandbox-test-done", 0);
+      }));
+  return IPC_OK();
+}
+
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/security/sandbox/common/test/SandboxTestingParent.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxTestingParent_h
+#define mozilla_SandboxTestingParent_h
+
+#include "mozilla/PSandboxTestingParent.h"
+#include "mozilla/Monitor.h"
+
+#if !defined(MOZ_SANDBOX) || !defined(MOZ_DEBUG) || !defined(ENABLE_TESTS)
+#  error "This file should not be used outside of debug with tests"
+#endif
+
+namespace mozilla {
+
+class SandboxTestingThread;
+
+class SandboxTestingParent : public PSandboxTestingParent {
+ public:
+  static SandboxTestingParent* Create(
+      Endpoint<PSandboxTestingParent>&& aParentEnd);
+  static void Destroy(SandboxTestingParent* aInstance);
+
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+
+  mozilla::ipc::IPCResult RecvReportTestResults(const nsCString& testName,
+                                                bool shouldSucceed,
+                                                bool didSucceed,
+                                                const nsCString& resultMessage);
+  mozilla::ipc::IPCResult RecvTestCompleted();
+
+ private:
+  explicit SandboxTestingParent(SandboxTestingThread* aThread,
+                                Endpoint<PSandboxTestingParent>&& aParentEnd);
+  virtual ~SandboxTestingParent() = default;
+  void ShutdownSandboxTestThread();
+  void Bind(Endpoint<PSandboxTestingParent>&& aEnd);
+
+  nsAutoPtr<SandboxTestingThread> mThread;
+  Monitor mMonitor;
+  bool mShutdownDone;
+};
+
+}  // namespace mozilla
+
+#endif  // mozilla_SandboxTestingParent_h
new file mode 100644
--- /dev/null
+++ b/security/sandbox/common/test/SandboxTestingThread.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxTestingThread_h
+#define mozilla_SandboxTestingThread_h
+
+#include "nsThreadManager.h"
+
+#if !defined(MOZ_SANDBOX) || !defined(MOZ_DEBUG) || !defined(ENABLE_TESTS)
+#  error "This file should not be used outside of debug with tests"
+#endif
+
+namespace mozilla {
+
+class SandboxTestingThread {
+ public:
+  void Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) {
+    mThread->Dispatch(std::move(aRunnable), nsIEventTarget::NS_DISPATCH_NORMAL);
+  }
+
+  bool IsOnThread() {
+    bool on;
+    return NS_SUCCEEDED(mThread->IsOnCurrentThread(&on)) && on;
+  }
+
+  static SandboxTestingThread* Create() {
+    MOZ_RELEASE_ASSERT(NS_IsMainThread());
+    nsCOMPtr<nsIThread> thread;
+    if (NS_FAILED(
+            NS_NewNamedThread("Sandbox Testing", getter_AddRefs(thread)))) {
+      return nullptr;
+    }
+    return new SandboxTestingThread(thread);
+  }
+
+  ~SandboxTestingThread() {
+    MOZ_RELEASE_ASSERT(NS_IsMainThread());
+    mThread->Shutdown();
+  }
+
+ private:
+  explicit SandboxTestingThread(nsIThread* aThread) : mThread(aThread) {
+    MOZ_ASSERT(mThread);
+  }
+
+  nsCOMPtr<nsIThread> mThread;
+};
+}  // namespace mozilla
+
+#endif  // mozilla_SandboxTestingThread_h
new file mode 100644
--- /dev/null
+++ b/security/sandbox/common/test/mozISandboxTest.idl
@@ -0,0 +1,28 @@
+/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+// This interface is only for testing Sandbox.
+
+[scriptable, builtinclass, uuid(2306c118-3544-4674-9222-670b88dc07a9)]
+interface mozISandboxTest : nsISupports
+{
+  void startTests(in Array<ACString> aProcessesList);
+  void finishTests();
+};
+
+%{ C++
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+#define MOZ_SANDBOX_TEST_CID \
+  {0x989dda27, 0xb144, 0x45f9, {0x90, 0x39, 0x69, 0x74, 0x4e, 0xc6, dd0xd9, 0x12}}
+#define MOZ_SANDBOX_TEST_CONTRACTID \
+    "@mozilla.org/sandbox/sandbox-test;1"
+#else
+#error "This file should not be used outside of debug with tests"
+#endif
+%}
--- a/security/sandbox/test/browser.ini
+++ b/security/sandbox/test/browser.ini
@@ -12,8 +12,11 @@ skip-if = !e10s || (debug && os == 'win'
 
 [browser_content_sandbox_syscalls.js]
 skip-if = !e10s
 
 [browser_bug1393259.js]
 support-files =
   bug1393259.html
 skip-if = !e10s || (os != 'mac') # This is a Mac-specific test
+
+[browser_sandbox_test.js]
+skip-if = !debug
new file mode 100644
--- /dev/null
+++ b/security/sandbox/test/browser_sandbox_test.js
@@ -0,0 +1,48 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function test() {
+  waitForExplicitFinish();
+  const { Services } = ChromeUtils.import(
+    "resource://gre/modules/Services.jsm"
+  );
+
+  // Types of processes to test, taken from GeckoProcessTypes.h
+  var processTypes = ["tab", "gpu"];
+
+  // A callback called after each test-result.
+  Services.obs.addObserver(function result(subject, topic, data) {
+    let { testid, shouldPermit, wasPermitted, message } = JSON.parse(data);
+    ok(
+      shouldPermit == wasPermitted,
+      "Test " +
+        testid +
+        " was " +
+        (wasPermitted ? "" : "not ") +
+        "permitted.  | " +
+        message
+    );
+  }, "sandbox-test-result");
+
+  // A callback that is notified when a child process is done running tests.
+  var remainingTests = processTypes.length;
+  Services.obs.addObserver(_ => {
+    remainingTests = remainingTests - 1;
+    if (remainingTests == 0) {
+      // Notify SandboxTest component that it should terminate the connection
+      // with the child processes.
+      comp.finishTests();
+      // Notify mochitest that all process tests are complete.
+      finish();
+    }
+  }, "sandbox-test-done");
+
+  var comp = Cc["@mozilla.org/sandbox/sandbox-test;1"].getService(
+    Ci.mozISandboxTest
+  );
+
+  comp.startTests(processTypes);
+}
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -662,17 +662,17 @@ fn matches_simple_selector<E, F>(
 where
     E: Element,
     F: FnMut(&E, ElementSelectorFlags),
 {
     debug_assert!(context.shared.is_nested() || !context.shared.in_negation());
 
     match *selector {
         Component::Combinator(_) => unreachable!(),
-        Component::Part(ref part) => element.is_part(part),
+        Component::Part(ref parts) => parts.iter().all(|part| element.is_part(part)),
         Component::Slotted(ref selector) => {
             // <slots> are never flattened tree slottables.
             !element.is_html_slot_element() &&
                 context.shared.nest(|context| {
                     matches_complex_selector(selector.iter(), element, context, flags_setter)
                 })
         },
         Component::PseudoElement(ref pseudo) => {
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -602,17 +602,17 @@ impl<Impl: SelectorImpl> Selector<Impl> 
     }
 
     #[inline]
     pub fn is_part(&self) -> bool {
         self.0.header.header.is_part()
     }
 
     #[inline]
-    pub fn part(&self) -> Option<&Impl::PartName> {
+    pub fn parts(&self) -> Option<&[Impl::PartName]> {
         if !self.is_part() {
             return None;
         }
 
         let mut iter = self.iter();
         if self.has_pseudo_element() {
             // Skip the pseudo-element.
             for _ in &mut iter {}
@@ -1008,17 +1008,17 @@ pub enum Component<Impl: SelectorImpl> {
     /// NOTE(emilio): This should support a list of selectors, but as of this
     /// writing no other browser does, and that allows them to put ::slotted()
     /// in the rule hash, so we do that too.
     ///
     /// See https://github.com/w3c/csswg-drafts/issues/2158
     Slotted(Selector<Impl>),
     /// The `::part` pseudo-element.
     ///   https://drafts.csswg.org/css-shadow-parts/#part
-    Part(#[shmem(field_bound)] Impl::PartName),
+    Part(#[shmem(field_bound)] Box<[Impl::PartName]>),
     /// The `:host` pseudo-class:
     ///
     /// https://drafts.csswg.org/css-scoping/#host-selector
     ///
     /// NOTE(emilio): This should support a list of selectors, but as of this
     /// writing no other browser does, and that allows them to put :host()
     /// in the rule hash, so we do that too.
     ///
@@ -1297,19 +1297,24 @@ impl<Impl: SelectorImpl> ToCss for Compo
 
         match *self {
             Combinator(ref c) => c.to_css(dest),
             Slotted(ref selector) => {
                 dest.write_str("::slotted(")?;
                 selector.to_css(dest)?;
                 dest.write_char(')')
             },
-            Part(ref part_name) => {
+            Part(ref part_names) => {
                 dest.write_str("::part(")?;
-                display_to_css_identifier(part_name, dest)?;
+                for (i, name) in part_names.iter().enumerate() {
+                    if i != 0 {
+                        dest.write_char(' ')?;
+                    }
+                    display_to_css_identifier(name, dest)?;
+                }
                 dest.write_char(')')
             },
             PseudoElement(ref p) => p.to_css(dest),
             ID(ref s) => {
                 dest.write_char('#')?;
                 display_to_css_identifier(s, dest)
             },
             Class(ref s) => {
@@ -1621,17 +1626,17 @@ where
     }
 }
 
 #[derive(Debug)]
 enum SimpleSelectorParseResult<Impl: SelectorImpl> {
     SimpleSelector(Component<Impl>),
     PseudoElement(Impl::PseudoElement),
     SlottedPseudo(Selector<Impl>),
-    PartPseudo(Impl::PartName),
+    PartPseudo(Box<[Impl::PartName]>),
 }
 
 #[derive(Debug)]
 enum QNamePrefix<Impl: SelectorImpl> {
     ImplicitNoNamespace,                          // `foo` in attr selectors
     ImplicitAnyNamespace,                         // `foo` in type selectors, without a default ns
     ImplicitDefaultNamespace(Impl::NamespaceUrl), // `foo` in type selectors, with a default ns
     ExplicitNoNamespace,                          // `|foo`
@@ -2024,20 +2029,20 @@ where
         };
 
         empty = false;
 
         match parse_result {
             SimpleSelectorParseResult::SimpleSelector(s) => {
                 builder.push_simple_selector(s);
             },
-            SimpleSelectorParseResult::PartPseudo(part_name) => {
+            SimpleSelectorParseResult::PartPseudo(part_names) => {
                 state.insert(SelectorParsingState::AFTER_PART);
                 builder.push_combinator(Combinator::Part);
-                builder.push_simple_selector(Component::Part(part_name));
+                builder.push_simple_selector(Component::Part(part_names));
             },
             SimpleSelectorParseResult::SlottedPseudo(selector) => {
                 state.insert(SelectorParsingState::AFTER_SLOTTED);
                 builder.push_combinator(Combinator::SlotAssignment);
                 builder.push_simple_selector(Component::Slotted(selector));
             },
             SimpleSelectorParseResult::PseudoElement(p) => {
                 state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
@@ -2188,20 +2193,25 @@ where
                 }
                 let pseudo_element = if is_functional {
                     if P::parse_part(parser) && name.eq_ignore_ascii_case("part") {
                         if !state.allows_part() {
                             return Err(
                                 input.new_custom_error(SelectorParseErrorKind::InvalidState)
                             );
                         }
-                        let name = input.parse_nested_block(|input| {
-                            Ok(input.expect_ident()?.as_ref().into())
+                        let names = input.parse_nested_block(|input| {
+                            let mut result = Vec::with_capacity(1);
+                            result.push(input.expect_ident()?.as_ref().into());
+                            while !input.is_exhausted() {
+                                result.push(input.expect_ident()?.as_ref().into());
+                            }
+                            Ok(result.into_boxed_slice())
                         })?;
-                        return Ok(Some(SimpleSelectorParseResult::PartPseudo(name)));
+                        return Ok(Some(SimpleSelectorParseResult::PartPseudo(names)));
                     }
                     if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") {
                         if !state.allows_slotted() {
                             return Err(
                                 input.new_custom_error(SelectorParseErrorKind::InvalidState)
                             );
                         }
                         let selector = input.parse_nested_block(|input| {
@@ -3046,18 +3056,17 @@ pub mod tests {
         assert!(parse("::slotted()").is_err());
         assert!(parse("::slotted(div)").is_ok());
         assert!(parse("::slotted(div).foo").is_err());
         assert!(parse("::slotted(div + bar)").is_err());
         assert!(parse("::slotted(div) + foo").is_err());
 
         assert!(parse("::part()").is_err());
         assert!(parse("::part(42)").is_err());
-        // Though note https://github.com/w3c/csswg-drafts/issues/3502
-        assert!(parse("::part(foo bar)").is_err());
+        assert!(parse("::part(foo bar)").is_ok());
         assert!(parse("::part(foo):hover").is_ok());
         assert!(parse("::part(foo) + bar").is_err());
 
         assert!(parse("div ::slotted(div)").is_ok());
         assert!(parse("div + slot::slotted(div)").is_ok());
         assert!(parse("div + slot::slotted(div.foo)").is_ok());
         assert!(parse("slot::slotted(div,foo)::first-line").is_err());
         assert!(parse("::slotted(div)::before").is_ok());
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -2027,21 +2027,27 @@ impl CascadeData {
                                     quirks_mode,
                                 )?;
                             }
                         }
 
                         // Part is special, since given it doesn't have any
                         // selectors inside, it's not worth using a whole
                         // SelectorMap for it.
-                        if let Some(part) = selector.part() {
+                        if let Some(parts) = selector.parts() {
+                            // ::part() has all semantics, so we just need to
+                            // put any of them in the selector map.
+                            //
+                            // We choose the last one quite arbitrarily,
+                            // expecting it's slightly more likely to be more
+                            // specific.
                             self.part_rules
                                 .get_or_insert_with(|| Box::new(Default::default()))
                                 .for_insertion(pseudo_element)
-                                .try_entry(part.clone())?
+                                .try_entry(parts.last().unwrap().clone())?
                                 .or_insert_with(SmallVec::new)
                                 .try_push(rule)?;
                         } else {
                             // NOTE(emilio): It's fine to look at :host and then at
                             // ::slotted(..), since :host::slotted(..) could never
                             // possibly match, as <slot> is not a valid shadow host.
                             let rules =
                                 if selector.is_featureless_host_selector_or_pseudo_element() {
--- a/taskcluster/ci/iris/kind.yml
+++ b/taskcluster/ci/iris/kind.yml
@@ -26,16 +26,36 @@ job-defaults:
         retrigger: true
     dependencies:
         build:
             by-platform:
                 linux64.*: build-linux64-shippable/opt
                 osx.*: build-macosx64-shippable/opt
                 windows10-64.*: build-win64-shippable/opt
     description: Run the iris test suite's {} tests
+    notify:
+        email:
+            by-project:
+                try:
+                    subject: 'Iris Firefox {chunk} has completed a test run for a patch on (try)'
+                    message: A team member has submitted a patch to try and the results are in.
+                    link:
+                        text: Treeherder Job
+                        href: '{th_root}jobs?repo={project}&revision={head_rev}{tiers}{filterstring}'
+                    on-reasons: [any]
+                    emails: [iris@mozilla.com]
+                mozilla-central:
+                    subject: 'Iris Firefox {chunk} tests failed (mozilla-central)'
+                    message: This calls for an action from the QA Automation team. Use the link to view it on Treeherder.
+                    link:
+                        text: Treeherder Job
+                        href: '{th_root}jobs?repo={project}&revision={head_rev}{tiers}{filterstring}'
+                    on-reasons: [failed]
+                    emails: [iris@mozilla.com]
+                default: []
     fetches:
         build:
             by-platform:
                 linux64.*:
                     - target.tar.bz2
                 osx.*:
                     - target.dmg
                 windows10-64.*:
--- a/taskcluster/ci/release-early-tagging/kind.yml
+++ b/taskcluster/ci/release-early-tagging/kind.yml
@@ -10,31 +10,28 @@ transforms:
     - taskgraph.transforms.task:transforms
 
 job-defaults:
     description: Release Promotion version tag for buildN
     run-on-projects: []
     shipping-phase: promote
     worker-type:
         by-project:
-            mozilla-(beta|release|esr.*): scriptworker-prov-v1/treescript-v1
-            maple: scriptworker-prov-v1/treescript-v1
-            birch: scriptworker-prov-v1/treescript-v1
-            jamun: scriptworker-prov-v1/treescript-v1
-            default: scriptworker-prov-v1/treescript-dev
+            mozilla-(beta|release|esr.*): scriptworker-k8s/gecko-3-tree
+            maple: scriptworker-k8s/gecko-3-tree
+            default: scriptworker-k8s/gecko-1-tree
     worker:
         implementation: treescript
         tags: ['buildN']
         bump: false
         dontbuild: true
         push:
             by-project:
                 mozilla-(beta|release|esr.*): true
                 maple: true
-                birch: true
                 default: false
 
 jobs:
     fennec:
         name: fennec-tag-buildN
         shipping-product: fennec
 
     firefox:
--- a/taskcluster/ci/release-version-bump/kind.yml
+++ b/taskcluster/ci/release-version-bump/kind.yml
@@ -13,43 +13,39 @@ kind-dependencies:
     - release-beetmover-push-to-release
 
 job-defaults:
     description: Release Promotion version bump/tag
     run-on-projects: []
     shipping-phase: ship
     worker-type:
         by-project:
-            mozilla-(beta|release|esr.*): scriptworker-prov-v1/treescript-v1
-            maple: scriptworker-prov-v1/treescript-v1
-            birch: scriptworker-prov-v1/treescript-v1
-            jamun: scriptworker-prov-v1/treescript-v1
-            default: scriptworker-prov-v1/treescript-dev
+            mozilla-(beta|release|esr.*): scriptworker-k8s/gecko-3-tree
+            maple: scriptworker-k8s/gecko-3-tree
+            default: scriptworker-k8s/gecko-1-tree
     worker:
         implementation: treescript
         dontbuild: true
         tags: ['release']
         bump: true
         bump-files:
             by-project:
                 default: ["browser/config/version_display.txt"]
                 mozilla-(release|esr.*):
                     - "browser/config/version.txt"
                     - "browser/config/version_display.txt"
                     - "config/milestone.txt"
-                jamun:
+                maple:
                     - "browser/config/version.txt"
                     - "browser/config/version_display.txt"
                     - "config/milestone.txt"
         push:
             by-project:
                 mozilla-(beta|release|esr.*): true
                 maple: true
-                birch: true
-                jamun: true
                 default: false
 
 jobs:
     fennec:
         name: fennec-version-bump
         shipping-product: fennec
 
     firefox:
--- a/taskcluster/docs/versioncontrol.rst
+++ b/taskcluster/docs/versioncontrol.rst
@@ -21,18 +21,19 @@ at the path ``hgext/robustcheckout/__ini
 When upgrading Mercurial, the ``robustcheckout`` extension should also
 be updated to ensure it is compatible with the version of Mercurial
 being upgraded to. Typically, one simply copies the latest version
 from ``version-control-tools`` into the vendored locations.
 
 The locations are as follows:
 
 - In-tree: ``testing/mozharness/external_tools/robustcheckout.py``
-- Treescript: ``https://github.com/mozilla-releng/treescript/tree/master/treescript/py2/robustcheckout.py``
+- Treescript: ``https://github.com/mozilla-releng/scriptworker-scripts/blob/master/treescript/treescript/py2/robustcheckout.py``
 - build-puppet: ``https://github.com/mozilla-releng/build-puppet/blob/master/modules/mercurial/files/robustcheckout.py``
+- ronin_puppet: ``https://github.com/mozilla-platform-ops/ronin_puppet/blob/master/modules/mercurial/files/robustcheckout.py``
 - OpenCloudConfig: ``https://github.com/mozilla-releng/OpenCloudConfig/blob/master/userdata/Configuration/FirefoxBuildResources/robustcheckout.py``
 
 
 Debian Packages for Debian Based Docker Images
 ----------------------------------------------
 
 ``taskcluster/ci/packages/kind.yml`` defines custom Debian packages for
 Mercurial. These are installed in various Docker images.
--- a/taskcluster/taskgraph/transforms/iris.py
+++ b/taskcluster/taskgraph/transforms/iris.py
@@ -56,8 +56,58 @@ def make_iris_tasks(config, jobs):
 
             # Clean up some entries when they aren't needed
             if clone["worker"]["docker-image"] is None:
                 del clone["worker"]["docker-image"]
             if clone["worker"]["env"]["PATH"] is None:
                 del clone["worker"]["env"]["PATH"]
 
             yield clone
+
+
+@transforms.add
+def fill_email_data(config, tasks):
+    format_kwargs = {
+        "head_rev": config.params["head_rev"],
+        "project": config.params["project"],
+        "th_root": "https://treeherder.mozilla.org/#/",
+        "tiers": "&tier=1%2C2%2C3",
+    }
+
+    for task in tasks:
+        format_kwargs["task_name"] = task["name"]
+        format_kwargs["filterstring"] = "&searchStr=iris%20{}".format(task["name"])
+        format_kwargs["chunk"] = task["worker"]["env"]["CURRENT_TEST_DIR"]
+
+        resolve_keyed_by(task, 'notify.email', item_name=task["name"], **{
+            'project': config.params["project"],
+        })
+
+        email = task["notify"].get("email")
+        if email:
+            email["link"]["href"] = email["link"]["href"].format(**format_kwargs)
+            email["subject"] = email["subject"].format(**format_kwargs)
+
+        yield task
+
+
+@transforms.add
+def add_notify_email(config, tasks):
+    for task in tasks:
+        notify = task.pop('notify', {})
+        email_config = notify.get('email')
+        if email_config:
+            extra = task.setdefault('extra', {})
+            notify = extra.setdefault('notify', {})
+            notify['email'] = {
+                'subject': email_config['subject'],
+                'content': email_config['message'],
+                'link': email_config.get('link', None),
+            }
+
+            routes = task.setdefault('routes', [])
+            routes.extend([
+                'notify.email.{}.on-{}'.format(address, reason)
+                for address in email_config['emails']
+                for reason in email_config['on-reasons']
+            ])
+
+        yield task
--- a/taskcluster/taskgraph/util/workertypes.py
+++ b/taskcluster/taskgraph/util/workertypes.py
@@ -19,17 +19,18 @@ WORKER_TYPES = {
     'invalid/always-optimized': ('always-optimized', None),
     'scriptworker-k8s/gecko-1-balrog': ('balrog', None),
     'scriptworker-k8s/gecko-3-balrog': ('balrog', None),
     'scriptworker-k8s/gecko-3-beetmover': ('beetmover', None),
     'scriptworker-prov-v1/pushapk-v1': ('push-apk', None),
     "scriptworker-prov-v1/signing-linux-v1": ('scriptworker-signing', None),
     "scriptworker-k8s/gecko-3-shipit": ('shipit', None),
     "scriptworker-k8s/gecko-1-shipit": ('shipit', None),
-    "scriptworker-prov-v1/treescript-v1": ('treescript', None),
+    "scriptworker-k8s/gecko-3-tree": ('treescript', None),
+    "scriptworker-k8s/gecko-1-tree": ('treescript', None),
     'terraform-packet/gecko-t-linux': ('docker-worker', 'linux'),
     'releng-hardware/gecko-t-osx-1014': ('generic-worker', 'macosx'),
     'releng-hardware/gecko-t-osx-1014-power': ('generic-worker', 'macosx'),
 }
 
 
 @memoize
 def _get(graph_config, alias, level):
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-shadow-parts/multiple-parts.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[multiple-parts.html]
-  [Double-part in selected host is styled]
-    expected: FAIL
-
--- a/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
+++ b/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
@@ -82,17 +82,17 @@ skip-if = toolkit == 'android' # autocom
 [test_basic_form.html]
 [test_basic_form_0pw.html]
 [test_basic_form_1pw.html]
 [test_basic_form_1pw_2.html]
 [test_basic_form_2pw_1.html]
 [test_basic_form_2pw_2.html]
 [test_basic_form_3pw_1.html]
 [test_basic_form_autocomplete.html]
-skip-if = toolkit == 'android' || (os == 'linux' && debug) # android:autocomplete, linux: bug 1538955
+skip-if = toolkit == 'android' # android:autocomplete
 scheme = https
 [test_basic_form_autocomplete_subdomain.html]
 skip-if = toolkit == 'android' # android:autocomplete.
 scheme = https
 [test_basic_form_autocomplete_formActionOrigin.html]
 skip-if = toolkit == 'android' # android:autocomplete.
 scheme = https
 [test_basic_form_honor_autocomplete_off.html]
--- a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
@@ -130,16 +130,23 @@ function sendFakeAutocompleteEvent(eleme
   acEvent.initEvent("DOMAutoComplete", true, false);
   element.dispatchEvent(acEvent);
 }
 
 function spinEventLoop() {
   return Promise.resolve();
 }
 
+async function promiseACPopupClosed() {
+  return SimpleTest.promiseWaitForCondition(async () => {
+    let popupState = await getPopupState();
+    return !popupState.open;
+  }, "Wait for AC popup to be closed");
+}
+
 add_task(async function setup() {
   listenForUnexpectedPopupShown();
 });
 
 add_task(async function test_form1_initial_empty() {
   await SimpleTest.promiseFocus(window);
 
   // Make sure initial form is empty.
@@ -160,19 +167,19 @@ add_task(async function test_form1_menui
   is(popupState.selectedIndex, -1, "Check no entries are selected upon opening");
 
   let expectedMenuItems = ["tempuser1",
                            "testuser2",
                            "testuser3",
                            "zzzuser4"];
   checkAutoCompleteResults(results, expectedMenuItems, "example.com", "Check all menuitems are displayed correctly.");
 
-  checkLoginForm(uname, "", pword, ""); // value shouldn't update just by selecting
-  synthesizeKey("KEY_Enter");
-  await spinEventLoop(); // let focus happen
+  checkLoginForm(uname, "", pword, ""); // value shouldn't update just by opening
+  synthesizeKey("KEY_Escape");
+  await promiseACPopupClosed();
   checkLoginForm(uname, "", pword, "");
 });
 
 add_task(async function test_form1_first_entry() {
   await SimpleTest.promiseFocus(window);
   // Trigger autocomplete popup
   restoreForm();
   let shownPromise = promiseACShown();
@@ -181,61 +188,65 @@ add_task(async function test_form1_first
 
   let popupState = await getPopupState();
   is(popupState.selectedIndex, -1, "Check no entries are selected upon opening");
 
   synthesizeKey("KEY_ArrowDown"); // first
   checkLoginForm(uname, "", pword, ""); // value shouldn't update just by selecting
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "tempuser1", pword, "temppass1");
 });
 
 add_task(async function test_form1_second_entry() {
   // Trigger autocomplete popup
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   synthesizeKey("KEY_ArrowDown"); // first
   synthesizeKey("KEY_ArrowDown"); // second
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "testuser2", pword, "testpass2");
 });
 
 add_task(async function test_form1_third_entry() {
   // Trigger autocomplete popup
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   synthesizeKey("KEY_ArrowDown"); // first
   synthesizeKey("KEY_ArrowDown"); // second
   synthesizeKey("KEY_ArrowDown"); // third
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "testuser3", pword, "testpass3");
 });
 
 add_task(async function test_form1_fourth_entry() {
   // Trigger autocomplete popup
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   synthesizeKey("KEY_ArrowDown"); // first
   synthesizeKey("KEY_ArrowDown"); // second
   synthesizeKey("KEY_ArrowDown"); // third
   synthesizeKey("KEY_ArrowDown"); // fourth
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "zzzuser4", pword, "zzzpass4");
 });
 
 add_task(async function test_form1_wraparound_first_entry() {
   // Trigger autocomplete popup
   restoreForm();
   await spinEventLoop(); // let focus happen
   let shownPromise = promiseACShown();
@@ -246,46 +257,49 @@ add_task(async function test_form1_wrapa
   synthesizeKey("KEY_ArrowDown"); // second
   synthesizeKey("KEY_ArrowDown"); // third
   synthesizeKey("KEY_ArrowDown"); // fourth
   synthesizeKey("KEY_ArrowDown"); // footer
   synthesizeKey("KEY_ArrowDown"); // deselects
   synthesizeKey("KEY_ArrowDown"); // first
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "tempuser1", pword, "temppass1");
 });
 
 add_task(async function test_form1_wraparound_up_last_entry() {
   // Trigger autocomplete popup
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   synthesizeKey("KEY_ArrowUp"); // footer
   synthesizeKey("KEY_ArrowUp"); // last (fourth)
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "zzzuser4", pword, "zzzpass4");
 });
 
 add_task(async function test_form1_wraparound_down_up_up() {
   // Trigger autocomplete popup
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   synthesizeKey("KEY_ArrowDown"); // select first entry
   synthesizeKey("KEY_ArrowUp"); // selects nothing!
   synthesizeKey("KEY_ArrowUp"); // footer
   synthesizeKey("KEY_ArrowUp"); // select last entry
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "zzzuser4", pword, "zzzpass4");
 });
 
 add_task(async function test_form1_wraparound_up_last() {
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
@@ -297,72 +311,76 @@ add_task(async function test_form1_wrapa
   synthesizeKey("KEY_ArrowUp");
   synthesizeKey("KEY_ArrowUp");
   synthesizeKey("KEY_ArrowUp"); // first entry
   synthesizeKey("KEY_ArrowUp"); // deselects
   synthesizeKey("KEY_ArrowUp"); // footer
   synthesizeKey("KEY_ArrowUp"); // last entry
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "zzzuser4", pword, "zzzpass4");
 });
 
 add_task(async function test_form1_fill_username_without_autofill_right() {
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   // Set first entry w/o triggering autocomplete
   synthesizeKey("KEY_ArrowDown"); // first
   synthesizeKey("KEY_ArrowRight");
-  await spinEventLoop();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "tempuser1", pword, ""); // empty password
 });
 
 add_task(async function test_form1_fill_username_without_autofill_left() {
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   // Set first entry w/o triggering autocomplete
   synthesizeKey("KEY_ArrowDown"); // first
   synthesizeKey("KEY_ArrowLeft");
+  await promiseACPopupClosed();
   checkLoginForm(uname, "tempuser1", pword, ""); // empty password
 });
 
 add_task(async function test_form1_pageup_first() {
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   // Check first entry (page up)
   synthesizeKey("KEY_ArrowDown"); // first
   synthesizeKey("KEY_ArrowDown"); // second
   synthesizeKey("KEY_PageUp"); // first
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "tempuser1", pword, "temppass1");
 });
 
 add_task(async function test_form1_pagedown_last() {
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   /* test 13 */
   // Check last entry (page down)
   synthesizeKey("KEY_ArrowDown"); // first
   synthesizeKey("KEY_PageDown"); // footer
   synthesizeKey("KEY_ArrowUp"); // last
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "zzzuser4", pword, "zzzpass4");
 });
 
 add_task(async function test_form1_untrusted_event() {
   restoreForm();
   await spinEventLoop();
 
   // Send a fake (untrusted) event.
@@ -397,29 +415,31 @@ add_task(async function test_form1_delet
   await deletionPromise;
 
   checkLoginForm(uname, "", pword, "");
   numLogins = await LoginManager.countLogins("https://example.com", "https://autocomplete:8888", null);
   is(numLogins, 4, "Correct number of logins after deleting one");
   await countChangedPromise;
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "testuser2", pword, "testpass2");
 });
 
 add_task(async function test_form1_first_after_deletion() {
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   // Check the new first entry (of 3)
   synthesizeKey("KEY_ArrowDown");
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "testuser2", pword, "testpass2");
 });
 
 add_task(async function test_form1_delete_second() {
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
@@ -428,29 +448,31 @@ add_task(async function test_form1_delet
   synthesizeKey("KEY_ArrowDown");
   synthesizeKey("KEY_ArrowDown");
   synthesizeKey("KEY_Delete", {shiftKey: true});
   checkLoginForm(uname, "", pword, "");
   let numLogins = await LoginManager.countLogins("https://example.com", "https://autocomplete:8888", null);
   is(numLogins, 3, "Correct number of logins after deleting one");
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "zzzuser4", pword, "zzzpass4");
 });
 
 add_task(async function test_form1_first_after_deletion2() {
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   // Check the new first entry (of 2)
   synthesizeKey("KEY_ArrowDown");
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "testuser2", pword, "testpass2");
 });
 
 add_task(async function test_form1_delete_last() {
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
@@ -461,29 +483,31 @@ add_task(async function test_form1_delet
   synthesizeKey("KEY_ArrowDown");
   synthesizeKey("KEY_Delete", {shiftKey: true});
   checkLoginForm(uname, "", pword, "");
   let numLogins = await LoginManager.countLogins("https://example.com", "https://autocomplete:8888", null);
   is(numLogins, 2, "Correct number of logins after deleting one");
   synthesizeKey("KEY_ArrowDown");
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "testuser2", pword, "testpass2");
 });
 
 add_task(async function test_form1_first_after_3_deletions() {
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   // Check the only remaining entry
   synthesizeKey("KEY_ArrowDown");
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "testuser2", pword, "testpass2");
 });
 
 add_task(async function test_form1_check_only_entry_remaining() {
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
@@ -496,16 +520,20 @@ add_task(async function test_form1_check
   checkLoginForm(uname, "", pword, "");
   let numLogins = await LoginManager.countLogins("https://example.com", "https://autocomplete:8888", null);
   is(numLogins, 1, "Correct number of logins after deleting one");
 
   // remove the logins for the previous tests
   setupScript.sendAsyncMessage("removeLogin", "login0");
   setupScript.sendAsyncMessage("addLogin", "login5");
   await storageChanged;
+
+  // ensure the popup is closed for the next test
+  synthesizeKey("KEY_Escape");
+  await promiseACPopupClosed();
 });
 
 /* Tests for single-user forms for ignoring autocomplete=off */
 add_task(async function test_form2() {
   await setFormAndWaitForFieldFilled(`
     <form id="form2" action="https://autocomplete2" onsubmit="return false;">
       <input  type="text"       name="uname">
       <input  type="password"   name="pword" autocomplete="off">
@@ -522,16 +550,17 @@ add_task(async function test_form2() {
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   // Check first entry
   synthesizeKey("KEY_ArrowDown");
   checkLoginForm(uname, "", pword, ""); // value shouldn't update
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "singleuser5", pword, "singlepass5");
 });
 
 add_task(async function test_form3() {
   await setFormAndWaitForFieldFilled(`
     <form id="form3" action="https://autocomplete2" onsubmit="return false;">
       <input  type="text"       name="uname" autocomplete="off">
       <input  type="password"   name="pword">
@@ -546,16 +575,17 @@ add_task(async function test_form3() {
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   // Check first entry
   synthesizeKey("KEY_ArrowDown");
   checkLoginForm(uname, "", pword, ""); // value shouldn't update
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "singleuser5", pword, "singlepass5");
 });
 
 add_task(async function test_form4() {
   await setFormAndWaitForFieldFilled(`
     <form id="form4" action="https://autocomplete2" onsubmit="return false;" autocomplete="off">
       <input  type="text"       name="uname">
       <input  type="password"   name="pword">
@@ -570,16 +600,17 @@ add_task(async function test_form4() {
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   // Check first entry
   synthesizeKey("KEY_ArrowDown");
   checkLoginForm(uname, "", pword, ""); // value shouldn't update
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "singleuser5", pword, "singlepass5");
 });
 
 add_task(async function test_form5() {
   await setFormAndWaitForFieldFilled(`
     <form id="form5" action="https://autocomplete2" onsubmit="return false;">
       <input  type="text"       name="uname" autocomplete="off">
       <input  type="password"   name="pword" autocomplete="off">
@@ -594,16 +625,17 @@ add_task(async function test_form5() {
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   // Check first entry
   synthesizeKey("KEY_ArrowDown");
   checkLoginForm(uname, "", pword, ""); // value shouldn't update
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "singleuser5", pword, "singlepass5");
 });
 
 add_task(async function test_form6() {
   await setFormAndWaitForFieldFilled(`
     <!-- control -->
     <form id="form6" action="https://autocomplete2" onsubmit="return false;">
       <input  type="text"       name="uname">
@@ -683,16 +715,17 @@ add_task(async function test_form7_2() {
   synthesizeKey("KEY_ArrowDown");
   checkLoginForm(uname, "", pword, ""); // value shouldn't update
   synthesizeKey("KEY_Enter");
   // The form changes, so we expect the old username field to get the
   // selected autocomplete value, but neither the new username field nor
   // the password field should have any values filled in.
   await SimpleTest.promiseWaitForCondition(() => uname.value == "form7user1",
                                            "Wait for username to get filled");
+  await promiseACPopupClosed();
   checkLoginForm(uname, "form7user1", pword, "");
   is($_(7, "uname2").value, "", "Verifying empty uname2");
   restoreForm(); // clear field, so reloading test doesn't fail
 
   let storageChanged = promiseStorageChanged(["removeLogin"]);
   setupScript.sendAsyncMessage("removeLogin", "login6A");
   await storageChanged;
 });
@@ -777,16 +810,17 @@ add_task(async function test_form9_filte
   synthesizeKey("A", {shiftKey: true});
   results = await shownPromise;
 
   checkLoginForm(uname, "form9userAAB", pword, "");
   checkAutoCompleteResults(results, ["form9userAAB"], "example.com", "Check dropdown is updated after inserting 'A'");
   synthesizeKey("KEY_ArrowDown");
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "form9userAAB", pword, "form9pass");
 });
 
 add_task(async function test_form9_autocomplete_cache() {
   // Note that this addLogin call will only be seen by the autocomplete
   // attempt for the synthesizeKey if we do not successfully cache the
   // autocomplete results.
   let storageChanged = promiseStorageChanged(["addLogin"]);
@@ -830,16 +864,17 @@ add_task(async function test_form11_form
   await shownPromise;
 
   // Trigger autocomplete
   synthesizeKey("KEY_ArrowDown");
   checkLoginForm(uname, "", pword, ""); // value shouldn't update
   let processedPromise = promiseFormsProcessed();
   synthesizeKey("KEY_Enter");
   await processedPromise;
+  await promiseACPopupClosed();
   checkLoginForm(uname, "testuser11", pword, "testpass11");
 });
 
 add_task(async function test_form11_open_on_trusted_focus() {
   uname = $_(11, "uname");
   pword = $_(11, "pword");
   uname.value = "";
   pword.value = "";
@@ -857,16 +892,17 @@ add_task(async function test_form11_open
   const shownPromise = promiseACShown();
   synthesizeMouseAtCenter(uname, {});
   await firePrivEventPromise;
   await shownPromise;
   synthesizeKey("KEY_ArrowDown");
   const processedPromise = promiseFormsProcessed();
   synthesizeKey("KEY_Enter");
   await processedPromise;
+  await promiseACPopupClosed();
   checkLoginForm(uname, "testuser11", pword, "testpass11");
   let storageChanged = promiseStorageChanged(["removeLogin"]);
   setupScript.sendAsyncMessage("removeLogin", "login11");
   await storageChanged;
 });
 
 add_task(async function test_form12_recipes() {
   let storageChanged = promiseStorageChanged(["addLogin"]);
@@ -900,16 +936,17 @@ add_task(async function test_form12_reci
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   synthesizeKey("KEY_ArrowDown");
   checkLoginForm(uname, "", pword, ""); // value shouldn't update
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
+  await promiseACPopupClosed();
   checkLoginForm(uname, "testuser10", pword, "testpass10");
 
   // Now test recipes with blur on the username field.
   restoreForm();
   checkLoginForm(uname, "", pword, "");
   uname.value = "testuser10";
   checkLoginForm(uname, "testuser10", pword, "");
   synthesizeKey("KEY_Tab");
@@ -947,13 +984,17 @@ add_task(async function test_form13_stay
   synthesizeMouseAtCenter(pword, {});
   pword.select();
   popupState = await getPopupState();
   is(popupState.open, false, "Check popup closed since password field isn't empty");
   shownPromise = promiseACShown();
   synthesizeKey("KEY_Delete");
   await shownPromise;
   checkLoginForm(uname, "", pword, "");
+
+  // ensure the popup is closed for the next test
+  synthesizeKey("KEY_Escape");
+  await promiseACPopupClosed();
 });
 </script>
 </pre>
 </body>
 </html>
--- a/toolkit/components/pictureinpicture/tests/browser.ini
+++ b/toolkit/components/pictureinpicture/tests/browser.ini
@@ -1,15 +1,16 @@
 [DEFAULT]
 support-files =
   click-event-helper.js
   head.js
   test-button-overlay.html
   test-opaque-overlay.html
   test-page.html
+  test-page-with-iframe.html
   test-pointer-events-none.html
   test-transparent-overlay-1.html
   test-transparent-overlay-2.html
   test-video.mp4
 
 prefs =
   media.videocontrols.picture-in-picture.enabled=true
   media.videocontrols.picture-in-picture.video-toggle.enabled=true
@@ -22,16 +23,18 @@ prefs =
 [browser_fullscreen.js]
 skip-if = (os == "mac" && debug) #Bug 1566173
 [browser_mouseButtonVariation.js]
 skip-if = debug
 [browser_removeVideoElement.js]
 [browser_rerequestPiP.js]
 [browser_showMessage.js]
 [browser_stripVideoStyles.js]
+[browser_thirdPartyIframe.js]
+skip-if = fission # Bug 1576915
 [browser_toggleAfterTabTearOutIn.js]
 [browser_toggleButtonOverlay.js]
 skip-if = true # Bug 1546455
 [browser_toggleOnInsertedVideo.js]
 [browser_toggleOpaqueOverlay.js]
 skip-if = true # Bug 1546455
 [browser_togglePointerEventsNone.js]
 [browser_toggleSimple.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/pictureinpicture/tests/browser_thirdPartyIframe.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that videos hosted inside of a third-party <iframe> can be opened
+ * in a Picture-in-Picture window.
+ */
+add_task(async () => {
+  for (let videoID of ["with-controls", "no-controls"]) {
+    info(`Testing ${videoID} case.`);
+
+    await BrowserTestUtils.withNewTab(
+      {
+        url: TEST_PAGE_WITH_IFRAME,
+        gBrowser,
+      },
+      async browser => {
+        // TEST_PAGE_WITH_IFRAME is hosted at a different domain from TEST_PAGE,
+        // so loading TEST_PAGE within the iframe will act as our third-party
+        // iframe.
+        await SpecialPowers.spawn(browser, [TEST_PAGE], async TEST_PAGE => {
+          let iframe = content.document.getElementById("iframe");
+          let loadPromise = ContentTaskUtils.waitForEvent(iframe, "load");
+          iframe.src = TEST_PAGE;
+          await loadPromise;
+        });
+
+        let iframeBc = browser.browsingContext.getChildren()[0];
+        let pipWin = await triggerPictureInPicture(iframeBc, videoID);
+        ok(pipWin, "Got Picture-in-Picture window.");
+
+        try {
+          await assertShowingMessage(iframeBc, videoID, true);
+        } finally {
+          let uaWidgetUpdate = SpecialPowers.spawn(iframeBc, [], async () => {
+            await ContentTaskUtils.waitForEvent(
+              content.windowRoot,
+              "UAWidgetSetupOrChange",
+              true /* capture */
+            );
+          });
+          await BrowserTestUtils.closeWindow(pipWin);
+          await uaWidgetUpdate;
+        }
+
+        // no-controls case is disabled until we ensure that there's a UAWidget for
+        // the no-controls case on Desktop (which should be fixed as part of
+        // bug 1535354).
+        if (videoID !== "no-controls") {
+          await assertShowingMessage(iframeBc, videoID, false);
+        }
+      }
+    );
+  }
+});
--- a/toolkit/components/pictureinpicture/tests/head.js
+++ b/toolkit/components/pictureinpicture/tests/head.js
@@ -2,38 +2,44 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const TEST_ROOT = getRootDirectory(gTestPath).replace(
   "chrome://mochitests/content",
   "http://example.com"
 );
+const TEST_ROOT_2 = getRootDirectory(gTestPath).replace(
+  "chrome://mochitests/content",
+  "http://example.org"
+);
 const TEST_PAGE = TEST_ROOT + "test-page.html";
+const TEST_PAGE_WITH_IFRAME = TEST_ROOT_2 + "test-page-with-iframe.html";
 const WINDOW_TYPE = "Toolkit:PictureInPicture";
 const TOGGLE_ID = "pictureInPictureToggleButton";
 const HOVER_VIDEO_OPACITY = 0.8;
 const HOVER_TOGGLE_OPACITY = 1.0;
 
 /**
  * Given a browser and the ID for a <video> element, triggers
  * Picture-in-Picture for that <video>, and resolves with the
  * Picture-in-Picture window once it is ready to be used.
  *
- * @param {Element} browser The <xul:browser> hosting the <video>
+ * @param {Element,BrowsingContext} browser The <xul:browser> or
+ * BrowsingContext hosting the <video>
  *
  * @param {String} videoID The ID of the video to trigger
  * Picture-in-Picture on.
  *
  * @return Promise
  * @resolves With the Picture-in-Picture window when ready.
  */
 async function triggerPictureInPicture(browser, videoID) {
   let domWindowOpened = BrowserTestUtils.domWindowOpened(null);
-  let videoReady = ContentTask.spawn(browser, videoID, async videoID => {
+  let videoReady = SpecialPowers.spawn(browser, [videoID], async videoID => {
     let video = content.document.getElementById(videoID);
     let event = new content.CustomEvent("MozTogglePictureInPicture", {
       bubbles: true,
     });
     video.dispatchEvent(event);
     await ContentTaskUtils.waitForCondition(() => {
       return video.isCloningElementVisually;
     }, "Video is being cloned visually.");
@@ -44,32 +50,33 @@ async function triggerPictureInPicture(b
   return win;
 }
 
 /**
  * Given a browser and the ID for a <video> element, checks that the
  * video is showing the "This video is playing in Picture-in-Picture mode."
  * status message overlay.
  *
- * @param {Element} browser The <xul:browser> hosting the <video>
+ * @param {Element,BrowsingContext} browser The <xul:browser> or
+ * BrowsingContext hosting the <video>
  *
  * @param {String} videoID The ID of the video to trigger
  * Picture-in-Picture on.
  *
  * @param {bool} expected True if we expect the message to be showing.
  *
  * @return Promise
  * @resolves When the checks have completed.
  */
 async function assertShowingMessage(browser, videoID, expected) {
-  let showing = await ContentTask.spawn(browser, videoID, async videoID => {
+  let showing = await SpecialPowers.spawn(browser, [videoID], async videoID => {
     let video = content.document.getElementById(videoID);
     let shadowRoot = video.openOrClosedShadowRoot;
     let pipOverlay = shadowRoot.querySelector(".pictureInPictureOverlay");
-    ok(pipOverlay, "Should be able to find Picture-in-Picture overlay.");
+    Assert.ok(pipOverlay, "Should be able to find Picture-in-Picture overlay.");
 
     let rect = pipOverlay.getBoundingClientRect();
     return rect.height > 0 && rect.width > 0;
   });
   Assert.equal(
     showing,
     expected,
     "Video should be showing the expected state."
new file mode 100644
--- /dev/null
+++ b/toolkit/components/pictureinpicture/tests/test-page-with-iframe.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Picture-in-Picture tests</title>
+  <script type="text/javascript" src="click-event-helper.js"></script>
+</head>
+<style>
+  html, body {
+    height: 100vh;
+    width: 100vw;
+    margin: 0;
+    padding: 0;
+    overflow: hidden;
+  }
+  #iframe {
+    height: 100vh;
+    width: 100vw;
+    padding: 0;
+    margin: 0;
+    border: 0;
+  }
+</style>
+<body>
+  <iframe id="iframe"></iframe>
+</body>
+</html>
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -1106,25 +1106,26 @@ webrtc.video:
     record_in_processes:
       - 'content'
 
 webrtc.sdp:
   parser_diff:
     bug_numbers:
       - 1432955
       - 1529787
+      - 1588571
     description: >
       The number of differences between the C based sipcc SDP parser and
       the new rust based rsdparsa SDP parser keyed by predefined
       names of attributes and values that do not match between
       the sipcc parsing result and the rsdparsa parsing result or
       between the rsdparsa parsing result and the original sdp.
       This should help to improve the new rsdparsa to replace
       the sipcc parser.
-    expires: "73"
+    expires: "77"
     kind: uint
     keyed: true
     notification_emails:
       - nohlmeier@mozilla.com
     release_channel_collection: opt-in
     products:
       - 'firefox'
       - 'fennec'
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -15,16 +15,20 @@
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/dom/KeyboardEventBinding.h"
 #include "nsCommandParams.h"
 #include "nsContentUtils.h"
 #include "nsIContent.h"
 #include "nsPrintfCString.h"
 
+#if defined(XP_WIN)
+#include "npapi.h"
+#endif
+
 namespace mozilla {
 
 /******************************************************************************
  * Global helper methods
  ******************************************************************************/
 
 const char* ToChar(EventMessage aEventMessage) {
   switch (aEventMessage) {
@@ -392,16 +396,25 @@ bool WidgetEvent::CanBeSentToRemoteProce
     case eTouchStart:
     case eTouchMove:
     case eTouchEnd:
     case eTouchCancel:
     case eDragOver:
     case eDragExit:
     case eDrop:
       return true;
+#if defined(XP_WIN)
+    case ePluginInputEvent:
+      {
+        auto evt = static_cast<const NPEvent*>(AsPluginEvent()->mPluginEvent);
+        return evt && evt->event == WM_SETTINGCHANGE &&
+            (evt->wParam == SPI_SETWHEELSCROLLLINES ||
+             evt->wParam == SPI_SETWHEELSCROLLCHARS);
+      }
+#endif
     default:
       return false;
   }
 }
 
 bool WidgetEvent::WillBeSentToRemoteProcess() const {
   // This event won't be posted to remote process if it's already explicitly
   // stopped.
--- a/widget/windows/WinIMEHandler.cpp
+++ b/widget/windows/WinIMEHandler.cpp
@@ -468,16 +468,20 @@ void IMEHandler::SetInputContext(nsWindo
   sLastContextActionCause = aAction.mCause;
   // FYI: If there is no composition, this call will do nothing.
   NotifyIME(aWindow, IMENotification(REQUEST_TO_COMMIT_COMPOSITION));
 
   const InputContext& oldInputContext = aWindow->GetInputContext();
 
   // Assume that SetInputContext() is called only when aWindow has focus.
   sPluginHasFocus = (aInputContext.mIMEState.mEnabled == IMEState::PLUGIN);
+  if (sPluginHasFocus) {
+    // Update some cached system settings in the plugin.
+    aWindow->DispatchPluginSettingEvents();
+  }
 
   if (aAction.UserMightRequestOpenVKB()) {
     IMEHandler::MaybeShowOnScreenKeyboard();
   }
 
   bool enable = WinUtils::IsIMEEnabled(aInputContext);
   bool adjustOpenState = (enable && aInputContext.mIMEState.mOpen !=
                                         IMEState::DONT_CHANGE_OPEN_STATE);
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -4094,16 +4094,35 @@ bool nsWindow::DispatchPluginEvent(UINT 
   bool ret = nsWindowBase::DispatchPluginEvent(
       WinUtils::InitMSG(aMessage, aWParam, aLParam, mWnd));
   if (aDispatchPendingEvents && !Destroyed()) {
     DispatchPendingEvents();
   }
   return ret;
 }
 
+void nsWindow::DispatchPluginSettingEvents() {
+  // Update scroll wheel properties.
+  {
+    LRESULT lresult;
+    MSGResult msgResult(&lresult);
+    MSG msg =
+        WinUtils::InitMSG(WM_SETTINGCHANGE, SPI_SETWHEELSCROLLLINES, 0, mWnd);
+    ProcessMessageForPlugin(msg, msgResult);
+  }
+
+  {
+    LRESULT lresult;
+    MSGResult msgResult(&lresult);
+    MSG msg =
+        WinUtils::InitMSG(WM_SETTINGCHANGE, SPI_SETWHEELSCROLLCHARS, 0, mWnd);
+    ProcessMessageForPlugin(msg, msgResult);
+  }
+}
+
 bool nsWindow::TouchEventShouldStartDrag(EventMessage aEventMessage,
                                          LayoutDeviceIntPoint aEventPoint) {
   // Allow users to start dragging by double-tapping.
   if (aEventMessage == eMouseDoubleClick) {
     return true;
   }
 
   // In chrome UI, allow touchdownstartsdrag attributes
@@ -4802,17 +4821,17 @@ const char16_t* GetQuitType() {
   }
   return nullptr;
 }
 
 // The main windows message processing method for plugins.
 // The result means whether this method processed the native
 // event for plugin. If false, the native event should be
 // processed by the caller self.
-bool nsWindow::ProcessMessageForPlugin(const MSG& aMsg, MSGResult& aResult) {
+bool nsWindow::ProcessMessageForPlugin(MSG aMsg, MSGResult& aResult) {
   aResult.mResult = 0;
   aResult.mConsumed = true;
 
   bool eventDispatched = false;
   switch (aMsg.message) {
     case WM_CHAR:
     case WM_SYSCHAR:
       aResult.mResult = ProcessCharMessage(aMsg, &eventDispatched);
@@ -4823,16 +4842,37 @@ bool nsWindow::ProcessMessageForPlugin(c
       aResult.mResult = ProcessKeyUpMessage(aMsg, &eventDispatched);
       break;
 
     case WM_KEYDOWN:
     case WM_SYSKEYDOWN:
       aResult.mResult = ProcessKeyDownMessage(aMsg, &eventDispatched);
       break;
 
+    case WM_SETTINGCHANGE: {
+      // If there was a change in scroll wheel settings then shove the new
+      // value into the unused lParam so that the client doesn't need to ask
+      // for it.
+      if ((aMsg.wParam != SPI_SETWHEELSCROLLLINES) &&
+          (aMsg.wParam != SPI_SETWHEELSCROLLCHARS)) {
+        return false;
+      }
+      UINT wheelDelta = 0;
+      UINT getMsg = (aMsg.wParam == SPI_SETWHEELSCROLLLINES)
+                        ? SPI_GETWHEELSCROLLLINES
+                        : SPI_GETWHEELSCROLLCHARS;
+      if (NS_WARN_IF(!::SystemParametersInfo(getMsg, 0, &wheelDelta, 0))) {
+        // Use system default scroll amount, 3, when
+        // SPI_GETWHEELSCROLLLINES/CHARS isn't available.
+        wheelDelta = 3;
+      }
+      aMsg.lParam = wheelDelta;
+      break;
+    }
+
     case WM_DEADCHAR:
     case WM_SYSDEADCHAR:
 
     case WM_CUT:
     case WM_COPY:
     case WM_PASTE:
     case WM_CLEAR:
     case WM_UNDO:
--- a/widget/windows/nsWindow.h
+++ b/widget/windows/nsWindow.h
@@ -325,16 +325,17 @@ class nsWindow final : public nsWindowBa
   virtual void SetCandidateWindowForPlugin(
       const mozilla::widget::CandidateWindowPosition& aPosition) override;
   virtual void DefaultProcOfPluginEvent(
       const mozilla::WidgetPluginEvent& aEvent) override;
   virtual void EnableIMEForPlugin(bool aEnable) override;
   virtual nsresult OnWindowedPluginKeyEvent(
       const mozilla::NativeEventData& aKeyEventData,
       nsIKeyEventInPluginCallback* aCallback) override;
+  void DispatchPluginSettingEvents();
 
   void GetCompositorWidgetInitData(
       mozilla::widget::CompositorWidgetInitData* aInitData) override;
   bool IsTouchWindow() const { return mTouchWindow; }
   bool SynchronouslyRepaintOnResize() override;
 
  protected:
   virtual ~nsWindow();
@@ -406,17 +407,17 @@ class nsWindow final : public nsWindowBa
    */
   void DispatchFocusToTopLevelWindow(bool aIsActivate);
   bool DispatchStandardEvent(mozilla::EventMessage aMsg);
   void RelayMouseEvent(UINT aMsg, WPARAM wParam, LPARAM lParam);
   virtual bool ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam,
                               LRESULT* aRetValue);
   bool ExternalHandlerProcessMessage(UINT aMessage, WPARAM& aWParam,
                                      LPARAM& aLParam, MSGResult& aResult);
-  bool ProcessMessageForPlugin(const MSG& aMsg, MSGResult& aResult);
+  bool ProcessMessageForPlugin(MSG aMsg, MSGResult& aResult);
   LRESULT ProcessCharMessage(const MSG& aMsg, bool* aEventDispatched);
   LRESULT ProcessKeyUpMessage(const MSG& aMsg, bool* aEventDispatched);
   LRESULT ProcessKeyDownMessage(const MSG& aMsg, bool* aEventDispatched);
   static bool EventIsInsideWindow(nsWindow* aWindow);
   // Convert nsEventStatus value to a windows boolean
   static bool ConvertStatus(nsEventStatus aStatus);
   static void PostSleepWakeNotification(const bool aIsSleepMode);
   int32_t ClientMarginHitTestPoint(int32_t mx, int32_t my);