Merge mozilla-central to mozilla-beta a=same-version-merge l10n=same-version-merge on a CLOSED TREE. DEVEDITION_68_0b3_BUILD1 DEVEDITION_68_0b3_RELEASE FIREFOX_68_0b3_BUILD1
authorGurzau Raul <rgurzau@mozilla.com>
Mon, 20 May 2019 16:08:57 +0300
changeset 533294 847755a7c3256dec59ea6936435b0cd5a2b1861e
parent 532865 e3bc6067298eaab8fff7a772a849c7bb9607809d (current diff)
parent 533293 97dae745c1b3ef2292127ba1c4e90b1345c8f576 (diff)
child 533295 4d774e730d76d2eae15bde2e4eda059920c97d39
push id11276
push userrgurzau@mozilla.com
push dateMon, 20 May 2019 13:11:24 +0000
treeherdermozilla-beta@847755a7c325 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssame-version-merge
milestone68.0
Merge mozilla-central to mozilla-beta a=same-version-merge l10n=same-version-merge on a CLOSED TREE.
browser/components/newtab/README.md
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
devtools/client/framework/attach-thread.js
devtools/client/webreplay/mochitest/browser_dbg_rr_recovery-01.js
devtools/client/webreplay/mochitest/examples/doc_rr_recovery.html
dom/html/test/forms/file_bug1495363.html
dom/html/test/forms/file_bug1495363.sjs
dom/html/test/forms/test_bug1495363.html
layout/printing/nsPrintPreviewListener.cpp
layout/printing/nsPrintPreviewListener.h
media/libcubeb/disable-device-switching.patch
python/l10n/fluent_migrations/bug_1517307_resetprofile.py
python/l10n/fluent_migrations/bug_1517493_pageInfo.py
python/l10n/fluent_migrations/bug_1517508_aboutRobots.py
python/l10n/fluent_migrations/bug_1517519_aboutTabCrashed.py
python/l10n/fluent_migrations/bug_1519923_aboutRights.py
python/l10n/fluent_migrations/bug_1523737_printDialogs.py
python/l10n/fluent_migrations/bug_1524777_certViewer.py
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
testing/web-platform/meta/css/filter-effects/filters-test-brightness-003.html.ini
testing/web-platform/meta/css/filter-effects/parsing/filter-computed.html.ini
testing/web-platform/meta/css/filter-effects/parsing/filter-parsing-valid.html.ini
testing/web-platform/meta/intersection-observer/display-none.html.ini
testing/web-platform/meta/intersection-observer/iframe-no-root.html.ini
testing/web-platform/meta/intersection-observer/multiple-thresholds.html.ini
testing/web-platform/meta/intersection-observer/same-document-no-root.html.ini
testing/web-platform/meta/intersection-observer/same-document-root.html.ini
testing/web-platform/meta/intersection-observer/same-document-zero-size-target.html.ini
testing/web-platform/meta/intersection-observer/shadow-content.html.ini
testing/web-platform/meta/intersection-observer/text-target.html.ini
testing/web-platform/meta/intersection-observer/v2/animated-occlusion.html.ini
testing/web-platform/meta/intersection-observer/zero-area-element-hidden.html.ini
third_party/rust/cranelift-codegen/meta-python/gen_legalizer.py
third_party/rust/cranelift-codegen/meta-python/gen_settings.py
third_party/rust/cranelift-codegen/meta-python/test_gen_legalizer.py
third_party/rust/fuchsia-cprng/LICENSE
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/mio/ci/ios/deploy_and_run_on_ios_simulator.rs
third_party/rust/mio/ci/run-ios.sh
third_party/rust/mio/test/test_timer.rs
third_party/rust/rand-0.4.3/appveyor.yml
third_party/rust/rand-0.4.3/benches/bench.rs
third_party/rust/rand-0.4.3/benches/distributions/exponential.rs
third_party/rust/rand-0.4.3/benches/distributions/gamma.rs
third_party/rust/rand-0.4.3/benches/distributions/mod.rs
third_party/rust/rand-0.4.3/benches/distributions/normal.rs
third_party/rust/rand-0.4.3/src/distributions/range.rs
third_party/rust/rand-0.4.3/src/jitter.rs
third_party/rust/rand-0.4.3/src/os.rs
third_party/rust/rand-0.4.3/src/prng/chacha.rs
third_party/rust/rand-0.4.3/src/prng/isaac.rs
third_party/rust/rand-0.4.3/src/prng/isaac64.rs
third_party/rust/rand-0.4.3/src/prng/xorshift.rs
third_party/rust/rand-0.4.3/src/rand_impls.rs
third_party/rust/rand-0.4.3/src/read.rs
third_party/rust/rand-0.4.3/src/reseeding.rs
third_party/rust/rand-0.4.3/src/seq.rs
third_party/rust/rand-0.4.3/utils/ziggurat_tables.py
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/rand_jitter/src/lib.rs
third_party/rust/sha1/.cargo-checksum.json
third_party/rust/sha1/Cargo.toml
third_party/rust/sha1/LICENSE
third_party/rust/sha1/README.md
third_party/rust/sha1/src/lib.rs
toolkit/components/places/tests/sync/test_bookmark_validation.js
toolkit/mozapps/extensions/content/updateinfo.xsl
toolkit/recordreplay/ipc/ChildNavigation.cpp
toolkit/themes/osx/global/icons/searchfield-cancel.svg
toolkit/themes/windows/global/icons/Search-close.png
--- a/.cargo/config.in
+++ b/.cargo/config.in
@@ -19,13 +19,13 @@ replace-with = "vendored-sources"
 
 [source."https://github.com/rust-lang-nursery/packed_simd"]
 git = "https://github.com/hsivonen/packed_simd"
 branch = "rust_1_32"
 replace-with = "vendored-sources"
 
 [source."https://github.com/CraneStation/Cranelift"]
 git = "https://github.com/CraneStation/Cranelift"
-rev = "538a0662bf90a1daa9921c10f34827ace134abf1"
+rev = "cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
 replace-with = "vendored-sources"
 
 [source.vendored-sources]
 directory = '@top_srcdir@/third_party/rust'
--- a/.eslintignore
+++ b/.eslintignore
@@ -96,22 +96,51 @@ devtools/server/tests/browser/storage-*.
 !devtools/server/tests/browser/storage-unsecured-iframe.html
 devtools/server/tests/browser/stylesheets-nested-iframes.html
 devtools/client/shared/webpack/shims/test/test_clipboard.html
 devtools/shared/qrcode/tests/mochitest/test_decode.html
 devtools/shared/tests/mochitest/*.html
 devtools/shared/webconsole/test/test_*.html
 devtools/client/webreplay/mochitest/examples/*.html
 
-# Soon to be removed, the new/ directory is explicitly excluded below due to
-# also being an imported repository.
-devtools/client/debugger/**
+# Ignore devtools debugger files
+# Keep in sync with devtools/client/debugger/.eslintignore
+devtools/client/debugger/assets/*
+devtools/client/debugger/src/test/examples/**
+devtools/client/debugger/src/test/integration/**
+devtools/client/debugger/src/test/unit-sources/**
+devtools/client/debugger/src/**/fixtures/**
+devtools/client/debugger/src/test/mochitest/**
+devtools/client/debugger/bin/
+devtools/client/debugger/packages/**/fixtures/**
+devtools/client/debugger/node_modules
+devtools/client/debugger/out
+
+# Ignore devtools debugger files
+# Keep in sync with devtools/client/debugger/.prettierignore
+devtools/client/debugger/src/workers/parser/tests/fixtures/functionNames.js
+devtools/client/debugger/src/workers/parser/tests/fixtures/scopes/*.js
+devtools/client/debugger/src/workers/parser/tests/fixtures/pause/*.js
+devtools/client/debugger/src/test/mochitest/examples/babel/polyfill-bundle.js
+devtools/client/debugger/src/test/mochitest/examples/babel/fixtures/*/input.js
+devtools/client/debugger/src/test/mochitest/examples/babel/fixtures/*/output.js
+devtools/client/debugger/src/test/mochitest/examples/babel/fixtures/*/output.js.map
+devtools/client/debugger/src/test/mochitest/examples/ember/quickstart
+
+# Ignore devtools debugger files which aren't intended for linting, and also
+# aren't included in any .eslintignore or .prettierignore file.
+# See https://github.com/firefox-devtools/debugger/blob/master/package.json#L24
+devtools/client/debugger/configs/**
+devtools/client/debugger/dist/**
+devtools/client/debugger/flow-typed/**
+devtools/client/debugger/images/**
+devtools/client/debugger/test/**
+devtools/client/debugger/index.html
 
 # Ignore devtools imported repositories
-devtools/client/debugger/**
 devtools/client/shared/components/reps/**
 
 # Ignore devtools preferences files
 devtools/client/preferences/**
 devtools/client/webide/preferences/**
 devtools/shared/preferences/**
 devtools/startup/preferences/devtools-startup.js
 
--- a/.gitignore
+++ b/.gitignore
@@ -38,16 +38,22 @@ ID
 security/manager/.nss.checkout
 
 # Build directories
 /obj*/
 
 # gecko.log is generated by various test harnesses
 /gecko.log
 
+# Ignore newtab component build assets
+browser/components/newtab/bin/prerender.js
+browser/components/newtab/bin/prerender.js.map
+browser/components/newtab/data/locales.json
+browser/components/newtab/logs/
+
 # Build directories for js shell
 *_DBG.OBJ/
 *_OPT.OBJ/
 /js/src/*-obj/
 /js/src/obj-*/
 
 # SpiderMonkey configury
 js/src/configure
--- a/.hgignore
+++ b/.hgignore
@@ -34,16 +34,22 @@ compile_commands\.json
 ^security/manager/\.nss\.checkout$
 
 # Build directories
 ^obj
 
 # gecko.log is generated by various test harnesses
 ^gecko\.log
 
+# Ignore newtab component build assets
+^browser/components/newtab/bin/prerender.js
+^browser/components/newtab/bin/prerender.js.map
+^browser/components/newtab/data/locales.json
+^browser/components/newtab/logs/
+
 # Build directories for js shell
 _DBG\.OBJ/
 _OPT\.OBJ/
 ^js/src/.*-obj/
 ^js/src/obj-.*/
 
 # SpiderMonkey configury
 ^js/src/configure$
new file mode 100644
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,5 @@
+{
+  "printWidth": 80,
+  "tabWidth": 2,
+  "trailingComma": "es5"
+}
--- a/.taskcluster.yml
+++ b/.taskcluster.yml
@@ -131,18 +131,16 @@ tasks:
                     "notify.email.${ownerEmail}.on-completed"
                 # These are the old index routes for the decision task.
                 # They are still here so external tools that referenced them continue to work.
                 - "index.gecko.v2.${repository.project}.latest.firefox.decision"
                 - "index.gecko.v2.${repository.project}.revision.${push.revision}.firefox.decision"
               else:
                 $if: 'tasks_for == "action"'
                 then:
-                - "notify.email.taskcluster-notifications+action-task@mozilla.com.on-failed"
-                - "notify.email.taskcluster-notifications+action-task@mozilla.com.on-exception"
                 - "index.gecko.v2.${repository.project}.pushlog-id.${push.pushlog_id}.actions.${ownTaskId}"
                 else:  # cron
                 - "index.gecko.v2.${repository.project}.latest.taskgraph.decision-${cron.job_name}"
                 - "index.gecko.v2.${repository.project}.revision.${push.revision}.taskgraph.decision-${cron.job_name}"
                 - "index.gecko.v2.${repository.project}.pushlog-id.${push.pushlog_id}.decision-${cron.job_name}"
                 # list each cron task on this revision, so actions can find them
                 - 'index.gecko.v2.${repository.project}.revision.${push.revision}.cron.${ownTaskId}'
                 # BUG 1500166 Notify ciduty by email if a nightly hook fails
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -136,16 +136,21 @@ dependencies = [
  "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)",
  "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-uds 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "autocfg"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
 name = "backtrace"
 version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-demangle 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -160,19 +165,19 @@ 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)",
- "cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
- "cranelift-wasm 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
+ "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]]
 name = "base64"
 version = "0.9.3"
@@ -222,25 +227,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)",
 ]
@@ -497,16 +502,24 @@ dependencies = [
  "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "cloudabi"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "cmake"
 version = "0.1.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cc 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -582,67 +595,67 @@ version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cose 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "cranelift-bforest"
 version = "0.30.0"
-source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
-dependencies = [
- "cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
+source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
+dependencies = [
+ "cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
 ]
 
 [[package]]
 name = "cranelift-codegen"
 version = "0.30.0"
-source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
-dependencies = [
- "cranelift-bforest 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
- "cranelift-codegen-meta 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
- "cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
+source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
+dependencies = [
+ "cranelift-bforest 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
+ "cranelift-codegen-meta 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
+ "cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
  "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "cranelift-codegen-meta"
 version = "0.30.0"
-source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
-dependencies = [
- "cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
+source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
+dependencies = [
+ "cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
 ]
 
 [[package]]
 name = "cranelift-entity"
 version = "0.30.0"
-source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
+source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
 
 [[package]]
 name = "cranelift-frontend"
 version = "0.30.0"
-source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
-dependencies = [
- "cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
+source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
+dependencies = [
+ "cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "cranelift-wasm"
 version = "0.30.0"
-source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
+source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
 dependencies = [
  "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
- "cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
- "cranelift-frontend 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
+ "cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
+ "cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
+ "cranelift-frontend 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
  "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "wasmparser 0.29.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "crc"
@@ -1114,16 +1127,21 @@ name = "fs2"
 version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.6 (git+https://github.com/froydnj/winapi-rs?branch=aarch64)",
 ]
 
 [[package]]
+name = "fuchsia-cprng"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
 name = "fuchsia-zircon"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1331,25 +1349,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"
@@ -1358,17 +1367,17 @@ source = "registry+https://github.com/ru
 dependencies = [
  "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "httparse"
-version = "1.2.3"
+version = "1.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "humantime"
 version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "quick-error 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1379,17 +1388,17 @@ name = "hyper"
 version = "0.12.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "h2 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
  "http 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-executor 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-io 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1461,17 +1470,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)",
@@ -1562,17 +1571,17 @@ source = "registry+https://github.com/ru
 
 [[package]]
 name = "lazycell"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "lazycell"
-version = "0.6.0"
+version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "libc"
 version = "0.2.51"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -1753,51 +1762,62 @@ dependencies = [
  "cc 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
  "crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
  "miniz_oxide 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "mio"
-version = "0.6.15"
+version = "0.6.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazycell 1.2.1 (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)",
  "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "mio-extras"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "mio-named-pipes"
 version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "mio-uds"
 version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "miow"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2312,16 +2332,112 @@ version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.6 (git+https://github.com/froydnj/winapi-rs?branch=aarch64)",
 ]
 
 [[package]]
+name = "rand"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (git+https://github.com/froydnj/winapi-rs?branch=aarch64)",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "rand_hc"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_isaac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_jitter"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (git+https://github.com/froydnj/winapi-rs?branch=aarch64)",
+]
+
+[[package]]
+name = "rand_os"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (git+https://github.com/froydnj/winapi-rs?branch=aarch64)",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "rayon"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "either 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -2333,16 +2449,24 @@ dependencies = [
  "crossbeam-deque 0.2.0 (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)",
  "num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "rdrand"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "redox_syscall"
 version = "0.1.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "redox_termios"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2465,17 +2589,17 @@ name = "rust_cascade"
 version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit_reverse 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "murmurhash3 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "rustc-demangle"
 version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -2619,19 +2743,25 @@ dependencies = [
 name = "servo_arc"
 version = "0.1.1"
 dependencies = [
  "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
  "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "sha1"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
+name = "sha-1"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "opaque-debug 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
 name = "sha2"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2746,17 +2876,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 +2943,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",
 ]
 
@@ -3026,17 +3157,17 @@ dependencies = [
 ]
 
 [[package]]
 name = "tokio"
 version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-executor 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-fs 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-io 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-reactor 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-tcp 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-threadpool 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-timer 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-udp 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3056,17 +3187,17 @@ dependencies = [
 name = "tokio-core"
 version = "0.1.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-executor 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-io 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-reactor 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-timer 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -3100,31 +3231,31 @@ dependencies = [
 
 [[package]]
 name = "tokio-reactor"
 version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-executor 0.1.3 (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 = "tokio-tcp"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-io 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-reactor 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "tokio-threadpool"
 version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3149,33 +3280,33 @@ dependencies = [
 [[package]]
 name = "tokio-udp"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-codec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-io 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-reactor 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "tokio-uds"
 version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "mio-uds 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-core 0.1.17 (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 = "toml"
 version = "0.4.5"
@@ -3389,17 +3520,17 @@ dependencies = [
  "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallvec 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "svg_fmt 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "thread_profiler 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_api 0.60.0",
  "webrender_build 0.0.1",
  "wr_malloc_size_of 0.0.1",
- "ws 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ws 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webrender_api"
 version = "0.60.0"
 dependencies = [
  "app_units 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3505,27 +3636,28 @@ name = "wr_malloc_size_of"
 version = "0.0.1"
 dependencies = [
  "app_units 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.19.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "ws"
-version = "0.7.3"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
- "sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "ws2_32-sys"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
@@ -3614,23 +3746,24 @@ dependencies = [
 "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
 "checksum app_units 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9dadc668390b373e73e4abbfc1f07238b09a25858f2f39c06cebc6d8e141d774"
 "checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392"
 "checksum arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd1479b7c29641adbd35ff3b5c293922d696a92f25c8c975da3e0acbc87258f"
 "checksum arrayvec 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2f0ef4a9820019a0c91d918918c93dc71d469f581a49b47ddc1d285d4270bbe2"
 "checksum ascii-canvas 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b385d69402821a1c254533a011a312531cbcc0e3e24f19bbb4747a5a2daf37e2"
 "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 autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
 "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"
@@ -3644,32 +3777,33 @@ dependencies = [
 "checksum bzip2-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2c5162604199bbb17690ede847eaa6120a3f33d5ab4dcc8e7c25b16d849ae79b"
 "checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427"
 "checksum cc 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)" = "30f813bf45048a18eda9190fd3c6b78644146056740c43172a5a3699118588fd"
 "checksum cexpr 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8fc0086be9ca82f7fc89fc873435531cb898b86e850005850de1f820e2db6e9b"
 "checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
 "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878"
 "checksum clang-sys 0.28.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4227269cec09f5f83ff160be12a1e9b0262dd1aa305302d5ba296c2ebd291055"
 "checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536"
+"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
 "checksum cmake 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "56d741ea7a69e577f6d06b36b7dff4738f680593dc27a701ffa8506b73ce28bb"
 "checksum comedy 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4f03fbb05a4df3523a44cda10340e6ae6bea03ee9d01240a1a2c1ef6c73e95"
 "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
 "checksum cookie 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1465f8134efa296b4c19db34d909637cb2bf0f7aaf21299e23e18fa29ac557cf"
 "checksum core-foundation 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4e2640d6d0bf22e82bed1b73c6aef8d5dd31e5abe6666c57e6d45e2649f4f887"
 "checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b"
 "checksum core-graphics 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)" = "62ceafe1622ffc9a332199096841d0ff9912ec8cf8f9cde01e254a7d5217cd10"
 "checksum core-text 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f3f46450d6f2397261af420b4ccce23807add2e45fa206410a03d66fb7f050ae"
 "checksum cose 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "72fa26cb151d3ae4b70f63d67d0fed57ce04220feafafbae7f503bef7aae590d"
 "checksum cose-c 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "49726015ab0ca765144fcca61e4a7a543a16b795a777fa53f554da2fffff9a94"
-"checksum cranelift-bforest 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
-"checksum cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
-"checksum cranelift-codegen-meta 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
-"checksum cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
-"checksum cranelift-frontend 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
-"checksum cranelift-wasm 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
+"checksum cranelift-bforest 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
+"checksum cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
+"checksum cranelift-codegen-meta 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
+"checksum cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
+"checksum cranelift-frontend 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
+"checksum cranelift-wasm 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
 "checksum crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd5d02c0aac6bd68393ed69e00bbc2457f3e89075c6349db7189618dc4ddc1d7"
 "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3"
 "checksum crossbeam-deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fe8153ef04a7594ded05b427ffad46ddeaf22e63fd48d42b3e1e3bb4db07cae7"
 "checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150"
 "checksum crossbeam-epoch 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2af0e75710d6181e234c8ecc79f14a97907850a541b13b0be1dd10992f2e4620"
 "checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9"
 "checksum crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d636a8b3bcc1b409d7ffd3facef8f21dcb4009626adbd0c5e6c4305c07253c7b"
 "checksum crossbeam-utils 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "41ee4864f4797060e52044376f7d107429ce1fb43460021b126424b7180ee21a"
@@ -3708,49 +3842,49 @@ dependencies = [
 "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
 "checksum filetime_win 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8c37abd4a58e0cb794bcae4a7dc4f02fff376949d8d1066d4c729e97bfb38ec"
 "checksum fixedbitset 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "85cb8fec437468d86dc7c83ca7cfc933341d561873275f22dd5eedefa63a6478"
 "checksum flate2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fac2277e84e5e858483756647a9d0aa8d9a2b7cba517fd84325a0aaa69a0909"
 "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
 "checksum foreign-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5ebc04f19019fff1f2d627b5581574ead502f80c48c88900575a46e0840fe5d0"
 "checksum freetype 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b659e75b7a7338fe75afd7f909fc2b71937845cffb6ebe54ba2e50f13d8e903d"
 "checksum fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
+"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
 "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
 "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
 "checksum futures 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)" = "884dbe32a6ae4cd7da5c6db9b78114449df9953b8d490c9d7e1b51720b922c62"
 "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4"
 "checksum fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
 "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 httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83"
 "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"
 "checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff"
 "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
 "checksum itertools 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b07332223953b5051bceb67e8c4700aa65291535568e1f12408c43c4a42c0394"
 "checksum itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c069bbec61e1ca5a596166e55dfe4773ff745c3d16b700013bcaff9a6df2c682"
 "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
 "checksum khronos_api 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
 "checksum lalrpop 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02888049e197dff0c5c9fd503bd2458ea373c5e845c2f5460db1f9e43050d55e"
 "checksum lalrpop-util 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)" = "488da0d45c65af229321623c62660627d02b0e7fbc768a4c3fcd121815404ef1"
 "checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
 "checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b"
-"checksum lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a6f08839bc70ef4a3fe1d566d5350f519c5912ea86be0df1740a7d247c7fc0ef"
+"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
 "checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917"
 "checksum libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3ad660d7cb8c5822cd83d10897b0f1f1526792737a179e73896152f85b88c2"
 "checksum libudev 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea626d3bdf40a1c5aee3bcd4f40826970cae8d80a8fec934c82a63840094dcfe"
 "checksum libz-sys 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "3fdd64ef8ee652185674455c1d450b83cbc8ad895625d543b5324d923f82e4d8"
 "checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e"
 "checksum lmdb-rkv 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1452294309db7977dc75e1e8135a8c654d9e52e04ff0c0bd06c880897a91defd"
 "checksum lmdb-rkv-sys 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1470e0168f1832e35afd6d0931ae60db625685332837b97aa156773ec9c5e393"
 "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c"
@@ -3761,17 +3895,18 @@ dependencies = [
 "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376"
 "checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a"
 "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
 "checksum memmap 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46f3c7359028b31999287dae4e5047ddfe90a23b7dca2282ce759b491080c99b"
 "checksum memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2ffa2c986de11a9df78620c01eeaaf27d94d3ff02bf81bfcca953102dd0c6ff"
 "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3"
 "checksum miniz_oxide 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aaa2d3ad070f428fffbd7d3ca2ea20bb0d8cffe9024405c44e1840bc1418b398"
 "checksum miniz_oxide_c_api 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "92d98fdbd6145645828069b37ea92ca3de225e000d80702da25c20d3584b38a5"
-"checksum mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)" = "4fcfcb32d63961fb6f367bfd5d21e4600b92cd310f71f9dca25acae196eb1560"
+"checksum mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "71646331f2619b1026cc302f87a2b8b648d5c6dd6937846a16cc8ce0f347f432"
+"checksum mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "46e73a04c2fa6250b8d802134d56d554a9ec2922bf977777c805ea5def61ce40"
 "checksum mio-named-pipes 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "82f43a815b57d2d652550f3d20cec88a495bb2d0956aa873dc43040278455677"
 "checksum mio-uds 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1731a873077147b626d89cc6c2a0db6288d607496c5d10c0cfcf3adc697ec673"
 "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
 "checksum moz_cbor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20c82a57087fd5990d7122dbff1607c3b20c3d2958e9d9ad9765aab415e2c91c"
 "checksum mp4parse_fallible 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6626c2aef76eb8f984eef02e475883d3fe9112e114720446c5810fc5f045cd30"
 "checksum msdos_time 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aad9dfe950c057b1bfe9c1f2aa51583a8468ef2a5baba2ebbe06d775efeb7729"
 "checksum murmurhash3 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a2983372caf4480544083767bf2d27defafe32af49ab4df3a0b7fc90793a3664"
 "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
@@ -3811,18 +3946,29 @@ dependencies = [
 "checksum proc-macro2 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "77997c53ae6edd6d187fec07ec41b207063b5ee6f33680e9fa86d405cdd313d4"
 "checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915"
 "checksum procedural-masquerade 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9f566249236c6ca4340f7ca78968271f0ed2b0f234007a61b66f9ecd0af09260"
 "checksum quick-error 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eda5fe9b71976e62bc81b781206aaa076401769b2143379d3eb2118388babac4"
 "checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8"
 "checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1"
 "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1"
 "checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd"
+"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
+"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
+"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
+"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0"
+"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
+"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
+"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
+"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
+"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
+"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
 "checksum rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "485541959c8ecc49865526fe6c4de9653dd6e60d829d6edf0be228167b60372d"
 "checksum rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d24ad214285a7729b174ed6d3bcfcb80177807f959d95fafd5bfc5c4f201ac8"
+"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
 "checksum redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "ab105df655884ede59d45b7070c8a65002d921461ee813a024558ca16030eea0"
 "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
 "checksum redox_users 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "214a97e49be64fd2c86f568dd0cb2c757d2cc53de95b273b6ad0a1c908482f26"
 "checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b"
 "checksum regex 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3d8c9f33201f46669484bacc312b00e7541bed6aaf296dffe2bb4e0ac6b8ce2a"
 "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db"
 "checksum regex-syntax 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1ac0f60d675cc6cf13a20ec076568254472551051ad5dd050364d70671bf6b"
 "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
@@ -3843,17 +3989,17 @@ dependencies = [
 "checksum scroll_derive 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1aa96c45e7f5a91cb7fabe7b279f02fea7126239fc40b732316e8b6a2d0fcb"
 "checksum semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537"
 "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
 "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
 "checksum serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)" = "9f301d728f2b94c9a7691c90f07b0b4e8a4517181d9461be94c04bddeb4bd850"
 "checksum serde_bytes 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "adb6e51a6b3696b301bc221d785f898b4457c619b51d7ce195a6d20baecb37b3"
 "checksum serde_derive 1.0.88 (git+https://github.com/servo/serde?branch=deserialize_from_enums10)" = "<none>"
 "checksum serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "44dd2cfde475037451fa99b7e5df77aa3cfd1536575fa8e7a538ab36dcde49ae"
-"checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c"
+"checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68"
 "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d"
 "checksum shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
 "checksum siphasher 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ffc669b726f2bc9a3bcff66e5e23b56ba6bf70e22a34c3d7b6d0b3450b65b84"
 "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23"
 "checksum slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5f9776d6b986f77b35c6cf846c11ad986ff128fe0b2b63a3628e3755e8d3102d"
 "checksum smallbitvec 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1764fe2b30ee783bfe3b9b37b2649d8d590b3148bb12e0079715d4d5c673562e"
 "checksum smallvec 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "622df2d454c29a4d89b30dc3b27b42d7d90d6b9e587dbf8f67652eb7514da484"
 "checksum stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15132e0e364248108c5e2c02e3ab539be8d6f5d52a01ca9bbf27ed657316f02b"
@@ -3917,13 +4063,13 @@ dependencies = [
 "checksum which 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4be6cfa54dab45266e98b5d7be2f8ce959ddd49abd141a05d52dce4b07f803bb"
 "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
 "checksum winapi 0.3.6 (git+https://github.com/froydnj/winapi-rs?branch=aarch64)" = "<none>"
 "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
 "checksum winapi-i686-pc-windows-gnu 0.4.0 (git+https://github.com/froydnj/winapi-rs?branch=aarch64)" = "<none>"
 "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (git+https://github.com/froydnj/winapi-rs?branch=aarch64)" = "<none>"
 "checksum wincolor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb06499a3a4d44302791052df005d5232b927ed1a9658146d842165c4de7767"
 "checksum winreg 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a"
-"checksum ws 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "89c48c53bf9dee34411a08993c10b879c36e105d609b46e25673befe3a5c1320"
+"checksum ws 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ec91ea61b83ce033c43c06c52ddc7532f465c0153281610d44c58b74083aee1a"
 "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
 "checksum xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "541b12c998c5b56aa2b4e6f18f03664eef9a4fd0a246a55594efae6cc2d964b5"
 "checksum yaml-rust 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "95acf0db5515d07da9965ec0e0ba6cc2d825e2caeb7303b66ca441729801254e"
 "checksum zip 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "36b9e08fb518a65cf7e08a1e482573eb87a2f4f8c6619316612a3c1f162fe822"
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -59,13 +59,13 @@ codegen-units = 1
 [patch.crates-io]
 libudev-sys = { path = "dom/webauthn/libudev-sys" }
 serde_derive = { git = "https://github.com/servo/serde", branch = "deserialize_from_enums10" }
 winapi = { git = "https://github.com/froydnj/winapi-rs", branch = "aarch64" }
 packed_simd = { git = "https://github.com/hsivonen/packed_simd", branch = "rust_1_32" }
 
 [patch.crates-io.cranelift-codegen]
 git = "https://github.com/CraneStation/Cranelift"
-rev = "538a0662bf90a1daa9921c10f34827ace134abf1"
+rev = "cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
 
 [patch.crates-io.cranelift-wasm]
 git = "https://github.com/CraneStation/Cranelift"
-rev = "538a0662bf90a1daa9921c10f34827ace134abf1"
+rev = "cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
--- a/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/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version='1.0' encoding='UTF-8'?>
-<blocklist lastupdate="1557497630665" xmlns="http://www.mozilla.org/2006/addons-blocklist">
+<blocklist lastupdate="1557871228984" xmlns="http://www.mozilla.org/2006/addons-blocklist">
   <emItems>
     <emItem blockID="i334" id="{0F827075-B026-42F3-885D-98981EE7B1AE}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
     <emItem blockID="i1211" id="flvto@hotger.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
@@ -2931,16 +2931,52 @@
     <emItem blockID="8ff19ad3-e4e0-40e3-8f02-fd80d18f63b5" id="jid1-NIfFY2CA8fy1tg@jetpack">
       <prefs/>
       <versionRange minVersion="3.19.0" maxVersion="3.28.0" severity="1"/>
     </emItem>
     <emItem blockID="fee4b92e-146b-437d-9cc0-95cfc800f0e0" id="/^((\{da61a3e5-5a98-4c47-ae6c-f4db738f1133\})|(\{b0e13c2b-c1cd-426b-bed9-905bf9557fbf\})|(\{328c22c5-5f1c-4eb7-95a3-148fd4ad429d\})|(\{f6cca5fb-5aa1-471f-88f3-e2ffa87281ef\})|(\{d342bf37-554e-41c9-b67b-72769e59b82b\})|(\{03ec69b5-3e8e-4bb8-8eda-28f12c54bff8\})|(\{a8c876cb-af13-4ad9-9a86-fc3c0722b48c\})|(\{56136c32-0159-4368-9d28-c1b8b1515c89\})|(\{79bf4660-9729-444b-ae03-6c8005869611\})|(\{aa7fdaa5-d888-47e2-b27b-4fa4b3225339\})|(\{31e0d180-52b1-4c1d-8f84-7e625715edc4\})|(\{f7d20549-e5ee-4045-9e8f-9705bb10c104\})|(\{303abacb-760b-43c3-9640-5b456d92db78\})|(\{debabd67-2e0a-485e-8213-ac081065a027\})|(\{971e739b-c528-41b6-a60c-48fc3cdb52d9\})|(\{ffb3a485-2723-4a88-b3ad-8b29773759c4\})|(\{b076177a-a5c4-4652-9f6d-953f89f9a81a\})|(\{66210cb7-6352-45d5-9d22-ad7a0fb5e247\})|(\{8053ad7b-5129-4c74-ade9-8166c38e8636\})|(\{1a435c36-133e-4163-ac71-8701a147880c\})|(\{8c40c6df-7c9d-4876-bcbe-0621734aba45\})|(\{40e1e7d9-ae29-4aec-9465-5e0d49859583\})|(\{74eab03b-35cd-4950-b436-7afce3876e58\})|(\{95839c11-63a7-4b2b-b3d3-eee9d2c5c42d\})|(\{bfaa03c3-744e-48eb-8fb6-4ad61791d4d8\})|(\{f123e726-9396-4899-822a-172b8bcb2c5f\})|(\{157e255b-2053-4140-b95c-ff003b62bf17\})|(\{3e49a17b-b58e-417b-9ebb-a7e8c2317893\})|(\{4df1d536-e30f-4344-bee6-6ef2def890c2\})|(\{f33ce070-63f4-4d2b-823e-d52fc7a30ba7\})|(\{2003e2a5-e848-4fc5-8e7d-3af1efe4f992\})|(\{ff2157da-6981-40b6-aa60-d8125e73868e\})|(\{d89fa1e5-c9d4-4104-ad8e-00b39e5c6d15\})|(\{66e45d14-550f-4489-98c6-8a0caed33375\})|(\{86e6d45f-1dfe-4e53-bf52-22bf65b9ae6d\})|(\{e71407fe-e1ed-4755-af8f-dd64a952ce1a\})|(\{b67b3615-d8fe-4961-a41e-391864afde2d\})|(\{5785789b-ccba-44a1-9018-1135b56bd37f\})|(\{6dfb93d1-2add-471c-bbbc-b6164b4c1d94\}))$/">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
+    <emItem blockID="a941e00d-4ec8-41db-ac90-75041af68a3b" id="{3555a8e7-6fc1-4ad8-9e35-b09877d94a8c}">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+    <emItem blockID="73b95004-eb42-428b-852b-3671edbc3913" id="{52484281-3051-4c52-9309-83896b989ddf}">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+    <emItem blockID="abc5acc2-c9ea-4d44-b8ab-3fefb5723194" id="{4037503e-7401-4ccf-8fc1-af9f8c9fc168}">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+    <emItem blockID="9ced28d2-c4bf-4933-b369-1ded1ca7f6dc" id="/^((\{2e510835-3d3c-4995-ba75-2eee6ff203c7\})|(\{bc72fefd-ab07-40ce-8555-45f9b23ef8c0\}))$/">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+    <emItem blockID="dbd3690c-2ce4-474b-b13d-97f9ab2c54c5" id="{61121092-5257-4607-b16a-12364832f0e4}">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+    <emItem blockID="8436c1bb-43f2-42d6-acec-05145fdbeccf" id="/^((\{198586f6-9fdc-4ce6-8f21-7bdd85eab432\})|(\{1cd349e4-1d52-46ec-b648-6da8ba2ef659\})|(\{28c26373-1066-4cb5-8e92-1926cb31f83c\})|(\{2b99ea1d-9e25-4005-adeb-2fc9fc6700fc\})|(\{2be44a2e-f432-4527-a249-f7a6aecc8464\})|(\{2eda700f-8674-43fd-842f-73289b6e317f\})|(\{3745fc58-1413-4029-aea4-e1aa8a2c0cad\})|(\{3c19f6fc-1b86-411c-8d9a-7fdde31600b2\})|(\{450f8d34-b065-46a6-bd9f-ee7f614d750b\})|(\{498e999a-2d6b-47e7-8da2-97e0f694f6ff\})|(\{56862943-b999-45ef-be94-b97211126ba4\})|(\{5dc4633d-2c01-4d8d-8980-a90055d0679c\})|(\{77101ac4-6fe7-43ed-8362-75ad2a4b3299\})|(\{83ae749b-9ab3-41f9-ba8a-f73470399abe\})|(\{8ef68e62-a602-477c-95c2-9b861f91e813\})|(\{b81c02f0-e563-4794-8fd3-18a65b0f35fe\})|(\{c03bf205-6673-4495-abd7-f12556d3d8ce\})|(\{d1e8be12-c4e4-481b-9be1-400f54257dfa\})|(\{d3f73060-8ca3-4c24-b389-6a896f43f538\})|(\{e5e98141-81c0-433d-ade2-4174ea951243\})|(\{ec6ff98f-7315-4cfb-88b9-e6a64bb97ef6\})|(\{ee765c0e-cf70-426e-ac5d-704c874202af\})|(\{f8a4dc88-e967-4c75-acb3-6176ab166bf4\}))$/">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+    <emItem blockID="a308009d-3320-49af-b0b8-1174f5a25fe6" id="{0b66e692-1991-4b46-89df-c8101925bad1}">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+    <emItem blockID="be42b13c-301b-4304-a1ed-89b4c13d9eb2" id="{d8f707bf-0a35-462f-8e4d-f90205770547}">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+    <emItem blockID="a2b8447b-ea50-45bb-936a-64d790ebe448" id="websurf@mizilla.org">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
   </emItems>
   <pluginItems>
     <pluginItem blockID="p332">
       <match exp="libflashplayer\.so" name="filename"/>
       <match exp="^Shockwave Flash 11.(0|1) r[0-9]{1,3}$" name="description"/>
       <infoURL>https://get.adobe.com/flashplayer/</infoURL>
       <versionRange severity="0" vulnerabilitystatus="1">
         <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1183,17 +1183,16 @@ pref("services.sync.prefs.sync.accessibi
 pref("services.sync.prefs.sync.accessibility.typeaheadfind.linksonly", true);
 pref("services.sync.prefs.sync.addons.ignoreUserEnabledChanges", true);
 // The addons prefs related to repository verification are intentionally
 // not synced for security reasons. If a system is compromised, a user
 // could weaken the pref locally, install an add-on from an untrusted
 // source, and this would propagate automatically to other,
 // uncompromised Sync-connected devices.
 pref("services.sync.prefs.sync.browser.contentblocking.category", true);
-pref("services.sync.prefs.sync.browser.contentblocking.features.standard", true);
 pref("services.sync.prefs.sync.browser.contentblocking.features.strict", true);
 pref("services.sync.prefs.sync.browser.contentblocking.introCount", true);
 pref("services.sync.prefs.sync.browser.crashReports.unsubmittedCheck.autoSubmit2", true);
 pref("services.sync.prefs.sync.browser.ctrlTab.recentlyUsedOrder", true);
 pref("services.sync.prefs.sync.browser.download.useDownloadDir", true);
 pref("services.sync.prefs.sync.browser.formfill.enable", true);
 pref("services.sync.prefs.sync.browser.link.open_newwindow", true);
 pref("services.sync.prefs.sync.browser.newtabpage.activity-stream.showSearch", true);
@@ -1586,17 +1585,17 @@ pref("browser.contentblocking.trackingpr
 pref("browser.contentblocking.rejecttrackers.control-center.ui.enabled", true);
 
 pref("browser.contentblocking.control-center.ui.showBlockedLabels", true);
 pref("browser.contentblocking.control-center.ui.showAllowedLabels", false);
 
 pref("browser.contentblocking.cryptomining.preferences.ui.enabled", true);
 pref("browser.contentblocking.fingerprinting.preferences.ui.enabled", true);
 
-// Possible values for browser.contentblocking.features.* prefs:
+// Possible values for browser.contentblocking.features.strict pref:
 //   Tracking Protection:
 //     "tp": tracking protection enabled
 //     "-tp": tracking protection disabled
 //   Tracking Protection in private windows:
 //     "tpPrivate": tracking protection in private windows enabled
 //     "-tpPrivate": tracking protection in private windows disabled
 //   Fingerprinting:
 //     "fp": fingerprinting blocking enabled
@@ -1605,27 +1604,18 @@ pref("browser.contentblocking.fingerprin
 //     "cm": cryptomining blocking enabled
 //     "-cm": cryptomining blocking disabled
 //   Cookie behavior:
 //     "cookieBehavior0": cookie behaviour BEHAVIOR_ACCEPT
 //     "cookieBehavior1": cookie behaviour BEHAVIOR_REJECT_FOREIGN
 //     "cookieBehavior2": cookie behaviour BEHAVIOR_REJECT
 //     "cookieBehavior3": cookie behaviour BEHAVIOR_LIMIT_FOREIGN
 //     "cookieBehavior4": cookie behaviour BEHAVIOR_REJECT_TRACKER
-// One value from each section must be included in each browser.contentblocking.features.* pref.
+// One value from each section must be included in the browser.contentblocking.features.strict pref.
 pref("browser.contentblocking.features.strict", "tp,tpPrivate,cookieBehavior4,cm,fp");
-// Enable blocking access to storage from tracking resources only in nightly
-// and early beta. By default the value is "cookieBehavior0": BEHAVIOR_ACCEPT
-// Enable cryptomining blocking in standard in nightly and early beta.
-// Enable fingerprinting blocking in standard in nightly and early beta.
-#ifdef EARLY_BETA_OR_EARLIER
-pref("browser.contentblocking.features.standard", "-tp,tpPrivate,cookieBehavior4,cm,fp");
-#else
-pref("browser.contentblocking.features.standard", "-tp,tpPrivate,cookieBehavior0,-cm,-fp");
-#endif
 
 // Enable the Report Breakage UI on Nightly and Beta but not on Release yet.
 #ifdef EARLY_BETA_OR_EARLIER
 pref("browser.contentblocking.reportBreakage.enabled", true);
 #else
 pref("browser.contentblocking.reportBreakage.enabled", false);
 #endif
 // Show report breakage for tracking cookies in all channels.
--- 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
@@ -135,16 +135,21 @@ var gIdentityHandler = {
     return this._identityPopupContentSupp =
       document.getElementById("identity-popup-content-supplemental");
   },
   get _identityPopupContentVerif() {
     delete this._identityPopupContentVerif;
     return this._identityPopupContentVerif =
       document.getElementById("identity-popup-content-verifier");
   },
+  get _identityPopupCustomRootLearnMore() {
+    delete this._identityPopupCustomRootLearnMore;
+    return this._identityPopupCustomRootLearnMore =
+      document.getElementById("identity-popup-custom-root-learn-more");
+  },
   get _identityPopupMixedContentLearnMore() {
     delete this._identityPopupMixedContentLearnMore;
     return this._identityPopupMixedContentLearnMore =
       [...document.querySelectorAll(".identity-popup-mcb-learn-more")];
   },
   get _identityPopupInsecureLoginFormsLearnMore() {
     delete this._identityPopupInsecureLoginFormsLearnMore;
     return this._identityPopupInsecureLoginFormsLearnMore =
@@ -505,16 +510,28 @@ var gIdentityHandler = {
     }
     if (this._uriHasHost && this._isSecure) {
       return "verifiedDomain";
     }
     return "unknownIdentity";
   },
 
   /**
+   * Returns whether the issuer of the current certificate chain is
+   * built-in (returns false) or imported (returns true).
+   */
+  _hasCustomRoot() {
+    let issuerCert = null;
+    // Walk the whole chain to get the last cert.
+    for (issuerCert of this._secInfo.succeededCertChain.getEnumerator());
+
+    return !issuerCert.isBuiltInRoot;
+  },
+
+  /**
    * Updates the identity block user interface with the data from this object.
    */
   refreshIdentityBlock() {
     if (!this._identityBox) {
       return;
     }
 
     let icon_label = "";
@@ -690,34 +707,39 @@ var gIdentityHandler = {
     }
 
     // Update "Learn More" for Mixed Content Blocking and Insecure Login Forms.
     let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
     this._identityPopupMixedContentLearnMore.forEach(
       e => e.setAttribute("href", baseURL + "mixed-content"));
     this._identityPopupInsecureLoginFormsLearnMore
         .setAttribute("href", baseURL + "insecure-password");
+    this._identityPopupCustomRootLearnMore
+        .setAttribute("href", baseURL + "enterprise-roots");
 
     // This is in the properties file because the expander used to switch its tooltip.
     this._popupExpander.tooltipText = gNavigatorBundle.getString("identity.showDetails.tooltip");
 
+    let customRoot = false;
+
     // Determine connection security information.
     let connection = "not-secure";
     if (this._isSecureInternalUI) {
       connection = "chrome";
     } else if (this._pageExtensionPolicy) {
       connection = "extension";
     } else if (this._isURILoadedFromFile) {
       connection = "file";
     } else if (this._isEV) {
       connection = "secure-ev";
     } else if (this._isCertUserOverridden) {
       connection = "secure-cert-user-overridden";
     } else if (this._isSecure) {
       connection = "secure";
+      customRoot = this._hasCustomRoot();
     }
 
     // Determine if there are insecure login forms.
     let loginforms = "secure";
     if (this._hasInsecureLoginForms) {
       loginforms = "insecure";
     }
 
@@ -757,16 +779,17 @@ var gIdentityHandler = {
 
     for (let id of elementIDs) {
       let element = document.getElementById(id);
       updateAttribute(element, "connection", connection);
       updateAttribute(element, "loginforms", loginforms);
       updateAttribute(element, "ciphers", ciphers);
       updateAttribute(element, "mixedcontent", mixedcontent);
       updateAttribute(element, "isbroken", this._isBroken);
+      updateAttribute(element, "customroot", customRoot);
     }
 
     // Initialize the optional strings to empty values
     let supplemental = "";
     let verifier = "";
     let host = this.getHostForDisplay();
     let owner = "";
 
@@ -1129,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);
 
@@ -1185,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/siteIdentity/browser.ini
+++ b/browser/base/content/test/siteIdentity/browser.ini
@@ -41,16 +41,17 @@ support-files =
 tags = mcb
 support-files =
   file_csp_block_all_mixedcontent.html
   file_csp_block_all_mixedcontent.js
 [browser_identity_UI.js]
 [browser_identityBlock_focus.js]
 support-files = ../permissions/permissions.html
 [browser_identityPopup_clearSiteData.js]
+[browser_identityPopup_custom_roots.js]
 [browser_identityPopup_focus.js]
 [browser_insecureLoginForms.js]
 support-files =
   insecure_opener.html
   !/toolkit/components/passwordmgr/test/browser/form_basic.html
   !/toolkit/components/passwordmgr/test/browser/insecure_test.html
   !/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html
 [browser_mcb_redirect.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/browser_identityPopup_custom_roots.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Test that the UI for imported root certificates shows up correctly in the identity popup.
+ */
+
+const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com");
+
+// This test is incredibly simple, because our test framework already
+// imports root certificates by default, so we just visit example.com
+// and verify that the custom root certificates UI is visible.
+add_task(async function test_https() {
+  await BrowserTestUtils.withNewTab("https://example.com", async function() {
+    let promisePanelOpen = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
+    gIdentityHandler._identityBox.click();
+    await promisePanelOpen;
+    let customRootWarning = document.getElementById("identity-popup-security-decription-custom-root");
+    ok(BrowserTestUtils.is_visible(customRootWarning), "custom root warning is visible");
+
+    let securityView = document.getElementById("identity-popup-securityView");
+    let shown = BrowserTestUtils.waitForEvent(securityView, "ViewShown");
+    document.getElementById("identity-popup-security-expander").click();
+    await shown;
+
+    let subPanelInfo = document.getElementById("identity-popup-content-verifier-unknown");
+    ok(BrowserTestUtils.is_visible(subPanelInfo), "custom root warning in sub panel is visible");
+  });
+});
+
+// Also check that there are conditions where this isn't shown.
+add_task(async function test_http() {
+  await BrowserTestUtils.withNewTab("http://example.com", async function() {
+    let promisePanelOpen = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
+    gIdentityHandler._identityBox.click();
+    await promisePanelOpen;
+    let customRootWarning = document.getElementById("identity-popup-security-decription-custom-root");
+    ok(BrowserTestUtils.is_hidden(customRootWarning), "custom root warning is hidden");
+
+    let securityView = document.getElementById("identity-popup-securityView");
+    let shown = BrowserTestUtils.waitForEvent(securityView, "ViewShown");
+    document.getElementById("identity-popup-security-expander").click();
+    await shown;
+
+    let subPanelInfo = document.getElementById("identity-popup-content-verifier-unknown");
+    ok(BrowserTestUtils.is_hidden(subPanelInfo), "custom root warning in sub panel is hidden");
+  });
+});
--- a/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js
+++ b/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js
@@ -46,25 +46,29 @@ add_task(async function testSiteSecurity
     is(Services.focus.focusedElement, document.getElementById("identity-popup-security-expander"));
 
     // 2. Access the Site Security section.
     let securityView = document.getElementById("identity-popup-securityView");
     shown = BrowserTestUtils.waitForEvent(securityView, "ViewShown");
     EventUtils.sendString(" ");
     await shown;
 
-    // 3. First press of tab should focus the Back button.
-    let backButton = gIdentityHandler._identityPopup.querySelector(".subviewbutton-back");
-    // Wait for focus to move somewhere. We use focusin because focus doesn't bubble.
+    // 3. Custom root learn more info should be focused by default
+    // This is probably not present in real-world scenarios, but needs to be present in our test infrastructure.
+    let customRootLearnMore = document.getElementById("identity-popup-custom-root-learn-more");
+    is(Services.focus.focusedElement, customRootLearnMore, "learn more option for custom roots is focused");
+
+    // 4. First press of tab should move to the More Information button.
+    let moreInfoButton = document.getElementById("identity-popup-more-info");
     let focused = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "focusin");
     EventUtils.sendKey("tab");
     await focused;
-    is(Services.focus.focusedElement, backButton);
+    is(Services.focus.focusedElement, moreInfoButton, "more info button is focused");
 
-    // 4. Second press of tab should move to the More Information button.
-    let moreInfoButton = document.getElementById("identity-popup-more-info");
+    // 5. Second press of tab should focus the Back button.
+    let backButton = gIdentityHandler._identityPopup.querySelector(".subviewbutton-back");
+    // Wait for focus to move somewhere. We use focusin because focus doesn't bubble.
     focused = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "focusin");
     EventUtils.sendKey("tab");
     await focused;
-    isnot(Services.focus.focusedElement, backButton);
-    is(Services.focus.focusedElement, moreInfoButton);
+    is(Services.focus.focusedElement, backButton, "back button is focused");
   });
 });
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -154,19 +154,16 @@ var whitelist = [
   // Bug 1356043
   {file: "resource://gre/modules/PerfMeasurement.jsm"},
   // Bug 1356045
   {file: "chrome://global/content/test-ipc.xul"},
   // Bug 1378173 (warning: still used by devtools)
   {file: "resource://gre/modules/Promise.jsm"},
   // Still used by WebIDE, which is going away but not entirely gone.
   {file: "resource://gre/modules/ZipUtils.jsm"},
-  // Bug 1483277 (temporarily unreferenced)
-  {file: AppConstants.BROWSER_CHROME_URL == "chrome://browser/content/browser.xul" ?
-    "chrome://browser/content/browser.xhtml" : "chrome://browser/content/browser.xul" },
   // Bug 1494170
   // (The references to these files are dynamically generated, so the test can't
   // find the references)
   {file: "chrome://devtools/skin/images/aboutdebugging-firefox-aurora.svg",
    isFromDevTools: true},
   {file: "chrome://devtools/skin/images/aboutdebugging-firefox-beta.svg",
    isFromDevTools: true},
   {file: "chrome://devtools/skin/images/aboutdebugging-firefox-release.svg",
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -25,18 +25,19 @@ browser.jar:
         content/browser/aboutFrameCrashed.html        (content/aboutFrameCrashed.html)
         content/browser/aboutTabCrashed.css           (content/aboutTabCrashed.css)
         content/browser/aboutTabCrashed.js            (content/aboutTabCrashed.js)
         content/browser/aboutTabCrashed.xhtml         (content/aboutTabCrashed.xhtml)
 *       content/browser/browser.css                   (content/browser.css)
         content/browser/browser.js                    (content/browser.js)
 #ifdef MOZ_BROWSER_XHTML
 *       content/browser/browser.xhtml                 (content/browser.xhtml)
+#else
+*       content/browser/browser.xul                   (content/browser.xul)
 #endif
-*       content/browser/browser.xul                   (content/browser.xul)
         content/browser/browser-addons.js             (content/browser-addons.js)
         content/browser/browser-allTabsMenu.js        (content/browser-allTabsMenu.js)
         content/browser/browser-captivePortal.js      (content/browser-captivePortal.js)
         content/browser/browser-ctrlTab.js            (content/browser-ctrlTab.js)
         content/browser/browser-customization.js      (content/browser-customization.js)
         content/browser/browser-data-submission-info-bar.js (content/browser-data-submission-info-bar.js)
         content/browser/browser-contentblocking.js    (content/browser-contentblocking.js)
 #ifndef MOZILLA_OFFICIAL
--- a/browser/base/moz.build
+++ b/browser/base/moz.build
@@ -54,17 +54,18 @@ BROWSER_CHROME_MANIFESTS += [
     'content/test/touch/browser.ini',
     'content/test/trackingUI/browser.ini',
     'content/test/webextensions/browser.ini',
     'content/test/webrtc/browser.ini',
 ]
 
 DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
 DEFINES['MOZ_APP_VERSION_DISPLAY'] = CONFIG['MOZ_APP_VERSION_DISPLAY']
-DEFINES['MOZ_BROWSER_XHTML'] = CONFIG['MOZ_BROWSER_XHTML']
+if CONFIG['MOZ_BROWSER_XHTML']:
+    DEFINES['MOZ_BROWSER_XHTML'] = CONFIG['MOZ_BROWSER_XHTML']
 
 DEFINES['APP_LICENSE_BLOCK'] = '%s/content/overrides/app-license.html' % SRCDIR
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk3', 'cocoa'):
     DEFINES['CONTEXT_COPY_IMAGE_CONTENTS'] = 1
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk3'):
     DEFINES['MENUBAR_CAN_AUTOHIDE'] = 1
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/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -1034,18 +1034,18 @@ BrowserGlue.prototype = {
     os.removeObserver(this, "xpi-signature-changed");
     os.removeObserver(this, "sync-ui-state:update");
     os.removeObserver(this, "shield-init-complete");
 
     Services.prefs.removeObserver("permissions.eventTelemetry.enabled", this._togglePermissionPromptTelemetry);
     Services.prefs.removeObserver("privacy.trackingprotection", this._matchCBCategory);
     Services.prefs.removeObserver("network.cookie.cookieBehavior", this._matchCBCategory);
     Services.prefs.removeObserver(ContentBlockingCategoriesPrefs.PREF_CB_CATEGORY, this._updateCBCategory);
-    Services.prefs.removeObserver("browser.contentblocking.features.standard", this._setPrefExpectations);
-    Services.prefs.removeObserver("browser.contentblocking.features.strict", this._setPrefExpectations);
+    Services.prefs.removeObserver("privacy.trackingprotection", this._setPrefExpectations);
+    Services.prefs.removeObserver("browser.contentblocking.features.strict", this._setPrefExpectationsAndUpdate);
   },
 
   // runs on startup, before the first command line handler is invoked
   // (i.e. before the first window is opened)
   _beforeUIStartup: function BG__beforeUIStartup() {
     SessionStartup.init();
 
     if (Services.prefs.prefHasUserValue(PREF_PDFJS_ENABLED_CACHE_STATE)) {
@@ -1386,35 +1386,39 @@ BrowserGlue.prototype = {
 
     this._firstWindowTelemetry(aWindow);
     this._firstWindowLoaded();
 
     this._collectStartupConditionsTelemetry();
 
     // Set the default favicon size for UI views that use the page-icon protocol.
     PlacesUtils.favicons.setDefaultIconURIPreferredSize(16 * aWindow.devicePixelRatio);
-    this._setPrefExpectations();
+    this._setPrefExpectationsAndUpdate();
     this._matchCBCategory();
 
     // This observes the entire privacy.trackingprotection.* pref tree.
     Services.prefs.addObserver("privacy.trackingprotection", this._matchCBCategory);
     Services.prefs.addObserver("network.cookie.cookieBehavior", this._matchCBCategory);
     Services.prefs.addObserver(ContentBlockingCategoriesPrefs.PREF_CB_CATEGORY, this._updateCBCategory);
     Services.prefs.addObserver("media.autoplay.default", this._updateAutoplayPref);
-    Services.prefs.addObserver("browser.contentblocking.features.standard", this._setPrefExpectations);
-    Services.prefs.addObserver("browser.contentblocking.features.strict", this._setPrefExpectations);
+    Services.prefs.addObserver("privacy.trackingprotection", this._setPrefExpectations);
+    Services.prefs.addObserver("browser.contentblocking.features.strict", this._setPrefExpectationsAndUpdate);
   },
 
   _updateAutoplayPref() {
     let blocked = Services.prefs.getIntPref("media.autoplay.default", 1);
     Services.telemetry.scalarSet("media.autoplay_default_blocked", blocked);
   },
 
   _setPrefExpectations() {
     ContentBlockingCategoriesPrefs.setPrefExpectations();
+  },
+
+  _setPrefExpectationsAndUpdate() {
+    ContentBlockingCategoriesPrefs.setPrefExpectations();
     ContentBlockingCategoriesPrefs.updateCBCategory();
   },
 
   _matchCBCategory() {
     ContentBlockingCategoriesPrefs.matchCBCategory();
   },
 
   _updateCBCategory() {
@@ -3013,108 +3017,99 @@ BrowserGlue.prototype = {
 
   QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver,
                                           Ci.nsISupportsWeakReference]),
 };
 
 var ContentBlockingCategoriesPrefs = {
   PREF_CB_CATEGORY: "browser.contentblocking.category",
   PREF_STRICT_DEF: "browser.contentblocking.features.strict",
-  PREF_STANDARD_DEF: "browser.contentblocking.features.standard",
   switchingCategory: false,
 
   setPrefExpectations() {
-    // The prefs inside CATEGORY_PREFS are initial values, these values then get set.
-    // If the pref remains null, then it will expect the default value,
-    // but the UI will not respond correctly.
+    // The prefs inside CATEGORY_PREFS are initial values.
+    // If the pref remains null, then it will expect the default value.
+    // The "standard" category is defined as expecting all 5 default values.
     this.CATEGORY_PREFS = {
       strict: {
         "network.cookie.cookieBehavior": null,
         "privacy.trackingprotection.pbmode.enabled": null,
         "privacy.trackingprotection.enabled": null,
         "privacy.trackingprotection.fingerprinting.enabled": null,
         "privacy.trackingprotection.cryptomining.enabled": null,
       },
       standard: {
         "network.cookie.cookieBehavior": null,
         "privacy.trackingprotection.pbmode.enabled": null,
         "privacy.trackingprotection.enabled": null,
         "privacy.trackingprotection.fingerprinting.enabled": null,
         "privacy.trackingprotection.cryptomining.enabled": null,
       },
     };
-    let types = ["strict", "standard"];
-    for (let type of types) {
-      let rulesArray;
-      if (type == "strict") {
-        rulesArray = Services.prefs.getStringPref(this.PREF_STRICT_DEF).split(",");
-      } else {
-        rulesArray = Services.prefs.getStringPref(this.PREF_STANDARD_DEF).split(",");
-      }
-      for (let item of rulesArray) {
-        switch (item) {
-        case "tp":
-          this.CATEGORY_PREFS[type]["privacy.trackingprotection.enabled"] = true;
-          break;
-        case "-tp":
-          this.CATEGORY_PREFS[type]["privacy.trackingprotection.enabled"] = false;
-          break;
-        case "tpPrivate":
-          this.CATEGORY_PREFS[type]["privacy.trackingprotection.pbmode.enabled"] = true;
-          break;
-        case "-tpPrivate":
-          this.CATEGORY_PREFS[type]["privacy.trackingprotection.pbmode.enabled"] = false;
-          break;
-        case "fp":
-          this.CATEGORY_PREFS[type]["privacy.trackingprotection.fingerprinting.enabled"] = true;
-          break;
-        case "-fp":
-          this.CATEGORY_PREFS[type]["privacy.trackingprotection.fingerprinting.enabled"] = false;
-          break;
-        case "cm":
-          this.CATEGORY_PREFS[type]["privacy.trackingprotection.cryptomining.enabled"] = true;
-          break;
-        case "-cm":
-          this.CATEGORY_PREFS[type]["privacy.trackingprotection.cryptomining.enabled"] = false;
-          break;
-        case "cookieBehavior0":
-          this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = Ci.nsICookieService.BEHAVIOR_ACCEPT;
-          break;
-        case "cookieBehavior1":
-          this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN;
-          break;
-        case "cookieBehavior2":
-          this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = Ci.nsICookieService.BEHAVIOR_REJECT;
-          break;
-        case "cookieBehavior3":
-          this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN;
-          break;
-        case "cookieBehavior4":
-          this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER;
-          break;
-        default:
-          Cu.reportError(`Error: Unknown rule observed ${item}`);
-        }
+    let type = "strict";
+    let rulesArray = Services.prefs.getStringPref(this.PREF_STRICT_DEF).split(",");
+    for (let item of rulesArray) {
+      switch (item) {
+      case "tp":
+        this.CATEGORY_PREFS[type]["privacy.trackingprotection.enabled"] = true;
+        break;
+      case "-tp":
+        this.CATEGORY_PREFS[type]["privacy.trackingprotection.enabled"] = false;
+        break;
+      case "tpPrivate":
+        this.CATEGORY_PREFS[type]["privacy.trackingprotection.pbmode.enabled"] = true;
+        break;
+      case "-tpPrivate":
+        this.CATEGORY_PREFS[type]["privacy.trackingprotection.pbmode.enabled"] = false;
+        break;
+      case "fp":
+        this.CATEGORY_PREFS[type]["privacy.trackingprotection.fingerprinting.enabled"] = true;
+        break;
+      case "-fp":
+        this.CATEGORY_PREFS[type]["privacy.trackingprotection.fingerprinting.enabled"] = false;
+        break;
+      case "cm":
+        this.CATEGORY_PREFS[type]["privacy.trackingprotection.cryptomining.enabled"] = true;
+        break;
+      case "-cm":
+        this.CATEGORY_PREFS[type]["privacy.trackingprotection.cryptomining.enabled"] = false;
+        break;
+      case "cookieBehavior0":
+        this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = Ci.nsICookieService.BEHAVIOR_ACCEPT;
+        break;
+      case "cookieBehavior1":
+        this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN;
+        break;
+      case "cookieBehavior2":
+        this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = Ci.nsICookieService.BEHAVIOR_REJECT;
+        break;
+      case "cookieBehavior3":
+        this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN;
+        break;
+      case "cookieBehavior4":
+        this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER;
+        break;
+      default:
+        Cu.reportError(`Error: Unknown rule observed ${item}`);
       }
     }
   },
 
   /**
    * Checks if CB prefs match perfectly with one of our pre-defined categories.
    */
   prefsMatch(category) {
     // The category pref must be either unset, or match.
     if (Services.prefs.prefHasUserValue(this.PREF_CB_CATEGORY) &&
         Services.prefs.getStringPref(this.PREF_CB_CATEGORY) != category) {
       return false;
     }
     for (let pref in this.CATEGORY_PREFS[category]) {
       let value = this.CATEGORY_PREFS[category][pref];
       if (value == null) {
-        Cu.reportError(`Error: ${pref} has not been defined in ${category}`);
         if (Services.prefs.prefHasUserValue(pref)) {
           return false;
         }
       } else {
         let prefType = Services.prefs.getPrefType(pref);
         if ((prefType == Services.prefs.PREF_BOOL && Services.prefs.getBoolPref(pref) != value) ||
             (prefType == Services.prefs.PREF_INT && Services.prefs.getIntPref(pref) != value) ||
             (prefType == Services.prefs.PREF_STRING && Services.prefs.getStringPref(pref) != value)) {
@@ -3170,17 +3165,16 @@ var ContentBlockingCategoriesPrefs = {
     if (category == "custom") {
       return;
     }
 
     for (let pref in this.CATEGORY_PREFS[category]) {
       let value = this.CATEGORY_PREFS[category][pref];
       if (!Services.prefs.prefIsLocked(pref)) {
         if (value == null) {
-          Cu.reportError(`Error: ${pref} has not been defined in ${category}`);
           Services.prefs.clearUserPref(pref);
         } else {
           switch (Services.prefs.getPrefType(pref)) {
           case Services.prefs.PREF_BOOL:
             Services.prefs.setBoolPref(pref, value);
             break;
           case Services.prefs.PREF_INT:
             Services.prefs.setIntPref(pref, value);
--- 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,21 @@ 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
+  .copied-password-button = ✓ Copied!
+  .copied-username-button = ✓ Copied!
+  .copy-password-button = Copy
+  .copy-username-button = Copy
   .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
@@ -5,67 +5,74 @@
 <!DOCTYPE html>
 <html>
   <head>
     <meta charset="utf-8">
     <meta http-equiv="Content-Security-Policy" content="default-src 'none'; object-src 'none'; script-src resource: chrome:; connect-src https:; img-src https: data: blob:; style-src 'unsafe-inline';"/>
     <title data-l10n-id="about-logins-page-title"></title>
     <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/copy-to-clipboard-button.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,
+                                 copy-password-button,
+                                 copy-username-button,
+                                 copied-password-button,
+                                 copied-username-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>
@@ -74,37 +81,48 @@
         </label>
         <button class="open-site-button"></button>
       </div>
       <div class="detail-row">
         <label>
           <span class="username-label field-label"></span>
           <modal-input name="username"/>
         </label>
+        <copy-to-clipboard-button class="copy-username-button"></copy-to-clipboard-button>
       </div>
       <div class="detail-row">
         <label>
           <span class="password-label field-label"></span>
           <modal-input type="password" name="password"/>
         </label>
+        <copy-to-clipboard-button class="copy-password-button"></copy-to-clipboard-button>
       </div>
       <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>
+
+    <template id="copy-to-clipboard-button-template">
+      <link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
+      <link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/copy-to-clipboard-button.css">
+      <button class="copy-button">
+        <span class="copied-button-text"></span>
+        <span class="copy-button-text"></span>
+      </button>
     </template>
   </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/components/aboutlogins/content/components/copy-to-clipboard-button.css
@@ -0,0 +1,29 @@
+/* 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 {
+  --success-color: #00c100;
+}
+
+@supports -moz-bool-pref("browser.in-content.dark-mode") {
+@media (prefers-color-scheme: dark) {
+  :host {
+    --success-color: #86DE74;
+  }
+}
+}
+
+:host(:not([copied])) .copied-button-text,
+:host([copied]) .copy-button-text {
+  display: none;
+}
+
+:host([copied]) {
+  color: var(--success-color);
+}
+
+:host([copied]) button {
+  background-color: transparent;
+  opacity: 1; /* override common.css fading out disabled buttons */
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/aboutlogins/content/components/copy-to-clipboard-button.js
@@ -0,0 +1,69 @@
+/* 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/. */
+
+/* globals ReflectedFluentElement */
+
+class CopyToClipboardButton extends ReflectedFluentElement {
+  static get BUTTON_RESET_TIMEOUT() {
+    return 5000;
+  }
+
+  constructor() {
+    super();
+
+    this._relatedInput = null;
+  }
+
+  connectedCallback() {
+    if (this.children.length) {
+      return;
+    }
+
+    let CopyToClipboardButtonTemplate = document.querySelector("#copy-to-clipboard-button-template");
+    this.attachShadow({mode: "open"})
+        .appendChild(CopyToClipboardButtonTemplate.content.cloneNode(true));
+
+    this.shadowRoot.querySelector(".copy-button").addEventListener("click", this);
+  }
+
+  static get reflectedFluentIDs() {
+    return ["copy-button-text", "copied-button-text"];
+  }
+
+  static get observedAttributes() {
+    return CopyToClipboardButton.reflectedFluentIDs;
+  }
+
+  handleSpecialCaseFluentString(attrName) {
+    if (attrName != "copied-button-text" &&
+        attrName != "copy-button-text") {
+      return false;
+    }
+
+    let span = this.shadowRoot.querySelector("." + attrName);
+    span.textContent = this.getAttribute(attrName);
+    return true;
+  }
+
+  handleEvent(event) {
+    let copyButton = this.shadowRoot.querySelector(".copy-button");
+    if (event.type != "click" || event.currentTarget != copyButton) {
+      return;
+    }
+
+    copyButton.disabled = true;
+    navigator.clipboard.writeText(this._relatedInput.value).then(() => {
+      this.setAttribute("copied", "");
+      setTimeout(() => {
+        copyButton.disabled = false;
+        this.removeAttribute("copied");
+      }, CopyToClipboardButton.BUTTON_RESET_TIMEOUT);
+    }, () => copyButton.disabled = false);
+  }
+
+  set relatedInput(val) {
+    this._relatedInput = val;
+  }
+}
+customElements.define("copy-to-clipboard-button", CopyToClipboardButton);
--- 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
@@ -18,59 +18,93 @@ class LoginItem extends ReflectedFluentE
 
     let loginItemTemplate = document.querySelector("#login-item-template");
     this.attachShadow({mode: "open"})
         .appendChild(loginItemTemplate.content.cloneNode(true));
 
     this.reflectFluentStrings();
 
     for (let selector of [
+      ".copy-password-button",
+      ".copy-username-button",
       ".delete-button",
       ".edit-button",
       ".open-site-button",
       ".save-changes-button",
       ".cancel-button",
     ]) {
       let button = this.shadowRoot.querySelector(selector);
       button.addEventListener("click", this);
     }
 
     window.addEventListener("AboutLoginsLoginSelected", this);
 
+    let copyUsernameButton = this.shadowRoot.querySelector(".copy-username-button");
+    let copyPasswordButton = this.shadowRoot.querySelector(".copy-password-button");
+    copyUsernameButton.relatedInput = this.shadowRoot.querySelector("modal-input[name='username']");
+    copyPasswordButton.relatedInput = this.shadowRoot.querySelector("modal-input[name='password']");
+
     this.render();
   }
 
   static get reflectedFluentIDs() {
     return [
       "cancel-button",
+      "copied-password-button",
+      "copied-username-button",
+      "copy-password-button",
+      "copy-username-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 "copied-password-button":
+      case "copy-password-button": {
+        let copyPasswordButton = this.shadowRoot.querySelector(".copy-password-button");
+        let newAttrName = attrName.substr(0, attrName.indexOf("-")) + "-button-text";
+        copyPasswordButton.setAttribute(newAttrName, this.getAttribute(attrName));
+        break;
+      }
+      case "copied-username-button":
+      case "copy-username-button": {
+        let copyUsernameButton = this.shadowRoot.querySelector(".copy-username-button");
+        let newAttrName = attrName.substr(0, attrName.indexOf("-")) + "-button-text";
+        copyUsernameButton.setAttribute(newAttrName, this.getAttribute(attrName));
+        break;
+      }
+      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 || "",
@@ -90,16 +124,22 @@ class LoginItem extends ReflectedFluentE
         break;
       }
       case "click": {
         if (event.target.classList.contains("cancel-button")) {
           this.toggleEditing();
           this.render();
           return;
         }
+        if (event.target.classList.contains("copy-password-button")) {
+          return;
+        }
+        if (event.target.classList.contains("copy-username-button")) {
+          return;
+        }
         if (event.target.classList.contains("delete-button")) {
           document.dispatchEvent(new CustomEvent("AboutLoginsDeleteLogin", {
             bubbles: true,
             detail: this._login,
           }));
           return;
         }
         if (event.target.classList.contains("edit-button")) {
--- 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
@@ -1,19 +1,25 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 browser.jar:
+  content/browser/aboutlogins/components/copy-to-clipboard-button.css (content/components/copy-to-clipboard-button.css)
+  content/browser/aboutlogins/components/copy-to-clipboard-button.js  (content/components/copy-to-clipboard-button.js)
   content/browser/aboutlogins/components/login-filter.css      (content/components/login-filter.css)
   content/browser/aboutlogins/components/login-filter.js       (content/components/login-filter.js)
   content/browser/aboutlogins/components/login-item.css        (content/components/login-item.css)
   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/browser/browser.ini
+++ b/browser/components/aboutlogins/tests/browser/browser.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 prefs =
   signon.management.page.enabled=true
 
+[browser_copyToClipboardButton.js]
 [browser_deleteLogin.js]
 [browser_loginListChanges.js]
 [browser_openSite.js]
 [browser_updateLogin.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/aboutlogins/tests/browser/browser_copyToClipboardButton.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function test() {
+  await SpecialPowers.pushPrefEnv({"set": [["dom.events.testing.asyncClipboard", true]] });
+
+  await BrowserTestUtils.withNewTab({gBrowser, url: "about:logins"}, async function(browser) {
+    let TEST_LOGIN = {
+      guid: "70a",
+      username: "jared",
+      password: "deraj",
+      hostname: "https://www.example.com",
+    };
+
+    await ContentTask.spawn(browser, TEST_LOGIN, async function(login) {
+      let loginItem = Cu.waiveXrays(content.document.querySelector("login-item"));
+
+      // The login object needs to be cloned into the content global.
+      loginItem.setLogin(Cu.cloneInto(login, content));
+
+      // Lower the timeout for the test.
+      let copyButton = loginItem.shadowRoot.querySelector(".copy-username-button");
+      Object.defineProperty(copyButton.constructor, "BUTTON_RESET_TIMEOUT", {
+          configurable: true,
+          writable: true,
+          value: 1000,
+      });
+    });
+
+    for (let testCase of [
+      [TEST_LOGIN.username, ".copy-username-button"],
+      [TEST_LOGIN.password, ".copy-password-button"],
+    ]) {
+      let testObj = {
+        expectedValue: testCase[0],
+        copyButtonSelector: testCase[1],
+      };
+      await SimpleTest.promiseClipboardChange(testObj.expectedValue, async () => {
+        await ContentTask.spawn(browser, testObj, async function(aTestObj) {
+          let loginItem = content.document.querySelector("login-item");
+          let copyButton = loginItem.shadowRoot.querySelector(aTestObj.copyButtonSelector);
+          let innerButton = copyButton.shadowRoot.querySelector("button");
+          info("Clicking 'copy' button");
+          innerButton.click();
+        });
+      });
+      ok(true, "Username is on clipboard now");
+
+      await ContentTask.spawn(browser, testObj, async function(aTestObj) {
+        let loginItem = content.document.querySelector("login-item");
+        let copyButton = loginItem.shadowRoot.querySelector(aTestObj.copyButtonSelector);
+        ok(copyButton.hasAttribute("copied"), "Success message should be shown");
+        await ContentTaskUtils.waitForCondition(() => !copyButton.hasAttribute("copied"),
+          "'copied' attribute should be removed after a timeout");
+      });
+    }
+  });
+});
--- 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/controlcenter/content/identityPanel.inc.xul
+++ b/browser/components/controlcenter/content/identityPanel.inc.xul
@@ -24,26 +24,29 @@
 
       <!-- Security Section -->
       <hbox id="identity-popup-security" class="identity-popup-section">
         <vbox class="identity-popup-security-content" flex="1">
           <label class="plain">
             <label class="identity-popup-headline">&identity.connection;</label>
           </label>
           <description class="identity-popup-connection-not-secure"
-                       when-connection="not-secure secure-cert-user-overridden">&identity.connectionNotSecure;</description>
+                       when-connection="not-secure secure-cert-user-overridden secure-custom-root">&identity.connectionNotSecure;</description>
           <description class="identity-popup-connection-secure"
                        when-connection="secure secure-ev">&identity.connectionSecure;</description>
           <description when-connection="chrome">&identity.connectionInternal;</description>
           <description when-connection="file">&identity.connectionFile;</description>
           <description when-connection="extension">&identity.extensionPage;</description>
 
           <vbox id="identity-popup-security-descriptions">
             <description class="identity-popup-warning-gray"
                          when-mixedcontent="active-blocked">&identity.activeBlocked;</description>
+           <description  id="identity-popup-security-decription-custom-root"
+                         class="identity-popup-warning-gray"
+                         when-customroot="true">&identity.customRoot;</description>
             <description class="identity-popup-warning-yellow"
                          when-mixedcontent="passive-loaded">&identity.passiveLoaded;</description>
             <description when-mixedcontent="active-loaded">&identity.activeLoaded;</description>
             <description class="identity-popup-warning-yellow"
                          when-ciphers="weak">&identity.weakEncryption;</description>
             <description when-loginforms="insecure">&identity.insecureLoginForms2;</description>
           </vbox>
         </vbox>
@@ -178,16 +181,19 @@
                      when-connection="secure-ev">&identity.connectionVerified2;</description>
         <description id="identity-popup-content-owner"
                      when-connection="secure-ev"
                      class="header"/>
         <description id="identity-popup-content-supplemental"
                      when-connection="secure-ev"/>
         <description id="identity-popup-content-verifier"
                      when-connection="secure secure-ev secure-cert-user-overridden"/>
+        <description id="identity-popup-content-verifier-unknown"
+                     class="identity-popup-warning-gray"
+                     when-customroot="true">&identity.description.customRoot; <label id="identity-popup-custom-root-learn-more" is="text-link" class="plain" value="&identity.learnMore;"/></description>
 
         <!-- Remove Certificate Exception -->
         <button when-connection="secure-cert-user-overridden"
                 label="&identity.removeCertException.label;"
                 accesskey="&identity.removeCertException.accesskey;"
                 class="panel-button"
                 oncommand="gIdentityHandler.removeCertException()"/>
 
--- 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/extensions/parent/ext-tabs.js
+++ b/browser/components/extensions/parent/ext-tabs.js
@@ -1095,17 +1095,17 @@ this.tabs = class extends ExtensionAPI {
         printPreview() {
           let activeTab = getTabOrActive(null);
           let {
             PrintUtils,
             PrintPreviewListener,
           } = activeTab.ownerGlobal;
 
           return new Promise((resolve, reject) => {
-            let ppBrowser = PrintUtils._shouldSimplify ?
+            let ppBrowser = PrintUtils.shouldSimplify ?
               PrintPreviewListener.getSimplifiedPrintPreviewBrowser() :
               PrintPreviewListener.getPrintPreviewBrowser();
 
             let mm = ppBrowser.messageManager;
 
             let onEntered = (message) => {
               mm.removeMessageListener("Printing:Preview:Entered", onEntered);
               if (message.data.failed) {
--- a/browser/components/extensions/test/browser/browser_ext_tabs_printPreview.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_printPreview.js
@@ -16,17 +16,17 @@ add_task(async function testPrintPreview
       browser.test.notifyPass("tabs.printPreview");
     },
   });
 
   await extension.startup();
   await extension.awaitFinish("tabs.printPreview");
   await extension.unload();
 
-  let ppTab = PrintUtils._shouldSimplify ?
+  let ppTab = PrintUtils.shouldSimplify ?
       PrintPreviewListener._simplifiedPrintPreviewTab :
       PrintPreviewListener._printPreviewTab;
 
   let ppToolbar = document.getElementById("print-preview-toolbar");
 
   is(window.gInPrintPreviewMode, true, "window in print preview mode");
 
   isnot(ppTab, null, "print preview tab created");
--- a/browser/components/newtab/.mcignore
+++ b/browser/components/newtab/.mcignore
@@ -9,14 +9,17 @@ npm-debug.log
 /.git/
 /bin/prerender.js
 /bin/prerender.js.map
 /data/locales.json
 /dist/
 /logs/
 /node_modules/
 
+# ignore README since it's GitHub specific
+/README.md
+
 # also ignores ping centre tests
 ping-centre/
 
 # ignore things from about:library for now
 aboutlibrary/
 content-src/aboutlibrary/
deleted file mode 100644
--- a/browser/components/newtab/README.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# activity-stream
-
-[![Task Status](https://github.taskcluster.net/v1/repository/mozilla/activity-stream/master/badge.svg)](https://github.taskcluster.net/v1/repository/mozilla/activity-stream/master/latest)
-
-This system add-on replaces the new tab page in Firefox with a new design and
-functionality as part of the Activity Stream project.
-
-The files in this directory, including vendor dependencies, are imported from the
-system-addon directory in https://github.com/mozilla/activity-stream.
-
-Read [docs/v2-system-addon](https://github.com/mozilla/activity-stream/tree/master/docs/v2-system-addon/1.GETTING_STARTED.md) for more detail.
-
-## Where should I file bugs?
-
-We regularly check the ActivityStream:NewTab component on Bugzilla.
-
-## For Developers
-
-If you are interested in contributing, take a look at [this guide](contributing.md) on where to find us and how to contribute,
-and [this guide](docs/v2-system-addon/1.GETTING_STARTED.md) for getting your development environment set up.
-
-## For Localizers
-
-Activity Stream localization is managed via [Pontoon](https://pontoon.mozilla.org/projects/activity-stream-new-tab/), not direct pull requests to the repository. If you want to fix a typo, add a new language, or simply know more about localization, please get in touch with the [existing localization team](https://pontoon.mozilla.org/teams/) for your language, or Mozilla’s [l10n-drivers](https://wiki.mozilla.org/L10n:Mozilla_Team#Mozilla_Corporation) for guidance.
--- a/browser/components/newtab/content-src/asrouter/components/RichText/RichText.jsx
+++ b/browser/components/newtab/content-src/asrouter/components/RichText/RichText.jsx
@@ -19,17 +19,18 @@ const ALLOWED_TAGS = {
  */
 export function convertLinks(links, sendClick, doNotAutoBlock, openNewWindow = false) {
   if (links) {
     return Object.keys(links).reduce((acc, linkTag) => {
       const {action} = links[linkTag];
       // Setting the value to false will not include the attribute in the anchor
       const url = action ? false : safeURI(links[linkTag].url);
 
-      acc[linkTag] = (<a href={url}
+      acc[linkTag] = (<a href={url} // eslint-disable-line jsx-a11y/anchor-has-content
+        // eslint was getting a false positive caused by the dynamic injection of content.
         target={openNewWindow ? "_blank" : ""}
         data-metric={links[linkTag].metric}
         data-action={action}
         data-args={links[linkTag].args}
         data-do_not_autoblock={doNotAutoBlock}
         onClick={sendClick} />);
       return acc;
     }, {});
--- a/browser/components/newtab/content-src/asrouter/components/SnippetBase/_SnippetBase.scss
+++ b/browser/components/newtab/content-src/asrouter/components/SnippetBase/_SnippetBase.scss
@@ -93,8 +93,28 @@
   position: absolute;
   top: 0;
   width: 100%;
 
   span {
     vertical-align: middle;
   }
 }
+
+// We show snippet icons for both themes and conditionally hide
+// based on which theme is currently active
+body {
+  &:not([lwt-newtab-brighttext]) {
+    .icon-dark-theme,
+    .icon.icon-dark-theme,
+    .scene2Icon .icon-dark-theme {
+      display: none;
+    }
+  }
+
+  &[lwt-newtab-brighttext] {
+    .icon-light-theme,
+    .icon.icon-light-theme,
+    .scene2Icon .icon-light-theme {
+      display: none;
+    }
+  }
+}
--- a/browser/components/newtab/content-src/asrouter/templates/EOYSnippet/EOYSnippet.schema.json
+++ b/browser/components/newtab/content-src/asrouter/templates/EOYSnippet/EOYSnippet.schema.json
@@ -1,12 +1,12 @@
 {
   "title": "EOYSnippet",
   "description": "Fundraising Snippet",
-  "version": "1.0.0",
+  "version": "1.1.0",
   "type": "object",
   "definitions": {
     "plainText": {
       "description": "Plain text (no HTML allowed)",
       "type": "string"
     },
     "richText": {
       "description": "Text with HTML subset allowed: i, b, u, strong, em, br",
@@ -71,26 +71,34 @@
       "type": "string",
       "description": "Default donation_amount_second. Donation amount button that's selected by default.",
       "default": "donation_amount_second"
     },
     "icon": {
       "type": "string",
       "description": "Snippet icon. 64x64px. SVG or PNG preferred."
     },
+    "icon_dark_theme": {
+      "type": "string",
+      "description": "Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."
+    },
     "title": {
       "allOf": [
         {"$ref": "#/definitions/plainText"},
         {"description": "Snippet title displayed before snippet text"}
       ]
     },
     "title_icon": {
       "type": "string",
       "description": "Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."
     },
+    "title_icon_dark_theme": {
+      "type": "string",
+      "description": "Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."
+    },
     "button_label": {
       "allOf": [
         {"$ref": "#/definitions/plainText"},
         {"description": "Text for a button next to main snippet text that links to button_url. Requires button_url."}
       ]
     },
     "button_color": {
       "type": "string",
--- a/browser/components/newtab/content-src/asrouter/templates/FXASignupSnippet/FXASignupSnippet.schema.json
+++ b/browser/components/newtab/content-src/asrouter/templates/FXASignupSnippet/FXASignupSnippet.schema.json
@@ -1,12 +1,12 @@
 {
   "title": "FXASignupSnippet",
   "description": "A snippet template for FxA sign up/sign in",
-  "version": "1.0.0",
+  "version": "1.1.0",
   "type": "object",
   "definitions": {
     "plainText": {
       "description": "Plain text (no HTML allowed)",
       "type": "string"
     },
     "richText": {
       "description": "Text with HTML subset allowed: i, b, u, strong, em, br",
@@ -42,20 +42,28 @@
         {"$ref": "#/definitions/richText"},
         {"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}
       ]
     },
     "scene1_icon": {
       "type": "string",
       "description": "Snippet icon. 64x64px. SVG or PNG preferred."
     },
+    "scene1_icon_dark_theme": {
+      "type": "string",
+      "description": "Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."
+    },
     "scene1_title_icon": {
       "type": "string",
       "description": "Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."
     },
+    "scene1_title_icon_dark_theme": {
+      "type": "string",
+      "description": "Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."
+    },
     "scene2_email_placeholder_text": {
       "type": "string",
       "description": "Value to show while input is empty.",
       "default": "Your email here"
     },
     "scene2_button_label": {
       "type": "string",
       "description": "Label for form submit button",
--- a/browser/components/newtab/content-src/asrouter/templates/NewsletterSnippet/NewsletterSnippet.schema.json
+++ b/browser/components/newtab/content-src/asrouter/templates/NewsletterSnippet/NewsletterSnippet.schema.json
@@ -1,12 +1,12 @@
 {
   "title": "NewsletterSnippet",
   "description": "A snippet template for send to device mobile download",
-  "version": "1.0.0",
+  "version": "1.1.0",
   "type": "object",
   "definitions": {
     "plainText": {
       "description": "Plain text (no HTML allowed)",
       "type": "string"
     },
     "richText": {
       "description": "Text with HTML subset allowed: i, b, u, strong, em, br",
@@ -47,20 +47,28 @@
         {"$ref": "#/definitions/richText"},
         {"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}
       ]
     },
     "scene1_icon": {
       "type": "string",
       "description": "Snippet icon. 64x64px. SVG or PNG preferred."
     },
+    "scene1_icon_dark_theme": {
+      "type": "string",
+      "description": "Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."
+    },
     "scene1_title_icon": {
       "type": "string",
       "description": "Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."
     },
+    "scene1_title_icon_dark_theme": {
+      "type": "string",
+      "description": "Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."
+    },
     "scene2_email_placeholder_text": {
       "type": "string",
       "description": "Value to show while input is empty.",
       "default": "Your email here"
     },
     "scene2_button_label": {
       "type": "string",
       "description": "Label for form submit button",
--- a/browser/components/newtab/content-src/asrouter/templates/ReturnToAMO/ReturnToAMO.jsx
+++ b/browser/components/newtab/content-src/asrouter/templates/ReturnToAMO/ReturnToAMO.jsx
@@ -1,11 +1,14 @@
 import React from "react";
 import {RichText} from "../../components/RichText/RichText";
 
+// Alt text if available; in the future this should come from the server. See bug 1551711
+const ICON_ALT_TEXT = "";
+
 export class ReturnToAMO extends React.PureComponent {
   constructor(props) {
     super(props);
     this.onClickAddExtension = this.onClickAddExtension.bind(this);
     this.onBlockButton = this.onBlockButton.bind(this);
   }
 
   componentDidMount() {
@@ -29,17 +32,17 @@ export class ReturnToAMO extends React.P
     document.body.classList.remove("welcome", "hide-main", "amo");
     this.props.sendUserActionTelemetry({
       event: "BLOCK",
       id: this.props.UISurface,
     });
   }
 
   renderText() {
-    const customElement = <img src={this.props.content.addon_icon} width="20px" height="20px" />;
+    const customElement = <img src={this.props.content.addon_icon} width="20px" height="20px" alt={ICON_ALT_TEXT} />;
     return (<RichText
       customElements={{icon: customElement}}
       amo_html={this.props.content.text}
       localization_id="amo_html" />);
   }
 
   render() {
     const {content} = this.props;
--- a/browser/components/newtab/content-src/asrouter/templates/SendToDeviceSnippet/SendToDeviceSnippet.schema.json
+++ b/browser/components/newtab/content-src/asrouter/templates/SendToDeviceSnippet/SendToDeviceSnippet.schema.json
@@ -1,12 +1,12 @@
 {
   "title": "SendToDeviceSnippet",
   "description": "A snippet template for send to device mobile download",
-  "version": "1.0.0",
+  "version": "1.1.0",
   "type": "object",
   "definitions": {
     "plainText": {
       "description": "Plain text (no HTML allowed)",
       "type": "string"
     },
     "richText": {
       "description": "Text with HTML subset allowed: i, b, u, strong, em, br",
@@ -52,24 +52,36 @@
         {"$ref": "#/definitions/richText"},
         {"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}
       ]
     },
     "scene1_icon": {
       "type": "string",
       "description": "Snippet icon. 64x64px. SVG or PNG preferred."
     },
+    "scene1_icon_dark_theme": {
+      "type": "string",
+      "description": "Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."
+    },
     "scene2_icon": {
       "type": "string",
+      "description": "(send to device) Image to display above the form. Dark theme variant. 98x98px. SVG or PNG preferred."
+    },
+    "scene2_icon_dark_theme": {
+      "type": "string",
       "description": "(send to device) Image to display above the form. 98x98px. SVG or PNG preferred."
     },
     "scene1_title_icon": {
       "type": "string",
       "description": "Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."
     },
+    "scene1_title_icon_dark_theme": {
+      "type": "string",
+      "description": "Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."
+    },
     "scene2_button_label": {
       "type": "string",
       "description": "Label for form submit button",
       "default": "Send"
     },
     "scene2_input_placeholder": {
       "type": "string",
       "description": "(send to device) Value to show while input is empty.",
--- a/browser/components/newtab/content-src/asrouter/templates/SimpleBelowSearchSnippet/SimpleBelowSearchSnippet.jsx
+++ b/browser/components/newtab/content-src/asrouter/templates/SimpleBelowSearchSnippet/SimpleBelowSearchSnippet.jsx
@@ -1,14 +1,16 @@
 import React from "react";
 import {RichText} from "../../components/RichText/RichText";
 import {safeURI} from "../../template-utils";
 import {SnippetBase} from "../../components/SnippetBase/SnippetBase";
 
 const DEFAULT_ICON_PATH = "chrome://branding/content/icon64.png";
+// Alt text if available; in the future this should come from the server. See bug 1551711
+const ICON_ALT_TEXT = "";
 
 export class SimpleBelowSearchSnippet extends React.PureComponent {
   renderText() {
     const {props} = this;
     return (<RichText text={props.content.text}
       customElements={this.props.customElements}
       localization_id="text"
       links={props.content.links}
@@ -19,16 +21,17 @@ export class SimpleBelowSearchSnippet ex
     const {props} = this;
     let className = "SimpleBelowSearchSnippet";
 
     if (props.className) {
       className += ` ${props.className}`;
     }
 
     return (<SnippetBase {...props} className={className} textStyle={this.props.textStyle}>
-      <img src={safeURI(props.content.icon) || DEFAULT_ICON_PATH} className="icon" />
+      <img src={safeURI(props.content.icon) || DEFAULT_ICON_PATH} className="icon icon-light-theme" alt={ICON_ALT_TEXT} />
+      <img src={safeURI(props.content.icon_dark_theme || props.content.icon) || DEFAULT_ICON_PATH} className="icon icon-dark-theme" alt={ICON_ALT_TEXT} />
       <div>
         <p className="body">{this.renderText()}</p>
         {this.props.extraContent}
       </div>
     </SnippetBase>);
   }
 }
--- a/browser/components/newtab/content-src/asrouter/templates/SimpleBelowSearchSnippet/SimpleBelowSearchSnippet.schema.json
+++ b/browser/components/newtab/content-src/asrouter/templates/SimpleBelowSearchSnippet/SimpleBelowSearchSnippet.schema.json
@@ -1,12 +1,12 @@
 {
   "title": "SimpleBelowSearchSnippet",
   "description": "A simple template with just an icon and rich text. It gets inserted below the Activity Stream search box.",
-  "version": "1.1.0",
+  "version": "1.2.0",
   "type": "object",
   "definitions": {
     "richText": {
       "description": "Text with HTML subset allowed: i, b, u, strong, em, br",
       "type": "string"
     },
     "link_url": {
       "description": "Target for links or buttons",
@@ -20,16 +20,20 @@
         {"$ref": "#/definitions/richText"},
         {"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}
       ]
     },
     "icon": {
       "type": "string",
       "description": "Snippet icon. 64x64px. SVG or PNG preferred."
     },
+    "icon_dark_theme": {
+      "type": "string",
+      "description": "Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."
+    },
     "block_button_text": {
       "type": "string",
       "description": "Tooltip text used for dismiss button.",
       "default": "Remove this"
     },
     "do_not_autoblock": {
       "type": "boolean",
       "description": "Used to prevent blocking the snippet after the CTA link has been clicked"
--- a/browser/components/newtab/content-src/asrouter/templates/SimpleSnippet/SimpleSnippet.jsx
+++ b/browser/components/newtab/content-src/asrouter/templates/SimpleSnippet/SimpleSnippet.jsx
@@ -1,16 +1,18 @@
 import {Button} from "../../components/Button/Button";
 import {ConditionalWrapper} from "../../components/ConditionalWrapper/ConditionalWrapper";
 import React from "react";
 import {RichText} from "../../components/RichText/RichText";
 import {safeURI} from "../../template-utils";
 import {SnippetBase} from "../../components/SnippetBase/SnippetBase";
 
 const DEFAULT_ICON_PATH = "chrome://branding/content/icon64.png";
+// Alt text if available; in the future this should come from the server. See bug 1551711
+const ICON_ALT_TEXT = "";
 
 export class SimpleSnippet extends React.PureComponent {
   constructor(props) {
     super(props);
     this.onButtonClick = this.onButtonClick.bind(this);
   }
 
   onButtonClick() {
@@ -36,18 +38,26 @@ export class SimpleSnippet extends React
   renderTitle() {
     const {title} = this.props.content;
     return title ?
       <h3 className={`title ${this._shouldRenderButton() ? "title-inline" : ""}`}>{this.renderTitleIcon()} {title}</h3> :
       null;
   }
 
   renderTitleIcon() {
-    const titleIcon = safeURI(this.props.content.title_icon);
-    return titleIcon ? <span className="titleIcon" style={{backgroundImage: `url("${titleIcon}")`}} /> : null;
+    const titleIconLight = safeURI(this.props.content.title_icon);
+    const titleIconDark = safeURI(this.props.content.title_icon_dark_theme || this.props.content.title_icon);
+    if (!titleIconLight) {
+      return null;
+    }
+
+    return (<React.Fragment>
+        <span className="titleIcon icon-light-theme" style={{backgroundImage: `url("${titleIconLight}")`}} />
+        <span className="titleIcon icon-dark-theme" style={{backgroundImage: `url("${titleIconDark}")`}} />
+      </React.Fragment>);
   }
 
   renderButton() {
     const {props} = this;
     if (!this._shouldRenderButton()) {
       return null;
     }
 
@@ -78,24 +88,26 @@ export class SimpleSnippet extends React
     return <div className="innerContentWrapper">{children}</div>;
   }
 
   renderSectionHeader() {
     const {props} = this;
 
     // an icon and text must be specified to render the section header
     if (props.content.section_title_icon && props.content.section_title_text) {
-      const sectionTitleIcon = safeURI(props.content.section_title_icon);
+      const sectionTitleIconLight = safeURI(props.content.section_title_icon);
+      const sectionTitleIconDark = safeURI(props.content.section_title_icon_dark_theme || props.content.section_title_icon);
       const sectionTitleURL = props.content.section_title_url;
 
       return (
         <div className="section-header">
           <h3 className="section-title">
             <ConditionalWrapper condition={sectionTitleURL} wrap={this.wrapSectionHeader(sectionTitleURL)}>
-              <span className="icon icon-small-spacer" style={{backgroundImage: `url("${sectionTitleIcon}")`}} />
+              <span className="icon icon-small-spacer icon-light-theme" style={{backgroundImage: `url("${sectionTitleIconLight}")`}} />
+              <span className="icon icon-small-spacer icon-dark-theme" style={{backgroundImage: `url("${sectionTitleIconDark}")`}} />
               <span className="section-title-text">{props.content.section_title_text}</span>
             </ConditionalWrapper>
           </h3>
         </div>
       );
     }
 
     return null;
@@ -114,17 +126,18 @@ export class SimpleSnippet extends React
     }
     if (sectionHeader) {
       className += " has-section-header";
     }
 
     return (<SnippetBase {...props} className={className} textStyle={this.props.textStyle}>
       {sectionHeader}
       <ConditionalWrapper condition={sectionHeader} wrap={this.wrapSnippetContent}>
-        <img src={safeURI(props.content.icon) || DEFAULT_ICON_PATH} className="icon" />
+        <img src={safeURI(props.content.icon) || DEFAULT_ICON_PATH} className="icon icon-light-theme" alt={ICON_ALT_TEXT} />
+        <img src={safeURI(props.content.icon_dark_theme || props.content.icon) || DEFAULT_ICON_PATH} className="icon icon-dark-theme" alt={ICON_ALT_TEXT} />
         <div>
           {this.renderTitle()} <p className="body">{this.renderText()}</p>
           {this.props.extraContent}
         </div>
         {<div>{this.renderButton()}</div>}
       </ConditionalWrapper>
     </SnippetBase>);
   }
--- a/browser/components/newtab/content-src/asrouter/templates/SimpleSnippet/SimpleSnippet.schema.json
+++ b/browser/components/newtab/content-src/asrouter/templates/SimpleSnippet/SimpleSnippet.schema.json
@@ -30,20 +30,28 @@
         {"$ref": "#/definitions/richText"},
         {"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}
       ]
     },
     "icon": {
       "type": "string",
       "description": "Snippet icon. 64x64px. SVG or PNG preferred."
     },
+    "icon_dark_theme": {
+      "type": "string",
+      "description": "Snippet icon, dark theme variant. 64x64px. SVG or PNG preferred."
+    },
     "title_icon": {
       "type": "string",
       "description": "Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."
     },
+    "title_icon_dark_theme": {
+      "type": "string",
+      "description": "Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."
+    },
     "button_action": {
       "type": "string",
       "description": "The type of action the button should trigger."
     },
     "button_url": {
       "allOf": [
         {"$ref": "#/definitions/link_url"},
         {"description": "A url, button_label links to this"}
@@ -97,16 +105,20 @@
           "description": "Additional parameters for link action, example which specific menu the button should open"
         }
       }
     },
     "section_title_icon": {
       "type": "string",
       "description": "Section title icon. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display."
     },
+    "section_title_icon_dark_theme": {
+      "type": "string",
+      "description": "Section title icon, dark theme variant. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display."
+    },
     "section_title_text": {
       "type": "string",
       "description": "Section title text. section_title_icon must also be specified to display."
     },
     "section_title_url": {
       "allOf": [
         {"$ref": "#/definitions/link_url"},
         {"description": "A url, section_title_text links to this"}
--- a/browser/components/newtab/content-src/asrouter/templates/SubmitFormSnippet/SubmitFormSnippet.jsx
+++ b/browser/components/newtab/content-src/asrouter/templates/SubmitFormSnippet/SubmitFormSnippet.jsx
@@ -1,14 +1,18 @@
 import {Button} from "../../components/Button/Button";
 import React from "react";
 import {RichText} from "../../components/RichText/RichText";
+import {safeURI} from "../../template-utils";
 import {SimpleSnippet} from "../SimpleSnippet/SimpleSnippet";
 import {SnippetBase} from "../../components/SnippetBase/SnippetBase";
 
+// Alt text if available; in the future this should come from the server. See bug 1551711
+const ICON_ALT_TEXT = "";
+
 export class SubmitFormSnippet extends React.PureComponent {
   constructor(props) {
     super(props);
     this.expandSnippet = this.expandSnippet.bind(this);
     this.handleSubmit = this.handleSubmit.bind(this);
     this.handleSubmitAttempt = this.handleSubmitAttempt.bind(this);
     this.onInputChange = this.onInputChange.bind(this);
     this.state = {
@@ -150,25 +154,28 @@ export class SubmitFormSnippet extends R
     const placholder = this.props.content.scene2_email_placeholder_text || this.props.content.scene2_input_placeholder;
     return (<input
       ref="mainInput"
       type={this.props.inputType || "email"}
       className={`mainInput${(this.state.submitAttempted ? "" : " clean")}`}
       name="email"
       required={true}
       placeholder={placholder}
-      onChange={this.props.validateInput ? this.onInputChange : null}
-      autoFocus={true} />);
+      onChange={this.props.validateInput ? this.onInputChange : null} />);
   }
 
   renderSignupView() {
     const {content} = this.props;
     const containerClass = `SubmitFormSnippet ${this.props.className}`;
     return (<SnippetBase {...this.props} className={containerClass} footerDismiss={true}>
-        {content.scene2_icon ? <div className="scene2Icon"><img src={content.scene2_icon} /></div> : null}
+        {content.scene2_icon ?
+          <div className="scene2Icon">
+            <img src={safeURI(content.scene2_icon)} className="icon-light-theme" alt={ICON_ALT_TEXT} />
+            <img src={safeURI(content.scene2_icon_dark_theme || content.scene2_icon)} className="icon-dark-theme" alt={ICON_ALT_TEXT} />
+          </div> : null}
         <div className="message">
           <p>
             {content.scene2_title && <h3 className="scene2Title">{content.scene2_title}</h3>}
             {" "}
             {content.scene2_text && <RichText scene2_text={content.scene2_text} localization_id="scene2_text" />}
           </p>
         </div>
         <form action={this.props.form_action} method={this.props.form_method} onSubmit={this.handleSubmit} ref="form">
--- a/browser/components/newtab/content-src/asrouter/templates/SubmitFormSnippet/SubmitFormSnippet.schema.json
+++ b/browser/components/newtab/content-src/asrouter/templates/SubmitFormSnippet/SubmitFormSnippet.schema.json
@@ -1,12 +1,12 @@
 {
   "title": "SubmitFormSnippet",
   "description": "A template with two states: a SimpleSnippet and another that contains a form",
-  "version": "1.0.0",
+  "version": "1.1.0",
   "type": "object",
   "definitions": {
     "plainText": {
       "description": "Plain text (no HTML allowed)",
       "type": "string"
     },
     "richText": {
       "description": "Text with HTML subset allowed: i, b, u, strong, em, br",
@@ -50,20 +50,28 @@
         {"$ref": "#/definitions/richText"},
         {"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}
       ]
     },
     "scene1_icon": {
       "type": "string",
       "description": "Snippet icon. 64x64px. SVG or PNG preferred."
     },
+    "scene1_icon_dark_theme": {
+      "type": "string",
+      "description": "Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."
+    },
     "scene1_title_icon": {
       "type": "string",
       "description": "Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."
     },
+    "scene1_title_icon_dark_theme": {
+      "type": "string",
+      "description": "Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."
+    },
     "form_action": {
       "type": "string",
       "description": "Endpoint to submit form data."
     },
     "success_title": {
       "type": "string",
       "description": "(send to device) Title shown before text on successful registration."
     },
@@ -98,16 +106,20 @@
     "scene2_dismiss_button_text": {
       "type": "string",
       "description": "Label for the dismiss button when the sign-up form is expanded."
     },
     "scene2_icon": {
       "type": "string",
       "description": "(send to device) Image to display above the form. 98x98px. SVG or PNG preferred."
     },
+    "scene2_icon_dark_theme": {
+      "type": "string",
+      "description": "(send to device) Image to display above the form. Dark theme variant. 98x98px. SVG or PNG preferred."
+    },
     "scene2_newsletter": {
       "type": "string",
       "description": "Newsletter/basket id user is subscribing to. Must be a value from the 'Slug' column here: https://basket.mozilla.org/news/. Default 'mozilla-foundation'."
     },
     "hidden_inputs": {
       "type": "object",
       "description": "Each entry represents a hidden input, key is used as value for the name property."
     },
--- a/browser/components/newtab/content-src/asrouter/templates/Trailhead/_Trailhead.scss
+++ b/browser/components/newtab/content-src/asrouter/templates/Trailhead/_Trailhead.scss
@@ -366,19 +366,20 @@
     line-height: 1.5;
     font-weight: 200;
   }
 
   .onboardingButton {
     color: var(--newtab-text-conditional-color);
     background: var(--trailhead-card-button-background-color);
     border: 0;
-    height: 30px;
+    margin: 14px;
     min-width: 70%;
-    padding: 0 14px;
+    padding: 6px 14px;
+    white-space: pre-wrap;
 
     &:focus,
     &:hover {
       box-shadow: none;
       background: var(--trailhead-card-button-background-hover-color);
     }
 
     &:focus {
@@ -386,19 +387,18 @@
     }
 
     &:active {
       background: var(--trailhead-card-button-background-active-color);
     }
   }
 
   .onboardingButtonContainer {
-    height: 60px;
     position: absolute;
-    bottom: 0;
+    bottom: 16px;
     left: 0;
     width: 100%;
     text-align: center;
   }
 }
 
 .inline-onboarding {
   &.activity-stream.welcome {
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/content-src/components/A11yLinkButton/A11yLinkButton.jsx
@@ -0,0 +1,14 @@
+import React from "react";
+
+export function A11yLinkButton(props) {
+  // function for merging classes, if necessary
+  let className = "a11y-link-button";
+  if (props.className) {
+    className += ` ${props.className}`;
+  }
+  return (
+    <button type="button" {...props} className={className}>
+      {props.children}
+    </button>
+  );
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/content-src/components/A11yLinkButton/_A11yLinkButton.scss
@@ -0,0 +1,13 @@
+
+.a11y-link-button {
+  border: 0;
+  padding: 0;
+  cursor: pointer;
+  text-align: unset;
+  color: var(--newtab-link-primary-color);
+
+  &:hover,
+  &:focus {
+    text-decoration: underline;
+  }
+}
--- a/browser/components/newtab/content-src/components/ASRouterAdmin/ASRouterAdmin.jsx
+++ b/browser/components/newtab/content-src/components/ASRouterAdmin/ASRouterAdmin.jsx
@@ -467,16 +467,17 @@ export class ASRouterAdminInner extends 
       {messagesToShow.map(msg => this.renderMessageItem(msg))}
     </tbody></table>);
   }
 
   renderMessageFilter() {
     if (!this.state.providers) {
       return null;
     }
+    // eslint-disable-next-line jsx-a11y/no-onchange
     return (<p>Show messages from <select value={this.state.messageFilter} onChange={this.onChangeMessageFilter}>
       <option value="all">all providers</option>
       {this.state.providers.map(provider => (<option key={provider.id} value={provider.id}>{provider.id}</option>))}
     </select></p>);
   }
 
   renderTableHead() {
     return (<thead>
@@ -536,17 +537,17 @@ export class ASRouterAdminInner extends 
     if (!this.state.pasteFromClipboard) {
       return null;
     }
     const errors = this.refs.targetingParamsEval && this.refs.targetingParamsEval.innerText.length;
     return (
       <ModalOverlay title="New targeting parameters" button_label={errors ? "Cancel" : "Done"} onDoneButton={this.onPasteTargetingParams}>
         <div className="onboardingMessage">
           <p>
-            <textarea onChange={this.onNewTargetingParams} value={this.state.newStringTargetingParameters} autoFocus={true} rows="20" cols="60" />
+            <textarea onChange={this.onNewTargetingParams} value={this.state.newStringTargetingParameters} rows="20" cols="60" />
           </p>
           <p ref="targetingParamsEval" />
         </div>
       </ModalOverlay>
     );
   }
 
   renderTargetingParameters() {
--- a/browser/components/newtab/content-src/components/Base/Base.jsx
+++ b/browser/components/newtab/content-src/components/Base/Base.jsx
@@ -130,18 +130,24 @@ export class BaseContent extends React.P
 
   render() {
     const {props} = this;
     const {App} = props;
     const {initialized} = App;
     const prefs = props.Prefs.values;
 
     const shouldBeFixedToTop = PrerenderData.arePrefsValid(name => prefs[name]);
-    const noSectionsEnabled = !prefs["feeds.topsites"] && props.Sections.filter(section => section.enabled).length === 0;
     const isDiscoveryStream = props.DiscoveryStream.config && props.DiscoveryStream.config.enabled;
+    let filteredSections = props.Sections;
+
+    // Filter out highlights for DS
+    if (isDiscoveryStream) {
+      filteredSections = filteredSections.filter(section => section.id !== "highlights");
+    }
+    const noSectionsEnabled = !prefs["feeds.topsites"] && filteredSections.filter(section => section.enabled).length === 0;
     const searchHandoffEnabled = prefs["improvesearch.handoffToAwesomebar"];
 
     const outerClassName = [
       "outer-wrapper",
       isDiscoveryStream && "ds-outer-wrapper-search-alignment",
       isDiscoveryStream && "ds-outer-wrapper-breakpoint-override",
       shouldBeFixedToTop && "fixed-to-top",
       prefs.showSearch && this.state.fixedSearch && !noSectionsEnabled && "fixed-search",
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
@@ -4,35 +4,35 @@ import React from "react";
 
 export class CardGrid extends React.PureComponent {
   renderCards() {
     const recs = this.props.data.recommendations.slice(0, this.props.items);
     const cards = [];
 
     for (let index = 0; index < this.props.items; index++) {
       const rec = recs[index];
-      cards.push(rec ? (
+      cards.push(!rec || rec.placeholder ? (
+        <PlaceholderDSCard key={`dscard-${index}`} />
+      ) : (
         <DSCard
           key={`dscard-${index}`}
           pos={rec.pos}
           campaignId={rec.campaign_id}
           image_src={rec.image_src}
           raw_image_src={rec.raw_image_src}
           title={rec.title}
           excerpt={rec.excerpt}
           url={rec.url}
           id={rec.id}
           type={this.props.type}
           context={rec.context}
           dispatch={this.props.dispatch}
           source={rec.domain}
           pocket_id={rec.pocket_id}
           bookmarkGuid={rec.bookmarkGuid} />
-      ) : (
-        <PlaceholderDSCard key={`dscard-${index}`} />
       ));
     }
 
     let divisibility = ``;
 
     if (this.props.items % 4 === 0) {
       divisibility = `divisible-by-4`;
     } else if (this.props.items % 3 === 0) {
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/_CardGrid.scss
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/_CardGrid.scss
@@ -23,26 +23,16 @@
     .ds-card:not(.placeholder) {
       @include dark-theme-only {
         box-shadow: 0 1px 4px $shadow-10;
         background: $grey-70;
       }
 
       box-shadow: 0 1px 4px 0 $grey-90-10;
 
-      &:hover {
-        @include dark-theme-only {
-          box-shadow: 0 0 0 5px $grey-60;
-        }
-
-        box-shadow: 0 0 0 5px $grey-30;
-        transition: box-shadow 150ms;
-        outline: none;
-      }
-
       .img-wrapper .img img {
         border-radius: 4px 4px 0 0;
       }
     }
   }
 
   &.ds-card-grid-no-border {
     .ds-card {
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
@@ -1,9 +1,10 @@
 import {actionCreators as ac} from "common/Actions.jsm";
+import {clampTotalLines} from "content-src/lib/clamp-total-lines";
 import {DSImage} from "../DSImage/DSImage.jsx";
 import {DSLinkMenu} from "../DSLinkMenu/DSLinkMenu";
 import {ImpressionStats} from "../../DiscoveryStreamImpressionStats/ImpressionStats";
 import React from "react";
 import {SafeAnchor} from "../SafeAnchor/SafeAnchor";
 
 export class DSCard extends React.PureComponent {
   constructor(props) {
@@ -35,20 +36,22 @@ export class DSCard extends React.PureCo
           className="ds-card-link"
           dispatch={this.props.dispatch}
           onLinkClick={!this.props.placeholder ? this.onLinkClick : undefined}
           url={this.props.url}>
           <div className="img-wrapper">
             <DSImage extraClassNames="img" source={this.props.image_src} rawSource={this.props.raw_image_src} />
           </div>
           <div className="meta">
-            <div className="info-wrap">
-              <p className="source">{this.props.source}</p>
-              <header className="title">{this.props.title}</header>
-              {this.props.excerpt && <p className="excerpt">{this.props.excerpt}</p>}
+            <div className="info-wrap"
+              data-total-lines="7"
+              ref={clampTotalLines}>
+              <p className="source clamp" data-clamp="1">{this.props.source}</p>
+              <header className="title clamp" data-clamp="4">{this.props.title}</header>
+              {this.props.excerpt && <p className="excerpt clamp">{this.props.excerpt}</p>}
             </div>
             {this.props.context && (
               <p className="context">{this.props.context}</p>
             )}
           </div>
           <ImpressionStats
             campaignId={this.props.campaignId}
             rows={[{id: this.props.id, pos: this.props.pos}]}
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
@@ -18,35 +18,16 @@
       cursor: default;
     }
 
     .img-wrapper {
       opacity: 0;
     }
   }
 
-  &:hover {
-    header {
-      @include dark-theme-only {
-        color: $blue-40;
-      }
-
-      color: $blue-60;
-    }
-  }
-
-  &:active {
-    header {
-      @include dark-theme-only {
-        color: $blue-50;
-      }
-
-      color: $blue-70;
-    }
-  }
 
   .img-wrapper {
     width: 100%;
   }
 
   .img {
     height: 0;
     padding-top: 50%; // 2:1 aspect ratio
@@ -60,16 +41,49 @@
   .ds-card-link {
     display: flex;
     flex-direction: column;
     justify-content: space-between;
     height: 100%;
 
     &:focus {
       @include ds-fade-in;
+
+      @include dark-theme-only {
+        @include ds-fade-in($blue-40-40);
+      }
+    }
+
+    &:hover {
+      @include ds-fade-in($grey-30);
+
+      @include dark-theme-only {
+        @include ds-fade-in($grey-60);
+      }
+    }
+
+    &:focus,
+    &:hover {
+      header {
+        @include dark-theme-only {
+          color: $blue-40;
+        }
+
+        color: $blue-60;
+      }
+    }
+
+    &:active {
+      header {
+        @include dark-theme-only {
+          color: $blue-50;
+        }
+
+        color: $blue-70;
+      }
     }
   }
 
   .meta {
     display: flex;
     flex-direction: column;
     flex-grow: 1;
     padding: 12px;
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx
@@ -1,10 +1,11 @@
 import {DSCard, PlaceholderDSCard} from "../DSCard/DSCard.jsx";
 import {actionCreators as ac} from "common/Actions.jsm";
+import {clampTotalLines} from "content-src/lib/clamp-total-lines";
 import {DSEmptyState} from "../DSEmptyState/DSEmptyState.jsx";
 import {DSImage} from "../DSImage/DSImage.jsx";
 import {DSLinkMenu} from "../DSLinkMenu/DSLinkMenu";
 import {ImpressionStats} from "../../DiscoveryStreamImpressionStats/ImpressionStats";
 import {List} from "../List/List.jsx";
 import React from "react";
 import {SafeAnchor} from "../SafeAnchor/SafeAnchor";
 
@@ -32,67 +33,65 @@ export class Hero extends React.PureComp
 
   renderHero() {
     let [heroRec, ...otherRecs] = this.props.data.recommendations.slice(0, this.props.items);
     this.heroRec = heroRec;
 
     const cards = [];
     for (let index = 0; index < this.props.items - 1; index++) {
       const rec = otherRecs[index];
-      cards.push(rec ? (
+      cards.push(!rec || rec.placeholder ? (
+        <PlaceholderDSCard key={`dscard-${index}`} />
+      ) : (
         <DSCard
         campaignId={rec.campaign_id}
         key={`dscard-${index}`}
         image_src={rec.image_src}
         raw_image_src={rec.raw_image_src}
         title={rec.title}
         url={rec.url}
         id={rec.id}
         pos={rec.pos}
         type={this.props.type}
         dispatch={this.props.dispatch}
         context={rec.context}
         source={rec.domain}
         pocket_id={rec.pocket_id}
         bookmarkGuid={rec.bookmarkGuid} />
-      ) : (
-        <PlaceholderDSCard key={`dscard-${index}`} />
       ));
     }
 
-    let list = (
-      <List
-        recStartingPoint={1}
-        data={this.props.data}
-        hasImages={true}
-        hasBorders={this.props.border === `border`}
-        items={this.props.items - 1}
-        type={`Hero`} />
-    );
+    let heroCard = null;
 
-    return (
-      <div className={`ds-hero ds-hero-${this.props.border}`}>
+    if (!heroRec || heroRec.placeholder) {
+      heroCard = (
+        <PlaceholderDSCard />
+      );
+    } else {
+      heroCard = (
         <div className="ds-hero-item">
           <SafeAnchor
             className="wrapper"
             dispatch={this.props.dispatch}
             onLinkClick={this.onLinkClick}
             url={heroRec.url}>
             <div className="img-wrapper">
               <DSImage extraClassNames="img" source={heroRec.image_src} rawSource={heroRec.raw_image_src} />
             </div>
             <div className="meta">
-              <div className="header-and-excerpt">
+              <div className="header-and-excerpt"
+                data-total-lines="7"
+                ref={clampTotalLines}>
                 {heroRec.context ? (
                   <p className="context">{heroRec.context}</p>
                 ) : (
-                  <p className="source">{heroRec.domain}</p>
+                  <p className="source clamp" data-clamp="1">{heroRec.domain}</p>
                 )}
-                <header>{heroRec.title}</header>
-                <p className="excerpt">{heroRec.excerpt}</p>
+                <header className="clamp" data-clamp="4">{heroRec.title}</header>
+                <p className="excerpt clamp">{heroRec.excerpt}</p>
               </div>
             </div>
             <ImpressionStats
               campaignId={heroRec.campaignId}
               rows={[{id: heroRec.id, pos: heroRec.pos}]}
               dispatch={this.props.dispatch}
               source={this.props.type} />
           </SafeAnchor>
@@ -103,16 +102,32 @@ export class Hero extends React.PureComp
             intl={this.props.intl}
             url={heroRec.url}
             title={heroRec.title}
             source={heroRec.domain}
             type={this.props.type}
             pocket_id={heroRec.pocket_id}
             bookmarkGuid={heroRec.bookmarkGuid} />
         </div>
+      );
+    }
+
+    let list = (
+      <List
+        recStartingPoint={1}
+        data={this.props.data}
+        hasImages={true}
+        hasBorders={this.props.border === `border`}
+        items={this.props.items - 1}
+        type={`Hero`} />
+    );
+
+    return (
+      <div className={`ds-hero ds-hero-${this.props.border}`}>
+        {heroCard}
         <div className={`${this.props.subComponentType}`}>
           { this.props.subComponentType === `cards` ? cards : list }
         </div>
       </div>
     );
   }
 
   render() {
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/_Hero.scss
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/_Hero.scss
@@ -143,18 +143,16 @@
       .source {
         @include dark-theme-only {
           color: $grey-40;
         }
 
         font-size: 13px;
         color: $grey-50;
         margin-bottom: 0;
-        overflow-x: hidden;
-        text-overflow: ellipsis;
       }
     }
   }
 
   // "2/3 width layout"
   .ds-column-5 &,
   .ds-column-6 &,
   .ds-column-7 &,
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/List/List.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/List/List.jsx
@@ -1,9 +1,10 @@
 import {actionCreators as ac} from "common/Actions.jsm";
+import {clampTotalLines} from "content-src/lib/clamp-total-lines";
 import {connect} from "react-redux";
 import {DSEmptyState} from "../DSEmptyState/DSEmptyState.jsx";
 import {DSImage} from "../DSImage/DSImage.jsx";
 import {DSLinkMenu} from "../DSLinkMenu/DSLinkMenu";
 import {ImpressionStats} from "../../DiscoveryStreamImpressionStats/ImpressionStats";
 import React from "react";
 import {SafeAnchor} from "../SafeAnchor/SafeAnchor";
 
@@ -38,28 +39,29 @@ export class ListItem extends React.Pure
     return (
       <li className={`ds-list-item${this.props.placeholder ? " placeholder" : ""}`} >
         <SafeAnchor
           className="ds-list-item-link"
           dispatch={this.props.dispatch}
           onLinkClick={!this.props.placeholder ? this.onLinkClick : undefined}
           url={this.props.url}>
           <div className="ds-list-item-text">
-            <div>
-              <div className="ds-list-item-title">{this.props.title}</div>
-              {this.props.excerpt && <div className="ds-list-item-excerpt">{this.props.excerpt}</div>}
+            <div data-total-lines="4"
+              ref={clampTotalLines}>
+              <div className="ds-list-item-title clamp">{this.props.title}</div>
+              {this.props.excerpt && <div className="ds-list-item-excerpt clamp">{this.props.excerpt}</div>}
             </div>
             <p>
               {this.props.context && (
                 <span>
                   <span className="ds-list-item-context">{this.props.context}</span>
                   <br />
                 </span>
               )}
-              <span className="ds-list-item-info">{this.props.domain}</span>
+              <span className="ds-list-item-info clamp">{this.props.domain}</span>
             </p>
           </div>
           <DSImage extraClassNames="ds-list-image" source={this.props.image_src} rawSource={this.props.raw_image_src} />
           <ImpressionStats
             campaignId={this.props.campaignId}
             rows={[{id: this.props.id, pos: this.props.pos}]}
             dispatch={this.props.dispatch}
             source={this.props.type} />
@@ -87,34 +89,34 @@ export const PlaceholderListItem = props
  */
 export function _List(props) {
   const renderList = () => {
     const recs = props.data.recommendations.slice(props.recStartingPoint, props.recStartingPoint + props.items);
     const recMarkup = [];
 
     for (let index = 0; index < props.items; index++) {
       const rec = recs[index];
-      recMarkup.push(rec ? (
+      recMarkup.push(!rec || rec.placeholder ? (
+        <PlaceholderListItem key={`ds-list-item-${index}`} />
+      ) : (
         <ListItem key={`ds-list-item-${index}`}
         dispatch={props.dispatch}
         campaignId={rec.campaign_id}
         domain={rec.domain}
         excerpt={rec.excerpt}
         id={rec.id}
         image_src={rec.image_src}
         raw_image_src={rec.raw_image_src}
         pos={rec.pos}
         title={rec.title}
         context={rec.context}
         type={props.type}
         url={rec.url}
         pocket_id={rec.pocket_id}
         bookmarkGuid={rec.bookmarkGuid} />
-      ) : (
-        <PlaceholderListItem key={`ds-list-item-${index}`} />
       ));
     }
 
     const listStyles = [
       "ds-list",
       props.fullWidth ? "ds-list-full-width" : "",
       props.hasBorders ? "ds-list-borders" : "",
       props.hasImages ? "ds-list-images" : "",
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/List/_List.scss
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/List/_List.scss
@@ -201,17 +201,20 @@
 
     display: flex;
     justify-content: space-between;
     height: 100%;
   }
 
   .ds-list-item-excerpt {
     @include limit-visibile-lines(2, $item-line-height, $item-font-size);
-    color: $grey-10-80;
+    @include dark-theme-only {
+      color: $grey-10-80;
+    }
+    color: $grey-50;
     margin: 4px 0 8px;
   }
 
   p {
     font-size: $item-font-size * 1px;
     line-height: $item-line-height * 1px;
     margin: 0;
   }
@@ -220,17 +223,17 @@
   .ds-list-item-context {
     @include limit-visibile-lines(1, $item-line-height, $item-font-size);
     @include dark-theme-only {
       color: $grey-40;
     }
 
     color: $grey-50;
     font-size: 13px;
-    text-overflow: ellipsis;
+    -webkit-line-clamp: 1;
   }
 
   .ds-list-item-title {
     font-weight: 600;
     margin-bottom: 4px;
   }
 
   .ds-list-item-text {
--- a/browser/components/newtab/content-src/components/TopSites/TopSite.jsx
+++ b/browser/components/newtab/content-src/components/TopSites/TopSite.jsx
@@ -178,17 +178,19 @@ export class TopSiteLink extends React.P
         onClick: this.onDragEvent,
         onDragEnd: this.onDragEvent,
         onDragStart: this.onDragEvent,
         onMouseDown: this.onDragEvent,
       };
     }
     return (<li className={topSiteOuterClassName} onDrop={this.onDragEvent} onDragOver={this.onDragEvent} onDragEnter={this.onDragEvent} onDragLeave={this.onDragEvent} {...draggableProps}>
       <div className="top-site-inner">
-         <a href={link.searchTopSite ? undefined : link.url} tabIndex="0" onKeyPress={this.onKeyPress} onClick={onClick} draggable={true}>
+        {/* We don't yet support an accessible drag-and-drop implementation, see Bug 1552005 */}
+        {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
+         <a className="top-site-button" href={link.searchTopSite ? undefined : link.url} tabIndex="0" onKeyPress={this.onKeyPress} onClick={onClick} draggable={true}>
             <div className="tile" aria-hidden={true} data-fallback={letterFallback}>
               <div className={imageClassName} style={imageStyle} />
               {link.searchTopSite && <div className="top-site-icon search-topsite" />}
               {showSmallFavicon && <div
                 className="top-site-icon default-icon"
                 data-fallback={smallFaviconFallback && letterFallback}
                 style={smallFaviconStyle} />}
            </div>
--- a/browser/components/newtab/content-src/components/TopSites/TopSiteForm.jsx
+++ b/browser/components/newtab/content-src/components/TopSites/TopSiteForm.jsx
@@ -1,9 +1,10 @@
 import {actionCreators as ac, actionTypes as at} from "common/Actions.jsm";
+import {A11yLinkButton} from "content-src/components/A11yLinkButton/A11yLinkButton";
 import {FormattedMessage} from "react-intl";
 import React from "react";
 import {TOP_SITES_SOURCE} from "./TopSitesConstants";
 import {TopSiteFormInput} from "./TopSiteFormInput";
 import {TopSiteLink} from "./TopSite";
 
 export class TopSiteForm extends React.PureComponent {
   constructor(props) {
@@ -158,19 +159,19 @@ export class TopSiteForm extends React.P
     const requestFailed = this.props.previewResponse === "";
     const validationError = (this.state.validationError && !this.validateCustomScreenshotUrl()) || requestFailed;
     // Set focus on error if the url field is valid or when the input is first rendered and is empty
     const shouldFocus = (validationError && this.validateUrl(this.state.url)) || !customScreenshotUrl;
     const isLoading = this.props.previewResponse === null &&
       customScreenshotUrl && this.props.previewUrl === this.cleanUrl(customScreenshotUrl);
 
     if (!this.state.showCustomScreenshotForm) {
-      return (<a className="enable-custom-image-input" onClick={this.onEnableScreenshotUrlForm}>
-        <FormattedMessage id="topsites_form_use_image_link" />
-      </a>);
+      return (<A11yLinkButton onClick={this.onEnableScreenshotUrlForm} className="enable-custom-image-input">
+                <FormattedMessage id="topsites_form_use_image_link" />
+              </A11yLinkButton>);
     }
     return (<div className="custom-image-input-container">
       <TopSiteFormInput
         errorMessageId={requestFailed ? "topsites_form_image_validation" : "topsites_form_url_validation"}
         loading={isLoading}
         onChange={this.onCustomScreenshotUrlChange}
         onClear={this.onClearScreenshotInput}
         shouldFocus={shouldFocus}
@@ -193,18 +194,20 @@ export class TopSiteForm extends React.P
     // Preview mode if changes were made to the custom screenshot URL and no preview was received yet
     // or the request failed
     const previewMode = changed && !this.props.previewResponse;
     const previewLink = Object.assign({}, this.props.site);
     if (this.props.previewResponse) {
       previewLink.screenshot = this.props.previewResponse;
       previewLink.customScreenshotURL = this.props.previewUrl;
     }
+    // Handles the form submit so an enter press performs the correct action
+    const onSubmit = previewMode ? this.onPreviewButtonClick : this.onDoneButtonClick;
     return (
-      <form className="topsite-form">
+      <form className="topsite-form" onSubmit={onSubmit}>
         <div className="form-input-container">
           <h3 className="section-title">
             <FormattedMessage id={showAsAdd ? "topsites_form_add_header" : "topsites_form_edit_header"} />
           </h3>
           <div className="fields-and-preview">
             <div className="form-wrapper">
               <TopSiteFormInput onChange={this.onLabelChange}
                 value={this.state.label}
@@ -224,24 +227,24 @@ export class TopSiteForm extends React.P
               {this._renderCustomScreenshotInput()}
             </div>
             <TopSiteLink link={previewLink}
               defaultStyle={requestFailed}
               title={this.state.label} />
           </div>
         </div>
         <section className="actions">
-          <button className="cancel" type="button" onClick={this.onCancelButtonClick}>
+          <button className="cancel" type="button" onClick={this.onCancelButtonClick} >
             <FormattedMessage id="topsites_form_cancel_button" />
           </button>
           {previewMode ?
-            <button className="done preview" type="submit" onClick={this.onPreviewButtonClick}>
+            <button className="done preview" type="submit" >
               <FormattedMessage id="topsites_form_preview_button" />
             </button> :
-            <button className="done" type="submit" onClick={this.onDoneButtonClick}>
+            <button className="done" type="submit" >
               <FormattedMessage id={showAsAdd ? "topsites_form_add_button" : "topsites_form_save_button"} />
             </button>}
         </section>
       </form>
     );
   }
 }
 
--- a/browser/components/newtab/content-src/components/TopSites/TopSiteFormInput.jsx
+++ b/browser/components/newtab/content-src/components/TopSites/TopSiteFormInput.jsx
@@ -2,59 +2,81 @@ import {FormattedMessage} from "react-in
 import React from "react";
 
 export class TopSiteFormInput extends React.PureComponent {
   constructor(props) {
     super(props);
     this.state = {validationError: this.props.validationError};
     this.onChange = this.onChange.bind(this);
     this.onMount = this.onMount.bind(this);
+    this.onClearIconPress = this.onClearIconPress.bind(this);
   }
 
   componentWillReceiveProps(nextProps) {
     if (nextProps.shouldFocus && !this.props.shouldFocus) {
       this.input.focus();
     }
     if (nextProps.validationError && !this.props.validationError) {
       this.setState({validationError: true});
     }
     // If the component is in an error state but the value was cleared by the parent
     if (this.state.validationError && !nextProps.value) {
       this.setState({validationError: false});
     }
   }
 
+  onClearIconPress(event) {
+    // If there is input in the URL or custom image URL fields,
+    // and we hit 'enter' while tabbed over the clear icon,
+    // we should execute the function to clear the field.
+    if (event.key === "Enter") {
+      this.props.onClear();
+    }
+  }
+
   onChange(ev) {
     if (this.state.validationError) {
       this.setState({validationError: false});
     }
     this.props.onChange(ev);
   }
 
   onMount(input) {
     this.input = input;
   }
 
+  renderLoadingOrCloseButton() {
+    const showClearButton = this.props.value && this.props.onClear;
+
+    if (this.props.loading) {
+      return (<div className="loading-container"><div className="loading-animation" /></div>);
+    } else if (showClearButton) {
+      return (<button type="button" className="icon icon-clear-input icon-button-style"
+        onClick={this.props.onClear}
+        onKeyPress={this.onClearIconPress} />);
+    }
+    return null;
+  }
+
   render() {
-    const showClearButton = this.props.value && this.props.onClear;
     const {typeUrl} = this.props;
     const {validationError} = this.state;
 
     return (<label><FormattedMessage id={this.props.titleId} />
       <div className={`field ${typeUrl ? "url" : ""}${validationError ? " invalid" : ""}`}>
-        {this.props.loading ?
-          <div className="loading-container"><div className="loading-animation" /></div> :
-          showClearButton && <div className="icon icon-clear-input" onClick={this.props.onClear} />}
         <input type="text"
           value={this.props.value}
           ref={this.onMount}
           onChange={this.onChange}
           placeholder={this.props.intl.formatMessage({id: this.props.placeholderId})}
+          // Set focus on error if the url field is valid or when the input is first rendered and is empty
+          // eslint-disable-next-line jsx-a11y/no-autofocus
           autoFocus={this.props.shouldFocus}
           disabled={this.props.loading} />
+        {this.renderLoadingOrCloseButton()}
         {validationError &&
           <aside className="error-tooltip">
             <FormattedMessage id={this.props.errorMessageId} />
           </aside>}
       </div>
     </label>);
   }
 }
--- a/browser/components/newtab/content-src/components/TopSites/TopSites.jsx
+++ b/browser/components/newtab/content-src/components/TopSites/TopSites.jsx
@@ -134,30 +134,30 @@ export class _TopSites extends React.Pur
         isFixed={props.isFixed}
         isFirst={props.isFirst}
         isLast={props.isLast}
         dispatch={props.dispatch}>
         <TopSiteList TopSites={props.TopSites} TopSitesRows={props.TopSitesRows} dispatch={props.dispatch} intl={props.intl} topSiteIconType={topSiteIconType} />
         <div className="edit-topsites-wrapper">
           {editForm &&
             <div className="edit-topsites">
-              <div className="modal-overlay" onClick={this.onEditFormClose} />
+              <div className="modal-overlay" onClick={this.onEditFormClose} role="presentation" />
               <div className="modal">
                 <TopSiteForm
                   site={props.TopSites.rows[editForm.index]}
                   onClose={this.onEditFormClose}
                   dispatch={this.props.dispatch}
                   intl={this.props.intl}
                   {...editForm} />
               </div>
             </div>
           }
           {showSearchShortcutsForm &&
             <div className="edit-search-shortcuts">
-              <div className="modal-overlay" onClick={this.onSearchShortcutsFormClose} />
+              <div className="modal-overlay" onClick={this.onSearchShortcutsFormClose} role="presentation" />
               <div className="modal">
                 <SearchShortcutsForm
                   TopSites={props.TopSites}
                   onClose={this.onSearchShortcutsFormClose}
                   dispatch={this.props.dispatch} />
               </div>
             </div>
           }
--- a/browser/components/newtab/content-src/components/TopSites/_TopSites.scss
+++ b/browser/components/newtab/content-src/components/TopSites/_TopSites.scss
@@ -401,20 +401,16 @@
       }
     }
 
     .enable-custom-image-input {
       display: inline-block;
       font-size: 13px;
       margin-top: 4px;
       cursor: pointer;
-
-      &:hover {
-        text-decoration: underline;
-      }
     }
 
     .custom-image-input-container {
       margin-top: 4px;
 
       .loading-container {
         width: 16px;
         height: 16px;
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/content-src/lib/clamp-total-lines.js
@@ -0,0 +1,32 @@
+/**
+ * Adjusts line-clamps of a node's children to fill a desired number of lines.
+ *
+ * This is a React callback ref that should be set on a parent node that also
+ * has a data-total-lines attribute. Children with "clamp" class name are
+ * clamped to allow as many lines to earlier children while making sure every
+ * child gets at least one line. Each child can be explicitly clamped to a max
+ * lines with a data-clamp attribute.
+ */
+export function clampTotalLines(parentNode) {
+  // Nothing to do if the node is removed or didn't configure how many lines
+  if (!parentNode || !parentNode.dataset.totalLines) {
+    return;
+  }
+
+  // Only handle clamp-able children that are displayed (not hidden)
+  const toClamp = Array.from(parentNode.querySelectorAll(".clamp"))
+    .filter(child => child.scrollHeight);
+
+  // Start with total allowed lines while reserving 1 line for each child
+  let maxLines = parentNode.dataset.totalLines - toClamp.length + 1;
+  toClamp.forEach(child => {
+    // Clamp to the remaining allowed, explicit limit or the natural line count
+    const lines = Math.min(maxLines,
+      child.dataset.clamp || Infinity,
+      child.scrollHeight / parseInt(global.getComputedStyle(child).lineHeight, 10));
+    child.style.webkitLineClamp = `${lines}`;
+
+    // Update the remaining line allowance less the already reserved 1 line
+    maxLines -= lines - 1;
+  });
+}
--- a/browser/components/newtab/content-src/lib/selectLayoutRender.js
+++ b/browser/components/newtab/content-src/lib/selectLayoutRender.js
@@ -34,88 +34,109 @@ export const selectLayoutRender = (state
     }
 
     return {
       ...data,
       recommendations,
     };
   }
 
-  function maybeInjectSpocs(data, spocsConfig) {
-    // Do we ever expect to possibly have a spoc.
-    if (data && spocsConfig && spocsConfig.positions && spocsConfig.positions.length) {
-      // We expect a spoc, spocs are loaded, but the server returned no spocs.
-      if (!spocs.data.spocs || !spocs.data.spocs.length) {
-        return data;
-      }
-
-      // We expect a spoc, spocs are loaded, and we have spocs available.
-      return rollForSpocs(data, spocsConfig);
-    }
-
-    return data;
-  }
-
   const positions = {};
   const DS_COMPONENTS = ["Message", "SectionTitle", "Navigation",
     "CardGrid", "Hero", "HorizontalRule", "List"];
 
   const filterArray = [];
 
   if (!prefs["feeds.topsites"]) {
     filterArray.push("TopSites");
   }
 
   if (!prefs["feeds.section.topstories"]) {
     filterArray.push(...DS_COMPONENTS);
   }
 
+  const placeholderComponent = component => {
+    const data = {
+      recommendations: [],
+    };
+
+    let items = 0;
+    if (component.properties && component.properties.items) {
+      items = component.properties.items;
+    }
+    for (let i = 0; i < items; i++) {
+      data.recommendations.push({"placeholder": true});
+    }
+
+    return {...component, data};
+  };
+
   const handleComponent = component => {
     positions[component.type] = positions[component.type] || 0;
 
-    let {data} = feeds.data[component.feed.url];
+    const feed = feeds.data[component.feed.url];
+    let data = {
+      recommendations: [],
+    };
+    if (feed && feed.data) {
+      data = {
+        ...feed.data,
+        recommendations: [...feed.data.recommendations],
+      };
+    }
 
     if (component && component.properties && component.properties.offset) {
       data = {
         ...data,
         recommendations: data.recommendations.slice(component.properties.offset),
       };
     }
 
-    data = maybeInjectSpocs(data, component.spocs);
+    // Do we ever expect to possibly have a spoc.
+    if (data && component.spocs && component.spocs.positions && component.spocs.positions.length) {
+      // We expect a spoc, spocs are loaded, and the server returned spocs.
+      if (spocs.loaded && spocs.data.spocs && spocs.data.spocs.length) {
+        data = rollForSpocs(data, component.spocs);
+      }
+    }
 
     let items = 0;
     if (component.properties && component.properties.items) {
       items = Math.min(component.properties.items, data.recommendations.length);
     }
 
     // loop through a component items
     // Store the items position sequentially for multiple components of the same type.
     // Example: A second card grid starts pos offset from the last card grid.
     for (let i = 0; i < items; i++) {
-      data.recommendations[i].pos = positions[component.type]++;
+      data.recommendations[i] = {
+        ...data.recommendations[i],
+        pos: positions[component.type]++,
+      };
     }
 
     return {...component, data};
   };
 
   const renderLayout = () => {
     const renderedLayoutArray = [];
     for (const row of layout.filter(r => r.components.filter(c => !filterArray.includes(c.type)).length)) {
       let components = [];
       renderedLayoutArray.push({
         ...row,
         components,
       });
-      for (const component of row.components) {
+      for (const component of row.components.filter(c => !filterArray.includes(c.type))) {
         if (component.feed) {
           const spocsConfig = component.spocs;
-          // Are we still waiting on a feed/spocs, render what we have, and bail out early.
+          // Are we still waiting on a feed/spocs, render what we have,
+          // add a placeholder for this component, and bail out early.
           if (!feeds.data[component.feed.url] ||
             (spocsConfig && spocsConfig.positions && spocsConfig.positions.length && !spocs.loaded)) {
+            components.push(placeholderComponent(component));
             return renderedLayoutArray;
           }
           components.push(handleComponent(component));
         } else {
           components.push(component);
         }
       }
     }
--- a/browser/components/newtab/content-src/styles/_activity-stream.scss
+++ b/browser/components/newtab/content-src/styles/_activity-stream.scss
@@ -98,17 +98,18 @@ a {
   border: $border-primary;
   border-radius: 4px;
   color: inherit;
   cursor: pointer;
   margin-bottom: 15px;
   padding: 10px 30px;
   white-space: nowrap;
 
-  &:hover:not(.dismiss) {
+  &:hover:not(.dismiss),
+  &:focus:not(.dismiss) {
     box-shadow: $shadow-primary;
     transition: box-shadow 150ms;
   }
 
   &.dismiss {
     background-color: transparent;
     border: 0;
     padding: 0;
@@ -127,17 +128,27 @@ a {
 
 input {
   &[type='text'],
   &[type='search'] {
     border-radius: $border-radius;
   }
 }
 
+// These styles are needed for -webkit-line-clamp to work correctly, so reuse
+// this class name while separately setting a clamp value via CSS or JS.
+.clamp {
+  -webkit-box-orient: vertical;
+  display: -webkit-box;
+  overflow: hidden;
+  word-break: break-word;
+}
+
 // Components
+@import '../components/A11yLinkButton/A11yLinkButton';
 @import '../components/Base/Base';
 @import '../components/ErrorBoundary/ErrorBoundary';
 @import '../components/TopSites/TopSites';
 @import '../components/Sections/Sections';
 @import '../components/Topics/Topics';
 @import '../components/Search/Search';
 @import '../components/ContextMenu/ContextMenu';
 @import '../components/ConfirmDialog/ConfirmDialog';
--- a/browser/components/newtab/content-src/styles/_icons.scss
+++ b/browser/components/newtab/content-src/styles/_icons.scss
@@ -4,34 +4,45 @@
   background-size: $icon-size;
   -moz-context-properties: fill;
   display: inline-block;
   fill: var(--newtab-icon-primary-color);
   height: $icon-size;
   vertical-align: middle;
   width: $icon-size;
 
+  // helper classes
   &.icon-spacer {
     margin-inline-end: 8px;
   }
 
   &.icon-small-spacer {
     margin-inline-end: 6px;
   }
 
+  &.icon-button-style {
+    fill: var(--newtab-icon-secondary-color);
+    border: 0;
+
+    &:focus,
+    &:hover {
+      fill: var(--newtab-text-primary-color);
+    }
+  }
+
+  // icon images
   &.icon-bookmark-added {
     background-image: url('chrome://browser/skin/bookmark.svg');
   }
 
   &.icon-bookmark-hollow {
     background-image: url('chrome://browser/skin/bookmark-hollow.svg');
   }
 
   &.icon-clear-input {
-    fill: var(--newtab-icon-secondary-color);
     background-image: url('#{$image-path}glyph-cancel-16.svg');
   }
 
   &.icon-delete {
     background-image: url('#{$image-path}glyph-delete-16.svg');
   }
 
   &.icon-search {
--- a/browser/components/newtab/content-src/styles/_mixins.scss
+++ b/browser/components/newtab/content-src/styles/_mixins.scss
@@ -4,24 +4,20 @@
   background-position: center;
   background-repeat: no-repeat;
   background-size: cover;
   border-radius: 4px;
   box-shadow: inset 0 0 0 0.5px $black-15;
 }
 
 // Note: lineHeight and fontSize should be unitless but can be derived from pixel values
+// Bug 1550624 to clean up / remove this mixin that no longer limits lines
 @mixin limit-visibile-lines($line-count, $line-height, $font-size) {
-  -webkit-box-orient: vertical;
-  display: -webkit-box;
   font-size: $font-size * 1px;
-  -webkit-line-clamp: $line-count;
   line-height: $line-height * 1px;
-  max-height: 1em * $line-count * $line-height / $font-size;
-  overflow: hidden;
 }
 
 @mixin dark-theme-only {
   [lwt-newtab-brighttext] & {
     @content;
   }
 }
 
@@ -40,14 +36,14 @@
 
   @include dark-theme-only {
     border-bottom: 1px solid $grey-60;
   }
 
   border-bottom: 1px solid $grey-30;
 }
 
-@mixin ds-fade-in {
-  box-shadow: 0 0 0 1px $blue-50 inset, 0 0 0 1px $blue-50, 0 0 0 5px $blue-50-30;
+@mixin ds-fade-in($halo-color: $blue-50-30) {
+  box-shadow: 0 0 0 5px $halo-color;
   transition: box-shadow 150ms;
   border-radius: 4px;
   outline: none;
 }
--- a/browser/components/newtab/content-src/styles/_variables.scss
+++ b/browser/components/newtab/content-src/styles/_variables.scss
@@ -41,16 +41,17 @@
 $grey-90-30: rgba($grey-90, 0.3);
 $grey-90-40: rgba($grey-90, 0.4);
 $grey-90-50: rgba($grey-90, 0.5);
 $grey-90-60: rgba($grey-90, 0.6);
 $grey-90-70: rgba($grey-90, 0.7);
 $grey-90-80: rgba($grey-90, 0.8);
 $grey-90-90: rgba($grey-90, 0.9);
 
+$blue-40-40: rgba($blue-40, 0.4);
 $blue-50-30: rgba($blue-50, 0.3);
 
 $black: #000;
 $black-5: rgba($black, 0.05);
 $black-10: rgba($black, 0.1);
 $black-12: rgba($black, 0.12);
 $black-15: rgba($black, 0.15);
 $black-20: rgba($black, 0.2);
--- a/browser/components/newtab/contributing.md
+++ b/browser/components/newtab/contributing.md
@@ -54,16 +54,32 @@ You have identified the bug, written cod
 All code is added using a pull request against the `master` branch of our repo.  Before submitting a PR, please go through this checklist:
 - all [unit tests](#unit-tests) must pass
 - if you haven't written unit tests for your patch, eyebrows will be curmudgeonly furrowed (write unit tests!)
 - if your pull request fixes a particular ticket (it does, right?), please use the `fixes #nnn` github annotation to indicate this
 - please add a `PR / Needs review` tag to your PR (if you have permission).  This starts the code review process.  If you cannot add a tag, don't worry, we will add it during triage.
 - if you can pick a module owner to be your reviewer by including `r? @username` in the comment (if not, don't worry, we will assign a reviewer)
 - make sure your PR will merge gracefully with `master` at the time you create the PR, and that your commit history is 'clean'
 
+### Setting up pre-push hooks
+
+If you contribute often and would like to set up a pre-push hook to always run `npm lint` before you push to Github,
+you can run the following from the root of the activity-stream directory:
+
+```
+cp hooks/pre-push .git/hooks/pre-push && chmod +x .git/hooks/pre-push
+```
+
+Your hook should now run whenever you run `git push`. To skip it, use the `--no-verify` option:
+
+```
+git push --no-verify
+```
+
+
 ## Code Reviews ##
 
 You have created a PR and submitted it to the repo, and now are waiting patiently for you code review feedback.  One of the projects
 module owners will be along and will either:
 - make suggestions for some improvements
 - give you an `R+` in the comments section, indicating the review is done and the code can be merged
 
 Typically, you will iterate on the PR, making changes and pushing your changes to new commits on the PR.  When the reviewer is
--- a/browser/components/newtab/css/activity-stream-linux.css
+++ b/browser/components/newtab/css/activity-stream-linux.css
@@ -132,22 +132,26 @@ body {
   fill: var(--newtab-icon-primary-color);
   height: 16px;
   vertical-align: middle;
   width: 16px; }
   .icon.icon-spacer {
     margin-inline-end: 8px; }
   .icon.icon-small-spacer {
     margin-inline-end: 6px; }
+  .icon.icon-button-style {
+    fill: var(--newtab-icon-secondary-color);
+    border: 0; }
+    .icon.icon-button-style:focus, .icon.icon-button-style:hover {
+      fill: var(--newtab-text-primary-color); }
   .icon.icon-bookmark-added {
     background-image: url("chrome://browser/skin/bookmark.svg"); }
   .icon.icon-bookmark-hollow {
     background-image: url("chrome://browser/skin/bookmark-hollow.svg"); }
   .icon.icon-clear-input {
-    fill: var(--newtab-icon-secondary-color);
     background-image: url("../data/content/assets/glyph-cancel-16.svg"); }
   .icon.icon-delete {
     background-image: url("../data/content/assets/glyph-delete-16.svg"); }
   .icon.icon-search {
     background-image: url("chrome://browser/skin/search-glass.svg"); }
   .icon.icon-modal-delete {
     flex-shrink: 0;
     background-image: url("../data/content/assets/glyph-modal-delete-32.svg");
@@ -311,18 +315,19 @@ a {
   background-color: var(--newtab-button-secondary-color);
   border: 1px solid var(--newtab-border-primary-color);
   border-radius: 4px;
   color: inherit;
   cursor: pointer;
   margin-bottom: 15px;
   padding: 10px 30px;
   white-space: nowrap; }
-  .button:hover:not(.dismiss),
-  .actions button:hover:not(.dismiss) {
+  .button:hover:not(.dismiss), .button:focus:not(.dismiss),
+  .actions button:hover:not(.dismiss),
+  .actions button:focus:not(.dismiss) {
     box-shadow: 0 0 0 5px var(--newtab-card-active-outline-color);
     transition: box-shadow 150ms; }
   .button.dismiss,
   .actions button.dismiss {
     background-color: transparent;
     border: 0;
     padding: 0;
     text-decoration: underline; }
@@ -332,16 +337,31 @@ a {
     background-color: var(--newtab-button-primary-color);
     border: solid 1px var(--newtab-button-primary-color);
     color: #FFF;
     margin-inline-start: auto; }
 
 input[type='text'], input[type='search'] {
   border-radius: 3px; }
 
+.clamp {
+  -webkit-box-orient: vertical;
+  display: -webkit-box;
+  overflow: hidden;
+  word-break: break-word; }
+
+.a11y-link-button {
+  border: 0;
+  padding: 0;
+  cursor: pointer;
+  text-align: unset;
+  color: var(--newtab-link-primary-color); }
+  .a11y-link-button:hover, .a11y-link-button:focus {
+    text-decoration: underline; }
+
 .outer-wrapper {
   color: var(--newtab-text-primary-color);
   display: flex;
   flex-grow: 1;
   min-height: 100vh;
   padding: 30px 32px 32px; }
   .outer-wrapper.fixed-to-top {
     display: block; }
@@ -717,18 +737,16 @@ main {
     .topsite-form .form-wrapper .url input:dir(rtl):not(:placeholder-shown) {
       direction: ltr;
       text-align: right; }
   .topsite-form .form-wrapper .enable-custom-image-input {
     display: inline-block;
     font-size: 13px;
     margin-top: 4px;
     cursor: pointer; }
-    .topsite-form .form-wrapper .enable-custom-image-input:hover {
-      text-decoration: underline; }
   .topsite-form .form-wrapper .custom-image-input-container {
     margin-top: 4px; }
     .topsite-form .form-wrapper .custom-image-input-container .loading-container {
       width: 16px;
       height: 16px;
       overflow: hidden;
       position: absolute;
       transform: translateY(-50%);
@@ -1892,31 +1910,25 @@ main {
   grid-gap: 24px;
   margin: 16px 0; }
   .ds-card-grid .ds-card {
     background: #FFF;
     border-radius: 4px; }
     [lwt-newtab-brighttext] .ds-card-grid .ds-card {
       background: none; }
   .ds-card-grid .ds-card-link:focus {
-    box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
+    box-shadow: 0 0 0 5px rgba(10, 132, 255, 0.3);
     transition: box-shadow 150ms;
     border-radius: 4px;
     outline: none; }
   .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) {
     box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); }
     [lwt-newtab-brighttext] .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) {
       box-shadow: 0 1px 4px rgba(12, 12, 13, 0.1);
       background: #38383D; }
-    .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder):hover {
-      box-shadow: 0 0 0 5px #D7D7DB;
-      transition: box-shadow 150ms;
-      outline: none; }
-      [lwt-newtab-brighttext] .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder):hover {
-        box-shadow: 0 0 0 5px #4A4A4F; }
     .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img {
       border-radius: 4px 4px 0 0; }
   .ds-card-grid.ds-card-grid-no-border .ds-card {
     background: none; }
     .ds-card-grid.ds-card-grid-no-border .ds-card .meta {
       padding: 12px 0; }
   .ds-column-5 .ds-card-grid,
   .ds-column-6 .ds-card-grid,
@@ -1938,41 +1950,31 @@ main {
       .ds-column-11 .ds-card-grid.ds-card-grid-divisible-by-3 .title,
       .ds-column-12 .ds-card-grid.ds-card-grid-divisible-by-3 .title {
         font-size: 17px;
         line-height: 24px; }
     .ds-column-9 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
     .ds-column-10 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
     .ds-column-11 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
     .ds-column-12 .ds-card-grid.ds-card-grid-divisible-by-4 .title {
-      -webkit-box-orient: vertical;
-      display: -webkit-box;
       font-size: 15px;
-      -webkit-line-clamp: 3;
-      line-height: 20px;
-      max-height: 4em;
-      overflow: hidden; }
+      line-height: 20px; }
   .ds-card-grid.empty {
     grid-template-columns: auto; }
 
 .ds-hero {
   position: relative; }
   .ds-hero header {
     font-weight: 600; }
   .ds-hero p {
     line-height: 1.538;
     margin: 8px 0; }
   .ds-hero .excerpt {
-    -webkit-box-orient: vertical;
-    display: -webkit-box;
     font-size: 14px;
-    -webkit-line-clamp: 3;
     line-height: 20px;
-    max-height: 4.28571em;
-    overflow: hidden;
     color: #0C0C0D;
     margin: 0 0 10px; }
     [lwt-newtab-brighttext] .ds-hero .excerpt {
       color: #F9F9FA; }
   .ds-hero .ds-card:not(.placeholder) {
     border: 0;
     padding-bottom: 20px; }
     .ds-hero .ds-card:not(.placeholder) p {
@@ -1999,17 +2001,17 @@ main {
     margin: 12px 0 16px;
     padding-top: 16px;
     height: 100%; }
     [lwt-newtab-brighttext] .ds-hero .wrapper {
       border-top: 1px solid #4A4A4F; }
     [lwt-newtab-brighttext] .ds-hero .wrapper {
       color: #D7D7DB; }
     .ds-hero .wrapper:focus {
-      box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
+      box-shadow: 0 0 0 5px rgba(10, 132, 255, 0.3);
       transition: box-shadow 150ms;
       border-radius: 4px;
       outline: none; }
     .ds-hero-no-border .ds-hero-item .wrapper {
       border-top: 0;
       border-bottom: 0;
       padding: 0 0 8px; }
     .ds-hero .wrapper:hover .meta header {
@@ -2028,40 +2030,33 @@ main {
       .ds-hero .wrapper .img img {
         border-radius: 4px;
         box-shadow: inset 0 0 0 0.5px rgba(0, 0, 0, 0.15); }
     .ds-hero .wrapper .meta {
       display: block;
       flex-direction: column;
       justify-content: space-between; }
       .ds-hero .wrapper .meta header {
-        -webkit-box-orient: vertical;
-        display: -webkit-box;
         font-size: 22px;
-        -webkit-line-clamp: 4;
         line-height: 28px;
-        max-height: 5.09091em;
-        overflow: hidden;
         color: #0C0C0D;
         margin-bottom: 0; }
         [lwt-newtab-brighttext] .ds-hero .wrapper .meta header {
           color: #FFF; }
       .ds-hero .wrapper .meta .context,
       .ds-hero .wrapper .meta .source {
         margin: 0 0 4px; }
       .ds-hero .wrapper .meta .context {
         color: #008EA4; }
         [lwt-newtab-brighttext] .ds-hero .wrapper .meta .context {
           color: #A7FFFE; }
       .ds-hero .wrapper .meta .source {
         font-size: 13px;
         color: #737373;
-        margin-bottom: 0;
-        overflow-x: hidden;
-        text-overflow: ellipsis; }
+        margin-bottom: 0; }
         [lwt-newtab-brighttext] .ds-hero .wrapper .meta .source {
           color: #B1B1B3; }
   .ds-column-5 .ds-hero .wrapper,
   .ds-column-6 .ds-hero .wrapper,
   .ds-column-7 .ds-hero .wrapper,
   .ds-column-8 .ds-hero .wrapper {
     display: grid;
     grid-template-columns: repeat(2, 1fr);
@@ -2151,23 +2146,18 @@ main {
       .ds-column-12 .ds-hero .wrapper .meta {
         flex-grow: 1;
         display: flex;
         padding: 0 24px 0 0; }
         .ds-column-9 .ds-hero .wrapper .meta header,
         .ds-column-10 .ds-hero .wrapper .meta header,
         .ds-column-11 .ds-hero .wrapper .meta header,
         .ds-column-12 .ds-hero .wrapper .meta header {
-          -webkit-box-orient: vertical;
-          display: -webkit-box;
           font-size: 22px;
-          -webkit-line-clamp: 3;
-          line-height: 28px;
-          max-height: 3.81818em;
-          overflow: hidden; }
+          line-height: 28px; }
         .ds-column-9 .ds-hero .wrapper .meta .source,
         .ds-column-10 .ds-hero .wrapper .meta .source,
         .ds-column-11 .ds-hero .wrapper .meta .source,
         .ds-column-12 .ds-hero .wrapper .meta .source {
           margin-bottom: 0; }
     .ds-column-9 .ds-hero .cards,
     .ds-column-10 .ds-hero .cards,
     .ds-column-11 .ds-hero .cards,
@@ -2190,23 +2180,18 @@ main {
       .ds-column-10 .ds-hero .cards .ds-card:active .title, [lwt-newtab-brighttext]
       .ds-column-11 .ds-hero .cards .ds-card:active .title, [lwt-newtab-brighttext]
       .ds-column-12 .ds-hero .cards .ds-card:active .title {
         color: #0A84FF; }
       .ds-column-9 .ds-hero .cards .ds-card .title,
       .ds-column-10 .ds-hero .cards .ds-card .title,
       .ds-column-11 .ds-hero .cards .ds-card .title,
       .ds-column-12 .ds-hero .cards .ds-card .title {
-        -webkit-box-orient: vertical;
-        display: -webkit-box;
         font-size: 14px;
-        -webkit-line-clamp: 3;
-        line-height: 20px;
-        max-height: 4.28571em;
-        overflow: hidden; }
+        line-height: 20px; }
         [lwt-newtab-brighttext] .ds-column-9 .ds-hero .cards .ds-card .title, [lwt-newtab-brighttext]
         .ds-column-10 .ds-hero .cards .ds-card .title, [lwt-newtab-brighttext]
         .ds-column-11 .ds-hero .cards .ds-card .title, [lwt-newtab-brighttext]
         .ds-column-12 .ds-hero .cards .ds-card .title {
           color: #FFF; }
   .ds-hero.empty {
     grid-template-columns: auto; }
 
@@ -2222,23 +2207,18 @@ main {
   grid-row-gap: 24px;
   grid-column-gap: 24px;
   padding-inline-start: 0; }
   .ds-list:not(.ds-list-full-width) .ds-list-item {
     font-size: 14px;
     line-height: 20px;
     position: relative; }
   .ds-list:not(.ds-list-full-width) .ds-list-item-title {
-    -webkit-box-orient: vertical;
-    display: -webkit-box;
     font-size: 14px;
-    -webkit-line-clamp: 3;
-    line-height: 20px;
-    max-height: 4.28571em;
-    overflow: hidden; }
+    line-height: 20px; }
   .ds-list:not(.ds-list-full-width) .ds-list-image {
     min-width: 80px;
     width: 80px; }
   .ds-column-5 .ds-list:not(.ds-list-full-width),
   .ds-column-6 .ds-list:not(.ds-list-full-width),
   .ds-column-7 .ds-list:not(.ds-list-full-width),
   .ds-column-8 .ds-list:not(.ds-list-full-width) {
     grid-template-columns: repeat(2, 1fr); }
@@ -2254,17 +2234,17 @@ main {
   .ds-list:not(.ds-list-images) .ds-list-image {
     display: none; }
   .ds-list a {
     color: #0C0C0D; }
     [lwt-newtab-brighttext] .ds-list a {
       color: #F9F9FA; }
 
 .ds-list-item-link:focus {
-  box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
+  box-shadow: 0 0 0 5px rgba(10, 132, 255, 0.3);
   transition: box-shadow 150ms;
   border-radius: 4px;
   outline: none; }
 
 .ds-list-numbers .ds-list-item {
   counter-increment: list; }
 
 .ds-list-numbers .ds-list-item:not(.placeholder) > .ds-list-item-link {
@@ -2339,23 +2319,18 @@ main {
       border-bottom: 1px solid #4A4A4F; }
 
 .ds-list-full-width .ds-list-item {
   font-size: 17px;
   line-height: 24px;
   position: relative; }
 
 .ds-list-full-width .ds-list-item-title {
-  -webkit-box-orient: vertical;
-  display: -webkit-box;
   font-size: 17px;
-  -webkit-line-clamp: 3;
-  line-height: 24px;
-  max-height: 4.23529em;
-  overflow: hidden; }
+  line-height: 24px; }
 
 .ds-list-full-width .ds-list-image {
   min-width: 160px;
   width: 160px; }
 
 .ds-list-item {
   display: block;
   text-align: start; }
@@ -2369,41 +2344,33 @@ main {
     .ds-list-item.placeholder .ds-list-image {
       opacity: 0; }
   .ds-list-item .ds-list-item-link {
     mix-blend-mode: normal;
     display: flex;
     justify-content: space-between;
     height: 100%; }
   .ds-list-item .ds-list-item-excerpt {
-    -webkit-box-orient: vertical;
-    display: -webkit-box;
     font-size: 14px;
-    -webkit-line-clamp: 2;
     line-height: 20px;
-    max-height: 2.85714em;
-    overflow: hidden;
-    color: rgba(249, 249, 250, 0.8);
+    color: #737373;
     margin: 4px 0 8px; }
+    [lwt-newtab-brighttext] .ds-list-item .ds-list-item-excerpt {
+      color: rgba(249, 249, 250, 0.8); }
   .ds-list-item p {
     font-size: 14px;
     line-height: 20px;
     margin: 0; }
   .ds-list-item .ds-list-item-info,
   .ds-list-item .ds-list-item-context {
-    -webkit-box-orient: vertical;
-    display: -webkit-box;
     font-size: 14px;
-    -webkit-line-clamp: 1;
     line-height: 20px;
-    max-height: 1.42857em;
-    overflow: hidden;
     color: #737373;
     font-size: 13px;
-    text-overflow: ellipsis; }
+    -webkit-line-clamp: 1; }
     [lwt-newtab-brighttext] .ds-list-item .ds-list-item-info, [lwt-newtab-brighttext]
     .ds-list-item .ds-list-item-context {
       color: #B1B1B3; }
   .ds-list-item .ds-list-item-title {
     font-weight: 600;
     margin-bottom: 4px; }
   .ds-list-item .ds-list-item-text {
     display: flex;
@@ -2468,17 +2435,17 @@ main {
     [lwt-newtab-brighttext] .ds-section-title .subtitle {
       color: #D7D7DB; }
 
 .ds-top-sites .top-sites {
   margin: 0 -25px; }
   .ds-top-sites .top-sites .top-site-outer {
     padding: 0 12px; }
     .ds-top-sites .top-sites .top-site-outer .top-site-inner > a:-moz-any(.active, :focus) .tile {
-      box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
+      box-shadow: 0 0 0 5px rgba(10, 132, 255, 0.3);
       transition: box-shadow 150ms;
       border-radius: 4px;
       outline: none; }
   .ds-top-sites .top-sites .top-sites-list {
     margin: 0 -12px; }
 
 .ds-top-sites .hide-for-narrow {
   display: none; }
@@ -2620,67 +2587,72 @@ main {
   .ds-card.placeholder {
     background: transparent;
     box-shadow: inset 0 0 0 1px var(--newtab-inner-box-shadow-color);
     border-radius: 4px; }
     .ds-card.placeholder .ds-card-link {
       cursor: default; }
     .ds-card.placeholder .img-wrapper {
       opacity: 0; }
-  .ds-card:hover header {
-    color: #0060DF; }
-    [lwt-newtab-brighttext] .ds-card:hover header {
-      color: #45A1FF; }
-  .ds-card:active header {
-    color: #003EAA; }
-    [lwt-newtab-brighttext] .ds-card:active header {
-      color: #0A84FF; }
   .ds-card .img-wrapper {
     width: 100%; }
   .ds-card .img {
     height: 0;
     padding-top: 50%; }
     .ds-card .img img {
       border-radius: 4px;
       box-shadow: inset 0 0 0 0.5px rgba(0, 0, 0, 0.15); }
   .ds-card .ds-card-link {
     display: flex;
     flex-direction: column;
     justify-content: space-between;
     height: 100%; }
     .ds-card .ds-card-link:focus {
-      box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
+      box-shadow: 0 0 0 5px rgba(10, 132, 255, 0.3);
+      transition: box-shadow 150ms;
+      border-radius: 4px;
+      outline: none; }
+      [lwt-newtab-brighttext] .ds-card .ds-card-link:focus {
+        box-shadow: 0 0 0 5px rgba(69, 161, 255, 0.4);
+        transition: box-shadow 150ms;
+        border-radius: 4px;
+        outline: none; }
+    .ds-card .ds-card-link:hover {
+      box-shadow: 0 0 0 5px #D7D7DB;
       transition: box-shadow 150ms;
       border-radius: 4px;
       outline: none; }
+      [lwt-newtab-brighttext] .ds-card .ds-card-link:hover {
+        box-shadow: 0 0 0 5px #4A4A4F;
+        transition: box-shadow 150ms;
+        border-radius: 4px;
+        outline: none; }
+    .ds-card .ds-card-link:focus header, .ds-card .ds-card-link:hover header {
+      color: #0060DF; }
+      [lwt-newtab-brighttext] .ds-card .ds-card-link:focus header, [lwt-newtab-brighttext] .ds-card .ds-card-link:hover header {
+        color: #45A1FF; }
+    .ds-card .ds-card-link:active header {
+      color: #003EAA; }
+      [lwt-newtab-brighttext] .ds-card .ds-card-link:active header {
+        color: #0A84FF; }
   .ds-card .meta {
     display: flex;
     flex-direction: column;
     flex-grow: 1;
     padding: 12px; }
     .ds-card .meta .info-wrap {
       flex-grow: 1;
       margin: 0 0 12px; }
     .ds-card .meta .title {
-      -webkit-box-orient: vertical;
-      display: -webkit-box;
       font-size: 17px;
-      -webkit-line-clamp: 3;
       line-height: 24px;
-      max-height: 4.23529em;
-      overflow: hidden;
       font-weight: 600; }
     .ds-card .meta .excerpt {
-      -webkit-box-orient: vertical;
-      display: -webkit-box;
       font-size: 14px;
-      -webkit-line-clamp: 3;
-      line-height: 20px;
-      max-height: 4.28571em;
-      overflow: hidden; }
+      line-height: 20px; }
     .ds-card .meta .source {
       margin-bottom: 2px; }
     .ds-card .meta .context,
     .ds-card .meta .source {
       font-size: 13px;
       color: #737373; }
       [lwt-newtab-brighttext] .ds-card .meta .context, [lwt-newtab-brighttext]
       .ds-card .meta .source {
@@ -2879,16 +2851,26 @@ main {
   background: rgba(215, 215, 219, 0.6);
   text-align: center;
   position: absolute;
   top: 0;
   width: 100%; }
   .snippets-preview-banner span {
     vertical-align: middle; }
 
+body:not([lwt-newtab-brighttext]) .icon-dark-theme,
+body:not([lwt-newtab-brighttext]) .icon.icon-dark-theme,
+body:not([lwt-newtab-brighttext]) .scene2Icon .icon-dark-theme {
+  display: none; }
+
+body[lwt-newtab-brighttext] .icon-light-theme,
+body[lwt-newtab-brighttext] .icon.icon-light-theme,
+body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
+  display: none; }
+
 .activity-stream.modal-open {
   overflow: hidden; }
 
 .modalOverlayOuter {
   background: var(--newtab-overlay-color);
   height: 100%;
   position: fixed;
   top: 0;
@@ -3982,30 +3964,30 @@ a.firstrun-link {
     margin: 0 0 60px;
     color: var(--newtab-text-conditional-color);
     line-height: 1.5;
     font-weight: 200; }
   .trailheadCard .onboardingButton {
     color: var(--newtab-text-conditional-color);
     background: var(--trailhead-card-button-background-color);
     border: 0;
-    height: 30px;
+    margin: 14px;
     min-width: 70%;
-    padding: 0 14px; }
+    padding: 6px 14px;
+    white-space: pre-wrap; }
     .trailheadCard .onboardingButton:focus, .trailheadCard .onboardingButton:hover {
       box-shadow: none;
       background: var(--trailhead-card-button-background-hover-color); }
     .trailheadCard .onboardingButton:focus {
       outline: dotted 1px; }
     .trailheadCard .onboardingButton:active {
       background: var(--trailhead-card-button-background-active-color); }
   .trailheadCard .onboardingButtonContainer {
-    height: 60px;
     position: absolute;
-    bottom: 0;
+    bottom: 16px;
     left: 0;
     width: 100%;
     text-align: center; }
 
 .inline-onboarding.activity-stream.welcome {
   overflow-y: scroll; }
 
 .inline-onboarding .modalOverlayInner {
--- a/browser/components/newtab/css/activity-stream-mac.css
+++ b/browser/components/newtab/css/activity-stream-mac.css
@@ -135,22 +135,26 @@ body {
   fill: var(--newtab-icon-primary-color);
   height: 16px;
   vertical-align: middle;
   width: 16px; }
   .icon.icon-spacer {
     margin-inline-end: 8px; }
   .icon.icon-small-spacer {
     margin-inline-end: 6px; }
+  .icon.icon-button-style {
+    fill: var(--newtab-icon-secondary-color);
+    border: 0; }
+    .icon.icon-button-style:focus, .icon.icon-button-style:hover {
+      fill: var(--newtab-text-primary-color); }
   .icon.icon-bookmark-added {
     background-image: url("chrome://browser/skin/bookmark.svg"); }
   .icon.icon-bookmark-hollow {
     background-image: url("chrome://browser/skin/bookmark-hollow.svg"); }
   .icon.icon-clear-input {
-    fill: var(--newtab-icon-secondary-color);
     background-image: url("../data/content/assets/glyph-cancel-16.svg"); }
   .icon.icon-delete {
     background-image: url("../data/content/assets/glyph-delete-16.svg"); }
   .icon.icon-search {
     background-image: url("chrome://browser/skin/search-glass.svg"); }
   .icon.icon-modal-delete {
     flex-shrink: 0;
     background-image: url("../data/content/assets/glyph-modal-delete-32.svg");
@@ -314,18 +318,19 @@ a {
   background-color: var(--newtab-button-secondary-color);
   border: 1px solid var(--newtab-border-primary-color);
   border-radius: 4px;
   color: inherit;
   cursor: pointer;
   margin-bottom: 15px;
   padding: 10px 30px;
   white-space: nowrap; }
-  .button:hover:not(.dismiss),
-  .actions button:hover:not(.dismiss) {
+  .button:hover:not(.dismiss), .button:focus:not(.dismiss),
+  .actions button:hover:not(.dismiss),
+  .actions button:focus:not(.dismiss) {
     box-shadow: 0 0 0 5px var(--newtab-card-active-outline-color);
     transition: box-shadow 150ms; }
   .button.dismiss,
   .actions button.dismiss {
     background-color: transparent;
     border: 0;
     padding: 0;
     text-decoration: underline; }
@@ -335,16 +340,31 @@ a {
     background-color: var(--newtab-button-primary-color);
     border: solid 1px var(--newtab-button-primary-color);
     color: #FFF;
     margin-inline-start: auto; }
 
 input[type='text'], input[type='search'] {
   border-radius: 3px; }
 
+.clamp {
+  -webkit-box-orient: vertical;
+  display: -webkit-box;
+  overflow: hidden;
+  word-break: break-word; }
+
+.a11y-link-button {
+  border: 0;
+  padding: 0;
+  cursor: pointer;
+  text-align: unset;
+  color: var(--newtab-link-primary-color); }
+  .a11y-link-button:hover, .a11y-link-button:focus {
+    text-decoration: underline; }
+
 .outer-wrapper {
   color: var(--newtab-text-primary-color);
   display: flex;
   flex-grow: 1;
   min-height: 100vh;
   padding: 30px 32px 32px; }
   .outer-wrapper.fixed-to-top {
     display: block; }
@@ -720,18 +740,16 @@ main {
     .topsite-form .form-wrapper .url input:dir(rtl):not(:placeholder-shown) {
       direction: ltr;
       text-align: right; }
   .topsite-form .form-wrapper .enable-custom-image-input {
     display: inline-block;
     font-size: 13px;
     margin-top: 4px;
     cursor: pointer; }
-    .topsite-form .form-wrapper .enable-custom-image-input:hover {
-      text-decoration: underline; }
   .topsite-form .form-wrapper .custom-image-input-container {
     margin-top: 4px; }
     .topsite-form .form-wrapper .custom-image-input-container .loading-container {
       width: 16px;
       height: 16px;
       overflow: hidden;
       position: absolute;
       transform: translateY(-50%);
@@ -1895,31 +1913,25 @@ main {
   grid-gap: 24px;
   margin: 16px 0; }
   .ds-card-grid .ds-card {
     background: #FFF;
     border-radius: 4px; }
     [lwt-newtab-brighttext] .ds-card-grid .ds-card {
       background: none; }
   .ds-card-grid .ds-card-link:focus {
-    box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
+    box-shadow: 0 0 0 5px rgba(10, 132, 255, 0.3);
     transition: box-shadow 150ms;
     border-radius: 4px;
     outline: none; }
   .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) {
     box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); }
     [lwt-newtab-brighttext] .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) {
       box-shadow: 0 1px 4px rgba(12, 12, 13, 0.1);
       background: #38383D; }
-    .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder):hover {
-      box-shadow: 0 0 0 5px #D7D7DB;
-      transition: box-shadow 150ms;
-      outline: none; }
-      [lwt-newtab-brighttext] .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder):hover {
-        box-shadow: 0 0 0 5px #4A4A4F; }
     .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img {
       border-radius: 4px 4px 0 0; }
   .ds-card-grid.ds-card-grid-no-border .ds-card {
     background: none; }
     .ds-card-grid.ds-card-grid-no-border .ds-card .meta {
       padding: 12px 0; }
   .ds-column-5 .ds-card-grid,
   .ds-column-6 .ds-card-grid,
@@ -1941,41 +1953,31 @@ main {
       .ds-column-11 .ds-card-grid.ds-card-grid-divisible-by-3 .title,
       .ds-column-12 .ds-card-grid.ds-card-grid-divisible-by-3 .title {
         font-size: 17px;
         line-height: 24px; }
     .ds-column-9 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
     .ds-column-10 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
     .ds-column-11 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
     .ds-column-12 .ds-card-grid.ds-card-grid-divisible-by-4 .title {
-      -webkit-box-orient: vertical;
-      display: -webkit-box;
       font-size: 15px;
-      -webkit-line-clamp: 3;
-      line-height: 20px;
-      max-height: 4em;
-      overflow: hidden; }
+      line-height: 20px; }
   .ds-card-grid.empty {
     grid-template-columns: auto; }
 
 .ds-hero {
   position: relative; }
   .ds-hero header {
     font-weight: 600; }
   .ds-hero p {
     line-height: 1.538;
     margin: 8px 0; }
   .ds-hero .excerpt {
-    -webkit-box-orient: vertical;
-    display: -webkit-box;
     font-size: 14px;
-    -webkit-line-clamp: 3;
     line-height: 20px;
-    max-height: 4.28571em;
-    overflow: hidden;
     color: #0C0C0D;
     margin: 0 0 10px; }
     [lwt-newtab-brighttext] .ds-hero .excerpt {
       color: #F9F9FA; }
   .ds-hero .ds-card:not(.placeholder) {
     border: 0;
     padding-bottom: 20px; }
     .ds-hero .ds-card:not(.placeholder) p {
@@ -2002,17 +2004,17 @@ main {
     margin: 12px 0 16px;
     padding-top: 16px;
     height: 100%; }
     [lwt-newtab-brighttext] .ds-hero .wrapper {
       border-top: 1px solid #4A4A4F; }
     [lwt-newtab-brighttext] .ds-hero .wrapper {
       color: #D7D7DB; }
     .ds-hero .wrapper:focus {
-      box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
+      box-shadow: 0 0 0 5px rgba(10, 132, 255, 0.3);
       transition: box-shadow 150ms;
       border-radius: 4px;
       outline: none; }
     .ds-hero-no-border .ds-hero-item .wrapper {
       border-top: 0;
       border-bottom: 0;
       padding: 0 0 8px; }
     .ds-hero .wrapper:hover .meta header {
@@ -2031,40 +2033,33 @@ main {
       .ds-hero .wrapper .img img {
         border-radius: 4px;
         box-shadow: inset 0 0 0 0.5px rgba(0, 0, 0, 0.15); }
     .ds-hero .wrapper .meta {
       display: block;
       flex-direction: column;
       justify-content: space-between; }
       .ds-hero .wrapper .meta header {
-        -webkit-box-orient: vertical;
-        display: -webkit-box;
         font-size: 22px;
-        -webkit-line-clamp: 4;
         line-height: 28px;
-        max-height: 5.09091em;
-        overflow: hidden;
         color: #0C0C0D;
         margin-bottom: 0; }
         [lwt-newtab-brighttext] .ds-hero .wrapper .meta header {
           color: #FFF; }
       .ds-hero .wrapper .meta .context,
       .ds-hero .wrapper .meta .source {
         margin: 0 0 4px; }
       .ds-hero .wrapper .meta .context {
         color: #008EA4; }
         [lwt-newtab-brighttext] .ds-hero .wrapper .meta .context {
           color: #A7FFFE; }
       .ds-hero .wrapper .meta .source {
         font-size: 13px;
         color: #737373;
-        margin-bottom: 0;
-        overflow-x: hidden;
-        text-overflow: ellipsis; }
+        margin-bottom: 0; }
         [lwt-newtab-brighttext] .ds-hero .wrapper .meta .source {
           color: #B1B1B3; }
   .ds-column-5 .ds-hero .wrapper,
   .ds-column-6 .ds-hero .wrapper,
   .ds-column-7 .ds-hero .wrapper,
   .ds-column-8 .ds-hero .wrapper {
     display: grid;
     grid-template-columns: repeat(2, 1fr);
@@ -2154,23 +2149,18 @@ main {
       .ds-column-12 .ds-hero .wrapper .meta {
         flex-grow: 1;
         display: flex;
         padding: 0 24px 0 0; }
         .ds-column-9 .ds-hero .wrapper .meta header,
         .ds-column-10 .ds-hero .wrapper .meta header,
         .ds-column-11 .ds-hero .wrapper .meta header,
         .ds-column-12 .ds-hero .wrapper .meta header {
-          -webkit-box-orient: vertical;
-          display: -webkit-box;
           font-size: 22px;
-          -webkit-line-clamp: 3;
-          line-height: 28px;
-          max-height: 3.81818em;
-          overflow: hidden; }
+          line-height: 28px; }
         .ds-column-9 .ds-hero .wrapper .meta .source,
         .ds-column-10 .ds-hero .wrapper .meta .source,
         .ds-column-11 .ds-hero .wrapper .meta .source,
         .ds-column-12 .ds-hero .wrapper .meta .source {
           margin-bottom: 0; }
     .ds-column-9 .ds-hero .cards,
     .ds-column-10 .ds-hero .cards,
     .ds-column-11 .ds-hero .cards,
@@ -2193,23 +2183,18 @@ main {
       .ds-column-10 .ds-hero .cards .ds-card:active .title, [lwt-newtab-brighttext]
       .ds-column-11 .ds-hero .cards .ds-card:active .title, [lwt-newtab-brighttext]
       .ds-column-12 .ds-hero .cards .ds-card:active .title {
         color: #0A84FF; }
       .ds-column-9 .ds-hero .cards .ds-card .title,
       .ds-column-10 .ds-hero .cards .ds-card .title,
       .ds-column-11 .ds-hero .cards .ds-card .title,
       .ds-column-12 .ds-hero .cards .ds-card .title {
-        -webkit-box-orient: vertical;
-        display: -webkit-box;
         font-size: 14px;
-        -webkit-line-clamp: 3;
-        line-height: 20px;
-        max-height: 4.28571em;
-        overflow: hidden; }
+        line-height: 20px; }
         [lwt-newtab-brighttext] .ds-column-9 .ds-hero .cards .ds-card .title, [lwt-newtab-brighttext]
         .ds-column-10 .ds-hero .cards .ds-card .title, [lwt-newtab-brighttext]
         .ds-column-11 .ds-hero .cards .ds-card .title, [lwt-newtab-brighttext]
         .ds-column-12 .ds-hero .cards .ds-card .title {
           color: #FFF; }
   .ds-hero.empty {
     grid-template-columns: auto; }
 
@@ -2225,23 +2210,18 @@ main {
   grid-row-gap: 24px;
   grid-column-gap: 24px;
   padding-inline-start: 0; }
   .ds-list:not(.ds-list-full-width) .ds-list-item {
     font-size: 14px;
     line-height: 20px;
     position: relative; }
   .ds-list:not(.ds-list-full-width) .ds-list-item-title {
-    -webkit-box-orient: vertical;
-    display: -webkit-box;
     font-size: 14px;
-    -webkit-line-clamp: 3;
-    line-height: 20px;
-    max-height: 4.28571em;
-    overflow: hidden; }
+    line-height: 20px; }
   .ds-list:not(.ds-list-full-width) .ds-list-image {
     min-width: 80px;
     width: 80px; }
   .ds-column-5 .ds-list:not(.ds-list-full-width),
   .ds-column-6 .ds-list:not(.ds-list-full-width),
   .ds-column-7 .ds-list:not(.ds-list-full-width),
   .ds-column-8 .ds-list:not(.ds-list-full-width) {
     grid-template-columns: repeat(2, 1fr); }
@@ -2257,17 +2237,17 @@ main {
   .ds-list:not(.ds-list-images) .ds-list-image {
     display: none; }
   .ds-list a {
     color: #0C0C0D; }
     [lwt-newtab-brighttext] .ds-list a {
       color: #F9F9FA; }
 
 .ds-list-item-link:focus {
-  box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
+  box-shadow: 0 0 0 5px rgba(10, 132, 255, 0.3);
   transition: box-shadow 150ms;
   border-radius: 4px;
   outline: none; }
 
 .ds-list-numbers .ds-list-item {
   counter-increment: list; }
 
 .ds-list-numbers .ds-list-item:not(.placeholder) > .ds-list-item-link {
@@ -2342,23 +2322,18 @@ main {
       border-bottom: 1px solid #4A4A4F; }
 
 .ds-list-full-width .ds-list-item {
   font-size: 17px;
   line-height: 24px;
   position: relative; }
 
 .ds-list-full-width .ds-list-item-title {
-  -webkit-box-orient: vertical;
-  display: -webkit-box;
   font-size: 17px;
-  -webkit-line-clamp: 3;
-  line-height: 24px;
-  max-height: 4.23529em;
-  overflow: hidden; }
+  line-height: 24px; }
 
 .ds-list-full-width .ds-list-image {
   min-width: 160px;
   width: 160px; }
 
 .ds-list-item {
   display: block;
   text-align: start; }
@@ -2372,41 +2347,33 @@ main {
     .ds-list-item.placeholder .ds-list-image {
       opacity: 0; }
   .ds-list-item .ds-list-item-link {
     mix-blend-mode: normal;
     display: flex;
     justify-content: space-between;
     height: 100%; }
   .ds-list-item .ds-list-item-excerpt {
-    -webkit-box-orient: vertical;
-    display: -webkit-box;
     font-size: 14px;
-    -webkit-line-clamp: 2;
     line-height: 20px;
-    max-height: 2.85714em;
-    overflow: hidden;
-    color: rgba(249, 249, 250, 0.8);
+    color: #737373;
     margin: 4px 0 8px; }
+    [lwt-newtab-brighttext] .ds-list-item .ds-list-item-excerpt {
+      color: rgba(249, 249, 250, 0.8); }
   .ds-list-item p {
     font-size: 14px;
     line-height: 20px;
     margin: 0; }
   .ds-list-item .ds-list-item-info,
   .ds-list-item .ds-list-item-context {
-    -webkit-box-orient: vertical;
-    display: -webkit-box;
     font-size: 14px;
-    -webkit-line-clamp: 1;
     line-height: 20px;
-    max-height: 1.42857em;
-    overflow: hidden;
     color: #737373;
     font-size: 13px;
-    text-overflow: ellipsis; }
+    -webkit-line-clamp: 1; }
     [lwt-newtab-brighttext] .ds-list-item .ds-list-item-info, [lwt-newtab-brighttext]
     .ds-list-item .ds-list-item-context {
       color: #B1B1B3; }
   .ds-list-item .ds-list-item-title {
     font-weight: 600;
     margin-bottom: 4px; }
   .ds-list-item .ds-list-item-text {
     display: flex;
@@ -2471,17 +2438,17 @@ main {
     [lwt-newtab-brighttext] .ds-section-title .subtitle {
       color: #D7D7DB; }
 
 .ds-top-sites .top-sites {
   margin: 0 -25px; }
   .ds-top-sites .top-sites .top-site-outer {
     padding: 0 12px; }
     .ds-top-sites .top-sites .top-site-outer .top-site-inner > a:-moz-any(.active, :focus) .tile {
-      box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
+      box-shadow: 0 0 0 5px rgba(10, 132, 255, 0.3);
       transition: box-shadow 150ms;
       border-radius: 4px;
       outline: none; }
   .ds-top-sites .top-sites .top-sites-list {
     margin: 0 -12px; }
 
 .ds-top-sites .hide-for-narrow {
   display: none; }
@@ -2623,67 +2590,72 @@ main {
   .ds-card.placeholder {
     background: transparent;
     box-shadow: inset 0 0 0 1px var(--newtab-inner-box-shadow-color);
     border-radius: 4px; }
     .ds-card.placeholder .ds-card-link {
       cursor: default; }
     .ds-card.placeholder .img-wrapper {
       opacity: 0; }
-  .ds-card:hover header {
-    color: #0060DF; }
-    [lwt-newtab-brighttext] .ds-card:hover header {
-      color: #45A1FF; }
-  .ds-card:active header {
-    color: #003EAA; }
-    [lwt-newtab-brighttext] .ds-card:active header {
-      color: #0A84FF; }
   .ds-card .img-wrapper {
     width: 100%; }
   .ds-card .img {
     height: 0;
     padding-top: 50%; }
     .ds-card .img img {
       border-radius: 4px;
       box-shadow: inset 0 0 0 0.5px rgba(0, 0, 0, 0.15); }
   .ds-card .ds-card-link {
     display: flex;
     flex-direction: column;
     justify-content: space-between;
     height: 100%; }
     .ds-card .ds-card-link:focus {
-      box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
+      box-shadow: 0 0 0 5px rgba(10, 132, 255, 0.3);
+      transition: box-shadow 150ms;
+      border-radius: 4px;
+      outline: none; }
+      [lwt-newtab-brighttext] .ds-card .ds-card-link:focus {
+        box-shadow: 0 0 0 5px rgba(69, 161, 255, 0.4);
+        transition: box-shadow 150ms;
+        border-radius: 4px;
+        outline: none; }
+    .ds-card .ds-card-link:hover {
+      box-shadow: 0 0 0 5px #D7D7DB;
       transition: box-shadow 150ms;
       border-radius: 4px;
       outline: none; }
+      [lwt-newtab-brighttext] .ds-card .ds-card-link:hover {
+        box-shadow: 0 0 0 5px #4A4A4F;
+        transition: box-shadow 150ms;
+        border-radius: 4px;
+        outline: none; }
+    .ds-card .ds-card-link:focus header, .ds-card .ds-card-link:hover header {
+      color: #0060DF; }
+      [lwt-newtab-brighttext] .ds-card .ds-card-link:focus header, [lwt-newtab-brighttext] .ds-card .ds-card-link:hover header {
+        color: #45A1FF; }
+    .ds-card .ds-card-link:active header {
+      color: #003EAA; }
+      [lwt-newtab-brighttext] .ds-card .ds-card-link:active header {
+        color: #0A84FF; }
   .ds-card .meta {
     display: flex;
     flex-direction: column;
     flex-grow: 1;
     padding: 12px; }
     .ds-card .meta .info-wrap {
       flex-grow: 1;
       margin: 0 0 12px; }
     .ds-card .meta .title {
-      -webkit-box-orient: vertical;
-      display: -webkit-box;
       font-size: 17px;
-      -webkit-line-clamp: 3;
       line-height: 24px;
-      max-height: 4.23529em;
-      overflow: hidden;
       font-weight: 600; }
     .ds-card .meta .excerpt {
-      -webkit-box-orient: vertical;
-      display: -webkit-box;
       font-size: 14px;
-      -webkit-line-clamp: 3;
-      line-height: 20px;
-      max-height: 4.28571em;
-      overflow: hidden; }
+      line-height: 20px; }
     .ds-card .meta .source {
       margin-bottom: 2px; }
     .ds-card .meta .context,
     .ds-card .meta .source {
       font-size: 13px;
       color: #737373; }
       [lwt-newtab-brighttext] .ds-card .meta .context, [lwt-newtab-brighttext]
       .ds-card .meta .source {
@@ -2882,16 +2854,26 @@ main {
   background: rgba(215, 215, 219, 0.6);
   text-align: center;
   position: absolute;
   top: 0;
   width: 100%; }
   .snippets-preview-banner span {
     vertical-align: middle; }
 
+body:not([lwt-newtab-brighttext]) .icon-dark-theme,
+body:not([lwt-newtab-brighttext]) .icon.icon-dark-theme,
+body:not([lwt-newtab-brighttext]) .scene2Icon .icon-dark-theme {
+  display: none; }
+
+body[lwt-newtab-brighttext] .icon-light-theme,
+body[lwt-newtab-brighttext] .icon.icon-light-theme,
+body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
+  display: none; }
+
 .activity-stream.modal-open {
   overflow: hidden; }
 
 .modalOverlayOuter {
   background: var(--newtab-overlay-color);
   height: 100%;
   position: fixed;
   top: 0;
@@ -3985,30 +3967,30 @@ a.firstrun-link {
     margin: 0 0 60px;
     color: var(--newtab-text-conditional-color);
     line-height: 1.5;
     font-weight: 200; }
   .trailheadCard .onboardingButton {
     color: var(--newtab-text-conditional-color);
     background: var(--trailhead-card-button-background-color);
     border: 0;
-    height: 30px;
+    margin: 14px;
     min-width: 70%;
-    padding: 0 14px; }
+    padding: 6px 14px;
+    white-space: pre-wrap; }
     .trailheadCard .onboardingButton:focus, .trailheadCard .onboardingButton:hover {
       box-shadow: none;
       background: var(--trailhead-card-button-background-hover-color); }
     .trailheadCard .onboardingButton:focus {
       outline: dotted 1px; }
     .trailheadCard .onboardingButton:active {
       background: var(--trailhead-card-button-background-active-color); }
   .trailheadCard .onboardingButtonContainer {
-    height: 60px;
     position: absolute;
-    bottom: 0;
+    bottom: 16px;
     left: 0;
     width: 100%;
     text-align: center; }
 
 .inline-onboarding.activity-stream.welcome {
   overflow-y: scroll; }
 
 .inline-onboarding .modalOverlayInner {
--- a/browser/components/newtab/css/activity-stream-windows.css
+++ b/browser/components/newtab/css/activity-stream-windows.css
@@ -132,22 +132,26 @@ body {
   fill: var(--newtab-icon-primary-color);
   height: 16px;
   vertical-align: middle;
   width: 16px; }
   .icon.icon-spacer {
     margin-inline-end: 8px; }
   .icon.icon-small-spacer {
     margin-inline-end: 6px; }
+  .icon.icon-button-style {
+    fill: var(--newtab-icon-secondary-color);
+    border: 0; }
+    .icon.icon-button-style:focus, .icon.icon-button-style:hover {
+      fill: var(--newtab-text-primary-color); }
   .icon.icon-bookmark-added {
     background-image: url("chrome://browser/skin/bookmark.svg"); }
   .icon.icon-bookmark-hollow {
     background-image: url("chrome://browser/skin/bookmark-hollow.svg"); }
   .icon.icon-clear-input {
-    fill: var(--newtab-icon-secondary-color);
     background-image: url("../data/content/assets/glyph-cancel-16.svg"); }
   .icon.icon-delete {
     background-image: url("../data/content/assets/glyph-delete-16.svg"); }
   .icon.icon-search {
     background-image: url("chrome://browser/skin/search-glass.svg"); }
   .icon.icon-modal-delete {
     flex-shrink: 0;
     background-image: url("../data/content/assets/glyph-modal-delete-32.svg");
@@ -311,18 +315,19 @@ a {
   background-color: var(--newtab-button-secondary-color);
   border: 1px solid var(--newtab-border-primary-color);
   border-radius: 4px;
   color: inherit;
   cursor: pointer;
   margin-bottom: 15px;
   padding: 10px 30px;
   white-space: nowrap; }
-  .button:hover:not(.dismiss),
-  .actions button:hover:not(.dismiss) {
+  .button:hover:not(.dismiss), .button:focus:not(.dismiss),
+  .actions button:hover:not(.dismiss),
+  .actions button:focus:not(.dismiss) {
     box-shadow: 0 0 0 5px var(--newtab-card-active-outline-color);
     transition: box-shadow 150ms; }
   .button.dismiss,
   .actions button.dismiss {
     background-color: transparent;
     border: 0;
     padding: 0;
     text-decoration: underline; }
@@ -332,16 +337,31 @@ a {
     background-color: var(--newtab-button-primary-color);
     border: solid 1px var(--newtab-button-primary-color);
     color: #FFF;
     margin-inline-start: auto; }
 
 input[type='text'], input[type='search'] {
   border-radius: 3px; }
 
+.clamp {
+  -webkit-box-orient: vertical;
+  display: -webkit-box;
+  overflow: hidden;
+  word-break: break-word; }
+
+.a11y-link-button {
+  border: 0;
+  padding: 0;
+  cursor: pointer;
+  text-align: unset;
+  color: var(--newtab-link-primary-color); }
+  .a11y-link-button:hover, .a11y-link-button:focus {
+    text-decoration: underline; }
+
 .outer-wrapper {
   color: var(--newtab-text-primary-color);
   display: flex;
   flex-grow: 1;
   min-height: 100vh;
   padding: 30px 32px 32px; }
   .outer-wrapper.fixed-to-top {
     display: block; }
@@ -717,18 +737,16 @@ main {
     .topsite-form .form-wrapper .url input:dir(rtl):not(:placeholder-shown) {
       direction: ltr;
       text-align: right; }
   .topsite-form .form-wrapper .enable-custom-image-input {
     display: inline-block;
     font-size: 13px;
     margin-top: 4px;
     cursor: pointer; }
-    .topsite-form .form-wrapper .enable-custom-image-input:hover {
-      text-decoration: underline; }
   .topsite-form .form-wrapper .custom-image-input-container {
     margin-top: 4px; }
     .topsite-form .form-wrapper .custom-image-input-container .loading-container {
       width: 16px;
       height: 16px;
       overflow: hidden;
       position: absolute;
       transform: translateY(-50%);
@@ -1892,31 +1910,25 @@ main {
   grid-gap: 24px;
   margin: 16px 0; }
   .ds-card-grid .ds-card {
     background: #FFF;
     border-radius: 4px; }
     [lwt-newtab-brighttext] .ds-card-grid .ds-card {
       background: none; }
   .ds-card-grid .ds-card-link:focus {
-    box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
+    box-shadow: 0 0 0 5px rgba(10, 132, 255, 0.3);
     transition: box-shadow 150ms;
     border-radius: 4px;
     outline: none; }
   .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) {
     box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1); }
     [lwt-newtab-brighttext] .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) {
       box-shadow: 0 1px 4px rgba(12, 12, 13, 0.1);
       background: #38383D; }
-    .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder):hover {
-      box-shadow: 0 0 0 5px #D7D7DB;
-      transition: box-shadow 150ms;
-      outline: none; }
-      [lwt-newtab-brighttext] .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder):hover {
-        box-shadow: 0 0 0 5px #4A4A4F; }
     .ds-card-grid.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img {
       border-radius: 4px 4px 0 0; }
   .ds-card-grid.ds-card-grid-no-border .ds-card {
     background: none; }
     .ds-card-grid.ds-card-grid-no-border .ds-card .meta {
       padding: 12px 0; }
   .ds-column-5 .ds-card-grid,
   .ds-column-6 .ds-card-grid,
@@ -1938,41 +1950,31 @@ main {
       .ds-column-11 .ds-card-grid.ds-card-grid-divisible-by-3 .title,
       .ds-column-12 .ds-card-grid.ds-card-grid-divisible-by-3 .title {
         font-size: 17px;
         line-height: 24px; }
     .ds-column-9 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
     .ds-column-10 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
     .ds-column-11 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
     .ds-column-12 .ds-card-grid.ds-card-grid-divisible-by-4 .title {
-      -webkit-box-orient: vertical;
-      display: -webkit-box;
       font-size: 15px;
-      -webkit-line-clamp: 3;
-      line-height: 20px;
-      max-height: 4em;
-      overflow: hidden; }
+      line-height: 20px; }
   .ds-card-grid.empty {
     grid-template-columns: auto; }
 
 .ds-hero {
   position: relative; }
   .ds-hero header {
     font-weight: 600; }
   .ds-hero p {
     line-height: 1.538;
     margin: 8px 0; }
   .ds-hero .excerpt {
-    -webkit-box-orient: vertical;
-    display: -webkit-box;
     font-size: 14px;
-    -webkit-line-clamp: 3;
     line-height: 20px;
-    max-height: 4.28571em;
-    overflow: hidden;
     color: #0C0C0D;
     margin: 0 0 10px; }
     [lwt-newtab-brighttext] .ds-hero .excerpt {
       color: #F9F9FA; }
   .ds-hero .ds-card:not(.placeholder) {
     border: 0;
     padding-bottom: 20px; }
     .ds-hero .ds-card:not(.placeholder) p {
@@ -1999,17 +2001,17 @@ main {
     margin: 12px 0 16px;
     padding-top: 16px;
     height: 100%; }
     [lwt-newtab-brighttext] .ds-hero .wrapper {
       border-top: 1px solid #4A4A4F; }
     [lwt-newtab-brighttext] .ds-hero .wrapper {
       color: #D7D7DB; }
     .ds-hero .wrapper:focus {
-      box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
+      box-shadow: 0 0 0 5px rgba(10, 132, 255, 0.3);
       transition: box-shadow 150ms;
       border-radius: 4px;
       outline: none; }
     .ds-hero-no-border .ds-hero-item .wrapper {
       border-top: 0;
       border-bottom: 0;
       padding: 0 0 8px; }
     .ds-hero .wrapper:hover .meta header {
@@ -2028,40 +2030,33 @@ main {
       .ds-hero .wrapper .img img {
         border-radius: 4px;
         box-shadow: inset 0 0 0 0.5px rgba(0, 0, 0, 0.15); }
     .ds-hero .wrapper .meta {
       display: block;
       flex-direction: column;
       justify-content: space-between; }
       .ds-hero .wrapper .meta header {
-        -webkit-box-orient: vertical;
-        display: -webkit-box;
         font-size: 22px;
-        -webkit-line-clamp: 4;
         line-height: 28px;
-        max-height: 5.09091em;
-        overflow: hidden;
         color: #0C0C0D;
         margin-bottom: 0; }
         [lwt-newtab-brighttext] .ds-hero .wrapper .meta header {
           color: #FFF; }
       .ds-hero .wrapper .meta .context,
       .ds-hero .wrapper .meta .source {
         margin: 0 0 4px; }
       .ds-hero .wrapper .meta .context {
         color: #008EA4; }
         [lwt-newtab-brighttext] .ds-hero .wrapper .meta .context {
           color: #A7FFFE; }
       .ds-hero .wrapper .meta .source {
         font-size: 13px;
         color: #737373;
-        margin-bottom: 0;
-        overflow-x: hidden;
-        text-overflow: ellipsis; }
+        margin-bottom: 0; }
         [lwt-newtab-brighttext] .ds-hero .wrapper .meta .source {
           color: #B1B1B3; }
   .ds-column-5 .ds-hero .wrapper,
   .ds-column-6 .ds-hero .wrapper,
   .ds-column-7 .ds-hero .wrapper,
   .ds-column-8 .ds-hero .wrapper {
     display: grid;
     grid-template-columns: repeat(2, 1fr);
@@ -2151,23 +2146,18 @@ main {
       .ds-column-12 .ds-hero .wrapper .meta {
         flex-grow: 1;
         display: flex;
         padding: 0 24px 0 0; }
         .ds-column-9 .ds-hero .wrapper .meta header,
         .ds-column-10 .ds-hero .wrapper .meta header,
         .ds-column-11 .ds-hero .wrapper .meta header,
         .ds-column-12 .ds-hero .wrapper .meta header {
-          -webkit-box-orient: vertical;
-          display: -webkit-box;
           font-size: 22px;
-          -webkit-line-clamp: 3;
-          line-height: 28px;
-          max-height: 3.81818em;
-          overflow: hidden; }
+          line-height: 28px; }
         .ds-column-9 .ds-hero .wrapper .meta .source,
         .ds-column-10 .ds-hero .wrapper .meta .source,
         .ds-column-11 .ds-hero .wrapper .meta .source,
         .ds-column-12 .ds-hero .wrapper .meta .source {
           margin-bottom: 0; }
     .ds-column-9 .ds-hero .cards,
     .ds-column-10 .ds-hero .cards,
     .ds-column-11 .ds-hero .cards,
@@ -2190,23 +2180,18 @@ main {
       .ds-column-10 .ds-hero .cards .ds-card:active .title, [lwt-newtab-brighttext]
       .ds-column-11 .ds-hero .cards .ds-card:active .title, [lwt-newtab-brighttext]
       .ds-column-12 .ds-hero .cards .ds-card:active .title {
         color: #0A84FF; }
       .ds-column-9 .ds-hero .cards .ds-card .title,
       .ds-column-10 .ds-hero .cards .ds-card .title,
       .ds-column-11 .ds-hero .cards .ds-card .title,
       .ds-column-12 .ds-hero .cards .ds-card .title {
-        -webkit-box-orient: vertical;
-        display: -webkit-box;
         font-size: 14px;
-        -webkit-line-clamp: 3;
-        line-height: 20px;
-        max-height: 4.28571em;
-        overflow: hidden; }
+        line-height: 20px; }
         [lwt-newtab-brighttext] .ds-column-9 .ds-hero .cards .ds-card .title, [lwt-newtab-brighttext]
         .ds-column-10 .ds-hero .cards .ds-card .title, [lwt-newtab-brighttext]
         .ds-column-11 .ds-hero .cards .ds-card .title, [lwt-newtab-brighttext]
         .ds-column-12 .ds-hero .cards .ds-card .title {
           color: #FFF; }
   .ds-hero.empty {
     grid-template-columns: auto; }
 
@@ -2222,23 +2207,18 @@ main {
   grid-row-gap: 24px;
   grid-column-gap: 24px;
   padding-inline-start: 0; }
   .ds-list:not(.ds-list-full-width) .ds-list-item {
     font-size: 14px;
     line-height: 20px;
     position: relative; }
   .ds-list:not(.ds-list-full-width) .ds-list-item-title {
-    -webkit-box-orient: vertical;
-    display: -webkit-box;
     font-size: 14px;
-    -webkit-line-clamp: 3;
-    line-height: 20px;
-    max-height: 4.28571em;
-    overflow: hidden; }
+    line-height: 20px; }
   .ds-list:not(.ds-list-full-width) .ds-list-image {
     min-width: 80px;
     width: 80px; }
   .ds-column-5 .ds-list:not(.ds-list-full-width),
   .ds-column-6 .ds-list:not(.ds-list-full-width),
   .ds-column-7 .ds-list:not(.ds-list-full-width),
   .ds-column-8 .ds-list:not(.ds-list-full-width) {
     grid-template-columns: repeat(2, 1fr); }
@@ -2254,17 +2234,17 @@ main {
   .ds-list:not(.ds-list-images) .ds-list-image {
     display: none; }
   .ds-list a {
     color: #0C0C0D; }
     [lwt-newtab-brighttext] .ds-list a {
       color: #F9F9FA; }
 
 .ds-list-item-link:focus {
-  box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
+  box-shadow: 0 0 0 5px rgba(10, 132, 255, 0.3);
   transition: box-shadow 150ms;
   border-radius: 4px;
   outline: none; }
 
 .ds-list-numbers .ds-list-item {
   counter-increment: list; }
 
 .ds-list-numbers .ds-list-item:not(.placeholder) > .ds-list-item-link {
@@ -2339,23 +2319,18 @@ main {
       border-bottom: 1px solid #4A4A4F; }
 
 .ds-list-full-width .ds-list-item {
   font-size: 17px;
   line-height: 24px;
   position: relative; }
 
 .ds-list-full-width .ds-list-item-title {
-  -webkit-box-orient: vertical;
-  display: -webkit-box;
   font-size: 17px;
-  -webkit-line-clamp: 3;
-  line-height: 24px;
-  max-height: 4.23529em;
-  overflow: hidden; }
+  line-height: 24px; }
 
 .ds-list-full-width .ds-list-image {
   min-width: 160px;
   width: 160px; }
 
 .ds-list-item {
   display: block;
   text-align: start; }
@@ -2369,41 +2344,33 @@ main {
     .ds-list-item.placeholder .ds-list-image {
       opacity: 0; }
   .ds-list-item .ds-list-item-link {
     mix-blend-mode: normal;
     display: flex;
     justify-content: space-between;
     height: 100%; }
   .ds-list-item .ds-list-item-excerpt {
-    -webkit-box-orient: vertical;
-    display: -webkit-box;
     font-size: 14px;
-    -webkit-line-clamp: 2;
     line-height: 20px;
-    max-height: 2.85714em;
-    overflow: hidden;
-    color: rgba(249, 249, 250, 0.8);
+    color: #737373;
     margin: 4px 0 8px; }
+    [lwt-newtab-brighttext] .ds-list-item .ds-list-item-excerpt {
+      color: rgba(249, 249, 250, 0.8); }
   .ds-list-item p {
     font-size: 14px;
     line-height: 20px;
     margin: 0; }
   .ds-list-item .ds-list-item-info,
   .ds-list-item .ds-list-item-context {
-    -webkit-box-orient: vertical;
-    display: -webkit-box;
     font-size: 14px;
-    -webkit-line-clamp: 1;
     line-height: 20px;
-    max-height: 1.42857em;
-    overflow: hidden;
     color: #737373;
     font-size: 13px;
-    text-overflow: ellipsis; }
+    -webkit-line-clamp: 1; }
     [lwt-newtab-brighttext] .ds-list-item .ds-list-item-info, [lwt-newtab-brighttext]
     .ds-list-item .ds-list-item-context {
       color: #B1B1B3; }
   .ds-list-item .ds-list-item-title {
     font-weight: 600;
     margin-bottom: 4px; }
   .ds-list-item .ds-list-item-text {
     display: flex;
@@ -2468,17 +2435,17 @@ main {
     [lwt-newtab-brighttext] .ds-section-title .subtitle {
       color: #D7D7DB; }
 
 .ds-top-sites .top-sites {
   margin: 0 -25px; }
   .ds-top-sites .top-sites .top-site-outer {
     padding: 0 12px; }
     .ds-top-sites .top-sites .top-site-outer .top-site-inner > a:-moz-any(.active, :focus) .tile {
-      box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
+      box-shadow: 0 0 0 5px rgba(10, 132, 255, 0.3);
       transition: box-shadow 150ms;
       border-radius: 4px;
       outline: none; }
   .ds-top-sites .top-sites .top-sites-list {
     margin: 0 -12px; }
 
 .ds-top-sites .hide-for-narrow {
   display: none; }
@@ -2620,67 +2587,72 @@ main {
   .ds-card.placeholder {
     background: transparent;
     box-shadow: inset 0 0 0 1px var(--newtab-inner-box-shadow-color);
     border-radius: 4px; }
     .ds-card.placeholder .ds-card-link {
       cursor: default; }
     .ds-card.placeholder .img-wrapper {
       opacity: 0; }
-  .ds-card:hover header {
-    color: #0060DF; }
-    [lwt-newtab-brighttext] .ds-card:hover header {
-      color: #45A1FF; }
-  .ds-card:active header {
-    color: #003EAA; }
-    [lwt-newtab-brighttext] .ds-card:active header {
-      color: #0A84FF; }
   .ds-card .img-wrapper {
     width: 100%; }
   .ds-card .img {
     height: 0;
     padding-top: 50%; }
     .ds-card .img img {
       border-radius: 4px;
       box-shadow: inset 0 0 0 0.5px rgba(0, 0, 0, 0.15); }
   .ds-card .ds-card-link {
     display: flex;
     flex-direction: column;
     justify-content: space-between;
     height: 100%; }
     .ds-card .ds-card-link:focus {
-      box-shadow: 0 0 0 1px #0A84FF inset, 0 0 0 1px #0A84FF, 0 0 0 5px rgba(10, 132, 255, 0.3);
+      box-shadow: 0 0 0 5px rgba(10, 132, 255, 0.3);
+      transition: box-shadow 150ms;
+      border-radius: 4px;
+      outline: none; }
+      [lwt-newtab-brighttext] .ds-card .ds-card-link:focus {
+        box-shadow: 0 0 0 5px rgba(69, 161, 255, 0.4);
+        transition: box-shadow 150ms;
+        border-radius: 4px;
+        outline: none; }
+    .ds-card .ds-card-link:hover {
+      box-shadow: 0 0 0 5px #D7D7DB;
       transition: box-shadow 150ms;
       border-radius: 4px;
       outline: none; }
+      [lwt-newtab-brighttext] .ds-card .ds-card-link:hover {
+        box-shadow: 0 0 0 5px #4A4A4F;
+        transition: box-shadow 150ms;
+        border-radius: 4px;
+        outline: none; }
+    .ds-card .ds-card-link:focus header, .ds-card .ds-card-link:hover header {
+      color: #0060DF; }
+      [lwt-newtab-brighttext] .ds-card .ds-card-link:focus header, [lwt-newtab-brighttext] .ds-card .ds-card-link:hover header {
+        color: #45A1FF; }
+    .ds-card .ds-card-link:active header {
+      color: #003EAA; }
+      [lwt-newtab-brighttext] .ds-card .ds-card-link:active header {
+        color: #0A84FF; }
   .ds-card .meta {
     display: flex;
     flex-direction: column;
     flex-grow: 1;
     padding: 12px; }
     .ds-card .meta .info-wrap {
       flex-grow: 1;
       margin: 0 0 12px; }
     .ds-card .meta .title {
-      -webkit-box-orient: vertical;
-      display: -webkit-box;
       font-size: 17px;
-      -webkit-line-clamp: 3;
       line-height: 24px;
-      max-height: 4.23529em;
-      overflow: hidden;
       font-weight: 600; }
     .ds-card .meta .excerpt {
-      -webkit-box-orient: vertical;
-      display: -webkit-box;
       font-size: 14px;
-      -webkit-line-clamp: 3;
-      line-height: 20px;
-      max-height: 4.28571em;
-      overflow: hidden; }
+      line-height: 20px; }
     .ds-card .meta .source {
       margin-bottom: 2px; }
     .ds-card .meta .context,
     .ds-card .meta .source {
       font-size: 13px;
       color: #737373; }
       [lwt-newtab-brighttext] .ds-card .meta .context, [lwt-newtab-brighttext]
       .ds-card .meta .source {
@@ -2879,16 +2851,26 @@ main {
   background: rgba(215, 215, 219, 0.6);
   text-align: center;
   position: absolute;
   top: 0;
   width: 100%; }
   .snippets-preview-banner span {
     vertical-align: middle; }
 
+body:not([lwt-newtab-brighttext]) .icon-dark-theme,
+body:not([lwt-newtab-brighttext]) .icon.icon-dark-theme,
+body:not([lwt-newtab-brighttext]) .scene2Icon .icon-dark-theme {
+  display: none; }
+
+body[lwt-newtab-brighttext] .icon-light-theme,
+body[lwt-newtab-brighttext] .icon.icon-light-theme,
+body[lwt-newtab-brighttext] .scene2Icon .icon-light-theme {
+  display: none; }
+
 .activity-stream.modal-open {
   overflow: hidden; }
 
 .modalOverlayOuter {
   background: var(--newtab-overlay-color);
   height: 100%;
   position: fixed;
   top: 0;
@@ -3982,30 +3964,30 @@ a.firstrun-link {
     margin: 0 0 60px;
     color: var(--newtab-text-conditional-color);
     line-height: 1.5;
     font-weight: 200; }
   .trailheadCard .onboardingButton {
     color: var(--newtab-text-conditional-color);
     background: var(--trailhead-card-button-background-color);
     border: 0;
-    height: 30px;
+    margin: 14px;
     min-width: 70%;
-    padding: 0 14px; }
+    padding: 6px 14px;
+    white-space: pre-wrap; }
     .trailheadCard .onboardingButton:focus, .trailheadCard .onboardingButton:hover {
       box-shadow: none;
       background: var(--trailhead-card-button-background-hover-color); }
     .trailheadCard .onboardingButton:focus {
       outline: dotted 1px; }
     .trailheadCard .onboardingButton:active {
       background: var(--trailhead-card-button-background-active-color); }
   .trailheadCard .onboardingButtonContainer {
-    height: 60px;
     position: absolute;
-    bottom: 0;
+    bottom: 16px;
     left: 0;
     width: 100%;
     text-align: center; }
 
 .inline-onboarding.activity-stream.welcome {
   overflow-y: scroll; }
 
 .inline-onboarding .modalOverlayInner {
--- a/browser/components/newtab/data/content/activity-stream.bundle.js
+++ b/browser/components/newtab/data/content/activity-stream.bundle.js
@@ -87,25 +87,25 @@
 /******/ ([
 /* 0 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* harmony import */ var content_src_components_Base_Base__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3);
-/* harmony import */ var content_src_lib_detect_user_session_start__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(51);
+/* harmony import */ var content_src_lib_detect_user_session_start__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(52);
 /* harmony import */ var content_src_lib_init_store__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(26);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_4__);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(11);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_5__);
 /* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(16);
 /* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_6__);
-/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(56);
+/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(58);
 
 
 
 
 
 
 
 
@@ -565,23 +565,23 @@ var actionUtils = {
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var content_src_components_ASRouterAdmin_ASRouterAdmin__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5);
 /* harmony import */ var _asrouter_asrouter_content__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6);
 /* harmony import */ var content_src_components_ConfirmDialog_ConfirmDialog__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(29);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(26);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_5__);
-/* harmony import */ var content_src_components_DiscoveryStreamBase_DiscoveryStreamBase__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(52);
-/* harmony import */ var content_src_components_ErrorBoundary_ErrorBoundary__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(35);
-/* harmony import */ var common_PrerenderData_jsm__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(45);
+/* harmony import */ var content_src_components_DiscoveryStreamBase_DiscoveryStreamBase__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(53);
+/* harmony import */ var content_src_components_ErrorBoundary_ErrorBoundary__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(36);
+/* harmony import */ var common_PrerenderData_jsm__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(46);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(11);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_9__);
-/* harmony import */ var content_src_components_Search_Search__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(46);
-/* harmony import */ var content_src_components_Sections_Sections__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(47);
+/* harmony import */ var content_src_components_Search_Search__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(47);
+/* harmony import */ var content_src_components_Sections_Sections__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(48);
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 
 
 
 
 
 
@@ -744,18 +744,24 @@ class BaseContent extends react__WEBPACK
     const {
       App
     } = props;
     const {
       initialized
     } = App;
     const prefs = props.Prefs.values;
     const shouldBeFixedToTop = common_PrerenderData_jsm__WEBPACK_IMPORTED_MODULE_8__["PrerenderData"].arePrefsValid(name => prefs[name]);
-    const noSectionsEnabled = !prefs["feeds.topsites"] && props.Sections.filter(section => section.enabled).length === 0;
     const isDiscoveryStream = props.DiscoveryStream.config && props.DiscoveryStream.config.enabled;
+    let filteredSections = props.Sections; // Filter out highlights for DS
+
+    if (isDiscoveryStream) {
+      filteredSections = filteredSections.filter(section => section.id !== "highlights");
+    }
+
+    const noSectionsEnabled = !prefs["feeds.topsites"] && filteredSections.filter(section => section.enabled).length === 0;
     const searchHandoffEnabled = prefs["improvesearch.handoffToAwesomebar"];
     const outerClassName = ["outer-wrapper", isDiscoveryStream && "ds-outer-wrapper-search-alignment", isDiscoveryStream && "ds-outer-wrapper-breakpoint-override", shouldBeFixedToTop && "fixed-to-top", prefs.showSearch && this.state.fixedSearch && !noSectionsEnabled && "fixed-search", prefs.showSearch && noSectionsEnabled && "only-search"].filter(v => v).join(" ");
     return react__WEBPACK_IMPORTED_MODULE_9___default.a.createElement("div", null, react__WEBPACK_IMPORTED_MODULE_9___default.a.createElement("div", {
       className: outerClassName
     }, react__WEBPACK_IMPORTED_MODULE_9___default.a.createElement("main", null, prefs.showSearch && react__WEBPACK_IMPORTED_MODULE_9___default.a.createElement("div", {
       className: "non-collapsible-section"
     }, react__WEBPACK_IMPORTED_MODULE_9___default.a.createElement(content_src_components_ErrorBoundary_ErrorBoundary__WEBPACK_IMPORTED_MODULE_7__["ErrorBoundary"], null, react__WEBPACK_IMPORTED_MODULE_9___default.a.createElement(content_src_components_Search_Search__WEBPACK_IMPORTED_MODULE_10__["Search"], _extends({
       showLogo: noSectionsEnabled,
@@ -1333,17 +1339,18 @@ class ASRouterAdminInner extends react__
 
     const messagesToShow = this.state.messageFilter === "all" ? this.state.messages : this.state.messages.filter(message => message.provider === this.state.messageFilter);
     return react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("table", null, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("tbody", null, messagesToShow.map(msg => this.renderMessageItem(msg))));
   }
 
   renderMessageFilter() {
     if (!this.state.providers) {
       return null;
-    }
+    } // eslint-disable-next-line jsx-a11y/no-onchange
+
 
     return react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("p", null, "Show messages from ", react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("select", {
       value: this.state.messageFilter,
       onChange: this.onChangeMessageFilter
     }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("option", {
       value: "all"
     }, "all providers"), this.state.providers.map(provider => react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("option", {
       key: provider.id,
@@ -1434,17 +1441,16 @@ class ASRouterAdminInner extends react__
       title: "New targeting parameters",
       button_label: errors ? "Cancel" : "Done",
       onDoneButton: this.onPasteTargetingParams
     }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", {
       className: "onboardingMessage"
     }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("p", null, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("textarea", {
       onChange: this.onNewTargetingParams,
       value: this.state.newStringTargetingParameters,
-      autoFocus: true,
       rows: "20",
       cols: "60"
     })), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("p", {
       ref: "targetingParamsEval"
     })));
   }
 
   renderTargetingParameters() {
@@ -1759,25 +1765,25 @@ const ASRouterAdmin = Object(react_redux
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ASRouterUtils", function() { return ASRouterUtils; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ASRouterUISurface", function() { return ASRouterUISurface; });
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
 /* harmony import */ var content_src_lib_init_store__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7);
 /* harmony import */ var _rich_text_strings__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9);
 /* harmony import */ var _components_ImpressionsWrapper_ImpressionsWrapper__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(10);
-/* harmony import */ var fluent_react__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(55);
+/* harmony import */ var fluent_react__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(56);
 /* harmony import */ var content_src_lib_constants__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(13);
 /* harmony import */ var _templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(14);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(11);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_8__);
 /* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(16);
 /* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_9__);
 /* harmony import */ var _templates_ReturnToAMO_ReturnToAMO__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(17);
-/* harmony import */ var _templates_template_manifest__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(53);
+/* harmony import */ var _templates_template_manifest__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(54);
 /* harmony import */ var _templates_StartupOverlay_StartupOverlay__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(25);
 /* harmony import */ var _templates_Trailhead_Trailhead__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(27);
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 
 
 
 
@@ -2431,17 +2437,17 @@ module.exports = Redux;
 /***/ }),
 /* 9 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RICH_TEXT_KEYS", function() { return RICH_TEXT_KEYS; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "generateMessages", function() { return generateMessages; });
-/* harmony import */ var fluent__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(54);
+/* harmony import */ var fluent__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55);
 
 /**
  * Properties that allow rich text MUST be added to this list.
  *   key: the localization_id that should be used
  *   value: a property or array of properties on the message.content object
  */
 
 const RICH_TEXT_CONFIG = {
@@ -2782,17 +2788,19 @@ module.exports = ReactDOM;
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ReturnToAMO", function() { return ReturnToAMO; });
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(11);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _components_RichText_RichText__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(18);
 
-
+ // Alt text if available; in the future this should come from the server. See bug 1551711
+
+const ICON_ALT_TEXT = "";
 class ReturnToAMO extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
   constructor(props) {
     super(props);
     this.onClickAddExtension = this.onClickAddExtension.bind(this);
     this.onBlockButton = this.onBlockButton.bind(this);
   }
 
   componentDidMount() {
@@ -2819,17 +2827,18 @@ class ReturnToAMO extends react__WEBPACK
       id: this.props.UISurface
     });
   }
 
   renderText() {
     const customElement = react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("img", {
       src: this.props.content.addon_icon,
       width: "20px",
-      height: "20px"
+      height: "20px",
+      alt: ICON_ALT_TEXT
     });
     return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_RichText_RichText__WEBPACK_IMPORTED_MODULE_1__["RichText"], {
       customElements: {
         icon: customElement
       },
       amo_html: this.props.content.text,
       localization_id: "amo_html"
     });
@@ -2865,17 +2874,17 @@ class ReturnToAMO extends react__WEBPACK
 /***/ }),
 /* 18 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "convertLinks", function() { return convertLinks; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RichText", function() { return RichText; });
-/* harmony import */ var fluent_react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55);
+/* harmony import */ var fluent_react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(56);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _rich_text_strings__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9);
 /* harmony import */ var _template_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(19);
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 
 
@@ -2899,17 +2908,19 @@ function convertLinks(links, sendClick, 
   if (links) {
     return Object.keys(links).reduce((acc, linkTag) => {
       const {
         action
       } = links[linkTag]; // Setting the value to false will not include the attribute in the anchor
 
       const url = action ? false : Object(_template_utils__WEBPACK_IMPORTED_MODULE_3__["safeURI"])(links[linkTag].url);
       acc[linkTag] = react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement("a", {
-        href: url,
+        href: url // eslint-disable-line jsx-a11y/anchor-has-content
+        // eslint was getting a false positive caused by the dynamic injection of content.
+        ,
         target: openNewWindow ? "_blank" : "",
         "data-metric": links[linkTag].metric,
         "data-action": action,
         "data-args": links[linkTag].args,
         "data-do_not_autoblock": doNotAutoBlock,
         onClick: sendClick
       });
       return acc;
@@ -2955,41 +2966,41 @@ function safeURI(url) {
 
   return isAllowed ? url : "";
 }
 
 /***/ }),
 /* 20 */
 /***/ (function(module) {
 
-module.exports = {"title":"EOYSnippet","description":"Fundraising Snippet","version":"1.0.0","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"donation_form_url":{"type":"string","description":"Url to the donation form."},"currency_code":{"type":"string","description":"The code for the currency. Examle gbp, cad, usd.","default":"usd"},"locale":{"type":"string","description":"String for the locale code.","default":"en-US"},"text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"text_color":{"type":"string","description":"Modify the text message color"},"background_color":{"type":"string","description":"Snippet background color."},"highlight_color":{"type":"string","description":"Paragraph em highlight color."},"donation_amount_first":{"type":"number","description":"First button amount."},"donation_amount_second":{"type":"number","description":"Second button amount."},"donation_amount_third":{"type":"number","description":"Third button amount."},"donation_amount_fourth":{"type":"number","description":"Fourth button amount."},"selected_button":{"type":"string","description":"Default donation_amount_second. Donation amount button that's selected by default.","default":"donation_amount_second"},"icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Snippet title displayed before snippet text"}]},"title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}]},"button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"block_button_text":{"type":"string","description":"Tooltip text used for dismiss button."},"monthly_checkbox_label_text":{"type":"string","description":"Label text for monthly checkbox.","default":"Make my donation monthly"},"test":{"type":"string","description":"Different styles for the snippet. Options are bold and takeover."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked"},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."},"args":{"type":"string","description":"Additional parameters for link action, example which specific menu the button should open"}}}},"additionalProperties":false,"required":["text","donation_form_url","donation_amount_first","donation_amount_second","donation_amount_third","donation_amount_fourth","button_label","currency_code"],"dependencies":{"button_color":["button_label"],"button_background_color":["button_label"]}};
+module.exports = {"title":"EOYSnippet","description":"Fundraising Snippet","version":"1.1.0","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"donation_form_url":{"type":"string","description":"Url to the donation form."},"currency_code":{"type":"string","description":"The code for the currency. Examle gbp, cad, usd.","default":"usd"},"locale":{"type":"string","description":"String for the locale code.","default":"en-US"},"text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"text_color":{"type":"string","description":"Modify the text message color"},"background_color":{"type":"string","description":"Snippet background color."},"highlight_color":{"type":"string","description":"Paragraph em highlight color."},"donation_amount_first":{"type":"number","description":"First button amount."},"donation_amount_second":{"type":"number","description":"Second button amount."},"donation_amount_third":{"type":"number","description":"Third button amount."},"donation_amount_fourth":{"type":"number","description":"Fourth button amount."},"selected_button":{"type":"string","description":"Default donation_amount_second. Donation amount button that's selected by default.","default":"donation_amount_second"},"icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"icon_dark_theme":{"type":"string","description":"Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."},"title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Snippet title displayed before snippet text"}]},"title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"title_icon_dark_theme":{"type":"string","description":"Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."},"button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}]},"button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"block_button_text":{"type":"string","description":"Tooltip text used for dismiss button."},"monthly_checkbox_label_text":{"type":"string","description":"Label text for monthly checkbox.","default":"Make my donation monthly"},"test":{"type":"string","description":"Different styles for the snippet. Options are bold and takeover."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked"},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."},"args":{"type":"string","description":"Additional parameters for link action, example which specific menu the button should open"}}}},"additionalProperties":false,"required":["text","donation_form_url","donation_amount_first","donation_amount_second","donation_amount_third","donation_amount_fourth","button_label","currency_code"],"dependencies":{"button_color":["button_label"],"button_background_color":["button_label"]}};
 
 /***/ }),
 /* 21 */
 /***/ (function(module) {
 
-module.exports = {"title":"SimpleSnippet","description":"A simple template with an icon, text, and optional button.","version":"1.1.1","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Snippet title displayed before snippet text"}]},"text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"button_action":{"type":"string","description":"The type of action the button should trigger."},"button_url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"A url, button_label links to this"}]},"button_action_args":{"type":"string","description":"Additional parameters for button action, example which specific menu the button should open"},"button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}]},"button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"block_button_text":{"type":"string","description":"Tooltip text used for dismiss button.","default":"Remove this"},"tall":{"type":"boolean","description":"To be used by fundraising only, increases height to roughly 120px. Defaults to false."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked"},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."},"args":{"type":"string","description":"Additional parameters for link action, example which specific menu the button should open"}}},"section_title_icon":{"type":"string","description":"Section title icon. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display."},"section_title_text":{"type":"string","description":"Section title text. section_title_icon must also be specified to display."},"section_title_url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"A url, section_title_text links to this"}]}},"additionalProperties":false,"required":["text"],"dependencies":{"button_action":["button_label"],"button_url":["button_label"],"button_color":["button_label"],"button_background_color":["button_label"],"section_title_url":["section_title_text"]}};
+module.exports = {"title":"SimpleSnippet","description":"A simple template with an icon, text, and optional button.","version":"1.1.1","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Snippet title displayed before snippet text"}]},"text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"icon_dark_theme":{"type":"string","description":"Snippet icon, dark theme variant. 64x64px. SVG or PNG preferred."},"title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"title_icon_dark_theme":{"type":"string","description":"Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."},"button_action":{"type":"string","description":"The type of action the button should trigger."},"button_url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"A url, button_label links to this"}]},"button_action_args":{"type":"string","description":"Additional parameters for button action, example which specific menu the button should open"},"button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}]},"button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"block_button_text":{"type":"string","description":"Tooltip text used for dismiss button.","default":"Remove this"},"tall":{"type":"boolean","description":"To be used by fundraising only, increases height to roughly 120px. Defaults to false."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked"},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."},"args":{"type":"string","description":"Additional parameters for link action, example which specific menu the button should open"}}},"section_title_icon":{"type":"string","description":"Section title icon. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display."},"section_title_icon_dark_theme":{"type":"string","description":"Section title icon, dark theme variant. 16x16px. SVG or PNG preferred. section_title_text must also be specified to display."},"section_title_text":{"type":"string","description":"Section title text. section_title_icon must also be specified to display."},"section_title_url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"A url, section_title_text links to this"}]}},"additionalProperties":false,"required":["text"],"dependencies":{"button_action":["button_label"],"button_url":["button_label"],"button_color":["button_label"],"button_background_color":["button_label"],"section_title_url":["section_title_text"]}};
 
 /***/ }),
 /* 22 */
 /***/ (function(module) {
 
-module.exports = {"title":"FXASignupSnippet","description":"A snippet template for FxA sign up/sign in","version":"1.0.0","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"scene1_title":{"allof":[{"$ref":"#/definitions/plainText"},{"description":"snippet title displayed before snippet text"}]},"scene1_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene2_title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Title displayed before text in scene 2. Should be plain text."}]},"scene2_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene1_icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"scene1_title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"scene2_email_placeholder_text":{"type":"string","description":"Value to show while input is empty.","default":"Your email here"},"scene2_button_label":{"type":"string","description":"Label for form submit button","default":"Sign me up"},"scene2_dismiss_button_text":{"type":"string","description":"Label for the dismiss button when the sign-up form is expanded.","default":"Dismiss"},"hidden_inputs":{"type":"object","description":"Each entry represents a hidden input, key is used as value for the name property.","properties":{"action":{"type":"string","enum":["email"]},"context":{"type":"string","enum":["fx_desktop_v3"]},"entrypoint":{"type":"string","enum":["snippets"]},"service":{"type":"string","enum":["sync"]},"utm_content":{"type":"number","description":"Firefox version number"},"utm_source":{"type":"string","enum":["snippet"]},"utm_campaign":{"type":"string","description":"(fxa) Value to pass through to GA as utm_campaign."},"utm_term":{"type":"string","description":"(fxa) Value to pass through to GA as utm_term."},"additionalProperties":false}},"scene1_button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}],"default":"Learn more"},"scene1_button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"scene1_button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked","default":false},"utm_campaign":{"type":"string","description":"(fxa) Value to pass through to GA as utm_campaign."},"utm_term":{"type":"string","description":"(fxa) Value to pass through to GA as utm_term."},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."}}}},"additionalProperties":false,"required":["scene1_text","scene2_text","scene1_button_label"],"dependencies":{"scene1_button_color":["scene1_button_label"],"scene1_button_background_color":["scene1_button_label"]}};
+module.exports = {"title":"FXASignupSnippet","description":"A snippet template for FxA sign up/sign in","version":"1.1.0","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"scene1_title":{"allof":[{"$ref":"#/definitions/plainText"},{"description":"snippet title displayed before snippet text"}]},"scene1_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene2_title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Title displayed before text in scene 2. Should be plain text."}]},"scene2_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene1_icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"scene1_icon_dark_theme":{"type":"string","description":"Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."},"scene1_title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"scene1_title_icon_dark_theme":{"type":"string","description":"Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."},"scene2_email_placeholder_text":{"type":"string","description":"Value to show while input is empty.","default":"Your email here"},"scene2_button_label":{"type":"string","description":"Label for form submit button","default":"Sign me up"},"scene2_dismiss_button_text":{"type":"string","description":"Label for the dismiss button when the sign-up form is expanded.","default":"Dismiss"},"hidden_inputs":{"type":"object","description":"Each entry represents a hidden input, key is used as value for the name property.","properties":{"action":{"type":"string","enum":["email"]},"context":{"type":"string","enum":["fx_desktop_v3"]},"entrypoint":{"type":"string","enum":["snippets"]},"service":{"type":"string","enum":["sync"]},"utm_content":{"type":"number","description":"Firefox version number"},"utm_source":{"type":"string","enum":["snippet"]},"utm_campaign":{"type":"string","description":"(fxa) Value to pass through to GA as utm_campaign."},"utm_term":{"type":"string","description":"(fxa) Value to pass through to GA as utm_term."},"additionalProperties":false}},"scene1_button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}],"default":"Learn more"},"scene1_button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"scene1_button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked","default":false},"utm_campaign":{"type":"string","description":"(fxa) Value to pass through to GA as utm_campaign."},"utm_term":{"type":"string","description":"(fxa) Value to pass through to GA as utm_term."},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."}}}},"additionalProperties":false,"required":["scene1_text","scene2_text","scene1_button_label"],"dependencies":{"scene1_button_color":["scene1_button_label"],"scene1_button_background_color":["scene1_button_label"]}};
 
 /***/ }),
 /* 23 */
 /***/ (function(module) {
 
-module.exports = {"title":"NewsletterSnippet","description":"A snippet template for send to device mobile download","version":"1.0.0","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"locale":{"type":"string","description":"Two to five character string for the locale code","default":"en-US"},"scene1_title":{"allof":[{"$ref":"#/definitions/plainText"},{"description":"snippet title displayed before snippet text"}]},"scene1_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene2_title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Title displayed before text in scene 2. Should be plain text."}]},"scene2_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene1_icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"scene1_title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"scene2_email_placeholder_text":{"type":"string","description":"Value to show while input is empty.","default":"Your email here"},"scene2_button_label":{"type":"string","description":"Label for form submit button","default":"Sign me up"},"scene2_privacy_html":{"type":"string","description":"(send to device) Html for disclaimer and link underneath input box."},"scene2_dismiss_button_text":{"type":"string","description":"Label for the dismiss button when the sign-up form is expanded.","default":"Dismiss"},"hidden_inputs":{"type":"object","description":"Each entry represents a hidden input, key is used as value for the name property.","properties":{"fmt":{"type":"string","description":"","default":"H"}}},"scene1_button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}],"default":"Learn more"},"scene1_button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"scene1_button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked","default":false},"success_text":{"type":"string","description":"Message shown on successful registration."},"error_text":{"type":"string","description":"Message shown if registration failed."},"scene2_newsletter":{"type":"string","description":"Newsletter/basket id user is subscribing to.","default":"mozilla-foundation"},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."}}}},"additionalProperties":false,"required":["scene1_text","scene2_text","scene1_button_label"],"dependencies":{"scene1_button_color":["scene1_button_label"],"scene1_button_background_color":["scene1_button_label"]}};
+module.exports = {"title":"NewsletterSnippet","description":"A snippet template for send to device mobile download","version":"1.1.0","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"locale":{"type":"string","description":"Two to five character string for the locale code","default":"en-US"},"scene1_title":{"allof":[{"$ref":"#/definitions/plainText"},{"description":"snippet title displayed before snippet text"}]},"scene1_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene2_title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Title displayed before text in scene 2. Should be plain text."}]},"scene2_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene1_icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"scene1_icon_dark_theme":{"type":"string","description":"Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."},"scene1_title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"scene1_title_icon_dark_theme":{"type":"string","description":"Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."},"scene2_email_placeholder_text":{"type":"string","description":"Value to show while input is empty.","default":"Your email here"},"scene2_button_label":{"type":"string","description":"Label for form submit button","default":"Sign me up"},"scene2_privacy_html":{"type":"string","description":"(send to device) Html for disclaimer and link underneath input box."},"scene2_dismiss_button_text":{"type":"string","description":"Label for the dismiss button when the sign-up form is expanded.","default":"Dismiss"},"hidden_inputs":{"type":"object","description":"Each entry represents a hidden input, key is used as value for the name property.","properties":{"fmt":{"type":"string","description":"","default":"H"}}},"scene1_button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}],"default":"Learn more"},"scene1_button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"scene1_button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked","default":false},"success_text":{"type":"string","description":"Message shown on successful registration."},"error_text":{"type":"string","description":"Message shown if registration failed."},"scene2_newsletter":{"type":"string","description":"Newsletter/basket id user is subscribing to.","default":"mozilla-foundation"},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."}}}},"additionalProperties":false,"required":["scene1_text","scene2_text","scene1_button_label"],"dependencies":{"scene1_button_color":["scene1_button_label"],"scene1_button_background_color":["scene1_button_label"]}};
 
 /***/ }),
 /* 24 */
 /***/ (function(module) {
 
-module.exports = {"title":"SendToDeviceSnippet","description":"A snippet template for send to device mobile download","version":"1.0.0","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"locale":{"type":"string","description":"Two to five character string for the locale code","default":"en-US"},"country":{"type":"string","description":"Two character string for the country code (used for SMS)","default":"us"},"scene1_title":{"allof":[{"$ref":"#/definitions/plainText"},{"description":"snippet title displayed before snippet text"}]},"scene1_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene2_title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Title displayed before text in scene 2. Should be plain text."}]},"scene2_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene1_icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"scene2_icon":{"type":"string","description":"(send to device) Image to display above the form. 98x98px. SVG or PNG preferred."},"scene1_title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"scene2_button_label":{"type":"string","description":"Label for form submit button","default":"Send"},"scene2_input_placeholder":{"type":"string","description":"(send to device) Value to show while input is empty.","default":"Your email here"},"scene2_disclaimer_html":{"type":"string","description":"(send to device) Html for disclaimer and link underneath input box."},"scene2_dismiss_button_text":{"type":"string","description":"Label for the dismiss button when the sign-up form is expanded.","default":"Dismiss"},"hidden_inputs":{"type":"object","description":"Each entry represents a hidden input, key is used as value for the name property.","properties":{"action":{"type":"string","enum":["email"]},"context":{"type":"string","enum":["fx_desktop_v3"]},"entrypoint":{"type":"string","enum":["snippets"]},"service":{"type":"string","enum":["sync"]},"utm_content":{"type":"string","description":"Firefox version number"},"utm_source":{"type":"string","enum":["snippet"]},"utm_campaign":{"type":"string","description":"(fxa) Value to pass through to GA as utm_campaign."},"utm_term":{"type":"string","description":"(fxa) Value to pass through to GA as utm_term."},"additionalProperties":false}},"scene1_button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}],"default":"Learn more"},"scene1_button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"scene1_button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked","default":false},"success_title":{"type":"string","description":"(send to device) Title shown before text on successful registration."},"success_text":{"type":"string","description":"Message shown on successful registration."},"error_text":{"type":"string","description":"Message shown if registration failed."},"include_sms":{"type":"boolean","description":"(send to device) Allow users to send an SMS message with the form?","default":false},"message_id_sms":{"type":"string","description":"(send to device) Newsletter/basket id representing the SMS message to be sent."},"message_id_email":{"type":"string","description":"(send to device) Newsletter/basket id representing the email message to be sent. Must be a value from the 'Slug' column here: https://basket.mozilla.org/news/."},"utm_campaign":{"type":"string","description":"(fxa) Value to pass through to GA as utm_campaign."},"utm_term":{"type":"string","description":"(fxa) Value to pass through to GA as utm_term."},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."}}}},"additionalProperties":false,"required":["scene1_text","scene2_text","scene1_button_label"],"dependencies":{"scene1_button_color":["scene1_button_label"],"scene1_button_background_color":["scene1_button_label"]}};
+module.exports = {"title":"SendToDeviceSnippet","description":"A snippet template for send to device mobile download","version":"1.1.0","type":"object","definitions":{"plainText":{"description":"Plain text (no HTML allowed)","type":"string"},"richText":{"description":"Text with HTML subset allowed: i, b, u, strong, em, br","type":"string"},"link_url":{"description":"Target for links or buttons","type":"string","format":"uri"}},"properties":{"locale":{"type":"string","description":"Two to five character string for the locale code","default":"en-US"},"country":{"type":"string","description":"Two character string for the country code (used for SMS)","default":"us"},"scene1_title":{"allof":[{"$ref":"#/definitions/plainText"},{"description":"snippet title displayed before snippet text"}]},"scene1_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene2_title":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Title displayed before text in scene 2. Should be plain text."}]},"scene2_text":{"allOf":[{"$ref":"#/definitions/richText"},{"description":"Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}]},"scene1_icon":{"type":"string","description":"Snippet icon. 64x64px. SVG or PNG preferred."},"scene1_icon_dark_theme":{"type":"string","description":"Snippet icon. Dark theme variant. 64x64px. SVG or PNG preferred."},"scene2_icon":{"type":"string","description":"(send to device) Image to display above the form. Dark theme variant. 98x98px. SVG or PNG preferred."},"scene2_icon_dark_theme":{"type":"string","description":"(send to device) Image to display above the form. 98x98px. SVG or PNG preferred."},"scene1_title_icon":{"type":"string","description":"Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."},"scene1_title_icon_dark_theme":{"type":"string","description":"Small icon that shows up before the title / text. Dark theme variant. 16x16px. SVG or PNG preferred. Grayscale."},"scene2_button_label":{"type":"string","description":"Label for form submit button","default":"Send"},"scene2_input_placeholder":{"type":"string","description":"(send to device) Value to show while input is empty.","default":"Your email here"},"scene2_disclaimer_html":{"type":"string","description":"(send to device) Html for disclaimer and link underneath input box."},"scene2_dismiss_button_text":{"type":"string","description":"Label for the dismiss button when the sign-up form is expanded.","default":"Dismiss"},"hidden_inputs":{"type":"object","description":"Each entry represents a hidden input, key is used as value for the name property.","properties":{"action":{"type":"string","enum":["email"]},"context":{"type":"string","enum":["fx_desktop_v3"]},"entrypoint":{"type":"string","enum":["snippets"]},"service":{"type":"string","enum":["sync"]},"utm_content":{"type":"string","description":"Firefox version number"},"utm_source":{"type":"string","enum":["snippet"]},"utm_campaign":{"type":"string","description":"(fxa) Value to pass through to GA as utm_campaign."},"utm_term":{"type":"string","description":"(fxa) Value to pass through to GA as utm_term."},"additionalProperties":false}},"scene1_button_label":{"allOf":[{"$ref":"#/definitions/plainText"},{"description":"Text for a button next to main snippet text that links to button_url. Requires button_url."}],"default":"Learn more"},"scene1_button_color":{"type":"string","description":"The text color of the button. Valid CSS color."},"scene1_button_background_color":{"type":"string","description":"The background color of the button. Valid CSS color."},"do_not_autoblock":{"type":"boolean","description":"Used to prevent blocking the snippet after the CTA (link or button) has been clicked","default":false},"success_title":{"type":"string","description":"(send to device) Title shown before text on successful registration."},"success_text":{"type":"string","description":"Message shown on successful registration."},"error_text":{"type":"string","description":"Message shown if registration failed."},"include_sms":{"type":"boolean","description":"(send to device) Allow users to send an SMS message with the form?","default":false},"message_id_sms":{"type":"string","description":"(send to device) Newsletter/basket id representing the SMS message to be sent."},"message_id_email":{"type":"string","description":"(send to device) Newsletter/basket id representing the email message to be sent. Must be a value from the 'Slug' column here: https://basket.mozilla.org/news/."},"utm_campaign":{"type":"string","description":"(fxa) Value to pass through to GA as utm_campaign."},"utm_term":{"type":"string","description":"(fxa) Value to pass through to GA as utm_term."},"links":{"additionalProperties":{"url":{"allOf":[{"$ref":"#/definitions/link_url"},{"description":"The url where the link points to."}]},"metric":{"type":"string","description":"Custom event name sent with telemetry event."}}}},"additionalProperties":false,"required":["scene1_text","scene2_text","scene1_button_label"],"dependencies":{"scene1_button_color":["scene1_button_label"],"scene1_button_background_color":["scene1_button_label"]}};
 
 /***/ }),
 /* 25 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_StartupOverlay", function() { return _StartupOverlay; });
@@ -3891,25 +3902,61 @@ class _ConfirmDialog extends react__WEBP
 const ConfirmDialog = Object(react_redux__WEBPACK_IMPORTED_MODULE_1__["connect"])(state => state.Dialog)(_ConfirmDialog);
 
 /***/ }),
 /* 30 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
+/* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "clampTotalLines", function() { return clampTotalLines; });
+/**
+ * Adjusts line-clamps of a node's children to fill a desired number of lines.
+ *
+ * This is a React callback ref that should be set on a parent node that also
+ * has a data-total-lines attribute. Children with "clamp" class name are
+ * clamped to allow as many lines to earlier children while making sure every
+ * child gets at least one line. Each child can be explicitly clamped to a max
+ * lines with a data-clamp attribute.
+ */
+function clampTotalLines(parentNode) {
+  // Nothing to do if the node is removed or didn't configure how many lines
+  if (!parentNode || !parentNode.dataset.totalLines) {
+    return;
+  } // Only handle clamp-able children that are displayed (not hidden)
+
+
+  const toClamp = Array.from(parentNode.querySelectorAll(".clamp")).filter(child => child.scrollHeight); // Start with total allowed lines while reserving 1 line for each child
+
+  let maxLines = parentNode.dataset.totalLines - toClamp.length + 1;
+  toClamp.forEach(child => {
+    // Clamp to the remaining allowed, explicit limit or the natural line count
+    const lines = Math.min(maxLines, child.dataset.clamp || Infinity, child.scrollHeight / parseInt(global.getComputedStyle(child).lineHeight, 10));
+    child.style.webkitLineClamp = `${lines}`; // Update the remaining line allowance less the already reserved 1 line
+
+    maxLines -= lines - 1;
+  });
+}
+/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
+
+/***/ }),
+/* 31 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_LinkMenu", function() { return _LinkMenu; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "LinkMenu", function() { return LinkMenu; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(26);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_1__);
-/* harmony import */ var content_src_components_ContextMenu_ContextMenu__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(31);
+/* harmony import */ var content_src_components_ContextMenu_ContextMenu__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(32);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_3__);
-/* harmony import */ var content_src_lib_link_menu_options__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(32);
+/* harmony import */ var content_src_lib_link_menu_options__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(33);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(11);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_5__);
 
 
 
 
 
 
@@ -3985,17 +4032,17 @@ class _LinkMenu extends react__WEBPACK_I
 const getState = state => ({
   isPrivateBrowsingEnabled: state.Prefs.values.isPrivateBrowsingEnabled,
   platform: state.Prefs.values.platform
 });
 
 const LinkMenu = Object(react_redux__WEBPACK_IMPORTED_MODULE_1__["connect"])(getState)(Object(react_intl__WEBPACK_IMPORTED_MODULE_3__["injectIntl"])(_LinkMenu));
 
 /***/ }),
-/* 31 */
+/* 32 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ContextMenu", function() { return ContextMenu; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ContextMenuItem", function() { return ContextMenuItem; });
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(11);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
@@ -4105,17 +4152,17 @@ class ContextMenuItem extends react__WEB
       className: `icon icon-spacer icon-${option.icon}`
     }), option.label));
   }
 
 }
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 32 */
+/* 33 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GetPlatformString", function() { return GetPlatformString; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "LinkMenuOptions", function() { return LinkMenuOptions; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 
@@ -4398,17 +4445,17 @@ const LinkMenuOptions = {
   CheckBookmark: site => site.bookmarkGuid ? LinkMenuOptions.RemoveBookmark(site) : LinkMenuOptions.AddBookmark(site),
   CheckPinTopSite: (site, index) => site.isPinned ? LinkMenuOptions.UnpinTopSite(site) : LinkMenuOptions.PinTopSite(site, index),
   CheckSavedToPocket: (site, index) => site.pocket_id ? LinkMenuOptions.DeleteFromPocket(site) : LinkMenuOptions.SaveToPocket(site, index),
   CheckBookmarkOrArchive: site => site.pocket_id ? LinkMenuOptions.ArchiveFromPocket(site) : LinkMenuOptions.CheckBookmark(site),
   OpenInPrivateWindow: (site, index, eventSource, isEnabled) => isEnabled ? _OpenInPrivateWindow(site) : LinkMenuOptions.EmptyItem()
 };
 
 /***/ }),
-/* 33 */
+/* 34 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "INTERSECTION_RATIO", function() { return INTERSECTION_RATIO; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ImpressionStats", function() { return ImpressionStats; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11);
@@ -4619,31 +4666,31 @@ ImpressionStats.defaultProps = {
   IntersectionObserver: global.IntersectionObserver,
   document: global.document,
   rows: [],
   source: ""
 };
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 34 */
+/* 35 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_CollapsibleSection", function() { return _CollapsibleSection; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CollapsibleSection", function() { return CollapsibleSection; });
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
-/* harmony import */ var content_src_components_ErrorBoundary_ErrorBoundary__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(35);
+/* harmony import */ var content_src_components_ErrorBoundary_ErrorBoundary__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(36);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(11);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_3__);
-/* harmony import */ var content_src_components_SectionMenu_SectionMenu__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(36);
-/* harmony import */ var content_src_lib_section_menu_options__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(37);
+/* harmony import */ var content_src_components_SectionMenu_SectionMenu__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(37);
+/* harmony import */ var content_src_lib_section_menu_options__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(38);
 
 
 
 
 
 
 const VISIBLE = "visible";
 const VISIBILITY_CHANGE_EVENT = "visibilitychange";
@@ -4915,17 +4962,17 @@ class _CollapsibleSection extends react_
   Prefs: {
     values: {}
   }
 };
 const CollapsibleSection = Object(react_intl__WEBPACK_IMPORTED_MODULE_0__["injectIntl"])(_CollapsibleSection);
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 35 */
+/* 36 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ErrorBoundaryFallback", function() { return ErrorBoundaryFallback; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ErrorBoundary", function() { return ErrorBoundary; });
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_0__);
@@ -5004,30 +5051,30 @@ class ErrorBoundary extends react__WEBPA
   }
 
 }
 ErrorBoundary.defaultProps = {
   FallbackComponent: ErrorBoundaryFallback
 };
 
 /***/ }),
-/* 36 */
+/* 37 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_SectionMenu", function() { return _SectionMenu; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SectionMenu", function() { return SectionMenu; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var content_src_components_ContextMenu_ContextMenu__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(31);
+/* harmony import */ var content_src_components_ContextMenu_ContextMenu__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(32);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_2__);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(11);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_3__);
-/* harmony import */ var content_src_lib_section_menu_options__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(37);
+/* harmony import */ var content_src_lib_section_menu_options__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(38);
 
 
 
 
 
 const DEFAULT_SECTION_MENU_OPTIONS = ["MoveUp", "MoveDown", "Separator", "RemoveSection", "CheckCollapsed", "Separator", "ManageSection"];
 const WEBEXT_SECTION_MENU_OPTIONS = ["MoveUp", "MoveDown", "Separator", "CheckCollapsed", "Separator", "ManageWebExtension"];
 class _SectionMenu extends react__WEBPACK_IMPORTED_MODULE_3___default.a.PureComponent {
@@ -5092,17 +5139,17 @@ class _SectionMenu extends react__WEBPAC
       options: this.getOptions()
     });
   }
 
 }
 const SectionMenu = Object(react_intl__WEBPACK_IMPORTED_MODULE_2__["injectIntl"])(_SectionMenu);
 
 /***/ }),
-/* 37 */
+/* 38 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SectionMenuOptions", function() { return SectionMenuOptions; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 
 /**
@@ -5219,37 +5266,37 @@ const SectionMenuOptions = {
       }
     }),
     userEvent: "MENU_PRIVACY_NOTICE"
   }),
   CheckCollapsed: section => section.collapsed ? SectionMenuOptions.ExpandSection(section) : SectionMenuOptions.CollapseSection(section)
 };
 
 /***/ }),
-/* 38 */
+/* 39 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_TopSites", function() { return _TopSites; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TopSites", function() { return TopSites; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(39);
-/* harmony import */ var content_src_components_CollapsibleSection_CollapsibleSection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(34);
-/* harmony import */ var content_src_components_ComponentPerfTimer_ComponentPerfTimer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(40);
+/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(40);
+/* harmony import */ var content_src_components_CollapsibleSection_CollapsibleSection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(35);
+/* harmony import */ var content_src_components_ComponentPerfTimer_ComponentPerfTimer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(41);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(26);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_4__);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_5__);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(11);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_6__);
-/* harmony import */ var _SearchShortcutsForm__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(42);
-/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(56);
-/* harmony import */ var _TopSiteForm__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(58);
-/* harmony import */ var _TopSite__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(43);
+/* harmony import */ var _SearchShortcutsForm__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(43);
+/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(58);
+/* harmony import */ var _TopSiteForm__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(57);
+/* harmony import */ var _TopSite__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(44);
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 
 
 
 
 
 
@@ -5416,29 +5463,31 @@ class _TopSites extends react__WEBPACK_I
       intl: props.intl,
       topSiteIconType: topSiteIconType
     }), react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement("div", {
       className: "edit-topsites-wrapper"
     }, editForm && react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement("div", {
       className: "edit-topsites"
     }, react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement("div", {
       className: "modal-overlay",
-      onClick: this.onEditFormClose
+      onClick: this.onEditFormClose,
+      role: "presentation"
     }), react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement("div", {
       className: "modal"
     }, react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement(_TopSiteForm__WEBPACK_IMPORTED_MODULE_9__["TopSiteForm"], _extends({
       site: props.TopSites.rows[editForm.index],
       onClose: this.onEditFormClose,
       dispatch: this.props.dispatch,
       intl: this.props.intl
     }, editForm)))), showSearchShortcutsForm && react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement("div", {
       className: "edit-search-shortcuts"
     }, react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement("div", {
       className: "modal-overlay",
-      onClick: this.onSearchShortcutsFormClose
+      onClick: this.onSearchShortcutsFormClose,
+      role: "presentation"
     }), react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement("div", {
       className: "modal"
     }, react__WEBPACK_IMPORTED_MODULE_6___default.a.createElement(_SearchShortcutsForm__WEBPACK_IMPORTED_MODULE_7__["SearchShortcutsForm"], {
       TopSites: props.TopSites,
       onClose: this.onSearchShortcutsFormClose,
       dispatch: this.props.dispatch
     }))))));
   }
@@ -5447,17 +5496,17 @@ class _TopSites extends react__WEBPACK_I
 const TopSites = Object(react_redux__WEBPACK_IMPORTED_MODULE_4__["connect"])(state => ({
   TopSites: state.TopSites,
   Prefs: state.Prefs,
   TopSitesRows: state.Prefs.values.topSitesRows
 }))(Object(react_intl__WEBPACK_IMPORTED_MODULE_5__["injectIntl"])(_TopSites));
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 39 */
+/* 40 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TOP_SITES_SOURCE", function() { return TOP_SITES_SOURCE; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TOP_SITES_CONTEXT_MENU_OPTIONS", function() { return TOP_SITES_CONTEXT_MENU_OPTIONS; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS", function() { return TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MIN_RICH_FAVICON_SIZE", function() { return MIN_RICH_FAVICON_SIZE; });
@@ -5467,24 +5516,24 @@ const TOP_SITES_CONTEXT_MENU_OPTIONS = [
 
 const TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS = ["CheckPinTopSite", "Separator", "BlockUrl"]; // minimum size necessary to show a rich icon instead of a screenshot
 
 const MIN_RICH_FAVICON_SIZE = 96; // minimum size necessary to show any icon in the top left corner with a screenshot
 
 const MIN_CORNER_FAVICON_SIZE = 16;
 
 /***/ }),
-/* 40 */
+/* 41 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ComponentPerfTimer", function() { return ComponentPerfTimer; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var common_PerfService_jsm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(41);
+/* harmony import */ var common_PerfService_jsm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(42);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(11);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_2__);
 
 
  // Currently record only a fixed set of sections. This will prevent data
 // from custom sections from showing up or from topstories.
 
 const RECORDED_SECTIONS = ["highlights", "topsites"];
@@ -5644,17 +5693,17 @@ class ComponentPerfTimer extends react__
     }
 
     return this.props.children;
   }
 
 }
 
 /***/ }),
-/* 41 */
+/* 42 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_PerfService", function() { return _PerfService; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "perfService", function() { return perfService; });
 
 
@@ -5782,29 +5831,29 @@ function _PerfService(options) {
     let mostRecentEntry = entries[entries.length - 1];
     return this._perf.timeOrigin + mostRecentEntry.startTime;
   }
 
 };
 var perfService = new _PerfService();
 
 /***/ }),
-/* 42 */
+/* 43 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SelectableSearchShortcut", function() { return SelectableSearchShortcut; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SearchShortcutsForm", function() { return SearchShortcutsForm; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(11);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_2__);
-/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(39);
+/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(40);
 
 
 
 
 class SelectableSearchShortcut extends react__WEBPACK_IMPORTED_MODULE_2___default.a.PureComponent {
   render() {
     const {
       shortcut,
@@ -5976,35 +6025,35 @@ class SearchShortcutsForm extends react_
     }, react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__["FormattedMessage"], {
       id: "topsites_form_save_button"
     }))));
   }
 
 }
 
 /***/ }),
-/* 43 */
+/* 44 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TopSiteLink", function() { return TopSiteLink; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TopSite", function() { return TopSite; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TopSitePlaceholder", function() { return TopSitePlaceholder; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_TopSiteList", function() { return _TopSiteList; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TopSiteList", function() { return TopSiteList; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_1__);
-/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(39);
-/* harmony import */ var content_src_components_LinkMenu_LinkMenu__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(30);
+/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(40);
+/* harmony import */ var content_src_components_LinkMenu_LinkMenu__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(31);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(11);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_4__);
-/* harmony import */ var content_src_lib_screenshot_utils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(44);
-/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(56);
+/* harmony import */ var content_src_lib_screenshot_utils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(45);
+/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(58);
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 
 
 
 
 
 
@@ -6220,16 +6269,17 @@ class TopSiteLink extends react__WEBPACK
       className: topSiteOuterClassName,
       onDrop: this.onDragEvent,
       onDragOver: this.onDragEvent,
       onDragEnter: this.onDragEvent,
       onDragLeave: this.onDragEvent
     }, draggableProps), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", {
       className: "top-site-inner"
     }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("a", {
+      className: "top-site-button",
       href: link.searchTopSite ? undefined : link.url,
       tabIndex: "0",
       onKeyPress: this.onKeyPress,
       onClick: onClick,
       draggable: true
     }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", {
       className: "tile",
       "aria-hidden": true,
@@ -6634,17 +6684,17 @@ class _TopSiteList extends react__WEBPAC
       className: `top-sites-list${this.state.draggedSite ? " dnd-active" : ""}`
     }, topSitesUI);
   }
 
 }
 const TopSiteList = Object(react_intl__WEBPACK_IMPORTED_MODULE_1__["injectIntl"])(_TopSiteList);
 
 /***/ }),
-/* 44 */
+/* 45 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ScreenshotUtils", function() { return ScreenshotUtils; });
 /**
  * List of helper functions for screenshot-based images.
  *
@@ -6699,17 +6749,17 @@ const ScreenshotUtils = {
 
     return !remoteImage && !localImage;
   }
 
 };
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 45 */
+/* 46 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_PrerenderData", function() { return _PrerenderData; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "PrerenderData", function() { return PrerenderData; });
 class _PrerenderData {
   constructor(options) {
@@ -6837,17 +6887,17 @@ var PrerenderData = new _PrerenderData({
     order: 2,
     title: {
       id: "header_highlights"
     }
   }]
 });
 
 /***/ }),
-/* 46 */
+/* 47 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_Search", function() { return _Search; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Search", function() { return Search; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
@@ -7045,39 +7095,39 @@ class _Search extends react__WEBPACK_IMP
       ref: this.onInputMount
     })));
   }
 
 }
 const Search = Object(react_redux__WEBPACK_IMPORTED_MODULE_2__["connect"])()(Object(react_intl__WEBPACK_IMPORTED_MODULE_1__["injectIntl"])(_Search));
 
 /***/ }),
-/* 47 */
+/* 48 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Section", function() { return Section; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SectionIntl", function() { return SectionIntl; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_Sections", function() { return _Sections; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Sections", function() { return Sections; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var content_src_components_Card_Card__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(57);
+/* harmony import */ var content_src_components_Card_Card__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(59);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_2__);
-/* harmony import */ var content_src_components_CollapsibleSection_CollapsibleSection__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(34);
-/* harmony import */ var content_src_components_ComponentPerfTimer_ComponentPerfTimer__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(40);
+/* harmony import */ var content_src_components_CollapsibleSection_CollapsibleSection__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(35);
+/* harmony import */ var content_src_components_ComponentPerfTimer_ComponentPerfTimer__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(41);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(26);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_5__);
-/* harmony import */ var content_src_components_MoreRecommendations_MoreRecommendations__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(48);
-/* harmony import */ var content_src_components_PocketLoggedInCta_PocketLoggedInCta__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(49);
+/* harmony import */ var content_src_components_MoreRecommendations_MoreRecommendations__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(49);
+/* harmony import */ var content_src_components_PocketLoggedInCta_PocketLoggedInCta__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(50);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(11);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_8__);
-/* harmony import */ var content_src_components_Topics_Topics__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(50);
-/* harmony import */ var content_src_components_TopSites_TopSites__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(38);
+/* harmony import */ var content_src_components_Topics_Topics__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(51);
+/* harmony import */ var content_src_components_TopSites_TopSites__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(39);
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 
 
 
 
 
 
@@ -7416,17 +7466,17 @@ class _Sections extends react__WEBPACK_I
 }
 const Sections = Object(react_redux__WEBPACK_IMPORTED_MODULE_5__["connect"])(state => ({
   Sections: state.Sections,
   Prefs: state.Prefs
 }))(_Sections);
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 48 */
+/* 49 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MoreRecommendations", function() { return MoreRecommendations; });
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11);
@@ -7449,17 +7499,17 @@ class MoreRecommendations extends react_
     }
 
     return null;
   }
 
 }
 
 /***/ }),
-/* 49 */
+/* 50 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_PocketLoggedInCta", function() { return _PocketLoggedInCta; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "PocketLoggedInCta", function() { return PocketLoggedInCta; });
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(26);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_0__);
@@ -7492,17 +7542,17 @@ class _PocketLoggedInCta extends react__
   }
 
 }
 const PocketLoggedInCta = Object(react_redux__WEBPACK_IMPORTED_MODULE_0__["connect"])(state => ({
   Pocket: state.Pocket
 }))(_PocketLoggedInCta);
 
 /***/ }),
-/* 50 */
+/* 51 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Topic", function() { return Topic; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Topics", function() { return Topics; });
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4);
 /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_0__);
@@ -7537,24 +7587,24 @@ class Topics extends react__WEBPACK_IMPO
       url: t.url,
       name: t.name
     }))));
   }
 
 }
 
 /***/ }),
-/* 51 */
+/* 52 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DetectUserSessionStart", function() { return DetectUserSessionStart; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var common_PerfService_jsm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(41);
+/* harmony import */ var common_PerfService_jsm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(42);
 
 
 const VISIBLE = "visible";
 const VISIBILITY_CHANGE_EVENT = "visibilitychange";
 class DetectUserSessionStart {
   constructor(store, options = {}) {
     this._store = store; // Overrides for testing
 
@@ -7616,25 +7666,28 @@ class DetectUserSessionStart {
       this.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
     }
   }
 
 }
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 52 */
+/* 53 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __we