Merge mozilla-central to autoland. CLOSED TREE
authorCsoregi Natalia <ncsoregi@mozilla.com>
Fri, 17 May 2019 00:58:59 +0300
changeset 474227 67eb2cac32c2db9e105ef3b38480c8de56f0ddfe
parent 474226 5fae8054799fd916ceca727611c2f99afc83e93e (current diff)
parent 474181 786f094a30ae94722246f078561928990ab1e0b7 (diff)
child 474228 296ca33d9a0e4bd501df7db94fac65961c7acec5
push id113144
push usershindli@mozilla.com
push dateFri, 17 May 2019 16:44:55 +0000
treeherdermozilla-inbound@f4c4b796f845 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. CLOSED TREE
devtools/client/webreplay/mochitest/browser_dbg_rr_recovery-01.js
devtools/client/webreplay/mochitest/examples/doc_rr_recovery.html
third_party/rust/cranelift-codegen/meta-python/gen_legalizer.py
third_party/rust/cranelift-codegen/meta-python/gen_settings.py
third_party/rust/cranelift-codegen/meta-python/test_gen_legalizer.py
toolkit/recordreplay/ipc/ChildNavigation.cpp
--- a/.cargo/config.in
+++ b/.cargo/config.in
@@ -19,13 +19,13 @@ replace-with = "vendored-sources"
 
 [source."https://github.com/rust-lang-nursery/packed_simd"]
 git = "https://github.com/hsivonen/packed_simd"
 branch = "rust_1_32"
 replace-with = "vendored-sources"
 
 [source."https://github.com/CraneStation/Cranelift"]
 git = "https://github.com/CraneStation/Cranelift"
-rev = "538a0662bf90a1daa9921c10f34827ace134abf1"
+rev = "cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
 replace-with = "vendored-sources"
 
 [source.vendored-sources]
 directory = '@top_srcdir@/third_party/rust'
--- a/.flake8
+++ b/.flake8
@@ -20,17 +20,16 @@ exclude =
     layout/style,
     media/libdav1d/generate_source.py,
     moz.configure,
     netwerk/dns/prepare_tlds.py,
     netwerk/protocol/http/make_incoming_tables.py,
     python/devtools/migrate-l10n/migrate/main.py,
     python/l10n/fluent_migrations,
     python/mozbuild/dumbmake,
-    python/mozbuild/mozbuild,
     servo/components/style,
     testing/jsshell/benchmark.py,
     testing/marionette/mach_commands.py,
     testing/mozharness/docs,
     testing/mozharness/examples,
     testing/mozharness/external_tools,
     testing/mozharness/mach_commands.py,
     testing/mozharness/manifestparser,
@@ -64,16 +63,17 @@ exclude =
     ipc/chromium/src/third_party/,
     js/*.configure,
     gfx/angle/,
     gfx/harfbuzz,
     gfx/skia/,
     memory/moz.configure,
     mobile/android/*.configure,
     node_modules,
+    python/mozbuild/mozbuild/test/configure/data,
     security/nss/,
     testing/marionette/harness/marionette_harness/runner/mixins,
     testing/marionette/harness/marionette_harness/tests,
     testing/mochitest/pywebsocket,
     testing/mozharness/configs/test/test_malformed.py,
     tools/lint/test/files,
     tools/infer/test/*.configure,
     tools/crashreporter/*.configure,
@@ -87,11 +87,13 @@ ignore =
     F632, F633, F811, E117, W504, W605, W606,
     # These are intentionally disabled (not necessarily for good reason).
     #   F723: syntax error in type comment
     #       text contains quotes which breaks our custom JSON formatter
     F723, E121, E123, E126, E129, E133, E226, E241, E242, E402, E704, E741, W503,
 
 per-file-ignores =
     ipc/ipdl/*: F403, F405
+    # cpp_eclipse has a lot of multi-line embedded XML which exceeds line length
+    python/mozbuild/mozbuild/backend/cpp_eclipse.py: E501
     testing/firefox-ui/**/__init__.py: F401
     testing/marionette/**/__init__.py: F401
     testing/mozharness/configs/*: E124, E127, E128, E131, E231, E261, E265, E266, E501, W391
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -161,18 +161,18 @@ dependencies = [
  "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "baldrdash"
 version = "0.1.0"
 dependencies = [
  "bindgen 0.49.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
- "cranelift-wasm 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
+ "cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
+ "cranelift-wasm 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
  "env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "base64"
 version = "0.9.3"
@@ -582,67 +582,67 @@ version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cose 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "cranelift-bforest"
 version = "0.30.0"
-source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
-dependencies = [
- "cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
+source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
+dependencies = [
+ "cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
 ]
 
 [[package]]
 name = "cranelift-codegen"
 version = "0.30.0"
-source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
-dependencies = [
- "cranelift-bforest 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
- "cranelift-codegen-meta 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
- "cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
+source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
+dependencies = [
+ "cranelift-bforest 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
+ "cranelift-codegen-meta 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
+ "cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
  "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "cranelift-codegen-meta"
 version = "0.30.0"
-source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
-dependencies = [
- "cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
+source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
+dependencies = [
+ "cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
 ]
 
 [[package]]
 name = "cranelift-entity"
 version = "0.30.0"
-source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
+source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
 
 [[package]]
 name = "cranelift-frontend"
 version = "0.30.0"
-source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
-dependencies = [
- "cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
+source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
+dependencies = [
+ "cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "cranelift-wasm"
 version = "0.30.0"
-source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
+source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
 dependencies = [
  "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
- "cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
- "cranelift-frontend 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
+ "cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
+ "cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
+ "cranelift-frontend 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
  "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "wasmparser 0.29.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "crc"
@@ -3654,22 +3654,22 @@ dependencies = [
 "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
 "checksum cookie 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1465f8134efa296b4c19db34d909637cb2bf0f7aaf21299e23e18fa29ac557cf"
 "checksum core-foundation 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4e2640d6d0bf22e82bed1b73c6aef8d5dd31e5abe6666c57e6d45e2649f4f887"
 "checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b"
 "checksum core-graphics 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)" = "62ceafe1622ffc9a332199096841d0ff9912ec8cf8f9cde01e254a7d5217cd10"
 "checksum core-text 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f3f46450d6f2397261af420b4ccce23807add2e45fa206410a03d66fb7f050ae"
 "checksum cose 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "72fa26cb151d3ae4b70f63d67d0fed57ce04220feafafbae7f503bef7aae590d"
 "checksum cose-c 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "49726015ab0ca765144fcca61e4a7a543a16b795a777fa53f554da2fffff9a94"
-"checksum cranelift-bforest 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
-"checksum cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
-"checksum cranelift-codegen-meta 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
-"checksum cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
-"checksum cranelift-frontend 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
-"checksum cranelift-wasm 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
+"checksum cranelift-bforest 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
+"checksum cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
+"checksum cranelift-codegen-meta 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
+"checksum cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
+"checksum cranelift-frontend 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
+"checksum cranelift-wasm 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
 "checksum crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd5d02c0aac6bd68393ed69e00bbc2457f3e89075c6349db7189618dc4ddc1d7"
 "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3"
 "checksum crossbeam-deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fe8153ef04a7594ded05b427ffad46ddeaf22e63fd48d42b3e1e3bb4db07cae7"
 "checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150"
 "checksum crossbeam-epoch 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2af0e75710d6181e234c8ecc79f14a97907850a541b13b0be1dd10992f2e4620"
 "checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9"
 "checksum crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d636a8b3bcc1b409d7ffd3facef8f21dcb4009626adbd0c5e6c4305c07253c7b"
 "checksum crossbeam-utils 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "41ee4864f4797060e52044376f7d107429ce1fb43460021b126424b7180ee21a"
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -59,13 +59,13 @@ codegen-units = 1
 [patch.crates-io]
 libudev-sys = { path = "dom/webauthn/libudev-sys" }
 serde_derive = { git = "https://github.com/servo/serde", branch = "deserialize_from_enums10" }
 winapi = { git = "https://github.com/froydnj/winapi-rs", branch = "aarch64" }
 packed_simd = { git = "https://github.com/hsivonen/packed_simd", branch = "rust_1_32" }
 
 [patch.crates-io.cranelift-codegen]
 git = "https://github.com/CraneStation/Cranelift"
-rev = "538a0662bf90a1daa9921c10f34827ace134abf1"
+rev = "cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
 
 [patch.crates-io.cranelift-wasm]
 git = "https://github.com/CraneStation/Cranelift"
-rev = "538a0662bf90a1daa9921c10f34827ace134abf1"
+rev = "cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
--- a/browser/components/extensions/parent/ext-tabs.js
+++ b/browser/components/extensions/parent/ext-tabs.js
@@ -1095,17 +1095,17 @@ this.tabs = class extends ExtensionAPI {
         printPreview() {
           let activeTab = getTabOrActive(null);
           let {
             PrintUtils,
             PrintPreviewListener,
           } = activeTab.ownerGlobal;
 
           return new Promise((resolve, reject) => {
-            let ppBrowser = PrintUtils._shouldSimplify ?
+            let ppBrowser = PrintUtils.shouldSimplify ?
               PrintPreviewListener.getSimplifiedPrintPreviewBrowser() :
               PrintPreviewListener.getPrintPreviewBrowser();
 
             let mm = ppBrowser.messageManager;
 
             let onEntered = (message) => {
               mm.removeMessageListener("Printing:Preview:Entered", onEntered);
               if (message.data.failed) {
--- a/browser/components/extensions/test/browser/browser_ext_tabs_printPreview.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_printPreview.js
@@ -16,17 +16,17 @@ add_task(async function testPrintPreview
       browser.test.notifyPass("tabs.printPreview");
     },
   });
 
   await extension.startup();
   await extension.awaitFinish("tabs.printPreview");
   await extension.unload();
 
-  let ppTab = PrintUtils._shouldSimplify ?
+  let ppTab = PrintUtils.shouldSimplify ?
       PrintPreviewListener._simplifiedPrintPreviewTab :
       PrintPreviewListener._printPreviewTab;
 
   let ppToolbar = document.getElementById("print-preview-toolbar");
 
   is(window.gInPrintPreviewMode, true, "window in print preview mode");
 
   isnot(ppTab, null, "print preview tab created");
--- a/devtools/client/debugger/panel.js
+++ b/devtools/client/debugger/panel.js
@@ -160,16 +160,20 @@ DebuggerPanel.prototype = {
     return this._actions.selectSourceURL(cx, url, { line, column });
   },
 
   selectSource(sourceId, line, column) {
     const cx = this._selectors.getContext(this._getState());
     return this._actions.selectSource(cx, sourceId, { line, column });
   },
 
+  canLoadSource(sourceId) {
+    return this._selectors.canLoadSource(this._getState(), sourceId);
+  },
+
   getSourceByActorId(sourceId) {
     return this._selectors.getSourceByActorId(this._getState(), sourceId);
   },
 
   getSourceByURL(sourceURL) {
     return this._selectors.getSourceByURL(this._getState(), sourceURL);
   },
 
--- a/devtools/client/debugger/src/actions/pause/commands.js
+++ b/devtools/client/debugger/src/actions/pause/commands.js
@@ -137,57 +137,29 @@ export function rewind(cx: ThreadContext
   return ({ dispatch, getState }: ThunkArgs) => {
     if (cx.isPaused) {
       return dispatch(command(cx, "rewind"));
     }
   };
 }
 
 /**
- * reverseStepIn
- * @memberof actions/pause
- * @static
- * @returns {Function} {@link command}
- */
-export function reverseStepIn(cx: ThreadContext) {
-  return ({ dispatch, getState }: ThunkArgs) => {
-    if (cx.isPaused) {
-      return dispatch(command(cx, "reverseStepIn"));
-    }
-  };
-}
-
-/**
  * reverseStepOver
  * @memberof actions/pause
  * @static
  * @returns {Function} {@link command}
  */
 export function reverseStepOver(cx: ThreadContext) {
   return ({ dispatch, getState }: ThunkArgs) => {
     if (cx.isPaused) {
       return dispatch(astCommand(cx, "reverseStepOver"));
     }
   };
 }
 
-/**
- * reverseStepOut
- * @memberof actions/pause
- * @static
- * @returns {Function} {@link command}
- */
-export function reverseStepOut(cx: ThreadContext) {
-  return ({ dispatch, getState }: ThunkArgs) => {
-    if (cx.isPaused) {
-      return dispatch(command(cx, "reverseStepOut"));
-    }
-  };
-}
-
 /*
  * Checks for await or yield calls on the paused line
  * This avoids potentially expensive parser calls when we are likely
  * not at an async expression.
  */
 function hasAwait(content: AsyncValue<SourceContent> | null, pauseLocation) {
   const { line, column } = pauseLocation;
   if (!content || !isFulfilled(content) || content.value.type !== "text") {
--- a/devtools/client/debugger/src/actions/pause/index.js
+++ b/devtools/client/debugger/src/actions/pause/index.js
@@ -11,19 +11,17 @@
 
 export {
   selectThread,
   stepIn,
   stepOver,
   stepOut,
   resume,
   rewind,
-  reverseStepIn,
-  reverseStepOver,
-  reverseStepOut
+  reverseStepOver
 } from "./commands";
 export { fetchScopes } from "./fetchScopes";
 export { paused } from "./paused";
 export { resumed } from "./resumed";
 export { continueToHere } from "./continueToHere";
 export { breakOnNext } from "./breakOnNext";
 export { mapFrames } from "./mapFrames";
 export { pauseOnExceptions } from "./pauseOnExceptions";
--- a/devtools/client/debugger/src/client/firefox/commands.js
+++ b/devtools/client/debugger/src/client/firefox/commands.js
@@ -142,28 +142,20 @@ function stepOver(thread: string): Promi
 function stepOut(thread: string): Promise<*> {
   return lookupThreadClient(thread).stepOut();
 }
 
 function rewind(thread: string): Promise<*> {
   return lookupThreadClient(thread).rewind();
 }
 
-function reverseStepIn(thread: string): Promise<*> {
-  return lookupThreadClient(thread).reverseStepIn();
-}
-
 function reverseStepOver(thread: string): Promise<*> {
   return lookupThreadClient(thread).reverseStepOver();
 }
 
-function reverseStepOut(thread: string): Promise<*> {
-  return lookupThreadClient(thread).reverseStepOut();
-}
-
 function breakOnNext(thread: string): Promise<*> {
   return lookupThreadClient(thread).breakOnNext();
 }
 
 async function sourceContents({
   actor,
   thread
 }: SourceActor): Promise<{| source: any, contentType: ?string |}> {
@@ -502,18 +494,16 @@ const clientCommands = {
   releaseActor,
   interrupt,
   pauseGrip,
   resume,
   stepIn,
   stepOut,
   stepOver,
   rewind,
-  reverseStepIn,
-  reverseStepOut,
   reverseStepOver,
   breakOnNext,
   sourceContents,
   getSourceForActor,
   getBreakpointPositions,
   getBreakableLines,
   hasBreakpoint,
   setBreakpoint,
--- a/devtools/client/debugger/src/client/firefox/types.js
+++ b/devtools/client/debugger/src/client/firefox/types.js
@@ -342,19 +342,17 @@ export type ObjectClient = {
  * @static
  */
 export type ThreadClient = {
   resume: Function => Promise<*>,
   stepIn: Function => Promise<*>,
   stepOver: Function => Promise<*>,
   stepOut: Function => Promise<*>,
   rewind: Function => Promise<*>,
-  reverseStepIn: Function => Promise<*>,
   reverseStepOver: Function => Promise<*>,
-  reverseStepOut: Function => Promise<*>,
   breakOnNext: () => Promise<*>,
   // FIXME: unclear if SourceId or ActorId here
   source: ({ actor: SourceId }) => SourceClient,
   pauseGrip: (Grip | Function) => ObjectClient,
   pauseOnExceptions: (boolean, boolean) => Promise<*>,
   setBreakpoint: (BreakpointLocation, BreakpointOptions) => Promise<*>,
   removeBreakpoint: PendingLocation => Promise<*>,
   setXHRBreakpoint: (path: string, method: string) => Promise<boolean>,
--- a/devtools/client/debugger/src/components/SecondaryPanes/CommandBar.js
+++ b/devtools/client/debugger/src/components/SecondaryPanes/CommandBar.js
@@ -82,18 +82,16 @@ type Props = {
   canRewind: boolean,
   skipPausing: boolean,
   resume: typeof actions.resume,
   stepIn: typeof actions.stepIn,
   stepOut: typeof actions.stepOut,
   stepOver: typeof actions.stepOver,
   breakOnNext: typeof actions.breakOnNext,
   rewind: typeof actions.rewind,
-  reverseStepIn: typeof actions.reverseStepIn,
-  reverseStepOut: typeof actions.reverseStepOut,
   reverseStepOver: typeof actions.reverseStepOver,
   pauseOnExceptions: typeof actions.pauseOnExceptions,
   toggleSkipPausing: typeof actions.toggleSkipPausing
 };
 
 class CommandBar extends Component<Props> {
   componentWillUnmount() {
     const shortcuts = this.context.shortcuts;
@@ -326,15 +324,13 @@ export default connect(
   mapStateToProps,
   {
     resume: actions.resume,
     stepIn: actions.stepIn,
     stepOut: actions.stepOut,
     stepOver: actions.stepOver,
     breakOnNext: actions.breakOnNext,
     rewind: actions.rewind,
-    reverseStepIn: actions.reverseStepIn,
-    reverseStepOut: actions.reverseStepOut,
     reverseStepOver: actions.reverseStepOver,
     pauseOnExceptions: actions.pauseOnExceptions,
     toggleSkipPausing: actions.toggleSkipPausing
   }
 )(CommandBar);
--- a/devtools/client/debugger/src/components/shared/AccessibleImage.css
+++ b/devtools/client/debugger/src/components/shared/AccessibleImage.css
@@ -149,26 +149,16 @@ html[dir="rtl"] .img.more-tabs {
 .img.regex-match {
   mask-image: url(resource://devtools/client/debugger/images/regex-match.svg);
 }
 
 .img.resume {
   mask-image: url(resource://devtools/client/debugger/images/resume.svg);
 }
 
-.img.reverseStepIn {
-  mask-image: url(resource://devtools/client/debugger/images/stepIn.svg);
-  transform: scaleX(-1);
-}
-
-.img.reverseStepOut {
-  mask-image: url(resource://devtools/client/debugger/images/stepOut.svg);
-  transform: scaleX(-1);
-}
-
 .img.reverseStepOver {
   mask-image: url(resource://devtools/client/debugger/images/stepOver.svg);
   transform: scaleX(-1);
 }
 
 .img.rewind {
   mask-image: url(resource://devtools/client/debugger/images/rewind.svg);
 }
--- a/devtools/client/debugger/src/reducers/pause.js
+++ b/devtools/client/debugger/src/reducers/pause.js
@@ -32,19 +32,17 @@ import type {
 
 export type Command =
   | null
   | "stepOver"
   | "stepIn"
   | "stepOut"
   | "resume"
   | "rewind"
-  | "reverseStepOver"
-  | "reverseStepIn"
-  | "reverseStepOut";
+  | "reverseStepOver";
 
 // Pause state associated with an individual thread.
 type ThreadPauseState = {
   why: ?Why,
   isWaitingOnBreak: boolean,
   frames: ?(any[]),
   frameScopes: {
     generated: {
--- a/devtools/client/debugger/src/reducers/sources.js
+++ b/devtools/client/debugger/src/reducers/sources.js
@@ -883,16 +883,35 @@ export function getSourceActorsForSource
   const actors = state.sources.actors[id];
   if (!actors) {
     return [];
   }
 
   return getSourceActors(state, actors);
 }
 
+export function canLoadSource(
+  state: OuterState & SourceActorOuterState,
+  sourceId: string)
+{
+  // Return false if we know that loadSourceText() will fail if called on this
+  // source. This is used to avoid viewing such sources in the debugger.
+  const source = getSource(state, sourceId);
+  if (!source) {
+    return false;
+  }
+
+  if (isOriginalSource(source)) {
+    return true;
+  }
+
+  const actors = getSourceActorsForSource(state, sourceId);
+  return actors.length != 0;
+}
+
 export function getBreakpointPositions(
   state: OuterState
 ): BreakpointPositionsMap {
   return state.sources.breakpointPositions;
 }
 
 export function getBreakpointPositionsForSource(
   state: OuterState,
--- a/devtools/client/shared/view-source.js
+++ b/devtools/client/shared/view-source.js
@@ -58,17 +58,17 @@ exports.viewSourceInDebugger = async fun
   sourceLine,
   sourceColumn,
   sourceId,
   reason = "unknown"
 ) {
   const dbg = await toolbox.loadTool("jsdebugger");
   const source =
     sourceId ? dbg.getSourceByActorId(sourceId) : dbg.getSourceByURL(sourceURL);
-  if (source) {
+  if (source && dbg.canLoadSource(source.id)) {
     await toolbox.selectTool("jsdebugger", reason);
     try {
       await dbg.selectSource(source.id, sourceLine, sourceColumn);
     } catch (err) {
       console.error("Failed to view source in debugger", err);
       return false;
     }
     return true;
--- a/devtools/client/webconsole/components/ConsoleOutput.js
+++ b/devtools/client/webconsole/components/ConsoleOutput.js
@@ -17,38 +17,43 @@ const {
   getVisibleMessages,
   getPausedExecutionPoint,
   getAllRepeatById,
   getAllWarningGroupsById,
   isMessageInWarningGroup,
 } = require("devtools/client/webconsole/selectors/messages");
 
 loader.lazyRequireGetter(this, "PropTypes", "devtools/client/shared/vendor/react-prop-types");
-loader.lazyRequireGetter(this, "sortBy", "devtools/client/shared/vendor/lodash", true);
 loader.lazyRequireGetter(this, "MessageContainer", "devtools/client/webconsole/components/MessageContainer", true);
+ChromeUtils.defineModuleGetter(this, "pointPrecedes", "resource://devtools/shared/execution-point-utils.js");
 
 const {
   MESSAGE_TYPE,
 } = require("devtools/client/webconsole/constants");
 const {
   getInitialMessageCountForViewport,
 } = require("devtools/client/webconsole/utils/messages.js");
 
 function getClosestMessage(visibleMessages, messages, executionPoint) {
   if (!executionPoint || !visibleMessages) {
     return null;
   }
 
-  const { progress } = executionPoint;
-  const getProgress = m => m && m.executionPoint && m.executionPoint.progress;
-
-  return sortBy(
-    visibleMessages.map(id => messages.get(id)),
-    m => Math.abs(progress - getProgress(m))
-  )[0];
+  const messageList = visibleMessages.map(id => messages.get(id));
+  const precedingMessages = messageList.filter(m => {
+    return m && m.executionPoint && pointPrecedes(m.executionPoint, executionPoint);
+  });
+  if (precedingMessages.length != 0) {
+    return precedingMessages.sort((a, b) => {
+      return pointPrecedes(a.executionPoint, b.executionPoint);
+    })[0];
+  }
+  return messageList.filter(m => m && m.executionPoint).sort((a, b) => {
+    return pointPrecedes(b.executionPoint, a.executionPoint);
+  })[0];
 }
 
 class ConsoleOutput extends Component {
   static get propTypes() {
     return {
       initialized: PropTypes.bool.isRequired,
       messages: PropTypes.object.isRequired,
       messagesUi: PropTypes.array.isRequired,
--- a/devtools/client/webconsole/components/Message.js
+++ b/devtools/client/webconsole/components/Message.js
@@ -15,16 +15,17 @@ const { MESSAGE_SOURCE, MESSAGE_TYPE } =
 const { MessageIndent } = require("devtools/client/webconsole/components/MessageIndent");
 const MessageIcon = require("devtools/client/webconsole/components/MessageIcon");
 const FrameView = createFactory(require("devtools/client/shared/components/Frame"));
 
 loader.lazyRequireGetter(this, "CollapseButton", "devtools/client/webconsole/components/CollapseButton");
 loader.lazyRequireGetter(this, "MessageRepeat", "devtools/client/webconsole/components/MessageRepeat");
 loader.lazyRequireGetter(this, "PropTypes", "devtools/client/shared/vendor/react-prop-types");
 loader.lazyRequireGetter(this, "SmartTrace", "devtools/client/shared/components/SmartTrace");
+ChromeUtils.defineModuleGetter(this, "pointPrecedes", "resource://devtools/shared/execution-point-utils.js");
 
 class Message extends Component {
   static get propTypes() {
     return {
       open: PropTypes.bool,
       collapsible: PropTypes.bool,
       collapseTitle: PropTypes.string,
       onToggle: PropTypes.func,
@@ -35,22 +36,18 @@ class Message extends Component {
       inWarningGroup: PropTypes.bool,
       topLevelClasses: PropTypes.array.isRequired,
       messageBody: PropTypes.any.isRequired,
       repeat: PropTypes.any,
       frame: PropTypes.any,
       attachment: PropTypes.any,
       stacktrace: PropTypes.any,
       messageId: PropTypes.string,
-      executionPoint: PropTypes.shape({
-        progress: PropTypes.number,
-      }),
-      pausedExecutionPoint: PropTypes.shape({
-        progress: PropTypes.number,
-      }),
+      executionPoint: PropTypes.object,
+      pausedExecutionPoint: PropTypes.object,
       scrollToMessage: PropTypes.bool,
       exceptionDocURL: PropTypes.string,
       request: PropTypes.object,
       dispatch: PropTypes.func,
       timeStamp: PropTypes.number,
       timestampsVisible: PropTypes.bool.isRequired,
       serviceContainer: PropTypes.shape({
         emitNewMessage: PropTypes.func.isRequired,
@@ -190,17 +187,17 @@ class Message extends Component {
       topLevelClasses.push("open");
     }
 
     if (isPaused) {
       topLevelClasses.push("paused");
 
       if (pausedExecutionPoint
         && executionPoint
-        && pausedExecutionPoint.progress < executionPoint.progress) {
+        && !pointPrecedes(executionPoint, pausedExecutionPoint)) {
         topLevelClasses.push("paused-before");
       }
     }
 
     let timestampEl;
     if (timestampsVisible === true) {
       timestampEl = dom.span({
         className: "timestamp devtools-monospace",
--- a/devtools/client/webconsole/reducers/messages.js
+++ b/devtools/client/webconsole/reducers/messages.js
@@ -20,33 +20,32 @@ const {
 
 loader.lazyRequireGetter(this, "getGripPreviewItems", "devtools/client/shared/components/reps/reps", true);
 loader.lazyRequireGetter(this, "getUnicodeUrlPath", "devtools/client/shared/unicode-url", true);
 loader.lazyRequireGetter(this, "getSourceNames", "devtools/client/shared/source-utils", true);
 loader.lazyRequireGetter(this, "createWarningGroupMessage", "devtools/client/webconsole/utils/messages", true);
 loader.lazyRequireGetter(this, "isWarningGroup", "devtools/client/webconsole/utils/messages", true);
 loader.lazyRequireGetter(this, "getWarningGroupType", "devtools/client/webconsole/utils/messages", true);
 loader.lazyRequireGetter(this, "getParentWarningGroupMessageId", "devtools/client/webconsole/utils/messages", true);
+ChromeUtils.defineModuleGetter(this, "pointPrecedes", "resource://devtools/shared/execution-point-utils.js");
 
 const {
   UPDATE_REQUEST,
 } = require("devtools/client/netmonitor/src/constants");
 
 const {
   processNetworkUpdates,
 } = require("devtools/client/netmonitor/src/utils/request-utils");
 
 const MessageState = overrides => Object.freeze(Object.assign({
   // List of all the messages added to the console.
   messagesById: new Map(),
   // List of additional data associated with messages (populated async or on-demand at a
   // later time after the message is received).
   messagesPayloadById: new Map(),
-  // When recording or replaying, all progress values in messagesById.
-  replayProgressMessages: new Set(),
   // Array of the visible messages.
   visibleMessages: [],
   // Object for the filtered messages.
   filteredMessagesCount: getDefaultFiltersCounter(),
   // List of the message ids which are opened.
   messagesUiById: [],
   // Map of the form {messageId : tableData}, which represent the data passed
   // as an argument in console.table calls.
@@ -65,35 +64,38 @@ const MessageState = overrides => Object
   removedActors: [],
   // Map of the form {messageId : numberOfRepeat}
   repeatById: {},
   // Map of the form {messageId : networkInformation}
   // `networkInformation` holds request, response, totalTime, ...
   networkMessagesUpdateById: {},
   // Set of logpoint IDs that have been removed
   removedLogpointIds: new Set(),
+  // Any execution point we are currently paused at, when replaying.
   pausedExecutionPoint: null,
+  // Whether any messages with execution points have been seen.
+  hasExecutionPoints: false,
 }, overrides));
 
 function cloneState(state) {
   return {
     messagesById: new Map(state.messagesById),
-    replayProgressMessages: new Set(state.replayProgressMessages),
     visibleMessages: [...state.visibleMessages],
     filteredMessagesCount: {...state.filteredMessagesCount},
     messagesUiById: [...state.messagesUiById],
     messagesPayloadById: new Map(state.messagesPayloadById),
     messagesTableDataById: new Map(state.messagesTableDataById),
     groupsById: new Map(state.groupsById),
     currentGroup: state.currentGroup,
     removedActors: [...state.removedActors],
     repeatById: {...state.repeatById},
     networkMessagesUpdateById: {...state.networkMessagesUpdateById},
     removedLogpointIds: new Set(state.removedLogpointIds),
     pausedExecutionPoint: state.pausedExecutionPoint,
+    hasExecutionPoints: state.hasExecutionPoints,
     warningGroupsById: new Map(state.warningGroupsById),
   };
 }
 
 /**
  * Add a console message to the state.
  *
  * @param {ConsoleMessage} newMessage: The message to add to the state.
@@ -101,38 +103,26 @@ function cloneState(state) {
  * @param {FiltersState} filtersState: The filters state.
  * @param {PrefsState} prefsState: The preferences state.
  * @param {UiState} uiState: The ui state.
  * @returns {MessageState} a new messages state.
  */
 function addMessage(newMessage, state, filtersState, prefsState, uiState) {
   const {
     messagesById,
-    replayProgressMessages,
     groupsById,
     currentGroup,
     repeatById,
   } = state;
 
   if (newMessage.type === constants.MESSAGE_TYPE.NULL_MESSAGE) {
     // When the message has a NULL type, we don't add it.
     return state;
   }
 
-  if (newMessage.executionPoint && !newMessage.logpointId) {
-    // When replaying old behaviors in a tab, we might see the same messages
-    // multiple times. Ignore duplicate messages with the same progress values.
-    // We don't need to do this for logpoint messages, which will only arrive once.
-    const progress = newMessage.executionPoint.progress;
-    if (replayProgressMessages.has(progress)) {
-      return state;
-    }
-    state.replayProgressMessages.add(progress);
-  }
-
   // After messages with a given logpoint ID have been removed, ignore all
   // future messages with that ID.
   if (newMessage.logpointId &&
       state.removedLogpointIds &&
       state.removedLogpointIds.has(newMessage.logpointId)) {
     return state;
   }
 
@@ -158,16 +148,20 @@ function addMessage(newMessage, state, f
   const parentGroups = getParentGroups(currentGroup, groupsById);
   if (!isWarningGroup(newMessage)) {
     newMessage.groupId = currentGroup;
     newMessage.indent = parentGroups.length;
   }
 
   ensureExecutionPoint(state, newMessage);
 
+  if (newMessage.executionPoint) {
+    state.hasExecutionPoints = true;
+  }
+
   // Check if the current message could be placed in a Warning Group.
   // This needs to be done before setting the new message in messagesById so we have a
   // proper message.
   const warningGroupType = getWarningGroupType(newMessage);
 
   // If the preference for warning grouping is true, and the new message could be in a
   // warning group.
   if (prefsState.groupWarnings && warningGroupType !== null) {
@@ -1186,17 +1180,17 @@ function getDefaultFiltersCounter() {
 // if other messages with real execution points appear later.
 function ensureExecutionPoint(state, newMessage) {
   if (newMessage.executionPoint) {
     return;
   }
 
   // Add a lastExecutionPoint property which will place this message immediately
   // after the last visible one when sorting.
-  let point = { progress: 0 }, messageCount = 1;
+  let point = { checkpoint: 0, progress: 0 }, messageCount = 1;
   if (state.visibleMessages.length) {
     const lastId = state.visibleMessages[state.visibleMessages.length - 1];
     const lastMessage = state.messagesById.get(lastId);
     if (lastMessage.executionPoint) {
       point = lastMessage.executionPoint;
     } else {
       point = lastMessage.lastExecutionPoint.point;
       messageCount = lastMessage.lastExecutionPoint.messageCount + 1;
@@ -1215,42 +1209,29 @@ function messageCountSinceLastExecutionP
   return message.lastExecutionPoint ? message.lastExecutionPoint.messageCount : 0;
 }
 
 function maybeSortVisibleMessages(state) {
   // When using log points while replaying, messages can be added out of order
   // with respect to how they originally executed. Use the execution point
   // information in the messages to sort visible messages according to how
   // they originally executed. This isn't necessary if we haven't seen any
-  // messages with progress counters, as either we aren't replaying or haven't
+  // messages with execution points, as either we aren't replaying or haven't
   // seen any messages yet.
-  if (state.replayProgressMessages.size) {
+  if (state.hasExecutionPoints) {
     state.visibleMessages.sort((a, b) => {
       const pointA = messageExecutionPoint(state, a);
       const pointB = messageExecutionPoint(state, b);
-      if (pointA.progress != pointB.progress) {
-        return pointA.progress > pointB.progress;
-      }
-      // Execution points without a progress counter predate execution points
-      // with one, i.e. a console.log() call (which bumps the progress value)
-      // predates the code that runs afterward.
-      if ("frameIndex" in pointA != "frameIndex" in pointB) {
-        return "frameIndex" in pointA;
+      if (pointPrecedes(pointB, pointA)) {
+        return true;
+      } else if (pointPrecedes(pointA, pointB)) {
+        return false;
       }
-      // Deeper frames predate shallower frames, if the progress counter is the
-      // same. We bump the progress counter when pushing frames, but not when
-      // popping them.
-      if (pointA.frameIndex != pointB.frameIndex) {
-        return pointA.frameIndex < pointB.frameIndex;
-      }
-      // Earlier script locations predate later script locations.
-      if (pointA.offset != pointB.offset) {
-        return pointA.offset > pointB.offset;
-      }
-      // When messages don't have their own execution point, they can still be
+
+      // When messages have the same execution point, they can still be
       // distinguished by the number of messages since the last one which did
       // have an execution point.
       const countA = messageCountSinceLastExecutionPoint(state, a);
       const countB = messageCountSinceLastExecutionPoint(state, b);
       return countA > countB;
     });
   }
 }
--- a/devtools/client/webreplay/mochitest/browser.ini
+++ b/devtools/client/webreplay/mochitest/browser.ini
@@ -11,34 +11,31 @@ support-files =
   !/devtools/client/shared/test/test-actor-registry.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/debugger/test/mochitest/helpers.js
   !/devtools/client/debugger/test/mochitest/helpers/context.js
   !/devtools/client/inspector/test/shared-head.js
   examples/doc_rr_basic.html
   examples/doc_rr_continuous.html
   examples/doc_rr_logs.html
-  examples/doc_rr_recovery.html
   examples/doc_rr_error.html
   examples/doc_inspector_basic.html
   examples/doc_inspector_styles.html
   examples/styles.css
 
 [browser_dbg_rr_breakpoints-01.js]
 [browser_dbg_rr_breakpoints-02.js]
 [browser_dbg_rr_breakpoints-03.js]
 [browser_dbg_rr_breakpoints-04.js]
 [browser_dbg_rr_breakpoints-05.js]
 [browser_dbg_rr_record.js]
 [browser_dbg_rr_stepping-01.js]
 [browser_dbg_rr_stepping-02.js]
 [browser_dbg_rr_stepping-03.js]
 [browser_dbg_rr_stepping-04.js]
-[browser_dbg_rr_recovery-01.js]
-skip-if = true # See bug 1481009
 [browser_dbg_rr_replay-01.js]
 [browser_dbg_rr_replay-02.js]
 [browser_dbg_rr_replay-03.js]
 [browser_dbg_rr_console_warp-01.js]
 skip-if = true
 [browser_dbg_rr_console_warp-02.js]
 skip-if = true
 [browser_dbg_rr_logpoint-01.js]
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_breakpoints-03.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_breakpoints-03.js
@@ -4,25 +4,22 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-undef */
 
 "use strict";
 
 // Test some issues when stepping around after hitting a breakpoint while recording.
 add_task(async function() {
   const dbg = await attachRecordingDebugger("doc_rr_continuous.html");
-  const {threadClient, tab, toolbox, target} = dbg;
+  const {threadClient, tab, toolbox} = dbg;
 
   await threadClient.interrupt();
   const bp1 = await setBreakpoint(threadClient, "doc_rr_continuous.html", 19);
   await resumeToLine(threadClient, 19);
   await reverseStepOverToLine(threadClient, 18);
-  await checkEvaluateInTopFrame(target,
-    "SpecialPowers.Cu.recordReplayDirective(/* AlwaysTakeTemporarySnapshots */ 3)",
-    undefined);
   await stepInToLine(threadClient, 22);
   const bp2 = await setBreakpoint(threadClient, "doc_rr_continuous.html", 24);
   await resumeToLine(threadClient, 24);
   const bp3 = await setBreakpoint(threadClient, "doc_rr_continuous.html", 22);
   await rewindToLine(threadClient, 22);
 
   await threadClient.removeBreakpoint(bp1);
   await threadClient.removeBreakpoint(bp2);
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_console_warp-02.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_console_warp-02.js
@@ -13,17 +13,17 @@ add_task(async function() {
     { waitForRecording: true }
   );
 
   const {tab, toolbox, threadClient} = dbg;
   const console = await getDebuggerSplitConsole(dbg);
   const hud = console.hud;
 
   let message = await warpToMessage(hud, dbg, "number: 1");
-  ok(!message.classList.contains("paused-before"), "paused before message is not shown");
+  ok(message.classList.contains("paused-before"), "paused before message is shown");
 
   await stepOverToLine(threadClient, 18);
   await reverseStepOverToLine(threadClient, 17);
 
   message = findMessage(hud, "number: 1");
   ok(message.classList.contains("paused-before"), "paused before message is shown");
 
   await toolbox.destroy();
deleted file mode 100644
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_recovery-01.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-/* eslint-disable no-undef */
-
-"use strict";
-
-// Test basic recovery of crashed child processes in web replay.
-add_task(async function() {
-  const tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
-  gBrowser.selectedTab = tab;
-  openTrustedLinkIn(EXAMPLE_URL + "doc_rr_recovery.html", "current");
-  await once(Services.ppmm, "RecordingFinished");
-
-  const { target, toolbox } = await attachDebugger(tab), client = toolbox.threadClient;
-  await client.interrupt();
-  await setBreakpoint(client, "doc_rr_recovery.html", 21);
-  await rewindToLine(client, 21);
-  await checkEvaluateInTopFrame(client,
-    "SpecialPowers.Cu.recordReplayDirective(/* CrashSoon */ 1)",
-    undefined);
-  await stepOverToLine(client, 22);
-  await stepOverToLine(client, 23);
-  await checkEvaluateInTopFrame(target,
-    "SpecialPowers.Cu.recordReplayDirective(/* CrashSoon */ 1); " +
-    "SpecialPowers.Cu.recordReplayDirective(/* MaybeCrash */ 2)",
-    undefined);
-  await toolbox.destroy();
-  await gBrowser.removeTab(tab);
-});
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_replay-02.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_replay-02.js
@@ -40,19 +40,24 @@ add_task(async function() {
   gBrowser.selectedTab = replayingTab;
   await once(Services.ppmm, "HitRecordingEndpoint");
 
   const rplyTab = await attachDebugger(replayingTab);
   toolbox = rplyTab.toolbox;
   target = rplyTab.target;
   client = toolbox.threadClient;
   await client.interrupt();
+
+  // The recording does not actually end at the point where we saved it, but
+  // will do at the next checkpoint. Rewind to the point we are interested in.
+  bp = await setBreakpoint(client, "doc_rr_continuous.html", 14);
+  await rewindToLine(client, 14);
+
   await checkEvaluateInTopFrame(target, "number", lastNumberValue);
   await reverseStepOverToLine(client, 13);
-  bp = await setBreakpoint(client, "doc_rr_continuous.html", 14);
   await rewindToLine(client, 14);
   await checkEvaluateInTopFrame(target, "number", lastNumberValue - 1);
   await resumeToLine(client, 14);
   await checkEvaluateInTopFrame(target, "number", lastNumberValue);
 
   await client.removeBreakpoint(bp);
   await toolbox.destroy();
   await gBrowser.removeTab(replayingTab);
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_stepping-02.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_stepping-02.js
@@ -15,17 +15,22 @@ add_task(async function() {
 
   const { toolbox } = await attachDebugger(tab), client = toolbox.threadClient;
   await client.interrupt();
   const bp = await setBreakpoint(client, "doc_rr_basic.html", 22);
   await rewindToLine(client, 22);
   await stepInToLine(client, 25);
   await stepOverToLine(client, 26);
   await stepOverToLine(client, 27);
-  await reverseStepInToLine(client, 33);
+  await reverseStepOverToLine(client, 26);
+  await stepInToLine(client, 30);
+  await stepOverToLine(client, 31);
+  await stepOverToLine(client, 32);
+  await stepOverToLine(client, 33);
   await reverseStepOverToLine(client, 32);
-  await reverseStepOutToLine(client, 26);
+  await stepOutToLine(client, 27);
+  await reverseStepOverToLine(client, 26);
   await reverseStepOverToLine(client, 25);
 
   await client.removeBreakpoint(bp);
   await toolbox.destroy();
   await gBrowser.removeTab(tab);
 });
--- a/devtools/client/webreplay/mochitest/examples/doc_rr_basic.html
+++ b/devtools/client/webreplay/mochitest/examples/doc_rr_basic.html
@@ -27,12 +27,10 @@ function testStepping() {
   return a;
 }
 function testStepping2() {
   var c = this; // Note: using 'this' causes the script to have a prologue.
   c++;
   c--;
 }
 window.setTimeout(f, 1);
-// Simulate a longer recording by marking major checkpoints whenever possible.
-SpecialPowers.Cu.recordReplayDirective(/* AlwaysMarkMajorCheckpoints */ 4);
 </script>
 </html>
--- a/devtools/client/webreplay/mochitest/examples/doc_rr_logs.html
+++ b/devtools/client/webreplay/mochitest/examples/doc_rr_logs.html
@@ -17,12 +17,10 @@ function f() {
   console.log({ number }); 
   number++;
   console.log({ number }); 
   number++;
   console.log({ number }); 
   window.setTimeout(recordingFinished);
 }
 window.setTimeout(f, 1);
-// Simulate a longer recording by marking major checkpoints whenever possible.
-SpecialPowers.Cu.recordReplayDirective(/* AlwaysMarkMajorCheckpoints */ 4);
 </script>
 </html>
deleted file mode 100644
--- a/devtools/client/webreplay/mochitest/examples/doc_rr_recovery.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<html lang="en" dir="ltr">
-<body>
-<div id="maindiv">Hello World!</div>
-</body>
-<script>
-// this line intentionally left blank
-// this line intentionally left blank
-// this line intentionally left blank
-const cpmm = SpecialPowers.Services.cpmm;
-var number = 0;
-function f() {
-  updateNumber();
-  if (number >= 10) {
-    cpmm.sendAsyncMessage("RecordingFinished");
-    return;
-  }
-  window.setTimeout(f, 1);
-}
-function updateNumber() {
-  number++;
-  document.getElementById("maindiv").innerHTML = "Number: " + number;
-  SpecialPowers.Cu.recordReplayDirective(/* MaybeCrash */ 2);
-}
-window.setTimeout(f, 1);
-</script>
-</html>
--- a/devtools/client/webreplay/mochitest/head.js
+++ b/devtools/client/webreplay/mochitest/head.js
@@ -66,19 +66,17 @@ function resumeThenPauseAtLineFunctionFa
 }
 
 // Define various methods that resume a thread in a specific way and ensure it
 // pauses at a specified line.
 var rewindToLine = resumeThenPauseAtLineFunctionFactory("rewind");
 var resumeToLine = resumeThenPauseAtLineFunctionFactory("resume");
 var reverseStepOverToLine = resumeThenPauseAtLineFunctionFactory("reverseStepOver");
 var stepOverToLine = resumeThenPauseAtLineFunctionFactory("stepOver");
-var reverseStepInToLine = resumeThenPauseAtLineFunctionFactory("reverseStepIn");
 var stepInToLine = resumeThenPauseAtLineFunctionFactory("stepIn");
-var reverseStepOutToLine = resumeThenPauseAtLineFunctionFactory("reverseStepOut");
 var stepOutToLine = resumeThenPauseAtLineFunctionFactory("stepOut");
 
 // Return a promise that resolves when a thread evaluates a string in the
 // topmost frame, with the result throwing an exception.
 async function checkEvaluateInTopFrameThrows(target, text) {
   const threadClient = target.threadClient;
   const consoleFront = await target.getFront("console");
   const { frames } = await threadClient.getFrames(0, 1);
--- a/devtools/server/actors/replay/control.js
+++ b/devtools/server/actors/replay/control.js
@@ -1,1127 +1,1292 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-/* eslint-disable spaced-comment, brace-style, indent-legacy */
+/* eslint-disable spaced-comment, brace-style, indent-legacy, no-shadow */
 
 "use strict";
 
 // This file provides an interface which the ReplayDebugger uses to interact
 // with a middleman's child recording and replaying processes. There can be
 // several child processes in existence at once; this is largely hidden from the
 // ReplayDebugger, and the position of each child is managed to provide a fast
 // and stable experience when rewinding or running forward.
 
 const CC = Components.Constructor;
 
 // Create a sandbox with the resources we need. require() doesn't work here.
 const sandbox = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")());
 Cu.evalInSandbox(
   "Components.utils.import('resource://gre/modules/jsdebugger.jsm');" +
   "Components.utils.import('resource://gre/modules/Services.jsm');" +
+  "Components.utils.import('resource://devtools/shared/execution-point-utils.js');" +
   "addDebuggerToGlobal(this);",
   sandbox
 );
-const RecordReplayControl = sandbox.RecordReplayControl;
-const Services = sandbox.Services;
+const {
+  RecordReplayControl,
+  Services,
+  pointPrecedes,
+  pointEquals,
+  positionEquals,
+  positionSubsumes,
+} = sandbox;
 
 const InvalidCheckpointId = 0;
 const FirstCheckpointId = 1;
 
-const gChildren = [];
-
-let gDebugger;
+// Application State Control
+//
+// This section describes the strategy used for managing child processes so that
+// we can be responsive to user interactions. There is at most one recording
+// child process, and one or more replaying child processes.
+//
+// The recording child cannot rewind: it only runs forward and adds new data to
+// the recording. If we are paused or trying to pause at a place within the
+// recording, then the recording child is also paused.
+//
+// To manage the replaying children, we identify a set of checkpoints that will
+// be saved by some replaying child. The duration between saved checkpoints
+// should be roughly equal, and they must be sufficiently closely spaced that
+// any point in the recording can be quickly reached by some replaying child
+// restoring the previous saved checkpoint and then running forward to the
+// target point.
+//
+// As we identify the saved checkpoints, each is assigned to some replaying
+// child, which is responsible for both saving that checkpoint and for scanning
+// the contents of the recording from that checkpoint until the next saved
+// checkpoint.
+//
+// When adding new data to the recording, replaying children will scan and save
+// the regions and checkpoints they are responsible for, and will otherwise play
+// forward as far as they can in the recording. We always want to have one or
+// more replaying children that are at far end of the recording and able to
+// start scanning/saving as the recording grows. In order to ensure this,
+// consider the following:
+//
+// - Replaying must be faster than recording. While recording there is idle time
+//   as we wait for user input, timers, etc. that is not present while
+//   replaying. Suppose that replaying speed is F times the recording speed.
+//   F must be less than one.
+//
+// - Scanning and saving a section of the recording is slower than recording.
+//   Both of these have a lot of overhead, and suppose that scanning is S times
+//   the recording speed. S will be more than one.
+//
+// - When there is more than one replaying child, each child can divide its time
+//   between scanning/saving and simply replaying. We want its average speed to
+//   match that of the recording. If there are N replaying children, each child
+//   scans 1/N of the recording, so the average speed compared to the recording
+//   is S/N + F*(N-1)/N. We want this term to be one or slightly less.
+//
+// For example, if F = 0.75 and S = 3, then replaying is 33% faster than
+// recording, and scanning is three times slower than recording. If N = 4,
+// the average speed is 3/4 + 0.75*3/4 = 1.31 times that of recording, which
+// will cause the replaying processes to fall further and further behind the
+// recording. If N = 12, the average speed is 3/12 + 0.75*11/12 = 0.94 times
+// that of the recording, which will allow the replaying processes to keep up
+// with the recording.
+//
+// Eventually we'll want to do this analysis dynamically to scale up or throttle
+// back the number of active replaying processes. For now, though, we rely on
+// a fixed number of replaying processes, and hope that it is enough.
 
-function ChildProcess(id, recording, role) {
-  assert(!gChildren[id]);
-  gChildren[id] = this;
+////////////////////////////////////////////////////////////////////////////////
+// Child Processes
+////////////////////////////////////////////////////////////////////////////////
 
+// Information about a child recording or replaying process.
+function ChildProcess(id, recording) {
   this.id = id;
+
+  // Whether this process is recording.
   this.recording = recording;
-  this.role = role;
+
+  // Whether this process is paused.
   this.paused = false;
 
+  // The last point we paused at.
   this.lastPausePoint = null;
-  this.lastPauseAtRecordingEndpoint = false;
+
+  // Manifests which this child needs to send asynchronously.
+  this.asyncManifests = [];
 
-  // The pauseNeeded flag indicates that background replaying children should
-  // not resume execution once the process has paused.
-  this.pauseNeeded = false;
+  // All checkpoints which this process has saved or will save, which is a
+  // subset of all the saved checkpoints.
+  this.savedCheckpoints = new Set(recording ? [] : [FirstCheckpointId]);
 
-  // All currently installed breakpoints
-  this.breakpoints = [];
+  // All saved checkpoints whose region of the recording has been scanned by
+  // this child.
+  this.scannedCheckpoints = new Set();
 
-  // Any debugger requests sent while paused at the current point.
-  this.debuggerRequests = [];
+  // Checkpoints in savedCheckpoints which haven't been sent to the child yet.
+  this.needSaveCheckpoints = [];
 
-  this._willSaveCheckpoints = [];
-  this._majorCheckpoints = [];
-  this._minorCheckpoints = new Set();
+  // Whether this child has diverged from the recording and cannot run forward.
+  this.divergedFromRecording = false;
 
-  // Replaying processes always save the first checkpoint.
-  if (!recording) {
-    this._willSaveCheckpoints.push(FirstCheckpointId);
-  }
-
-  dumpv(`InitRole #${this.id} ${role.name}`);
-  this.role.initialize(this, { startup: true });
+  // Any manifest which is currently being executed. Child processes initially
+  // have a manifest to run forward to the first checkpoint.
+  this.manifest = {
+    onFinished: ({ point }) => {
+      if (this == gMainChild) {
+        getCheckpointInfo(FirstCheckpointId).point = point;
+        Services.tm.dispatchToMainThread(recording ? maybeResumeRecording : setMainChild);
+      }
+    },
+  };
 }
 
 ChildProcess.prototype = {
-  hitExecutionPoint(msg) {
-    assert(!this.paused);
-    this.paused = true;
-    this.lastPausePoint = msg.point;
-    this.lastPauseAtRecordingEndpoint = msg.recordingEndpoint;
-
-    this.role.hitExecutionPoint(msg);
+  // Get the execution point where this child is currently paused.
+  pausePoint() {
+    assert(this.paused);
+    return this.lastPausePoint;
   },
 
-  setRole(role) {
-    dumpv(`SetRole #${this.id} ${role.name}`);
-
-    this.role = role;
-    this.role.initialize(this, { startup: false });
-  },
-
-  addMajorCheckpoint(checkpointId) {
-    this._majorCheckpoints.push(checkpointId);
-  },
-
-  addMinorCheckpoint(checkpointId) {
-    this._minorCheckpoints.add(checkpointId);
+  // Get the checkpoint where this child is currently paused.
+  pauseCheckpoint() {
+    const point = this.pausePoint();
+    assert(!point.position);
+    return point.checkpoint;
   },
 
-  _unpause() {
+  // Send a manifest to paused child to execute. The child unpauses while
+  // executing the manifest, and pauses again when it finishes. Manifests have
+  // the following properties:
+  //
+  // contents: The JSON object to send to the child describing the operation.
+  // onFinished: A callback which is called after the manifest finishes with the
+  //   manifest's result.
+  sendManifest(manifest) {
+    assert(this.paused);
     this.paused = false;
-    this.debuggerRequests.length = 0;
-  },
+    this.manifest = manifest;
 
-  sendResume({ forward }) {
-    assert(this.paused);
-    this._unpause();
-    RecordReplayControl.sendResume(this.id, forward);
+    dumpv(`SendManifest #${this.id} ${JSON.stringify(manifest.contents)}`);
+    RecordReplayControl.sendManifest(this.id, manifest.contents);
   },
 
-  sendRestoreCheckpoint(checkpoint) {
-    assert(this.paused);
-    this._unpause();
-    RecordReplayControl.sendRestoreCheckpoint(this.id, checkpoint);
+  // Called when the child's current manifest finishes.
+  manifestFinished(response) {
+    assert(!this.paused);
+    if (response && response.point) {
+      this.lastPausePoint = response.point;
+    }
+    this.paused = true;
+    this.manifest.onFinished(response);
+    this.manifest = null;
   },
 
-  sendRunToPoint(point) {
-    assert(this.paused);
-    this._unpause();
-    RecordReplayControl.sendRunToPoint(this.id, point);
-  },
-
-  sendFlushRecording() {
-    assert(this.paused);
-    RecordReplayControl.sendFlushRecording(this.id);
-  },
-
+  // Block until this child is paused. If maybeCreateCheckpoint is specified
+  // then a checkpoint is created if this child is recording, so that it pauses
+  // quickly (otherwise it could sit indefinitely if there is no page activity).
   waitUntilPaused(maybeCreateCheckpoint) {
     if (this.paused) {
       return;
     }
-    const msg =
-      RecordReplayControl.waitUntilPaused(this.id, maybeCreateCheckpoint);
-    this.hitExecutionPoint(msg);
+    RecordReplayControl.waitUntilPaused(this.id, maybeCreateCheckpoint);
     assert(this.paused);
   },
 
-  lastCheckpoint() {
-    return this.lastPausePoint.checkpoint;
-  },
-
-  rewindTargetCheckpoint() {
-    return this.lastPausePoint.position
-           ? this.lastCheckpoint()
-           : this.lastCheckpoint() - 1;
-  },
-
-  // Get the last major checkpoint at or before id.
-  lastMajorCheckpointPreceding(id) {
-    let last = InvalidCheckpointId;
-    for (const major of this._majorCheckpoints) {
-      if (major > id) {
-        break;
-      }
-      last = major;
-    }
-    return last;
-  },
-
-  isMajorCheckpoint(id) {
-    return this._majorCheckpoints.some(major => major == id);
-  },
-
-  isMinorCheckpoint(id) {
-    return this._minorCheckpoints.has(id);
-  },
-
-  ensureCheckpointSaved(id, shouldSave) {
-    const willSaveIndex = this._willSaveCheckpoints.indexOf(id);
-    if (shouldSave != (willSaveIndex != -1)) {
-      if (shouldSave) {
-        this._willSaveCheckpoints.push(id);
-      } else {
-        const last = this._willSaveCheckpoints.pop();
-        if (willSaveIndex != this._willSaveCheckpoints.length) {
-          this._willSaveCheckpoints[willSaveIndex] = last;
-        }
-      }
-      RecordReplayControl.sendSetSaveCheckpoint(this.id, id, shouldSave);
+  // Add a checkpoint for this child to save.
+  addSavedCheckpoint(checkpoint) {
+    dumpv(`AddSavedCheckpoint #${this.id} ${checkpoint}`);
+    this.savedCheckpoints.add(checkpoint);
+    if (checkpoint != FirstCheckpointId) {
+      this.needSaveCheckpoints.push(checkpoint);
     }
   },
 
-  // Ensure a checkpoint is saved in this child iff it is a major one.
-  ensureMajorCheckpointSaved(id) {
-    // The first checkpoint is always saved, even if not marked as major.
-    this.ensureCheckpointSaved(id, this.isMajorCheckpoint(id) || id == FirstCheckpointId);
-  },
-
-  hasSavedCheckpoint(id) {
-    return (id <= this.lastCheckpoint()) &&
-           this._willSaveCheckpoints.includes(id);
+  // Get any checkpoints to inform the child that it needs to save.
+  flushNeedSaveCheckpoints() {
+    const rv = this.needSaveCheckpoints;
+    this.needSaveCheckpoints = [];
+    return rv;
   },
 
-  // Return whether this child has saved all minor checkpoints between the last
-  // major checkpoint preceding to id and id itself. This is required in order
-  // for the child to rewind through this span of checkpoints.
-  canRewindFrom(id) {
-    const lastMajorCheckpoint = this.lastMajorCheckpointPreceding(id);
-    for (let i = lastMajorCheckpoint + 1; i <= id; i++) {
-      if (this.isMinorCheckpoint(i) && !this.hasSavedCheckpoint(i)) {
-        return false;
-      }
-    }
-    return true;
+  // Send a manifest to this child asynchronously. The child does not need to be
+  // paused, and will process async manifests in the order they were added.
+  // Async manifests can end up being reassigned to a different child. This
+  // returns a promise that resolves when the manifest finishes. Async manifests
+  // have the following properties:
+  //
+  // shouldSkip: Optional callback invoked with the executing child when it is
+  //   about to be sent. Returns true if the manifest should not be sent, and
+  //   the promise resolved immediately.
+  //
+  // contents: Callback invoked with the executing child when it is being sent.
+  //   Returns the contents to send to the child.
+  //
+  // onFinished: Optional callback invoked with the executing child and manifest
+  //   response after the manifest finishes.
+  //
+  // noReassign: Optional boolean which can be set to prevent the manifest from
+  //   being reassigned to another child.
+  //
+  // The optional point parameter specifies an execution point which the child
+  // should be paused at before executing the manifest. Otherwise it could be
+  // paused anywhere. The returned value is the child which ended up executing
+  // the manifest.
+  sendManifestAsync(manifest, point) {
+    pokeChildSoon(this);
+    return new Promise(resolve => {
+      this.asyncManifests.push({ resolve, manifest, point });
+    });
   },
 
-  lastSavedCheckpointPriorTo(id) {
-    while (!this.hasSavedCheckpoint(id)) {
-      id--;
+  // Return true if progress was made while executing the next async manifest.
+  processAsyncManifest() {
+    if (this.asyncManifests.length == 0) {
+      return false;
     }
-    return id;
-  },
-
-  sendAddBreakpoint(pos) {
-    assert(this.paused);
-    this.breakpoints.push(pos);
-    RecordReplayControl.sendAddBreakpoint(this.id, pos);
-  },
+    const { resolve, manifest, point } = this.asyncManifests[0];
+    if (manifest.shouldSkip && manifest.shouldSkip(this)) {
+      resolve(this);
+      this.asyncManifests.shift();
+      pokeChildSoon(this);
+      return true;
+    }
 
-  sendClearBreakpoints() {
-    assert(this.paused);
-    this.breakpoints.length = 0;
-    RecordReplayControl.sendClearBreakpoints(this.id);
-  },
-
-  sendDebuggerRequest(request) {
-    assert(this.paused);
-    this.debuggerRequests.push(request);
-    return RecordReplayControl.sendDebuggerRequest(this.id, request);
-  },
+    // If this is the active child then we can't process arbitrary manifests.
+    // Only handle those which cannot be reassigned, and hand off others to
+    // random other children.
+    if (this == gActiveChild && !manifest.noReassign) {
+      const child = pickReplayingChild();
+      child.asyncManifests.push(this.asyncManifests.shift());
+      pokeChildSoon(child);
+      pokeChildSoon(this);
+      return true;
+    }
 
-  // When a background child pauses, it does not immediately resume. This will
-  // asynchronously let the role know that it may be able to make progress,
-  // depending on where the active child is and what it is doing.
-  pokeSoon() {
-    if (!this.recording) {
-      Services.tm.dispatchToMainThread(() => {
-        if (this.paused) {
-          this.role.poke();
+    if (point && maybeReachPoint(this, point)) {
+      return true;
+    }
+    this.sendManifest({
+      contents: manifest.contents(this),
+      onFinished: data => {
+        if (manifest.onFinished) {
+          manifest.onFinished(this, data);
         }
-      });
-    }
+        resolve(this);
+        pokeChildSoon(this);
+      },
+    });
+    this.asyncManifests.shift();
+    return true;
   },
 };
 
-function pokeChildren() {
-  for (const child of gChildren) {
+// Child which is always at the end of the recording. When there is a recording
+// child this is it, and when we are replaying an old execution this is a
+// replaying child that is unable to rewind and is used in the same way as the
+// recording child.
+let gMainChild;
+
+// Replaying children available for exploring the interior of the recording,
+// indexed by their ID.
+const gReplayingChildren = [];
+
+function lookupChild(id) {
+  if (id == gMainChild.id) {
+    return gMainChild;
+  }
+  assert(gReplayingChildren[id]);
+  return gReplayingChildren[id];
+}
+
+// ID of the last replaying child we picked for an operation.
+let lastPickedChildId = 0;
+
+function pickReplayingChild() {
+  // Use a round robin approach when picking new children for operations,
+  // to try to keep activity among the children evenly distributed.
+  while (true) {
+    lastPickedChildId = (lastPickedChildId + 1) % gReplayingChildren.length;
+    const child = gReplayingChildren[lastPickedChildId];
     if (child) {
-      child.pokeSoon();
+      return child;
     }
   }
 }
 
+// The singleton ReplayDebugger, or undefined if it doesn't exist.
+let gDebugger;
+
+////////////////////////////////////////////////////////////////////////////////
+// Application State
+////////////////////////////////////////////////////////////////////////////////
+
+// Any child the user is interacting with, which may be paused or not.
+let gActiveChild = null;
+
+// Information about each checkpoint, indexed by the checkpoint's id.
+const gCheckpoints = [ null ];
+
+function CheckpointInfo() {
+  // The time taken to run from this checkpoint to the next one, excluding idle
+  // time.
+  this.duration = 0;
+
+  // Execution point at the checkpoint.
+  this.point = null;
+
+  // If the checkpoint is saved, the replaying child responsible for saving it
+  // and scanning the region up to the next saved checkpoint.
+  this.owner = null;
+}
+
+function getCheckpointInfo(id) {
+  while (id >= gCheckpoints.length) {
+    gCheckpoints.push(new CheckpointInfo());
+  }
+  return gCheckpoints[id];
+}
+
+// How much execution time has elapsed since a checkpoint.
+function timeSinceCheckpoint(id) {
+  let time = 0;
+  for (let i = id ? id : FirstCheckpointId; i < gCheckpoints.length; i++) {
+    time += gCheckpoints[i].duration;
+  }
+  return time;
+}
+
+// The checkpoint up to which the recording runs.
+let gLastFlushCheckpoint = InvalidCheckpointId;
+
+// The last saved checkpoint.
+let gLastSavedCheckpoint = FirstCheckpointId;
+
+// How often we want to flush the recording.
 const FlushMs = .5 * 1000;
-const MajorCheckpointMs = 2 * 1000;
-const MinorCheckpointMs = .25 * 1000;
+
+// How often we want to save a checkpoint.
+const SavedCheckpointMs = .25 * 1000;
+
+function addSavedCheckpoint(checkpoint) {
+  if (getCheckpointInfo(checkpoint).owner) {
+    return;
+  }
+
+  const owner = pickReplayingChild();
+  getCheckpointInfo(checkpoint).owner = owner;
+  owner.addSavedCheckpoint(checkpoint);
+  gLastSavedCheckpoint = checkpoint;
+}
+
+function addCheckpoint(checkpoint, duration) {
+  assert(!getCheckpointInfo(checkpoint).duration);
+  getCheckpointInfo(checkpoint).duration = duration;
+
+  // Mark saved checkpoints as required, unless we haven't spawned any replaying
+  // children yet.
+  if (timeSinceCheckpoint(gLastSavedCheckpoint) >= SavedCheckpointMs &&
+      gReplayingChildren.length > 0) {
+    addSavedCheckpoint(checkpoint + 1);
+  }
+}
+
+function ownerChild(checkpoint) {
+  assert(checkpoint <= gLastSavedCheckpoint);
+  while (!getCheckpointInfo(checkpoint).owner) {
+    checkpoint--;
+  }
+  return getCheckpointInfo(checkpoint).owner;
+}
 
-// This section describes the strategy used for managing child processes. When
-// recording, there is a single recording process and two replaying processes.
-// When replaying, there are two replaying processes. The main advantage of
-// using two replaying processes is to provide a smooth experience when
-// rewinding.
-//
-// At any time there is one active child: the process which the user is
-// interacting with. This may be any of the two or three children in existence,
-// depending on the user's behavior. The other processes do not interact with
-// the user: inactive recording processes are inert, and sit idle until
-// recording is ready to resume, while inactive replaying processes are on
-// standby, staying close to the active process in the recording's execution
-// space and saving checkpoints in case the user starts rewinding.
-//
-// Below are some scenarios showing the state we attempt to keep the children
-// in, and ways in which the active process switches from one to another.
-// The execution diagrams show the position of each process, with '*' and '-'
-// indicating checkpoints the process reached and, respectively, whether
-// the checkpoint was saved or not.
-//
-// When the recording process is actively recording, flushes are issued to it
-// every FlushMs to keep the recording reasonably current and allow the
-// replaying processes to stay behind but close to the position of the
-// recording process. Additionally, one replaying process saves a checkpoint
-// every MajorCheckpointMs with the process saving the checkpoint alternating
-// back and forth so that individual processes save checkpoints every
-// MajorCheckpointMs*2. These are the major checkpoints for each replaying
-// process.
-//
-// Active  Recording:    -----------------------
-// Standby Replaying #1: *---------*---------*
-// Standby Replaying #2: -----*---------*-----
-//
-// When the recording process is explicitly paused (via the debugger UI) at a
-// checkpoint or breakpoint, it is flushed and the replaying processes will
-// navigate around the recording to save a second set of checkpoints going back
-// at least MajorCheckpointSeconds, with the goal of making sure saved
-// checkpoints are no more than MinorCheckpointSeconds apart. No replaying
-// process needs to rewind past its last major checkpoint, and a given
-// minor checkpoint will only ever be saved by the replaying process with the
-// most recent major checkpoint.
-//
-// Active  Recording:    -----------------------
-// Standby Replaying #1: *---------*---------*-*
-// Standby Replaying #2: -----*---------*-*-*
-//
-// If the user starts rewinding, the replaying process with the most recent
-// major checkpoint (and which has been saving the most recent minor
-// checkpoints) becomes the active child.
-//
-// Inert   Recording:    -----------------------
-// Active  Replaying #1: *---------*---------*-
-// Standby Replaying #2: -----*---------*-*-*
-//
-// As the user continues rewinding, the replaying process stays active until it
-// goes past its most recent major checkpoint. At that time the other replaying
-// process (which has been saving checkpoints prior to that point) becomes the
-// active child and allows continuous rewinding. The first replaying process
-// rewinds to its last major checkpoint and begins saving older minor
-// checkpoints, attempting to maintain the invariant that we have saved (or are
-// saving) all checkpoints going back MajorCheckpointMs.
-//
-// Inert   Recording:    -----------------------
-// Standby Replaying #1: *---------*-*-*
-// Active  Replaying #2: -----*---------*-
-//
-// Rewinding continues in this manner, alternating back and forth between the
-// replaying processes as the user continues going back in time.
-//
-// Inert   Recording:    -----------------------
-// Active  Replaying #1: *---------*-*
-// Standby Replaying #2: -----*-*-*
-//
-// If the user starts navigating forward, the replaying processes both run
-// forward and save checkpoints at the same major checkpoints as earlier.
-// Note that this is how all forward execution works when there is no recording
-// process (i.e. we started from a saved recording).
-//
-// Inert   Recording:    -----------------------
-// Active  Replaying #1: *---------*-*-----
-// Standby Replaying #2: -----*-*-*-----*--
-//
-// If the user pauses at a checkpoint or breakpoint in the replay, we again
-// want to fill in all the checkpoints going back MajorCheckpointMs to allow
-// smooth rewinding. This cannot be done simultaneously -- as it was when the
-// recording process was active -- since we need to keep one of the replaying
-// processes at an up to date point and be the active one. This falls on the one
-// whose most recent major checkpoint is oldest, as the other is responsible for
-// saving the most recent minor checkpoints.
-//
-// Inert   Recording:    -----------------------
-// Active  Replaying #1: *---------*-*-----
-// Standby Replaying #2: -----*-*-*-----*-*
-//
-// After the recent minor checkpoints have been saved the process which
-// took them can become active so the older minor checkpoints can be
-// saved.
-//
-// Inert   Recording:    -----------------------
-// Standby Replaying #1: *---------*-*-*
-// Active  Replaying #2: -----*-*-*-----*-*
-//
-// Finally, if the replay plays forward to the end of the recording (the point
-// where the recording process is situated), the recording process takes over
-// again as the active child and the user can resume interacting with a live
-// process.
-//
-// Active  Recording:    ----------------------------------------
-// Standby Replaying #1: *---------*-*-*-----*---------*-------
-// Standby Replaying #2: -----*-*-*-----*-*-------*---------*--
+// Unpause a child and restore it to its most recent saved checkpoint at or
+// before target.
+function restoreCheckpoint(child, target) {
+  while (!child.savedCheckpoints.has(target)) {
+    target--;
+  }
+  child.sendManifest({
+    contents: { kind: "restoreCheckpoint", target },
+    onFinished({ restoredCheckpoint }) {
+      assert(restoredCheckpoint);
+      child.divergedFromRecording = false;
+      pokeChildSoon(child);
+    },
+  });
+}
+
+// Bring a child to the specified execution point, sending it one or more
+// manifests if necessary. Returns true if the child has not reached the point
+// yet but some progress was made, or false if the child is at the point.
+function maybeReachPoint(child, endpoint) {
+  if (pointEquals(child.pausePoint(), endpoint) && !child.divergedFromRecording) {
+    return false;
+  }
+  if (child.divergedFromRecording || child.pausePoint().position) {
+    restoreCheckpoint(child, child.pausePoint().checkpoint);
+    return true;
+  }
+  if (endpoint.checkpoint < child.pauseCheckpoint()) {
+    restoreCheckpoint(child, endpoint.checkpoint);
+    return true;
+  }
+  child.sendManifest({
+    contents: {
+      kind: "runToPoint",
+      endpoint,
+      needSaveCheckpoints: child.flushNeedSaveCheckpoints(),
+    },
+    onFinished() {
+      pokeChildSoon(child);
+    },
+  });
+  return true;
+}
+
+function nextSavedCheckpoint(checkpoint) {
+  assert(gCheckpoints[checkpoint].owner);
+  // eslint-disable-next-line no-empty
+  while (!gCheckpoints[++checkpoint].owner) {}
+  return checkpoint;
+}
 
-// Child processes that can participate in the above management.
-let gRecordingChild;
-let gFirstReplayingChild;
-let gSecondReplayingChild;
-let gActiveChild;
+function forSavedCheckpointsInRange(start, end, callback) {
+  assert(gCheckpoints[start].owner);
+  for (let checkpoint = start;
+       checkpoint < end;
+       checkpoint = nextSavedCheckpoint(checkpoint)) {
+    callback(checkpoint);
+  }
+}
+
+function getSavedCheckpoint(checkpoint) {
+  while (!gCheckpoints[checkpoint].owner) {
+    checkpoint--;
+  }
+  return checkpoint;
+}
+
+// Get the execution point to use for a checkpoint.
+function checkpointExecutionPoint(checkpoint) {
+  return gCheckpoints[checkpoint].point;
+}
+
+// Check to see if an idle replaying child can make any progress.
+function pokeChild(child) {
+  assert(!child.recording);
 
-function otherReplayingChild(child) {
-  assert(child == gFirstReplayingChild || child == gSecondReplayingChild);
-  return child == gFirstReplayingChild
-         ? gSecondReplayingChild
-         : gFirstReplayingChild;
+  if (!child.paused) {
+    return;
+  }
+
+  if (child.processAsyncManifest()) {
+    return;
+  }
+
+  if (child == gActiveChild) {
+    sendChildToPausePoint(child);
+    return;
+  }
+
+  // If there is nothing to do, run forward to the end of the recording.
+  maybeReachPoint(child, checkpointExecutionPoint(gLastFlushCheckpoint));
+}
+
+function pokeChildSoon(child) {
+  Services.tm.dispatchToMainThread(() => pokeChild(child));
+}
+
+function pokeChildren() {
+  for (const child of gReplayingChildren) {
+    if (child) {
+      pokeChild(child);
+    }
+  }
+}
+
+function pokeChildrenSoon() {
+  Services.tm.dispatchToMainThread(() => pokeChildren());
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// Child Roles
+// Search State
 ////////////////////////////////////////////////////////////////////////////////
 
-function ChildRoleActive() {}
-
-ChildRoleActive.prototype = {
-  name: "Active",
-
-  initialize(child, { startup }) {
-    this.child = child;
-    gActiveChild = child;
-
-    // Mark the child as active unless we are starting up, in which case it is
-    // unpaused and we can't send messages to it.
-    if (!startup) {
-      RecordReplayControl.setActiveChild(child.id);
-    }
-  },
-
-  hitExecutionPoint(msg) {
-    // Ignore HitCheckpoint messages received while doing a time warp.
-    // timeWarp() will immediately resume the child and we don't want to tell
-    // the debugger it ever paused.
-    if (gTimeWarpInProgress) {
-      return;
-    }
-
-    // Make sure the active child is marked as such when starting up.
-    if (msg.point.checkpoint == FirstCheckpointId) {
-      RecordReplayControl.setActiveChild(this.child.id);
-    }
-
-    updateCheckpointTimes(msg);
-
-    // When at the endpoint of the recording, immediately resume. We don't
-    // want to notify the debugger about this: if the user installed a
-    // breakpoint here we will have already gotten a HitExecutionPoint message
-    // *without* mRecordingEndpoint set, and we don't want to pause twice at
-    // the same point.
-    if (msg.recordingEndpoint) {
-      resume(true);
-
-      // When resuming at the end of the recording, we will either switch to a
-      // recording child or stay paused at the endpoint. In either case, this
-      // process will stay paused.
-      assert(this.child.paused);
-      return;
-    }
-
-    // Run forward by default if there is no debugger attached, but post a
-    // runnable so that callers waiting for the child to pause don't starve.
-    if (!gDebugger) {
-      Services.tm.dispatchToMainThread(() => this.child.sendResume({ forward: true }));
-      return;
-    }
+// All currently installed breakpoints.
+const gBreakpoints = [];
 
-    gDebugger._onPause();
-  },
-
-  poke() {},
-};
-
-// The last checkpoint included in the recording.
-let gLastRecordingCheckpoint;
-
-// The role taken by replaying children trying to stay close to the active
-// child and save either major or minor checkpoints, depending on whether the
-// active child is paused or rewinding.
-function ChildRoleStandby() {}
-
-ChildRoleStandby.prototype = {
-  name: "Standby",
-
-  initialize(child) {
-    this.child = child;
-    this.child.pokeSoon();
-  },
-
-  hitExecutionPoint(msg) {
-    assert(!msg.point.position);
-    this.child.pokeSoon();
-  },
-
-  poke() {
-    assert(this.child.paused && !this.child.lastPausePoint.position);
-    const currentCheckpoint = this.child.lastCheckpoint();
-
-    // Stay paused if we need to while the recording is flushed.
-    if (this.child.pauseNeeded) {
-      return;
-    }
-
-    // Minor checkpoints are only saved when the active child is paused
-    // or rewinding.
-    let targetCheckpoint = getActiveChildTargetCheckpoint();
-    if (targetCheckpoint == undefined) {
-      // Minor checkpoints do not need to be saved. Run forward until we
-      // reach either the active child's position, or the last checkpoint
-      // included in the on-disk recording. Only save major checkpoints.
-      if ((currentCheckpoint < gActiveChild.lastCheckpoint()) &&
-          (!gRecordingChild || currentCheckpoint < gLastRecordingCheckpoint)) {
-        this.child.ensureMajorCheckpointSaved(currentCheckpoint + 1);
-        this.child.sendResume({ forward: true });
-      }
-      return;
-    }
-
-    // The startpoint of the range is the most recent major checkpoint prior
-    // to the target.
-    const lastMajorCheckpoint =
-      this.child.lastMajorCheckpointPreceding(targetCheckpoint);
+// Recording Scanning
+//
+// Scanning a section of the recording between two neighboring saved checkpoints
+// allows the execution points for each script breakpoint position to be queried
+// by sending a manifest to the child which performed the scan.
 
-    // If there is no major checkpoint prior to the target, just idle.
-    if (lastMajorCheckpoint == InvalidCheckpointId) {
-      return;
-    }
-
-    // If we haven't reached the last major checkpoint, we need to run forward
-    // without saving minor checkpoints.
-    if (currentCheckpoint < lastMajorCheckpoint) {
-      this.child.ensureMajorCheckpointSaved(currentCheckpoint + 1);
-      this.child.sendResume({ forward: true });
-      return;
-    }
-
-    // The endpoint of the range is the checkpoint prior to either the active
-    // child's current position, or the other replaying child's most recent
-    // major checkpoint.
-    const otherChild = otherReplayingChild(this.child);
-    const otherMajorCheckpoint =
-      otherChild.lastMajorCheckpointPreceding(targetCheckpoint);
-    if (otherMajorCheckpoint > lastMajorCheckpoint) {
-      assert(otherMajorCheckpoint <= targetCheckpoint);
-      targetCheckpoint = otherMajorCheckpoint - 1;
-    }
-
-    // Find the first minor checkpoint in the fill range which we have not saved.
-    let missingCheckpoint;
-    for (let i = lastMajorCheckpoint + 1; i <= targetCheckpoint; i++) {
-      if (this.child.isMinorCheckpoint(i) && !this.child.hasSavedCheckpoint(i)) {
-        missingCheckpoint = i;
-        break;
-      }
-    }
-
-    // If we have already saved everything we need to, we can idle.
-    if (missingCheckpoint == undefined) {
-      return;
-    }
-
-    if (this.child.lastCheckpoint() < missingCheckpoint) {
-      // We can run forward to reach the missing checkpoint.
-    } else {
-      // We need to rewind in order to save the missing checkpoint. Find the
-      // last saved checkpoint prior to the missing one. This must be
-      // lastMajorCheckpoint or later, as we always save major checkpoints.
-      let restoreTarget = missingCheckpoint - 1;
-      while (!this.child.hasSavedCheckpoint(restoreTarget)) {
-        restoreTarget--;
-      }
-      assert(restoreTarget >= lastMajorCheckpoint);
-
-      this.child.sendRestoreCheckpoint(restoreTarget);
-      return;
-    }
+// Ensure the region for a saved checkpoint has been scanned by some child,
+// returning a promise that resolves with that child.
+function scanRecording(checkpoint) {
+  assert(checkpoint < gLastFlushCheckpoint);
 
-    // Make sure the process will save minor checkpoints as it runs forward.
-    if (missingCheckpoint == this.child.lastCheckpoint() + 1) {
-      this.child.ensureCheckpointSaved(missingCheckpoint, true);
-    }
-
-    // Run forward to the next checkpoint.
-    this.child.sendResume({ forward: true });
-  },
-};
-
-// The role taken by a child that always sits idle.
-function ChildRoleInert() {}
-
-ChildRoleInert.prototype = {
-  name: "Inert",
-
-  initialize() {},
-  hitExecutionPoint() {},
-  poke() {},
-};
-
-////////////////////////////////////////////////////////////////////////////////
-// Child Switching
-////////////////////////////////////////////////////////////////////////////////
-
-// Change the current active child, and select a new role for the old one.
-function switchActiveChild(child, recoverPosition = true) {
-  assert(child != gActiveChild);
-  assert(gActiveChild.paused);
-
-  const oldActiveChild = gActiveChild;
-  child.waitUntilPaused();
-
-  // Move the installed breakpoints from the old child to the new child.
-  assert(child.breakpoints.length == 0);
-  for (const pos of oldActiveChild.breakpoints) {
-    child.sendAddBreakpoint(pos);
-  }
-  oldActiveChild.sendClearBreakpoints();
-
-  if (recoverPosition && !child.recording) {
-    child.setRole(new ChildRoleInert());
-    const targetCheckpoint = oldActiveChild.lastCheckpoint();
-    if (child.lastCheckpoint() > targetCheckpoint) {
-      const restoreCheckpoint =
-        child.lastSavedCheckpointPriorTo(targetCheckpoint);
-      child.sendRestoreCheckpoint(restoreCheckpoint);
-      child.waitUntilPaused();
-    }
-    while (child.lastCheckpoint() < targetCheckpoint) {
-      child.ensureMajorCheckpointSaved(child.lastCheckpoint() + 1);
-      child.sendResume({ forward: true });
-      child.waitUntilPaused();
-    }
-    assert(!child.lastPausePoint.position);
-    if (oldActiveChild.lastPausePoint.position) {
-      child.sendRunToPoint(oldActiveChild.lastPausePoint);
-      child.waitUntilPaused();
-    }
-    for (const request of oldActiveChild.debuggerRequests) {
-      child.sendDebuggerRequest(request);
+  for (const child of gReplayingChildren) {
+    if (child && child.scannedCheckpoints.has(checkpoint)) {
+      return child;
     }
   }
 
-  child.setRole(new ChildRoleActive());
-  oldActiveChild.setRole(new ChildRoleInert());
+  const initialChild = ownerChild(checkpoint);
+  const endpoint = nextSavedCheckpoint(checkpoint);
+  return initialChild.sendManifestAsync({
+    shouldSkip: child => child.scannedCheckpoints.has(checkpoint),
+    contents(child) {
+      return {
+        kind: "scanRecording",
+        endpoint,
+        needSaveCheckpoints: child.flushNeedSaveCheckpoints(),
+      };
+    },
+    onFinished: child => child.scannedCheckpoints.add(checkpoint),
+  }, checkpointExecutionPoint(checkpoint));
+}
 
-  if (!oldActiveChild.recording) {
-    if (oldActiveChild.lastPausePoint.position) {
-      // Standby replaying children must be paused at a checkpoint.
-      const oldCheckpoint = oldActiveChild.lastCheckpoint();
-      const restoreCheckpoint =
-        oldActiveChild.lastSavedCheckpointPriorTo(oldCheckpoint);
-      oldActiveChild.sendRestoreCheckpoint(restoreCheckpoint);
-      oldActiveChild.waitUntilPaused();
-    }
-    oldActiveChild.setRole(new ChildRoleStandby());
+// Map from saved checkpoints to information about breakpoint hits within the
+// range of that checkpoint.
+const gHitSearches = new Map();
+
+// Only hits on script locations (Break and OnStep positions) can be found by
+// scanning the recording.
+function canFindHits(position) {
+  return position.kind == "Break" || position.kind == "OnStep";
+}
+
+// Find all hits on the specified position between a saved checkpoint and the
+// following saved checkpoint, using data from scanning the recording. This
+// returns a promise that resolves with the resulting hits.
+async function findHits(checkpoint, position) {
+  assert(canFindHits(position));
+  assert(gCheckpoints[checkpoint].owner);
+
+  if (!gHitSearches.has(checkpoint)) {
+    gHitSearches.set(checkpoint, []);
   }
 
-  // Notify the debugger when switching between recording and replaying
-  // children.
-  if (child.recording != oldActiveChild.recording) {
-    gDebugger._onSwitchChild();
+  // Check if we already have the hits.
+  if (!gHitSearches.has(checkpoint)) {
+    gHitSearches.set(checkpoint, []);
+  }
+  const checkpointHits = gHitSearches.get(checkpoint);
+  let hits = findExistingHits();
+  if (hits) {
+    return hits;
+  }
+
+  const child = await scanRecording(checkpoint);
+  const endpoint = nextSavedCheckpoint(checkpoint);
+  await child.sendManifestAsync({
+    shouldSkip: () => findExistingHits() != null,
+    contents() {
+      return {
+        kind: "findHits",
+        position,
+        startpoint: checkpoint,
+        endpoint,
+      };
+    },
+    onFinished: (_, hits) => checkpointHits.push({ position, hits }),
+    // findHits has to be sent to the child which scanned this portion of the
+    // recording. It can be sent to the active child, though, because it
+    // does not have side effects.
+    noReassign: true,
+  });
+
+  hits = findExistingHits();
+  assert(hits);
+  return hits;
+
+  function findExistingHits() {
+    const entry = checkpointHits.find(({ position: existingPosition, hits }) => {
+      return positionEquals(position, existingPosition);
+    });
+    return entry ? entry.hits : null;
   }
 }
 
-function maybeSwitchToReplayingChild() {
-  if (gActiveChild.recording && RecordReplayControl.canRewind()) {
-    flushRecording();
-    const checkpoint = gActiveChild.rewindTargetCheckpoint();
-    const child = otherReplayingChild(
-      replayingChildResponsibleForSavingCheckpoint(checkpoint));
-    switchActiveChild(child);
+// Frame Steps
+//
+// When the recording scanning is not sufficient to figure out where to stop
+// when resuming, the steps for the currently paused frame can be fetched. This
+// mainly helps with finding the targets for EnterFrame breakpoints used when
+// stepping in, and will be used in the future to improve stepping performance.
+//
+// The steps for a frame are the list of execution points for breakpoint
+// positions traversed when executing a particular script frame, from the
+// initial EnterFrame to the final OnPop. The steps also include the EnterFrame
+// execution points for any direct callees of the frame.
+
+// All steps for frames which have been determined.
+const gFrameSteps = [];
+
+// When there are stepping breakpoints installed, we need to know the steps in
+// the current frame in order to find the next or previous hit.
+function hasSteppingBreakpoint() {
+  return gBreakpoints.some(bp => bp.kind == "EnterFrame" || bp.kind == "OnPop");
+}
+
+// Find all the steps in the frame which point is part of. This returns a
+// promise that resolves with the steps that were found.
+async function findFrameSteps(point) {
+  if (!point.position) {
+    return null;
+  }
+
+  assert(point.position.kind == "EnterFrame" ||
+         point.position.kind == "OnStep" ||
+         point.position.kind == "OnPop");
+
+  let steps = findExistingSteps();
+  if (steps) {
+    return steps;
+  }
+
+  const savedCheckpoint = getSavedCheckpoint(point.checkpoint);
+
+  let entryPoint;
+  if (point.position.kind == "EnterFrame") {
+    entryPoint = point;
+  } else {
+    // The point is in the interior of the frame. Figure out the initial
+    // EnterFrame point for the frame.
+    const {
+      progress: targetProgress,
+      position: { script, frameIndex: targetFrameIndex },
+    } = point;
+
+    // Find a position for the entry point of the frame.
+    const { firstBreakpointOffset } = gControl.sendRequestMainChild({
+      type: "getScript",
+      id: script,
+    });
+    const entryPosition = {
+      kind: "OnStep",
+      script,
+      offset: firstBreakpointOffset,
+      frameIndex: targetFrameIndex,
+    };
+
+    const entryHits = await findHits(savedCheckpoint, entryPosition);
+
+    // Find the last hit on the entry position before the target point, which must
+    // be the entry point of the frame containing the target point. Since frames
+    // do not span checkpoints the hit must be in the range we are searching. Note
+    // that we are not dealing with async/generator frames very well here.
+    let progressAtFrameStart = 0;
+    for (const { progress, position: { frameIndex } } of entryHits) {
+      if (frameIndex == targetFrameIndex &&
+          progress <= targetProgress &&
+          progress > progressAtFrameStart) {
+        progressAtFrameStart = progress;
+      }
+    }
+    assert(progressAtFrameStart);
+
+    // The progress at the initial offset should be the same as at the
+    // EnterFrame which pushed the frame onto the stack. No scripts should be
+    // able to run between these two points, though we don't have a way to check
+    // this.
+    entryPoint = {
+      checkpoint: point.checkpoint,
+      progress: progressAtFrameStart,
+      position: { kind: "EnterFrame" },
+    };
+  }
+
+  const child = ownerChild(savedCheckpoint);
+  await child.sendManifestAsync({
+    shouldSkip: () => findExistingSteps() != null,
+    contents() {
+      return { kind: "findFrameSteps", entryPoint };
+    },
+    onFinished: (_, { frameSteps }) => gFrameSteps.push(frameSteps),
+  }, entryPoint);
+
+  steps = findExistingSteps();
+  assert(steps);
+  return steps;
+
+  function findExistingSteps() {
+    // Frame steps will include EnterFrame for both the initial and callee
+    // frames, so the same point can appear in two sets of steps. In this case
+    // the EnterFrame needs to be the first step.
+    if (point.position.kind == "EnterFrame") {
+      return gFrameSteps.find(steps => pointEquals(point, steps[0]));
+    }
+    return gFrameSteps.find(steps => steps.some(p => pointEquals(point, p)));
   }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// Major and Minor Checkpoints
+// Pause State
 ////////////////////////////////////////////////////////////////////////////////
 
-// For each checkpoint N, this vector keeps track of the time intervals taken
-// for the active child (excluding idle time) to run from N to N+1.
-const gCheckpointTimes = [];
+// The pause mode classifies the current state of the debugger.
+const PauseModes = {
+  // Process is actively recording. gPausePoint is the last point the main child
+  // reached.
+  RUNNING: "RUNNING",
+
+  // gActiveChild is paused at gPausePoint.
+  PAUSED: "PAUSED",
 
-// How much time has elapsed (per gCheckpointTimes) since the last flush or
-// major/minor checkpoint was noted.
-let gTimeSinceLastFlush;
-let gTimeSinceLastMajorCheckpoint = 0;
-let gTimeSinceLastMinorCheckpoint = 0;
+  // gActiveChild is being taken to gPausePoint, after which we will pause.
+  ARRIVING: "ARRIVING",
+
+  // gActiveChild is null, and we are looking for the last breakpoint hit prior
+  // to or following gPausePoint, at which we will pause.
+  RESUMING_BACKWARD: "RESUMING_BACKWARD",
+  RESUMING_FORWARD: "RESUMING_FORWARD",
+};
 
-// The replaying process that was given the last major checkpoint.
-let gLastAssignedMajorCheckpoint;
+// Current pause mode.
+let gPauseMode = PauseModes.RUNNING;
+
+// In PAUSED or ARRIVING mode, the point we are paused at or sending the active child to.
+let gPausePoint = null;
+
+// In PAUSED mode, any debugger requests that have been sent to the child.
+const gDebuggerRequests = [];
 
-function assignMajorCheckpoint(child, checkpointId) {
-  dumpv(`AssignMajorCheckpoint: #${child.id} Checkpoint ${checkpointId}`);
-  child.addMajorCheckpoint(checkpointId);
-  gLastAssignedMajorCheckpoint = child;
-}
+function setPauseState(mode, point, child) {
+  assert(mode);
+  const idString = child ? ` #${child.id}` : "";
+  dumpv(`SetPauseState ${mode} ${JSON.stringify(point)}${idString}`);
 
-function assignMinorCheckpoint(child, checkpointId) {
-  dumpv(`AssignMinorCheckpoint: #${child.id} Checkpoint ${checkpointId}`);
-  child.addMinorCheckpoint(checkpointId);
+  gPauseMode = mode;
+  gPausePoint = point;
+  gActiveChild = child;
+
+  pokeChildrenSoon();
 }
 
-function updateCheckpointTimes(msg) {
-  if (msg.point.checkpoint != gCheckpointTimes.length + 1 ||
-      msg.point.position) {
-    return;
-  }
-  gCheckpointTimes.push(msg.duration);
+// Asynchronously send a child to the specific point and pause the debugger.
+function setReplayingPauseTarget(point) {
+  setPauseState(PauseModes.ARRIVING, point, ownerChild(point.checkpoint));
+  gDebuggerRequests.length = 0;
+
+  findFrameSteps(point);
+}
 
-  if (gActiveChild.recording) {
-    gTimeSinceLastFlush += msg.duration;
+// Synchronously send a child to the specific point and pause.
+function pauseReplayingChild(point) {
+  const child = ownerChild(point.checkpoint);
+
+  do {
+    child.waitUntilPaused();
+  } while (maybeReachPoint(child, point));
+
+  setPauseState(PauseModes.PAUSED, point, child);
 
-    // Occasionally flush while recording so replaying processes stay
-    // reasonably current.
-    if (msg.point.checkpoint == FirstCheckpointId ||
-        gTimeSinceLastFlush >= FlushMs) {
-      if (maybeFlushRecording()) {
-        gTimeSinceLastFlush = 0;
-      }
-    }
-  }
+  findFrameSteps(point);
+}
+
+function sendChildToPausePoint(child) {
+  assert(child.paused && child == gActiveChild);
+
+  switch (gPauseMode) {
+  case PauseModes.PAUSED:
+    assert(pointEquals(child.pausePoint(), gPausePoint));
+    return;
 
-  gTimeSinceLastMajorCheckpoint += msg.duration;
-  gTimeSinceLastMinorCheckpoint += msg.duration;
+  case PauseModes.ARRIVING:
+    if (pointEquals(child.pausePoint(), gPausePoint)) {
+      setPauseState(PauseModes.PAUSED, gPausePoint, gActiveChild);
+      gDebugger._onPause();
+      return;
+    }
+    maybeReachPoint(child, gPausePoint);
+    return;
 
-  if (gTimeSinceLastMajorCheckpoint >= MajorCheckpointMs) {
-    // Alternate back and forth between assigning major checkpoints to the
-    // two replaying processes.
-    const child = otherReplayingChild(gLastAssignedMajorCheckpoint);
-    assignMajorCheckpoint(child, msg.point.checkpoint + 1);
-    gTimeSinceLastMajorCheckpoint = 0;
-  } else if (gTimeSinceLastMinorCheckpoint >= MinorCheckpointMs) {
-    // Assign a minor checkpoint to the process which saved the last major one.
-    assignMinorCheckpoint(gLastAssignedMajorCheckpoint, msg.point.checkpoint + 1);
-    gTimeSinceLastMinorCheckpoint = 0;
+  default:
+    throw new Error(`Unexpected pause mode: ${gPauseMode}`);
   }
 }
 
-// Get the replaying process responsible for saving id when rewinding: the one
-// with the most recent major checkpoint preceding id.
-function replayingChildResponsibleForSavingCheckpoint(id) {
-  assert(gFirstReplayingChild && gSecondReplayingChild);
-  const firstMajor = gFirstReplayingChild.lastMajorCheckpointPreceding(id);
-  const secondMajor = gSecondReplayingChild.lastMajorCheckpointPreceding(id);
-  return (firstMajor < secondMajor)
-         ? gSecondReplayingChild
-         : gFirstReplayingChild;
+// After the debugger resumes, find the point where it should pause next.
+async function finishResume() {
+  assert(gPauseMode == PauseModes.RESUMING_FORWARD ||
+         gPauseMode == PauseModes.RESUMING_BACKWARD);
+  const forward = gPauseMode == PauseModes.RESUMING_FORWARD;
+
+  let startCheckpoint = gPausePoint.checkpoint;
+  if (!forward && !gPausePoint.position) {
+    startCheckpoint--;
+  }
+  startCheckpoint = getSavedCheckpoint(startCheckpoint);
+
+  let checkpoint = startCheckpoint;
+  for (;; forward ? checkpoint++ : checkpoint--) {
+    if (checkpoint == gMainChild.pauseCheckpoint()) {
+      // We searched the entire space forward to the end of the recording and
+      // didn't find any breakpoint hits, so resume recording.
+      assert(forward);
+      setPauseState(PauseModes.RUNNING, null, gMainChild);
+      maybeResumeRecording();
+      return;
+    }
+
+    if (checkpoint == InvalidCheckpointId) {
+      // We searched backward to the beginning of the recording, so restore the
+      // first checkpoint.
+      assert(!forward);
+      setReplayingPauseTarget(checkpointExecutionPoint(FirstCheckpointId));
+      return;
+    }
+
+    if (!gCheckpoints[checkpoint].owner) {
+      continue;
+    }
+
+    let hits = [];
+
+    // Find any breakpoint hits in this region of the recording.
+    for (const bp of gBreakpoints) {
+      if (canFindHits(bp)) {
+        const bphits = await findHits(checkpoint, bp);
+        hits = hits.concat(bphits);
+      }
+    }
+
+    // When there are stepping breakpoints, look for breakpoint hits in the
+    // steps for the current frame.
+    if (checkpoint == startCheckpoint && hasSteppingBreakpoint()) {
+      const steps = await findFrameSteps(gPausePoint);
+      hits = hits.concat(steps.filter(point => {
+        return gBreakpoints.some(bp => positionSubsumes(bp, point.position));
+      }));
+    }
+
+    if (forward) {
+      hits = hits.filter(p => pointPrecedes(gPausePoint, p));
+    } else {
+      hits = hits.filter(p => pointPrecedes(p, gPausePoint));
+    }
+
+    if (hits.length) {
+      // We've found the point where the search should end.
+      hits.sort((a, b) => forward ? pointPrecedes(b, a) : pointPrecedes(a, b));
+      setReplayingPauseTarget(hits[0]);
+      return;
+    }
+  }
+}
+
+// Unpause the active child and asynchronously pause at the next or previous
+// breakpoint hit.
+function resume(forward) {
+  if (gActiveChild.recording) {
+    if (forward) {
+      maybeResumeRecording();
+      return;
+    }
+  }
+  if (gPausePoint.checkpoint == FirstCheckpointId && !gPausePoint.position && !forward) {
+    gDebugger._onPause();
+    return;
+  }
+  setPauseState(forward ? PauseModes.RESUMING_FORWARD : PauseModes.RESUMING_BACKWARD,
+                gActiveChild.pausePoint(), null);
+  finishResume();
+  pokeChildren();
+}
+
+// Synchronously bring the active child to the specified execution point.
+function timeWarp(point) {
+  setReplayingPauseTarget(point);
+  while (gPauseMode != PauseModes.PAUSED) {
+    gActiveChild.waitUntilPaused();
+    pokeChildren();
+  }
+  Services.cpmm.sendAsyncMessage("TimeWarpFinished");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Logpoints
+////////////////////////////////////////////////////////////////////////////////
+
+// All installed logpoints. Logpoints are given to us by the debugger, after
+// which we need to asynchronously send a child to every point where the
+// logpoint's position is reached, evaluate code there and invoke the callback
+// associated with the logpoint.
+const gLogpoints = [];
+
+// Asynchronously invoke a logpoint's callback with all results from hitting
+// the logpoint in the range of the recording covered by checkpoint.
+async function findLogpointHits(checkpoint, { position, text, condition, callback }) {
+  const hits = await findHits(checkpoint, position);
+  const child = ownerChild(checkpoint);
+  for (const point of hits) {
+    await child.sendManifestAsync({
+      contents() {
+        return { kind: "hitLogpoint", text, condition };
+      },
+      onFinished(child, { result }) {
+        if (result) {
+          callback(point, gDebugger._convertCompletionValue(result));
+        }
+        child.divergedFromRecording = true;
+      },
+    }, point);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Saving Recordings
 ////////////////////////////////////////////////////////////////////////////////
 
-// Synchronously flush the recording to disk.
-function flushRecording() {
-  assert(gActiveChild.recording && gActiveChild.paused);
-
-  // All replaying children must be paused while the recording is flushed.
-  for (const child of gChildren) {
-    if (child && !child.recording) {
-      child.waitUntilPaused();
-    }
+// Resume manifests are sent when the main child is sent forward through the
+// recording. Update state according to new data produced by the resume.
+function handleResumeManifestResponse({ point, duration, consoleMessages, scripts }) {
+  if (!point.position) {
+    addCheckpoint(point.checkpoint - 1, duration);
+    getCheckpointInfo(point.checkpoint).point = point;
   }
 
-  gActiveChild.sendFlushRecording();
-
-  // Clear out pauseNeeded state set by any earlier maybeFlushRecording().
-  for (const child of gChildren) {
-    if (child && !child.recording) {
-      child.pauseNeeded = false;
-      child.pokeSoon();
-    }
+  if (gDebugger && gDebugger.onConsoleMessage) {
+    consoleMessages.forEach(msg => gDebugger.onConsoleMessage(msg));
   }
 
-  // After flushing the recording there may be more search results.
-  maybeResumeSearch();
-
-  gLastRecordingCheckpoint = gActiveChild.lastCheckpoint();
-
-  // We now have a usable recording for replaying children.
-  if (!gFirstReplayingChild) {
-    spawnInitialReplayingChildren();
+  if (gDebugger && gDebugger.onNewScript) {
+    scripts.forEach(script => gDebugger.onNewScript(script));
   }
 }
 
-// Get the replaying children to pause, and flush the recording if they already
-// are.
-function maybeFlushRecording() {
-  assert(gActiveChild.recording && gActiveChild.paused);
+// If necessary, continue executing in the main child.
+function maybeResumeRecording() {
+  if (gActiveChild != gMainChild) {
+    return;
+  }
 
-  let allPaused = true;
-  for (const child of gChildren) {
-    if (child && !child.recording) {
-      child.pauseNeeded = true;
-      allPaused &= child.paused;
-    }
+  if (timeSinceCheckpoint(gLastFlushCheckpoint) >= FlushMs) {
+    ensureFlushed();
   }
 
-  if (allPaused) {
-    flushRecording();
-    return true;
+  const checkpoint = gMainChild.pausePoint().checkpoint;
+  if (!gMainChild.recording && checkpoint == gRecordingEndpoint) {
+    ensureFlushed();
+    Services.cpmm.sendAsyncMessage("HitRecordingEndpoint");
+    if (gDebugger) {
+      gDebugger._onPause();
+    }
+    return;
+  }
+  gMainChild.sendManifest({
+    contents: { kind: "resume", breakpoints: gBreakpoints },
+    onFinished(response) {
+      handleResumeManifestResponse(response);
+
+      gPausePoint = gMainChild.pausePoint();
+      if (gDebugger) {
+        gDebugger._onPause();
+      } else {
+        Services.tm.dispatchToMainThread(maybeResumeRecording);
+      }
+    },
+  });
+}
+
+// If necessary, synchronously flush the recording to disk.
+function ensureFlushed() {
+  assert(gActiveChild == gMainChild);
+  gMainChild.waitUntilPaused(true);
+
+  if (gLastFlushCheckpoint == gActiveChild.pauseCheckpoint()) {
+    return;
   }
-  return false;
+
+  if (gMainChild.recording) {
+    gMainChild.sendManifest({
+      contents: { kind: "flushRecording" },
+      onFinished() {},
+    });
+    gMainChild.waitUntilPaused();
+  }
+
+  const oldFlushCheckpoint = gLastFlushCheckpoint || FirstCheckpointId;
+  gLastFlushCheckpoint = gMainChild.pauseCheckpoint();
+
+  // We now have a usable recording for replaying children, so spawn them if
+  // necessary.
+  if (gReplayingChildren.length == 0) {
+    spawnReplayingChildren();
+  }
+
+  // Checkpoints where the recording was flushed to disk are always saved.
+  // This allows the recording to be scanned as soon as it has been flushed.
+  addSavedCheckpoint(gLastFlushCheckpoint);
+
+  // Flushing creates a new region of the recording for replaying children
+  // to scan.
+  forSavedCheckpointsInRange(oldFlushCheckpoint, gLastFlushCheckpoint, checkpoint => {
+    scanRecording(checkpoint);
+
+    // Scan for breakpoint and search hits in this new region.
+    gBreakpoints.forEach(position => findHits(checkpoint, position));
+    gLogpoints.forEach(logpoint => findLogpointHits(checkpoint, logpoint));
+  });
+
+  pokeChildren();
 }
 
 // eslint-disable-next-line no-unused-vars
 function BeforeSaveRecording() {
-  if (gActiveChild.recording) {
-    // The recording might not be up to date, flush it now.
-    gActiveChild.waitUntilPaused(true);
-    flushRecording();
+  if (gActiveChild == gMainChild) {
+    // The recording might not be up to date, ensure it flushes after pausing.
+    ensureFlushed();
   }
 }
 
 // eslint-disable-next-line no-unused-vars
 function AfterSaveRecording() {
   Services.cpmm.sendAsyncMessage("SaveRecordingFinished");
 }
 
+let gRecordingEndpoint;
+
+function setMainChild() {
+  assert(!gMainChild.recording);
+
+  gMainChild.sendManifest({
+    contents: { kind: "setMainChild" },
+    onFinished({ endpoint }) {
+      gRecordingEndpoint = endpoint;
+      Services.tm.dispatchToMainThread(maybeResumeRecording);
+    },
+  });
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Child Management
 ////////////////////////////////////////////////////////////////////////////////
 
-function spawnReplayingChild(role) {
-  const id = RecordReplayControl.spawnReplayingChild();
-  return new ChildProcess(id, false, role);
-}
+// How many replaying children to spawn. This should be a pref instead...
+const NumReplayingChildren = 4;
 
-function spawnInitialReplayingChildren() {
-  gFirstReplayingChild = spawnReplayingChild(gRecordingChild
-                                             ? new ChildRoleStandby()
-                                             : new ChildRoleActive());
-  gSecondReplayingChild = spawnReplayingChild(new ChildRoleStandby());
-
-  assignMajorCheckpoint(gSecondReplayingChild, FirstCheckpointId);
+function spawnReplayingChildren() {
+  for (let i = 0; i < NumReplayingChildren; i++) {
+    const id = RecordReplayControl.spawnReplayingChild();
+    gReplayingChildren[id] = new ChildProcess(id, false);
+  }
+  addSavedCheckpoint(FirstCheckpointId);
 }
 
 // eslint-disable-next-line no-unused-vars
 function Initialize(recordingChildId) {
   try {
     if (recordingChildId != undefined) {
-      gRecordingChild = new ChildProcess(recordingChildId, true,
-                                         new ChildRoleActive());
+      gMainChild = new ChildProcess(recordingChildId, true);
     } else {
       // If there is no recording child, we have now initialized enough state
       // that we can start spawning replaying children.
-      spawnInitialReplayingChildren();
+      const id = RecordReplayControl.spawnReplayingChild();
+      gMainChild = new ChildProcess(id, false);
+      spawnReplayingChildren();
     }
+    gActiveChild = gMainChild;
     return gControl;
   } catch (e) {
     dump(`ERROR: Initialize threw exception: ${e}\n`);
   }
 }
 
 // eslint-disable-next-line no-unused-vars
-function HitExecutionPoint(id, msg) {
+function ManifestFinished(id, response) {
   try {
-    dumpv(`HitExecutionPoint #${id} ${JSON.stringify(msg)}`);
-    gChildren[id].hitExecutionPoint(msg);
+    dumpv(`ManifestFinished #${id} ${JSON.stringify(response)}`);
+    lookupChild(id).manifestFinished(response);
   } catch (e) {
-    dump(`ERROR: HitExecutionPoint threw exception: ${e}\n`);
-  }
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Explicit Pauses
-///////////////////////////////////////////////////////////////////////////////
-
-// At the last time the active child was explicitly paused, the ID of the
-// checkpoint that needs to be saved for the child to rewind.
-let gLastExplicitPause = FirstCheckpointId;
-
-// Returns a checkpoint if the active child is explicitly paused somewhere,
-// has started rewinding after being explicitly paused, or is attempting to
-// warp to an execution point. Standby roles will try to save minor checkpoints
-// in the range from their most recent major checkpoint up to the returned
-// checkpoint.
-function getActiveChildTargetCheckpoint() {
-  if (gActiveChild.rewindTargetCheckpoint() <= gLastExplicitPause) {
-    return gActiveChild.rewindTargetCheckpoint();
+    dump(`ERROR: ManifestFinished threw exception: ${e} ${e.stack}\n`);
   }
-  return undefined;
-}
-
-function markExplicitPause() {
-  assert(gActiveChild.paused);
-  const targetCheckpoint = gActiveChild.rewindTargetCheckpoint();
-
-  if (gActiveChild.recording) {
-    // Make sure any replaying children can play forward to the same point as
-    // the recording.
-    flushRecording();
-  } else if (RecordReplayControl.canRewind()) {
-    // Make sure we have a replaying child that has saved the right checkpoints
-    // for rewinding from this point. Switch to the other one if (a) this process
-    // is responsible for rewinding from this point, and (b) this process has
-    // not saved all minor checkpoints going back to its last major checkpoint.
-    if (gActiveChild ==
-        replayingChildResponsibleForSavingCheckpoint(targetCheckpoint)) {
-      if (!gActiveChild.canRewindFrom(targetCheckpoint)) {
-        switchActiveChild(otherReplayingChild(gActiveChild));
-      }
-    }
-  }
-
-  gLastExplicitPause = targetCheckpoint;
-  dumpv(`MarkActiveChildExplicitPause ${gLastExplicitPause}`);
-
-  pokeChildren();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Debugger Operations
 ////////////////////////////////////////////////////////////////////////////////
 
-function maybeSendRepaintMessage() {
-  // In repaint stress mode, we want to trigger a repaint at every checkpoint,
-  // so before resuming after the child pauses at each checkpoint, send it a
-  // repaint message. There might not be a debugger open, so manually craft the
-  // same message which the debugger would send to trigger a repaint and parse
-  // the result.
-  if (RecordReplayControl.inRepaintStressMode()) {
-    maybeSwitchToReplayingChild();
-    const rv = gActiveChild.sendRequest({ type: "repaint" });
-    if ("width" in rv && "height" in rv) {
-      RecordReplayControl.hadRepaint(rv.width, rv.height);
-    }
-  }
-}
+// From the debugger's perspective, there is a single target to interact with,
+// represented by gActiveChild. The details of the various children the control
+// system is managing are hidden away. This object describes the interface which
+// the debugger uses to access the control system.
+const gControl = {
+  // Get the current point where the active child is paused, or null.
+  pausePoint() {
+    return gActiveChild && gActiveChild.paused ? gActiveChild.pausePoint() : null;
+  },
 
-function waitUntilChildHasSavedCheckpoint(child, checkpoint) {
-  while (true) {
-    child.waitUntilPaused();
-    if (child.hasSavedCheckpoint(checkpoint)) {
-      return;
-    }
-    child.role.poke();
-  }
-}
+  // Return whether the active child is currently recording.
+  childIsRecording() {
+    return gActiveChild && gActiveChild.recording;
+  },
 
-function resume(forward) {
-  assert(gActiveChild.paused);
-
-  maybeSendRepaintMessage();
+  // Ensure the active child is paused.
+  waitUntilPaused() {
+    // The debugger should not use this method while we are actively resuming.
+    assert(gActiveChild);
 
-  if (!forward) {
-    const targetCheckpoint = gActiveChild.rewindTargetCheckpoint();
-
-    // Don't rewind if we are at the beginning of the recording.
-    if (targetCheckpoint == InvalidCheckpointId) {
-      Services.cpmm.sendAsyncMessage("HitRecordingBeginning");
-      gDebugger._onPause(gActiveChild.lastPausePoint);
+    if (gActiveChild == gMainChild) {
+      gActiveChild.waitUntilPaused(true);
       return;
     }
 
-    // Make sure the active child has saved minor checkpoints prior to its
-    // position.
-    const targetChild =
-      replayingChildResponsibleForSavingCheckpoint(targetCheckpoint);
-    if (targetChild == gActiveChild) {
-      // markExplicitPause() should ensure that we are only active if the child
-      // has saved the appropriate minor checkpoints.
-      assert(gActiveChild.canRewindFrom(targetCheckpoint));
-    } else {
-      let saveTarget = targetCheckpoint;
-      while (!targetChild.isMajorCheckpoint(saveTarget) &&
-             !targetChild.isMinorCheckpoint(saveTarget)) {
-        saveTarget--;
-      }
-      waitUntilChildHasSavedCheckpoint(targetChild, saveTarget);
-      switchActiveChild(targetChild);
-    }
-  }
-
-  if (forward) {
-    // Don't send a replaying process past the recording endpoint.
-    if (gActiveChild.lastPauseAtRecordingEndpoint) {
-      // Look for a recording child we can transition into.
-      assert(!gActiveChild.recording);
-      if (!gRecordingChild) {
-        Services.cpmm.sendAsyncMessage("HitRecordingEndpoint");
-        if (gDebugger) {
-          gDebugger._onPause(gActiveChild.lastPausePoint);
-        }
+    while (true) {
+      gActiveChild.waitUntilPaused();
+      if (pointEquals(gActiveChild.pausePoint(), gPausePoint)) {
         return;
       }
+      pokeChild(gActiveChild);
+    }
+  },
 
-      // Switch to the recording child as the active child and continue
-      // execution.
-      switchActiveChild(gRecordingChild);
+  // Add a breakpoint where the active child should pause while resuming.
+  addBreakpoint(position) {
+    gBreakpoints.push(position);
+
+    // Start searching for breakpoint hits in the recording immediately.
+    if (canFindHits(position)) {
+      forSavedCheckpointsInRange(FirstCheckpointId, gLastFlushCheckpoint, checkpoint => {
+        findHits(checkpoint, position);
+      });
     }
 
-    gActiveChild.ensureMajorCheckpointSaved(gActiveChild.lastCheckpoint() + 1);
-
-    // Idle children might change their behavior as we run forward.
-    pokeChildren();
-  }
-
-  gActiveChild.sendResume({ forward });
-}
-
-let gTimeWarpInProgress;
-
-function timeWarp(targetPoint) {
-  assert(gActiveChild.paused);
-  const targetCheckpoint = targetPoint.checkpoint;
-
-  // Find the replaying child responsible for saving the target checkpoint.
-  const targetChild =
-    replayingChildResponsibleForSavingCheckpoint(targetCheckpoint);
-  if (targetChild != gActiveChild) {
-    switchActiveChild(otherReplayingChild(gActiveChild));
-  }
+    if (gActiveChild == gMainChild) {
+      // The recording child will update its breakpoints when it reaches the
+      // next checkpoint, so force it to create a checkpoint now.
+      gActiveChild.waitUntilPaused(true);
+    }
+  },
 
-  // Rewind first if the child is past the warp target or if it is not paused
-  // at a checkpoint. RunToPoint can only be used when the child is at a
-  // checkpoint.
-  let restoreTarget;
-  if (gActiveChild.lastCheckpoint() >= targetCheckpoint) {
-    restoreTarget = targetCheckpoint;
-  } else if (gActiveChild.lastPausePoint.position) {
-    restoreTarget = gActiveChild.lastPausePoint.checkpoint;
-  }
-
-  if (restoreTarget) {
-    while (!gActiveChild.hasSavedCheckpoint(restoreTarget)) {
-      restoreTarget--;
-    }
-
-    assert(!gTimeWarpInProgress);
-    gTimeWarpInProgress = true;
-
-    gActiveChild.sendRestoreCheckpoint(restoreTarget);
-    gActiveChild.waitUntilPaused();
-
-    gTimeWarpInProgress = false;
-  }
-
-  gActiveChild.sendRunToPoint(targetPoint);
-  gActiveChild.waitUntilPaused();
-
-  Services.cpmm.sendAsyncMessage("TimeWarpFinished");
-}
-
-const gControl = {
-  pausePoint() { return gActiveChild.paused ? gActiveChild.lastPausePoint : null; },
-  childIsRecording() { return gActiveChild.recording; },
-  waitUntilPaused() {
-    // Use a loop because the active child can change while running if a
-    // replaying active child hits the end of the recording.
-    while (!gActiveChild.paused) {
+  // Clear all installed breakpoints.
+  clearBreakpoints() {
+    gBreakpoints.length = 0;
+    if (gActiveChild == gMainChild) {
+      // As for addBreakpoint(), update the active breakpoints in the recording
+      // child immediately.
       gActiveChild.waitUntilPaused(true);
     }
   },
-  addBreakpoint(pos) { gActiveChild.sendAddBreakpoint(pos); },
-  clearBreakpoints() { gActiveChild.sendClearBreakpoints(); },
-  sendRequest(request) { return gActiveChild.sendDebuggerRequest(request); },
-  markExplicitPause,
-  maybeSwitchToReplayingChild,
-  resume,
-  timeWarp,
-};
 
-////////////////////////////////////////////////////////////////////////////////
-// Search Operations
-////////////////////////////////////////////////////////////////////////////////
-
-let gSearchChild;
-
-function ChildRoleSearch() {}
-
-ChildRoleSearch.prototype = {
-  name: "Search",
-
-  initialize(child, { startup }) {
-    this.child = child;
+  // Get the last known point in the recording.
+  recordingEndpoint() {
+    return gMainChild.lastPausePoint;
   },
 
-  hitExecutionPoint({ point, recordingEndpoint }) {
-    if (point.position) {
-      gDebugger._onSearchPause(point);
-    }
+  // If the active child is currently recording, switch to a replaying one if
+  // possible.
+  maybeSwitchToReplayingChild() {
+    assert(gActiveChild.paused);
+    if (gActiveChild == gMainChild && RecordReplayControl.canRewind()) {
+      const point = gActiveChild.pausePoint();
 
-    if (!recordingEndpoint) {
-      this.child.pokeSoon();
+      if (point.position) {
+        // We can only flush the recording at checkpoints, so we need to send the
+        // main child forward and pause/flush ASAP.
+        gMainChild.sendManifest({
+          contents: { kind: "resume", breakpoints: [] },
+          onFinished(response) {
+            handleResumeManifestResponse(response);
+          },
+        });
+        gMainChild.waitUntilPaused(true);
+      }
+
+      ensureFlushed();
+      pauseReplayingChild(point);
     }
   },
 
-  poke() {
-    if (!this.child.pauseNeeded) {
-      this.child.sendResume({ forward: true });
-    }
-  },
-};
-
-function ensureHasSearchChild() {
-  if (!gSearchChild) {
-    gSearchChild = spawnReplayingChild(new ChildRoleSearch());
-  }
-}
+  // Synchronously send a debugger request to a paused active child, returning
+  // the response.
+  sendRequest(request) {
+    let data;
+    gActiveChild.sendManifest({
+      contents: { kind: "debuggerRequest", request },
+      onFinished(finishData) { data = finishData; },
+    });
+    gActiveChild.waitUntilPaused();
 
-function maybeResumeSearch() {
-  if (gSearchChild && gSearchChild.paused) {
-    gSearchChild.sendResume({ forward: true });
-  }
-}
+    if (data.restoredCheckpoint) {
+      // The child had an unhandled recording diverge and restored an earlier
+      // checkpoint. Restore the child to the point it should be paused at and
+      // fill its paused state back in by resending earlier debugger requests.
+      pauseReplayingChild(gPausePoint);
+      gActiveChild.sendManifest({
+        contents: { kind: "batchDebuggerRequest", requests: gDebuggerRequests },
+        onFinished(finishData) { assert(!finishData.restoredCheckpoint); },
+      });
+      gActiveChild.waitUntilPaused();
+      return { unhandledDivergence: true };
+    }
 
-const gSearchControl = {
-  reset() {
-    ensureHasSearchChild();
-    gSearchChild.waitUntilPaused();
+    if (data.divergedFromRecording) {
+      // Remember whether the child diverged from the recording.
+      gActiveChild.divergedFromRecording = true;
+    }
 
-    if (gSearchChild.lastPausePoint.checkpoint != FirstCheckpointId ||
-        gSearchChild.lastPausePoint.position) {
-      gSearchChild.sendRestoreCheckpoint(FirstCheckpointId);
-      gSearchChild.waitUntilPaused();
-    }
-    gSearchChild.sendClearBreakpoints();
-    gDebugger._forEachSearch(pos => gSearchChild.sendAddBreakpoint(pos));
-    gSearchChild.sendResume({ forward: true });
+    gDebuggerRequests.push(request);
+    return data.response;
   },
 
-  sendRequest(request) { return gSearchChild.sendDebuggerRequest(request); },
+  // Synchronously send a debugger request to the main child, which will always
+  // be at the end of the recording and can receive requests even when the
+  // active child is not currently paused.
+  sendRequestMainChild(request) {
+    gMainChild.waitUntilPaused(true);
+    let data;
+    gMainChild.sendManifest({
+      contents: { kind: "debuggerRequest", request },
+      onFinished(finishData) { data = finishData; },
+    });
+    gMainChild.waitUntilPaused();
+    assert(!data.restoredCheckpoint && !data.divergedFromRecording);
+    return data.response;
+  },
+
+  resume,
+  timeWarp,
+
+  // Add a new logpoint.
+  addLogpoint(logpoint) {
+    gLogpoints.push(logpoint);
+    forSavedCheckpointsInRange(FirstCheckpointId, gLastFlushCheckpoint,
+                               checkpoint => findLogpointHits(checkpoint, logpoint));
+  },
 };
 
 ///////////////////////////////////////////////////////////////////////////////
 // Utilities
 ///////////////////////////////////////////////////////////////////////////////
 
 // eslint-disable-next-line no-unused-vars
 function ConnectDebugger(dbg) {
   gDebugger = dbg;
   dbg._control = gControl;
-  dbg._searchControl = gSearchControl;
 }
 
 function dumpv(str) {
-  //dump("[ReplayControl] " + str + "\n");
+  //dump(`[ReplayControl] ${str}\n`);
 }
 
 function assert(v) {
   if (!v) {
     ThrowError("Assertion Failed!");
   }
 }
 
 function ThrowError(msg)
 {
   const error = new Error(msg);
-  dump("ReplayControl Server Error: " + msg + " Stack: " + error.stack + "\n");
+  dump(`ReplayControl Server Error: ${msg} Stack: ${error.stack}\n`);
   throw error;
 }
 
 // eslint-disable-next-line no-unused-vars
 var EXPORTED_SYMBOLS = [
   "Initialize",
   "ConnectDebugger",
-  "HitExecutionPoint",
+  "ManifestFinished",
   "BeforeSaveRecording",
   "AfterSaveRecording",
 ];
--- a/devtools/server/actors/replay/debugger.js
+++ b/devtools/server/actors/replay/debugger.js
@@ -15,16 +15,20 @@
 // created in the middleman process, and describe things that exist in the
 // recording/replaying process, inspecting them via the interface provided by
 // control.js.
 
 "use strict";
 
 const RecordReplayControl = !isWorker && require("RecordReplayControl");
 const Services = require("Services");
+const ChromeUtils = require("ChromeUtils");
+
+ChromeUtils.defineModuleGetter(this, "positionSubsumes",
+                               "resource://devtools/shared/execution-point-utils.js");
 
 ///////////////////////////////////////////////////////////////////////////////
 // ReplayDebugger
 ///////////////////////////////////////////////////////////////////////////////
 
 // Possible preferred directions of travel.
 const Direction = {
   FORWARD: "FORWARD",
@@ -37,17 +41,16 @@ function ReplayDebugger() {
   if (existing) {
     // There is already a ReplayDebugger in existence, use that. There can only
     // be one ReplayDebugger in the process.
     return existing;
   }
 
   // We should have been connected to control.js by the call above.
   assert(this._control);
-  assert(this._searchControl);
 
   // Preferred direction of travel when not explicitly resumed.
   this._direction = Direction.NONE;
 
   // All breakpoint positions and handlers installed by this debugger.
   this._breakpoints = [];
 
   // All ReplayDebuggerFramees that have been created while paused at the
@@ -71,19 +74,16 @@ function ReplayDebugger() {
 
   // Flag set if the dispatched _performPause() call can be ignored because the
   // server entered a thread-wide pause first.
   this._cancelPerformPause = false;
 
   // After we are done pausing, callback describing how to resume.
   this._resumeCallback = null;
 
-  // Information about all searches that exist.
-  this._searches = [];
-
   // Handler called when hitting the beginning/end of the recording, or when
   // a time warp target has been reached.
   this.replayingOnForcedPause = null;
 
   // Handler called when the child pauses for any reason.
   this.replayingOnPositionChange = null;
 }
 
@@ -96,62 +96,76 @@ ReplayDebugger.prototype = {
   // General methods
   /////////////////////////////////////////////////////////
 
   replaying: true,
 
   canRewind: RecordReplayControl.canRewind,
 
   replayCurrentExecutionPoint() {
-    assert(this._paused);
+    this._ensurePaused();
     return this._control.pausePoint();
   },
 
   replayRecordingEndpoint() {
-    return this._sendRequest({ type: "recordingEndpoint" });
+    return this._control.recordingEndpoint();
   },
 
   replayIsRecording() {
     return this._control.childIsRecording();
   },
 
   addDebuggee() {},
   removeAllDebuggees() {},
 
   replayingContent(url) {
-    this._ensurePaused();
-    return this._sendRequest({ type: "getContent", url });
+    return this._sendRequestMainChild({ type: "getContent", url });
+  },
+
+  _processResponse(request, response, divergeResponse) {
+    dumpv(`SendRequest: ${JSON.stringify(request)} -> ${JSON.stringify(response)}`);
+    if (response.exception) {
+      ThrowError(response.exception);
+    }
+    if (response.unhandledDivergence) {
+      if (divergeResponse) {
+        return divergeResponse;
+      }
+      ThrowError(`Unhandled recording divergence in ${request.type}`);
+    }
+    return response;
   },
 
   // Send a request object to the child process, and synchronously wait for it
-  // to respond.
-  _sendRequest(request) {
-    const data = this._control.sendRequest(request);
-    dumpv("SendRequest: " +
-          JSON.stringify(request) + " -> " + JSON.stringify(data));
-    if (data.exception) {
-      ThrowError(data.exception);
-    }
-    return data;
+  // to respond. divergeResponse must be specified for requests that can diverge
+  // from the recording and which we want to recover gracefully.
+  _sendRequest(request, divergeResponse) {
+    const response = this._control.sendRequest(request);
+    return this._processResponse(request, response, divergeResponse);
   },
 
   // Send a request that requires the child process to perform actions that
   // diverge from the recording. In such cases we want to be interacting with a
   // replaying process (if there is one), as recording child processes won't
   // provide useful responses to such requests.
-  _sendRequestAllowDiverge(request) {
+  _sendRequestAllowDiverge(request, divergeResponse) {
     this._control.maybeSwitchToReplayingChild();
-    return this._sendRequest(request);
+    return this._sendRequest(request, divergeResponse);
+  },
+
+  _sendRequestMainChild(request) {
+    const response = this._control.sendRequestMainChild(request);
+    return this._processResponse(request, response);
   },
 
   // Update graphics according to the current state of the child process. This
   // should be done anytime we pause and allow the user to interact with the
   // debugger.
   _repaint() {
-    const rv = this._sendRequestAllowDiverge({ type: "repaint" });
+    const rv = this._sendRequestAllowDiverge({ type: "repaint" }, {});
     if ("width" in rv && "height" in rv) {
       RecordReplayControl.hadRepaint(rv.width, rv.height);
     } else {
       RecordReplayControl.hadRepaintFailure();
     }
   },
 
   /////////////////////////////////////////////////////////
@@ -255,17 +269,17 @@ ReplayDebugger.prototype = {
     const point = this.replayCurrentExecutionPoint();
     dumpv("PerformPause " + JSON.stringify(point));
 
     if (!point.position) {
       // We paused at a checkpoint, and there are no handlers to call.
     } else {
       // Call any handlers for this point, unless one resumes execution.
       for (const { handler, position } of this._breakpoints) {
-        if (RecordReplayControl.positionSubsumes(position, point.position)) {
+        if (positionSubsumes(position, point.position)) {
           handler();
           assert(!this._threadPauseCount);
           if (this._resumeCallback) {
             break;
           }
         }
       }
     }
@@ -292,19 +306,16 @@ ReplayDebugger.prototype = {
   },
 
   replayPushThreadPause() {
     // The thread has paused so that the user can interact with it. The child
     // will stay paused until this thread-wide pause has been popped.
     assert(this._paused);
     assert(!this._resumeCallback);
     if (++this._threadPauseCount == 1) {
-      // Save checkpoints near the current position in case the user rewinds.
-      this._control.markExplicitPause();
-
       // There is no preferred direction of travel after an explicit pause.
       this._direction = Direction.NONE;
 
       // Update graphics according to the current state of the child.
       this._repaint();
 
       // If breakpoint handlers for the pause haven't been called yet, don't
       // call them at all.
@@ -391,88 +402,31 @@ ReplayDebugger.prototype = {
       this._getObject(data.id)._names = names;
     }
 
     for (const frame of pauseData.frames) {
       this._frames[frame.index] = new ReplayDebuggerFrame(this, frame);
     }
   },
 
-  /////////////////////////////////////////////////////////
-  // Search management
-  /////////////////////////////////////////////////////////
-
-  _forEachSearch(callback) {
-    for (const { position } of this._searches) {
-      callback(position);
-    }
-  },
-
   _virtualConsoleLog(position, text, condition, callback) {
-    this._searches.push({ position, text, condition, callback, results: [] });
-    this._searchControl.reset();
-  },
-
-  _evaluateVirtualConsoleLog(search) {
-    const frameData = this._searchControl.sendRequest({
-      type: "getFrame",
-      index: NewestFrameIndex,
-    });
-    if (!("index" in frameData)) {
-      return null;
-    }
-    if (search.condition) {
-      const rv = this._searchControl.sendRequest({
-        type: "frameEvaluate",
-        index: frameData.index,
-        text: search.condition,
-        convertOptions: { snapshot: true },
-      });
-      const crv = this._convertCompletionValue(rv);
-      if ("return" in crv && !crv.return) {
-        return null;
-      }
-    }
-    const rv = this._searchControl.sendRequest({
-      type: "frameEvaluate",
-      index: frameData.index,
-      text: search.text,
-      convertOptions: { snapshot: true },
-    });
-    return this._convertCompletionValue(rv);
-  },
-
-  _onSearchPause(point) {
-    for (const search of this._searches) {
-      if (RecordReplayControl.positionSubsumes(search.position, point.position)) {
-        if (!search.results.some(existing => point.progress == existing.progress)) {
-          search.results.push(point);
-
-          const evaluateResult = this._evaluateVirtualConsoleLog(search);
-          if (evaluateResult) {
-            search.callback(point, evaluateResult);
-          }
-        }
-      }
-    }
+    this._control.addLogpoint({ position, text, condition, callback });
   },
 
   /////////////////////////////////////////////////////////
   // Breakpoint management
   /////////////////////////////////////////////////////////
 
   _setBreakpoint(handler, position, data) {
-    this._ensurePaused();
     dumpv("AddBreakpoint " + JSON.stringify(position));
     this._control.addBreakpoint(position);
     this._breakpoints.push({handler, position, data});
   },
 
   _clearMatchingBreakpoints(callback) {
-    this._ensurePaused();
     const newBreakpoints = this._breakpoints.filter(bp => !callback(bp));
     if (newBreakpoints.length != this._breakpoints.length) {
       dumpv("ClearBreakpoints");
       this._control.clearBreakpoints();
       for (const { position } of newBreakpoints) {
         dumpv("AddBreakpoint " + JSON.stringify(position));
         this._control.addBreakpoint(position);
       }
@@ -535,36 +489,36 @@ ReplayDebugger.prototype = {
     return this._scripts[data.id];
   },
 
   _convertScriptQuery(query) {
     // Make a copy of the query, converting properties referring to debugger
     // things into their associated ids.
     const rv = Object.assign({}, query);
     if ("global" in query) {
-      rv.global = query.global._data.id;
+      // Script queries might be sent to a different process from the one which
+      // is paused at the current point and which we are interacting with.
+      NYI();
     }
     if ("source" in query) {
       rv.source = query.source._data.id;
     }
     return rv;
   },
 
   findScripts(query) {
-    this._ensurePaused();
-    const data = this._sendRequest({
+    const data = this._sendRequestMainChild({
       type: "findScripts",
       query: this._convertScriptQuery(query),
     });
     return data.map(script => this._addScript(script));
   },
 
   findAllConsoleMessages() {
-    this._ensurePaused();
-    const messages = this._sendRequest({ type: "findConsoleMessages" });
+    const messages = this._sendRequestMainChild({ type: "findConsoleMessages" });
     return messages.map(this._convertConsoleMessage.bind(this));
   },
 
   /////////////////////////////////////////////////////////
   // ScriptSource methods
   /////////////////////////////////////////////////////////
 
   _getSource(id) {
@@ -578,18 +532,17 @@ ReplayDebugger.prototype = {
   _addSource(data) {
     if (!this._scriptSources[data.id]) {
       this._scriptSources[data.id] = new ReplayDebuggerScriptSource(this, data);
     }
     return this._scriptSources[data.id];
   },
 
   findSources() {
-    this._ensurePaused();
-    const data = this._sendRequest({ type: "findSources" });
+    const data = this._sendRequestMainChild({ type: "findSources" });
     return data.map(source => this._addSource(source));
   },
 
   adoptSource(source) {
     assert(source._dbg == this);
     return source;
   },
 
@@ -713,64 +666,22 @@ ReplayDebugger.prototype = {
     }
     return message;
   },
 
   /////////////////////////////////////////////////////////
   // Handlers
   /////////////////////////////////////////////////////////
 
-  _getNewScript() {
-    return this._addScript(this._sendRequest({ type: "getNewScript" }));
-  },
-
-  get onNewScript() { return this._breakpointKindGetter("NewScript"); },
-  set onNewScript(handler) {
-    this._breakpointKindSetter("NewScript", handler,
-                               () => handler.call(this, this._getNewScript()));
-  },
-
   get onEnterFrame() { return this._breakpointKindGetter("EnterFrame"); },
   set onEnterFrame(handler) {
     this._breakpointKindSetter("EnterFrame", handler,
                                () => { handler.call(this, this.getNewestFrame()); });
   },
 
-  get replayingOnPopFrame() {
-    return this._searchBreakpoints(({position, data}) => {
-      return (position.kind == "OnPop" && !position.script) ? data : null;
-    });
-  },
-
-  set replayingOnPopFrame(handler) {
-    if (handler) {
-      this._setBreakpoint(() => {
-        this._capturePauseData();
-        handler.call(this, this.getNewestFrame());
-      }, { kind: "OnPop" }, handler);
-    } else {
-      this._clearMatchingBreakpoints(({position}) => {
-        return position.kind == "OnPop" && !position.script;
-      });
-    }
-  },
-
-  getNewConsoleMessage() {
-    const message = this._sendRequest({ type: "getNewConsoleMessage" });
-    return this._convertConsoleMessage(message);
-  },
-
-  get onConsoleMessage() {
-    return this._breakpointKindGetter("ConsoleMessage");
-  },
-  set onConsoleMessage(handler) {
-    this._breakpointKindSetter("ConsoleMessage", handler,
-                               () => handler.call(this, this.getNewConsoleMessage()));
-  },
-
   clearAllBreakpoints: NYI,
 
 }; // ReplayDebugger.prototype
 
 ///////////////////////////////////////////////////////////////////////////////
 // ReplayDebuggerScript
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -786,18 +697,17 @@ ReplayDebuggerScript.prototype = {
   get startLine() { return this._data.startLine; },
   get lineCount() { return this._data.lineCount; },
   get source() { return this._dbg._getSource(this._data.sourceId); },
   get sourceStart() { return this._data.sourceStart; },
   get sourceLength() { return this._data.sourceLength; },
   get format() { return this._data.format; },
 
   _forward(type, value) {
-    this._dbg._ensurePaused();
-    return this._dbg._sendRequest({ type, id: this._data.id, value });
+    return this._dbg._sendRequestMainChild({ type, id: this._data.id, value });
   },
 
   getLineOffsets(line) { return this._forward("getLineOffsets", line); },
   getOffsetLocation(pc) { return this._forward("getOffsetLocation", pc); },
   getSuccessorOffsets(pc) { return this._forward("getSuccessorOffsets", pc); },
   getPredecessorOffsets(pc) { return this._forward("getPredecessorOffsets", pc); },
   getAllColumnOffsets() { return this._forward("getAllColumnOffsets"); },
   getPossibleBreakpoints(query) {
@@ -901,17 +811,17 @@ ReplayDebuggerFrame.prototype = {
   get live() { return true; },
 
   eval(text, options) {
     const rv = this._dbg._sendRequestAllowDiverge({
       type: "frameEvaluate",
       index: this._data.index,
       text,
       options,
-    });
+    }, { throw: "Recording divergence in frameEvaluate" });
     return this._dbg._convertCompletionValue(rv);
   },
 
   _positionMatches(position, kind) {
     return position.kind == kind
         && position.script == this._data.script
         && position.frameIndex == this._data.index;
   },
@@ -1058,17 +968,17 @@ ReplayDebuggerObject.prototype = {
     this._ensureProperties();
     return this._convertPropertyDescriptor(this._properties[name]);
   },
 
   _ensureProperties() {
     if (!this._properties) {
       const id = this._data.id;
       this._properties =
-        this._dbg._sendRequestAllowDiverge({ type: "getObjectProperties", id });
+        this._dbg._sendRequestAllowDiverge({ type: "getObjectProperties", id }, []);
     }
   },
 
   _convertPropertyDescriptor(desc) {
     if (!desc) {
       return undefined;
     }
     const rv = Object.assign({}, desc);
@@ -1128,17 +1038,17 @@ ReplayDebuggerObject.prototype = {
     thisv = this._dbg._convertValueForChild(thisv);
     args = (args || []).map(v => this._dbg._convertValueForChild(v));
 
     const rv = this._dbg._sendRequestAllowDiverge({
       type: "objectApply",
       id: this._data.id,
       thisv,
       args,
-    });
+    }, { throw: "Recording divergence in objectApply" });
     return this._dbg._convertCompletionValue(rv);
   },
 
   get allocationSite() { NYI(); },
   get errorMessageName() { NYI(); },
   get errorNotes() { NYI(); },
   get errorLineNumber() { NYI(); },
   get errorColumnNumber() { NYI(); },
@@ -1198,17 +1108,17 @@ ReplayDebuggerEnvironment.prototype = {
   get callee() { return this._dbg._getObject(this._data.callee); },
   get optimizedOut() { return this._data.optimizedOut; },
 
   _ensureNames() {
     if (!this._names) {
       const names = this._dbg._sendRequestAllowDiverge({
         type: "getEnvironmentNames",
         id: this._data.id,
-      });
+      }, []);
       this._names = {};
       names.forEach(({ name, value }) => {
         this._names[name] = this._dbg._convertValue(value);
       });
     }
   },
 
   names() {
--- a/devtools/server/actors/replay/replay.js
+++ b/devtools/server/actors/replay/replay.js
@@ -1,29 +1,29 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-/* eslint-disable spaced-comment, brace-style, indent-legacy */
+/* eslint-disable spaced-comment, brace-style, indent-legacy, consistent-return */
 
 // This file defines the logic that runs in the record/replay devtools sandbox.
 // This code is loaded into all recording/replaying processes, and responds to
 // requests and other instructions from the middleman via the exported symbols
 // defined at the end of this file.
 //
 // Like all other JavaScript in the recording/replaying process, this code's
 // state is included in memory snapshots and reset when checkpoints are
 // restored. In the process of handling the middleman's requests, however, its
 // state may vary between recording and replaying, or between different
 // replays. As a result, we have to be very careful about performing operations
 // that might interact with the recording --- any time we enter the debuggee
 // and evaluate code or perform other operations.
-// The RecordReplayControl.maybeDivergeFromRecording function should be used at
-// any point where such interactions might occur.
+// The divergeFromRecording function should be used at any point where such
+// interactions might occur.
 // eslint-disable spaced-comment
 
 "use strict";
 
 const CC = Components.Constructor;
 
 // Create a sandbox with the resources we need. require() doesn't work here.
 const sandbox = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")(), {
@@ -65,21 +65,26 @@ dbg.onNewGlobalObject = function(global)
 ///////////////////////////////////////////////////////////////////////////////
 // Utilities
 ///////////////////////////////////////////////////////////////////////////////
 
 const dump = RecordReplayControl.dump;
 
 function assert(v) {
   if (!v) {
-    dump("Assertion Failed: " + (new Error()).stack + "\n");
+    dump(`Assertion Failed: ${Error().stack}\n`);
     throw new Error("Assertion Failed!");
   }
 }
 
+function throwError(v) {
+  dump(`Error: ${v}\n`);
+  throw new Error(v);
+}
+
 // Bidirectional map between objects and IDs.
 function IdMap() {
   this._idToObject = [ undefined ];
   this._objectToId = new Map();
 }
 
 IdMap.prototype = {
   add(object) {
@@ -99,20 +104,16 @@ IdMap.prototype = {
     return this._idToObject[id];
   },
 
   forEach(callback) {
     for (let i = 1; i < this._idToObject.length; i++) {
       callback(i, this._idToObject[i]);
     }
   },
-
-  lastId() {
-    return this._idToObject.length - 1;
-  },
 };
 
 function countScriptFrames() {
   let count = 0;
   let frame = dbg.getNewestFrame();
   while (frame) {
     if (considerScript(frame.script)) {
       count++;
@@ -149,16 +150,19 @@ function isNonNullObject(obj) {
 // this table (like all JS state) is included in snapshots, rolled back when
 // rewinding, and so forth.  In debuggee time, this table only grows (there is
 // no way to remove entries). Scripts created for debugger activity (e.g. eval)
 // are ignored, and off thread compilation is disabled, so this table acquires
 // the same scripts in the same order as we roll back and run forward in the
 // recording.
 const gScripts = new IdMap();
 
+// Any scripts added since the last checkpoint.
+const gNewScripts = [];
+
 function addScript(script) {
   gScripts.add(script);
   script.getChildScripts().forEach(addScript);
 }
 
 // Association between Debugger.ScriptSources and their IDs. As for gScripts,
 // the indices assigned to a script source are consistent across all replays
 // and rewinding.
@@ -191,17 +195,19 @@ dbg.onNewScript = function(script) {
   addScript(script);
   addScriptSource(script.source);
 
   // Each onNewScript call advances the progress counter, to preserve the
   // ProgressCounter invariant when onNewScript is called multiple times
   // without executing any scripts.
   RecordReplayControl.advanceProgressCounter();
 
-  hitGlobalHandler("NewScript");
+  if (gManifest.kind == "resume") {
+    gNewScripts.push(getScriptData(gScripts.getId(script)));
+  }
 
   // Check in case any handlers we need to install are on the scripts just
   // created.
   installPendingHandlers();
 };
 
 ///////////////////////////////////////////////////////////////////////////////
 // Object Snapshots
@@ -246,58 +252,59 @@ function makeObjectSnapshot(object) {
     properties: Object.entries(getObjectProperties(object)).map(snapshotObjectProperty),
   };
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Console Message State
 ///////////////////////////////////////////////////////////////////////////////
 
+// All console messages that have been generated.
 const gConsoleMessages = [];
 
+// Any new console messages since the last checkpoint.
+const gNewConsoleMessages = [];
+
 function newConsoleMessage(messageType, executionPoint, contents) {
-  // Each new console message advances the progress counter, to make sure
-  // that different messages have different progress values.
-  RecordReplayControl.advanceProgressCounter();
-
   if (!executionPoint) {
-    executionPoint =
-      RecordReplayControl.currentExecutionPoint({ kind: "ConsoleMessage" });
+    executionPoint = currentScriptedExecutionPoint();
   }
 
   contents.messageType = messageType;
   contents.executionPoint = executionPoint;
   gConsoleMessages.push(contents);
 
-  hitGlobalHandler("ConsoleMessage");
+  if (gManifest.kind == "resume") {
+    gNewConsoleMessages.push(contents);
+  }
 }
 
 function convertStack(stack) {
   if (stack) {
     const { source, line, column, functionDisplayName } = stack;
     const parent = convertStack(stack.parent);
     return { source, line, column, functionDisplayName, parent };
   }
   return null;
 }
 
+// Map from warp target values attached to messages to the associated execution
+// point.
+const gWarpTargetPoints = [ null ];
+
 // Listen to all console messages in the process.
 Services.console.registerListener({
   QueryInterface: ChromeUtils.generateQI([Ci.nsIConsoleListener]),
 
   observe(message) {
     if (message instanceof Ci.nsIScriptError) {
-      // If there is a warp target associated with the execution point, use
-      // that. This will take users to the point where the error was originally
-      // generated, rather than where it was reported to the console.
-      let executionPoint;
-      if (message.timeWarpTarget) {
-        executionPoint =
-          RecordReplayControl.timeWarpTargetExecutionPoint(message.timeWarpTarget);
-      }
+      // If there is a warp target associated with the error, use that. This
+      // will take users to the point where the error was originally generated,
+      // rather than where it was reported to the console.
+      const executionPoint = gWarpTargetPoints[message.timeWarpTarget];
 
       const contents = JSON.parse(JSON.stringify(message));
       contents.stack = convertStack(message.stack);
       newConsoleMessage("PageError", executionPoint, contents);
     }
   },
 });
 
@@ -321,16 +328,51 @@ Services.obs.addObserver({
         return convertValue(makeDebuggeeValue(v), { snapshot: true });
       });
     }
 
     newConsoleMessage("ConsoleAPI", null, contents);
   },
 }, "console-api-log-event");
 
+// eslint-disable-next-line no-unused-vars
+function NewTimeWarpTarget() {
+  // Create a counter which will be associated with the current scripted
+  // location if we want to warp here later.
+  gWarpTargetPoints.push(currentScriptedExecutionPoint());
+  return gWarpTargetPoints.length - 1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Recording Scanning
+///////////////////////////////////////////////////////////////////////////////
+
+const gScannedScripts = new Set();
+
+function startScanningScript(script) {
+  const id = gScripts.getId(script);
+  const offsets = script.getPossibleBreakpointOffsets();
+  let lastFrame = null, lastFrameIndex = 0;
+  for (const offset of offsets) {
+    const handler = {
+      hit(frame) {
+        let frameIndex;
+        if (frame == lastFrame) {
+          frameIndex = lastFrameIndex;
+        } else {
+          lastFrame = frame;
+          lastFrameIndex = frameIndex = countScriptFrames() - 1;
+        }
+        RecordReplayControl.addScriptHit(id, offset, frameIndex);
+      },
+    };
+    script.setBreakpoint(offset, handler);
+  }
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Position Handler State
 ///////////////////////////////////////////////////////////////////////////////
 
 // Position kinds we are expected to hit.
 let gPositionHandlerKinds = Object.create(null);
 
 // Handlers we tried to install but couldn't due to a script not existing.
@@ -342,59 +384,58 @@ const gPendingPcHandlers = [];
 
 // Script/offset pairs where we have installed a breakpoint handler. We have to
 // avoid installing duplicate handlers here because they will both be called.
 const gInstalledPcHandlers = [];
 
 // Callbacks to test whether a frame should have an OnPop handler.
 const gOnPopFilters = [];
 
-// eslint-disable-next-line no-unused-vars
-function ClearPositionHandlers() {
+function clearPositionHandlers() {
   dbg.clearAllBreakpoints();
   dbg.onEnterFrame = undefined;
 
   gPositionHandlerKinds = Object.create(null);
   gPendingPcHandlers.length = 0;
   gInstalledPcHandlers.length = 0;
   gOnPopFilters.length = 0;
 }
 
 function installPendingHandlers() {
   const pending = gPendingPcHandlers.map(position => position);
   gPendingPcHandlers.length = 0;
 
-  pending.forEach(EnsurePositionHandler);
+  pending.forEach(ensurePositionHandler);
 }
 
 // Hit a position with the specified kind if we are expected to. This is for
 // use with position kinds that have no script/offset/frameIndex information.
-function hitGlobalHandler(kind) {
+function hitGlobalHandler(kind, frame) {
   if (gPositionHandlerKinds[kind]) {
-    RecordReplayControl.positionHit({ kind });
+    positionHit({ kind }, frame);
   }
 }
 
 // The completion state of any frame that is being popped.
 let gPopFrameResult = null;
 
 function onPopFrame(completion) {
   gPopFrameResult = completion;
-  RecordReplayControl.positionHit({
+  positionHit({
     kind: "OnPop",
     script: gScripts.getId(this.script),
     frameIndex: countScriptFrames() - 1,
   });
   gPopFrameResult = null;
 }
 
 function onEnterFrame(frame) {
-  hitGlobalHandler("EnterFrame");
+  if (considerScript(frame.script)) {
+    hitGlobalHandler("EnterFrame", frame);
 
-  if (considerScript(frame.script)) {
     gOnPopFilters.forEach(filter => {
       if (filter(frame)) {
         frame.onPop = onPopFrame;
       }
     });
   }
 }
 
@@ -406,17 +447,17 @@ function addOnPopFilter(filter) {
     }
     frame = frame.older;
   }
 
   gOnPopFilters.push(filter);
   dbg.onEnterFrame = onEnterFrame;
 }
 
-function EnsurePositionHandler(position) {
+function ensurePositionHandler(position) {
   gPositionHandlerKinds[position.kind] = true;
 
   switch (position.kind) {
   case "Break":
   case "OnStep":
     let debugScript;
     if (position.script) {
       debugScript = gScripts.getObject(position.script);
@@ -433,54 +474,36 @@ function EnsurePositionHandler(position)
       return script == position.script && offset == position.offset;
     };
     if (gInstalledPcHandlers.some(match)) {
       return;
     }
     gInstalledPcHandlers.push({ script: position.script, offset: position.offset });
 
     debugScript.setBreakpoint(position.offset, {
-      hit() {
-        RecordReplayControl.positionHit({
+      hit(frame) {
+        positionHit({
           kind: "OnStep",
           script: position.script,
           offset: position.offset,
           frameIndex: countScriptFrames() - 1,
-        });
+        }, frame);
       },
     });
     break;
   case "OnPop":
-    if (position.script) {
-      addOnPopFilter(frame => gScripts.getId(frame.script) == position.script);
-    } else {
-      addOnPopFilter(frame => true);
-    }
+    assert(position.script);
+    addOnPopFilter(frame => gScripts.getId(frame.script) == position.script);
     break;
   case "EnterFrame":
     dbg.onEnterFrame = onEnterFrame;
     break;
   }
 }
 
-// eslint-disable-next-line no-unused-vars
-function GetEntryPosition(position) {
-  if (position.kind == "Break" || position.kind == "OnStep") {
-    const script = gScripts.getObject(position.script);
-    if (script) {
-      return {
-        kind: "Break",
-        script: position.script,
-        offset: script.mainOffset,
-      };
-    }
-  }
-  return null;
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 // Paused State
 ///////////////////////////////////////////////////////////////////////////////
 
 let gPausedObjects = new IdMap();
 let gDereferencedObjects = new Map();
 
 function getObjectId(obj) {
@@ -529,17 +552,17 @@ function convertedValueIsObject(value) {
 
 function convertCompletionValue(value, options) {
   if ("return" in value) {
     return { return: convertValue(value.return, options) };
   }
   if ("throw" in value) {
     return { throw: convertValue(value.throw, options) };
   }
-  throw new Error("Unexpected completion value");
+  throwError("Unexpected completion value");
 }
 
 // Convert a value we received from the parent.
 function convertValueFromParent(value) {
   if (isNonNullObject(value)) {
     if (value.object) {
       return gPausedObjects.getObject(value.object);
     }
@@ -581,31 +604,338 @@ function getDebuggeeValue(value) {
 
 // eslint-disable-next-line no-unused-vars
 function ClearPausedState() {
   gPausedObjects = new IdMap();
   gDereferencedObjects = new Map();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+// Manifest Management
+///////////////////////////////////////////////////////////////////////////////
+
+// The manifest that is currently being processed.
+let gManifest;
+
+// When processing "resume" manifests this tracks the execution time when we
+// started execution from the initial checkpoint.
+let gTimeWhenResuming;
+
+// Handlers that run when a manifest is first received. This must be specified
+// for all manifests.
+const gManifestStartHandlers = {
+  resume({ breakpoints }) {
+    RecordReplayControl.resumeExecution();
+    gTimeWhenResuming = RecordReplayControl.currentExecutionTime();
+    breakpoints.forEach(ensurePositionHandler);
+  },
+
+  restoreCheckpoint({ target }) {
+    RecordReplayControl.restoreCheckpoint(target);
+    throwError("Unreachable!");
+  },
+
+  runToPoint({ needSaveCheckpoints }) {
+    for (const checkpoint of needSaveCheckpoints) {
+      RecordReplayControl.saveCheckpoint(checkpoint);
+    }
+    RecordReplayControl.resumeExecution();
+  },
+
+  scanRecording(manifest) {
+    gManifestStartHandlers.runToPoint(manifest);
+  },
+
+  findHits({ position, startpoint, endpoint }) {
+    const { kind, script, offset, frameIndex: bpFrameIndex } = position;
+    const hits = [];
+    const allHits = RecordReplayControl.findScriptHits(script, offset);
+    for (const { checkpoint, progress, frameIndex } of allHits) {
+      if (checkpoint >= startpoint && checkpoint < endpoint) {
+        switch (kind) {
+        case "OnStep":
+          if (bpFrameIndex != frameIndex) {
+            continue;
+          }
+          // FALLTHROUGH
+        case "Break":
+          hits.push({
+            checkpoint,
+            progress,
+            position: { kind: "OnStep", script, offset, frameIndex },
+          });
+        }
+      }
+    }
+    RecordReplayControl.manifestFinished(hits);
+  },
+
+  findFrameSteps({ entryPoint }) {
+    assert(entryPoint.position.kind == "EnterFrame");
+    const frameIndex = countScriptFrames() - 1;
+    const script = getFrameData(frameIndex).script;
+    const offsets = gScripts.getObject(script).getPossibleBreakpointOffsets();
+    for (const offset of offsets) {
+      ensurePositionHandler({ kind: "OnStep", script, offset, frameIndex });
+    }
+    ensurePositionHandler({ kind: "EnterFrame" });
+    ensurePositionHandler({ kind: "OnPop", script, frameIndex });
+
+    gFrameSteps = [ entryPoint ];
+    gFrameStepsFrameIndex = frameIndex;
+    RecordReplayControl.resumeExecution();
+  },
+
+  flushRecording() {
+    RecordReplayControl.flushRecording();
+    RecordReplayControl.manifestFinished();
+  },
+
+  setMainChild() {
+    const endpoint = RecordReplayControl.setMainChild();
+    RecordReplayControl.manifestFinished({ endpoint });
+  },
+
+  debuggerRequest({ request }) {
+    const response = processRequest(request);
+    RecordReplayControl.manifestFinished({
+      response,
+      divergedFromRecording: gDivergedFromRecording,
+    });
+  },
+
+  batchDebuggerRequest({ requests }) {
+    for (const request of requests) {
+      processRequest(request);
+    }
+    RecordReplayControl.manifestFinished();
+  },
+
+  hitLogpoint({ text, condition }) {
+    divergeFromRecording();
+
+    const frame = scriptFrameForIndex(countScriptFrames() - 1);
+    if (condition) {
+      const crv = frame.eval(condition);
+      if ("return" in crv && !crv.return) {
+        RecordReplayControl.manifestFinished({ result: null });
+        return;
+      }
+    }
+
+    const rv = frame.eval(text);
+    const converted = convertCompletionValue(rv, { snapshot: true });
+    RecordReplayControl.manifestFinished({ result: converted });
+  },
+};
+
+// eslint-disable-next-line no-unused-vars
+function ManifestStart(manifest) {
+  try {
+    gManifest = manifest;
+
+    if (gManifestStartHandlers[manifest.kind]) {
+      gManifestStartHandlers[manifest.kind](manifest);
+    } else {
+      dump(`Unknown manifest: ${JSON.stringify(manifest)}\n`);
+    }
+  } catch (e) {
+    printError("ManifestStart", e);
+  }
+}
+
+// eslint-disable-next-line no-unused-vars
+function BeforeCheckpoint() {
+  clearPositionHandlers();
+  gScannedScripts.clear();
+}
+
+const FirstCheckpointId = 1;
+
+// The most recent encountered checkpoint.
+let gLastCheckpoint;
+
+function currentExecutionPoint(position) {
+  const checkpoint = gLastCheckpoint;
+  const progress = RecordReplayControl.progressCounter();
+  return { checkpoint, progress, position };
+}
+
+function currentScriptedExecutionPoint() {
+  const numFrames = countScriptFrames();
+  if (!numFrames) {
+    return null;
+  }
+  const frame = getFrameData(numFrames - 1);
+  return currentExecutionPoint({
+    kind: "OnStep",
+    script: frame.script,
+    offset: frame.offset,
+    frameIndex: frame.index,
+  });
+}
+
+// Handlers that run after a checkpoint is reached to see if the manifest has
+// finished. This does not need to be specified for all manifests.
+const gManifestFinishedAfterCheckpointHandlers = {
+  resume(_, point) {
+    RecordReplayControl.manifestFinished({
+      point,
+      duration: RecordReplayControl.currentExecutionTime() - gTimeWhenResuming,
+      consoleMessages: gNewConsoleMessages,
+      scripts: gNewScripts,
+    });
+    gNewConsoleMessages.length = 0;
+    gNewScripts.length = 0;
+  },
+
+  runToPoint({ endpoint }, point) {
+    if (!endpoint.position && point.checkpoint == endpoint.checkpoint) {
+      RecordReplayControl.manifestFinished({ point });
+    }
+  },
+
+  scanRecording({ endpoint }, point) {
+    if (point.checkpoint == endpoint) {
+      RecordReplayControl.manifestFinished({ point });
+    }
+  },
+};
+
+// Handlers that run after a checkpoint is reached and before execution resumes.
+// This does not need to be specified for all manifests. This is specified
+// separately from gManifestFinishedAfterCheckpointHandlers to ensure that if
+// we finish a manifest after the checkpoint and then start a new one, that new
+// one is able to prepare to execute. These handlers must therefore not finish
+// the current manifest.
+const gManifestPrepareAfterCheckpointHandlers = {
+  runToPoint({ endpoint }, point) {
+    if (point.checkpoint == endpoint.checkpoint) {
+      assert(endpoint.position);
+      ensurePositionHandler(endpoint.position);
+    }
+  },
+
+  scanRecording() {
+    dbg.onEnterFrame = frame => {
+      if (considerScript(frame.script) && !gScannedScripts.has(frame.script)) {
+        startScanningScript(frame.script);
+        gScannedScripts.add(frame.script);
+      }
+    };
+  },
+};
+
+function processManifestAfterCheckpoint(point, restoredCheckpoint) {
+  // After rewinding gManifest won't be correct, so we always mark the current
+  // manifest as finished and rely on the middleman to give us a new one.
+  if (restoredCheckpoint) {
+    RecordReplayControl.manifestFinished({
+      restoredCheckpoint,
+      point: currentExecutionPoint(),
+    });
+  }
+
+  if (!gManifest) {
+    // The process is considered to have an initial manifest to run forward to
+    // the first checkpoint.
+    assert(point.checkpoint == FirstCheckpointId);
+    RecordReplayControl.manifestFinished({ point });
+    assert(gManifest);
+  } else if (gManifestFinishedAfterCheckpointHandlers[gManifest.kind]) {
+    gManifestFinishedAfterCheckpointHandlers[gManifest.kind](gManifest, point);
+  }
+
+  if (gManifestPrepareAfterCheckpointHandlers[gManifest.kind]) {
+    gManifestPrepareAfterCheckpointHandlers[gManifest.kind](gManifest, point);
+  }
+}
+
+// eslint-disable-next-line no-unused-vars
+function AfterCheckpoint(id, restoredCheckpoint) {
+  gLastCheckpoint = id;
+  const point = currentExecutionPoint();
+
+  try {
+    processManifestAfterCheckpoint(point, restoredCheckpoint);
+  } catch (e) {
+    printError("AfterCheckpoint", e);
+  }
+}
+
+// In the findFrameSteps manifest, all steps that have been found.
+let gFrameSteps = null;
+
+let gFrameStepsFrameIndex = 0;
+
+// Handlers that run after reaching a position watched by ensurePositionHandler.
+// This must be specified for any manifest that uses ensurePositionHandler.
+const gManifestPositionHandlers = {
+  resume(manifest, point) {
+    RecordReplayControl.manifestFinished({
+      point,
+      consoleMessages: gNewConsoleMessages,
+      scripts: gNewScripts,
+    });
+  },
+
+  runToPoint({ endpoint }, point) {
+    if (point.progress == endpoint.progress &&
+        point.position.frameIndex == endpoint.position.frameIndex) {
+      clearPositionHandlers();
+      RecordReplayControl.manifestFinished({ point });
+    }
+  },
+
+  findFrameSteps(_, point) {
+    switch (point.position.kind) {
+    case "OnStep":
+      gFrameSteps.push(point);
+      break;
+    case "EnterFrame":
+      if (countScriptFrames() == gFrameStepsFrameIndex + 2) {
+        gFrameSteps.push(point);
+      }
+      break;
+    case "OnPop":
+      gFrameSteps.push(point);
+      clearPositionHandlers();
+      RecordReplayControl.manifestFinished({ point, frameSteps: gFrameSteps });
+      break;
+    }
+  },
+};
+
+function positionHit(position, frame) {
+  const point = currentExecutionPoint(position);
+
+  if (gManifestPositionHandlers[gManifest.kind]) {
+    gManifestPositionHandlers[gManifest.kind](gManifest, point);
+  } else {
+    throwError(`Unexpected manifest in positionHit: ${gManifest.kind}`);
+  }
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // Handler Helpers
 ///////////////////////////////////////////////////////////////////////////////
 
 function getScriptData(id) {
   const script = gScripts.getObject(id);
   return {
     id,
     sourceId: gScriptSources.getId(script.source),
     startLine: script.startLine,
     lineCount: script.lineCount,
     sourceStart: script.sourceStart,
     sourceLength: script.sourceLength,
     displayName: script.displayName,
     url: script.url,
     format: script.format,
+    firstBreakpointOffset: script.getPossibleBreakpointOffsets()[0],
   };
 }
 
 function getSourceData(id) {
   const source = gScriptSources.getObject(id);
   const introductionScript = gScripts.getId(source.introductionScript);
   return {
     id: id,
@@ -700,17 +1030,17 @@ function getObjectData(id) {
       kind: "Environment",
       type: object.type,
       parent: getObjectId(object.parent),
       object: object.type == "declarative" ? 0 : getObjectId(object.object),
       callee: getObjectId(object.callee),
       optimizedOut: object.optimizedOut,
     };
   }
-  throw new Error("Unknown object kind");
+  throwError("Unknown object kind");
 }
 
 function getObjectProperties(object) {
   let names;
   try {
     names = object.getOwnPropertyNames();
   } catch (e) {
     return unknownObjectProperties(e.toString());
@@ -916,22 +1246,29 @@ function getPauseData() {
 
   return rv;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Handlers
 ///////////////////////////////////////////////////////////////////////////////
 
+let gDivergedFromRecording = false;
+
+function divergeFromRecording() {
+  RecordReplayControl.divergeFromRecording();
+
+  // This flag can only be unset when we rewind.
+  gDivergedFromRecording = true;
+}
+
 const gRequestHandlers = {
 
   repaint() {
-    if (!RecordReplayControl.maybeDivergeFromRecording()) {
-      return {};
-    }
+    divergeFromRecording();
     return RecordReplayControl.repaint();
   },
 
   /////////////////////////////////////////////////////////
   // Debugger Requests
   /////////////////////////////////////////////////////////
 
   findScripts(request) {
@@ -954,20 +1291,16 @@ const gRequestHandlers = {
     });
     return rv;
   },
 
   getScript(request) {
     return getScriptData(request.id);
   },
 
-  getNewScript(request) {
-    return getScriptData(gScripts.lastId());
-  },
-
   getContent(request) {
     return RecordReplayControl.getContent(request.url);
   },
 
   findSources(request) {
     const sources = [];
     gScriptSources.forEach((id) => {
       sources.push(getSourceData(id));
@@ -979,41 +1312,32 @@ const gRequestHandlers = {
     return getSourceData(request.id);
   },
 
   getObject(request) {
     return getObjectData(request.id);
   },
 
   getObjectProperties(request) {
-    if (!RecordReplayControl.maybeDivergeFromRecording()) {
-      return unknownObjectProperties("Recording divergence in getObjectProperties");
-    }
-
+    divergeFromRecording();
     const object = gPausedObjects.getObject(request.id);
     return getObjectProperties(object);
   },
 
   objectApply(request) {
-    if (!RecordReplayControl.maybeDivergeFromRecording()) {
-      return { throw: "Recording divergence in objectApply" };
-    }
+    divergeFromRecording();
     const obj = gPausedObjects.getObject(request.id);
     const thisv = convertValueFromParent(request.thisv);
     const args = request.args.map(v => convertValueFromParent(v));
     const rv = obj.apply(thisv, args);
     return convertCompletionValue(rv);
   },
 
   getEnvironmentNames(request) {
-    if (!RecordReplayControl.maybeDivergeFromRecording()) {
-      return [{name: "Unknown names",
-               value: "Recording divergence in getEnvironmentNames" }];
-    }
-
+    divergeFromRecording();
     const env = gPausedObjects.getObject(request.id);
     return getEnvironmentNames(env);
   },
 
   getFrame(request) {
     if (request.index == -1 /* NewestFrameIndex */) {
       const numFrames = countScriptFrames();
 
@@ -1023,111 +1347,81 @@ const gRequestHandlers = {
       }
       request.index = numFrames - 1;
     }
 
     return getFrameData(request.index);
   },
 
   pauseData(request) {
-    if (!RecordReplayControl.maybeDivergeFromRecording()) {
-      return { error: "Recording divergence in pauseData" };
-    }
-
+    divergeFromRecording();
     return getPauseData();
   },
 
   getLineOffsets: forwardToScript("getLineOffsets"),
   getOffsetLocation: forwardToScript("getOffsetLocation"),
   getSuccessorOffsets: forwardToScript("getSuccessorOffsets"),
   getPredecessorOffsets: forwardToScript("getPredecessorOffsets"),
   getAllColumnOffsets: forwardToScript("getAllColumnOffsets"),
   getOffsetMetadata: forwardToScript("getOffsetMetadata"),
   getPossibleBreakpoints: forwardToScript("getPossibleBreakpoints"),
   getPossibleBreakpointOffsets: forwardToScript("getPossibleBreakpointOffsets"),
 
   frameEvaluate(request) {
-    if (!RecordReplayControl.maybeDivergeFromRecording()) {
-      return { throw: "Recording divergence in frameEvaluate" };
-    }
-
+    divergeFromRecording();
     const frame = scriptFrameForIndex(request.index);
     const rv = frame.eval(request.text, request.options);
     return convertCompletionValue(rv, request.convertOptions);
   },
 
   popFrameResult(request) {
     return gPopFrameResult ? convertCompletionValue(gPopFrameResult) : {};
   },
 
   findConsoleMessages(request) {
     return gConsoleMessages;
   },
 
-  getNewConsoleMessage(request) {
-    return gConsoleMessages[gConsoleMessages.length - 1];
-  },
-
-  currentExecutionPoint(request) {
-    return RecordReplayControl.currentExecutionPoint();
-  },
-
-  recordingEndpoint(request) {
-    return RecordReplayControl.recordingEndpoint();
-  },
-
   /////////////////////////////////////////////////////////
   // Inspector Requests
   /////////////////////////////////////////////////////////
 
   getFixedObjects(request) {
-    if (!RecordReplayControl.maybeDivergeFromRecording()) {
-      return { throw: "Recording divergence in getWindow" };
-    }
-
+    divergeFromRecording();
     const window = getWindow();
     return {
       window: getObjectId(makeDebuggeeValue(window)),
       document: getObjectId(makeDebuggeeValue(window.document)),
       Services: getObjectId(makeDebuggeeValue(Services)),
       InspectorUtils: getObjectId(makeDebuggeeValue(InspectorUtils)),
       CSSRule: getObjectId(makeDebuggeeValue(CSSRule)),
     };
   },
 
   newDeepTreeWalker(request) {
-    if (!RecordReplayControl.maybeDivergeFromRecording()) {
-      return { throw: "Recording divergence in newDeepTreeWalker" };
-    }
-
+    divergeFromRecording();
     const walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"]
       .createInstance(Ci.inIDeepTreeWalker);
     return { id: getObjectId(makeDebuggeeValue(walker)) };
   },
 
   getObjectPropertyValue(request) {
-    if (!RecordReplayControl.maybeDivergeFromRecording()) {
-      return { throw: "Recording divergence in getObjectPropertyValue" };
-    }
-
+    divergeFromRecording();
     const object = gPausedObjects.getObject(request.id);
 
     try {
       const rv = object.unsafeDereference()[request.name];
       return { "return": convertValue(makeDebuggeeValue(rv)) };
     } catch (e) {
       return { "throw": "" + e };
     }
   },
 
   setObjectPropertyValue(request) {
-    if (!RecordReplayControl.maybeDivergeFromRecording()) {
-      return { throw: "Recording divergence in getObjectPropertyValue" };
-    }
-
+    divergeFromRecording();
     const object = gPausedObjects.getObject(request.id);
     const value = getDebuggeeValue(convertValueFromParent(request.value));
 
     try {
       object.unsafeDereference()[request.name] = value;
       return { "return": request.value };
     } catch (e) {
       return { "throw": "" + e };
@@ -1146,35 +1440,37 @@ const gRequestHandlers = {
     if (!element) {
       return { id: 0 };
     }
     const obj = makeDebuggeeValue(element);
     return { id: getObjectId(obj) };
   },
 };
 
-// eslint-disable-next-line no-unused-vars
-function ProcessRequest(request) {
+function processRequest(request) {
   try {
     if (gRequestHandlers[request.type]) {
       return gRequestHandlers[request.type](request);
     }
     return { exception: "No handler for " + request.type };
   } catch (e) {
-    let msg;
-    try {
-      msg = "" + e + " line " + e.lineNumber;
-    } catch (ee) {
-      msg = "Unknown";
-    }
-    dump("ReplayDebugger Record/Replay Error: " + msg + "\n");
-    return { exception: msg };
+    printError("processRequest", e);
+    return { exception: `Request failed: ${request.type}` };
   }
 }
 
+function printError(why, e) {
+  let msg;
+  try {
+    msg = "" + e + " line " + e.lineNumber;
+  } catch (ee) {
+    msg = "Unknown";
+  }
+  dump(`Record/Replay Error: ${why}: ${msg}\n`);
+}
+
 // eslint-disable-next-line no-unused-vars
 var EXPORTED_SYMBOLS = [
-  "EnsurePositionHandler",
-  "ClearPositionHandlers",
-  "ClearPausedState",
-  "ProcessRequest",
-  "GetEntryPosition",
+  "ManifestStart",
+  "BeforeCheckpoint",
+  "AfterCheckpoint",
+  "NewTimeWarpTarget",
 ];
--- a/devtools/server/actors/replay/rrIControl.idl
+++ b/devtools/server/actors/replay/rrIControl.idl
@@ -6,12 +6,12 @@
 #include "nsISupports.idl"
 
 // This interface defines the methods used for calling into control.js in a
 // middleman process.
 [scriptable, uuid(c296e7c3-8a27-4fd0-94c2-b6e5126909ba)]
 interface rrIControl : nsISupports {
   void Initialize(in jsval recordingChildId);
   void ConnectDebugger(in jsval replayDebugger);
-  void HitExecutionPoint(in long childId, in jsval msg);
+  void ManifestFinished(in long childId, in jsval response);
   void BeforeSaveRecording();
   void AfterSaveRecording();
 };
--- a/devtools/server/actors/replay/rrIReplay.idl
+++ b/devtools/server/actors/replay/rrIReplay.idl
@@ -5,14 +5,13 @@
 
 #include "nsISupports.idl"
 
 // This interface defines the methods used for calling into replay.js in a
 // recording/replaying process. See JSControl.h for the documentation of these
 // methods.
 [scriptable, uuid(8b86b71f-8471-472e-9997-c5f21f9d0598)]
 interface rrIReplay : nsISupports {
-  jsval ProcessRequest(in jsval request);
-  void EnsurePositionHandler(in jsval position);
-  void ClearPositionHandlers();
-  void ClearPausedState();
-  jsval GetEntryPosition(in jsval position);
+  void ManifestStart(in jsval manifest);
+  void BeforeCheckpoint();
+  void AfterCheckpoint(in long checkpoint, in bool restoredCheckpoint);
+  long NewTimeWarpTarget();
 };
--- a/devtools/server/actors/targets/browsing-context.js
+++ b/devtools/server/actors/targets/browsing-context.js
@@ -1517,16 +1517,18 @@ DebuggerProgressListener.prototype = {
     handler.addEventListener("pageshow", this._onWindowCreated, true);
     handler.addEventListener("pagehide", this._onWindowHidden, true);
 
     // Dispatch the _windowReady event on the targetActor for pre-existing windows
     for (const win of this._getWindowsInDocShell(docShell)) {
       this._targetActor._windowReady(win);
       this._knownWindowIDs.set(getWindowID(win), win);
     }
+
+    docShell.watchedByDevtools = true;
   },
 
   unwatch(docShell) {
     const docShellWindow = docShell.domWindow;
     if (!this._watchedDocShells.has(docShellWindow)) {
       return;
     }
 
@@ -1543,16 +1545,18 @@ DebuggerProgressListener.prototype = {
     handler.removeEventListener("DOMWindowCreated",
       this._onWindowCreated, true);
     handler.removeEventListener("pageshow", this._onWindowCreated, true);
     handler.removeEventListener("pagehide", this._onWindowHidden, true);
 
     for (const win of this._getWindowsInDocShell(docShell)) {
       this._knownWindowIDs.delete(getWindowID(win));
     }
+
+    docShell.watchedByDevtools = false;
   },
 
   _getWindowsInDocShell(docShell) {
     return getChildDocShells(docShell).map(d => {
       return d.domWindow;
     });
   },
 
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -552,27 +552,21 @@ const ThreadActor = ActorClassWithSpec(t
     }
 
     // If the parent actor has been closed, terminate the debuggee script
     // instead of continuing. Executing JS after the content window is gone is
     // a bad idea.
     return this._parentClosed ? null : undefined;
   },
 
-  _makeOnEnterFrame: function({ pauseAndRespond, rewinding }) {
+  _makeOnEnterFrame: function({ pauseAndRespond }) {
     return frame => {
       const { generatedSourceActor } = this.sources.getFrameLocation(frame);
 
       const url = generatedSourceActor.url;
-
-      // When rewinding into a frame, we end up at the point when it is being popped.
-      if (rewinding) {
-        frame.reportedPop = true;
-      }
-
       if (this.sources.isBlackBoxed(url)) {
         return undefined;
       }
 
       return pauseAndRespond(frame);
     };
   },
 
@@ -838,21 +832,18 @@ const ThreadActor = ActorClassWithSpec(t
     );
 
     // Make sure there is still a frame on the stack if we are to continue
     // stepping.
     const stepFrame = this._getNextStepFrame(this.youngestFrame, rewinding);
     if (stepFrame) {
       switch (steppingType) {
         case "step":
-          if (rewinding) {
-            this.dbg.replayingOnPopFrame = onEnterFrame;
-          } else {
-            this.dbg.onEnterFrame = onEnterFrame;
-          }
+          assert(!rewinding, "'step' resume limit cannot be used while rewinding");
+          this.dbg.onEnterFrame = onEnterFrame;
           // Fall through.
         case "break":
         case "next":
           if (stepFrame.script) {
             if (this.dbg.replaying) {
               const offsets =
                 this._findReplayingStepOffsets(generatedLocation, stepFrame, rewinding);
               stepFrame.setReplayingOnStep(onStep, offsets);
@@ -863,17 +854,21 @@ const ThreadActor = ActorClassWithSpec(t
           // Fall through.
         case "finish":
           if (rewinding) {
             let olderFrame = stepFrame.older;
             while (olderFrame && !olderFrame.script) {
               olderFrame = olderFrame.older;
             }
             if (olderFrame) {
-              olderFrame.setReplayingOnStep(onStep, [olderFrame.offset]);
+              // Set an onStep handler in the older frame to stop at the call site.
+              // Make sure the offsets we use are valid breakpoint locations, as we
+              // cannot stop at other offsets when replaying.
+              const offsets = this._findReplayingStepOffsets({}, olderFrame, true);
+              olderFrame.setReplayingOnStep(onStep, offsets);
             }
           } else {
             stepFrame.onPop = onPop;
           }
           break;
       }
     }
 
@@ -1189,17 +1184,16 @@ const ThreadActor = ActorClassWithSpec(t
     if (this.state === "paused") {
       return undefined;
     }
 
     this._state = "paused";
 
     // Clear stepping hooks.
     this.dbg.onEnterFrame = undefined;
-    this.dbg.replayingOnPopFrame = undefined;
     this.dbg.onExceptionUnwind = undefined;
     this._clearSteppingHooks();
 
     // Create the actor pool that will hold the pause actor and its
     // children.
     assert(!this._pausePool, "No pause pool should exist yet");
     this._pausePool = new ActorPool(this.conn);
     this.conn.addActorPool(this._pausePool);
--- a/devtools/shared/client/thread-client.js
+++ b/devtools/shared/client/thread-client.js
@@ -155,30 +155,16 @@ ThreadClient.prototype = {
   /**
    * Rewind step over a function call.
    */
   reverseStepOver: function() {
     return this._doResume({ type: "next" }, true);
   },
 
   /**
-   * Rewind step into a function call.
-   */
-  reverseStepIn: function() {
-    return this._doResume({ type: "step" }, true);
-  },
-
-  /**
-   * Rewind step out of a function call.
-   */
-  reverseStepOut: function() {
-    return this._doResume({ type: "finish" }, true);
-  },
-
-  /**
    * Immediately interrupt a running thread.
    */
   interrupt: function() {
     return this._doInterrupt(null);
   },
 
   /**
    * Pause execution right before the next JavaScript bytecode is executed.
new file mode 100644
--- /dev/null
+++ b/devtools/shared/execution-point-utils.js
@@ -0,0 +1,146 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Utilities for working with execution points and breakpoint positions, which
+// are used when replaying to identify points in the execution where the
+// debugger can stop or where events of interest have occurred.
+//
+// A breakpoint position describes where breakpoints can be installed when
+// replaying, and has the following properties:
+//
+// kind: The kind of breakpoint, which has one of the following values:
+//   "Break": Break at an offset in a script.
+//   "OnStep": Break at an offset in a script with a given frame depth.
+//   "OnPop": Break when a script's frame with a given frame depth is popped.
+//   "EnterFrame": Break when any script is entered.
+//
+// script: For all kinds but "EnterFrame", the ID of the position's script.
+//
+// offset: For "Break" and "OnStep", the offset within the script.
+//
+// frameIndex: For "OnStep" and "OnPop", the index of the topmost script frame.
+//   Indexes start at zero for the first frame pushed, and increase with the
+//   depth of the frame.
+//
+// An execution point is a unique identifier for a point in the recording where
+// the debugger can pause, and has the following properties:
+//
+// checkpoint: ID of the most recent checkpoint.
+//
+// progress: Value of the progress counter when the point is reached.
+//
+// position: Optional breakpoint position where the pause occurs at. This cannot
+//   have the "Break" kind (see below) and is missing if the execution point is
+//   at the checkpoint itself.
+//
+// The above properties must uniquely identify a single point in the recording.
+// This property is ensured mainly through how the progress counter is managed.
+// The position in the point must not be able to execute multiple times without
+// the progress counter being incremented.
+//
+// The progress counter is incremented whenever a frame is pushed onto the stack
+// or a loop entry point is reached. Because it increments when looping, a
+// specific JS frame cannot reach the same position multiple times with the same
+// progress counter. Because it increments when pushing frames, different JS
+// frames with the same frame depth cannot reach the same position multiple
+// times with the same progress counter. The position must specify the frame
+// depth for this argument to hold, so "Break" positions are not used in
+// execution points. They are used when the user-specified breakpoints are being
+// installed, though, and when pausing the execution point will use the
+// appropriate "OnStep" position for the frame depth.
+
+// Return whether pointA happens before pointB in the recording.
+function pointPrecedes(pointA, pointB) {
+  if (pointA.checkpoint != pointB.checkpoint) {
+    return pointA.checkpoint < pointB.checkpoint;
+  }
+  if (pointA.progress != pointB.progress) {
+    return pointA.progress < pointB.progress;
+  }
+
+  const posA = pointA.position;
+  const posB = pointB.position;
+
+  // Except when we're at a checkpoint, all execution points have positions.
+  // Because the progress counter is bumped when executing script, points with
+  // the same checkpoint and progress counter will either both be at that
+  // checkpoint, or both be at an intra-checkpoint point.
+  assert(!!posA == !!posB);
+  if (!posA || positionEquals(posA, posB)) {
+    return false;
+  }
+
+  // If an execution point doesn't have a frame index (i.e. EnterFrame) then it
+  // has bumped the progress counter and predates everything else that is
+  // associated with the same progress counter.
+  if ("frameIndex" in posA != "frameIndex" in posB) {
+    return "frameIndex" in posB;
+  }
+
+  // Only certain execution point kinds do not bump the progress counter.
+  assert(posA.kind == "OnStep" || posA.kind == "OnPop");
+  assert(posB.kind == "OnStep" || posB.kind == "OnPop");
+
+  // Deeper frames predate shallower frames, if the progress counter is the
+  // same. We bump the progress counter when pushing frames, but not when
+  // popping them.
+  assert("frameIndex" in posA && "frameIndex" in posB);
+  if (posA.frameIndex != posB.frameIndex) {
+    return posA.frameIndex > posB.frameIndex;
+  }
+
+  // Within a frame, OnStep points come before OnPop points.
+  if (posA.kind != posB.kind) {
+    return posA.kind == "OnStep";
+  }
+
+  // Earlier script locations predate later script locations.
+  assert("offset" in posA && "offset" in posB);
+  return posA.offset < posB.offset;
+}
+
+// Return whether two execution points are the same.
+// eslint-disable-next-line no-unused-vars
+function pointEquals(pointA, pointB) {
+  return !pointPrecedes(pointA, pointB) && !pointPrecedes(pointB, pointA);
+}
+
+// Return whether two breakpoint positions are the same.
+function positionEquals(posA, posB) {
+  return posA.kind == posB.kind
+      && posA.script == posB.script
+      && posA.offset == posB.offset
+      && posA.frameIndex == posB.frameIndex;
+}
+
+// Return whether an execution point matching posB also matches posA.
+// eslint-disable-next-line no-unused-vars
+function positionSubsumes(posA, posB) {
+  if (positionEquals(posA, posB)) {
+    return true;
+  }
+
+  if (posA.kind == "Break" && posB.kind == "OnStep" &&
+      posA.script == posB.script && posA.offset == posB.offset) {
+    return true;
+  }
+
+  return false;
+}
+
+function assert(v) {
+  if (!v) {
+    dump(`Assertion failed: ${Error().stack}\n`);
+    throw new Error("Assertion failed!");
+  }
+}
+
+this.EXPORTED_SYMBOLS = [
+  "pointPrecedes",
+  "pointEquals",
+  "positionEquals",
+  "positionSubsumes",
+];
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -51,16 +51,17 @@ DevToolsModules(
     'content-observer.js',
     'debounce.js',
     'defer.js',
     'deprecated-sync-thenables.js',
     'DevToolsUtils.js',
     'dom-node-constants.js',
     'dom-node-filter-constants.js',
     'event-emitter.js',
+    'execution-point-utils.js',
     'extend.js',
     'flags.js',
     'generate-uuid.js',
     'indentation.js',
     'indexed-db.js',
     'l10n.js',
     'loader-plugin-raw.jsm',
     'Loader.jsm',
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -381,17 +381,18 @@ nsDocShell::nsDocShell(BrowsingContext* 
       mSavingOldViewer(false),
       mDynamicallyCreated(false),
       mAffectPrivateSessionLifetime(true),
       mInvisible(false),
       mHasLoadedNonBlankURI(false),
       mBlankTiming(false),
       mTitleValidForCurrentURI(false),
       mIsFrame(false),
-      mSkipBrowsingContextDetachOnDestroy(false) {
+      mSkipBrowsingContextDetachOnDestroy(false),
+      mWatchedByDevtools(false) {
   mHistoryID.m0 = 0;
   mHistoryID.m1 = 0;
   mHistoryID.m2 = 0;
   AssertOriginAttributesMatchPrivateBrowsing();
 
   nsContentUtils::GenerateUUIDInPlace(mHistoryID);
 
   if (gDocShellCount++ == 0) {
@@ -13543,8 +13544,21 @@ bool nsDocShell::GetIsAttemptingToNaviga
       // This is a javascript: load that might lead to a new document,
       // hence a navigation.
       return true;
     }
   }
 
   return false;
 }
+
+NS_IMETHODIMP
+nsDocShell::GetWatchedByDevtools(bool* aWatched) {
+  NS_ENSURE_ARG(aWatched);
+  *aWatched = mWatchedByDevtools;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetWatchedByDevtools(bool aWatched) {
+  mWatchedByDevtools = aWatched;
+  return NS_OK;
+}
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -1240,11 +1240,14 @@ class nsDocShell final : public nsDocLoa
   bool mTitleValidForCurrentURI : 1;
 
   bool mIsFrame : 1;
 
   // If mSkipBrowsingContextDetachOnDestroy is set to true, then when the
   // docshell is destroyed, the browsing context will not be detached. This is
   // for cases where we want to preserve the BC for future use.
   bool mSkipBrowsingContextDetachOnDestroy : 1;
+
+  // Set when activity in this docshell is being watched by the developer tools.
+  bool mWatchedByDevtools : 1;
 };
 
 #endif /* nsDocShell_h__ */
--- a/docshell/base/nsIContentViewer.idl
+++ b/docshell/base/nsIContentViewer.idl
@@ -185,17 +185,18 @@ interface nsIContentViewer : nsISupports
    * the saved presentation state.
    */
   void clearHistoryEntry();
 
   /**
    * Change the layout to view the document with page layout (like print preview), but
    * dynamic and editable (like Galley layout).
    */
-  void setPageMode(in boolean aPageMode, in nsIPrintSettings aPrintSettings);
+  void setPageModeForTesting(in boolean aPageMode,
+                             in nsIPrintSettings aPrintSettings);
 
   /**
    * Get the history entry that this viewer will save itself into when
    * destroyed.  Can return null
    */
   readonly attribute nsISHEntry historyEntry;
 
   /**
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -1162,9 +1162,14 @@ interface nsIDocShell : nsIDocShellTreeI
    */
   Promise getContentBlockingLog();
 
   /**
    * Return whether this docshell is "attempting to navigate" in the
    * sense that's relevant to document.open.
    */
   [notxpcom, nostdcall] readonly attribute boolean isAttemptingToNavigate;
+
+  /**
+   * Whether developer tools are watching activity in this docshell.
+   */
+  [infallible] attribute boolean watchedByDevtools;
 };
--- a/dom/base/SerializedStackHolder.cpp
+++ b/dom/base/SerializedStackHolder.cpp
@@ -79,16 +79,19 @@ JSObject* SerializedStackHolder::ReadSta
 
   JS::RootedValue stackValue(aCx);
   mHolder.Read(xpc::CurrentNativeGlobal(aCx), aCx, &stackValue,
                IgnoreErrors());
   return stackValue.isObject() ? &stackValue.toObject() : nullptr;
 }
 
 UniquePtr<SerializedStackHolder> GetCurrentStackForNetMonitor(JSContext* aCx) {
+  MOZ_ASSERT_IF(!NS_IsMainThread(),
+                GetCurrentThreadWorkerPrivate()->IsWatchedByDevtools());
+
   UniquePtr<SerializedStackHolder> stack = MakeUnique<SerializedStackHolder>();
   stack->SerializeCurrentStack(aCx);
   return stack;
 }
 
 void NotifyNetworkMonitorAlternateStack(nsISupports* aChannel,
                                         UniquePtr<SerializedStackHolder> aStackHolder) {
   if (!aStackHolder) {
--- a/dom/base/SerializedStackHolder.h
+++ b/dom/base/SerializedStackHolder.h
@@ -44,20 +44,19 @@ class SerializedStackHolder {
   // This returns null on failure, and does not leave an exception on aCx.
   JSObject* ReadStack(JSContext* aCx);
 };
 
 // Construct a stack for the current thread, which may be consumed by the net
 // monitor later on. This may be called on either the main or a worker thread.
 //
 // This always creates a stack, even if the net monitor isn't active for the
-// associated window. Ideally we would only create the stack if the net monitor
-// was active, but there doesn't seem to be an easy way to determine this.
-// The operations this is used with should be rare enough and/or have enough
-// other associated costs that the perf impact is low. See bug 1546736.
+// associated window. The net monitor will only be active if the associated
+// docshell or worker's WatchedByDevtools flag is set, so this should be checked
+// before creating the stack.
 UniquePtr<SerializedStackHolder> GetCurrentStackForNetMonitor(JSContext* aCx);
 
 // If aStackHolder is non-null, this notifies the net monitor that aStackHolder
 // is the stack from which aChannel originates. This must be called on the main
 // thread. This call is synchronous, and aChannel and aStackHolder will not be
 // used afterward. aChannel is an nsISupports object because this can be used
 // with either nsIChannel or nsIWebSocketChannel.
 void NotifyNetworkMonitorAlternateStack(nsISupports* aChannel,
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -5650,13 +5650,24 @@ void CanvasPath::EnsurePathBuilder() con
 
   // if there is not pathbuilder, there must be a path
   MOZ_ASSERT(mPath);
   mPathBuilder = mPath->CopyToBuilder();
   mPath = nullptr;
 }
 
 size_t BindingJSObjectMallocBytes(CanvasRenderingContext2D* aContext) {
-  return aContext->GetWidth() * aContext->GetHeight() * 4;
+  int32_t width = aContext->GetWidth();
+  int32_t height = aContext->GetHeight();
+
+  // TODO: Bug 1552137: No memory will be allocated if either dimension is
+  // greater than gfxPrefs::MaxCanvasSize(). We should check this here too.
+
+  CheckedInt<uint32_t> bytes = CheckedInt<uint32_t>(width) * height * 4;
+  if (!bytes.isValid()) {
+    return 0;
+  }
+
+  return bytes.value();
 }
 
 }  // namespace dom
 }  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/canvas/crashtests/1551745.html
@@ -0,0 +1,9 @@
+<script>
+window.addEventListener('load', function() {
+  b.getContext('2d')
+  a.src = b.toDataURL('image/jpeg', 0.1)
+  b.setAttribute('width', 2684354)
+})
+</script>
+<img id='a' src='data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs='/>
+<canvas id='b' height='800' width='800'></canvas>
--- a/dom/canvas/crashtests/crashtests.list
+++ b/dom/canvas/crashtests/crashtests.list
@@ -48,8 +48,10 @@ load 1305850.html
 load 1334366-1.html
 load 1334647-1.html
 load 1349067.html
 pref(gfx.offscreencanvas.enabled,true) load 1348976-1.html
 load 1357092.html
 load 1441613.html
 pref(gfx.offscreencanvas.enabled,true) load 1443671.html
 pref(gfx.offscreencanvas.enabled,true) load 1546390.html
+load 1549853.html
+load 1551745.html
--- a/dom/localstorage/LSDatabase.cpp
+++ b/dom/localstorage/LSDatabase.cpp
@@ -19,18 +19,22 @@ typedef nsDataHashtable<nsCStringHashKey
 
 StaticAutoPtr<LSDatabaseHashtable> gLSDatabases;
 
 }  // namespace
 
 StaticRefPtr<LSDatabase::Observer> LSDatabase::sObserver;
 
 class LSDatabase::Observer final : public nsIObserver {
+  bool mInvalidated;
+
  public:
-  Observer() { MOZ_ASSERT(NS_IsMainThread()); }
+  Observer() : mInvalidated(false) { MOZ_ASSERT(NS_IsMainThread()); }
+
+  void Invalidate() { mInvalidated = true; }
 
  private:
   ~Observer() { MOZ_ASSERT(NS_IsMainThread()); }
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 };
 
@@ -350,27 +354,39 @@ void LSDatabase::AllowToClose() {
     MOZ_ASSERT(sObserver);
 
     nsCOMPtr<nsIObserverService> obsSvc = GetObserverService();
     MOZ_ASSERT(obsSvc);
 
     MOZ_ALWAYS_SUCCEEDS(
         obsSvc->RemoveObserver(sObserver, XPCOM_SHUTDOWN_OBSERVER_TOPIC));
 
+    // We also need to invalidate the observer because AllowToClose can be
+    // triggered by an indirectly related observer, so the observer service
+    // may still keep our observer alive and call Observe on it. This is
+    // possible because observer service snapshots the observer list for given
+    // subject before looping over the list.
+    sObserver->Invalidate();
+
     sObserver = nullptr;
   }
 }
 
 NS_IMPL_ISUPPORTS(LSDatabase::Observer, nsIObserver)
 
 NS_IMETHODIMP
 LSDatabase::Observer::Observe(nsISupports* aSubject, const char* aTopic,
                               const char16_t* aData) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!strcmp(aTopic, XPCOM_SHUTDOWN_OBSERVER_TOPIC));
+
+  if (mInvalidated) {
+    return NS_OK;
+  }
+
   MOZ_ASSERT(gLSDatabases);
 
   nsTArray<RefPtr<LSDatabase>> databases;
 
   for (auto iter = gLSDatabases->ConstIter(); !iter.Done(); iter.Next()) {
     LSDatabase* database = iter.Data();
     MOZ_ASSERT(database);
 
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -6079,16 +6079,22 @@ bool QuotaManager::IsPrincipalInfoValid(
                    originNoSuffix.get(), info.originNoSuffix().get());
         return false;
       }
 
       if (NS_WARN_IF(info.originNoSuffix().EqualsLiteral(kChromeOrigin))) {
         return false;
       }
 
+      if (NS_WARN_IF(info.originNoSuffix().FindChar('^', 0) != -1)) {
+        QM_WARNING("originNoSuffix (%s) contains the '^' character!",
+                   info.originNoSuffix().get());
+        return false;
+      }
+
       // Verify the principal baseDomain exists.
       if (NS_WARN_IF(info.baseDomain().IsVoid())) {
         return false;
       }
 
       // Verify the principal baseDomain matches spec.
       nsCString baseDomain;
       rv = specURL->BaseDomain(baseDomain);
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/unit/test_originWithCaret.js
@@ -0,0 +1,15 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+async function testSteps() {
+  const principal = getPrincipal("http://example.com^123");
+
+  try {
+    getSimpleDatabase(principal);
+    ok(false, "Should have thrown");
+  } catch (ex) {
+    ok(true, "Did throw");
+  }
+}
--- a/dom/quota/test/unit/xpcshell.ini
+++ b/dom/quota/test/unit/xpcshell.ini
@@ -38,16 +38,17 @@ support-files =
 [test_listInitializedOrigins.js]
 [test_localStorageArchive1upgrade.js]
 [test_localStorageArchive4upgrade.js]
 [test_localStorageArchiveDowngrade.js]
 [test_morgueCleanup.js]
 [test_obsoleteOriginAttributesUpgrade.js]
 [test_obsoleteOrigins.js]
 [test_originAttributesUpgrade.js]
+[test_originWithCaret.js]
 [test_persist.js]
 [test_persist_eviction.js]
 [test_persist_globalLimit.js]
 [test_persist_groupLimit.js]
 [test_removeLocalStorage.js]
 [test_simpledb.js]
 [test_specialOrigins.js]
 [test_storagePersistentUpgrade.js]
--- a/dom/workers/WorkerLoadInfo.cpp
+++ b/dom/workers/WorkerLoadInfo.cpp
@@ -82,16 +82,17 @@ WorkerLoadInfoData::WorkerLoadInfoData()
     : mLoadFlags(nsIRequest::LOAD_NORMAL),
       mWindowID(UINT64_MAX),
       mReferrerPolicy(net::RP_Unset),
       mFromWindow(false),
       mEvalAllowed(false),
       mReportCSPViolations(false),
       mXHRParamsAllowed(false),
       mPrincipalIsSystem(false),
+      mWatchedByDevtools(false),
       mStorageAccess(nsContentUtils::StorageAccess::eDeny),
       mFirstPartyStorageAccessGranted(false),
       mServiceWorkersTestingInWindow(false),
       mSecureContext(eNotSet) {}
 
 nsresult WorkerLoadInfo::SetPrincipalsOnMainThread(
     nsIPrincipal* aPrincipal, nsIPrincipal* aStoragePrincipal,
     nsILoadGroup* aLoadGroup) {
--- a/dom/workers/WorkerLoadInfo.h
+++ b/dom/workers/WorkerLoadInfo.h
@@ -113,16 +113,17 @@ struct WorkerLoadInfoData {
   uint64_t mWindowID;
 
   net::ReferrerPolicy mReferrerPolicy;
   bool mFromWindow;
   bool mEvalAllowed;
   bool mReportCSPViolations;
   bool mXHRParamsAllowed;
   bool mPrincipalIsSystem;
+  bool mWatchedByDevtools;
   nsContentUtils::StorageAccess mStorageAccess;
   bool mFirstPartyStorageAccessGranted;
   bool mServiceWorkersTestingInWindow;
   OriginAttributes mOriginAttributes;
 
   enum {
     eNotSet,
     eInsecureContext,
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -2253,17 +2253,20 @@ already_AddRefed<WorkerPrivate> WorkerPr
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   worker->EnableDebugger();
 
   MOZ_DIAGNOSTIC_ASSERT(worker->PrincipalIsValid());
 
-  UniquePtr<SerializedStackHolder> stack = GetCurrentStackForNetMonitor(aCx);
+  UniquePtr<SerializedStackHolder> stack;
+  if (worker->IsWatchedByDevtools()) {
+    stack = GetCurrentStackForNetMonitor(aCx);
+  }
 
   RefPtr<CompileScriptRunnable> compiler =
       new CompileScriptRunnable(worker, std::move(stack), aScriptURL);
   if (!compiler->Dispatch()) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
@@ -2361,16 +2364,17 @@ nsresult WorkerPrivate::GetLoadInfo(JSCo
     loadInfo.mDomain = aParent->Domain();
     loadInfo.mFromWindow = aParent->IsFromWindow();
     loadInfo.mWindowID = aParent->WindowID();
     loadInfo.mStorageAccess = aParent->StorageAccess();
     loadInfo.mOriginAttributes = aParent->GetOriginAttributes();
     loadInfo.mServiceWorkersTestingInWindow =
         aParent->ServiceWorkersTestingInWindow();
     loadInfo.mParentController = aParent->GetController();
+    loadInfo.mWatchedByDevtools = aParent->IsWatchedByDevtools();
   } else {
     AssertIsOnMainThread();
 
     // Make sure that the IndexedDatabaseManager is set up
     Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate());
 
     nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
     MOZ_ASSERT(ssm);
@@ -2481,16 +2485,21 @@ nsresult WorkerPrivate::GetLoadInfo(JSCo
 
       uint32_t perm;
       rv = permMgr->TestPermissionFromPrincipal(
           loadInfo.mLoadingPrincipal, NS_LITERAL_CSTRING("systemXHR"), &perm);
       NS_ENSURE_SUCCESS(rv, rv);
 
       loadInfo.mXHRParamsAllowed = perm == nsIPermissionManager::ALLOW_ACTION;
 
+      nsIDocShell* docShell = globalWindow->GetDocShell();
+      if (docShell) {
+        loadInfo.mWatchedByDevtools = docShell->GetWatchedByDevtools();
+      }
+
       loadInfo.mFromWindow = true;
       loadInfo.mWindowID = globalWindow->WindowID();
       loadInfo.mStorageAccess =
           nsContentUtils::StorageAllowedForWindow(globalWindow);
       loadInfo.mCookieSettings = document->CookieSettings();
       loadInfo.mOriginAttributes =
           nsContentUtils::GetOriginAttributes(document);
       loadInfo.mParentController = globalWindow->GetController();
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -763,16 +763,20 @@ class WorkerPrivate : public RelativeTim
     return mLoadInfo.mOriginAttributes;
   }
 
   // Determine if the SW testing per-window flag is set by devtools
   bool ServiceWorkersTestingInWindow() const {
     return mLoadInfo.mServiceWorkersTestingInWindow;
   }
 
+  bool IsWatchedByDevtools() const {
+    return mLoadInfo.mWatchedByDevtools;
+  }
+
   // Determine if the worker is currently loading its top level script.
   bool IsLoadingWorkerScript() const { return mLoadingWorkerScript; }
 
   // Called by ScriptLoader to track when this worker is loading its
   // top level script.
   void SetLoadingWorkerScript(bool aLoadingWorkerScript) {
     // any thread
     mLoadingWorkerScript = aLoadingWorkerScript;
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -235,17 +235,20 @@ void WorkerGlobalScope::SetOnerror(OnErr
   }
 }
 
 void WorkerGlobalScope::ImportScripts(JSContext* aCx,
                                       const Sequence<nsString>& aScriptURLs,
                                       ErrorResult& aRv) {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
-  UniquePtr<SerializedStackHolder> stack = GetCurrentStackForNetMonitor(aCx);
+  UniquePtr<SerializedStackHolder> stack;
+  if (mWorkerPrivate->IsWatchedByDevtools()) {
+    stack = GetCurrentStackForNetMonitor(aCx);
+  }
 
   workerinternals::Load(mWorkerPrivate, std::move(stack), aScriptURLs,
                         WorkerScript, aRv);
 }
 
 int32_t WorkerGlobalScope::SetTimeout(JSContext* aCx, Function& aHandler,
                                       const int32_t aTimeout,
                                       const Sequence<JS::Value>& aArguments,
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -21,16 +21,17 @@
 #include "mozilla/dom/FileCreatorHelper.h"
 #include "mozilla/dom/FetchUtil.h"
 #include "mozilla/dom/FormData.h"
 #include "mozilla/dom/MutableBlobStorage.h"
 #include "mozilla/dom/XMLDocument.h"
 #include "mozilla/dom/URLSearchParams.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/WorkerError.h"
 #include "mozilla/Encoding.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/LoadContext.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/dom/ProgressEvent.h"
@@ -2628,16 +2629,19 @@ nsresult XMLHttpRequestMainThread::Initi
   nsCOMPtr<nsIStreamListener> listener = new net::nsStreamListenerWrapper(this);
 
   // Check if this XHR is created from a tracking script.
   // If yes, lower the channel's priority.
   if (StaticPrefs::privacy_trackingprotection_lower_network_priority()) {
     MaybeLowerChannelPriority();
   }
 
+  // Associate any originating stack with the channel.
+  NotifyNetworkMonitorAlternateStack(mChannel, std::move(mOriginStack));
+
   // Start reading from the channel
   rv = mChannel->AsyncOpen(listener);
   listener = nullptr;
   if (NS_WARN_IF(NS_FAILED(rv))) {
     // Drop our ref to the channel to avoid cycles. Also drop channel's
     // ref to us to be extra safe.
     mChannel->SetNotificationCallbacks(mNotificationCallbacks);
     mChannel = nullptr;
@@ -3133,16 +3137,21 @@ nsresult XMLHttpRequestMainThread::SetMo
 }
 
 void XMLHttpRequestMainThread::SetMozBackgroundRequest(
     bool aMozBackgroundRequest, ErrorResult& aRv) {
   // No errors for this webIDL method on main-thread.
   SetMozBackgroundRequest(aMozBackgroundRequest);
 }
 
+void XMLHttpRequestMainThread::SetOriginStack(
+    UniquePtr<SerializedStackHolder> aOriginStack) {
+  mOriginStack = std::move(aOriginStack);
+}
+
 bool XMLHttpRequestMainThread::WithCredentials() const {
   return mFlagACwithCredentials;
 }
 
 void XMLHttpRequestMainThread::SetWithCredentials(bool aWithCredentials,
                                                   ErrorResult& aRv) {
   NOT_CALLABLE_IN_SYNC_SEND_RV
 
--- a/dom/xhr/XMLHttpRequestMainThread.h
+++ b/dom/xhr/XMLHttpRequestMainThread.h
@@ -61,16 +61,17 @@ typedef __StatusTmp Status;
 class nsIJARChannel;
 class nsILoadGroup;
 
 namespace mozilla {
 namespace dom {
 
 class DOMString;
 class XMLHttpRequestUpload;
+class SerializedStackHolder;
 struct OriginAttributesDictionary;
 
 // A helper for building up an ArrayBuffer object's data
 // before creating the ArrayBuffer itself.  Will do doubling
 // based reallocation, up to an optional maximum growth given.
 //
 // When all the data has been appended, call getArrayBuffer,
 // passing in the JSContext* for which the ArrayBuffer object
@@ -387,16 +388,18 @@ class XMLHttpRequestMainThread final : p
 
   virtual bool MozBackgroundRequest() const override;
 
   nsresult SetMozBackgroundRequest(bool aMozBackgroundRequest);
 
   virtual void SetMozBackgroundRequest(bool aMozBackgroundRequest,
                                        ErrorResult& aRv) override;
 
+  void SetOriginStack(UniquePtr<SerializedStackHolder> aOriginStack);
+
   virtual uint16_t ErrorCode() const override {
     return static_cast<uint16_t>(mErrorLoad);
   }
 
   virtual bool MozAnon() const override;
 
   virtual bool MozSystem() const override;
 
@@ -719,16 +722,20 @@ class XMLHttpRequestMainThread final : p
   bool mEofDecoded;
 
   // Our parse-end listener, if we are parsing.
   RefPtr<nsXHRParseEndListener> mParseEndListener;
 
   RefPtr<XMLHttpRequestDoneNotifier> mDelayedDoneNotifier;
   void DisconnectDoneNotifier();
 
+  // Any stack information for the point the XHR was opened. This is deleted
+  // after the XHR is opened, to avoid retaining references to the worker.
+  UniquePtr<SerializedStackHolder> mOriginStack;
+
   static bool sDontWarnAboutSyncXHR;
 };
 
 class MOZ_STACK_CLASS AutoDontWarnAboutSyncXHR {
  public:
   AutoDontWarnAboutSyncXHR()
       : mOldVal(XMLHttpRequestMainThread::DontWarnAboutSyncXHR()) {
     XMLHttpRequestMainThread::SetDontWarnAboutSyncXHR(true);
--- a/dom/xhr/XMLHttpRequestWorker.cpp
+++ b/dom/xhr/XMLHttpRequestWorker.cpp
@@ -663,32 +663,38 @@ class OpenRunnable final : public Worker
   Optional<nsAString> mPassword;
   nsString mPasswordStr;
   bool mBackgroundRequest;
   bool mWithCredentials;
   uint32_t mTimeout;
   XMLHttpRequestResponseType mResponseType;
   const nsString mMimeTypeOverride;
 
+  // Remember the worker thread's stack when the XHR was opened, so that it can
+  // be passed on to the net monitor.
+  UniquePtr<SerializedStackHolder> mOriginStack;
+
  public:
   OpenRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                const nsACString& aMethod, const nsAString& aURL,
                const Optional<nsAString>& aUser,
                const Optional<nsAString>& aPassword, bool aBackgroundRequest,
                bool aWithCredentials, uint32_t aTimeout,
                XMLHttpRequestResponseType aResponseType,
-               const nsString& aMimeTypeOverride)
+               const nsString& aMimeTypeOverride,
+               UniquePtr<SerializedStackHolder> aOriginStack)
       : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
         mMethod(aMethod),
         mURL(aURL),
         mBackgroundRequest(aBackgroundRequest),
         mWithCredentials(aWithCredentials),
         mTimeout(aTimeout),
         mResponseType(aResponseType),
-        mMimeTypeOverride(aMimeTypeOverride) {
+        mMimeTypeOverride(aMimeTypeOverride),
+        mOriginStack(std::move(aOriginStack)) {
     if (aUser.WasPassed()) {
       mUserStr = aUser.Value();
       mUser = &mUserStr;
     }
     if (aPassword.WasPassed()) {
       mPasswordStr = aPassword.Value();
       mPassword = &mPasswordStr;
     }
@@ -1271,16 +1277,20 @@ nsresult OpenRunnable::MainThreadRunInte
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   if (mBackgroundRequest) {
     nsresult rv = mProxy->mXHR->SetMozBackgroundRequest(mBackgroundRequest);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  if (mOriginStack) {
+    mProxy->mXHR->SetOriginStack(std::move(mOriginStack));
+  }
+
   ErrorResult rv;
 
   if (mWithCredentials) {
     mProxy->mXHR->SetWithCredentials(mWithCredentials, rv);
     if (NS_WARN_IF(rv.Failed())) {
       return rv.StealNSResult();
     }
   }
@@ -1772,20 +1782,29 @@ void XMLHttpRequestWorker::Open(const ns
     }
     mProxy = new Proxy(this, clientInfo.ref(), mWorkerPrivate->GetController(),
                        mMozAnon, mMozSystem);
     alsoOverrideMimeType = true;
   }
 
   mProxy->mOuterEventStreamId++;
 
+  UniquePtr<SerializedStackHolder> stack;
+  if (mWorkerPrivate->IsWatchedByDevtools()) {
+    if (JSContext* cx = nsContentUtils::GetCurrentJSContext()) {
+      stack = GetCurrentStackForNetMonitor(cx);
+    }
+  }
+
   RefPtr<OpenRunnable> runnable = new OpenRunnable(
       mWorkerPrivate, mProxy, aMethod, aUrl, aUser, aPassword,
       mBackgroundRequest, mWithCredentials, mTimeout, mResponseType,
-      alsoOverrideMimeType ? mMimeTypeOverride : VoidString());
+      alsoOverrideMimeType ? mMimeTypeOverride : VoidString(),
+      std::move(stack));
+
   ++mProxy->mOpenCount;
   runnable->Dispatch(Canceling, aRv);
   if (aRv.Failed()) {
     if (mProxy && !--mProxy->mOpenCount) {
       ReleaseProxy();
     }
 
     return;
--- a/gfx/wr/webrender/Cargo.toml
+++ b/gfx/wr/webrender/Cargo.toml
@@ -9,17 +9,17 @@ build = "build.rs"
 edition = "2018"
 
 [features]
 default = ["freetype-lib"]
 freetype-lib = ["freetype/servo-freetype-sys"]
 profiler = ["thread_profiler/thread_profiler"]
 debugger = ["ws", "serde_json", "serde", "image_loader", "base64"]
 capture = ["api/serialize", "ron", "serde", "smallvec/serde"]
-replay = ["api/deserialize", "ron", "serde"]
+replay = ["api/deserialize", "ron", "serde", "smallvec/serde"]
 display_list_stats = ["api/display_list_stats"]
 pathfinder = ["pathfinder_font_renderer", "pathfinder_gfx_utils", "pathfinder_partitioner", "pathfinder_path_utils"]
 serialize_program = ["serde", "webrender_build/serialize_program"]
 no_static_freetype = []
 
 [build-dependencies]
 webrender_build = { version = "0.0.1", path = "../webrender_build" }
 
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -16,17 +16,17 @@ use crate::gpu_types::{PrimitiveHeader, 
 use crate::internal_types::{FastHashMap, SavedTargetIndex, TextureSource, Filter};
 use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive};
 use crate::prim_store::{DeferredResolve, EdgeAaSegmentMask, PrimitiveInstanceKind, PrimitiveVisibilityIndex};
 use crate::prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex};
 use crate::prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, VECS_PER_SEGMENT};
 use crate::prim_store::{recompute_snap_offsets};
 use crate::prim_store::image::ImageSource;
 use crate::render_backend::DataStores;
-use crate::render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree, TileBlit};
+use crate::render_task::{RenderTaskAddress, RenderTaskId, RenderTaskGraph, TileBlit};
 use crate::renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use crate::renderer::{BLOCKS_PER_UV_RECT, MAX_VERTEX_TEXTURE_WIDTH};
 use crate::resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties};
 use smallvec::SmallVec;
 use std::{f32, i32, usize};
 use crate::tiling::{RenderTargetContext};
 use crate::util::{project_rect, TransformedRectKind};
 
@@ -563,17 +563,17 @@ impl BatchBuilder {
 
     /// Add a picture to a given batch builder.
     pub fn add_pic_to_batch(
         &mut self,
         pic: &PicturePrimitive,
         batcher: &mut AlphaBatchBuilder,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
-        render_tasks: &RenderTaskTree,
+        render_tasks: &RenderTaskGraph,
         deferred_resolves: &mut Vec<DeferredResolve>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         root_spatial_node_index: SpatialNodeIndex,
         z_generator: &mut ZBufferIdGenerator,
     ) {
         // Add each run in this picture to the batch.
         for prim_instance in &pic.prim_list.prim_instances {
@@ -597,17 +597,17 @@ impl BatchBuilder {
     // example if it encounters a picture where the items
     // in that picture are being drawn into the same target.
     fn add_prim_to_batch(
         &mut self,
         prim_instance: &PrimitiveInstance,
         batcher: &mut AlphaBatchBuilder,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
-        render_tasks: &RenderTaskTree,
+        render_tasks: &RenderTaskGraph,
         deferred_resolves: &mut Vec<DeferredResolve>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         root_spatial_node_index: SpatialNodeIndex,
         z_generator: &mut ZBufferIdGenerator,
     ) {
         if prim_instance.visibility_info == PrimitiveVisibilityIndex::INVALID {
             return;
@@ -1322,17 +1322,17 @@ impl BatchBuilder {
                                         // Draw an instance per shadow first, following by the content.
 
                                         // The shadows and the content get drawn as a brush image.
                                         let kind = BatchKind::Brush(
                                             BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
                                         );
 
                                         // Gets the saved render task ID of the content, which is
-                                        // deeper in the render task tree than the direct child.
+                                        // deeper in the render task graph than the direct child.
                                         let secondary_id = picture.secondary_render_task_id.expect("no secondary!?");
                                         let saved_index = render_tasks[secondary_id].saved_index.expect("no saved index!?");
                                         debug_assert_ne!(saved_index, SavedTargetIndex::PENDING);
 
                                         // Build BatchTextures for shadow/content
                                         let shadow_textures = BatchTextures::render_target_cache();
                                         let content_textures = BatchTextures {
                                             colors: [
@@ -1359,16 +1359,17 @@ impl BatchBuilder {
                                         for (shadow, shadow_gpu_data) in shadows.iter().zip(picture.extra_gpu_data_handles.iter()) {
                                             // Get the GPU cache address of the extra data handle.
                                             let shadow_prim_address = gpu_cache.get_address(shadow_gpu_data);
 
                                             let shadow_rect = prim_header.local_rect.translate(&shadow.offset);
 
                                             let shadow_prim_header = PrimitiveHeader {
                                                 local_rect: shadow_rect,
+                                                snap_offsets: prim_info.shadow_snap_offsets,
                                                 specific_prim_address: shadow_prim_address,
                                                 ..prim_header
                                             };
 
                                             let shadow_prim_header_index = prim_headers.push(&shadow_prim_header, z_id, [
                                                 ShaderColorMode::Alpha as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
                                                 RasterizationSpace::Screen as i32,
                                                 get_shader_opacity(1.0),
@@ -1413,129 +1414,28 @@ impl BatchBuilder {
 
                                         batcher.current_batch_list().push_single_instance(
                                             content_key,
                                             bounding_rect,
                                             z_id_content,
                                             PrimitiveInstanceData::from(content_instance),
                                         );
                                     }
-                                    Filter::DropShadow(shadow) => {
-                                        // Draw an instance of the shadow first, following by the content.
-
-                                        // Both the shadow and the content get drawn as a brush image.
-                                        let kind = BatchKind::Brush(
-                                            BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
-                                        );
-
-                                        // Gets the saved render task ID of the content, which is
-                                        // deeper in the render task tree than the direct child.
-                                        let secondary_id = picture.secondary_render_task_id.expect("no secondary!?");
-                                        let saved_index = render_tasks[secondary_id].saved_index.expect("no saved index!?");
-                                        debug_assert_ne!(saved_index, SavedTargetIndex::PENDING);
-
-                                        // Build BatchTextures for shadow/content
-                                        let shadow_textures = BatchTextures::render_target_cache();
-                                        let content_textures = BatchTextures {
-                                            colors: [
-                                                TextureSource::RenderTaskCache(saved_index),
-                                                TextureSource::Invalid,
-                                                TextureSource::Invalid,
-                                            ],
-                                        };
-
-                                        // Build batch keys for shadow/content
-                                        let shadow_key = BatchKey::new(kind, non_segmented_blend_mode, shadow_textures);
-                                        let content_key = BatchKey::new(kind, non_segmented_blend_mode, content_textures);
-
-                                        // Retrieve the UV rect addresses for shadow/content.
-                                        let cache_task_id = surface.expect("bug: surface must be allocated by now");
-                                        let shadow_uv_rect_address = render_tasks[cache_task_id]
-                                            .get_texture_address(gpu_cache)
-                                            .as_int();
-                                        let content_uv_rect_address = render_tasks[secondary_id]
-                                            .get_texture_address(gpu_cache)
-                                            .as_int();
-
-                                        // Get the GPU cache address of the extra data handle.
-                                        let shadow_prim_address = gpu_cache.get_address(&picture.extra_gpu_data_handles[0]);
-
-                                        let z_id_shadow = z_id;
-                                        let z_id_content = z_generator.next();
-
-                                        let content_prim_header_index = prim_headers.push(&prim_header, z_id_content, [
-                                            ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
-                                            RasterizationSpace::Screen as i32,
-                                            get_shader_opacity(1.0),
-                                            0,
-                                        ]);
-
-                                        let shadow_rect = prim_header.local_rect.translate(&shadow.offset);
-
-                                        let shadow_prim_header = PrimitiveHeader {
-                                            local_rect: shadow_rect,
-                                            snap_offsets: prim_info.shadow_snap_offsets,
-                                            specific_prim_address: shadow_prim_address,
-                                            ..prim_header
-                                        };
-
-                                        let shadow_prim_header_index = prim_headers.push(&shadow_prim_header, z_id_shadow, [
-                                            ShaderColorMode::Alpha as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
-                                            RasterizationSpace::Screen as i32,
-                                            get_shader_opacity(1.0),
-                                            0,
-                                        ]);
-
-                                        let shadow_instance = BrushInstance {
-                                            prim_header_index: shadow_prim_header_index,
-                                            clip_task_address,
-                                            render_task_address,
-                                            segment_index: INVALID_SEGMENT_INDEX,
-                                            edge_flags: EdgeAaSegmentMask::empty(),
-                                            brush_flags,
-                                            user_data: shadow_uv_rect_address,
-                                        };
-
-                                        let content_instance = BrushInstance {
-                                            prim_header_index: content_prim_header_index,
-                                            clip_task_address,
-                                            render_task_address,
-                                            segment_index: INVALID_SEGMENT_INDEX,
-                                            edge_flags: EdgeAaSegmentMask::empty(),
-                                            brush_flags,
-                                            user_data: content_uv_rect_address,
-                                        };
-
-                                        batcher.current_batch_list().push_single_instance(
-                                            shadow_key,
-                                            bounding_rect,
-                                            z_id_shadow,
-                                            PrimitiveInstanceData::from(shadow_instance),
-                                        );
-
-                                        batcher.current_batch_list().push_single_instance(
-                                            content_key,
-                                            bounding_rect,
-                                            z_id_content,
-                                            PrimitiveInstanceData::from(content_instance),
-                                        );
-                                    }
                                     _ => {
                                         let filter_mode = match filter {
                                             Filter::Identity => 1, // matches `Contrast(1)`
                                             Filter::Blur(..) => 0,
                                             Filter::Contrast(..) => 1,
                                             Filter::Grayscale(..) => 2,
                                             Filter::HueRotate(..) => 3,
                                             Filter::Invert(..) => 4,
                                             Filter::Saturate(..) => 5,
                                             Filter::Sepia(..) => 6,
                                             Filter::Brightness(..) => 7,
                                             Filter::Opacity(..) => 8,
-                                            Filter::DropShadow(..) |
                                             Filter::DropShadowStack(..) => 9,
                                             Filter::ColorMatrix(..) => 10,
                                             Filter::SrgbToLinear => 11,
                                             Filter::LinearToSrgb => 12,
                                             Filter::ComponentTransfer => unreachable!(),
                                         };
 
                                         let user_data = match filter {
@@ -1550,18 +1450,17 @@ impl BatchBuilder {
                                                 (amount * 65536.0) as i32
                                             }
                                             Filter::SrgbToLinear | Filter::LinearToSrgb => 0,
                                             Filter::HueRotate(angle) => {
                                                 (0.01745329251 * angle * 65536.0) as i32
                                             }
                                             // Go through different paths
                                             Filter::Blur(..) |
-                                            Filter::DropShadowStack(..) |
-                                            Filter::DropShadow(..) => {
+                                            Filter::DropShadowStack(..) => {
                                                 unreachable!();
                                             }
                                             Filter::ColorMatrix(_) => {
                                                 picture.extra_gpu_data_handles[0].as_int(gpu_cache)
                                             }
                                             Filter::ComponentTransfer => unreachable!(),
                                         };
 
@@ -2469,17 +2368,17 @@ impl BatchBuilder {
         segment: &BrushSegment,
         segment_data: &SegmentInstanceData,
         segment_index: i32,
         batch_kind: BrushBatchKind,
         prim_header_index: PrimitiveHeaderIndex,
         alpha_blend_mode: BlendMode,
         bounding_rect: &PictureRect,
         transform_kind: TransformedRectKind,
-        render_tasks: &RenderTaskTree,
+        render_tasks: &RenderTaskGraph,
         z_id: ZBufferId,
         prim_opacity: PrimitiveOpacity,
         render_task_address: RenderTaskAddress,
         clip_task_index: ClipTaskIndex,
         ctx: &RenderTargetContext,
     ) {
         debug_assert!(clip_task_index != ClipTaskIndex::INVALID);
 
@@ -2531,17 +2430,17 @@ impl BatchBuilder {
         brush_segments: Option<&[BrushSegment]>,
         prim_opacity: PrimitiveOpacity,
         params: &BrushBatchParameters,
         alpha_blend_mode: BlendMode,
         non_segmented_blend_mode: BlendMode,
         prim_header_index: PrimitiveHeaderIndex,
         bounding_rect: &PictureRect,
         transform_kind: TransformedRectKind,
-        render_tasks: &RenderTaskTree,
+        render_tasks: &RenderTaskGraph,
         z_id: ZBufferId,
         render_task_address: RenderTaskAddress,
         clip_task_index: ClipTaskIndex,
         ctx: &RenderTargetContext,
     ) {
         match (brush_segments, &params.segment_data) {
             (Some(ref brush_segments), SegmentDataKind::Instanced(ref segment_data)) => {
                 // In this case, we have both a list of segments, and a list of
@@ -2800,17 +2699,17 @@ impl PrimitiveInstance {
             Some(ImageProperties { external_image: Some(_), .. }) => {
                 false
             }
             _ => true
         }
     }
 }
 
-impl RenderTaskTree {
+impl RenderTaskGraph {
     fn resolve_surface(
         &self,
         task_id: RenderTaskId,
         gpu_cache: &GpuCache,
     ) -> (GpuCacheAddress, BatchTextures) {
         (
             self[task_id].get_texture_address(gpu_cache),
             BatchTextures::render_target_cache(),
@@ -3268,17 +3167,17 @@ impl<'a, 'rc> RenderTargetContext<'a, 'r
     /// Retrieve the GPU task address for a given clip task instance.
     /// Returns None if the segment was completely clipped out.
     /// Returns Some(OPAQUE_TASK_ADDRESS) if no clip mask is needed.
     /// Returns Some(task_address) if there was a valid clip mask.
     fn get_clip_task_address(
         &self,
         clip_task_index: ClipTaskIndex,
         offset: i32,
-        render_tasks: &RenderTaskTree,
+        render_tasks: &RenderTaskGraph,
     ) -> Option<RenderTaskAddress> {
         let address = match self.scratch.clip_mask_instances[clip_task_index.0 as usize + offset as usize] {
             ClipMaskKind::Mask(task_id) => {
                 render_tasks.get_task_address(task_id)
             }
             ClipMaskKind::None => {
                 OPAQUE_TASK_ADDRESS
             }
@@ -3290,17 +3189,17 @@ impl<'a, 'rc> RenderTargetContext<'a, 'r
         Some(address)
     }
 
     /// Helper function to get the clip task address for a
     /// non-segmented primitive.
     fn get_prim_clip_task_address(
         &self,
         clip_task_index: ClipTaskIndex,
-        render_tasks: &RenderTaskTree,
+        render_tasks: &RenderTaskGraph,
     ) -> Option<RenderTaskAddress> {
         self.get_clip_task_address(
             clip_task_index,
             0,
             render_tasks,
         )
     }
 }
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -16,17 +16,17 @@ use crate::hit_test::HitTestingSceneStat
 use crate::internal_types::{FastHashMap, PlaneSplitter};
 use crate::picture::{PictureUpdateState, SurfaceInfo, ROOT_SURFACE_INDEX, SurfaceIndex};
 use crate::picture::{RetainedTiles, TileCache, DirtyRegion, SurfaceRenderTasks};
 use crate::prim_store::{PrimitiveStore, SpaceMapper, PictureIndex, PrimitiveDebugId, PrimitiveScratchBuffer};
 #[cfg(feature = "replay")]
 use crate::prim_store::{PrimitiveStoreStats};
 use crate::profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use crate::render_backend::{DataStores, FrameStamp};
-use crate::render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree, RenderTaskTreeCounters};
+use crate::render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskGraph, RenderTaskGraphCounters};
 use crate::resource_cache::{ResourceCache};
 use crate::scene::{ScenePipeline, SceneProperties};
 use crate::scene_builder::DocumentStats;
 use crate::segment::SegmentBuilder;
 use std::{f32, mem};
 use std::sync::Arc;
 use crate::tiling::{Frame, RenderPassKind, RenderTargetContext, RenderTarget};
 use crate::util::MaxRect;
@@ -128,32 +128,32 @@ pub struct FrameVisibilityState<'a> {
     pub clip_store: &'a mut ClipStore,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub scratch: &'a mut PrimitiveScratchBuffer,
     pub tile_cache: Option<TileCache>,
     pub retained_tiles: &'a mut RetainedTiles,
     pub data_stores: &'a mut DataStores,
     pub clip_chain_stack: ClipChainStack,
-    pub render_tasks: &'a mut RenderTaskTree,
+    pub render_tasks: &'a mut RenderTaskGraph,
 }
 
 pub struct FrameBuildingContext<'a> {
     pub global_device_pixel_scale: DevicePixelScale,
     pub scene_properties: &'a SceneProperties,
     pub pipelines: &'a FastHashMap<PipelineId, Arc<ScenePipeline>>,
     pub screen_world_rect: WorldRect,
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub max_local_clip: LayoutRect,
     pub debug_flags: DebugFlags,
     pub fb_config: &'a FrameBuilderConfig,
 }
 
 pub struct FrameBuildingState<'a> {
-    pub render_tasks: &'a mut RenderTaskTree,
+    pub render_tasks: &'a mut RenderTaskGraph,
     pub profile_counters: &'a mut FrameProfileCounters,
     pub clip_store: &'a mut ClipStore,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub transforms: &'a mut TransformPalette,
     pub segment_builder: SegmentBuilder,
     pub surfaces: &'a mut Vec<SurfaceInfo>,
     pub dirty_region_stack: Vec<DirtyRegion>,
@@ -298,17 +298,17 @@ impl FrameBuilder {
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(
         &mut self,
         screen_world_rect: WorldRect,
         clip_scroll_tree: &ClipScrollTree,
         pipelines: &FastHashMap<PipelineId, Arc<ScenePipeline>>,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
-        render_tasks: &mut RenderTaskTree,
+        render_tasks: &mut RenderTaskGraph,
         profile_counters: &mut FrameProfileCounters,
         global_device_pixel_scale: DevicePixelScale,
         scene_properties: &SceneProperties,
         transform_palette: &mut TransformPalette,
         data_stores: &mut DataStores,
         surfaces: &mut Vec<SurfaceInfo>,
         scratch: &mut PrimitiveScratchBuffer,
         debug_flags: DebugFlags,
@@ -511,17 +511,17 @@ impl FrameBuilder {
         layer: DocumentLayer,
         device_origin: DeviceIntPoint,
         pan: WorldPoint,
         texture_cache_profile: &mut TextureCacheProfileCounters,
         gpu_cache_profile: &mut GpuCacheProfileCounters,
         scene_properties: &SceneProperties,
         data_stores: &mut DataStores,
         scratch: &mut PrimitiveScratchBuffer,
-        render_task_counters: &mut RenderTaskTreeCounters,
+        render_task_counters: &mut RenderTaskGraphCounters,
         debug_flags: DebugFlags,
     ) -> Frame {
         profile_scope!("build");
         profile_marker!("BuildFrame");
 
         let mut profile_counters = FrameProfileCounters::new();
         profile_counters
             .total_primitives
@@ -534,17 +534,17 @@ impl FrameBuilder {
 
         clip_scroll_tree.update_tree(
             pan,
             scene_properties,
         );
         let mut transform_palette = clip_scroll_tree.build_transform_palette();
         self.clip_store.clear_old_instances();
 
-        let mut render_tasks = RenderTaskTree::new(
+        let mut render_tasks = RenderTaskGraph::new(
             stamp.frame_id(),
             render_task_counters,
         );
         let mut surfaces = Vec::new();
 
         let output_size = self.output_rect.size.to_i32();
         let screen_world_rect = (self.output_rect.to_f32() / global_device_pixel_scale).round_out();
 
--- a/gfx/wr/webrender/src/glyph_rasterizer/mod.rs
+++ b/gfx/wr/webrender/src/glyph_rasterizer/mod.rs
@@ -840,17 +840,17 @@ mod test_glyph_rasterizer {
         // 50 glyphs each, deletes the font and waits for the result.
 
         use rayon::ThreadPoolBuilder;
         use std::fs::File;
         use std::io::Read;
         use crate::texture_cache::TextureCache;
         use crate::glyph_cache::GlyphCache;
         use crate::gpu_cache::GpuCache;
-        use crate::render_task::{RenderTaskCache, RenderTaskTree, RenderTaskTreeCounters};
+        use crate::render_task::{RenderTaskCache, RenderTaskGraph, RenderTaskGraphCounters};
         use crate::profiler::TextureCacheProfileCounters;
         use api::{FontKey, FontInstanceKey, FontTemplate, FontRenderMode,
                   IdNamespace, ColorU};
         use api::units::{Au, DevicePoint};
         use crate::render_backend::FrameId;
         use thread_profiler::register_thread_with_profiler;
         use std::sync::Arc;
         use crate::glyph_rasterizer::{FontInstance, BaseFontInstance, GlyphKey, GlyphRasterizer};
@@ -862,17 +862,17 @@ mod test_glyph_rasterizer {
             })
             .build();
         let workers = Arc::new(worker.unwrap());
         let mut glyph_rasterizer = GlyphRasterizer::new(workers).unwrap();
         let mut glyph_cache = GlyphCache::new();
         let mut gpu_cache = GpuCache::new_for_testing();
         let mut texture_cache = TextureCache::new_for_testing(2048, 1024);
         let mut render_task_cache = RenderTaskCache::new();
-        let mut render_task_tree = RenderTaskTree::new(FrameId::INVALID, &RenderTaskTreeCounters::new());
+        let mut render_task_tree = RenderTaskGraph::new(FrameId::INVALID, &RenderTaskGraphCounters::new());
         let mut font_file =
             File::open("../wrench/reftests/text/VeraBd.ttf").expect("Couldn't open font file");
         let mut font_data = vec![];
         font_file
             .read_to_end(&mut font_data)
             .expect("failed to read font file");
 
         let font_key = FontKey::new(IdNamespace(0), 0);
--- a/gfx/wr/webrender/src/glyph_rasterizer/no_pathfinder.rs
+++ b/gfx/wr/webrender/src/glyph_rasterizer/no_pathfinder.rs
@@ -13,17 +13,17 @@ use rayon::prelude::*;
 use std::sync::{Arc, MutexGuard};
 use crate::platform::font::FontContext;
 use crate::glyph_rasterizer::{FontInstance, FontContexts, GlyphKey};
 use crate::glyph_rasterizer::{GlyphRasterizer, GlyphRasterJob, GlyphRasterJobs};
 use crate::glyph_cache::{GlyphCache, CachedGlyphInfo, GlyphCacheEntry};
 use crate::resource_cache::CachedImageData;
 use crate::texture_cache::{TextureCache, TextureCacheHandle, Eviction};
 use crate::gpu_cache::GpuCache;
-use crate::render_task::{RenderTaskTree, RenderTaskCache};
+use crate::render_task::{RenderTaskGraph, RenderTaskCache};
 use crate::profiler::TextureCacheProfileCounters;
 use std::collections::hash_map::Entry;
 
 impl FontContexts {
     /// Get access to the font context associated to the current thread.
     pub fn lock_current_context(&self) -> MutexGuard<FontContext> {
         let id = self.current_worker_id();
         self.lock_context(id)
@@ -39,17 +39,17 @@ impl GlyphRasterizer {
     pub fn request_glyphs(
         &mut self,
         glyph_cache: &mut GlyphCache,
         font: FontInstance,
         glyph_keys: &[GlyphKey],
         texture_cache: &mut TextureCache,
         gpu_cache: &mut GpuCache,
         _: &mut RenderTaskCache,
-        _: &mut RenderTaskTree,
+        _: &mut RenderTaskGraph,
     ) {
         assert!(
             self.font_contexts
                 .lock_shared_context()
                 .has_font(&font.font_key)
         );
         let mut new_glyphs = Vec::new();
 
@@ -134,17 +134,17 @@ impl GlyphRasterizer {
     }
 
     pub fn resolve_glyphs(
         &mut self,
         glyph_cache: &mut GlyphCache,
         texture_cache: &mut TextureCache,
         gpu_cache: &mut GpuCache,
         _: &mut RenderTaskCache,
-        _: &mut RenderTaskTree,
+        _: &mut RenderTaskGraph,
         _: &mut TextureCacheProfileCounters,
     ) {
         // Pull rasterized glyphs from the queue and update the caches.
         while self.pending_glyphs > 0 {
             self.pending_glyphs -= 1;
 
             // TODO: rather than blocking until all pending glyphs are available
             // we could try_recv and steal work from the thread pool to take advantage
--- a/gfx/wr/webrender/src/glyph_rasterizer/pathfinder.rs
+++ b/gfx/wr/webrender/src/glyph_rasterizer/pathfinder.rs
@@ -5,17 +5,17 @@
 //! Module only available when pathfinder is activated
 
 use api::{FontKey, FontTemplate, NativeFontHandle};
 use api::units::{DeviceIntPoint, DeviceIntSize, DevicePixel};
 use euclid::{TypedPoint2D, TypedSize2D, TypedVector2D};
 use pathfinder_font_renderer;
 use pathfinder_partitioner::mesh::Mesh as PathfinderMesh;
 use pathfinder_path_utils::cubic_to_quadratic::CubicToQuadraticTransformer;
-use crate::render_task::{RenderTask, RenderTaskTree, RenderTaskCache, RenderTaskCacheKey,
+use crate::render_task::{RenderTask, RenderTaskGraph, RenderTaskCache, RenderTaskCacheKey,
                          RenderTaskCacheEntryHandle, RenderTaskCacheKeyKind, RenderTaskId,
                          RenderTaskLocation};
 use crate::resource_cache::CacheItem;
 use std::ops::Deref;
 use std::sync::{Arc, Mutex, MutexGuard};
 use crate::glyph_rasterizer::AddFont;
 use crate::internal_types::ResourceCacheError;
 use crate::glyph_cache::{GlyphCache, GlyphCacheEntry, CachedGlyphInfo};
@@ -117,17 +117,17 @@ impl GlyphRasterizer {
         &mut self,
         glyph_key: &GlyphKey,
         font: &FontInstance,
         scale: f32,
         cached_glyph_info: CachedGlyphInfo,
         texture_cache: &mut TextureCache,
         gpu_cache: &mut GpuCache,
         render_task_cache: &mut RenderTaskCache,
-        render_task_tree: &mut RenderTaskTree,
+        render_task_tree: &mut RenderTaskGraph,
     ) -> Result<(RenderTaskCacheEntryHandle,GlyphFormat), ()> {
         let mut pathfinder_font_context = self.font_contexts.lock_pathfinder_context();
         let render_task_cache_key = cached_glyph_info.render_task_cache_key;
         let (glyph_origin, glyph_size) = (cached_glyph_info.origin, render_task_cache_key.size);
         let user_data = [glyph_origin.x as f32, (glyph_origin.y - glyph_size.height) as f32, scale];
         let handle = render_task_cache.request_render_task(render_task_cache_key,
                                                            texture_cache,
                                                            gpu_cache,
@@ -152,17 +152,17 @@ impl GlyphRasterizer {
     pub fn request_glyphs(
         &mut self,
         glyph_cache: &mut GlyphCache,
         font: FontInstance,
         glyph_keys: &[GlyphKey],
         texture_cache: &mut TextureCache,
         gpu_cache: &mut GpuCache,
         render_task_cache: &mut RenderTaskCache,
-        render_task_tree: &mut RenderTaskTree,
+        render_task_tree: &mut RenderTaskGraph,
     ) {
         debug_assert!(self.font_contexts.lock_shared_context().has_font(&font.font_key));
 
         let glyph_key_cache = glyph_cache.get_glyph_key_cache_for_font_mut(font.clone());
 
         let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
         let scale = font.oversized_scale_factor(x_scale, y_scale) as f32;
 
@@ -239,17 +239,17 @@ impl GlyphRasterizer {
     }
 
     pub fn resolve_glyphs(
         &mut self,
         _: &mut GlyphCache,
         _: &mut TextureCache,
         _: &mut GpuCache,
         _: &mut RenderTaskCache,
-        _: &mut RenderTaskTree,
+        _: &mut RenderTaskGraph,
         _: &mut TextureCacheProfileCounters,
     ) {
         self.remove_dead_fonts();
     }
 }
 
 impl FontContexts {
     pub fn lock_pathfinder_context(&self) -> MutexGuard<PathfinderFontContext> {
@@ -264,17 +264,17 @@ fn compute_embolden_amount(ppem: f32) ->
 
 fn request_render_task_from_pathfinder(
     glyph_key: &GlyphKey,
     font: &FontInstance,
     scale: f32,
     glyph_origin: &DeviceIntPoint,
     glyph_size: &DeviceIntSize,
     font_context: &mut PathfinderFontContext,
-    render_tasks: &mut RenderTaskTree,
+    render_tasks: &mut RenderTaskGraph,
 ) -> Result<RenderTaskId, ()> {
     let size = font.size.scale_by(scale.recip());
     let pathfinder_font_instance = pathfinder_font_renderer::FontInstance {
         font_key: font.font_key.clone(),
         size,
     };
 
     // TODO: pathfinder will need to support 2D subpixel offset
--- a/gfx/wr/webrender/src/internal_types.rs
+++ b/gfx/wr/webrender/src/internal_types.rs
@@ -7,16 +7,17 @@ use api::{ImageFormat, ItemTag, Notifica
 use api::units::*;
 use api;
 use crate::device::TextureFilter;
 use crate::renderer::PipelineInfo;
 use crate::gpu_cache::GpuCacheUpdateList;
 use fxhash::FxHasher;
 use plane_split::BspSplitter;
 use crate::profiler::BackendProfileCounters;
+use smallvec::SmallVec;
 use std::{usize, i32};
 use std::collections::{HashMap, HashSet};
 use std::f32;
 use std::hash::BuildHasherDefault;
 use std::path::PathBuf;
 use std::sync::Arc;
 
 #[cfg(feature = "capture")]
@@ -44,36 +45,31 @@ pub enum Filter {
     Brightness(f32),
     Contrast(f32),
     Grayscale(f32),
     HueRotate(f32),
     Invert(f32),
     Opacity(api::PropertyBinding<f32>, f32),
     Saturate(f32),
     Sepia(f32),
-    DropShadow(Shadow),
-    #[allow(dead_code)]
-    DropShadowStack(Vec<Shadow>),
-    ColorMatrix([f32; 20]),
+    DropShadowStack(SmallVec<[Shadow; 1]>),
+    ColorMatrix(Box<[f32; 20]>),
     SrgbToLinear,
     LinearToSrgb,
     ComponentTransfer,
 }
 
 impl Filter {
     /// Ensure that the parameters for a filter operation
     /// are sensible.
     pub fn sanitize(&mut self) {
         match self {
             Filter::Blur(ref mut radius) => {
                 *radius = radius.min(MAX_BLUR_RADIUS);
             }
-            Filter::DropShadow(ref mut shadow) => {
-                shadow.blur_radius = shadow.blur_radius.min(MAX_BLUR_RADIUS);
-            }
             Filter::DropShadowStack(ref mut stack) => {
                 for shadow in stack {
                     shadow.blur_radius = shadow.blur_radius.min(MAX_BLUR_RADIUS);
                 }
             }
             _ => {},
         }
     }
@@ -84,17 +80,16 @@ impl Filter {
             Filter::Blur(..) |
             Filter::Brightness(..) |
             Filter::Contrast(..) |
             Filter::Grayscale(..) |
             Filter::HueRotate(..) |
             Filter::Invert(..) |
             Filter::Saturate(..) |
             Filter::Sepia(..) |
-            Filter::DropShadow(..) |
             Filter::DropShadowStack(..) |
             Filter::ColorMatrix(..) |
             Filter::SrgbToLinear |
             Filter::LinearToSrgb |
             Filter::ComponentTransfer  => true,
             Filter::Opacity(_, amount) => {
                 amount > OPACITY_EPSILON
             }
@@ -117,25 +112,24 @@ impl Filter {
                 for shadow in shadows {
                     if shadow.offset.x != 0.0 || shadow.offset.y != 0.0 || shadow.blur_radius != 0.0 {
                         return false;
                     }
                 }
 
                 true
             }
-            Filter::DropShadow(shadow) => {
-                shadow.offset.x == 0.0 && shadow.offset.y == 0.0 && shadow.blur_radius == 0.0
-            },
-            Filter::ColorMatrix(matrix) => {
-                matrix == [1.0, 0.0, 0.0, 0.0,
-                           0.0, 1.0, 0.0, 0.0,
-                           0.0, 0.0, 1.0, 0.0,
-                           0.0, 0.0, 0.0, 1.0,
-                           0.0, 0.0, 0.0, 0.0]
+            Filter::ColorMatrix(ref matrix) => {
+                **matrix == [
+                    1.0, 0.0, 0.0, 0.0,
+                    0.0, 1.0, 0.0, 0.0,
+                    0.0, 0.0, 1.0, 0.0,
+                    0.0, 0.0, 0.0, 1.0,
+                    0.0, 0.0, 0.0, 0.0
+                ]
             }
             Filter::SrgbToLinear |
             Filter::LinearToSrgb |
             Filter::ComponentTransfer => false,
         }
     }
 }
 
@@ -147,21 +141,21 @@ impl From<FilterOp> for Filter {
             FilterOp::Brightness(b) => Filter::Brightness(b),
             FilterOp::Contrast(c) => Filter::Contrast(c),
             FilterOp::Grayscale(g) => Filter::Grayscale(g),
             FilterOp::HueRotate(h) => Filter::HueRotate(h),
             FilterOp::Invert(i) => Filter::Invert(i),
             FilterOp::Opacity(binding, opacity) => Filter::Opacity(binding, opacity),
             FilterOp::Saturate(s) => Filter::Saturate(s),
             FilterOp::Sepia(s) => Filter::Sepia(s),
-            FilterOp::DropShadow(shadow) => Filter::DropShadow(shadow),
-            FilterOp::ColorMatrix(mat) => Filter::ColorMatrix(mat),
+            FilterOp::ColorMatrix(mat) => Filter::ColorMatrix(Box::new(mat)),
             FilterOp::SrgbToLinear => Filter::SrgbToLinear,
             FilterOp::LinearToSrgb => Filter::LinearToSrgb,
             FilterOp::ComponentTransfer => Filter::ComponentTransfer,
+            FilterOp::DropShadow(shadow) => Filter::DropShadowStack(smallvec![shadow]),
         }
     }
 }
 
 /// An ID for a texture that is owned by the `texture_cache` module.
 ///
 /// This can include atlases or standalone textures allocated via the texture
 /// cache (e.g.  if an image is too large to be added to an atlas). The texture
--- a/gfx/wr/webrender/src/lib.rs
+++ b/gfx/wr/webrender/src/lib.rs
@@ -183,16 +183,17 @@ extern crate pathfinder_partitioner;
 extern crate pathfinder_path_utils;
 extern crate plane_split;
 extern crate rayon;
 #[cfg(feature = "ron")]
 extern crate ron;
 #[cfg(feature = "debugger")]
 extern crate serde_json;
 extern crate sha2;
+#[macro_use]
 extern crate smallvec;
 extern crate time;
 #[cfg(feature = "debugger")]
 extern crate ws;
 #[cfg(feature = "debugger")]
 extern crate image_loader;
 #[cfg(feature = "debugger")]
 extern crate base64;
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -2524,72 +2524,16 @@ impl PicturePrimitive {
                             frame_state.render_tasks,
                             RenderTargetKind::Color,
                             ClearMode::Transparent,
                             None,
                         );
 
                         (blur_render_task_id, picture_task_id)
                     }
-                    PictureCompositeMode::Filter(Filter::DropShadow(shadow)) => {
-                        let blur_std_deviation = shadow.blur_radius * device_pixel_scale.0;
-                        let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
-                        let rounded_std_dev = blur_std_deviation.round();
-                        let rounded_std_dev = DeviceSize::new(rounded_std_dev, rounded_std_dev);
-                        // The clipped field is the part of the picture that is visible
-                        // on screen. The unclipped field is the screen-space rect of
-                        // the complete picture, if no screen / clip-chain was applied
-                        // (this includes the extra space for blur region). To ensure
-                        // that we draw a large enough part of the picture to get correct
-                        // blur results, inflate that clipped area by the blur range, and
-                        // then intersect with the total screen rect, to minimize the
-                        // allocation size.
-                        let mut device_rect = clipped.inflate(blur_range, blur_range)
-                                .intersection(&unclipped.to_i32())
-                                .unwrap();
-                        device_rect.size = RenderTask::adjusted_blur_source_size(
-                            device_rect.size,
-                            rounded_std_dev,
-                        );
-
-                        let uv_rect_kind = calculate_uv_rect_kind(
-                            &pic_rect,
-                            &transform,
-                            &device_rect,
-                            device_pixel_scale,
-                            true,
-                        );
-
-                        let mut picture_task = RenderTask::new_picture(
-                            RenderTaskLocation::Dynamic(None, device_rect.size),
-                            unclipped.size,
-                            pic_index,
-                            device_rect.origin,
-                            Vec::new(),
-                            uv_rect_kind,
-                            raster_spatial_node_index,
-                            device_pixel_scale,
-                        );
-                        picture_task.mark_for_saving();
-
-                        let picture_task_id = frame_state.render_tasks.add(picture_task);
-
-                        let blur_render_task_id = RenderTask::new_blur(
-                            rounded_std_dev,
-                            picture_task_id,
-                            frame_state.render_tasks,
-                            RenderTargetKind::Color,
-                            ClearMode::Transparent,
-                            None,
-                        );
-
-                        self.secondary_render_task_id = Some(picture_task_id);
-
-                        (blur_render_task_id, picture_task_id)
-                    }
                     PictureCompositeMode::Filter(Filter::DropShadowStack(ref shadows)) => {
                         let mut max_std_deviation = 0.0;
                         for shadow in shadows {
                             // TODO(nical) presumably we should compute the clipped rect for each shadow
                             // and compute the union of them to determine what we need to rasterize and blur?
                             max_std_deviation = f32::max(max_std_deviation, shadow.blur_radius * device_pixel_scale.0);
                         }
         
@@ -2640,17 +2584,17 @@ impl PicturePrimitive {
                                 frame_state.render_tasks,
                                 RenderTargetKind::Color,
                                 ClearMode::Transparent,
                                 Some(&mut blur_tasks),
                             );      
                         }
         
                         // TODO(nical) the second one should to be the blur's task id but we have several blurs now
-                        (picture_task_id, blur_render_task_id)
+                        (blur_render_task_id, picture_task_id)
                     }
                     PictureCompositeMode::MixBlend(..) if !frame_context.fb_config.gpu_supports_advanced_blend => {
                         let uv_rect_kind = calculate_uv_rect_kind(
                             &pic_rect,
                             &transform,
                             &clipped,
                             device_pixel_scale,
                             true,
@@ -3189,19 +3133,16 @@ impl PicturePrimitive {
             let mut surface_rect = {
                 let surface = state.current_surface_mut();
                 // Inflate the local bounding rect if required by the filter effect.
                 // This inflaction factor is to be applied to the surface itsefl.
                 // TODO: in prepare_for_render we round before multiplying with the
                 // blur sample scale. Should we do this here as well?
                 let inflation_size = match raster_config.composite_mode {
                     PictureCompositeMode::Filter(Filter::Blur(_)) => surface.inflation_factor,
-                    PictureCompositeMode::Filter(Filter::DropShadow(shadow)) => {
-                        (shadow.blur_radius * BLUR_SAMPLE_SCALE).ceil()
-                    }
                     PictureCompositeMode::Filter(Filter::DropShadowStack(ref shadows)) => {
                         let mut max = 0.0;
                         for shadow in shadows {
                             max = f32::max(max, shadow.blur_radius * BLUR_SAMPLE_SCALE);
                         }
                         max.ceil()
                     }
                     _ => 0.0,
@@ -3227,21 +3168,16 @@ impl PicturePrimitive {
                     raster_config.establishes_raster_root = false;
                     state.are_raster_roots_assigned = false;
                 }
             }
 
             // Drop shadows draw both a content and shadow rect, so need to expand the local
             // rect of any surfaces to be composited in parent surfaces correctly.
             match raster_config.composite_mode {
-                PictureCompositeMode::Filter(Filter::DropShadow(shadow)) => {
-                    let content_rect = surface_rect;
-                    let shadow_rect = surface_rect.translate(&shadow.offset);
-                    surface_rect = content_rect.union(&shadow_rect);
-                }
                 PictureCompositeMode::Filter(Filter::DropShadowStack(ref shadows)) => {
                     for shadow in shadows {
                         let content_rect = surface_rect;
                         let shadow_rect = surface_rect.translate(&shadow.offset);
                         surface_rect = content_rect.union(&shadow_rect);
                     }
                 }
                 _ => {}
@@ -3290,49 +3226,16 @@ impl PicturePrimitive {
         //           with a ColorMatrix, which stores the color matrix here. It's
         //           probably worth tidying this code up to be a bit more consistent.
         //           Perhaps store the color matrix after the common data, even though
         //           it's not used by that shader.
 
         match raster_config.composite_mode {
             PictureCompositeMode::TileCache { .. } => {}
             PictureCompositeMode::Filter(Filter::Blur(..)) => {}
-            PictureCompositeMode::Filter(Filter::DropShadow(shadow)) => {
-                if self.extra_gpu_data_handles.is_empty() {
-                    self.extra_gpu_data_handles.push(GpuCacheHandle::new());
-                }
-
-                if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handles[0]) {
-                    // TODO(gw): This is very hacky code below! It stores an extra
-                    //           brush primitive below for the special case of a
-                    //           drop-shadow where we need a different local
-                    //           rect for the shadow. To tidy this up in future,
-                    //           we could consider abstracting the code in prim_store.rs
-                    //           that writes a brush primitive header.
-
-                    // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
-                    //  [brush specific data]
-                    //  [segment_rect, segment data]
-                    let shadow_rect = self.snapped_local_rect.translate(&shadow.offset);
-
-                    // ImageBrush colors
-                    request.push(shadow.color.premultiplied());
-                    request.push(PremultipliedColorF::WHITE);
-                    request.push([
-                        self.snapped_local_rect.size.width,
-                        self.snapped_local_rect.size.height,
-                        0.0,
-                        0.0,
-                    ]);
-
-                    // segment rect / extra data
-                    request.push(shadow_rect);
-                    request.push([0.0, 0.0, 0.0, 0.0]);
-                }
-            }
             PictureCompositeMode::Filter(Filter::DropShadowStack(ref shadows)) => {
                 self.extra_gpu_data_handles.resize(shadows.len(), GpuCacheHandle::new());
                 for (shadow, extra_handle) in shadows.iter().zip(self.extra_gpu_data_handles.iter_mut()) {
                     if let Some(mut request) = frame_state.gpu_cache.request(extra_handle) {
                         // Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
                         //  [brush specific data]
                         //  [segment_rect, segment data]
                         let shadow_rect = self.snapped_local_rect.translate(&shadow.offset);
@@ -3350,17 +3253,17 @@ impl PicturePrimitive {
                         // segment rect / extra data
                         request.push(shadow_rect);
                         request.push([0.0, 0.0, 0.0, 0.0]);
                     }
                 }
             }
             PictureCompositeMode::MixBlend(..) if !frame_context.fb_config.gpu_supports_advanced_blend => {}
             PictureCompositeMode::Filter(ref filter) => {
-                if let Filter::ColorMatrix(m) = *filter {
+                if let Filter::ColorMatrix(ref m) = *filter {
                     if self.extra_gpu_data_handles.is_empty() {
                         self.extra_gpu_data_handles.push(GpuCacheHandle::new());
                     }
                     if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handles[0]) {
                         for i in 0..5 {
                             request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
                         }
                     }
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -1412,17 +1412,17 @@ pub struct PrimitiveInstance {
     pub prim_origin: LayoutPoint,
 
     /// Local space clip rect for this instance
     pub local_clip_rect: LayoutRect,
 
     #[cfg(debug_assertions)]
     pub id: PrimitiveDebugId,
 
-    /// The last frame ID (of the `RenderTaskTree`) this primitive
+    /// The last frame ID (of the `RenderTaskGraph`) this primitive
     /// was prepared for rendering in.
     #[cfg(debug_assertions)]
     pub prepared_frame_id: FrameId,
 
     /// If this primitive is visible, an index into the instance
     /// visibility scratch buffer. If not visible, INVALID.
     pub visibility_info: PrimitiveVisibilityIndex,
 
@@ -1876,17 +1876,16 @@ impl PrimitiveStore {
                     } else {
                         frame_state.clip_chain_stack.pop_clip();
                     }
 
                     let shadow_rect = match pic.raster_config {
                         Some(ref rc) => match rc.composite_mode {
                             // If we have a drop shadow filter, we also need to include the shadow in
                             // our local rect for the purpose of calculating the size of the picture.
-                            PictureCompositeMode::Filter(Filter::DropShadow(shadow)) => pic.snapped_local_rect.translate(&shadow.offset),
                             PictureCompositeMode::Filter(Filter::DropShadowStack(ref shadows)) => {
                                 let mut rect = LayoutRect::zero();
                                 for shadow in shadows {
                                     rect = rect.union(&pic.snapped_local_rect.translate(&shadow.offset));
                                 }
 
                                 rect
                             }
@@ -2167,38 +2166,33 @@ impl PrimitiveStore {
         // TODO(gw): In future, if we support specifying a flag which gets the
         //           stretch size from the segment rect in the shaders, we can
         //           remove this invalidation here completely.
         if let Some(ref raster_config) = pic.raster_config {
             // Inflate the local bounding rect if required by the filter effect.
             // This inflaction factor is to be applied to the surface itself.
             let inflation_size = match raster_config.composite_mode {
                 PictureCompositeMode::Filter(Filter::Blur(_)) => surface.inflation_factor,
-                PictureCompositeMode::Filter(Filter::DropShadow(shadow)) => {
-                    (shadow.blur_radius * BLUR_SAMPLE_SCALE).ceil()
-                }
                 PictureCompositeMode::Filter(Filter::DropShadowStack(ref shadows)) => {
                     let mut max = 0.0;
                     for shadow in shadows {
                         max = f32::max(max, shadow.blur_radius * BLUR_SAMPLE_SCALE);
                     }
                     max.ceil()
                 }
                 _ => 0.0,
             };
             surface_rect = surface_rect.inflate(inflation_size, inflation_size);
 
             // Layout space for the picture is picture space from the
             // perspective of its child primitives.
             let pic_local_rect = surface_rect * TypedScale::new(1.0);
             if pic.snapped_local_rect != pic_local_rect {
                 match raster_config.composite_mode {
-                    PictureCompositeMode::Filter(Filter::DropShadow(..)) 
-                    | PictureCompositeMode::Filter(Filter::DropShadowStack(..))
-                    => {
+                    PictureCompositeMode::Filter(Filter::DropShadowStack(..)) => {
                         for handle in &pic.extra_gpu_data_handles {
                             frame_state.gpu_cache.invalidate(handle);
                         }
                     }
                     _ => {}
                 }
                 // Invalidate any segments built for this picture, since the local
                 // rect has changed.
@@ -2677,17 +2671,17 @@ impl PrimitiveStore {
                 let dirty_region = frame_state.current_dirty_region();
 
                 // Check if the primitive world rect intersects with the overall dirty rect first.
                 match visibility_info.clipped_world_rect.intersection(&dirty_region.combined.world_rect) {
                     Some(rect) => {
                         // It does intersect the overall dirty rect, so it *might* be visible.
                         // Store this reduced rect here, which is used for clip mask and other
                         // render task size calculations. In future, we may consider creating multiple
-                        // render task trees, one per dirty region.
+                        // render task graphs, one per dirty region.
                         visibility_info.clipped_world_rect = rect;
 
                         // If there is more than one dirty region, it's possible that this primitive
                         // is inside the overal dirty rect, but doesn't intersect any of the individual
                         // dirty rects. If that's the case, then we can skip drawing this primitive too.
                         if dirty_region.dirty_rects.len() > 1 {
                             for (region_index, region) in dirty_region.dirty_rects.iter().enumerate() {
                                 if visibility_info.clipped_world_rect.intersects(&region.world_rect) {
--- a/gfx/wr/webrender/src/prim_store/picture.rs
+++ b/gfx/wr/webrender/src/prim_store/picture.rs
@@ -33,17 +33,16 @@ pub enum PictureCompositeKey {
     Contrast(Au),
     Grayscale(Au),
     HueRotate(Au),
     Invert(Au),
     Opacity(Au),
     OpacityBinding(PropertyBindingId, Au),
     Saturate(Au),
     Sepia(Au),
-    DropShadow(VectorKey, Au, ColorU),
     DropShadowStack(Vec<(VectorKey, Au, ColorU)>),
     ColorMatrix([Au; 20]),
     SrgbToLinear,
     LinearToSrgb,
     ComponentTransfer(ItemUid),
 
     // MixBlendMode
     Multiply,
@@ -101,19 +100,16 @@ impl From<Option<PictureCompositeMode>> 
                     Filter::Identity => PictureCompositeKey::Identity,
                     Filter::DropShadowStack(ref shadows) => {
                         PictureCompositeKey::DropShadowStack(
                             shadows.iter().map(|shadow| {
                                 (shadow.offset.into(), Au::from_f32_px(shadow.blur_radius), shadow.color.into())
                             }).collect()
                         )
                     }
-                    Filter::DropShadow(shadow) => {
-                        PictureCompositeKey::DropShadow(shadow.offset.into(), Au::from_f32_px(shadow.blur_radius), shadow.color.into())
-                    }
                     Filter::Opacity(binding, _) => {
                         match binding {
                             PropertyBinding::Value(value) => {
                                 PictureCompositeKey::Opacity(Au::from_f32_px(value))
                             }
                             PropertyBinding::Binding(key, default) => {
                                 PictureCompositeKey::OpacityBinding(key.id, Au::from_f32_px(default))
                             }
--- a/gfx/wr/webrender/src/prim_store/text_run.rs
+++ b/gfx/wr/webrender/src/prim_store/text_run.rs
@@ -8,17 +8,17 @@ use crate::display_list_flattener::{Crea
 use crate::frame_builder::FrameBuildingState;
 use crate::glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT};
 use crate::gpu_cache::GpuCache;
 use crate::intern;
 use crate::internal_types::LayoutPrimitiveInfo;
 use crate::picture::SurfaceInfo;
 use crate::prim_store::{PrimitiveOpacity, PrimitiveSceneData,  PrimitiveScratchBuffer};
 use crate::prim_store::{PrimitiveStore, PrimKeyCommonData, PrimTemplateCommonData};
-use crate::render_task::{RenderTaskTree};
+use crate::render_task::{RenderTaskGraph};
 use crate::renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use crate::resource_cache::{ResourceCache};
 use crate::util::{MatrixHelpers};
 use crate::prim_store::{InternablePrimitive, PrimitiveInstanceKind};
 use std::ops;
 use std::sync::Arc;
 use crate::storage;
 use crate::util::PrimaryArc;
@@ -288,17 +288,17 @@ impl TextRunPrimitive {
         prim_offset: LayoutVector2D,
         specified_font: &FontInstance,
         glyphs: &[GlyphInstance],
         transform: &LayoutToWorldTransform,
         surface: &SurfaceInfo,
         raster_space: RasterSpace,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
-        render_tasks: &mut RenderTaskTree,
+        render_tasks: &mut RenderTaskGraph,
         scratch: &mut PrimitiveScratchBuffer,
     ) {
         let device_pixel_scale = surface.device_pixel_scale;
 
         let cache_dirty = self.update_font_instance(
             specified_font,
             device_pixel_scale,
             transform,
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -33,17 +33,17 @@ use crate::intern::DataStore;
 use crate::internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
 use crate::picture::RetainedTiles;
 use crate::prim_store::{PrimitiveScratchBuffer, PrimitiveInstance};
 use crate::prim_store::{PrimitiveInstanceKind, PrimTemplateCommonData};
 use crate::prim_store::interned::*;
 use crate::profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
 use crate::record::ApiRecordingReceiver;
-use crate::render_task::RenderTaskTreeCounters;
+use crate::render_task::RenderTaskGraphCounters;
 use crate::renderer::{AsyncPropertySampler, PipelineInfo};
 use crate::resource_cache::ResourceCache;
 #[cfg(feature = "replay")]
 use crate::resource_cache::PlainCacheOwn;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use crate::resource_cache::PlainResources;
 use crate::scene::{Scene, SceneProperties};
 use crate::scene_builder::*;
@@ -346,19 +346,19 @@ struct Document {
     has_built_scene: bool,
 
     data_stores: DataStores,
 
     /// Contains various vecs of data that is used only during frame building,
     /// where we want to recycle the memory each new display list, to avoid constantly
     /// re-allocating and moving memory around.
     scratch: PrimitiveScratchBuffer,
-    /// Keep track of the size of render task tree to pre-allocate memory up-front
+    /// Keep track of the size of render task graph to pre-allocate memory up-front
     /// the next frame.
-    render_task_counters: RenderTaskTreeCounters,
+    render_task_counters: RenderTaskGraphCounters,
 }
 
 impl Document {
     pub fn new(
         id: DocumentId,
         size: DeviceIntSize,
         layer: DocumentLayer,
         default_device_pixel_ratio: f32,
@@ -382,17 +382,17 @@ impl Document {
             hit_tester: None,
             dynamic_properties: SceneProperties::new(),
             frame_is_valid: false,
             hit_tester_is_valid: false,
             rendered_frame_is_valid: false,
             has_built_scene: false,
             data_stores: DataStores::default(),
             scratch: PrimitiveScratchBuffer::new(),
-            render_task_counters: RenderTaskTreeCounters::new(),
+            render_task_counters: RenderTaskGraphCounters::new(),
         }
     }
 
     fn can_render(&self) -> bool {
         self.frame_builder.is_some() && self.scene.has_root_pipeline()
     }
 
     fn has_pixels(&self) -> bool {
@@ -1817,17 +1817,17 @@ impl RenderBackend {
                 dynamic_properties: SceneProperties::new(),
                 hit_tester: None,
                 frame_is_valid: false,
                 hit_tester_is_valid: false,
                 rendered_frame_is_valid: false,
                 has_built_scene: false,
                 data_stores,
                 scratch: PrimitiveScratchBuffer::new(),
-                render_task_counters: RenderTaskTreeCounters::new(),
+                render_task_counters: RenderTaskGraphCounters::new(),
             };
 
             let frame_name = format!("frame-{}-{}", id.namespace_id.0, id.id);
             let frame = CaptureConfig::deserialize::<Frame, _>(root, frame_name);
             let build_frame = match frame {
                 Some(frame) => {
                     info!("\tloaded a built frame with {} passes", frame.passes.len());
 
--- a/gfx/wr/webrender/src/render_task.rs
+++ b/gfx/wr/webrender/src/render_task.rs
@@ -73,61 +73,61 @@ impl RenderTaskId {
 #[repr(C)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderTaskAddress(pub u16);
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct RenderTaskTree {
+pub struct RenderTaskGraph {
     pub tasks: Vec<RenderTask>,
     pub task_data: Vec<RenderTaskData>,
     /// Tasks that don't have dependencies, and that may be shared between
     /// picture tasks.
     ///
     /// We render these unconditionally before-rendering the rest of the tree.
     pub cacheable_render_tasks: Vec<RenderTaskId>,
     next_saved: SavedTargetIndex,
     frame_id: FrameId,
 }
 
 #[derive(Debug)]
-pub struct RenderTaskTreeCounters {
+pub struct RenderTaskGraphCounters {
     tasks_len: usize,
     task_data_len: usize,
     cacheable_render_tasks_len: usize,
 }
 
-impl RenderTaskTreeCounters {
+impl RenderTaskGraphCounters {
     pub fn new() -> Self {
-        RenderTaskTreeCounters {
+        RenderTaskGraphCounters {
             tasks_len: 0,
             task_data_len: 0,
             cacheable_render_tasks_len: 0,
         }
     }
 }
 
-impl RenderTaskTree {
-    pub fn new(frame_id: FrameId, counters: &RenderTaskTreeCounters) -> Self {
+impl RenderTaskGraph {
+    pub fn new(frame_id: FrameId, counters: &RenderTaskGraphCounters) -> Self {
         // Preallocate a little more than what we needed in the previous frame so that small variations
         // in the number of items don't cause us to constantly reallocate.
         let extra_items = 8;
-        RenderTaskTree {
+        RenderTaskGraph {
             tasks: Vec::with_capacity(counters.tasks_len + extra_items),
             task_data: Vec::with_capacity(counters.task_data_len + extra_items),
             cacheable_render_tasks: Vec::with_capacity(counters.cacheable_render_tasks_len + extra_items),
             next_saved: SavedTargetIndex(0),
             frame_id,
         }
     }
 
-    pub fn counters(&self) -> RenderTaskTreeCounters {
-        RenderTaskTreeCounters {
+    pub fn counters(&self) -> RenderTaskGraphCounters {
+        RenderTaskGraphCounters {
             tasks_len: self.tasks.len(),
             task_data_len: self.task_data.len(),
             cacheable_render_tasks_len: self.cacheable_render_tasks.len(),
         }
     }
 
     pub fn add(&mut self, task: RenderTask) -> RenderTaskId {
         let index = self.tasks.len() as _;
@@ -407,26 +407,26 @@ impl RenderTaskTree {
     }
 
     #[cfg(debug_assertions)]
     pub fn frame_id(&self) -> FrameId {
         self.frame_id
     }
 }
 
-impl ops::Index<RenderTaskId> for RenderTaskTree {
+impl ops::Index<RenderTaskId> for RenderTaskGraph {
     type Output = RenderTask;
     fn index(&self, id: RenderTaskId) -> &RenderTask {
         #[cfg(debug_assertions)]
         debug_assert_eq!(self.frame_id, id.frame_id);
         &self.tasks[id.index as usize]
     }
 }
 
-impl ops::IndexMut<RenderTaskId> for RenderTaskTree {
+impl ops::IndexMut<RenderTaskId> for RenderTaskGraph {
     fn index_mut(&mut self, id: RenderTaskId) -> &mut RenderTask {
         #[cfg(debug_assertions)]
         debug_assert_eq!(self.frame_id, id.frame_id);
         &mut self.tasks[id.index as usize]
     }
 }
 
 /// Identifies the output buffer location for a given `RenderTask`.
@@ -873,17 +873,17 @@ impl RenderTask {
 
     pub fn new_mask(
         outer_rect: DeviceIntRect,
         clip_node_range: ClipNodeRange,
         root_spatial_node_index: SpatialNodeIndex,
         clip_store: &mut ClipStore,
         gpu_cache: &mut GpuCache,
         resource_cache: &mut ResourceCache,
-        render_tasks: &mut RenderTaskTree,
+        render_tasks: &mut RenderTaskGraph,
         clip_data_store: &mut ClipDataStore,
         snap_offsets: SnapOffsets,
         device_pixel_scale: DevicePixelScale,
         fb_config: &FrameBuilderConfig,
     ) -> Self {
         // Step through the clip sources that make up this mask. If we find
         // any box-shadow clip sources, request that image from the render
         // task cache. This allows the blurred box-shadow rect to be cached
@@ -1040,17 +1040,17 @@ impl RenderTask {
     //           |
     //    HorizontalBlurTask: Apply the separable horizontal blur to the vertical blur.
     //           |
     //           +---- This is stored as the input task to the primitive shader.
     //
     pub fn new_blur(
         blur_std_deviation: DeviceSize,
         src_task_id: RenderTaskId,
-        render_tasks: &mut RenderTaskTree,
+        render_tasks: &mut RenderTaskGraph,
         target_kind: RenderTargetKind,
         clear_mode: ClearMode,
         mut blur_cache: Option<&mut BlurTaskCache>,
     ) -> RenderTaskId {
         // Adjust large std deviation value.
         let mut adjusted_blur_std_deviation = blur_std_deviation;
         let (blur_target_size, uv_rect_kind) = {
             let src_task = &render_tasks[src_task_id];
@@ -1148,17 +1148,17 @@ impl RenderTask {
                 instances,
             }),
             ClearMode::Transparent,
         )
     }
 
     pub fn new_scaling(
         src_task_id: RenderTaskId,
-        render_tasks: &mut RenderTaskTree,
+        render_tasks: &mut RenderTaskGraph,
         target_kind: RenderTargetKind,
         target_size: DeviceIntSize,
     ) -> Self {
         let uv_rect_kind = render_tasks[src_task_id].uv_rect_kind();
 
         RenderTask::with_dynamic_location(
             target_size,
             vec![src_task_id],
@@ -1459,17 +1459,17 @@ impl RenderTask {
                 user_data: [0.0; 3],
                 uv_rect_kind,
             };
             image_source.write_gpu_blocks(&mut request);
         }
     }
 
     #[cfg(feature = "debugger")]
-    pub fn print_with<T: PrintTreePrinter>(&self, pt: &mut T, tree: &RenderTaskTree) -> bool {
+    pub fn print_with<T: PrintTreePrinter>(&self, pt: &mut T, tree: &RenderTaskGraph) -> bool {
         match self.kind {
             RenderTaskKind::Picture(ref task) => {
                 pt.new_level(format!("Picture of {:?}", task.pic_index));
             }
             RenderTaskKind::CacheMask(ref task) => {
                 pt.new_level(format!("CacheMask with {} clips", task.clip_node_range.count));
                 pt.add_item(format!("rect: {:?}", task.actual_rect));
             }
@@ -1630,17 +1630,17 @@ impl RenderTaskCache {
         });
     }
 
     fn alloc_render_task(
         entry: &mut RenderTaskCacheEntry,
         render_task_id: RenderTaskId,
         gpu_cache: &mut GpuCache,
         texture_cache: &mut TextureCache,
-        render_tasks: &mut RenderTaskTree,
+        render_tasks: &mut RenderTaskGraph,
     ) {
         let render_task = &mut render_tasks[render_task_id];
         let target_kind = render_task.target_kind();
 
         // Find out what size to alloc in the texture cache.
         let size = match render_task.location {
             RenderTaskLocation::Fixed(..) |
             RenderTaskLocation::TextureCache { .. } => {
@@ -1700,23 +1700,23 @@ impl RenderTaskCache {
         };
     }
 
     pub fn request_render_task<F>(
         &mut self,
         key: RenderTaskCacheKey,
         texture_cache: &mut TextureCache,
         gpu_cache: &mut GpuCache,
-        render_tasks: &mut RenderTaskTree,
+        render_tasks: &mut RenderTaskGraph,
         user_data: Option<[f32; 3]>,
         is_opaque: bool,
         f: F,
     ) -> Result<RenderTaskCacheEntryHandle, ()>
     where
-        F: FnOnce(&mut RenderTaskTree) -> Result<RenderTaskId, ()>,
+        F: FnOnce(&mut RenderTaskGraph) -> Result<RenderTaskId, ()>,
     {
         // Get the texture cache handle for this cache key,
         // or create one.
         let cache_entries = &mut self.cache_entries;
         let entry_handle = self.map.entry(key).or_insert_with(|| {
             let entry = RenderTaskCacheEntry {
                 handle: TextureCacheHandle::invalid(),
                 user_data,
@@ -1779,29 +1779,29 @@ impl RenderTaskCache {
     }
 }
 
 // TODO(gw): Rounding the content rect here to device pixels is not
 // technically correct. Ideally we should ceil() here, and ensure that
 // the extra part pixel in the case of fractional sizes is correctly
 // handled. For now, just use rounding which passes the existing
 // Gecko tests.
-// Note: zero-square tasks are prohibited in WR task tree, so
+// Note: zero-square tasks are prohibited in WR task graph, so
 // we ensure each dimension to be at least the length of 1 after rounding.
 pub fn to_cache_size(size: DeviceSize) -> DeviceIntSize {
     DeviceIntSize::new(
         1.max(size.width.round() as i32),
         1.max(size.height.round() as i32),
     )
 }
 
 // Dump an SVG visualization of the render graph for debugging purposes
 #[allow(dead_code)]
 pub fn dump_render_tasks_as_svg(
-    render_tasks: &RenderTaskTree,
+    render_tasks: &RenderTaskGraph,
     passes: &[RenderPass],
     output: &mut dyn io::Write,
 ) -> io::Result<()> {
     use svg_fmt::*;
 
     let node_width = 80.0;
     let node_height = 30.0;
     let vertical_spacing = 8.0;
@@ -1991,18 +1991,18 @@ fn diamond_task_graph() {
     //     [b1]
     //    /    \
     // [a]      [main_pic]
     //    \    /
     //     [b2]
 
     let color = RenderTargetKind::Color;
 
-    let counters = RenderTaskTreeCounters::new();
-    let mut tasks = RenderTaskTree::new(FrameId::first(), &counters);
+    let counters = RenderTaskGraphCounters::new();
+    let mut tasks = RenderTaskGraph::new(FrameId::first(), &counters);
 
     let a = tasks.add(RenderTask::new_test(color, dyn_location(640, 640), Vec::new()));
     let b1 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![a]));
     let b2 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![a]));
 
     let main_pic = tasks.add(RenderTask::new_test(
         color,
         RenderTaskLocation::Fixed(rect(0, 0, 3200, 1800)),
@@ -2028,18 +2028,18 @@ fn diamond_task_graph() {
 
 #[test]
 fn blur_task_graph() {
     // This test simulates a complicated shadow stack effect with target allocation
     // conflicts to resolve.
 
     let color = RenderTargetKind::Color;
 
-    let counters = RenderTaskTreeCounters::new();
-    let mut tasks = RenderTaskTree::new(FrameId::first(), &counters);
+    let counters = RenderTaskGraphCounters::new();
+    let mut tasks = RenderTaskGraph::new(FrameId::first(), &counters);
 
     let pic = tasks.add(RenderTask::new_test(color, dyn_location(640, 640), Vec::new()));
     let scale1 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![pic]));
     let scale2 = tasks.add(RenderTask::new_test(color, dyn_location(160, 160), vec![scale1]));
     let scale3 = tasks.add(RenderTask::new_test(color, dyn_location(80, 80), vec![scale2]));
     let scale4 = tasks.add(RenderTask::new_test(color, dyn_location(40, 40), vec![scale3]));
 
     let vblur1 = tasks.add(RenderTask::new_test(color, dyn_location(40, 40), vec![scale4]));
@@ -2113,18 +2113,18 @@ fn blur_task_graph() {
 
 #[test]
 fn culled_tasks() {
     // This test checks that tasks that do not contribute to the frame don't appear in the
     // generated passes.
 
     let color = RenderTargetKind::Color;
 
-    let counters = RenderTaskTreeCounters::new();
-    let mut tasks = RenderTaskTree::new(FrameId::first(), &counters);
+    let counters = RenderTaskGraphCounters::new();
+    let mut tasks = RenderTaskGraph::new(FrameId::first(), &counters);
 
     let a1 = tasks.add(RenderTask::new_test(color, dyn_location(640, 640), Vec::new()));
     let _a2 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![a1]));
 
     let b1 = tasks.add(RenderTask::new_test(color, dyn_location(640, 640), Vec::new()));
     let b2 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![b1]));
     let _b3 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![b2]));
 
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -77,17 +77,17 @@ use crate::profiler::{BackendProfileCoun
 use crate::profiler::{Profiler, ChangeIndicator};
 use crate::device::query::{GpuProfiler, GpuDebugMethod};
 use rayon::{ThreadPool, ThreadPoolBuilder};
 use crate::record::ApiRecordingReceiver;
 use crate::render_backend::{FrameId, RenderBackend};
 use crate::scene_builder::{SceneBuilder, LowPrioritySceneBuilder};
 use crate::shade::{Shaders, WrShaders};
 use smallvec::SmallVec;
-use crate::render_task::{RenderTask, RenderTaskData, RenderTaskKind, RenderTaskTree};
+use crate::render_task::{RenderTask, RenderTaskData, RenderTaskKind, RenderTaskGraph};
 use crate::resource_cache::ResourceCache;
 use crate::util::drain_filter;
 
 use std;
 use std::cmp;
 use std::collections::{HashMap, VecDeque};
 use std::collections::hash_map::Entry;
 use std::f32;
@@ -3578,17 +3578,17 @@ impl Renderer {
 
     //TODO: make this nicer. Currently we can't accept `&mut self` because the `DrawTarget` parameter
     // needs to borrow self.texture_resolver
     fn handle_blits(
         gpu_profile: &mut GpuProfiler<GpuProfileTag>,
         device: &mut Device,
         texture_resolver: &TextureResolver,
         blits: &[BlitJob],
-        render_tasks: &RenderTaskTree,
+        render_tasks: &RenderTaskGraph,
         draw_target: DrawTarget,
         content_origin: &DeviceIntPoint,
     ) {
         if blits.is_empty() {
             return;
         }
 
         let _timer = gpu_profile.start_timer(GPU_TAG_BLIT);
@@ -3663,17 +3663,17 @@ impl Renderer {
 
     fn draw_color_target(
         &mut self,
         draw_target: DrawTarget,
         target: &ColorRenderTarget,
         content_origin: DeviceIntPoint,
         clear_color: Option<[f32; 4]>,
         clear_depth: Option<f32>,
-        render_tasks: &RenderTaskTree,
+        render_tasks: &RenderTaskGraph,
         projection: &Transform3D<f32>,
         frame_id: GpuFrameId,
         stats: &mut RendererStats,
     ) {
         self.profile_counters.color_targets.inc();
         let _gm = self.gpu_profile.start_marker("color target");
 
         // sanity check for the depth buffer
@@ -4188,17 +4188,17 @@ impl Renderer {
         }
     }
 
     fn draw_alpha_target(
         &mut self,
         draw_target: DrawTarget,
         target: &AlphaRenderTarget,
         projection: &Transform3D<f32>,
-        render_tasks: &RenderTaskTree,
+        render_tasks: &RenderTaskGraph,
         stats: &mut RendererStats,
     ) {
         self.profile_counters.alpha_targets.inc();
         let _gm = self.gpu_profile.start_marker("alpha target");
         let alpha_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_ALPHA);
 
         {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_SETUP_TARGET);
@@ -4303,17 +4303,17 @@ impl Renderer {
         self.gpu_profile.finish_sampler(alpha_sampler);
     }
 
     fn draw_texture_cache_target(
         &mut self,
         texture: &CacheTextureId,
         layer: LayerIndex,
         target: &TextureCacheRenderTarget,
-        render_tasks: &RenderTaskTree,
+        render_tasks: &RenderTaskGraph,
         stats: &mut RendererStats,
     ) {
         let texture_source = TextureSource::TextureCache(*texture);
         let (target_size, projection) = {
             let texture = self.texture_resolver
                 .resolve(&texture_source)
                 .expect("BUG: invalid target texture");
             let target_size = texture.get_dimensions();
--- a/gfx/wr/webrender/src/resource_cache.rs
+++ b/gfx/wr/webrender/src/resource_cache.rs
@@ -25,17 +25,17 @@ use crate::glyph_cache::GlyphCacheEntry;
 use crate::glyph_rasterizer::{BaseFontInstance, FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer};
 use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use crate::gpu_types::UvRectKind;
 use crate::image::{compute_tile_size, compute_tile_range, for_each_tile_in_range};
 use crate::internal_types::{FastHashMap, FastHashSet, TextureSource, TextureUpdateList};
 use crate::profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
 use crate::render_backend::{FrameId, FrameStamp};
 use crate::render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId};
-use crate::render_task::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle, RenderTaskTree};
+use crate::render_task::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle, RenderTaskGraph};
 use smallvec::SmallVec;
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 use std::collections::hash_map::IterMut;
 use std::collections::VecDeque;
 use std::{cmp, mem};
 use std::fmt::Debug;
 use std::hash::Hash;
 use std::os::raw::c_void;
@@ -531,21 +531,21 @@ impl ResourceCache {
     // task. If the item is already cached, the texture cache
     // handle will be returned. Otherwise, the user supplied
     // closure will be invoked to generate the render task
     // chain that is required to draw this task.
     pub fn request_render_task<F>(
         &mut self,
         key: RenderTaskCacheKey,
         gpu_cache: &mut GpuCache,
-        render_tasks: &mut RenderTaskTree,
+        render_tasks: &mut RenderTaskGraph,
         user_data: Option<[f32; 3]>,
         is_opaque: bool,
         f: F,
-    ) -> RenderTaskCacheEntryHandle where F: FnOnce(&mut RenderTaskTree) -> RenderTaskId {
+    ) -> RenderTaskCacheEntryHandle where F: FnOnce(&mut RenderTaskGraph) -> RenderTaskId {
         self.cached_render_tasks.request_render_task(
             key,
             &mut self.texture_cache,
             gpu_cache,
             render_tasks,
             user_data,
             is_opaque,
             |render_task_tree| Ok(f(render_task_tree))
@@ -1372,17 +1372,17 @@ impl ResourceCache {
         }
     }
 
     pub fn request_glyphs(
         &mut self,
         mut font: FontInstance,
         glyph_keys: &[GlyphKey],
         gpu_cache: &mut GpuCache,
-        render_task_tree: &mut RenderTaskTree,
+        render_task_tree: &mut RenderTaskGraph,
     ) {
         debug_assert_eq!(self.state, State::AddResources);
 
         self.glyph_rasterizer.prepare_font(&mut font);
         self.glyph_rasterizer.request_glyphs(
             &mut self.cached_glyphs,
             font,
             glyph_keys,
@@ -1588,17 +1588,17 @@ impl ResourceCache {
         // pop the old frame and push a new one
         self.deleted_blob_keys.pop_front();
         self.deleted_blob_keys.push_back(Vec::new());
     }
 
     pub fn block_until_all_resources_added(
         &mut self,
         gpu_cache: &mut GpuCache,
-        render_tasks: &mut RenderTaskTree,
+        render_tasks: &mut RenderTaskGraph,
         texture_cache_profile: &mut TextureCacheProfileCounters,
     ) {
         profile_scope!("block_until_all_resources_added");
 
         debug_assert_eq!(self.state, State::AddResources);
         self.state = State::QueryResources;
 
         self.glyph_rasterizer.resolve_glyphs(
--- a/gfx/wr/webrender/src/tiling.rs
+++ b/gfx/wr/webrender/src/tiling.rs
@@ -22,17 +22,17 @@ use crate::internal_types::{CacheTexture
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
 use crate::picture::{RecordedDirtyRegion, SurfaceInfo};
 use crate::prim_store::gradient::GRADIENT_FP_STOPS;
 use crate::prim_store::{PrimitiveStore, DeferredResolve, PrimitiveScratchBuffer};
 use crate::profiler::FrameProfileCounters;
 use crate::render_backend::{DataStores, FrameId};
 use crate::render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
-use crate::render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskTree, ScalingTask};
+use crate::render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskGraph, ScalingTask};
 use crate::resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32, mem};
 use crate::texture_allocator::{ArrayAllocationTracker, FreeRectSlice};
 
 
 const STYLE_SOLID: i32 = ((BorderStyle::Solid as i32) << 8) | ((BorderStyle::Solid as i32) << 16);
 const STYLE_MASK: i32 = 0x00FF_FF00;
 
@@ -87,17 +87,17 @@ pub trait RenderTarget {
     ) -> Self;
 
     /// Optional hook to provide additional processing for the target at the
     /// end of the build phase.
     fn build(
         &mut self,
         _ctx: &mut RenderTargetContext,
         _gpu_cache: &mut GpuCache,
-        _render_tasks: &mut RenderTaskTree,
+        _render_tasks: &mut RenderTaskGraph,
         _deferred_resolves: &mut Vec<DeferredResolve>,
         _prim_headers: &mut PrimitiveHeaders,
         _transforms: &mut TransformPalette,
         _z_generator: &mut ZBufferIdGenerator,
     ) {
     }
 
     /// Associates a `RenderTask` with this target. That task must be assigned
@@ -109,17 +109,17 @@ pub trait RenderTarget {
     /// bit of extra overhead to store the image key here and the resolve them
     /// in the build step separately.  BUT: if/when we add more texture cache
     /// target jobs, we might want to tidy this up.
     fn add_task(
         &mut self,
         task_id: RenderTaskId,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
-        render_tasks: &RenderTaskTree,
+        render_tasks: &RenderTaskGraph,
         clip_store: &ClipStore,
         transforms: &mut TransformPalette,
         deferred_resolves: &mut Vec<DeferredResolve>,
     );
 
     fn needs_depth(&self) -> bool;
     fn must_be_drawn(&self) -> bool;
 
@@ -196,17 +196,17 @@ impl<T: RenderTarget> RenderTargetList<T
             gpu_supports_fast_clears,
         }
     }
 
     fn build(
         &mut self,
         ctx: &mut RenderTargetContext,
         gpu_cache: &mut GpuCache,
-        render_tasks: &mut RenderTaskTree,
+        render_tasks: &mut RenderTaskGraph,
         deferred_resolves: &mut Vec<DeferredResolve>,
         saved_index: Option<SavedTargetIndex>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         z_generator: &mut ZBufferIdGenerator,
     ) {
         debug_assert_eq!(None, self.saved_index);
         self.saved_index = saved_index;
@@ -399,17 +399,17 @@ impl RenderTarget for ColorRenderTarget 
             batch_builder: BatchBuilder::new(),
         }
     }
 
     fn build(
         &mut self,
         ctx: &mut RenderTargetContext,
         gpu_cache: &mut GpuCache,
-        render_tasks: &mut RenderTaskTree,
+        render_tasks: &mut RenderTaskGraph,
         deferred_resolves: &mut Vec<DeferredResolve>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         z_generator: &mut ZBufferIdGenerator,
     ) {
         let mut merged_batches = AlphaBatchContainer::new(None, Vec::new());
 
         for task_id in &self.alpha_tasks {
@@ -473,17 +473,17 @@ impl RenderTarget for ColorRenderTarget 
         }
     }
 
     fn add_task(
         &mut self,
         task_id: RenderTaskId,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
-        render_tasks: &RenderTaskTree,
+        render_tasks: &RenderTaskGraph,
         _: &ClipStore,
         _: &mut TransformPalette,
         deferred_resolves: &mut Vec<DeferredResolve>,
     ) {
         let task = &render_tasks[task_id];
 
         match task.kind {
             RenderTaskKind::VerticalBlur(ref info) => {
@@ -641,17 +641,17 @@ impl RenderTarget for AlphaRenderTarget 
         }
     }
 
     fn add_task(
         &mut self,
         task_id: RenderTaskId,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
-        render_tasks: &RenderTaskTree,
+        render_tasks: &RenderTaskGraph,
         clip_store: &ClipStore,
         transforms: &mut TransformPalette,
         _: &mut Vec<DeferredResolve>,
     ) {
         let task = &render_tasks[task_id];
         let (target_rect, _) = task.get_target_rect();
 
         match task.clear_mode {
@@ -781,17 +781,17 @@ impl TextureCacheRenderTarget {
             line_decorations: vec![],
             gradients: vec![],
         }
     }
 
     fn add_task(
         &mut self,
         task_id: RenderTaskId,
-        render_tasks: &mut RenderTaskTree,
+        render_tasks: &mut RenderTaskGraph,
     ) {
         let task_address = render_tasks.get_task_address(task_id);
         let src_task_address = render_tasks[task_id].children.get(0).map(|src_task_id| {
             render_tasks.get_task_address(*src_task_id)
         });
 
         let task = &mut render_tasks[task_id];
         let target_rect = task.get_target_rect();
@@ -927,17 +927,17 @@ pub enum RenderPassKind {
 /// target to do all of the rendering for that pass. See `RenderTargetList`.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct RenderPass {
     /// The kind of pass, as well as the set of targets associated with that
     /// kind of pass.
     pub kind: RenderPassKind,
     /// The set of tasks to be performed in this pass, as indices into the
-    /// `RenderTaskTree`.
+    /// `RenderTaskGraph`.
     pub tasks: Vec<RenderTaskId>,
 }
 
 impl RenderPass {
     /// Creates a pass for the main framebuffer. There is only one of these, and
     /// it is always the last pass.
     pub fn new_main_framebuffer(
         screen_size: DeviceIntSize,
@@ -1005,17 +1005,17 @@ impl RenderPass {
     ///
     /// Among other things, this allocates output regions for each of our tasks
     /// (added via `add_render_task`) in a RenderTarget and assigns it into that
     /// target.
     pub fn build(
         &mut self,
         ctx: &mut RenderTargetContext,
         gpu_cache: &mut GpuCache,
-        render_tasks: &mut RenderTaskTree,
+        render_tasks: &mut RenderTaskGraph,
         deferred_resolves: &mut Vec<DeferredResolve>,
         clip_store: &ClipStore,
         transforms: &mut TransformPalette,
         prim_headers: &mut PrimitiveHeaders,
         z_generator: &mut ZBufferIdGenerator,
     ) {
         profile_scope!("RenderPass::build");
 
@@ -1198,17 +1198,17 @@ pub struct Frame {
     pub device_rect: DeviceIntRect,
     pub background_color: Option<ColorF>,
     pub layer: DocumentLayer,
     pub passes: Vec<RenderPass>,
     #[cfg_attr(any(feature = "capture", feature = "replay"), serde(default = "FrameProfileCounters::new", skip))]
     pub profile_counters: FrameProfileCounters,
 
     pub transform_palette: Vec<TransformData>,
-    pub render_tasks: RenderTaskTree,
+    pub render_tasks: RenderTaskGraph,
     pub prim_headers: PrimitiveHeaders,
 
     /// The GPU cache frame that the contents of Self depend on
     pub gpu_cache_frame_id: FrameId,
 
     /// List of textures that we don't know about yet
     /// from the backend thread. The render thread
     /// will use a callback to resolve these and
--- a/js/src/fuzz-tests/testWasm.cpp
+++ b/js/src/fuzz-tests/testWasm.cpp
@@ -157,22 +157,16 @@ static int testWasmFuzz(const uint8_t* b
         // to baseline instead.
         if (IonCanCompile()) {
           enableWasmIon = true;
         } else {
           enableWasmBaseline = true;
         }
       }
 
-      // TODO: Cranelift is not stable for fuzzing, defer to baseline
-      if (enableWasmCranelift) {
-        enableWasmCranelift = false;
-        enableWasmBaseline = true;
-      }
-
       if (enableWasmAwaitTier2) {
         // Tier 2 needs Baseline + {Ion,Cranelift}
         enableWasmBaseline = true;
 
         if (!enableWasmIon && !enableWasmCranelift) {
           enableWasmIon = true;
         }
       }
--- a/js/src/wasm/WasmCraneliftCompile.cpp
+++ b/js/src/wasm/WasmCraneliftCompile.cpp
@@ -404,34 +404,18 @@ BD_ConstantValue global_constantValue(co
       v.u.f64 = value.f64();
       break;
     default:
       MOZ_CRASH("Bad type");
   }
   return v;
 }
 
-#ifdef DEBUG
-static bool IsCraneliftCompatible(TypeCode type) {
-  switch (type) {
-    case TypeCode::I32:
-    case TypeCode::I64:
-    case TypeCode::F32:
-    case TypeCode::F64:
-      return true;
-    default:
-      return false;
-  }
-}
-#endif
-
 TypeCode global_type(const GlobalDesc* global) {
-  TypeCode type = TypeCode(global->type().code());
-  MOZ_ASSERT(IsCraneliftCompatible(type));
-  return type;
+  return TypeCode(global->type().code());
 }
 
 size_t global_tlsOffset(const GlobalDesc* global) {
   return globalToTlsOffset(global->offset());
 }
 
 // TableDesc
 
@@ -444,21 +428,16 @@ size_t table_tlsOffset(const TableDesc* 
 
 // Sig
 
 size_t funcType_numArgs(const FuncTypeWithId* funcType) {
   return funcType->args().length();
 }
 
 const BD_ValType* funcType_args(const FuncTypeWithId* funcType) {
-#ifdef DEBUG
-  for (ValType valType : funcType->args()) {
-    MOZ_ASSERT(IsCraneliftCompatible(TypeCode(valType.code())));
-  }
-#endif
   static_assert(sizeof(BD_ValType) == sizeof(ValType), "update BD_ValType");
   return (const BD_ValType*)&funcType->args()[0];
 }
 
 TypeCode funcType_retType(const FuncTypeWithId* funcType) {
   return TypeCode(funcType->ret().code());
 }
 
--- a/js/src/wasm/cranelift/src/baldrapi.rs
+++ b/js/src/wasm/cranelift/src/baldrapi.rs
@@ -15,16 +15,19 @@
 
 //! This module exports the bindings generated by bindgen form the baldrapi.h file.
 //!
 //! The Baldr API consists of a set of C functions and some associated types.
 
 #![allow(non_upper_case_globals)]
 #![allow(non_camel_case_types)]
 #![allow(non_snake_case)]
+// We need to allow dead code because the Rustc compiler complains about variants never being
+// constructed in TypeCode, which is true because these values come from C++.
+#![allow(dead_code)]
 
 use cranelift_codegen::binemit::CodeOffset;
 use cranelift_codegen::entity::EntityRef;
 use cranelift_codegen::ir::SourceLoc;
 use cranelift_wasm::FuncIndex;
 
 use compile::CompiledFunc;
 
--- a/js/src/wasm/cranelift/src/baldrdash.rs
+++ b/js/src/wasm/cranelift/src/baldrdash.rs
@@ -11,106 +11,102 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 // Safe wrappers to the low-level ABI.  This re-exports all types in
 // baldrapi but none of the functions.
 
-use baldrapi::CraneliftModuleEnvironment;
 use cranelift_codegen::cursor::FuncCursor;
 use cranelift_codegen::entity::EntityRef;
 use cranelift_codegen::ir::immediates::{Ieee32, Ieee64};
 use cranelift_codegen::ir::{self, InstBuilder};
-use cranelift_wasm::{FuncIndex, GlobalIndex, SignatureIndex, TableIndex};
+use cranelift_wasm::{FuncIndex, GlobalIndex, SignatureIndex, TableIndex, WasmResult};
+
 use std::mem;
 use std::slice;
 
 use baldrapi;
+use baldrapi::BD_ValType as ValType;
+use baldrapi::CraneliftModuleEnvironment;
+use baldrapi::TypeCode;
+
+use utils::BasicError;
 
 pub use baldrapi::BD_SymbolicAddress as SymbolicAddress;
-pub use baldrapi::BD_ValType as ValType;
 pub use baldrapi::CraneliftCompiledFunc as CompiledFunc;
 pub use baldrapi::CraneliftFuncCompileInput as FuncCompileInput;
 pub use baldrapi::CraneliftMetadataEntry as MetadataEntry;
 pub use baldrapi::CraneliftStaticEnvironment as StaticEnvironment;
 pub use baldrapi::FuncTypeIdDescKind;
 pub use baldrapi::Trap;
-pub use baldrapi::TypeCode;
 
-/// Convert a `TypeCode` into the equivalent Cranelift type.
-///
-/// We expect Cranelift's `VOID` type to go away in the future, so use `None` to represent a
-/// function without a return value.
-impl Into<Option<ir::Type>> for TypeCode {
-    fn into(self) -> Option<ir::Type> {
-        match self {
-            TypeCode::I32 => Some(ir::types::I32),
-            TypeCode::I64 => Some(ir::types::I64),
-            TypeCode::F32 => Some(ir::types::F32),
-            TypeCode::F64 => Some(ir::types::F64),
-            TypeCode::BlockVoid => None,
-            _ => panic!("unexpected type"),
-        }
+/// Converts a `TypeCode` into the equivalent Cranelift type, if it's a known type, or an error
+/// otherwise.
+#[inline]
+fn typecode_to_type(type_code: TypeCode) -> WasmResult<Option<ir::Type>> {
+    match type_code {
+        TypeCode::I32 => Ok(Some(ir::types::I32)),
+        TypeCode::I64 => Ok(Some(ir::types::I64)),
+        TypeCode::F32 => Ok(Some(ir::types::F32)),
+        TypeCode::F64 => Ok(Some(ir::types::F64)),
+        TypeCode::BlockVoid => Ok(None),
+        _ => Err(BasicError::new(format!("unknown type code: {:?}", type_code)).into()),
     }
 }
 
 /// Convert a non-void `TypeCode` into the equivalent Cranelift type.
-impl Into<ir::Type> for TypeCode {
-    fn into(self) -> ir::Type {
-        match self.into() {
-            Some(t) => t,
-            None => panic!("unexpected void type"),
-        }
-    }
+#[inline]
+fn typecode_to_nonvoid_type(type_code: TypeCode) -> WasmResult<ir::Type> {
+    Ok(typecode_to_type(type_code)?.expect("unexpected void type"))
 }
 
 /// Convert a `TypeCode` into the equivalent Cranelift type.
-impl Into<ir::Type> for ValType {
-    fn into(self) -> ir::Type {
-        unsafe { baldrapi::env_unpack(self) }.into()
-    }
+#[inline]
+fn valtype_to_type(val_type: ValType) -> WasmResult<ir::Type> {
+    let type_code = unsafe { baldrapi::env_unpack(val_type) };
+    typecode_to_nonvoid_type(type_code)
 }
 
 /// Convert a u32 into a `BD_SymbolicAddress`.
 impl From<u32> for SymbolicAddress {
     fn from(x: u32) -> SymbolicAddress {
         assert!(x < SymbolicAddress::Limit as u32);
         unsafe { mem::transmute(x) }
     }
 }
 
 #[derive(Clone, Copy)]
 pub struct GlobalDesc(*const baldrapi::GlobalDesc);
 
 impl GlobalDesc {
-    pub fn value_type(self) -> TypeCode {
-        unsafe { baldrapi::global_type(self.0) }
+    pub fn value_type(self) -> WasmResult<ir::Type> {
+        let type_code = unsafe { baldrapi::global_type(self.0) };
+        typecode_to_nonvoid_type(type_code)
     }
 
     pub fn is_constant(self) -> bool {
         unsafe { baldrapi::global_isConstant(self.0) }
     }
 
     pub fn is_indirect(self) -> bool {
         unsafe { baldrapi::global_isIndirect(self.0) }
     }
 
     /// Insert an instruction at `pos` that materialized the constant value.
-    pub fn emit_constant(self, pos: &mut FuncCursor) -> ir::Value {
+    pub fn emit_constant(self, pos: &mut FuncCursor) -> WasmResult<ir::Value> {
         unsafe {
             let v = baldrapi::global_constantValue(self.0);
-            // Note that the floating point constants below
             match v.t {
-                TypeCode::I32 => pos.ins().iconst(ir::types::I32, i64::from(v.u.i32)),
-                TypeCode::I64 => pos.ins().iconst(ir::types::I64, v.u.i64),
-                TypeCode::F32 => pos.ins().f32const(Ieee32::with_bits(v.u.i32 as u32)),
-                TypeCode::F64 => pos.ins().f64const(Ieee64::with_bits(v.u.i64 as u64)),
-                _ => panic!("unexpected type"),
+                TypeCode::I32 => Ok(pos.ins().iconst(ir::types::I32, i64::from(v.u.i32))),
+                TypeCode::I64 => Ok(pos.ins().iconst(ir::types::I64, v.u.i64)),
+                TypeCode::F32 => Ok(pos.ins().f32const(Ieee32::with_bits(v.u.i32 as u32))),
+                TypeCode::F64 => Ok(pos.ins().f64const(Ieee64::with_bits(v.u.i64 as u64))),
+                _ => Err(BasicError::new(format!("unexpected type: {}", v.t as u64)).into()),
             }
         }
     }
 
     /// Get the offset from the `WasmTlsReg` to the memory representing this global variable.
     pub fn tls_offset(self) -> usize {
         unsafe { baldrapi::global_tlsOffset(self.0) }
     }
@@ -125,33 +121,37 @@ impl TableDesc {
         unsafe { baldrapi::table_tlsOffset(self.0) }
     }
 }
 
 #[derive(Clone, Copy)]
 pub struct FuncTypeWithId(*const baldrapi::FuncTypeWithId);
 
 impl FuncTypeWithId {
-    pub fn args<'a>(self) -> &'a [ValType] {
-        unsafe {
-            let num_args = baldrapi::funcType_numArgs(self.0);
-            // The `funcType_args` callback crashes when there are no arguments. Also note that
-            // `slice::from_raw_parts()` requires a non-null pointer for empty slices.
-            // TODO: We should get all the parts of a signature in a single callback that returns a
-            // struct.
-            if num_args == 0 {
-                &[]
-            } else {
-                slice::from_raw_parts(baldrapi::funcType_args(self.0), num_args)
+    pub fn args<'a>(self) -> WasmResult<Vec<ir::Type>> {
+        let num_args = unsafe { baldrapi::funcType_numArgs(self.0) };
+        // The `funcType_args` callback crashes when there are no arguments. Also note that
+        // `slice::from_raw_parts()` requires a non-null pointer for empty slices.
+        // TODO: We should get all the parts of a signature in a single callback that returns a
+        // struct.
+        if num_args == 0 {
+            Ok(Vec::new())
+        } else {
+            let args = unsafe { slice::from_raw_parts(baldrapi::funcType_args(self.0), num_args) };
+            let mut ret = Vec::new();
+            for &arg in args {
+                ret.push(valtype_to_type(arg)?);
             }
+            Ok(ret)
         }
     }
 
-    pub fn ret_type(self) -> TypeCode {
-        unsafe { baldrapi::funcType_retType(self.0) }
+    pub fn ret_type(self) -> WasmResult<Option<ir::Type>> {
+        let type_code = unsafe { baldrapi::funcType_retType(self.0) };
+        typecode_to_type(type_code)
     }
 
     pub fn id_kind(self) -> FuncTypeIdDescKind {
         unsafe { baldrapi::funcType_idKind(self.0) }
     }
 
     pub fn id_immediate(self) -> usize {
         unsafe { baldrapi::funcType_idImmediate(self.0) }
--- a/js/src/wasm/cranelift/src/compile.rs
+++ b/js/src/wasm/cranelift/src/compile.rs
@@ -104,17 +104,17 @@ impl<'a, 'b> BatchCompiler<'a, 'b> {
         &mut self,
         func: &bd::FuncCompileInput,
     ) -> WasmResult<bd::FuncTypeWithId> {
         self.context.clear();
 
         // Set up the signature before translating the WebAssembly byte code.
         // The translator refers to it.
         let index = FuncIndex::new(func.index as usize);
-        let wsig = init_sig(&mut self.context.func.signature, &self.environ, index);
+        let wsig = init_sig(&mut self.context.func.signature, &self.environ, index)?;
         self.context.func.name = wasm_function_name(index);
 
         let tenv = &mut TransEnv::new(&*self.isa, &self.environ, self.static_environ);
         self.trans.translate(
             func.bytecode(),
             func.offset_in_module as usize,
             &mut self.context.func,
             tenv,
--- a/js/src/wasm/cranelift/src/utils.rs
+++ b/js/src/wasm/cranelift/src/utils.rs
@@ -12,16 +12,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 /// Helpers common to other source files here.
 use std::error;
 use std::fmt;
 
+use cranelift_wasm::WasmError;
+
 type DashError = Box<error::Error>;
 pub type DashResult<T> = Result<T, DashError>;
 
 /// A simple error type that contains a string message, used to wrap raw Cranelift error types
 /// which don't implement std::error::Error.
 
 #[derive(Debug)]
 pub struct BasicError {
@@ -40,8 +42,14 @@ impl fmt::Display for BasicError {
     }
 }
 
 impl error::Error for BasicError {
     fn description(&self) -> &str {
         &self.msg
     }
 }
+
+impl Into<WasmError> for BasicError {
+    fn into(self) -> WasmError {
+        WasmError::User(self.msg)
+    }
+}
--- a/js/src/wasm/cranelift/src/wasm2clif.rs
+++ b/js/src/wasm/cranelift/src/wasm2clif.rs
@@ -63,49 +63,51 @@ fn imm64(offset: usize) -> ir::immediate
 }
 
 /// Convert a usize offset into a `Uimm64`.
 fn uimm64(offset: usize) -> ir::immediates::Uimm64 {
     (offset as u64).into()
 }
 
 /// Initialize a `Signature` from a wasm signature.
-fn init_sig_from_wsig(sig: &mut ir::Signature, wsig: bd::FuncTypeWithId) {
+fn init_sig_from_wsig(sig: &mut ir::Signature, wsig: bd::FuncTypeWithId) -> WasmResult<()> {
     sig.clear(CallConv::Baldrdash);
-    for &arg in wsig.args() {
-        sig.params.push(ir::AbiParam::new(arg.into()));
+    for arg in wsig.args()? {
+        sig.params.push(ir::AbiParam::new(arg));
     }
 
-    if let Some(ret_type) = wsig.ret_type().into() {
+    if let Some(ret_type) = wsig.ret_type()? {
         let ret = match ret_type {
             // Spidermonkey requires i32 returns to have their high 32 bits
             // zero so that it can directly box them.
             ir::types::I32 => ir::AbiParam::new(ret_type).uext(),
             _ => ir::AbiParam::new(ret_type),
         };
         sig.returns.push(ret);
     }
 
     // Add a VM context pointer argument.
     // This corresponds to SpiderMonkey's `WasmTlsReg` hidden argument.
     sig.params.push(ir::AbiParam::special(
         native_pointer_type(),
         ir::ArgumentPurpose::VMContext,
     ));
+
+    Ok(())
 }
 
 /// Initialize the signature `sig` to match the function with `index` in `env`.
 pub fn init_sig(
     sig: &mut ir::Signature,
     env: &bd::ModuleEnvironment,
     func_index: FuncIndex,
-) -> bd::FuncTypeWithId {
+) -> WasmResult<bd::FuncTypeWithId> {
     let wsig = env.function_signature(func_index);
-    init_sig_from_wsig(sig, wsig);
-    wsig
+    init_sig_from_wsig(sig, wsig)?;
+    Ok(wsig)
 }
 
 /// A `TargetIsa` and `ModuleEnvironment` joined so we can implement `FuncEnvironment`.
 pub struct TransEnv<'a, 'b, 'c> {
     isa: &'a TargetIsa,
     env: &'b bd::ModuleEnvironment<'b>,
     static_env: &'c bd::StaticEnvironment,
 
@@ -383,25 +385,29 @@ impl<'a, 'b, 'c> FuncEnvironment for Tra
     fn target_config(&self) -> TargetFrontendConfig {
         self.isa.frontend_config()
     }
 
     fn pointer_type(&self) -> ir::Type {
         native_pointer_type()
     }
 
-    fn make_global(&mut self, func: &mut ir::Function, index: GlobalIndex) -> GlobalVariable {
+    fn make_global(
+        &mut self,
+        func: &mut ir::Function,
+        index: GlobalIndex,
+    ) -> WasmResult<GlobalVariable> {
         let global = self.env.global(index);
         if global.is_constant() {
             // Constant globals have a known value at compile time. We insert an instruction to
             // materialize the constant at the front of the entry block.
             let mut pos = FuncCursor::new(func);
             pos.next_ebb().expect("empty function");
             pos.next_inst();
-            return GlobalVariable::Const(global.emit_constant(&mut pos));
+            return Ok(GlobalVariable::Const(global.emit_constant(&mut pos)?));
         }
 
         // This is a global variable. Here we don't care if it is mutable or not.
         let vmctx_gv = self.get_vmctx_gv(func);
         let offset = global.tls_offset();
 
         // Some globals are represented as a pointer to the actual data, in which case we
         // must do an extra dereference to get to them.
@@ -412,24 +418,26 @@ impl<'a, 'b, 'c> FuncEnvironment for Tra
                 global_type: native_pointer_type(),
                 readonly: false,
             });
             (gv, 0.into())
         } else {
             (vmctx_gv, offset32(offset))
         };
 
-        GlobalVariable::Memory {
+        let mem_ty = global.value_type()?;
+
+        Ok(GlobalVariable::Memory {
             gv: base_gv,
-            ty: global.value_type().into(),
+            ty: mem_ty,
             offset,
-        }
+        })
     }
 
-    fn make_heap(&mut self, func: &mut ir::Function, index: MemoryIndex) -> ir::Heap {
+    fn make_heap(&mut self, func: &mut ir::Function, index: MemoryIndex) -> WasmResult<ir::Heap> {
         assert_eq!(index.index(), 0, "Only one WebAssembly memory supported");
         // Get the address of the `TlsData::memoryBase` field.
         let base_addr = self.get_vmctx_gv(func);
         // Get the `TlsData::memoryBase` field. We assume this is never modified during execution
         // of the function.
         let base = func.create_global_value(ir::GlobalValueData::Load {
             base: base_addr,
             offset: offset32(0),
@@ -450,42 +458,46 @@ impl<'a, 'b, 'c> FuncEnvironment for Tra
                 base: base_addr,
                 offset: native_pointer_size().into(),
                 global_type: ir::types::I32,
                 readonly: false,
             });
             ir::HeapStyle::Dynamic { bound_gv }
         };
 
-        func.create_heap(ir::HeapData {
+        Ok(func.create_heap(ir::HeapData {
             base,
             min_size,
             offset_guard_size: guard_size,
             style,
             index_type: ir::types::I32,
-        })
+        }))
     }
 
-    fn make_indirect_sig(&mut self, func: &mut ir::Function, index: SignatureIndex) -> ir::SigRef {
+    fn make_indirect_sig(
+        &mut self,
+        func: &mut ir::Function,
+        index: SignatureIndex,
+    ) -> WasmResult<ir::SigRef> {
         let mut sigdata = ir::Signature::new(CallConv::Baldrdash);
         let wsig = self.env.signature(index);
-        init_sig_from_wsig(&mut sigdata, wsig);
+        init_sig_from_wsig(&mut sigdata, wsig)?;
 
         if wsig.id_kind() != bd::FuncTypeIdDescKind::None {
             // A signature to be used for an indirect call also takes a signature id.
             sigdata.params.push(ir::AbiParam::special(
                 native_pointer_type(),
                 ir::ArgumentPurpose::SignatureId,
             ));
         }
 
-        func.import_signature(sigdata)
+        Ok(func.import_signature(sigdata))
     }
 
-    fn make_table(&mut self, func: &mut ir::Function, index: TableIndex) -> ir::Table {
+    fn make_table(&mut self, func: &mut ir::Function, index: TableIndex) -> WasmResult<ir::Table> {
         let table_desc = self.get_table(func, index);
 
         // TODO we'd need a better way to synchronize the shape of GlobalDataDesc and these
         // offsets.
         let bound_gv = func.create_global_value(ir::GlobalValueData::Load {
             base: table_desc.global,
             offset: 0.into(),
             global_type: ir::types::I32,
@@ -494,36 +506,40 @@ impl<'a, 'b, 'c> FuncEnvironment for Tra
 
         let base_gv = func.create_global_value(ir::GlobalValueData::Load {
             base: table_desc.global,
             offset: offset32(native_pointer_size() as usize),
             global_type: native_pointer_type(),
             readonly: false,
         });
 
-        func.create_table(ir::TableData {
+        Ok(func.create_table(ir::TableData {
             base_gv,
             min_size: ir::immediates::Uimm64::new(0),
             bound_gv,
             element_size: ir::immediates::Uimm64::new(u64::from(self.pointer_bytes()) * 2),
             index_type: ir::types::I32,
-        })
+        }))
     }
 
-    fn make_direct_func(&mut self, func: &mut ir::Function, index: FuncIndex) -> ir::FuncRef {
+    fn make_direct_func(
+        &mut self,
+        func: &mut ir::Function,
+        index: FuncIndex,
+    ) -> WasmResult<ir::FuncRef> {
         // Create a signature.
         let mut sigdata = ir::Signature::new(CallConv::Baldrdash);
-        init_sig(&mut sigdata, &self.env, index);
+        init_sig(&mut sigdata, &self.env, index)?;
         let signature = func.import_signature(sigdata);
 
-        func.import_function(ir::ExtFuncData {
+        Ok(func.import_function(ir::ExtFuncData {
             name: wasm_function_name(index),
             signature,
             colocated: true,
-        })
+        }))
     }
 
     fn translate_call_indirect(
         &mut self,
         mut pos: FuncCursor,
         table_index: TableIndex,
         table: ir::Table,
         sig_index: SignatureIndex,
@@ -747,19 +763,20 @@ impl<'a, 'b, 'c> FuncEnvironment for Tra
             .special_param(ir::ArgumentPurpose::VMContext)
             .expect("Missing vmctx arg");
         let addr = pos.ins().func_addr(native_pointer_type(), fnref);
         let call = pos.ins().call_indirect(sigref, addr, &[instance, vmctx]);
         self.switch_to_wasm_tls_realm(&mut pos);
         Ok(pos.func.dfg.first_result(call))
     }
 
-    fn translate_loop_header(&mut self, mut pos: FuncCursor) {
+    fn translate_loop_header(&mut self, mut pos: FuncCursor) -> WasmResult<()> {
         let interrupt = self.load_interrupt_flag(&mut pos);
         pos.ins().trapnz(interrupt, ir::TrapCode::Interrupt);
+        Ok(())
     }
 
     fn return_mode(&self) -> ReturnMode {
         // Since we're using SM's epilogue insertion code, we can only handle a single return
         // instruction at the end of the function.
         ReturnMode::FallthroughReturn
     }
 }
--- a/js/xpconnect/idl/xpccomponents.idl
+++ b/js/xpconnect/idl/xpccomponents.idl
@@ -664,19 +664,16 @@ interface nsIXPCComponents_Utils : nsISu
     AUTF8String readUTF8File(in nsIFile file);
 
     /*
      * Reads the given local file URL and returns its contents. This has the
      * same semantics of readUTF8File.
      */
     AUTF8String readUTF8URI(in nsIURI url);
 
-    /* Give a directive to the record/replay system. */
-    void recordReplayDirective(in long directive);
-
     /* Create a spellchecker object. */
     nsIEditorSpellCheck createSpellChecker();
 
     /* Create a commandline object. */
     nsISupports createCommandLine();
 
     /* Create a command params object. */
     nsICommandParams createCommandParams();
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -2405,22 +2405,16 @@ nsXPCComponents_Utils::ReadUTF8URI(nsIUR
 NS_IMETHODIMP
 nsXPCComponents_Utils::Now(double* aRetval) {
   TimeStamp start = TimeStamp::ProcessCreation();
   *aRetval = (TimeStamp::Now() - start).ToMilliseconds();
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsXPCComponents_Utils::RecordReplayDirective(int aDirective) {
-  recordreplay::RecordReplayDirective(aDirective);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsXPCComponents_Utils::CreateSpellChecker(nsIEditorSpellCheck** aSpellChecker) {
   NS_ENSURE_ARG_POINTER(aSpellChecker);
   nsCOMPtr<nsIEditorSpellCheck> spellChecker = new mozilla::EditorSpellCheck();
   spellChecker.forget(aSpellChecker);
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -847,17 +847,17 @@ static nsPresContext* CreatePresContext(
 // all the new objects or just initialize the existing ones
 nsresult nsDocumentViewer::InitInternal(nsIWidget* aParentWidget,
                                         nsISupports* aState,
                                         const nsIntRect& aBounds,
                                         bool aDoCreation,
                                         bool aNeedMakeCX /*= true*/,
                                         bool aForceSetNewDocument /* = true*/) {
   if (mIsPageMode) {
-    // XXXbz should the InitInternal in SetPageMode just pass false
+    // XXXbz should the InitInternal in SetPageModeForTesting just pass false
     // here itself?
     aForceSetNewDocument = false;
   }
 
   // We don't want any scripts to run here. That can cause flushing,
   // which can cause reentry into initialization of this document viewer,
   // which would be disastrous.
   nsAutoScriptBlocker blockScripts;
@@ -880,18 +880,18 @@ nsresult nsDocumentViewer::InitInternal(
     // it in one place (Show()) and require that callers call init(), open(),
     // show() in that order or something.
     if (!mPresContext &&
         (aParentWidget || containerView || mDocument->IsBeingUsedAsImage() ||
          (mDocument->GetDisplayDocument() &&
           mDocument->GetDisplayDocument()->GetPresShell()))) {
       // Create presentation context
       if (mIsPageMode) {
-        // Presentation context already created in SetPageMode which is calling
-        // this method
+        // Presentation context already created in SetPageModeForTesting which
+        // is calling this method
       } else {
         mPresContext = CreatePresContext(
             mDocument, nsPresContext::eContext_Galley, containerView);
       }
       NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY);
 
       nsresult rv = mPresContext->Init(mDeviceContext);
       if (NS_FAILED(rv)) {
@@ -4098,18 +4098,18 @@ void nsDocumentViewer::OnDonePrinting() 
         mDocument = nullptr;
       }
       mClosingWhilePrinting = false;
     }
   }
 #endif  // NS_PRINTING && NS_PRINT_PREVIEW
 }
 
-NS_IMETHODIMP nsDocumentViewer::SetPageMode(bool aPageMode,
-                                            nsIPrintSettings* aPrintSettings) {
+NS_IMETHODIMP nsDocumentViewer::SetPageModeForTesting(
+    bool aPageMode, nsIPrintSettings* aPrintSettings) {
   // XXX Page mode is only partially working; it's currently used for
   // reftests that require a paginated context
   mIsPageMode = aPageMode;
 
   // The DestroyPresShell call requires a script blocker, since the
   // PresShell::Destroy call it does can cause scripts to run, which could
   // re-entrantly call methods on the nsDocumentViewer.
   nsAutoScriptBlocker scriptBlocker;
--- a/layout/printing/nsPrintJob.cpp
+++ b/layout/printing/nsPrintJob.cpp
@@ -451,16 +451,59 @@ static void MapContentToWebShells(const 
   }
 
   // Continue recursively walking the chilren of this PO
   for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
     MapContentToWebShells(aRootPO, kid);
   }
 }
 
+/**
+ * On platforms that support it, sets the printer name stored in the
+ * nsIPrintSettings to the default printer if a printer name is not already
+ * set.
+ * XXXjwatt: Why is this necessary? Can't the code that reads the printer
+ * name later "just" use the default printer if a name isn't specified? Then
+ * we wouldn't have this inconsistency between platforms and processes.
+ */
+static nsresult EnsureSettingsHasPrinterNameSet(
+    nsIPrintSettings* aPrintSettings) {
+#if defined(XP_MACOSX) || defined(ANDROID)
+  // Mac doesn't support retrieving a printer list.
+  return NS_OK;
+#else
+#  if defined(MOZ_X11)
+  // On Linux, default printer name should be requested on the parent side.
+  // Unless we are in the parent, we ignore this function
+  if (!XRE_IsParentProcess()) {
+    return NS_OK;
+  }
+#  endif
+  NS_ENSURE_ARG_POINTER(aPrintSettings);
+
+  // See if aPrintSettings already has a printer
+  nsString printerName;
+  nsresult rv = aPrintSettings->GetPrinterName(printerName);
+  if (NS_SUCCEEDED(rv) && !printerName.IsEmpty()) {
+    return NS_OK;
+  }
+
+  // aPrintSettings doesn't have a printer set. Try to fetch the default.
+  nsCOMPtr<nsIPrintSettingsService> printSettingsService =
+      do_GetService(sPrintSettingsServiceContractID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = printSettingsService->GetDefaultPrinterName(printerName);
+  if (NS_SUCCEEDED(rv) && !printerName.IsEmpty()) {
+    rv = aPrintSettings->SetPrinterName(printerName);
+  }
+  return rv;
+#endif
+}
+
 //-------------------------------------------------------
 
 NS_IMPL_ISUPPORTS(nsPrintJob, nsIWebProgressListener, nsISupportsWeakReference,
                   nsIObserver)
 
 //-------------------------------------------------------
 nsPrintJob::~nsPrintJob() {
   Destroy();  // for insurance
@@ -637,17 +680,17 @@ nsresult nsPrintJob::DoCommonPrint(bool 
 
   // if they don't pass in a PrintSettings, then get the Global PS
   printData->mPrintSettings = aPrintSettings;
   if (!printData->mPrintSettings) {
     rv = GetGlobalPrintSettings(getter_AddRefs(printData->mPrintSettings));
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  rv = CheckForPrinters(printData->mPrintSettings);
+  rv = EnsureSettingsHasPrinterNameSet(printData->mPrintSettings);
   NS_ENSURE_SUCCESS(rv, rv);
 
   printData->mPrintSettings->SetIsCancelled(false);
   printData->mPrintSettings->GetShrinkToFit(&printData->mShrinkToFit);
 
   if (aIsPrintPreview) {
     mIsCreatingPrintPreview = true;
     SetIsPrintPreview(true);
@@ -1134,54 +1177,16 @@ already_AddRefed<nsIPrintSettings> nsPri
   }
   return nullptr;
 }
 
 //-----------------------------------------------------------------
 //-- Section: Pre-Reflow Methods
 //-----------------------------------------------------------------
 
-//---------------------------------------------------------------------
-// This method checks to see if there is at least one printer defined
-// and if so, it sets the first printer in the list as the default name
-// in the PrintSettings which is then used for Printer Preview
-nsresult nsPrintJob::CheckForPrinters(nsIPrintSettings* aPrintSettings) {
-#if defined(XP_MACOSX) || defined(ANDROID)
-  // Mac doesn't support retrieving a printer list.
-  return NS_OK;
-#else
-#  if defined(MOZ_X11)
-  // On Linux, default printer name should be requested on the parent side.
-  // Unless we are in the parent, we ignore this function
-  if (!XRE_IsParentProcess()) {
-    return NS_OK;
-  }
-#  endif
-  NS_ENSURE_ARG_POINTER(aPrintSettings);
-
-  // See if aPrintSettings already has a printer
-  nsString printerName;
-  nsresult rv = aPrintSettings->GetPrinterName(printerName);
-  if (NS_SUCCEEDED(rv) && !printerName.IsEmpty()) {
-    return NS_OK;
-  }
-
-  // aPrintSettings doesn't have a printer set. Try to fetch the default.
-  nsCOMPtr<nsIPrintSettingsService> printSettingsService =
-      do_GetService(sPrintSettingsServiceContractID, &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = printSettingsService->GetDefaultPrinterName(printerName);
-  if (NS_SUCCEEDED(rv) && !printerName.IsEmpty()) {
-    rv = aPrintSettings->SetPrinterName(printerName);
-  }
-  return rv;
-#endif
-}
-
 //----------------------------------------------------------------------
 // Set up to use the "pluggable" Print Progress Dialog
 void nsPrintJob::ShowPrintProgress(bool aIsForPrinting, bool& aDoNotify) {
   // default to not notifying, that if something here goes wrong
   // or we aren't going to show the progress dialog we can straight into
   // reflowing the doc for printing.
   aDoNotify = false;
 
@@ -1238,18 +1243,18 @@ void nsPrintJob::ShowPrintProgress(bool 
           getter_AddRefs(printData->mPrintProgressParams), &aDoNotify);
       if (NS_SUCCEEDED(rv)) {
         if (printProgressListener) {
           printData->mPrintProgressListeners.AppendObject(
               printProgressListener);
         }
 
         if (printData->mPrintProgressParams) {
-          SetDocAndURLIntoProgress(printData->mPrintObject,
-                                   printData->mPrintProgressParams);
+          SetURLAndTitleOnProgressParams(printData->mPrintObject,
+                                         printData->mPrintProgressParams);
         }
       }
     }
   }
 }
 
 //---------------------------------------------------------------------
 bool nsPrintJob::IsThereARangeSelection(nsPIDOMWindowOuter* aDOMWin) {
@@ -2481,17 +2486,17 @@ nsresult nsPrintJob::DoPrint(const Uniqu
                "How did this context end up here?");
 
   // Guarantee that mPrt and the objects it owns won't be deleted in this method
   // because it might be cleared if other modules called from here may fire
   // events, notifying observers and/or listeners.
   RefPtr<nsPrintData> printData = mPrt;
 
   if (printData->mPrintProgressParams) {
-    SetDocAndURLIntoProgress(aPO, printData->mPrintProgressParams);
+    SetURLAndTitleOnProgressParams(aPO, printData->mPrintProgressParams);
   }
 
   {
     // Ask the page sequence frame to print all the pages
     nsIPageSequenceFrame* pageSequence = poPresShell->GetPageSequenceFrame();
     NS_ASSERTION(nullptr != pageSequence, "no page sequence frame");
 
     // We are done preparing for printing, so we can turn this off
@@ -2533,18 +2538,18 @@ nsresult nsPrintJob::DoPrint(const Uniqu
            gFrameTypesStr[aPO->mFrameType]));
     StartPagePrintTimer(aPO);
   }
 
   return NS_OK;
 }
 
 //---------------------------------------------------------------------
-void nsPrintJob::SetDocAndURLIntoProgress(const UniquePtr<nsPrintObject>& aPO,
-                                          nsIPrintProgressParams* aParams) {
+void nsPrintJob::SetURLAndTitleOnProgressParams(
+    const UniquePtr<nsPrintObject>& aPO, nsIPrintProgressParams* aParams) {
   NS_ASSERTION(aPO, "Must have valid nsPrintObject");
   NS_ASSERTION(aParams, "Must have valid nsIPrintProgressParams");
 
   if (!aPO || !aPO->mDocShell || !aParams) {
     return;
   }
   const uint32_t kTitleLength = 64;
 
--- a/layout/printing/nsPrintJob.h
+++ b/layout/printing/nsPrintJob.h
@@ -130,20 +130,20 @@ class nsPrintJob final : public nsIObser
   void CheckForChildFrameSets(const mozilla::UniquePtr<nsPrintObject>& aPO);
 
   void CalcNumPrintablePages(int32_t& aNumPages);
   void ShowPrintProgress(bool aIsForPrinting, bool& aDoNotify);
   nsresult CleanupOnFailure(nsresult aResult, bool aIsPrinting);
   // If FinishPrintPreview() fails, caller may need to reset the state of the
   // object, for example by calling CleanupOnFailure().
   nsresult FinishPrintPreview();
-  void SetDocAndURLIntoProgress(const mozilla::UniquePtr<nsPrintObject>& aPO,
-                                nsIPrintProgressParams* aParams);
+  void SetURLAndTitleOnProgressParams(
+      const mozilla::UniquePtr<nsPrintObject>& aPO,
+      nsIPrintProgressParams* aParams);
   void EllipseLongString(nsAString& aStr, const uint32_t aLen, bool aDoFront);
-  nsresult CheckForPrinters(nsIPrintSettings* aPrintSettings);
   void CleanupDocTitleArray(char16_t**& aArray, int32_t& aCount);
 
   bool IsThereARangeSelection(nsPIDOMWindowOuter* aDOMWin);
 
   void FirePrintingErrorEvent(nsresult aPrintError);
   //---------------------------------------------------------------------
 
   // Timer Methods
--- a/layout/printing/nsPrintObject.cpp
+++ b/layout/printing/nsPrintObject.cpp
@@ -17,17 +17,19 @@
 #include "nsIBaseWindow.h"
 #include "nsDocShell.h"
 
 #include "mozilla/PresShell.h"
 #include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/dom/Document.h"
 #include "mozilla/dom/Element.h"
 
+using mozilla::PresShell;
 using mozilla::dom::BrowsingContext;
+using mozilla::dom::Document;
 using mozilla::dom::Element;
 
 //---------------------------------------------------
 //-- nsPrintObject Class Impl
 //---------------------------------------------------
 nsPrintObject::nsPrintObject()
     : mContent(nullptr),
       mFrameType(eFrame),
--- a/layout/tools/reftest/reftest-content.js
+++ b/layout/tools/reftest/reftest-content.js
@@ -214,17 +214,17 @@ function setupPrintMode() {
    ps.unwriteableMarginRight = 0;
 
    ps.headerStrLeft = "";
    ps.headerStrCenter = "";
    ps.headerStrRight = "";
    ps.footerStrLeft = "";
    ps.footerStrCenter = "";
    ps.footerStrRight = "";
-   docShell.contentViewer.setPageMode(true, ps);
+   docShell.contentViewer.setPageModeForTesting(/* aPageMode */ true, ps);
 }
 
 // Prints current page to a PDF file and calls callback when sucessfully
 // printed and written.
 function printToPdf(callback) {
     let currentDoc = content.document;
     let isPrintSelection = false;
     let printRange = '';
--- a/mfbt/RecordReplay.cpp
+++ b/mfbt/RecordReplay.cpp
@@ -75,19 +75,17 @@ namespace recordreplay {
                       ()) Macro(InternalRecordReplayAssert,                    \
                                 (const char* aFormat, va_list aArgs),          \
                                 (aFormat, aArgs))                              \
                       Macro(InternalRecordReplayAssertBytes,                   \
                             (const void* aData, size_t aSize),                 \
                             (aData, aSize)) Macro(InternalRegisterThing,       \
                                                   (void* aThing), (aThing))    \
                           Macro(InternalUnregisterThing, (void* aThing),       \
-                                (aThing)) Macro(InternalRecordReplayDirective, \
-                                                (long aDirective),             \
-                                                (aDirective))                  \
+                                (aThing))                                      \
                               Macro(BeginContentParse,                         \
                                     (const void* aToken, const char* aURL,     \
                                      const char* aContentType),                \
                                     (aToken, aURL, aContentType))              \
                                   Macro(AddContentParseData8,                  \
                                         (const void* aToken,                   \
                                          const mozilla::Utf8Unit* aUtf8Buffer, \
                                          size_t aLength),                      \
--- a/mfbt/RecordReplay.h
+++ b/mfbt/RecordReplay.h
@@ -253,20 +253,16 @@ static inline bool HasDivergedFromRecord
 // will be consistent between recording/replaying and can be used in assertion
 // strings.
 static inline void RecordReplayAssert(const char* aFormat, ...);
 static inline void RecordReplayAssertBytes(const void* aData, size_t aSize);
 static inline void RegisterThing(void* aThing);
 static inline void UnregisterThing(void* aThing);
 static inline size_t ThingIndex(void* aThing);
 
-// Give a directive to the record/replay system. For possible values for
-// aDirective, see ProcessRecordReplay.h. This is used for testing purposes.
-static inline void RecordReplayDirective(long aDirective);
-
 // Helper for record/replay asserts, try to determine a name for a C++ object
 // with virtual methods based on its vtable.
 static inline const char* VirtualThingName(void* aThing);
 
 // Enum which describes whether to preserve behavior between recording and
 // replay sessions.
 enum class Behavior { DontPreserve, Preserve };
 
@@ -435,18 +431,16 @@ MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(Weak
 MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(RecordReplayAssertBytes,
                                     (const void* aData, size_t aSize),
                                     (aData, aSize))
 MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(RegisterThing, (void* aThing), (aThing))
 MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(UnregisterThing, (void* aThing), (aThing))
 MOZ_MAKE_RECORD_REPLAY_WRAPPER(ThingIndex, size_t, 0, (void* aThing), (aThing))
 MOZ_MAKE_RECORD_REPLAY_WRAPPER(VirtualThingName, const char*, nullptr,
                                (void* aThing), (aThing))
-MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(RecordReplayDirective, (long aDirective),
-                                    (aDirective))
 
 #undef MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID
 #undef MOZ_MAKERECORDREPLAYWRAPPER
 
 MFBT_API void InternalRecordReplayAssert(const char* aFormat, va_list aArgs);
 
 static inline void RecordReplayAssert(const char* aFormat, ...) {
   if (IsRecordingOrReplaying()) {
--- a/python/mozbuild/mozbuild/action/buildlist.py
+++ b/python/mozbuild/mozbuild/action/buildlist.py
@@ -12,41 +12,42 @@ from __future__ import absolute_import, 
 import sys
 import os
 
 from mozbuild.util import (
     ensureParentDir,
     lock_file,
 )
 
+
 def addEntriesToListFile(listFile, entries):
-  """Given a file |listFile| containing one entry per line,
-  add each entry in |entries| to the file, unless it is already
-  present."""
-  ensureParentDir(listFile)
-  lock = lock_file(listFile + ".lck")
-  try:
-    if os.path.exists(listFile):
-      f = open(listFile)
-      existing = set(x.strip() for x in f.readlines())
-      f.close()
-    else:
-      existing = set()
-    for e in entries:
-      if e not in existing:
-        existing.add(e)
-    with open(listFile, 'wb') as f:
-      f.write("\n".join(sorted(existing))+"\n")
-  finally:
-    lock = None
+    """Given a file |listFile| containing one entry per line,
+    add each entry in |entries| to the file, unless it is already
+    present."""
+    ensureParentDir(listFile)
+    lock = lock_file(listFile + ".lck")
+    try:
+        if os.path.exists(listFile):
+            f = open(listFile)
+            existing = set(x.strip() for x in f.readlines())
+            f.close()
+        else:
+            existing = set()
+        for e in entries:
+            if e not in existing:
+                existing.add(e)
+        with open(listFile, 'wb') as f:
+            f.write("\n".join(sorted(existing))+"\n")
+    finally:
+        del lock  # Explicitly release the lock_file to free it
 
 
 def main(args):
     if len(args) < 2:
         print("Usage: buildlist.py <list file> <entry> [<entry> ...]",
-            file=sys.stderr)
+              file=sys.stderr)
         return 1
 
     return addEntriesToListFile(args[0], args[1:])
 
 
 if __name__ == '__main__':
     sys.exit(main(sys.argv[1:]))
--- a/python/mozbuild/mozbuild/action/check_binary.py
+++ b/python/mozbuild/mozbuild/action/check_binary.py
@@ -1,13 +1,13 @@
 # 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/.
 
-from __future__ import print_function, unicode_literals
+from __future__ import absolute_import, print_function, unicode_literals
 
 import argparse
 import os
 import re
 import subprocess
 import sys
 
 from distutils.version import StrictVersion as Version
@@ -275,23 +275,24 @@ def check_networking(binary):
     except Empty:
         raise RuntimeError('Could not parse llvm-objdump output?')
 
     basename = os.path.basename(binary)
     if bad_occurences_names:
         s = 'TEST-UNEXPECTED-FAIL | check_networking | {} | Identified {} ' + \
             'networking function(s) being imported in the rust static library ({})'
         print(s.format(basename, len(bad_occurences_names),
-            ",".join(sorted(bad_occurences_names))),
-            file=sys.stderr)
+                       ",".join(sorted(bad_occurences_names))),
+              file=sys.stderr)
         retcode = 1
     elif buildconfig.substs.get('MOZ_AUTOMATION'):
         print('TEST-PASS | check_networking | {}'.format(basename))
     return retcode
 
+
 def checks(target, binary):
     # The clang-plugin is built as target but is really a host binary.
     # Cheat and pretend we were passed the right argument.
     if 'clang-plugin' in binary:
         target = HOST
     checks = []
     if target['MOZ_LIBSTDCXX_VERSION']:
         checks.append(check_stdcxx)
@@ -340,17 +341,17 @@ def main(args):
 
     if options.host == options.target:
         print('Exactly one of --host or --target must be given',
               file=sys.stderr)
         return 1
 
     if options.networking and options.host:
         print('--networking is only valid with --target',
-               file=sys.stderr)
+              file=sys.stderr)
         return 1
 
     if options.networking:
         return check_networking(options.binary)
     elif options.host:
         return checks(HOST, options.binary)
     elif options.target:
         return checks(TARGET, options.binary)
--- a/python/mozbuild/mozbuild/action/download_wpt_manifest.py
+++ b/python/mozbuild/mozbuild/action/download_wpt_manifest.py
@@ -1,15 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # This action is used to generate the wpt manifest
 
-import os
+from __future__ import absolute_import, print_function
+
 import sys
 
 import buildconfig
 
 
 def main():
     print("Downloading wpt manifest")
     sys.path.insert(0, buildconfig.topsrcdir)
--- a/python/mozbuild/mozbuild/action/dump_env.py
+++ b/python/mozbuild/mozbuild/action/dump_env.py
@@ -1,12 +1,14 @@
 # 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/.
 
+from __future__ import absolute_import, print_function
+
 # We invoke a Python program to dump our environment in order to get
 # native paths printed on Windows so that these paths can be incorporated
 # into Python configure's environment.
 import os
 import sys
 
 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
 
--- a/python/mozbuild/mozbuild/action/dumpsymbols.py
+++ b/python/mozbuild/mozbuild/action/dumpsymbols.py
@@ -6,16 +6,17 @@ from __future__ import absolute_import, 
 
 import argparse
 import buildconfig
 import subprocess
 import shutil
 import sys
 import os
 
+
 def dump_symbols(target, tracking_file, count_ctors=False):
     # Our tracking file, if present, will contain path(s) to the previously generated
     # symbols. Remove them in this case so we don't simply accumulate old symbols
     # during incremental builds.
     if os.path.isfile(os.path.normpath(tracking_file)):
         with open(tracking_file, 'r') as fh:
             files = fh.read().splitlines()
         dirs = set(os.path.dirname(f) for f in files)
@@ -53,31 +54,33 @@ def dump_symbols(target, tracking_file, 
                                                                      'dist_include'),
                                                         os.path.join(buildconfig.topobjdir,
                                                                      'dist',
                                                                      'include')))
     objcopy = buildconfig.substs.get('OBJCOPY')
     if objcopy:
         os.environ['OBJCOPY'] = objcopy
 
-    args = ([buildconfig.substs['PYTHON'], os.path.join(buildconfig.topsrcdir, 'toolkit',
-                                                       'crashreporter', 'tools', 'symbolstore.py')] +
+    args = ([buildconfig.substs['PYTHON'],
+             os.path.join(buildconfig.topsrcdir, 'toolkit',
+                          'crashreporter', 'tools', 'symbolstore.py')] +
             sym_store_args +
             ['-s', buildconfig.topsrcdir, dump_syms_bin, os.path.join(buildconfig.topobjdir,
                                                                       'dist',
                                                                       'crashreporter-symbols'),
              os.path.abspath(target)])
     if count_ctors:
         args.append('--count-ctors')
     print('Running: %s' % ' '.join(args))
     out_files = subprocess.check_output(args)
     with open(tracking_file, 'w') as fh:
         fh.write(out_files)
         fh.flush()
 
+
 def main(argv):
     parser = argparse.ArgumentParser(
         usage="Usage: dumpsymbols.py <library or program> <tracking file>")
     parser.add_argument("--count-ctors",
                         action="store_true", default=False,
                         help="Count static initializers")
     parser.add_argument("library_or_program",
                         help="Path to library or program")
--- a/python/mozbuild/mozbuild/action/exe_7z_archive.py
+++ b/python/mozbuild/mozbuild/action/exe_7z_archive.py
@@ -1,23 +1,24 @@
 # 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/.
 
-from __future__ import print_function
+from __future__ import absolute_import, print_function
 
 import os
 import shutil
 import sys
 import subprocess
 import tempfile
 import mozpack.path as mozpath
 import buildconfig
 from mozbuild.base import BuildEnvironmentNotFoundException
 
+
 def archive_exe(pkg_dir, tagfile, sfx_package, package, use_upx):
     tmpdir = tempfile.mkdtemp(prefix='tmp')
     try:
         if pkg_dir:
             shutil.move(pkg_dir, 'core')
 
         if use_upx:
             final_sfx = mozpath.join(tmpdir, '7zSD.sfx')
@@ -25,30 +26,35 @@ def archive_exe(pkg_dir, tagfile, sfx_pa
         else:
             final_sfx = sfx_package
 
         try:
             sevenz = buildconfig.config.substs['7Z']
         except BuildEnvironmentNotFoundException:
             # configure hasn't been run, just use the default
             sevenz = '7z'
-        subprocess.check_call([sevenz, 'a', '-r', '-t7z', mozpath.join(tmpdir, 'app.7z'), '-mx', '-m0=BCJ2', '-m1=LZMA:d25', '-m2=LZMA:d19', '-m3=LZMA:d19', '-mb0:1', '-mb0s1:2', '-mb0s2:3'])
+        subprocess.check_call([
+            sevenz, 'a', '-r', '-t7z', mozpath.join(tmpdir, 'app.7z'), '-mx',
+            '-m0=BCJ2', '-m1=LZMA:d25', '-m2=LZMA:d19', '-m3=LZMA:d19', '-mb0:1',
+            '-mb0s1:2', '-mb0s2:3'])
 
         with open(package, 'wb') as o:
             for i in [final_sfx, tagfile, mozpath.join(tmpdir, 'app.7z')]:
                 shutil.copyfileobj(open(i, 'rb'), o)
         os.chmod(package, 0o0755)
     finally:
         if pkg_dir:
             shutil.move('core', pkg_dir)
         shutil.rmtree(tmpdir)
 
+
 def main(args):
     if len(args) != 4:
         print('Usage: exe_7z_archive.py <pkg_dir> <tagfile> <sfx_package> <package> <use_upx>',
               file=sys.stderr)
         return 1
     else:
         archive_exe(args[0], args[1], args[2], args[3], args[4])
         return 0
 
+
 if __name__ == '__main__':
     sys.exit(main(sys.argv[1:]))
--- a/python/mozbuild/mozbuild/action/exe_7z_extract.py
+++ b/python/mozbuild/mozbuild/action/exe_7z_extract.py
@@ -1,25 +1,28 @@
 # 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/.
 
-from __future__ import print_function
+from __future__ import absolute_import, print_function
 
 import shutil
 import sys
 import subprocess
 
+
 def extract_exe(package, target):
     subprocess.check_call(['7z', 'x', package, 'core'])
     shutil.move('core', target)
 
+
 def main(args):
     if len(args) != 2:
         print('Usage: exe_7z_extract.py <package> <target>',
               file=sys.stderr)
         return 1
     else:
         extract_exe(args[0], args[1])
         return 0
 
+
 if __name__ == '__main__':
     sys.exit(main(sys.argv[1:]))
--- a/python/mozbuild/mozbuild/action/file_generate.py
+++ b/python/mozbuild/mozbuild/action/file_generate.py
@@ -64,17 +64,17 @@ def main(argv):
               file=sys.stderr)
         return 1
 
     ret = 1
     try:
         with FileAvoidWrite(args.output_file, mode='rb') as output:
             try:
                 ret = module.__dict__[method](output, *args.additional_arguments, **kwargs)
-            except:
+            except Exception:
                 # Ensure that we don't overwrite the file if the script failed.
                 output.avoid_writing_to_file()
                 raise
 
             # The following values indicate a statement of success:
             #  - a set() (see below)
             #  - 0
             #  - False
@@ -111,10 +111,11 @@ def main(argv):
                 output.avoid_writing_to_file()
 
     except IOError as e:
         print('Error opening file "{0}"'.format(e.filename), file=sys.stderr)
         traceback.print_exc()
         return 1
     return ret
 
+
 if __name__ == '__main__':
     sys.exit(main(sys.argv[1:]))
--- a/python/mozbuild/mozbuild/action/generate_searchjson.py
+++ b/python/mozbuild/mozbuild/action/generate_searchjson.py
@@ -1,67 +1,76 @@
 # 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/.
 
+from __future__ import absolute_import, print_function
+
 import sys
 import json
 import copy
 
 engines = []
 
 locale = sys.argv[2]
 output_file = sys.argv[3]
 
 output = open(output_file, 'w')
 
 with open(sys.argv[1]) as f:
-  searchinfo = json.load(f)
+    searchinfo = json.load(f)
 
 # If we have a locale, use it, otherwise use the default
 if locale in searchinfo["locales"]:
-  localeSearchInfo = searchinfo["locales"][locale]
+    localeSearchInfo = searchinfo["locales"][locale]
 else:
-  localeSearchInfo = {}
-  localeSearchInfo["default"] = searchinfo["default"]
+    localeSearchInfo = {}
+    localeSearchInfo["default"] = searchinfo["default"]
+
 
 def validateDefault(key):
-  if (not key in searchinfo["default"]):
-    print >>sys.stderr, "Error: Missing default %s in list.json" % (key)
-    sys.exit(1)
+    if key not in searchinfo["default"]:
+        print("Error: Missing default %s in list.json" % (key), file=sys.stderr)
+        sys.exit(1)
 
-validateDefault("searchDefault");
-validateDefault("visibleDefaultEngines");
+
+validateDefault("searchDefault")
+validateDefault("visibleDefaultEngines")
 
 # If the selected locale doesn't have a searchDefault,
 # use the global one.
-if not "searchDefault" in localeSearchInfo["default"]:
-  localeSearchInfo["default"]["searchDefault"] = searchinfo["default"]["searchDefault"]
+if "searchDefault" not in localeSearchInfo["default"]:
+    localeSearchInfo["default"]["searchDefault"] = searchinfo["default"]["searchDefault"]
 
 # If the selected locale doesn't have a searchOrder,
 # use the global one if present.
 # searchOrder is NOT required.
-if not "searchOrder" in localeSearchInfo["default"] and "searchOrder" in searchinfo["default"]:
+if (
+    "searchOrder" not in localeSearchInfo["default"]
+    and "searchOrder" in searchinfo["default"]
+):
     localeSearchInfo["default"]["searchOrder"] = searchinfo["default"]["searchOrder"]
 
 # If we have region overrides, enumerate through them
 # and add the additional regions to the locale information.
 if "regionOverrides" in searchinfo:
-  regionOverrides = searchinfo["regionOverrides"]
+    regionOverrides = searchinfo["regionOverrides"]
 
-  for region in regionOverrides:
-    # Only add a new engine list if there is an engine that is overridden
-    enginesToOverride = set(regionOverrides[region].keys())
-    if region in localeSearchInfo and "visibleDefaultEngines" in localeSearchInfo[region]:
-       visibleDefaultEngines = localeSearchInfo[region]["visibleDefaultEngines"]
-    else:
-       visibleDefaultEngines = localeSearchInfo["default"]["visibleDefaultEngines"]
-    if set(visibleDefaultEngines) & enginesToOverride:
-      if region not in localeSearchInfo:
-        localeSearchInfo[region] = {}
-      localeSearchInfo[region]["visibleDefaultEngines"] = copy.deepcopy(visibleDefaultEngines)
-      for i, engine in enumerate(localeSearchInfo[region]["visibleDefaultEngines"]):
-        if engine in regionOverrides[region]:
-          localeSearchInfo[region]["visibleDefaultEngines"][i] = regionOverrides[region][engine]
+    for region in regionOverrides:
+        # Only add a new engine list if there is an engine that is overridden
+        enginesToOverride = set(regionOverrides[region].keys())
+        if region in localeSearchInfo and "visibleDefaultEngines" in localeSearchInfo[region]:
+            visibleDefaultEngines = localeSearchInfo[region]["visibleDefaultEngines"]
+        else:
+            visibleDefaultEngines = localeSearchInfo["default"]["visibleDefaultEngines"]
+        if set(visibleDefaultEngines) & enginesToOverride:
+            if region not in localeSearchInfo:
+                localeSearchInfo[region] = {}
+            localeSearchInfo[region]["visibleDefaultEngines"] = copy.deepcopy(
+                visibleDefaultEngines)
+            for i, engine in enumerate(localeSearchInfo[region]["visibleDefaultEngines"]):
+                if engine in regionOverrides[region]:
+                    localeSearchInfo[region]["visibleDefaultEngines"][i] = \
+                        regionOverrides[region][engine]
 
 output.write(json.dumps(localeSearchInfo, ensure_ascii=False).encode('utf8'))
 
-output.close();
+output.close()
--- a/python/mozbuild/mozbuild/action/generate_strings_xml.py
+++ b/python/mozbuild/mozbuild/action/generate_strings_xml.py
@@ -1,13 +1,13 @@
 # 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/.
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import sys
 
 import buildconfig
 
 from mozbuild import preprocessor
 
 
--- a/python/mozbuild/mozbuild/action/generate_suggestedsites.py
+++ b/python/mozbuild/mozbuild/action/generate_suggestedsites.py
@@ -33,23 +33,19 @@ import copy
 import errno
 import json
 import sys
 import os
 
 from mozbuild.dotproperties import (
     DotProperties,
 )
-from mozbuild.util import (
-    FileAvoidWrite,
-)
 from mozpack.files import (
     FileFinder,
 )
-import mozpack.path as mozpath
 
 
 def merge_properties(paths):
     """Merges properties from the given paths."""
     properties = DotProperties()
     for path in paths:
         try:
             properties.update(path)
@@ -87,59 +83,65 @@ def main(output, *args, **kwargs):
         print('Fallback path {fallback} is not a file!'.format(fallback=opts.fallback))
         return 1
 
     # Use reversed order so that the first srcdir has higher priority to override keys.
     sources = [opts.fallback] + list(reversed(opts.inputs))
     properties = merge_properties(sources)
 
     # Keep these two in sync.
-    image_url_template = 'android.resource://%s/drawable/suggestedsites_{name}' % opts.android_package_name
+    image_url_template = \
+        'android.resource://%s/drawable/suggestedsites_{name}' % opts.android_package_name
     drawables_template = 'drawable*/suggestedsites_{name}.*'
 
     # Load properties corresponding to each site name and define their
     # respective image URL.
     sites = []
 
     def add_names(names, defaults={}):
         for name in names:
             site = copy.deepcopy(defaults)
-            site.update(properties.get_dict('browser.suggestedsites.{name}'.format(name=name), required_keys=('title', 'url', 'bgcolor')))
+            site.update(properties.get_dict('browser.suggestedsites.{name}'.format(
+                name=name), required_keys=('title', 'url', 'bgcolor')))
             site['imageurl'] = image_url_template.format(name=name)
             sites.append(site)
 
             # Now check for existence of an appropriately named drawable.  If none
             # exists, throw.  This stops a locale discovering, at runtime, that the
             # corresponding drawable was not added to en-US.
             if not opts.resources:
                 continue
             resources = os.path.abspath(opts.resources)
             finder = FileFinder(resources)
             matches = [p for p, _ in finder.find(drawables_template.format(name=name))]
             if not matches:
                 raise Exception("Could not find drawable in '{resources}' for '{name}'"
-                    .format(resources=resources, name=name))
+                                .format(resources=resources, name=name))
             else:
                 if opts.verbose:
                     print("Found {len} drawables in '{resources}' for '{name}': {matches}"
-                          .format(len=len(matches), resources=resources, name=name, matches=matches))
+                          .format(len=len(matches), resources=resources,
+                                  name=name, matches=matches)
+                          )
 
     # We want the lists to be ordered for reproducibility.  Each list has a
     # "default" JSON list item which will be extended by the properties read.
     lists = [
         ('browser.suggestedsites.list', {}),
         ('browser.suggestedsites.restricted.list', {'restricted': True}),
     ]
     if opts.verbose:
-        print('Reading {len} suggested site lists: {lists}'.format(len=len(lists), lists=[list_name for list_name, _ in lists]))
+        print('Reading {len} suggested site lists: {lists}'.format(
+            len=len(lists), lists=[list_name for list_name, _ in lists]))
 
     for (list_name, list_item_defaults) in lists:
         names = properties.get_list(list_name)
         if opts.verbose:
-            print('Reading {len} suggested sites from {list}: {names}'.format(len=len(names), list=list_name, names=names))
+            print('Reading {len} suggested sites from {list}: {names}'.format(
+                len=len(names), list=list_name, names=names))
         add_names(names, list_item_defaults)
 
     # We must define at least one site -- that's what the fallback is for.
     if not sites:
         print('No sites defined: searched in {}!'.format(sources))
         return 1
 
     json.dump(sites, output)
--- a/python/mozbuild/mozbuild/action/jar_maker.py
+++ b/python/mozbuild/mozbuild/action/jar_maker.py
@@ -1,13 +1,13 @@
 # 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/.
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import sys
 
 import mozbuild.jar
 
 
 def main(args):
     return mozbuild.jar.main(args)
--- a/python/mozbuild/mozbuild/action/langpack_manifest.py
+++ b/python/mozbuild/mozbuild/action/langpack_manifest.py
@@ -3,17 +3,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 ###
 # This script generates a web manifest JSON file based on the xpi-stage
 # directory structure. It extracts the data from defines.inc files from
 # the locale directory, chrome registry entries and other information
 # necessary to produce the complete manifest file for a language pack.
 ###
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import argparse
 import sys
 import os
 import json
 import io
 import datetime
 import requests
@@ -21,17 +21,16 @@ import mozversioncontrol
 import mozpack.path as mozpath
 from mozpack.chrome.manifest import (
     Manifest,
     ManifestLocale,
     parse_manifest,
 )
 from mozbuild.configure.util import Version
 from mozbuild.preprocessor import Preprocessor
-import buildconfig
 
 
 def write_file(path, content):
     with io.open(path, 'w', encoding='utf-8') as out:
         out.write(content + '\n')
 
 
 pushlog_api_url = "{0}/json-rev/{1}"
@@ -303,17 +302,17 @@ def parse_chrome_manifest(path, base_pat
 # Returns:
 #    str - Version to use, may include buildid
 #
 ###
 def get_version_maybe_buildid(min_version):
     version = str(min_version)
     buildid = os.environ.get('MOZ_BUILD_DATE')
     if buildid and len(buildid) != 14:
-        print >>sys.stderr, 'Ignoring invalid MOZ_BUILD_DATE: %s' % buildid
+        print('Ignoring invalid MOZ_BUILD_DATE: %s' % buildid, file=sys.stderr)
         buildid = None
     if buildid:
         version = version + "buildid" + buildid
     return version
 
 
 ###
 # Generates a new web manifest dict with values specific for a language pack.
--- a/python/mozbuild/mozbuild/action/make_dmg.py
+++ b/python/mozbuild/mozbuild/action/make_dmg.py
@@ -1,13 +1,13 @@
 # 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/.
 
-from __future__ import print_function
+from __future__ import absolute_import, print_function
 
 from mozpack import dmg
 
 import argparse
 import sys
 
 
 def main(args):
--- a/python/mozbuild/mozbuild/action/make_unzip.py
+++ b/python/mozbuild/mozbuild/action/make_unzip.py
@@ -1,23 +1,26 @@
 # 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/.
 
-from __future__ import print_function
+from __future__ import absolute_import, print_function
 
 import sys
 import subprocess
 
+
 def make_unzip(package):
     subprocess.check_call(['unzip', package])
 
+
 def main(args):
     if len(args) != 1:
         print('Usage: make_unzip.py <package>',
               file=sys.stderr)
         return 1
     else:
         make_unzip(args[0])
         return 0
 
+
 if __name__ == '__main__':
     sys.exit(main(sys.argv[1:]))
--- a/python/mozbuild/mozbuild/action/make_zip.py
+++ b/python/mozbuild/mozbuild/action/make_zip.py
@@ -1,23 +1,26 @@
 # 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/.
 
-from __future__ import print_function
+from __future__ import absolute_import, print_function
 
 import sys
 import subprocess
 
+
 def make_zip(source, package):
     subprocess.check_call(['zip', '-r9D', package, source, '-x', '\*/.mkdir.done'])
 
+
 def main(args):
     if len(args) != 2:
         print('Usage: make_zip.py <source> <package>',
               file=sys.stderr)
         return 1
     else:
         make_zip(args[0], args[1])
         return 0
 
+
 if __name__ == '__main__':
     sys.exit(main(sys.argv[1:]))
--- a/python/mozbuild/mozbuild/action/output_searchplugins_list.py
+++ b/python/mozbuild/mozbuild/action/output_searchplugins_list.py
@@ -1,33 +1,35 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+from __future__ import absolute_import, print_function
+
 import sys
 import json
 
 engines = []
 
 locale = sys.argv[2]
 
 with open(sys.argv[1]) as f:
-  searchinfo = json.load(f)
+    searchinfo = json.load(f)
 
 # Get a list of the engines from the locale or the default
 engines = set()
 if locale in searchinfo["locales"]:
-  for region, table in searchinfo["locales"][locale].iteritems():
-    if "visibleDefaultEngines" in table:
-      engines.update(table["visibleDefaultEngines"])
+    for region, table in searchinfo["locales"][locale].iteritems():
+        if "visibleDefaultEngines" in table:
+            engines.update(table["visibleDefaultEngines"])
 
 if not engines:
-  engines.update(searchinfo["default"]["visibleDefaultEngines"])
+    engines.update(searchinfo["default"]["visibleDefaultEngines"])
 
 # Get additional engines from regionOverrides
 for region, overrides in searchinfo["regionOverrides"].iteritems():
-  for originalengine, replacement in overrides.iteritems():
-    if originalengine in engines:
-      # We add the engine because we still need the original
-      engines.add(replacement)
+    for originalengine, replacement in overrides.iteritems():
+        if originalengine in engines:
+            # We add the engine because we still need the original
+            engines.add(replacement)
 
 # join() will take an iterable, not just a list.
 print('\n'.join(engines))
--- a/python/mozbuild/mozbuild/action/package_fennec_apk.py
+++ b/python/mozbuild/mozbuild/action/package_fennec_apk.py
@@ -6,17 +6,16 @@
 Script to produce an Android package (.apk) for Fennec.
 '''
 
 from __future__ import absolute_import, print_function
 
 import argparse
 import buildconfig
 import os
-import subprocess
 import sys
 
 from mozpack.copier import Jarrer
 from mozpack.files import (
     DeflatedFile,
     File,
     FileFinder,
 )
@@ -65,17 +64,17 @@ def package_fennec_apk(inputs=[], omni_j
                 jarrer.remove(path)
             jarrer.add(path, DeflatedFile(file), compress=file.compressed)
 
     def add(path, file, compress=None):
         abspath = os.path.abspath(file.path)
         if verbose:
             print('Packaging %s from %s' % (path, file.path))
         if not os.path.exists(abspath):
-            raise ValueError('File %s not found (looked for %s)' % \
+            raise ValueError('File %s not found (looked for %s)' %
                              (file.path, abspath))
         if jarrer.contains(path):
             jarrer.remove(path)
         jarrer.add(path, file, compress=compress)
 
     for features_dir in features_dirs:
         finder = FileFinder(features_dir)
         for p, f in finder.find('**'):
--- a/python/mozbuild/mozbuild/action/package_generated_sources.py
+++ b/python/mozbuild/mozbuild/action/package_generated_sources.py
@@ -1,40 +1,40 @@
 # 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/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import argparse
-import json
-import os.path
 import sys
 
 import buildconfig
 import mozpack.path as mozpath
 from mozpack.archive import create_tar_gz_from_files
 from mozpack.files import BaseFile
 from mozbuild.generated_sources import get_generated_sources
 
 
 def main(argv):
     parser = argparse.ArgumentParser(
         description='Produce archive of generated sources')
     parser.add_argument('outputfile', help='File to write output to')
     args = parser.parse_args(argv)
 
     objdir_abspath = mozpath.abspath(buildconfig.topobjdir)
+
     def is_valid_entry(entry):
         if isinstance(entry[1], BaseFile):
             entry_abspath = mozpath.abspath(entry[1].path)
         else:
             entry_abspath = mozpath.abspath(entry[1])
         if not entry_abspath.startswith(objdir_abspath):
-            print("Warning: omitting generated source [%s] from archive" % entry_abspath, file=sys.stderr)
+            print("Warning: omitting generated source [%s] from archive" % entry_abspath,
+                  file=sys.stderr)
             return False
         return True
 
     files = dict(filter(is_valid_entry, get_generated_sources()))
     with open(args.outputfile, 'wb') as fh:
         create_tar_gz_from_files(fh, files, compresslevel=5)
 
 
--- a/python/mozbuild/mozbuild/action/preprocessor.py
+++ b/python/mozbuild/mozbuild/action/preprocessor.py
@@ -1,24 +1,25 @@
 # 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/.
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import sys
 
 from mozbuild.preprocessor import Preprocessor
 
 
 def generate(output, *args):
     pp = Preprocessor()
     pp.out = output
     pp.handleCommandLine(list(args), True)
     return set(pp.includes)
 
+
 def main(args):
     pp = Preprocessor()
     pp.handleCommandLine(args, True)
 
 
 if __name__ == "__main__":
-  main(sys.argv[1:])
+    main(sys.argv[1:])
--- a/python/mozbuild/mozbuild/action/process_define_files.py
+++ b/python/mozbuild/mozbuild/action/process_define_files.py
@@ -5,17 +5,16 @@
 from __future__ import absolute_import, print_function, unicode_literals
 
 import argparse
 import os
 import re
 import sys
 from buildconfig import topsrcdir, topobjdir
 from mozbuild.backend.configenvironment import PartialConfigEnvironment
-from mozbuild.util import FileAvoidWrite
 import mozpack.path as mozpath
 
 
 def process_define_file(output, input):
     '''Creates the given config header. A config header is generated by
     taking the corresponding source file and replacing some #define/#undef
     occurences:
         "#undef NAME" is turned into "#define NAME VALUE"
@@ -46,21 +45,22 @@ def process_define_file(output, input):
                 name = m.group('name')
                 value = m.group('value')
                 if name:
                     if name == 'ALLDEFINES':
                         if cmd == 'define':
                             raise Exception(
                                 '`#define ALLDEFINES` is not allowed in a '
                                 'CONFIGURE_DEFINE_FILE')
-                        # WebRTC files like to define WINVER and _WIN32_WINNT
-                        # via the command line, which raises a mass of macro
-                        # redefinition warnings.  Just handle those macros
-                        # specially here.
+
                         def define_for_name(name, val):
+                            """WebRTC files like to define WINVER and _WIN32_WINNT
+                            via the command line, which raises a mass of macro
+                            redefinition warnings.  Just handle those macros
+                            specially here."""
                             define = "#define {name} {val}".format(name=name, val=val)
                             if name in ('WINVER', '_WIN32_WINNT'):
                                 return '#if !defined({name})\n{define}\n#endif' \
                                     .format(name=name, define=define)
                             return define
                         defines = '\n'.join(sorted(
                             define_for_name(name, val)
                             for name, val in config.defines['ALLDEFINES'].iteritems()))
--- a/python/mozbuild/mozbuild/action/process_install_manifest.py
+++ b/python/mozbuild/mozbuild/action/process_install_manifest.py
@@ -24,90 +24,91 @@ from mozbuild.util import DefinesAction
 
 
 COMPLETE = 'Elapsed: {elapsed:.2f}s; From {dest}: Kept {existing} existing; ' \
     'Added/updated {updated}; ' \
     'Removed {rm_files} files and {rm_dirs} directories.'
 
 
 def process_manifest(destdir, paths, track,
-        no_symlinks=False,
-        defines={}):
+                     no_symlinks=False,
+                     defines={}):
 
     if os.path.exists(track):
         # We use the same format as install manifests for the tracking
         # data.
         manifest = InstallManifest(path=track)
         remove_unaccounted = FileRegistry()
         dummy_file = BaseFile()
 
         finder = FileFinder(destdir, find_dotfiles=True)
         for dest in manifest._dests:
             for p, f in finder.find(dest):
                 remove_unaccounted.add(p, dummy_file)
 
-        remove_empty_directories=True
-        remove_all_directory_symlinks=True
+        remove_empty_directories = True
+        remove_all_directory_symlinks = True
 
     else:
         # If tracking is enabled and there is no file, we don't want to
         # be removing anything.
         remove_unaccounted = False
-        remove_empty_directories=False
-        remove_all_directory_symlinks=False
+        remove_empty_directories = False
+        remove_all_directory_symlinks = False
 
     manifest = InstallManifest()
     for path in paths:
         manifest |= InstallManifest(path=path)
 
     copier = FileCopier()
     link_policy = "copy" if no_symlinks else "symlink"
     manifest.populate_registry(
         copier, defines_override=defines, link_policy=link_policy
     )
     result = copier.copy(destdir,
-        remove_unaccounted=remove_unaccounted,
-        remove_all_directory_symlinks=remove_all_directory_symlinks,
-        remove_empty_directories=remove_empty_directories)
+                         remove_unaccounted=remove_unaccounted,
+                         remove_all_directory_symlinks=remove_all_directory_symlinks,
+                         remove_empty_directories=remove_empty_directories)
 
     if track:
         # We should record files that we actually copied.
         # It is too late to expand wildcards when the track file is read.
         manifest.write(path=track, expand_pattern=True)
 
     return result
 
 
 def main(argv):
     parser = argparse.ArgumentParser(
         description='Process install manifest files.')
 
     parser.add_argument('destdir', help='Destination directory.')
     parser.add_argument('manifests', nargs='+', help='Path to manifest file(s).')
     parser.add_argument('--no-symlinks', action='store_true',
-        help='Do not install symbolic links. Always copy files')
+                        help='Do not install symbolic links. Always copy files')
     parser.add_argument('--track', metavar="PATH", required=True,
-        help='Use installed files tracking information from the given path.')
+                        help='Use installed files tracking information from the given path.')
     parser.add_argument('-D', action=DefinesAction,
-        dest='defines', metavar="VAR[=VAL]",
-        help='Define a variable to override what is specified in the manifest')
+                        dest='defines', metavar="VAR[=VAL]",
+                        help='Define a variable to override what is specified in the manifest')
 
     args = parser.parse_args(argv)
 
     start = time.time()
 
     result = process_manifest(args.destdir, args.manifests,
-        track=args.track,
-        no_symlinks=args.no_symlinks,
-        defines=args.defines)
+                              track=args.track,
+                              no_symlinks=args.no_symlinks,
+                              defines=args.defines)
 
     elapsed = time.time() - start
 
     print(COMPLETE.format(
         elapsed=elapsed,
         dest=args.destdir,
         existing=result.existing_files_count,
         updated=result.updated_files_count,
         rm_files=result.removed_files_count,
         rm_dirs=result.removed_directories_count))
 
+
 if __name__ == '__main__':
     main(sys.argv[1:])
--- a/python/mozbuild/mozbuild/action/symbols_archive.py
+++ b/python/mozbuild/mozbuild/action/symbols_archive.py
@@ -7,45 +7,49 @@ from __future__ import absolute_import, 
 import argparse
 import sys
 import os
 
 from mozpack.files import FileFinder
 from mozpack.mozjar import JarWriter
 import mozpack.path as mozpath
 
+
 def make_archive(archive_name, base, exclude, include):
     compress = ['**/*.sym']
     finder = FileFinder(base, ignore=exclude)
     if not include:
         include = ['*']
     archive_basename = os.path.basename(archive_name)
     with open(archive_name, 'wb') as fh:
         with JarWriter(fileobj=fh, compress_level=5) as writer:
             for pat in include:
                 for p, f in finder.find(pat):
                     print('  Adding to "%s":\n\t"%s"' % (archive_basename, p))
                     should_compress = any(mozpath.match(p, pat) for pat in compress)
                     writer.add(p.encode('utf-8'), f, mode=f.mode,
                                compress=should_compress, skip_duplicates=True)
 
+
 def main(argv):
     parser = argparse.ArgumentParser(description='Produce a symbols archive')
     parser.add_argument('archive', help='Which archive to generate')
     parser.add_argument('base', help='Base directory to package')
-    parser.add_argument('--full-archive', action='store_true', help='Generate a full symbol archive')
+    parser.add_argument('--full-archive', action='store_true',
+                        help='Generate a full symbol archive')
 
     args = parser.parse_args(argv)
 
     excludes = []
     includes = []
 
     if args.full_archive:
         # We allow symbols for tests to be included when building on try
         if os.environ.get('MH_BRANCH', 'unknown') != 'try':
             excludes = ['*test*', '*Test*']
     else:
         includes = ['**/*.sym']
 
     make_archive(args.archive, args.base, excludes, includes)
 
+
 if __name__ == '__main__':
     main(sys.argv[1:])
--- a/python/mozbuild/mozbuild/action/test_archive.py
+++ b/python/mozbuild/mozbuild/action/test_archive.py
@@ -637,17 +637,17 @@ if buildconfig.substs.get('commtopsrcdir
 # Verify nothing sneaks into ARCHIVE_FILES without a corresponding exclusion
 # rule in the "common" archive.
 for k, v in ARCHIVE_FILES.items():
     # Skip mozharness because it isn't staged.
     if k in ('common', 'mozharness'):
         continue
 
     ignores = set(itertools.chain(*(e.get('ignore', [])
-                                  for e in ARCHIVE_FILES['common'])))
+                                    for e in ARCHIVE_FILES['common'])))
 
     if not any(p.startswith('%s/' % k) for p in ignores):
         raise Exception('"common" ignore list probably should contain %s' % k)
 
 
 def find_generated_harness_files():
     # TEST_HARNESS_FILES end up in an install manifest at
     # $topsrcdir/_build_manifests/install/_tests.
--- a/python/mozbuild/mozbuild/action/tooltool.py
+++ b/python/mozbuild/mozbuild/action/tooltool.py
@@ -12,16 +12,18 @@
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301, USA.
 
+from __future__ import absolute_import, print_function
+
 # A manifest file specifies files in that directory that are stored
 # elsewhere. This file should only list files in the same directory
 # in which the manifest file resides and it should be called
 # 'manifest.tt'
 
 from __future__ import print_function
 
 import base64
@@ -743,17 +745,17 @@ CHECKSUM_SUFFIX = ".checksum"
 def _cache_checksum_matches(base_file, checksum):
     try:
         with open(base_file + CHECKSUM_SUFFIX, "rb") as f:
             prev_checksum = f.read().strip()
             if prev_checksum == checksum:
                 log.info("Cache matches, avoiding extracting in '%s'" % base_file)
                 return True
             return False
-    except IOError as e:
+    except IOError:
         return False
 
 
 def _compute_cache_checksum(filename):
     with open(filename, "rb") as f:
         return digest_file(f, "sha256")
 
 
@@ -1017,17 +1019,17 @@ def _authorize(req, auth_file):
         return
 
     is_taskcluster_auth = False
     with open(auth_file) as f:
         auth_file_content = f.read().strip()
         try:
             auth_file_content = json.loads(auth_file_content)
             is_taskcluster_auth = True
-        except:
+        except Exception:
             pass
 
     if is_taskcluster_auth:
         taskcluster_header = make_taskcluster_header(auth_file_content, req)
         log.debug("Using taskcluster credentials in %s" % auth_file)
         req.add_unredirected_header('Authorization', taskcluster_header)
     else:
         log.debug("Using Bearer token in %s" % auth_file)
@@ -1295,10 +1297,11 @@ def main(argv, _skip_logging=False):
     if options['algorithm'] != 'sha512':
         parser.error('only --algorithm sha512 is supported')
 
     if len(args) < 1:
         parser.error('You must specify a command')
 
     return 0 if process_command(options, args) else 1
 
+
 if __name__ == "__main__":  # pragma: no cover
     sys.exit(main(sys.argv))
--- a/python/mozbuild/mozbuild/action/unpack_dmg.py
+++ b/python/mozbuild/mozbuild/action/unpack_dmg.py
@@ -1,13 +1,13 @@
 # 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/.
 
-from __future__ import print_function
+from __future__ import absolute_import, print_function
 
 from mozpack import dmg
 
 import argparse
 import sys
 
 
 def main(args):
--- a/python/mozbuild/mozbuild/action/webidl.py
+++ b/python/mozbuild/mozbuild/action/webidl.py
@@ -1,13 +1,13 @@
 # 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/.
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import sys
 
 from mozwebidlcodegen import BuildSystemWebIDL
 
 
 def main(argv):
     """Perform WebIDL code generation required by the build system."""
--- a/python/mozbuild/mozbuild/action/wrap_rustc.py
+++ b/python/mozbuild/mozbuild/action/wrap_rustc.py
@@ -4,16 +4,17 @@
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import argparse
 import subprocess
 import sys
 import os
 
+
 def parse_outputs(crate_output, dep_outputs, pass_l_flag):
     env = {}
     args = []
 
     def parse_line(line):
         if line.startswith('cargo:'):
             return line[len('cargo:'):].split('=', 1)
 
@@ -54,24 +55,26 @@ def parse_outputs(crate_output, dep_outp
             elif key:
                 # Todo: Distinguish between direct and transitive
                 # dependencies so we can pass metadata environment
                 # variables correctly.
                 pass
 
     return env, args
 
+
 def wrap_rustc(args):
     parser = argparse.ArgumentParser()
     parser.add_argument('--crate-out', nargs='?')
     parser.add_argument('--deps-out', nargs='*')
     parser.add_argument('--cwd')
     parser.add_argument('--pass-l-flag', action='store_true')
     parser.add_argument('--cmd', nargs=argparse.REMAINDER)
     args = parser.parse_args(args)
 
     new_env, new_args = parse_outputs(args.crate_out, args.deps_out,
                                       args.pass_l_flag)
     os.environ.update(new_env)
     return subprocess.Popen(args.cmd + new_args, cwd=args.cwd).wait()
 
+
 if __name__ == '__main__':
     sys.exit(wrap_rustc(sys.argv[1:]))
--- a/python/mozbuild/mozbuild/action/xpccheck.py
+++ b/python/mozbuild/mozbuild/action/xpccheck.py
@@ -3,81 +3,98 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 '''A generic script to verify all test files are in the
 corresponding .ini file.
 
 Usage: xpccheck.py <directory> [<directory> ...]
 '''
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import sys
 import os
 from glob import glob
 import manifestparser
 
+
 def getIniTests(testdir):
-  mp = manifestparser.ManifestParser(strict=False)
-  mp.read(os.path.join(testdir, 'xpcshell.ini'))
-  return mp.tests
+    mp = manifestparser.ManifestParser(strict=False)
+    mp.read(os.path.join(testdir, 'xpcshell.ini'))
+    return mp.tests
+
 
 def verifyDirectory(initests, directory):
-  files = glob(os.path.join(os.path.abspath(directory), "test_*"))
-  for f in files:
-    if (not os.path.isfile(f)):
-      continue
+    files = glob(os.path.join(os.path.abspath(directory), "test_*"))
+    for f in files:
+        if (not os.path.isfile(f)):
+            continue
 
-    name = os.path.basename(f)
-    if name.endswith('.in'):
-      name = name[:-3]
+        name = os.path.basename(f)
+        if name.endswith('.in'):
+            name = name[:-3]
+
+        if not name.endswith('.js'):
+            continue
 
-    if not name.endswith('.js'):
-      continue
+        found = False
+        for test in initests:
+            if os.path.join(os.path.abspath(directory), name) == test['path']:
+                found = True
+                break
 
-    found = False
-    for test in initests:
-      if os.path.join(os.path.abspath(directory), name) == test['path']:
-        found = True
-        break
-   
-    if not found:
-      print >>sys.stderr, "TEST-UNEXPECTED-FAIL | xpccheck | test %s is missing from test manifest %s!" % (name, os.path.join(directory, 'xpcshell.ini'))
-      sys.exit(1)
+        if not found:
+            print(('TEST-UNEXPECTED-FAIL | xpccheck | test '
+                   '%s is missing from test manifest %s!') % (
+                    name,
+                    os.path.join(directory, 'xpcshell.ini'),
+                    ),
+                  file=sys.stderr,
+                  )
+            sys.exit(1)
+
 
 def verifyIniFile(initests, directory):
-  files = glob(os.path.join(os.path.abspath(directory), "test_*"))
-  for test in initests:
-    name = test['path'].split('/')[-1]
+    files = glob(os.path.join(os.path.abspath(directory), "test_*"))
+    for test in initests:
+        name = test['path'].split('/')[-1]
 
-    found = False
-    for f in files:
+        found = False
+        for f in files:
+
+            fname = f.split('/')[-1]
+            if fname.endswith('.in'):
+                fname = '.in'.join(fname.split('.in')[:-1])
 
-      fname = f.split('/')[-1]
-      if fname.endswith('.in'):
-        fname = '.in'.join(fname.split('.in')[:-1])
+            if os.path.join(os.path.abspath(directory), fname) == test['path']:
+                found = True
+                break
 
-      if os.path.join(os.path.abspath(directory), fname) == test['path']:
-        found = True
-        break
+        if not found:
+            print(("TEST-UNEXPECTED-FAIL | xpccheck | found "
+                   "%s in xpcshell.ini and not in directory '%s'") % (
+                    name,
+                    directory,
+                    ),
+                  file=sys.stderr,
+                  )
+            sys.exit(1)
 
-    if not found:
-      print >>sys.stderr, "TEST-UNEXPECTED-FAIL | xpccheck | found %s in xpcshell.ini and not in directory '%s'" % (name, directory)
-      sys.exit(1)
 
 def main(argv):
-  if len(argv) < 2:
-    print >>sys.stderr, "Usage: xpccheck.py <topsrcdir> <directory> [<directory> ...]"
-    sys.exit(1)
+    if len(argv) < 2:
+        print("Usage: xpccheck.py <topsrcdir> <directory> [<directory> ...]",
+              file=sys.stderr)
+        sys.exit(1)
 
-  topsrcdir = argv[0]
-  for d in argv[1:]:
-    # xpcshell-unpack is a copy of xpcshell sibling directory and in the Makefile
-    # we copy all files (including xpcshell.ini from the sibling directory.
-    if d.endswith('toolkit/mozapps/extensions/test/xpcshell-unpack'):
-      continue
+    for d in argv[1:]:
+        # xpcshell-unpack is a copy of xpcshell sibling directory and in the Makefile
+        # we copy all files (including xpcshell.ini from the sibling directory.
+        if d.endswith('toolkit/mozapps/extensions/test/xpcshell-unpack'):
+            continue
 
-    initests = getIniTests(d)
-    verifyDirectory(initests, d)
-    verifyIniFile(initests, d)
+        initests = getIniTests(d)
+        verifyDirectory(initests, d)
+        verifyIniFile(initests, d)
+
 
 if __name__ == '__main__':
     main(sys.argv[1:])
--- a/python/mozbuild/mozbuild/action/xpidl-process.py
+++ b/python/mozbuild/mozbuild/action/xpidl-process.py
@@ -2,24 +2,22 @@
 # 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/.
 
 # This script is used to generate an output header and xpt file for
 # input IDL file(s). It's purpose is to directly support the build
 # system. The API will change to meet the needs of the build system.
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import argparse
 import os
 import sys
 
-from io import BytesIO
-
 from xpidl import jsonxpt
 from buildconfig import topsrcdir
 from xpidl.header import print_header
 from xpidl.rust import print_rust_bindings
 from xpidl.rust_macros import print_rust_macros_bindings
 from xpidl.xpidl import IDLParser
 
 from mozbuild.makeutil import Makefile
@@ -82,37 +80,38 @@ def process(input_dirs, inc_paths, bindi
         deps_path = os.path.join(deps_dir, '%s.pp' % module)
         with FileAvoidWrite(deps_path) as fh:
             mk.dump(fh)
 
 
 def main(argv):
     parser = argparse.ArgumentParser()
     parser.add_argument('--cache-dir',
-        help='Directory in which to find or write cached lexer data.')
+                        help='Directory in which to find or write cached lexer data.')
     parser.add_argument('--depsdir',
-        help='Directory in which to write dependency files.')
+                        help='Directory in which to write dependency files.')
     parser.add_argument('--bindings-conf',
-        help='Path to the WebIDL binding configuration file.')
+                        help='Path to the WebIDL binding configuration file.')
     parser.add_argument('--input-dir', dest='input_dirs',
                         action='append', default=[],
                         help='Directory(ies) in which to find source .idl files.')
     parser.add_argument('headerdir',
-        help='Directory in which to write header files.')
+                        help='Directory in which to write header files.')
     parser.add_argument('xpcrsdir',
-        help='Directory in which to write rust