Merge mozilla-central to mozilla-inbound. a=merge CLOSED TREE
authorCiure Andrei <aciure@mozilla.com>
Wed, 23 Jan 2019 20:55:29 +0200
changeset 512285 309abca54dae664c346136ec2d0be957b39b676c
parent 512284 6a1c4140f4796d03368e9ab4b8f547209422fdd5 (current diff)
parent 512251 4f1ff0e34dd59539c8c0d133ecdff5660ec4e263 (diff)
child 512289 211eda3487ef5bb72a2482e15051808ec508166d
push id10566
push userarchaeopteryx@coole-files.de
push dateMon, 28 Jan 2019 12:41:12 +0000
treeherdermozilla-beta@69a3d7c8d04b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound. a=merge CLOSED TREE
browser/components/urlbar/tests/unit/test_QueryContext.js
devtools/server/tests/mochitest/animation-data.html
devtools/server/tests/mochitest/test_animation_actor-lifetime.html
devtools/server/tests/mochitest/test_inspector-anonymous.html
devtools/server/tests/mochitest/test_inspector-insert.html
devtools/server/tests/mochitest/test_inspector-mutations-childlist.html
devtools/server/tests/mochitest/test_inspector-mutations-frameload.html
devtools/server/tests/mochitest/test_inspector-release.html
devtools/server/tests/mochitest/test_inspector-remove.html
devtools/server/tests/mochitest/test_inspector-retain.html
devtools/server/tests/mochitest/test_inspector-search.html
devtools/server/tests/mochitest/test_inspector-traversal.html
devtools/server/tests/mochitest/test_memory_allocations_01.html
devtools/shared/webconsole/test/test_commands_registration.html
devtools/shared/webconsole/test/test_network_longstring.html
js/xpconnect/public/nsTArrayHelpers.h
netwerk/cookie/nsCookieService.cpp
old-configure.in
other-licenses/7zstub/firefox/7zSD.manifest
other-licenses/7zstub/firefox/7zSD.sfx
testing/mozharness/configs/single_locale/alder.py
testing/mozharness/configs/single_locale/ash.py
testing/mozharness/configs/single_locale/ash_android-api-16.py
testing/mozharness/configs/single_locale/autoland.py
testing/mozharness/configs/single_locale/autoland_android-api-16.py
testing/mozharness/configs/single_locale/jamun.py
testing/mozharness/configs/single_locale/jamun_android-api-16.py
testing/mozharness/configs/single_locale/linux.py
testing/mozharness/configs/single_locale/linux32.py
testing/mozharness/configs/single_locale/linux32_devedition.py
testing/mozharness/configs/single_locale/linux64_devedition.py
testing/mozharness/configs/single_locale/linux_devedition.py
testing/mozharness/configs/single_locale/macosx64_devedition.py
testing/mozharness/configs/single_locale/maple_android-api-16.py
testing/mozharness/configs/single_locale/mozilla-aurora.py
testing/mozharness/configs/single_locale/mozilla-aurora_android-api-16.py
testing/mozharness/configs/single_locale/mozilla-beta.py
testing/mozharness/configs/single_locale/mozilla-beta_android-api-16.py
testing/mozharness/configs/single_locale/mozilla-beta_devedition.py
testing/mozharness/configs/single_locale/mozilla-central.py
testing/mozharness/configs/single_locale/mozilla-central_android-api-16.py
testing/mozharness/configs/single_locale/mozilla-esr60.py
testing/mozharness/configs/single_locale/mozilla-inbound.py
testing/mozharness/configs/single_locale/mozilla-inbound_android-api-16.py
testing/mozharness/configs/single_locale/mozilla-release.py
testing/mozharness/configs/single_locale/mozilla-release_android-api-16.py
testing/mozharness/configs/single_locale/onchange.py
testing/mozharness/configs/single_locale/tc_linux64.py
testing/mozharness/configs/single_locale/try.py
testing/mozharness/configs/single_locale/try_android-api-16.py
testing/mozharness/configs/single_locale/win32_devedition.py
testing/mozharness/configs/single_locale/win64_devedition.py
testing/mozharness/mozharness/mozilla/mar.py
--- a/.eslintignore
+++ b/.eslintignore
@@ -203,17 +203,17 @@ dom/quota/**
 dom/security/test/cors/**
 dom/security/test/csp/**
 dom/security/test/general/**
 dom/security/test/mixedcontentblocker/**
 dom/security/test/sri/**
 dom/serviceworkers/**
 dom/smil/**
 dom/svg/**
-dom/tests/browser/**
+
 dom/tests/html/**
 dom/tests/mochitest/**
 dom/u2f/**
 dom/url/**
 dom/vr/**
 dom/webauthn/**
 dom/webgpu/**
 dom/websocket/**
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -731,16 +731,27 @@ version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "darling_core 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
  "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "derive_more"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "devd-rs"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
  "nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1108,17 +1119,17 @@ dependencies = [
  "mp4parse_capi 0.11.2",
  "netwerk_helper 0.0.1",
  "nserror 0.1.0",
  "nsstring 0.1.0",
  "prefs_parser 0.0.1",
  "profiler_helper 0.1.0",
  "rkv 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "rsdparsa_capi 0.1.0",
- "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "u2fhid 0.2.3",
  "webrender_bindings 0.1.0",
  "xpcom 0.1.0",
 ]
 
 [[package]]
 name = "gl_generator"
 version = "0.10.0"
@@ -2180,20 +2191,20 @@ source = "registry+https://github.com/ru
 
 [[package]]
 name = "rustc-demangle"
 version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "rustc_version"
-version = "0.2.1"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "ryu"
 version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -2219,17 +2230,17 @@ name = "scopeguard"
 version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "scroll"
 version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "scroll_derive 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "scroll_derive"
 version = "0.9.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
@@ -2239,16 +2250,17 @@ dependencies = [
 ]
 
 [[package]]
 name = "selectors"
 version = "0.21.0"
 dependencies = [
  "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "derive_more 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_arc 0.1.1",
  "smallvec 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2259,16 +2271,24 @@ dependencies = [
 name = "semver"
 version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "semver-parser"
 version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "serde"
 version = "1.0.80"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2428,16 +2448,17 @@ version = "0.0.1"
 dependencies = [
  "app_units 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "arrayvec 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bindgen 0.43.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "derive_more 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.19.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "fallible 0.0.1",
  "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "hashglobe 0.1.0",
  "indexmap 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "itertools 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3027,16 +3048,17 @@ name = "webrender_api"
 version = "0.58.0"
 dependencies = [
  "app_units 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "derive_more 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.19.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "malloc_size_of_derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_bytes 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 1.0.80 (git+https://github.com/servo/serde?branch=deserialize_from_enums9)",
  "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
  "wr_malloc_size_of 0.0.1",
@@ -3264,16 +3286,17 @@ dependencies = [
 "checksum cstr-macros 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0472c17c83d3ec1af32fb6ee2b3ad56ae0b6e69355d63d1d30602055c34324a8"
 "checksum cubeb 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8a3502aafa1bf95c524f65d2ba46d8741700c6a8a9543ea52c6da3d8b69a2896"
 "checksum cubeb-backend 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdcac95519416d9ec814db2dc40e6293e7da25b906023d93f48b87f0587ab138"
 "checksum cubeb-core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37f7b20f757a4e4b6aa28863236551bff77682dc6db192eba15af615492b5445"
 "checksum cubeb-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "653b9e245d35dbe2a2da7c4586275cee75ff656ddeb02d4a73b4afdfa6d67502"
 "checksum darling 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f593353cad5af2df6d54810de2b61aa8acba5b5fbc70b0d75e7cc5bdd80aca73"
 "checksum darling_core 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "168c246e87e727d58575d9b4595491010627f0cdc910e3e6ea3b36db2b9a9d9a"
 "checksum darling_macro 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "99c4eff4bcbeaf6a22578012ff79c95910338668278d1901e528bd34a22f575d"
+"checksum derive_more 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f57d78cf3bd45270dad4e70c21ec77a960b36c7a841ff9db76aaa775a8fb871"
 "checksum devd-rs 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e7c9ac481c38baf400d3b732e4a06850dfaa491d1b6379a249d9d40d14c2434c"
 "checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a"
 "checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90"
 "checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c"
 "checksum dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88972de891f6118092b643d85a0b28e0678e0f948d7f879aa32f2d5aafe97d2a"
 "checksum docopt 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d8acd393692c503b168471874953a2531df0e9ab77d0b6bbc582395743300a4a"
 "checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
 "checksum dtoa-short 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "068d4026697c1a18f0b0bb8cfcad1b0c151b90d8edb9bf4c235ad68128920d1d"
@@ -3397,25 +3420,26 @@ dependencies = [
 "checksum regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75ecf88252dce580404a22444fc7d626c01815debba56a7f4f536772a5ff19d3"
 "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db"
 "checksum regex-syntax 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1ac0f60d675cc6cf13a20ec076568254472551051ad5dd050364d70671bf6b"
 "checksum rkv 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "663e50c3b2454387726a83b01629892824dcf113c0471841ea4bc9b5929eb75e"
 "checksum ron 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "da06feaa07f69125ab9ddc769b11de29090122170b402547f64b86fe16ebc399"
 "checksum runloop 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d79b4b604167921892e84afbbaad9d5ad74e091bf6c511d9dbfb0593f09fabd"
 "checksum rust-ini 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8a654c5bda722c699be6b0fe4c0d90de218928da5b724c3e467fc48865c37263"
 "checksum rustc-demangle 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "76d7ba1feafada44f2d38eed812bd2489a03c0f5abb975799251518b68848649"
-"checksum rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9743a7670d88d5d52950408ecdb7c71d8986251ab604d4689dd2ca25c9bca69"
+"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
 "checksum ryu 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0568787116e13c652377b6846f5931454a363a8fdf8ae50463ee40935b278b"
 "checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
 "checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d"
 "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
 "checksum scopeguard 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c79eb2c3ac4bc2507cda80e7f3ac5b88bd8eae4c0914d5663e6a8933994be918"
 "checksum scroll 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2f84d114ef17fd144153d608fba7c446b0145d038985e7a8cc5d08bb0ce20383"
 "checksum scroll_derive 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1aa96c45e7f5a91cb7fabe7b279f02fea7126239fc40b732316e8b6a2d0fcb"
 "checksum semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537"
+"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
 "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
 "checksum serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)" = "15c141fc7027dd265a47c090bf864cf62b42c4d228bbcf4e51a0c9e2b0d3f7ef"
 "checksum serde_bytes 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "adb6e51a6b3696b301bc221d785f898b4457c619b51d7ce195a6d20baecb37b3"
 "checksum serde_derive 1.0.80 (git+https://github.com/servo/serde?branch=deserialize_from_enums9)" = "<none>"
 "checksum serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "44dd2cfde475037451fa99b7e5df77aa3cfd1536575fa8e7a538ab36dcde49ae"
 "checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0"
 "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d"
 "checksum simd 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0048b17eb9577ac545c61d85c3559b41dfb4cbea41c9bd9ca6a4f73ff05fda84"
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -113,16 +113,22 @@ static bool MustBeAccessible(nsIContent*
   if (aContent->GetPrimaryFrame()->IsFocusable()) return true;
 
   if (aContent->IsElement()) {
     uint32_t attrCount = aContent->AsElement()->GetAttrCount();
     for (uint32_t attrIdx = 0; attrIdx < attrCount; attrIdx++) {
       const nsAttrName* attr = aContent->AsElement()->GetAttrNameAt(attrIdx);
       if (attr->NamespaceEquals(kNameSpaceID_None)) {
         nsAtom* attrAtom = attr->Atom();
+        if (attrAtom == nsGkAtoms::title && aContent->IsHTMLElement()) {
+          // If the author provided a title on an element that would not
+          // be accessible normally, assume an intent and make it accessible.
+          return true;
+        }
+
         nsDependentAtomString attrStr(attrAtom);
         if (!StringBeginsWith(attrStr, NS_LITERAL_STRING("aria-")))
           continue;  // not ARIA
 
         // A global state or a property and in case of token defined.
         uint8_t attrFlags = aria::AttrCharacteristicsFor(attrAtom);
         if ((attrFlags & ATTR_GLOBAL) &&
             (!(attrFlags & ATTR_VALTOKEN) ||
--- a/accessible/tests/mochitest/elm/test_HTMLSpec.html
+++ b/accessible/tests/mochitest/elm/test_HTMLSpec.html
@@ -1246,16 +1246,23 @@
       ok(!isAccessible("source"), "source element is not accessible");
 
       // ////////////////////////////////////////////////////////////////////////
       // HTML:span
 
       ok(!isAccessible("span"), "span element is not accessible");
 
       // ////////////////////////////////////////////////////////////////////////
+      // html:span with a title attribute, which should make it accessible.
+      obj = {
+        role: ROLE_TEXT,
+      };
+      testElm("span_explicit", obj);
+
+      // ////////////////////////////////////////////////////////////////////////
       // HTML:strong contained by paragraph
 
       obj = {
         role: ROLE_PARAGRAPH,
         children: [
           { role: ROLE_TEXT_LEAF }, // plain text
           { role: ROLE_TEXT_LEAF }, // HTML:strong text
         ],
@@ -1757,16 +1764,17 @@
   </ruby>
 
   <p id="s_container">normal<s>striked</s></p>
   <p id="samp_container">normal<samp>sample</samp></p>
   <section id="section">section</section>
   <section id="named_section" aria-label="foo">named section</section>
   <p id="small_container">normal<small>small</small></p>
   <span id="span"></span>
+  <span id="span_explicit" title="explicit"></span>
   <p id="strong_container">normal<strong>strong</strong></p>
   <p id="sub_container">normal<sub>sub</sub></p>
   <p id="sup_container">normal<sup>sup</sup></p>
 
   <svg id="svg"></svg>
   <textarea id="textarea"></textarea>
 
   <p>The concert took place on <time id="time" datetime="2001-05-15 19:00">May 15</time></p>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1468,26 +1468,26 @@ pref("media.gmp.trial-create.enabled", t
 // to enable the CDM if its disabled; it's as if the keysystem is completely
 // unsupported.
 
 #ifdef MOZ_WIDEVINE_EME
 pref("media.gmp-widevinecdm.visible", true);
 pref("media.gmp-widevinecdm.enabled", true);
 #endif
 
-#ifdef NIGHTLY_BUILD
+
 // Switch block autoplay logic to v2, and enable UI.
 pref("media.autoplay.enabled.user-gestures-needed", true);
 // Set Firefox to block autoplay, asking for permission by default.
 pref("media.autoplay.default", 1); // 0=Allowed, 1=Blocked
+
+#ifdef NIGHTLY_BUILD
 // Block WebAudio from playing automatically.
 pref("media.autoplay.block-webaudio", true);
 #else
-pref("media.autoplay.default", 0); // 0=Allowed, 1=Blocked
-pref("media.autoplay.enabled.user-gestures-needed", false);
 pref("media.autoplay.block-webaudio", false);
 #endif
 
 
 // Play with different values of the decay time and get telemetry,
 // 0 means to randomize (and persist) the experiment value in users' profiles,
 // -1 means no experiment is run and we use the preferred value for frecency (6h)
 pref("browser.cache.frecency_experiment", 0);
--- a/browser/base/content/browser-allTabsMenu.js
+++ b/browser/base/content/browser-allTabsMenu.js
@@ -129,12 +129,11 @@ var gTabsPanel = {
     }
     this.allTabsView.addEventListener("ViewShown", (e) => {
       PanelUI.showSubView(this.kElements.hiddenTabsView, this.hiddenTabsButton);
     }, {once: true});
     this.showAllTabsPanel();
   },
 
   searchTabs() {
-    focusAndSelectUrlBar();
     gURLBar.typeRestrictToken(UrlbarTokenizer.RESTRICT.OPENPAGE);
   },
 };
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -537,17 +537,16 @@ var PlacesCommandHook = {
                  "", "chrome,toolbar=yes,dialog=no,resizable", item);
     } else {
       organizer.PlacesOrganizer.selectLeftPaneContainerByHierarchy(item);
       organizer.focus();
     }
   },
 
   searchBookmarks() {
-    focusAndSelectUrlBar();
     gURLBar.typeRestrictToken(UrlbarTokenizer.RESTRICT.BOOKMARK);
   },
 };
 
 ChromeUtils.defineModuleGetter(this, "RecentlyClosedTabsAndWindowsMenuUtils",
   "resource:///modules/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm");
 
 // View for the history menu.
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -1050,17 +1050,18 @@ var gIdentityHandler = {
     nameLabel.textContent = label;
     let nameLabelId = "identity-popup-permission-label-" + aPermission.id;
     nameLabel.setAttribute("id", nameLabelId);
 
     let isPolicyPermission = [
       SitePermissions.SCOPE_POLICY, SitePermissions.SCOPE_GLOBAL,
     ].includes(aPermission.scope);
 
-    if (aPermission.id == "popup" && !isPolicyPermission) {
+    if ((aPermission.id == "popup" && !isPolicyPermission) ||
+        aPermission.id == "autoplay-media") {
       let menulist = document.createXULElement("menulist");
       let menupopup = document.createXULElement("menupopup");
       let block = document.createXULElement("vbox");
       block.setAttribute("id", "identity-popup-popup-container");
       menulist.setAttribute("sizetopopup", "none");
       menulist.setAttribute("class", "identity-popup-popup-menulist");
       menulist.setAttribute("id", "identity-popup-popup-menulist");
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3995,17 +3995,16 @@ const BrowserSearch = {
                                 "chrome,all,dialog=no", "about:blank");
         Services.obs.addObserver(observer, "browser-delayed-startup-finished");
       }
       return;
     }
 
     let focusUrlBarIfSearchFieldIsNotActive = function(aSearchBar) {
       if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField) {
-        focusAndSelectUrlBar(true);
         // Limit the results to search suggestions, like the search bar.
         gURLBar.typeRestrictToken(UrlbarTokenizer.RESTRICT.SEARCH);
       }
     };
 
     let searchBar = this.searchBar;
     let placement = CustomizableUI.getPlacementOfWidget("search-container");
     let focusSearchBar = () => {
--- a/browser/base/content/test/permissions/browser_autoplay_blocked.js
+++ b/browser/base/content/test/permissions/browser_autoplay_blocked.js
@@ -49,13 +49,32 @@ add_task(async function testMainViewVisi
     ok(!BrowserTestUtils.is_hidden(autoplayBlockedIcon()), "Blocked icon is shown");
 
     await openIdentityPopup();
     ok(BrowserTestUtils.is_hidden(emptyLabel), "List of permissions is not empty");
     let labelText = SitePermissions.getPermissionLabel("autoplay-media");
     let labels = permissionsList.querySelectorAll(".identity-popup-permission-label");
     is(labels.length, 1, "One permission visible in main view");
     is(labels[0].textContent, labelText, "Correct value");
+
+    let menulist = document.getElementById("identity-popup-popup-menulist");
+    Assert.equal(menulist.label, "Block");
+
+    await EventUtils.synthesizeMouseAtCenter(menulist, { type: "mousedown" });
+    await BrowserTestUtils.waitForCondition(() => {
+      return menulist.getElementsByTagName("menuitem")[0].label === "Allow";
+    });
+
+    let menuitem = menulist.getElementsByTagName("menuitem")[0];
+    Assert.equal(menuitem.getAttribute("label"), "Allow");
+
+    menuitem.click();
+    menulist.menupopup.hidePopup();
     await closeIdentityPopup();
+
+    let uri = Services.io.newURI(AUTOPLAY_PAGE);
+    let state = SitePermissions.get(uri, "autoplay-media").state;
+    Assert.equal(state, SitePermissions.ALLOW);
   });
 
+  Services.perms.removeAll();
   Services.prefs.clearUserPref("media.autoplay.default");
 });
--- a/browser/base/content/test/trackingUI/browser_trackingUI_cookies_subview.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_cookies_subview.js
@@ -16,117 +16,171 @@ add_task(async function setup() {
   await UrlClassifierTestUtils.addTestTrackers();
 
   registerCleanupFunction(() => {
     UrlClassifierTestUtils.cleanupTestTrackers();
   });
 });
 
 async function assertSitesListed(trackersBlocked, thirdPartyBlocked, firstPartyBlocked) {
-  await BrowserTestUtils.withNewTab(COOKIE_PAGE, async function(browser) {
-    await openIdentityPopup();
+  let promise = BrowserTestUtils.openNewForegroundTab({url: COOKIE_PAGE, gBrowser});
+  let specialCase = firstPartyBlocked ||
+                    (trackersBlocked && thirdPartyBlocked && !firstPartyBlocked) ||
+                    (!trackersBlocked && !thirdPartyBlocked && !firstPartyBlocked);
+  let count = 4;
+  if (firstPartyBlocked) {
+    count = 6;
+  } else if (trackersBlocked && thirdPartyBlocked && !firstPartyBlocked) {
+    count = 5;
+  } else if (!trackersBlocked && !thirdPartyBlocked && !firstPartyBlocked) {
+    count = 3;
+  }
+  let [tab] = await Promise.all([promise, waitForContentBlockingEvent(count)]);
+  let browser = tab.linkedBrowser;
+
+  await openIdentityPopup();
+
+  let categoryItem =
+    document.getElementById("identity-popup-content-blocking-category-cookies");
+  ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible");
+  let cookiesView = document.getElementById("identity-popup-cookiesView");
+  let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
+  categoryItem.click();
+  await viewShown;
 
-    let categoryItem =
-      document.getElementById("identity-popup-content-blocking-category-cookies");
-    ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible");
-    let cookiesView = document.getElementById("identity-popup-cookiesView");
-    let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
-    categoryItem.click();
-    await viewShown;
+  ok(true, "Cookies view was shown");
+
+  let listHeaders = cookiesView.querySelectorAll(".identity-popup-cookiesView-list-header");
+  is(listHeaders.length, 3, "We have 3 list headers");
+
+  let emptyLabels = cookiesView.querySelectorAll(".identity-popup-content-blocking-empty-label");
+  if (specialCase) {
+    count = 1;
+  } else {
+    count = 2;
+  }
+  is(emptyLabels.length, count, `We have ${count} empty labels`);
+
+  let listItems = cookiesView.querySelectorAll(".identity-popup-content-blocking-list-item");
+  if (specialCase) {
+    count = 2;
+  } else {
+    count = 1;
+  }
+  is(listItems.length, count, `We have ${count} cookie in the list`);
+
+  let listItem = listItems[specialCase ? 1 : 0];
+  let label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
+  is(label.value, "http://trackertest.org", "Has an item for trackertest.org");
+  ok(BrowserTestUtils.is_visible(listItem), "List item is visible");
+  is(listItem.classList.contains("allowed"), !trackersBlocked,
+    "Indicates whether the cookie was blocked or allowed");
 
-    ok(true, "Cookies view was shown");
+  if (specialCase) {
+    listItem = listItems[0];
+    label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
+    is(label.value, "http://not-tracking.example.com", "Has an item for not-tracking.example.com");
+    ok(BrowserTestUtils.is_visible(listItem), "List item is visible");
+    is(listItem.classList.contains("allowed"), !firstPartyBlocked,
+      "Indicates whether the cookie was blocked");
+  }
 
-    let listHeaders = cookiesView.querySelectorAll(".identity-popup-cookiesView-list-header");
-    is(listHeaders.length, 3, "We have 3 list headers");
+  let mainView = document.getElementById("identity-popup-mainView");
+  viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
+  let backButton = cookiesView.querySelector(".subviewbutton-back");
+  backButton.click();
+  await viewShown;
+
+  ok(true, "Main view was shown");
+
+  let change = waitForContentBlockingEvent();
+  let timeoutPromise = new Promise(resolve => setTimeout(resolve, 1000));
+
+  await ContentTask.spawn(browser, {}, function() {
+    content.postMessage("third-party-cookie", "*");
+  });
+
+  let result = await Promise.race([change, timeoutPromise]);
+  is(result, undefined, "No contentBlockingEvent events should be received");
 
-    let emptyLabels = cookiesView.querySelectorAll(".identity-popup-content-blocking-empty-label");
-    is(emptyLabels.length, 2, "We have 2 empty labels");
+  viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
+  categoryItem.click();
+  await viewShown;
+
+  ok(true, "Cookies view was shown");
+
+  emptyLabels = cookiesView.querySelectorAll(".identity-popup-content-blocking-empty-label");
+  if (specialCase) {
+    count = 0;
+  } else {
+    count = 1;
+  }
+  is(emptyLabels.length, count, `We have ${count} empty label`);
 
-    let listItems = cookiesView.querySelectorAll(".identity-popup-content-blocking-list-item");
-    is(listItems.length, 1, "We have 1 cookie in the list");
+  listItems = cookiesView.querySelectorAll(".identity-popup-content-blocking-list-item");
+  if (specialCase) {
+    count = 3;
+  } else {
+    count = 2;
+  }
+  is(listItems.length, count, `We have ${count} cookies in the list`);
 
-    let listItem = listItems[0];
-    let label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
+  listItem = listItems[specialCase ? 2 : 1];
+  label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
+  is(label.value, "https://test1.example.org", "Has an item for test1.example.org");
+  ok(BrowserTestUtils.is_visible(listItem), "List item is visible");
+  is(listItem.classList.contains("allowed"), !thirdPartyBlocked,
+    "Indicates whether the cookie was blocked or allowed");
+
+  if (specialCase) {
+    listItem = listItems[1];
+    label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
     is(label.value, "http://trackertest.org", "Has an item for trackertest.org");
     ok(BrowserTestUtils.is_visible(listItem), "List item is visible");
-    is(listItem.classList.contains("allowed"), !trackersBlocked,
-      "Indicates whether the cookie was blocked or allowed");
+    is(listItem.classList.contains("allowed"),
+      (!trackersBlocked && !thirdPartyBlocked && !firstPartyBlocked),
+      "Indicates whether the cookie was blocked");
+  }
 
-    let mainView = document.getElementById("identity-popup-mainView");
-    viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
-    let backButton = cookiesView.querySelector(".subviewbutton-back");
-    backButton.click();
-    await viewShown;
-
-    ok(true, "Main view was shown");
-
-    let change = waitForSecurityChange();
-    let timeoutPromise = new Promise(resolve => setTimeout(resolve, 1000));
+  viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
+  backButton.click();
+  await viewShown;
 
-    await ContentTask.spawn(browser, {}, function() {
-      content.postMessage("third-party-cookie", "*");
-    });
+  ok(true, "Main view was shown");
 
-    let result = await Promise.race([change, timeoutPromise]);
-    is(result, undefined, "No securityChange events should be received");
+  change = waitForSecurityChange();
+  timeoutPromise = new Promise(resolve => setTimeout(resolve, 1000));
 
-    viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
-    categoryItem.click();
-    await viewShown;
-
-    ok(true, "Cookies view was shown");
+  await ContentTask.spawn(browser, {}, function() {
+    content.postMessage("first-party-cookie", "*");
+  });
 
-    emptyLabels = cookiesView.querySelectorAll(".identity-popup-content-blocking-empty-label");
-    is(emptyLabels.length, 1, "We have 1 empty label");
-
-    listItems = cookiesView.querySelectorAll(".identity-popup-content-blocking-list-item");
-    is(listItems.length, 2, "We have 2 cookies in the list");
+  result = await Promise.race([change, timeoutPromise]);
+  is(result, undefined, "No securityChange events should be received");
 
-    listItem = listItems[1];
-    label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
-    is(label.value, "https://test1.example.org", "Has an item for test1.example.org");
-    ok(BrowserTestUtils.is_visible(listItem), "List item is visible");
-    is(listItem.classList.contains("allowed"), !thirdPartyBlocked,
-      "Indicates whether the cookie was blocked or allowed");
+  viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
+  categoryItem.click();
+  await viewShown;
 
-    viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
-    backButton.click();
-    await viewShown;
-
-    ok(true, "Main view was shown");
+  ok(true, "Cookies view was shown");
 
-    change = waitForSecurityChange();
-    timeoutPromise = new Promise(resolve => setTimeout(resolve, 1000));
-
-    await ContentTask.spawn(browser, {}, function() {
-      content.postMessage("first-party-cookie", "*");
-    });
+  emptyLabels = cookiesView.querySelectorAll(".identity-popup-content-blocking-empty-label");
+  is(emptyLabels.length, 0, "We have 0 empty label");
 
-    result = await Promise.race([change, timeoutPromise]);
-    is(result, undefined, "No securityChange events should be received");
-
-    viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
-    categoryItem.click();
-    await viewShown;
-
-    ok(true, "Cookies view was shown");
+  listItems = cookiesView.querySelectorAll(".identity-popup-content-blocking-list-item");
+  is(listItems.length, 3, "We have 2 cookies in the list");
 
-    emptyLabels = cookiesView.querySelectorAll(".identity-popup-content-blocking-empty-label");
-    is(emptyLabels.length, 0, "We have 0 empty label");
-
-    listItems = cookiesView.querySelectorAll(".identity-popup-content-blocking-list-item");
-    is(listItems.length, 3, "We have 2 cookies in the list");
+  listItem = listItems[0];
+  label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
+  is(label.value, "http://not-tracking.example.com", "Has an item for the first party");
+  ok(BrowserTestUtils.is_visible(listItem), "List item is visible");
+  is(listItem.classList.contains("allowed"), !firstPartyBlocked,
+    "Indicates whether the cookie was blocked or allowed");
 
-    listItem = listItems[0];
-    label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
-    is(label.value, "http://not-tracking.example.com", "Has an item for the first party");
-    ok(BrowserTestUtils.is_visible(listItem), "List item is visible");
-    is(listItem.classList.contains("allowed"), !firstPartyBlocked,
-      "Indicates whether the cookie was blocked or allowed");
-  });
+  BrowserTestUtils.removeTab(tab);
 }
 
 add_task(async function testCookiesSubView() {
   info("Testing cookies subview with reject tracking cookies.");
   Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER);
   await assertSitesListed(true, false, false);
   info("Testing cookies subview with reject third party cookies.");
   Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN);
@@ -141,137 +195,165 @@ add_task(async function testCookiesSubVi
   Services.prefs.clearUserPref(TPC_PREF);
 });
 
 add_task(async function testCookiesSubViewAllowed() {
   Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER);
   let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("http://trackertest.org/");
   Services.perms.addFromPrincipal(principal, "cookie", Services.perms.ALLOW_ACTION);
 
-  await BrowserTestUtils.withNewTab(COOKIE_PAGE, async function(browser) {
-    await openIdentityPopup();
+  let promise = BrowserTestUtils.openNewForegroundTab({url: COOKIE_PAGE, gBrowser});
+  let [tab] = await Promise.all([promise, waitForContentBlockingEvent(3)]);
+
+  await openIdentityPopup();
 
-    let categoryItem =
-      document.getElementById("identity-popup-content-blocking-category-cookies");
-    ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible");
-    let cookiesView = document.getElementById("identity-popup-cookiesView");
-    let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
-    categoryItem.click();
-    await viewShown;
+  let categoryItem =
+    document.getElementById("identity-popup-content-blocking-category-cookies");
+  ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible");
+  let cookiesView = document.getElementById("identity-popup-cookiesView");
+  let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
+  categoryItem.click();
+  await viewShown;
 
-    ok(true, "Cookies view was shown");
+  ok(true, "Cookies view was shown");
+
+  let listItems = cookiesView.querySelectorAll(".identity-popup-content-blocking-list-item");
+  is(listItems.length, 2, "We have 1 cookie in the list");
 
-    let listItems = cookiesView.querySelectorAll(".identity-popup-content-blocking-list-item");
-    is(listItems.length, 1, "We have 1 cookie in the list");
+  let listItem = listItems[0];
+  let label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
+  is(label.value, "http://not-tracking.example.com", "has an item for not-tracking.example.com");
+  ok(BrowserTestUtils.is_visible(listItem), "list item is visible");
+  ok(listItem.classList.contains("allowed"), "indicates whether the cookie was blocked or allowed");
 
-    let listItem = listItems[0];
-    let label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
-    is(label.value, "http://trackertest.org", "Has an item for trackertest.org");
-    ok(BrowserTestUtils.is_visible(listItem), "List item is visible");
-    ok(listItem.classList.contains("allowed"), "Indicates whether the cookie was blocked or allowed");
+  listItem = listItems[1];
+  label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
+  is(label.value, "http://trackertest.org", "has an item for trackertest.org");
+  ok(BrowserTestUtils.is_visible(listItem), "list item is visible");
+  ok(listItem.classList.contains("allowed"), "indicates whether the cookie was blocked or allowed");
 
-    let button = listItem.querySelector(".identity-popup-permission-remove-button");
-    ok(BrowserTestUtils.is_visible(button), "Permission remove button is visible");
-    button.click();
-    is(Services.perms.testExactPermissionFromPrincipal(principal, "cookie"), Services.perms.UNKNOWN_ACTION, "Button click should remove cookie pref.");
-    ok(!listItem.classList.contains("allowed"), "Has removed the allowed class");
-  });
+  let button = listItem.querySelector(".identity-popup-permission-remove-button");
+  ok(BrowserTestUtils.is_visible(button), "Permission remove button is visible");
+  button.click();
+  is(Services.perms.testExactPermissionFromPrincipal(principal, "cookie"), Services.perms.UNKNOWN_ACTION, "Button click should remove cookie pref.");
+  ok(!listItem.classList.contains("allowed"), "Has removed the allowed class");
+
+  BrowserTestUtils.removeTab(tab);
 
   Services.prefs.clearUserPref(TPC_PREF);
 });
 
 add_task(async function testCookiesSubViewAllowedHeuristic() {
   Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER);
   let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("http://not-tracking.example.com/");
 
   // Pretend that the tracker has already been interacted with
   let trackerPrincipal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("http://trackertest.org/");
   Services.perms.addFromPrincipal(trackerPrincipal, "storageAccessAPI", Services.perms.ALLOW_ACTION);
 
-  await BrowserTestUtils.withNewTab(COOKIE_PAGE, async function(browser) {
-    let popup;
-    let windowCreated = TestUtils.topicObserved("chrome-document-global-created", (subject, data) => {
-      popup = subject;
-      return true;
+  let promise = BrowserTestUtils.openNewForegroundTab({url: COOKIE_PAGE, gBrowser});
+  let [tab] = await Promise.all([promise, waitForContentBlockingEvent(5)]);
+  let browser = tab.linkedBrowser;
+
+  let popup;
+  let windowCreated = TestUtils.topicObserved("chrome-document-global-created", (subject, data) => {
+    popup = subject;
+    return true;
+  });
+  let permChanged = TestUtils.topicObserved("perm-changed",
+    (subject, data) => {
+      return subject &&
+             subject.QueryInterface(Ci.nsIPermission)
+                    .type == "3rdPartyStorage^http://trackertest.org" &&
+             subject.principal.origin == principal.origin &&
+             data == "added";
     });
-    let permChanged = TestUtils.topicObserved("perm-changed",
-      (subject, data) => {
-        return subject &&
-               subject.QueryInterface(Ci.nsIPermission)
-                      .type == "3rdPartyStorage^http://trackertest.org" &&
-               subject.principal.origin == principal.origin &&
-               data == "added";
-      });
 
-    await ContentTask.spawn(browser, {}, function() {
-      content.postMessage("window-open", "*");
-    });
-    await Promise.all([windowCreated, permChanged]);
+  await ContentTask.spawn(browser, {}, function() {
+    content.postMessage("window-open", "*");
+  });
+  await Promise.all([windowCreated, permChanged]);
 
-    await new Promise(resolve => waitForFocus(resolve, popup));
-    await new Promise(resolve => waitForFocus(resolve, window));
+  await new Promise(resolve => waitForFocus(resolve, popup));
+  await new Promise(resolve => waitForFocus(resolve, window));
 
-    await openIdentityPopup();
+  await openIdentityPopup();
 
-    let categoryItem =
-      document.getElementById("identity-popup-content-blocking-category-cookies");
-    ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible");
-    let cookiesView = document.getElementById("identity-popup-cookiesView");
-    let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
-    categoryItem.click();
-    await viewShown;
+  let categoryItem =
+    document.getElementById("identity-popup-content-blocking-category-cookies");
+  ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible");
+  let cookiesView = document.getElementById("identity-popup-cookiesView");
+  let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
+  categoryItem.click();
+  await viewShown;
 
-    ok(true, "Cookies view was shown");
+  ok(true, "Cookies view was shown");
 
-    let listItems = cookiesView.querySelectorAll(".identity-popup-content-blocking-list-item");
-    is(listItems.length, 1, "We have 1 cookie in the list");
+  let listItems = cookiesView.querySelectorAll(".identity-popup-content-blocking-list-item");
+  is(listItems.length, 2, "We have 2 cookie in the list");
 
-    let listItem = listItems[0];
-    let label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
-    is(label.value, "http://trackertest.org", "Has an item for trackertest.org");
-    ok(BrowserTestUtils.is_visible(listItem), "List item is visible");
-    ok(listItem.classList.contains("allowed"), "Indicates whether the cookie was blocked or allowed");
+  let listItem = listItems[0];
+  let label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
+  is(label.value, "http://not-tracking.example.com", "has an item for not-tracking.example.com");
+  ok(BrowserTestUtils.is_visible(listItem), "list item is visible");
+  ok(listItem.classList.contains("allowed"), "indicates whether the cookie was blocked or allowed");
+
+  listItem = listItems[1];
+  label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
+  is(label.value, "http://trackertest.org", "has an item for trackertest.org");
+  ok(BrowserTestUtils.is_visible(listItem), "list item is visible");
+  ok(listItem.classList.contains("allowed"), "indicates whether the cookie was blocked or allowed");
 
-    let button = listItem.querySelector(".identity-popup-permission-remove-button");
-    ok(BrowserTestUtils.is_visible(button), "Permission remove button is visible");
-    button.click();
-    is(Services.perms.testExactPermissionFromPrincipal(principal, "3rdPartyStorage^http://trackertest.org"), Services.perms.UNKNOWN_ACTION, "Button click should remove the storage pref.");
-    ok(!listItem.classList.contains("allowed"), "Has removed the allowed class");
+  let button = listItem.querySelector(".identity-popup-permission-remove-button");
+  ok(BrowserTestUtils.is_visible(button), "Permission remove button is visible");
+  button.click();
+  is(Services.perms.testExactPermissionFromPrincipal(principal, "3rdPartyStorage^http://trackertest.org"), Services.perms.UNKNOWN_ACTION, "Button click should remove the storage pref.");
+  ok(!listItem.classList.contains("allowed"), "Has removed the allowed class");
 
-    await ContentTask.spawn(browser, {}, function() {
-      content.postMessage("window-close", "*");
-    });
+  await ContentTask.spawn(browser, {}, function() {
+    content.postMessage("window-close", "*");
   });
 
+  BrowserTestUtils.removeTab(tab);
+
   Services.prefs.clearUserPref(TPC_PREF);
 });
 
 add_task(async function testCookiesSubViewBlockedDoublyNested() {
   Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER);
 
-  await BrowserTestUtils.withNewTab(CONTAINER_PAGE, async function(browser) {
-    await openIdentityPopup();
+  let promise = BrowserTestUtils.openNewForegroundTab({url: CONTAINER_PAGE, gBrowser});
+  let [tab] = await Promise.all([promise, waitForContentBlockingEvent(3)]);
+
+  await openIdentityPopup();
 
-    let categoryItem =
-      document.getElementById("identity-popup-content-blocking-category-cookies");
-    ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible");
-    let cookiesView = document.getElementById("identity-popup-cookiesView");
-    let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
-    categoryItem.click();
-    await viewShown;
+  let categoryItem =
+    document.getElementById("identity-popup-content-blocking-category-cookies");
+  ok(BrowserTestUtils.is_visible(categoryItem), "TP category item is visible");
+  let cookiesView = document.getElementById("identity-popup-cookiesView");
+  let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
+  categoryItem.click();
+  await viewShown;
 
-    ok(true, "Cookies view was shown");
+  ok(true, "Cookies view was shown");
 
-    let listItems = cookiesView.querySelectorAll(".identity-popup-content-blocking-list-item");
-    is(listItems.length, 1, "We have 1 cookie in the list");
+  let listItems = cookiesView.querySelectorAll(".identity-popup-content-blocking-list-item");
+  is(listItems.length, 2, "We have 2 cookie in the list");
+
+  let listItem = listItems[0];
+  let label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
+  is(label.value, "http://not-tracking.example.com", "has an item for not-tracking.example.com");
+  ok(BrowserTestUtils.is_visible(listItem), "list item is visible");
+  ok(listItem.classList.contains("allowed"), "indicates whether the cookie was blocked or allowed");
 
-    let listItem = listItems[0];
-    let label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
-    is(label.value, "http://trackertest.org", "Has an item for trackertest.org");
-    ok(BrowserTestUtils.is_visible(listItem), "List item is visible");
-    ok(!listItem.classList.contains("allowed"), "Indicates whether the cookie was blocked or allowed");
+  listItem = listItems[1];
+  label = listItem.querySelector(".identity-popup-content-blocking-list-host-label");
+  is(label.value, "http://trackertest.org", "has an item for trackertest.org");
+  ok(BrowserTestUtils.is_visible(listItem), "list item is visible");
+  ok(!listItem.classList.contains("allowed"), "indicates whether the cookie was blocked or allowed");
 
-    let button = listItem.querySelector(".identity-popup-permission-remove-button");
-    ok(!button, "Permission remove button doesn't exist");
-  });
+  let button = listItem.querySelector(".identity-popup-permission-remove-button");
+  ok(!button, "Permission remove button doesn't exist");
+
+  BrowserTestUtils.removeTab(tab);
 
   Services.prefs.clearUserPref(TPC_PREF);
 });
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1693,16 +1693,18 @@ file, You can obtain one at http://mozil
             this.controller.startSearch(value);
           }
         ]]></body>
       </method>
 
       <method name="typeRestrictToken">
         <parameter name="char"/>
         <body><![CDATA[
+          focusAndSelectUrlBar();
+
           this.inputField.value = char + " ";
 
           let event = this.document.createEvent("UIEvents");
           event.initUIEvent("input", true, false, window, 0);
           this.inputField.dispatchEvent(event);
         ]]></body>
       </method>
 
--- a/browser/components/customizableui/PanelMultiView.jsm
+++ b/browser/components/customizableui/PanelMultiView.jsm
@@ -599,16 +599,27 @@ var PanelMultiView = class extends Assoc
    * @param viewIdOrNode
    *        DOM element or string ID of the <panelview> to display.
    * @param anchor
    *        DOM element that triggered the subview, which will be highlighted
    *        and whose "label" attribute will be used for the title of the
    *        subview when a "title" attribute is not specified.
    */
   showSubView(viewIdOrNode, anchor) {
+    // When autoPosition is true, the popup window manager would attempt to re-position
+    // the panel as subviews are opened and it changes size. The resulting popoppositioned
+    // events triggers the binding's arrow position adjustment - and its reflow.
+    // This is not needed here, as we calculated and set maxHeight so it is known
+    // to fit the screen while open.
+    // We do need autoposition for cases where the panel's anchor moves, which can happen
+    // especially with the "page actions" button in the URL bar (see bug 1520607), so
+    // we only set this to false when showing a subview, and set it back to true after we
+    // activate the subview.
+    this._panel.autoPosition = false;
+
     this._showSubView(viewIdOrNode, anchor).catch(Cu.reportError);
   }
   async _showSubView(viewIdOrNode, anchor) {
     let viewNode = typeof viewIdOrNode == "string" ?
                    this.document.getElementById(viewIdOrNode) : viewIdOrNode;
     if (!viewNode) {
       Cu.reportError(new Error(`Subview ${viewIdOrNode} doesn't exist.`));
       return;
@@ -802,16 +813,19 @@ var PanelMultiView = class extends Assoc
   _activateView(panelView) {
     if (panelView.isOpenIn(this)) {
       panelView.active = true;
       if (panelView.focusWhenActive) {
         panelView.focusFirstNavigableElement();
         panelView.focusWhenActive = false;
       }
       panelView.dispatchCustomEvent("ViewShown");
+
+      // Re-enable panel autopositioning.
+      this._panel.autoPosition = true;
     }
   }
 
   /**
    * Closes the most recent PanelView and raises the ViewHiding event.
    *
    * @note The ViewHiding event is not cancelable and should probably be renamed
    *       to ViewHidden or ViewClosed instead, see bug 1438507.
@@ -1099,25 +1113,16 @@ var PanelMultiView = class extends Assoc
         this._viewContainer.setAttribute("panelopen", "true");
         if (!this.node.hasAttribute("disablekeynav")) {
           this.window.addEventListener("keydown", this);
           this._panel.addEventListener("mousemove", this);
         }
         break;
       }
       case "popuppositioned": {
-        // When autoPosition is true, the popup window manager would attempt to re-position
-        // the panel as subviews are opened and it changes size. The resulting popoppositioned
-        // events triggers the binding's arrow position adjustment - and its reflow.
-        // This is not needed here, as we calculated and set maxHeight so it is known
-        // to fit the screen while open.
-        // autoPosition gets reset after each popuppositioned event, and when the
-        // popup closes, so we must set it back to false each time.
-        this._panel.autoPosition = false;
-
         if (this._panel.state == "showing") {
           let maxHeight = this._calculateMaxHeight();
           this._viewStack.style.maxHeight = maxHeight + "px";
           this._offscreenViewStack.style.maxHeight = maxHeight + "px";
         }
         break;
       }
       case "popupshown":
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus.js
@@ -646,21 +646,16 @@ add_task(async function test_organizer_c
   let leftTree = library.document.getElementById("placesList");
 
   let tests = [
     [mainTree, bookmarkContextMenuExtension],
     [mainTree, bookmarkFolderContextMenuExtension],
     [leftTree, bookmarkFolderContextMenuExtension],
   ];
 
-  if (AppConstants.DEBUG) {
-    // Avoid intermittent leak - bug 1520047
-    tests.pop();
-  }
-
   for (let [tree, makeExtension] of tests) {
     let extension = makeExtension();
     await extension.startup();
     let bookmarkGuid = await extension.awaitMessage("bookmark-created");
 
     tree.selectItems([bookmarkGuid]);
     let shown = BrowserTestUtils.waitForEvent(menu, "popupshown");
     synthesizeClickOnSelectedTreeCell(tree, {type: "contextmenu"});
--- a/browser/components/places/content/tree.xml
+++ b/browser/components/places/content/tree.xml
@@ -17,34 +17,29 @@
     <implementation>
       <constructor><![CDATA[
         // Force an initial build.
         if (this.place)
           this.place = this.place;
       ]]></constructor>
 
       <destructor><![CDATA[
-        // Break the treeviewer->result->treeviewer cycle.
-        // Note: unsetting the result's viewer also unsets
-        // the viewer's reference to our tree.
-        var result = this.result;
-        if (result) {
-          result.root.containerOpen = false;
-        }
-
         // Unregister the controllber before unlinking the view, otherwise it
         // may still try to update commands on a view with a null result.
         if (this._controller) {
           this._controller.terminate();
           this.controllers.removeController(this._controller);
         }
 
         if (this.view) {
           this.view.uninit();
         }
+        // view.setTree(null) will be called upon unsetting the view, which
+        // breaks the reference cycle between the PlacesTreeView and result.
+        // See the "setTree" method of PlacesTreeView in treeView.js.
         this.view = null;
       ]]></destructor>
 
       <property name="controller"
                 readonly="true"
                 onget="return this._controller"/>
 
       <property name="disableUserActions"
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -1458,16 +1458,17 @@ PlacesTreeView.prototype = {
     let hasOldTree = this._tree != null;
     this._tree = aTree;
 
     if (this._result) {
       if (hasOldTree) {
         // detach from result when we are detaching from the tree.
         // This breaks the reference cycle between us and the result.
         if (!aTree) {
+          // Balances the addObserver call from the load method in tree.xml
           this._result.removeObserver(this);
           this._rootNode.containerOpen = false;
         }
       }
       if (aTree)
         this._finishInit();
     }
   },
--- a/browser/components/places/tests/browser/browser.ini
+++ b/browser/components/places/tests/browser/browser.ini
@@ -67,16 +67,17 @@ skip-if = (verify && debug && (os == 'ma
 [browser_library_left_pane_select_hierarchy.js]
 [browser_library_middleclick.js]
 [browser_library_new_bookmark.js]
 [browser_library_open_leak.js]
 [browser_library_openFlatContainer.js]
 [browser_library_open_bookmark.js]
 [browser_library_panel_leak.js]
 [browser_library_search.js]
+[browser_library_tree_leak.js]
 [browser_library_views_liveupdate.js]
 [browser_library_warnOnOpen.js]
 [browser_markPageAsFollowedLink.js]
 [browser_panelview_bookmarks_delete.js]
 [browser_paste_bookmarks.js]
 subsuite = clipboard
 [browser_paste_into_tags.js]
 [browser_paste_resets_cut_highlights.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_library_tree_leak.js
@@ -0,0 +1,22 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+
+"use strict";
+
+add_task(async function bookmark_leak_window() {
+  // A library window has two trees after selecting a bookmark item:
+  // A left tree (#placesList) and a right tree (#placeContent).
+  // Upon closing the window, both trees are destructed, in an unspecified
+  // order. In bug 1520047, a memory leak was observed when the left tree
+  // was destroyed last.
+
+  let library = await promiseLibrary("BookmarksToolbar");
+  let tree = library.document.getElementById("placesList");
+  tree.selectItems(["toolbar_____"]);
+
+  await synthesizeClickOnSelectedTreeCell(tree, {type: "mousedown"});
+  await promiseLibraryClosed(library);
+
+  Assert.ok(true, "Closing a window after selecting a node in the tree should not cause a leak");
+});
+
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -404,31 +404,21 @@
   <richlistbox id="handlersView"
                preference="pref.downloads.disable_button.edit_actions"/>
 </groupbox>
 
 
 <!-- DRM Content -->
 <groupbox id="drmGroup" data-category="paneGeneral" data-subcategory="drm" hidden="true">
   <label><html:h2 data-l10n-id="drm-content-header"/></label>
-  <grid id="contentGrid2">
-    <columns>
-      <column flex="1"/>
-      <column/>
-    </columns>
-    <rows id="contentRows-2">
-      <row id="playDRMContentRow">
-        <hbox align="center">
-          <checkbox id="playDRMContent" preference="media.eme.enabled"
-                    class="tail-with-learn-more" data-l10n-id="play-drm-content" />
-          <label id="playDRMContentLink" class="learnMore text-link" data-l10n-id="play-drm-content-learn-more"/>
-        </hbox>
-      </row>
-    </rows>
-  </grid>
+  <hbox align="center">
+    <checkbox id="playDRMContent" preference="media.eme.enabled"
+              class="tail-with-learn-more" data-l10n-id="play-drm-content" />
+    <label id="playDRMContentLink" class="learnMore text-link" data-l10n-id="play-drm-content-learn-more"/>
+  </hbox>
 </groupbox>
 
 #ifdef HAVE_SHELL_SERVICE
   <stringbundle id="bundleShell" src="chrome://browser/locale/shellservice.properties"/>
   <stringbundle id="bundleBrand" src="chrome://branding/locale/brand.properties"/>
 #endif
 
 <hbox id="updatesCategory"
--- a/browser/components/preferences/siteDataSettings.js
+++ b/browser/components/preferences/siteDataSettings.js
@@ -1,23 +1,24 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
 /* 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";
+
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 ChromeUtils.defineModuleGetter(this, "SiteDataManager",
                                "resource:///modules/SiteDataManager.jsm");
 ChromeUtils.defineModuleGetter(this, "DownloadUtils",
                                "resource://gre/modules/DownloadUtils.jsm");
 
-"use strict";
-
 let gSiteDataSettings = {
 
   // Array of metadata of sites. Each array element is object holding:
   // - uri: uri of site; instance of nsIURI
   // - baseDomain: base domain of the site
   // - cookies: array of cookies of that site
   // - usage: disk usage which site uses
   // - userAction: "remove" or "update-permission"; the action user wants to take.
--- a/browser/components/urlbar/UrlbarController.jsm
+++ b/browser/components/urlbar/UrlbarController.jsm
@@ -1,15 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-var EXPORTED_SYMBOLS = ["QueryContext", "UrlbarController"];
+var EXPORTED_SYMBOLS = [
+  "UrlbarController",
+];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetters(this, {
   AppConstants: "resource://gre/modules/AppConstants.jsm",
   // BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.jsm",
   UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
@@ -77,17 +79,17 @@ class UrlbarController {
    */
   setView(view) {
     this.view = view;
   }
 
   /**
    * Takes a query context and starts the query based on the user input.
    *
-   * @param {QueryContext} queryContext The query details.
+   * @param {UrlbarQueryContext} queryContext The query details.
    */
   async startQuery(queryContext) {
     // Cancel any running query.
     if (this._lastQueryContext) {
       this.cancelQuery(this._lastQueryContext);
     }
     this._lastQueryContext = queryContext;
 
@@ -99,34 +101,34 @@ class UrlbarController {
     await this.manager.startQuery(queryContext, this);
     this._notify("onQueryFinished", queryContext);
   }
 
   /**
    * Cancels an in-progress query. Note, queries may continue running if they
    * can't be canceled.
    *
-   * @param {QueryContext} queryContext The query details.
+   * @param {UrlbarQueryContext} queryContext The query details.
    */
   cancelQuery(queryContext) {
     if (queryContext === this._lastQueryContext) {
       delete this._lastQueryContext;
     }
 
     TelemetryStopwatch.cancel(TELEMETRY_1ST_RESULT, queryContext);
     TelemetryStopwatch.cancel(TELEMETRY_6_FIRST_RESULTS, queryContext);
 
     this.manager.cancelQuery(queryContext);
     this._notify("onQueryCancelled", queryContext);
   }
 
   /**
    * Receives results from a query.
    *
-   * @param {QueryContext} queryContext The query details.
+   * @param {UrlbarQueryContext} queryContext The query details.
    */
   receiveResults(queryContext) {
     if (queryContext.lastTelemetryResultCount < 1 &&
         queryContext.results.length >= 1) {
       TelemetryStopwatch.finish(TELEMETRY_1ST_RESULT, queryContext);
     }
     if (queryContext.lastTelemetryResultCount < 6 &&
         queryContext.results.length >= 6) {
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -5,25 +5,25 @@
 "use strict";
 
 var EXPORTED_SYMBOLS = ["UrlbarInput"];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AppConstants: "resource://gre/modules/AppConstants.jsm",
+  ExtensionSearchHandler: "resource://gre/modules/ExtensionSearchHandler.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
-  QueryContext: "resource:///modules/UrlbarUtils.jsm",
   Services: "resource://gre/modules/Services.jsm",
   UrlbarController: "resource:///modules/UrlbarController.jsm",
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
+  UrlbarQueryContext: "resource:///modules/UrlbarUtils.jsm",
   UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
   UrlbarValueFormatter: "resource:///modules/UrlbarValueFormatter.jsm",
   UrlbarView: "resource:///modules/UrlbarView.jsm",
-  ExtensionSearchHandler: "resource://gre/modules/ExtensionSearchHandler.jsm",
 });
 
 XPCOMUtils.defineLazyServiceGetter(this, "ClipboardHelper",
                                    "@mozilla.org/widget/clipboardhelper;1",
                                    "nsIClipboardHelper");
 
 /**
  * Represents the urlbar <textbox>.
@@ -237,17 +237,17 @@ class UrlbarInput {
     openParams.postData = null;
     openParams.allowInheritPrincipal = false;
 
     // TODO: Work out how we get the user selection behavior, probably via passing
     // it in, since we don't have the old autocomplete controller to work with.
     // BrowserUsageTelemetry.recordUrlbarSelectedResultMethod(
     //   event, this.userSelectionBehavior);
 
-    url = url.trim();
+    url = this._maybeCanonizeURL(event, url) || url.trim();
 
     try {
       new URL(url);
     } catch (ex) {
       let browser = this.window.gBrowser.selectedBrowser;
       let lastLocationChange = browser.lastLocationChange;
 
       UrlbarUtils.getShortcutOrURIAndPostData(url).then(data => {
@@ -289,16 +289,19 @@ class UrlbarInput {
     //   event, this.userSelectionBehavior);
 
     let where = this._whereToOpen(event);
     let openParams = {
       postData: null,
       allowInheritPrincipal: false,
     };
 
+    // TODO bug 1521702: Call _maybeCanonizeURL for autofilled results with the
+    // typed string (not the autofilled one).
+
     let url = result.payload.url;
 
     switch (result.type) {
       case UrlbarUtils.MATCH_TYPE.TAB_SWITCH: {
         if (this._overrideDefaultAction(event)) {
           where = "current";
           break;
         }
@@ -311,16 +314,22 @@ class UrlbarInput {
 
         if (this.window.switchToTabHavingURI(Services.io.newURI(result.payload.url), false, loadOpts) &&
             prevTab.isEmpty) {
           this.window.gBrowser.removeTab(prevTab);
         }
         return;
       }
       case UrlbarUtils.MATCH_TYPE.SEARCH: {
+        url = this._maybeCanonizeURL(event,
+                result.payload.suggestion || result.payload.query);
+        if (url) {
+          break;
+        }
+
         const actionDetails = {
           isSuggestion: !!result.payload.suggestion,
           alias: result.payload.keyword,
         };
         const engine = Services.search.getEngineByName(result.payload.engine);
 
         [url, openParams.postData] = this._getSearchQueryUrl(
           engine, result.payload.suggestion || result.payload.query);
@@ -376,28 +385,30 @@ class UrlbarInput {
    */
   startQuery({
     lastKey = null,
   } = {}) {
     if (this._suppressStartQuery) {
       return;
     }
 
-    this.controller.startQuery(new QueryContext({
+    this.controller.startQuery(new UrlbarQueryContext({
       enableAutofill: UrlbarPrefs.get("autoFill"),
       isPrivate: this.isPrivate,
       lastKey,
       maxResults: UrlbarPrefs.get("maxRichResults"),
       muxer: "UnifiedComplete",
       providers: ["UnifiedComplete"],
       searchString: this.textValue,
     }));
   }
 
   typeRestrictToken(char) {
+    this.window.focusAndSelectUrlBar();
+
     this.inputField.value = char + " ";
 
     let event = this.document.createEvent("UIEvents");
     event.initUIEvent("input", true, false, this.window, 0);
     this.inputField.dispatchEvent(event);
   }
 
   // Getters and Setters below.
@@ -590,16 +601,60 @@ class UrlbarInput {
     let details = searchActionDetails;
     details.isOneOff = isOneOff;
     details.type = eventType;
 
     this.window.BrowserSearch.recordSearchInTelemetry(engine, "urlbar", details);
   }
 
   /**
+   * If appropriate, this prefixes a search string with 'www.' and suffixes it
+   * with browser.fixup.alternate.suffix prior to navigating.
+   *
+   * @param {Event} event
+   *   The event that triggered this query.
+   * @param {string} value
+   *   The search string that should be canonized.
+   * @returns {string}
+   *   Returns the canonized URL if available and null otherwise.
+   */
+  _maybeCanonizeURL(event, value) {
+    // Only add the suffix when the URL bar value isn't already "URL-like",
+    // and only if we get a keyboard event, to match user expectations.
+    if (!(event instanceof KeyboardEvent) ||
+        !event.ctrlKey ||
+        !UrlbarPrefs.get("ctrlCanonizesURLs") ||
+        !/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(value)) {
+      return null;
+    }
+
+    let suffix = Services.prefs.getCharPref("browser.fixup.alternate.suffix");
+    if (!suffix.endsWith("/")) {
+      suffix += "/";
+    }
+
+    // trim leading/trailing spaces (bug 233205)
+    value = value.trim();
+
+    // Tack www. and suffix on.  If user has appended directories, insert
+    // suffix before them (bug 279035).  Be careful not to get two slashes.
+    let firstSlash = value.indexOf("/");
+    if (firstSlash >= 0) {
+      value = value.substring(0, firstSlash) + suffix +
+              value.substring(firstSlash + 1);
+    } else {
+      value = value + suffix;
+    }
+    value = "http://www." + value;
+
+    this.value = value;
+    return value;
+  }
+
+  /**
    * Loads the url in the appropriate place.
    *
    * @param {string} url
    *   The URL to open.
    * @param {string} openUILinkWhere
    *   Where we expect the result to be opened.
    * @param {object} params
    *   The parameters related to how and where the result will be opened.
@@ -674,17 +729,18 @@ class UrlbarInput {
   _whereToOpen(event) {
     let isMouseEvent = event instanceof MouseEvent;
     let reuseEmpty = !isMouseEvent;
     let where = undefined;
     if (!isMouseEvent && event && event.altKey) {
       // We support using 'alt' to open in a tab, because ctrl/shift
       // might be used for canonizing URLs:
       where = event.shiftKey ? "tabshifted" : "tab";
-    } else if (!isMouseEvent && this._ctrlCanonizesURLs && event && event.ctrlKey) {
+    } else if (!isMouseEvent && event && event.ctrlKey &&
+               UrlbarPrefs.get("ctrlCanonizesURLs")) {
       // If we're allowing canonization, and this is a key event with ctrl
       // pressed, open in current tab to allow ctrl-enter to canonize URL.
       where = "current";
     } else {
       where = this.window.whereToOpenLink(event, false, false);
     }
     if (UrlbarPrefs.get("openintab")) {
       if (where == "current") {
--- a/browser/components/urlbar/UrlbarMuxerUnifiedComplete.jsm
+++ b/browser/components/urlbar/UrlbarMuxerUnifiedComplete.jsm
@@ -1,16 +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/. */
 
 "use strict";
 
 /**
- * This module exports a component used to sort matches in a QueryContext.
+ * This module exports a component used to sort matches in a UrlbarQueryContext.
  */
 
 var EXPORTED_SYMBOLS = ["UrlbarMuxerUnifiedComplete"];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetters(this, {
   Log: "resource://gre/modules/Log.jsm",
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
@@ -27,30 +27,30 @@ const MATCH_TYPE_TO_GROUP = new Map([
   [ UrlbarUtils.MATCH_TYPE.URL, UrlbarUtils.MATCH_GROUP.GENERAL ],
   [ UrlbarUtils.MATCH_TYPE.KEYWORD, UrlbarUtils.MATCH_GROUP.GENERAL ],
   [ UrlbarUtils.MATCH_TYPE.OMNIBOX, UrlbarUtils.MATCH_GROUP.EXTENSION ],
   [ UrlbarUtils.MATCH_TYPE.REMOTE_TAB, UrlbarUtils.MATCH_GROUP.GENERAL ],
 ]);
 
 /**
  * Class used to create a muxer.
- * The muxer receives and sorts matches in a QueryContext.
+ * The muxer receives and sorts matches in a UrlbarQueryContext.
  */
 class MuxerUnifiedComplete extends UrlbarMuxer {
   constructor() {
     super();
   }
 
   get name() {
     return "UnifiedComplete";
   }
 
   /**
-   * Sorts matches in the given QueryContext.
-   * @param {object} context a QueryContext
+   * Sorts matches in the given UrlbarQueryContext.
+   * @param {UrlbarQueryContext} context The query context.
    */
   sort(context) {
     if (!context.results.length) {
       return;
     }
     // Check the first match, if it's a preselected search match, use search buckets.
     let firstMatch = context.results[0];
     let buckets = context.preselected &&
--- a/browser/components/urlbar/UrlbarPrefs.jsm
+++ b/browser/components/urlbar/UrlbarPrefs.jsm
@@ -39,16 +39,22 @@ const PREF_URLBAR_DEFAULTS = new Map([
   // this value.  See UnifiedComplete.
   ["autoFill.stddevMultiplier", [0.0, "getFloatPref"]],
 
   // If true, this optimizes for replacing the full URL rather than editing
   // part of it. This also copies the urlbar value to the selection clipboard
   // on systems that support it.
   ["clickSelectsAll", false],
 
+  // Whether using `ctrl` when hitting return/enter in the URL bar
+  // (or clicking 'go') should prefix 'www.' and suffix
+  // browser.fixup.alternate.suffix to the URL bar value prior to
+  // navigating.
+  ["ctrlCanonizesURLs", true],
+
   // Whether copying the entire URL from the location bar will put a human
   // readable (percent-decoded) URL on the clipboard.
   ["decodeURLsOnCopy", false],
 
   // The amount of time (ms) to wait after the user has stopped typing before
   // fetching results.  However, we ignore this for the very first result (the
   // "heuristic" result).  We fetch it as fast as possible.
   ["delay", 50],
--- a/browser/components/urlbar/UrlbarProviderUnifiedComplete.jsm
+++ b/browser/components/urlbar/UrlbarProviderUnifiedComplete.jsm
@@ -154,17 +154,17 @@ var UrlbarProviderUnifiedComplete = new 
 /**
  * Convert from a nsIAutocompleteResult to a list of new matches.
  * Note that at every call we get the full set of matches, included the
  * previously returned ones, and new matches may be inserted in the middle.
  * This means we could sort these wrongly, the muxer should take care of it.
  * In any case at least we're sure there's just one heuristic result and it
  * comes first.
  *
- * @param {object} context the QueryContext
+ * @param {UrlbarQueryContext} context the query context.
  * @param {object} result an nsIAutocompleteResult
  * @param {set} urls a Set containing all the found urls, used to discard
  *        already added matches.
  * @returns {object} { matches: {array}, done: {boolean} }
  */
 function convertResultToMatches(context, result, urls) {
   let matches = [];
   let done = [
--- a/browser/components/urlbar/UrlbarProvidersManager.jsm
+++ b/browser/components/urlbar/UrlbarProvidersManager.jsm
@@ -408,18 +408,18 @@ class SkippableTimer {
     logger.debug(`Canceling timer for ${this._timer.delay}ms`);
     this._timer.cancel();
     delete this._timer;
     return this.fire();
   }
 }
 
 /**
- * Gets an array of the provider sources accepted for a given QueryContext.
- * @param {object} context The QueryContext to examine
+ * Gets an array of the provider sources accepted for a given UrlbarQueryContext.
+ * @param {UrlbarQueryContext} context The query context to examine
  * @returns {array} Array of accepted sources
  */
 function getAcceptableMatchSources(context) {
   let acceptedSources = [];
   // There can be only one restrict token about sources.
   let restrictToken = context.tokens.find(t => [ UrlbarTokenizer.TYPE.RESTRICT_HISTORY,
                                                  UrlbarTokenizer.TYPE.RESTRICT_BOOKMARK,
                                                  UrlbarTokenizer.TYPE.RESTRICT_TAG,
--- a/browser/components/urlbar/UrlbarTokenizer.jsm
+++ b/browser/components/urlbar/UrlbarTokenizer.jsm
@@ -159,20 +159,21 @@ var UrlbarTokenizer = {
     return !this.REGEXP_LIKE_PROTOCOL.test(hostPort) &&
            !this.REGEXP_USERINFO_INVALID_CHARS.test(userinfo) &&
            !this.REGEXP_HOSTPORT_INVALID_CHARS.test(hostPort) &&
            (!this.REGEXP_HOSTPORT_IP_LIKE.test(hostPort) ||
             !this.REGEXP_HOSTPORT_INVALID_IP.test(hostPort));
   },
 
   /**
-   * Tokenizes the searchString from a QueryContext.
-   * @param {object} queryContext
-   *        The QueryContext object to tokenize
-   * @returns {object} the same QueryContext object with a new tokens property.
+   * Tokenizes the searchString from a UrlbarQueryContext.
+   * @param {UrlbarQueryContext} queryContext
+   *        The query context object to tokenize
+   * @returns {UrlbarQueryContext} the same query context object with a new
+   *          tokens property.
    */
   tokenize(queryContext) {
     logger.info("Tokenizing", queryContext);
     let searchString = queryContext.searchString;
     if (searchString.length == 0) {
       queryContext.tokens = [];
       return queryContext;
     }
--- a/browser/components/urlbar/UrlbarUtils.jsm
+++ b/browser/components/urlbar/UrlbarUtils.jsm
@@ -5,19 +5,19 @@
 "use strict";
 
 /**
  * This module exports the UrlbarUtils singleton, which contains constants and
  * helper functions that are useful to all components of the urlbar.
  */
 
 var EXPORTED_SYMBOLS = [
-  "QueryContext",
   "UrlbarMuxer",
   "UrlbarProvider",
+  "UrlbarQueryContext",
   "UrlbarUtils",
 ];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   BinarySearch: "resource://gre/modules/BinarySearch.jsm",
   BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
@@ -227,26 +227,26 @@ var UrlbarUtils = {
         }
       }
       return matches;
     }, []);
   },
 };
 
 /**
- * QueryContext defines a user's autocomplete input from within the Address Bar.
+ * UrlbarQueryContext defines a user's autocomplete input from within the urlbar.
  * It supplements it with details of how the search results should be obtained
  * and what they consist of.
  */
-class QueryContext {
+class UrlbarQueryContext {
   /**
-   * Constructs the QueryContext instance.
+   * Constructs the UrlbarQueryContext instance.
    *
    * @param {object} options
-   *   The initial options for QueryContext.
+   *   The initial options for UrlbarQueryContext.
    * @param {string} options.searchString
    *   The string the user entered in autocomplete. Could be the empty string
    *   in the case of the user opening the popup via the mouse.
    * @param {number} options.lastKey
    *   The last key the user entered (as a key code). Could be null if the search
    *   was started via the mouse.
    * @param {boolean} options.isPrivate
    *   Set to true if this query was started from a private browsing window.
@@ -260,17 +260,17 @@ class QueryContext {
       "enableAutofill",
       "isPrivate",
       "lastKey",
       "maxResults",
       "searchString",
     ]);
 
     if (isNaN(parseInt(options.maxResults))) {
-      throw new Error(`Invalid maxResults property provided to QueryContext`);
+      throw new Error(`Invalid maxResults property provided to UrlbarQueryContext`);
     }
 
     if (options.providers &&
         (!Array.isArray(options.providers) || !options.providers.length)) {
       throw new Error(`Invalid providers list`);
     }
 
     if (options.sources &&
@@ -284,17 +284,17 @@ class QueryContext {
    *
    * @param {object} options The options object to check.
    * @param {array} optionNames The names of the options to check for.
    * @throws {Error} Throws if there is a missing option.
    */
   _checkRequiredOptions(options, optionNames) {
     for (let optionName of optionNames) {
       if (!(optionName in options)) {
-        throw new Error(`Missing or empty ${optionName} provided to QueryContext`);
+        throw new Error(`Missing or empty ${optionName} provided to UrlbarQueryContext`);
       }
       this[optionName] = options[optionName];
     }
   }
 }
 
 /**
  * Base class for a muxer.
@@ -306,17 +306,17 @@ class UrlbarMuxer {
    * Not using a unique name will cause the newest registration to win.
    * @abstract
    */
   get name() {
     return "UrlbarMuxerBase";
   }
   /**
    * Sorts queryContext matches in-place.
-   * @param {object} queryContext the context to sort matches for.
+   * @param {UrlbarQueryContext} queryContext the context to sort matches for.
    * @abstract
    */
   sort(queryContext) {
     throw new Error("Trying to access the base class, must be overridden");
   }
 }
 
 /**
@@ -344,27 +344,28 @@ class UrlbarProvider {
    * the provider.
    * @abstract
    */
   get sources() {
     throw new Error("Trying to access the base class, must be overridden");
   }
   /**
    * Starts querying.
-   * @param {object} queryContext The query context object
+   * @param {UrlbarQueryContext} queryContext The query context object
    * @param {function} addCallback Callback invoked by the provider to add a new
    *        match. A UrlbarMatch should be passed to it.
    * @note Extended classes should return a Promise resolved when the provider
    *       is done searching AND returning matches.
    * @abstract
    */
   startQuery(queryContext, addCallback) {
     throw new Error("Trying to access the base class, must be overridden");
   }
   /**
    * Cancels a running query,
-   * @param {object} queryContext the QueryContext object to cancel query for.
+   * @param {UrlbarQueryContext} queryContext the query context object to cancel
+   *        query for.
    * @abstract
    */
   cancelQuery(queryContext) {
     throw new Error("Trying to access the base class, must be overridden");
   }
 }
--- a/browser/components/urlbar/tests/browser/browser_UrlbarInput_unit.js
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_unit.js
@@ -11,49 +11,49 @@
 let fakeController;
 let generalListener;
 let input;
 let inputOptions;
 
 /**
  * Asserts that the query context has the expected values.
  *
- * @param {QueryContext} context
- * @param {object} expectedValues The expected values for the QueryContext.
+ * @param {UrlbarQueryContext} context
+ * @param {object} expectedValues The expected values for the UrlbarQueryContext.
  */
 function assertContextMatches(context, expectedValues) {
-  Assert.ok(context instanceof QueryContext,
-    "Should be a QueryContext");
+  Assert.ok(context instanceof UrlbarQueryContext,
+    "Should be a UrlbarQueryContext");
 
   for (let [key, value] of Object.entries(expectedValues)) {
     Assert.equal(context[key], value,
-      `Should have the expected value for ${key} in the QueryContext`);
+      `Should have the expected value for ${key} in the UrlbarQueryContext`);
   }
 }
 
 /**
  * Checks the result of a startQuery call on the controller.
  *
  * @param {object} stub The sinon stub that should have been called with the
- *                      QueryContext.
+ *                      UrlbarQueryContext.
  * @param {object} expectedQueryContextProps
  *                   An object consisting of name/value pairs to check against the
- *                   QueryContext properties.
+ *                   UrlbarQueryContext properties.
  */
 function checkStartQueryCall(stub, expectedQueryContextProps) {
   Assert.equal(stub.callCount, 1,
     "Should have called startQuery on the controller");
 
   let args = stub.args[0];
   Assert.equal(args.length, 1,
     "Should have called startQuery with one argument");
 
   let queryContext = args[0];
-  Assert.ok(queryContext instanceof QueryContext,
-    "Should have been passed a QueryContext");
+  Assert.ok(queryContext instanceof UrlbarQueryContext,
+    "Should have been passed a UrlbarQueryContext");
 
   for (let [name, value] of Object.entries(expectedQueryContextProps)) {
     Assert.deepEqual(queryContext[name],
      value, `Should have the correct value for queryContext.${name}`);
   }
 }
 
 add_task(async function setup() {
--- a/browser/components/urlbar/tests/browser/head.js
+++ b/browser/components/urlbar/tests/browser/head.js
@@ -8,19 +8,19 @@
 "use strict";
 
 let sandbox;
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetters(this, {
   PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
   Services: "resource://gre/modules/Services.jsm",
-  QueryContext: "resource:///modules/UrlbarUtils.jsm",
   UrlbarController: "resource:///modules/UrlbarController.jsm",
   UrlbarMatch: "resource:///modules/UrlbarMatch.jsm",
+  UrlbarQueryContext: "resource:///modules/UrlbarUtils.jsm",
   UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
 });
 
 /* import-globals-from head-common.js */
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/browser/components/urlbar/tests/browser/head-common.js",
   this);
 
--- a/browser/components/urlbar/tests/unit/head.js
+++ b/browser/components/urlbar/tests/unit/head.js
@@ -33,20 +33,21 @@ XPCOMUtils.defineLazyModuleGetters(this,
 ChromeUtils.import("resource://gre/modules/Timer.jsm");
 Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js", this);
 /* globals sinon */
 // ================================================
 
 /**
  * @param {string} searchString The search string to insert into the context.
  * @param {object} properties Overrides for the default values.
- * @returns {QueryContext} Creates a dummy query context with pre-filled required options.
+ * @returns {UrlbarQueryContext} Creates a dummy query context with pre-filled
+ *          required options.
  */
 function createContext(searchString = "foo", properties = {}) {
-  let context = new QueryContext({
+  let context = new UrlbarQueryContext({
     enableAutofill: UrlbarPrefs.get("autoFill"),
     isPrivate: true,
     lastKey: searchString ? searchString[searchString.length - 1] : "",
     maxResults: UrlbarPrefs.get("maxRichResults"),
     searchString,
   });
   return Object.assign(context, properties);
 }
--- a/browser/components/urlbar/tests/unit/test_UrlbarController_integration.js
+++ b/browser/components/urlbar/tests/unit/test_UrlbarController_integration.js
@@ -13,26 +13,26 @@ const TEST_URL = "http://example.com";
 const match = new UrlbarMatch(UrlbarUtils.MATCH_TYPE.TAB_SWITCH,
                               UrlbarUtils.MATCH_SOURCE.TABS,
                               { url: TEST_URL });
 let controller;
 
 /**
  * Asserts that the query context has the expected values.
  *
- * @param {QueryContext} context
- * @param {object} expectedValues The expected values for the QueryContext.
+ * @param {UrlbarQueryContext} context
+ * @param {object} expectedValues The expected values for the UrlbarQueryContext.
  */
 function assertContextMatches(context, expectedValues) {
-  Assert.ok(context instanceof QueryContext,
-    "Should be a QueryContext");
+  Assert.ok(context instanceof UrlbarQueryContext,
+    "Should be a UrlbarQueryContext");
 
   for (let [key, value] of Object.entries(expectedValues)) {
     Assert.equal(context[key], value,
-      `Should have the expected value for ${key} in the QueryContext`);
+      `Should have the expected value for ${key} in the UrlbarQueryContext`);
   }
 }
 
 add_task(async function setup() {
   controller = new UrlbarController({
     browserWindow: {
       location: {
         href: AppConstants.BROWSER_CHROME_URL,
--- a/browser/components/urlbar/tests/unit/test_UrlbarController_unit.js
+++ b/browser/components/urlbar/tests/unit/test_UrlbarController_unit.js
@@ -12,26 +12,26 @@
 let fPM;
 let sandbox;
 let generalListener;
 let controller;
 
 /**
  * Asserts that the query context has the expected values.
  *
- * @param {QueryContext} context
- * @param {object} expectedValues The expected values for the QueryContext.
+ * @param {UrlbarQueryContext} context
+ * @param {object} expectedValues The expected values for the UrlbarQueryContext.
  */
 function assertContextMatches(context, expectedValues) {
-  Assert.ok(context instanceof QueryContext,
-    "Should be a QueryContext");
+  Assert.ok(context instanceof UrlbarQueryContext,
+    "Should be a UrlbarQueryContext");
 
   for (let [key, value] of Object.entries(expectedValues)) {
     Assert.equal(context[key], value,
-      `Should have the expected value for ${key} in the QueryContext`);
+      `Should have the expected value for ${key} in the UrlbarQueryContext`);
   }
 }
 
 add_task(function setup() {
   sandbox = sinon.sandbox.create();
 
   fPM = {
     startQuery: sandbox.stub(),
rename from browser/components/urlbar/tests/unit/test_QueryContext.js
rename to browser/components/urlbar/tests/unit/test_UrlbarQueryContext.js
--- a/browser/components/urlbar/tests/unit/test_QueryContext.js
+++ b/browser/components/urlbar/tests/unit/test_UrlbarQueryContext.js
@@ -1,51 +1,51 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 add_task(function test_constructor() {
-  Assert.throws(() => new QueryContext(),
-    /Missing or empty enableAutofill provided to QueryContext/,
+  Assert.throws(() => new UrlbarQueryContext(),
+    /Missing or empty enableAutofill provided to UrlbarQueryContext/,
     "Should throw with no arguments");
 
-  Assert.throws(() => new QueryContext({
+  Assert.throws(() => new UrlbarQueryContext({
     enableAutofill: true,
     isPrivate: false,
     maxResults: 1,
     searchString: "foo",
-  }), /Missing or empty lastKey provided to QueryContext/,
+  }), /Missing or empty lastKey provided to UrlbarQueryContext/,
     "Should throw with a missing lastKey parameter");
 
-  Assert.throws(() => new QueryContext({
+  Assert.throws(() => new UrlbarQueryContext({
     enableAutofill: true,
     isPrivate: false,
     lastKey: "b",
     searchString: "foo",
-  }), /Missing or empty maxResults provided to QueryContext/,
+  }), /Missing or empty maxResults provided to UrlbarQueryContext/,
     "Should throw with a missing maxResults parameter");
 
-  Assert.throws(() => new QueryContext({
+  Assert.throws(() => new UrlbarQueryContext({
     enableAutofill: true,
     lastKey: "b",
     maxResults: 1,
     searchString: "foo",
-  }), /Missing or empty isPrivate provided to QueryContext/,
+  }), /Missing or empty isPrivate provided to UrlbarQueryContext/,
     "Should throw with a missing isPrivate parameter");
 
-  Assert.throws(() => new QueryContext({
+  Assert.throws(() => new UrlbarQueryContext({
     isPrivate: false,
     lastKey: "b",
     maxResults: 1,
     searchString: "foo",
-  }), /Missing or empty enableAutofill provided to QueryContext/,
+  }), /Missing or empty enableAutofill provided to UrlbarQueryContext/,
     "Should throw with a missing enableAutofill parameter");
 
-  let qc = new QueryContext({
+  let qc = new UrlbarQueryContext({
     enableAutofill: false,
     isPrivate: true,
     lastKey: "b",
     maxResults: 1,
     searchString: "foo",
   });
 
   Assert.strictEqual(qc.enableAutofill, false,
--- a/browser/components/urlbar/tests/unit/xpcshell.ini
+++ b/browser/components/urlbar/tests/unit/xpcshell.ini
@@ -5,15 +5,15 @@ support-files =
   data/engine-suggestions.xml
 
 [test_muxer.js]
 [test_providerOpenTabs.js]
 [test_providersManager.js]
 [test_providerUnifiedComplete.js]
 [test_providersManager_filtering.js]
 [test_providersManager_maxResults.js]
-[test_QueryContext.js]
 [test_tokenizer.js]
 [test_UrlbarController_unit.js]
 [test_UrlbarController_telemetry.js]
 [test_UrlbarController_integration.js]
+[test_UrlbarQueryContext.js]
 [test_UrlbarUtils_addToUrlbarHistory.js]
 [test_UrlbarUtils_getShortcutOrURIAndPostData.js]
copy from browser/config/mozconfigs/win64/l10n-mozconfig
copy to browser/config/mozconfigs/win64-aarch64/l10n-mozconfig
--- a/browser/config/mozconfigs/win64/l10n-mozconfig
+++ b/browser/config/mozconfigs/win64-aarch64/l10n-mozconfig
@@ -1,13 +1,13 @@
 . "$topsrcdir/browser/config/mozconfigs/common"
-. "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
+. "$topsrcdir/browser/config/mozconfigs/win64-aarch64/common-win64"
 . "$topsrcdir/build/mozconfig.no-compile"
 
-ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-update-channel="${MOZ_UPDATE_CHANNEL}"
 ac_add_options --with-l10n-base=../../l10n
 ac_add_options --with-branding=browser/branding/nightly
 
 ac_add_options --disable-nodejs
 unset NODEJS
 
 export MOZILLA_OFFICIAL=1
 
--- a/browser/docs/AddressBar.rst
+++ b/browser/docs/AddressBar.rst
@@ -29,37 +29,37 @@ The *Address Bar* component lives in the
 
 Global Architecture Overview
 ============================
 
 The *Address Bar* is implemented as a *Model-View-Controller* (MVC) system. One of
 the scopes of this architecture is to allow easy replacement of its components,
 for easier experimentation.
 
-Each search is represented by a unique object, the *QueryContext*. This object,
-created by the *View*, describes the search and is passed through all of the
-components, along the way it gets augmented with additional information.
-The *QueryContext* is passed to the *Controller*, and finally to the *Model*.
-The model appends matches to a property of *QueryContext* in chunks, it sorts
-them through a *Muxer* and then notifies the *Controller*.
+Each search is represented by a unique object, the *UrlbarQueryContext*. This
+object, created by the *View*, describes the search and is passed through all of
+the components, along the way it gets augmented with additional information.
+The *UrlbarQueryContext* is passed to the *Controller*, and finally to the
+*Model*.  The model appends matches to a property of *UrlbarQueryContext* in
+chunks, it sorts them through a *Muxer* and then notifies the *Controller*.
 
 See the specific components below, for additional details about each one's tasks
 and responsibilities.
 
 
-The QueryContext
+The UrlbarQueryContext
 ================
 
-The *QueryContext* object describes a single instance of a search.
+The *UrlbarQueryContext* object describes a single instance of a search.
 It is augmented as it progresses through the system, with various information:
 
 .. highlight:: JavaScript
 .. code::
 
-  QueryContext {
+  UrlbarQueryContext {
     enableAutofill; // {boolean} Whether or not to include autofill results.
     isPrivate; // {boolean} Whether the search started in a private context.
     lastKey; // {string} The last key pressed by the user. This can affect the
              // behavior, for example by not autofilling again when the user
              // hit backspace.
     maxResults; // {integer} The maximum number of results requested. It is
                 // possible to request more results than the shown ones, and
                 // do additional filtering at the View level.
@@ -141,17 +141,17 @@ information sources. Internal providers 
 *jsm* modules with a *UrlbarProvider* name prefix. External providers can be
 registered as *Objects* through the *UrlbarProvidersManager*.
 Each provider is independent and must satisfy a base API, while internal
 implementation details may vary deeply among different providers.
 
 .. important::
 
   Providers are singleton, and must track concurrent searches internally, for
-  example mapping them by QueryContext.
+  example mapping them by UrlbarQueryContext.
 
 .. note::
 
   Internal providers can access the Places database through the
   *PlacesUtils.promiseLargeCacheDBConnection* utility.
 
 .. highlight:: JavaScript
 .. code::
@@ -177,42 +177,43 @@ class UrlbarProvider {
    * the provider.
    * @abstract
    */
   get sources() {
     throw new Error("Trying to access the base class, must be overridden");
   }
   /**
    * Starts querying.
-   * @param {object} QueryContext The query context object
-   * @param {function} AddCallback Callback invoked by the provider to add a new
+   * @param {UrlbarQueryContext} queryContext The query context object
+   * @param {function} addCallback Callback invoked by the provider to add a new
    *        match. A UrlbarMatch should be passed to it.
    * @note Extended classes should return a Promise resolved when the provider
    *       is done searching AND returning matches.
    * @abstract
    */
-  startQuery(QueryContext, AddCallback) {
+  startQuery(queryContext, addCallback) {
     throw new Error("Trying to access the base class, must be overridden");
   }
   /**
    * Cancels a running query,
-   * @param {object} QueryContext the QueryContext object to cancel query for.
+   * @param {UrlbarQueryContext} queryContext The query context object to cancel
+   *        query for.
    * @abstract
    */
-  cancelQuery(QueryContext) {
+  cancelQuery(queryContext) {
     throw new Error("Trying to access the base class, must be overridden");
   }
 }
 
 UrlbarMuxer
 -----------
 
 The *Muxer* is responsible for sorting matches based on their importance and
-additional rules that depend on the QueryContext. The muxer to use is indicated
-by the QueryContext.muxer property.
+additional rules that depend on the UrlbarQueryContext. The muxer to use is
+indicated by the UrlbarQueryContext.muxer property.
 
 .. caution::
 
   The Muxer is a replaceable component, as such what is described here is a
   reference for the default View, but may not be valid for other implementations.
 
 .. highlight:: JavaScript
 .. code::
@@ -222,21 +223,21 @@ class UrlbarMuxer {
    * Unique name for the muxer, used by the context to sort matches.
    * Not using a unique name will cause the newest registration to win.
    * @abstract
    */
   get name() {
     return "UrlbarMuxerBase";
   }
   /**
-   * Sorts QueryContext matches in-place.
-   * @param {object} QueryContext the context to sort matches for.
+   * Sorts UrlbarQueryContext matches in-place.
+   * @param {UrlbarQueryContext} queryContext the context to sort matches for.
    * @abstract
    */
-  sort(QueryContext) {
+  sort(queryContext) {
     throw new Error("Trying to access the base class, must be overridden");
   }
 }
 
 
 The Controller
 ==============
 
@@ -248,17 +249,17 @@ View (e.g. showing/hiding a panel). It i
 .. note::
 
   Each *View* has a different *Controller* instance.
 
 .. highlight:: JavaScript
 .. code::
 
   UrlbarController {
-    async startQuery(QueryContext);
+    async startQuery(queryContext);
     cancelQuery(queryContext);
     // Invoked by the ProvidersManager when matches are available.
     receiveResults(queryContext);
     // Used by the View to listen for matches.
     addQueryListener(listener);
     removeQueryListener(listener);
     // Used to indicate the View context changed, as such any cached information
     // should be reset.
--- a/browser/installer/windows/Makefile.in
+++ b/browser/installer/windows/Makefile.in
@@ -1,16 +1,20 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 include $(topsrcdir)/toolkit/mozapps/installer/package-name.mk
 
 CONFIG_DIR = instgen
-SFX_MODULE = $(topsrcdir)/other-licenses/7zstub/firefox/7zSD.sfx
+ifeq ($(CPU_ARCH), aarch64)
+SFX_MODULE = $(topsrcdir)/other-licenses/7zstub/firefox/7zSD.ARM64.sfx
+else
+SFX_MODULE = $(topsrcdir)/other-licenses/7zstub/firefox/7zSD.Win32.sfx
+endif
 
 INSTALLER_FILES = \
 	app.tag \
 	nsis/installer.nsi \
 	nsis/uninstaller.nsi \
 	nsis/stub.nsi \
 	nsis/shared.nsh \
 	stub.tag \
--- a/browser/locales/Makefile.in
+++ b/browser/locales/Makefile.in
@@ -32,17 +32,21 @@ endif
 
 ifneq (,$(filter cocoa,$(MOZ_WIDGET_TOOLKIT)))
 MOZ_PKG_MAC_DSSTORE=$(ABS_DIST)/branding/dsstore
 MOZ_PKG_MAC_BACKGROUND=$(ABS_DIST)/branding/background.png
 MOZ_PKG_MAC_ICON=$(ABS_DIST)/branding/disk.icns
 MOZ_PKG_MAC_EXTRA=--symlink '/Applications:/ '
 endif
 
-MOZ_SFX_PACKAGE=$(topsrcdir)/other-licenses/7zstub/firefox/7zSD.sfx
+ifeq ($(CPU_ARCH), aarch64)
+MOZ_SFX_PACKAGE=$(topsrcdir)/other-licenses/7zstub/firefox/7zSD.ARM64.sfx
+else
+MOZ_SFX_PACKAGE=$(topsrcdir)/other-licenses/7zstub/firefox/7zSD.Win32.sfx
+endif
 
 # Required for l10n.mk - defines a list of app sub dirs that should
 # be included in langpack xpis.
 DIST_SUBDIRS = $(DIST_SUBDIR)
 
 include $(topsrcdir)/config/rules.mk
 
 include $(topsrcdir)/toolkit/locales/l10n.mk
--- a/browser/modules/SitePermissions.jsm
+++ b/browser/modules/SitePermissions.jsm
@@ -701,16 +701,17 @@ var gPermissionObject = {
       if (state == Ci.nsIAutoplay.ALLOWED) {
         return SitePermissions.ALLOW;
       } else if (state == Ci.nsIAutoplay.BLOCKED) {
         return SitePermissions.BLOCK;
       }
       return SitePermissions.UNKNOWN;
     },
     labelID: "autoplay-media",
+    states: [ SitePermissions.ALLOW, SitePermissions.BLOCK ],
   },
 
   "image": {
     states: [ SitePermissions.ALLOW, SitePermissions.BLOCK ],
   },
 
   "cookie": {
     states: [ SitePermissions.ALLOW, SitePermissions.ALLOW_COOKIES_FOR_SESSION, SitePermissions.BLOCK ],
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -920,17 +920,16 @@ panelview .toolbarbutton-1,
 .subviewbutton > .menu-accel-container > .menu-iconic-accel,
 .subviewbutton > .menu-accel-container > .menu-accel {
   margin-inline-end: 0 !important; /* to override menu.css on Windows */
 }
 
 .subviewbutton[shortcut]::after {
   content: attr(shortcut);
   float: right;
-  color: var(--panel-disabled-color);
 }
 
 .PanelUI-subView .subviewbutton-nav::after {
   -moz-context-properties: fill, fill-opacity;
   content: url(chrome://browser/skin/back-12.svg);
   fill: currentColor;
   fill-opacity: 0.6;
   float: right;
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -80,16 +80,18 @@
   skin/classic/browser/notification-icons/plugin-badge.svg                  (../shared/notification-icons/plugin-badge.svg)
   skin/classic/browser/notification-icons/popup.svg                         (../shared/notification-icons/popup.svg)
   skin/classic/browser/notification-icons/popup-subitem.svg                 (../shared/notification-icons/popup-subitem.svg)
   skin/classic/browser/notification-icons/screen-blocked.svg                (../shared/notification-icons/screen-blocked.svg)
   skin/classic/browser/notification-icons/screen.svg                        (../shared/notification-icons/screen.svg)
   skin/classic/browser/notification-icons/update.svg                        (../shared/notification-icons/update.svg)
   skin/classic/browser/notification-icons/midi.svg                          (../shared/notification-icons/midi.svg)
   skin/classic/browser/notification-icons/webauthn.svg                      (../shared/notification-icons/webauthn.svg)
+  skin/classic/browser/notification-icons/images.svg                        (../shared/notification-icons/images.svg)
+  skin/classic/browser/notification-icons/images-blocked.svg                (../shared/notification-icons/images-blocked.svg)
 
   skin/classic/browser/tracking-protection.svg                 (../shared/identity-block/tracking-protection.svg)
   skin/classic/browser/tracking-protection-disabled.svg        (../shared/identity-block/tracking-protection-disabled.svg)
   skin/classic/browser/tracking-protection-animation.svg        (../shared/identity-block/tracking-protection-animation.svg)
   skin/classic/browser/tracking-protection-animation-brighttext.svg (../shared/identity-block/tracking-protection-animation-brighttext.svg)
   skin/classic/browser/panel-icon-arrow-left.svg               (../shared/panel-icon-arrow-left.svg)
   skin/classic/browser/panel-icon-arrow-right.svg              (../shared/panel-icon-arrow-right.svg)
   skin/classic/browser/panel-icon-cancel.svg                   (../shared/panel-icon-cancel.svg)
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -70,16 +70,24 @@
 .popup-notification-icon[popupid="autoplay-media"] {
   list-style-image: url(chrome://browser/skin/notification-icons/autoplay-media-detailed.svg);
 }
 
 .autoplay-media-icon.blocked-permission-icon {
   list-style-image: url(chrome://browser/skin/notification-icons/autoplay-media-blocked.svg);
 }
 
+.image-icon {
+  list-style-image: url(chrome://browser/skin/notification-icons/images.svg);
+}
+
+.image-icon.blocked-permission-icon {
+  list-style-image: url(chrome://browser/skin/notification-icons/images-blocked.svg);
+}
+
 .storage-access-notification-content {
   color: var(--panel-disabled-color);
   font-style: italic;
   margin-top: 15px;
 }
 
 .storage-access-notification-content .text-link {
   color: -moz-nativehyperlinktext;
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/notification-icons/images-blocked.svg
@@ -0,0 +1,11 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill-opacity="context-fill-opacity" fill="context-fill">
+  <path  d="M2 15a1 1 0 0 1-.71-.29 1 1 0 0 1 0-1.42l12-12a1 1 0 1 1 1.42 1.42l-12 12A1 1 0 0 1 2 15z"/>
+  <path  d="M14 5v7a1 1 0 0 1-1 1H6l-2 2h9a3 3 0 0 0 3-3V4a3 3 0 0 0-.14-.86z"/>
+  <path  d="M3 13a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10l1.55-1.55A2.93 2.93 0 0 0 13 1H3a3 3 0 0 0-3 3v8a3 3 0 0 0 1.45 2.55z"/>
+  <path  d="M11.64 7.36L7 12h6V9.18a.47.47 0 0 0-.12-.32z"/>
+  <path  d="M7.29 8.71l-1.52-.6a.5.5 0 0 0-.5.07L3.19 9.85a.49.49 0 0 0-.19.39V12h1z"/>
+  <circle  cx="5" cy="6" r="1"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/notification-icons/images.svg
@@ -0,0 +1,8 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill-opacity="context-fill-opacity" fill="context-fill">
+  <path d="M13 1H3a3 3 0 0 0-3 3v8a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V4a3 3 0 0 0-3-3zm1 11a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1z"/>
+  <path d="M3.19 9.85l2.08-1.67a.5.5 0 0 1 .5-.07l1.89.76a.5.5 0 0 0 .57-.15l1.89-2.26a.49.49 0 0 1 .76 0l2 2.4a.47.47 0 0 1 .12.32V12H3v-1.76a.49.49 0 0 1 .19-.39z"/>
+  <circle cx="5" cy="6" r="1"/>
+</svg>
--- a/build/macosx/cross-mozconfig.common
+++ b/build/macosx/cross-mozconfig.common
@@ -18,16 +18,18 @@ mk_add_options "export LD_LIBRARY_PATH=$
 #   tar -C $(dirname ${sdk_path}) -cHjf /tmp/$(basename ${sdk_path}).tar.bz2 $(basename ${sdk_path})
 # Upload the resulting tarball from /tmp to tooltool, and change the entry in
 # `browser/config/tooltool-manifests/macosx64/cross-releng.manifest`.
 CROSS_SYSROOT=$topsrcdir/MacOSX10.11.sdk
 CROSS_PRIVATE_FRAMEWORKS=$CROSS_SYSROOT/System/Library/PrivateFrameworks
 
 export CC="$topsrcdir/clang/bin/clang"
 export CXX="$topsrcdir/clang/bin/clang++"
+export CFLAGS="$CFLAGS -fcrash-diagnostics-dir=${UPLOAD_PATH}"
+export CXXFLAGS="$CXXFLAGS -fcrash-diagnostics-dir=${UPLOAD_PATH}"
 export LLVMCONFIG=$topsrcdir/clang/bin/llvm-config
 export BINDGEN_CFLAGS="-isysroot $CROSS_SYSROOT"
 export DSYMUTIL=$topsrcdir/build/macosx/llvm-dsymutil
 mk_add_options "export REAL_DSYMUTIL=$topsrcdir/llvm-dsymutil/bin/dsymutil"
 export MKFSHFS=$topsrcdir/hfsplus-tools/newfs_hfs
 export DMG_TOOL=$topsrcdir/dmg/dmg
 export HFS_TOOL=$topsrcdir/dmg/hfsplus
 
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -1166,18 +1166,18 @@ def compiler(language, host_or_target, c
 
     # Set CC_TYPE/CC_VERSION/HOST_CC_TYPE/HOST_CC_VERSION to allow
     # old-configure to do some of its still existing checks.
     if language == 'C':
         set_config(
             '%s_TYPE' % var, valid_compiler.type)
         add_old_configure_assignment(
             '%s_TYPE' % var, valid_compiler.type)
-        add_old_configure_assignment(
-            '%s_VERSION' % var, valid_compiler.version)
+        set_config(
+            '%s_VERSION' % var, depends(valid_compiler.version)(lambda v: str(v)))
 
     valid_compiler = compiler_class(valid_compiler, host_or_target)
 
     def compiler_error():
         raise FatalCheckError('Failed compiling a simple %s source with %s'
                               % (language, what))
 
     valid_compiler.try_compile(check_msg='%s works' % what,
--- a/build/mozconfig.clang-cl
+++ b/build/mozconfig.clang-cl
@@ -8,8 +8,14 @@ if test -d "$topsrcdir/clang/bin"; then
         export LIB=$LIB:$CLANG_LIB_DIR
         mk_export_correct_style LIB
     fi
 fi
 
 export CC=clang-cl
 export CXX=clang-cl
 export ENABLE_CLANG_PLUGIN=1
+
+if [ -n "$UPLOAD_PATH" ]; then
+    DIAGNOSTICS_DIR="$(cmd.exe //e:on //c if not exist ${UPLOAD_PATH} mkdir ${UPLOAD_PATH} && cd ${UPLOAD_PATH} && pwd)"
+    export CFLAGS="$CFLAGS -fcrash-diagnostics-dir=${DIAGNOSTICS_DIR}"
+    export CXXFLAGS="$CXXFLAGS -fcrash-diagnostics-dir=${DIAGNOSTICS_DIR}"
+fi
--- a/build/unix/mozconfig.unix
+++ b/build/unix/mozconfig.unix
@@ -8,16 +8,18 @@ if [ -n "$FORCE_GCC" ]; then
 
     # We want to make sure we use binutils and other binaries in the tooltool
     # package.
     mk_add_options "export PATH=$TOOLTOOL_DIR/gcc/bin:$PATH"
 else
     CC="$TOOLTOOL_DIR/clang/bin/clang"
     CXX="$TOOLTOOL_DIR/clang/bin/clang++"
     export ENABLE_CLANG_PLUGIN=1
+    export CFLAGS="$CFLAGS -fcrash-diagnostics-dir=${UPLOAD_PATH}"
+    export CXXFLAGS="$CXXFLAGS -fcrash-diagnostics-dir=${UPLOAD_PATH}"
 
     if [ -n "$MOZ_PGO" ]; then
         if [ -z "$USE_ARTIFACT" ]; then
             ac_add_options --enable-lto
         fi
         export LLVM_PROFDATA="$TOOLTOOL_DIR/clang/bin/llvm-profdata"
         export AR="$topsrcdir/clang/bin/llvm-ar"
         export NM="$topsrcdir/clang/bin/llvm-nm"
--- a/config/config.mk
+++ b/config/config.mk
@@ -101,23 +101,23 @@ ACDEFINES += -DXPI_NAME=$(XPI_NAME)
 endif
 
 # The VERSION_NUMBER is suffixed onto the end of the DLLs we ship.
 VERSION_NUMBER		= 50
 
 CONFIG_TOOLS	= $(MOZ_BUILD_ROOT)/config
 AUTOCONF_TOOLS	= $(MOZILLA_DIR)/build/autoconf
 
-ifdef _MSC_VER
+ifeq (msvc,$(CC_TYPE))
 # clang-cl is smart enough to generate dependencies directly.
-ifeq (,$(CLANG_CL)$(MOZ_USING_SCCACHE))
+ifeq (,$(MOZ_USING_SCCACHE))
 CC_WRAPPER ?= $(call py_action,cl)
 CXX_WRAPPER ?= $(call py_action,cl)
-endif # CLANG_CL/MOZ_USING_SCCACHE
-endif # _MSC_VER
+endif # MOZ_USING_SCCACHE
+endif # CC_TYPE
 
 CC := $(CC_WRAPPER) $(CC)
 CXX := $(CXX_WRAPPER) $(CXX)
 MKDIR ?= mkdir
 SLEEP ?= sleep
 TOUCH ?= touch
 
 PYTHON_PATH = $(PYTHON) $(topsrcdir)/config/pythonpath.py
@@ -295,17 +295,17 @@ else # MOZ_WINCONSOLE
 WIN32_EXE_LDFLAGS	+= $(WIN32_GUI_EXE_LDFLAGS)
 endif
 else
 # For setting subsystem version
 WIN32_EXE_LDFLAGS	+= $(WIN32_CONSOLE_EXE_LDFLAGS)
 endif
 endif # WINNT
 
-ifdef _MSC_VER
+ifneq (,$(filter msvc clang-cl,$(CC_TYPE)))
 ifeq ($(CPU_ARCH),x86_64)
 # Normal operation on 64-bit Windows needs 2 MB of stack. (Bug 582910)
 # ASAN requires 6 MB of stack.
 # Setting the stack to 8 MB to match the capability of other systems
 # to deal with frame construction for unreasonably deep DOM trees
 # with worst-case styling. This uses address space unnecessarily for
 # non-main threads, but that should be tolerable on 64-bit systems.
 # (Bug 256180)
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -669,17 +669,17 @@ ifeq ($(OS_ARCH),WINNT)
 # have run before considering other targets that depend on the import library.
 # See bug 795204.
 $(IMPORT_LIBRARY): $(SHARED_LIBRARY) ;
 endif
 
 $(HOST_SHARED_LIBRARY): Makefile
 	$(REPORT_BUILD)
 	$(RM) $@
-ifdef _MSC_VER
+ifneq (,$(filter msvc clang-cl,$(HOST_CC_TYPE)))
 	$(HOST_LINKER) -NOLOGO -DLL -OUT:$@ $($(notdir $@)_OBJS) $(HOST_CXX_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LINKER_LIBPATHS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 else
 	$(HOST_CXX) $(HOST_OUTOPTION)$@ $($(notdir $@)_OBJS) $(HOST_CXX_LDFLAGS) $(HOST_LDFLAGS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
 endif
 
 # On Darwin (Mac OS X), dwarf2 debugging uses debug info left in .o files,
 # so instead of deleting .o files after repacking them into a dylib, we make
 # symlinks back to the originals. The symlinks are a no-op for stabs debugging,
--- a/devtools/client/shared/test/browser_dbg_target-scoped-actor-01.js
+++ b/devtools/client/shared/test/browser_dbg_target-scoped-actor-01.js
@@ -5,17 +5,17 @@
 
 "use strict";
 
 /**
  * Check target-scoped actor lifetimes.
  */
 
 const ACTORS_URL = EXAMPLE_URL + "testactors.js";
-const TAB_URL = EXAMPLE_URL + "doc_empty-tab-01.html";
+const TAB_URL = TEST_URI_ROOT + "doc_empty-tab-01.html";
 
 add_task(async function test() {
   const tab = await addTab(TAB_URL);
 
   await registerActorInContentProcess(ACTORS_URL, {
     prefix: "testOne",
     constructor: "TestActor1",
     type: { target: true },
--- a/devtools/client/shared/test/browser_dbg_target-scoped-actor-02.js
+++ b/devtools/client/shared/test/browser_dbg_target-scoped-actor-02.js
@@ -5,17 +5,17 @@
 
 "use strict";
 
 /**
  * Check target-scoped actor lifetimes.
  */
 
 const ACTORS_URL = EXAMPLE_URL + "testactors.js";
-const TAB_URL = EXAMPLE_URL + "doc_empty-tab-01.html";
+const TAB_URL = TEST_URI_ROOT + "doc_empty-tab-01.html";
 
 add_task(async function() {
   const tab = await addTab(TAB_URL);
 
   await registerActorInContentProcess(ACTORS_URL, {
     prefix: "testOne",
     constructor: "TestActor1",
     type: { target: true },
rename from devtools/server/tests/mochitest/animation-data.html
rename to devtools/server/tests/browser/animation-data.html
--- a/devtools/server/tests/mochitest/animation-data.html
+++ b/devtools/server/tests/browser/animation-data.html
@@ -95,23 +95,16 @@
     }
 
     @keyframes no-compositor {
       100% {
         margin-right: 600px;
       }
     }
   </style>
-  <script type="text/javascript">
-    "use strict";
-
-    window.onload = function() {
-      window.opener.postMessage("ready", "*");
-    };
-  </script>
 </head>
 </body>
   <div class="ball still"></div>
   <div class="ball animated"></div>
   <div class="ball multi"></div>
   <div class="ball delayed"></div>
   <div class="ball multi-finite"></div>
   <div class="ball short"></div>
--- a/devtools/server/tests/browser/browser.ini
+++ b/devtools/server/tests/browser/browser.ini
@@ -1,50 +1,55 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   head.js
   animation.html
+  animation-data.html
   doc_accessibility_infobar.html
   doc_accessibility.html
   doc_allocations.html
   doc_force_cc.html
   doc_force_gc.html
   doc_innerHTML.html
   doc_perf.html
   doc_promise-get-allocation-stack.html
   doc_promise-get-fulfillment-stack.html
   doc_promise-get-rejection-stack.html
   error-actor.js
   grid.html
   inspectedwindow-reload-target.sjs
+  inspector-search-data.html
+  inspector-traversal-data.html
   inspector-shadow.html
   navigate-first.html
   navigate-second.html
   storage-cookies-same-name.html
   storage-dynamic-windows.html
   storage-listings.html
   storage-unsecured-iframe.html
   storage-updates.html
   storage-secured-iframe.html
   stylesheets-nested-iframes.html
   test-spawn-actor-in-parent.js
+  inspector-helpers.js
   storage-helpers.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/server/tests/mochitest/hello-actor.js
 
 [browser_accessibility_highlighter_infobar.js]
 [browser_accessibility_infobar_show.js]
 [browser_accessibility_node.js]
 [browser_accessibility_node_events.js]
 [browser_accessibility_simple.js]
 [browser_accessibility_walker.js]
 [browser_actor_error.js]
+[browser_animation_actor-lifetime.js]
 [browser_animation_emitMutations.js]
 [browser_animation_getProperties.js]
 [browser_animation_getMultipleStates.js]
 [browser_animation_getPlayers.js]
 [browser_animation_getStateAfterFinished.js]
 [browser_animation_getSubTreeAnimations.js]
 [browser_animation_keepFinished.js]
 [browser_animation_playerState.js]
@@ -62,25 +67,35 @@ skip-if = e10s # Bug 1183605 - devtools/
 [browser_canvasframe_helper_03.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_canvasframe_helper_04.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_canvasframe_helper_05.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_canvasframe_helper_06.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
+[browser_inspector-anonymous.js]
+[browser_inspector-insert.js]
+[browser_inspector-mutations-childlist.js]
+[browser_inspector-mutations-frameload.js]
+[browser_inspector-release.js]
+[browser_inspector-remove.js]
+[browser_inspector-retain.js]
+[browser_inspector-search.js]
 [browser_inspector-shadow.js]
+[browser_inspector-traversal.js]
 [browser_layout_getGrids.js]
 [browser_layout_simple.js]
 [browser_markers-cycle-collection.js]
 [browser_markers-gc.js]
 [browser_markers-minor-gc.js]
 [browser_markers-parse-html.js]
 [browser_markers-styles.js]
 [browser_markers-timestamp.js]
+[browser_memory_allocations_01.js]
 [browser_navigateEvents.js]
 [browser_perf-01.js]
 [browser_perf-02.js]
 [browser_perf-03.js]
 [browser_perf-04.js]
 [browser_perf-allocation-data.js]
 [browser_perf-profiler-01.js]
 [browser_perf-profiler-02.js]
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_actor-lifetime.js
@@ -0,0 +1,65 @@
+/* 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/ */
+
+"use strict";
+
+// Test for Bug 1247243
+
+add_task(async function setup() {
+  info("Setting up inspector and animation actors.");
+  const { animations, walker } =
+    await initAnimationsFrontForUrl(MAIN_DOMAIN + "animation-data.html");
+
+  info("Testing animated node actor");
+  const animatedNodeActor = await walker.querySelector(walker.rootNode,
+    ".animated");
+  await animations.getAnimationPlayersForNode(animatedNodeActor);
+
+  await assertNumberOfAnimationActors(1, "AnimationActor have 1 AnimationPlayerActors");
+
+  info("Testing AnimationPlayerActors release");
+  const stillNodeActor = await walker.querySelector(walker.rootNode,
+    ".still");
+  await animations.getAnimationPlayersForNode(stillNodeActor);
+  await assertNumberOfAnimationActors(0,
+    "AnimationActor does not have any AnimationPlayerActors anymore");
+
+  info("Testing multi animated node actor");
+  const multiNodeActor = await walker.querySelector(walker.rootNode,
+    ".multi");
+  await animations.getAnimationPlayersForNode(multiNodeActor);
+  await assertNumberOfAnimationActors(2,
+    "AnimationActor has now 2 AnimationPlayerActors");
+
+  info("Testing single animated node actor");
+  await animations.getAnimationPlayersForNode(animatedNodeActor);
+  await assertNumberOfAnimationActors(1,
+    "AnimationActor has only one AnimationPlayerActors");
+
+  info("Testing AnimationPlayerActors release again");
+  await animations.getAnimationPlayersForNode(stillNodeActor);
+  await assertNumberOfAnimationActors(0,
+    "AnimationActor does not have any AnimationPlayerActors anymore");
+
+  async function assertNumberOfAnimationActors(expected, message) {
+    const actors = await ContentTask.spawn(
+      gBrowser.selectedBrowser,
+      [animations.actorID],
+      function(actorID) {
+        const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
+        const { DebuggerServer } = require("devtools/server/main");
+        // Convert actorID to current compartment string otherwise
+        // searchAllConnectionsForActor is confused and won't find the actor.
+        actorID = String(actorID);
+        const animationActors = DebuggerServer
+          .searchAllConnectionsForActor(actorID);
+        if (!animationActors) {
+          return 0;
+        }
+        return animationActors.actors.length;
+      }
+    );
+    is(actors, expected, message);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_inspector-anonymous.js
@@ -0,0 +1,174 @@
+/* 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/ */
+
+"use strict";
+
+// Test for Bug 777674
+
+add_task(async function() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+
+  await testXBLAnonymousInHTMLDocument(walker);
+  await testNativeAnonymous(walker);
+  await testNativeAnonymousStartingNode(walker);
+
+  await testPseudoElements(walker);
+  await testEmptyWithPseudo(walker);
+  await testShadowAnonymous(walker);
+});
+
+async function testXBLAnonymousInHTMLDocument(walker) {
+  info("Testing XBL anonymous in an HTML document.");
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+    const rawToolbarbutton = content.document.createElementNS(XUL_NS, "toolbarbutton");
+    content.document.documentElement.appendChild(rawToolbarbutton);
+  });
+
+  const toolbarbutton = await walker.querySelector(walker.rootNode, "toolbarbutton");
+  const children = await walker.children(toolbarbutton);
+
+  is(toolbarbutton.numChildren, 0, "XBL content is not visible in HTML doc");
+  is(children.nodes.length, 0, "XBL content is not returned in HTML doc");
+}
+
+async function testNativeAnonymous(walker) {
+  info("Testing native anonymous content with walker.");
+
+  const select = await walker.querySelector(walker.rootNode, "select");
+  const children = await walker.children(select);
+
+  is(select.numChildren, 2, "No native anon content for form control");
+  is(children.nodes.length, 2, "No native anon content for form control");
+}
+
+async function testNativeAnonymousStartingNode(walker) {
+  info("Tests attaching an element that a walker can't see.");
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, [walker.actorID],
+    async function(actorID) {
+      const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
+      const { DebuggerServer } = require("devtools/server/main");
+
+      const {DocumentWalker} =
+        require("devtools/server/actors/inspector/document-walker");
+      const nodeFilterConstants =
+        require("devtools/shared/dom-node-filter-constants");
+
+      const docwalker = new DocumentWalker(
+        content.document.querySelector("select"),
+        content,
+        {
+          whatToShow: nodeFilterConstants.SHOW_ALL,
+          filter: () => {
+            return nodeFilterConstants.FILTER_ACCEPT;
+          },
+        }
+      );
+      const scrollbar = docwalker.lastChild();
+      is(scrollbar.tagName, "scrollbar", "An anonymous child has been fetched");
+
+      // Convert actorID to current compartment string otherwise
+      // searchAllConnectionsForActor is confused and won't find the actor.
+      actorID = String(actorID);
+      const serverWalker = DebuggerServer.searchAllConnectionsForActor(actorID);
+      const node = await serverWalker.attachElement(scrollbar);
+
+      ok(node, "A response has arrived");
+      ok(node.node, "A node is in the response");
+      is(node.node.rawNode.tagName, "SELECT",
+        "The node has changed to a parent that the walker recognizes");
+    });
+}
+
+async function testPseudoElements(walker) {
+  info("Testing pseudo elements with walker.");
+
+  // Markup looks like: <div><::before /><span /><::after /></div>
+  const pseudo = await walker.querySelector(walker.rootNode, "#pseudo");
+  const children = await walker.children(pseudo);
+
+  is(pseudo.numChildren, 1, "::before/::after are not counted if there is a child");
+  is(children.nodes.length, 3, "Correct number of children");
+
+  const before = children.nodes[0];
+  ok(before.isAnonymous, "Child is anonymous");
+  ok(!before._form.isXBLAnonymous, "Child is not XBL anonymous");
+  ok(!before._form.isShadowAnonymous, "Child is not shadow anonymous");
+  ok(before._form.isNativeAnonymous, "Child is native anonymous");
+
+  const span = children.nodes[1];
+  ok(!span.isAnonymous, "Child is not anonymous");
+
+  const after = children.nodes[2];
+  ok(after.isAnonymous, "Child is anonymous");
+  ok(!after._form.isXBLAnonymous, "Child is not XBL anonymous");
+  ok(!after._form.isShadowAnonymous, "Child is not shadow anonymous");
+  ok(after._form.isNativeAnonymous, "Child is native anonymous");
+}
+
+async function testEmptyWithPseudo(walker) {
+  info("Testing elements with no childrent, except for pseudos.");
+
+  info("Checking an element whose only child is a pseudo element");
+  const pseudo = await walker.querySelector(walker.rootNode, "#pseudo-empty");
+  const children = await walker.children(pseudo);
+
+  is(pseudo.numChildren, 1,
+     "::before/::after are is counted if there are no other children");
+  is(children.nodes.length, 1, "Correct number of children");
+
+  const before = children.nodes[0];
+  ok(before.isAnonymous, "Child is anonymous");
+  ok(!before._form.isXBLAnonymous, "Child is not XBL anonymous");
+  ok(!before._form.isShadowAnonymous, "Child is not shadow anonymous");
+  ok(before._form.isNativeAnonymous, "Child is native anonymous");
+}
+
+async function testShadowAnonymous(walker) {
+  if (true) {
+    // FIXME(bug 1465114)
+    return;
+  }
+
+  info("Testing shadow DOM content.");
+
+  const shadow = await walker.querySelector(walker.rootNode, "#shadow");
+  const children = await walker.children(shadow);
+
+  is(shadow.numChildren, 3, "Children of the shadow root are counted");
+  is(children.nodes.length, 3, "Children returned from walker");
+
+  const before = children.nodes[0];
+  ok(before.isAnonymous, "Child is anonymous");
+  ok(!before._form.isXBLAnonymous, "Child is not XBL anonymous");
+  ok(!before._form.isShadowAnonymous, "Child is not shadow anonymous");
+  ok(before._form.isNativeAnonymous, "Child is native anonymous");
+
+  // <h3>Shadow <em>DOM</em></h3>
+  const shadowChild1 = children.nodes[1];
+  ok(shadowChild1.isAnonymous, "Child is anonymous");
+  ok(!shadowChild1._form.isXBLAnonymous, "Child is not XBL anonymous");
+  ok(shadowChild1._form.isShadowAnonymous, "Child is shadow anonymous");
+  ok(!shadowChild1._form.isNativeAnonymous, "Child is not native anonymous");
+
+  const shadowSubChildren = await walker.children(children.nodes[1]);
+  is(shadowChild1.numChildren, 2, "Subchildren of the shadow root are counted");
+  is(shadowSubChildren.nodes.length, 2, "Subchildren are returned from walker");
+
+  // <em>DOM</em>
+  const shadowSubChild = children.nodes[1];
+  ok(shadowSubChild.isAnonymous, "Child is anonymous");
+  ok(!shadowSubChild._form.isXBLAnonymous, "Child is not XBL anonymous");
+  ok(shadowSubChild._form.isShadowAnonymous, "Child is shadow anonymous");
+  ok(!shadowSubChild._form.isNativeAnonymous, "Child is not native anonymous");
+
+  // <select multiple></select>
+  const shadowChild2 = children.nodes[2];
+  ok(shadowChild2.isAnonymous, "Child is anonymous");
+  ok(!shadowChild2._form.isXBLAnonymous, "Child is not XBL anonymous");
+  ok(shadowChild2._form.isShadowAnonymous, "Child is shadow anonymous");
+  ok(!shadowChild2._form.isNativeAnonymous, "Child is not native anonymous");
+}
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_inspector-insert.js
@@ -0,0 +1,119 @@
+/* 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/ */
+
+"use strict";
+
+add_task(async function setup() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+
+  await testRearrange(walker);
+  await testInsertInvalidInput(walker);
+});
+
+async function testRearrange(walker) {
+  const longlist = await walker.querySelector(walker.rootNode, "#longlist");
+  let children = await walker.children(longlist);
+  const nodeA = children.nodes[0];
+  is(nodeA.id, "a", "Got the expected node.");
+
+  // Move nodeA to the end of the list.
+  await walker.insertBefore(nodeA, longlist, null);
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    ok(!content.document.querySelector("#a").nextSibling,
+       "a should now be at the end of the list.");
+  });
+
+  children = await walker.children(longlist);
+  is(nodeA, children.nodes[children.nodes.length - 1],
+     "a should now be the last returned child.");
+
+  // Now move it to the middle of the list.
+  const nextNode = children.nodes[13];
+  await walker.insertBefore(nodeA, longlist, nextNode);
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, [nextNode.actorID],
+    async function(actorID) {
+      const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
+      const { DebuggerServer } = require("devtools/server/main");
+      const {DocumentWalker} = require("devtools/server/actors/inspector/document-walker");
+      const sibling =
+        new DocumentWalker(content.document.querySelector("#a"), content).nextSibling();
+      // Convert actorID to current compartment string otherwise
+      // searchAllConnectionsForActor is confused and won't find the actor.
+      actorID = String(actorID);
+      const nodeActor = DebuggerServer.searchAllConnectionsForActor(actorID);
+      is(sibling, nodeActor.rawNode, "Node should match the expected next node.");
+    });
+
+  children = await walker.children(longlist);
+  is(nodeA, children.nodes[13], "a should be where we expect it.");
+  is(nextNode, children.nodes[14], "next node should be where we expect it.");
+}
+
+async function testInsertInvalidInput(walker) {
+  const longlist = await walker.querySelector(walker.rootNode, "#longlist");
+  const children = await walker.children(longlist);
+  const nodeA = children.nodes[0];
+  const nextSibling = children.nodes[1];
+
+  // Now move it to the original location and make sure no mutation happens.
+  await ContentTask.spawn(gBrowser.selectedBrowser, [longlist.actorID],
+    async function(actorID) {
+      const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
+      const { DebuggerServer } = require("devtools/server/main");
+      // Convert actorID to current compartment string otherwise
+      // searchAllConnectionsForActor is confused and won't find the actor.
+      actorID = String(actorID);
+      const nodeActor = DebuggerServer.searchAllConnectionsForActor(actorID);
+      content.hasMutated = false;
+      content.observer = new content.MutationObserver(() => {
+        content.hasMutated = true;
+      });
+      content.observer.observe(nodeActor.rawNode, {
+        childList: true,
+      });
+    });
+
+  await walker.insertBefore(nodeA, longlist, nodeA);
+  let hasMutated = await ContentTask.spawn(gBrowser.selectedBrowser, null,
+    async function() {
+      const state = content.hasMutated;
+      content.hasMutated = false;
+      return state;
+    });
+  ok(!hasMutated, "hasn't mutated");
+
+  await walker.insertBefore(nodeA, longlist, nextSibling);
+  hasMutated = await ContentTask.spawn(gBrowser.selectedBrowser, null,
+    async function() {
+      const state = content.hasMutated;
+      content.hasMutated = false;
+      return state;
+    });
+  ok(!hasMutated, "still hasn't mutated after inserting before nextSibling");
+
+  await walker.insertBefore(nodeA, longlist);
+  hasMutated = await ContentTask.spawn(gBrowser.selectedBrowser, null,
+    async function() {
+      const state = content.hasMutated;
+      content.hasMutated = false;
+      return state;
+    });
+  ok(hasMutated, "has mutated after inserting with null sibling");
+
+  await walker.insertBefore(nodeA, longlist);
+  hasMutated = await ContentTask.spawn(gBrowser.selectedBrowser, null,
+    async function() {
+      const state = content.hasMutated;
+      content.hasMutated = false;
+      return state;
+    });
+  ok(!hasMutated, "hasn't mutated after inserting with null sibling again");
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    content.observer.disconnect();
+  });
+}
rename from devtools/server/tests/mochitest/test_inspector-mutations-childlist.html
rename to devtools/server/tests/browser/browser_inspector-mutations-childlist.js
--- a/devtools/server/tests/mochitest/test_inspector-mutations-childlist.html
+++ b/devtools/server/tests/browser/browser_inspector-mutations-childlist.js
@@ -1,300 +1,251 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test for Bug </title>
+/* 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/ */
 
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
-  <script type="application/javascript" src="inspector-helpers.js"></script>
-  <script type="application/javascript">
 "use strict";
 
-window.onload = function() {
-  SimpleTest.waitForExplicitFinish();
-  runNextTest();
-};
-
-let gInspectee = null;
-let gWalker = null;
-
-async function setup(callback) {
-  const url = document.getElementById("inspectorContent").href;
-  const { target, doc } = await attachURL(url);
-  gInspectee = doc;
-  const inspector = await target.getInspector();
-  gWalker = inspector.walker;
-  callback();
-}
+/* import-globals-from inspector-helpers.js */
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js", this);
 
-function teardown() {
-  gWalker = null;
-  gInspectee = null;
-}
-
-function assertOwnership() {
-  return assertOwnershipTrees(gWalker);
-}
-
-function setParent(nodeSelector, newParentSelector) {
-  const node = gInspectee.querySelector(nodeSelector);
-  if (newParentSelector) {
-    const newParent = gInspectee.querySelector(newParentSelector);
-    newParent.appendChild(node);
-  } else {
-    node.remove();
-  }
-}
-
-function loadSelector(selector) {
-  return gWalker.querySelectorAll(gWalker.rootNode, selector).then(nodeList => {
+function loadSelector(walker, selector) {
+  return walker.querySelectorAll(walker.rootNode, selector).then(nodeList => {
     return nodeList.items();
   });
 }
 
-function loadSelectors(selectors) {
-  return Promise.all(Array.from(selectors, (sel) => loadSelector(sel)));
+function loadSelectors(walker, selectors) {
+  return Promise.all(Array.from(selectors, (sel) => loadSelector(walker, sel)));
 }
 
-function doMoves(moves) {
-  for (const move of moves) {
-    setParent(move[0], move[1]);
-  }
+function doMoves(movesArg) {
+  return ContentTask.spawn(gBrowser.selectedBrowser, movesArg, function(moves) {
+    function setParent(nodeSelector, newParentSelector) {
+      const node = content.document.querySelector(nodeSelector);
+      if (newParentSelector) {
+        const newParent = content.document.querySelector(newParentSelector);
+        newParent.appendChild(node);
+      } else {
+        node.remove();
+      }
+    }
+    for (const move of moves) {
+      setParent(move[0], move[1]);
+    }
+  });
 }
 
 /**
  * Test a set of tree rearrangements and make sure they cause the expected changes.
  */
 
 var gDummySerial = 0;
 
 function mutationTest(testSpec) {
-  return function() {
-    setup(() => {
-      promiseDone(loadSelectors(testSpec.load || ["html"]).then(() => {
-        gWalker.autoCleanup = !!testSpec.autoCleanup;
-        if (testSpec.preCheck) {
-          testSpec.preCheck();
-        }
-        doMoves(testSpec.moves || []);
+  return async function() {
+    const { walker } =
+      await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+    await loadSelectors(walker, testSpec.load || ["html"]);
+    walker.autoCleanup = !!testSpec.autoCleanup;
+    if (testSpec.preCheck) {
+      testSpec.preCheck();
+    }
+    const onMutations = walker.once("mutations");
 
-        // Some of these moves will trigger no mutation events,
-        // so do a dummy change to the root node to trigger
-        // a mutation event anyway.
-        gInspectee.documentElement.setAttribute("data-dummy", gDummySerial++);
+    await doMoves(testSpec.moves || []);
 
-        gWalker.once("mutations", (mutations) => {
-          // Filter out our dummy mutation.
-          // eslint-disable-next-line max-nested-callbacks
-          mutations = mutations.filter(change => {
-            if (change.type == "attributes" &&
-                change.attributeName == "data-dummy") {
-              return false;
-            }
-            return true;
-          });
-          assertOwnership();
-          if (testSpec.postCheck) {
-            testSpec.postCheck(mutations);
-          }
-          teardown();
-          runNextTest();
-        });
-      }));
+    // Some of these moves will trigger no mutation events,
+    // so do a dummy change to the root node to trigger
+    // a mutation event anyway.
+    await ContentTask.spawn(gBrowser.selectedBrowser, [gDummySerial++],
+      function(serial) {
+        content.document.documentElement.setAttribute("data-dummy", serial);
+      });
+
+    let mutations = await onMutations;
+
+    // Filter out our dummy mutation.
+    mutations = mutations.filter(change => {
+      if (change.type == "attributes" &&
+          change.attributeName == "data-dummy") {
+        return false;
+      }
+      return true;
     });
+    await assertOwnershipTrees(walker);
+    if (testSpec.postCheck) {
+      testSpec.postCheck(walker, mutations);
+    }
   };
 }
 
 // Verify that our dummy mutation works.
-addTest(mutationTest({
+add_task(mutationTest({
   autoCleanup: false,
-  postCheck: function(mutations) {
+  postCheck: function(walker, mutations) {
     is(mutations.length, 0, "Dummy mutation is filtered out.");
   },
 }));
 
 // Test a simple move to a different location in the sibling list for the same
 // parent.
-addTest(mutationTest({
+add_task(mutationTest({
   autoCleanup: false,
   load: ["#longlist div"],
   moves: [
     ["#a", "#longlist"],
   ],
-  postCheck: function(mutations) {
+  postCheck: function(walker, mutations) {
     const remove = mutations[0];
     is(remove.type, "childList", "First mutation should be a childList.");
     ok(remove.removed.length > 0, "First mutation should be a removal.");
     const add = mutations[1];
     is(add.type, "childList", "Second mutation should be a childList removal.");
     ok(add.added.length > 0, "Second mutation should be an addition.");
     const a = add.added[0];
     is(a.id, "a", "Added node should be #a");
     is(a.parentNode(), remove.target, "Should still be a child of longlist.");
     is(remove.target, add.target,
        "First and second mutations should be against the same node.");
   },
 }));
 
 // Test a move to another location that is within our ownership tree.
-addTest(mutationTest({
+add_task(mutationTest({
   autoCleanup: false,
   load: ["#longlist div", "#longlist-sibling"],
   moves: [
     ["#a", "#longlist-sibling"],
   ],
-  postCheck: function(mutations) {
+  postCheck: function(walker, mutations) {
     const remove = mutations[0];
     is(remove.type, "childList", "First mutation should be a childList.");
     ok(remove.removed.length > 0, "First mutation should be a removal.");
     const add = mutations[1];
     is(add.type, "childList", "Second mutation should be a childList removal.");
     ok(add.added.length > 0, "Second mutation should be an addition.");
     const a = add.added[0];
     is(a.id, "a", "Added node should be #a");
     is(a.parentNode(), add.target, "Should still be a child of longlist.");
     is(add.target.id, "longlist-sibling", "long-sibling should be the target.");
   },
 }));
 
 // Move an unseen node with a seen parent into our ownership tree - should generate a
 // childList pair with no adds or removes.
-addTest(mutationTest({
+add_task(mutationTest({
   autoCleanup: false,
   load: ["#longlist"],
   moves: [
     ["#longlist-sibling", "#longlist"],
   ],
-  postCheck: function(mutations) {
+  postCheck: function(walker, mutations) {
     is(mutations.length, 2, "Should generate two mutations");
     is(mutations[0].type, "childList", "Should be childList mutations.");
     is(mutations[0].added.length, 0, "Should have no adds.");
     is(mutations[0].removed.length, 0, "Should have no removes.");
     is(mutations[1].type, "childList", "Should be childList mutations.");
     is(mutations[1].added.length, 0, "Should have no adds.");
     is(mutations[1].removed.length, 0, "Should have no removes.");
   },
 }));
 
 // Move an unseen node with an unseen parent into our ownership tree.  Should only
 // generate one childList mutation with no adds or removes.
-addTest(mutationTest({
+add_task(mutationTest({
   autoCleanup: false,
   load: ["#longlist div"],
   moves: [
     ["#longlist-sibling-firstchild", "#longlist"],
   ],
-  postCheck: function(mutations) {
+  postCheck: function(walker, mutations) {
     is(mutations.length, 1, "Should generate two mutations");
     is(mutations[0].type, "childList", "Should be childList mutations.");
     is(mutations[0].added.length, 0, "Should have no adds.");
     is(mutations[0].removed.length, 0, "Should have no removes.");
   },
 }));
 
 // Move a node between unseen nodes, should generate no mutations.
-addTest(mutationTest({
+add_task(mutationTest({
   autoCleanup: false,
   load: ["html"],
   moves: [
     ["#longlist-sibling", "#longlist"],
   ],
-  postCheck: function(mutations) {
+  postCheck: function(walker, mutations) {
     is(mutations.length, 0, "Should generate no mutations.");
   },
 }));
 
 // Orphan a node and don't clean it up
-addTest(mutationTest({
+add_task(mutationTest({
   autoCleanup: false,
   load: ["#longlist div"],
   moves: [
     ["#longlist", null],
   ],
-  postCheck: function(mutations) {
+  postCheck: function(walker, mutations) {
     is(mutations.length, 1, "Should generate one mutation.");
     const change = mutations[0];
     is(change.type, "childList", "Should be a childList.");
     is(change.removed.length, 1, "Should have removed a child.");
-    const ownership = clientOwnershipTree(gWalker);
+    const ownership = clientOwnershipTree(walker);
     is(ownership.orphaned.length, 1, "Should have one orphaned subtree.");
     is(ownershipTreeSize(ownership.orphaned[0]), 1 + 26 + 26,
        "Should have orphaned longlist, and 26 children, and 26 singleTextChilds");
   },
 }));
 
 // Orphan a node, and do clean it up.
-addTest(mutationTest({
+add_task(mutationTest({
   autoCleanup: true,
   load: ["#longlist div"],
   moves: [
     ["#longlist", null],
   ],
-  postCheck: function(mutations) {
+  postCheck: function(walker, mutations) {
     is(mutations.length, 1, "Should generate one mutation.");
     const change = mutations[0];
     is(change.type, "childList", "Should be a childList.");
     is(change.removed.length, 1, "Should have removed a child.");
-    const ownership = clientOwnershipTree(gWalker);
+    const ownership = clientOwnershipTree(walker);
     is(ownership.orphaned.length, 0, "Should have no orphaned subtrees.");
   },
 }));
 
 // Orphan a node by moving it into the tree but out of our visible subtree.
-addTest(mutationTest({
+add_task(mutationTest({
   autoCleanup: false,
   load: ["#longlist div"],
   moves: [
     ["#longlist", "#longlist-sibling"],
   ],
-  postCheck: function(mutations) {
+  postCheck: function(walker, mutations) {
     is(mutations.length, 1, "Should generate one mutation.");
     const change = mutations[0];
     is(change.type, "childList", "Should be a childList.");
     is(change.removed.length, 1, "Should have removed a child.");
-    const ownership = clientOwnershipTree(gWalker);
+    const ownership = clientOwnershipTree(walker);
     is(ownership.orphaned.length, 1, "Should have one orphaned subtree.");
     is(ownershipTreeSize(ownership.orphaned[0]), 1 + 26 + 26,
        "Should have orphaned longlist, 26 children, and 26 singleTextChilds.");
   },
 }));
 
 // Orphan a node by moving it into the tree but out of our visible subtree,
 // and clean it up.
-addTest(mutationTest({
+add_task(mutationTest({
   autoCleanup: true,
   load: ["#longlist div"],
   moves: [
     ["#longlist", "#longlist-sibling"],
   ],
-  postCheck: function(mutations) {
+  postCheck: function(walker, mutations) {
     is(mutations.length, 1, "Should generate one mutation.");
     const change = mutations[0];
     is(change.type, "childList", "Should be a childList.");
     is(change.removed.length, 1, "Should have removed a child.");
-    const ownership = clientOwnershipTree(gWalker);
+    const ownership = clientOwnershipTree(walker);
     is(ownership.orphaned.length, 0, "Should have no orphaned subtrees.");
   },
 }));
-
-addTest(function cleanup() {
-  gInspectee = null;
-  gWalker = null;
-  runNextTest();
-});
-  </script>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
-<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-</pre>
-</body>
-</html>
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_inspector-mutations-frameload.js
@@ -0,0 +1,151 @@
+/* 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/ */
+
+"use strict";
+
+/* import-globals-from inspector-helpers.js */
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js", this);
+
+async function loadChildSelector(walker, selector) {
+  const frame = await walker.querySelector(walker.rootNode, "#childFrame");
+  ok(frame.numChildren > 0,
+     "Child frame should consider its loaded document as a child.");
+  const children = await walker.children(frame);
+  const nodeList = await walker.querySelectorAll(children.nodes[0], selector);
+  return nodeList.items();
+}
+
+function getUnloadedDoc(mutations) {
+  for (const change of mutations) {
+    if (isUnload(change)) {
+      return change.target;
+    }
+  }
+  return null;
+}
+
+add_task(async function loadNewChild() {
+  const { target, walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+  // Load a bunch of fronts for actors inside the child frame.
+  await loadChildSelector(walker, "#longlist div");
+  const onMutations = waitForMutation(walker, isChildList);
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    const childFrame = content.document.querySelector("#childFrame");
+    childFrame.src = "data:text/html,<html>new child</html>";
+  });
+  let mutations = await onMutations;
+  const unloaded = getUnloadedDoc(mutations);
+  mutations = assertSrcChange(mutations);
+  mutations = assertUnload(mutations);
+  mutations = assertFrameLoad(mutations);
+  mutations = assertChildList(mutations);
+
+  is(mutations.length, 0, "Got the expected mutations.");
+
+  assertOwnershipTrees(walker);
+
+  return checkMissing(target, unloaded);
+});
+
+add_task(async function loadNewChild() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+  // Load a bunch of fronts for actors inside the child frame.
+  await loadChildSelector(walker, "#longlist div");
+  let onMutations = waitForMutation(walker, isChildList);
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    const childFrame = content.document.querySelector("#childFrame");
+    childFrame.src = "data:text/html,<html>new child</html>";
+  });
+  await onMutations;
+
+  onMutations = waitForMutation(walker, isChildList);
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    // The first load went through as expected (as tested in loadNewChild)
+    // Now change the source again, but this time we *don't* expect
+    // an unload, because we haven't seen the new child document yet.
+    const childFrame = content.document.querySelector("#childFrame");
+    childFrame.src = "data:text/html,<html>second new child</html>";
+  });
+  let mutations = await onMutations;
+  mutations = assertSrcChange(mutations);
+  mutations = assertFrameLoad(mutations);
+  mutations = assertChildList(mutations);
+  ok(!getUnloadedDoc(mutations), "Should not have gotten an unload.");
+
+  is(mutations.length, 0, "Got the expected mutations.");
+
+  assertOwnershipTrees(walker);
+});
+
+add_task(async function loadNewChildTwiceAndCareAboutIt() {
+  const { target, walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+  // Load a bunch of fronts for actors inside the child frame.
+  await loadChildSelector(walker, "#longlist div");
+  let onMutations = waitForMutation(walker, isChildList);
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    const childFrame = content.document.querySelector("#childFrame");
+    childFrame.src = "data:text/html,<html>new child</html>";
+  });
+  await onMutations;
+  // Read the new child
+  await loadChildSelector(walker, "#longlist div");
+
+  onMutations = waitForMutation(walker, isChildList);
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    // Now change the source again, and expect the same results as loadNewChild.
+    const childFrame = content.document.querySelector("#childFrame");
+    childFrame.src = "data:text/html,<html>second new child</html>";
+  });
+  let mutations = await onMutations;
+  const unloaded = getUnloadedDoc(mutations);
+
+  mutations = assertSrcChange(mutations);
+  mutations = assertUnload(mutations);
+  mutations = assertFrameLoad(mutations);
+  mutations = assertChildList(mutations);
+
+  is(mutations.length, 0, "Got the expected mutations.");
+
+  assertOwnershipTrees(walker);
+
+  return checkMissing(target, unloaded);
+});
+
+add_task(async function testBack() {
+  const { target, walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+  // Load a bunch of fronts for actors inside the child frame.
+  await loadChildSelector(walker, "#longlist div");
+  let onMutations = waitForMutation(walker, isChildList);
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    const childFrame = content.document.querySelector("#childFrame");
+    childFrame.src = "data:text/html,<html>new child</html>";
+  });
+  await onMutations;
+
+  // Read the new child
+  await loadChildSelector(walker, "#longlist div");
+
+  onMutations = waitForMutation(walker, isChildList);
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    // Now use history.back to change the source,
+    // and expect the same results as loadNewChild.
+    const childFrame = content.document.querySelector("#childFrame");
+    childFrame.contentWindow.history.back();
+  });
+  let mutations = await onMutations;
+  const unloaded = getUnloadedDoc(mutations);
+  mutations = assertSrcChange(mutations);
+  mutations = assertUnload(mutations);
+  mutations = assertFrameLoad(mutations);
+  mutations = assertChildList(mutations);
+  is(mutations.length, 0, "Got the expected mutations.");
+
+  assertOwnershipTrees(walker);
+
+  return checkMissing(target, unloaded);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_inspector-release.js
@@ -0,0 +1,49 @@
+/* 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/ */
+
+"use strict";
+
+/* import-globals-from inspector-helpers.js */
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js", this);
+
+add_task(async function loadNewChild() {
+  const { target, walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+
+  let originalOwnershipSize = 0;
+  let longlist = null;
+  let firstChild = null;
+  const list = await walker.querySelectorAll(walker.rootNode, "#longlist div");
+  // Make sure we have the 26 children of longlist in our ownership tree.
+  is(list.length, 26, "Expect 26 div children.");
+  // Make sure we've read in all those children and incorporated them
+  // in our ownership tree.
+  const items = await list.items();
+  originalOwnershipSize = await assertOwnershipTrees(walker);
+
+  // Here is how the ownership tree is summed up:
+  // #document                      1
+  //   <html>                       1
+  //     <body>                     1
+  //       <div id=longlist>        1
+  //         <div id=a>a</div>   26*2 (each child plus it's singleTextChild)
+  //         ...
+  //         <div id=z>z</div>
+  //                             -----
+  //                               56
+  is(originalOwnershipSize, 56, "Correct number of items in ownership tree");
+  firstChild = items[0].actorID;
+  // Now get the longlist and release it from the ownership tree.
+  const node = await walker.querySelector(walker.rootNode, "#longlist");
+  longlist = node.actorID;
+  await walker.releaseNode(node);
+  // Our ownership size should now be 53 fewer
+  // (we forgot about #longlist + 26 children + 26 singleTextChild nodes)
+  const newOwnershipSize = await assertOwnershipTrees(walker);
+  is(newOwnershipSize, originalOwnershipSize - 53,
+    "Ownership tree should be lower");
+  // Now verify that some nodes have gone away
+  await checkMissing(target, longlist);
+  await checkMissing(target, firstChild);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_inspector-remove.js
@@ -0,0 +1,80 @@
+/* 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/ */
+
+"use strict";
+
+/* import-globals-from inspector-helpers.js */
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js", this);
+
+add_task(async function testRemoveSubtree() {
+  const { target, walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    function ignoreNode(node) {
+      // Duplicate the walker logic to skip blank nodes...
+      return node.nodeType === Node.TEXT_NODE &&
+        !/[^\s]/.test(node.nodeValue);
+    }
+
+    let nextSibling = content.document.querySelector("#longlist").nextSibling;
+    while (nextSibling && ignoreNode(nextSibling)) {
+      nextSibling = nextSibling.nextSibling;
+    }
+
+    let previousSibling = content.document.querySelector("#longlist").previousSibling;
+    while (previousSibling && ignoreNode(previousSibling)) {
+      previousSibling = previousSibling.previousSibling;
+    }
+    content.nextSibling = nextSibling;
+    content.previousSibling = previousSibling;
+  });
+
+  let originalOwnershipSize = 0;
+  const longlist = await walker.querySelector(walker.rootNode, "#longlist");
+  const longlistID = longlist.actorID;
+  await walker.children(longlist);
+  originalOwnershipSize = await assertOwnershipTrees(walker);
+  // Here is how the ownership tree is summed up:
+  // #document                      1
+  //   <html>                       1
+  //     <body>                     1
+  //       <div id=longlist>        1
+  //         <div id=a>a</div>   26*2 (each child plus it's singleTextChild)
+  //         ...
+  //         <div id=z>z</div>
+  //                             -----
+  //                               56
+  is(originalOwnershipSize, 56, "Correct number of items in ownership tree");
+
+  const onMutation = waitForMutation(walker, isChildList);
+  const siblings = await walker.removeNode(longlist);
+
+  await ContentTask.spawn(gBrowser.selectedBrowser,
+    [siblings.previousSibling.actorID, siblings.nextSibling.actorID],
+    function([previousActorID, nextActorID]) {
+      const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
+      const { DebuggerServer } = require("devtools/server/main");
+
+      // Convert actorID to current compartment string otherwise
+      // searchAllConnectionsForActor is confused and won't find the actor.
+      previousActorID = String(previousActorID);
+      nextActorID = String(nextActorID);
+      const previous = DebuggerServer.searchAllConnectionsForActor(previousActorID);
+      const next = DebuggerServer.searchAllConnectionsForActor(nextActorID);
+
+      is(previous.rawNode, content.previousSibling,
+        "Should have returned the previous sibling.");
+      is(next.rawNode, content.nextSibling, "Should have returned the next sibling.");
+    });
+  await onMutation;
+  // Our ownership size should now be 51 fewer (we forgot about #longlist + 26
+  // children + 26 singleTextChild nodes, but learned about #longlist's
+  // prev/next sibling)
+  const newOwnershipSize = await assertOwnershipTrees(walker);
+  is(newOwnershipSize, originalOwnershipSize - 51,
+    "Ownership tree should be lower");
+  // Now verify that some nodes have gone away
+  return checkMissing(target, longlistID);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_inspector-retain.js
@@ -0,0 +1,125 @@
+/* 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/ */
+
+"use strict";
+
+/* import-globals-from inspector-helpers.js */
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js", this);
+
+// Retain a node, and a second-order child (in another document, for kicks)
+// Release the parent of the top item, which should cause one retained orphan.
+
+// Then unretain the top node, which should retain the orphan.
+
+// Then change the source of the iframe, which should kill that orphan.
+
+add_task(async function testRetain() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+
+  // Get the toplevel body element and retain it.
+  const bodyFront = await walker.querySelector(walker.rootNode, "body");
+  await walker.retainNode(bodyFront);
+  // Get an element in the child frame and retain it.
+  const frame = await walker.querySelector(walker.rootNode, "#childFrame");
+  const children = await walker.children(frame, { maxNodes: 1 });
+  const childDoc = children.nodes[0];
+  const childListFront = await walker.querySelector(childDoc, "#longlist");
+  const originalOwnershipSize = await assertOwnershipTrees(walker);
+  // and retain it.
+  await walker.retainNode(childListFront);
+  // OK, try releasing the parent of the first retained.
+  await walker.releaseNode(bodyFront.parentNode());
+  const clientTree = clientOwnershipTree(walker);
+
+  // That request should have freed the parent of the first retained
+  // but moved the rest into the retained orphaned tree.
+  is(ownershipTreeSize(clientTree.root) + ownershipTreeSize(clientTree.retained[0]) + 1,
+     originalOwnershipSize,
+     "Should have only lost one item overall.");
+  is(walker._retainedOrphans.size, 1, "Should have retained one orphan");
+  ok(walker._retainedOrphans.has(bodyFront),
+     "Should have retained the expected node.");
+  // Unretain the body, which should promote the childListFront to a retained orphan.
+  await walker.unretainNode(bodyFront);
+  await assertOwnershipTrees(walker);
+
+  is(walker._retainedOrphans.size, 1, "Should still only have one retained orphan.");
+  ok(!walker._retainedOrphans.has(bodyFront), "Should have dropped the body node.");
+  ok(walker._retainedOrphans.has(childListFront),
+     "Should have retained the child node.");
+
+  // Change the source of the iframe, which should kill the retained orphan.
+  const onMutations = waitForMutation(walker, isUnretained);
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    content.document.querySelector("#childFrame").src =
+      "data:text/html,<html>new child</html>";
+  });
+  await onMutations;
+
+  await assertOwnershipTrees(walker);
+  is(walker._retainedOrphans.size, 0, "Should have no more retained orphans.");
+});
+
+// Get a hold of a node, remove it from the doc and retain it at the same time.
+// We should always win that race (even though the mutation happens before the
+// retain request), because we haven't issued `getMutations` yet.
+add_task(async function testWinRace() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+
+  const front = await walker.querySelector(walker.rootNode, "#a");
+  const onMutation = waitForMutation(walker, isChildList);
+  ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    const contentNode = content.document.querySelector("#a");
+    contentNode.remove();
+  });
+  // Now wait for that mutation and retain response to come in.
+  await walker.retainNode(front);
+  await onMutation;
+
+  await assertOwnershipTrees(walker);
+  is(walker._retainedOrphans.size, 1, "Should have a retained orphan.");
+  ok(walker._retainedOrphans.has(front), "Should have retained our expected node.");
+  await walker.unretainNode(front);
+
+  // Make sure we're clear for the next test.
+  await assertOwnershipTrees(walker);
+  is(walker._retainedOrphans.size, 0, "Should have no more retained orphans.");
+});
+
+// Same as above, but issue the request right after the 'new-mutations' event, so that
+// we *lose* the race.
+add_task(async function testLoseRace() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+
+  const front = await walker.querySelector(walker.rootNode, "#z");
+  const onMutation = walker.once("new-mutations");
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    const contentNode = content.document.querySelector("#z");
+    contentNode.remove();
+  });
+  await onMutation;
+
+  // Verify that we have an outstanding request (no good way to tell that it's a
+  // getMutations request, but there's nothing else it would be).
+  is(walker._requests.length, 1, "Should have an outstanding request.");
+  try {
+    await walker.retainNode(front);
+    ok(false, "Request should not have succeeded!");
+  } catch (err) {
+    // XXX: Switched to from ok() to todo_is() in Bug 1467712. Follow up in
+    // 1500960
+    // This is throwing because of
+    // `gInspectee.querySelector("#z").parentNode = null;` two blocks above...
+    // Even if you fix that, the test is still failing because "#a" was removed
+    // by the previous test. I am switching this to "#z" because I think that
+    // was the original intent. Still not failing with the expected error message
+    // Needs more work.
+    // ok(err, "noSuchActor", "Should have lost the race.");
+    is(walker._retainedOrphans.size, 0, "Should have no more retained orphans.");
+    // Don't re-throw the error.
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_inspector-search.js
@@ -0,0 +1,242 @@
+/* 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/ */
+
+"use strict";
+
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js", this);
+
+// Test for Bug 835896
+// WalkerSearch specific tests.  This is to make sure search results are
+// coming back as expected.
+// See also test_inspector-search-front.html.
+
+add_task(async function() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-search-data.html");
+
+  await ContentTask.spawn(gBrowser.selectedBrowser,
+    [walker.actorID],
+    async function(actorID) {
+      const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
+      const { DebuggerServer } = require("devtools/server/main");
+      const {DocumentWalker: _documentWalker} = require("devtools/server/actors/inspector/document-walker");
+
+      // Convert actorID to current compartment string otherwise
+      // searchAllConnectionsForActor is confused and won't find the actor.
+      actorID = String(actorID);
+      const walkerActor = DebuggerServer.searchAllConnectionsForActor(actorID);
+      const walkerSearch = walkerActor.walkerSearch;
+      const {WalkerSearch, WalkerIndex} =
+        require("devtools/server/actors/utils/walker-search");
+
+      info("Testing basic index APIs exist.");
+      const index = new WalkerIndex(walkerActor);
+      ok(index.data.size > 0, "public index is filled after getting");
+
+      index.clearIndex();
+      ok(!index._data, "private index is empty after clearing");
+      ok(index.data.size > 0, "public index is filled after getting");
+
+      index.destroy();
+
+      info("Testing basic search APIs exist.");
+
+      ok(walkerSearch, "walker search exists on the WalkerActor");
+      ok(walkerSearch.search, "walker search has `search` method");
+      ok(walkerSearch.index, "walker search has `index` property");
+      is(walkerSearch.walker, walkerActor, "referencing the correct WalkerActor");
+
+      const walkerSearch2 = new WalkerSearch(walkerActor);
+      ok(walkerSearch2, "a new search instance can be created");
+      ok(walkerSearch2.search, "new search instance has `search` method");
+      ok(walkerSearch2.index, "new search instance has `index` property");
+      isnot(walkerSearch2, walkerSearch,
+        "new search instance differs from the WalkerActor's");
+
+      walkerSearch2.destroy();
+
+      info("Testing search with an empty query.");
+      let results = walkerSearch.search("");
+      is(results.length, 0, "No results when searching for ''");
+
+      results = walkerSearch.search(null);
+      is(results.length, 0, "No results when searching for null");
+
+      results = walkerSearch.search(undefined);
+      is(results.length, 0, "No results when searching for undefined");
+
+      results = walkerSearch.search(10);
+      is(results.length, 0, "No results when searching for 10");
+
+      const inspectee = content.document;
+      const testData = [
+      {
+        desc: "Search for tag with one result.",
+        search: "body",
+        expected: [
+          {node: inspectee.body, type: "tag"},
+        ],
+      },
+      {
+        desc: "Search for tag with multiple results",
+        search: "h2",
+        expected: [
+          {node: inspectee.querySelectorAll("h2")[0], type: "tag"},
+          {node: inspectee.querySelectorAll("h2")[1], type: "tag"},
+          {node: inspectee.querySelectorAll("h2")[2], type: "tag"},
+        ],
+      },
+      {
+        desc: "Search for selector with multiple results",
+        search: "body > h2",
+        expected: [
+          {node: inspectee.querySelectorAll("h2")[0], type: "selector"},
+          {node: inspectee.querySelectorAll("h2")[1], type: "selector"},
+          {node: inspectee.querySelectorAll("h2")[2], type: "selector"},
+        ],
+      },
+      {
+        desc: "Search for selector with multiple results",
+        search: ":root h2",
+        expected: [
+          {node: inspectee.querySelectorAll("h2")[0], type: "selector"},
+          {node: inspectee.querySelectorAll("h2")[1], type: "selector"},
+          {node: inspectee.querySelectorAll("h2")[2], type: "selector"},
+        ],
+      },
+      {
+        desc: "Search for selector with multiple results",
+        search: "* h2",
+        expected: [
+          {node: inspectee.querySelectorAll("h2")[0], type: "selector"},
+          {node: inspectee.querySelectorAll("h2")[1], type: "selector"},
+          {node: inspectee.querySelectorAll("h2")[2], type: "selector"},
+        ],
+      },
+      {
+        desc: "Search with multiple matches in a single tag expecting a single result",
+        search: "💩",
+        expected: [
+          {node: inspectee.getElementById("💩"), type: "attributeValue"},
+        ],
+      },
+      {
+        desc: "Search that has tag and text results",
+        search: "h1",
+        expected: [
+          {node: inspectee.querySelector("h1"), type: "tag"},
+          {node: inspectee.querySelector("h1 + p").childNodes[0], type: "text"},
+          {node: inspectee.querySelector("h1 + p > strong").childNodes[0], type: "text"},
+        ],
+      },
+      ];
+
+      const isDeeply = (a, b, msg) => {
+        return is(JSON.stringify(a), JSON.stringify(b), msg);
+      };
+      for (const {desc, search, expected} of testData) {
+        info("Running test: " + desc);
+        results = walkerSearch.search(search);
+        isDeeply(results, expected,
+          "Search returns correct results with '" + search + "'");
+      }
+
+      info("Testing ::before and ::after element matching");
+
+      const beforeElt = new _documentWalker(inspectee.querySelector("#pseudo"),
+                                          inspectee.defaultView).firstChild();
+      const afterElt = new _documentWalker(inspectee.querySelector("#pseudo"),
+                                         inspectee.defaultView).lastChild();
+      const styleText = inspectee.querySelector("style").childNodes[0];
+
+      // ::before
+      results = walkerSearch.search("::before");
+      isDeeply(results, [ {node: beforeElt, type: "tag"} ],
+               "Tag search works for pseudo element");
+
+      results = walkerSearch.search("_moz_generated_content_before");
+      is(results.length, 0, "No results for anon tag name");
+
+      results = walkerSearch.search("before element");
+      isDeeply(results, [
+        {node: styleText, type: "text"},
+        {node: beforeElt, type: "text"},
+      ], "Text search works for pseudo element");
+
+      // ::after
+      results = walkerSearch.search("::after");
+      isDeeply(results, [ {node: afterElt, type: "tag"} ],
+               "Tag search works for pseudo element");
+
+      results = walkerSearch.search("_moz_generated_content_after");
+      is(results.length, 0, "No results for anon tag name");
+
+      results = walkerSearch.search("after element");
+      isDeeply(results, [
+        {node: styleText, type: "text"},
+        {node: afterElt, type: "text"},
+      ], "Text search works for pseudo element");
+
+      info("Testing search before and after a mutation.");
+      const expected = [
+        {node: inspectee.querySelectorAll("h3")[0], type: "tag"},
+        {node: inspectee.querySelectorAll("h3")[1], type: "tag"},
+        {node: inspectee.querySelectorAll("h3")[2], type: "tag"},
+      ];
+
+      results = walkerSearch.search("h3");
+      isDeeply(results, expected, "Search works with tag results");
+
+      function mutateDocumentAndWaitForMutation(mutationFn) {
+        // eslint-disable-next-line new-cap
+        return new Promise(resolve => {
+          info("Listening to markup mutation on the inspectee");
+          const observer = new inspectee.defaultView.MutationObserver(resolve);
+          observer.observe(inspectee, {childList: true, subtree: true});
+          mutationFn();
+        });
+      }
+      await mutateDocumentAndWaitForMutation(() => {
+        expected[0].node.remove();
+      });
+
+      results = walkerSearch.search("h3");
+      isDeeply(results, [
+        expected[1],
+        expected[2],
+      ], "Results are updated after removal");
+
+      // eslint-disable-next-line new-cap
+      await new Promise(resolve => {
+        info("Waiting for a mutation to happen");
+        const observer = new inspectee.defaultView.MutationObserver(() => {
+          resolve();
+        });
+        observer.observe(inspectee, {attributes: true, subtree: true});
+        inspectee.body.setAttribute("h3", "true");
+      });
+
+      results = walkerSearch.search("h3");
+      isDeeply(results, [
+        {node: inspectee.body, type: "attributeName"},
+        expected[1],
+        expected[2],
+      ], "Results are updated after addition");
+
+      // eslint-disable-next-line new-cap
+      await new Promise(resolve => {
+        info("Waiting for a mutation to happen");
+        const observer = new inspectee.defaultView.MutationObserver(() => {
+          resolve();
+        });
+        observer.observe(inspectee, {attributes: true, childList: true, subtree: true});
+        inspectee.body.removeAttribute("h3");
+        expected[1].node.remove();
+        expected[2].node.remove();
+      });
+
+      results = walkerSearch.search("h3");
+      is(results.length, 0, "Results are updated after removal");
+    });
+});
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_inspector-traversal.js
@@ -0,0 +1,274 @@
+/* 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/ */
+
+"use strict";
+
+/* import-globals-from inspector-helpers.js */
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/inspector-helpers.js", this);
+
+const checkActorIDs = [];
+
+add_task(async function loadNewChild() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+
+  // Make sure that refetching the root document of the walker returns the same
+  // actor as the getWalker returned.
+  const root = await walker.document();
+  ok(root === walker.rootNode,
+     "Re-fetching the document node should match the root document node.");
+  checkActorIDs.push(root.actorID);
+  await assertOwnershipTrees(walker);
+});
+
+add_task(async function testInnerHTML() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+
+  const docElement = await walker.documentElement();
+  const longstring = await walker.innerHTML(docElement);
+  const innerHTML = await longstring.string();
+  const actualInnerHTML = await ContentTask.spawn(gBrowser.selectedBrowser, null,
+    function() {
+      return content.document.documentElement.innerHTML;
+    });
+  ok(innerHTML === actualInnerHTML, "innerHTML should match");
+});
+
+add_task(async function testOuterHTML() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+
+  const docElement = await walker.documentElement();
+  const longstring = await walker.outerHTML(docElement);
+  const outerHTML = await longstring.string();
+  const actualOuterHTML = await ContentTask.spawn(gBrowser.selectedBrowser, null,
+    function() {
+      return content.document.documentElement.outerHTML;
+    });
+  ok(outerHTML === actualOuterHTML, "outerHTML should match");
+});
+
+add_task(async function testSetOuterHTMLNode() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+  const newHTML = "<p id=\"edit-html-done\">after edit</p>";
+  let node = await walker.querySelector(walker.rootNode, "#edit-html");
+  await walker.setOuterHTML(node, newHTML);
+  node = await walker.querySelector(walker.rootNode, "#edit-html-done");
+  const longstring = await walker.outerHTML(node);
+  const outerHTML = await longstring.string();
+  is(outerHTML, newHTML, "outerHTML has been updated");
+  node = await walker.querySelector(walker.rootNode, "#edit-html");
+  ok(!node, "The node with the old ID cannot be selected anymore");
+});
+
+add_task(async function testQuerySelector() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+  let node = await walker.querySelector(walker.rootNode, "#longlist");
+  is(node.getAttribute("data-test"), "exists", "should have found the right node");
+  await assertOwnershipTrees(walker);
+  node = await walker.querySelector(walker.rootNode, "unknownqueryselector");
+  ok(!node, "Should not find a node here.");
+  await assertOwnershipTrees(walker);
+});
+
+add_task(async function testQuerySelectors() {
+  const { target, walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+  const nodeList = await walker.querySelectorAll(walker.rootNode, "#longlist div");
+  is(nodeList.length, 26, "Expect 26 div children.");
+  await assertOwnershipTrees(walker);
+  const firstNode = await nodeList.item(0);
+  checkActorIDs.push(firstNode.actorID);
+  is(firstNode.id, "a", "First child should be a");
+  await assertOwnershipTrees(walker);
+  let nodes = await nodeList.items();
+  is(nodes.length, 26, "Expect 26 nodes");
+  is(nodes[0], firstNode, "First node should be reused.");
+  ok(nodes[0]._parent, "Parent node should be set.");
+  ok(nodes[0]._next || nodes[0]._prev, "Siblings should be set.");
+  ok(nodes[25]._next || nodes[25]._prev,
+     "Siblings of " + nodes[25] + " should be set.");
+  await assertOwnershipTrees(walker);
+  nodes = await nodeList.items(-1);
+  is(nodes.length, 1, "Expect 1 node");
+  is(nodes[0].id, "z", "Expect it to be the last node.");
+  checkActorIDs.push(nodes[0].actorID);
+  // Save the node list ID so we can ensure it was destroyed.
+  const nodeListID = nodeList.actorID;
+  await assertOwnershipTrees(walker);
+  await nodeList.release();
+  ok(!nodeList.actorID, "Actor should have been destroyed.");
+  await assertOwnershipTrees(walker);
+  await checkMissing(target, nodeListID);
+});
+
+// Helper to check the response of requests that return hasFirst/hasLast/nodes
+// node lists (like `children` and `siblings`)
+async function checkArray(walker, children, first, last, ids) {
+  is(children.hasFirst, first,
+     "Should " + (first ? "" : "not ") + " have the first node.");
+  is(children.hasLast, last, "Should " + (last ? "" : "not ") + " have the last node.");
+  is(children.nodes.length, ids.length,
+     "Should have " + ids.length + " children listed.");
+  let responseIds = "";
+  for (const node of children.nodes) {
+    responseIds += node.id;
+  }
+  is(responseIds, ids, "Correct nodes were returned.");
+  await assertOwnershipTrees(walker);
+}
+
+add_task(async function testNoChildren() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+  const empty = await walker.querySelector(walker.rootNode, "#empty");
+  await assertOwnershipTrees(walker);
+  const children = await walker.children(empty);
+  await checkArray(walker, children, true, true, "");
+});
+
+add_task(async function testLongListTraversal() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+  const longList = await walker.querySelector(walker.rootNode, "#longlist");
+  // First call with no options, expect all children.
+  await assertOwnershipTrees(walker);
+  let children = await walker.children(longList);
+  await checkArray(walker, children, true, true, "abcdefghijklmnopqrstuvwxyz");
+  const allChildren = children.nodes;
+  await assertOwnershipTrees(walker);
+  // maxNodes should limit us to the first 5 nodes.
+  await assertOwnershipTrees(walker);
+  children = await walker.children(longList, { maxNodes: 5 });
+  await checkArray(walker, children, true, false, "abcde");
+  await assertOwnershipTrees(walker);
+  // maxNodes with the second item centered should still give us the first 5 nodes.
+  children = await walker.children(longList, { maxNodes: 5, center: allChildren[1] });
+  await checkArray(walker, children, true, false, "abcde");
+  // maxNodes with a center in the middle of the list should put that item in the middle
+  const center = allChildren[13];
+  is(center.id, "n", "Make sure I know how to count letters.");
+  children = await walker.children(longList, { maxNodes: 5, center: center });
+  await checkArray(walker, children, false, false, "lmnop");
+  // maxNodes with the second-to-last item centered should give us the last 5 nodes.
+  children = await walker.children(longList, { maxNodes: 5, center: allChildren[24] });
+  await checkArray(walker, children, false, true, "vwxyz");
+  // maxNodes with a start in the middle should start at that node and fetch 5
+  const start = allChildren[13];
+  is(start.id, "n", "Make sure I know how to count letters.");
+  children = await walker.children(longList, { maxNodes: 5, start: start });
+  await checkArray(walker, children, false, false, "nopqr");
+  // maxNodes near the end should only return what's left
+  children = await walker.children(longList, { maxNodes: 5, start: allChildren[24] });
+  await checkArray(walker, children, false, true, "yz");
+});
+
+add_task(async function testObjectNodeChildren() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+  const object = await walker.querySelector(walker.rootNode, "object");
+  const children = await walker.children(object);
+  await checkArray(walker, children, true, true, "1");
+});
+
+add_task(async function testNextSibling() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+  const y = await walker.querySelector(walker.rootNode, "#y");
+  is(y.id, "y", "Got the right node.");
+  const z = await walker.nextSibling(y);
+  is(z.id, "z", "nextSibling got the next node.");
+  const nothing = await walker.nextSibling(z);
+  is(nothing, null, "nextSibling on the last node returned null.");
+});
+
+add_task(async function testPreviousSibling() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+  const b = await walker.querySelector(walker.rootNode, "#b");
+  is(b.id, "b", "Got the right node.");
+  const a = await walker.previousSibling(b);
+  is(a.id, "a", "nextSibling got the next node.");
+  const nothing = await walker.previousSibling(a);
+  is(nothing, null, "previousSibling on the first node returned null.");
+});
+
+add_task(async function testFrameTraversal() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+  const childFrame = await walker.querySelector(walker.rootNode, "#childFrame");
+  const children = await walker.children(childFrame);
+  const nodes = children.nodes;
+  is(nodes.length, 1, "There should be only one child of the iframe");
+  is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
+  await walker.querySelector(nodes[0], "#z");
+});
+
+add_task(async function testLongValue() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+
+  SimpleTest.registerCleanupFunction(async function() {
+    await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+      const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
+      const WalkerActor = require("devtools/server/actors/inspector/walker");
+      WalkerActor.setValueSummaryLength(WalkerActor.DEFAULT_VALUE_SUMMARY_LENGTH);
+    });
+  });
+
+  const longstringText = await ContentTask.spawn(gBrowser.selectedBrowser, null,
+    function() {
+      const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
+      const testSummaryLength = 10;
+      const WalkerActor = require("devtools/server/actors/inspector/walker");
+
+      WalkerActor.setValueSummaryLength(testSummaryLength);
+      return content.document.getElementById("longstring").firstChild.nodeValue;
+    });
+
+  const node = await walker.querySelector(walker.rootNode, "#longstring");
+  ok(!node.inlineTextChild, "Text is too long to be inlined");
+  // Now we need to get the text node child...
+  const children = await walker.children(node, { maxNodes: 1 });
+  const textNode = children.nodes[0];
+  is(textNode.nodeType, Node.TEXT_NODE, "Value should be a text node");
+  const value = await textNode.getNodeValue();
+  const valueStr = await value.string();
+  is(valueStr, longstringText,
+     "Full node value should match the string from the document.");
+});
+
+add_task(async function testShortValue() {
+  const { walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+  const shortstringText = await ContentTask.spawn(gBrowser.selectedBrowser, null,
+    function() {
+      return content.document.getElementById("shortstring").firstChild.nodeValue;
+    });
+
+  const node = await walker.querySelector(walker.rootNode, "#shortstring");
+  ok(!!node.inlineTextChild, "Text is short enough to be inlined");
+  // Now we need to get the text node child...
+  const children = await walker.children(node, { maxNodes: 1 });
+  const textNode = children.nodes[0];
+  is(textNode.nodeType, Node.TEXT_NODE, "Value should be a text node");
+  const value = await textNode.getNodeValue();
+  const valueStr = await value.string();
+  is(valueStr, shortstringText,
+     "Full node value should match the string from the document.");
+});
+
+add_task(async function testReleaseWalker() {
+  const { target, walker } =
+    await initInspectorFront(MAIN_DOMAIN + "inspector-traversal-data.html");
+  checkActorIDs.push(walker.actorID);
+
+  await walker.release();
+  for (const id of checkActorIDs) {
+    await checkMissing(target, id);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_memory_allocations_01.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function() {
+  const target = await addTabTarget("data:text/html;charset=utf-8,test-doc");
+  const memory = await target.getFront("memory");
+
+  await memory.attach();
+
+  await memory.startRecordingAllocations();
+  ok(true, "Can start recording allocations");
+
+  // Allocate some objects.
+  const [line1, line2, line3] =
+    await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+      // Use eval to ensure allocating the object in the page's compartment
+      return content.eval("(" + function() {
+        let alloc1, alloc2, alloc3;
+
+        /* eslint-disable max-nested-callbacks */
+        (function outer() {
+          (function middle() {
+            (function inner() {
+              alloc1 = {}; alloc1.line = Error().lineNumber;
+              alloc2 = []; alloc2.line = Error().lineNumber;
+              // eslint-disable-next-line new-parens
+              alloc3 = new function() {}; alloc3.line = Error().lineNumber;
+            }());
+          }());
+        }());
+        /* eslint-enable max-nested-callbacks */
+
+        return [ alloc1.line, alloc2.line, alloc3.line ];
+      } + ")()");
+    });
+
+  const response = await memory.getAllocations();
+
+  await memory.stopRecordingAllocations();
+  ok(true, "Can stop recording allocations");
+
+  // Filter out allocations by library and test code, and get only the
+  // allocations that occurred in our test case above.
+
+  function isTestAllocation(alloc) {
+    const frame = response.frames[alloc];
+    return frame
+      && frame.functionDisplayName === "inner"
+      && (frame.line === line1
+          || frame.line === line2
+          || frame.line === line3);
+  }
+
+  const testAllocations = response.allocations.filter(isTestAllocation);
+  ok(testAllocations.length >= 3,
+     "Should find our 3 test allocations (plus some allocations for the error "
+     + "objects used to get line numbers)");
+
+  // For each of the test case's allocations, ensure that the parent frame
+  // indices are correct. Also test that we did get an allocation at each
+  // line we expected (rather than a bunch on the first line and none on the
+  // others, etc).
+
+  const expectedLines = new Set([line1, line2, line3]);
+  is(expectedLines.size, 3, "We are expecting 3 allocations");
+
+  for (const alloc of testAllocations) {
+    const innerFrame = response.frames[alloc];
+    ok(innerFrame, "Should get the inner frame");
+    is(innerFrame.functionDisplayName, "inner");
+    expectedLines.delete(innerFrame.line);
+
+    const middleFrame = response.frames[innerFrame.parent];
+    ok(middleFrame, "Should get the middle frame");
+    is(middleFrame.functionDisplayName, "middle");
+
+    const outerFrame = response.frames[middleFrame.parent];
+    ok(outerFrame, "Should get the outer frame");
+    is(outerFrame.functionDisplayName, "outer");
+
+    // Not going to test the rest of the frames because they are Task.jsm
+    // and promise frames and it gets gross. Plus, I wouldn't want this test
+    // to start failing if they changed their implementations in a way that
+    // added or removed stack frames here.
+  }
+
+  is(expectedLines.size, 0,
+     "Should have found all the expected lines");
+
+  await memory.detach();
+
+  await target.destroy();
+});
copy from devtools/server/tests/mochitest/inspector-helpers.js
copy to devtools/server/tests/browser/inspector-helpers.js
--- a/devtools/server/tests/mochitest/inspector-helpers.js
+++ b/devtools/server/tests/browser/inspector-helpers.js
@@ -1,140 +1,72 @@
-/* exported attachURL, promiseDone, assertOwnershipTrees, checkMissing, checkAvailable,
-   promiseOnce, isSrcChange, isUnretained, isNewRoot, assertSrcChange, assertUnload,
-   assertFrameLoad, assertChildList, waitForMutation, addTest, addAsyncTest,
-   runNextTest */
+/* 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/ */
+
 "use strict";
 
-const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
-const {TargetFactory} = require("devtools/client/framework/target");
-const {DebuggerServer} = require("devtools/server/main");
-const {BrowserTestUtils} = require("resource://testing-common/BrowserTestUtils.jsm");
-
-const Services = require("Services");
-const {DocumentWalker: _documentWalker} = require("devtools/server/actors/inspector/document-walker");
-
-// Always log packets when running tests.
-Services.prefs.setBoolPref("devtools.debugger.log", true);
-SimpleTest.registerCleanupFunction(function() {
-  Services.prefs.clearUserPref("devtools.debugger.log");
-});
+/* exported assertOwnershipTrees, checkMissing, waitForMutation,
+   isSrcChange, isUnretained, isNewRoot,
+   assertSrcChange, assertUnload, assertFrameLoad, assertChildList,
+*/
 
-if (!DebuggerServer.initialized) {
-  DebuggerServer.init();
-  DebuggerServer.registerAllActors();
-  SimpleTest.registerCleanupFunction(function() {
-    DebuggerServer.destroy();
-  });
-}
-
-var gAttachCleanups = [];
+function serverOwnershipTree(walkerArg) {
+  return ContentTask.spawn(gBrowser.selectedBrowser, [walkerArg.actorID],
+    function(actorID) {
+      const { require } =
+        ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
+      const { DebuggerServer } = require("devtools/server/main");
+      const { DocumentWalker } =
+        require("devtools/server/actors/inspector/document-walker");
 
-SimpleTest.registerCleanupFunction(function() {
-  for (const cleanup of gAttachCleanups) {
-    cleanup();
-  }
-});
+      // Convert actorID to current compartment string otherwise
+      // searchAllConnectionsForActor is confused and won't find the actor.
+      actorID = String(actorID);
+      const serverWalker = DebuggerServer.searchAllConnectionsForActor(actorID);
 
-/**
- * Add a new test tab in the browser and load the given url.
- * @return Promise a promise that resolves to the new target representing
- *         the page currently opened.
- */
-
-async function getTargetForSelectedTab(gBrowser) {
-  const selectedTab = gBrowser.selectedTab;
-  await BrowserTestUtils.browserLoaded(selectedTab.linkedBrowser);
-  return TargetFactory.forTab(selectedTab);
-}
+      function sortOwnershipChildrenContentScript(children) {
+        return children.sort((a, b) => a.name.localeCompare(b.name));
+      }
 
-/**
- * Open a tab, load the url, wait for it to signal its readiness,
- * find the tab with the debugger server, and call the callback.
- *
- * Returns a function which can be called to close the opened ta
- * and disconnect its debugger client.
- */
-async function attachURL(url) {
-  // Get the current browser window
-  const gBrowser = Services.wm.getMostRecentWindow("navigator:browser").gBrowser;
-
-  // open the url in a new tab, save a reference to the new inner window global object
-  // and wait for it to load. The tests rely on this window object to send a "ready"
-  // event to its opener (the test page). This window reference is used within
-  // the test tab, to reference the webpage being tested against, which is in another
-  // tab.
-  const windowOpened = BrowserTestUtils.waitForNewTab(gBrowser, url);
-  const win = window.open(url, "_blank");
-  await windowOpened;
+      function serverOwnershipSubtree(walker, node) {
+        const actor = walker._refMap.get(node);
+        if (!actor) {
+          return undefined;
+        }
 
-  const target = await getTargetForSelectedTab(gBrowser);
-  await target.attach();
-
-  const cleanup = async function() {
-    await target.destroy();
-    if (win) {
-      win.close();
-    }
-  };
-
-  gAttachCleanups.push(cleanup);
-  return { target, doc: win.document };
-}
-
-function promiseOnce(target, event) {
-  return new Promise(resolve => {
-    target.on(event, (...args) => {
-      if (args.length === 1) {
-        resolve(args[0]);
-      } else {
-        resolve(args);
+        const children = [];
+        const docwalker = new DocumentWalker(node, content);
+        let child = docwalker.firstChild();
+        while (child) {
+          const item = serverOwnershipSubtree(walker, child);
+          if (item) {
+            children.push(item);
+          }
+          child = docwalker.nextSibling();
+        }
+        return {
+          name: actor.actorID,
+          children: sortOwnershipChildrenContentScript(children),
+        };
       }
+      return {
+        root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc),
+        orphaned: [...serverWalker._orphaned]
+                  .map(o => serverOwnershipSubtree(serverWalker, o.rawNode)),
+        retained: [...serverWalker._retainedOrphans]
+                  .map(o => serverOwnershipSubtree(serverWalker, o.rawNode)),
+      };
     });
-  });
 }
 
 function sortOwnershipChildren(children) {
   return children.sort((a, b) => a.name.localeCompare(b.name));
 }
 
-function serverOwnershipSubtree(walker, node) {
-  const actor = walker._refMap.get(node);
-  if (!actor) {
-    return undefined;
-  }
-
-  const children = [];
-  const docwalker = new _documentWalker(node, window);
-  let child = docwalker.firstChild();
-  while (child) {
-    const item = serverOwnershipSubtree(walker, child);
-    if (item) {
-      children.push(item);
-    }
-    child = docwalker.nextSibling();
-  }
-  return {
-    name: actor.actorID,
-    children: sortOwnershipChildren(children),
-  };
-}
-
-function serverOwnershipTree(walker) {
-  const serverWalker = DebuggerServer.searchAllConnectionsForActor(walker.actorID);
-
-  return {
-    root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc),
-    orphaned: [...serverWalker._orphaned]
-              .map(o => serverOwnershipSubtree(serverWalker, o.rawNode)),
-    retained: [...serverWalker._retainedOrphans]
-              .map(o => serverOwnershipSubtree(serverWalker, o.rawNode)),
-  };
-}
-
 function clientOwnershipSubtree(node) {
   return {
     name: node.actorID,
     children: sortOwnershipChildren(node.treeChildren()
               .map(child => clientOwnershipSubtree(child))),
   };
 }
 
@@ -149,18 +81,18 @@ function clientOwnershipTree(walker) {
 function ownershipTreeSize(tree) {
   let size = 1;
   for (const child of tree.children) {
     size += ownershipTreeSize(child);
   }
   return size;
 }
 
-function assertOwnershipTrees(walker) {
-  const serverTree = serverOwnershipTree(walker);
+async function assertOwnershipTrees(walker) {
+  const serverTree = await serverOwnershipTree(walker);
   const clientTree = clientOwnershipTree(walker);
   is(JSON.stringify(clientTree, null, " "), JSON.stringify(serverTree, null, " "),
      "Server and client ownership trees should match.");
 
   return ownershipTreeSize(clientTree.root);
 }
 
 // Verify that an actorID is inaccessible both from the client library and the server.
@@ -175,45 +107,35 @@ function checkMissing({client}, actorID)
     }, response => {
       is(response.error, "noSuchActor",
         "node list actor should no longer be contactable.");
       resolve(undefined);
     });
   });
 }
 
-// Verify that an actorID is accessible both from the client library and the server.
-function checkAvailable({client}, actorID) {
+// Load mutations aren't predictable, so keep accumulating mutations until
+// the one we're looking for shows up.
+function waitForMutation(walker, test, mutations = []) {
   return new Promise(resolve => {
-    const front = client.getActor(actorID);
-    ok(front, "Front should be accessible from the client for actorID: " + actorID);
+    for (const change of mutations) {
+      if (test(change)) {
+        resolve(mutations);
+      }
+    }
 
-    client.request({
-      to: actorID,
-      type: "garbageAvailableTest",
-    }, response => {
-      is(response.error, "unrecognizedPacketType",
-        "node list actor should be contactable.");
-      resolve(undefined);
+    walker.once("mutations", newMutations => {
+      waitForMutation(walker, test, mutations.concat(newMutations))
+        .then(finalMutations => {
+          resolve(finalMutations);
+        });
     });
   });
 }
 
-function promiseDone(currentPromise) {
-  currentPromise.catch(err => {
-    ok(false, "Promise failed: " + err);
-    if (err.stack) {
-      dump(err.stack);
-    }
-    SimpleTest.finish();
-  });
-}
-
-// Mutation list testing
-
 function assertAndStrip(mutations, message, test) {
   const size = mutations.length;
   mutations = mutations.filter(test);
   ok((mutations.size != size), message);
   return mutations;
 }
 
 function isSrcChange(change) {
@@ -259,50 +181,8 @@ function assertFrameLoad(mutations) {
   return assertAndStrip(mutations, "Should have had a frame load change.", isFrameLoad);
 }
 
 // Make sure there's a childList change in the mutation list and strip
 // that mutation out of the list
 function assertChildList(mutations) {
   return assertAndStrip(mutations, "Should have had a frame load change.", isChildList);
 }
-
-// Load mutations aren't predictable, so keep accumulating mutations until
-// the one we're looking for shows up.
-function waitForMutation(walker, test, mutations = []) {
-  return new Promise(resolve => {
-    for (const change of mutations) {
-      if (test(change)) {
-        resolve(mutations);
-      }
-    }
-
-    walker.once("mutations", newMutations => {
-      waitForMutation(walker, test, mutations.concat(newMutations))
-        .then(finalMutations => {
-          resolve(finalMutations);
-        });
-    });
-  });
-}
-
-var _tests = [];
-function addTest(test) {
-  _tests.push(test);
-}
-
-function addAsyncTest(generator) {
-  _tests.push(() => (generator)().catch(ok.bind(null, false)));
-}
-
-function runNextTest() {
-  if (_tests.length == 0) {
-    SimpleTest.finish();
-    return;
-  }
-  const fn = _tests.shift();
-  try {
-    fn();
-  } catch (ex) {
-    info("Test function " + (fn.name ? "'" + fn.name + "' " : "") +
-         "threw an exception: " + ex);
-  }
-}
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/inspector-search-data.html
@@ -0,0 +1,54 @@
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>Inspector Search Test Data</title>
+  <style>
+    #pseudo {
+      display: block;
+      margin: 0;
+    }
+    #pseudo:before {
+      content: "before element";
+    }
+    #pseudo:after {
+      content: "after element";
+    }
+  </style>
+  <script type="text/javascript">
+    "use strict";
+
+    window.onload = function() {
+      window.opener.postMessage("ready", "*");
+    };
+  </script>
+</head>
+</body>
+  <!-- A comment
+       spread across multiple lines -->
+
+  <img width="100" height="100" src="large-image.jpg" />
+
+  <h1 id="pseudo">Heading 1</h1>
+  <p>A p tag with the text 'h1' inside of it.
+    <strong>A strong h1 result</strong>
+  </p>
+
+  <div id="arrows" northwest="↖" northeast="↗" southeast="↘" southwest="↙">
+    Unicode arrows
+  </div>
+
+  <h2>Heading 2</h2>
+  <h2>Heading 2</h2>
+  <h2>Heading 2</h2>
+
+  <h3>Heading 3</h3>
+  <h3>Heading 3</h3>
+  <h3>Heading 3</h3>
+
+  <h4>Heading 4</h4>
+  <h4>Heading 4</h4>
+  <h4>Heading 4</h4>
+
+  <div class="💩" id="💩" 💩="💩"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/inspector-traversal-data.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>Inspector Traversal Test Data</title>
+  <style type="text/css">
+    #pseudo::before {
+      content: "before";
+    }
+    #pseudo::after {
+      content: "after";
+    }
+    #pseudo-empty::before {
+      content: "before an empty element";
+    }
+    #shadow::before {
+      content: "Testing ::before on a shadow host";
+    }
+  </style>
+  <script type="text/javascript">
+    "use strict";
+
+    window.onload = function() {
+      // Set up a basic shadow DOM
+      const host = document.querySelector("#shadow");
+      if (host.attachShadow) {
+        const root = host.attachShadow({ mode: "open" });
+
+        const h3 = document.createElement("h3");
+        h3.append("Shadow ");
+
+        const em = document.createElement("em");
+        em.append("DOM");
+
+        const select = document.createElement("select");
+        select.setAttribute("multiple", "");
+        h3.appendChild(em);
+        root.appendChild(h3);
+        root.appendChild(select);
+      }
+
+      // Put a copy of the body in an iframe to test frame traversal.
+      const body = document.querySelector("body");
+      const data = "data:text/html,<html>" + body.outerHTML + "<html>";
+      const iframe = document.createElement("iframe");
+      iframe.setAttribute("id", "childFrame");
+      iframe.src = data;
+      body.appendChild(iframe);
+    };
+  </script>
+</head>
+<body style="background-color:white">
+  <h1>Inspector Actor Tests</h1>
+  <span id="longstring">longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong</span>
+  <span id="shortstring">short</span>
+  <span id="empty"></span>
+  <div id="longlist" data-test="exists">
+    <div id="a">a</div>
+    <div id="b">b</div>
+    <div id="c">c</div>
+    <div id="d">d</div>
+    <div id="e">e</div>
+    <div id="f">f</div>
+    <div id="g">g</div>
+    <div id="h">h</div>
+    <div id="i">i</div>
+    <div id="j">j</div>
+    <div id="k">k</div>
+    <div id="l">l</div>
+    <div id="m">m</div>
+    <div id="n">n</div>
+    <div id="o">o</div>
+    <div id="p">p</div>
+    <div id="q">q</div>
+    <div id="r">r</div>
+    <div id="s">s</div>
+    <div id="t">t</div>
+    <div id="u">u</div>
+    <div id="v">v</div>
+    <div id="w">w</div>
+    <div id="x">x</div>
+    <div id="y">y</div>
+    <div id="z">z</div>
+  </div>
+  <div id="longlist-sibling">
+    <div id="longlist-sibling-firstchild"></div>
+  </div>
+  <p id="edit-html"></p>
+
+  <select multiple><option>one</option><option>two</option></select>
+  <div id="pseudo"><span>middle</span></div>
+  <div id="pseudo-empty"></div>
+  <div id="shadow">light dom</div>
+  <object>
+      <div id="1"></div>
+  </object>
+  <div class="node-to-duplicate"></div>
+  <div id="scroll-into-view" style="margin-top: 1000px;">scroll</div>
+</body>
+</html>
--- a/devtools/server/tests/mochitest/chrome.ini
+++ b/devtools/server/tests/mochitest/chrome.ini
@@ -1,13 +1,12 @@
 [DEFAULT]
 tags = devtools
 skip-if = os == 'android'
 support-files =
-  animation-data.html
   doc_Debugger.Source.prototype.introductionType.xul
   Debugger.Source.prototype.element.js
   Debugger.Source.prototype.element-2.js
   Debugger.Source.prototype.element.html
   framerate-helpers.js
   hello-actor.js
   iframe1_makeGlobalObjectReference.html
   iframe2_makeGlobalObjectReference.html
@@ -30,17 +29,16 @@ support-files =
   suspendTimeouts_content.js
   suspendTimeouts_worker.js
   small-image.gif
   setup-in-child.js
   setup-in-parent.js
   test_suspendTimeouts.js
   webconsole-helpers.js
   webextension-helpers.js
-[test_animation_actor-lifetime.html]
 [test_animation-type-longhand.html]
 [test_connection-manager.html]
 skip-if = (verify && debug && (os == 'win'))
 [test_connectToFrame.html]
 [test_css-logic.html]
 [test_css-logic-media-queries.html]
 [test_css-logic-specificity.html]
 [test_css-properties.html]
@@ -53,50 +51,40 @@ skip-if = (verify && debug && (os == 'wi
 [test_framerate_01.html]
 [test_framerate_02.html]
 [test_framerate_03.html]
 [test_framerate_04.html]
 [test_framerate_05.html]
 [test_framerate_06.html]
 [test_getProcess.html]
 [test_highlighter_paused_debugger.html]
-[test_inspector-anonymous.html]
 [test_inspector-changeattrs.html]
 [test_inspector-changevalue.html]
 [test_inspector-dead-nodes.html]
 [test_inspector-display-type.html]
 [test_inspector-duplicate-node.html]
 [test_inspector_getImageData.html]
 [test_inspector_getImageDataFromURL.html]
 [test_inspector_getImageData-wait-for-load.html]
 [test_inspector_getNodeFromActor.html]
 [test_inspector_getOffsetParent.html]
 [test_inspector-hide.html]
-[test_inspector-insert.html]
 [test_inspector-mutations-attr.html]
 [test_inspector-mutations-events.html]
-[test_inspector-mutations-childlist.html]
-[test_inspector-mutations-frameload.html]
 [test_inspector-mutations-value.html]
 [test_inspector-pick-color.html]
 [test_inspector-pseudoclass-lock.html]
-[test_inspector-release.html]
 [test_inspector-reload.html]
-[test_inspector-remove.html]
 [test_inspector-resize.html]
 [test_inspector-resolve-url.html]
-[test_inspector-retain.html]
-[test_inspector-search.html]
 [test_inspector-search-front.html]
 [test_inspector-scroll-into-view.html]
 [test_inspector-template.html]
-[test_inspector-traversal.html]
 [test_makeGlobalObjectReference.html]
 [test_memory.html]
-[test_memory_allocations_01.html]
 [test_memory_allocations_02.html]
 [test_memory_allocations_03.html]
 [test_memory_allocations_04.html]
 [test_memory_allocations_05.html]
 [test_memory_allocations_06.html]
 [test_memory_allocations_07.html]
 [test_memory_attach_01.html]
 [test_memory_attach_02.html]
--- a/devtools/server/tests/mochitest/inspector-helpers.js
+++ b/devtools/server/tests/mochitest/inspector-helpers.js
@@ -1,21 +1,21 @@
-/* exported attachURL, promiseDone, assertOwnershipTrees, checkMissing, checkAvailable,
-   promiseOnce, isSrcChange, isUnretained, isNewRoot, assertSrcChange, assertUnload,
-   assertFrameLoad, assertChildList, waitForMutation, addTest, addAsyncTest,
-   runNextTest */
+/* exported attachURL, promiseDone,
+   promiseOnce, isNewRoot,
+   waitForMutation, addTest, addAsyncTest,
+   runNextTest, _documentWalker */
 "use strict";
 
 const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
 const {TargetFactory} = require("devtools/client/framework/target");
 const {DebuggerServer} = require("devtools/server/main");
 const {BrowserTestUtils} = require("resource://testing-common/BrowserTestUtils.jsm");
+const {DocumentWalker: _documentWalker} = require("devtools/server/actors/inspector/document-walker");
 
 const Services = require("Services");
-const {DocumentWalker: _documentWalker} = require("devtools/server/actors/inspector/document-walker");
 
 // Always log packets when running tests.
 Services.prefs.setBoolPref("devtools.debugger.log", true);
 SimpleTest.registerCleanupFunction(function() {
   Services.prefs.clearUserPref("devtools.debugger.log");
 });
 
 if (!DebuggerServer.initialized) {
@@ -87,189 +87,32 @@ function promiseOnce(target, event) {
         resolve(args[0]);
       } else {
         resolve(args);
       }
     });
   });
 }
 
-function sortOwnershipChildren(children) {
-  return children.sort((a, b) => a.name.localeCompare(b.name));
-}
-
-function serverOwnershipSubtree(walker, node) {
-  const actor = walker._refMap.get(node);
-  if (!actor) {
-    return undefined;
-  }
-
-  const children = [];
-  const docwalker = new _documentWalker(node, window);
-  let child = docwalker.firstChild();
-  while (child) {
-    const item = serverOwnershipSubtree(walker, child);
-    if (item) {
-      children.push(item);
-    }
-    child = docwalker.nextSibling();
-  }
-  return {
-    name: actor.actorID,
-    children: sortOwnershipChildren(children),
-  };
-}
-
-function serverOwnershipTree(walker) {
-  const serverWalker = DebuggerServer.searchAllConnectionsForActor(walker.actorID);
-
-  return {
-    root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc),
-    orphaned: [...serverWalker._orphaned]
-              .map(o => serverOwnershipSubtree(serverWalker, o.rawNode)),
-    retained: [...serverWalker._retainedOrphans]
-              .map(o => serverOwnershipSubtree(serverWalker, o.rawNode)),
-  };
-}
-
-function clientOwnershipSubtree(node) {
-  return {
-    name: node.actorID,
-    children: sortOwnershipChildren(node.treeChildren()
-              .map(child => clientOwnershipSubtree(child))),
-  };
-}
-
-function clientOwnershipTree(walker) {
-  return {
-    root: clientOwnershipSubtree(walker.rootNode),
-    orphaned: [...walker._orphaned].map(o => clientOwnershipSubtree(o)),
-    retained: [...walker._retainedOrphans].map(o => clientOwnershipSubtree(o)),
-  };
-}
-
-function ownershipTreeSize(tree) {
-  let size = 1;
-  for (const child of tree.children) {
-    size += ownershipTreeSize(child);
-  }
-  return size;
-}
-
-function assertOwnershipTrees(walker) {
-  const serverTree = serverOwnershipTree(walker);
-  const clientTree = clientOwnershipTree(walker);
-  is(JSON.stringify(clientTree, null, " "), JSON.stringify(serverTree, null, " "),
-     "Server and client ownership trees should match.");
-
-  return ownershipTreeSize(clientTree.root);
-}
-
-// Verify that an actorID is inaccessible both from the client library and the server.
-function checkMissing({client}, actorID) {
-  return new Promise(resolve => {
-    const front = client.getActor(actorID);
-    ok(!front, "Front shouldn't be accessible from the client for actorID: " + actorID);
-
-    client.request({
-      to: actorID,
-      type: "request",
-    }, response => {
-      is(response.error, "noSuchActor",
-        "node list actor should no longer be contactable.");
-      resolve(undefined);
-    });
-  });
-}
-
-// Verify that an actorID is accessible both from the client library and the server.
-function checkAvailable({client}, actorID) {
-  return new Promise(resolve => {
-    const front = client.getActor(actorID);
-    ok(front, "Front should be accessible from the client for actorID: " + actorID);
-
-    client.request({
-      to: actorID,
-      type: "garbageAvailableTest",
-    }, response => {
-      is(response.error, "unrecognizedPacketType",
-        "node list actor should be contactable.");
-      resolve(undefined);
-    });
-  });
-}
-
 function promiseDone(currentPromise) {
   currentPromise.catch(err => {
     ok(false, "Promise failed: " + err);
     if (err.stack) {
       dump(err.stack);
     }
     SimpleTest.finish();
   });
 }
 
 // Mutation list testing
 
-function assertAndStrip(mutations, message, test) {
-  const size = mutations.length;
-  mutations = mutations.filter(test);
-  ok((mutations.size != size), message);
-  return mutations;
-}
-
-function isSrcChange(change) {
-  return change.type === "attributes" && change.attributeName === "src";
-}
-
-function isUnload(change) {
-  return change.type === "documentUnload";
-}
-
-function isFrameLoad(change) {
-  return change.type === "frameLoad";
-}
-
-function isUnretained(change) {
-  return change.type === "unretained";
-}
-
-function isChildList(change) {
-  return change.type === "childList";
-}
-
 function isNewRoot(change) {
   return change.type === "newRoot";
 }
 
-// Make sure an iframe's src attribute changed and then
-// strip that mutation out of the list.
-function assertSrcChange(mutations) {
-  return assertAndStrip(mutations, "Should have had an iframe source change.",
-                        isSrcChange);
-}
-
-// Make sure there's an unload in the mutation list and strip
-// that mutation out of the list
-function assertUnload(mutations) {
-  return assertAndStrip(mutations, "Should have had a document unload change.", isUnload);
-}
-
-// Make sure there's a frame load in the mutation list and strip
-// that mutation out of the list
-function assertFrameLoad(mutations) {
-  return assertAndStrip(mutations, "Should have had a frame load change.", isFrameLoad);
-}
-
-// Make sure there's a childList change in the mutation list and strip
-// that mutation out of the list
-function assertChildList(mutations) {
-  return assertAndStrip(mutations, "Should have had a frame load change.", isChildList);
-}
-
 // Load mutations aren't predictable, so keep accumulating mutations until
 // the one we're looking for shows up.
 function waitForMutation(walker, test, mutations = []) {
   return new Promise(resolve => {
     for (const change of mutations) {
       if (test(change)) {
         resolve(mutations);
       }
deleted file mode 100644
--- a/devtools/server/tests/mochitest/test_animation_actor-lifetime.html
+++ /dev/null
@@ -1,81 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=1247243
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test for Bug 1247243</title>
-
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
-  <script type="application/javascript" src="inspector-helpers.js"></script>
-  <script type="application/javascript">
-"use strict";
-
-window.onload = function() {
-  SimpleTest.waitForExplicitFinish();
-
-  let gWalker = null;
-  let animationsFront = null;
-
-  addTest(async function setup() {
-    info("Setting up inspector and animation actors.");
-
-    const url = document.getElementById("animationContent").href;
-
-    const { target } = await attachURL(url);
-    const inspector = await target.getInspector();
-    animationsFront = await target.getFront("animations");
-    gWalker = inspector.walker;
-    runNextTest();
-  });
-
-  addAsyncTest(async function testActorLifetime() {
-    info("Testing animated node actor");
-    const animatedNodeActor = await gWalker.querySelector(gWalker.rootNode,
-      ".animated");
-    await animationsFront.getAnimationPlayersForNode(animatedNodeActor);
-
-    const animationsActor = DebuggerServer
-                          .searchAllConnectionsForActor(animationsFront.actorID);
-
-    is(animationsActor.actors.length, 1,
-      "AnimationActor have 1 AnimationPlayerActors");
-
-    info("Testing AnimationPlayerActors release");
-    const stillNodeActor = await gWalker.querySelector(gWalker.rootNode,
-      ".still");
-    await animationsFront.getAnimationPlayersForNode(stillNodeActor);
-    is(animationsActor.actors.length, 0,
-      "AnimationActor does not have any AnimationPlayerActors anymore");
-
-    info("Testing multi animated node actor");
-    const multiNodeActor = await gWalker.querySelector(gWalker.rootNode,
-      ".multi");
-    await animationsFront.getAnimationPlayersForNode(multiNodeActor);
-    is(animationsActor.actors.length, 2,
-      "AnimationActor has now 2 AnimationPlayerActors");
-
-    info("Testing single animated node actor");
-    await animationsFront.getAnimationPlayersForNode(animatedNodeActor);
-    is(animationsActor.actors.length, 1,
-      "AnimationActor has only one AnimationPlayerActors");
-
-    info("Testing AnimationPlayerActors release again");
-    await animationsFront.getAnimationPlayersForNode(stillNodeActor);
-    is(animationsActor.actors.length, 0,
-      "AnimationActor does not have any AnimationPlayerActors anymore");
-
-    runNextTest();
-  });
-
-  runNextTest();
-};
-  </script>
-</head>
-<body>
-  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1247243">Mozilla Bug 1247243</a>
-  <a id="animationContent" target="_blank" href="animation-data.html">Test Document</a>
-</body>
-</html>
deleted file mode 100644
--- a/devtools/server/tests/mochitest/test_inspector-anonymous.html
+++ /dev/null
@@ -1,204 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=777674
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test for Bug 777674</title>
-
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
-  <script type="application/javascript" src="inspector-helpers.js"></script>
-  <script type="application/javascript">
-"use strict";
-
-window.onload = function() {
-  const {DocumentWalker: _documentWalker} =
-    require("devtools/server/actors/inspector/document-walker");
-
-  const nodeFilterConstants =
-    require("devtools/shared/dom-node-filter-constants");
-  const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
-  SimpleTest.waitForExplicitFinish();
-
-  let gWalker = null;
-  let gInspectee = null;
-
-  addTest(async function setup() {
-    info("Setting up inspector and walker actors.");
-
-    const url = document.getElementById("inspectorContent").href;
-    const { target, doc } = await attachURL(url);
-    gInspectee = doc;
-    const inspector = await target.getInspector();
-    gWalker = inspector.walker;
-    runNextTest();
-  });
-
-  addAsyncTest(async function testXBLAnonymousInHTMLDocument() {
-    info("Testing XBL anonymous in an HTML document.");
-    const rawToolbarbutton = gInspectee.createElementNS(XUL_NS, "toolbarbutton");
-    gInspectee.documentElement.appendChild(rawToolbarbutton);
-
-    const toolbarbutton = await gWalker.querySelector(gWalker.rootNode, "toolbarbutton");
-    const children = await gWalker.children(toolbarbutton);
-
-    is(toolbarbutton.numChildren, 0, "XBL content is not visible in HTML doc");
-    is(children.nodes.length, 0, "XBL content is not returned in HTML doc");
-
-    runNextTest();
-  });
-
-  addAsyncTest(async function testNativeAnonymous() {
-    info("Testing native anonymous content with walker.");
-
-    const select = await gWalker.querySelector(gWalker.rootNode, "select");
-    const children = await gWalker.children(select);
-
-    is(select.numChildren, 2, "No native anon content for form control");
-    is(children.nodes.length, 2, "No native anon content for form control");
-
-    runNextTest();
-  });
-
-  addAsyncTest(async function testNativeAnonymousStartingNode() {
-    info("Tests attaching an element that a walker can't see.");
-
-    const serverWalker = DebuggerServer.searchAllConnectionsForActor(gWalker.actorID);
-    const docwalker = new _documentWalker(
-      gInspectee.querySelector("select"),
-      gInspectee.defaultView,
-      {
-        whatToShow: nodeFilterConstants.SHOW_ALL,
-        filter: () => {
-          return nodeFilterConstants.FILTER_ACCEPT;
-        },
-      }
-    );
-    const scrollbar = docwalker.lastChild();
-    is(scrollbar.tagName, "scrollbar", "An anonymous child has been fetched");
-
-    const node = await serverWalker.attachElement(scrollbar);
-
-    ok(node, "A response has arrived");
-    ok(node.node, "A node is in the response");
-    is(node.node.rawNode.tagName, "SELECT",
-      "The node has changed to a parent that the walker recognizes");
-
-    runNextTest();
-  });
-
-  addAsyncTest(async function testPseudoElements() {
-    info("Testing pseudo elements with walker.");
-
-    // Markup looks like: <div><::before /><span /><::after /></div>
-    const pseudo = await gWalker.querySelector(gWalker.rootNode, "#pseudo");
-    const children = await gWalker.children(pseudo);
-
-    is(pseudo.numChildren, 1, "::before/::after are not counted if there is a child");
-    is(children.nodes.length, 3, "Correct number of children");
-
-    const before = children.nodes[0];
-    ok(before.isAnonymous, "Child is anonymous");
-    ok(!before._form.isXBLAnonymous, "Child is not XBL anonymous");
-    ok(!before._form.isShadowAnonymous, "Child is not shadow anonymous");
-    ok(before._form.isNativeAnonymous, "Child is native anonymous");
-
-    const span = children.nodes[1];
-    ok(!span.isAnonymous, "Child is not anonymous");
-
-    const after = children.nodes[2];
-    ok(after.isAnonymous, "Child is anonymous");
-    ok(!after._form.isXBLAnonymous, "Child is not XBL anonymous");
-    ok(!after._form.isShadowAnonymous, "Child is not shadow anonymous");
-    ok(after._form.isNativeAnonymous, "Child is native anonymous");
-
-    runNextTest();
-  });
-
-  addAsyncTest(async function testEmptyWithPseudo() {
-    info("Testing elements with no childrent, except for pseudos.");
-
-    info("Checking an element whose only child is a pseudo element");
-    const pseudo = await gWalker.querySelector(gWalker.rootNode, "#pseudo-empty");
-    const children = await gWalker.children(pseudo);
-
-    is(pseudo.numChildren, 1,
-       "::before/::after are is counted if there are no other children");
-    is(children.nodes.length, 1, "Correct number of children");
-
-    const before = children.nodes[0];
-    ok(before.isAnonymous, "Child is anonymous");
-    ok(!before._form.isXBLAnonymous, "Child is not XBL anonymous");
-    ok(!before._form.isShadowAnonymous, "Child is not shadow anonymous");
-    ok(before._form.isNativeAnonymous, "Child is native anonymous");
-
-    runNextTest();
-  });
-
-  addAsyncTest(async function testShadowAnonymous() {
-    if (true) {
-      // FIXME(bug 1465114)
-      runNextTest();
-      return;
-    }
-
-    info("Testing shadow DOM content.");
-
-    const shadow = await gWalker.querySelector(gWalker.rootNode, "#shadow");
-    const children = await gWalker.children(shadow);
-
-    is(shadow.numChildren, 3, "Children of the shadow root are counted");
-    is(children.nodes.length, 3, "Children returned from walker");
-
-    const before = children.nodes[0];
-    ok(before.isAnonymous, "Child is anonymous");
-    ok(!before._form.isXBLAnonymous, "Child is not XBL anonymous");
-    ok(!before._form.isShadowAnonymous, "Child is not shadow anonymous");
-    ok(before._form.isNativeAnonymous, "Child is native anonymous");
-
-    // <h3>Shadow <em>DOM</em></h3>
-    const shadowChild1 = children.nodes[1];
-    ok(shadowChild1.isAnonymous, "Child is anonymous");
-    ok(!shadowChild1._form.isXBLAnonymous, "Child is not XBL anonymous");
-    ok(shadowChild1._form.isShadowAnonymous, "Child is shadow anonymous");
-    ok(!shadowChild1._form.isNativeAnonymous, "Child is not native anonymous");
-
-    const shadowSubChildren = await gWalker.children(children.nodes[1]);
-    is(shadowChild1.numChildren, 2, "Subchildren of the shadow root are counted");
-    is(shadowSubChildren.nodes.length, 2, "Subchildren are returned from walker");
-
-    // <em>DOM</em>
-    const shadowSubChild = children.nodes[1];
-    ok(shadowSubChild.isAnonymous, "Child is anonymous");
-    ok(!shadowSubChild._form.isXBLAnonymous, "Child is not XBL anonymous");
-    ok(shadowSubChild._form.isShadowAnonymous, "Child is shadow anonymous");
-    ok(!shadowSubChild._form.isNativeAnonymous, "Child is not native anonymous");
-
-    // <select multiple></select>
-    const shadowChild2 = children.nodes[2];
-    ok(shadowChild2.isAnonymous, "Child is anonymous");
-    ok(!shadowChild2._form.isXBLAnonymous, "Child is not XBL anonymous");
-    ok(shadowChild2._form.isShadowAnonymous, "Child is shadow anonymous");
-    ok(!shadowChild2._form.isNativeAnonymous, "Child is not native anonymous");
-
-    runNextTest();
-  });
-
-  runNextTest();
-};
-  </script>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
-<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/devtools/server/tests/mochitest/test_inspector-insert.html
+++ /dev/null
@@ -1,114 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test for Bug </title>
-
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
-  <script type="application/javascript" src="inspector-helpers.js"></script>
-  <script type="application/javascript">
-"use strict";
-
-const {DocumentWalker} = require("devtools/server/actors/inspector/document-walker");
-
-window.onload = function() {
-  SimpleTest.waitForExplicitFinish();
-  runNextTest();
-};
-
-let gWalker = null;
-let gInspectee = null;
-
-addTest(async function setup() {
-  const url = document.getElementById("inspectorContent").href;
-  const { target, doc } = await attachURL(url);
-  gInspectee = doc;
-  const inspector = await target.getInspector();
-  gWalker = inspector.walker;
-  runNextTest();
-});
-
-addAsyncTest(async function testRearrange() {
-  const longlist = await gWalker.querySelector(gWalker.rootNode, "#longlist");
-  let children = await gWalker.children(longlist);
-  const nodeA = children.nodes[0];
-  is(nodeA.id, "a", "Got the expected node.");
-
-  // Move nodeA to the end of the list.
-  await gWalker.insertBefore(nodeA, longlist, null);
-  ok(!gInspectee.querySelector("#a").nextSibling,
-     "a should now be at the end of the list.");
-  children = await gWalker.children(longlist);
-  is(nodeA, children.nodes[children.nodes.length - 1],
-     "a should now be the last returned child.");
-
-  // Now move it to the middle of the list.
-  const nextNode = children.nodes[13];
-  await gWalker.insertBefore(nodeA, longlist, nextNode);
-  const sibling =
-    new DocumentWalker(gInspectee.querySelector("#a"), window).nextSibling();
-  is(sibling, nextNode.rawNode(), "Node should match the expected next node.");
-  children = await gWalker.children(longlist);
-  is(nodeA, children.nodes[13], "a should be where we expect it.");
-  is(nextNode, children.nodes[14], "next node should be where we expect it.");
-
-  runNextTest();
-});
-
-addAsyncTest(async function testInsertInvalidInput() {
-  const longlist = await gWalker.querySelector(gWalker.rootNode, "#longlist");
-  const children = await gWalker.children(longlist);
-  const nodeA = children.nodes[0];
-  const nextSibling = children.nodes[1];
-
-  // Now move it to the original location and make sure no mutation happens.
-  let hasMutated = false;
-  const observer = new gInspectee.defaultView.MutationObserver(() => {
-    hasMutated = true;
-  });
-  observer.observe(longlist.rawNode(), {
-    childList: true,
-  });
-
-  await gWalker.insertBefore(nodeA, longlist, nodeA);
-  ok(!hasMutated, "hasn't mutated");
-  hasMutated = false;
-
-  await gWalker.insertBefore(nodeA, longlist, nextSibling);
-  ok(!hasMutated, "still hasn't mutated after inserting before nextSibling");
-  hasMutated = false;
-
-  await gWalker.insertBefore(nodeA, longlist);
-  ok(hasMutated, "has mutated after inserting with null sibling");
-  hasMutated = false;
-
-  await gWalker.insertBefore(nodeA, longlist);
-  ok(!hasMutated, "hasn't mutated after inserting with null sibling again");
-
-  observer.disconnect();
-  runNextTest();
-});
-
-addTest(function cleanup() {
-  gWalker = null;
-  gInspectee = null;
-  runNextTest();
-});
-
-  </script>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
-<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/devtools/server/tests/mochitest/test_inspector-mutations-frameload.html
+++ /dev/null
@@ -1,198 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test for Bug </title>
-
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
-  <script type="application/javascript" src="inspector-helpers.js"></script>
-  <script type="application/javascript">
-"use strict";
-
-window.onload = function() {
-  SimpleTest.waitForExplicitFinish();
-  runNextTest();
-};
-
-let gInspectee = null;
-let gWalker = null;
-let gTarget = null;
-
-async function setup(callback) {
-  const url = document.getElementById("inspectorContent").href;
-  const { target, doc } = await attachURL(url);
-  gInspectee = doc;
-  gTarget = target;
-  const inspector = await target.getInspector();
-  gWalker = inspector.walker;
-  await callback();
-}
-
-function teardown() {
-  gWalker = null;
-  gInspectee = null;
-}
-
-function assertOwnership() {
-  return assertOwnershipTrees(gWalker);
-}
-
-function loadChildSelector(selector) {
-  return gWalker.querySelector(gWalker.rootNode, "#childFrame").then(frame => {
-    ok(frame.numChildren > 0,
-       "Child frame should consider its loaded document as a child.");
-    return gWalker.children(frame);
-  }).then(children => {
-    return gWalker.querySelectorAll(children.nodes[0], selector);
-  }).then(nodeList => {
-    return nodeList.items();
-  });
-}
-
-function getUnloadedDoc(mutations) {
-  for (const change of mutations) {
-    if (isUnload(change)) {
-      return change.target;
-    }
-  }
-  return null;
-}
-
-addTest(function loadNewChild() {
-  setup(() => {
-    // Load a bunch of fronts for actors inside the child frame.
-    promiseDone(loadChildSelector("#longlist div").then(() => {
-      const childFrame = gInspectee.querySelector("#childFrame");
-      childFrame.src = "data:text/html,<html>new child</html>";
-      return waitForMutation(gWalker, isChildList);
-    }).then(mutations => {
-      const unloaded = getUnloadedDoc(mutations);
-      mutations = assertSrcChange(mutations);
-      mutations = assertUnload(mutations);
-      mutations = assertFrameLoad(mutations);
-      mutations = assertChildList(mutations);
-
-      is(mutations.length, 0, "Got the expected mutations.");
-
-      assertOwnership();
-
-      return checkMissing(gTarget, unloaded);
-    }).then(() => {
-      teardown();
-    }).then(runNextTest));
-  });
-});
-
-addTest(function loadNewChildTwice() {
-  setup(() => {
-    // Load a bunch of fronts for actors inside the child frame.
-    promiseDone(loadChildSelector("#longlist div").then(() => {
-      const childFrame = gInspectee.querySelector("#childFrame");
-      childFrame.src = "data:text/html,<html>new child</html>";
-      return waitForMutation(gWalker, isChildList);
-    }).then(mutations => {
-      // The first load went through as expected (as tested in loadNewChild)
-      // Now change the source again, but this time we *don't* expect
-      // an unload, because we haven't seen the new child document yet.
-      const childFrame = gInspectee.querySelector("#childFrame");
-      childFrame.src = "data:text/html,<html>second new child</html>";
-      return waitForMutation(gWalker, isChildList);
-    }).then(mutations => {
-      mutations = assertSrcChange(mutations);
-      mutations = assertFrameLoad(mutations);
-      mutations = assertChildList(mutations);
-      ok(!getUnloadedDoc(mutations), "Should not have gotten an unload.");
-
-      is(mutations.length, 0, "Got the expected mutations.");
-
-      assertOwnership();
-    }).then(() => {
-      teardown();
-    }).then(runNextTest));
-  });
-});
-
-addTest(function loadNewChildTwiceAndCareAboutIt() {
-  setup(() => {
-    // Load a bunch of fronts for actors inside the child frame.
-    promiseDone(loadChildSelector("#longlist div").then(() => {
-      const childFrame = gInspectee.querySelector("#childFrame");
-      childFrame.src = "data:text/html,<html>new child</html>";
-      return waitForMutation(gWalker, isChildList);
-    }).then(mutations => {
-      // Read the new child
-      return loadChildSelector("#longlist div");
-    }).then(() => {
-      // Now change the source again, and expect the same results as loadNewChild.
-      const childFrame = gInspectee.querySelector("#childFrame");
-      childFrame.src = "data:text/html,<html>second new child</html>";
-      return waitForMutation(gWalker, isChildList);
-    }).then(mutations => {
-      const unloaded = getUnloadedDoc(mutations);
-
-      mutations = assertSrcChange(mutations);
-      mutations = assertUnload(mutations);
-      mutations = assertFrameLoad(mutations);
-      mutations = assertChildList(mutations);
-
-      is(mutations.length, 0, "Got the expected mutations.");
-
-      assertOwnership();
-
-      return checkMissing(gTarget, unloaded);
-    }).then(() => {
-      teardown();
-    }).then(runNextTest));
-  });
-});
-
-addTest(function testBack() {
-  setup(() => {
-    // Load a bunch of fronts for actors inside the child frame.
-    promiseDone(loadChildSelector("#longlist div").then(() => {
-      const childFrame = gInspectee.querySelector("#childFrame");
-      childFrame.src = "data:text/html,<html>new child</html>";
-      return waitForMutation(gWalker, isChildList);
-    }).then(mutations => {
-      // Read the new child
-      return loadChildSelector("#longlist div");
-    }).then(() => {
-      // Now use history.back to change the source,
-      // and expect the same results as loadNewChild.
-      const childFrame = gInspectee.querySelector("#childFrame");
-      childFrame.contentWindow.history.back();
-      return waitForMutation(gWalker, isChildList);
-    }).then(mutations => {
-      const unloaded = getUnloadedDoc(mutations);
-      mutations = assertSrcChange(mutations);
-      mutations = assertUnload(mutations);
-      mutations = assertFrameLoad(mutations);
-      mutations = assertChildList(mutations);
-      is(mutations.length, 0, "Got the expected mutations.");
-
-      assertOwnership();
-
-      return checkMissing(gTarget, unloaded);
-    }).then(() => {
-      teardown();
-    }).then(runNextTest));
-  });
-});
-
-  </script>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
-<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/devtools/server/tests/mochitest/test_inspector-release.html
+++ /dev/null
@@ -1,98 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test for Bug </title>
-
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
-  <script type="application/javascript" src="inspector-helpers.js"></script>
-  <script type="application/javascript">
-"use strict";
-
-window.onload = function() {
-  SimpleTest.waitForExplicitFinish();
-  runNextTest();
-};
-
-let gWalker = null;
-let gTarget = null;
-
-function assertOwnership() {
-  return assertOwnershipTrees(gWalker);
-}
-
-addTest(async function setup() {
-  const url = document.getElementById("inspectorContent").href;
-  const { target } = await attachURL(url);
-  const inspector = await target.getInspector();
-  gTarget = target;
-  gWalker = inspector.walker;
-  runNextTest();
-});
-
-addTest(function testReleaseSubtree() {
-  let originalOwnershipSize = 0;
-  let longlist = null;
-  let firstChild = null;
-  promiseDone(gWalker.querySelectorAll(gWalker.rootNode, "#longlist div").then(list => {
-    // Make sure we have the 26 children of longlist in our ownership tree.
-    is(list.length, 26, "Expect 26 div children.");
-    // Make sure we've read in all those children and incorporated them
-    // in our ownership tree.
-    return list.items();
-  }).then((items)=> {
-    originalOwnershipSize = assertOwnership();
-
-    // Here is how the ownership tree is summed up:
-    // #document                      1
-    //   <html>                       1
-    //     <body>                     1
-    //       <div id=longlist>        1
-    //         <div id=a>a</div>   26*2 (each child plus it's singleTextChild)
-    //         ...
-    //         <div id=z>z</div>
-    //                             -----
-    //                               56
-    is(originalOwnershipSize, 56, "Correct number of items in ownership tree");
-    firstChild = items[0].actorID;
-  }).then(() => {
-    // Now get the longlist and release it from the ownership tree.
-    return gWalker.querySelector(gWalker.rootNode, "#longlist");
-  }).then(node => {
-    longlist = node.actorID;
-    return gWalker.releaseNode(node);
-  }).then(() => {
-    // Our ownership size should now be 53 fewer
-    // (we forgot about #longlist + 26 children + 26 singleTextChild nodes)
-    const newOwnershipSize = assertOwnership();
-    is(newOwnershipSize, originalOwnershipSize - 53,
-      "Ownership tree should be lower");
-    // Now verify that some nodes have gone away
-    return checkMissing(gTarget, longlist);
-  }).then(() => {
-    return checkMissing(gTarget, firstChild);
-  }).then(runNextTest));
-});
-
-addTest(function cleanup() {
-  gWalker = null;
-  gTarget = null;
-  runNextTest();
-});
-  </script>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
-<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/devtools/server/tests/mochitest/test_inspector-remove.html
+++ /dev/null
@@ -1,115 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test for Bug </title>
-
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
-  <script type="application/javascript" src="inspector-helpers.js"></script>
-  <script type="application/javascript">
-"use strict";
-
-window.onload = function() {
-  SimpleTest.waitForExplicitFinish();
-  runNextTest();
-};
-
-let gWalker = null;
-let gTarget = null;
-let gInspectee = null;
-
-function assertOwnership() {
-  return assertOwnershipTrees(gWalker);
-}
-
-function ignoreNode(node) {
-  // Duplicate the walker logic to skip blank nodes...
-  return node.nodeType === Node.TEXT_NODE &&
-    !/[^\s]/.test(node.nodeValue);
-}
-
-addTest(async function setup() {
-  const url = document.getElementById("inspectorContent").href;
-  const { target, doc } = await attachURL(url);
-  gInspectee = doc;
-  const inspector = await target.getInspector();
-  gWalker = inspector.walker;
-  gTarget = target;
-  runNextTest();
-});
-
-addTest(function testRemoveSubtree() {
-  let originalOwnershipSize = 0;
-  let longlist = null;
-  let longlistID = null;
-
-  let nextSibling = gInspectee.querySelector("#longlist").nextSibling;
-  while (nextSibling && ignoreNode(nextSibling)) {
-    nextSibling = nextSibling.nextSibling;
-  }
-
-  let previousSibling = gInspectee.querySelector("#longlist").previousSibling;
-  while (previousSibling && ignoreNode(previousSibling)) {
-    previousSibling = previousSibling.previousSibling;
-  }
-
-  promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(listFront => {
-    longlist = listFront;
-    longlistID = longlist.actorID;
-  }).then(() => {
-    return gWalker.children(longlist);
-  }).then((items)=> {
-    originalOwnershipSize = assertOwnership();
-    // Here is how the ownership tree is summed up:
-    // #document                      1
-    //   <html>                       1
-    //     <body>                     1
-    //       <div id=longlist>        1
-    //         <div id=a>a</div>   26*2 (each child plus it's singleTextChild)
-    //         ...
-    //         <div id=z>z</div>
-    //                             -----
-    //                               56
-    is(originalOwnershipSize, 56, "Correct number of items in ownership tree");
-    return gWalker.removeNode(longlist);
-  }).then(siblings => {
-    is(siblings.previousSibling.rawNode(), previousSibling,
-       "Should have returned the previous sibling.");
-    is(siblings.nextSibling.rawNode(), nextSibling,
-       "Should have returned the next sibling.");
-    return waitForMutation(gWalker, isChildList);
-  }).then(() => {
-    // Our ownership size should now be 51 fewer (we forgot about #longlist + 26
-    // children + 26 singleTextChild nodes, but learned about #longlist's
-    // prev/next sibling)
-    const newOwnershipSize = assertOwnership();
-    is(newOwnershipSize, originalOwnershipSize - 51,
-      "Ownership tree should be lower");
-    // Now verify that some nodes have gone away
-    return checkMissing(gTarget, longlistID);
-  }).then(runNextTest));
-});
-
-addTest(function cleanup() {
-  gWalker = null;
-  gTarget = null;
-  gInspectee = null;
-  runNextTest();
-});
-  </script>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
-<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/devtools/server/tests/mochitest/test_inspector-retain.html
+++ /dev/null
@@ -1,174 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test for Bug </title>
-
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
-  <script type="application/javascript" src="inspector-helpers.js"></script>
-  <script type="application/javascript">
-"use strict";
-
-window.onload = function() {
-  SimpleTest.waitForExplicitFinish();
-  runNextTest();
-};
-
-let gWalker = null;
-let gInspectee = null;
-
-function assertOwnership() {
-  return assertOwnershipTrees(gWalker);
-}
-
-addTest(async function setup() {
-  const url = document.getElementById("inspectorContent").href;
-  const { target, doc } = await attachURL(url);
-  const inspector = await target.getInspector();
-  gInspectee = doc;
-  gWalker = inspector.walker;
-  runNextTest();
-});
-
-// Retain a node, and a second-order child (in another document, for kicks)
-// Release the parent of the top item, which should cause one retained orphan.
-
-// Then unretain the top node, which should retain the orphan.
-
-// Then change the source of the iframe, which should kill that orphan.
-
-addTest(function testRetain() {
-  let originalOwnershipSize = 0;
-  let bodyFront = null;
-  let childListFront = null;
-  // Get the toplevel body element and retain it.
-  promiseDone(gWalker.querySelector(gWalker.rootNode, "body").then(front => {
-    bodyFront = front;
-    return gWalker.retainNode(bodyFront);
-  }).then(() => {
-    // Get an element in the child frame and retain it.
-    return gWalker.querySelector(gWalker.rootNode, "#childFrame");
-  }).then(frame => {
-    return gWalker.children(frame, { maxNodes: 1 }).then(children => {
-      return children.nodes[0];
-    });
-  }).then(childDoc => {
-    return gWalker.querySelector(childDoc, "#longlist");
-  }).then(list => {
-    childListFront = list;
-    originalOwnershipSize = assertOwnership();
-    // and rtain it.
-    return gWalker.retainNode(childListFront);
-  }).then(() => {
-    // OK, try releasing the parent of the first retained.
-    return gWalker.releaseNode(bodyFront.parentNode());
-  }).then(() => {
-    const clientTree = clientOwnershipTree(gWalker);
-
-    // That request should have freed the parent of the first retained
-    // but moved the rest into the retained orphaned tree.
-    is(ownershipTreeSize(clientTree.root) + ownershipTreeSize(clientTree.retained[0]) + 1,
-       originalOwnershipSize,
-       "Should have only lost one item overall.");
-    is(gWalker._retainedOrphans.size, 1, "Should have retained one orphan");
-    ok(gWalker._retainedOrphans.has(bodyFront),
-       "Should have retained the expected node.");
-  }).then(() => {
-    // Unretain the body, which should promote the childListFront to a retained orphan.
-    return gWalker.unretainNode(bodyFront);
-  }).then(() => {
-    assertOwnership();
-
-    is(gWalker._retainedOrphans.size, 1, "Should still only have one retained orphan.");
-    ok(!gWalker._retainedOrphans.has(bodyFront), "Should have dropped the body node.");
-    ok(gWalker._retainedOrphans.has(childListFront),
-       "Should have retained the child node.");
-  }).then(() => {
-    // Change the source of the iframe, which should kill the retained orphan.
-    gInspectee.querySelector("#childFrame").src = "data:text/html,<html>new child</html>";
-    return waitForMutation(gWalker, isUnretained);
-  }).then(mutations => {
-    assertOwnership();
-    is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
-  }).then(runNextTest));
-});
-
-// Get a hold of a node, remove it from the doc and retain it at the same time.
-// We should always win that race (even though the mutation happens before the
-// retain request), because we haven't issued `getMutations` yet.
-addTest(function testWinRace() {
-  let front = null;
-  promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(node => {
-    front = node;
-    const contentNode = gInspectee.querySelector("#a");
-    contentNode.remove();
-    // Now wait for that mutation and retain response to come in.
-    return Promise.all([
-      gWalker.retainNode(front),
-      waitForMutation(gWalker, isChildList),
-    ]);
-  }).then(() => {
-    assertOwnership();
-    is(gWalker._retainedOrphans.size, 1, "Should have a retained orphan.");
-    ok(gWalker._retainedOrphans.has(front), "Should have retained our expected node.");
-    return gWalker.unretainNode(front);
-  }).then(() => {
-    // Make sure we're clear for the next test.
-    assertOwnership();
-    is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
-  }).then(runNextTest));
-});
-
-// Same as above, but issue the request right after the 'new-mutations' event, so that
-// we *lose* the race.
-addTest(function testLoseRace() {
-  let front = null;
-  promiseDone(gWalker.querySelector(gWalker.rootNode, "#z").then(node => {
-    front = node;
-    const contentNode = gInspectee.querySelector("#z");
-    contentNode.remove();
-    return promiseOnce(gWalker, "new-mutations");
-  }).then(() => {
-    // Verify that we have an outstanding request (no good way to tell that it's a
-    // getMutations request, but there's nothing else it would be).
-    is(gWalker._requests.length, 1, "Should have an outstanding request.");
-    return gWalker.retainNode(front);
-  }).then(() => ok(false, "Request should not have succeeded!"),
-          (err) => {
-            // XXX: Switched to from ok() to todo_is() in Bug 1467712. Follow up in
-            // 1500960
-            // This is throwing because of
-            // `gInspectee.querySelector("#z").parentNode = null;` two blocks above...
-            // Even if you fix that, the test is still failing because "#a" was removed
-            // by the previous test. I am switching this to "#z" because I think that
-            // was the original intent. Still not failing with the expected error message
-            // Needs more work.
-            // ok(err, "noSuchActor", "Should have lost the race.");
-            is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
-            // Don't re-throw the error.
-          }).then(runNextTest));
-});
-
-addTest(function cleanup() {
-  gWalker = null;
-  gInspectee = null;
-  runNextTest();
-});
-
-  </script>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
-<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/devtools/server/tests/mochitest/test_inspector-search.html
+++ /dev/null
@@ -1,292 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=835896
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test for Bug 835896</title>
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
-  <script type="application/javascript" src="inspector-helpers.js"></script>
-  <script type="application/javascript">
-"use strict";
-
-window.onload = function() {
-  const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
-  const {WalkerSearch, WalkerIndex} =
-    require("devtools/server/actors/utils/walker-search");
-
-  SimpleTest.waitForExplicitFinish();
-
-  let walkerActor = null;
-  let walkerSearch = null;
-  let inspectee = null;
-  let inspector = null;
-
-  // WalkerSearch specific tests.  This is to make sure search results are
-  // coming back as expected.
-  // See also test_inspector-search-front.html.
-
-  addAsyncTest(async function setup() {
-    info("Setting up inspector and walker actors.");
-
-    const url = document.getElementById("inspectorContent").href;
-
-    const { target, doc } = await attachURL(url);
-    inspectee = doc;
-    inspector = await target.getInspector();
-
-    const walkerFront = inspector.walker;
-
-    walkerActor = DebuggerServer.searchAllConnectionsForActor(walkerFront.actorID);
-    ok(walkerActor,
-      "Got a reference to the walker actor (" + walkerFront.actorID + ")");
-
-    walkerSearch = walkerActor.walkerSearch;
-
-    runNextTest();
-  });
-
-  addAsyncTest(function testIndexExists() {
-    info("Testing basic index APIs exist.");
-
-    const index = new WalkerIndex(walkerActor);
-    ok(index.data.size > 0, "public index is filled after getting");
-
-    index.clearIndex();
-    ok(!index._data, "private index is empty after clearing");
-    ok(index.data.size > 0, "public index is filled after getting");
-
-    index.destroy();
-    runNextTest();
-  });
-
-  addAsyncTest(function testSearchExists() {
-    info("Testing basic search APIs exist.");
-
-    ok(walkerSearch, "walker search exists on the WalkerActor");
-    ok(walkerSearch.search, "walker search has `search` method");
-    ok(walkerSearch.index, "walker search has `index` property");
-    is(walkerSearch.walker, walkerActor, "referencing the correct WalkerActor");
-
-    const search = new WalkerSearch(walkerActor);
-    ok(search, "a new search instance can be created");
-    ok(search.search, "new search instance has `search` method");
-    ok(search.index, "new search instance has `index` property");
-    isnot(search, walkerSearch, "new search instance differs from the WalkerActor's");
-
-    search.destroy();
-    runNextTest();
-  });
-
-  addAsyncTest(function testEmptySearch() {
-    info("Testing search with an empty query.");
-    let results = walkerSearch.search("");
-    is(results.length, 0, "No results when searching for ''");
-
-    results = walkerSearch.search(null);
-    is(results.length, 0, "No results when searching for null");
-
-    results = walkerSearch.search(undefined);
-    is(results.length, 0, "No results when searching for undefined");
-
-    results = walkerSearch.search(10);
-    is(results.length, 0, "No results when searching for 10");
-
-    runNextTest();
-  });
-
-  addAsyncTest(function testBasicSearchData() {
-    const testData = [
-    {
-      desc: "Search for tag with one result.",
-      search: "body",
-      expected: [
-        {node: inspectee.body, type: "tag"},
-      ],
-    },
-    {
-      desc: "Search for tag with multiple results",
-      search: "h2",
-      expected: [
-        {node: inspectee.querySelectorAll("h2")[0], type: "tag"},
-        {node: inspectee.querySelectorAll("h2")[1], type: "tag"},
-        {node: inspectee.querySelectorAll("h2")[2], type: "tag"},
-      ],
-    },
-    {
-      desc: "Search for selector with multiple results",
-      search: "body > h2",
-      expected: [
-        {node: inspectee.querySelectorAll("h2")[0], type: "selector"},
-        {node: inspectee.querySelectorAll("h2")[1], type: "selector"},
-        {node: inspectee.querySelectorAll("h2")[2], type: "selector"},
-      ],
-    },
-    {
-      desc: "Search for selector with multiple results",
-      search: ":root h2",
-      expected: [
-        {node: inspectee.querySelectorAll("h2")[0], type: "selector"},
-        {node: inspectee.querySelectorAll("h2")[1], type: "selector"},
-        {node: inspectee.querySelectorAll("h2")[2], type: "selector"},
-      ],
-    },
-    {
-      desc: "Search for selector with multiple results",
-      search: "* h2",
-      expected: [
-        {node: inspectee.querySelectorAll("h2")[0], type: "selector"},
-        {node: inspectee.querySelectorAll("h2")[1], type: "selector"},
-        {node: inspectee.querySelectorAll("h2")[2], type: "selector"},
-      ],
-    },
-    {
-      desc: "Search with multiple matches in a single tag expecting a single result",
-      search: "💩",
-      expected: [
-        {node: inspectee.getElementById("💩"), type: "attributeValue"},
-      ],
-    },
-    {
-      desc: "Search that has tag and text results",
-      search: "h1",
-      expected: [
-        {node: inspectee.querySelector("h1"), type: "tag"},
-        {node: inspectee.querySelector("h1 + p").childNodes[0], type: "text"},
-        {node: inspectee.querySelector("h1 + p > strong").childNodes[0], type: "text"},
-      ],
-    },
-    ];
-
-    for (const {desc, search, expected} of testData) {
-      info("Running test: " + desc);
-      const results = walkerSearch.search(search);
-      isDeeply(results, expected,
-        "Search returns correct results with '" + search + "'");
-    }
-
-    runNextTest();
-  });
-
-  addAsyncTest(function testPseudoElements() {
-    info("Testing ::before and ::after element matching");
-
-    const beforeElt = new _documentWalker(inspectee.querySelector("#pseudo"),
-                                        inspectee.defaultView).firstChild();
-    const afterElt = new _documentWalker(inspectee.querySelector("#pseudo"),
-                                       inspectee.defaultView).lastChild();
-    const styleText = inspectee.querySelector("style").childNodes[0];
-
-    // ::before
-    let results = walkerSearch.search("::before");
-    isDeeply(results, [ {node: beforeElt, type: "tag"} ],
-             "Tag search works for pseudo element");
-
-    results = walkerSearch.search("_moz_generated_content_before");
-    is(results.length, 0, "No results for anon tag name");
-
-    results = walkerSearch.search("before element");
-    isDeeply(results, [
-      {node: styleText, type: "text"},
-      {node: beforeElt, type: "text"},
-    ], "Text search works for pseudo element");
-
-    // ::after
-    results = walkerSearch.search("::after");
-    isDeeply(results, [ {node: afterElt, type: "tag"} ],
-             "Tag search works for pseudo element");
-
-    results = walkerSearch.search("_moz_generated_content_after");
-    is(results.length, 0, "No results for anon tag name");
-
-    results = walkerSearch.search("after element");
-    isDeeply(results, [
-      {node: styleText, type: "text"},
-      {node: afterElt, type: "text"},
-    ], "Text search works for pseudo element");
-
-    runNextTest();
-  });
-
-  addAsyncTest(async function testSearchMutationChangeResults() {
-    info("Testing search before and after a mutation.");
-    const expected = [
-      {node: inspectee.querySelectorAll("h3")[0], type: "tag"},
-      {node: inspectee.querySelectorAll("h3")[1], type: "tag"},
-      {node: inspectee.querySelectorAll("h3")[2], type: "tag"},
-    ];
-
-    let results = walkerSearch.search("h3");
-    isDeeply(results, expected, "Search works with tag results");
-
-    await mutateDocumentAndWaitForMutation(() => {
-      expected[0].node.remove();
-    });
-
-    results = walkerSearch.search("h3");
-    isDeeply(results, [
-      expected[1],
-      expected[2],
-    ], "Results are updated after removal");
-
-    // eslint-disable-next-line new-cap
-    await new Promise(resolve => {
-      info("Waiting for a mutation to happen");
-      const observer = new inspectee.defaultView.MutationObserver(() => {
-        resolve();
-      });
-      observer.observe(inspectee, {attributes: true, subtree: true});
-      inspectee.body.setAttribute("h3", "true");
-    });
-
-    results = walkerSearch.search("h3");
-    isDeeply(results, [
-      {node: inspectee.body, type: "attributeName"},
-      expected[1],
-      expected[2],
-    ], "Results are updated after addition");
-
-    // eslint-disable-next-line new-cap
-    await new Promise(resolve => {
-      info("Waiting for a mutation to happen");
-      const observer = new inspectee.defaultView.MutationObserver(() => {
-        resolve();
-      });
-      observer.observe(inspectee, {attributes: true, childList: true, subtree: true});
-      inspectee.body.removeAttribute("h3");
-      expected[1].node.remove();
-      expected[2].node.remove();
-    });
-
-    results = walkerSearch.search("h3");
-    is(results.length, 0, "Results are updated after removal");
-
-    runNextTest();
-  });
-
-  runNextTest();
-
-  function mutateDocumentAndWaitForMutation(mutationFn) {
-    // eslint-disable-next-line new-cap
-    return new Promise(resolve => {
-      info("Listening to markup mutation on the inspectee");
-      const observer = new inspectee.defaultView.MutationObserver(resolve);
-      observer.observe(inspectee, {childList: true, subtree: true});
-      mutationFn();
-    });
-  }
-};
-  </script>
-</head>
-<body>
-<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/devtools/server/tests/mochitest/test_inspector-traversal.html
+++ /dev/null
@@ -1,335 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test for Bug </title>
-
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
-  <script type="application/javascript" src="inspector-helpers.js"></script>
-  <script type="application/javascript">
-"use strict";
-
-window.onload = function() {
-  SimpleTest.waitForExplicitFinish();
-  runNextTest();
-};
-
-let gInspectee = null;
-let gTarget = null;
-let gWalker = null;
-const checkActorIDs = [];
-
-function assertOwnership() {
-  assertOwnershipTrees(gWalker);
-}
-addTest(async function setup() {
-  const url = document.getElementById("inspectorContent").href;
-  const { target, doc } = await attachURL(url);
-  const inspector = await target.getInspector();
-  gInspectee = doc;
-  gTarget = target;
-  gWalker = inspector.walker;
-  runNextTest();
-});
-
-addTest(function testWalkerRoot() {
-  // Make sure that refetching the root document of the walker returns the same
-  // actor as the getWalker returned.
-  promiseDone(gWalker.document().then(root => {
-    ok(root === gWalker.rootNode,
-       "Re-fetching the document node should match the root document node.");
-    checkActorIDs.push(root.actorID);
-    assertOwnership();
-  }).then(runNextTest));
-});
-
-addTest(function testInnerHTML() {
-  promiseDone(gWalker.documentElement().then(docElement => {
-    return gWalker.innerHTML(docElement);
-  }).then(longstring => {
-    return longstring.string();
-  }).then(innerHTML => {
-    ok(innerHTML === gInspectee.documentElement.innerHTML, "innerHTML should match");
-  }).then(runNextTest));
-});
-
-addTest(function testOuterHTML() {
-  promiseDone(gWalker.documentElement().then(docElement => {
-    return gWalker.outerHTML(docElement);
-  }).then(longstring => {
-    return longstring.string();
-  }).then(outerHTML => {
-    ok(outerHTML === gInspectee.documentElement.outerHTML, "outerHTML should match");
-  }).then(runNextTest));
-});
-
-addTest(function testSetOuterHTMLNode() {
-  const newHTML = "<p id=\"edit-html-done\">after edit</p>";
-  promiseDone(gWalker.querySelector(gWalker.rootNode, "#edit-html").then(node => {
-    return gWalker.setOuterHTML(node, newHTML);
-  }).then(() => {
-    return gWalker.querySelector(gWalker.rootNode, "#edit-html-done");
-  }).then(node => {
-    return gWalker.outerHTML(node);
-  }).then(longstring => {
-    return longstring.string();
-  }).then(outerHTML => {
-    is(outerHTML, newHTML, "outerHTML has been updated");
-  }).then(() => {
-    return gWalker.querySelector(gWalker.rootNode, "#edit-html");
-  }).then(node => {
-    ok(!node, "The node with the old ID cannot be selected anymore");
-  }).then(runNextTest));
-});
-
-addTest(function testQuerySelector() {
-  promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(node => {
-    is(node.getAttribute("data-test"), "exists", "should have found the right node");
-    assertOwnership();
-  }).then(() => {
-    return gWalker.querySelector(gWalker.rootNode, "unknownqueryselector").then(node => {
-      ok(!node, "Should not find a node here.");
-      assertOwnership();
-    });
-  }).then(runNextTest));
-});
-
-addTest(function testQuerySelectors() {
-  let nodeList = null;
-  let firstNode = null;
-  let nodeListID = null;
-  promiseDone(gWalker.querySelectorAll(gWalker.rootNode, "#longlist div").then(list => {
-    nodeList = list;
-    is(nodeList.length, 26, "Expect 26 div children.");
-    assertOwnership();
-    return nodeList.item(0);
-  }).then(node => {
-    firstNode = node;
-    checkActorIDs.push(node.actorID);
-    is(node.id, "a", "First child should be a");
-    assertOwnership();
-    return nodeList.items();
-  }).then(nodes => {
-    is(nodes.length, 26, "Expect 26 nodes");
-    is(nodes[0], firstNode, "First node should be reused.");
-    ok(nodes[0]._parent, "Parent node should be set.");
-    ok(nodes[0]._next || nodes[0]._prev, "Siblings should be set.");
-    ok(nodes[25]._next || nodes[25]._prev,
-       "Siblings of " + nodes[25] + " should be set.");
-    assertOwnership();
-    return nodeList.items(-1);
-  }).then(nodes => {
-    is(nodes.length, 1, "Expect 1 node");
-    is(nodes[0].id, "z", "Expect it to be the last node.");
-    checkActorIDs.push(nodes[0].actorID);
-    // Save the node list ID so we can ensure it was destroyed.
-    nodeListID = nodeList.actorID;
-    assertOwnership();
-    return nodeList.release();
-  }).then(() => {
-    ok(!nodeList.actorID, "Actor should have been destroyed.");
-    assertOwnership();
-    return checkMissing(gTarget, nodeListID);
-  }).then(runNextTest));
-});
-
-// Helper to check the response of requests that return hasFirst/hasLast/nodes
-// node lists (like `children` and `siblings`)
-function nodeArrayChecker(first, last, ids) {
-  return function(response) {
-    is(response.hasFirst, first,
-       "Should " + (first ? "" : "not ") + " have the first node.");
-    is(response.hasLast, last, "Should " + (last ? "" : "not ") + " have the last node.");
-    is(response.nodes.length, ids.length,
-       "Should have " + ids.length + " children listed.");
-    let responseIds = "";
-    for (const node of response.nodes) {
-      responseIds += node.id;
-    }
-    is(responseIds, ids, "Correct nodes were returned.");
-    assertOwnership();
-  };
-}
-
-addTest(function testNoChildren() {
-  promiseDone(gWalker.querySelector(gWalker.rootNode, "#empty").then(empty => {
-    assertOwnership();
-    return gWalker.children(empty).then(nodeArrayChecker(true, true, ""));
-  }).then(runNextTest));
-});
-
-addTest(function testLongListTraversal() {
-  let longList;
-  let allChildren;
-  promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(node => {
-    longList = node;
-    // First call with no options, expect all children.
-    assertOwnership();
-    return gWalker.children(longList).then(response => {
-      nodeArrayChecker(true, true, "abcdefghijklmnopqrstuvwxyz")(response);
-      allChildren = response.nodes;
-      assertOwnership();
-    });
-  }).then(() => {
-    // maxNodes should limit us to the first 5 nodes.
-    assertOwnership();
-    return gWalker.children(longList, { maxNodes: 5 })
-           .then(nodeArrayChecker(true, false, "abcde"));
-  }).then(() => {
-    assertOwnership();
-    // maxNodes with the second item centered should still give us the first 5 nodes.
-    return gWalker.children(longList, { maxNodes: 5, center: allChildren[1] }).then(
-      nodeArrayChecker(true, false, "abcde")
-    );
-  }).then(() => {
-    // maxNodes with a center in the middle of the list should put that item in the middle
-    const center = allChildren[13];
-    is(center.id, "n", "Make sure I know how to count letters.");
-    return gWalker.children(longList, { maxNodes: 5, center: center }).then(
-      nodeArrayChecker(false, false, "lmnop")
-    );
-  }).then(() => {
-    // maxNodes with the second-to-last item centered should give us the last 5 nodes.
-    return gWalker.children(longList, { maxNodes: 5, center: allChildren[24] }).then(
-      nodeArrayChecker(false, true, "vwxyz")
-    );
-  }).then(() => {
-    // maxNodes with a start in the middle should start at that node and fetch 5
-    const start = allChildren[13];
-    is(start.id, "n", "Make sure I know how to count letters.");
-    return gWalker.children(longList, { maxNodes: 5, start: start }).then(
-      nodeArrayChecker(false, false, "nopqr")
-    );
-  }).then(() => {
-    // maxNodes near the end should only return what's left
-    return gWalker.children(longList, { maxNodes: 5, start: allChildren[24] }).then(
-      nodeArrayChecker(false, true, "yz")
-    );
-  }).then(runNextTest));
-});
-
-addTest(function testObjectNodeChildren() {
-  promiseDone(
-    gWalker.querySelector(gWalker.rootNode, "object")
-    .then(object => gWalker.children(object))
-    .then(nodeArrayChecker(true, true, "1"))
-    .then(runNextTest));
-});
-
-addTest(function testNextSibling() {
-  promiseDone(gWalker.querySelector(gWalker.rootNode, "#y").then(y => {
-    is(y.id, "y", "Got the right node.");
-    return gWalker.nextSibling(y);
-  }).then(z => {
-    is(z.id, "z", "nextSibling got the next node.");
-    return gWalker.nextSibling(z);
-  }).then(nothing => {
-    is(nothing, null, "nextSibling on the last node returned null.");
-  }).then(runNextTest));
-});
-
-addTest(function testPreviousSibling() {
-  promiseDone(gWalker.querySelector(gWalker.rootNode, "#b").then(b => {
-    is(b.id, "b", "Got the right node.");
-    return gWalker.previousSibling(b);
-  }).then(a => {
-    is(a.id, "a", "nextSibling got the next node.");
-    return gWalker.previousSibling(a);
-  }).then(nothing => {
-    is(nothing, null, "previousSibling on the first node returned null.");
-  }).then(runNextTest));
-});
-
-addTest(function testFrameTraversal() {
-  promiseDone(gWalker.querySelector(gWalker.rootNode, "#childFrame").then(childFrame => {
-    return gWalker.children(childFrame);
-  }).then(children => {
-    const nodes = children.nodes;
-    is(nodes.length, 1, "There should be only one child of the iframe");
-    is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
-    return gWalker.querySelector(nodes[0], "#z");
-  }).then(runNextTest));
-});
-
-addTest(function testLongValue() {
-  const testSummaryLength = 10;
-  const WalkerActor = require("devtools/server/actors/inspector/walker");
-
-  WalkerActor.setValueSummaryLength(testSummaryLength);
-  SimpleTest.registerCleanupFunction(function() {
-    WalkerActor.setValueSummaryLength(WalkerActor.DEFAULT_VALUE_SUMMARY_LENGTH);
-  });
-
-  const longstringText = gInspectee.getElementById("longstring").firstChild.nodeValue;
-
-  promiseDone(gWalker.querySelector(gWalker.rootNode, "#longstring").then(node => {
-    ok(!node.inlineTextChild, "Text is too long to be inlined");
-    // Now we need to get the text node child...
-    return gWalker.children(node, { maxNodes: 1 });
-  }).then(children => {
-    const textNode = children.nodes[0];
-    is(textNode.nodeType, Node.TEXT_NODE, "Value should be a text node");
-    return textNode;
-  }).then(textNode => {
-    return textNode.getNodeValue();
-  }).then(value => {
-    return value.string();
-  }).then(valueStr => {
-    is(valueStr, longstringText,
-       "Full node value should match the string from the document.");
-  }).then(runNextTest));
-});
-
-addTest(function testShortValue() {
-  const shortstringText = gInspectee.getElementById("shortstring").firstChild.nodeValue;
-
-  promiseDone(gWalker.querySelector(gWalker.rootNode, "#shortstring").then(node => {
-    ok(!!node.inlineTextChild, "Text is short enough to be inlined");
-    // Now we need to get the text node child...
-    return gWalker.children(node, { maxNodes: 1 });
-  }).then(children => {
-    const textNode = children.nodes[0];
-    is(textNode.nodeType, Node.TEXT_NODE, "Value should be a text node");
-    return textNode;
-  }).then(textNode => {
-    return textNode.getNodeValue();
-  }).then(value => {
-    return value.string();
-  }).then(valueStr => {
-    is(valueStr, shortstringText,
-       "Full node value should match the string from the document.");
-  }).then(runNextTest));
-});
-
-addTest(function testReleaseWalker() {
-  checkActorIDs.push(gWalker.actorID);
-
-  promiseDone(gWalker.release().then(() => {
-    const promises = Array.from(checkActorIDs, (id) => checkMissing(gTarget, id));
-    return Promise.all(promises);
-  }).then(runNextTest));
-});
-
-addTest(function cleanup() {
-  gWalker = null;
-  gInspectee = null;
-  gTarget = null;
-  runNextTest();
-});
-  </script>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
-<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/devtools/server/tests/mochitest/test_memory_allocations_01.html
+++ /dev/null
@@ -1,104 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-Bug 1067491 - Test recording allocations.
--->
-<head>
-  <meta charset="utf-8">
-  <title>Memory monitoring actor test</title>
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
-</head>
-<body>
-<pre id="test">
-<script src="memory-helpers.js" type="application/javascript"></script>
-<script>
-"use strict";
-
-window.onload = function() {
-  SimpleTest.waitForExplicitFinish();
-
-  (async function() {
-    const { memory, target } = await startServerAndGetSelectedTabMemory();
-    await memory.attach();
-
-    await memory.startRecordingAllocations();
-    ok(true, "Can start recording allocations");
-
-    // Allocate some objects.
-
-    let alloc1, alloc2, alloc3;
-
-    /* eslint-disable max-nested-callbacks */
-    (function outer() {
-      (function middle() {
-        (function inner() {
-          alloc1 = {}; alloc1.line = Error().lineNumber;
-          alloc2 = []; alloc2.line = Error().lineNumber;
-          // eslint-disable-next-line new-parens
-          alloc3 = new function() {}; alloc3.line = Error().lineNumber;
-        }());
-      }());
-    }());
-    /* eslint-enable max-nested-callbacks */
-
-    const response = await memory.getAllocations();
-
-    await memory.stopRecordingAllocations();
-    ok(true, "Can stop recording allocations");
-
-    // Filter out allocations by library and test code, and get only the
-    // allocations that occurred in our test case above.
-
-    function isTestAllocation(alloc) {
-      const frame = response.frames[alloc];
-      return frame
-        && frame.functionDisplayName === "inner"
-        && (frame.line === alloc1.line
-            || frame.line === alloc2.line
-            || frame.line === alloc3.line);
-    }
-
-    const testAllocations = response.allocations.filter(isTestAllocation);
-    ok(testAllocations.length >= 3,
-       "Should find our 3 test allocations (plus some allocations for the error "
-       + "objects used to get line numbers)");
-
-    // For each of the test case's allocations, ensure that the parent frame
-    // indices are correct. Also test that we did get an allocation at each
-    // line we expected (rather than a bunch on the first line and none on the
-    // others, etc).
-
-    const expectedLines = new Set([alloc1.line, alloc2.line, alloc3.line]);
-
-    for (const alloc of testAllocations) {
-      const innerFrame = response.frames[alloc];
-      ok(innerFrame, "Should get the inner frame");
-      is(innerFrame.functionDisplayName, "inner");
-      expectedLines.delete(innerFrame.line);
-
-      const middleFrame = response.frames[innerFrame.parent];
-      ok(middleFrame, "Should get the middle frame");
-      is(middleFrame.functionDisplayName, "middle");
-
-      const outerFrame = response.frames[middleFrame.parent];
-      ok(outerFrame, "Should get the outer frame");
-      is(outerFrame.functionDisplayName, "outer");
-
-      // Not going to test the rest of the frames because they are Task.jsm
-      // and promise frames and it gets gross. Plus, I wouldn't want this test
-      // to start failing if they changed their implementations in a way that
-      // added or removed stack frames here.
-    }
-
-    is(expectedLines.size, 0,
-       "Should have found all the expected lines");
-
-    await memory.detach();
-    destroyServerAndFinish(target);
-  })();
-};
-</script>
-</pre>
-</body>
-</html>
--- a/devtools/shared/webconsole/moz.build
+++ b/devtools/shared/webconsole/moz.build
@@ -2,16 +2,17 @@
 # vim: set filetype=python:
 # 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/.
 
 if CONFIG['OS_TARGET'] != 'Android':
     MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
     XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+    BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
 
 # Compute where to put transpiled files into omni.ja package
 # All DevTools modules are used via resource://devtools/ URI
 # See devtools/shared/jar.mn for how this resource is mapped into jar package.
 base = FINAL_TARGET_FILES.chrome.devtools.modules
 
 # Now, navigate to the right sub-directory into devtools root modules folder
 for dir in RELATIVEDIR.split('/'):
@@ -23,9 +24,9 @@ ReservedWordsGenerated = GENERATED_FILES
 ReservedWordsGenerated.script = 'GenerateReservedWordsJS.py'
 ReservedWordsGenerated.inputs = ['/js/src/frontend/ReservedWords.h']
 
 DevToolsModules(
     'client.js',
     'js-property-provider.js',
     'network-helper.js',
     'throttle.js',
-)
\ No newline at end of file
+)
new file mode 100644
--- /dev/null
+++ b/devtools/shared/webconsole/test/browser/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+  // Extend from the shared list of defined globals for mochitests.
+  "extends": "../../../../.eslintrc.mochitests.js"
+};
new file mode 100644
--- /dev/null
+++ b/devtools/shared/webconsole/test/browser/browser.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+  head.js
+  data.json
+  data.json^headers^
+  network_requests_iframe.html
+
+[browser_commands_registration.js]
+[browser_network_longstring.js]
new file mode 100644
--- /dev/null
+++ b/devtools/shared/webconsole/test/browser/browser_commands_registration.js
@@ -0,0 +1,181 @@
+/* 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/ */
+
+"use strict";
+
+// Test for Web Console commands registration.
+
+add_task(async function() {
+  const tab = await addTab("data:text/html,<div id=quack></div>");
+  const target = await getTargetForTab(tab);
+
+  const consoleClient = target.activeConsole;
+
+  // Fetch WebConsoleCommands so that it is available for next Content Tasks
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
+    const { WebConsoleCommands } = require("devtools/server/actors/webconsole/utils");
+
+    // Bind the symbol on this in order to make it available for next tasks
+    this.WebConsoleCommands = WebConsoleCommands;
+  });
+
+  await registerNewCommand(consoleClient);
+  await wrapCommand(consoleClient);
+  await unregisterCommand(consoleClient);
+  await registerAccessor(consoleClient);
+  await unregisterAfterOverridingTwice(consoleClient);
+});
+
+async function evaluateJSAndCheckResult(consoleClient, input, expected) {
+  const response = await consoleClient.evaluateJS(input);
+  checkObject(response, expected);
+}
+
+async function registerNewCommand(consoleClient) {
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    this.WebConsoleCommands.register("setFoo", (owner, value) => {
+      owner.window.foo = value;
+      return "ok";
+    });
+
+    ok(this.WebConsoleCommands.hasCommand("setFoo"),
+        "The command should be registered");
+  });
+
+  const command = "setFoo('bar')";
+  await evaluateJSAndCheckResult(consoleClient, command, {
+    from: consoleClient.actor,
+    input: command,
+    result: "ok",
+  });
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    is(content.top.foo, "bar", "top.foo should equal to 'bar'");
+  });
+}
+
+async function wrapCommand(consoleClient) {
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    const origKeys = this.WebConsoleCommands.getCommand("keys");
+
+    const newKeys = (...args) => {
+      const [, arg0] = args;
+      if (arg0 === ">o_/") {
+        return "bang!";
+      }
+      return origKeys(...args);
+    };
+
+    this.WebConsoleCommands.register("keys", newKeys);
+    is(this.WebConsoleCommands.getCommand("keys"), newKeys,
+        "the keys() command should have been replaced");
+
+    this.origKeys = origKeys;
+  });
+
+  await evaluateJSAndCheckResult(consoleClient, "keys('>o_/')", {
+    from: consoleClient.actor,
+    result: "bang!",
+  });
+
+  await evaluateJSAndCheckResult(consoleClient, "keys({foo: 'bar'})", {
+    from: consoleClient.actor,
+    result: {
+      class: "Array",
+      preview: {
+        items: ["foo"],
+      },
+    },
+  });
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    this.WebConsoleCommands.register("keys", this.origKeys);
+    is(this.WebConsoleCommands.getCommand("keys"), this.origKeys,
+      "the keys() command should be restored");
+    delete this.origKeys;
+  });
+}
+
+async function unregisterCommand(consoleClient) {
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    this.WebConsoleCommands.unregister("setFoo");
+  });
+
+  await evaluateJSAndCheckResult(consoleClient, "setFoo", {
+    from: consoleClient.actor,
+    input: "setFoo",
+    result: {
+      type: "undefined",
+    },
+    exceptionMessage: /setFoo is not defined/,
+  });
+}
+
+async function registerAccessor(consoleClient) {
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    this.WebConsoleCommands.register("$foo", {
+      get(owner) {
+        const foo = owner.window.document.getElementById("quack");
+        return owner.makeDebuggeeValue(foo);
+      },
+    });
+  });
+
+  const command = "$foo.textContent = '>o_/'";
+  await evaluateJSAndCheckResult(consoleClient, command, {
+    from: consoleClient.actor,
+    input: command,
+    result: ">o_/",
+  });
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    is(content.document.getElementById("quack").textContent, ">o_/",
+       "#foo textContent should equal to \">o_/\"");
+    this.WebConsoleCommands.unregister("$foo");
+    ok(!this.WebConsoleCommands.hasCommand("$foo"), "$foo should be unregistered");
+  });
+}
+
+async function unregisterAfterOverridingTwice(consoleClient) {
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    this.WebConsoleCommands.register("keys", (owner, obj) => "command 1");
+  });
+
+  info("checking the value of the first override");
+  await evaluateJSAndCheckResult(consoleClient, "keys('foo');", {
+    result: "command 1",
+  });
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    const orig = this.WebConsoleCommands.getCommand("keys");
+    this.WebConsoleCommands.register("keys", (owner, obj) => {
+      if (obj === "quack") {
+        return "bang!";
+      }
+      return orig(owner, obj);
+    });
+  });
+
+  info("checking the values after the second override");
+  await evaluateJSAndCheckResult(consoleClient, "keys({});", {
+    result: "command 1",
+  });
+  await evaluateJSAndCheckResult(consoleClient, "keys('quack');", {
+    result: "bang!",
+  });
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    this.WebConsoleCommands.unregister("keys");
+  });
+
+  info("checking the value after unregistration (should restore " +
+    "the original command)");
+  await evaluateJSAndCheckResult(consoleClient, "keys({});", {
+    result: {
+      class: "Array",
+      preview: {items: []},
+    },
+  });
+}
rename from devtools/shared/webconsole/test/test_network_longstring.html
rename to devtools/shared/webconsole/test/browser/browser_network_longstring.js
--- a/devtools/shared/webconsole/test/test_network_longstring.html
+++ b/devtools/shared/webconsole/test/browser/browser_network_longstring.js
@@ -1,100 +1,128 @@
-<!DOCTYPE HTML>
-<html lang="en">
-<head>
-  <meta charset="utf8">
-  <title>Test that the network actor uses the LongStringActor</title>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="common.js"></script>
-  <!-- Any copyright is dedicated to the Public Domain.
-     - http://creativecommons.org/publicdomain/zero/1.0/ -->
-</head>
-<body>
-<p>Test that the network actor uses the LongStringActor</p>
+/* 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/ */
+
+"use strict";
 
-<iframe src="http://example.com/chrome/devtools/shared/webconsole/test/network_requests_iframe.html"></iframe>
+// Test that the network actor uses the LongStringActor
+
+const { DebuggerServer } = require("devtools/server/main");
+const LONG_STRING_LENGTH = 400;
+const LONG_STRING_INITIAL_LENGTH = 400;
+let ORIGINAL_LONG_STRING_LENGTH, ORIGINAL_LONG_STRING_INITIAL_LENGTH;
 
-<script class="testbody" type="text/javascript">
-SimpleTest.waitForExplicitFinish();
+add_task(async function() {
+  const tab = await addTab(URL_ROOT + "network_requests_iframe.html");
 
-function startTest()
-{
-  removeEventListener("load", startTest);
+  const target = await getTargetForTab(tab);
+  const { client } = target;
+  const consoleClient = target.activeConsole;
 
-  attachConsoleToTab(["NetworkActivity"], onAttach);
-}
+  await consoleClient.startListeners(["NetworkActivity"]);
 
-function onAttach(aState, aResponse)
-{
-  info("set long string length");
-
-  window.ORIGINAL_LONG_STRING_LENGTH = DebuggerServer.LONG_STRING_LENGTH;
-  window.ORIGINAL_LONG_STRING_INITIAL_LENGTH =
+  // Override the default long string settings to lower values.
+  // This is done from the parent process's DebuggerServer as the LongString
+  // actor is being created from the parent process as network requests are
+  // watched from the parent process.
+  ORIGINAL_LONG_STRING_LENGTH = DebuggerServer.LONG_STRING_LENGTH;
+  ORIGINAL_LONG_STRING_INITIAL_LENGTH =
     DebuggerServer.LONG_STRING_INITIAL_LENGTH;
 
-  DebuggerServer.LONG_STRING_LENGTH = 400;
-  DebuggerServer.LONG_STRING_INITIAL_LENGTH = 400;
+  DebuggerServer.LONG_STRING_LENGTH = LONG_STRING_LENGTH;
+  DebuggerServer.LONG_STRING_INITIAL_LENGTH = LONG_STRING_INITIAL_LENGTH;
 
   info("test network POST request");
 
-  onNetworkEvent = onNetworkEvent.bind(null, aState);
-  aState.dbgClient.addListener("networkEvent", onNetworkEvent);
-  onNetworkEventUpdate = onNetworkEventUpdate.bind(null, aState);
-  aState.dbgClient.addListener("networkEventUpdate", onNetworkEventUpdate);
+  const onNetworkEvent = client.addOneTimeListener("networkEvent");
+  const updates = [];
+  let netActor = null;
+  const onAllNetworkEventUpdateReceived = new Promise(resolve => {
+    const onNetworkEventUpdate = (type, packet) => {
+      updates.push(packet.updateType);
+      assertNetworkEventUpdate(netActor, packet);
+
+      if (updates.includes("responseContent") &&
+          updates.includes("eventTimings")) {
+        client.removeListener("networkEventUpdate", onNetworkEventUpdate);
+        resolve();
+      }
+    };
+    client.addListener("networkEventUpdate", onNetworkEventUpdate);
+  });
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
+    content.wrappedJSObject.testXhrPost();
+  });
+
+  info("Waiting for networkEvent");
+  const netEvent = await onNetworkEvent;
+  netActor = assertNetworkEvent(client, consoleClient, netEvent);
 
-  let iframe = document.querySelector("iframe").contentWindow;
-  iframe.wrappedJSObject.testXhrPost();
-}
+  info("Waiting for all networkEventUpdate");
+  await onAllNetworkEventUpdateReceived;
+  const requestHeaders = await consoleClient.getRequestHeaders(netActor);
+  assertRequestHeaders(requestHeaders);
+  const requestCookies = await consoleClient.getRequestCookies(netActor);
+  assertRequestCookies(requestCookies);
+  const requestPostData = await consoleClient.getRequestPostData(netActor);
+  assertRequestPostData(requestPostData);
+  const responseHeaders = await consoleClient.getResponseHeaders(netActor);
+  assertResponseHeaders(responseHeaders);
+  const responseCookies = await consoleClient.getResponseCookies(netActor);
+  assertResponseCookies(responseCookies);
+  const responseContent = await consoleClient.getResponseContent(netActor);
+  assertResponseContent(responseContent);
+  const eventTimings = await consoleClient.getEventTimings(netActor);
+  assertEventTimings(eventTimings);
 
-function onNetworkEvent(aState, aType, aPacket)
-{
-  is(aPacket.from, aState.actor, "network event actor");
+  await target.destroy();
+
+  DebuggerServer.LONG_STRING_LENGTH = ORIGINAL_LONG_STRING_LENGTH;
+  DebuggerServer.LONG_STRING_INITIAL_LENGTH = ORIGINAL_LONG_STRING_INITIAL_LENGTH;
+});
+
+function assertNetworkEvent(client, consoleClient, packet) {
+  is(packet.from, consoleClient.actor, "network event actor");
 
   info("checking the network event packet");
 
-  let netActor = aPacket.eventActor;
+  const netActor = packet.eventActor;
 
   checkObject(netActor, {
     actor: /[a-z]/,
     startedDateTime: /^\d+\-\d+\-\d+T.+$/,
     url: /data\.json/,
     method: "POST",
   });
 
-  aState.netActor = netActor.actor;
-
-  aState.dbgClient.removeListener("networkEvent", onNetworkEvent);
+  return netActor.actor;
 }
 
-let updates = [];
-
-function onNetworkEventUpdate(aState, aType, aPacket)
-{
-  info("received networkEventUpdate " + aPacket.updateType);
-  is(aPacket.from, aState.netActor, "networkEventUpdate actor");
-
-  updates.push(aPacket.updateType);
+function assertNetworkEventUpdate(netActor, packet) {
+  info("received networkEventUpdate " + packet.updateType);
+  is(packet.from, netActor, "networkEventUpdate actor");
 
   let expectedPacket = null;
 
-  switch (aPacket.updateType) {
+  switch (packet.updateType) {
     case "requestHeaders":
     case "responseHeaders":
-      ok(aPacket.headers > 0, "headers > 0");
-      ok(aPacket.headersSize > 0, "headersSize > 0");
+      ok(packet.headers > 0, "headers > 0");
+      ok(packet.headersSize > 0, "headersSize > 0");
       break;
     case "requestCookies":
       expectedPacket = {
         cookies: 3,
       };
       break;
     case "requestPostData":
-      ok(aPacket.dataSize > 0, "dataSize > 0");
-      ok(!aPacket.discardRequestBody, "discardRequestBody");
+      ok(packet.dataSize > 0, "dataSize > 0");
+      ok(!packet.discardRequestBody, "discardRequestBody");
       break;
     case "responseStart":
       expectedPacket = {
         response: {
           httpVersion: /^HTTP\/\d\.\d$/,
           status: "200",
           statusText: "OK",
           headersSize: /^\d+$/,
@@ -121,173 +149,120 @@ function onNetworkEventUpdate(aState, aT
       break;
     case "eventTimings":
       expectedPacket = {
         totalTime: /^\d+$/,
       };
       break;
     default:
       ok(false, "unknown network event update type: " +
-         aPacket.updateType);
+         packet.updateType);
       return;
   }
 
   if (expectedPacket) {
     info("checking the packet content");
-    checkObject(aPacket, expectedPacket);
-  }
-
-  if (updates.indexOf("responseContent") > -1 &&
-      updates.indexOf("eventTimings") > -1) {
-    aState.dbgClient.removeListener("networkEventUpdate",
-                                    onNetworkEvent);
-
-    onRequestHeaders = onRequestHeaders.bind(null, aState);
-    aState.client.getRequestHeaders(aState.netActor,
-                                    onRequestHeaders);
+    checkObject(packet, expectedPacket);
   }
 }
 
-function onRequestHeaders(aState, aResponse)
-{
+function assertRequestHeaders(response) {
   info("checking request headers");
 
-  ok(aResponse.headers.length > 0, "request headers > 0");
-  ok(aResponse.headersSize > 0, "request headersSize > 0");
+  ok(response.headers.length > 0, "request headers > 0");
+  ok(response.headersSize > 0, "request headersSize > 0");
 
-  checkHeadersOrCookies(aResponse.headers, {
+  checkHeadersOrCookies(response.headers, {
     Referer: /network_requests_iframe\.html/,
     Cookie: /bug768096/,
   });
-
-  onRequestCookies = onRequestCookies.bind(null, aState);
-  aState.client.getRequestCookies(aState.netActor,
-                                  onRequestCookies);
 }
 
-function onRequestCookies(aState, aResponse)
-{
+function assertRequestCookies(response) {
   info("checking request cookies");
 
-  is(aResponse.cookies.length, 3, "request cookies length");
+  is(response.cookies.length, 3, "request cookies length");
 
-  checkHeadersOrCookies(aResponse.cookies, {
+  checkHeadersOrCookies(response.cookies, {
     foobar: "fooval",
     omgfoo: "bug768096",
     badcookie: "bug826798=st3fan",
   });
-
-  onRequestPostData = onRequestPostData.bind(null, aState);
-  aState.client.getRequestPostData(aState.netActor,
-                                   onRequestPostData);
 }
 
-function onRequestPostData(aState, aResponse)
-{
+function assertRequestPostData(response) {
   info("checking request POST data");
 
-  checkObject(aResponse, {
+  checkObject(response, {
     postData: {
       text: {
         type: "longString",
         initial: /^Hello world! foobaz barr.+foobaz barrfo$/,
-        length: 552,
+        length: 563,
         actor: /[a-z]/,
       },
     },
     postDataDiscarded: false,
   });
 
-  is(aResponse.postData.text.initial.length,
-     DebuggerServer.LONG_STRING_INITIAL_LENGTH, "postData text initial length");
-
-  onResponseHeaders = onResponseHeaders.bind(null, aState);
-  aState.client.getResponseHeaders(aState.netActor,
-                                   onResponseHeaders);
+  is(response.postData.text.initial.length,
+     LONG_STRING_INITIAL_LENGTH, "postData text initial length");
 }
 
-function onResponseHeaders(aState, aResponse)
-{
+function assertResponseHeaders(response) {
   info("checking response headers");
 
-  ok(aResponse.headers.length > 0, "response headers > 0");
-  ok(aResponse.headersSize > 0, "response headersSize > 0");
+  ok(response.headers.length > 0, "response headers > 0");
+  ok(response.headersSize > 0, "response headersSize > 0");
 
-  checkHeadersOrCookies(aResponse.headers, {
+  checkHeadersOrCookies(response.headers, {
     "content-type": /^application\/(json|octet-stream)$/,
     "content-length": /^\d+$/,
     "x-very-short": "hello world",
     "x-very-long": {
       "type": "longString",
       "length": 521,
       "initial": /^Lorem ipsum.+\. Donec vitae d$/,
       "actor": /[a-z]/,
     },
   });
-
-  onResponseCookies = onResponseCookies.bind(null, aState);
-  aState.client.getResponseCookies(aState.netActor,
-                                  onResponseCookies);
 }
 
-function onResponseCookies(aState, aResponse)
-{
+function assertResponseCookies(response) {
   info("checking response cookies");
 
-  is(aResponse.cookies.length, 0, "response cookies length");
-
-  onResponseContent = onResponseContent.bind(null, aState);
-  aState.client.getResponseContent(aState.netActor,
-                                   onResponseContent);
+  is(response.cookies.length, 0, "response cookies length");
 }
 
-function onResponseContent(aState, aResponse)
-{
+function assertResponseContent(response) {
   info("checking response content");
 
-  checkObject(aResponse, {
+  checkObject(response, {
     content: {
       text: {
         type: "longString",
         initial: /^\{ id: "test JSON data"(.|\r|\n)+ barfoo ba$/g,
         length: 1070,
         actor: /[a-z]/,
       },
     },
     contentDiscarded: false,
   });
 
-  is(aResponse.content.text.initial.length,
-     DebuggerServer.LONG_STRING_INITIAL_LENGTH, "content initial length");
-
-  onEventTimings = onEventTimings.bind(null, aState);
-  aState.client.getEventTimings(aState.netActor,
-                                onEventTimings);
+  is(response.content.text.initial.length,
+     LONG_STRING_INITIAL_LENGTH, "content initial length");
 }
 
-function onEventTimings(aState, aResponse)
-{
+function assertEventTimings(response) {
   info("checking event timings");
 
-  checkObject(aResponse, {
+  checkObject(response, {
     timings: {
       blocked: /^-1|\d+$/,
       dns: /^-1|\d+$/,
       connect: /^-1|\d+$/,
       send: /^-1|\d+$/,
       wait: /^-1|\d+$/,
       receive: /^-1|\d+$/,
     },
     totalTime: /^\d+$/,
   });
-
-  closeDebugger(aState, function() {
-    DebuggerServer.LONG_STRING_LENGTH = ORIGINAL_LONG_STRING_LENGTH;
-    DebuggerServer.LONG_STRING_INITIAL_LENGTH = ORIGINAL_LONG_STRING_INITIAL_LENGTH;
-
-    SimpleTest.finish();
-  });
 }
-
-addEventListener("load", startTest);
-</script>
-</body>
-</html>
new file mode 100644
--- /dev/null
+++ b/devtools/shared/webconsole/test/browser/data.json
@@ -0,0 +1,3 @@
+{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ],
+  veryLong: "foo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo bar"
+}
new file mode 100644
--- /dev/null
+++ b/devtools/shared/webconsole/test/browser/data.json^headers^
@@ -0,0 +1,3 @@
+Content-Type: application/json
+x-very-long: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse a ipsum massa. Phasellus at elit dictum libero laoreet sagittis. Phasellus condimentum ultricies imperdiet. Nam eu ligula justo, ut tincidunt quam. Etiam sollicitudin, tortor sed egestas blandit, sapien sem tincidunt nulla, eu luctus libero odio quis leo. Nam elit massa, mattis quis blandit ac, facilisis vitae arcu. Donec vitae dictum neque. Proin ornare nisl at lectus commodo iaculis eget eget est. Quisque scelerisque vestibulum quam sed interdum.
+x-very-short: hello world
new file mode 100644
--- /dev/null
+++ b/devtools/shared/webconsole/test/browser/head.js
@@ -0,0 +1,66 @@
+/* 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";
+
+/* import-globals-from ../../../../client/shared/test/shared-head.js */
+
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
+  this);
+
+async function getTargetForTab(tab) {
+  const target = await TargetFactory.forTab(tab);
+  info("Attaching to the active tab.");
+  await target.attach();
+  return target;
+}
+
+function checkObject(object, expected) {
+  for (const name of Object.keys(expected)) {
+    const expectedValue = expected[name];
+    const value = object[name];
+    checkValue(name, value, expectedValue);
+  }
+}
+
+function checkValue(name, value, expected) {
+  if (expected === null) {
+    is(value, null, "'" + name + "' is null");
+  } else if (value === null) {
+    ok(false, "'" + name + "' is null");
+  } else if (value === undefined) {
+    ok(false, "'" + name + "' is undefined");
+  } else if (typeof expected == "string" || typeof expected == "number" ||
+    typeof expected == "boolean") {
+    is(value, expected, "property '" + name + "'");
+  } else if (expected instanceof RegExp) {
+    ok(expected.test(value), name + ": " + expected + " matched " + value);
+  } else if (Array.isArray(expected)) {
+    info("checking array for property '" + name + "'");
+    checkObject(value, expected);
+  } else if (typeof expected == "object") {
+    info("checking object for property '" + name + "'");
+    checkObject(value, expected);
+  }
+}
+
+function checkHeadersOrCookies(array, expected) {
+  const foundHeaders = {};
+
+  for (const elem of array) {
+    if (!(elem.name in expected)) {
+      continue;
+    }
+    foundHeaders[elem.name] = true;
+    info("checking value of header " + elem.name);
+    checkValue(elem.name, elem.value, expected[elem.name]);
+  }
+
+  for (const header in expected) {
+    if (!(header in foundHeaders)) {
+      ok(false, header + " was not found");
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/shared/webconsole/test/browser/network_requests_iframe.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Console HTTP test page</title>
+    <!-- Any copyright is dedicated to the Public Domain.
+       - http://creativecommons.org/publicdomain/zero/1.0/ -->
+    <script type="text/javascript"><!--
+      "use strict";
+      let setAllowAllCookies = false;
+
+      function makeXhr(method, url, requestBody, callback) {
+        // On the first call, allow all cookies and set cookies, then resume the actual test
+        if (!setAllowAllCookies) {
+          SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0]]},
+          function() {
+            setAllowAllCookies = true;
+            setCookies();
+            makeXhrCallback(method, url, requestBody, callback);
+          });
+        } else {
+          makeXhrCallback(method, url, requestBody, callback);
+        }
+      }
+
+      function makeXhrCallback(method, url, requestBody, callback) {
+        const xmlhttp = new XMLHttpRequest();
+        xmlhttp.open(method, url, true);
+        if (callback) {
+          xmlhttp.onreadystatechange = function() {
+            if (xmlhttp.readyState == 4) {
+              callback();
+            }
+          };
+        }
+        xmlhttp.send(requestBody);
+      }
+
+      /* exported testXhrGet */
+      function testXhrGet(callback) {
+        makeXhr("get", "data.json", null, callback);
+      }
+
+      /* exported testXhrPost */
+      function testXhrPost(callback) {
+        const body = "Hello world! " + "foobaz barr".repeat(50);
+        makeXhr("post", "data.json", body, callback);
+      }
+
+      function setCookies() {
+        document.cookie = "foobar=fooval";
+        document.cookie = "omgfoo=bug768096";
+        document.cookie = "badcookie=bug826798=st3fan";
+      }
+      </script>
+  </head>
+  <body>
+    <h1>Web Console HTTP Logging Testpage</h1>
+    <h2>This page is used to test the HTTP logging.</h2>
+
+    <form action="?" method="post">
+      <input name="name" type="text" value="foo bar"><br>
+      <input name="age" type="text" value="144"><br>
+    </form>
+  </body>
+</html>
--- a/devtools/shared/webconsole/test/chrome.ini
+++ b/devtools/shared/webconsole/test/chrome.ini
@@ -10,17 +10,16 @@ support-files =
   console-test-worker.js
   !/browser/base/content/test/general/browser_star_hsts.sjs
   !/browser/base/content/test/general/pinning_headers.sjs
 
 [test_basics.html]
 [test_bug819670_getter_throws.html]
 [test_cached_messages.html]
 [test_commands_other.html]
-[test_commands_registration.html]
 [test_consoleapi.html]
 [test_consoleapi_innerID.html]
 [test_console_assert.html]
 [test_console_group_styling.html]
 [test_console_serviceworker.html]
 [test_console_serviceworker_cached.html]
 [test_console_styling.html]
 [test_console_timestamp.html]
@@ -29,17 +28,16 @@ support-files =
 [test_reflow.html]
 [test_jsterm.html]
 [test_jsterm_autocomplete.html]
 [test_jsterm_cd_iframe.html]
 [test_jsterm_last_result.html]
 [test_jsterm_queryselector.html]
 [test_network_get.html]
 skip-if = verify
-[test_network_longstring.html]
 [test_network_post.html]
 [test_network_security-hpkp.html]
 [test_network_security-hsts.html]
 [test_nsiconsolemessage.html]
 [test_object_actor.html]
 [test_object_actor_native_getters.html]
 [test_object_actor_native_getters_lenient_this.html]
 [test_page_errors.html]
deleted file mode 100644
--- a/devtools/shared/webconsole/test/test_commands_registration.html
+++ /dev/null
@@ -1,191 +0,0 @@
-<!DOCTYPE HTML>
-<html lang="en">
-<head>
-  <meta charset="utf8">
-  <title>Test for Web Console commands registration.</title>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="common.js"></script>
-  <!-- Any copyright is dedicated to the Public Domain.
-     - http://creativecommons.org/publicdomain/zero/1.0/ -->
-</head>
-<body>
-<p>Test for Web Console commands registration.</p>
-<p id="quack"></p>
-
-<script class="testbody" type="text/javascript">
-SimpleTest.waitForExplicitFinish();
-
-let gState;
-let tests;
-
-let {WebConsoleCommands} = require("devtools/server/actors/webconsole/utils");
-
-function evaluateJS(input) {
-  return gState.client.evaluateJS(input);
-}
-
-async function evaluateJSAndCheckResult(input, result) {
-  let response = await evaluateJS(input);
-  checkObject(response, {result});
-}
-
-function startTest()
-{
-  removeEventListener("load", startTest);
-
-  attachConsoleToTab(["PageError"], onAttach);
-}
-
-function onAttach(aState, aResponse)
-{
-  gState = aState;
-
-  runTests(tests, testEnd);
-}
-
-tests = [
-  async function registerNewCommand() {
-    let win;
-    WebConsoleCommands.register("setFoo", (owner, value) => {
-      owner.window.foo = value;
-      return "ok";
-    });
-
-    ok(WebConsoleCommands.hasCommand("setFoo"),
-        "The command should be registered");
-
-    let command = "setFoo('bar')";
-    let response = await evaluateJS(command);
-
-    checkObject(response, {
-      from: gState.actor,
-      input: command,
-      result: "ok"
-    });
-    is(top.foo, "bar", "top.foo should equal to 'bar'");
-    nextTest();
-  },
-
-  async function wrapCommand() {
-    let origKeys = WebConsoleCommands.getCommand("keys");
-
-    let newKeys = (...args) => {
-      let [owner, arg0] = args;
-      if (arg0 === ">o_/") {
-        return "bang!";
-      }
-      else {
-        return origKeys(...args);
-      }
-    };
-
-    WebConsoleCommands.register("keys", newKeys);
-    is(WebConsoleCommands.getCommand("keys"), newKeys,
-        "the keys() command should have been replaced");
-
-    let response = await evaluateJS("keys('>o_/')");
-    checkObject(response, {
-      from: gState.actor,
-      result: "bang!"
-    });
-
-    response = await evaluateJS("keys({foo: 'bar'})");
-    checkObject(response, {
-      from: gState.actor,
-      result: {
-        class: "Array",
-        preview: {
-          items: ["foo"]
-        }
-      }
-    });
-
-    WebConsoleCommands.register("keys", origKeys);
-    is(WebConsoleCommands.getCommand("keys"), origKeys,
-      "the keys() command should be restored");
-    nextTest();
-  },
-
-  async function unregisterCommand() {
-    WebConsoleCommands.unregister("setFoo");
-
-    let response = await evaluateJS("setFoo");
-
-    checkObject(response, {
-      from: gState.actor,
-      input: "setFoo",
-      result: {
-        type: "undefined"
-      },
-      exceptionMessage: /setFoo is not defined/
-    });
-    nextTest();
-  },
-
-  async function registerAccessor() {
-    WebConsoleCommands.register("$foo", {
-      get(owner) {
-        let foo = owner.window.frames[0].window.document.getElementById("quack");
-        return owner.makeDebuggeeValue(foo);
-      }
-    });
-    let command = "$foo.textContent = '>o_/'";
-    let response = await evaluateJS(command);
-
-    checkObject(response, {
-      from: gState.actor,
-      input: command,
-      result: ">o_/"
-    });
-    is(document.getElementById("quack").textContent, ">o_/",
-        "#foo textContent should equal to \">o_/\"");
-    WebConsoleCommands.unregister("$foo");
-    ok(!WebConsoleCommands.hasCommand("$foo"), "$foo should be unregistered");
-    nextTest();
-  },
-
-  async function unregisterAfterOverridingTwice() {
-    WebConsoleCommands.register("keys", (owner, obj) => "command 1");
-    info("checking the value of the first override");
-    await evaluateJSAndCheckResult("keys('foo');", "command 1");
-
-    let orig = WebConsoleCommands.getCommand("keys");
-    WebConsoleCommands.register("keys", (owner, obj) => {
-      if (obj === "quack")
-        return "bang!";
-      return orig(owner, obj);
-    });
-
-    info("checking the values after the second override");
-    await evaluateJSAndCheckResult("keys({});", "command 1");
-    await evaluateJSAndCheckResult("keys('quack');", "bang!");
-
-    WebConsoleCommands.unregister("keys");
-
-    info("checking the value after unregistration (should restore " +
-      "the original command)");
-    await evaluateJSAndCheckResult("keys({});", {
-      class: "Array",
-      preview: {items: []}
-    });
-    nextTest();
-
-  }
-];
-
-function testEnd()
-{
-  // If this is the first run, reload the page and do it again.
-  // Otherwise, end the test.
-  delete top.foo;
-  closeDebugger(gState, function() {
-    gState = null;
-    SimpleTest.finish();
-  });
-}
-
-addEventListener("load", startTest);
-</script>
-</body>
-</html>
-
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -49,17 +49,16 @@
 #include "mozilla/TouchEvents.h"
 
 #include "nsViewManager.h"
 
 #include "nsLayoutUtils.h"
 #include "nsComputedDOMStyle.h"
 #include "nsIPresShell.h"
 #include "nsCSSProps.h"
-#include "nsTArrayHelpers.h"
 #include "nsIDocShell.h"
 #include "nsIContentViewer.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FileBinding.h"
 #include "mozilla/dom/DOMRect.h"
 #include <algorithm>
 
@@ -2951,22 +2950,17 @@ NS_IMETHODIMP
 nsDOMWindowUtils::GetPlugins(JSContext* cx,
                              JS::MutableHandle<JS::Value> aPlugins) {
   nsCOMPtr<Document> doc = GetDocument();
   NS_ENSURE_STATE(doc);
 
   nsTArray<nsIObjectLoadingContent*> plugins;
   doc->GetPlugins(plugins);
 
-  JS::Rooted<JSObject*> jsPlugins(cx);
-  nsresult rv = nsTArrayToJSArray(cx, plugins, &jsPlugins);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  aPlugins.setObject(*jsPlugins);
-  return NS_OK;
+  return ToJSValue(cx, plugins, aPlugins) ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SetVisualViewportSize(float aWidth, float aHeight) {
   if (!(aWidth >= 0.0 && aHeight >= 0.0)) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
 
--- a/dom/canvas/WebGLTexture.cpp
+++ b/dom/canvas/WebGLTexture.cpp
@@ -443,16 +443,17 @@ static void SetSwizzle(gl::GLContext* gl
   gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_A, swizzle[3]);
 }
 
 void WebGLTexture::RefreshSwizzle() const {
   const auto& imageInfo = BaseImageInfo();
   const auto& swizzle = imageInfo.mFormat->textureSwizzleRGBA;
 
   if (swizzle != mCurSwizzle) {
+    const gl::ScopedBindTexture scopeBindTexture(mContext->gl, mGLName, mTarget.get());
     SetSwizzle(mContext->gl, mTarget, swizzle);
     mCurSwizzle = swizzle;
   }
 }
 
 bool WebGLTexture::EnsureImageDataInitialized(const TexImageTarget target,
                                               const uint32_t level) {
   auto& imageInfo = ImageInfoAt(target, level);
--- a/dom/html/HTMLOptionsCollection.cpp
+++ b/dom/html/HTMLOptionsCollection.cpp
@@ -128,23 +128,22 @@ void HTMLOptionsCollection::IndexedSette
   nsCOMPtr<nsINode> parent = refChild->GetParent();
   if (!parent) {
     return;
   }
 
   parent->ReplaceChild(*aOption, *refChild, aError);
 }
 
-int32_t HTMLOptionsCollection::GetSelectedIndex(ErrorResult& aError) {
+int32_t HTMLOptionsCollection::SelectedIndex() {
   return mSelect->SelectedIndex();
 }
 
-void HTMLOptionsCollection::SetSelectedIndex(int32_t aSelectedIndex,
-                                             ErrorResult& aError) {
-  mSelect->SetSelectedIndex(aSelectedIndex, aError);
+void HTMLOptionsCollection::SetSelectedIndex(int32_t aSelectedIndex) {
+  mSelect->SetSelectedIndex(aSelectedIndex);
 }
 
 Element* HTMLOptionsCollection::GetElementAt(uint32_t aIndex) {
   return ItemAsOption(aIndex);
 }
 
 HTMLOptionElement* HTMLOptionsCollection::NamedGetter(const nsAString& aName,
                                                       bool& aFound) {
@@ -201,14 +200,12 @@ void HTMLOptionsCollection::GetSupported
 }
 
 void HTMLOptionsCollection::Add(const HTMLOptionOrOptGroupElement& aElement,
                                 const Nullable<HTMLElementOrLong>& aBefore,
                                 ErrorResult& aError) {
   mSelect->Add(aElement, aBefore, aError);
 }
 
-void HTMLOptionsCollection::Remove(int32_t aIndex, ErrorResult& aError) {
-  mSelect->Remove(aIndex);
-}
+void HTMLOptionsCollection::Remove(int32_t aIndex) { mSelect->Remove(aIndex); }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/html/HTMLOptionsCollection.h
+++ b/dom/html/HTMLOptionsCollection.h
@@ -122,19 +122,19 @@ class HTMLOptionsCollection final : publ
   }
   HTMLOptionElement* NamedGetter(const nsAString& aName, bool& aFound);
   virtual Element* GetFirstNamedElement(const nsAString& aName,
                                         bool& aFound) override {
     return NamedGetter(aName, aFound);
   }
   void Add(const HTMLOptionOrOptGroupElement& aElement,
            const Nullable<HTMLElementOrLong>& aBefore, ErrorResult& aError);
-  void Remove(int32_t aIndex, ErrorResult& aError);
-  int32_t GetSelectedIndex(ErrorResult& aError);
-  void SetSelectedIndex(int32_t aSelectedIndex, ErrorResult& aError);
+  void Remove(int32_t aIndex);
+  int32_t SelectedIndex();
+  void SetSelectedIndex(int32_t aSelectedIndex);
   void IndexedSetter(uint32_t aIndex, HTMLOptionElement* aOption,
                      ErrorResult& aError);
   virtual void GetSupportedNames(nsTArray<nsString>& aNames) override;
   void SetLength(uint32_t aLength, ErrorResult& aError);
 
  private:
   /** The list of options (holds strong references).  This is infallible, so
    * various members such as InsertOptionAt are also infallible. */
--- a/dom/html/HTMLSelectElement.cpp
+++ b/dom/html/HTMLSelectElement.cpp
@@ -617,35 +617,31 @@ bool HTMLSelectElement::MatchSelectedOpt
 nsIHTMLCollection* HTMLSelectElement::SelectedOptions() {
   if (!mSelectedOptions) {
     mSelectedOptions = new nsContentList(this, MatchSelectedOptions, nullptr,
                                          nullptr, /* deep */ true);
   }
   return mSelectedOptions;
 }
 
-nsresult HTMLSelectElement::SetSelectedIndexInternal(int32_t aIndex,
-                                                     bool aNotify) {
+void HTMLSelectElement::SetSelectedIndexInternal(int32_t aIndex, bool aNotify) {
   int32_t oldSelectedIndex = mSelectedIndex;
   uint32_t mask = IS_SELECTED | CLEAR_ALL | SET_DISABLED;
   if (aNotify) {
     mask |= NOTIFY;
   }
 
   SetOptionsSelectedByIndex(aIndex, aIndex, mask);
 
-  nsresult rv = NS_OK;
   nsISelectControlFrame* selectFrame = GetSelectFrame();
   if (selectFrame) {
-    rv = selectFrame->OnSetSelectedIndex(oldSelectedIndex, mSelectedIndex);
+    selectFrame->OnSetSelectedIndex(oldSelectedIndex, mSelectedIndex);
   }
 
   SetSelectionChanged(true, aNotify);
-
-  return rv;
 }
 
 bool HTMLSelectElement::IsOptionSelectedByIndex(int32_t aIndex) {
   HTMLOptionElement* option = Item(static_cast<uint32_t>(aIndex));
   return option && option->Selected();
 }
 
 void HTMLSelectElement::OnOptionSelected(nsISelectControlFrame* aSelectFrame,
@@ -990,18 +986,17 @@ bool HTMLSelectElement::SelectSomething(
   }
 
   uint32_t count = Length();
   for (uint32_t i = 0; i < count; i++) {
     bool disabled;
     nsresult rv = IsOptionDisabled(i, &disabled);
 
     if (NS_FAILED(rv) || !disabled) {
-      rv = SetSelectedIndexInternal(i, aNotify);
-      NS_ENSURE_SUCCESS(rv, false);
+      SetSelectedIndexInternal(i, aNotify);
 
       UpdateValueMissingValidityState();
       UpdateState(aNotify);
 
       return true;
     }
   }
 
--- a/dom/html/HTMLSelectElement.h
+++ b/dom/html/HTMLSelectElement.h
@@ -175,19 +175,17 @@ class HTMLSelectElement final : public n
     mOptions->IndexedSetter(aIndex, aOption, aRv);
   }
 
   static bool MatchSelectedOptions(Element* aElement, int32_t, nsAtom*, void*);
 
   nsIHTMLCollection* SelectedOptions();
 
   int32_t SelectedIndex() const { return mSelectedIndex; }
-  void SetSelectedIndex(int32_t aIdx, ErrorResult& aRv) {
-    aRv = SetSelectedIndexInternal(aIdx, true);
-  }
+  void SetSelectedIndex(int32_t aIdx) { SetSelectedIndexInternal(aIdx, true); }
   void GetValue(DOMString& aValue);
   void SetValue(const nsAString& aValue);
 
   // Override SetCustomValidity so we update our state properly when it's called
   // via bindings.
   void SetCustomValidity(const nsAString& aError);
 
   using nsINode::Remove;
@@ -449,17 +447,17 @@ class HTMLSelectElement final : public n
    * Rebuilds the options array from scratch as a fallback in error cases.
    */
   void RebuildOptionsArray(bool aNotify);
 
 #ifdef DEBUG
   void VerifyOptionsArray();
 #endif
 
-  nsresult SetSelectedIndexInternal(int32_t aIndex, bool aNotify);
+  void SetSelectedIndexInternal(int32_t aIndex, bool aNotify);
 
   void SetSelectionChanged(bool aValue, bool aNotify);
 
   /**
    * Marks the selectedOptions list as dirty, so that it'll populate itself
    * again.
    */
   void UpdateSelectedOptions();
--- a/dom/html/TextTrackManager.cpp
+++ b/dom/html/TextTrackManager.cpp
@@ -13,17 +13,16 @@
 #include "mozilla/dom/HTMLTrackElement.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/TextTrack.h"
 #include "mozilla/dom/TextTrackCue.h"
 #include "nsComponentManagerUtils.h"
 #include "nsGlobalWindow.h"
 #include "nsIFrame.h"
 #include "nsIWebVTTParserWrapper.h"
-#include "nsTArrayHelpers.h"
 #include "nsVariant.h"
 #include "nsVideoFrame.h"
 
 static mozilla::LazyLogModule gTextTrackLog("TextTrackManager");
 #define WEBVTT_LOG(...) MOZ_LOG(gTextTrackLog, LogLevel::Debug, (__VA_ARGS__))
 #define WEBVTT_LOGV(...) \
   MOZ_LOG(gTextTrackLog, LogLevel::Verbose, (__VA_ARGS__))
 
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -2437,23 +2437,29 @@ void MediaDecoderStateMachine::DecodingS
       mMaster->HasLowBufferedData() && !mMaster->mCanPlayThrough) {
     SetState<BufferingState>();
   }
 }
 
 void MediaDecoderStateMachine::LoopingDecodingState::HandleError(
     const MediaResult& aError) {
   SLOG("audio looping failed, aError=%s", aError.ErrorName().get());
-  // This would happen after we've closed resouce so that we won't be able to
-  // get any sample anymore.
-  if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
-    SetState<CompletedState>();
-    return;
+  switch (aError.Code()) {
+    case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
+      HandleWaitingForAudio();
+      break;
+    case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
+      // This would happen after we've closed resouce so that we won't be able
+      // to get any sample anymore.
+      SetState<CompletedState>();
+      break;
+    default:
+      mMaster->DecodeError(aError);
+      break;
   }
-  mMaster->DecodeError(aError);
 }
 
 void MediaDecoderStateMachine::SeekingState::SeekCompleted() {
   const auto newCurrentTime = CalculateNewCurrentTime();
 
   if (newCurrentTime == mMaster->Duration() && !mMaster->mIsLiveStream) {
     // Seeked to end of media. Explicitly finish the queues so DECODING
     // will transition to COMPLETED immediately. Note we don't do
--- a/dom/tests/browser/beforeunload_test_page.html
+++ b/dom/tests/browser/beforeunload_test_page.html
@@ -79,14 +79,14 @@
       }
 
       this.$totalOuter.textContent = this._outerEventHandlers.length;
     },
 
     reset() {
       this.popInner(this._innerEventHandlers.length);
       this.popOuter(this._outerEventHandlers.length);
-    }
+    },
   };
 </script>
 
 </body>
 </html>
\ No newline at end of file
--- a/dom/tests/browser/browser_ConsoleAPITests.js
+++ b/dom/tests/browser/browser_ConsoleAPITests.js
@@ -60,17 +60,17 @@ function spawnWithObserver(browser, obse
     "        ok(false, 'Exception thrown in observe: ' + ex);",
     "      }",
     "    }",
     "  };",
     "  Services.obs.addObserver(ConsoleObserver, 'console-api-log-event', false);",
     // Call the initialization function (if present)
     func ? ("(" + func.toString() + ")();") : "",
     "});",
-  ].join('\n');
+  ].join("\n");
 
   return ContentTask.spawn(browser, null, new Function(source));
 }
 
 function waitForResolve(browser) {
   return ContentTask.spawn(browser, null, function() {
     return content._promise;
   });
@@ -96,16 +96,18 @@ async function consoleAPISanityTest(brow
     ok(win.console.time, "console.time is here");
     ok(win.console.timeEnd, "console.timeEnd is here");
     ok(win.console.timeStamp, "console.timeStamp is here");
     ok(win.console.assert, "console.assert is here");
     ok(win.console.count, "console.count is here");
   });
 }
 
+// These globals are all defined in spawnWithObserver in a sub-process.
+/* global gWindow, gArgs:true, gLevel:true, gStyle:true, expect, resolve */
 function testConsoleData(aMessageObject) {
   let messageWindow = Services.wm.getOuterWindowWithId(aMessageObject.ID);
   is(messageWindow, gWindow, "found correct window by window ID");
 
   is(aMessageObject.level, gLevel, "expected level received");
   ok(aMessageObject.arguments, "we have arguments");
 
   switch (gLevel) {
@@ -117,17 +119,17 @@ function testConsoleData(aMessageObject)
   }
   case "count": {
     is(aMessageObject.counter.label, gArgs[0].label, "label matches");
     is(aMessageObject.counter.count, gArgs[0].count, "count matches");
     break;
   }
   default: {
     is(aMessageObject.arguments.length, gArgs.length, "arguments.length matches");
-    gArgs.forEach(function (a, i) {
+    gArgs.forEach(function(a, i) {
       // Waive Xray so that we don't get messed up by Xray ToString.
       //
       // It'd be nice to just use XPCNativeWrapper.unwrap here, but there are
       // a number of dumb reasons we can't. See bug 868675.
       var arg = aMessageObject.arguments[i];
       if (Cu.isXrayWrapper(arg))
         arg = arg.wrappedJSObject;
       is(arg, a, "correct arg " + i);
@@ -265,17 +267,17 @@ async function startTraceTest(browser) {
   dump("HERE\n");
   await spawnWithObserver(browser, testTraceConsoleData, function(opts) {
     dump("Observer attached\n");
     gLevel = "trace";
     gArgs = [
       {columnNumber: 9, filename: TEST_URI, functionName: "window.foobar585956c", lineNumber: 6},
       {columnNumber: 16, filename: TEST_URI, functionName: "foobar585956b", lineNumber: 11},
       {columnNumber: 16, filename: TEST_URI, functionName: "foobar585956a", lineNumber: 15},
-      {columnNumber: 1, filename: TEST_URI, functionName: "onclick", lineNumber: 1}
+      {columnNumber: 1, filename: TEST_URI, functionName: "onclick", lineNumber: 1},
     ];
 
   });
 
   BrowserTestUtils.synthesizeMouseAtCenter("#test-trace", {}, browser);
   await waitForResolve(browser);
 }
 
@@ -285,28 +287,28 @@ function testLocationData(aMessageObject
 
   is(aMessageObject.level, gLevel, "expected level received");
   ok(aMessageObject.arguments, "we have arguments");
 
   is(aMessageObject.filename, gArgs[0].filename, "filename matches");
   is(aMessageObject.lineNumber, gArgs[0].lineNumber, "lineNumber matches");
   is(aMessageObject.functionName, gArgs[0].functionName, "functionName matches");
   is(aMessageObject.arguments.length, gArgs[0].arguments.length, "arguments.length matches");
-  gArgs[0].arguments.forEach(function (a, i) {
+  gArgs[0].arguments.forEach(function(a, i) {
     is(aMessageObject.arguments[i], a, "correct arg " + i);
   });
 
   resolve();
 }
 
 async function startLocationTest(browser) {
   await spawnWithObserver(browser, testLocationData, function(opts) {
     gLevel = "log";
     gArgs = [
-      {filename: TEST_URI, functionName: "foobar646025", arguments: ["omg", "o", "d"], lineNumber: 19}
+      {filename: TEST_URI, functionName: "foobar646025", arguments: ["omg", "o", "d"], lineNumber: 19},
     ];
   });
 
   BrowserTestUtils.synthesizeMouseAtCenter("#test-location", {}, browser);
   await waitForResolve(browser);
 }
 
 function testNativeCallback(aMessageObject) {
@@ -336,23 +338,21 @@ function testConsoleGroup(aMessageObject
 
   is(aMessageObject.functionName, "testGroups", "functionName matches");
   ok(aMessageObject.lineNumber >= 46 && aMessageObject.lineNumber <= 50,
      "lineNumber matches");
   if (aMessageObject.level == "groupCollapsed") {
     is(aMessageObject.groupName, "a group", "groupCollapsed groupName matches");
     is(aMessageObject.arguments[0], "a", "groupCollapsed arguments[0] matches");
     is(aMessageObject.arguments[1], "group", "groupCollapsed arguments[0] matches");
-  }
-  else if (aMessageObject.level == "group") {
+  } else if (aMessageObject.level == "group") {
     is(aMessageObject.groupName, "b group", "group groupName matches");
     is(aMessageObject.arguments[0], "b", "group arguments[0] matches");
     is(aMessageObject.arguments[1], "group", "group arguments[1] matches");
-  }
-  else if (aMessageObject.level == "groupEnd") {
+  } else if (aMessageObject.level == "groupEnd") {
     is(aMessageObject.groupName, "b group", "groupEnd groupName matches");
   }
 
   if (aMessageObject.level == "groupEnd") {
     resolve();
   }
 }
 
@@ -369,31 +369,31 @@ function testConsoleTime(aMessageObject)
 
   is(aMessageObject.level, gLevel, "expected level received");
 
   is(aMessageObject.filename, gArgs[0].filename, "filename matches");
   is(aMessageObject.lineNumber, gArgs[0].lineNumber, "lineNumber matches");
   is(aMessageObject.functionName, gArgs[0].functionName, "functionName matches");
   is(aMessageObject.timer.name, gArgs[0].timer.name, "timer.name matches");
 
-  gArgs[0].arguments.forEach(function (a, i) {
+  gArgs[0].arguments.forEach(function(a, i) {
     is(aMessageObject.arguments[i], a, "correct arg " + i);
   });
 
   resolve();
 }
 
 async function startTimeTest(browser) {
   await spawnWithObserver(browser, testConsoleTime, function(opts) {
     gLevel = "time";
     gArgs = [
       {filename: TEST_URI, lineNumber: 23, functionName: "startTimer",
        arguments: ["foo"],
        timer: { name: "foo" },
-      }
+      },
     ];
   });
 
   BrowserTestUtils.synthesizeMouseAtCenter("#test-time", {}, browser);
   await waitForResolve(browser);
 }
 
 function testConsoleTimeEnd(aMessageObject) {
@@ -407,17 +407,17 @@ function testConsoleTimeEnd(aMessageObje
   is(aMessageObject.lineNumber, gArgs[0].lineNumber, "lineNumber matches");
   is(aMessageObject.functionName, gArgs[0].functionName, "functionName matches");
   is(aMessageObject.arguments.length, gArgs[0].arguments.length, "arguments.length matches");
   is(aMessageObject.timer.name, gArgs[0].timer.name, "timer name matches");
   is(typeof aMessageObject.timer.duration, "number", "timer duration is a number");
   info("timer duration: " + aMessageObject.timer.duration);
   ok(aMessageObject.timer.duration >= 0, "timer duration is positive");
 
-  gArgs[0].arguments.forEach(function (a, i) {
+  gArgs[0].arguments.forEach(function(a, i) {
     is(aMessageObject.arguments[i], a, "correct arg " + i);
   });
 
   resolve();
 }
 
 async function startTimeEndTest(browser) {
   await spawnWithObserver(browser, testConsoleTimeEnd, function(opts) {
@@ -440,30 +440,30 @@ function testConsoleTimeStamp(aMessageOb
 
   is(aMessageObject.level, gLevel, "expected level received");
 
   is(aMessageObject.filename, gArgs[0].filename, "filename matches");
   is(aMessageObject.lineNumber, gArgs[0].lineNumber, "lineNumber matches");
   is(aMessageObject.functionName, gArgs[0].functionName, "functionName matches");
   ok(aMessageObject.timeStamp > 0, "timeStamp is a positive value");
 
-  gArgs[0].arguments.forEach(function (a, i) {
+  gArgs[0].arguments.forEach(function(a, i) {
     is(aMessageObject.arguments[i], a, "correct arg " + i);
   });
 
   resolve();
 }
 
 async function startTimeStampTest(browser) {
   await spawnWithObserver(browser, testConsoleTimeStamp, function() {
     gLevel = "timeStamp";
     gArgs = [
       {filename: TEST_URI, lineNumber: 58, functionName: "timeStamp",
-       arguments: ["!!!"]
-      }
+       arguments: ["!!!"],
+      },
     ];
   });
 
   BrowserTestUtils.synthesizeMouseAtCenter("#test-timeStamp", {}, browser);
   await waitForResolve(browser);
 }
 
 function testEmptyConsoleTimeStamp(aMessageObject) {
@@ -481,18 +481,18 @@ function testEmptyConsoleTimeStamp(aMess
   resolve();
 }
 
 async function startEmptyTimeStampTest(browser) {
   await spawnWithObserver(browser, testEmptyConsoleTimeStamp, function() {
     gLevel = "timeStamp";
     gArgs = [
       {filename: TEST_URI, lineNumber: 58, functionName: "timeStamp",
-       arguments: []
-      }
+       arguments: [],
+      },
     ];
   });
 
   BrowserTestUtils.synthesizeMouseAtCenter("#test-emptyTimeStamp", {}, browser);
   await waitForResolve(browser);
 }
 
 function testEmptyTimer(aMessageObject) {
--- a/dom/tests/browser/browser_ConsoleAPI_originAttributes.js
+++ b/dom/tests/browser/browser_ConsoleAPI_originAttributes.js
@@ -35,21 +35,20 @@ const ConsoleObserver = {
       });
 
       is(cachedMessages.length, 1, "found the expected cached console messages from the addon");
       is(cachedMessages[0] && cachedMessages[0].addonId, FAKE_ADDON_ID,
          "the cached message originAttributes contains the expected addonId");
 
       finish();
     }
-  }
+  },
 };
 
-function test()
-{
+function test() {
   ConsoleObserver.init();
 
   waitForExplicitFinish();
 
   let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
   let uuid = uuidGenerator.generateUUID().number;
   uuid = uuid.slice(1, -1); // Strip { and } off the UUID.
 
--- a/dom/tests/browser/browser_ConsoleStorageAPITests.js
+++ b/dom/tests/browser/browser_ConsoleStorageAPITests.js
@@ -1,21 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI_NAV = "http://example.com/browser/dom/tests/browser/";
 
-function tearDown()
-{
+function tearDown() {
   while (gBrowser.tabs.length > 1)
     gBrowser.removeCurrentTab();
 }
 
-add_task(async function()
-{
+add_task(async function() {
   // Don't cache removed tabs, so "clear console cache on tab close" triggers.
   await SpecialPowers.pushPrefEnv({ set: [[ "browser.tabs.max_tabs_undo", 0 ]] });
 
   registerCleanupFunction(tearDown);
 
   // Open a keepalive tab in the background to make sure we don't accidentally
   // kill the content process
   var keepaliveTab = BrowserTestUtils.addTab(gBrowser, "about:blank");
@@ -30,31 +28,31 @@ add_task(async function()
     let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
           .getService(Ci.nsIConsoleAPIStorage);
 
     let observerPromise = new Promise(resolve => {
       let apiCallCount = 0;
       let ConsoleObserver = {
         QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
 
-        observe: function(aSubject, aTopic, aData) {
+        observe(aSubject, aTopic, aData) {
           if (aTopic == "console-storage-cache-event") {
             apiCallCount++;
             if (apiCallCount == 4) {
               let windowId = content.window.windowUtils.currentInnerWindowID;
 
               Services.obs.removeObserver(this, "console-storage-cache-event");
               ok(ConsoleAPIStorage.getEvents(windowId).length >= 4, "Some messages found in the storage service");
               ConsoleAPIStorage.clearEvents();
               is(ConsoleAPIStorage.getEvents(windowId).length, 0, "Cleared Storage");
 
               resolve(windowId);
             }
           }
-        }
+        },
       };
 
       Services.obs.addObserver(ConsoleObserver, "console-storage-cache-event");
 
       // Redirect the browser to the test URI
       content.window.location = TEST_URI;
     });
 
@@ -80,17 +78,17 @@ add_task(async function()
   // Ensure actual window destruction is not delayed (too long).
   SpecialPowers.DOMWindowUtils.garbageCollect();
 
   // Spawn the check in the keepaliveTab, so that we can read the ConsoleAPIStorage correctly
   gBrowser.selectedTab = keepaliveTab;
   browser = gBrowser.selectedBrowser;
 
   // Spin the event loop to make sure everything is cleared.
-  await ContentTask.spawn(browser, null, function () {
+  await ContentTask.spawn(browser, null, function() {
     return Promise.resolve();
   });
 
   await ContentTask.spawn(browser, windowId, function(windowId) {
     var ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
           .getService(Ci.nsIConsoleAPIStorage);
     is(ConsoleAPIStorage.getEvents(windowId).length, 0, "tab close is clearing the cache");
   });
--- a/dom/tests/browser/browser_ConsoleStoragePBTest_perwindowpb.js
+++ b/dom/tests/browser/browser_ConsoleStoragePBTest_perwindowpb.js
@@ -24,28 +24,28 @@ function test() {
     win.addEventListener("load", function() {
       aCallback(win);
     }, {once: true});
   }
 
   function doTest(aIsPrivateMode, aWindow, aCallback) {
     BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser).then(() => {
       consoleObserver = {
-        observe: function(aSubject, aTopic, aData) {
+        observe(aSubject, aTopic, aData) {
           if (aTopic == "console-api-log-event") {
             afterEvents = ConsoleAPIStorage.getEvents(innerID);
             is(beforeEvents.length == afterEvents.length - 1, storageShouldOccur,
               "storage should" + (storageShouldOccur ? "" : " not") + " occur");
 
             executeSoon(function() {
               Services.obs.removeObserver(consoleObserver, "console-api-log-event");
               aCallback();
             });
           }
-        }
+        },
       };
 
       aWindow.Services.obs.addObserver(
         consoleObserver, "console-api-log-event");
       aWindow.nativeConsole.log("foo bar baz (private: " + aIsPrivateMode + ")");
     });
 
     // We expect that console API messages are always stored.
@@ -58,17 +58,17 @@ function test() {
   function testOnWindow(aOptions, aCallback) {
     whenNewWindowLoaded(aOptions, function(aWin) {
       windowsToClose.push(aWin);
       // execute should only be called when need, like when you are opening
       // web pages on the test. If calling executeSoon() is not necesary, then
       // call whenNewWindowLoaded() instead of testOnWindow() on your test.
       executeSoon(() => aCallback(aWin));
     });
-  };
+  }
 
    // this function is called after calling finish() on the test.
   registerCleanupFunction(function() {
     windowsToClose.forEach(function(aWin) {
       aWin.close();
     });
   });
 
--- a/dom/tests/browser/browser_allocateGigabyte.js
+++ b/dom/tests/browser/browser_allocateGigabyte.js
@@ -1,43 +1,43 @@
 /* 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/. */
 
 const TEST_URI = "http://example.com/browser/dom/tests/browser/test_largeAllocation.html";
 
 function expectProcessCreated() {
-  let os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+  let os = Services.obs;
   return new Promise(resolve => {
     let topic = "ipc:content-created";
     function observer() {
       os.removeObserver(observer, topic);
       ok(true, "Expect process created");
       resolve();
     }
     os.addObserver(observer, topic);
   });
 }
 
 add_task(async function() {
   await SpecialPowers.pushPrefEnv({
     set: [
       ["dom.largeAllocationHeader.enabled", true],
-    ]
+    ],
   });
 
   // A toplevel tab should be able to navigate cross process!
   await BrowserTestUtils.withNewTab("about:blank", async function(aBrowser) {
     let epc = expectProcessCreated();
     await ContentTask.spawn(aBrowser, TEST_URI, TEST_URI => {
       content.document.location = TEST_URI;
     });
 
     // Wait for the new process to be created by the Large-Allocation header
     await epc;
 
     // Allocate a gigabyte of memory in the content process
     await ContentTask.spawn(aBrowser, null, () => {
-      let arrayBuffer = new ArrayBuffer(1024*1024*1024);
+      let arrayBuffer = new ArrayBuffer(1024 * 1024 * 1024);
       ok(arrayBuffer, "Successfully allocated a gigabyte of memory in content process");
     });
   });
 });
--- a/dom/tests/browser/browser_beforeunload_between_chrome_content.js
+++ b/dom/tests/browser/browser_beforeunload_between_chrome_content.js
@@ -1,21 +1,21 @@
 const TEST_URL = "http://www.example.com/browser/dom/tests/browser/dummy.html";
 
 function pageScript() {
-  window.addEventListener("beforeunload", function (event) {
+  window.addEventListener("beforeunload", function(event) {
     var str = "Leaving?";
     event.returnValue = str;
     return str;
   }, true);
 }
 
 function injectBeforeUnload(browser) {
   return ContentTask.spawn(browser, null, async function() {
-    content.window.addEventListener("beforeunload", function (event) {
+    content.window.addEventListener("beforeunload", function(event) {
       sendAsyncMessage("Test:OnBeforeUnloadReceived");
       var str = "Leaving?";
       event.returnValue = str;
       return str;
     }, true);
   });
 }
 
@@ -36,16 +36,17 @@ function awaitAndCloseBeforeUnloadDialog
 
 SpecialPowers.pushPrefEnv(
   {"set": [["dom.require_user_interaction_for_beforeunload", false]]});
 
 /**
  * Test navigation from a content page to a chrome page. Also check that only
  * one beforeunload event is fired.
  */
+/* global messageManager */
 add_task(async function() {
   let beforeUnloadCount = 0;
   messageManager.addMessageListener("Test:OnBeforeUnloadReceived", function() {
     beforeUnloadCount++;
   });
 
   // Open a content page.
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
@@ -54,17 +55,17 @@ add_task(async function() {
   ok(browser.isRemoteBrowser, "Browser should be remote.");
 
   await injectBeforeUnload(browser);
   // Navigate to a chrome page.
   let dialogShown1 = awaitAndCloseBeforeUnloadDialog(false);
   await BrowserTestUtils.loadURI(browser, "about:support");
   await Promise.all([
     dialogShown1,
-    BrowserTestUtils.browserLoaded(browser)
+    BrowserTestUtils.browserLoaded(browser),
   ]);
 
   is(beforeUnloadCount, 1, "Should have received one beforeunload event.");
   ok(!browser.isRemoteBrowser, "Browser should not be remote.");
 
   // Go back to content page.
   ok(gBrowser.webNavigation.canGoBack, "Should be able to go back.");
   gBrowser.goBack();
@@ -72,17 +73,17 @@ add_task(async function() {
   await injectBeforeUnload(browser);
 
   // Test that going forward triggers beforeunload prompt as well.
   ok(gBrowser.webNavigation.canGoForward, "Should be able to go forward.");
   let dialogShown2 = awaitAndCloseBeforeUnloadDialog(false);
   gBrowser.goForward();
   await Promise.all([
     dialogShown2,
-    BrowserTestUtils.browserLoaded(browser)
+    BrowserTestUtils.browserLoaded(browser),
   ]);
   is(beforeUnloadCount, 2, "Should have received two beforeunload events.");
 
   BrowserTestUtils.removeTab(tab);
 });
 
 /**
  * Test navigation from a chrome page to a content page. Also check that only
@@ -96,51 +97,51 @@ add_task(async function() {
 
   // Open a chrome page.
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser,
                                                         "about:support");
   let browser = tab.linkedBrowser;
 
   ok(!browser.isRemoteBrowser, "Browser should not be remote.");
   await ContentTask.spawn(browser, null, async function() {
-    content.window.addEventListener("beforeunload", function (event) {
+    content.window.addEventListener("beforeunload", function(event) {
       sendAsyncMessage("Test:OnBeforeUnloadReceived");
       var str = "Leaving?";
       event.returnValue = str;
       return str;
     }, true);
   });
 
   // Navigate to a content page.
   let dialogShown1 = awaitAndCloseBeforeUnloadDialog(false);
   await BrowserTestUtils.loadURI(browser, TEST_URL);
   await Promise.all([
     dialogShown1,
-    BrowserTestUtils.browserLoaded(browser)
+    BrowserTestUtils.browserLoaded(browser),
   ]);
   is(beforeUnloadCount, 1, "Should have received one beforeunload event.");
   ok(browser.isRemoteBrowser, "Browser should be remote.");
 
   // Go back to chrome page.
   ok(gBrowser.webNavigation.canGoBack, "Should be able to go back.");
   gBrowser.goBack();
   await BrowserTestUtils.browserLoaded(browser);
   await ContentTask.spawn(browser, null, async function() {
-    content.window.addEventListener("beforeunload", function (event) {
+    content.window.addEventListener("beforeunload", function(event) {
       sendAsyncMessage("Test:OnBeforeUnloadReceived");
       var str = "Leaving?";
       event.returnValue = str;
       return str;
     }, true);
   });
 
   // Test that going forward triggers beforeunload prompt as well.
   ok(gBrowser.webNavigation.canGoForward, "Should be able to go forward.");
   let dialogShown2 = awaitAndCloseBeforeUnloadDialog(false);
   gBrowser.goForward();
   await Promise.all([
     dialogShown2,
-    BrowserTestUtils.browserLoaded(browser)
+    BrowserTestUtils.browserLoaded(browser),
   ]);
   is(beforeUnloadCount, 2, "Should have received two beforeunload events.");
 
   BrowserTestUtils.removeTab(tab);
 });
--- a/dom/tests/browser/browser_bug1004814.js
+++ b/dom/tests/browser/browser_bug1004814.js
@@ -1,32 +1,32 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 add_task(async function() {
   await BrowserTestUtils.withNewTab("about:blank", async function(aBrowser) {
-    let duration = await ContentTask.spawn(aBrowser, null, function (opts) {
+    let duration = await ContentTask.spawn(aBrowser, null, function(opts) {
       const TEST_URI = "http://example.com/browser/dom/tests/browser/test_bug1004814.html";
 
       return new Promise(resolve => {
         let ConsoleObserver = {
           QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
 
-          observe: function(aSubject, aTopic, aData) {
+          observe(aSubject, aTopic, aData) {
             var obj = aSubject.wrappedJSObject;
-            if (obj.arguments.length != 1 || obj.arguments[0] != 'bug1004814' ||
-                obj.level != 'timeEnd') {
+            if (obj.arguments.length != 1 || obj.arguments[0] != "bug1004814" ||
+                obj.level != "timeEnd") {
               return;
             }
 
             Services.obs.removeObserver(this, "console-api-log-event");
             resolve(obj.timer.duration);
-          }
+          },
         };
 
         Services.obs.addObserver(ConsoleObserver, "console-api-log-event");
 
         // Redirect the browser to the correct document to start the test
         content.document.location = TEST_URI;
       });
     });
--- a/dom/tests/browser/browser_bug1238427.js
+++ b/dom/tests/browser/browser_bug1238427.js
@@ -18,14 +18,14 @@ add_task(async function() {
   // it does not reply before we close the tab.
   Services.prefs.setCharPref("geo.wifi.uri", BASE_GEO_URL + "?delay=100000");
 
   // Open the test URI and close it. The test harness will make sure that the
   // page is cleaned up after some GCs. If geolocation is not shut down properly,
   // it will show up as a non-shutdown leak.
   await BrowserTestUtils.withNewTab({
     gBrowser,
-    url: TEST_URI
+    url: TEST_URI,
   }, function(browser) { /* ... */ });
 
   ok(true, "Need to do something in this test");
 });
 
--- a/dom/tests/browser/browser_bug1316330.js
+++ b/dom/tests/browser/browser_bug1316330.js
@@ -1,11 +1,11 @@
 const URL =
   "data:text/html,<script>" +
-  "window.focus();" + 
+  "window.focus();" +
   "var down = 0; var press = 0;" +
   "onkeydown = function(e) {" +
   "  var startTime = Date.now();" +
   "  document.body.setAttribute('data-down', ++down);" +
   "  if (e.keyCode == KeyboardEvent.DOM_VK_D) while (Date.now() - startTime < 500) {}" +
   "};" +
   "onkeypress = function(e) {" +
   "  var startTime = Date.now();" +
--- a/dom/tests/browser/browser_bug396843.js
+++ b/dom/tests/browser/browser_bug396843.js
@@ -6,185 +6,185 @@ function testInDocument(doc, documentID)
     var XMLNodes = [];
 
     // HTML
     function HTML_TAG(name) {
         allNodes.push(doc.createElement(name));
     }
 
     /* List copy/pasted from nsHTMLTagList.h */
-    HTML_TAG("a", "Anchor")
-    HTML_TAG("abbr", "Span")
-    HTML_TAG("acronym", "Span")
-    HTML_TAG("address", "Span")
-    HTML_TAG("applet", "Unknown")
-    HTML_TAG("area", "Area")
-    HTML_TAG("b", "Span")
-    HTML_TAG("base", "Shared")
-    HTML_TAG("basefont", "Span")
-    HTML_TAG("bdi", "")
-    HTML_TAG("bdo", "Span")
-    HTML_TAG("bgsound", "Span")
-    HTML_TAG("big", "Span")
-    HTML_TAG("blockquote", "Shared")
-    HTML_TAG("body", "Body")
-    HTML_TAG("br", "BR")
-    HTML_TAG("button", "Button")
-    HTML_TAG("canvas", "Canvas")
-    HTML_TAG("caption", "TableCaption")
-    HTML_TAG("center", "Span")
-    HTML_TAG("cite", "Span")
-    HTML_TAG("code", "Span")
-    HTML_TAG("col", "TableCol")
-    HTML_TAG("colgroup", "TableCol")
-    HTML_TAG("dd", "Span")
-    HTML_TAG("del", "Mod")
-    HTML_TAG("dfn", "Span")
-    HTML_TAG("dir", "Shared")
-    HTML_TAG("div", "Div")
-    HTML_TAG("dl", "SharedList")
-    HTML_TAG("dt", "Span")
-    HTML_TAG("em", "Span")
-    HTML_TAG("embed", "Embed")
-    HTML_TAG("fieldset", "FieldSet")
-    HTML_TAG("font", "Font")
-    HTML_TAG("form", "Form")
-    HTML_TAG("frame", "Frame")
-    HTML_TAG("frameset", "FrameSet")
-    HTML_TAG("h1", "Heading")
-    HTML_TAG("h2", "Heading")
-    HTML_TAG("h3", "Heading")
-    HTML_TAG("h4", "Heading")
-    HTML_TAG("h5", "Heading")
-    HTML_TAG("h6", "Heading")
-    HTML_TAG("head", "Head")
-    HTML_TAG("hr", "HR")
-    HTML_TAG("html", "Html")
-    HTML_TAG("i", "Span")
-    HTML_TAG("iframe", "IFrame")
-    HTML_TAG("image", "")
-    HTML_TAG("img", "Image")
-    HTML_TAG("input", "Input")
-    HTML_TAG("ins", "Mod")
-    HTML_TAG("isindex", "Unknown")
-    HTML_TAG("kbd", "Span")
-    HTML_TAG("keygen", "Span")
-    HTML_TAG("label", "Label")
-    HTML_TAG("legend", "Legend")
-    HTML_TAG("li", "LI")
-    HTML_TAG("link", "Link")
-    HTML_TAG("listing", "Span")
-    HTML_TAG("map", "Map")
-    HTML_TAG("marquee", "Div")
-    HTML_TAG("menu", "Shared")
-    HTML_TAG("meta", "Meta")
-    HTML_TAG("multicol", "Unknown")
-    HTML_TAG("nobr", "Span")
-    HTML_TAG("noembed", "Div")
-    HTML_TAG("noframes", "Div")
-    HTML_TAG("noscript", "Div")
-    HTML_TAG("object", "Object")
-    HTML_TAG("ol", "SharedList")
-    HTML_TAG("optgroup", "OptGroup")
-    HTML_TAG("option", "Option")
-    HTML_TAG("p", "Paragraph")
-    HTML_TAG("param", "Shared")
-    HTML_TAG("plaintext", "Span")
-    HTML_TAG("pre", "Pre")
-    HTML_TAG("q", "Shared")
-    HTML_TAG("s", "Span")
-    HTML_TAG("samp", "Span")
-    HTML_TAG("script", "Script")
-    HTML_TAG("select", "Select")
-    HTML_TAG("small", "Span")
-    HTML_TAG("spacer", "Unknown")
-    HTML_TAG("span", "Span")
-    HTML_TAG("strike", "Span")
-    HTML_TAG("strong", "Span")
-    HTML_TAG("style", "Style")
-    HTML_TAG("sub", "Span")
-    HTML_TAG("sup", "Span")
-    HTML_TAG("table", "Table")
-    HTML_TAG("tbody", "TableSection")
-    HTML_TAG("td", "TableCell")
-    HTML_TAG("textarea", "TextArea")
-    HTML_TAG("tfoot", "TableSection")
-    HTML_TAG("th", "TableCell")
-    HTML_TAG("thead", "TableSection")
-    HTML_TAG("template", "Template")
-    HTML_TAG("title", "Title")
-    HTML_TAG("tr", "TableRow")
-    HTML_TAG("tt", "Span")
-    HTML_TAG("u", "Span")
-    HTML_TAG("ul", "SharedList")
-    HTML_TAG("var", "Span")
-    HTML_TAG("wbr", "Shared")
-    HTML_TAG("xmp", "Span")
+    HTML_TAG("a", "Anchor");
+    HTML_TAG("abbr", "Span");
+    HTML_TAG("acronym", "Span");
+    HTML_TAG("address", "Span");
+    HTML_TAG("applet", "Unknown");
+    HTML_TAG("area", "Area");
+    HTML_TAG("b", "Span");
+    HTML_TAG("base", "Shared");
+    HTML_TAG("basefont", "Span");
+    HTML_TAG("bdi", "");
+    HTML_TAG("bdo", "Span");
+    HTML_TAG("bgsound", "Span");
+    HTML_TAG("big", "Span");
+    HTML_TAG("blockquote", "Shared");
+    HTML_TAG("body", "Body");
+    HTML_TAG("br", "BR");
+    HTML_TAG("button", "Button");
+    HTML_TAG("canvas", "Canvas");
+    HTML_TAG("caption", "TableCaption");
+    HTML_TAG("center", "Span");
+    HTML_TAG("cite", "Span");
+    HTML_TAG("code", "Span");
+    HTML_TAG("col", "TableCol");
+    HTML_TAG("colgroup", "TableCol");
+    HTML_TAG("dd", "Span");
+    HTML_TAG("del", "Mod");
+    HTML_TAG("dfn", "Span");
+    HTML_TAG("dir", "Shared");
+    HTML_TAG("div", "Div");
+    HTML_TAG("dl", "SharedList");
+    HTML_TAG("dt", "Span");
+    HTML_TAG("em", "Span");
+    HTML_TAG("embed", "Embed");
+    HTML_TAG("fieldset", "FieldSet");
+    HTML_TAG("font", "Font");
+    HTML_TAG("form", "Form");
+    HTML_TAG("frame", "Frame");
+    HTML_TAG("frameset", "FrameSet");
+    HTML_TAG("h1", "Heading");
+    HTML_TAG("h2", "Heading");
+    HTML_TAG("h3", "Heading");
+    HTML_TAG("h4", "Heading");
+    HTML_TAG("h5", "Heading");
+    HTML_TAG("h6", "Heading");
+    HTML_TAG("head", "Head");
+    HTML_TAG("hr", "HR");
+    HTML_TAG("html", "Html");
+    HTML_TAG("i", "Span");
+    HTML_TAG("iframe", "IFrame");
+    HTML_TAG("image", "");
+    HTML_TAG("img", "Image");
+    HTML_TAG("input", "Input");
+    HTML_TAG("ins", "Mod");
+    HTML_TAG("isindex", "Unknown");
+    HTML_TAG("kbd", "Span");
+    HTML_TAG("keygen", "Span");
+    HTML_TAG("label", "Label");
+    HTML_TAG("legend", "Legend");
+    HTML_TAG("li", "LI");
+    HTML_TAG("link", "Link");
+    HTML_TAG("listing", "Span");
+    HTML_TAG("map", "Map");
+    HTML_TAG("marquee", "Div");
+    HTML_TAG("menu", "Shared");
+    HTML_TAG("meta", "Meta");
+    HTML_TAG("multicol", "Unknown");
+    HTML_TAG("nobr", "Span");
+    HTML_TAG("noembed", "Div");
+    HTML_TAG("noframes", "Div");
+    HTML_TAG("noscript", "Div");
+    HTML_TAG("object", "Object");
+    HTML_TAG("ol", "SharedList");
+    HTML_TAG("optgroup", "OptGroup");
+    HTML_TAG("option", "Option");
+    HTML_TAG("p", "Paragraph");
+    HTML_TAG("param", "Shared");
+    HTML_TAG("plaintext", "Span");
+    HTML_TAG("pre", "Pre");
+    HTML_TAG("q", "Shared");
+    HTML_TAG("s", "Span");
+    HTML_TAG("samp", "Span");
+    HTML_TAG("script", "Script");
+    HTML_TAG("select", "Select");
+    HTML_TAG("small", "Span");
+    HTML_TAG("spacer", "Unknown");
+    HTML_TAG("span", "Span");
+    HTML_TAG("strike", "Span");
+    HTML_TAG("strong", "Span");
+    HTML_TAG("style", "Style");
+    HTML_TAG("sub", "Span");
+    HTML_TAG("sup", "Span");
+    HTML_TAG("table", "Table");
+    HTML_TAG("tbody", "TableSection");
+    HTML_TAG("td", "TableCell");
+    HTML_TAG("textarea", "TextArea");
+    HTML_TAG("tfoot", "TableSection");
+    HTML_TAG("th", "TableCell");
+    HTML_TAG("thead", "TableSection");
+    HTML_TAG("template", "Template");
+    HTML_TAG("title", "Title");
+    HTML_TAG("tr", "TableRow");
+    HTML_TAG("tt", "Span");
+    HTML_TAG("u", "Span");
+    HTML_TAG("ul", "SharedList");
+    HTML_TAG("var", "Span");
+    HTML_TAG("wbr", "Shared");
+    HTML_TAG("xmp", "Span");
 
     function SVG_TAG(name) {
         allNodes.push(doc.createElementNS("http://www.w3.org/2000/svg", name));
     }
 
     // List sorta stolen from SVG element factory.
-    SVG_TAG("a")
-    SVG_TAG("polyline")
-    SVG_TAG("polygon")
-    SVG_TAG("circle")
-    SVG_TAG("ellipse")
-    SVG_TAG("line")
-    SVG_TAG("rect")
-    SVG_TAG("svg")
-    SVG_TAG("g")
-    SVG_TAG("foreignObject")
-    SVG_TAG("path")
-    SVG_TAG("text")
-    SVG_TAG("tspan")
-    SVG_TAG("image")
-    SVG_TAG("style")
-    SVG_TAG("linearGradient")
-    SVG_TAG("metadata")
-    SVG_TAG("radialGradient")
-    SVG_TAG("stop")
-    SVG_TAG("defs")
-    SVG_TAG("desc")
-    SVG_TAG("script")
-    SVG_TAG("use")
-    SVG_TAG("symbol")
-    SVG_TAG("marker")
-    SVG_TAG("title")
-    SVG_TAG("clipPath")
-    SVG_TAG("textPath")
-    SVG_TAG("filter")
-    SVG_TAG("feBlend")
-    SVG_TAG("feColorMatrix")
-    SVG_TAG("feComponentTransfer")
-    SVG_TAG("feComposite")
-    SVG_TAG("feFuncR")
-    SVG_TAG("feFuncG")
-    SVG_TAG("feFuncB")
-    SVG_TAG("feFuncA")
-    SVG_TAG("feGaussianBlur")
-    SVG_TAG("feMerge")
-    SVG_TAG("feMergeNode")
-    SVG_TAG("feMorphology")
-    SVG_TAG("feOffset")
-    SVG_TAG("feFlood")
-    SVG_TAG("feTile")
-    SVG_TAG("feTurbulence")
-    SVG_TAG("feConvolveMatrix")
-    SVG_TAG("feDistantLight")
-    SVG_TAG("fePointLight")
-    SVG_TAG("feSpotLight")
-    SVG_TAG("feDiffuseLighting")
-    SVG_TAG("feSpecularLighting")
-    SVG_TAG("feDisplacementMap")
-    SVG_TAG("feImage")
-    SVG_TAG("pattern")
-    SVG_TAG("mask")
-    SVG_TAG("svgSwitch")
+    SVG_TAG("a");
+    SVG_TAG("polyline");
+    SVG_TAG("polygon");
+    SVG_TAG("circle");
+    SVG_TAG("ellipse");
+    SVG_TAG("line");
+    SVG_TAG("rect");
+    SVG_TAG("svg");
+    SVG_TAG("g");
+    SVG_TAG("foreignObject");
+    SVG_TAG("path");
+    SVG_TAG("text");
+    SVG_TAG("tspan");
+    SVG_TAG("image");
+    SVG_TAG("style");
+    SVG_TAG("linearGradient");
+    SVG_TAG("metadata");
+    SVG_TAG("radialGradient");
+    SVG_TAG("stop");
+    SVG_TAG("defs");
+    SVG_TAG("desc");
+    SVG_TAG("script");
+    SVG_TAG("use");
+    SVG_TAG("symbol");
+    SVG_TAG("marker");
+    SVG_TAG("title");
+    SVG_TAG("clipPath");
+    SVG_TAG("textPath");
+    SVG_TAG("filter");
+    SVG_TAG("feBlend");
+    SVG_TAG("feColorMatrix");
+    SVG_TAG("feComponentTransfer");
+    SVG_TAG("feComposite");
+    SVG_TAG("feFuncR");
+    SVG_TAG("feFuncG");
+    SVG_TAG("feFuncB");
+    SVG_TAG("feFuncA");
+    SVG_TAG("feGaussianBlur");
+    SVG_TAG("feMerge");
+    SVG_TAG("feMergeNode");
+    SVG_TAG("feMorphology");
+    SVG_TAG("feOffset");
+    SVG_TAG("feFlood");
+    SVG_TAG("feTile");
+    SVG_TAG("feTurbulence");
+    SVG_TAG("feConvolveMatrix");
+    SVG_TAG("feDistantLight");
+    SVG_TAG("fePointLight");
+    SVG_TAG("feSpotLight");
+    SVG_TAG("feDiffuseLighting");
+    SVG_TAG("feSpecularLighting");
+    SVG_TAG("feDisplacementMap");
+    SVG_TAG("feImage");
+    SVG_TAG("pattern");
+    SVG_TAG("mask");
+    SVG_TAG("svgSwitch");
 
     // Toss in some other namespaced stuff too, for good measure
     // XUL stuff might not be creatable in content documents
     try {
         allNodes.push(doc.createElementNS(
             "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
             "window"));
     } catch (e) {}
@@ -197,17 +197,17 @@ function testInDocument(doc, documentID)
     var XMLDoc = doc.implementation.createDocument("", "", null);
 
     // And non-elements
     allNodes.push(doc.createTextNode("some text"));
     allNodes.push(doc.createComment("some text"));
     allNodes.push(doc.createDocumentFragment());
     XMLNodes.push(XMLDoc.createCDATASection("some text"));
     XMLNodes.push(XMLDoc.createProcessingInstruction("PI", "data"));
-        
+
     function runTestUnwrapped() {
         if (!("wrappedJSObject" in doc)) {
             return;
         }
         ok(doc.wrappedJSObject.nodePrincipal === undefined,
             "Must not have document principal for " + documentID);
         ok(doc.wrappedJSObject.baseURIObject === undefined,
             "Must not have document base URI for " + documentID);
@@ -268,16 +268,17 @@ add_task(async function test1() {
     testInDocument(document, "browser window");
 });
 
 async function newTabTest(location) {
     await BrowserTestUtils.withNewTab({ gBrowser, url: location },
         async function(browser) {
           await ContentTask.spawn(browser, { location, testInDocument_: testInDocument.toSource() },
             async function({ location, testInDocument_ }) {
+              // eslint-disable-next-line no-eval
               let testInDocument = eval(`(() => (${testInDocument_}))()`);
               testInDocument(content.document, location);
             });
         });
 }
 
 add_task(async function test2() {
     await newTabTest("about:blank");