Merge mozilla-central to inbound. a=merge CLOSED TREE
authorshindli <shindli@mozilla.com>
Fri, 17 May 2019 19:38:09 +0300
changeset 474330 f4c4b796f845283408175b4b30383d1b83347a58
parent 474196 76e3a842e496d78a80cd547b7bf94f041f9bc612 (current diff)
parent 474329 bc17771ceb28c31cd06889be51ae2eda72efc451 (diff)
child 474331 62321377e204a921c6cf4282565756267fbea2dc
push id113144
push usershindli@mozilla.com
push dateFri, 17 May 2019 16:44:55 +0000
treeherdermozilla-inbound@f4c4b796f845 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
devtools/client/aboutdebugging-new/src/components/ExtensionDebugSetting.js
devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionAction.js
devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_setting_thisfirefox.js
devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_setting_usb.js
gfx/wr/webrender/src/picture.rs
servo/components/style/gecko/rules.rs
servo/components/style/gecko_bindings/sugar/ns_css_shadow_array.rs
servo/components/style/gecko_bindings/sugar/ns_css_shadow_item.rs
servo/components/style/gecko_bindings/sugar/ns_css_value.rs
testing/raptor/raptor/filter.py
third_party/rust/hashbrown/.cargo-checksum.json
third_party/rust/hashbrown/CHANGELOG.md
third_party/rust/hashbrown/Cargo.toml
third_party/rust/hashbrown/LICENSE-APACHE
third_party/rust/hashbrown/LICENSE-MIT
third_party/rust/hashbrown/README.md
third_party/rust/hashbrown/benches/bench.rs
third_party/rust/hashbrown/bors.toml
third_party/rust/hashbrown/src/external_trait_impls/mod.rs
third_party/rust/hashbrown/src/external_trait_impls/rayon/helpers.rs
third_party/rust/hashbrown/src/external_trait_impls/rayon/map.rs
third_party/rust/hashbrown/src/external_trait_impls/rayon/mod.rs
third_party/rust/hashbrown/src/external_trait_impls/rayon/raw.rs
third_party/rust/hashbrown/src/external_trait_impls/rayon/set.rs
third_party/rust/hashbrown/src/external_trait_impls/serde.rs
third_party/rust/hashbrown/src/fx.rs
third_party/rust/hashbrown/src/lib.rs
third_party/rust/hashbrown/src/map.rs
third_party/rust/hashbrown/src/raw/bitmask.rs
third_party/rust/hashbrown/src/raw/generic.rs
third_party/rust/hashbrown/src/raw/mod.rs
third_party/rust/hashbrown/src/raw/sse2.rs
third_party/rust/hashbrown/src/set.rs
third_party/rust/hashbrown/tests/rayon.rs
third_party/rust/hashbrown/tests/serde.rs
third_party/rust/hashbrown/tests/set.rs
third_party/rust/rand/appveyor.yml
third_party/rust/rand/benches/bench.rs
third_party/rust/rand/benches/distributions/exponential.rs
third_party/rust/rand/benches/distributions/gamma.rs
third_party/rust/rand/benches/distributions/mod.rs
third_party/rust/rand/benches/distributions/normal.rs
third_party/rust/rand/src/distributions/range.rs
third_party/rust/rand/src/jitter.rs
third_party/rust/rand/src/os.rs
third_party/rust/rand/src/prng/chacha.rs
third_party/rust/rand/src/prng/isaac.rs
third_party/rust/rand/src/prng/isaac64.rs
third_party/rust/rand/src/prng/xorshift.rs
third_party/rust/rand/src/rand_impls.rs
third_party/rust/rand/src/read.rs
third_party/rust/rand/src/reseeding.rs
third_party/rust/rand/src/seq.rs
third_party/rust/rand/utils/ziggurat_tables.py
third_party/rust/sha1/LICENSE
toolkit/components/telemetry/tests/marionette/moz.build
toolkit/themes/osx/global/icons/searchfield-cancel.svg
toolkit/themes/windows/global/icons/Search-close.png
--- a/.flake8
+++ b/.flake8
@@ -20,16 +20,17 @@ exclude =
     layout/style,
     media/libdav1d/generate_source.py,
     moz.configure,
     netwerk/dns/prepare_tlds.py,
     netwerk/protocol/http/make_incoming_tables.py,
     python/devtools/migrate-l10n/migrate/main.py,
     python/l10n/fluent_migrations,
     python/mozbuild/dumbmake,
+    python/mozbuild/mozbuild,
     servo/components/style,
     testing/jsshell/benchmark.py,
     testing/marionette/mach_commands.py,
     testing/mozharness/docs,
     testing/mozharness/examples,
     testing/mozharness/external_tools,
     testing/mozharness/mach_commands.py,
     testing/mozharness/manifestparser,
@@ -63,17 +64,16 @@ exclude =
     ipc/chromium/src/third_party/,
     js/*.configure,
     gfx/angle/,
     gfx/harfbuzz,
     gfx/skia/,
     memory/moz.configure,
     mobile/android/*.configure,
     node_modules,
-    python/mozbuild/mozbuild/test/configure/data,
     security/nss/,
     testing/marionette/harness/marionette_harness/runner/mixins,
     testing/marionette/harness/marionette_harness/tests,
     testing/mochitest/pywebsocket,
     testing/mozharness/configs/test/test_malformed.py,
     tools/lint/test/files,
     tools/infer/test/*.configure,
     tools/crashreporter/*.configure,
@@ -87,13 +87,11 @@ ignore =
     F632, F633, F811, E117, W504, W605, W606,
     # These are intentionally disabled (not necessarily for good reason).
     #   F723: syntax error in type comment
     #       text contains quotes which breaks our custom JSON formatter
     F723, E121, E123, E126, E129, E133, E226, E241, E242, E402, E704, E741, W503,
 
 per-file-ignores =
     ipc/ipdl/*: F403, F405
-    # cpp_eclipse has a lot of multi-line embedded XML which exceeds line length
-    python/mozbuild/mozbuild/backend/cpp_eclipse.py: E501
     testing/firefox-ui/**/__init__.py: F401
     testing/marionette/**/__init__.py: F401
     testing/mozharness/configs/*: E124, E127, E128, E131, E231, E261, E265, E266, E501, W391
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -160,17 +160,17 @@ dependencies = [
  "cc 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "baldrdash"
 version = "0.1.0"
 dependencies = [
- "bindgen 0.49.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bindgen 0.49.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
  "cranelift-wasm 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
  "env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -222,25 +222,25 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "bindgen"
-version = "0.49.0"
+version = "0.49.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "cexpr 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "clang-sys 0.28.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "which 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -1331,25 +1331,16 @@ dependencies = [
  "indexmap 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "string 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-io 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "hashbrown"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "scopeguard 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
 name = "hashglobe"
 version = "0.1.0"
 dependencies = [
  "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "http"
@@ -1461,17 +1452,17 @@ dependencies = [
 name = "itoa"
 version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "js"
 version = "0.1.4"
 dependencies = [
- "bindgen 0.49.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bindgen 0.49.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "cmake 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "mozjs_sys 0.0.0",
  "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2746,17 +2737,17 @@ source = "registry+https://github.com/ru
 
 [[package]]
 name = "style"
 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.49.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bindgen 0.49.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.25.3 (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",
@@ -2813,16 +2804,17 @@ dependencies = [
 [[package]]
 name = "style_traits"
 version = "0.0.1"
 dependencies = [
  "app_units 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.25.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.19.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "malloc_size_of 0.0.1",
  "malloc_size_of_derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.21.0",
  "servo_arc 0.1.1",
  "to_shmem 0.0.1",
  "to_shmem_derive 0.0.1",
 ]
 
@@ -3620,17 +3612,17 @@ dependencies = [
 "checksum atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb2dcb6e6d35f20276943cc04bb98e538b348d525a04ac79c10021561d202f21"
 "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
 "checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a"
 "checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0"
 "checksum base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "621fc7ecb8008f86d7fb9b95356cd692ce9514b80a86d85b397f32a22da7b9e2"
 "checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
 "checksum binary-space-partition 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88ceb0d16c4fd0e42876e298d7d3ce3780dd9ebdcbe4199816a32c77e08597ff"
 "checksum bincode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bda13183df33055cbb84b847becce220d392df502ebe7a4a78d7021771ed94d0"
-"checksum bindgen 0.49.0 (registry+https://github.com/rust-lang/crates.io-index)" = "33e1b67a27bca31fd12a683b2a3618e275311117f48cfcc892e18403ff889026"
+"checksum bindgen 0.49.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6bd7710ac8399ae1ebe1e3aac7c9047c4f39f2c94b33c997f482f49e96991f7c"
 "checksum binjs_meta 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "430239e4551e42b80fa5d92322ac80ea38c9dda56e5d5582e057e2288352b71a"
 "checksum bit-set 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6f1efcc46c18245a69c38fcc5cc650f16d3a59d034f3106e9ed63748f695730a"
 "checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb"
 "checksum bit_reverse 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5e97e02db5a2899c0377f3d6031d5da8296ca2b47abef6ed699de51b9e40a28c"
 "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
 "checksum bitreader 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "80b13e2ab064ff3aa0bdbf1eff533f9822dc37899821f5f98c67f263eab51707"
 "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400"
 "checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
@@ -3721,17 +3713,16 @@ dependencies = [
 "checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb"
 "checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592"
 "checksum gl_generator 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39a23d5e872a275135d66895d954269cf5e8661d234eb1c2480f4ce0d586acbd"
 "checksum gleam 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7f46fd8874e043ffac0d638ed1567a2584f7814f6d72b4db37ab1689004a26c4"
 "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
 "checksum goblin 0.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "5911d7df7b8f65ab676c5327b50acea29d3c6a1a4ad05e444cf5dce321b26db2"
 "checksum guid_win 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "87261686cc5e35b6584f4c2a430c2b153d8a92ab1ef820c16be34c1df8f5f58b"
 "checksum h2 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "a27e7ed946e8335bdf9a191bc1b9b14a03ba822d013d2f58437f4fabcbd7fc2c"
-"checksum hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da"
 "checksum http 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "dca621d0fa606a5ff2850b6e337b57ad6137ee4d67e940449643ff45af6874c6"
 "checksum httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "af2f2dd97457e8fb1ae7c5a420db346af389926e36f43768b96f101546b04a07"
 "checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e"
 "checksum hyper 0.12.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c087746de95e20e4dabe86606c3a019964a8fde2d5f386152939063c116c5971"
 "checksum ident_case 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c9826188e666f2ed92071d2dadef6edc430b11b158b5b2b3f4babbcc891eaaa"
 "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d"
 "checksum image 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)" = "293e54ce142a936a39da748ba8178ae6aa1914b82d846a4278f11590c89bf116"
 "checksum indexmap 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08173ba1e906efb6538785a8844dd496f5d34f0a2d88038e95195172fc667220"
--- a/accessible/tests/browser/head.js
+++ b/accessible/tests/browser/head.js
@@ -10,18 +10,17 @@
 /**
  * Set e10s related preferences in the test environment.
  * @return {Promise} promise that resolves when preferences are set.
  */
 function setE10sPrefs() {
   return new Promise(resolve =>
     SpecialPowers.pushPrefEnv({
       set: [
-        ["browser.tabs.remote.autostart", true],
-        ["browser.tabs.remote.force-enable", true]
+        ["browser.tabs.remote.autostart", true]
       ]
     }, resolve));
 }
 
 /**
  * Unset e10s related preferences in the test environment.
  * @return {Promise} promise that resolves when preferences are unset.
  */
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -475,32 +475,44 @@ var gXPInstallObserver = {
       action = {
         label: gNavigatorBundle.getString("xpinstallPromptMessage.install"),
         accessKey: gNavigatorBundle.getString("xpinstallPromptMessage.install.accesskey"),
         callback() {
           secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED_CLICK_THROUGH);
           installInfo.install();
         },
       };
-      let secondaryAction = {
+      let dontAllowAction = {
         label: gNavigatorBundle.getString("xpinstallPromptMessage.dontAllow"),
         accessKey: gNavigatorBundle.getString("xpinstallPromptMessage.dontAllow.accesskey"),
         callback: () => {
           for (let install of installInfo.installs) {
             if (install.state != AddonManager.STATE_CANCELLED) {
               install.cancel();
             }
           }
         },
       };
+      let neverAllowAction = {
+        label: gNavigatorBundle.getString("xpinstallPromptMessage.neverAllow"),
+        accessKey: gNavigatorBundle.getString("xpinstallPromptMessage.neverAllow.accesskey"),
+        callback: () => {
+          SitePermissions.set(browser.currentURI, "install", SitePermissions.BLOCK);
+          for (let install of installInfo.installs) {
+            if (install.state != AddonManager.STATE_CANCELLED) {
+              install.cancel();
+            }
+          }
+        },
+      };
 
       secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED);
       let popup = PopupNotifications.show(browser, notificationID,
                                           messageString, anchorID,
-                                          action, [secondaryAction], options);
+                                          action, [dontAllowAction, neverAllowAction], options);
       removeNotificationOnEnd(popup, installInfo.installs);
       break; }
     case "addon-install-started": {
       let needsDownload = function needsDownload(aInstall) {
         return aInstall.state != AddonManager.STATE_DOWNLOADED;
       };
       // If all installs have already been downloaded then there is no need to
       // show the download progress
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -1152,19 +1152,19 @@ var gIdentityHandler = {
       if (aPermission.state == SitePermissions.getDefault(aPermission.id)) {
         menulist.value = "0";
       } else {
         menulist.value = aPermission.state;
       }
 
       // Avoiding listening to the "select" event on purpose. See Bug 1404262.
       menulist.addEventListener("command", () => {
-        SitePermissions.setForPrincipal(gBrowser.contentPrincipal,
-                                        aPermission.id,
-                                        menulist.selectedItem.value);
+        SitePermissions.set(gBrowser.currentURI,
+                            aPermission.id,
+                            menulist.selectedItem.value);
       });
 
       container.appendChild(img);
       container.appendChild(nameLabel);
       container.appendChild(menulist);
       container.setAttribute("aria-labelledby", nameLabelId);
       block.appendChild(container);
 
@@ -1208,35 +1208,35 @@ var gIdentityHandler = {
           ["camera", "microphone", "screen"].includes(aPermission.id)) {
         let windowId = this._sharingState.windowId;
         if (aPermission.id == "screen") {
           windowId = "screen:" + windowId;
         } else {
           // If we set persistent permissions or the sharing has
           // started due to existing persistent permissions, we need
           // to handle removing these even for frames with different hostnames.
-          let principals = browser._devicePermissionPrincipals || [];
-          for (let principal of principals) {
+          let uris = browser._devicePermissionURIs || [];
+          for (let uri of uris) {
             // It's not possible to stop sharing one of camera/microphone
             // without the other.
             for (let id of ["camera", "microphone"]) {
               if (this._sharingState[id]) {
-                let perm = SitePermissions.getForPrincipal(principal, id);
+                let perm = SitePermissions.get(uri, id);
                 if (perm.state == SitePermissions.ALLOW &&
                     perm.scope == SitePermissions.SCOPE_PERSISTENT) {
-                  SitePermissions.removeFromPrincipal(principal, id);
+                  SitePermissions.remove(uri, id);
                 }
               }
             }
           }
         }
         browser.messageManager.sendAsyncMessage("webrtc:StopSharing", windowId);
         webrtcUI.forgetActivePermissionsFromBrowser(gBrowser.selectedBrowser);
       }
-      SitePermissions.removeFromPrincipal(gBrowser.contentPrincipal, aPermission.id, browser);
+      SitePermissions.remove(gBrowser.currentURI, aPermission.id, browser);
 
       this._permissionReloadHint.removeAttribute("hidden");
       PanelView.forNode(this._identityPopupMainView)
                .descriptionHeightWorkaround();
     });
 
     container.appendChild(button);
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7248,78 +7248,75 @@ var CanvasPermissionPromptHelper = {
   },
 
   uninit() {
     Services.obs.removeObserver(this, this._permissionsPrompt);
     Services.obs.removeObserver(this, this._permissionsPromptHideDoorHanger);
   },
 
   // aSubject is an nsIBrowser (e10s) or an nsIDOMWindow (non-e10s).
-  // aData is an Origin string.
+  // aData is an URL string.
   observe(aSubject, aTopic, aData) {
     if (aTopic != this._permissionsPrompt &&
         aTopic != this._permissionsPromptHideDoorHanger) {
       return;
     }
 
     let browser;
     if (aSubject instanceof Ci.nsIDOMWindow) {
       let contentWindow = aSubject.QueryInterface(Ci.nsIDOMWindow);
       browser = contentWindow.docShell.chromeEventHandler;
     } else {
       browser = aSubject;
     }
 
+    let uri = Services.io.newURI(aData);
     if (gBrowser.selectedBrowser !== browser) {
       // Must belong to some other window.
       return;
     }
 
     let message = gNavigatorBundle.getFormattedString("canvas.siteprompt", ["<>"], 1);
 
-    let principal = Services.scriptSecurityManager
-                            .createCodebasePrincipalFromOrigin(aData);
-
-    function setCanvasPermission(aPerm, aPersistent) {
-      Services.perms.addFromPrincipal(
-        principal, "canvas", aPerm,
-        aPersistent ? Ci.nsIPermissionManager.EXPIRE_NEVER
-                    : Ci.nsIPermissionManager.EXPIRE_SESSION);
+    function setCanvasPermission(aURI, aPerm, aPersistent) {
+      Services.perms.add(aURI, "canvas", aPerm,
+                          aPersistent ? Ci.nsIPermissionManager.EXPIRE_NEVER
+                                      : Ci.nsIPermissionManager.EXPIRE_SESSION);
     }
 
     let mainAction = {
       label: gNavigatorBundle.getString("canvas.allow"),
       accessKey: gNavigatorBundle.getString("canvas.allow.accesskey"),
       callback(state) {
-        setCanvasPermission(Ci.nsIPermissionManager.ALLOW_ACTION,
+        setCanvasPermission(uri, Ci.nsIPermissionManager.ALLOW_ACTION,
                             state && state.checkboxChecked);
       },
     };
 
     let secondaryActions = [{
       label: gNavigatorBundle.getString("canvas.notAllow"),
       accessKey: gNavigatorBundle.getString("canvas.notAllow.accesskey"),
       callback(state) {
-        setCanvasPermission(Ci.nsIPermissionManager.DENY_ACTION,
+        setCanvasPermission(uri, Ci.nsIPermissionManager.DENY_ACTION,
                             state && state.checkboxChecked);
       },
     }];
 
     let checkbox = {
       // In PB mode, we don't want the "always remember" checkbox
       show: !PrivateBrowsingUtils.isWindowPrivate(window),
     };
     if (checkbox.show) {
       checkbox.checked = true;
       checkbox.label = gBrowserBundle.GetStringFromName("canvas.remember");
     }
 
     let options = {
       checkbox,
-      name: principal.URI.host,
+      name: uri.asciiHost,
       learnMoreURL: Services.urlFormatter.formatURLPref("app.support.baseURL") + "fingerprint-permission",
       dismissed: aTopic == this._permissionsPromptHideDoorHanger,
     };
     PopupNotifications.show(browser, this._permissionsPrompt, message,
                             this._notificationIcon, mainAction,
                             secondaryActions, options);
   },
 };
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -883,16 +883,18 @@
                   <image data-permission-id="autoplay-media" class="blocked-permission-icon autoplay-media-icon" role="button"
                          tooltiptext="&urlbar.autoplayMediaBlocked.tooltip;"/>
                   <image data-permission-id="canvas" class="blocked-permission-icon canvas-icon" role="button"
                          tooltiptext="&urlbar.canvasBlocked.tooltip;"/>
                   <image data-permission-id="plugin:flash" class="blocked-permission-icon plugin-icon" role="button"
                          tooltiptext="&urlbar.flashPluginBlocked.tooltip;"/>
                   <image data-permission-id="midi" class="blocked-permission-icon midi-icon" role="button"
                          tooltiptext="&urlbar.midiBlocked.tooltip;"/>
+                  <image data-permission-id="install" class="blocked-permission-icon install-icon" role="button"
+                         tooltiptext="&urlbar.installBlocked.tooltip;"/>
                 </box>
                 <box id="notification-popup-box"
                      hidden="true"
                      onmouseover="document.getElementById('identity-box').classList.add('no-hover');"
                      onmouseout="document.getElementById('identity-box').classList.remove('no-hover');"
                      align="center">
                   <image id="default-notification-icon" class="notification-anchor-icon" role="button"
                          tooltiptext="&urlbar.defaultNotificationAnchor.tooltip;"/>
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -1,45 +1,47 @@
 /* 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/. */
 
 /* import-globals-from pageInfo.js */
 
 const {SitePermissions} = ChromeUtils.import("resource:///modules/SitePermissions.jsm");
 
+var gPermURI;
 var gPermPrincipal;
 var gUsageRequest;
 
 // Array of permissionIDs sorted alphabetically by label.
 var gPermissions = SitePermissions.listPermissions()
   .filter(p => SitePermissions.getPermissionLabel(p) != null)
   .sort((a, b) => {
     let firstLabel = SitePermissions.getPermissionLabel(a);
     let secondLabel = SitePermissions.getPermissionLabel(b);
     return firstLabel.localeCompare(secondLabel);
   });
 
 var permissionObserver = {
   observe(aSubject, aTopic, aData) {
     if (aTopic == "perm-changed") {
       var permission = aSubject.QueryInterface(Ci.nsIPermission);
-      if (permission.matches(gPermPrincipal, true) && gPermissions.includes(permission.type)) {
+      if (permission.matchesURI(gPermURI, true) && gPermissions.includes(permission.type)) {
           initRow(permission.type);
       }
     }
   },
 };
 
 function onLoadPermission(uri, principal) {
   var permTab = document.getElementById("permTab");
-  if (SitePermissions.isSupportedPrincipal(principal)) {
+  if (SitePermissions.isSupportedURI(uri)) {
+    gPermURI = uri;
     gPermPrincipal = principal;
     var hostText = document.getElementById("hostText");
-    hostText.value = uri.displayPrePath;
+    hostText.value = gPermURI.displayPrePath;
 
     for (var i of gPermissions) {
       initRow(i);
     }
     Services.obs.addObserver(permissionObserver, "perm-changed");
     onUnloadRegistry.push(onUnloadPermission);
     permTab.hidden = false;
   } else {
@@ -56,17 +58,17 @@ function onUnloadPermission() {
   }
 }
 
 function initRow(aPartId) {
   createRow(aPartId);
 
   var checkbox = document.getElementById(aPartId + "Def");
   var command  = document.getElementById("cmd_" + aPartId + "Toggle");
-  var {state, scope} = SitePermissions.getForPrincipal(gPermPrincipal, aPartId);
+  var {state, scope} = SitePermissions.get(gPermURI, aPartId);
   let defaultState = SitePermissions.getDefault(aPartId);
 
   // Since cookies preferences have many different possible configuration states
   // we don't consider any permission except "no permission" to be default.
   if (aPartId == "cookie") {
     state = Services.perms.testPermissionFromPrincipal(gPermPrincipal, "cookie");
 
     if (state == SitePermissions.UNKNOWN) {
@@ -161,29 +163,29 @@ function createRow(aPartId) {
 
   document.getElementById("permList").appendChild(row);
 }
 
 function onCheckboxClick(aPartId) {
   var command  = document.getElementById("cmd_" + aPartId + "Toggle");
   var checkbox = document.getElementById(aPartId + "Def");
   if (checkbox.checked) {
-    SitePermissions.removeFromPrincipal(gPermPrincipal, aPartId);
+    SitePermissions.remove(gPermURI, aPartId);
     command.setAttribute("disabled", "true");
   } else {
     onRadioClick(aPartId);
     command.removeAttribute("disabled");
   }
 }
 
 function onRadioClick(aPartId) {
   var radioGroup = document.getElementById(aPartId + "RadioGroup");
   var id = radioGroup.selectedItem ? radioGroup.selectedItem.id : "#1";
   var permission = parseInt(id.split("#")[1]);
-  SitePermissions.setForPrincipal(gPermPrincipal, aPartId, permission);
+  SitePermissions.set(gPermURI, aPartId, permission);
 }
 
 function setRadioState(aPartId, aValue) {
   var radio = document.getElementById(aPartId + "#" + aValue);
   if (radio) {
     radio.radioGroup.selectedItem = radio;
   }
 }
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -1173,17 +1173,19 @@ window._gBrowser = {
       }
     }
 
     // Focus the location bar if it was previously focused for that tab.
     // In full screen mode, only bother making the location bar visible
     // if the tab is a blank one.
     if (newBrowser._urlbarFocused && gURLBar) {
       // Explicitly close the popup if the URL bar retains focus
-      gURLBar.closePopup();
+      if (!gURLBar.openViewOnFocus) {
+        gURLBar.closePopup();
+      }
 
       // If the user happened to type into the URL bar for this browser
       // by the time we got here, focusing will cause the text to be
       // selected which could cause them to overwrite what they've
       // already typed in.
       if (gURLBar.focused && newBrowser.userTypedValue) {
         return;
       }
--- a/browser/base/content/test/performance/browser_startup_content_mainthreadio.js
+++ b/browser/base/content/test/performance/browser_startup_content_mainthreadio.js
@@ -81,16 +81,17 @@ const processes = {
     { // bug 1357205
       path: "XREAppFeat:webcompat@mozilla.org.xpi",
       condition: !WIN,
       stat: 1,
     },
     { // bug 1357205
       path: "XREAppFeat:formautofill@mozilla.org.xpi",
       condition: !WIN,
+      ignoreIfUnused: true,
       stat: 1,
     },
   ],
   "Privileged Content": [
     {
       path: "GreD:omni.ja",
       condition: !WIN, // Visible on Windows with an open marker
       stat: 1,
@@ -339,16 +340,30 @@ add_task(async function() {
       }
       info(`(${marker.source}) ${marker.operation} - ${marker.filename}`);
       if (kDumpAllStacks) {
         info(getStackFromProfile(profile, marker.stackId).map(f => "  " + f)
                                                          .join("\n"));
       }
     }
 
+    if (!whitelist.length) {
+      continue;
+    }
+    // The I/O interposer is disabled if !RELEASE_OR_BETA, so we expect to have
+    // no I/O marker in that case, but it's good to keep the test running to check
+    // that we are still able to produce startup profiles.
+    is(markers.length > 0, !AppConstants.RELEASE_OR_BETA,
+       procName + " startup profiles should have IO markers in builds that are not RELEASE_OR_BETA");
+    if (!markers.length) {
+      // If a profile unexpectedly contains no I/O marker, avoid generating
+      // plenty of confusing "unused whitelist entry" failures.
+      continue;
+    }
+
     for (let entry of whitelist) {
       for (let op in entry) {
         if (["path", "condition", "ignoreIfUnused", "_used"].includes(op)) {
           continue;
         }
         let message = `${op} on ${entry.path} `;
         if (entry[op] == 0) {
           message += "as many times as expected";
@@ -356,28 +371,29 @@ add_task(async function() {
           message += `allowed ${entry[op]} more times`;
         } else {
           message += `${entry[op] * -1} more times than expected`;
         }
         ok(entry[op] >= 0, `${message} in ${procName} process`);
       }
       if (!("_used" in entry) && !entry.ignoreIfUnused) {
         ok(false, `unused whitelist entry ${procName}: ${entry.path}`);
+        shouldPass = false;
       }
     }
   }
 
   if (shouldPass) {
     ok(shouldPass, "No unexpected main thread I/O during startup");
   } else {
     const filename = "child-startup-mainthreadio-profile.json";
     let path = Cc["@mozilla.org/process/environment;1"]
                  .getService(Ci.nsIEnvironment)
                  .get("MOZ_UPLOAD_DIR");
     let encoder = new TextEncoder();
     let profilePath = OS.Path.join(path, filename);
     await OS.File.writeAtomic(profilePath,
                               encoder.encode(JSON.stringify(startupRecorder.data.profile)));
     ok(false,
-       "Found some unexpected main thread I/O during child process startup; " +
+       "Unexpected main thread I/O behavior during child process startup; " +
        "profile uploaded in " + filename);
   }
 });
--- a/browser/base/content/test/performance/browser_startup_mainthreadio.js
+++ b/browser/base/content/test/performance/browser_startup_mainthreadio.js
@@ -779,16 +779,17 @@ add_task(async function() {
   let profile = startupRecorder.data.profile.threads[0];
 
   let phases = {};
   {
     const nameCol = profile.markers.schema.name;
     const dataCol = profile.markers.schema.data;
 
     let markersForCurrentPhase = [];
+    let foundIOMarkers = false;
 
     for (let m of profile.markers.data) {
       let markerName = profile.stringTable[m[nameCol]];
       if (markerName.startsWith("startupRecorder:")) {
         phases[markerName.split("startupRecorder:")[1]] = markersForCurrentPhase;
         markersForCurrentPhase = [];
         continue;
       }
@@ -802,16 +803,28 @@ add_task(async function() {
       }
 
       let samples = markerData.stack.samples;
       let stack = samples.data[0][samples.schema.stack];
       markersForCurrentPhase.push({operation: markerData.operation,
                                    filename: markerData.filename,
                                    source: markerData.source,
                                    stackId: stack});
+      foundIOMarkers = true;
+    }
+
+    // The I/O interposer is disabled if !RELEASE_OR_BETA, so we expect to have
+    // no I/O marker in that case, but it's good to keep the test running to check
+    // that we are still able to produce startup profiles.
+    is(foundIOMarkers, !AppConstants.RELEASE_OR_BETA,
+       "The IO interposer should be enabled in builds that are not RELEASE_OR_BETA");
+    if (!foundIOMarkers) {
+      // If a profile unexpectedly contains no I/O marker, it's better to return
+      // early to avoid having plenty of confusing "unused whitelist entry" failures.
+      return;
     }
   }
 
   for (let phase in startupPhases) {
     startupPhases[phase] =
       startupPhases[phase].filter(entry => !("condition" in entry) || entry.condition);
     startupPhases[phase].forEach(entry => {
       entry.path = expandWhitelistPath(entry.path, entry.canonicalize);
@@ -894,28 +907,29 @@ add_task(async function() {
           message += `allowed ${entry[op]} more times`;
         } else {
           message += `${entry[op] * -1} more times than expected`;
         }
         ok(entry[op] >= 0, `${message} ${phase}`);
       }
       if (!("_used" in entry) && !entry.ignoreIfUnused) {
         ok(false, `unused whitelist entry ${phase}: ${entry.path}`);
+        shouldPass = false;
       }
     }
   }
 
   if (shouldPass) {
     ok(shouldPass, "No unexpected main thread I/O during startup");
   } else {
     const filename = "startup-mainthreadio-profile.json";
     let path = Cc["@mozilla.org/process/environment;1"]
                  .getService(Ci.nsIEnvironment)
                  .get("MOZ_UPLOAD_DIR");
     let encoder = new TextEncoder();
     let profilePath = OS.Path.join(path, filename);
     await OS.File.writeAtomic(profilePath,
                               encoder.encode(JSON.stringify(startupRecorder.data.profile)));
     ok(false,
-       "Found some unexpected main thread I/O during startup; profile uploaded in " +
+       "Unexpected main thread I/O behavior during startup; profile uploaded in " +
        filename);
   }
 });
--- a/browser/base/content/test/performance/io/browser.ini
+++ b/browser/base/content/test/performance/io/browser.ini
@@ -1,11 +1,13 @@
 [DEFAULT]
 # Currently disabled on debug due to debug-only failures, see bug 1549723.
-skip-if = debug || (os == "linux" && asan) # bug 1549729
+# Disabled on Linux asan due to bug 1549729.
+# Disabled on Windows Arm64 due to bug 1551493.
+skip-if = debug || (os == "linux" && asan) || (os == "win" && processor == "aarch64")
 # to avoid overhead when running the browser normally, startupRecorder.js will
 # do almost nothing unless browser.startup.record is true.
 # gfx.canvas.willReadFrequently.enable is just an optimization, but needs to be
 # set during early startup to have an impact as a canvas will be used by
 # startupRecorder.js
 prefs =
   # Skip migration work in BG__migrateUI for browser_startup.js since it isn't
   # representative of common startup, and triggers Places I/O.
new file mode 100644
--- /dev/null
+++ b/browser/branding/aurora/content/aboutlogins.svg
@@ -0,0 +1,59 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="80" width="460" viewBox="0 0 460 80">
+  <defs>
+    <linearGradient id="a" x1="57.63" y1="9.47" x2="21.37" y2="72.26" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#ff980e"/>
+      <stop offset=".11" stop-color="#ff851b"/>
+      <stop offset=".57" stop-color="#ff3750"/>
+      <stop offset=".8" stop-color="#f92261"/>
+      <stop offset="1" stop-color="#f5156c"/>
+    </linearGradient>
+    <linearGradient id="b" x1="57.31" y1="-.8" x2="27.68" y2="69.03" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#fff261" stop-opacity=".8"/>
+      <stop offset=".06" stop-color="#fff261" stop-opacity=".68"/>
+      <stop offset=".19" stop-color="#fff261" stop-opacity=".48"/>
+      <stop offset=".31" stop-color="#fff261" stop-opacity=".31"/>
+      <stop offset=".42" stop-color="#fff261" stop-opacity=".17"/>
+      <stop offset=".53" stop-color="#fff261" stop-opacity=".08"/>
+      <stop offset=".63" stop-color="#fff261" stop-opacity=".02"/>
+      <stop offset=".72" stop-color="#fff261" stop-opacity="0"/>
+    </linearGradient>
+    <linearGradient id="c" x1="71.71" y1="75.85" x2="71.71" y2="28.29" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#0090ed"/>
+      <stop offset=".5" stop-color="#9059ff"/>
+      <stop offset=".81" stop-color="#b833e1"/>
+    </linearGradient>
+    <linearGradient id="d" x1="17.89" y1="78.48" x2="48.5" y2="26.39" gradientUnits="userSpaceOnUse">
+      <stop offset=".02" stop-color="#0090ed"/>
+      <stop offset=".49" stop-color="#9059ff"/>
+      <stop offset="1" stop-color="#b833e1"/>
+    </linearGradient>
+    <linearGradient id="e" x1="21.87" y1="58.41" x2="4.02" y2="40.56" gradientUnits="userSpaceOnUse">
+      <stop offset=".14" stop-color="#592acb" stop-opacity="0"/>
+      <stop offset=".33" stop-color="#542bc8" stop-opacity=".03"/>
+      <stop offset=".53" stop-color="#462fbf" stop-opacity=".11"/>
+      <stop offset=".74" stop-color="#2f35b1" stop-opacity=".25"/>
+      <stop offset=".95" stop-color="#0f3d9c" stop-opacity=".44"/>
+      <stop offset="1" stop-color="#054096" stop-opacity=".5"/>
+    </linearGradient>
+    <linearGradient id="f" x1="75.86" y1="38.71" x2="66.87" y2="54.27" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#722291" stop-opacity=".5"/>
+      <stop offset=".5" stop-color="#b833e1" stop-opacity="0"/>
+    </linearGradient>
+    <linearGradient id="g" x1="56.84" y1="60.96" x2="46.4" y2="72.73" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#054096" stop-opacity=".5"/>
+      <stop offset=".03" stop-color="#0f3d9c" stop-opacity=".44"/>
+      <stop offset=".17" stop-color="#2f35b1" stop-opacity=".25"/>
+      <stop offset=".3" stop-color="#462fbf" stop-opacity=".11"/>
+      <stop offset=".43" stop-color="#542bc8" stop-opacity=".03"/>
+      <stop offset=".56" stop-color="#592acb" stop-opacity="0"/>
+    </linearGradient>
+  </defs>
+  <path d="M76.46 30.15A312.48 312.48 0 0 0 49.84 3.53a15.47 15.47 0 0 0-19.69 0A312.48 312.48 0 0 0 3.53 30.16a15.47 15.47 0 0 0 0 19.69 312.48 312.48 0 0 0 26.63 26.62A14.87 14.87 0 0 0 40 80a14.93 14.93 0 0 0 9.88-3.56c4.9-4.42 9.37-8.69 13.68-13.07a4.45 4.45 0 0 0-.34-6.11L50 44.93a15.18 15.18 0 0 0 5.08-12 15.4 15.4 0 0 0-14.4-14.64 15.2 15.2 0 0 0-11.36 4.16 15.28 15.28 0 0 0 .3 22.48l-4.78 4.33A3.86 3.86 0 0 0 30 55l5.29-4.8.14-.13a7.24 7.24 0 0 0 2.11-5.43A7.34 7.34 0 0 0 35 39.35a7.58 7.58 0 1 1 9.77 0 7.39 7.39 0 0 0-2.62 5.32 7.25 7.25 0 0 0 2.11 5.4l.09.09 11.1 10.4c-3.4 3.37-6.94 6.71-10.71 10.13a7.89 7.89 0 0 1-9.46 0 307.34 307.34 0 0 1-26-26 7.91 7.91 0 0 1 0-9.46 304.52 304.52 0 0 1 26-26 7.89 7.89 0 0 1 9.45 0 304.52 304.52 0 0 1 26 26 7.89 7.89 0 0 1 0 9.45c-1.33 1.5-2.66 3-4 4.37a3.85 3.85 0 1 0 5.67 5.22c1.32-1.43 2.68-2.93 4-4.47a15.47 15.47 0 0 0 .06-19.65z" fill="url(#a)"/>
+  <path d="M76.46 30.15A312.48 312.48 0 0 0 49.84 3.53a15.47 15.47 0 0 0-19.69 0A312.48 312.48 0 0 0 3.53 30.16a15.47 15.47 0 0 0 0 19.69 312.48 312.48 0 0 0 26.63 26.62A14.87 14.87 0 0 0 40 80a14.93 14.93 0 0 0 9.88-3.56c4.9-4.42 9.37-8.69 13.68-13.07a4.45 4.45 0 0 0-.34-6.11L50 44.93a15.18 15.18 0 0 0 5.08-12 15.4 15.4 0 0 0-14.4-14.64 15.2 15.2 0 0 0-11.36 4.16 15.28 15.28 0 0 0 .3 22.48l-4.78 4.33A3.86 3.86 0 0 0 30 55l5.29-4.8.14-.13a7.24 7.24 0 0 0 2.11-5.43A7.34 7.34 0 0 0 35 39.35a7.58 7.58 0 1 1 9.77 0 7.39 7.39 0 0 0-2.62 5.32 7.25 7.25 0 0 0 2.11 5.4l.09.09 11.1 10.4c-3.4 3.37-6.94 6.71-10.71 10.13a7.89 7.89 0 0 1-9.46 0 307.34 307.34 0 0 1-26-26 7.91 7.91 0 0 1 0-9.46 304.52 304.52 0 0 1 26-26 7.89 7.89 0 0 1 9.45 0 304.52 304.52 0 0 1 26 26 7.89 7.89 0 0 1 0 9.45c-1.33 1.5-2.66 3-4 4.37a3.85 3.85 0 1 0 5.67 5.22c1.32-1.43 2.68-2.93 4-4.47a15.47 15.47 0 0 0 .06-19.65z" fill="url(#b)"/>
+  <path d="M70.69 35.27a7.89 7.89 0 0 1 0 9.45c-1.33 1.5-2.66 3-4 4.37a3.85 3.85 0 1 0 5.67 5.22c1.32-1.43 2.68-2.93 4-4.47 4.82-5.33-5.67-14.57-5.67-14.57z" fill="url(#c)"/>
+  <path d="M55.45 60.56c-3.4 3.37-6.94 6.71-10.71 10.13a7.89 7.89 0 0 1-9.46 0 307.34 307.34 0 0 1-26-26 7.91 7.91 0 0 1 0-9.46l-1.75 2a12.89 12.89 0 0 0 .21 17.27 309.82 309.82 0 0 0 22.42 21.97A14.87 14.87 0 0 0 40 80a14.93 14.93 0 0 0 9.88-3.56c2.79-2.52 5.89-5.43 8.67-8.11a3.37 3.37 0 0 0 0-4.86z" fill="url(#d)"/>
+  <path d="M7.78 54.53c2.92 3.17 5.83 6.2 8.81 9.16l1.19-1.94c1-1.59 2-3.15 3.07-4.71-3.85-3.91-7.66-7.95-11.54-12.3a7.91 7.91 0 0 1 0-9.46l-1.75 2a12.89 12.89 0 0 0 .18 17.22z" fill="url(#e)" opacity=".9"/>
+  <path d="M70.69 35.27a7.89 7.89 0 0 1 0 9.45c-1.33 1.5-2.66 3-4 4.37a3.85 3.85 0 1 0 5.67 5.22c1.32-1.43 2.68-2.93 4-4.47 4.82-5.33-5.67-14.57-5.67-14.57z" fill="url(#f)"/>
+  <path d="M58.51 63.47l-3.06-2.91c-3.4 3.37-6.94 6.71-10.72 10.13a7.71 7.71 0 0 1-6.07 1.48v7.77c.44 0 .88.06 1.33.06a14.93 14.93 0 0 0 9.88-3.56c2.79-2.52 5.89-5.43 8.67-8.11a3.36 3.36 0 0 0-.03-4.86z" fill="url(#g)" opacity=".9"/>
+  <path d="M97 56.15h6.25v-13h14.44v-5.8h-14.48v-7.41h14.44v-5.89H97zm28.35-34.38a3.79 3.79 0 0 0-3.87 3.95 3.9 3.9 0 0 0 7.79 0 3.77 3.77 0 0 0-3.96-3.95zm-3.08 34.38h6.21V32.41h-6.21zm17-20.09v-3.65h-6v23.74h6V43.62c0-4 2-5.58 5.15-5.58a5.59 5.59 0 0 1 3.17.83l2.2-6a8.78 8.78 0 0 0-4-.92c-3 .05-5.38 1.29-6.52 4.11zm23.42-4.14a12.27 12.27 0 0 0-12.46 12.41c0 6.9 4.93 12.31 12.59 12.31a12.5 12.5 0 0 0 11-5.5l-5-2.9a6.5 6.5 0 0 1-5.9 3.17 6.61 6.61 0 0 1-6.83-5H175V44.1a11.84 11.84 0 0 0-12.31-12.18zm-6.43 9.72a6.61 6.61 0 0 1 6.43-4.57 6 6 0 0 1 6.25 4.57zm24.39-10.07v.84h-3.92v5.45h3.92v18.29h6V37.86h5.72v-5.45h-5.72v-1.15c0-3.6.49-4.22 4.71-4.22h1V21.5h-1.58c-7.71 0-10.13 2.38-10.13 10.07zm25.71.35A12.36 12.36 0 1 0 219 44.28a12.31 12.31 0 0 0-12.64-12.36zm0 19a6.64 6.64 0 1 1 6.52-6.64 6.53 6.53 0 0 1-6.48 6.6zM244 32.41h-7.13l-5 7-5-7h-7.22L228.06 44l-8.89 12.18h7.31l5.33-7.43 5.45 7.43h7.36L235.55 44zm18.89-8.36h-3.7v32.1h20.25v-3.56h-16.55zm31 7.87a12.36 12.36 0 1 0 12.37 12.36 12.2 12.2 0 0 0-12.38-12.36zm0 21.29a8.61 8.61 0 0 1-8.67-8.93 8.7 8.7 0 1 1 17.39 0 8.65 8.65 0 0 1-8.73 8.93zm28.35 0a8.88 8.88 0 0 1-8.89-9 8.77 8.77 0 0 1 8.76-9 8.4 8.4 0 0 1 8 5.28l3.3-1.45a12 12 0 0 0-11.23-7.22 12.36 12.36 0 1 0 .05 24.72 11.88 11.88 0 0 0 11.31-7.48l-3.34-1.41a8.5 8.5 0 0 1-7.97 5.6zm37.06-20.8h-4.53l-12.59 13.41V21.5h-3.57v34.65h3.57V50l5.06-5.36 8.27 11.56h4.36l-10-13.8zm30.16 18.69l-7.09-18.69H379l-7.26 18.82-6-18.82H362l7.79 23.74h3.52l7.31-18.82 7.31 18.82h3.25l8.24-23.74h-3.75zm16-28.85a2.86 2.86 0 1 0 2.86 2.86 2.79 2.79 0 0 0-2.88-2.86zm-1.81 33.9h3.66V32.41h-3.66zm19-13.72c-3.7-.74-6.56-1.23-6.56-3.69 0-2.2 1.5-3.78 5.15-3.78a7.65 7.65 0 0 1 6.51 3.16l3-2a11.52 11.52 0 0 0-9.51-4.22c-5.64 0-8.85 3.21-8.85 6.9 0 4.75 4.36 5.85 8.45 6.64 3.48.71 6.74 1.24 6.74 3.87 0 2.38-1.94 4.14-5.64 4.14a8.79 8.79 0 0 1-7.66-3.87l-3.12 2.11c2.11 3 5.5 5 10.7 5 6.2 0 9.37-3.65 9.37-7.39-.04-4.93-4.53-6.07-8.62-6.87zM459.76 44a11.72 11.72 0 0 0-12.1-12.1 12.2 12.2 0 0 0-12.42 12.45c0 7 4.93 12.27 12.55 12.27a12.21 12.21 0 0 0 11-6l-3.08-1.8a8.5 8.5 0 0 1-8 4.57c-5 0-8.5-3.21-9-7.74h21zm-20.91-1.54a8.84 8.84 0 0 1 8.81-7.35 8 8 0 0 1 8.41 7.35z" fill="#20123a"/>
+</svg>
--- a/browser/branding/aurora/content/jar.mn
+++ b/browser/branding/aurora/content/jar.mn
@@ -3,16 +3,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 browser.jar:
 % content branding %content/branding/ contentaccessible=yes
   content/branding/about.png
   content/branding/about-logo.png
   content/branding/about-logo@2x.png
   content/branding/about-wordmark.svg
+  content/branding/aboutlogins.svg
   content/branding/icon16.png                    (../default16.png)
   content/branding/icon32.png                    (../default32.png)
   content/branding/icon48.png                    (../default48.png)
   content/branding/icon64.png                    (../default64.png)
   content/branding/icon128.png                   (../default128.png)
   content/branding/identity-icons-brand.svg
   content/branding/aboutDialog.css
   content/branding/horizontal-lockup.svg
new file mode 100644
--- /dev/null
+++ b/browser/branding/nightly/content/aboutlogins.svg
@@ -0,0 +1,59 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="80" width="460" viewBox="0 0 460 80">
+  <defs>
+    <linearGradient id="a" x1="57.63" y1="9.47" x2="21.37" y2="72.26" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#ff980e"/>
+      <stop offset=".11" stop-color="#ff851b"/>
+      <stop offset=".57" stop-color="#ff3750"/>
+      <stop offset=".8" stop-color="#f92261"/>
+      <stop offset="1" stop-color="#f5156c"/>
+    </linearGradient>
+    <linearGradient id="b" x1="57.31" y1="-.8" x2="27.68" y2="69.03" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#fff261" stop-opacity=".8"/>
+      <stop offset=".06" stop-color="#fff261" stop-opacity=".68"/>
+      <stop offset=".19" stop-color="#fff261" stop-opacity=".48"/>
+      <stop offset=".31" stop-color="#fff261" stop-opacity=".31"/>
+      <stop offset=".42" stop-color="#fff261" stop-opacity=".17"/>
+      <stop offset=".53" stop-color="#fff261" stop-opacity=".08"/>
+      <stop offset=".63" stop-color="#fff261" stop-opacity=".02"/>
+      <stop offset=".72" stop-color="#fff261" stop-opacity="0"/>
+    </linearGradient>
+    <linearGradient id="c" x1="71.71" y1="75.85" x2="71.71" y2="28.29" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#0090ed"/>
+      <stop offset=".5" stop-color="#9059ff"/>
+      <stop offset=".81" stop-color="#b833e1"/>
+    </linearGradient>
+    <linearGradient id="d" x1="17.89" y1="78.48" x2="48.5" y2="26.39" gradientUnits="userSpaceOnUse">
+      <stop offset=".02" stop-color="#0090ed"/>
+      <stop offset=".49" stop-color="#9059ff"/>
+      <stop offset="1" stop-color="#b833e1"/>
+    </linearGradient>
+    <linearGradient id="e" x1="21.87" y1="58.41" x2="4.02" y2="40.56" gradientUnits="userSpaceOnUse">
+      <stop offset=".14" stop-color="#592acb" stop-opacity="0"/>
+      <stop offset=".33" stop-color="#542bc8" stop-opacity=".03"/>
+      <stop offset=".53" stop-color="#462fbf" stop-opacity=".11"/>
+      <stop offset=".74" stop-color="#2f35b1" stop-opacity=".25"/>
+      <stop offset=".95" stop-color="#0f3d9c" stop-opacity=".44"/>
+      <stop offset="1" stop-color="#054096" stop-opacity=".5"/>
+    </linearGradient>
+    <linearGradient id="f" x1="75.86" y1="38.71" x2="66.87" y2="54.27" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#722291" stop-opacity=".5"/>
+      <stop offset=".5" stop-color="#b833e1" stop-opacity="0"/>
+    </linearGradient>
+    <linearGradient id="g" x1="56.84" y1="60.96" x2="46.4" y2="72.73" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#054096" stop-opacity=".5"/>
+      <stop offset=".03" stop-color="#0f3d9c" stop-opacity=".44"/>
+      <stop offset=".17" stop-color="#2f35b1" stop-opacity=".25"/>
+      <stop offset=".3" stop-color="#462fbf" stop-opacity=".11"/>
+      <stop offset=".43" stop-color="#542bc8" stop-opacity=".03"/>
+      <stop offset=".56" stop-color="#592acb" stop-opacity="0"/>
+    </linearGradient>
+  </defs>
+  <path d="M76.46 30.15A312.48 312.48 0 0 0 49.84 3.53a15.47 15.47 0 0 0-19.69 0A312.48 312.48 0 0 0 3.53 30.16a15.47 15.47 0 0 0 0 19.69 312.48 312.48 0 0 0 26.63 26.62A14.87 14.87 0 0 0 40 80a14.93 14.93 0 0 0 9.88-3.56c4.9-4.42 9.37-8.69 13.68-13.07a4.45 4.45 0 0 0-.34-6.11L50 44.93a15.18 15.18 0 0 0 5.08-12 15.4 15.4 0 0 0-14.4-14.64 15.2 15.2 0 0 0-11.36 4.16 15.28 15.28 0 0 0 .3 22.48l-4.78 4.33A3.86 3.86 0 0 0 30 55l5.29-4.8.14-.13a7.24 7.24 0 0 0 2.11-5.43A7.34 7.34 0 0 0 35 39.35a7.58 7.58 0 1 1 9.77 0 7.39 7.39 0 0 0-2.62 5.32 7.25 7.25 0 0 0 2.11 5.4l.09.09 11.1 10.4c-3.4 3.37-6.94 6.71-10.71 10.13a7.89 7.89 0 0 1-9.46 0 307.34 307.34 0 0 1-26-26 7.91 7.91 0 0 1 0-9.46 304.52 304.52 0 0 1 26-26 7.89 7.89 0 0 1 9.45 0 304.52 304.52 0 0 1 26 26 7.89 7.89 0 0 1 0 9.45c-1.33 1.5-2.66 3-4 4.37a3.85 3.85 0 1 0 5.67 5.22c1.32-1.43 2.68-2.93 4-4.47a15.47 15.47 0 0 0 .06-19.65z" fill="url(#a)"/>
+  <path d="M76.46 30.15A312.48 312.48 0 0 0 49.84 3.53a15.47 15.47 0 0 0-19.69 0A312.48 312.48 0 0 0 3.53 30.16a15.47 15.47 0 0 0 0 19.69 312.48 312.48 0 0 0 26.63 26.62A14.87 14.87 0 0 0 40 80a14.93 14.93 0 0 0 9.88-3.56c4.9-4.42 9.37-8.69 13.68-13.07a4.45 4.45 0 0 0-.34-6.11L50 44.93a15.18 15.18 0 0 0 5.08-12 15.4 15.4 0 0 0-14.4-14.64 15.2 15.2 0 0 0-11.36 4.16 15.28 15.28 0 0 0 .3 22.48l-4.78 4.33A3.86 3.86 0 0 0 30 55l5.29-4.8.14-.13a7.24 7.24 0 0 0 2.11-5.43A7.34 7.34 0 0 0 35 39.35a7.58 7.58 0 1 1 9.77 0 7.39 7.39 0 0 0-2.62 5.32 7.25 7.25 0 0 0 2.11 5.4l.09.09 11.1 10.4c-3.4 3.37-6.94 6.71-10.71 10.13a7.89 7.89 0 0 1-9.46 0 307.34 307.34 0 0 1-26-26 7.91 7.91 0 0 1 0-9.46 304.52 304.52 0 0 1 26-26 7.89 7.89 0 0 1 9.45 0 304.52 304.52 0 0 1 26 26 7.89 7.89 0 0 1 0 9.45c-1.33 1.5-2.66 3-4 4.37a3.85 3.85 0 1 0 5.67 5.22c1.32-1.43 2.68-2.93 4-4.47a15.47 15.47 0 0 0 .06-19.65z" fill="url(#b)"/>
+  <path d="M70.69 35.27a7.89 7.89 0 0 1 0 9.45c-1.33 1.5-2.66 3-4 4.37a3.85 3.85 0 1 0 5.67 5.22c1.32-1.43 2.68-2.93 4-4.47 4.82-5.33-5.67-14.57-5.67-14.57z" fill="url(#c)"/>
+  <path d="M55.45 60.56c-3.4 3.37-6.94 6.71-10.71 10.13a7.89 7.89 0 0 1-9.46 0 307.34 307.34 0 0 1-26-26 7.91 7.91 0 0 1 0-9.46l-1.75 2a12.89 12.89 0 0 0 .21 17.27 309.82 309.82 0 0 0 22.42 21.97A14.87 14.87 0 0 0 40 80a14.93 14.93 0 0 0 9.88-3.56c2.79-2.52 5.89-5.43 8.67-8.11a3.37 3.37 0 0 0 0-4.86z" fill="url(#d)"/>
+  <path d="M7.78 54.53c2.92 3.17 5.83 6.2 8.81 9.16l1.19-1.94c1-1.59 2-3.15 3.07-4.71-3.85-3.91-7.66-7.95-11.54-12.3a7.91 7.91 0 0 1 0-9.46l-1.75 2a12.89 12.89 0 0 0 .18 17.22z" fill="url(#e)" opacity=".9"/>
+  <path d="M70.69 35.27a7.89 7.89 0 0 1 0 9.45c-1.33 1.5-2.66 3-4 4.37a3.85 3.85 0 1 0 5.67 5.22c1.32-1.43 2.68-2.93 4-4.47 4.82-5.33-5.67-14.57-5.67-14.57z" fill="url(#f)"/>
+  <path d="M58.51 63.47l-3.06-2.91c-3.4 3.37-6.94 6.71-10.72 10.13a7.71 7.71 0 0 1-6.07 1.48v7.77c.44 0 .88.06 1.33.06a14.93 14.93 0 0 0 9.88-3.56c2.79-2.52 5.89-5.43 8.67-8.11a3.36 3.36 0 0 0-.03-4.86z" fill="url(#g)" opacity=".9"/>
+  <path d="M97 56.15h6.25v-13h14.44v-5.8h-14.48v-7.41h14.44v-5.89H97zm28.35-34.38a3.79 3.79 0 0 0-3.87 3.95 3.9 3.9 0 0 0 7.79 0 3.77 3.77 0 0 0-3.96-3.95zm-3.08 34.38h6.21V32.41h-6.21zm17-20.09v-3.65h-6v23.74h6V43.62c0-4 2-5.58 5.15-5.58a5.59 5.59 0 0 1 3.17.83l2.2-6a8.78 8.78 0 0 0-4-.92c-3 .05-5.38 1.29-6.52 4.11zm23.42-4.14a12.27 12.27 0 0 0-12.46 12.41c0 6.9 4.93 12.31 12.59 12.31a12.5 12.5 0 0 0 11-5.5l-5-2.9a6.5 6.5 0 0 1-5.9 3.17 6.61 6.61 0 0 1-6.83-5H175V44.1a11.84 11.84 0 0 0-12.31-12.18zm-6.43 9.72a6.61 6.61 0 0 1 6.43-4.57 6 6 0 0 1 6.25 4.57zm24.39-10.07v.84h-3.92v5.45h3.92v18.29h6V37.86h5.72v-5.45h-5.72v-1.15c0-3.6.49-4.22 4.71-4.22h1V21.5h-1.58c-7.71 0-10.13 2.38-10.13 10.07zm25.71.35A12.36 12.36 0 1 0 219 44.28a12.31 12.31 0 0 0-12.64-12.36zm0 19a6.64 6.64 0 1 1 6.52-6.64 6.53 6.53 0 0 1-6.48 6.6zM244 32.41h-7.13l-5 7-5-7h-7.22L228.06 44l-8.89 12.18h7.31l5.33-7.43 5.45 7.43h7.36L235.55 44zm18.89-8.36h-3.7v32.1h20.25v-3.56h-16.55zm31 7.87a12.36 12.36 0 1 0 12.37 12.36 12.2 12.2 0 0 0-12.38-12.36zm0 21.29a8.61 8.61 0 0 1-8.67-8.93 8.7 8.7 0 1 1 17.39 0 8.65 8.65 0 0 1-8.73 8.93zm28.35 0a8.88 8.88 0 0 1-8.89-9 8.77 8.77 0 0 1 8.76-9 8.4 8.4 0 0 1 8 5.28l3.3-1.45a12 12 0 0 0-11.23-7.22 12.36 12.36 0 1 0 .05 24.72 11.88 11.88 0 0 0 11.31-7.48l-3.34-1.41a8.5 8.5 0 0 1-7.97 5.6zm37.06-20.8h-4.53l-12.59 13.41V21.5h-3.57v34.65h3.57V50l5.06-5.36 8.27 11.56h4.36l-10-13.8zm30.16 18.69l-7.09-18.69H379l-7.26 18.82-6-18.82H362l7.79 23.74h3.52l7.31-18.82 7.31 18.82h3.25l8.24-23.74h-3.75zm16-28.85a2.86 2.86 0 1 0 2.86 2.86 2.79 2.79 0 0 0-2.88-2.86zm-1.81 33.9h3.66V32.41h-3.66zm19-13.72c-3.7-.74-6.56-1.23-6.56-3.69 0-2.2 1.5-3.78 5.15-3.78a7.65 7.65 0 0 1 6.51 3.16l3-2a11.52 11.52 0 0 0-9.51-4.22c-5.64 0-8.85 3.21-8.85 6.9 0 4.75 4.36 5.85 8.45 6.64 3.48.71 6.74 1.24 6.74 3.87 0 2.38-1.94 4.14-5.64 4.14a8.79 8.79 0 0 1-7.66-3.87l-3.12 2.11c2.11 3 5.5 5 10.7 5 6.2 0 9.37-3.65 9.37-7.39-.04-4.93-4.53-6.07-8.62-6.87zM459.76 44a11.72 11.72 0 0 0-12.1-12.1 12.2 12.2 0 0 0-12.42 12.45c0 7 4.93 12.27 12.55 12.27a12.21 12.21 0 0 0 11-6l-3.08-1.8a8.5 8.5 0 0 1-8 4.57c-5 0-8.5-3.21-9-7.74h21zm-20.91-1.54a8.84 8.84 0 0 1 8.81-7.35 8 8 0 0 1 8.41 7.35z" fill="#20123a"/>
+</svg>
--- a/browser/branding/nightly/content/jar.mn
+++ b/browser/branding/nightly/content/jar.mn
@@ -3,16 +3,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 browser.jar:
 % content branding %content/branding/ contentaccessible=yes
   content/branding/about.png
   content/branding/about-logo.png
   content/branding/about-logo@2x.png
   content/branding/about-wordmark.svg
+  content/branding/aboutlogins.svg
   content/branding/icon16.png                    (../default16.png)
   content/branding/icon32.png                    (../default32.png)
   content/branding/icon48.png                    (../default48.png)
   content/branding/icon64.png                    (../default64.png)
   content/branding/icon128.png                   (../default128.png)
   content/branding/identity-icons-brand.svg
   content/branding/aboutDialog.css
   content/branding/horizontal-lockup.svg
new file mode 100644
--- /dev/null
+++ b/browser/branding/official/content/aboutlogins.svg
@@ -0,0 +1,59 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="80" width="460" viewBox="0 0 460 80">
+  <defs>
+    <linearGradient id="a" x1="57.63" y1="9.47" x2="21.37" y2="72.26" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#ff980e"/>
+      <stop offset=".11" stop-color="#ff851b"/>
+      <stop offset=".57" stop-color="#ff3750"/>
+      <stop offset=".8" stop-color="#f92261"/>
+      <stop offset="1" stop-color="#f5156c"/>
+    </linearGradient>
+    <linearGradient id="b" x1="57.31" y1="-.8" x2="27.68" y2="69.03" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#fff261" stop-opacity=".8"/>
+      <stop offset=".06" stop-color="#fff261" stop-opacity=".68"/>
+      <stop offset=".19" stop-color="#fff261" stop-opacity=".48"/>
+      <stop offset=".31" stop-color="#fff261" stop-opacity=".31"/>
+      <stop offset=".42" stop-color="#fff261" stop-opacity=".17"/>
+      <stop offset=".53" stop-color="#fff261" stop-opacity=".08"/>
+      <stop offset=".63" stop-color="#fff261" stop-opacity=".02"/>
+      <stop offset=".72" stop-color="#fff261" stop-opacity="0"/>
+    </linearGradient>
+    <linearGradient id="c" x1="71.71" y1="75.85" x2="71.71" y2="28.29" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#0090ed"/>
+      <stop offset=".5" stop-color="#9059ff"/>
+      <stop offset=".81" stop-color="#b833e1"/>
+    </linearGradient>
+    <linearGradient id="d" x1="17.89" y1="78.48" x2="48.5" y2="26.39" gradientUnits="userSpaceOnUse">
+      <stop offset=".02" stop-color="#0090ed"/>
+      <stop offset=".49" stop-color="#9059ff"/>
+      <stop offset="1" stop-color="#b833e1"/>
+    </linearGradient>
+    <linearGradient id="e" x1="21.87" y1="58.41" x2="4.02" y2="40.56" gradientUnits="userSpaceOnUse">
+      <stop offset=".14" stop-color="#592acb" stop-opacity="0"/>
+      <stop offset=".33" stop-color="#542bc8" stop-opacity=".03"/>
+      <stop offset=".53" stop-color="#462fbf" stop-opacity=".11"/>
+      <stop offset=".74" stop-color="#2f35b1" stop-opacity=".25"/>
+      <stop offset=".95" stop-color="#0f3d9c" stop-opacity=".44"/>
+      <stop offset="1" stop-color="#054096" stop-opacity=".5"/>
+    </linearGradient>
+    <linearGradient id="f" x1="75.86" y1="38.71" x2="66.87" y2="54.27" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#722291" stop-opacity=".5"/>
+      <stop offset=".5" stop-color="#b833e1" stop-opacity="0"/>
+    </linearGradient>
+    <linearGradient id="g" x1="56.84" y1="60.96" x2="46.4" y2="72.73" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#054096" stop-opacity=".5"/>
+      <stop offset=".03" stop-color="#0f3d9c" stop-opacity=".44"/>
+      <stop offset=".17" stop-color="#2f35b1" stop-opacity=".25"/>
+      <stop offset=".3" stop-color="#462fbf" stop-opacity=".11"/>
+      <stop offset=".43" stop-color="#542bc8" stop-opacity=".03"/>
+      <stop offset=".56" stop-color="#592acb" stop-opacity="0"/>
+    </linearGradient>
+  </defs>
+  <path d="M76.46 30.15A312.48 312.48 0 0 0 49.84 3.53a15.47 15.47 0 0 0-19.69 0A312.48 312.48 0 0 0 3.53 30.16a15.47 15.47 0 0 0 0 19.69 312.48 312.48 0 0 0 26.63 26.62A14.87 14.87 0 0 0 40 80a14.93 14.93 0 0 0 9.88-3.56c4.9-4.42 9.37-8.69 13.68-13.07a4.45 4.45 0 0 0-.34-6.11L50 44.93a15.18 15.18 0 0 0 5.08-12 15.4 15.4 0 0 0-14.4-14.64 15.2 15.2 0 0 0-11.36 4.16 15.28 15.28 0 0 0 .3 22.48l-4.78 4.33A3.86 3.86 0 0 0 30 55l5.29-4.8.14-.13a7.24 7.24 0 0 0 2.11-5.43A7.34 7.34 0 0 0 35 39.35a7.58 7.58 0 1 1 9.77 0 7.39 7.39 0 0 0-2.62 5.32 7.25 7.25 0 0 0 2.11 5.4l.09.09 11.1 10.4c-3.4 3.37-6.94 6.71-10.71 10.13a7.89 7.89 0 0 1-9.46 0 307.34 307.34 0 0 1-26-26 7.91 7.91 0 0 1 0-9.46 304.52 304.52 0 0 1 26-26 7.89 7.89 0 0 1 9.45 0 304.52 304.52 0 0 1 26 26 7.89 7.89 0 0 1 0 9.45c-1.33 1.5-2.66 3-4 4.37a3.85 3.85 0 1 0 5.67 5.22c1.32-1.43 2.68-2.93 4-4.47a15.47 15.47 0 0 0 .06-19.65z" fill="url(#a)"/>
+  <path d="M76.46 30.15A312.48 312.48 0 0 0 49.84 3.53a15.47 15.47 0 0 0-19.69 0A312.48 312.48 0 0 0 3.53 30.16a15.47 15.47 0 0 0 0 19.69 312.48 312.48 0 0 0 26.63 26.62A14.87 14.87 0 0 0 40 80a14.93 14.93 0 0 0 9.88-3.56c4.9-4.42 9.37-8.69 13.68-13.07a4.45 4.45 0 0 0-.34-6.11L50 44.93a15.18 15.18 0 0 0 5.08-12 15.4 15.4 0 0 0-14.4-14.64 15.2 15.2 0 0 0-11.36 4.16 15.28 15.28 0 0 0 .3 22.48l-4.78 4.33A3.86 3.86 0 0 0 30 55l5.29-4.8.14-.13a7.24 7.24 0 0 0 2.11-5.43A7.34 7.34 0 0 0 35 39.35a7.58 7.58 0 1 1 9.77 0 7.39 7.39 0 0 0-2.62 5.32 7.25 7.25 0 0 0 2.11 5.4l.09.09 11.1 10.4c-3.4 3.37-6.94 6.71-10.71 10.13a7.89 7.89 0 0 1-9.46 0 307.34 307.34 0 0 1-26-26 7.91 7.91 0 0 1 0-9.46 304.52 304.52 0 0 1 26-26 7.89 7.89 0 0 1 9.45 0 304.52 304.52 0 0 1 26 26 7.89 7.89 0 0 1 0 9.45c-1.33 1.5-2.66 3-4 4.37a3.85 3.85 0 1 0 5.67 5.22c1.32-1.43 2.68-2.93 4-4.47a15.47 15.47 0 0 0 .06-19.65z" fill="url(#b)"/>
+  <path d="M70.69 35.27a7.89 7.89 0 0 1 0 9.45c-1.33 1.5-2.66 3-4 4.37a3.85 3.85 0 1 0 5.67 5.22c1.32-1.43 2.68-2.93 4-4.47 4.82-5.33-5.67-14.57-5.67-14.57z" fill="url(#c)"/>
+  <path d="M55.45 60.56c-3.4 3.37-6.94 6.71-10.71 10.13a7.89 7.89 0 0 1-9.46 0 307.34 307.34 0 0 1-26-26 7.91 7.91 0 0 1 0-9.46l-1.75 2a12.89 12.89 0 0 0 .21 17.27 309.82 309.82 0 0 0 22.42 21.97A14.87 14.87 0 0 0 40 80a14.93 14.93 0 0 0 9.88-3.56c2.79-2.52 5.89-5.43 8.67-8.11a3.37 3.37 0 0 0 0-4.86z" fill="url(#d)"/>
+  <path d="M7.78 54.53c2.92 3.17 5.83 6.2 8.81 9.16l1.19-1.94c1-1.59 2-3.15 3.07-4.71-3.85-3.91-7.66-7.95-11.54-12.3a7.91 7.91 0 0 1 0-9.46l-1.75 2a12.89 12.89 0 0 0 .18 17.22z" fill="url(#e)" opacity=".9"/>
+  <path d="M70.69 35.27a7.89 7.89 0 0 1 0 9.45c-1.33 1.5-2.66 3-4 4.37a3.85 3.85 0 1 0 5.67 5.22c1.32-1.43 2.68-2.93 4-4.47 4.82-5.33-5.67-14.57-5.67-14.57z" fill="url(#f)"/>
+  <path d="M58.51 63.47l-3.06-2.91c-3.4 3.37-6.94 6.71-10.72 10.13a7.71 7.71 0 0 1-6.07 1.48v7.77c.44 0 .88.06 1.33.06a14.93 14.93 0 0 0 9.88-3.56c2.79-2.52 5.89-5.43 8.67-8.11a3.36 3.36 0 0 0-.03-4.86z" fill="url(#g)" opacity=".9"/>
+  <path d="M97 56.15h6.25v-13h14.44v-5.8h-14.48v-7.41h14.44v-5.89H97zm28.35-34.38a3.79 3.79 0 0 0-3.87 3.95 3.9 3.9 0 0 0 7.79 0 3.77 3.77 0 0 0-3.96-3.95zm-3.08 34.38h6.21V32.41h-6.21zm17-20.09v-3.65h-6v23.74h6V43.62c0-4 2-5.58 5.15-5.58a5.59 5.59 0 0 1 3.17.83l2.2-6a8.78 8.78 0 0 0-4-.92c-3 .05-5.38 1.29-6.52 4.11zm23.42-4.14a12.27 12.27 0 0 0-12.46 12.41c0 6.9 4.93 12.31 12.59 12.31a12.5 12.5 0 0 0 11-5.5l-5-2.9a6.5 6.5 0 0 1-5.9 3.17 6.61 6.61 0 0 1-6.83-5H175V44.1a11.84 11.84 0 0 0-12.31-12.18zm-6.43 9.72a6.61 6.61 0 0 1 6.43-4.57 6 6 0 0 1 6.25 4.57zm24.39-10.07v.84h-3.92v5.45h3.92v18.29h6V37.86h5.72v-5.45h-5.72v-1.15c0-3.6.49-4.22 4.71-4.22h1V21.5h-1.58c-7.71 0-10.13 2.38-10.13 10.07zm25.71.35A12.36 12.36 0 1 0 219 44.28a12.31 12.31 0 0 0-12.64-12.36zm0 19a6.64 6.64 0 1 1 6.52-6.64 6.53 6.53 0 0 1-6.48 6.6zM244 32.41h-7.13l-5 7-5-7h-7.22L228.06 44l-8.89 12.18h7.31l5.33-7.43 5.45 7.43h7.36L235.55 44zm18.89-8.36h-3.7v32.1h20.25v-3.56h-16.55zm31 7.87a12.36 12.36 0 1 0 12.37 12.36 12.2 12.2 0 0 0-12.38-12.36zm0 21.29a8.61 8.61 0 0 1-8.67-8.93 8.7 8.7 0 1 1 17.39 0 8.65 8.65 0 0 1-8.73 8.93zm28.35 0a8.88 8.88 0 0 1-8.89-9 8.77 8.77 0 0 1 8.76-9 8.4 8.4 0 0 1 8 5.28l3.3-1.45a12 12 0 0 0-11.23-7.22 12.36 12.36 0 1 0 .05 24.72 11.88 11.88 0 0 0 11.31-7.48l-3.34-1.41a8.5 8.5 0 0 1-7.97 5.6zm37.06-20.8h-4.53l-12.59 13.41V21.5h-3.57v34.65h3.57V50l5.06-5.36 8.27 11.56h4.36l-10-13.8zm30.16 18.69l-7.09-18.69H379l-7.26 18.82-6-18.82H362l7.79 23.74h3.52l7.31-18.82 7.31 18.82h3.25l8.24-23.74h-3.75zm16-28.85a2.86 2.86 0 1 0 2.86 2.86 2.79 2.79 0 0 0-2.88-2.86zm-1.81 33.9h3.66V32.41h-3.66zm19-13.72c-3.7-.74-6.56-1.23-6.56-3.69 0-2.2 1.5-3.78 5.15-3.78a7.65 7.65 0 0 1 6.51 3.16l3-2a11.52 11.52 0 0 0-9.51-4.22c-5.64 0-8.85 3.21-8.85 6.9 0 4.75 4.36 5.85 8.45 6.64 3.48.71 6.74 1.24 6.74 3.87 0 2.38-1.94 4.14-5.64 4.14a8.79 8.79 0 0 1-7.66-3.87l-3.12 2.11c2.11 3 5.5 5 10.7 5 6.2 0 9.37-3.65 9.37-7.39-.04-4.93-4.53-6.07-8.62-6.87zM459.76 44a11.72 11.72 0 0 0-12.1-12.1 12.2 12.2 0 0 0-12.42 12.45c0 7 4.93 12.27 12.55 12.27a12.21 12.21 0 0 0 11-6l-3.08-1.8a8.5 8.5 0 0 1-8 4.57c-5 0-8.5-3.21-9-7.74h21zm-20.91-1.54a8.84 8.84 0 0 1 8.81-7.35 8 8 0 0 1 8.41 7.35z" fill="#20123a"/>
+</svg>
--- a/browser/branding/official/content/jar.mn
+++ b/browser/branding/official/content/jar.mn
@@ -3,16 +3,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 browser.jar:
 % content branding %content/branding/ contentaccessible=yes
   content/branding/about.png
   content/branding/about-logo.png
   content/branding/about-logo@2x.png
   content/branding/about-wordmark.svg
+  content/branding/aboutlogins.svg
   content/branding/icon16.png                    (../default16.png)
   content/branding/icon32.png                    (../default32.png)
   content/branding/icon48.png                    (../default48.png)
   content/branding/icon64.png                    (../default64.png)
   content/branding/icon128.png                   (../default128.png)
   content/branding/identity-icons-brand.svg
   content/branding/aboutDialog.css
   content/branding/horizontal-lockup.svg
new file mode 100644
--- /dev/null
+++ b/browser/branding/unofficial/content/aboutlogins.svg
@@ -0,0 +1,2 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="80" width="460" viewBox="0 0 460 80">
+</svg>
--- a/browser/branding/unofficial/content/jar.mn
+++ b/browser/branding/unofficial/content/jar.mn
@@ -3,16 +3,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 browser.jar:
 % content branding %content/branding/ contentaccessible=yes
   content/branding/about.png
   content/branding/about-background.png
   content/branding/about-logo.png
   content/branding/about-wordmark.svg
+  content/branding/aboutlogins.svg
   content/branding/icon16.png                    (../default16.png)
   content/branding/icon32.png                    (../default32.png)
   content/branding/icon48.png                    (../default48.png)
   content/branding/icon64.png                    (../default64.png)
   content/branding/icon128.png                   (../default128.png)
   content/branding/identity-icons-brand.svg
   content/branding/aboutDialog.css
   content/branding/horizontal-lockup.svg
--- a/browser/components/aboutlogins/AboutLoginsChild.jsm
+++ b/browser/components/aboutlogins/AboutLoginsChild.jsm
@@ -3,23 +3,28 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["AboutLoginsChild"];
 
 const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
 const {LoginHelper} = ChromeUtils.import("resource://gre/modules/LoginHelper.jsm");
+ChromeUtils.defineModuleGetter(this, "AppConstants",
+                               "resource://gre/modules/AppConstants.jsm");
 
 class AboutLoginsChild extends ActorChild {
   handleEvent(event) {
     switch (event.type) {
       case "AboutLoginsInit": {
         this.mm.sendAsyncMessage("AboutLogins:Subscribe");
 
+        let documentElement = this.content.document.documentElement;
+        documentElement.classList.toggle("official-branding", AppConstants.MOZILLA_OFFICIAL);
+
         let waivedContent = Cu.waiveXrays(this.content);
         let AboutLoginsUtils = {
           doLoginsMatch(loginA, loginB) {
             return LoginHelper.doLoginsMatch(loginA, loginB, {});
           },
         };
         waivedContent.AboutLoginsUtils = Cu.cloneInto(AboutLoginsUtils, waivedContent, {
           cloneFunctions: true,
--- a/browser/components/aboutlogins/content/aboutLogins.css
+++ b/browser/components/aboutlogins/content/aboutLogins.css
@@ -9,16 +9,17 @@ body {
   grid-template-areas: "header header"
                        "logins login";
   height: 100vh;
 }
 
 header {
   display: flex;
   grid-area: header;
+  align-items: center;
   background-color: var(--in-content-box-background);
   border-bottom: 1px solid var(--in-content-box-border-color);
 }
 
 login-filter {
   flex: auto;
   align-self: center;
 }
@@ -26,8 +27,17 @@ login-filter {
 login-list {
   grid-area: logins;
 }
 
 login-item {
   grid-area: login;
   max-width: 800px;
 }
+
+#branding-logo {
+  height: 32px;
+  margin-inline-start: 18px;
+}
+
+:root:not(.official-branding) #branding-logo {
+  display: none;
+}
--- a/browser/components/aboutlogins/content/aboutLogins.ftl
+++ b/browser/components/aboutlogins/content/aboutLogins.ftl
@@ -22,16 +22,17 @@ login-list =
        *[other] { $count } entries
     }
 
 login-item =
   .cancel-button = Cancel
   .delete-button = Delete
   .edit-button = Edit
   .hostname-label = Website Address
-  .modal-input-reveal-button = Toggle password visibility
+  .modal-input-reveal-checkbox-hide = Hide password
+  .modal-input-reveal-checkbox-show = Show password
   .open-site-button = Launch
   .password-label = Password
   .save-changes-button = Save Changes
   .time-created = Created: { DATETIME($timeCreated, day: "numeric", month: "long", year: "numeric") }
   .time-changed = Last changed: { DATETIME($timeChanged, day: "numeric", month: "long", year: "numeric") }
   .time-used = Last used: { DATETIME($timeUsed, day: "numeric", month: "long", year: "numeric") }
   .username-label = Username
--- a/browser/components/aboutlogins/content/aboutLogins.html
+++ b/browser/components/aboutlogins/content/aboutLogins.html
@@ -11,61 +11,63 @@
     <link rel="localization" href="browser/aboutLogins.ftl">
     <script defer="defer" src="chrome://browser/content/aboutlogins/components/reflected-fluent-element.js"></script>
     <script defer="defer" src="chrome://browser/content/aboutlogins/components/login-filter.js"></script>
     <script defer="defer" src="chrome://browser/content/aboutlogins/components/login-item.js"></script>
     <script defer="defer" src="chrome://browser/content/aboutlogins/components/login-list.js"></script>
     <script defer="defer" src="chrome://browser/content/aboutlogins/components/login-list-item.js"></script>
     <script defer="defer" src="chrome://browser/content/aboutlogins/components/modal-input.js"></script>
     <script defer="defer" src="chrome://browser/content/aboutlogins/aboutLogins.js"></script>
-    <link rel="stylesheet" type="text/css" href="chrome://global/skin/in-content/common.css">
-    <link rel="stylesheet" type="text/css" href="chrome://browser/content/aboutlogins/aboutLogins.css">
+    <link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
+    <link rel="stylesheet" href="chrome://browser/content/aboutlogins/aboutLogins.css">
   </head>
   <body>
     <header>
+      <img id="branding-logo" src="chrome://branding/content/aboutlogins.svg" alt=""/>
       <login-filter data-l10n-id="login-filter"
                     data-l10n-attrs="placeholder"></login-filter>
     </header>
     <login-list data-l10n-id="login-list"
                 data-l10n-attrs="count"
                 data-l10n-args='{"count": 0}'></login-list>
     <login-item data-l10n-id="login-item"
                 data-l10n-args='{"timeCreated": 0, "timeChanged": 0, "timeUsed": 0}'
                 data-l10n-attrs="cancel-button,
                                  delete-button,
                                  edit-button,
                                  hostname-label,
-                                 modal-input-reveal-button,
+                                 modal-input-reveal-checkbox-hide,
+                                 modal-input-reveal-checkbox-show,
                                  open-site-button,
                                  password-label,
                                  save-changes-button,
                                  time-created,
                                  time-changed,
                                  time-used,
                                  username-label"></login-item>
 
     <template id="login-list-template">
-      <link rel="stylesheet" type="text/css" href="chrome://global/skin/in-content/common.css">
+      <link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
       <link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/login-list.css">
       <div class="meta">
         <span class="count"></span>
       </div>
       <ol>
       </ol>
     </template>
 
     <template id="login-list-item-template">
-      <link rel="stylesheet" type="text/css" href="chrome://global/skin/in-content/common.css">
+      <link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
       <link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/login-list-item.css">
       <span class="hostname"></span>
       <span class="username"></span>
     </template>
 
     <template id="login-item-template">
-      <link rel="stylesheet" type="text/css" href="chrome://global/skin/in-content/common.css">
+      <link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
       <link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/login-item.css">
       <div class="header">
         <h2 class="title"></h2>
         <button class="edit-button"></button>
         <button class="delete-button"></button>
       </div>
       <div class="detail-row">
         <label>
@@ -89,22 +91,22 @@
       <p class="time-created meta-info"></p>
       <p class="time-changed meta-info"></p>
       <p class="time-used meta-info"></p>
       <button class="save-changes-button"></button>
       <button class="cancel-button"></button>
     </template>
 
     <template id="login-filter-template">
-      <link rel="stylesheet" type="text/css" href="chrome://global/skin/in-content/common.css">
+      <link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
       <link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/login-filter.css">
       <input type="text"/>
     </template>
 
     <template id="modal-input-template">
-      <link rel="stylesheet" type="text/css" href="chrome://global/skin/in-content/common.css">
+      <link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
       <link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/modal-input.css">
       <span class="locked-value"></span>
       <input type="text" class="unlocked-value"/>
-      <button class="reveal-button"/>
+      <input type="checkbox" class="reveal-checkbox"/>
     </template>
   </body>
 </html>
--- a/browser/components/aboutlogins/content/components/login-item.css
+++ b/browser/components/aboutlogins/content/components/login-item.css
@@ -19,33 +19,57 @@
 }
 
 .title {
   margin-top: 0;
   margin-bottom: 0;
   flex: auto;
 }
 
+.delete-button,
+.edit-button {
+  background-repeat: no-repeat;
+  background-position: 8px;
+  -moz-context-properties: fill;
+  fill: currentColor;
+  min-width: auto;
+}
+
+.delete-button:dir(rtl),
+.edit-button:dir(rtl) {
+  background-position: right 8px center;
+}
+
+.delete-button {
+  background-image: url("chrome://browser/content/aboutlogins/icons/delete.svg");
+  padding-inline-start: 30px; /* 8px on each side, and 14px for icon width */
+}
+
+.edit-button {
+  background-image: url("chrome://browser/content/aboutlogins/icons/edit.svg");
+  padding-inline-start: 32px; /* 8px on each side, and 16px for icon width */
+}
+
 .detail-row {
   display: flex;
   margin-bottom: 20px;
 }
 
 .detail-row > label {
   flex: auto;
 }
 
 .detail-row > button {
   align-self: end;
 }
 
 .field-label {
   display: block;
   font-size: smaller;
-  opacity: .7;
+  color: var(--in-content-deemphasized-text);
   margin-bottom: 5px;
 }
 
 .meta-info {
   font-size: smaller;
 }
 
 .meta-info:not(:first-of-type) {
--- a/browser/components/aboutlogins/content/components/login-item.js
+++ b/browser/components/aboutlogins/content/components/login-item.js
@@ -39,38 +39,47 @@ class LoginItem extends ReflectedFluentE
   }
 
   static get reflectedFluentIDs() {
     return [
       "cancel-button",
       "delete-button",
       "edit-button",
       "hostname-label",
-      "modal-input-reveal-button",
+      "modal-input-reveal-checkbox-hide",
+      "modal-input-reveal-checkbox-show",
       "open-site-button",
       "password-label",
       "save-changes-button",
       "time-created",
       "time-changed",
       "time-used",
       "username-label",
     ];
   }
 
   static get observedAttributes() {
     return this.reflectedFluentIDs;
   }
 
   handleSpecialCaseFluentString(attrName) {
-    if (attrName != "modal-input-reveal-button") {
-      return false;
+    switch (attrName) {
+      case "modal-input-reveal-checkbox-hide": {
+        this.shadowRoot.querySelector("modal-input[name='password']")
+                       .setAttribute("reveal-checkbox-hide", this.getAttribute(attrName));
+        break;
+      }
+      case "modal-input-reveal-checkbox-show": {
+        this.shadowRoot.querySelector("modal-input[name='password']")
+                       .setAttribute("reveal-checkbox-show", this.getAttribute(attrName));
+        break;
+      }
+      default:
+        return false;
     }
-
-    this.shadowRoot.querySelector("modal-input[name='password']")
-                   .setAttribute("reveal-button", this.getAttribute(attrName));
     return true;
   }
 
   render() {
     let l10nArgs = {
       timeCreated: this._login.timeCreated || "",
       timeChanged: this._login.timePasswordChanged || "",
       timeUsed: this._login.timeLastUsed || "",
--- a/browser/components/aboutlogins/content/components/modal-input.css
+++ b/browser/components/aboutlogins/content/components/modal-input.css
@@ -1,12 +1,54 @@
 /* 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/. */
 
+:host {
+  --reveal-button-opacity: .8;
+  --reveal-button-opacity-hover: .6;
+  --reveal-button-opacity-active: 1;
+
+  display: flex;
+}
+
 :host([editing]) .locked-value,
 :host(:not([editing])) .unlocked-value {
   display: none;
 }
 
-:host(:not([type="password"])) .reveal-button {
+:host(:not([type="password"])) .reveal-checkbox {
   display: none;
 }
+
+.reveal-checkbox {
+  /* !important is needed to override common.css styling for checkboxes */
+  background-color: transparent !important;
+  border-width: 0 !important;
+  background-image: url("chrome://browser/content/aboutlogins/icons/show-password.svg") !important;
+  margin-inline-start: 8px !important;
+  cursor: pointer;
+  -moz-context-properties: fill;
+  fill: currentColor !important;
+  opacity: var(--reveal-button-opacity);
+}
+
+.reveal-checkbox:hover {
+  opacity: var(--reveal-button-opacity-hover);
+}
+
+.reveal-checkbox:hover:active {
+  opacity: var(--reveal-button-opacity-active);
+}
+
+.reveal-checkbox:checked {
+  background-image: url("chrome://browser/content/aboutlogins/icons/hide-password.svg") !important;
+}
+
+@supports -moz-bool-pref("browser.in-content.dark-mode") {
+@media (prefers-color-scheme: dark) {
+  :host {
+    --reveal-button-opacity: .8;
+    --reveal-button-opacity-hover: 1;
+    --reveal-button-opacity-active: .6;
+  }
+}
+}
--- a/browser/components/aboutlogins/content/components/modal-input.js
+++ b/browser/components/aboutlogins/content/components/modal-input.js
@@ -22,27 +22,37 @@ class ModalInput extends ReflectedFluent
       this.value = this.getAttribute("value");
     }
 
     if (this.getAttribute("type") == "password") {
       let unlockedValue = this.shadowRoot.querySelector(".unlocked-value");
       unlockedValue.setAttribute("type", "password");
     }
 
-    this.shadowRoot.querySelector(".reveal-button").addEventListener("click", this);
+    this.shadowRoot.querySelector(".reveal-checkbox").addEventListener("click", this);
   }
 
   static get reflectedFluentIDs() {
-    return ["reveal-button"];
+    return ["reveal-checkbox-hide", "reveal-checkbox-show"];
   }
 
   static get observedAttributes() {
     return ["editing", "type", "value"].concat(ModalInput.reflectedFluentIDs);
   }
 
+  handleSpecialCaseFluentString(attrName) {
+    if (attrName != "reveal-checkbox-hide" &&
+        attrName != "reveal-checkbox-show") {
+      return false;
+    }
+
+    this.updateRevealCheckboxTitle();
+    return true;
+  }
+
   attributeChangedCallback(attr, oldValue, newValue) {
     super.attributeChangedCallback(attr, oldValue, newValue);
 
     if (!this.shadowRoot) {
       return;
     }
 
     let lockedValue = this.shadowRoot.querySelector(".locked-value");
@@ -69,34 +79,32 @@ class ModalInput extends ReflectedFluent
       case "value": {
         this.value = newValue;
         break;
       }
     }
   }
 
   handleEvent(event) {
-    switch (event.type) {
-      case "click": {
-        if (event.target.classList.contains("reveal-button")) {
-          let lockedValue = this.shadowRoot.querySelector(".locked-value");
-          let unlockedValue = this.shadowRoot.querySelector(".unlocked-value");
-          let editing = this.hasAttribute("editing");
-          if ((editing && unlockedValue.getAttribute("type") == "password") ||
-              (!editing && lockedValue.textContent == this.constructor.LOCKED_PASSWORD_DISPLAY)) {
-            lockedValue.textContent = this.value;
-            unlockedValue.setAttribute("type", "text");
-          } else {
-            lockedValue.textContent = this.constructor.LOCKED_PASSWORD_DISPLAY;
-            unlockedValue.setAttribute("type", "password");
-          }
-        }
-        break;
-      }
+    if (event.type != "click" ||
+        !event.target.classList.contains("reveal-checkbox")) {
+      return;
     }
+
+    let revealCheckbox = event.target;
+    let lockedValue = this.shadowRoot.querySelector(".locked-value");
+    let unlockedValue = this.shadowRoot.querySelector(".unlocked-value");
+    if (revealCheckbox.checked) {
+      lockedValue.textContent = this.value;
+      unlockedValue.setAttribute("type", "text");
+    } else {
+      lockedValue.textContent = this.constructor.LOCKED_PASSWORD_DISPLAY;
+      unlockedValue.setAttribute("type", "password");
+    }
+    this.updateRevealCheckboxTitle();
   }
 
   get value() {
     return this.hasAttribute("editing") ? this.shadowRoot.querySelector(".unlocked-value").value.trim()
                                         : this.getAttribute("value") || "";
   }
 
   set value(val) {
@@ -107,10 +115,18 @@ class ModalInput extends ReflectedFluent
     this.shadowRoot.querySelector(".unlocked-value").value = val;
     let lockedValue = this.shadowRoot.querySelector(".locked-value");
     if (this.getAttribute("type") == "password" && val && val.length) {
       lockedValue.textContent = this.constructor.LOCKED_PASSWORD_DISPLAY;
     } else {
       lockedValue.textContent = val;
     }
   }
+
+  updateRevealCheckboxTitle() {
+    let revealCheckbox = this.shadowRoot.querySelector(".reveal-checkbox");
+    let labelAttr = revealCheckbox.checked ? "reveal-checkbox-hide"
+                                           : "reveal-checkbox-show";
+    revealCheckbox.setAttribute("aria-label", this.getAttribute(labelAttr));
+    revealCheckbox.setAttribute("title", this.getAttribute(labelAttr));
+  }
 }
 customElements.define("modal-input", ModalInput);
new file mode 100644
--- /dev/null
+++ b/browser/components/aboutlogins/content/icons/delete.svg
@@ -0,0 +1,6 @@
+<!-- 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" width="14" height="16" viewBox="0 0 14 16" fill="context-fill">
+  <path d="M6,12.0025063 C6.27614237,12.0025063 6.5,11.7786487 6.5,11.5025063 L6.5,5.50250628 C6.5,5.22636391 6.27614237,5.00250628 6,5.00250628 C5.72385763,5.00250628 5.5,5.22636391 5.5,5.50250628 L5.5,11.5025063 C5.5,11.7786487 5.72385763,12.0025063 6,12.0025063 Z M8,12.0025063 C8.27614237,12.0025063 8.5,11.7786487 8.5,11.5025063 L8.5,5.50250628 C8.5,5.22636391 8.27614237,5.00250628 8,5.00250628 C7.72385763,5.00250628 7.5,5.22636391 7.5,5.50250628 L7.5,11.5025063 C7.5,11.7786487 7.72385763,12.0025063 8,12.0025063 Z M10,12.0025063 C10.2761424,12.0025063 10.5,11.7786487 10.5,11.5025063 L10.5,5.50250628 C10.5,5.22636391 10.2761424,5.00250628 10,5.00250628 C9.72385763,5.00250628 9.5,5.22636391 9.5,5.50250628 L9.5,11.5025063 C9.5,11.7786487 9.72385763,12.0025063 10,12.0025063 Z M13.5,2.00250628 L10.45,2.00250628 C10.2134038,0.837344292 9.18894081,-1.91253263e-16 8,0 C6.81105919,1.91253263e-16 5.78659623,0.837344292 5.55,2.00250628 L2.5,2.00250628 C1.94771525,2.00250628 1.5,2.45022153 1.5,3.00250628 C1.5,3.55479103 1.94771525,4.00250628 2.5,4.00250628 L2.5,13.0025063 C2.5,14.6593605 3.84314575,16.0025063 5.5,16.0025063 L10.5,16.0025063 C12.1568542,16.0025063 13.5,14.6593605 13.5,13.0025063 L13.5,4.00250628 C14.0522847,4.00250628 14.5,3.55479103 14.5,3.00250628 C14.5,2.45022153 14.0522847,2.00250628 13.5,2.00250628 L13.5,2.00250628 Z M8,1.00250628 C8.62819604,1.00902136 9.18471788,1.40910895 9.391,2.00250628 L6.609,2.00250628 C6.81528212,1.40910895 7.37180396,1.00902136 8,1.00250628 Z M11.5,13.0025063 C11.5,13.554791 11.0522847,14.0025063 10.5,14.0025063 L5.5,14.0025063 C4.94771525,14.0025063 4.5,13.554791 4.5,13.0025063 L4.5,4.00250628 L11.5,4.00250628 L11.5,13.0025063 Z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/components/aboutlogins/content/icons/edit.svg
@@ -0,0 +1,6 @@
+<!-- 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" width="16" height="16" viewBox="0 0 16 16" fill="context-fill">
+  <path d="M14.354,2.353 L13.646,1.646 C12.8634135,0.869077203 11.6005865,0.869077203 10.818,1.646 L10.439,2.025 C10.243809,2.22024993 10.243809,2.53675007 10.439,2.732 L13.268,5.561 C13.4632499,5.75619097 13.7797501,5.75619097 13.975,5.561 L14.354,5.182 C15.1310392,4.39907156 15.1310392,3.13592844 14.354,2.353 L14.354,2.353 Z M9.732,3.439 C9.53675007,3.24380903 9.22024993,3.24380903 9.025,3.439 L3.246,9.218 C3.04609788,9.4202372 2.89195626,9.66304436 2.794,9.93 L1.038,14.32 C0.978937968,14.4730418 0.998703668,14.64532 1.09089211,14.7810086 C1.18308054,14.9166972 1.33596355,14.998534 1.5,15 C1.56446593,14.9999016 1.62830455,14.9873376 1.688,14.963 L6.07,13.211 C6.33884461,13.1135406 6.58319198,12.9586052 6.786,12.757 L12.565,6.979 C12.760191,6.78375007 12.760191,6.46724993 12.565,6.272 L9.732,3.439 Z M5.161,12.5 L2.612,13.52 C2.57485383,13.5348687 2.53242052,13.5261642 2.50412814,13.4978719 C2.47583577,13.4695795 2.46713127,13.4271462 2.482,13.39 L3.5,10.831 C3.51340062,10.80154 3.54023032,10.780387 3.5720041,10.7742308 C3.60377787,10.7680746 3.63656633,10.7776766 3.66,10.8 L5.2,12.335 C5.22422581,12.3595088 5.23412532,12.3947644 5.22619838,12.4283014 C5.21827143,12.4618385 5.1936351,12.488931 5.161,12.5 L5.161,12.5 Z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/components/aboutlogins/content/icons/hide-password.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="context-fill">
+  <path d="M12 7l-4 4a4 4 0 0 0 4-4zm3.955.7a1 1 0 0 1 0 .6A8.325 8.325 0 0 1 8 14a8.478 8.478 0 0 1-2.59-.409l1.66-1.661c.308.046.619.069.93.07a6.331 6.331 0 0 0 5.943-4 5.781 5.781 0 0 0-1.118-1.828l1.41-1.41a7.817 7.817 0 0 1 1.72 2.938zm-1.248-6.407a1 1 0 0 1 0 1.414l-12 12a1 1 0 1 1-1.414-1.414l1.284-1.287A7.874 7.874 0 0 1 .045 8.294a1 1 0 0 1 0-.594A8.355 8.355 0 0 1 11.7 2.882l1.593-1.589a1 1 0 0 1 1.414 0zM8.5 5A1.5 1.5 0 0 0 7 6.5c.003.295.094.581.263.823l2.06-2.06A1.46 1.46 0 0 0 8.5 5zM2.057 8a5.928 5.928 0 0 0 1.936 2.595l.986-.986A3.933 3.933 0 0 1 4.557 5a6.061 6.061 0 0 0-2.5 3z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/browser/components/aboutlogins/content/icons/show-password.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="context-fill">
+  <path d="M15.955 7.7A8.325 8.325 0 0 0 8 2 8.325 8.325 0 0 0 .045 7.7a1 1 0 0 0 0 .594A8.325 8.325 0 0 0 8 14a8.325 8.325 0 0 0 7.955-5.7 1 1 0 0 0 0-.6zM8.5 5a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3zM8 12a6.331 6.331 0 0 1-5.943-4 6.061 6.061 0 0 1 2.5-3A3.955 3.955 0 0 0 4 7a4 4 0 0 0 8 0 3.955 3.955 0 0 0-.555-2 6.061 6.061 0 0 1 2.5 3A6.331 6.331 0 0 1 8 12z"/>
+</svg>
--- a/browser/components/aboutlogins/jar.mn
+++ b/browser/components/aboutlogins/jar.mn
@@ -9,11 +9,15 @@ browser.jar:
   content/browser/aboutlogins/components/login-item.js         (content/components/login-item.js)
   content/browser/aboutlogins/components/login-list.css        (content/components/login-list.css)
   content/browser/aboutlogins/components/login-list.js         (content/components/login-list.js)
   content/browser/aboutlogins/components/login-list-item.css   (content/components/login-list-item.css)
   content/browser/aboutlogins/components/login-list-item.js    (content/components/login-list-item.js)
   content/browser/aboutlogins/components/modal-input.css       (content/components/modal-input.css)
   content/browser/aboutlogins/components/modal-input.js        (content/components/modal-input.js)
   content/browser/aboutlogins/components/reflected-fluent-element.js  (content/components/reflected-fluent-element.js)
+  content/browser/aboutlogins/icons/delete.svg  (content/icons/delete.svg)
+  content/browser/aboutlogins/icons/edit.svg    (content/icons/edit.svg)
+  content/browser/aboutlogins/icons/hide-password.svg (content/icons/hide-password.svg)
+  content/browser/aboutlogins/icons/show-password.svg (content/icons/show-password.svg)
   content/browser/aboutlogins/aboutLogins.css   (content/aboutLogins.css)
   content/browser/aboutlogins/aboutLogins.js    (content/aboutLogins.js)
   content/browser/aboutlogins/aboutLogins.html  (content/aboutLogins.html)
--- a/browser/components/aboutlogins/tests/mochitest/test_login_filter.html
+++ b/browser/components/aboutlogins/tests/mochitest/test_login_filter.html
@@ -9,17 +9,17 @@ Test the login-filter component
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <script src="/tests/SimpleTest/EventUtils.js"></script>
   <script src="reflected-fluent-element.js"></script>
   <script src="login-filter.js"></script>
   <script src="login-list-item.js"></script>
   <script src="login-list.js"></script>
   <script src="aboutlogins_common.js"></script>
 
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
   <p id="display">
   </p>
 <div id="content" style="display: none">
   <iframe id="templateFrame" src="aboutLogins.html"
           sandbox="allow-same-origin"></iframe>
 </div>
--- a/browser/components/aboutlogins/tests/mochitest/test_login_item.html
+++ b/browser/components/aboutlogins/tests/mochitest/test_login_item.html
@@ -7,17 +7,17 @@ Test the login-item component
   <meta charset="utf-8">
   <title>Test the login-item component</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <script src="reflected-fluent-element.js"></script>
   <script src="login-item.js"></script>
   <script src="modal-input.js"></script>
   <script src="aboutlogins_common.js"></script>
 
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
   <p id="display">
   </p>
 <div id="content" style="display: none">
   <iframe id="templateFrame" src="aboutLogins.html"
           sandbox="allow-same-origin"></iframe>
 </div>
--- a/browser/components/aboutlogins/tests/mochitest/test_login_list.html
+++ b/browser/components/aboutlogins/tests/mochitest/test_login_list.html
@@ -7,17 +7,17 @@ Test the login-list component
   <meta charset="utf-8">
   <title>Test the login-list component</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <script src="reflected-fluent-element.js"></script>
   <script src="login-list-item.js"></script>
   <script src="login-list.js"></script>
   <script src="aboutlogins_common.js"></script>
 
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
   <p id="display">
   </p>
 <div id="content" style="display: none">
   <iframe id="templateFrame" src="aboutLogins.html"
           sandbox="allow-same-origin"></iframe>
 </div>
--- a/browser/components/aboutlogins/tests/mochitest/test_modal_input.html
+++ b/browser/components/aboutlogins/tests/mochitest/test_modal_input.html
@@ -6,17 +6,17 @@ Test the modal-input component
 <head>
   <meta charset="utf-8">
   <title>Test the modal-input component</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <script src="reflected-fluent-element.js"></script>
   <script src="modal-input.js"></script>
   <script src="aboutlogins_common.js"></script>
 
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
   <p id="display">
   </p>
 <div id="content" style="display: none">
   <iframe id="templateFrame" src="aboutLogins.html"
           sandbox="allow-same-origin"></iframe>
 </div>
@@ -36,35 +36,35 @@ add_task(async function setup() {
   gModalInput.setAttribute("value", TEST_INPUT_VALUE);
   displayEl.appendChild(gModalInput);
 });
 
 add_task(async function test_initial_state() {
   ok(gModalInput, "modalInput exists");
   is(gModalInput.shadowRoot.querySelector(".locked-value").textContent, TEST_INPUT_VALUE, "Values are set initially");
   is(gModalInput.shadowRoot.querySelector(".unlocked-value").value, TEST_INPUT_VALUE, "Values are set initially");
-  is(getComputedStyle(gModalInput.shadowRoot.querySelector(".locked-value")).display, "inline", ".locked-value is visible by default");
+  is(getComputedStyle(gModalInput.shadowRoot.querySelector(".locked-value")).display, "block", ".locked-value is visible by default");
   is(getComputedStyle(gModalInput.shadowRoot.querySelector(".unlocked-value")).display, "none", ".unlocked-value is hidden by default");
 });
 
 add_task(async function test_editing_set_unset() {
   let lockedValue = gModalInput.shadowRoot.querySelector(".locked-value");
   let unlockedValue = gModalInput.shadowRoot.querySelector(".unlocked-value");
   gModalInput.setAttribute("editing", "");
   is(getComputedStyle(lockedValue).display, "none", ".locked-value is hidden when editing");
-  is(getComputedStyle(unlockedValue).display, "inline", ".unlocked-value is visible when editing");
+  is(getComputedStyle(unlockedValue).display, "block", ".unlocked-value is visible when editing");
 
   const NEW_VALUE = "editedValue";
   SpecialPowers.wrap(unlockedValue).setUserInput(NEW_VALUE);
   gModalInput.removeAttribute("editing");
 
   is(lockedValue.textContent, NEW_VALUE, "Values are updated from edit");
   is(unlockedValue.value, NEW_VALUE, "Values are updated from edit");
   is(gModalInput.getAttribute("value"), NEW_VALUE, "The value attribute on the host element is updated from edit");
-  is(getComputedStyle(lockedValue).display, "inline", ".locked-value is visible when not editing");
+  is(getComputedStyle(lockedValue).display, "block", ".locked-value is visible when not editing");
   is(getComputedStyle(unlockedValue).display, "none", ".unlocked-value is hidden when not editing");
 });
 
 add_task(async function test_password() {
   gModalInput.setAttribute("type", "password");
   let lockedValue = gModalInput.shadowRoot.querySelector(".locked-value");
   let unlockedValue = gModalInput.shadowRoot.querySelector(".unlocked-value");
 
--- a/browser/components/aboutlogins/tests/mochitest/test_reflected_fluent_element.html
+++ b/browser/components/aboutlogins/tests/mochitest/test_reflected_fluent_element.html
@@ -5,17 +5,17 @@ Test the reflected-fluent-element compon
 -->
 <head>
   <meta charset="utf-8">
   <title>Test the reflected-fluent-element component</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <script src="reflected-fluent-element.js"></script>
   <script src="aboutlogins_common.js"></script>
 
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
   <p id="display">
   </p>
 <div id="content" style="display: none">
   <iframe id="templateFrame" src="aboutLogins.html"
           sandbox="allow-same-origin"></iframe>
 </div>
--- a/browser/components/enterprisepolicies/tests/xpcshell/head.js
+++ b/browser/components/enterprisepolicies/tests/xpcshell/head.js
@@ -2,18 +2,26 @@
  * 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";
 
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 const {Preferences} = ChromeUtils.import("resource://gre/modules/Preferences.jsm");
+const {updateAppInfo, getAppInfo} = ChromeUtils.import("resource://testing-common/AppInfo.jsm");
 const {FileTestUtils} = ChromeUtils.import("resource://testing-common/FileTestUtils.jsm");
 
+updateAppInfo({
+  name: "XPCShell",
+  ID: "xpcshell@tests.mozilla.org",
+  version: "48",
+  platformVersion: "48",
+});
+
 // This initializes the policy engine for xpcshell tests
 let policies = Cc["@mozilla.org/enterprisepolicies;1"].getService(Ci.nsIObserver);
 policies.observe(null, "policies-startup", null);
 
 // Any changes to this function should also be made to the corresponding version
 // in browser/components/enterprisepolicies/tests/browser/head.js
 async function setupPolicyEngineWithJson(json, customSchema) {
   let filePath;
--- a/browser/components/enterprisepolicies/tests/xpcshell/test_extensionsettings.js
+++ b/browser/components/enterprisepolicies/tests/xpcshell/test_extensionsettings.js
@@ -2,17 +2,17 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const {AddonTestUtils} = ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm");
 const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
 
 AddonTestUtils.init(this);
 AddonTestUtils.overrideCertDB();
-AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "48", "48");
+AddonTestUtils.appInfo = getAppInfo();
 
 const server = AddonTestUtils.createHttpServer({hosts: ["example.com"]});
 const BASE_URL = `http://example.com/data`;
 
 let addonID = "policytest2@mozilla.com";
 
 add_task(async function setup() {
   await AddonTestUtils.promiseStartupManager();
--- a/browser/components/extensions/ExtensionPopups.jsm
+++ b/browser/components/extensions/ExtensionPopups.jsm
@@ -234,17 +234,16 @@ class BasePopup {
     }
   }
 
   createBrowser(viewNode, popupURL = null) {
     let document = viewNode.ownerDocument;
 
     let stack = document.createXULElement("stack");
     stack.setAttribute("class", "webextension-popup-stack");
-    stack.setAttribute("renderroot", "content");
 
     let browser = document.createXULElement("browser");
     browser.setAttribute("type", "content");
     browser.setAttribute("disableglobalhistory", "true");
     browser.setAttribute("transparent", "true");
     browser.setAttribute("class", "webextension-popup-browser");
     browser.setAttribute("webextension-view-type", "popup");
     browser.setAttribute("tooltip", "aHTMLTooltip");
@@ -252,16 +251,17 @@ class BasePopup {
     browser.setAttribute("autocompletepopup", "PopupAutoComplete");
     browser.setAttribute("selectmenulist", "ContentSelectDropdown");
     browser.setAttribute("selectmenuconstrained", "false");
     browser.sameProcessAsFrameLoader = this.extension.groupFrameLoader;
 
     if (this.extension.remote) {
       browser.setAttribute("remote", "true");
       browser.setAttribute("remoteType", E10SUtils.EXTENSION_REMOTE_TYPE);
+      browser.setAttribute("renderroot", "content");
     }
 
     // We only need flex sizing for the sake of the slide-in sub-views of the
     // main menu panel, so that the browser occupies the full width of the view,
     // and also takes up any extra height that's available to it.
     browser.setAttribute("flex", "1");
     stack.setAttribute("flex", "1");
 
--- a/browser/components/preferences/sitePermissions.js
+++ b/browser/components/preferences/sitePermissions.js
@@ -369,21 +369,23 @@ var gSitePermissionsManager = {
 
   onApplyChanges() {
     // Stop observing permission changes since we are about
     // to write out the pending adds/deletes and don't need
     // to update the UI
     this.uninit();
 
     for (let p of this._permissionsToChange.values()) {
-      SitePermissions.setForPrincipal(p.principal, p.type, p.capability);
+      let uri = Services.io.newURI(p.origin);
+      SitePermissions.set(uri, p.type, p.capability);
     }
 
     for (let p of this._permissionsToDelete.values()) {
-      SitePermissions.removeFromPrincipal(p.principal, p.type);
+      let uri = Services.io.newURI(p.origin);
+      SitePermissions.remove(uri, p.type);
     }
 
     if (this._checkbox.checked) {
       Services.prefs.setIntPref(this._defaultPermissionStatePrefName, SitePermissions.BLOCK);
     } else if (this._currentDefaultPermissionsState == SitePermissions.BLOCK) {
       Services.prefs.setIntPref(this._defaultPermissionStatePrefName, SitePermissions.UNKNOWN);
     }
 
--- a/browser/components/search/test/browser/browser.ini
+++ b/browser/components/search/test/browser/browser.ini
@@ -42,16 +42,15 @@ skip-if = verify
 [browser_oneOffHeader.js]
 skip-if = os == "mac" #1421238
 [browser_private_search_perwindowpb.js]
 [browser_searchbar_openpopup.js]
 skip-if = os == "linux" # Linux has different focus behaviours.
 [browser_searchbar_keyboard_navigation.js]
 [browser_searchbar_smallpanel_keyboard_navigation.js]
 [browser_searchEngine_behaviors.js]
-skip-if = artifact # bug 1315953
 [browser_searchTelemetry.js]
 skip-if = !debug && (os == 'linux') # Bug 1515466
 support-files =
   searchTelemetry.html
   searchTelemetryAd.html
 [browser_webapi.js]
 [browser_tooManyEnginesOffered.js]
--- a/browser/components/search/test/browser/google_codes/browser.ini
+++ b/browser/components/search/test/browser/google_codes/browser.ini
@@ -1,9 +1,7 @@
 [DEFAULT]
 prefs =
   browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar=false
   browser.search.region='DE'
 
 [../browser_google.js]
-skip-if = artifact # bug 1315953
 [../browser_google_behavior.js]
-skip-if = artifact # bug 1315953
--- a/browser/components/tests/browser/browser_urlbar_matchBuckets_migration60.js
+++ b/browser/components/tests/browser/browser_urlbar_matchBuckets_migration60.js
@@ -106,18 +106,25 @@ add_task(async function installStudyAndM
 // pref has unnecessary spaces in it, and then migrates.  The study should be
 // stopped and the pref should remain cleared.  i.e., the migration code should
 // parse the pref value and compare the resulting buckets instead of comparing
 // strings directly.
 add_task(async function installStudyPrefWithSpacesAndMigrate() {
   await sanityCheckInitialState();
 
   // Install the study.  It should set the pref.
-  let preferenceValue = " suggestion : 4, general : 5 ";
-  await PreferenceExperiments.start(newExperimentOpts({ preferenceValue }));
+  const preferenceValue = " suggestion : 4, general : 5 ";
+  const experiment = newExperimentOpts({
+    preferences: {
+      [PREF_NAME]: {
+        preferenceValue,
+      },
+    },
+  });
+  await PreferenceExperiments.start(experiment);
   Assert.ok(await PreferenceExperiments.has(STUDY_NAME),
             "Study installed");
   Assert.equal(Services.prefs.getCharPref(PREF_NAME, ""), preferenceValue,
                "Pref should be set by study");
 
   // Trigger migration.  The study should be stopped, and the pref should be
   // cleared since it's the default value.
   await promiseMigration();
@@ -133,17 +140,24 @@ add_task(async function installStudyPref
 // ("ratio": 1, "value": "general:3,suggestion:6") and migrates.  The study
 // should be stopped and the pref should be preserved since it's not the new
 // default.
 add_task(async function installStudyMinorityPrefAndMigrate() {
   await sanityCheckInitialState();
 
   // Install the study.  It should set the pref.
   let preferenceValue = "general:3,suggestion:6";
-  await PreferenceExperiments.start(newExperimentOpts({ preferenceValue }));
+  const experiment = newExperimentOpts({
+    preferences: {
+      [PREF_NAME]: {
+        preferenceValue,
+      },
+    },
+  });
+  await PreferenceExperiments.start(experiment);
   Assert.ok(await PreferenceExperiments.has(STUDY_NAME),
             "Study installed");
   Assert.equal(Services.prefs.getCharPref(PREF_NAME, ""), preferenceValue,
                "Pref should be set by study");
 
   // Trigger migration.  The study should be stopped, and the pref should remain
   // the same since it's a non-default value.  It should be set on the user
   // branch.
@@ -205,25 +219,36 @@ function promiseMigration() {
     return "migrateMatchBucketsPrefForUI66-done" == data;
   });
   Cc["@mozilla.org/browser/browserglue;1"]
     .getService(Ci.nsIObserver)
     .observe(null, topic, "migrateMatchBucketsPrefForUI66");
   return donePromise;
 }
 
-function newExperimentOpts(opts) {
+function newExperimentOpts(opts = {}) {
+  const defaultPref = {
+    [PREF_NAME]: {},
+  };
+  const defaultPrefInfo = {
+    preferenceValue: PREF_VALUE_SUGGESTIONS_FIRST,
+    preferenceBranchType: "default",
+    preferenceType: "string",
+  };
+  const preferences = {};
+  for (const [prefName, prefInfo] of Object.entries(opts.preferences || defaultPref)) {
+    preferences[prefName] = { ...defaultPrefInfo, ...prefInfo };
+  }
+
   return Object.assign({
     name: STUDY_NAME,
     branch: "branch",
-    preferenceName: PREF_NAME,
-    preferenceValue: PREF_VALUE_SUGGESTIONS_FIRST,
-    preferenceBranchType: "default",
-    preferenceType: "string",
-  }, opts);
+  }, opts, {
+    preferences,
+  });
 }
 
 async function getNonExpiredExperiment() {
   try {
     let exp = await PreferenceExperiments.get(STUDY_NAME);
     if (exp.expired) {
       return null;
     }
--- a/browser/components/translation/Translation.jsm
+++ b/browser/components/translation/Translation.jsm
@@ -63,17 +63,17 @@ var Translation = {
                                                  : aData.state;
     trUI.detectedLanguage = aData.detectedLanguage;
     trUI.translatedFrom = aData.translatedFrom;
     trUI.translatedTo = aData.translatedTo;
     trUI.originalShown = aData.originalShown;
 
     trUI.showURLBarIcon();
 
-    if (trUI.shouldShowInfoBar(aBrowser.contentPrincipal))
+    if (trUI.shouldShowInfoBar(aBrowser.currentURI))
       trUI.showTranslationInfoBar();
   },
 
   openProviderAttribution() {
     let attribution = this.supportedEngines[this.translationEngine];
     const {BrowserWindowTracker} = ChromeUtils.import("resource:///modules/BrowserWindowTracker.jsm");
     BrowserWindowTracker.getTopWindow().openWebLinkIn(attribution, "tab");
   },
@@ -229,33 +229,33 @@ TranslationUI.prototype = {
     let notificationBox = this.notificationBox;
     let notif = notificationBox.appendNotification("", "translation", null,
       notificationBox.PRIORITY_INFO_HIGH, null, null,
       "translation-notification");
     notif.init(this);
     return notif;
   },
 
-  shouldShowInfoBar(aPrincipal) {
+  shouldShowInfoBar(aURI) {
     // Never show the infobar automatically while the translation
     // service is temporarily unavailable.
     if (Translation.serviceUnavailable)
       return false;
 
     // Check if we should never show the infobar for this language.
     let neverForLangs =
       Services.prefs.getCharPref("browser.translation.neverForLanguages");
     if (neverForLangs.split(",").includes(this.detectedLanguage)) {
       TranslationTelemetry.recordAutoRejectedTranslationOffer();
       return false;
     }
 
     // or if we should never show the infobar for this domain.
     let perms = Services.perms;
-    if (perms.testExactPermissionFromPrincipal(aPrincipal, "translate") == perms.DENY_ACTION) {
+    if (perms.testExactPermission(aURI, "translate") == perms.DENY_ACTION) {
       TranslationTelemetry.recordAutoRejectedTranslationOffer();
       return false;
     }
 
     return true;
   },
 
   receiveMessage(msg) {
--- a/browser/components/translation/content/translation-notification.js
+++ b/browser/components/translation/content/translation-notification.js
@@ -307,40 +307,40 @@ class MozTranslationNotification extends
 
     // We may need to disable the menuitems if they have already been used.
     // Check if translation is already disabled for this language:
     let neverForLangs =
       Services.prefs.getCharPref("browser.translation.neverForLanguages");
     item.disabled = neverForLangs.split(",").includes(lang);
 
     // Check if translation is disabled for the domain:
-    let principal = this.translation.browser.contentPrincipal;
+    let uri = this.translation.browser.currentURI;
     let perms = Services.perms;
     item = this._getAnonElt("neverForSite");
     item.disabled =
-      perms.testExactPermissionFromPrincipal(principal, "translate") == perms.DENY_ACTION;
+      perms.testExactPermission(uri, "translate") == perms.DENY_ACTION;
   }
 
   neverForLanguage() {
     const kPrefName = "browser.translation.neverForLanguages";
 
     let val = Services.prefs.getCharPref(kPrefName);
     if (val)
       val += ",";
     val += this._getAnonElt("neverForLanguage").langCode;
 
     Services.prefs.setCharPref(kPrefName, val);
 
     this.closeCommand();
   }
 
   neverForSite() {
-    let principal = this.translation.browser.contentPrincipal;
+    let uri = this.translation.browser.currentURI;
     let perms = Services.perms;
-    perms.addFromPrincipal(principal, "translate", perms.DENY_ACTION);
+    perms.add(uri, "translate", perms.DENY_ACTION);
 
     this.closeCommand();
   }
 
   openProviderAttribution() {
     Translation.openProviderAttribution();
   }
 }
--- a/browser/components/translation/test/browser_translation_exceptions.js
+++ b/browser/components/translation/test/browser_translation_exceptions.js
@@ -107,18 +107,18 @@ var gTests = [
     // Show the infobar for example.com and fr.
     Translation.documentStateReceived(gBrowser.selectedBrowser,
                                       {state: Translation.STATE_OFFER,
                                        originalShown: true,
                                        detectedLanguage: "fr"});
     let notif = await getInfoBar();
     ok(notif, "the infobar is visible");
     let ui = gBrowser.selectedBrowser.translationUI;
-    let principal = gBrowser.selectedBrowser.contentPrincipal;
-    ok(ui.shouldShowInfoBar(principal, "fr"),
+    let uri = gBrowser.selectedBrowser.currentURI;
+    ok(ui.shouldShowInfoBar(uri, "fr"),
        "check shouldShowInfoBar initially returns true");
 
     // Open the "options" drop down.
     await openPopup(notif._getAnonElt("options"));
     ok(notif._getAnonElt("options").getAttribute("open"),
        "the options menu is open");
 
     // Check that the item is not disabled.
@@ -129,17 +129,17 @@ var gTests = [
     notif._getAnonElt("neverForLanguage").click();
     notif = await getInfoBar();
     ok(!notif, "infobar hidden");
 
     // Check this has been saved to the exceptions list.
     let langs = getLanguageExceptions();
     is(langs.length, 1, "one language in the exception list");
     is(langs[0], "fr", "correct language in the exception list");
-    ok(!ui.shouldShowInfoBar(principal, "fr"),
+    ok(!ui.shouldShowInfoBar(uri, "fr"),
        "the infobar wouldn't be shown anymore");
 
     // Reopen the infobar.
     PopupNotifications.getNotification("translate").anchorElement.click();
     notif = await getInfoBar();
     // Open the "options" drop down.
     await openPopup(notif._getAnonElt("options"));
     ok(notif._getAnonElt("neverForLanguage").disabled,
@@ -157,18 +157,18 @@ var gTests = [
     // Show the infobar for example.com and fr.
     Translation.documentStateReceived(gBrowser.selectedBrowser,
                                       {state: Translation.STATE_OFFER,
                                        originalShown: true,
                                        detectedLanguage: "fr"});
     let notif = await getInfoBar();
     ok(notif, "the infobar is visible");
     let ui = gBrowser.selectedBrowser.translationUI;
-    let principal = gBrowser.selectedBrowser.contentPrincipal;
-    ok(ui.shouldShowInfoBar(principal, "fr"),
+    let uri = gBrowser.selectedBrowser.currentURI;
+    ok(ui.shouldShowInfoBar(uri, "fr"),
        "check shouldShowInfoBar initially returns true");
 
     // Open the "options" drop down.
     await openPopup(notif._getAnonElt("options"));
     ok(notif._getAnonElt("options").getAttribute("open"),
        "the options menu is open");
 
     // Check that the item is not disabled.
@@ -179,17 +179,17 @@ var gTests = [
     notif._getAnonElt("neverForSite").click();
     notif = await getInfoBar();
     ok(!notif, "infobar hidden");
 
     // Check this has been saved to the exceptions list.
     let sites = getDomainExceptions();
     is(sites.length, 1, "one site in the exception list");
     is(sites[0].origin, "http://example.com", "correct site in the exception list");
-    ok(!ui.shouldShowInfoBar(principal, "fr"),
+    ok(!ui.shouldShowInfoBar(uri, "fr"),
        "the infobar wouldn't be shown anymore");
 
     // Reopen the infobar.
     PopupNotifications.getNotification("translate").anchorElement.click();
     notif = await getInfoBar();
     // Open the "options" drop down.
     await openPopup(notif._getAnonElt("options"));
     ok(notif._getAnonElt("neverForSite").disabled,
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -93,23 +93,25 @@ class UrlbarInput {
     this.lastQueryContextPromise = Promise.resolve();
     this._actionOverrideKeyCount = 0;
     this._autofillPlaceholder = "";
     this._deletedEndOfAutofillPlaceholder = false;
     this._lastSearchString = "";
     this._resultForCurrentValue = null;
     this._suppressStartQuery = false;
     this._untrimmedValue = "";
+    this._openViewOnFocus = false;
 
     // This exists only for tests.
     this._enableAutofillPlaceholder = true;
 
     // Forward textbox methods and properties.
     const METHODS = ["addEventListener", "removeEventListener",
-      "setAttribute", "hasAttribute", "removeAttribute", "getAttribute",
+      "getAttribute", "hasAttribute",
+      "setAttribute", "removeAttribute", "toggleAttribute",
       "select"];
     const READ_ONLY_PROPERTIES = ["inputField", "editor"];
     const READ_WRITE_PROPERTIES = ["placeholder", "readOnly",
       "selectionStart", "selectionEnd"];
 
     for (let method of METHODS) {
       this[method] = (...args) => {
         return this.textbox[method](...args);
@@ -465,17 +467,17 @@ class UrlbarInput {
         return;
       }
       case UrlbarUtils.RESULT_TYPE.SEARCH: {
         if (result.payload.isKeywordOffer) {
           // Picking a keyword offer just fills it in the input and doesn't
           // visit anything.  The user can then type a search string.  Also
           // start a new search so that the offer appears in the view by itself
           // to make it even clearer to the user what's going on.
-          this.startQuery();
+          this.startQuery({ allowEmptyInput: true });
           return;
         }
         const actionDetails = {
           isSuggestion: !!result.payload.suggestion,
           alias: result.payload.keyword,
         };
         const engine = Services.search.getEngineByName(result.payload.engine);
         this._recordSearch(engine, event, actionDetails);
@@ -607,17 +609,17 @@ class UrlbarInput {
    *   If true and the search string is empty, then the input will become empty
    *   when no result is selected.  If false, the input will continue showing
    *   the last non-empty search string when no result is selected.
    */
   startQuery({
     allowAutofill = true,
     searchString = null,
     resetSearchState = true,
-    allowEmptyInput = true,
+    allowEmptyInput = false,
   } = {}) {
     if (this._suppressStartQuery) {
       return;
     }
 
     if (resetSearchState) {
       this._resetSearchState();
     }
@@ -739,16 +741,25 @@ class UrlbarInput {
   get value() {
     return this._untrimmedValue;
   }
 
   set value(val) {
     return this._setValue(val, true);
   }
 
+  get openViewOnFocus() {
+    return this._openViewOnFocus;
+  }
+
+  set openViewOnFocus(val) {
+    this._openViewOnFocus = val;
+    this.toggleAttribute("hidedropmarker", val);
+  }
+
   // Private methods below.
 
   _setValue(val, allowTrim) {
     this._untrimmedValue = val;
 
     let originalUrl = ReaderMode.getOriginalUrlObjectForDisplay(val);
     if (originalUrl) {
       val = originalUrl.displaySpec;
@@ -1296,16 +1307,20 @@ class UrlbarInput {
   _on_focus(event) {
     this._updateUrlTooltip();
     this.formatValue();
 
     // Hide popup notifications, to reduce visual noise.
     if (this.getAttribute("pageproxystate") != "valid") {
       this.window.UpdatePopupNotificationsVisibility();
     }
+
+    if (this.openViewOnFocus) {
+      this.startQuery();
+    }
   }
 
   _on_mouseover(event) {
     this._updateUrlTooltip();
   }
 
   _on_mousedown(event) {
     if (event.originalTarget == this.inputField &&
@@ -1317,17 +1332,17 @@ class UrlbarInput {
       return;
     }
 
     if (event.originalTarget.classList.contains("urlbar-history-dropmarker") &&
         event.button == 0) {
       if (this.view.isOpen) {
         this.view.close();
       } else {
-        this.startQuery({ allowEmptyInput: false });
+        this.startQuery();
       }
     }
   }
 
   _on_input() {
     let value = this.textValue;
     this.valueIsTyped = true;
     let valueIsPasted = this._valueIsPasted;
@@ -1378,16 +1393,17 @@ class UrlbarInput {
     let deletedAutofilledSubstring =
       deletedEndOfAutofillPlaceholder && value == this._lastSearchString;
     let allowAutofill = !valueIsPasted &&
       this._maybeAutofillOnInput(value, deletedAutofilledSubstring);
 
     this.startQuery({
       searchString: value,
       allowAutofill,
+      allowEmptyInput: true,
       resetSearchState: false,
     });
   }
 
   _on_select(event) {
     if (!this.window.windowUtils.isHandlingUserInput) {
       // Register the editor listener we use to detect when the user deletes
       // autofilled substrings.  The editor is destroyed and removes all its
@@ -1471,16 +1487,19 @@ class UrlbarInput {
 
   _on_scrollend(event) {
     this._updateTextOverflow();
   }
 
   _on_TabSelect(event) {
     this._resetSearchState();
     this.controller.viewContextChanged();
+    if (this.focused && this.openViewOnFocus) {
+      this.startQuery();
+    }
   }
 
   _on_keydown(event) {
     // Due to event deferring, it's possible preventDefault() won't be invoked
     // soon enough to actually prevent some of the default behaviors, thus we
     // have to handle the event "twice". This first immediate call passes false
     // as second argument so that handleKeyNavigation will only simulate the
     // event handling, without actually executing actions.
--- a/browser/components/urlbar/UrlbarResult.jsm
+++ b/browser/components/urlbar/UrlbarResult.jsm
@@ -107,17 +107,19 @@ class UrlbarResult {
       case UrlbarUtils.RESULT_TYPE.URL:
       case UrlbarUtils.RESULT_TYPE.OMNIBOX:
       case UrlbarUtils.RESULT_TYPE.REMOTE_TAB:
         return this.payload.title ?
                [this.payload.title, this.payloadHighlights.title] :
                [this.payload.url || "", this.payloadHighlights.url || []];
       case UrlbarUtils.RESULT_TYPE.SEARCH:
         if (this.payload.isKeywordOffer) {
-          return [this.payload.keyword, this.payloadHighlights.keyword];
+          return this.heuristic ?
+                 ["", []] :
+                 [this.payload.keyword, this.payloadHighlights.keyword];
         }
         return this.payload.suggestion ?
                [this.payload.suggestion, this.payloadHighlights.suggestion] :
                [this.payload.query, this.payloadHighlights.query];
       default:
         return ["", []];
     }
   }
--- a/browser/components/urlbar/UrlbarView.jsm
+++ b/browser/components/urlbar/UrlbarView.jsm
@@ -281,26 +281,16 @@ class UrlbarView {
       newSelectionIndex = this._queryContext.results.length - 1;
     }
     if (newSelectionIndex >= 0) {
       this.selectedIndex = newSelectionIndex;
     }
   }
 
   /**
-   * Notified when the view context changes, for example when switching tabs.
-   * It can be used to reset internal state tracking.
-   */
-  onViewContextChanged() {
-    // Clear rows, so that when reusing results we don't visually leak them
-    // across different contexts.
-    this._rows.textContent = "";
-  }
-
-  /**
    * Passes DOM events for the view to the _on_<event type> methods.
    * @param {Event} event
    *   DOM event from the <view>.
    */
   handleEvent(event) {
     let methodName = "_on_" + event.type;
     if (methodName in this) {
       this[methodName](event);
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -243,16 +243,17 @@ These should match what Safari and other
 <!ENTITY urlbar.geolocationBlocked.tooltip       "You have blocked location information for this website.">
 <!ENTITY urlbar.webNotificationsBlocked.tooltip  "You have blocked notifications for this website.">
 <!ENTITY urlbar.persistentStorageBlocked.tooltip "You have blocked persistent storage for this website.">
 <!ENTITY urlbar.popupBlocked.tooltip             "You have blocked pop-ups for this website.">
 <!ENTITY urlbar.autoplayMediaBlocked.tooltip     "You have blocked autoplay media with sound for this website.">
 <!ENTITY urlbar.canvasBlocked.tooltip            "You have blocked canvas data extraction for this website.">
 <!ENTITY urlbar.flashPluginBlocked.tooltip       "You have blocked this website from using the Adobe Flash plugin.">
 <!ENTITY urlbar.midiBlocked.tooltip              "You have blocked MIDI access for this website.">
+<!ENTITY urlbar.installBlocked.tooltip           "You have blocked add-on installation for this website.">
 
 <!ENTITY urlbar.openHistoryPopup.tooltip                "Show history">
 
 <!ENTITY searchItem.title             "Search">
 
 <!-- Toolbar items -->
 <!ENTITY homeButton.label             "Home">
 <!ENTITY homeButton.defaultPage.tooltip "&brandShortName; Home Page">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -25,16 +25,18 @@ xpinstallPromptMessage=%S prevented this
 # The string contains the hostname of the site the add-on is being installed from.
 xpinstallPromptMessage.header=Allow %S to install an add-on?
 xpinstallPromptMessage.message=You are attempting to install an add-on from %S. Make sure you trust this site before continuing.
 xpinstallPromptMessage.header.unknown=Allow an unknown site to install an add-on?
 xpinstallPromptMessage.message.unknown=You are attempting to install an add-on from an unknown site. Make sure you trust this site before continuing.
 xpinstallPromptMessage.learnMore=Learn more about installing add-ons safely
 xpinstallPromptMessage.dontAllow=Don’t Allow
 xpinstallPromptMessage.dontAllow.accesskey=D
+xpinstallPromptMessage.neverAllow=Never Allow
+xpinstallPromptMessage.neverAllow.accesskey=N
 # Accessibility Note:
 # Be sure you do not choose an accesskey that is used elsewhere in the active context (e.g. main menu bar, submenu of the warning popup button)
 # See https://website-archive.mozilla.org/www.mozilla.org/access/access/keyboard/ for details
 xpinstallPromptMessage.install=Continue to Installation
 xpinstallPromptMessage.install.accesskey=C
 
 xpinstallDisabledMessageLocked=Software installation has been disabled by your system administrator.
 xpinstallDisabledMessage=Software installation is currently disabled. Click Enable and try again.
--- a/browser/modules/PermissionUI.jsm
+++ b/browser/modules/PermissionUI.jsm
@@ -359,19 +359,19 @@ var PermissionPromptPrototype = {
       return;
     }
 
     if (this.usePermissionManager &&
         this.permissionKey) {
       // If we're reading and setting permissions, then we need
       // to check to see if we already have a permission setting
       // for this particular principal.
-      let {state} = SitePermissions.getForPrincipal(this.principal,
-                                                    this.permissionKey,
-                                                    this.browser);
+      let {state} = SitePermissions.get(requestingURI,
+                                        this.permissionKey,
+                                        this.browser);
 
       if (state == SitePermissions.BLOCK) {
         // If this block was done based on a global user setting, we want to show
         // a post prompt to give the user some more granular control without
         // annoying them too much.
         if (this.postPromptEnabled &&
             SitePermissions.getDefault(this.permissionKey) == SitePermissions.BLOCK) {
           this.postPrompt();
@@ -434,29 +434,29 @@ var PermissionPromptPrototype = {
             if ((state && state.checkboxChecked && state.source != "esc-press") ||
                 promptAction.scope == SitePermissions.SCOPE_PERSISTENT) {
               // Permanently store permission.
               let scope = SitePermissions.SCOPE_PERSISTENT;
               // Only remember permission for session if in PB mode.
               if (PrivateBrowsingUtils.isBrowserPrivate(this.browser)) {
                 scope = SitePermissions.SCOPE_SESSION;
               }
-              SitePermissions.setForPrincipal(this.principal,
-                                              this.permissionKey,
-                                              promptAction.action,
-                                              scope);
+              SitePermissions.set(this.principal.URI,
+                                  this.permissionKey,
+                                  promptAction.action,
+                                  scope);
             } else if (promptAction.action == SitePermissions.BLOCK) {
               // Temporarily store BLOCK permissions only
               // SitePermissions does not consider subframes when storing temporary
               // permissions on a tab, thus storing ALLOW could be exploited.
-              SitePermissions.setForPrincipal(this.principal,
-                                              this.permissionKey,
-                                              promptAction.action,
-                                              SitePermissions.SCOPE_TEMPORARY,
-                                              this.browser);
+              SitePermissions.set(this.principal.URI,
+                                  this.permissionKey,
+                                  promptAction.action,
+                                  SitePermissions.SCOPE_TEMPORARY,
+                                  this.browser);
             }
 
             // Grant permission if action is ALLOW.
             // Record buttonAction for telemetry.
             if (promptAction.action == SitePermissions.ALLOW) {
               this._buttonAction = "accept";
               this.allow();
             } else {
--- a/browser/modules/SitePermissions.jsm
+++ b/browser/modules/SitePermissions.jsm
@@ -254,61 +254,39 @@ var SitePermissions = {
   SCOPE_PERSISTENT: "{SitePermissions.SCOPE_PERSISTENT}",
   SCOPE_POLICY: "{SitePermissions.SCOPE_POLICY}",
   SCOPE_GLOBAL: "{SitePermissions.SCOPE_GLOBAL}",
 
   _permissionsArray: null,
   _defaultPrefBranch: Services.prefs.getBranch("permissions.default."),
 
   /**
-   * Deprecated! Please use getAllByPrincipal(principal) instead.
    * Gets all custom permissions for a given URI.
    * Install addon permission is excluded, check bug 1303108.
    *
    * @return {Array} a list of objects with the keys:
    *          - id: the permissionId of the permission
    *          - scope: the scope of the permission (e.g. SitePermissions.SCOPE_TEMPORARY)
    *          - state: a constant representing the current permission state
    *            (e.g. SitePermissions.ALLOW)
    */
   getAllByURI(uri) {
     if (!(uri instanceof Ci.nsIURI))
       throw new Error("uri parameter should be an nsIURI");
-
-    let principal = uri ? Services.scriptSecurityManager.createCodebasePrincipal(uri, {}) : null;
-    return this.getAllByPrincipal(principal);
-  },
-
-  /**
-   * Gets all custom permissions for a given principal.
-   * Install addon permission is excluded, check bug 1303108.
-   *
-   * @return {Array} a list of objects with the keys:
-   *          - id: the permissionId of the permission
-   *          - scope: the scope of the permission (e.g. SitePermissions.SCOPE_TEMPORARY)
-   *          - state: a constant representing the current permission state
-   *            (e.g. SitePermissions.ALLOW)
-   */
-  getAllByPrincipal(principal) {
     let result = [];
-    if (!this.isSupportedPrincipal(principal)) {
+    if (!this.isSupportedURI(uri)) {
       return result;
     }
 
-    let permissions = Services.perms.getAllForPrincipal(principal);
+    let permissions = Services.perms.getAllForURI(uri);
     while (permissions.hasMoreElements()) {
       let permission = permissions.getNext();
 
       // filter out unknown permissions
       if (gPermissionObject[permission.type]) {
-        // XXX Bug 1303108 - Control Center should only show non-default permissions
-        if (permission.type == "install") {
-          continue;
-        }
-
         // Hide canvas permission when privacy.resistFingerprinting is false.
         if ((permission.type == "canvas") && !this.resistFingerprinting) {
           continue;
         }
 
         let scope = this.SCOPE_PERSISTENT;
         if (permission.expireType == Services.perms.EXPIRE_SESSION) {
           scope = this.SCOPE_SESSION;
@@ -350,17 +328,17 @@ var SitePermissions = {
       permission.scope = this.SCOPE_TEMPORARY;
       permissions[permission.id] = permission;
     }
 
     for (let permission of GloballyBlockedPermissions.getAll(browser)) {
       permissions[permission.id] = permission;
     }
 
-    for (let permission of this.getAllByPrincipal(browser.contentPrincipal)) {
+    for (let permission of this.getAllByURI(browser.currentURI)) {
       permissions[permission.id] = permission;
     }
 
     return Object.values(permissions);
   },
 
   /**
    * Returns a list of objects with detailed information on all permissions
@@ -378,46 +356,30 @@ var SitePermissions = {
    *           - label: the localized label, or null if none is available.
    */
   getAllPermissionDetailsForBrowser(browser) {
     return this.getAllForBrowser(browser).map(({id, scope, state}) =>
       ({id, scope, state, label: this.getPermissionLabel(id)}));
   },
 
   /**
-   * Deprecated! Please use isSupportedPrincipal(principal) instead.
    * Checks whether a UI for managing permissions should be exposed for a given
    * URI. This excludes file URIs, for instance, as they don't have a host,
    * even though nsIPermissionManager can still handle them.
    *
    * @param {nsIURI} uri
    *        The URI to check.
    *
    * @return {boolean} if the URI is supported.
    */
   isSupportedURI(uri) {
     return uri && ["http", "https", "moz-extension"].includes(uri.scheme);
   },
 
   /**
-   * Checks whether a UI for managing permissions should be exposed for a given
-   * principal. This excludes file URIs, for instance, as they don't have a host,
-   * even though nsIPermissionManager can still handle them.
-   *
-   * @param {nsIPrincipal} principal
-   *        The principal to check.
-   *
-   * @return {boolean} if the principal is supported.
-   */
-  isSupportedPrincipal(principal) {
-    return principal && principal.URI &&
-      ["http", "https", "moz-extension"].includes(principal.URI.scheme);
-  },
-
- /**
    * Gets an array of all permission IDs.
    *
    * @return {Array<String>} an array of all permission IDs.
    */
   listPermissions() {
     if (this._permissionsArray === null) {
       let permissions = Object.keys(gPermissionObject);
 
@@ -506,50 +468,25 @@ var SitePermissions = {
    *           - state: The current state of the permission
    *             (e.g. SitePermissions.ALLOW)
    *           - scope: The scope of the permission
    *             (e.g. SitePermissions.SCOPE_PERSISTENT)
    */
   get(uri, permissionID, browser) {
     if ((!uri && !browser) || (uri && !(uri instanceof Ci.nsIURI)))
       throw new Error("uri parameter should be an nsIURI or a browser parameter is needed");
-
-    let principal = uri ? Services.scriptSecurityManager.createCodebasePrincipal(uri, {}) : null;
-    return this.getForPrincipal(principal, permissionID, browser);
-  },
-
- /**
-   * Returns the state and scope of a particular permission for a given principal.
-   *
-   * This method will NOT dispatch a "PermissionStateChange" event on the specified
-   * browser if a temporary permission was removed because it has expired.
-   *
-   * @param {nsIPrincipal} principal
-   *        The principal to check.
-   * @param {String} permissionID
-   *        The id of the permission.
-   * @param {Browser} browser (optional)
-   *        The browser object to check for temporary permissions.
-   *
-   * @return {Object} an object with the keys:
-   *           - state: The current state of the permission
-   *             (e.g. SitePermissions.ALLOW)
-   *           - scope: The scope of the permission
-   *             (e.g. SitePermissions.SCOPE_PERSISTENT)
-   */
-  getForPrincipal(principal, permissionID, browser) {
     let defaultState = this.getDefault(permissionID);
     let result = { state: defaultState, scope: this.SCOPE_PERSISTENT };
-    if (this.isSupportedPrincipal(principal)) {
+    if (this.isSupportedURI(uri)) {
       let permission = null;
       if (permissionID in gPermissionObject &&
         gPermissionObject[permissionID].exactHostMatch) {
-        permission = Services.perms.getPermissionObject(principal, permissionID, true);
+        permission = Services.perms.getPermissionObjectForURI(uri, permissionID, true);
       } else {
-        permission = Services.perms.getPermissionObject(principal, permissionID, false);
+        permission = Services.perms.getPermissionObjectForURI(uri, permissionID, false);
       }
 
       if (permission) {
         result.state = permission.capability;
         if (permission.expireType == Services.perms.EXPIRE_SESSION) {
           result.scope = this.SCOPE_SESSION;
         } else if (permission.expireType == Services.perms.EXPIRE_POLICY) {
           result.scope = this.SCOPE_POLICY;
@@ -567,17 +504,16 @@ var SitePermissions = {
         result.scope = this.SCOPE_TEMPORARY;
       }
     }
 
     return result;
   },
 
   /**
-   * Deprecated! Use setForPrincipal(...) instead.
    * Sets the state of a particular permission for a given URI or browser.
    * This method will dispatch a "PermissionStateChange" event on the specified
    * browser if a temporary permission was set
    *
    * @param {nsIURI} uri
    *        The URI to set the permission for.
    *        Note that this will be ignored if the scope is set to SCOPE_TEMPORARY
    * @param {String} permissionID
@@ -588,130 +524,87 @@ var SitePermissions = {
    *        The scope of the permission. Defaults to SCOPE_PERSISTENT.
    * @param {Browser} browser (optional)
    *        The browser object to set temporary permissions on.
    *        This needs to be provided if the scope is SCOPE_TEMPORARY!
    */
   set(uri, permissionID, state, scope = this.SCOPE_PERSISTENT, browser = null) {
     if ((!uri && !browser) || (uri && !(uri instanceof Ci.nsIURI)))
       throw new Error("uri parameter should be an nsIURI or a browser parameter is needed");
-
-    let principal = uri ? Services.scriptSecurityManager.createCodebasePrincipal(uri, {}) : null;
-    return this.setForPrincipal(principal, permissionID, state, scope, browser);
-  },
-
-  /**
-   * Sets the state of a particular permission for a given principal or browser.
-   * This method will dispatch a "PermissionStateChange" event on the specified
-   * browser if a temporary permission was set
-   *
-   * @param {nsIPrincipal} principal
-   *        The principal to set the permission for.
-   *        Note that this will be ignored if the scope is set to SCOPE_TEMPORARY
-   * @param {String} permissionID
-   *        The id of the permission.
-   * @param {SitePermissions state} state
-   *        The state of the permission.
-   * @param {SitePermissions scope} scope (optional)
-   *        The scope of the permission. Defaults to SCOPE_PERSISTENT.
-   * @param {Browser} browser (optional)
-   *        The browser object to set temporary permissions on.
-   *        This needs to be provided if the scope is SCOPE_TEMPORARY!
-   */
-  setForPrincipal(principal, permissionID, state, scope = this.SCOPE_PERSISTENT, browser = null) {
     if (scope == this.SCOPE_GLOBAL && state == this.BLOCK) {
       GloballyBlockedPermissions.set(browser, permissionID);
       browser.dispatchEvent(new browser.ownerGlobal.CustomEvent("PermissionStateChange"));
       return;
     }
 
     if (state == this.UNKNOWN || state == this.getDefault(permissionID)) {
       // Because they are controlled by two prefs with many states that do not
       // correspond to the classical ALLOW/DENY/PROMPT model, we want to always
       // allow the user to add exceptions to their cookie rules without removing them.
       if (permissionID != "cookie") {
-        this.removeFromPrincipal(principal, permissionID, browser);
+        this.remove(uri, permissionID, browser);
         return;
       }
     }
 
     if (state == this.ALLOW_COOKIES_FOR_SESSION && permissionID != "cookie") {
       throw new Error("ALLOW_COOKIES_FOR_SESSION can only be set on the cookie permission");
     }
 
     // Save temporary permissions.
     if (scope == this.SCOPE_TEMPORARY) {
       // We do not support setting temp ALLOW for security reasons.
       // In its current state, this permission could be exploited by subframes
       // on the same page. This is because for BLOCK we ignore the request
-      // principal and only consider the current browser principal, to avoid notification spamming.
+      // URI and only consider the current browser URI, to avoid notification spamming.
       //
       // If you ever consider removing this line, you likely want to implement
       // a more fine-grained TemporaryPermissions that temporarily blocks for the
       // entire browser, but temporarily allows only for specific frames.
       if (state != this.BLOCK) {
         throw new Error("'Block' is the only permission we can save temporarily on a browser");
       }
 
       if (!browser) {
         throw new Error("TEMPORARY scoped permissions require a browser object");
       }
 
       TemporaryPermissions.set(browser, permissionID, state);
 
       browser.dispatchEvent(new browser.ownerGlobal
                                        .CustomEvent("PermissionStateChange"));
-    } else if (this.isSupportedPrincipal(principal)) {
+    } else if (this.isSupportedURI(uri)) {
       let perms_scope = Services.perms.EXPIRE_NEVER;
       if (scope == this.SCOPE_SESSION) {
         perms_scope = Services.perms.EXPIRE_SESSION;
       } else if (scope == this.SCOPE_POLICY) {
         perms_scope = Services.perms.EXPIRE_POLICY;
       }
 
-      Services.perms.addFromPrincipal(principal, permissionID, state, perms_scope);
+      Services.perms.add(uri, permissionID, state, perms_scope);
     }
   },
 
   /**
-   * Deprecated! Please use removeFromPrincipal(principal, permissionID, browser).
    * Removes the saved state of a particular permission for a given URI and/or browser.
    * This method will dispatch a "PermissionStateChange" event on the specified
    * browser if a temporary permission was removed.
    *
    * @param {nsIURI} uri
    *        The URI to remove the permission for.
    * @param {String} permissionID
    *        The id of the permission.
    * @param {Browser} browser (optional)
    *        The browser object to remove temporary permissions on.
    */
   remove(uri, permissionID, browser) {
     if ((!uri && !browser) || (uri && !(uri instanceof Ci.nsIURI)))
       throw new Error("uri parameter should be an nsIURI or a browser parameter is needed");
-
-    let principal = uri ? Services.scriptSecurityManager.createCodebasePrincipal(uri, {}) : null;
-    return this.removeFromPrincipal(principal, permissionID, browser);
-  },
-
-  /**
-   * Removes the saved state of a particular permission for a given principal and/or browser.
-   * This method will dispatch a "PermissionStateChange" event on the specified
-   * browser if a temporary permission was removed.
-   *
-   * @param {nsIPrincipal} principal
-   *        The principal to remove the permission for.
-   * @param {String} permissionID
-   *        The id of the permission.
-   * @param {Browser} browser (optional)
-   *        The browser object to remove temporary permissions on.
-   */
-  removeFromPrincipal(principal, permissionID, browser) {
-    if (this.isSupportedPrincipal(principal))
-      Services.perms.removeFromPrincipal(principal, permissionID);
+    if (this.isSupportedURI(uri))
+      Services.perms.remove(uri, permissionID);
 
     // TemporaryPermissions.get() deletes expired permissions automatically,
     if (TemporaryPermissions.get(browser, permissionID)) {
       // If it exists but has not expired, remove it explicitly.
       TemporaryPermissions.remove(browser, permissionID);
       // Send a PermissionStateChange event only if the permission hasn't expired.
       browser.dispatchEvent(new browser.ownerGlobal
                                        .CustomEvent("PermissionStateChange"));
@@ -914,19 +807,18 @@ var gPermissionObject = {
                SitePermissions.BLOCK : SitePermissions.ALLOW;
     },
     states: [ SitePermissions.ALLOW, SitePermissions.BLOCK ],
   },
 
   "install": {
     getDefault() {
       return Services.prefs.getBoolPref("xpinstall.whitelist.required") ?
-               SitePermissions.BLOCK : SitePermissions.ALLOW;
+               SitePermissions.UNKNOWN : SitePermissions.ALLOW;
     },
-    states: [ SitePermissions.ALLOW, SitePermissions.BLOCK ],
   },
 
   "geo": {
     exactHostMatch: true,
   },
 
   "focus-tab-by-prompt": {
     exactHostMatch: true,
--- a/browser/modules/test/unit/test_SitePermissions.js
+++ b/browser/modules/test/unit/test_SitePermissions.js
@@ -74,21 +74,16 @@ add_task(async function testGetAllByURI(
       { id: "camera", state: SitePermissions.ALLOW, scope: SitePermissions.SCOPE_PERSISTENT },
       { id: "desktop-notification", state: SitePermissions.BLOCK, scope: SitePermissions.SCOPE_PERSISTENT },
   ]);
 
   SitePermissions.remove(uri, "camera");
   SitePermissions.remove(uri, "desktop-notification");
   Assert.deepEqual(SitePermissions.getAllByURI(uri), []);
 
-  // XXX Bug 1303108 - Control Center should only show non-default permissions
-  SitePermissions.set(uri, "addon", SitePermissions.BLOCK);
-  Assert.deepEqual(SitePermissions.getAllByURI(uri), []);
-  SitePermissions.remove(uri, "addon");
-
   Assert.equal(Services.prefs.getIntPref("permissions.default.shortcuts"), 0);
   SitePermissions.set(uri, "shortcuts", SitePermissions.BLOCK);
 
   // Customized preference should have been enabled, but the default should not.
   Assert.equal(Services.prefs.getIntPref("permissions.default.shortcuts"), 0);
   Assert.deepEqual(SitePermissions.getAllByURI(uri), [
       { id: "shortcuts", state: SitePermissions.BLOCK, scope: SitePermissions.SCOPE_PERSISTENT },
   ]);
--- a/browser/modules/webrtcUI.jsm
+++ b/browser/modules/webrtcUI.jsm
@@ -403,24 +403,30 @@ function stopRecording(aBrowser, aReques
     set.delete(aRequest.windowID + aRequest.mediaSource + aRequest.rawID);
   }
 }
 
 function prompt(aBrowser, aRequest) {
   let { audioDevices, videoDevices, sharingScreen, sharingAudio,
         requestTypes } = aRequest;
 
-  let principal = aBrowser.contentPrincipal;
+  let uri;
+  try {
+    // This fails for principals that serialize to "null", e.g. file URIs.
+    uri = Services.io.newURI(aRequest.origin);
+  } catch (e) {
+    uri = Services.io.newURI(aRequest.documentURI);
+  }
 
   // If the user has already denied access once in this tab,
   // deny again without even showing the notification icon.
   if ((audioDevices.length && SitePermissions
-        .getForPrincipal(principal, "microphone", aBrowser).state == SitePermissions.BLOCK) ||
+        .get(uri, "microphone", aBrowser).state == SitePermissions.BLOCK) ||
       (videoDevices.length && SitePermissions
-        .getForPrincipal(principal, sharingScreen ? "screen" : "camera", aBrowser).state == SitePermissions.BLOCK)) {
+        .get(uri, sharingScreen ? "screen" : "camera", aBrowser).state == SitePermissions.BLOCK)) {
     denyRequest(aBrowser, aRequest);
     return;
   }
 
   // Tell the browser to refresh the identity block display in case there
   // are expired permission states.
   aBrowser.dispatchEvent(new aBrowser.ownerGlobal
                                      .CustomEvent("PermissionStateChange"));
@@ -463,29 +469,29 @@ function prompt(aBrowser, aRequest) {
       accessKey: stringBundle.getString("getUserMedia.dontAllow.accesskey"),
       callback(aState) {
         denyRequest(notification.browser, aRequest);
         let scope = SitePermissions.SCOPE_TEMPORARY;
         if (aState && aState.checkboxChecked) {
           scope = SitePermissions.SCOPE_PERSISTENT;
         }
         if (audioDevices.length)
-          SitePermissions.setForPrincipal(principal, "microphone",
-                                          SitePermissions.BLOCK, scope, notification.browser);
+          SitePermissions.set(uri, "microphone",
+                              SitePermissions.BLOCK, scope, notification.browser);
         if (videoDevices.length)
-          SitePermissions.setForPrincipal(principal, sharingScreen ? "screen" : "camera",
-                                          SitePermissions.BLOCK, scope, notification.browser);
+          SitePermissions.set(uri, sharingScreen ? "screen" : "camera",
+                              SitePermissions.BLOCK, scope, notification.browser);
       },
     },
   ];
 
   let productName = gBrandBundle.GetStringFromName("brandShortName");
 
   let options = {
-    name: getHostOrExtensionName(principal.URI),
+    name: getHostOrExtensionName(uri),
     persistent: true,
     hideClose: true,
     eventCallback(aTopic, aNewBrowser) {
       if (aTopic == "swapping")
         return true;
 
       let doc = this.browser.ownerDocument;
 
@@ -511,25 +517,25 @@ function prompt(aBrowser, aRequest) {
 
       // BLOCK is handled immediately by MediaManager if it has been set
       // persistently in the permission manager. If it has been set on the tab,
       // it is handled synchronously before we add the notification.
       // Handling of ALLOW is delayed until the popupshowing event,
       // to avoid granting permissions automatically to background tabs.
       if (aRequest.secure) {
         let micAllowed =
-          SitePermissions.getForPrincipal(principal, "microphone").state == SitePermissions.ALLOW;
+          SitePermissions.get(uri, "microphone").state == SitePermissions.ALLOW;
         let camAllowed =
-          SitePermissions.getForPrincipal(principal, "camera").state == SitePermissions.ALLOW;
+          SitePermissions.get(uri, "camera").state == SitePermissions.ALLOW;
 
         let perms = Services.perms;
         let mediaManagerPerm =
-          perms.testExactPermissionFromPrincipal(principal, "MediaManagerVideo");
+          perms.testExactPermission(uri, "MediaManagerVideo");
         if (mediaManagerPerm) {
-          perms.removeFromPrincipal(principal, "MediaManagerVideo");
+          perms.remove(uri, "MediaManagerVideo");
         }
 
         // Screen sharing shouldn't follow the camera permissions.
         if (videoDevices.length && sharingScreen)
           camAllowed = false;
 
         let activeCamera;
         let activeMic;
@@ -553,31 +559,31 @@ function prompt(aBrowser, aRequest) {
           }
         }
 
         if ((!audioDevices.length || micAllowed || activeMic) &&
             (!videoDevices.length || camAllowed || activeCamera)) {
           let allowedDevices = [];
           if (videoDevices.length) {
             allowedDevices.push((activeCamera || videoDevices[0]).deviceIndex);
-            Services.perms.addFromPrincipal(principal, "MediaManagerVideo",
-                                            Services.perms.ALLOW_ACTION,
-                                            Services.perms.EXPIRE_SESSION);
+            Services.perms.add(uri, "MediaManagerVideo",
+                               Services.perms.ALLOW_ACTION,
+                               Services.perms.EXPIRE_SESSION);
           }
           if (audioDevices.length) {
             allowedDevices.push((activeMic || audioDevices[0]).deviceIndex);
           }
 
           // Remember on which URIs we found persistent permissions so that we
           // can remove them if the user clicks 'Stop Sharing'. There's no
           // other way for the stop sharing code to know the hostnames of frames
           // using devices until bug 1066082 is fixed.
           let browser = this.browser;
-          browser._devicePermissionPrincipals = browser._devicePermissionPrincipals || [];
-          browser._devicePermissionPrincipals.push(principal);
+          browser._devicePermissionURIs = browser._devicePermissionURIs || [];
+          browser._devicePermissionURIs.push(uri);
 
           let camNeeded = videoDevices.length > 0;
           let micNeeded = audioDevices.length > 0;
           checkOSPermission(camNeeded, micNeeded).then((havePermission) => {
             if (havePermission) {
               let mm = browser.messageManager;
               mm.sendAsyncMessage("webrtc:Allow", {callID: aRequest.callID,
                                                    windowID: aRequest.windowID,
@@ -728,19 +734,19 @@ function prompt(aBrowser, aRequest) {
 
             let [pre, post] = string.split("<>");
             warning.textContent = pre;
             warning.appendChild(learnMore);
             warning.appendChild(chromeWin.document.createTextNode(post));
           }
 
           let perms = Services.perms;
-          let chromePrincipal = Services.scriptSecurityManager.getSystemPrincipal();
-          perms.addFromPrincipal(chromePrincipal, "MediaManagerVideo", perms.ALLOW_ACTION,
-                                 perms.EXPIRE_SESSION);
+          let chromeUri = Services.io.newURI(doc.documentURI);
+          perms.add(chromeUri, "MediaManagerVideo", perms.ALLOW_ACTION,
+                    perms.EXPIRE_SESSION);
 
           video.deviceId = deviceId;
           let constraints = { video: { mediaSource: type, deviceId: {exact: deviceId } } };
           chromeWin.navigator.mediaDevices.getUserMedia(constraints).then(stream => {
             if (video.deviceId != deviceId) {
               // The user has selected a different device or closed the panel
               // before getUserMedia finished.
               stream.getTracks().forEach(t => t.stop());
@@ -805,31 +811,31 @@ function prompt(aBrowser, aRequest) {
         if (videoDevices.length) {
           let listId = "webRTC-select" + (sharingScreen ? "Window" : "Camera") + "-menulist";
           let videoDeviceIndex = doc.getElementById(listId).value;
           let allowVideoDevice = videoDeviceIndex != "-1";
           if (allowVideoDevice) {
             allowedDevices.push(videoDeviceIndex);
             // Session permission will be removed after use
             // (it's really one-shot, not for the entire session)
-            perms.addFromPrincipal(principal, "MediaManagerVideo", perms.ALLOW_ACTION,
-                                   perms.EXPIRE_SESSION);
+            perms.add(uri, "MediaManagerVideo", perms.ALLOW_ACTION,
+                      perms.EXPIRE_SESSION);
             if (!webrtcUI.activePerms.has(aBrowser.outerWindowID)) {
               webrtcUI.activePerms.set(aBrowser.outerWindowID, new Set());
             }
 
             for (let device of videoDevices) {
               if (device.deviceIndex == videoDeviceIndex) {
                 webrtcUI.activePerms.get(aBrowser.outerWindowID)
                         .add(aRequest.windowID + device.mediaSource + device.id);
                 break;
               }
             }
             if (remember)
-              SitePermissions.setForPrincipal(principal, "camera", SitePermissions.ALLOW);
+              SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
           }
         }
         if (audioDevices.length) {
           if (!sharingAudio) {
             let audioDeviceIndex = doc.getElementById("webRTC-selectMicrophone-menulist").value;
             let allowMic = audioDeviceIndex != "-1";
             if (allowMic) {
               allowedDevices.push(audioDeviceIndex);
@@ -840,34 +846,34 @@ function prompt(aBrowser, aRequest) {
               for (let device of audioDevices) {
                 if (device.deviceIndex == audioDeviceIndex) {
                   webrtcUI.activePerms.get(aBrowser.outerWindowID)
                           .add(aRequest.windowID + device.mediaSource + device.id);
                   break;
                 }
               }
               if (remember)
-                SitePermissions.setForPrincipal(principal, "microphone", SitePermissions.ALLOW);
+                SitePermissions.set(uri, "microphone", SitePermissions.ALLOW);
             }
           } else {
             // Only one device possible for audio capture.
             allowedDevices.push(0);
           }
         }
 
         if (!allowedDevices.length) {
           denyRequest(notification.browser, aRequest);
           return;
         }
 
         if (remember) {
           // Remember on which URIs we set persistent permissions so that we
           // can remove them if the user clicks 'Stop Sharing'.
-          aBrowser._devicePermissionPrincipals = aBrowser._devicePermissionPrincipals || [];
-          aBrowser._devicePermissionPrincipals.push(principal);
+          aBrowser._devicePermissionURIs = aBrowser._devicePermissionURIs || [];
+          aBrowser._devicePermissionURIs.push(uri);
         }
 
         let camNeeded = videoDevices.length > 0;
         let micNeeded = audioDevices.length > 0;
         let havePermission = await checkOSPermission(camNeeded, micNeeded);
         if (!havePermission) {
           denyRequestNoPermission(notification.browser, aRequest);
           return;
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -245,16 +245,17 @@ html|*#webRTC-previewVideo {
 }
 
 /* INSTALL ADDONS */
 
 .install-icon {
   list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.svg);
 }
 
+.install-icon.blocked-permission-icon,
 .popup-notification-icon[popupid="xpinstall-disabled"],
 .popup-notification-icon[popupid="addon-install-blocked"],
 .popup-notification-icon[popupid="addon-install-origin-blocked"] {
   list-style-image: url(chrome://browser/skin/addons/addon-install-blocked.svg);
 }
 
 .popup-notification-icon[popupid="addon-progress"] {
   list-style-image: url(chrome://browser/skin/addons/addon-install-downloading.svg);
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -343,16 +343,20 @@
 }
 
 .urlbar-history-dropmarker {
   -moz-appearance: none;
   list-style-image: url(chrome://global/skin/icons/arrow-dropdown-16.svg);
   transition: opacity 0.15s ease;
 }
 
+#urlbar[hidedropmarker] > .urlbar-history-dropmarker {
+  display: none;
+}
+
 /* Avoid re-opening the popup when the dropmarker is clicked while the popup is still open. */
 #urlbar[quantumbar="false"] > .urlbar-history-dropmarker[open] {
   pointer-events: none;
 }
 
 #urlbar[switchingtabs] > .urlbar-history-dropmarker {
   transition: none;
 }
--- a/browser/themes/windows/syncedtabs/sidebar.css
+++ b/browser/themes/windows/syncedtabs/sidebar.css
@@ -30,17 +30,17 @@
 
 .search-box > .textbox-input-box:dir(rtl) {
   background-position: right center;
 }
 
 .textbox-search-clear {
   width: 16px;
   height: 16px;
-  background-image: url(chrome://global/skin/icons/Search-close.png);
+  background-image: url(chrome://global/skin/icons/searchfield-cancel.svg);
   background-repeat: no-repeat;
 }
 
 .textbox-search-clear:not([disabled]) {
   cursor: default;
 }
 
 .textbox-search-clear:not([disabled]):hover {
--- a/build/moz.configure/bindgen.configure
+++ b/build/moz.configure/bindgen.configure
@@ -10,17 +10,17 @@ cbindgen_is_needed = depends(build_proje
 option(env='CBINDGEN', nargs=1, when=cbindgen_is_needed,
        help='Path to cbindgen')
 
 
 @imports(_from='textwrap', _import='dedent')
 def check_cbindgen_version(cbindgen, fatal=False):
     log.debug("trying cbindgen: %s" % cbindgen)
 
-    cbindgen_min_version = Version('0.8.6')
+    cbindgen_min_version = Version('0.8.7')
 
     # cbindgen x.y.z
     version = Version(check_cmd_output(cbindgen, '--version').strip().split(" ")[1])
     log.debug("%s has version %s" % (cbindgen, version))
     if version >= cbindgen_min_version:
         return True
     if not fatal:
         return False
--- a/devtools/client/aboutdebugging-new/src/actions/runtimes.js
+++ b/devtools/client/aboutdebugging-new/src/actions/runtimes.js
@@ -12,17 +12,16 @@ const {
   getAllRuntimes,
   getCurrentRuntime,
   findRuntimeById,
 } = require("../modules/runtimes-state-helper");
 
 const { l10n } = require("../modules/l10n");
 const { createClientForRuntime } = require("../modules/runtime-client-factory");
 const {
-  isExtensionDebugSettingNeeded,
   isSupportedDebugTargetPane,
 } = require("../modules/debug-target-support");
 
 const { remoteClientManager } =
   require("devtools/client/shared/remote-debugging/remote-client-manager");
 
 const {
   CONNECT_RUNTIME_CANCEL,
@@ -40,19 +39,16 @@ const {
   RUNTIMES,
   THIS_FIREFOX_RUNTIME_CREATED,
   UNWATCH_RUNTIME_FAILURE,
   UNWATCH_RUNTIME_START,
   UNWATCH_RUNTIME_SUCCESS,
   UPDATE_CONNECTION_PROMPT_SETTING_FAILURE,
   UPDATE_CONNECTION_PROMPT_SETTING_START,
   UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS,
-  UPDATE_EXTENSION_DEBUG_SETTING_FAILURE,
-  UPDATE_EXTENSION_DEBUG_SETTING_START,
-  UPDATE_EXTENSION_DEBUG_SETTING_SUCCESS,
   UPDATE_RUNTIME_MULTIE10S_FAILURE,
   UPDATE_RUNTIME_MULTIE10S_START,
   UPDATE_RUNTIME_MULTIE10S_SUCCESS,
   WATCH_RUNTIME_FAILURE,
   WATCH_RUNTIME_START,
   WATCH_RUNTIME_SUCCESS,
 } = require("../constants");
 
@@ -119,29 +115,22 @@ function connectRuntime(id) {
       const runtime = findRuntimeById(id, getState().runtimes);
       const clientWrapper = await createClientForRuntime(runtime);
 
       const deviceDescription = await clientWrapper.getDeviceDescription();
       const compatibilityReport = await clientWrapper.checkVersionCompatibility();
       const icon = await getRuntimeIcon(runtime, deviceDescription.channel);
 
       const {
-        CHROME_DEBUG_ENABLED,
         CONNECTION_PROMPT,
         PERMANENT_PRIVATE_BROWSING,
-        REMOTE_DEBUG_ENABLED,
         SERVICE_WORKERS_ENABLED,
       } = RUNTIME_PREFERENCE;
       const connectionPromptEnabled =
         await clientWrapper.getPreference(CONNECTION_PROMPT, false);
-      const extensionDebugEnabled =
-        isExtensionDebugSettingNeeded(runtime.type)
-          ? await clientWrapper.getPreference(CHROME_DEBUG_ENABLED, true) &&
-            await clientWrapper.getPreference(REMOTE_DEBUG_ENABLED, true)
-          : true;
       const privateBrowsing =
         await clientWrapper.getPreference(PERMANENT_PRIVATE_BROWSING, false);
       const serviceWorkersEnabled =
         await clientWrapper.getPreference(SERVICE_WORKERS_ENABLED, true);
       const serviceWorkersAvailable = serviceWorkersEnabled && !privateBrowsing;
 
       // Fenix specific workarounds are needed until we can get proper server side APIs
       // to detect Fenix and get the proper application names and versions.
@@ -155,17 +144,16 @@ function connectRuntime(id) {
       // retrieved from ADB, and not the Gecko version returned by the Device actor.
       const version = runtime.isFenix ?
         runtime.extra.adbPackageVersion : deviceDescription.version;
 
       const runtimeDetails = {
         clientWrapper,
         compatibilityReport,
         connectionPromptEnabled,
-        extensionDebugEnabled,
         info: {
           deviceName: deviceDescription.deviceName,
           icon,
           name: runtimeName,
           os: deviceDescription.os,
           type: runtime.type,
           version,
         },
@@ -267,42 +255,16 @@ function updateConnectionPromptSetting(c
       dispatch({ type: UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS,
                  runtime, connectionPromptEnabled });
     } catch (e) {
       dispatch({ type: UPDATE_CONNECTION_PROMPT_SETTING_FAILURE, error: e });
     }
   };
 }
 
-function updateExtensionDebugSetting(extensionDebugEnabled) {
-  return async (dispatch, getState) => {
-    dispatch({ type: UPDATE_EXTENSION_DEBUG_SETTING_START });
-    try {
-      const runtime = getCurrentRuntime(getState().runtimes);
-      const { clientWrapper } = runtime.runtimeDetails;
-
-      const { CHROME_DEBUG_ENABLED, REMOTE_DEBUG_ENABLED } = RUNTIME_PREFERENCE;
-      await clientWrapper.setPreference(CHROME_DEBUG_ENABLED, extensionDebugEnabled);
-      await clientWrapper.setPreference(REMOTE_DEBUG_ENABLED, extensionDebugEnabled);
-
-      // Re-get actual value from the runtime.
-      const isChromeDebugEnabled =
-        await clientWrapper.getPreference(CHROME_DEBUG_ENABLED, extensionDebugEnabled);
-      const isRemoveDebugEnabled =
-        await clientWrapper.getPreference(REMOTE_DEBUG_ENABLED, extensionDebugEnabled);
-      extensionDebugEnabled = isChromeDebugEnabled && isRemoveDebugEnabled;
-
-      dispatch({ type: UPDATE_EXTENSION_DEBUG_SETTING_SUCCESS,
-                 runtime, extensionDebugEnabled });
-    } catch (e) {
-      dispatch({ type: UPDATE_EXTENSION_DEBUG_SETTING_FAILURE, error: e });
-    }
-  };
-}
-
 function updateMultiE10s() {
   return async (dispatch, getState) => {
     dispatch({ type: UPDATE_RUNTIME_MULTIE10S_START });
     try {
       const runtime = getCurrentRuntime(getState().runtimes);
       const { clientWrapper } = runtime.runtimeDetails;
       // Re-get actual value from the runtime.
       const { isMultiE10s } = await clientWrapper.getDeviceDescription();
@@ -515,13 +477,12 @@ function removeRuntimeListeners() {
 
 module.exports = {
   connectRuntime,
   createThisFirefoxRuntime,
   disconnectRuntime,
   removeRuntimeListeners,
   unwatchRuntime,
   updateConnectionPromptSetting,
-  updateExtensionDebugSetting,
   updateNetworkRuntimes,
   updateUSBRuntimes,
   watchRuntime,
 };
--- a/devtools/client/aboutdebugging-new/src/base.css
+++ b/devtools/client/aboutdebugging-new/src/base.css
@@ -154,21 +154,16 @@ p, h1 {
   white-space: nowrap;
 }
 
 /* Technical text that should use a monospace font, such as code, error messages. */
 .technical-text {
   font-family: var(--monospace-font-family);
 }
 
-/* Text that is not selectable */
-.unselectable-text {
-  -moz-user-select: none;
-}
-
 /* Links that need to look like current text */
 .undecorated-link,
 .undecorated-link:hover {
   text-decoration: none;
   color: currentColor;
 }
 
 /* Text needs to wrap anywhere */
@@ -372,23 +367,16 @@ Form controls
   height: 100%;
 
   border: 1px solid var(--box-border-color);
   border-radius: 2px;
   color: var(--text-color);
   background-color: var(--box-background);
 }
 
-/* Standard checkbox, from Photon */
-.default-checkbox {
-  height: calc(var(--base-unit) * 4);
-  margin-inline-end: var(--base-unit);
-  width: calc(var(--base-unit) * 4);
-}
-
 /*
 * Other UI components
 */
 
 /*
 * A small, colored badge.
 * NOTE: styles borrowed from Photon's micro buttons (there aren't badges)
 */
deleted file mode 100644
--- a/devtools/client/aboutdebugging-new/src/components/ExtensionDebugSetting.js
+++ /dev/null
@@ -1,77 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
-const dom = require("devtools/client/shared/vendor/react-dom-factories");
-const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
-
-const FluentReact = require("devtools/client/shared/vendor/fluent-react");
-const Localized = createFactory(FluentReact.Localized);
-
-const Actions = require("../actions/index");
-
-const DOC_URL =
-  "https://developer.mozilla.org/docs/Tools/about:debugging#Enabling_add-on_debugging";
-
-class ExtensionDebugSetting extends PureComponent {
-  static get propTypes() {
-    return {
-      className: PropTypes.string,
-      dispatch: PropTypes.func.isRequired,
-      extensionDebugEnabled: PropTypes.bool.isRequired,
-    };
-  }
-
-  onToggle() {
-    const { extensionDebugEnabled, dispatch } = this.props;
-    dispatch(Actions.updateExtensionDebugSetting(!extensionDebugEnabled));
-  }
-
-  renderCheckbox() {
-    const { extensionDebugEnabled } = this.props;
-
-    return dom.input(
-      {
-        type: "checkbox",
-        id: "extension-debug-setting-input",
-        className: "default-checkbox  qa-extension-debug-checkbox",
-        checked: extensionDebugEnabled,
-        onChange: () => this.onToggle(),
-      }
-    );
-  }
-
-  renderLabel() {
-    return Localized(
-      {
-        id: "about-debugging-extension-debug-setting-label",
-        a: dom.a({
-          href: DOC_URL,
-          target: "_blank",
-        }),
-      },
-      dom.label(
-        {
-          className: "unselectable-text",
-          htmlFor: "extension-debug-setting-input",
-        },
-        "Enable extension debugging [Learn more]"
-      )
-    );
-  }
-
-  render() {
-    const { className } = this.props;
-
-    return dom.aside(
-      { className },
-      this.renderCheckbox(),
-      this.renderLabel(),
-    );
-  }
-}
-
-module.exports = ExtensionDebugSetting;
--- a/devtools/client/aboutdebugging-new/src/components/RuntimeActions.js
+++ b/devtools/client/aboutdebugging-new/src/components/RuntimeActions.js
@@ -7,24 +7,21 @@
 const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 const FluentReact = require("devtools/client/shared/vendor/fluent-react");
 const Localized = createFactory(FluentReact.Localized);
 
 const ConnectionPromptSetting = createFactory(require("./ConnectionPromptSetting"));
-const ExtensionDebugSetting = createFactory(require("./ExtensionDebugSetting"));
 
 const Actions = require("../actions/index");
 const { RUNTIMES } = require("../constants");
 const Types = require("../types/index");
 
-const { isExtensionDebugSettingNeeded } = require("../modules/debug-target-support");
-
 class RuntimeActions extends PureComponent {
   static get propTypes() {
     return {
       dispatch: PropTypes.func.isRequired,
       runtimeDetails: Types.runtimeDetails,
       runtimeId: PropTypes.string.isRequired,
     };
   }
@@ -40,27 +37,16 @@ class RuntimeActions extends PureCompone
     return runtimeId !== RUNTIMES.THIS_FIREFOX
              ? ConnectionPromptSetting({
                  connectionPromptEnabled,
                  dispatch,
              })
              : null;
   }
 
-  renderExtensionDebugSetting() {
-    const { dispatch, runtimeDetails } = this.props;
-    const { extensionDebugEnabled, info } = runtimeDetails;
-    return isExtensionDebugSettingNeeded(info.type)
-             ? ExtensionDebugSetting({
-                 dispatch,
-                 extensionDebugEnabled,
-             })
-             : null;
-  }
-
   renderProfileButton() {
     const { runtimeId } = this.props;
 
     return runtimeId !== RUNTIMES.THIS_FIREFOX
          ? Localized(
            {
              id: "about-debugging-runtime-profile-button2",
            },
@@ -72,22 +58,18 @@ class RuntimeActions extends PureCompone
              "about-debugging-runtime-profile-button2"
            ),
          )
          : null;
   }
 
   render() {
     return dom.div(
-      {},
-      dom.div(
-        {
-          className: "runtime-actions__toolbar",
-        },
-        this.renderProfileButton(),
-        this.renderConnectionPromptSetting(),
-      ),
-      this.renderExtensionDebugSetting(),
+      {
+        className: "runtime-actions__toolbar",
+      },
+      this.renderProfileButton(),
+      this.renderConnectionPromptSetting(),
     );
   }
 }
 
 module.exports = RuntimeActions;
--- a/devtools/client/aboutdebugging-new/src/components/RuntimePage.js
+++ b/devtools/client/aboutdebugging-new/src/components/RuntimePage.js
@@ -9,17 +9,16 @@ const { createFactory, PureComponent } =
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 const FluentReact = require("devtools/client/shared/vendor/fluent-react");
 const Localized = createFactory(FluentReact.Localized);
 
 const CompatibilityWarning = createFactory(require("./CompatibilityWarning"));
 const DebugTargetPane = createFactory(require("./debugtarget/DebugTargetPane"));
-const ExtensionAction = createFactory(require("./debugtarget/ExtensionAction"));
 const ExtensionDetail = createFactory(require("./debugtarget/ExtensionDetail"));
 const InspectAction = createFactory(require("./debugtarget/InspectAction"));
 const ProfilerDialog = createFactory(require("./ProfilerDialog"));
 const RuntimeActions = createFactory(require("./RuntimeActions"));
 const RuntimeInfo = createFactory(require("./RuntimeInfo"));
 const ServiceWorkerAction = createFactory(require("./debugtarget/ServiceWorkerAction"));
 const ServiceWorkerAdditionalActions =
   createFactory(require("./debugtarget/ServiceWorkerAdditionalActions"));
@@ -161,26 +160,26 @@ class RuntimePage extends PureComponent 
                                  null,
                                  TabDetail,
                                  DEBUG_TARGET_PANE.TAB,
                                  "about-debugging-runtime-tabs"),
       this.renderDebugTargetPane("Temporary Extensions",
                                  this.getIconByType(DEBUG_TARGETS.EXTENSION),
                                  temporaryExtensions,
                                  this.renderTemporaryExtensionInstallSection(),
-                                 ExtensionAction,
+                                 InspectAction,
                                  TemporaryExtensionAdditionalActions,
                                  TemporaryExtensionDetail,
                                  DEBUG_TARGET_PANE.TEMPORARY_EXTENSION,
                                  "about-debugging-runtime-temporary-extensions"),
       this.renderDebugTargetPane("Extensions",
                                  this.getIconByType(DEBUG_TARGETS.EXTENSION),
                                  installedExtensions,
                                  null,
-                                 ExtensionAction,
+                                 InspectAction,
                                  null,
                                  ExtensionDetail,
                                  DEBUG_TARGET_PANE.INSTALLED_EXTENSION,
                                  "about-debugging-runtime-extensions"),
       this.renderDebugTargetPane("Service Workers",
                                  this.getIconByType(DEBUG_TARGETS.WORKER),
                                  serviceWorkers,
                                  null,
deleted file mode 100644
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionAction.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const { connect } = require("devtools/client/shared/vendor/react-redux");
-const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
-const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
-
-const InspectAction = createFactory(require("./InspectAction"));
-
-const Types = require("../../types/index");
-
-const { getCurrentRuntimeDetails } = require("../../modules/runtimes-state-helper");
-
-/**
- * This component provides components that inspect extension.
- */
-class ExtensionAction extends PureComponent {
-  static get propTypes() {
-    return {
-      dispatch: PropTypes.func.isRequired,
-      runtimeDetails: Types.runtimeDetails.isRequired,
-      target: Types.debugTarget.isRequired,
-    };
-  }
-
-  render() {
-    const { dispatch, runtimeDetails, target } = this.props;
-    const { extensionDebugEnabled } = runtimeDetails;
-    return InspectAction({
-      disabled: !extensionDebugEnabled,
-      dispatch,
-      target,
-    });
-  }
-}
-
-const mapStateToProps = state => {
-  return {
-    runtimeDetails: getCurrentRuntimeDetails(state.runtimes),
-  };
-};
-module.exports = connect(mapStateToProps)(ExtensionAction);
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/moz.build
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/moz.build
@@ -4,17 +4,16 @@
 
 DevToolsModules(
     'DebugTargetItem.css',
     'DebugTargetItem.js',
     'DebugTargetList.css',
     'DebugTargetList.js',
     'DebugTargetPane.css',
     'DebugTargetPane.js',
-    'ExtensionAction.js',
     'ExtensionDetail.js',
     'FieldPair.css',
     'FieldPair.js',
     'InspectAction.js',
     'ProcessDetail.js',
     'ServiceWorkerAction.css',
     'ServiceWorkerAction.js',
     'ServiceWorkerAdditionalActions.js',
--- a/devtools/client/aboutdebugging-new/src/components/moz.build
+++ b/devtools/client/aboutdebugging-new/src/components/moz.build
@@ -9,17 +9,16 @@ DIRS += [
     'sidebar',
 ]
 
 DevToolsModules(
     'App.css',
     'App.js',
     'CompatibilityWarning.js',
     'ConnectionPromptSetting.js',
-    'ExtensionDebugSetting.js',
     'ProfilerDialog.css',
     'ProfilerDialog.js',
     'RuntimeActions.css',
     'RuntimeActions.js',
     'RuntimeInfo.css',
     'RuntimeInfo.js',
     'RuntimePage.js',
     'ServiceWorkersWarning.js',
--- a/devtools/client/aboutdebugging-new/src/constants.js
+++ b/devtools/client/aboutdebugging-new/src/constants.js
@@ -56,19 +56,16 @@ const actionTypes = {
   TEMPORARY_EXTENSION_RELOAD_SUCCESS: "TEMPORARY_EXTENSION_RELOAD_SUCCESS",
   THIS_FIREFOX_RUNTIME_CREATED: "THIS_FIREFOX_RUNTIME_CREATED",
   UNWATCH_RUNTIME_FAILURE: "UNWATCH_RUNTIME_FAILURE",
   UNWATCH_RUNTIME_START: "UNWATCH_RUNTIME_START",
   UNWATCH_RUNTIME_SUCCESS: "UNWATCH_RUNTIME_SUCCESS",
   UPDATE_CONNECTION_PROMPT_SETTING_FAILURE: "UPDATE_CONNECTION_PROMPT_SETTING_FAILURE",
   UPDATE_CONNECTION_PROMPT_SETTING_START: "UPDATE_CONNECTION_PROMPT_SETTING_START",
   UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS: "UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS",
-  UPDATE_EXTENSION_DEBUG_SETTING_FAILURE: "UPDATE_EXTENSION_DEBUG_SETTING_FAILURE",
-  UPDATE_EXTENSION_DEBUG_SETTING_START: "UPDATE_EXTENSION_DEBUG_SETTING_START",
-  UPDATE_EXTENSION_DEBUG_SETTING_SUCCESS: "UPDATE_EXTENSION_DEBUG_SETTING_SUCCESS",
   UPDATE_RUNTIME_MULTIE10S_FAILURE: "UPDATE_RUNTIME_MULTIE10S_FAILURE",
   UPDATE_RUNTIME_MULTIE10S_START: "UPDATE_RUNTIME_MULTIE10S_START",
   UPDATE_RUNTIME_MULTIE10S_SUCCESS: "UPDATE_RUNTIME_MULTIE10S_SUCCESS",
   USB_RUNTIMES_SCAN_START: "USB_RUNTIMES_SCAN_START",
   USB_RUNTIMES_SCAN_SUCCESS: "USB_RUNTIMES_SCAN_SUCCESS",
   WATCH_RUNTIME_FAILURE: "WATCH_RUNTIME_FAILURE",
   WATCH_RUNTIME_START: "WATCH_RUNTIME_START",
   WATCH_RUNTIME_SUCCESS: "WATCH_RUNTIME_SUCCESS",
@@ -111,20 +108,18 @@ const PREFERENCES = {
   SHOW_HIDDEN_ADDONS: "devtools.aboutdebugging.showHiddenAddons",
   // Preference to store the last path used for loading a temporary extension.
   TEMPORARY_EXTENSION_PATH: "devtools.aboutdebugging.tmpExtDirPath",
   // Preference that disables installing extensions when set to false.
   XPINSTALL_ENABLED: "xpinstall.enabled",
 };
 
 const RUNTIME_PREFERENCE = {
-  CHROME_DEBUG_ENABLED: "devtools.chrome.enabled",
   CONNECTION_PROMPT: "devtools.debugger.prompt-connection",
   PERMANENT_PRIVATE_BROWSING: "browser.privatebrowsing.autostart",
-  REMOTE_DEBUG_ENABLED: "devtools.debugger.remote-enabled",
   SERVICE_WORKERS_ENABLED: "dom.serviceWorkers.enabled",
 };
 
 const RUNTIMES = {
   NETWORK: CONNECTION_TYPES.NETWORK,
   THIS_FIREFOX: CONNECTION_TYPES.THIS_FIREFOX,
   USB: CONNECTION_TYPES.USB,
 };
--- a/devtools/client/aboutdebugging-new/src/modules/debug-target-support.js
+++ b/devtools/client/aboutdebugging-new/src/modules/debug-target-support.js
@@ -50,28 +50,15 @@ const THIS_FIREFOX_DEBUG_TARGET_PANES = 
 
 const SUPPORTED_TARGET_PANE_BY_RUNTIME = {
   [RUNTIMES.THIS_FIREFOX]: THIS_FIREFOX_DEBUG_TARGET_PANES,
   [RUNTIMES.USB]: REMOTE_DEBUG_TARGET_PANES,
   [RUNTIMES.NETWORK]: REMOTE_DEBUG_TARGET_PANES,
 };
 
 /**
- * If extension debug setting is needed for given runtime type, return true.
- *
- * @param {String} runtimeType
- * @return {bool} true: needed
- */
-function isExtensionDebugSettingNeeded(runtimeType) {
-  // Debugging local addons for This Firefox reuses the Browser Toolbox, which requires
-  // some preferences to be enabled.
-  return runtimeType === RUNTIMES.THIS_FIREFOX;
-}
-exports.isExtensionDebugSettingNeeded = isExtensionDebugSettingNeeded;
-
-/**
  * A debug target pane is more specialized than a debug target. For instance EXTENSION is
  * a DEBUG_TARGET but INSTALLED_EXTENSION and TEMPORARY_EXTENSION are DEBUG_TARGET_PANES.
  */
 function isSupportedDebugTargetPane(runtimeType, debugTargetPaneKey) {
   return SUPPORTED_TARGET_PANE_BY_RUNTIME[runtimeType].includes(debugTargetPaneKey);
 }
 exports.isSupportedDebugTargetPane = isSupportedDebugTargetPane;
--- a/devtools/client/aboutdebugging-new/src/reducers/runtimes-state.js
+++ b/devtools/client/aboutdebugging-new/src/reducers/runtimes-state.js
@@ -8,17 +8,16 @@ const {
   CONNECT_RUNTIME_CANCEL,
   CONNECT_RUNTIME_FAILURE,
   CONNECT_RUNTIME_NOT_RESPONDING,
   CONNECT_RUNTIME_START,
   CONNECT_RUNTIME_SUCCESS,
   DISCONNECT_RUNTIME_SUCCESS,
   RUNTIMES,
   UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS,
-  UPDATE_EXTENSION_DEBUG_SETTING_SUCCESS,
   UPDATE_RUNTIME_MULTIE10S_SUCCESS,
   REMOTE_RUNTIMES_UPDATED,
   SELECTED_RUNTIME_ID_UPDATED,
   THIS_FIREFOX_RUNTIME_CREATED,
 } = require("../constants");
 
 const {
   findRuntimeById,
@@ -147,25 +146,16 @@ function runtimesReducer(state = Runtime
       const { connectionPromptEnabled } = action;
       const { id: runtimeId } = action.runtime;
       const runtime = findRuntimeById(runtimeId, state);
       const runtimeDetails =
         Object.assign({}, runtime.runtimeDetails, { connectionPromptEnabled });
       return _updateRuntimeById(runtimeId, { runtimeDetails }, state);
     }
 
-    case UPDATE_EXTENSION_DEBUG_SETTING_SUCCESS: {
-      const { extensionDebugEnabled } = action;
-      const { id: runtimeId } = action.runtime;
-      const runtime = findRuntimeById(runtimeId, state);
-      const runtimeDetails =
-        Object.assign({}, runtime.runtimeDetails, { extensionDebugEnabled });
-      return _updateRuntimeById(runtimeId, { runtimeDetails }, state);
-    }
-
     case UPDATE_RUNTIME_MULTIE10S_SUCCESS: {
       const { isMultiE10s } = action;
       const { id: runtimeId } = action.runtime;
       const runtime = findRuntimeById(runtimeId, state);
       const runtimeDetails =
         Object.assign({}, runtime.runtimeDetails, { isMultiE10s });
       return _updateRuntimeById(runtimeId, { runtimeDetails }, state);
     }
--- a/devtools/client/aboutdebugging-new/src/types/runtime.js
+++ b/devtools/client/aboutdebugging-new/src/types/runtime.js
@@ -56,20 +56,16 @@ const runtimeDetails = {
 
   // compatibility report to check if the target runtime is in range of the backward
   // compatibility policy for DevTools remote debugging.
   compatibilityReport: PropTypes.shape(compatibilityReport).isRequired,
 
   // reflect devtools.debugger.prompt-connection preference of this runtime
   connectionPromptEnabled: PropTypes.bool.isRequired,
 
-  // In case that runtime is this-firefox, reflects devtools.chrome.enabled and
-  // devtools.debugger.remote-enabled preference. Otherwise, this sould be true.
-  extensionDebugEnabled: PropTypes.bool.isRequired,
-
   // runtime information
   info: PropTypes.shape(runtimeInfo).isRequired,
 
   // True if this runtime supports multiple content processes
   // This might be undefined when connecting to runtimes older than Fx 66
   isMultiE10s: PropTypes.bool,
 
   // True if service workers should be available in the target runtime. Service workers
--- a/devtools/client/aboutdebugging-new/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging-new/test/browser/browser.ini
@@ -22,25 +22,25 @@ support-files =
   resources/test-temporary-extension/*
   test-tab-favicons.html
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/shared-redux-head.js
   !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_aboutdebugging_addons_debug_console.js]
 tags = webextensions
+[browser_aboutdebugging_addons_debug_debugger.js]
+tags = webextensions
 [browser_aboutdebugging_addons_debug_inspector.js]
 tags = webextensions
 [browser_aboutdebugging_addons_debug_nobg.js]
 tags = webextensions
 [browser_aboutdebugging_addons_debug_popup.js]
 skip-if = (verify && debug) || (debug && os == "linux" && bits == 64) # verify: crashes on shutdown, timeouts linux debug Bug 1299001
 tags = webextensions
-[browser_aboutdebugging_addons_debug_setting_thisfirefox.js]
-[browser_aboutdebugging_addons_debug_setting_usb.js]
 [browser_aboutdebugging_addons_manifest_url.js]
 skip-if = (os == 'linux' && bits == 32) # ADB start() fails on linux 32, see Bug 1499638
 [browser_aboutdebugging_addons_remote_runtime.js]
 [browser_aboutdebugging_addons_temporary_addon_buttons.js]
 skip-if = (os == 'win') # On windows the AddonManager locks the XPI file loaded as a temporary extension and we can not test the reload of the extension.
 [browser_aboutdebugging_addons_temporary_id_message.js]
 [browser_aboutdebugging_addons_temporary_install_error.js]
 [browser_aboutdebugging_addons_temporary_install_path.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_debugger.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/* import-globals-from helper-addons.js */
+Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-addons.js", this);
+
+add_task(async () => {
+  const EXTENSION_NAME = "temporary-web-extension";
+  const EXTENSION_ID = "test-devtools@mozilla.org";
+
+  await enableExtensionDebugging();
+
+  info("The debugger should show the source codes of extension even if " +
+       "devtools.chrome.enabled and devtools.debugger.remote-enabled are off");
+  await pushPref("devtools.chrome.enabled", false);
+  await pushPref("devtools.debugger.remote-enabled", false);
+
+  const { document, tab, window } = await openAboutDebugging();
+  await selectThisFirefoxPage(document, window.AboutDebugging.store);
+
+  await installTemporaryExtensionFromXPI({
+    background: function() {
+      window.someRandomMethodName = () => {
+        // This will not be referred from anywhere.
+        // However this is necessary to show as the source code in the debugger.
+      };
+    },
+    id: EXTENSION_ID,
+    name: EXTENSION_NAME,
+  }, document);
+
+  const { devtoolsTab, devtoolsWindow } =
+    await openAboutDevtoolsToolbox(document, tab, window, EXTENSION_NAME);
+  const toolbox = getToolbox(devtoolsWindow);
+  await toolbox.selectTool("jsdebugger");
+  const { panelWin } = toolbox.getCurrentPanel();
+
+  info("Check the state of redux");
+  ok(panelWin.dbg.store.getState().debuggee.isWebExtension,
+     "isWebExtension flag in debuggee is true");
+
+  info("Check whether the element displays correctly");
+  const sourceList = panelWin.document.querySelector(".sources-list");
+  ok(sourceList, "Source list element displays correctly");
+  ok(sourceList.textContent.includes("moz-extension"),
+     "moz-extension displays correctly");
+
+  await closeAboutDevtoolsToolbox(document, devtoolsTab, window);
+  await removeTemporaryExtension(EXTENSION_NAME, document);
+  await removeTab(tab);
+});
deleted file mode 100644
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_setting_thisfirefox.js
+++ /dev/null
@@ -1,110 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-/* import-globals-from helper-addons.js */
-Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-addons.js", this);
-/* import-globals-from helper-collapsibilities.js */
-Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-collapsibilities.js", this);
-
-const TEMPORARY_EXTENSION_ID = "test-devtools-webextension@mozilla.org";
-const TEMPORARY_EXTENSION_NAME = "test-devtools-webextension";
-const REGULAR_EXTENSION_ID = "packaged-extension@tests";
-const REGULAR_EXTENSION_NAME = "Packaged extension";
-
-const TEST_DATA = [
-  {
-    chromeEnabled: false,
-    debuggerRemoteEnable: false,
-    isEnabledAtInitial: false,
-  }, {
-    chromeEnabled: false,
-    debuggerRemoteEnable: true,
-    isEnabledAtInitial: false,
-  }, {
-    chromeEnabled: true,
-    debuggerRemoteEnable: false,
-    isEnabledAtInitial: false,
-  }, {
-    chromeEnabled: true,
-    debuggerRemoteEnable: true,
-    isEnabledAtInitial: true,
-  },
-];
-
-/**
- * Check the state of extension debug setting checkbox, inspect buttons and prefs on
- * this-firefox at initializing and after toggling.
- */
-add_task(async function() {
-  info("Force all debug target panes to be expanded");
-  prepareCollapsibilitiesTest();
-
-  for (const testData of TEST_DATA) {
-    await testState(testData);
-  }
-});
-
-async function testState({ chromeEnabled, debuggerRemoteEnable, isEnabledAtInitial }) {
-  info(`Test for chromeEnabled: ${ chromeEnabled }, ` +
-       `debuggerRemoteEnable:${ debuggerRemoteEnable }`);
-
-  info("Set initial state for test");
-  await pushPref("devtools.chrome.enabled", chromeEnabled);
-  await pushPref("devtools.debugger.remote-enabled", debuggerRemoteEnable);
-
-  const { document, tab, window } = await openAboutDebugging();
-  await selectThisFirefoxPage(document, window.AboutDebugging.store);
-  await installExtensions(document);
-
-  info("Check the status of extension debug setting checkbox and inspect buttons");
-  const checkbox = document.querySelector(".qa-extension-debug-checkbox");
-  ok(checkbox, "Extension debug setting checkbox exists");
-  is(checkbox.checked, isEnabledAtInitial, "The state of checkbox is correct");
-  assertInspectButtons(isEnabledAtInitial, document);
-
-  info("Check the status after toggling checkbox");
-  checkbox.click();
-  await waitUntil(() => checkbox.checked !== isEnabledAtInitial);
-  ok(true, "The state of checkbox is changed correctly after toggling");
-  assertPreferences(!isEnabledAtInitial);
-  assertInspectButtons(!isEnabledAtInitial, document);
-
-  await uninstallExtensions(document);
-  await removeTab(tab);
-}
-
-function assertPreferences(expected) {
-  for (const pref of ["devtools.chrome.enabled", "devtools.debugger.remote-enabled"]) {
-    is(Services.prefs.getBoolPref(pref), expected, `${ pref } should be ${expected}`);
-  }
-}
-
-function assertInspectButtons(shouldBeEnabled, document) {
-  assertInspectButtonsOnCategory(shouldBeEnabled, "Temporary Extensions", document);
-  assertInspectButtonsOnCategory(shouldBeEnabled, "Extensions", document);
-  // Inspect button on Tabs category should be always enabled.
-  assertInspectButtonsOnCategory(true, "Tabs", document);
-}
-
-function assertInspectButtonsOnCategory(shouldBeEnabled, category, document) {
-  const pane = getDebugTargetPane(category, document);
-  const buttons = pane.querySelectorAll(".qa-debug-target-inspect-button");
-  ok([...buttons].every(b => b.disabled !== shouldBeEnabled),
-     `disabled attribute should be ${ !shouldBeEnabled } on ${ category }`);
-}
-
-async function installExtensions(document) {
-  await installTemporaryExtensionFromXPI({
-    id: TEMPORARY_EXTENSION_ID,
-    name: TEMPORARY_EXTENSION_NAME,
-  }, document);
-
-  await installRegularExtension("resources/packaged-extension/packaged-extension.xpi");
-}
-
-async function uninstallExtensions(document) {
-  await removeTemporaryExtension(TEMPORARY_EXTENSION_NAME, document);
-  await removeExtension(REGULAR_EXTENSION_ID, REGULAR_EXTENSION_NAME, document);
-}
deleted file mode 100644
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_setting_usb.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-/* import-globals-from helper-collapsibilities.js */
-Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-collapsibilities.js", this);
-
-/**
- * Check extension debug setting on USB runtime.
- */
-add_task(async function() {
-  info("Force all debug target panes to be expanded");
-  prepareCollapsibilitiesTest();
-
-  info("Set initial state for test");
-  await pushPref("devtools.debugger.remote-enabled", false);
-
-  // Setup USB devices mocks
-  const mocks = new Mocks();
-  const usbRuntime = mocks.createUSBRuntime("1337id", {
-    deviceName: "Fancy Phone",
-    name: "Lorem ipsum",
-  });
-  const extension = { name: "Test extension name", debuggable: true };
-  usbRuntime.listAddons = () => [extension];
-
-  const { document, tab, window } = await openAboutDebugging();
-  await selectThisFirefoxPage(document, window.AboutDebugging.store);
-  mocks.emitUSBUpdate();
-
-  info("Check the status of extension debug setting checkbox and inspect buttons");
-  await connectToRuntime("Fancy Phone", document);
-  await selectRuntime("Fancy Phone", "Lorem ipsum", document);
-  ok(!document.querySelector(".qa-extension-debug-checkbox"),
-     "Extension debug setting checkbox should not exist");
-  const buttons = document.querySelectorAll(".qa-debug-target-inspect-button");
-  ok([...buttons].every(b => !b.disabled), "All inspect buttons should be enabled");
-
-  await removeTab(tab);
-});
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_devtoolstoolbox_tooltip_markupview.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_devtoolstoolbox_tooltip_markupview.js
@@ -8,16 +8,19 @@ Services.scriptloader.loadSubScript(CHRO
 
 /**
  * Test tooltip of markup view on about:devtools-toolbox page.
  */
 add_task(async function() {
   info("Force all debug target panes to be expanded");
   prepareCollapsibilitiesTest();
 
+  info("Turn on devtools.chrome.enabled to show event badges");
+  await pushPref("devtools.chrome.enabled", true);
+
   const { document, tab, window } = await openAboutDebugging();
   await selectThisFirefoxPage(document, window.AboutDebugging.store);
   const { devtoolsDocument, devtoolsTab, devtoolsWindow } =
     await openAboutDevtoolsToolbox(document, tab, window);
 
   info("Select inspector tool");
   const toolbox = getToolbox(devtoolsWindow);
   await toolbox.selectTool("inspector");
--- a/devtools/client/aboutdebugging-new/test/browser/helper-addons.js
+++ b/devtools/client/aboutdebugging-new/test/browser/helper-addons.js
@@ -9,19 +9,16 @@ function _getSupportsFile(path) {
   const cr = Cc["@mozilla.org/chrome/chrome-registry;1"]
     .getService(Ci.nsIChromeRegistry);
   const uri = Services.io.newURI(CHROME_URL_ROOT + path);
   const fileurl = cr.convertChromeURL(uri);
   return fileurl.QueryInterface(Ci.nsIFileURL);
 }
 
 async function enableExtensionDebugging() {
-  // Force enabling of addons debugging
-  await pushPref("devtools.chrome.enabled", true);
-  await pushPref("devtools.debugger.remote-enabled", true);
   // Disable security prompt
   await pushPref("devtools.debugger.prompt-connection", false);
   // Enable Browser toolbox test script execution via env variable
   await pushPref("devtools.browser-toolbox.allow-unsafe-script", true);
 }
 /* exported enableExtensionDebugging */
 
 /**
--- a/devtools/client/accessibility/accessibility.css
+++ b/devtools/client/accessibility/accessibility.css
@@ -171,16 +171,24 @@ body {
   display: flex;
   flex-wrap: nowrap;
   flex-direction: row;
   align-items: center;
   white-space: nowrap;
   margin-inline-end: 5px;
 }
 
+/* @remove after release 68 (See Bug 1551574) */
+.devtools-toolbar .beta {
+  color: var(--theme-highlight-blue);
+  font-size: 80%;
+  font-weight: 500;
+  margin-inline-end: 3px;
+}
+
 #audit-progress-container {
   position: fixed;
   display: flex;
   flex-direction: column;
   align-items: center;
   width: 100%;
   height: 100%;
   z-index: 9999;
--- a/devtools/client/accessibility/components/AccessibilityTreeFilter.js
+++ b/devtools/client/accessibility/components/AccessibilityTreeFilter.js
@@ -2,17 +2,17 @@
  * 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";
 
 /* global gTelemetry */
 
 // React
 const { createFactory, Component } = require("devtools/client/shared/vendor/react");
-const { div } = require("devtools/client/shared/vendor/react-dom-factories");
+const { div, span } = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { L10N } = require("../utils/l10n");
 const ToggleButton = createFactory(require("./Button").ToggleButton);
 
 const actions = require("../actions/audit");
 
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { FILTERS } = require("../constants");
@@ -24,16 +24,17 @@ const FILTER_LABELS = {
 
 class AccessibilityTreeFilter extends Component {
   static get propTypes() {
     return {
       auditing: PropTypes.string.isRequired,
       filters: PropTypes.object.isRequired,
       dispatch: PropTypes.func.isRequired,
       walker: PropTypes.object.isRequired,
+      describedby: PropTypes.string,
     };
   }
 
   async toggleFilter(filterKey) {
     const { dispatch, filters, walker } = this.props;
 
     if (!filters[filterKey]) {
       if (gTelemetry) {
@@ -67,33 +68,37 @@ class AccessibilityTreeFilter extends Co
     if (![" ", "Enter"].includes(e.key)) {
       return;
     }
 
     this.toggleFilter(filterKey);
   }
 
   render() {
-    const { auditing, filters } = this.props;
+    const { auditing, filters, describedby } = this.props;
+    const toolbarLabelID = "accessibility-tree-filters-label";
     const filterButtons = Object.entries(filters).map(([filterKey, active]) =>
       ToggleButton({
         className: "badge",
         key: filterKey,
         active,
         label: L10N.getStr(FILTER_LABELS[filterKey]),
         onClick: this.onClick.bind(this, filterKey),
         onKeyDown: this.onKeyDown.bind(this, filterKey),
         busy: auditing === filterKey,
       }));
 
     return div({
       role: "toolbar",
       className: "accessibility-tree-filters",
+      "aria-labelledby": toolbarLabelID,
+      "aria-describedby": describedby,
     },
-      L10N.getStr("accessibility.tree.filters"),
+      span({ id: toolbarLabelID, role: "presentation" },
+        L10N.getStr("accessibility.tree.filters")),
       ...filterButtons);
   }
 }
 
 const mapStateToProps = ({ audit: { filters, auditing } }) => {
   return { filters, auditing };
 };
 
--- a/devtools/client/accessibility/components/Toolbar.js
+++ b/devtools/client/accessibility/components/Toolbar.js
@@ -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";
 
 // React
 const { createFactory, Component } = require("devtools/client/shared/vendor/react");
-const { div } = require("devtools/client/shared/vendor/react-dom-factories");
+const { div, span } = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { L10N } = require("../utils/l10n");
 const Button = createFactory(require("./Button").Button);
 const AccessibilityTreeFilter = createFactory(require("./AccessibilityTreeFilter"));
 
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { disable, updateCanBeDisabled } = require("../actions/ui");
 
@@ -66,16 +66,17 @@ class Toolbar extends Component {
       "?utm_source=devtools&utm_medium=a11y-panel-toolbar");
   }
 
   render() {
     const { canBeDisabled, walker } = this.props;
     const { disabling } = this.state;
     const disableButtonStr = disabling ?
       "accessibility.disabling" : "accessibility.disable";
+    const betaID = "beta";
     let title;
     let isDisabled = false;
 
     if (canBeDisabled) {
       title = L10N.getStr("accessibility.disable.enabledTitle");
     } else {
       isDisabled = true;
       title = L10N.getStr("accessibility.disable.disabledTitle");
@@ -92,17 +93,24 @@ class Toolbar extends Component {
         disabled: disabling || isDisabled,
         busy: disabling,
         title,
       }, L10N.getStr(disableButtonStr)),
         div({
           role: "separator",
           className: "devtools-separator",
         }),
-        AccessibilityTreeFilter({ walker }),
+        // @remove after release 68 (See Bug 1551574)
+        span({
+          className: "beta",
+          role: "presentation",
+          id: betaID,
+        },
+          L10N.getStr("accessibility.beta")),
+        AccessibilityTreeFilter({ walker, describedby: betaID }),
         Button({
           className: "help",
           title: L10N.getStr("accessibility.learnMore"),
           onClick: this.onLearnMoreClick,
         }))
     );
   }
 }
--- a/devtools/client/accessibility/test/jest/components/__snapshots__/accessibility-tree-filter.test.js.snap
+++ b/devtools/client/accessibility/test/jest/components/__snapshots__/accessibility-tree-filter.test.js.snap
@@ -1,11 +1,11 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`AccessibilityTreeFilter component: audit filter filtered 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\">accessibility.tree.filters<button aria-pressed=\\"true\\" aria-busy=\\"false\\" class=\\"badge toggle-button checked\\">accessibility.badge.contrast</button></div>"`;
+exports[`AccessibilityTreeFilter component: audit filter filtered 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"true\\" aria-busy=\\"false\\" class=\\"badge toggle-button checked\\">accessibility.badge.contrast</button></div>"`;
 
-exports[`AccessibilityTreeFilter component: audit filter filtered auditing 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\">accessibility.tree.filters<button aria-pressed=\\"true\\" aria-busy=\\"true\\" class=\\"badge toggle-button checked devtools-throbber\\">accessibility.badge.contrast</button></div>"`;
+exports[`AccessibilityTreeFilter component: audit filter filtered auditing 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"true\\" aria-busy=\\"true\\" class=\\"badge toggle-button checked devtools-throbber\\">accessibility.badge.contrast</button></div>"`;
 
-exports[`AccessibilityTreeFilter component: audit filter not filtered 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\">accessibility.tree.filters<button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.badge.contrast</button></div>"`;
+exports[`AccessibilityTreeFilter component: audit filter not filtered 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.badge.contrast</button></div>"`;
 
-exports[`AccessibilityTreeFilter component: audit filter not filtered auditing 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\">accessibility.tree.filters<button aria-pressed=\\"false\\" aria-busy=\\"true\\" class=\\"badge toggle-button devtools-throbber\\">accessibility.badge.contrast</button></div>"`;
+exports[`AccessibilityTreeFilter component: audit filter not filtered auditing 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"false\\" aria-busy=\\"true\\" class=\\"badge toggle-button devtools-throbber\\">accessibility.badge.contrast</button></div>"`;
 
-exports[`AccessibilityTreeFilter component: toggle filter 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\">accessibility.tree.filters<button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.badge.contrast</button></div>"`;
+exports[`AccessibilityTreeFilter component: toggle filter 1`] = `"<div role=\\"toolbar\\" class=\\"accessibility-tree-filters\\" aria-labelledby=\\"accessibility-tree-filters-label\\"><span id=\\"accessibility-tree-filters-label\\" role=\\"presentation\\">accessibility.tree.filters</span><button aria-pressed=\\"false\\" aria-busy=\\"false\\" class=\\"badge toggle-button\\">accessibility.badge.contrast</button></div>"`;
--- a/devtools/client/debugger/dist/vendors.js
+++ b/devtools/client/debugger/dist/vendors.js
@@ -2528,17 +2528,17 @@ var substr = 'ab'.substr(-1) === 'b'
 /* 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 { Menu, MenuItem } = __webpack_require__(183);
 
 function inToolbox() {
   try {
-    return window.parent.document.documentURI == "about:devtools-toolbox";
+    return window.parent.document.documentURI.startsWith("about:devtools-toolbox");
   } catch (e) {
     // If `window` is not available, it's very likely that we are in the toolbox.
     return true;
   }
 }
 
 if (!inToolbox()) {
   __webpack_require__(431);
@@ -2707,17 +2707,17 @@ function formatKeyShortcut(shortcut) {
   if (isMacOS) {
     return shortcut.replace(/Shift\+/g, "\u21E7").replace(/Command\+|Cmd\+/g, "\u2318").replace(/CommandOrControl\+|CmdOrCtrl\+/g, "\u2318").replace(/Alt\+/g, "\u2325");
   }
   return shortcut.replace(/CommandOrControl\+|CmdOrCtrl\+/g, `${L10N.getStr("ctrl")}+`).replace(/Shift\+/g, "Shift+");
 }
 
 function inToolbox() {
   try {
-    return window.parent.document.documentURI == "about:devtools-toolbox";
+    return window.parent.document.documentURI.startsWith("about:devtools-toolbox");
   } catch (e) {
     // If `window` is not available, it's very likely that we are in the toolbox.
     return true;
   }
 }
 
 /**
  * A partial implementation of the Menu API provided by electron:
--- a/devtools/client/debugger/src/actions/navigation.js
+++ b/devtools/client/debugger/src/actions/navigation.js
@@ -43,24 +43,30 @@ export function willNavigate(event: Obje
 
     dispatch({
       type: "NAVIGATE",
       mainThread: { ...thread, url: event.url }
     });
   };
 }
 
-export function connect(url: string, actor: string, canRewind: boolean) {
+export function connect(
+  url: string,
+  actor: string,
+  canRewind: boolean,
+  isWebExtension: boolean
+) {
   return async function({ dispatch }: ThunkArgs) {
     await dispatch(updateWorkers());
     dispatch(
       ({
         type: "CONNECT",
         mainThread: { url, actor, type: -1, name: "" },
-        canRewind
+        canRewind,
+        isWebExtension
       }: Action)
     );
   };
 }
 
 /**
  * @memberof actions/navigation
  * @static
--- a/devtools/client/debugger/src/actions/preview.js
+++ b/devtools/client/debugger/src/actions/preview.js
@@ -4,16 +4,17 @@
 
 // @flow
 
 import { isConsole } from "../utils/preview";
 import { findBestMatchExpression } from "../utils/ast";
 import { PROMISE } from "./utils/middleware/promise";
 import { getExpressionFromCoords } from "../utils/editor/get-expression";
 import { isOriginal } from "../utils/source";
+import { isTesting } from "devtools-environment";
 
 import {
   getPreview,
   isLineInScope,
   isSelectedFrameVisible,
   getSelectedSource,
   getSelectedFrame,
   getSymbols,
@@ -65,32 +66,37 @@ export function updatePreview(
     }
 
     const { expression, location } = match;
 
     if (isConsole(expression)) {
       return;
     }
 
-    dispatch(setPreview(cx, expression, location, tokenPos, cursorPos));
+    dispatch(setPreview(cx, expression, location, tokenPos, cursorPos, target));
   };
 }
 
 export function setPreview(
   cx: Context,
   expression: string,
   location: AstLocation,
   tokenPos: Position,
-  cursorPos: ClientRect
+  cursorPos: ClientRect,
+  target: HTMLElement
 ) {
   return async ({ dispatch, getState, client, sourceMaps }: ThunkArgs) => {
     await dispatch({
       type: "SET_PREVIEW",
       cx,
       [PROMISE]: (async function() {
+        if (getPreview(getState())) {
+          dispatch(clearPreview(cx));
+        }
+
         const source = getSelectedSource(getState());
         if (!source) {
           return;
         }
 
         const thread = getCurrentThread(getState());
         const selectedFrame = getSelectedFrame(getState(), thread);
 
@@ -126,24 +132,32 @@ export function setPreview(
 
         const root = {
           name: expression,
           path: expression,
           contents: { value: result }
         };
         const properties = await client.loadObjectProperties(root);
 
+        // The first time a popup is rendered, the mouse should be hovered
+        // on the token. If it happens to be hovered on whitespace, it should
+        // not render anything
+        if (!target.matches(":hover") && !isTesting()) {
+          return;
+        }
+
         return {
           expression,
           result,
           properties,
           root,
           location,
           tokenPos,
-          cursorPos
+          cursorPos,
+          target
         };
       })()
     });
   };
 }
 
 export function clearPreview(cx: Context) {
   return ({ dispatch, getState, client }: ThunkArgs) => {
--- a/devtools/client/debugger/src/actions/tests/navigation.spec.js
+++ b/devtools/client/debugger/src/actions/tests/navigation.spec.js
@@ -37,16 +37,17 @@ describe("navigation", () => {
     const { dispatch, getState } = createStore({
       fetchWorkers: () => Promise.resolve([]),
       getMainThread: () => "FakeThread"
     });
     await dispatch(
       actions.connect(
         "http://test.com/foo",
         "actor",
+        false,
         false
       )
     );
     expect(selectors.getDebuggeeUrl(getState())).toEqual("http://test.com/foo");
   });
 
   it("navigation closes project-search", async () => {
     const { dispatch, getState, cx } = createStore(threadClient);
--- a/devtools/client/debugger/src/actions/types/index.js
+++ b/devtools/client/debugger/src/actions/types/index.js
@@ -66,17 +66,22 @@ type UpdateTabAction = {|
   +type: "UPDATE_TAB",
   +url: string,
   +framework?: string,
   +isOriginal?: boolean,
   +sourceId?: string
 |};
 
 type NavigateAction =
-  | {| +type: "CONNECT", +mainThread: MainThread, +canRewind: boolean |}
+  | {|
+      +type: "CONNECT",
+      +mainThread: MainThread,
+      +canRewind: boolean,
+      +isWebExtension: boolean
+    |}
   | {| +type: "NAVIGATE", +mainThread: MainThread |};
 
 export type FocusItem = TreeNode;
 
 export type SourceTreeAction =
   | {| +type: "SET_EXPANDED_STATE", +thread: string, +expanded: any |}
   | {| +type: "SET_FOCUSED_SOURCE_ITEM", +cx: Context, item: FocusItem |};
 
--- a/devtools/client/debugger/src/client/firefox.js
+++ b/devtools/client/debugger/src/client/firefox.js
@@ -53,17 +53,18 @@ export async function onConnect(connecti
   // the debugger (if it's paused already, or if loading the page from
   // bfcache) so explicity fire `newSource` events for all returned
   // sources.
   const sourceInfo = await clientCommands.fetchSources();
   const traits = tabTarget.traits;
   await actions.connect(
     tabTarget.url,
     threadClient.actor,
-    traits && traits.canRewind
+    traits && traits.canRewind,
+    tabTarget.isWebExtension
   );
   await actions.newGeneratedSources(sourceInfo);
 
   // If the threadClient is already paused, make sure to show a
   // paused state.
   const pausedPacket = threadClient.getLastPausePacket();
   if (pausedPacket) {
     clientEvents.paused(threadClient, "paused", pausedPacket);
--- a/devtools/client/debugger/src/components/Editor/Preview/Popup.js
+++ b/devtools/client/debugger/src/components/Editor/Preview/Popup.js
@@ -16,92 +16,90 @@ const {
 
 const { ObjectInspector, utils } = objectInspector;
 
 const {
   node: { nodeIsPrimitive, nodeIsFunction, nodeIsObject }
 } = utils;
 
 import actions from "../../../actions";
-import { getThreadContext } from "../../../selectors";
+import { getThreadContext, getPreview } from "../../../selectors";
 import Popover from "../../shared/Popover";
 import PreviewFunction from "../../shared/PreviewFunction";
 
 import { createObjectClient } from "../../../client/firefox";
 
 import "./Popup.css";
 
 import type { Coords } from "../../shared/Popover";
 import type { ThreadContext } from "../../../types";
 import type { PreviewValue } from "../../../reducers/types";
 
 type Props = {
   cx: ThreadContext,
   preview: PreviewValue,
-  onClose: () => void,
   editor: any,
   editorRef: ?HTMLDivElement,
   addExpression: typeof actions.addExpression,
   selectSourceURL: typeof actions.selectSourceURL,
   openLink: typeof actions.openLink,
   openElementInInspector: typeof actions.openElementInInspectorCommand,
   highlightDomElement: typeof actions.highlightDomElement,
-  unHighlightDomElement: typeof actions.unHighlightDomElement
+  unHighlightDomElement: typeof actions.unHighlightDomElement,
+  clearPreview: typeof actions.clearPreview
 };
 
 type State = {
   top: number
 };
 
-function inPreview(event) {
-  const relatedTarget: Element = (event.relatedTarget: any);
-
-  if (
-    !relatedTarget ||
-    (relatedTarget.classList &&
-      relatedTarget.classList.contains("preview-expression"))
-  ) {
-    return true;
-  }
-
-  // $FlowIgnore
-  const inPreviewSelection = document
-    .elementsFromPoint(event.clientX, event.clientY)
-    .some(el => el.classList.contains("preview-selection"));
-
-  return inPreviewSelection;
-}
-
 export class Popup extends Component<Props, State> {
   marker: any;
   pos: any;
+  popup: ?HTMLDivElement;
+  timerId: ?IntervalID;
 
   constructor(props: Props) {
     super(props);
     this.state = {
       top: 0
     };
   }
 
-  onMouseLeave = (e: SyntheticMouseEvent<HTMLDivElement>) => {
-    const relatedTarget: Element = (e.relatedTarget: any);
+  componentDidMount() {
+    this.startTimer();
+  }
+
+  componentWillUnmount() {
+    if (this.timerId) {
+      clearInterval(this.timerId);
+    }
+  }
 
-    if (!relatedTarget) {
-      return this.props.onClose();
+  startTimer() {
+    this.timerId = setInterval(this.onInterval, 300);
+  }
+
+  onInterval = () => {
+    const { preview, clearPreview, cx } = this.props;
+
+    // Don't clear the current preview if mouse is hovered on
+    // the current preview's element (target) or the popup element
+    const currentTarget = preview.target;
+    if (
+      currentTarget.matches(":hover") ||
+      (this.popup && this.popup.matches(":hover"))
+    ) {
+      return;
     }
 
-    if (!inPreview(e)) {
-      this.props.onClose();
-    }
-  };
-
-  onKeyDown = (e: KeyboardEvent) => {
-    if (e.key === "Escape") {
-      this.props.onClose();
-    }
+    // Clear the interval and the preview if it is not hovered
+    // on the current preview's element or the popup element
+    clearInterval(this.timerId);
+    return clearPreview(cx);
   };
 
   calculateMaxHeight = () => {
     const { editorRef } = this.props;
     if (!editorRef) {
       return "auto";
     }
     return editorRef.getBoundingClientRect().height - this.state.top;
@@ -112,16 +110,17 @@ export class Popup extends Component<Pro
       cx,
       selectSourceURL,
       preview: { result }
     } = this.props;
 
     return (
       <div
         className="preview-popup"
+        ref={a => (this.popup = a)}
         onClick={() =>
           selectSourceURL(cx, result.location.url, {
             line: result.location.line
           })
         }
       >
         <PreviewFunction func={result} />
       </div>
@@ -136,16 +135,17 @@ export class Popup extends Component<Pro
       highlightDomElement,
       unHighlightDomElement
     } = this.props;
 
     return (
       <div
         className="preview-popup"
         style={{ maxHeight: this.calculateMaxHeight() }}
+        ref={a => (this.popup = a)}
       >
         <ObjectInspector
           roots={properties}
           autoExpandDepth={0}
           disableWrap={true}
           focusable={false}
           openLink={openLink}
           createObjectClient={grip => createObjectClient(grip)}
@@ -159,17 +159,17 @@ export class Popup extends Component<Pro
   }
 
   renderSimplePreview() {
     const {
       openLink,
       preview: { result }
     } = this.props;
     return (
-      <div className="preview-popup">
+      <div className="preview-popup" ref={a => (this.popup = a)}>
         {Rep({
           object: result,
           mode: MODE.LONG,
           openLink
         })}
       </div>
     );
   }
@@ -218,46 +218,47 @@ export class Popup extends Component<Pro
 
     if (typeof result == "undefined" || result.optimizedOut) {
       return null;
     }
 
     return (
       <Popover
         targetPosition={cursorPos}
-        onMouseLeave={this.onMouseLeave}
-        onKeyDown={this.onKeyDown}
         type={type}
         onPopoverCoords={this.onPopoverCoords}
         editorRef={editorRef}
       >
         {this.renderPreview()}
       </Popover>
     );
   }
 }
 
 const mapStateToProps = state => ({
-  cx: getThreadContext(state)
+  cx: getThreadContext(state),
+  preview: getPreview(state)
 });
 
 const {
   addExpression,
   selectSourceURL,
   openLink,
   openElementInInspectorCommand,
   highlightDomElement,
-  unHighlightDomElement
+  unHighlightDomElement,
+  clearPreview
 } = actions;
 
 const mapDispatchToProps = {
   addExpression,
   selectSourceURL,
   openLink,
   openElementInInspector: openElementInInspectorCommand,
   highlightDomElement,
-  unHighlightDomElement
+  unHighlightDomElement,
+  clearPreview
 };
 
 export default connect(
   mapStateToProps,
   mapDispatchToProps
 )(Popup);
--- a/devtools/client/debugger/src/components/Editor/Preview/index.js
+++ b/devtools/client/debugger/src/components/Editor/Preview/index.js
@@ -25,29 +25,16 @@ type Props = {
   addExpression: typeof actions.addExpression,
   updatePreview: typeof actions.updatePreview
 };
 
 type State = {
   selecting: boolean
 };
 
-function inPopup(e) {
-  const { relatedTarget } = e;
-
-  if (!relatedTarget) {
-    return true;
-  }
-
-  const pop =
-    relatedTarget.closest(".tooltip") || relatedTarget.closest(".popover");
-
-  return pop;
-}
-
 function getElementFromPos(pos: DOMRect) {
   // We need to use element*s*AtPoint because the tooltip overlays
   // the token and thus an undesirable element may be returned
   const elementsAtPoint = [
     // $FlowIgnore
     ...document.elementsFromPoint(pos.x + pos.width / 2, pos.y + pos.height / 2)
   ];
 
@@ -64,60 +51,63 @@ class Preview extends PureComponent<Prop
   componentDidMount() {
     this.updateListeners();
   }
 
   componentWillUnmount() {
     const { codeMirror } = this.props.editor;
     const codeMirrorWrapper = codeMirror.getWrapperElement();
 
+    codeMirror.off("tokenenter", this.onTokenEnter);
     codeMirror.off("scroll", this.onScroll);
-    codeMirror.off("tokenenter", this.onTokenEnter);
-    codeMirror.off("tokenleave", this.onTokenLeave);
     codeMirrorWrapper.removeEventListener("mouseup", this.onMouseUp);
     codeMirrorWrapper.removeEventListener("mousedown", this.onMouseDown);
   }
 
   componentDidUpdate(prevProps) {
     this.updateHighlight(prevProps);
   }
 
   updateListeners(prevProps: ?Props) {
     const { codeMirror } = this.props.editor;
     const codeMirrorWrapper = codeMirror.getWrapperElement();
+    codeMirror.on("tokenenter", this.onTokenEnter);
     codeMirror.on("scroll", this.onScroll);
-    codeMirror.on("tokenenter", this.onTokenEnter);
-    codeMirror.on("tokenleave", this.onTokenLeave);
     codeMirrorWrapper.addEventListener("mouseup", this.onMouseUp);
     codeMirrorWrapper.addEventListener("mousedown", this.onMouseDown);
   }
 
   updateHighlight(prevProps) {
     const { preview } = this.props;
 
-    if (preview && !preview.updating) {
+    if (preview && !preview.updating && preview.target.matches(":hover")) {
       const target = getElementFromPos(preview.cursorPos);
       target && target.classList.add("preview-selection");
     }
 
-    if (prevProps.preview && !prevProps.preview.updating) {
+    if (
+      prevProps.preview &&
+      !prevProps.preview.updating &&
+      prevProps.preview !== preview
+    ) {
       const target = getElementFromPos(prevProps.preview.cursorPos);
       target && target.classList.remove("preview-selection");
     }
   }
 
   onTokenEnter = ({ target, tokenPos }) => {
-    const { cx, updatePreview, editor } = this.props;
-    if (cx.isPaused) {
+    const { cx, editor, updatePreview, preview } = this.props;
+
+    if (cx.isPaused || (!preview || target !== preview.target)) {
       updatePreview(cx, target, tokenPos, editor.codeMirror);
     }
   };
 
-  onTokenLeave = e => {
-    if (this.props.cx.isPaused && !inPopup(e)) {
+  onScroll = () => {
+    if (this.props.cx.isPaused) {
       this.props.clearPreview(this.props.cx);
     }
   };
 
   onMouseUp = () => {
     if (this.props.cx.isPaused) {
       this.setState({ selecting: false });
       return true;
@@ -126,40 +116,27 @@ class Preview extends PureComponent<Prop
 
   onMouseDown = () => {
     if (this.props.cx.isPaused) {
       this.setState({ selecting: true });
       return true;
     }
   };
 
-  onScroll = () => {
-    if (this.props.cx.isPaused) {
-      this.props.clearPreview(this.props.cx);
-    }
-  };
-
-  onClose = e => {
-    if (this.props.cx.isPaused) {
-      this.props.clearPreview(this.props.cx);
-    }
-  };
-
   render() {
     const { preview } = this.props;
     if (!preview || preview.updating || this.state.selecting) {
       return null;
     }
 
     return (
       <Popup
         preview={preview}
         editor={this.props.editor}
         editorRef={this.props.editorRef}
-        onClose={this.onClose}
       />
     );
   }
 }
 
 const mapStateToProps = state => ({
   cx: getThreadContext(state),
   preview: getPreview(state)
--- a/devtools/client/debugger/src/components/shared/Popover.js
+++ b/devtools/client/debugger/src/components/shared/Popover.js
@@ -9,18 +9,16 @@ import BracketArrow from "./BracketArrow
 
 import "./Popover.css";
 
 type Props = {
   editorRef: ?HTMLDivElement,
   targetPosition: Object,
   children: ?React$Element<any>,
   onPopoverCoords: Function,
-  onMouseLeave: (e: SyntheticMouseEvent<HTMLDivElement>) => void,
-  onKeyDown: (e: KeyboardEvent) => void,
   type?: "popover" | "tooltip"
 };
 
 type Orientation = "up" | "down" | "right";
 type TargetMid = {
   x: number,
   y: number
 };
@@ -41,19 +39,17 @@ class Popover extends Component<Props, S
       left: 0,
       top: 0,
       orientation: "down",
       targetMid: { x: 0, y: 0 }
     }
   };
 
   static defaultProps = {
-    onMouseLeave: () => {},
     onPopoverCoords: () => {},
-    onKeyDown: () => {},
     type: "popover"
   };
 
   componentDidMount() {
     const { type } = this.props;
     const coords =
       type == "popover" ? this.getPopoverCoords() : this.getTooltipCoords();
 
@@ -224,43 +220,37 @@ class Popover extends Component<Props, S
       Object.assign(arrowProps, { orientation: "left", top, left: -9 });
     }
 
     return <BracketArrow {...arrowProps} />;
   }
 
   renderPopover() {
     const { top, left, orientation, targetMid } = this.state.coords;
-    const { onMouseLeave, onKeyDown } = this.props;
     const arrow = this.getPopoverArrow(orientation, targetMid.x, targetMid.y);
 
     return (
       <div
         className={classNames("popover", `orientation-${orientation}`, {
           up: orientation === "up"
         })}
-        onMouseLeave={onMouseLeave}
-        onKeyDown={onKeyDown}
         style={{ top, left }}
         ref={c => (this.$popover = c)}
       >
         {arrow}
         {this.getChildren()}
       </div>
     );
   }
 
   renderTooltip() {
     const { top, left } = this.state.coords;
-    const { onMouseLeave, onKeyDown } = this.props;
     return (
       <div
         className="tooltip"
-        onMouseLeave={onMouseLeave}
-        onKeyDown={onKeyDown}
         style={{ top, left }}
         ref={c => (this.$tooltip = c)}
       >
         {this.getChildren()}
       </div>
     );
   }
 
--- a/devtools/client/debugger/src/reducers/ast.js
+++ b/devtools/client/debugger/src/reducers/ast.js
@@ -34,17 +34,18 @@ export type Preview = {| updating: true 
 export type PreviewValue = {|
   expression: string,
   result: Grip,
   root: Node,
   properties: GripProperties,
   location: AstLocation,
   cursorPos: any,
   tokenPos: AstLocation,
-  updating: false
+  updating: false,
+  target: HTMLDivElement
 |};
 
 export type ASTState = {
   +symbols: SymbolsMap,
   +outOfScopeLocations: ?Array<AstLocation>,
   +inScopeLines: ?Array<number>,
   +preview: Preview
 };
--- a/devtools/client/debugger/src/reducers/debuggee.js
+++ b/devtools/client/debugger/src/reducers/debuggee.js
@@ -15,35 +15,38 @@ import { createSelector } from "reselect
 import { getDisplayName } from "../utils/workers";
 
 import type { Selector } from "./types";
 import type { MainThread, WorkerList, Thread } from "../types";
 import type { Action } from "../actions/types";
 
 export type DebuggeeState = {
   workers: WorkerList,
-  mainThread: MainThread
+  mainThread: MainThread,
+  isWebExtension: boolean
 };
 
 export function initialDebuggeeState(): DebuggeeState {
   return {
     workers: [],
-    mainThread: { actor: "", url: "", type: -1, name: "" }
+    mainThread: { actor: "", url: "", type: -1, name: "" },
+    isWebExtension: false
   };
 }
 
 export default function debuggee(
   state: DebuggeeState = initialDebuggeeState(),
   action: Action
 ): DebuggeeState {
   switch (action.type) {
     case "CONNECT":
       return {
         ...state,
-        mainThread: { ...action.mainThread, name: L10N.getStr("mainThread") }
+        mainThread: { ...action.mainThread, name: L10N.getStr("mainThread") },
+        isWebExtension: action.isWebExtension
       };
     case "INSERT_WORKERS":
       return insertWorkers(state, action.workers);
     case "REMOVE_WORKERS":
       const { workers } = action;
       return {
         ...state,
         workers: state.workers.filter(w => !workers.includes(w.actor))
--- a/devtools/client/debugger/src/reducers/sources.js
+++ b/devtools/client/debugger/src/reducers/sources.js
@@ -57,16 +57,17 @@ import type {
   SourceWithContent,
   ThreadId,
   MappedLocation,
   BreakpointPositions
 } from "../types";
 import type { PendingSelectedLocation, Selector } from "./types";
 import type { Action, DonePromiseAction, FocusItem } from "../actions/types";
 import type { LoadSourceAction } from "../actions/types/SourceAction";
+import type { DebuggeeState } from "./debuggee";
 import { uniq } from "lodash";
 
 export type SourcesMap = { [SourceId]: Source };
 type SourcesContentMap = {
   [SourceId]: AsyncValue<SourceContent> | null
 };
 export type SourcesMapByThread = { [ThreadId]: SourcesMap };
 
@@ -489,16 +490,17 @@ export function getBlackBoxList() {
 // Unfortunately, it's really hard to make these functions accept just
 // the state that we care about and still type it with Flow. The
 // problem is that we want to re-export all selectors from a single
 // module for the UI, and all of those selectors should take the
 // top-level app state, so we'd have to "wrap" them to automatically
 // pick off the piece of state we're interested in. It's impossible
 // (right now) to type those wrapped functions.
 type OuterState = { sources: SourcesState };
+type DebuggeeOuterState = { debuggee: DebuggeeState };
 
 const getSourcesState = (state: OuterState) => state.sources;
 
 export function getSourceThreads(
   state: OuterState & SourceActorOuterState,
   source: Source
 ): ThreadId[] {
   return uniq(
@@ -683,17 +685,17 @@ export function getPlainUrls(state: Oute
   return state.sources.plainUrls;
 }
 
 export function getSourceList(state: OuterState): Source[] {
   return querySourceList(getSources(state));
 }
 
 export function getDisplayedSourcesList(
-  state: OuterState & SourceActorOuterState
+  state: OuterState & SourceActorOuterState & DebuggeeOuterState
 ): Source[] {
   return ((Object.values(getDisplayedSources(state)): any).flatMap(
     Object.values
   ): any);
 }
 
 export function getSourceCount(state: OuterState) {
   return getSourceList(state).length;
@@ -792,49 +794,62 @@ export function getSelectedSourceId(stat
 }
 
 export function getProjectDirectoryRoot(state: OuterState): string {
   return state.sources.projectDirectoryRoot;
 }
 
 const queryAllDisplayedSources: ReduceQuery<
   SourceResource,
-  {| projectDirectoryRoot: string, chromeAndExtensionsEnabled: boolean |},
+  {|
+    projectDirectoryRoot: string,
+    chromeAndExtensionsEnabled: boolean,
+    debuggeeIsWebExtension: boolean
+  |},
   Array<SourceId>
 > = makeReduceQuery(
   makeMapWithArgs(
     (
       resource,
       ident,
-      { projectDirectoryRoot, chromeAndExtensionsEnabled }
+      {
+        projectDirectoryRoot,
+        chromeAndExtensionsEnabled,
+        debuggeeIsWebExtension
+      }
     ) => ({
       id: resource.id,
       displayed:
         underRoot(resource, projectDirectoryRoot) &&
-        (!resource.isExtension || chromeAndExtensionsEnabled)
+        (!resource.isExtension ||
+          chromeAndExtensionsEnabled ||
+          debuggeeIsWebExtension)
     })
   ),
   items =>
     items.reduce((acc, { id, displayed }) => {
       if (displayed) {
         acc.push(id);
       }
       return acc;
     }, [])
 );
 
-function getAllDisplayedSources(state: OuterState): Array<SourceId> {
+function getAllDisplayedSources(
+  state: OuterState & DebuggeeOuterState
+): Array<SourceId> {
   return queryAllDisplayedSources(state.sources.sources, {
     projectDirectoryRoot: state.sources.projectDirectoryRoot,
-    chromeAndExtensionsEnabled: state.sources.chromeAndExtenstionsEnabled
+    chromeAndExtensionsEnabled: state.sources.chromeAndExtenstionsEnabled,
+    debuggeeIsWebExtension: state.debuggee.isWebExtension
   });
 }
 
 type GetDisplayedSourceIDsSelector = (
-  OuterState & SourceActorOuterState
+  OuterState & SourceActorOuterState & DebuggeeOuterState
 ) => { [ThreadId]: Set<SourceId> };
 const getDisplayedSourceIDs: GetDisplayedSourceIDsSelector = createSelector(
   getThreadsBySource,
   getAllDisplayedSources,
   (threadsBySource, displayedSources) => {
     const sourceIDsByThread = {};
 
     for (const sourceId of displayedSources) {
@@ -850,17 +865,17 @@ const getDisplayedSourceIDs: GetDisplaye
         sourceIDsByThread[thread].add(sourceId);
       }
     }
     return sourceIDsByThread;
   }
 );
 
 type GetDisplayedSourcesSelector = (
-  OuterState & SourceActorOuterState
+  OuterState & SourceActorOuterState & DebuggeeOuterState
 ) => SourcesMapByThread;
 export const getDisplayedSources: GetDisplayedSourcesSelector = createSelector(
   state => state.sources.sources,
   getDisplayedSourceIDs,
   (sources, idsByThread) => {
     const result = {};
 
     for (const thread of Object.keys(idsByThread)) {
--- a/devtools/client/debugger/src/reducers/tests/sources.spec.js
+++ b/devtools/client/debugger/src/reducers/tests/sources.spec.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 // @flow
 declare var describe: (name: string, func: () => void) => void;
 declare var it: (desc: string, func: () => void) => void;
 declare var expect: (value: any) => any;
 
 import update, { initialSourcesState, getDisplayedSources } from "../sources";
+import { initialDebuggeeState } from "../debuggee";
 import updateSourceActors from "../source-actors";
 import type { Source, SourceActor } from "../../types";
 import { prefs } from "../../utils/prefs";
 import { makeMockSource, mockcx } from "../../utils/test-mockup";
 import { getResourceIds } from "../../utils/resource";
 
 const extensionSource = {
   ...makeMockSource(),
@@ -86,17 +87,18 @@ describe("sources selectors", () => {
       sourceActors: undefined
     };
     const insertAction = {
       type: "INSERT_SOURCE_ACTORS",
       items: mockSourceActors
     };
     state = {
       sources: update(state.sources, insertAction),
-      sourceActors: updateSourceActors(state.sourceActors, insertAction)
+      sourceActors: updateSourceActors(state.sourceActors, insertAction),
+      debuggee: initialDebuggeeState()
     };
     const threadSources = getDisplayedSources(state);
     expect(Object.values(threadSources.foo)).toHaveLength(3);
   });
 
   it("should omit all extensions when chrome preference enabled", () => {
     prefs.chromeAndExtenstionsEnabled = false;
     let state = initialSourcesState();
@@ -111,14 +113,15 @@ describe("sources selectors", () => {
 
     const insertAction = {
       type: "INSERT_SOURCE_ACTORS",
       items: mockSourceActors
     };
 
     state = {
       sources: update(state.sources, insertAction),
-      sourceActors: updateSourceActors(state.sourceActors, insertAction)
+      sourceActors: updateSourceActors(state.sourceActors, insertAction),
+      debuggee: initialDebuggeeState()
     };
     const threadSources = getDisplayedSources(state);
     expect(Object.values(threadSources.foo)).toHaveLength(1);
   });
 });
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -2447,25 +2447,29 @@ Toolbox.prototype = {
 
     return promise.resolve();
   },
 
   /**
    * Toggles the options panel.
    * If the option panel is already selected then select the last selected panel.
    */
-  toggleOptions: function() {
+  toggleOptions: function(event) {
     // Flip back to the last used panel if we are already
     // on the options panel.
     if (this.currentToolId === "options" &&
         gDevTools.getToolDefinition(this.lastUsedToolId)) {
       this.selectTool(this.lastUsedToolId, "toggle_settings_off");
     } else {
       this.selectTool("options", "toggle_settings_on");
     }
+
+    // preventDefault will avoid a Linux only bug when the focus is on a text input
+    // See Bug 1519087.
+    event.preventDefault();
   },
 
   /**
    * Tells the target tab to reload.
    */
   reloadTarget: function(force) {
     if (this.target.canRewind) {
       // Recording tabs need to be reloaded in a new content process.
--- a/devtools/client/locales/en-US/aboutdebugging.ftl
+++ b/devtools/client/locales/en-US/aboutdebugging.ftl
@@ -251,22 +251,16 @@ about-debugging-connection-prompt-enable
 
 # Text of the connection prompt button displayed in Runtime pages, when the preference
 # "devtools.debugger.prompt-connection" is true on the target runtime.
 about-debugging-connection-prompt-disable-button = Disable connection prompt
 
 # Title of a modal dialog displayed on remote runtime pages after clicking on the Profile Runtime button.
 about-debugging-profiler-dialog-title2 = Profiler
 
-# Label of a checkbox displayed in the runtime page for "This Firefox".
-# This checkbox will toggle preferences that enable local addon debugging.
-# The "Learn more" link points to MDN.
-# https://developer.mozilla.org/docs/Tools/about:debugging#Enabling_add-on_debugging
-about-debugging-extension-debug-setting-label = Enable extension debugging. <a>Learn more</a>
-
 # Clicking on the header of a debug target category will expand or collapse the debug
 # target items in the category. This text is used as ’title’ attribute of the header,
 # to describe this feature.
 about-debugging-collapse-expand-debug-targets = Collapse / expand
 
 # Debug Targets strings
 
 # Displayed in the categories of "runtime" pages that don't have any debug target to
--- a/devtools/client/locales/en-US/accessibility.properties
+++ b/devtools/client/locales/en-US/accessibility.properties
@@ -188,8 +188,12 @@ accessibility.progress.initializing=Initializing…
 # accessibility panel overlay shown when accessibility audit is running showing
 # the number of nodes being audited. Semi-colon list of plural forms. See:
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 accessibility.progress.progressbar=Checking #1 node;Checking #1 nodes
 
 # LOCALIZATION NOTE (accessibility.progress.finishing): A title text for the
 # accessibility panel overlay shown when accessibility audit is finishing up.
 accessibility.progress.finishing=Finishing up…
+
+# LOCALIZATION NOTE (accessibility.beta): A title text for the features in the
+# accessibility panel that are currently in beta.
+accessibility.beta=beta
--- a/devtools/client/netmonitor/src/actions/requests.js
+++ b/devtools/client/netmonitor/src/actions/requests.js
@@ -2,18 +2,20 @@
  * 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";
 
 const {
   ADD_REQUEST,
   CLEAR_REQUESTS,
+  CLONE_REQUEST,
   CLONE_SELECTED_REQUEST,
   REMOVE_SELECTED_CUSTOM_REQUEST,
+  RIGHT_CLICK_REQUEST,
   SEND_CUSTOM_REQUEST,
   TOGGLE_RECORDING,
   UPDATE_REQUEST,
 } = require("../constants");
 const { getSelectedRequest, getRequestById } = require("../selectors/index");
 
 function addRequest(id, data, batch) {
   return {
@@ -29,16 +31,37 @@ function updateRequest(id, data, batch) 
     type: UPDATE_REQUEST,
     id,
     data,
     meta: { batch },
   };
 }
 
 /**
+ * Clone request by id. Used when cloning a request
+ * through the "Edit and Resend" option present in the context menu.
+ */
+function cloneRequest(id) {
+  return {
+    id,
+    type: CLONE_REQUEST,
+  };
+}
+
+/**
+ * Right click a request without selecting it.
+ */
+function rightClickRequest(id) {
+  return {
+    id,
+    type: RIGHT_CLICK_REQUEST,
+  };
+}
+
+/**
  * Clone the currently selected request, set the "isCustom" attribute.
  * Used by the "Edit and Resend" feature.
  */
 function cloneSelectedRequest() {
   return {
     type: CLONE_SELECTED_REQUEST,
   };
 }
@@ -138,15 +161,17 @@ function toggleRecording() {
     type: TOGGLE_RECORDING,
   };
 }
 
 module.exports = {
   addRequest,
   blockSelectedRequestURL,
   clearRequests,
+  cloneRequest,
   cloneSelectedRequest,
+  rightClickRequest,
   removeSelectedCustomRequest,
   sendCustomRequest,
   toggleRecording,
   unblockSelectedRequestURL,
   updateRequest,
 };
--- a/devtools/client/netmonitor/src/assets/styles/RequestList.css
+++ b/devtools/client/netmonitor/src/assets/styles/RequestList.css
@@ -464,37 +464,41 @@
 
 /* Request list item */
 
 .request-list-item {
   height: 24px;
   line-height: 24px;
 }
 
-.request-list-item.selected {
-  background-color: var(--theme-selection-background);
-  color: var(--theme-selection-color);
-}
-
 .request-list-item:not(.selected).odd {
   background-color: var(--table-zebra-background);
 }
 
 .request-list-item:not(.selected):hover {
   background-color: var(--table-selection-background-hover);
 }
 
 .request-list-item:not(.selected).fromCache > .requests-list-column:not(.requests-list-waterfall) {
   opacity: 0.7;
 }
 
 .request-list-item.blocked {
   color: var(--timing-blocked-color);
 }
 
+/*
+ * Put ahead of .request-list-item.blocked to avoid specificity conflict.
+ * Bug 1530914 - Highlighted Security Value is difficult to read.
+ */
+.request-list-item.selected {
+  background-color: var(--theme-selection-background);
+  color: var(--theme-selection-color);
+}
+
 /* Responsive web design support */
 
 @media (max-width: 700px) {
   .requests-list-status-code {
     width: auto;
   }
 
   .requests-list-size {
--- a/devtools/client/netmonitor/src/components/RequestListContent.js
+++ b/devtools/client/netmonitor/src/components/RequestListContent.js
@@ -39,40 +39,47 @@ const { div } = dom;
 
 // Tooltip show / hide delay in ms
 const REQUESTS_TOOLTIP_TOGGLE_DELAY = 500;
 // Tooltip image maximum dimension in px
 const REQUESTS_TOOLTIP_IMAGE_MAX_DIM = 400;
 // Gecko's scrollTop is int32_t, so the maximum value is 2^31 - 1 = 2147483647
 const MAX_SCROLL_HEIGHT = 2147483647;
 
+const LEFT_MOUSE_BUTTON = 0;
+const RIGHT_MOUSE_BUTTON = 2;
+
 /**
  * Renders the actual contents of the request list.
  */
 class RequestListContent extends Component {
   static get propTypes() {
     return {
       blockSelectedRequestURL: PropTypes.func.isRequired,
       connector: PropTypes.object.isRequired,
       columns: PropTypes.object.isRequired,
       networkDetailsOpen: PropTypes.bool.isRequired,
       networkDetailsWidth: PropTypes.number,
       networkDetailsHeight: PropTypes.number,
-      cloneSelectedRequest: PropTypes.func.isRequired,
+      cloneRequest: PropTypes.func.isRequired,
+      clickedRequest: PropTypes.object,
+      openDetailsPanelTab: PropTypes.func.isRequired,
       sendCustomRequest: PropTypes.func.isRequired,
       displayedRequests: PropTypes.array.isRequired,
       firstRequestStartedMillis: PropTypes.number.isRequired,
       fromCache: PropTypes.bool,
       onCauseBadgeMouseDown: PropTypes.func.isRequired,
+      onItemRightMouseButtonDown: PropTypes.func.isRequired,
       onItemMouseDown: PropTypes.func.isRequired,
       onSecurityIconMouseDown: PropTypes.func.isRequired,
       onSelectDelta: PropTypes.func.isRequired,
       onWaterfallMouseDown: PropTypes.func.isRequired,
       openStatistics: PropTypes.func.isRequired,
       scale: PropTypes.number,
+      selectRequest: PropTypes.func.isRequired,
       selectedRequest: PropTypes.object,
       unblockSelectedRequestURL: PropTypes.func.isRequired,
       requestFilterTypes: PropTypes.object.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
@@ -80,16 +87,17 @@ class RequestListContent extends Compone
     this.onHover = this.onHover.bind(this);
     this.onScroll = this.onScroll.bind(this);
     this.onResize = this.onResize.bind(this);
     this.onKeyDown = this.onKeyDown.bind(this);
     this.openRequestInTab = this.openRequestInTab.bind(this);
     this.onDoubleClick = this.onDoubleClick.bind(this);
     this.onContextMenu = this.onContextMenu.bind(this);
     this.onFocusedNodeChange = this.onFocusedNodeChange.bind(this);
+    this.onMouseDown = this.onMouseDown.bind(this);
   }
 
   componentWillMount() {
     this.tooltip = new HTMLTooltip(window.parent.document, { type: "arrow" });
     window.addEventListener("resize", this.onResize);
   }
 
   componentDidMount() {
@@ -211,16 +219,24 @@ class RequestListContent extends Compone
 
   /**
    * Scroll listener for the requests menu view.
    */
   onScroll() {
     this.tooltip.hide();
   }
 
+  onMouseDown(evt, id) {
+    if (evt.button === LEFT_MOUSE_BUTTON) {
+      this.props.selectRequest(id);
+    } else if (evt.button === RIGHT_MOUSE_BUTTON) {
+      this.props.onItemRightMouseButtonDown(id);
+    }
+  }
+
   /**
    * Handler for keyboard events. For arrow up/down, page up/down, home/end,
    * move the selection up or down.
    */
   onKeyDown(evt) {
     let delta;
 
     switch (evt.key) {
@@ -268,39 +284,41 @@ class RequestListContent extends Compone
   }
 
   onDoubleClick({ id, url, requestHeaders, requestPostData }) {
     this.openRequestInTab(id, url, requestHeaders, requestPostData);
   }
 
   onContextMenu(evt) {
     evt.preventDefault();
-    const { selectedRequest, displayedRequests } = this.props;
+    const { clickedRequest, displayedRequests } = this.props;
 
     if (!this.contextMenu) {
       const {
         blockSelectedRequestURL,
         connector,
-        cloneSelectedRequest,
+        cloneRequest,
+        openDetailsPanelTab,
         sendCustomRequest,
         openStatistics,
         unblockSelectedRequestURL,
       } = this.props;
       this.contextMenu = new RequestListContextMenu({
         blockSelectedRequestURL,
         connector,
-        cloneSelectedRequest,
+        cloneRequest,
+        openDetailsPanelTab,
         sendCustomRequest,
         openStatistics,
         openRequestInTab: this.openRequestInTab,
         unblockSelectedRequestURL,
       });
     }
 
-    this.contextMenu.open(evt, selectedRequest, displayedRequests);
+    this.contextMenu.open(evt, clickedRequest, displayedRequests);
   }
 
   /**
    * If selection has just changed (by keyboard navigation), don't keep the list
    * scrolled to bottom, but allow scrolling up with the selection.
    */
   onFocusedNodeChange() {
     this.shouldScrollBottom = false;
@@ -308,17 +326,16 @@ class RequestListContent extends Compone
 
   render() {
     const {
       connector,
       columns,
       displayedRequests,
       firstRequestStartedMillis,
       onCauseBadgeMouseDown,
-      onItemMouseDown,
       onSecurityIconMouseDown,
       onWaterfallMouseDown,
       requestFilterTypes,
       scale,
       selectedRequest,
     } = this.props;
 
     return (
@@ -345,17 +362,17 @@ class RequestListContent extends Compone
               columns,
               item,
               index,
               isSelected: item.id === (selectedRequest && selectedRequest.id),
               key: item.id,
               onContextMenu: this.onContextMenu,
               onFocusedNodeChange: this.onFocusedNodeChange,
               onDoubleClick: () => this.onDoubleClick(item),
-              onMouseDown: () => onItemMouseDown(item.id),
+              onMouseDown: (evt) => this.onMouseDown(evt, item.id),
               onCauseBadgeMouseDown: () => onCauseBadgeMouseDown(item.cause),
               onSecurityIconMouseDown: () => onSecurityIconMouseDown(item.securityState),
               onWaterfallMouseDown: () => onWaterfallMouseDown(),
               requestFilterTypes,
             }))
           ) // end of requests-list-row-group">
         )
       )
@@ -364,40 +381,44 @@ class RequestListContent extends Compone
 }
 
 module.exports = connect(
   (state) => ({
     columns: state.ui.columns,
     networkDetailsOpen: state.ui.networkDetailsOpen,
     networkDetailsWidth: state.ui.networkDetailsWidth,
     networkDetailsHeight: state.ui.networkDetailsHeight,
+    clickedRequest: state.requests.clickedRequest,
     displayedRequests: getDisplayedRequests(state),
     firstRequestStartedMillis: state.requests.firstStartedMillis,
     selectedRequest: getSelectedRequest(state),
     scale: getWaterfallScale(state),
     requestFilterTypes: state.filters.requestFilterTypes,
   }),
   (dispatch, props) => ({
     blockSelectedRequestURL: () => {
       dispatch(Actions.blockSelectedRequestURL(props.connector));
     },
-    cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
+    cloneRequest: (id) => dispatch(Actions.cloneRequest(id)),
+    openDetailsPanelTab: () => dispatch(Actions.openNetworkDetails(true)),
     sendCustomRequest: () => dispatch(Actions.sendCustomRequest(props.connector)),
     openStatistics: (open) => dispatch(Actions.openStatistics(props.connector, open)),
     unblockSelectedRequestURL: () => {
       dispatch(Actions.unblockSelectedRequestURL(props.connector));
     },
     /**
      * A handler that opens the stack trace tab when a stack trace is available
      */
     onCauseBadgeMouseDown: (cause) => {
       if (cause.stacktrace && cause.stacktrace.length > 0) {
         dispatch(Actions.selectDetailsPanelTab("stack-trace"));
       }
     },
+    selectRequest: (id) => dispatch(Actions.selectRequest(id)),
+    onItemRightMouseButtonDown: (id) => dispatch(Actions.rightClickRequest(id)),
     onItemMouseDown: (id) => dispatch(Actions.selectRequest(id)),
     /**
      * A handler that opens the security tab in the details view if secure or
      * broken security indicator is clicked.
      */
     onSecurityIconMouseDown: (securityState) => {
       if (securityState && securityState !== "insecure") {
         dispatch(Actions.selectDetailsPanelTab("security"));
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -7,20 +7,22 @@
 const actionTypes = {
   ADD_REQUEST: "ADD_REQUEST",
   ADD_TIMING_MARKER: "ADD_TIMING_MARKER",
   BATCH_ACTIONS: "BATCH_ACTIONS",
   BATCH_ENABLE: "BATCH_ENABLE",
   BATCH_FLUSH: "BATCH_FLUSH",
   CLEAR_REQUESTS: "CLEAR_REQUESTS",
   CLEAR_TIMING_MARKERS: "CLEAR_TIMING_MARKERS",
+  CLONE_REQUEST: "CLONE_REQUEST",
   CLONE_SELECTED_REQUEST: "CLONE_SELECTED_REQUEST",
   ENABLE_REQUEST_FILTER_TYPE_ONLY: "ENABLE_REQUEST_FILTER_TYPE_ONLY",
   OPEN_NETWORK_DETAILS: "OPEN_NETWORK_DETAILS",
   RESIZE_NETWORK_DETAILS: "RESIZE_NETWORK_DETAILS",
+  RIGHT_CLICK_REQUEST: "RIGHT_CLICK_REQUEST",
   ENABLE_PERSISTENT_LOGS: "ENABLE_PERSISTENT_LOGS",
   DISABLE_BROWSER_CACHE: "DISABLE_BROWSER_CACHE",
   OPEN_STATISTICS: "OPEN_STATISTICS",
   REMOVE_SELECTED_CUSTOM_REQUEST: "REMOVE_SELECTED_CUSTOM_REQUEST",
   RESET_COLUMNS: "RESET_COLUMNS",
   SELECT_REQUEST: "SELECT_REQUEST",
   SELECT_DETAILS_PANEL_TAB: "SELECT_DETAILS_PANEL_TAB",
   SEND_CUSTOM_REQUEST: "SEND_CUSTOM_REQUEST",
--- a/devtools/client/netmonitor/src/reducers/requests.js
+++ b/devtools/client/netmonitor/src/reducers/requests.js
@@ -6,19 +6,21 @@
 
 const {
   getUrlDetails,
   processNetworkUpdates,
 } = require("../utils/request-utils");
 const {
   ADD_REQUEST,
   CLEAR_REQUESTS,
+  CLONE_REQUEST,
   CLONE_SELECTED_REQUEST,
   OPEN_NETWORK_DETAILS,
   REMOVE_SELECTED_CUSTOM_REQUEST,
+  RIGHT_CLICK_REQUEST,
   SELECT_REQUEST,
   SEND_CUSTOM_REQUEST,
   TOGGLE_RECORDING,
   UPDATE_REQUEST,
 } = require("../constants");
 
 /**
  * This structure stores list of all HTTP requests received
@@ -106,52 +108,41 @@ function requestsReducer(state = Request
       return {
         ...Requests(),
         recording: state.recording,
       };
     }
 
     // Select specific request.
     case SELECT_REQUEST: {
+      // Selected request represents the last request that was clicked
+      // before the context menu is shown
+      const clickedRequest = state.requests.get(action.id);
       return {
         ...state,
+        clickedRequest,
         selectedId: action.id,
       };
     }
 
     // Clone selected request for re-send.
-    case CLONE_SELECTED_REQUEST: {
-      const { requests, selectedId } = state;
-
-      if (!selectedId) {
-        return state;
-      }
-
-      const clonedRequest = requests.get(selectedId);
-      if (!clonedRequest) {
-        return state;
-      }
+    case CLONE_REQUEST: {
+      return cloneRequest(state, action.id);
+    }
 
-      const newRequest = {
-        id: clonedRequest.id + "-clone",
-        cause: clonedRequest.cause,
-        method: clonedRequest.method,
-        url: clonedRequest.url,
-        urlDetails: clonedRequest.urlDetails,
-        requestHeaders: clonedRequest.requestHeaders,
-        requestPostData: clonedRequest.requestPostData,
-        requestPostDataAvailable: clonedRequest.requestPostDataAvailable,
-        isCustom: true,
-      };
+    case CLONE_SELECTED_REQUEST: {
+      return cloneRequest(state, state.selectedId);
+    }
 
+    case RIGHT_CLICK_REQUEST: {
+      const { requests } = state;
+      const clickedRequest = requests.get(action.id);
       return {
         ...state,
-        requests: mapSet(requests, newRequest.id, newRequest),
-        selectedId: newRequest.id,
-        preselectedId: selectedId,
+        clickedRequest,
       };
     }
 
     // Removing temporary cloned request (created for re-send, but canceled).
     case REMOVE_SELECTED_CUSTOM_REQUEST: {
       return closeCustomRequest(state);
     }
 
@@ -189,16 +180,48 @@ function requestsReducer(state = Request
 
     default:
       return state;
   }
 }
 
 // Helpers
 
+function cloneRequest(state, id) {
+  const { requests } = state;
+
+  if (!id) {
+    return state;
+  }
+
+  const clonedRequest = requests.get(id);
+  if (!clonedRequest) {
+    return state;
+  }
+
+  const newRequest = {
+    id: clonedRequest.id + "-clone",
+    method: clonedRequest.method,
+    cause: clonedRequest.cause,
+    url: clonedRequest.url,
+    urlDetails: clonedRequest.urlDetails,
+    requestHeaders: clonedRequest.requestHeaders,
+    requestPostData: clonedRequest.requestPostData,
+    requestPostDataAvailable: clonedRequest.requestPostDataAvailable,
+    isCustom: true,
+  };
+
+  return {
+    ...state,
+    requests: mapSet(requests, newRequest.id, newRequest),
+    selectedId: newRequest.id,
+    preselectedId: id,
+  };
+}
+
 /**
  * Remove the currently selected custom request.
  */
 function closeCustomRequest(state) {
   const { requests, selectedId, preselectedId } = state;
 
   if (!selectedId) {
     return state;
--- a/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
+++ b/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
@@ -20,17 +20,17 @@ loader.lazyRequireGetter(this, "copyStri
 loader.lazyRequireGetter(this, "showMenu", "devtools/client/shared/components/menu/utils", true);
 loader.lazyRequireGetter(this, "HarMenuUtils", "devtools/client/netmonitor/src/har/har-menu-utils", true);
 
 class RequestListContextMenu {
   constructor(props) {
     this.props = props;
   }
 
-  open(event, selectedRequest, requests) {
+  open(event, clickedRequest, requests) {
     const {
       id,
       blockedReason,
       isCustom,
       formDataSections,
       method,
       mimeType,
       httpVersion,
@@ -38,115 +38,116 @@ class RequestListContextMenu {
       requestHeadersAvailable,
       requestPostData,
       requestPostDataAvailable,
       responseHeaders,
       responseHeadersAvailable,
       responseContent,
       responseContentAvailable,
       url,
-    } = selectedRequest;
+    } = clickedRequest;
     const {
       blockSelectedRequestURL,
       connector,
-      cloneSelectedRequest,
+      cloneRequest,
+      openDetailsPanelTab,
       sendCustomRequest,
       openStatistics,
       openRequestInTab,
       unblockSelectedRequestURL,
     } = this.props;
     const menu = [];
     const copySubmenu = [];
 
     copySubmenu.push({
       id: "request-list-context-copy-url",
       label: L10N.getStr("netmonitor.context.copyUrl"),
       accesskey: L10N.getStr("netmonitor.context.copyUrl.accesskey"),
-      visible: !!selectedRequest,
+      visible: !!clickedRequest,
       click: () => this.copyUrl(url),
     });
 
     copySubmenu.push({
       id: "request-list-context-copy-url-params",
       label: L10N.getStr("netmonitor.context.copyUrlParams"),
       accesskey: L10N.getStr("netmonitor.context.copyUrlParams.accesskey"),
-      visible: !!(selectedRequest && getUrlQuery(url)),
+      visible: !!(clickedRequest && getUrlQuery(url)),
       click: () => this.copyUrlParams(url),
     });
 
     copySubmenu.push({
       id: "request-list-context-copy-post-data",
       label: L10N.getFormatStr("netmonitor.context.copyRequestData", method),
       accesskey: L10N.getStr("netmonitor.context.copyRequestData.accesskey"),
       // Menu item will be visible even if data hasn't arrived, so we need to check
       // *Available property and then fetch data lazily once user triggers the action.
-      visible: !!(selectedRequest && (requestPostDataAvailable || requestPostData)),
+      visible: !!(clickedRequest && (requestPostDataAvailable || requestPostData)),
       click: () => this.copyPostData(id, formDataSections, requestPostData),
     });
 
     copySubmenu.push({
       id: "request-list-context-copy-as-curl",
       label: L10N.getStr("netmonitor.context.copyAsCurl"),
       accesskey: L10N.getStr("netmonitor.context.copyAsCurl.accesskey"),
       // Menu item will be visible even if data hasn't arrived, so we need to check
       // *Available property and then fetch data lazily once user triggers the action.
-      visible: !!selectedRequest,
+      visible: !!clickedRequest,
       click: () =>
         this.copyAsCurl(id, url, method, httpVersion, requestHeaders, requestPostData),
     });
 
     copySubmenu.push({
       id: "request-list-context-copy-as-fetch",
       label: L10N.getStr("netmonitor.context.copyAsFetch"),
       accesskey: L10N.getStr("netmonitor.context.copyAsFetch.accesskey"),
-      visible: !!selectedRequest,
+      visible: !!clickedRequest,
       click: () =>
         this.copyAsFetch(id, url, method, requestHeaders, requestPostData),
     });
 
     copySubmenu.push({
       type: "separator",
       visible: copySubmenu.slice(0, 4).some((subMenu) => subMenu.visible),
     });
 
     copySubmenu.push({
       id: "request-list-context-copy-request-headers",
       label: L10N.getStr("netmonitor.context.copyRequestHeaders"),
       accesskey: L10N.getStr("netmonitor.context.copyRequestHeaders.accesskey"),
       // Menu item will be visible even if data hasn't arrived, so we need to check
       // *Available property and then fetch data lazily once user triggers the action.
-      visible: !!(selectedRequest && (requestHeadersAvailable || requestHeaders)),
+      visible: !!(clickedRequest && (requestHeadersAvailable || requestHeaders)),
       click: () => this.copyRequestHeaders(id, requestHeaders),
     });
 
     copySubmenu.push({
       id: "response-list-context-copy-response-headers",
       label: L10N.getStr("netmonitor.context.copyResponseHeaders"),
       accesskey: L10N.getStr("netmonitor.context.copyResponseHeaders.accesskey"),
       // Menu item will be visible even if data hasn't arrived, so we need to check
       // *Available property and then fetch data lazily once user triggers the action.
-      visible: !!(selectedRequest && (responseHeadersAvailable || responseHeaders)),
+      visible: !!(clickedRequest && (responseHeadersAvailable || responseHeaders)),
       click: () => this.copyResponseHeaders(id, responseHeaders),
     });
 
     copySubmenu.push({
       id: "request-list-context-copy-response",
       label: L10N.getStr("netmonitor.context.copyResponse"),
       accesskey: L10N.getStr("netmonitor.context.copyResponse.accesskey"),
       // Menu item will be visible even if data hasn't arrived, so we need to check
       // *Available property and then fetch data lazily once user triggers the action.
-      visible: !!(selectedRequest && (responseContentAvailable || responseContent)),
+      visible: !!(clickedRequest && (responseContentAvailable || responseContent)),
       click: () => this.copyResponse(id, responseContent),
     });
 
     copySubmenu.push({
       id: "request-list-context-copy-image-as-data-uri",
       label: L10N.getStr("netmonitor.context.copyImageAsDataUri"),
       accesskey: L10N.getStr("netmonitor.context.copyImageAsDataUri.accesskey"),
-      visible: !!(selectedRequest && (responseContentAvailable || responseContent) &&
+      visible: !!(clickedRequest && (responseContentAvailable || responseContent) &&
         mimeType && mimeType.includes("image/")),
       click: () => this.copyImageAsDataUri(id, mimeType, responseContent),
     });
 
     copySubmenu.push({
       type: "separator",
       visible: copySubmenu.slice(5, 9).some((subMenu) => subMenu.visible),
     });
@@ -157,98 +158,106 @@ class RequestListContextMenu {
       accesskey: L10N.getStr("netmonitor.context.copyAllAsHar.accesskey"),
       visible: requests.length > 0,
       click: () => HarMenuUtils.copyAllAsHar(requests, connector),
     });
 
     menu.push({
       label: L10N.getStr("netmonitor.context.copy"),
       accesskey: L10N.getStr("netmonitor.context.copy.accesskey"),
-      visible: !!selectedRequest,
+      visible: !!clickedRequest,
       submenu: copySubmenu,
     });
 
     menu.push({
       id: "request-list-context-save-all-as-har",
       label: L10N.getStr("netmonitor.context.saveAllAsHar"),
       accesskey: L10N.getStr("netmonitor.context.saveAllAsHar.accesskey"),
       visible: requests.length > 0,
       click: () => HarMenuUtils.saveAllAsHar(requests, connector),
     });
 
     menu.push({
       id: "request-list-context-save-image-as",
       label: L10N.getStr("netmonitor.context.saveImageAs"),
       accesskey: L10N.getStr("netmonitor.context.saveImageAs.accesskey"),
-      visible: !!(selectedRequest && (responseContentAvailable || responseContent) &&
+      visible: !!(clickedRequest && (responseContentAvailable || responseContent) &&
         mimeType && mimeType.includes("image/")),
       click: () => this.saveImageAs(id, url, responseContent),
     });
 
     menu.push({
       type: "separator",
       visible: copySubmenu.slice(10, 14).some((subMenu) => subMenu.visible),
     });
 
     menu.push({
       id: "request-list-context-resend-only",
       label: L10N.getStr("netmonitor.context.resend.label"),
       accesskey: L10N.getStr("netmonitor.context.resend.accesskey"),
-      visible: !!(selectedRequest && !isCustom),
-      click: sendCustomRequest,
+      visible: !!(clickedRequest && !isCustom),
+      click: () => {
+        cloneRequest(id);
+        sendCustomRequest();
+      },
     });
 
     menu.push({
       id: "request-list-context-resend",
       label: L10N.getStr("netmonitor.context.editAndResend"),
       accesskey: L10N.getStr("netmonitor.context.editAndResend.accesskey"),
-      visible: !!(selectedRequest && !isCustom),
-      click: cloneSelectedRequest,
+      visible: !!(clickedRequest && !isCustom),
+      click: () => {
+        this.fetchRequestHeaders(id).then(() => {
+          cloneRequest(id);
+          openDetailsPanelTab();
+        });
+      },
     });
 
     menu.push({
       id: "request-list-context-block-url",
       label: L10N.getStr("netmonitor.context.blockURL"),
-      visible: !!(selectedRequest && !blockedReason),
+      visible: !!(clickedRequest && !blockedReason),
       click: blockSelectedRequestURL,
     });
 
     menu.push({
       id: "request-list-context-unblock-url",
       label: L10N.getStr("netmonitor.context.unblockURL"),
-      visible: !!(selectedRequest && blockedReason),
+      visible: !!(clickedRequest && blockedReason),
       click: unblockSelectedRequestURL,
     });
 
     menu.push({
       type: "separator",
       visible: copySubmenu.slice(15, 16).some((subMenu) => subMenu.visible),
     });
 
     menu.push({
       id: "request-list-context-newtab",
       label: L10N.getStr("netmonitor.context.newTab"),
       accesskey: L10N.getStr("netmonitor.context.newTab.accesskey"),
-      visible: !!selectedRequest,
+      visible: !!clickedRequest,
       click: () => openRequestInTab(id, url, requestHeaders, requestPostData),
     });
 
     menu.push({
       id: "request-list-context-open-in-debugger",
       label: L10N.getStr("netmonitor.context.openInDebugger"),
       accesskey: L10N.getStr("netmonitor.context.openInDebugger.accesskey"),
-      visible: !!(selectedRequest && mimeType && mimeType.includes("javascript")),
+      visible: !!(clickedRequest && mimeType && mimeType.includes("javascript")),
       click: () => this.openInDebugger(url),
     });
 
     menu.push({
       id: "request-list-context-open-in-style-editor",
       label: L10N.getStr("netmonitor.context.openInStyleEditor"),
       accesskey: L10N.getStr("netmonitor.context.openInStyleEditor.accesskey"),
-      visible: !!(selectedRequest &&
+      visible: !!(clickedRequest &&
         Services.prefs.getBoolPref("devtools.styleeditor.enabled") &&
         mimeType && mimeType.includes("css")),
       click: () => this.openInStyleEditor(url),
     });
 
     menu.push({
       id: "request-list-context-perf",
       label: L10N.getStr("netmonitor.context.perfTools"),
@@ -260,17 +269,17 @@ class RequestListContextMenu {
     menu.push({
       type: "separator",
     });
 
     menu.push({
       id: "request-list-context-use-as-fetch",
       label: L10N.getStr("netmonitor.context.useAsFetch"),
       accesskey: L10N.getStr("netmonitor.context.useAsFetch.accesskey"),
-      visible: !!selectedRequest,
+      visible: !!clickedRequest,
       click: () =>
         this.useAsFetch(id, url, method, requestHeaders, requestPostData),
     });
 
     showMenu(menu, {
       screenX: event.screenX,
       screenY: event.screenY,
     });
@@ -531,11 +540,15 @@ class RequestListContextMenu {
    * Copy response data as a string.
    */
   async copyResponse(id, responseContent) {
     responseContent = responseContent ||
       await this.props.connector.requestData(id, "responseContent");
 
     copyString(responseContent.content.text);
   }
+
+  async fetchRequestHeaders(id) {
+    await this.props.connector.requestData(id, "requestHeaders");
+  }
 }
 
 module.exports = RequestListContextMenu;
--- a/devtools/client/netmonitor/test/browser_net_edit_resend_cancel.js
+++ b/devtools/client/netmonitor/test/browser_net_edit_resend_cancel.js
@@ -29,20 +29,21 @@ add_task(async function() {
   EventUtils.sendMouseEvent({ type: "mousedown" }, firstRequest);
   await waitForHeaders;
   EventUtils.sendMouseEvent({ type: "contextmenu" }, firstRequest);
   const firstRequestState = getSelectedRequest(store.getState());
   const contextResend = getContextMenuItem(monitor, "request-list-context-resend");
   contextResend.click();
 
   // Waits for "Edit & Resend" panel to appear > New request "Cancel"
+  await waitUntil(() => document.querySelector(".custom-request-panel"));
   document.querySelector("#custom-request-close-button").click();
   const finalRequestState = getSelectedRequest(store.getState());
 
-  ok(firstRequestState === finalRequestState,
+  ok(firstRequestState.id === finalRequestState.id,
     "Original request is selected after cancel button is clicked"
   );
 
   ok(document.querySelector(".headers-overview") !== null,
     "Request is selected and headers panel is visible"
   );
 
   return teardown(monitor);
--- a/devtools/client/netmonitor/test/browser_net_edit_resend_with_filtering.js
+++ b/devtools/client/netmonitor/test/browser_net_edit_resend_with_filtering.js
@@ -6,49 +6,58 @@
 "use strict";
 
 /**
  * Tests if resending a XHR request while filtering XHR displays
  * the correct requests
  */
 add_task(async function() {
   const { tab, monitor } = await initNetMonitor(POST_RAW_URL);
-
   const { document, store, windowRequire } = monitor.panelWin;
+  const {
+    getSelectedRequest,
+  } = windowRequire("devtools/client/netmonitor/src/selectors/index");
   const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
   store.dispatch(Actions.batchEnable(false));
 
   // Execute XHR request and filter by XHR
   await performRequests(monitor, tab, 1);
   document.querySelector(".requests-list-filter-xhr-button").click();
 
   // Confirm XHR request and click it
   const xhrRequestItem = document.querySelectorAll(".request-list-item")[0];
   EventUtils.sendMouseEvent({ type: "mousedown" }, xhrRequestItem);
-
-  const {
-    getSelectedRequest,
-  } = windowRequire("devtools/client/netmonitor/src/selectors/index");
+  const waitForHeaders = waitUntil(() => document.querySelector(".headers-overview"));
+  await waitForHeaders;
   const firstRequest = getSelectedRequest(store.getState());
 
   // Open context menu and execute "Edit & Resend".
   EventUtils.sendMouseEvent({ type: "contextmenu" }, xhrRequestItem);
   getContextMenuItem(monitor, "request-list-context-resend").click();
 
-  // Click Resend
+  // Wait for "Edit & Resend" panel to appear
   await waitUntil(() => document.querySelector("#custom-request-send-button"));
+
+  // Select the temporary clone-request and check its ID
+  // it should be calculated from the original request
+  // by appending '-clone' suffix.
+  document.querySelectorAll(".request-list-item")[1].click();
+  const cloneRequest = getSelectedRequest(store.getState());
+
+  ok(cloneRequest.id.replace(/-clone$/, "") == firstRequest.id,
+    "The second XHR request is a clone of the first");
+
+  // Click the "Send" button and wait till the new request appears in the list
   document.querySelector("#custom-request-send-button").click();
+  await waitForNetworkEvents(monitor, 1);
 
   // Filtering by "other" so the resent request is visible after completion
   document.querySelector(".requests-list-filter-other-button").click();
 
-  // Select the cloned request
+  // Select the new (cloned) request
   document.querySelectorAll(".request-list-item")[0].click();
   const resendRequest = getSelectedRequest(store.getState());
 
   ok(resendRequest.id !== firstRequest.id,
     "The second XHR request was made and is unique");
 
-  ok(resendRequest.id.replace(/-clone$/, "") == firstRequest.id,
-    "The second XHR request is a clone of the first");
-
   return teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_edit_resend_xhr.js
+++ b/devtools/client/netmonitor/test/browser_net_edit_resend_xhr.js
@@ -26,18 +26,22 @@ add_task(async function() {
   const { getSelectedRequest }
   = windowRequire("devtools/client/netmonitor/src/selectors/index");
   const original = getSelectedRequest(store.getState());
 
   // Context Menu > "Edit & Resend"
   EventUtils.sendMouseEvent({ type: "contextmenu" }, xhrRequest);
   getContextMenuItem(monitor, "request-list-context-resend").click();
 
-  // Waits for "Edit & Resend" panel to appear > New request "Send"
+  // 1) Wait for "Edit & Resend" panel to appear
+  // 2) Click the "Send" button
+  // 3) Wait till the new request appears in the list
+  await waitUntil(() => document.querySelector(".custom-request-panel"));
   document.querySelector("#custom-request-send-button").click();
+  await waitForNetworkEvents(monitor, 1);
 
   // Selects cloned request
   const clonedRequest = document.querySelectorAll(".request-list-item")[1];
   EventUtils.sendMouseEvent({ type: "mousedown" }, clonedRequest);
   const cloned = getSelectedRequest(store.getState());
 
   // Compares if the requests have the same cause type (XHR)
   ok(original.cause.type === cloned.cause.type,
--- a/devtools/client/responsive.html/test/browser/browser.ini
+++ b/devtools/client/responsive.html/test/browser/browser.ini
@@ -69,15 +69,17 @@ skip-if = true # Bug 1413765
 [browser_toolbox_rule_view.js]
 [browser_toolbox_rule_view_reload.js]
 skip-if = os == "linux" || os == "mac" # Bug 1498336
 [browser_toolbox_swap_browsers.js]
 [browser_toolbox_swap_inspector.js]
 [browser_touch_device.js]
 [browser_touch_does_not_trigger_hover_states.js]
 [browser_touch_simulation.js]
+[browser_typeahead_find.js]
+fail-if = true # Bug 1547783
 [browser_user_agent_input.js]
 [browser_viewport_basics.js]
 [browser_viewport_resizing_fixed_width.js]
 [browser_viewport_resizing_fixed_width_and_zoom.js]
 [browser_viewport_resizing_scrollbar.js]
 fail-if = true # Bug 1547101
 [browser_window_close.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/browser/browser_typeahead_find.js
@@ -0,0 +1,75 @@
+/* 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 test attempts to exercise automatic triggering of typeaheadfind
+ * within RDM content. It does this by simulating keystrokes while
+ * various elements in the RDM content are focused.
+
+ * The test currently does not work due to two reasons:
+ * 1) The simulated key events do not behave the same as real key events.
+ *    Specifically, when this test is run on an un-patched build, it
+ *    fails to trigger typeaheadfind when the input element is focused.
+ * 2) In order to test that typeahead find has or has not occurred, this
+ *    test checks the values held by the findBar associated with the
+ *    tab that contains the RDM pane. That findBar has no values, though
+ *    they appear correctly when the steps are run interactively. This
+ *    indicates that some other findBar is receiving the typeaheadfind
+ *    characters.
+ */
+
+const TEST_URL = "data:text/html;charset=utf-8," +
+  "<body id=\"body\"><input id=\"input\" type=\"text\"/><p>text</body>";
+
+addRDMTask(TEST_URL, async function({ ui, manager }) {
+  // Turn on the pref that allows meta viewport support.
+  await pushPref("accessibility.typeaheadfind", true);
+
+  const store = ui.toolWindow.store;
+
+  // Wait until the viewport has been added.
+  await waitUntilState(store, state => state.viewports.length == 1);
+
+  const browser = ui.getViewportBrowser();
+
+  info("--- Starting test output ---");
+
+  const expected = [
+    {
+      id: "body",
+      findTriggered: true,
+    },
+    {
+      id: "input",
+      findTriggered: false,
+    },
+  ];
+
+  for (const e of expected) {
+    await ContentTask.spawn(browser, { e }, async function(args) {
+      const { e: values } = args;
+      const element = content.document.getElementById(values.id);
+
+      // Set focus on the desired element.
+      element.focus();
+    });
+
+    // Press the 'T' key and see if find is triggered.
+    await BrowserTestUtils.synthesizeKey("t", {}, browser);
+
+    const findBar = await gBrowser.getFindBar();
+
+    const findIsTriggered = (findBar._findField.value == "t");
+    is(findIsTriggered, e.findTriggered, "Text input with focused element " + e.id +
+      " should " + (e.findTriggered ? "" : "not ") + "trigger find.");
+    findBar._findField.value = "";
+
+    await ContentTask.spawn(browser, {}, async function() {
+      // Clear focus.
+      content.document.activeElement.blur();
+    });
+  }
+});
--- a/devtools/client/webconsole/test/mocha-test-setup.js
+++ b/devtools/client/webconsole/test/mocha-test-setup.js
@@ -62,16 +62,22 @@ global.isWorker = false;
 global.indexedDB = {open: () => ({})};
 
 // URLSearchParams was added to the global object in Node 10.0.0. To not cause any issue
 // with prior versions, we add it to the global object if it is not defined there.
 if (!global.URLSearchParams) {
   global.URLSearchParams = require("url").URLSearchParams;
 }
 
+// Mock ChromeUtils.
+global.ChromeUtils = {
+  import: () => {},
+  defineModuleGetter: () => {},
+};
+
 // Point to vendored-in files and mocks when needed.
 const requireHacker = require("require-hacker");
 requireHacker.global_hook("default", (path, module) => {
   switch (path) {
     // For Enzyme
     case "react-dom":
       return getModule("devtools/client/shared/vendor/react-dom");
     case "react-dom/server":
@@ -81,18 +87,16 @@ requireHacker.global_hook("default", (pa
     case "react-redux":
       return getModule("devtools/client/shared/vendor/react-redux");
     // Use react-dev. This would be handled by browserLoader in Firefox.
     case "react":
     case "devtools/client/shared/vendor/react":
       return getModule("devtools/client/shared/vendor/react-dev");
     case "chrome":
       return `module.exports = { Cc: {}, Ci: {}, Cu: {} }`;
-    case "ChromeUtils":
-      return `module.exports = { import: () => {} }`;
   }
 
   // Some modules depend on Chrome APIs which don't work in mocha. When such a module
   // is required, replace it with a mock version.
   switch (path) {
     case "devtools/shared/l10n":
       return getModule(
         "devtools/client/webconsole/test/fixtures/LocalizationHelper");
--- a/devtools/shared/l10n.js
+++ b/devtools/shared/l10n.js
@@ -85,35 +85,43 @@ function getProperties(url) {
   return propertiesMap[url];
 }
 
 /**
  * Localization convenience methods.
  *
  * @param string stringBundleName
  *        The desired string bundle's name.
+ * @param boolean strict
+ *        (legacy) pass true to force the helper to throw if the l10n id cannot be found.
  */
-function LocalizationHelper(stringBundleName) {
+function LocalizationHelper(stringBundleName, strict = false) {
   this.stringBundleName = stringBundleName;
+  this.strict = strict;
 }
 
 LocalizationHelper.prototype = {
   /**
    * L10N shortcut function.
    *
    * @param string name
    * @return string
    */
   getStr: function(name) {
     const properties = getProperties(this.stringBundleName);
     if (name in properties) {
       return properties[name];
     }
 
-    throw new Error("No localization found for [" + name + "]");
+    if (this.strict) {
+      throw new Error("No localization found for [" + name + "]");
+    }
+
+    console.error("No localization found for [" + name + "]");
+    return name;
   },
 
   /**
    * L10N shortcut function.
    *
    * @param string name
    * @param array args
    * @return string
@@ -236,17 +244,19 @@ function localizeMarkup(root) {
 const sharedL10N = new LocalizationHelper("devtools/shared/locales/shared.properties");
 
 /**
  * A helper for having the same interface as LocalizationHelper, but for more
  * than one file. Useful for abstracting l10n string locations.
  */
 function MultiLocalizationHelper(...stringBundleNames) {
   const instances = stringBundleNames.map(bundle => {
-    return new LocalizationHelper(bundle);
+    // Use strict = true because the MultiLocalizationHelper logic relies on try/catch
+    // around the underlying LocalizationHelper APIs.
+    return new LocalizationHelper(bundle, true);
   });
 
   // Get all function members of the LocalizationHelper class, making sure we're
   // not executing any potential getters while doing so, and wrap all the
   // methods we've found to work on all given string bundles.
   Object.getOwnPropertyNames(LocalizationHelper.prototype)
     .map(name => ({
       name: name,
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -1970,16 +1970,22 @@ class Document : public nsINode,
   OrientationType CurrentOrientationType() const {
     return mCurrentOrientationType;
   }
   void SetOrientationPendingPromise(Promise* aPromise);
   Promise* GetOrientationPendingPromise() const {
     return mOrientationPendingPromise;
   }
 
+  void SetRDMPaneOrientation(OrientationType aType, uint16_t aAngle) {
+    if (mInRDMPane) {
+      SetCurrentOrientation(aType, aAngle);
+    }
+  }
+
   //----------------------------------------------------------------------
 
   // Document notification API's
 
   /**
    * Add a new observer of document change notifications. Whenever
    * content is changed, appended, inserted or removed the observers are
    * informed.  An observer that is already observing the document must
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -1318,21 +1318,22 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Fr
     }
 
     if (tmp->IsHTMLElement() || tmp->IsSVGElement()) {
       nsStaticAtom* const* props =
           Element::HTMLSVGPropertiesToTraverseAndUnlink();
       for (uint32_t i = 0; props[i]; ++i) {
         tmp->DeleteProperty(props[i]);
       }
-      if (tmp->MayHaveAnimations()) {
-        nsAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
-        for (uint32_t i = 0; effectProps[i]; ++i) {
-          tmp->DeleteProperty(effectProps[i]);
-        }
+    }
+
+    if (tmp->MayHaveAnimations()) {
+      nsAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
+      for (uint32_t i = 0; effectProps[i]; ++i) {
+        tmp->DeleteProperty(effectProps[i]);
       }
     }
   }
 
   // Unlink child content (and unbind our subtree).
   if (tmp->UnoptimizableCCNode() || !nsCCUncollectableMarker::sGeneration) {
     // Don't allow script to run while we're unbinding everything.
     nsAutoScriptBlocker scriptBlocker;
@@ -1833,24 +1834,24 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
     if (tmp->IsHTMLElement() || tmp->IsSVGElement()) {
       nsStaticAtom* const* props =
           Element::HTMLSVGPropertiesToTraverseAndUnlink();
       for (uint32_t i = 0; props[i]; ++i) {
         nsISupports* property =
             static_cast<nsISupports*>(tmp->GetProperty(props[i]));
         cb.NoteXPCOMChild(property);
       }
-      if (tmp->MayHaveAnimations()) {
-        nsAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
-        for (uint32_t i = 0; effectProps[i]; ++i) {
-          EffectSet* effectSet =
-              static_cast<EffectSet*>(tmp->GetProperty(effectProps[i]));
-          if (effectSet) {
-            effectSet->Traverse(cb);
-          }
+    }
+    if (tmp->MayHaveAnimations()) {
+      nsAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
+      for (uint32_t i = 0; effectProps[i]; ++i) {
+        EffectSet* effectSet =
+            static_cast<EffectSet*>(tmp->GetProperty(effectProps[i]));
+        if (effectSet) {
+          effectSet->Traverse(cb);
         }
       }
     }
   }
 
   // Traverse attribute names.
   if (tmp->IsElement()) {
     Element* element = tmp->AsElement();
--- a/dom/base/nsCopySupport.cpp
+++ b/dom/base/nsCopySupport.cpp
@@ -77,256 +77,368 @@ static nsresult AppendDOMNode(nsITransfe
 
 #ifdef XP_WIN
 // copy image as file promise onto the transferable
 static nsresult AppendImagePromise(nsITransferable* aTransferable,
                                    imgIRequest* aImgRequest,
                                    nsIImageLoadingContent* aImageElement);
 #endif
 
-// Helper used for HTMLCopy and GetTransferableForSelection since both routines
-// share common code.
-static nsresult SelectionCopyHelper(Selection* aSel, Document* aDoc,
-                                    bool doPutOnClipboard, int16_t aClipboardID,
-                                    uint32_t aFlags,
-                                    nsITransferable** aTransferable) {
-  // Clear the output parameter for the transferable, if provided.
-  if (aTransferable) {
-    *aTransferable = nullptr;
-  }
-
-  nsresult rv;
-
-  nsCOMPtr<nsIDocumentEncoder> docEncoder;
-  docEncoder = do_createHTMLCopyEncoder();
-
+static nsresult EncodeForTextUnicode(nsIDocumentEncoder& aEncoder,
+                                     Document& aDocument, Selection* aSelection,
+                                     uint32_t aAdditionalEncoderFlags,
+                                     bool& aEncodedAsTextHTMLResult,
+                                     nsAutoString& aSerializationResult) {
   // note that we assign text/unicode as mime type, but in fact
   // nsHTMLCopyEncoder ignore it and use text/html or text/plain depending where
   // the selection is. if it is a selection into input/textarea element or in a
   // html content with pre-wrap style : text/plain. Otherwise text/html. see
   // nsHTMLCopyEncoder::SetSelection
   nsAutoString mimeType;
   mimeType.AssignLiteral(kUnicodeMime);
 
   // Do the first and potentially trial encoding as preformatted and raw.
-  uint32_t flags = aFlags | nsIDocumentEncoder::OutputPreformatted |
+  uint32_t flags = aAdditionalEncoderFlags |
+                   nsIDocumentEncoder::OutputPreformatted |
                    nsIDocumentEncoder::OutputRaw |
                    nsIDocumentEncoder::OutputForPlainTextClipboardCopy;
 
-  rv = docEncoder->Init(aDoc, mimeType, flags);
+  nsresult rv = aEncoder.Init(&aDocument, mimeType, flags);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = docEncoder->SetSelection(aSel);
+  rv = aEncoder.SetSelection(aSelection);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // SetSelection set the mime type to text/plain if the selection is inside a
   // text widget.
-  rv = docEncoder->GetMimeType(mimeType);
+  rv = aEncoder.GetMimeType(mimeType);
   NS_ENSURE_SUCCESS(rv, rv);
   bool selForcedTextPlain = mimeType.EqualsLiteral(kTextMime);
 
   nsAutoString buf;
-  rv = docEncoder->EncodeToString(buf);
+  rv = aEncoder.EncodeToString(buf);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = docEncoder->GetMimeType(mimeType);
+  rv = aEncoder.GetMimeType(mimeType);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!selForcedTextPlain && mimeType.EqualsLiteral(kTextMime)) {
     // SetSelection and EncodeToString use this case to signal that text/plain
     // was forced because the document is either not an nsIHTMLDocument or it's
     // XHTML.  We want to pretty print XHTML but not non-nsIHTMLDocuments.
-    nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(aDoc);
+    nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(&aDocument);
     if (!htmlDoc) {
       selForcedTextPlain = true;
     }
   }
 
   // The mime type is ultimately text/html if the encoder successfully encoded
   // the selection as text/html.
-  bool encodedTextHTML = mimeType.EqualsLiteral(kHTMLMime);
+  aEncodedAsTextHTMLResult = mimeType.EqualsLiteral(kHTMLMime);
 
-  // First, prepare the text/plain clipboard flavor.
-  nsAutoString textPlainBuf;
   if (selForcedTextPlain) {
     // Nothing to do.  buf contains the final, preformatted, raw text/plain.
-    textPlainBuf.Assign(buf);
+    aSerializationResult.Assign(buf);
   } else {
     // Redo the encoding, but this time use pretty printing.
-    flags = nsIDocumentEncoder::OutputSelectionOnly |
-            nsIDocumentEncoder::OutputAbsoluteLinks |
-            nsIDocumentEncoder::SkipInvisibleContent |
-            nsIDocumentEncoder::OutputDropInvisibleBreak |
-            (aFlags & (nsIDocumentEncoder::OutputNoScriptContent |
-                       nsIDocumentEncoder::OutputRubyAnnotation));
+    flags =
+        nsIDocumentEncoder::OutputSelectionOnly |
+        nsIDocumentEncoder::OutputAbsoluteLinks |
+        nsIDocumentEncoder::SkipInvisibleContent |
+        nsIDocumentEncoder::OutputDropInvisibleBreak |
+        (aAdditionalEncoderFlags & (nsIDocumentEncoder::OutputNoScriptContent |
+                                    nsIDocumentEncoder::OutputRubyAnnotation));
 
     mimeType.AssignLiteral(kTextMime);
-    rv = docEncoder->Init(aDoc, mimeType, flags);
+    rv = aEncoder.Init(&aDocument, mimeType, flags);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = docEncoder->SetSelection(aSel);
+    rv = aEncoder.SetSelection(aSelection);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = docEncoder->EncodeToString(textPlainBuf);
+    rv = aEncoder.EncodeToString(aSerializationResult);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  // Second, prepare the text/html flavor.
-  nsAutoString textHTMLBuf;
-  nsAutoString htmlParentsBuf;
-  nsAutoString htmlInfoBuf;
-  if (encodedTextHTML) {
+  return rv;
+}
+
+static nsresult EncodeAsTextHTMLWithContext(
+    nsIDocumentEncoder& aEncoder, Document& aDocument, Selection* aSelection,
+    uint32_t aEncoderFlags, nsAutoString& aTextHTMLEncodingResult,
+    nsAutoString& aHTMLParentsBufResult, nsAutoString& aHTMLInfoBufResult) {
+  nsAutoString mimeType;
+  mimeType.AssignLiteral(kHTMLMime);
+  nsresult rv = aEncoder.Init(&aDocument, mimeType, aEncoderFlags);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aEncoder.SetSelection(aSelection);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = aEncoder.EncodeToStringWithContext(
+      aHTMLParentsBufResult, aHTMLInfoBufResult, aTextHTMLEncodingResult);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return rv;
+}
+
+struct EncodedDocumentWithContext {
+  // When determening `mSerializationForTextUnicode`, `text/unicode` is passed
+  // as mime type to the encoder. It uses this as a switch to decide whether to
+  // encode the document as `text/html` or `text/plain`. It  is `true` iff
+  // `text/html` was used.
+  bool mUnicodeEncodingIsTextHTML;
+
+  // The serialized document when encoding the document with `text/unicode`. See
+  // comment of `mUnicodeEncodingIsTextHTML`.
+  nsAutoString mSerializationForTextUnicode;
+
+  // When `mUnicodeEncodingIsTextHTML` is true, this is the serialized document
+  // using `text/html`. Its value may differ from `mSerializationForTextHTML`,
+  // because different flags were passed to the encoder.
+  nsAutoString mSerializationForTextHTML;
+
+  // When `mUnicodeEncodingIsTextHTML` is true, this contains the serialized
+  // ancestor elements.
+  nsAutoString mHTMLContextBuffer;
+
+  // When `mUnicodeEncodingIsTextHTML` is true, this contains numbers
+  // identifying where in the context the serialization came from.
+  nsAutoString mHTMLInfoBuffer;
+};
+
+/**
+ * @param aSelection Can be nullptr.
+ * @param aAdditionalEncoderFlags nsIDocumentEncoder flags.
+ */
+static nsresult EncodeDocumentWithContext(
+    Document& aDocument, Selection* aSelection,
+    uint32_t aAdditionalEncoderFlags,
+    EncodedDocumentWithContext& aEncodedDocumentWithContext) {
+  nsCOMPtr<nsIDocumentEncoder> docEncoder = do_createHTMLCopyEncoder();
+
+  bool unicodeEncodingIsTextHTML{false};
+  nsAutoString serializationForTextUnicode;
+  nsresult rv = EncodeForTextUnicode(
+      *docEncoder, aDocument, aSelection, aAdditionalEncoderFlags,
+      unicodeEncodingIsTextHTML, serializationForTextUnicode);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoString serializationForTextHTML;
+  nsAutoString htmlContextBuffer;
+  nsAutoString htmlInfoBuffer;
+  if (unicodeEncodingIsTextHTML) {
     // Redo the encoding, but this time use the passed-in flags.
     // Don't allow wrapping of CJK strings.
-    mimeType.AssignLiteral(kHTMLMime);
-    rv = docEncoder->Init(
-        aDoc, mimeType,
-        aFlags | nsIDocumentEncoder::OutputDisallowLineBreaking);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = docEncoder->SetSelection(aSel);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = docEncoder->EncodeToStringWithContext(htmlParentsBuf, htmlInfoBuf,
-                                               textHTMLBuf);
+    rv = EncodeAsTextHTMLWithContext(
+        *docEncoder, aDocument, aSelection,
+        aAdditionalEncoderFlags |
+            nsIDocumentEncoder::OutputDisallowLineBreaking,
+        serializationForTextHTML, htmlContextBuffer, htmlInfoBuffer);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  // Get the Clipboard
-  nsCOMPtr<nsIClipboard> clipboard;
-  if (doPutOnClipboard) {
-    clipboard = do_GetService(kCClipboardCID, &rv);
-    if (NS_FAILED(rv)) return rv;
+  aEncodedDocumentWithContext = {
+      unicodeEncodingIsTextHTML, std::move(serializationForTextUnicode),
+      std::move(serializationForTextHTML), std::move(htmlContextBuffer),
+      std::move(htmlInfoBuffer)};
+
+  return rv;
+}
+
+static nsresult CreateTransferable(
+    const EncodedDocumentWithContext& aEncodedDocumentWithContext,
+    Document& aDocument, nsCOMPtr<nsITransferable>& aTransferable) {
+  nsresult rv = NS_OK;
+
+  aTransferable = do_CreateInstance(kCTransferableCID);
+  NS_ENSURE_TRUE(aTransferable, NS_ERROR_NULL_POINTER);
+
+  aTransferable->Init(aDocument.GetLoadContext());
+  if (aEncodedDocumentWithContext.mUnicodeEncodingIsTextHTML) {
+    // Set up a format converter so that clipboard flavor queries work.
+    // This converter isn't really used for conversions.
+    nsCOMPtr<nsIFormatConverter> htmlConverter =
+        do_CreateInstance(kHTMLConverterCID);
+    aTransferable->SetConverter(htmlConverter);
+
+    if (!aEncodedDocumentWithContext.mSerializationForTextHTML.IsEmpty()) {
+      // Add the html DataFlavor to the transferable
+      rv = AppendString(aTransferable,
+                        aEncodedDocumentWithContext.mSerializationForTextHTML,
+                        kHTMLMime);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    // Add the htmlcontext DataFlavor to the transferable. Even if the context
+    // buffer is empty, this flavor should be attached to the transferable.
+    rv = AppendString(aTransferable,
+                      aEncodedDocumentWithContext.mHTMLContextBuffer,
+                      kHTMLContext);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (!aEncodedDocumentWithContext.mHTMLInfoBuffer.IsEmpty()) {
+      // Add the htmlinfo DataFlavor to the transferable
+      rv = AppendString(aTransferable,
+                        aEncodedDocumentWithContext.mHTMLInfoBuffer, kHTMLInfo);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    if (!aEncodedDocumentWithContext.mSerializationForTextUnicode.IsEmpty()) {
+      // unicode text
+      // Add the unicode DataFlavor to the transferable
+      // If we didn't have this, then nsDataObj::GetData matches
+      // text/unicode against the kURLMime flavour which is not desirable
+      // (eg. when pasting into Notepad)
+      rv =
+          AppendString(aTransferable,
+                       aEncodedDocumentWithContext.mSerializationForTextUnicode,
+                       kUnicodeMime);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    // Try and get source URI of the items that are being dragged
+    nsIURI* uri = aDocument.GetDocumentURI();
+    if (uri) {
+      nsAutoCString spec;
+      nsresult rv = uri->GetSpec(spec);
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (!spec.IsEmpty()) {
+        nsAutoString shortcut;
+        AppendUTF8toUTF16(spec, shortcut);
+
+        // Add the URL DataFlavor to the transferable. Don't use kURLMime,
+        // as it will cause an unnecessary UniformResourceLocator to be
+        // added which confuses some apps eg. Outlook 2000 - (See Bug
+        // 315370). Don't use kURLDataMime, as it will cause a bogus 'url '
+        // flavor to show up on the Mac clipboard, confusing other apps,
+        // like Terminal (see bug 336012).
+        rv = AppendString(aTransferable, shortcut, kURLPrivateMime);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+    }
+  } else {
+    if (!aEncodedDocumentWithContext.mSerializationForTextUnicode.IsEmpty()) {
+      // Add the unicode DataFlavor to the transferable
+      rv =
+          AppendString(aTransferable,
+                       aEncodedDocumentWithContext.mSerializationForTextUnicode,
+                       kUnicodeMime);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
   }
 
-  if ((doPutOnClipboard && clipboard) || aTransferable != nullptr) {
-    // Create a transferable for putting data on the Clipboard
-    nsCOMPtr<nsITransferable> trans = do_CreateInstance(kCTransferableCID);
-    if (trans) {
-      trans->Init(aDoc->GetLoadContext());
-      if (encodedTextHTML) {
-        // Set up a format converter so that clipboard flavor queries work.
-        // This converter isn't really used for conversions.
-        nsCOMPtr<nsIFormatConverter> htmlConverter =
-            do_CreateInstance(kHTMLConverterCID);
-        trans->SetConverter(htmlConverter);
-
-        if (!textHTMLBuf.IsEmpty()) {
-          // Add the html DataFlavor to the transferable
-          rv = AppendString(trans, textHTMLBuf, kHTMLMime);
-          NS_ENSURE_SUCCESS(rv, rv);
-        }
-
-        // Add the htmlcontext DataFlavor to the transferable
-        // Even if parents is empty string, this flavor should
-        // be attached to the transferable
-        rv = AppendString(trans, htmlParentsBuf, kHTMLContext);
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        if (!htmlInfoBuf.IsEmpty()) {
-          // Add the htmlinfo DataFlavor to the transferable
-          rv = AppendString(trans, htmlInfoBuf, kHTMLInfo);
-          NS_ENSURE_SUCCESS(rv, rv);
-        }
-
-        if (!textPlainBuf.IsEmpty()) {
-          // unicode text
-          // Add the unicode DataFlavor to the transferable
-          // If we didn't have this, then nsDataObj::GetData matches
-          // text/unicode against the kURLMime flavour which is not desirable
-          // (eg. when pasting into Notepad)
-          rv = AppendString(trans, textPlainBuf, kUnicodeMime);
-          NS_ENSURE_SUCCESS(rv, rv);
-        }
+  return rv;
+}
 
-        // Try and get source URI of the items that are being dragged
-        nsIURI* uri = aDoc->GetDocumentURI();
-        if (uri) {
-          nsAutoCString spec;
-          nsresult rv = uri->GetSpec(spec);
-          NS_ENSURE_SUCCESS(rv, rv);
-          if (!spec.IsEmpty()) {
-            nsAutoString shortcut;
-            AppendUTF8toUTF16(spec, shortcut);
+static nsresult PutToClipboard(
+    const EncodedDocumentWithContext& aEncodedDocumentWithContext,
+    int16_t aClipboardID, Document& aDocument) {
+  nsresult rv;
+  nsCOMPtr<nsIClipboard> clipboard = do_GetService(kCClipboardCID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_TRUE(clipboard, NS_ERROR_NULL_POINTER);
 
-            // Add the URL DataFlavor to the transferable. Don't use kURLMime,
-            // as it will cause an unnecessary UniformResourceLocator to be
-            // added which confuses some apps eg. Outlook 2000 - (See Bug
-            // 315370). Don't use kURLDataMime, as it will cause a bogus 'url '
-            // flavor to show up on the Mac clipboard, confusing other apps,
-            // like Terminal (see bug 336012).
-            rv = AppendString(trans, shortcut, kURLPrivateMime);
-            NS_ENSURE_SUCCESS(rv, rv);
-          }
-        }
-      } else {
-        if (!textPlainBuf.IsEmpty()) {
-          // Add the unicode DataFlavor to the transferable
-          rv = AppendString(trans, textPlainBuf, kUnicodeMime);
-          NS_ENSURE_SUCCESS(rv, rv);
-        }
-      }
+  nsCOMPtr<nsITransferable> transferable;
+  rv = CreateTransferable(aEncodedDocumentWithContext, aDocument, transferable);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-      if (doPutOnClipboard && clipboard) {
-        // put the transferable on the clipboard
-        clipboard->SetData(trans, nullptr, aClipboardID);
-      }
+  rv = clipboard->SetData(transferable, nullptr, aClipboardID);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-      // Return the transferable to the caller if requested.
-      if (aTransferable != nullptr) {
-        trans.swap(*aTransferable);
-      }
-    }
-  }
   return rv;
 }
 
 nsresult nsCopySupport::HTMLCopy(Selection* aSel, Document* aDoc,
                                  int16_t aClipboardID,
                                  bool aWithRubyAnnotation) {
-  uint32_t flags = nsIDocumentEncoder::SkipInvisibleContent;
+  NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER);
+
+  uint32_t additionalFlags = nsIDocumentEncoder::SkipInvisibleContent;
   if (aWithRubyAnnotation) {
-    flags |= nsIDocumentEncoder::OutputRubyAnnotation;
+    additionalFlags |= nsIDocumentEncoder::OutputRubyAnnotation;
   }
-  return SelectionCopyHelper(aSel, aDoc, true, aClipboardID, flags, nullptr);
+
+  EncodedDocumentWithContext encodedDocumentWithContext;
+  nsresult rv = EncodeDocumentWithContext(*aDoc, aSel, additionalFlags,
+                                          encodedDocumentWithContext);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = PutToClipboard(encodedDocumentWithContext, aClipboardID, *aDoc);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return rv;
 }
 
 nsresult nsCopySupport::ClearSelectionCache() {
   nsresult rv;
   nsCOMPtr<nsIClipboard> clipboard = do_GetService(kCClipboardCID, &rv);
   clipboard->EmptyClipboard(nsIClipboard::kSelectionCache);
   return rv;
 }
 
+/**
+ * @param aAdditionalEncoderFlags flags of `nsIDocumentEncoder`.
+ * @param aTransferable Needs to be not `nullptr`.
+ */
+static nsresult EncodeDocumentWithContextAndCreateTransferable(
+    Document& aDocument, Selection* aSelection,
+    uint32_t aAdditionalEncoderFlags, nsITransferable** aTransferable) {
+  NS_ENSURE_TRUE(aTransferable, NS_ERROR_NULL_POINTER);
+
+  // Clear the output parameter for the transferable.
+  *aTransferable = nullptr;
+
+  EncodedDocumentWithContext encodedDocumentWithContext;
+  nsresult rv =
+      EncodeDocumentWithContext(aDocument, aSelection, aAdditionalEncoderFlags,
+                                encodedDocumentWithContext);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsITransferable> transferable;
+  rv = CreateTransferable(encodedDocumentWithContext, aDocument, transferable);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  transferable.swap(*aTransferable);
+  return rv;
+}
+
 nsresult nsCopySupport::GetTransferableForSelection(
     Selection* aSel, Document* aDoc, nsITransferable** aTransferable) {
-  return SelectionCopyHelper(aSel, aDoc, false, 0,
-                             nsIDocumentEncoder::SkipInvisibleContent,
-                             aTransferable);
+  NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER);
+  NS_ENSURE_TRUE(aTransferable, NS_ERROR_NULL_POINTER);
+
+  const uint32_t additionalFlags = nsIDocumentEncoder::SkipInvisibleContent;
+  return EncodeDocumentWithContextAndCreateTransferable(
+      *aDoc, aSel, additionalFlags, aTransferable);
 }
 
 nsresult nsCopySupport::GetTransferableForNode(
     nsINode* aNode, Document* aDoc, nsITransferable** aTransferable) {
+  NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
+  NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER);
+  NS_ENSURE_TRUE(aTransferable, NS_ERROR_NULL_POINTER);
+
   // Make a temporary selection with aNode in a single range.
   // XXX We should try to get rid of the Selection object here.
   // XXX bug 1245883
   RefPtr<Selection> selection = new Selection();
   RefPtr<nsRange> range = new nsRange(aNode);
   ErrorResult result;
   range->SelectNode(*aNode, result);
   if (NS_WARN_IF(result.Failed())) {
     return result.StealNSResult();
   }
   selection->AddRangeInternal(*range, aDoc, result);
   if (NS_WARN_IF(result.Failed())) {
     return result.StealNSResult();
   }
   // It's not the primary selection - so don't skip invisible content.
-  uint32_t flags = 0;
-  return SelectionCopyHelper(selection, aDoc, false, 0, flags, aTransferable);
+  uint32_t additionalFlags = 0;
+  return EncodeDocumentWithContextAndCreateTransferable(
+      *aDoc, selection, additionalFlags, aTransferable);
 }
 
 nsresult nsCopySupport::GetContents(const nsACString& aMimeType,
                                     uint32_t aFlags, Selection* aSel,
                                     Document* aDoc, nsAString& outdata) {
   nsCOMPtr<nsIDocumentEncoder> docEncoder =
       do_createDocumentEncoder(PromiseFlatCString(aMimeType).get());
   NS_ENSURE_TRUE(docEncoder, NS_ERROR_FAILURE);
--- a/dom/base/nsCopySupport.h
+++ b/dom/base/nsCopySupport.h
@@ -24,37 +24,47 @@ class Document;
 class Selection;
 }  // namespace dom
 }  // namespace mozilla
 
 class nsCopySupport {
   // class of static helper functions for copy support
  public:
   static nsresult ClearSelectionCache();
+
+  /**
+   * @param aDoc Needs to be not nullptr.
+   */
   static nsresult HTMLCopy(mozilla::dom::Selection* aSel,
                            mozilla::dom::Document* aDoc, int16_t aClipboardID,
                            bool aWithRubyAnnotation);
 
   // Get the selection, or entire document, in the format specified by the mime
   // type (text/html or text/plain). If aSel is non-null, use it, otherwise get
   // the entire doc.
   static nsresult GetContents(const nsACString& aMimeType, uint32_t aFlags,
                               mozilla::dom::Selection* aSel,
                               mozilla::dom::Document* aDoc, nsAString& outdata);
 
   static nsresult ImageCopy(nsIImageLoadingContent* aImageElement,
                             nsILoadContext* aLoadContext, int32_t aCopyFlags);
 
   // Get the selection as a transferable. Similar to HTMLCopy except does
   // not deal with the clipboard.
+  // @param aSelection Can be nullptr.
+  // @param aDocument Needs to be not nullptr.
+  // @param aTransferable Needs to be not nullptr.
   static nsresult GetTransferableForSelection(
       mozilla::dom::Selection* aSelection, mozilla::dom::Document* aDocument,
       nsITransferable** aTransferable);
 
   // Same as GetTransferableForSelection, but doesn't skip invisible content.
+  // @param aNode Needs to be not nullptr.
+  // @param aDoc Needs to be not nullptr.
+  // @param aTransferable Needs to be not nullptr.
   MOZ_CAN_RUN_SCRIPT_BOUNDARY
   static nsresult GetTransferableForNode(nsINode* aNode,
                                          mozilla::dom::Document* aDoc,
                                          nsITransferable** aTransferable);
   /**
    * Retrieve the selection for the given document. If the current focus
    * within the document has its own selection, aSelection will be set to it
    * and this focused content node returned. Otherwise, aSelection will be
--- a/dom/base/test/chrome/cpows_parent.xul
+++ b/dom/base/test/chrome/cpows_parent.xul
@@ -505,16 +505,16 @@
       BrowserTestUtils.loadURI(browser, "http://mochi.test:8888/tests/dom/base/test/chrome/cpows_child.html");
       await BrowserTestUtils.browserLoaded(browser);
 
       run_tests('remote');
     }
 
     function finish() {
       ok(gReceivedErrorProbe, "Should have reported error probe");
-      opener.setTimeout("done()", 0);
+      opener.setTimeout(function() { this.done(); }, 0);
       window.close();
     }
   ]]></script>
 
   <browser type="content" src="about:blank" id="cpowbrowser_remote" remote="true"/>
   <browser type="content" src="about:blank" id="cpowbrowser_inprocess"/>
 </window>
--- a/dom/base/test/chrome/file_bug1139964.xul
+++ b/dom/base/test/chrome/file_bug1139964.xul
@@ -39,17 +39,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   }
 
   function tabListener(m) {
     messageManager.removeMessageListener(msgName, tabListener);
     ok(m.data.hasPromise, "BrowserChildGlobal should have Promise object in the global scope!");
     ok(m.data.hasTextEncoder, "BrowserChildGlobal should have TextEncoder object in the global scope!");
     ok(m.data.hasWindow, "BrowserChildGlobal should have Window object in the global scope!");
 
-    opener.setTimeout("done()", 0);
+    opener.setTimeout(function() { this.done(); }, 0);
     window.close();
   }
 
   function run() {
     ppm.addMessageListener(msgName, processListener)
     ppm.loadProcessScript("data:,(" + mmScriptForPromiseTest.toString() + ")()", true);
   }
 
--- a/dom/base/test/chrome/file_bug1209621.xul
+++ b/dom/base/test/chrome/file_bug1209621.xul
@@ -59,17 +59,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     remote2.setAttribute("primary", "true");
     var tp2 = remote2.frameLoader.remoteTab;
     ok(tp2, "Remote browsers should have a remoteTab.");
     is(treeOwner.primaryRemoteTab, tp2,
        "primary remote browser should be the primaryRemoteTab.");
     is(treeOwner.primaryContentShell, null,
        "There shouldn't be primaryContentShell because no browser has primary=true.");
 
-    opener.setTimeout("done()", 0);
+    opener.setTimeout(function() { this.done(); }, 0);
     window.close();
   }
 
   ]]></script>
   <browser type="content" src="about:blank" id="inprocess"/>
   <browser type="content" remote="true" src="about:blank" id="remote"/>
   <browser type="content" remote="true" src="about:blank" id="remote2"/>
 </window>
--- a/dom/base/test/chrome/file_bug549682.xul
+++ b/dom/base/test/chrome/file_bug549682.xul
@@ -114,17 +114,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   };
 
   function weakDoneListener() {
     ok(weakMessageReceived, 'Got "weak" message.');
     finish();
   }
 
   function finish() {
-    opener.setTimeout("done()", 0);
+    opener.setTimeout(function() { this.done(); }, 0);
     var i = document.getElementById("ifr");
     i.remove(); // This is a crash test!
     window.close();
   }
 
   function loadScript() {
     // Async should be processed first!
     messageManager.loadFrameScript("data:,\
--- a/dom/base/test/chrome/file_bug616841.xul
+++ b/dom/base/test/chrome/file_bug616841.xul
@@ -48,16 +48,16 @@ https://bugzilla.mozilla.org/show_bug.cg
 
     function start() {
       messageManager.addMessageListener("contentReady", recvContentReady);
       messageManager.addMessageListener("cmp", recvCmp);
       messageManager.loadFrameScript(FRAME_SCRIPT, true);
     }
 
     function finish() {
-      opener.setTimeout("done()", 0);
+      opener.setTimeout(function() { this.done(); }, 0);
       window.close();
     }
 
   ]]></script>
 
   <browser id="browser" type="content" src="about:blank"/>
 </window>
--- a/dom/base/test/chrome/file_bug816340.xul
+++ b/dom/base/test/chrome/file_bug816340.xul
@@ -55,16 +55,16 @@ https://bugzilla.mozilla.org/show_bug.cg
       testElement("div", false, true);
       testElement("div", true, true);
 
       for (var i = 0; i < elems.length; ++i) {
         testElement(elems[i], false, true);
         testElement(elems[i], true, false);
       }
       ok(true, "done");
-      opener.setTimeout("done()", 0);
+      opener.setTimeout(function() { this.done(); }, 0);
       window.close();
     }
 
   ]]></script>
 
   <browser id="browser" type="content" src="about:blank"/>
 </window>
--- a/dom/base/test/chrome/file_bug990812-1.xul
+++ b/dom/base/test/chrome/file_bug990812-1.xul
@@ -35,17 +35,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 
       var order = ["global", "window", "group"];
 
       messageManager.addMessageListener("test", function onMessage(msg) {
         var next = order.shift();
         opener.wrappedJSObject.is(msg.data, next, "received test:" + next);
 
         if (order.length == 0) {
-          opener.setTimeout("next()");
+          opener.setTimeout(function() { this.next(); });
           window.close();
         }
       });
 
       var browser = document.createElement("browser");
       browser.setAttribute("messagemanagergroup", "test");
       browser.setAttribute("src", "about:mozilla");
       browser.setAttribute("type", "content");
--- a/dom/base/test/chrome/file_bug990812-2.xul
+++ b/dom/base/test/chrome/file_bug990812-2.xul
@@ -39,17 +39,17 @@ https://bugzilla.mozilla.org/show_bug.cg
       var global = promiseMessage("global", globalMM);
       var window = promiseMessage("window", messageManager);
       var group = promiseMessage("group", getGroupMessageManager("test"));
 
       var browser = document.querySelector("browser");
       browser.messageManager.loadFrameScript(FRAME_SCRIPT, true);
 
       Promise.all([global, window, group]).then(function () {
-        opener.setTimeout("next()");
+        opener.setTimeout(function() { this.next(); });
         self.close();
       });
     }
 
   ]]></script>
 
   <browser messagemanagergroup="test" type="content" src="about:mozilla" />
 
--- a/dom/base/test/chrome/file_bug990812-3.xul
+++ b/dom/base/test/chrome/file_bug990812-3.xul
@@ -50,17 +50,17 @@ https://bugzilla.mozilla.org/show_bug.cg
       mm2.loadFrameScript(FRAME_SCRIPT, true);
 
       getGroupMessageManager("test1").broadcastAsyncMessage("test", "group1");
       getGroupMessageManager("test2").broadcastAsyncMessage("test", "group2");
       messageManager.broadcastAsyncMessage("test", "window");
       globalMM.broadcastAsyncMessage("test", "global");
 
       Promise.all([promise1, promise2]).then(function () {
-        opener.setTimeout("next()");
+        opener.setTimeout(function() { this.next(); });
         window.close();
       });
     }
 
   ]]></script>
 
   <browser messagemanagergroup="test1" type="content" src="about:mozilla" />
   <browser messagemanagergroup="test2" type="content" src="about:mozilla" />
--- a/dom/base/test/chrome/file_bug990812-4.xul
+++ b/dom/base/test/chrome/file_bug990812-4.xul
@@ -45,17 +45,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 
       var promise1 = promiseMessage("frame2", getGroupMessageManager("test1"));
       var promise2 = promiseMessage("frame1", getGroupMessageManager("test2"));
 
       browser1.swapFrameLoaders(browser2);
       messageManager.broadcastAsyncMessage("test");
 
       Promise.all([promise1, promise2]).then(function () {
-        opener.setTimeout("next()");
+        opener.setTimeout(function() { this.next(); });
         window.close();
       });
     }
 
   ]]></script>
 
   <browser messagemanagergroup="test1" type="content" src="about:mozilla" />
   <browser messagemanagergroup="test2" type="content" src="about:mozilla" />
--- a/dom/base/test/chrome/file_bug990812-5.xul
+++ b/dom/base/test/chrome/file_bug990812-5.xul
@@ -53,17 +53,17 @@ https://bugzilla.mozilla.org/show_bug.cg
       gmm2.loadFrameScript(FRAME_SCRIPT2, true);
 
       var promise1 = promiseTwoMessages("group1", gmm1);
       var promise2 = promiseTwoMessages("group2", gmm2);
 
       messageManager.broadcastAsyncMessage("test");
 
       Promise.all([promise1, promise2]).then(function () {
-        opener.setTimeout("next()");
+        opener.setTimeout(function() { this.next(); });
         window.close();
       });
     }
 
   ]]></script>
 
   <browser messagemanagergroup="test1" type="content" src="about:mozilla" />
   <browser messagemanagergroup="test1" type="content" src="about:mozilla" />
new file mode 100644
--- /dev/null
+++ b/dom/base/test/file_webAudioAudible.html
@@ -0,0 +1,7 @@
+<meta charset=utf-8>
+<script>
+var ac = new AudioContext();
+var osc = ac.createOscillator();
+osc.connect(ac.destination);
+osc.start();
+</script>
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -192,16 +192,17 @@ support-files =
   w3element_traversal.svg
   wholeTexty-helper.xml
   referrerHelper.js
   img_referrer_testserver.sjs
   file_audioLoop.html
   file_webaudioLoop.html
   file_webaudioLoop2.html
   file_webaudio_startstop.html
+  file_webAudioAudible.html
   file_pluginAudio.html
   file_pluginAudioNonAutoStart.html
   noaudio.webm
   referrer_helper.js
   referrer_testserver.sjs
   script_postmessages_fileList.js
   iframe_postMessages.html
   test_anonymousContent_style_csp.html^headers^
@@ -267,16 +268,19 @@ skip-if = (os == "win" && processor == "
 tags = audiochannel
 skip-if = (os == "win" && processor == "aarch64") # bug 1535775
 [test_audioNotificationStream.html]
 tags = audiochannel
 skip-if = (os == "win" && processor == "aarch64") # bug 1535775
 [test_audioNotificationStopOnNavigation.html]
 tags = audiochannel
 skip-if = (os == "win" && processor == "aarch64") # bug 1535775
+[test_audioNotificationNavigationWebAudio.html]
+tags = audiochannel
+skip-if = (os == "win" && processor == "aarch64") # bug 1535775
 [test_audioNotificationWithEarlyPlay.html]
 tags = audiochannel
 skip-if = (os == "win" && processor == "aarch64") # bug 1535775
 [test_base.xhtml]
 [test_bug5141.html]
 [test_bug28293.html]
 [test_bug28293.xhtml]
 [test_bug51034.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_audioNotificationNavigationWebAudio.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for audio controller in windows, when using the Web Audio API</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+<iframe></iframe>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// playback starts, stops when the page is reloaded, and starts again.
+var expectedNotification = ["active", "inactive-pause", "active"];
+var iframe = null;
+
+var observer = {
+  observe: function(subject, topic, data) {
+    is(topic, "audio-playback", "audio-playback received");
+    var expected = expectedNotification.shift();
+    is(data, expected, "This is the right notification");
+    if (expected != "inactive-pause") {
+      runTest();
+    }
+  }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+                                   .getService(SpecialPowers.Ci.nsIObserverService);
+
+var tests = [
+  function() {
+    iframe = document.querySelector("iframe");
+    observerService.addObserver(observer, "audio-playback");
+    ok(true, "Observer set");
+    runTest();
+  },
+
+  function() {
+    iframe.src = "file_webAudioAudible.html";
+  },
+
+  function() {
+    // Reload
+    iframe.src = "file_webAudioAudible.html";
+  },
+  function() {
+    // Wait for the "active" notification, that is expected after the previous
+    // load of "file_webAudioAudible.html".
+    runTest();
+  }
+];
+
+function runTest() {
+  if (!tests.length) {
+    observerService.removeObserver(observer, "audio-playback");
+    ok(true, "Observer removed");
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+onload = runTest;
+
+</script>
+</body>
+</html>
+
--- a/dom/canvas/CanvasUtils.cpp
+++ b/dom/canvas/CanvasUtils.cpp
@@ -119,18 +119,18 @@ bool IsImageExtractionAllowed(Document* 
   // Load Permission Manager service.
   nsCOMPtr<nsIPermissionManager> permissionManager =
       do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, false);
 
   // Check if the site has permission to extract canvas data.
   // Either permit or block extraction if a stored permission setting exists.
   uint32_t permission;
-  rv = permissionManager->TestPermissionFromPrincipal(
-      principal, PERMISSION_CANVAS_EXTRACT_DATA, &permission);
+  rv = permissionManager->TestPermission(
+      topLevelDocURI, PERMISSION_CANVAS_EXTRACT_DATA, &permission);
   NS_ENSURE_SUCCESS(rv, false);
   switch (permission) {
     case nsIPermissionManager::ALLOW_ACTION:
       return true;
     case nsIPermissionManager::DENY_ACTION:
       return false;
     default:
       break;
@@ -162,34 +162,30 @@ bool IsImageExtractionAllowed(Document* 
         docURISpec.get());
     nsContentUtils::ReportToConsoleNonLocalized(
         message, nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Security"),
         aDocument);
   }
 
   // Prompt the user (asynchronous).
   nsPIDOMWindowOuter* win = aDocument->GetWindow();
-  nsAutoCString origin;
-  rv = principal->GetOrigin(origin);
-  NS_ENSURE_SUCCESS(rv, false);
-
   if (XRE_IsContentProcess()) {
     BrowserChild* browserChild = BrowserChild::GetFrom(win);
     if (browserChild) {
-      browserChild->SendShowCanvasPermissionPrompt(origin,
+      browserChild->SendShowCanvasPermissionPrompt(topLevelDocURISpec,
                                                    isAutoBlockCanvas);
     }
   } else {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (obs) {
       obs->NotifyObservers(win,
                            isAutoBlockCanvas
                                ? TOPIC_CANVAS_PERMISSIONS_PROMPT_HIDE_DOORHANGER
                                : TOPIC_CANVAS_PERMISSIONS_PROMPT,
-                           NS_ConvertUTF8toUTF16(origin).get());
+                           NS_ConvertUTF8toUTF16(topLevelDocURISpec).get());
     }
   }
 
   // We don't extract the image for now -- user may override at prompt.
   return false;
 }
 
 bool GetCanvasContextType(const nsAString& str,
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -116,17 +116,16 @@ bool WebGLContextOptions::operator==(con
 WebGLContext::WebGLContext()
     : gl(mGL_OnlyClearInDestroyResourcesAndContext)  // const reference
       ,
       mMaxPerfWarnings(gfxPrefs::WebGLMaxPerfWarnings()),
       mNumPerfWarnings(0),
       mMaxAcceptableFBStatusInvals(
           gfxPrefs::WebGLMaxAcceptableFBStatusInvals()),
       mDataAllocGLCallCount(0),
-      mBypassShaderValidation(false),
       mEmptyTFO(0),
       mContextLossHandler(this),
       mNeedsFakeNoAlpha(false),
       mNeedsFakeNoDepth(false),
       mNeedsFakeNoStencil(false),
       mAllowFBInvalidation(gfxPrefs::WebGLFBInvalidation()),
       mMsaaSamples((uint8_t)gfxPrefs::WebGLMsaaSamples()) {
   mGeneration = 0;
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -1486,18 +1486,16 @@ class WebGLContext : public nsICanvasRen
   void DeleteWebGLObjectsArray(nsTArray<WebGLObjectType>& array);
 
   GLuint mActiveTexture = 0;
   GLenum mDefaultFB_DrawBuffer0 = 0;
   GLenum mDefaultFB_ReadBuffer = 0;
 
   mutable GLenum mWebGLError;
 
-  bool mBypassShaderValidation;
-
   webgl::ShaderValidator* CreateShaderValidator(GLenum shaderType) const;
 
   // some GL constants
   uint32_t mGLMaxTextureUnits = 0;
 
   uint32_t mGLMaxVertexAttribs = 0;
   uint32_t mGLMaxFragmentUniformVectors = 0;
   uint32_t mGLMaxVertexUniformVectors = 0;
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -577,19 +577,16 @@ bool WebGLContext::InitAndValidateGL(Fai
                          LOCAL_GL_LOWER_LEFT);
   }
 #endif
 
   if (gl->IsSupported(gl::GLFeature::seamless_cube_map_opt_in)) {
     gl->fEnable(LOCAL_GL_TEXTURE_CUBE_MAP_SEAMLESS);
   }
 
-  // Check the shader validator pref
-  mBypassShaderValidation = gfxPrefs::WebGLBypassShaderValidator();
-
   // initialize shader translator
   if (!sh::Initialize()) {
     *out_failReason = {"FEATURE_FAILURE_WEBGL_GLSL",
                        "GLSL translator initialization failed!"};
     return false;
   }
 
   // Mesa can only be detected with the GL_VERSION string, of the form
--- a/dom/canvas/WebGLProgram.cpp
+++ b/dom/canvas/WebGLProgram.cpp
@@ -471,17 +471,17 @@ static RefPtr<const webgl::LinkedProgram
   }
 
   // Frag outputs
 
   {
     const auto& fragShader = prog->FragShader();
     MOZ_RELEASE_ASSERT(fragShader);
     MOZ_RELEASE_ASSERT(fragShader->Validator());
-    const auto& handle = fragShader->Validator()->Handle();
+    const auto& handle = fragShader->Validator()->mHandle;
     const auto version = sh::GetShaderVersion(handle);
 
     const auto fnAddInfo = [&](const webgl::FragOutputInfo& x) {
       info->fragOutputs.insert({x.loc, x});
     };
 
     if (version == 300) {
       const auto& fragOutputs = sh::GetOutputVariables(handle);
@@ -738,23 +738,24 @@ void WebGLProgram::BindAttribLocation(GL
 
   if (StringBeginsWith(name, NS_LITERAL_STRING("gl_"))) {
     mContext->ErrorInvalidOperation(
         "Can't set the location of a"
         " name that starts with 'gl_'.");
     return;
   }
 
-  NS_LossyConvertUTF16toASCII asciiName(name);
-
-  auto res = mNextLink_BoundAttribLocs.insert({asciiName, loc});
+  const NS_LossyConvertUTF16toASCII asciiName(name);
+  const std::string asciiNameStr(asciiName.BeginReading());
 
-  const bool wasInserted = res.second;
+  auto res = mNextLink_BoundAttribLocs.insert({asciiNameStr, loc});
+
+  const auto& wasInserted = res.second;
   if (!wasInserted) {
-    auto itr = res.first;
+    const auto& itr = res.first;
     itr->second = loc;
   }
 }
 
 void WebGLProgram::DetachShader(const WebGLShader* shader) {
   MOZ_ASSERT(shader);
 
   WebGLRefPtr<WebGLShader>* shaderSlot;
--- a/dom/canvas/WebGLProgram.h
+++ b/dom/canvas/WebGLProgram.h
@@ -224,17 +224,17 @@ class WebGLProgram final : public nsWrap
  public:
   const GLuint mGLName;
 
  private:
   WebGLRefPtr<WebGLShader> mVertShader;
   WebGLRefPtr<WebGLShader> mFragShader;
   size_t mNumActiveTFOs;
 
-  std::map<nsCString, GLuint> mNextLink_BoundAttribLocs;
+  std::map<std::string, GLuint> mNextLink_BoundAttribLocs;
 
   std::vector<nsString> mNextLink_TransformFeedbackVaryings;
   GLenum mNextLink_TransformFeedbackBufferMode;
 
   nsCString mLinkLog;
   RefPtr<const webgl::LinkedProgramInfo> mMostRecentLinkInfo;
 };
 
--- a/dom/canvas/WebGLShader.cpp
+++ b/dom/canvas/WebGLShader.cpp
@@ -40,96 +40,37 @@ static void PrintLongString(const char* 
 
 // On success, writes to out_validator and out_translatedSource.
 // On failure, writes to out_translationLog.
 static bool Translate(const nsACString& source,
                       webgl::ShaderValidator* validator,
                       nsACString* const out_translationLog,
                       nsACString* const out_translatedSource) {
   if (!validator->ValidateAndTranslate(source.BeginReading())) {
-    validator->GetInfoLog(out_translationLog);
+    const std::string& log = sh::GetInfoLog(validator->mHandle);
+    out_translationLog->Assign(log.data(), log.length());
     return false;
   }
 
   // Success
-  validator->GetOutput(out_translatedSource);
+  const std::string& output = sh::GetObjectCode(validator->mHandle);
+  out_translatedSource->Assign(output.data(), output.length());
+
   return true;
 }
 
 template <size_t N>
 static bool SubstringStartsWith(const std::string& testStr, size_t offset,
                                 const char (&refStr)[N]) {
   for (size_t i = 0; i < N - 1; i++) {
     if (testStr[offset + i] != refStr[i]) return false;
   }
   return true;
 }
 
-/* On success, writes to out_translatedSource.
- * On failure, writes to out_translationLog.
- *
- * Requirements:
- *   #version is either omitted, `#version 100`, or `version 300 es`.
- */
-static bool TranslateWithoutValidation(const nsACString& sourceNS,
-                                       bool isWebGL2,
-                                       nsACString* const out_translationLog,
-                                       nsACString* const out_translatedSource) {
-  std::string source = sourceNS.BeginReading();
-
-  size_t versionStrStart = source.find("#version");
-  size_t versionStrLen;
-  uint32_t glesslVersion;
-
-  if (versionStrStart != std::string::npos) {
-    static const char versionStr100[] = "#version 100\n";
-    static const char versionStr300es[] = "#version 300 es\n";
-
-    if (isWebGL2 &&
-        SubstringStartsWith(source, versionStrStart, versionStr300es)) {
-      glesslVersion = 300;
-      versionStrLen = strlen(versionStr300es);
-
-    } else if (SubstringStartsWith(source, versionStrStart, versionStr100)) {
-      glesslVersion = 100;
-      versionStrLen = strlen(versionStr100);
-
-    } else {
-      nsPrintfCString error("#version, if declared, must be %s.",
-                            isWebGL2 ? "`100` or `300 es`" : "`100`");
-      *out_translationLog = error;
-      return false;
-    }
-  } else {
-    versionStrStart = 0;
-    versionStrLen = 0;
-    glesslVersion = 100;
-  }
-
-  std::string reversionedSource = source;
-  reversionedSource.erase(versionStrStart, versionStrLen);
-
-  switch (glesslVersion) {
-    case 100:
-      /* According to ARB_ES2_compatibility extension glsl
-       * should accept #version 100 for ES 2 shaders. */
-      reversionedSource.insert(versionStrStart, "#version 100\n");
-      break;
-    case 300:
-      reversionedSource.insert(versionStrStart, "#version 330\n");
-      break;
-    default:
-      MOZ_CRASH("GFX: Bad `glesslVersion`.");
-  }
-
-  out_translatedSource->Assign(reversionedSource.c_str(),
-                               reversionedSource.length());
-  return true;
-}
-
 static void GetCompilationStatusAndLog(gl::GLContext* gl, GLuint shader,
                                        bool* const out_success,
                                        nsACString* const out_log) {
   GLint compileStatus = LOCAL_GL_FALSE;
   gl->fGetShaderiv(shader, LOCAL_GL_COMPILE_STATUS, &compileStatus);
 
   // It's simpler if we always get the log.
   GLint lenWithNull = 0;
@@ -180,31 +121,26 @@ void WebGLShader::ShaderSource(const nsA
 void WebGLShader::CompileShader() {
   mValidator = nullptr;
   mTranslationSuccessful = false;
   mCompilationSuccessful = false;
 
   gl::GLContext* gl = mContext->gl;
 
   mValidator.reset(mContext->CreateShaderValidator(mType));
+  MOZ_ASSERT(mValidator);
 
   static const bool kDumpShaders = PR_GetEnv("MOZ_WEBGL_DUMP_SHADERS");
   if (MOZ_UNLIKELY(kDumpShaders)) {
     printf_stderr("==== begin MOZ_WEBGL_DUMP_SHADERS ====\n");
     PrintLongString(mCleanSource.BeginReading(), mCleanSource.Length());
   }
 
-  bool success;
-  if (mValidator) {
-    success = Translate(mCleanSource, mValidator.get(), &mValidationLog,
+  const bool success = Translate(mCleanSource, mValidator.get(), &mValidationLog,
                         &mTranslatedSource);
-  } else {
-    success = TranslateWithoutValidation(mCleanSource, mContext->IsWebGL2(),
-                                         &mValidationLog, &mTranslatedSource);
-  }
 
   if (MOZ_UNLIKELY(kDumpShaders)) {
     printf_stderr("\n==== \\/ \\/ \\/ ====\n");
     if (success) {
       PrintLongString(mTranslatedSource.BeginReading(),
                       mTranslatedSource.Length());
     } else {
       printf_stderr("Validation failed:\n%s", mValidationLog.BeginReading());
@@ -257,119 +193,132 @@ void WebGLShader::GetShaderTranslatedSou
   out->SetIsVoid(false);
   CopyASCIItoUTF16(mTranslatedSource, *out);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
 bool WebGLShader::CanLinkTo(const WebGLShader* prev,
                             nsCString* const out_log) const {
-  if (!mValidator) return true;
-
   return mValidator->CanLinkTo(prev->mValidator.get(), out_log);
 }
 
 size_t WebGLShader::CalcNumSamplerUniforms() const {
-  if (mValidator) return mValidator->CalcNumSamplerUniforms();
-
-  // TODO
-  return 0;
+  const auto& uniforms = *sh::GetUniforms(mValidator->mHandle);
+  size_t accum = 0;
+  for (const auto& cur : uniforms) {
+    const auto& type = cur.type;
+    if (type == LOCAL_GL_SAMPLER_2D || type == LOCAL_GL_SAMPLER_CUBE) {
+      accum += cur.getArraySizeProduct();
+    }
+  }
+  return accum;
 }
 
 size_t WebGLShader::NumAttributes() const {
-  if (mValidator) return mValidator->NumAttributes();
-
-  // TODO
-  return 0;
+  return sh::GetAttributes(mValidator->mHandle)->size();
 }
 
-void WebGLShader::BindAttribLocation(GLuint prog, const nsCString& userName,
+void WebGLShader::BindAttribLocation(GLuint prog, const std::string& userName,
                                      GLuint index) const {
-  std::string userNameStr(userName.BeginReading());
-
-  const std::string* mappedNameStr = &userNameStr;
-  if (mValidator)
-    mValidator->FindAttribMappedNameByUserName(userNameStr, &mappedNameStr);
-
-  mContext->gl->fBindAttribLocation(prog, index, mappedNameStr->c_str());
+  const auto& attribs = *sh::GetAttributes(mValidator->mHandle);
+  for (const auto& attrib : attribs) {
+    if (attrib.name == userName) {
+      mContext->gl->fBindAttribLocation(prog, index, attrib.mappedName.c_str());
+      return;
+    }
+  }
 }
 
 bool WebGLShader::FindAttribUserNameByMappedName(
     const nsACString& mappedName, nsCString* const out_userName) const {
-  if (!mValidator) return false;
+  const std::string mappedNameStr(mappedName.BeginReading());
 
-  const std::string mappedNameStr(mappedName.BeginReading());
-  const std::string* userNameStr;
-  if (!mValidator->FindAttribUserNameByMappedName(mappedNameStr, &userNameStr))
-    return false;
-
-  *out_userName = userNameStr->c_str();
-  return true;
+  const auto& attribs = *sh::GetAttributes(mValidator->mHandle);
+  for (const auto& cur : attribs) {
+    if (cur.mappedName == mappedNameStr) {
+      *out_userName = cur.name.c_str();
+      return true;
+    }
+  }
+  return false;
 }
 
 bool WebGLShader::FindVaryingByMappedName(const nsACString& mappedName,
                                           nsCString* const out_userName,
                                           bool* const out_isArray) const {
-  if (!mValidator) return false;
+  const std::string mappedNameStr(mappedName.BeginReading());
 
-  const std::string mappedNameStr(mappedName.BeginReading());
-  std::string userNameStr;
-  if (!mValidator->FindVaryingByMappedName(mappedNameStr, &userNameStr,
-                                           out_isArray))
-    return false;
+  const auto& varyings = *sh::GetVaryings(mValidator->mHandle);
+  for (const auto& cur : varyings) {
+    const sh::ShaderVariable* found;
+    std::string userName;
+    if (!cur.findInfoByMappedName(mappedNameStr, &found, &userName)) continue;
 
-  *out_userName = userNameStr.c_str();
-  return true;
+    *out_userName = userName.c_str();
+    *out_isArray = found->isArray();
+    return true;
+  }
+
+  return false;
 }
 
 bool WebGLShader::FindUniformByMappedName(const nsACString& mappedName,
                                           nsCString* const out_userName,
                                           bool* const out_isArray) const {
-  if (!mValidator) return false;
-
   const std::string mappedNameStr(mappedName.BeginReading(),
                                   mappedName.Length());
   std::string userNameStr;
   if (!mValidator->FindUniformByMappedName(mappedNameStr, &userNameStr,
                                            out_isArray))
     return false;
 
   *out_userName = userNameStr.c_str();
   return true;
 }
 
 bool WebGLShader::UnmapUniformBlockName(
     const nsACString& baseMappedName, nsCString* const out_baseUserName) const {
-  if (!mValidator) {
-    *out_baseUserName = baseMappedName;
-    return true;
+  const auto& interfaces = *sh::GetInterfaceBlocks(mValidator->mHandle);
+  for (const auto& interface : interfaces) {
+    const nsDependentCString interfaceMappedName(interface.mappedName.data(),
+                                                 interface.mappedName.size());
+    if (baseMappedName == interfaceMappedName) {
+      *out_baseUserName = interface.name.data();
+      return true;
+    }
   }
 
-  return mValidator->UnmapUniformBlockName(baseMappedName, out_baseUserName);
+  return false;
 }
 
 void WebGLShader::MapTransformFeedbackVaryings(
     const std::vector<nsString>& varyings,
     std::vector<std::string>* out_mappedVaryings) const {
   MOZ_ASSERT(mType == LOCAL_GL_VERTEX_SHADER);
   MOZ_ASSERT(out_mappedVaryings);
 
   out_mappedVaryings->clear();
   out_mappedVaryings->reserve(varyings.size());
 
+  const auto& shaderVaryings = *sh::GetVaryings(mValidator->mHandle);
+
   for (const auto& wideUserName : varyings) {
     const NS_LossyConvertUTF16toASCII mozUserName(
         wideUserName);  // Don't validate here.
     const std::string userName(mozUserName.BeginReading(),
                                mozUserName.Length());
-    const std::string* pMappedName = &userName;
-    if (mValidator) {
-      mValidator->FindVaryingMappedNameByUserName(userName, &pMappedName);
+    const auto* mappedName = &userName;
+    for (const auto& shaderVarying : shaderVaryings) {
+      if (shaderVarying.name == userName) {
+        mappedName = &shaderVarying.mappedName;
+        break;
+      }
     }
-    out_mappedVaryings->push_back(*pMappedName);
+    out_mappedVaryings->push_back(*mappedName);
   }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Boilerplate
 
 JSObject* WebGLShader::WrapObject(JSContext* js,
                                   JS::Handle<JSObject*> givenProto) {
--- a/dom/canvas/WebGLShader.h
+++ b/dom/canvas/WebGLShader.h
@@ -62,17 +62,17 @@ class WebGLShader final : public nsWrapp
 
   bool IsCompiled() const {
     return mTranslationSuccessful && mCompilationSuccessful;
   }
   const auto* Validator() const { return mValidator.get(); }
   const auto& TranslatedSource() const { return mTranslatedSource; }
 
  private:
-  void BindAttribLocation(GLuint prog, const nsCString& userName,
+  void BindAttribLocation(GLuint prog, const std::string& userName,
                           GLuint index) const;
   void MapTransformFeedbackVaryings(
       const std::vector<nsString>& varyings,
       std::vector<std::string>* out_mappedVaryings) const;
 
  public:
   // Other funcs
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
--- a/dom/canvas/WebGLShaderValidator.cpp
+++ b/dom/canvas/WebGLShaderValidator.cpp
@@ -136,18 +136,16 @@ static ShShaderOutput ShaderOutput(gl::G
     }
   }
 
   return SH_GLSL_COMPATIBILITY_OUTPUT;
 }
 
 webgl::ShaderValidator* WebGLContext::CreateShaderValidator(
     GLenum shaderType) const {
-  if (mBypassShaderValidation) return nullptr;
-
   const auto spec = (IsWebGL2() ? SH_WEBGL2_SPEC : SH_WEBGL_SPEC);
   const auto outputLanguage = ShaderOutput(gl);
 
   ShBuiltInResources resources;
   memset(&resources, 0, sizeof(resources));
   sh::InitBuiltInResources(&resources);
 
   resources.HashFunction = webgl::IdentifierHashFunc;
@@ -227,30 +225,16 @@ ShaderValidator::~ShaderValidator() { sh
 bool ShaderValidator::ValidateAndTranslate(const char* source) {
   MOZ_ASSERT(!mHasRun);
   mHasRun = true;
 
   const char* const parts[] = {source};
   return sh::Compile(mHandle, parts, ArrayLength(parts), mCompileOptions);
 }
 
-void ShaderValidator::GetInfoLog(nsACString* out) const {
-  MOZ_ASSERT(mHasRun);
-
-  const std::string& log = sh::GetInfoLog(mHandle);
-  out->Assign(log.data(), log.length());
-}
-
-void ShaderValidator::GetOutput(nsACString* out) const {
-  MOZ_ASSERT(mHasRun);
-
-  const std::string& output = sh::GetObjectCode(mHandle);
-  out->Assign(output.data(), output.length());
-}
-
 template <size_t N>
 static bool StartsWith(const std::string& haystack, const char (&needle)[N]) {
   return haystack.compare(0, N - 1, needle) == 0;
 }
 
 bool ShaderValidator::CanLinkTo(const ShaderValidator* prev,
                                 nsCString* const out_log) const {
   if (!prev) {
@@ -433,93 +417,16 @@ bool ShaderValidator::CanLinkTo(const Sh
           " Specification 1.0.17, p39)";
       return false;
     }
   }
 
   return true;
 }
 
-size_t ShaderValidator::CalcNumSamplerUniforms() const {
-  size_t accum = 0;
-
-  const std::vector<sh::Uniform>& uniforms = *sh::GetUniforms(mHandle);
-
-  for (auto itr = uniforms.begin(); itr != uniforms.end(); ++itr) {
-    GLenum type = itr->type;
-    if (type == LOCAL_GL_SAMPLER_2D || type == LOCAL_GL_SAMPLER_CUBE) {
-      accum += itr->getArraySizeProduct();
-    }
-  }
-
-  return accum;
-}
-
-size_t ShaderValidator::NumAttributes() const {
-  return sh::GetAttributes(mHandle)->size();
-}
-
-// Attribs cannot be structs or arrays, and neither can vertex inputs in ES3.
-// Therefore, attrib names are always simple.
-bool ShaderValidator::FindAttribUserNameByMappedName(
-    const std::string& mappedName,
-    const std::string** const out_userName) const {
-  const std::vector<sh::Attribute>& attribs = *sh::GetAttributes(mHandle);
-  for (auto itr = attribs.begin(); itr != attribs.end(); ++itr) {
-    if (itr->mappedName == mappedName) {
-      *out_userName = &(itr->name);
-      return true;
-    }
-  }
-
-  return false;
-}
-
-bool ShaderValidator::FindAttribMappedNameByUserName(
-    const std::string& userName,
-    const std::string** const out_mappedName) const {
-  const std::vector<sh::Attribute>& attribs = *sh::GetAttributes(mHandle);
-  for (auto itr = attribs.begin(); itr != attribs.end(); ++itr) {
-    if (itr->name == userName) {
-      *out_mappedName = &(itr->mappedName);
-      return true;
-    }
-  }
-
-  return false;
-}
-
-bool ShaderValidator::FindVaryingByMappedName(const std::string& mappedName,
-                                              std::string* const out_userName,
-                                              bool* const out_isArray) const {
-  const std::vector<sh::Varying>& varyings = *sh::GetVaryings(mHandle);
-  for (auto itr = varyings.begin(); itr != varyings.end(); ++itr) {
-    const sh::ShaderVariable* found;
-    if (!itr->findInfoByMappedName(mappedName, &found, out_userName)) continue;
-
-    *out_isArray = found->isArray();
-    return true;
-  }
-
-  return false;
-}
-
-bool ShaderValidator::FindVaryingMappedNameByUserName(
-    const std::string& userName,
-    const std::string** const out_mappedName) const {
-  const std::vector<sh::Varying>& attribs = *sh::GetVaryings(mHandle);
-  for (auto itr = attribs.begin(); itr != attribs.end(); ++itr) {
-    if (itr->name == userName) {
-      *out_mappedName = &(itr->mappedName);
-      return true;
-    }
-  }
-
-  return false;
-}
 // This must handle names like "foo.bar[0]".
 bool ShaderValidator::FindUniformByMappedName(const std::string& mappedName,
                                               std::string* const out_userName,
                                               bool* const out_isArray) const {
   const std::vector<sh::Uniform>& uniforms = *sh::GetUniforms(mHandle);
   for (auto itr = uniforms.begin(); itr != uniforms.end(); ++itr) {
     const sh::ShaderVariable* found;
     if (!itr->findInfoByMappedName(mappedName, &found, out_userName)) continue;
@@ -566,26 +473,10 @@ bool ShaderValidator::FindUniformByMappe
       *out_isArray = found->isArray();
       return true;
     }
   }
 
   return false;
 }
 
-bool ShaderValidator::UnmapUniformBlockName(
-    const nsACString& baseMappedName, nsCString* const out_baseUserName) const {
-  const std::vector<sh::InterfaceBlock>& interfaces =
-      *sh::GetInterfaceBlocks(mHandle);
-  for (const auto& interface : interfaces) {
-    const nsDependentCString interfaceMappedName(interface.mappedName.data(),
-                                                 interface.mappedName.size());
-    if (baseMappedName == interfaceMappedName) {
-      *out_baseUserName = interface.name.data();
-      return true;
-    }
-  }
-
-  return false;
-}
-
 }  // namespace webgl
 }  // namespace mozilla
--- a/dom/canvas/WebGLShaderValidator.h
+++ b/dom/canvas/WebGLShaderValidator.h
@@ -11,17 +11,19 @@
 #include "GLDefs.h"
 #include "GLSLANG/ShaderLang.h"
 #include "nsString.h"
 
 namespace mozilla {
 namespace webgl {
 
 class ShaderValidator final {
+public:
   const ShHandle mHandle;
+private:
   const ShCompileOptions mCompileOptions;
   const int mMaxVaryingVectors;
   bool mHasRun;
 
  public:
   static ShaderValidator* Create(GLenum shaderType, ShShaderSpec spec,
                                  ShShaderOutput outputLanguage,
                                  const ShBuiltInResources& resources,
@@ -34,43 +36,21 @@ class ShaderValidator final {
         mCompileOptions(compileOptions),
         mMaxVaryingVectors(maxVaryingVectors),
         mHasRun(false) {}
 
  public:
   ~ShaderValidator();
 
   bool ValidateAndTranslate(const char* source);
-  void GetInfoLog(nsACString* out) const;
-  void GetOutput(nsACString* out) const;
   bool CanLinkTo(const ShaderValidator* prev, nsCString* const out_log) const;
-  size_t CalcNumSamplerUniforms() const;
-  size_t NumAttributes() const;
-  const auto& Handle() const { return mHandle; }
-
-  bool FindAttribUserNameByMappedName(
-      const std::string& mappedName,
-      const std::string** const out_userName) const;
 
-  bool FindAttribMappedNameByUserName(
-      const std::string& userName,
-      const std::string** const out_mappedName) const;
-
-  bool FindVaryingMappedNameByUserName(
-      const std::string& userName,
-      const std::string** const out_mappedName) const;
-
-  bool FindVaryingByMappedName(const std::string& mappedName,
-                               std::string* const out_userName,
-                               bool* const out_isArray) const;
   bool FindUniformByMappedName(const std::string& mappedName,
                                std::string* const out_userName,
                                bool* const out_isArray) const;
-  bool UnmapUniformBlockName(const nsACString& baseMappedName,
-                             nsCString* const out_baseUserName) const;
 
   bool ValidateTransformFeedback(
       const std::vector<nsString>& userNames, uint32_t maxComponents,
       nsCString* const out_errorText,
       std::vector<std::string>* const out_mappedNames);
 };
 
 }  // namespace webgl
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -724,16 +724,18 @@ class HTMLMediaElement : public nsGeneri
     aSinkId = mSink.first();
   }
 
   // This is used to notify MediaElementAudioSourceNode that media element is
   // allowed to play when media element is used as a source for web audio, so
   // that we can start AudioContext if it was not allowed to start.
   RefPtr<GenericNonExclusivePromise> GetAllowedToPlayPromise();
 
+  bool GetShowPosterFlag() const { return mShowPoster; }
+
  protected:
   virtual ~HTMLMediaElement();
 
   class AudioChannelAgentCallback;
   class ChannelLoader;
   class ErrorSink;
   class MediaLoadListener;
   class MediaStreamTrackListener;
--- a/dom/html/TextTrackManager.cpp
+++ b/dom/html/TextTrackManager.cpp
@@ -197,17 +197,17 @@ void TextTrackManager::AddCues(TextTrack
 
   TextTrackCueList* cueList = aTextTrack->GetCues();
   if (cueList) {
     bool dummy;
     WEBVTT_LOGV("AddCues, CuesNum=%d", cueList->Length());
     for (uint32_t i = 0; i < cueList->Length(); ++i) {
       mNewCues->AddCue(*cueList->IndexedGetter(i, dummy));
     }
-    TimeMarchesOn();
+    MaybeRunTimeMarchesOn();
   }
 }
 
 void TextTrackManager::RemoveTextTrack(TextTrack* aTextTrack,
                                        bool aPendingListOnly) {
   if (!mPendingTextTracks || !mTextTracks) {
     return;
   }
@@ -221,17 +221,17 @@ void TextTrackManager::RemoveTextTrack(T
   mTextTracks->RemoveTextTrack(aTextTrack);
   // Remove the cues in mNewCues belong to aTextTrack.
   TextTrackCueList* removeCueList = aTextTrack->GetCues();
   if (removeCueList) {
     WEBVTT_LOGV("RemoveTextTrack removeCuesNum=%d", removeCueList->Length());
     for (uint32_t i = 0; i < removeCueList->Length(); ++i) {
       mNewCues->RemoveCue(*((*removeCueList)[i]));
     }
-    TimeMarchesOn();
+    MaybeRunTimeMarchesOn();
   }
 }
 
 void TextTrackManager::DidSeek() {
   WEBVTT_LOG("DidSeek");
   mHasSeeked = true;
 }
 
@@ -276,26 +276,26 @@ void TextTrackManager::UpdateCueDisplay(
   }
 }
 
 void TextTrackManager::NotifyCueAdded(TextTrackCue& aCue) {
   WEBVTT_LOG("NotifyCueAdded, cue=%p", &aCue);
   if (mNewCues) {
     mNewCues->AddCue(aCue);
   }
-  TimeMarchesOn();
+  MaybeRunTimeMarchesOn();
   ReportTelemetryForCue();
 }
 
 void TextTrackManager::NotifyCueRemoved(TextTrackCue& aCue) {
   WEBVTT_LOG("NotifyCueRemoved, cue=%p", &aCue);
   if (mNewCues) {
     mNewCues->RemoveCue(aCue);
   }
-  TimeMarchesOn();
+  MaybeRunTimeMarchesOn();
   DispatchUpdateCueDisplay();
 }
 
 void TextTrackManager::PopulatePendingList() {
   if (!mTextTracks || !mPendingTextTracks || !mMediaElement) {
     return;
   }
   uint32_t len = mTextTracks->Length();
@@ -563,18 +563,17 @@ class TextTrackListInternal {
     return mTextTracks.SafeElementAt(aIndex, nullptr);
   }
 
  private:
   nsTArray<RefPtr<TextTrack>> mTextTracks;
 };
 
 void TextTrackManager::DispatchUpdateCueDisplay() {
-  if (!mUpdateCueDisplayDispatched && !IsShutdown() &&
-      mMediaElement->IsCurrentlyPlaying()) {
+  if (!mUpdateCueDisplayDispatched && !IsShutdown()) {
     WEBVTT_LOG("DispatchUpdateCueDisplay");
     nsPIDOMWindowInner* win = mMediaElement->OwnerDoc()->GetInnerWindow();
     if (win) {
       nsGlobalWindowInner::Cast(win)->Dispatch(
           TaskCategory::Other,
           NewRunnableMethod("dom::TextTrackManager::UpdateCueDisplay", this,
                             &TextTrackManager::UpdateCueDisplay));
       mUpdateCueDisplayDispatched = true;
@@ -582,18 +581,17 @@ void TextTrackManager::DispatchUpdateCue
   }
 }
 
 void TextTrackManager::DispatchTimeMarchesOn() {
   // Run the algorithm if no previous instance is still running, otherwise
   // enqueue the current playback position and whether only that changed
   // through its usual monotonic increase during normal playback; current
   // executing call upon completion will check queue for further 'work'.
-  if (!mTimeMarchesOnDispatched && !IsShutdown() &&
-      mMediaElement->IsCurrentlyPlaying()) {
+  if (!mTimeMarchesOnDispatched && !IsShutdown()) {
     WEBVTT_LOG("DispatchTimeMarchesOn");
     nsPIDOMWindowInner* win = mMediaElement->OwnerDoc()->GetInnerWindow();
     if (win) {
       nsGlobalWindowInner::Cast(win)->Dispatch(
           TaskCategory::Other,
           NewRunnableMethod("dom::TextTrackManager::TimeMarchesOn", this,
                             &TextTrackManager::TimeMarchesOn));
       mTimeMarchesOnDispatched = true;
@@ -816,17 +814,17 @@ void TextTrackManager::TimeMarchesOn() {
 
   // Step 18.
   UpdateCueDisplay();
 }
 
 void TextTrackManager::NotifyCueUpdated(TextTrackCue* aCue) {
   // TODO: Add/Reorder the cue to mNewCues if we have some optimization?
   WEBVTT_LOG("NotifyCueUpdated, cue=%p", aCue);
-  TimeMarchesOn();
+  MaybeRunTimeMarchesOn();
   // For the case "Texttrack.mode = hidden/showing", if the mode
   // changing between showing and hidden, TimeMarchesOn
   // doesn't render the cue. Call DispatchUpdateCueDisplay() explicitly.
   DispatchUpdateCueDisplay();
 }
 
 void TextTrackManager::NotifyReset() {
   // https://html.spec.whatwg.org/multipage/media.html#text-track-cue-active-flag
@@ -861,10 +859,23 @@ void TextTrackManager::ReportTelemetryFo
 bool TextTrackManager::IsLoaded() {
   return mTextTracks ? mTextTracks->AreTextTracksLoaded() : true;
 }
 
 bool TextTrackManager::IsShutdown() const {
   return (mShutdown || !sParserWrapper);
 }
 
+void TextTrackManager::MaybeRunTimeMarchesOn() {
+  MOZ_ASSERT(mMediaElement);
+  // According to spec, we should check media element's show poster flag before
+  // running `TimeMarchesOn` in following situations, (1) add cue (2) remove cue
+  // (3) cue's start time changes (4) cues's end time changes
+  // https://html.spec.whatwg.org/multipage/media.html#playing-the-media-resource:time-marches-on
+  // https://html.spec.whatwg.org/multipage/media.html#text-track-api:time-marches-on
+  if (mMediaElement->GetShowPosterFlag()) {
+    return;
+  }
+  TimeMarchesOn();
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/html/TextTrackManager.h
+++ b/dom/html/TextTrackManager.h
@@ -150,16 +150,20 @@ class TextTrackManager final : public ns
   void ReportTelemetryForCue();
 
   bool IsShutdown() const;
 
   // If there is at least one cue has been added to the cue list once, we would
   // report the usage of cue to Telemetry.
   bool mCueTelemetryReported;
 
+  // This function will check media element's show poster flag to decide whether
+  // we need to run `TimeMarchesOn`.
+  void MaybeRunTimeMarchesOn();
+
   class ShutdownObserverProxy final : public nsIObserver {
     NS_DECL_ISUPPORTS
 
    public:
     explicit ShutdownObserverProxy(TextTrackManager* aManager)
         : mManager(aManager) {
       nsContentUtils::RegisterShutdownObserver(this);
     }
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -3733,33 +3733,33 @@ mozilla::ipc::IPCResult BrowserParent::R
   }
 
   widget->LookUpDictionary(aText, aFontRangeArray, aIsVertical,
                            TransformChildToParent(aPoint));
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult BrowserParent::RecvShowCanvasPermissionPrompt(
-    const nsCString& aOrigin, const bool& aHideDoorHanger) {
+    const nsCString& aFirstPartyURI, const bool& aHideDoorHanger) {
   nsCOMPtr<nsIBrowser> browser =
       mFrameElement ? mFrameElement->AsBrowser() : nullptr;
   if (!browser) {
     // If the tab is being closed, the browser may not be available.
     // In this case we can ignore the request.
     return IPC_OK();
   }
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (!os) {
     return IPC_FAIL_NO_REASON(this);
   }
   nsresult rv = os->NotifyObservers(
       browser,
       aHideDoorHanger ? "canvas-permissions-prompt-hide-doorhanger"
                       : "canvas-permissions-prompt",
-      NS_ConvertUTF8toUTF16(aOrigin).get());
+      NS_ConvertUTF8toUTF16(aFirstPartyURI).get());
   if (NS_FAILED(rv)) {
     return IPC_FAIL_NO_REASON(this);
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult BrowserParent::RecvVisitURI(
     const URIParams& aURI, const Maybe<URIParams>& aLastVisitedURI,
--- a/dom/ipc/BrowserParent.h
+++ b/dom/ipc/BrowserParent.h
@@ -705,17 +705,17 @@ class BrowserParent final : public PBrow
 
   mozilla::ipc::IPCResult RecvSetDimensions(const uint32_t& aFlags,
                                             const int32_t& aX,
                                             const int32_t& aY,
                                             const int32_t& aCx,
                                             const int32_t& aCy);
 
   mozilla::ipc::IPCResult RecvShowCanvasPermissionPrompt(
-      const nsCString& aOrigin, const bool& aHideDoorHanger);
+      const nsCString& aFirstPartyURI, const bool& aHideDoorHanger);
 
   mozilla::ipc::IPCResult RecvSetSystemFont(const nsCString& aFontName);
   mozilla::ipc::IPCResult RecvGetSystemFont(nsCString* aFontName);
 
   mozilla::ipc::IPCResult RecvVisitURI(const URIParams& aURI,
                                        const Maybe<URIParams>& aLastVisitedURI,
                                        const uint32_t& aFlags);
 
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -615,19 +615,19 @@ parent:
     // arrive before the BrowserChild attempts to use its cross-process compositor
     // bridge.
     sync EnsureLayersConnected() returns (CompositorOptions compositorOptions);
 
     /**
      * This function is used to notify the parent that it should display a
      * canvas permission prompt.
      *
-     * @param aOrigin origin string of the document that is requesting access.
+     * @param aFirstPartyURI first party of the tab that is requesting access.
      */
-    async ShowCanvasPermissionPrompt(nsCString aOrigin,
+    async ShowCanvasPermissionPrompt(nsCString aFirstPartyURI,
                                      bool aHideDoorHanger);
 
     sync SetSystemFont(nsCString aFontName);
     sync GetSystemFont() returns (nsCString retval);
 
     sync SetPrefersReducedMotionOverrideForTest(bool aValue);
     sync ResetPrefersReducedMotionOverrideForTest();
 
--- a/dom/media/test/test_webvtt_update_display_after_adding_or_removing_cue.html
+++ b/dom/media/test/test_webvtt_update_display_after_adding_or_removing_cue.html
@@ -5,33 +5,35 @@
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <script src="manifest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <script class="testbody" type="text/javascript">
 /**
  * This test is used to ensure that we will update cue display immediately after
- * adding or removing cue, even if the video hasn't started yet. In this test,
- * we start with adding a cue [0:5] to video, which should be showed in the
- * beginning, and then remove the cue later.
+ * adding or removing cue after video starts, because `show-poster` flag would be
+ * reset after video starts, which allows us to process cues instead of showing
+ * a poster. In this test, we start with adding a cue [0:5] to video, which
+ * should be showed in the beginning, and then remove the cue later. The cue
+ * should be removed immediately, not show
  */
 async function startTest() {
   const video = await createVideo();
+  await startVideo(video);
 
   info(`cue should be showed immediately after it was added.`);
   const cue = createCueAndAddCueToVideo(video);
   await waitUntilCueShows(cue);
 
   info(`cue should be hid immediately after it was removed.`);
   removeCueFromVideo(cue, video);
-  checkIfCueHides(cue);
+  checkIfCueHides(cue, video);
 
-  removeNodeAndSource(video);
-  SimpleTest.finish();
+  endTestAndClearVideo(video);
 }
 
 SimpleTest.waitForExplicitFinish();
 onload = startTest;
 
 /**
  * The following are test helper functions.
  */
@@ -41,37 +43,51 @@ async function createVideo() {
   video.controls = true;
   document.body.appendChild(video);
   // wait until media has loaded any data, because we won't update cue if it has
   // not got any data.
   await once(video, "loadedmetadata");
   return video;
 }
 
+async function startVideo(video) {
+  info(`start play video`);
+  const played = video && await video.play().then(() => true, () => false);
+  ok(played, "video has started playing");
+}
+
 function createCueAndAddCueToVideo(video) {
   let track = video.addTextTrack("subtitles");
   track.mode = "showing";
   let cue = new VTTCue(0, 5, "Test");
   track.addCue(cue);
   return cue;
 }
 
 function removeCueFromVideo(cue, video) {
   let track = video.textTracks[0];
   track.removeCue(cue);
 }
 
 async function waitUntilCueShows(cue) {
+  info(`wait until cue shows`);
   // cue has not been showed yet.
   cue = SpecialPowers.wrap(cue);
   if (!cue.getActive) {
     await once(cue, "enter");
   }
   ok(cue.getActive, `cue has been showed,`);
 }
 
-function checkIfCueHides(cue) {
+function checkIfCueHides(cue, video) {
   ok(!SpecialPowers.wrap(cue).getActive, `cue has been hidden.`);
+  ok(video.currentTime < cue.endTime,
+    `cue is removed at ${video.currentTime}s before reaching its endtime.`);
+}
+
+function endTestAndClearVideo(video) {
+  removeNodeAndSource(video);
+  SimpleTest.finish();
 }
 
 </script>
 </body>
 </html>
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -1112,33 +1112,31 @@ already_AddRefed<Promise> AudioContext::
     return promise.forget();
   }
 
   if (mAudioContextState == AudioContextState::Closed) {
     promise->MaybeResolve(NS_ERROR_DOM_INVALID_STATE_ERR);
     return promise.forget();
   }
 
-  if (Destination()) {
-    Destination()->DestroyAudioChannelAgent();
-  }
-
   mPromiseGripArray.AppendElement(promise);
 
   CloseInternal(promise, AudioContextOperationFlags::SendStateChange);
 
   return promise.forget();
 }
 
 void AudioContext::CloseInternal(void* aPromise,
                                  AudioContextOperationFlags aFlags) {
   // This can be called when freeing a document, and the streams are dead at
   // this point, so we need extra null-checks.
   AudioNodeStream* ds = DestinationStream();
   if (ds) {
+    Destination()->DestroyAudioChannelAgent();
+
     nsTArray<MediaStream*> streams;
     // If mSuspendCalled or mCloseCalled are true then we already suspended
     // all our streams, so don't suspend them again. But we still need to do
     // ApplyAudioContextOperation to ensure our new promise is resolved.
     if (!mSuspendCalled && !mCloseCalled) {
       streams = GetAllStreams();
     }
     Graph()->ApplyAudioContextOperation(
--- a/dom/messagechannel/tests/mm_messageChannelParent.js
+++ b/dom/messagechannel/tests/mm_messageChannelParent.js
@@ -13,17 +13,17 @@ function is(v1, v2, message) {
   return opener.wrappedJSObject.is(v1, v2, message);
 }
 
 function todo_is(v1, v2, message) {
   return opener.wrappedJSObject.todo_is(v1, v2, message);
 }
 
 function finish() {
-  opener.setTimeout("done()", 0);
+  opener.setTimeout(function() { this.done(); }, 0);
   window.close();
 }
 
 function debug(msg) {
   dump("[mmMessageChannelParent]" + msg + "\n");
 }
 
 let tests = [ basic_test,
--- a/dom/tests/mochitest/chrome/test_fullscreen.xul
+++ b/dom/tests/mochitest/chrome/test_fullscreen.xul
@@ -14,17 +14,17 @@
 SimpleTest.waitForExplicitFinish();
 
 newwindow = window.open("fullscreen.xul", "_blank","chrome,resizable=yes");
 
 function done()
 {
   // because we are cancelling the fullscreen event, it
   // takes a bit for the fullScreen property to be set
-  setTimeout("complete()", 0);
+  setTimeout(function() { this.complete(); }, 0);
 }
 
 function complete()
 {
   ok(newwindow.fullScreen, "window.fullScreen is true.");
   newwindow.close();
   SimpleTest.finish();
 }
--- a/dom/webauthn/U2FTokenManager.cpp
+++ b/dom/webauthn/U2FTokenManager.cpp
@@ -307,25 +307,30 @@ void U2FTokenManager::Register(
     AbortTransaction(aTransactionId, NS_ERROR_DOM_NOT_ALLOWED_ERR);
     return;
   }
 
   mLastTransactionId = aTransactionId;
 
   // Determine whether direct attestation was requested.
   bool directAttestationRequested = false;
+
+// On Android, let's always reject direct attestations until we have a
+// mechanism to solicit user consent, from Bug 1550164
+#ifndef MOZ_WIDGET_ANDROID
   if (aTransactionInfo.Extra().isSome()) {
     const auto& extra = aTransactionInfo.Extra().ref();
 
     AttestationConveyancePreference attestation =
         extra.attestationConveyancePreference();
 
     directAttestationRequested =
         attestation == AttestationConveyancePreference::Direct;
   }
+#endif  // not MOZ_WIDGET_ANDROID
 
   // Start a register request immediately if direct attestation
   // wasn't requested or the test pref is set.
   if (!directAttestationRequested ||
       U2FPrefManager::Get()->GetAllowDirectAttestationForTesting()) {
     // Force "none" attestation when "direct" attestation wasn't requested.
     DoRegister(aTransactionInfo, !directAttestationRequested);
     return;
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -579,9 +579,13 @@ partial interface Document {
 /**
  * Document extensions to support devtools.
  */
 partial interface Document {
   // Is the Document embedded in a Responsive Design Mode pane. This property
   // is not propegated to descendant Documents upon settting.
   [ChromeOnly]
   attribute boolean inRDMPane;
+  // Extension to give chrome JS the ability to set the window screen
+  // orientation while in RDM.
+  [ChromeOnly]
+  void setRDMPaneOrientation(OrientationType type, float rotationAngle);
 };
--- a/extensions/permissions/nsPermission.cpp
+++ b/extensions/permissions/nsPermission.cpp
@@ -26,17 +26,18 @@ nsPermission::nsPermission(nsIPrincipal*
       mExpireTime(aExpireTime),
       mModificationTime(aModificationTime) {}
 
 already_AddRefed<nsIPrincipal> nsPermission::ClonePrincipalForPermission(
     nsIPrincipal* aPrincipal) {
   MOZ_ASSERT(aPrincipal);
 
   mozilla::OriginAttributes attrs = aPrincipal->OriginAttributesRef();
-  attrs.StripAttributes(mozilla::OriginAttributes::STRIP_USER_CONTEXT_ID);
+  attrs.StripAttributes(mozilla::OriginAttributes::STRIP_USER_CONTEXT_ID |
+                        mozilla::OriginAttributes::STRIP_FIRST_PARTY_DOMAIN);
 
   nsAutoCString originNoSuffix;
   nsresult rv = aPrincipal->GetOriginNoSuffix(originNoSuffix);
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   nsCOMPtr<nsIURI> uri;
   rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix);
   NS_ENSURE_SUCCESS(rv, nullptr);
--- a/extensions/permissions/nsPermission.h
+++ b/extensions/permissions/nsPermission.h
@@ -18,17 +18,18 @@ class nsPermission : public nsIPermissio
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPERMISSION
 
   static already_AddRefed<nsPermission> Create(
       nsIPrincipal* aPrincipal, const nsACString& aType, uint32_t aCapability,
       uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime);
 
   // This method creates a new nsIPrincipal with a stripped OriginAttributes (no
-  // userContextId) and a codebase equal to the origin of 'aPrincipal'.
+  // userContextId, and no FirstPartyDomain) and a codebase equal to the origin
+  // of 'aPrincipal'.
   static already_AddRefed<nsIPrincipal> ClonePrincipalForPermission(
       nsIPrincipal* aPrincipal);
 
  protected:
   nsPermission(nsIPrincipal* aPrincipal, const nsACString& aType,
                uint32_t aCapability, uint32_t aExpireType, int64_t aExpireTime,
                int64_t aModificationTime);
 
--- a/extensions/permissions/nsPermissionManager.cpp
+++ b/extensions/permissions/nsPermissionManager.cpp
@@ -158,18 +158,19 @@ nsresult GetOriginFromPrincipal(nsIPrinc
     return NS_ERROR_FAILURE;
   }
 
   // mPrivateBrowsingId must be set to false because PermissionManager is not
   // supposed to have any knowledge of private browsing. Allowing it to be true
   // changes the suffix being hashed.
   attrs.mPrivateBrowsingId = 0;
 
-  // Disable userContext for permissions.
-  attrs.StripAttributes(mozilla::OriginAttributes::STRIP_USER_CONTEXT_ID);
+  // Disable userContext and firstParty isolation for permissions.
+  attrs.StripAttributes(mozilla::OriginAttributes::STRIP_USER_CONTEXT_ID |
+                        mozilla::OriginAttributes::STRIP_FIRST_PARTY_DOMAIN);
 
   attrs.CreateSuffix(suffix);
   aOrigin.Append(suffix);
   return NS_OK;
 }
 
 nsresult GetPrincipalFromOrigin(const nsACString& aOrigin,
                                 nsIPrincipal** aPrincipal) {
@@ -179,18 +180,19 @@ nsresult GetPrincipalFromOrigin(const ns
     return NS_ERROR_FAILURE;
   }
 
   // mPrivateBrowsingId must be set to false because PermissionManager is not
   // supposed to have any knowledge of private browsing. Allowing it to be true
   // changes the suffix being hashed.
   attrs.mPrivateBrowsingId = 0;
 
-  // Disable userContext for permissions.
-  attrs.StripAttributes(mozilla::OriginAttributes::STRIP_USER_CONTEXT_ID);
+  // Disable userContext and firstParty isolation for permissions.
+  attrs.StripAttributes(mozilla::OriginAttributes::STRIP_USER_CONTEXT_ID |
+                        mozilla::OriginAttributes::STRIP_FIRST_PARTY_DOMAIN);
 
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIPrincipal> principal =
       mozilla::BasePrincipal::CreateCodebasePrincipal(uri, attrs);
   principal.forget(aPrincipal);
@@ -271,18 +273,19 @@ already_AddRefed<nsIPrincipal> GetNextSu
   nsCOMPtr<nsIURI> newURI = GetNextSubDomainURI(uri);
   if (!newURI) {
     return nullptr;
   }
 
   // Copy the attributes over
   mozilla::OriginAttributes attrs = aPrincipal->OriginAttributesRef();
 
-  // Disable userContext for permissions.
-  attrs.StripAttributes(mozilla::OriginAttributes::STRIP_USER_CONTEXT_ID);
+  // Disable userContext and firstParty isolation for permissions.
+  attrs.StripAttributes(mozilla::OriginAttributes::STRIP_USER_CONTEXT_ID |
+                        mozilla::OriginAttributes::STRIP_FIRST_PARTY_DOMAIN);
 
   nsCOMPtr<nsIPrincipal> principal =
       mozilla::BasePrincipal::CreateCodebasePrincipal(newURI, attrs);
 
   return principal.forget();
 }
 
 class MOZ_STACK_CLASS UpgradeHostToOriginHelper {
@@ -1567,68 +1570,18 @@ nsresult nsPermissionManager::InitDB(boo
 
         rv = mDBConn->SetSchemaVersion(9);
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
         // fall through to the next upgrade
         MOZ_FALLTHROUGH;
 
-      // Version 10 removes appId from moz_hosts. SQLite doesn't support the
-      // dropping of columns from existing tables. We need to create a temporary
-      // table, copy the data, drop the old table, rename the new one.
       case 9: {
-        rv = mDBConn->BeginTransaction();
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        bool tableExists = false;
-        mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_v9"), &tableExists);
-        if (tableExists) {
-          NS_WARNING(
-              "The temporary database moz_hosts_v9 already exists, dropping "
-              "it.");
-          rv = mDBConn->ExecuteSimpleSQL(
-              NS_LITERAL_CSTRING("DROP TABLE moz_hosts_v9"));
-          NS_ENSURE_SUCCESS(rv, rv);
-        }
-
-        rv = mDBConn->ExecuteSimpleSQL(
-            NS_LITERAL_CSTRING("CREATE TABLE moz_hosts_v9 ("
-                               " id INTEGER PRIMARY KEY"
-                               ",host TEXT"
-                               ",type TEXT"
-                               ",permission INTEGER"
-                               ",expireType INTEGER"
-                               ",expireTime INTEGER"
-                               ",modificationTime INTEGER"
-                               ",isInBrowserElement INTEGER"
-                               ")"));
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-            "INSERT INTO moz_hosts_v9 "
-            "(id, host, type, permission, expireType, "
-            "expireTime, modificationTime, isInBrowserElement) "
-            "SELECT id, host, type, permission, expireType, expireTime, "
-            "modificationTime, isInBrowserElement FROM moz_hosts WHERE appId = "
-            "0"));
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        rv = mDBConn->ExecuteSimpleSQL(
-            NS_LITERAL_CSTRING("DROP TABLE moz_hosts"));
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        rv = mDBConn->ExecuteSimpleSQL(
-            NS_LITERAL_CSTRING("ALTER TABLE moz_hosts_v9 RENAME TO moz_hosts"));
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        rv = mDBConn->SetSchemaVersion(10);
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        rv = mDBConn->CommitTransaction();
+        rv = mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
         // fall through to the next upgrade
         MOZ_FALLTHROUGH;
 
       // current version.
       case HOSTS_SCHEMA_VERSION:
@@ -3323,18 +3276,19 @@ void nsPermissionManager::GetKeyForOrigi
     return;
   }
 
   // mPrivateBrowsingId must be set to false because PermissionManager is not
   // supposed to have any knowledge of private browsing. Allowing it to be true
   // changes the suffix being hashed.
   attrs.mPrivateBrowsingId = 0;
 
-  // Disable userContext for permissions.
-  attrs.StripAttributes(OriginAttributes::STRIP_USER_CONTEXT_ID);
+  // Disable userContext and firstParty isolation for permissions.
+  attrs.StripAttributes(OriginAttributes::STRIP_USER_CONTEXT_ID |
+                        OriginAttributes::STRIP_FIRST_PARTY_DOMAIN);
 
 #ifdef DEBUG
   // Parse the origin string into a principal, and extract some useful
   // information from it for assertions.
   nsCOMPtr<nsIPrincipal> dbgPrincipal;
   MOZ_ALWAYS_SUCCEEDS(
       GetPrincipalFromOrigin(aOrigin, getter_AddRefs(dbgPrincipal)));
   nsCOMPtr<nsIURI> dbgUri;
--- a/extensions/permissions/test/unit/test_permmanager_defaults.js
+++ b/extensions/permissions/test/unit/test_permmanager_defaults.js
@@ -92,85 +92,85 @@ add_task(async function do_test() {
   pm.removeAll();
 
   Assert.equal(Ci.nsIPermissionManager.ALLOW_ACTION,
                pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
   Assert.equal(Ci.nsIPermissionManager.ALLOW_ACTION,
                pm.testPermissionFromPrincipal(principal3, TEST_PERMISSION));
   Assert.equal(Ci.nsIPermissionManager.ALLOW_ACTION,
                pm.testPermissionFromPrincipal(principal4, TEST_PERMISSION));
-  // make sure principals with userContextId use the same permissions
+  // make sure principals with userContextId or firstPartyDomain use the same permissions
   Assert.equal(Ci.nsIPermissionManager.ALLOW_ACTION,
                pm.testPermissionFromPrincipal(principal6, TEST_PERMISSION));
-  // make sure principals with a firstPartyDomain use different permissions
-  Assert.equal(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+  Assert.equal(Ci.nsIPermissionManager.ALLOW_ACTION,
                pm.testPermissionFromPrincipal(principal7, TEST_PERMISSION));
-  Assert.equal(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+  Assert.equal(Ci.nsIPermissionManager.ALLOW_ACTION,
                pm.testPermissionFromPrincipal(principal8, TEST_PERMISSION));
 
   // Asking for this permission to be removed should result in that permission
   // having UNKNOWN_ACTION
   pm.removeFromPrincipal(principal, TEST_PERMISSION);
   Assert.equal(Ci.nsIPermissionManager.UNKNOWN_ACTION,
                pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
   // make sure principals with userContextId or firstPartyDomain use the same permissions
   Assert.equal(Ci.nsIPermissionManager.UNKNOWN_ACTION,
                pm.testPermissionFromPrincipal(principal6, TEST_PERMISSION));
+  Assert.equal(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+               pm.testPermissionFromPrincipal(principal7, TEST_PERMISSION));
+  Assert.equal(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+               pm.testPermissionFromPrincipal(principal8, TEST_PERMISSION));
   // and we should have this UNKNOWN_ACTION reflected in the DB
   await checkCapabilityViaDB(Ci.nsIPermissionManager.UNKNOWN_ACTION);
   // but the permission should *not* appear in the enumerator.
   Assert.equal(null, findCapabilityViaEnum());
 
   // and a subsequent RemoveAll should restore the default
   pm.removeAll();
 
   Assert.equal(Ci.nsIPermissionManager.ALLOW_ACTION,
                pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
-  // make sure principals with userContextId use the same permissions
+  // make sure principals with userContextId or firstPartyDomain use the same permissions
   Assert.equal(Ci.nsIPermissionManager.ALLOW_ACTION,
                pm.testPermissionFromPrincipal(principal6, TEST_PERMISSION));
-  // make sure principals with firstPartyDomain use different permissions
-  Assert.equal(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+  Assert.equal(Ci.nsIPermissionManager.ALLOW_ACTION,
                pm.testPermissionFromPrincipal(principal7, TEST_PERMISSION));
-  Assert.equal(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+  Assert.equal(Ci.nsIPermissionManager.ALLOW_ACTION,
                pm.testPermissionFromPrincipal(principal8, TEST_PERMISSION));
   // and allow it to again be seen in the enumerator.
   Assert.equal(Ci.nsIPermissionManager.ALLOW_ACTION, findCapabilityViaEnum());
 
   // now explicitly add a permission - this too should override the default.
   pm.addFromPrincipal(principal, TEST_PERMISSION, Ci.nsIPermissionManager.DENY_ACTION);
 
   // it should be reflected in a permission check, in the enumerator and the DB
   Assert.equal(Ci.nsIPermissionManager.DENY_ACTION,
                pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
-  // make sure principals with userContextId use the same permissions
+  // make sure principals with userContextId or firstPartyDomain use the same permissions
   Assert.equal(Ci.nsIPermissionManager.DENY_ACTION,
                pm.testPermissionFromPrincipal(principal6, TEST_PERMISSION));
-  // make sure principals with firstPartyDomain use different permissions
-  Assert.equal(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+  Assert.equal(Ci.nsIPermissionManager.DENY_ACTION,
                pm.testPermissionFromPrincipal(principal7, TEST_PERMISSION));
-  Assert.equal(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+  Assert.equal(Ci.nsIPermissionManager.DENY_ACTION,
                pm.testPermissionFromPrincipal(principal8, TEST_PERMISSION));
   Assert.equal(Ci.nsIPermissionManager.DENY_ACTION, findCapabilityViaEnum());
   await checkCapabilityViaDB(Ci.nsIPermissionManager.DENY_ACTION);
 
   // explicitly add a different permission - in this case we are no longer
   // replacing the default, but instead replacing the replacement!
   pm.addFromPrincipal(principal, TEST_PERMISSION, Ci.nsIPermissionManager.PROMPT_ACTION);
 
   // it should be reflected in a permission check, in the enumerator and the DB
   Assert.equal(Ci.nsIPermissionManager.PROMPT_ACTION,
                pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
-  // make sure principals with userContextId use the same permissions
+  // make sure principals with userContextId or firstPartyDomain use the same permissions
   Assert.equal(Ci.nsIPermissionManager.PROMPT_ACTION,
                pm.testPermissionFromPrincipal(principal6, TEST_PERMISSION));
-  // make sure principals with firstPartyDomain use different permissions
-  Assert.equal(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+  Assert.equal(Ci.nsIPermissionManager.PROMPT_ACTION,
                pm.testPermissionFromPrincipal(principal7, TEST_PERMISSION));
-  Assert.equal(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+  Assert.equal(Ci.nsIPermissionManager.PROMPT_ACTION,
                pm.testPermissionFromPrincipal(principal8, TEST_PERMISSION));
   Assert.equal(Ci.nsIPermissionManager.PROMPT_ACTION, findCapabilityViaEnum());
   await checkCapabilityViaDB(Ci.nsIPermissionManager.PROMPT_ACTION);
 
   // --------------------------------------------------------------
   // check default permissions and removeAllSince work as expected.
   pm.removeAll(); // ensure only defaults are there.
 
--- a/extensions/permissions/test/unit/test_permmanager_matches.js
+++ b/extensions/permissions/test/unit/test_permmanager_matches.js
@@ -73,39 +73,39 @@ function run_test() {
   let perm_n = pm.getPermissionObject(uri0_n, "test/matches", true);
   pm.addFromPrincipal(uri0_y_, "test/matches", pm.ALLOW_ACTION);
   let perm_y_ = pm.getPermissionObject(uri0_y_, "test/matches", true);
   pm.addFromPrincipal(uri0_1, "test/matches", pm.ALLOW_ACTION);
   let perm_1 = pm.getPermissionObject(uri0_n, "test/matches", true);
   pm.addFromPrincipal(uri0_cnn, "test/matches", pm.ALLOW_ACTION);
   let perm_cnn = pm.getPermissionObject(uri0_n, "test/matches", true);
 
-  matches_always(perm_n, [uri0_n, uri0_1]);
-  matches_weak(perm_n, [uri1_n, uri1_1]);
+  matches_always(perm_n, [uri0_n, uri0_1, uri0_cnn]);
+  matches_weak(perm_n, [uri1_n, uri1_1, uri1_cnn]);
   matches_never(perm_n, [uri2_n, uri3_n, uri4_n, uri5_n,
                            uri0_y_, uri1_y_, uri2_y_, uri3_y_, uri4_y_, uri5_y_,
                            uri2_1, uri3_1, uri4_1, uri5_1,
-                           uri0_cnn, uri1_cnn, uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
+                           uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
 
   matches_always(perm_y_, [uri0_y_]);
   matches_weak(perm_y_, [uri1_y_]);
   matches_never(perm_y_, [uri2_y_, uri3_y_, uri4_y_, uri5_y_,
                               uri0_n, uri1_n, uri2_n, uri3_n, uri4_n, uri5_n,
                               uri0_1, uri1_1, uri2_1, uri3_1, uri4_1, uri5_1,
                               uri0_cnn, uri1_cnn, uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
 
-  matches_always(perm_1, [uri0_n, uri0_1]);
-  matches_weak(perm_1, [uri1_n, uri1_1]);
+  matches_always(perm_1, [uri0_n, uri0_1, uri0_cnn]);
+  matches_weak(perm_1, [uri1_n, uri1_1, uri1_cnn]);
   matches_never(perm_1, [uri2_n, uri3_n, uri4_n, uri5_n,
                          uri0_y_, uri1_y_, uri2_y_, uri3_y_, uri4_y_, uri5_y_,
                          uri2_1, uri3_1, uri4_1, uri5_1,
-                         uri0_cnn, uri1_cnn, uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
+                         uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
 
-  matches_always(perm_cnn, [uri0_n, uri0_1]);
-  matches_weak(perm_cnn, [uri1_n, uri1_1]);
+  matches_always(perm_cnn, [uri0_n, uri0_1, uri0_cnn]);
+  matches_weak(perm_cnn, [uri1_n, uri1_1, uri1_cnn]);
   matches_never(perm_cnn, [uri2_n, uri3_n, uri4_n, uri5_n,
                            uri0_y_, uri1_y_, uri2_y_, uri3_y_, uri4_y_, uri5_y_,
                            uri2_1, uri3_1, uri4_1, uri5_1,
-                           uri0_cnn, uri1_cnn, uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
+                           uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
 
   // Clean up!
   pm.removeAll();
 }
--- a/extensions/permissions/test/unit/test_permmanager_migrate_9-10.js
+++ b/extensions/permissions/test/unit/test_permmanager_migrate_9-10.js
@@ -16,17 +16,17 @@ function GetPermissionsFile(profile)
 add_task(async function test() {
   /* Create and set up the permissions database */
   let profile = do_get_profile();
   Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
 
   let db = Services.storage.openDatabase(GetPermissionsFile(profile));
   db.schemaVersion = 9;
   db.executeSimpleSQL("DROP TABLE moz_perms");
-  db.executeSimpleSQL("DROP TABLE moz_hosts");
+  db.executeSimpleSQL("DROP TABLE IF EXISTS moz_hosts");
 
   db.executeSimpleSQL(
     "CREATE TABLE moz_perms (" +
       " id INTEGER PRIMARY KEY" +
       ",origin TEXT" +
       ",type TEXT" +
       ",permission INTEGER" +
       ",expireType INTEGER" +
@@ -194,17 +194,17 @@ add_task(async function test() {
     let db = Services.storage.openDatabase(GetPermissionsFile(profile));
     Assert.ok(db.tableExists("moz_perms"));
     Assert.ok(db.tableExists("moz_hosts"));
     Assert.ok(!db.tableExists("moz_perms_v6"));
 
     let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
     try {
       mozHostsCount.executeStep();
-      Assert.equal(mozHostsCount.getInt64(0), 1);
+      Assert.equal(mozHostsCount.getInt64(0), 3);
     } finally {
       mozHostsCount.finalize();
     }
 
     let mozPermsCount = db.createStatement("SELECT count(*) FROM moz_perms");
     try {
       mozPermsCount.executeStep();
       Assert.equal(mozPermsCount.getInt64(0), expected.length);
--- a/gfx/layers/AnimationHelper.cpp
+++ b/gfx/layers/AnimationHelper.cpp
@@ -590,53 +590,59 @@ bool AnimationHelper::SampleAnimations(C
   }
 
   return isAnimating;
 }
 
 gfx::Matrix4x4 AnimationHelper::ServoAnimationValueToMatrix4x4(
     const nsTArray<RefPtr<RawServoAnimationValue>>& aValues,
     const TransformData& aTransformData) {
-  // FIXME: Bug 1457033: We should convert servo's animation value to matrix
-  // directly without nsCSSValueSharedList.
+  // This is a bit silly just to avoid the transform list copy from the
+  // animation transform list.
+  auto noneTranslate = StyleTranslate::None();
+  auto noneRotate = StyleRotate::None();
+  auto noneScale = StyleScale::None();
+  const StyleTransform noneTransform;
+
+  const StyleTranslate* translate = nullptr;
+  const StyleRotate* rotate = nullptr;
+  const StyleScale* scale = nullptr;
+  const StyleTransform* transform = nullptr;
+
   // TODO: Bug 1429305: Support compositor animations for motion-path.
-  RefPtr<nsCSSValueSharedList> transform, translate, rotate, scale;
   for (const auto& value : aValues) {
     MOZ_ASSERT(value);
-    RefPtr<nsCSSValueSharedList> list;
-    nsCSSPropertyID id = Servo_AnimationValue_GetTransform(value, &list);
+    nsCSSPropertyID id = Servo_AnimationValue_GetPropertyId(value);
     switch (id) {
       case eCSSProperty_transform:
         MOZ_ASSERT(!transform);
-        transform = list.forget();
+        transform = Servo_AnimationValue_GetTransform(value);
         break;
       case eCSSProperty_translate:
         MOZ_ASSERT(!translate);
-        translate = list.forget();
+        translate = Servo_AnimationValue_GetTranslate(value);
         break;
       case eCSSProperty_rotate:
         MOZ_ASSERT(!rotate);
-        rotate = list.forget();
+        rotate = Servo_AnimationValue_GetRotate(value);
         break;
       case eCSSProperty_scale:
         MOZ_ASSERT(!scale);
-        scale = list.forget();
+        scale = Servo_AnimationValue_GetScale(value);
         break;
       default:
         MOZ_ASSERT_UNREACHABLE("Unsupported transform-like property");
     }
   }
-  RefPtr<nsCSSValueSharedList> individualList =
-      nsStyleDisplay::GenerateCombinedIndividualTransform(translate, rotate,
-                                                          scale);
-
   // We expect all our transform data to arrive in device pixels
   gfx::Point3D transformOrigin = aTransformData.transformOrigin();
   nsDisplayTransform::FrameTransformProperties props(
-      std::move(individualList), std::move(transform), transformOrigin);
+      translate ? *translate : noneTranslate, rotate ? *rotate : noneRotate,
+      scale ? *scale : noneScale, transform ? *transform : noneTransform,
+      transformOrigin);
 
   return nsDisplayTransform::GetResultingTransformMatrix(
       props, aTransformData.origin(), aTransformData.appUnitsPerDevPixel(), 0,
       &aTransformData.bounds());
 }
 
 }  // namespace layers
 }  // namespace mozilla
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -41,18 +41,16 @@
 #  include "mozilla/layers/UiCompositorControllerParent.h"
 #  include "mozilla/widget/AndroidCompositorWidget.h"
 #endif
 #include "GeckoProfiler.h"
 #include "FrameUniformityData.h"
 #include "TreeTraversal.h"  // for ForEachNode, BreadthFirstSearch
 #include "VsyncSource.h"
 
-struct nsCSSValueSharedList;
-
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gfx;
 
 static bool IsSameDimension(hal::ScreenOrientation o1,
                             hal::ScreenOrientation o2) {
   bool isO1portrait = (o1 == hal::eScreenOrientation_PortraitPrimary ||
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -2606,21 +2606,27 @@ int32_t RecordContentFrameTime(
   }
 
   return 0;
 }
 
 mozilla::ipc::IPCResult CompositorBridgeParent::RecvBeginRecording(
     const TimeStamp& aRecordingStart) {
   mCompositionRecorder.reset(new CompositionRecorder(aRecordingStart));
-  mLayerManager->SetCompositionRecorder(mCompositionRecorder.get());
+
+  if (mLayerManager) {
+    mLayerManager->SetCompositionRecorder(mCompositionRecorder.get());
+  }
+
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult CompositorBridgeParent::RecvEndRecording() {
-  mLayerManager->SetCompositionRecorder(nullptr);
+  if (mLayerManager) {
+    mLayerManager->SetCompositionRecorder(nullptr);
+  }
   mCompositionRecorder->WriteCollectedFrames();
   mCompositionRecorder.reset(nullptr);
   return IPC_OK();
 }
 
 }  // namespace layers
 }  // namespace mozilla
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -778,17 +778,16 @@ class gfxPrefs final {
 
   // WebGL (for pref access from Worker threads)
   DECL_GFX_PREF(Live, "webgl.1.allow-core-profiles",           WebGL1AllowCoreProfile, bool, false);
 
   DECL_GFX_PREF(Live, "webgl.all-angle-options",               WebGLAllANGLEOptions, bool, false);
   DECL_GFX_PREF(Live, "webgl.angle.force-d3d11",               WebGLANGLEForceD3D11, bool, false);
   DECL_GFX_PREF(Live, "webgl.angle.try-d3d11",                 WebGLANGLETryD3D11, bool, false);
   DECL_GFX_PREF(Live, "webgl.angle.force-warp",                WebGLANGLEForceWARP, bool, false);
-  DECL_GFX_PREF(Live, "webgl.bypass-shader-validation",        WebGLBypassShaderValidator, bool, true);
   DECL_GFX_PREF(Live, "webgl.can-lose-context-in-foreground",  WebGLCanLoseContextInForeground, bool, true);
   DECL_GFX_PREF(Live, "webgl.default-low-power",               WebGLDefaultLowPower, bool, false);
   DECL_GFX_PREF(Live, "webgl.default-no-alpha",                WebGLDefaultNoAlpha, bool, false);
   DECL_GFX_PREF(Live, "webgl.disable-angle",                   WebGLDisableANGLE, bool, false);
   DECL_GFX_PREF(Live, "webgl.disable-wgl",                     WebGLDisableWGL, bool, false);
   DECL_GFX_PREF(Live, "webgl.disable-extensions",              WebGLDisableExtensions, bool, false);
   DECL_GFX_PREF(Live, "webgl.dxgl.enabled",                    WebGLDXGLEnabled, bool, false);
   DECL_GFX_PREF(Live, "webgl.dxgl.needs-finish",               WebGLDXGLNeedsFinish, bool, false);
--- a/gfx/vr/moz.build
+++ b/gfx/vr/moz.build
@@ -64,16 +64,23 @@ SOURCES += [
 
 if CONFIG['OS_TARGET'] == 'Android':
     LOCAL_INCLUDES += ['/widget/android']
 else:
     DIRS += [
         'service',
     ]
 
+# Only target x64 for vrhost since WebVR is only supported on 64bit.
+# Also, only use MSVC compiler for Windows-specific linking
+if CONFIG['OS_ARCH'] == 'WINNT' and CONFIG['HAVE_64BIT_BUILD'] and CONFIG['CC_TYPE'] not in ('clang', 'gcc'):
+    DIRS += [
+        'vrhost'
+    ]
+
 IPDL_SOURCES = [
     'ipc/PVR.ipdl',
     'ipc/PVRGPU.ipdl',
     'ipc/PVRLayer.ipdl',
     'ipc/PVRManager.ipdl',
 ]
 
 CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
new file mode 100644
--- /dev/null
+++ b/gfx/vr/vrhost/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+SOURCES += [
+  'vrhost.cpp'
+]
+
+EXPORTS.vrhost = [
+  'vrhostex.h'
+]
+
+DEFFILE = 'vrhost.def'
+
+DIRS += [
+  'testhost'
+]
+
+# Use SharedLibrary to generate the dll
+SharedLibrary('vrhost')
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/gfx/vr/vrhost/testhost/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+SOURCES += [
+  'testhost.cpp'
+]
+
+USE_LIBS += [
+  'vrhost'
+]
+
+# Use Progam to generate the executable
+Program('vrtesthost')
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/gfx/vr/vrhost/testhost/testhost.cpp
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+#include "vrhost/vrhostex.h"
+
+int main() {
+  HINSTANCE hVR = ::LoadLibrary("vrhost.dll");
+  if (hVR != nullptr) {
+    PFN_SAMPLE lpfnSample = (PFN_SAMPLE)GetProcAddress(hVR, "SampleExport");
+    if (lpfnSample != nullptr) {
+      lpfnSample();
+    }
+
+    ::FreeLibrary(hVR);
+    hVR = nullptr;
+  }
+
+  return 0;
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/gfx/vr/vrhost/vrhost.cpp
@@ -0,0 +1,13 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// vrhost.cpp
+// Definition of functions that are exported from this dll
+
+#include "vrhostex.h"
+#include <stdio.h>
+
+void SampleExport() { printf("vrhost.cpp hello world"); }
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/gfx/vr/vrhost/vrhost.def
@@ -0,0 +1,7 @@
+;+# 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/.
+
+LIBRARY        vrhost.dll
+
+EXPORTS        SampleExport      PRIVATE
new file mode 100644
--- /dev/null
+++ b/gfx/vr/vrhost/vrhostex.h
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// vrhostex.h
+// This file declares the functions and their typedefs for functions exported
+// by vrhost.dll
+
+#pragma once
+
+// void SampleExport();
+typedef void (*PFN_SAMPLE)();
\ No newline at end of file
--- a/gfx/wr/webrender/src/device/gl.rs
+++ b/gfx/wr/webrender/src/device/gl.rs
@@ -999,63 +999,93 @@ pub struct Device {
     optimal_pbo_stride: NonZeroUsize,
 
     // GL extensions
     extensions: Vec<String>,
 }
 
 /// Contains the parameters necessary to bind a draw target.
 #[derive(Clone, Copy)]
-pub enum DrawTarget<'a> {
+pub enum DrawTarget {
     /// Use the device's default draw target, with the provided dimensions,
     /// which are used to set the viewport.
     Default {
         /// Target rectangle to draw.
         rect: FramebufferIntRect,
         /// Total size of the target.
         total_size: FramebufferIntSize,
     },
     /// Use the provided texture.
     Texture {
-        /// The target texture.
-        texture: &'a Texture,
-        /// The slice within the texture array to draw to.
+        /// Size of the texture in pixels
+        dimensions: DeviceIntSize,
+        /// The slice within the texture array to draw to
         layer: LayerIndex,
-        /// Whether to draw with the texture's associated depth target.
+        /// Whether to draw with the texture's associated depth target
         with_depth: bool,
+        /// Workaround buffers for devices with broken texture array copy implementation
+        blit_workaround_buffer: Option<(RBOId, FBOId)>,
+        /// FBO that corresponds to the selected layer / depth mode
+        fbo_id: FBOId,
+        /// Native GL texture ID
+        id: gl::GLuint,
+        /// Native GL texture target
+        target: gl::GLuint,
     },
     /// Use an FBO attached to an external texture.
     External {
         fbo: FBOId,
         size: FramebufferIntSize,
     },
 }
 
-impl<'a> DrawTarget<'a> {
+impl DrawTarget {
     pub fn new_default(size: DeviceIntSize) -> Self {
         let total_size = FramebufferIntSize::from_untyped(&size.to_untyped());
         DrawTarget::Default {
             rect: total_size.into(),
             total_size,
         }
     }
 
     /// Returns true if this draw target corresponds to the default framebuffer.
     pub fn is_default(&self) -> bool {
         match *self {
             DrawTarget::Default {..} => true,
             _ => false,
         }
     }
 
+    pub fn from_texture(
+        texture: &Texture,
+        layer: usize,
+        with_depth: bool,
+    ) -> Self {
+        let fbo_id = if with_depth {
+            texture.fbos_with_depth[layer]
+        } else {
+            texture.fbos[layer]
+        };
+
+        DrawTarget::Texture {
+            dimensions: texture.get_dimensions(),
+            fbo_id,
+            with_depth,
+            layer,
+            blit_workaround_buffer: texture.blit_workaround_buffer,
+            id: texture.id,
+            target: texture.target,
+        }
+    }
+
     /// Returns the dimensions of this draw-target.
     pub fn dimensions(&self) -> DeviceIntSize {
         match *self {
             DrawTarget::Default { total_size, .. } => DeviceIntSize::from_untyped(&total_size.to_untyped()),
-            DrawTarget::Texture { texture, .. } => texture.get_dimensions(),
+            DrawTarget::Texture { dimensions, .. } => dimensions,
             DrawTarget::External { size, .. } => DeviceIntSize::from_untyped(&size.to_untyped()),
         }
     }
 
     pub fn to_framebuffer_rect(&self, device_rect: DeviceIntRect) -> FramebufferIntRect {
         let mut fb_rect = FramebufferIntRect::from_untyped(&device_rect.to_untyped());
         match *self {
             DrawTarget::Default { ref rect, .. } => {
@@ -1096,38 +1126,47 @@ impl<'a> DrawTarget<'a> {
                 )
             }
         }
     }
 }
 
 /// Contains the parameters necessary to bind a texture-backed read target.
 #[derive(Clone, Copy)]
-pub enum ReadTarget<'a> {
+pub enum ReadTarget {
     /// Use the device's default draw target.
     Default,
     /// Use the provided texture,
     Texture {
-        /// The source texture.
-        texture: &'a Texture,
-        /// The slice within the texture array to read from.
-        layer: LayerIndex,
+        /// ID of the FBO to read from.
+        fbo_id: FBOId,
     },
     /// Use an FBO attached to an external texture.
     External {
         fbo: FBOId,
     },
 }
 
-impl<'a> From<DrawTarget<'a>> for ReadTarget<'a> {
-    fn from(t: DrawTarget<'a>) -> Self {
+impl ReadTarget {
+    pub fn from_texture(
+        texture: &Texture,
+        layer: usize,
+    ) -> Self {
+        ReadTarget::Texture {
+            fbo_id: texture.fbos[layer],
+        }
+    }
+}
+
+impl From<DrawTarget> for ReadTarget {
+    fn from(t: DrawTarget) -> Self {
         match t {
             DrawTarget::Default { .. } => ReadTarget::Default,
-            DrawTarget::Texture { texture, layer, .. } =>
-                ReadTarget::Texture { texture, layer },
+            DrawTarget::Texture { fbo_id, .. } =>
+                ReadTarget::Texture { fbo_id },
             DrawTarget::External { fbo, .. } =>
                 ReadTarget::External { fbo },
         }
     }
 }
 
 impl Device {
     pub fn new(
@@ -1502,17 +1541,17 @@ impl Device {
             self.bound_read_fbo = fbo_id;
             fbo_id.bind(self.gl(), FBOTarget::Read);
         }
     }
 
     pub fn bind_read_target(&mut self, target: ReadTarget) {
         let fbo_id = match target {
             ReadTarget::Default => self.default_read_fbo,
-            ReadTarget::Texture { texture, layer } => texture.fbos[layer],
+            ReadTarget::Texture { fbo_id } => fbo_id,
             ReadTarget::External { fbo } => fbo,
         };
 
         self.bind_read_target_impl(fbo_id)
     }
 
     fn bind_draw_target_impl(&mut self, fbo_id: FBOId) {
         debug_assert!(self.inside_frame);
@@ -1536,26 +1575,22 @@ impl Device {
     }
 
     pub fn bind_draw_target(
         &mut self,
         target: DrawTarget,
     ) {
         let (fbo_id, rect, depth_available) = match target {
             DrawTarget::Default { rect, .. } => (self.default_draw_fbo, rect, true),
-            DrawTarget::Texture { texture, layer, with_depth } => {
+            DrawTarget::Texture { dimensions, fbo_id, with_depth, .. } => {
                 let rect = FramebufferIntRect::new(
                     FramebufferIntPoint::zero(),
-                    FramebufferIntSize::from_untyped(&texture.get_dimensions().to_untyped()),
+                    FramebufferIntSize::from_untyped(&dimensions.to_untyped()),
                 );
-                if with_depth {
-                    (texture.fbos_with_depth[layer], rect, true)
-                } else {
-                    (texture.fbos[layer], rect, false)
-                }
+                (fbo_id, rect, with_depth)
             },
             DrawTarget::External { fbo, size } => (fbo, size.into(), false),
         };
 
         self.depth_available = depth_available;
         self.bind_draw_target_impl(fbo_id);
         self.gl.viewport(
             rect.origin.x,
@@ -1935,19 +1970,19 @@ impl Device {
             }
         } else {
             let rect = FramebufferIntRect::new(
                 FramebufferIntPoint::zero(),
                 FramebufferIntSize::from_untyped(&src.get_dimensions().to_untyped()),
             );
             for layer in 0..src.layer_count.min(dst.layer_count) as LayerIndex {
                 self.blit_render_target(
-                    ReadTarget::Texture { texture: src, layer },
+                    ReadTarget::from_texture(src, layer),
                     rect,
-                    DrawTarget::Texture { texture: dst, layer, with_depth: false },
+                    DrawTarget::from_texture(dst, layer, false),
                     rect,
                     TextureFilter::Linear
                 );
             }
             self.reset_draw_target();
             self.reset_read_target();
         }
     }
@@ -2134,21 +2169,21 @@ impl Device {
         dest_rect: FramebufferIntRect,
         filter: TextureFilter,
     ) {
         debug_assert!(self.inside_frame);
 
         self.bind_read_target(src_target);
 
         match dest_target {
-            DrawTarget::Texture { texture, layer, .. } if layer != 0 &&
+            DrawTarget::Texture { layer, blit_workaround_buffer, dimensions, id, target, .. } if layer != 0 &&
                 !self.capabilities.supports_blit_to_texture_array =>
             {
                 // This should have been initialized in create_texture().
-                let (_rbo, fbo) = texture.blit_workaround_buffer.expect("Blit workaround buffer has not been initialized.");
+                let (_rbo, fbo) = blit_workaround_buffer.expect("Blit workaround buffer has not been initialized.");
 
                 // Blit from read target to intermediate buffer.
                 self.bind_draw_target_impl(fbo);
                 self.blit_render_target_impl(
                     src_rect,
                     dest_rect,
                     filter
                 );
@@ -2161,24 +2196,28 @@ impl Device {
                     DeviceIntPoint::new(
                         dest_rect.min_x().min(dest_rect.max_x()),
                         dest_rect.min_y().min(dest_rect.max_y()),
                     ),
                     DeviceIntSize::new(
                         dest_rect.size.width.abs(),
                         dest_rect.size.height.abs(),
                     ),
-                ).intersection(&texture.size.into()).unwrap_or(DeviceIntRect::zero());
+                ).intersection(&dimensions.into()).unwrap_or(DeviceIntRect::zero());
 
                 self.bind_read_target_impl(fbo);
-                self.bind_texture(DEFAULT_TEXTURE, texture);
+                self.bind_texture_impl(
+                    DEFAULT_TEXTURE,