Merge inbound to mozilla-central. a=merge
authorCsoregi Natalia <ncsoregi@mozilla.com>
Fri, 17 May 2019 00:55:45 +0300
changeset 474178 786f094a30ae94722246f078561928990ab1e0b7
parent 474109 96802be91766718fa33fe2e98f7a910e4dd1bb5e (current diff)
parent 474177 d394c173d6a4a527d6da32fd0ef8fa110a83bb6b (diff)
child 474179 3d88030030a181816e5fe300b6b8d66cb718d8ba
child 474186 3d36061643f440a3f06380e690bf38cfbe89892b
child 474228 67eb2cac32c2db9e105ef3b38480c8de56f0ddfe
push id36023
push userncsoregi@mozilla.com
push dateThu, 16 May 2019 21:56:43 +0000
treeherdermozilla-central@786f094a30ae [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
786f094a30ae / 68.0a1 / 20190516215643 / files
nightly linux64
786f094a30ae / 68.0a1 / 20190516215643 / files
nightly mac
786f094a30ae / 68.0a1 / 20190516215643 / files
nightly win32
786f094a30ae / 68.0a1 / 20190516215643 / files
nightly win64
786f094a30ae / 68.0a1 / 20190516215643 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
Cargo.lock
devtools/client/debugger/src/reducers/sources.js
devtools/client/webconsole/reducers/messages.js
devtools/client/webreplay/mochitest/browser.ini
devtools/client/webreplay/mochitest/browser_dbg_rr_recovery-01.js
devtools/client/webreplay/mochitest/examples/doc_rr_recovery.html
layout/base/nsDocumentViewer.cpp
python/mozbuild/mozbuild/action/test_archive.py
python/mozbuild/mozbuild/frontend/context.py
python/mozbuild/mozbuild/testing.py
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> ...]"