Merge mozilla-central to autoland. a=merge CLOSED TREE
authorCsoregi Natalia <ncsoregi@mozilla.com>
Tue, 21 Aug 2018 01:02:45 +0300
changeset 487568 2646b2e0a72345c761d5ec032d7f3bcb14a31eb2
parent 487567 1e98d3f45a4c0828b64a5b0212183db9a8be01bb (current diff)
parent 487557 d0d2e0f4b33cd28bc05c353c185873256f7f926e (diff)
child 487569 c2422757c912799714b0dd9238748585aa3d575a
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. a=merge CLOSED TREE
browser/components/preferences/in-content/tests/browser_privacypane_1.js
browser/components/preferences/in-content/tests/browser_privacypane_8.js
devtools/server/actors/webconsole/listeners.js
devtools/shared/webconsole/network-monitor.js
js/src/tests/test262/built-ins/Atomics/wake/bad-range.js
js/src/tests/test262/built-ins/Atomics/wake/bigint/bad-range.js
js/src/tests/test262/built-ins/Atomics/wake/bigint/browser.js
js/src/tests/test262/built-ins/Atomics/wake/bigint/non-bigint64-typedarray-throws.js
js/src/tests/test262/built-ins/Atomics/wake/bigint/non-shared-bufferdata-throws.js
js/src/tests/test262/built-ins/Atomics/wake/bigint/null-bufferdata-throws.js
js/src/tests/test262/built-ins/Atomics/wake/bigint/shell.js
js/src/tests/test262/built-ins/Atomics/wake/bigint/wake-all-on-loc.js
js/src/tests/test262/built-ins/Atomics/wake/browser.js
js/src/tests/test262/built-ins/Atomics/wake/count-boundary-cases.js
js/src/tests/test262/built-ins/Atomics/wake/count-defaults-to-infinity-missing.js
js/src/tests/test262/built-ins/Atomics/wake/count-defaults-to-infinity-undefined.js
js/src/tests/test262/built-ins/Atomics/wake/count-from-nans.js
js/src/tests/test262/built-ins/Atomics/wake/count-symbol-throws.js
js/src/tests/test262/built-ins/Atomics/wake/count-tointeger-throws-then-wake-throws.js
js/src/tests/test262/built-ins/Atomics/wake/descriptor.js
js/src/tests/test262/built-ins/Atomics/wake/length.js
js/src/tests/test262/built-ins/Atomics/wake/name.js
js/src/tests/test262/built-ins/Atomics/wake/negative-count.js
js/src/tests/test262/built-ins/Atomics/wake/negative-index-throws.js
js/src/tests/test262/built-ins/Atomics/wake/non-int32-typedarray-throws.js
js/src/tests/test262/built-ins/Atomics/wake/non-shared-bufferdata-throws.js
js/src/tests/test262/built-ins/Atomics/wake/non-shared-bufferdatate-non-shared-int-views.js
js/src/tests/test262/built-ins/Atomics/wake/non-shared-int-views.js
js/src/tests/test262/built-ins/Atomics/wake/non-views.js
js/src/tests/test262/built-ins/Atomics/wake/not-a-typedarray-throws.js
js/src/tests/test262/built-ins/Atomics/wake/not-an-object-throws.js
js/src/tests/test262/built-ins/Atomics/wake/null-bufferdata-throws.js
js/src/tests/test262/built-ins/Atomics/wake/out-of-range-index-throws.js
js/src/tests/test262/built-ins/Atomics/wake/shared-nonint-views.js
js/src/tests/test262/built-ins/Atomics/wake/shell.js
js/src/tests/test262/built-ins/Atomics/wake/symbol-for-index-throws.js
js/src/tests/test262/built-ins/Atomics/wake/undefined-index-defaults-to-zero.js
js/src/tests/test262/built-ins/Atomics/wake/wake-all-on-loc.js
js/src/tests/test262/built-ins/Atomics/wake/wake-all.js
js/src/tests/test262/built-ins/Atomics/wake/wake-in-order-one-time.js
js/src/tests/test262/built-ins/Atomics/wake/wake-in-order.js
js/src/tests/test262/built-ins/Atomics/wake/wake-nan.js
js/src/tests/test262/built-ins/Atomics/wake/wake-one.js
js/src/tests/test262/built-ins/Atomics/wake/wake-rewake-noop.js
js/src/tests/test262/built-ins/Atomics/wake/wake-two.js
js/src/tests/test262/built-ins/Atomics/wake/wake-with-no-agents-waiting.js
js/src/tests/test262/built-ins/Atomics/wake/wake-with-no-matching-agents-waiting.js
js/src/tests/test262/built-ins/Atomics/wake/wake-zero.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A1_T1.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A1_T2.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A1_T3.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A1_T4.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A1_T5.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A2_T2.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A2_T3.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A2_T4.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A2_T5.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A3_T1.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A3_T2.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A3_T3.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A3_T4.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A4_T1.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A4_T2.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A4_T3.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A4_T4.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A5_T1.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A5_T2.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A5_T3.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A5_T4.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A6_T1.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A6_T2.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A6_T3.js
js/src/tests/test262/built-ins/RegExp/S15.10.2.12_A6_T4.js
js/src/tests/test262/built-ins/RegExp/property-escapes/binary-properties-with-value.js
js/src/tests/test262/built-ins/RegExp/property-escapes/grammar-extensions.js
js/src/tests/test262/built-ins/RegExp/property-escapes/loose-matching.js
js/src/tests/test262/built-ins/RegExp/property-escapes/non-binary-properties-without-value.js
js/src/tests/test262/built-ins/RegExp/property-escapes/non-existent-properties.js
js/src/tests/test262/built-ins/RegExp/property-escapes/non-existent-property-values.js
js/src/tests/test262/built-ins/RegExp/property-escapes/unsupported-binary-properties.js
js/src/tests/test262/built-ins/RegExp/property-escapes/unsupported-properties.js
js/src/tests/test262/harness/assert-throws-early-incorrect-ctor.js
js/src/tests/test262/harness/assert-throws-early-not-early.js
js/src/tests/test262/harness/assert-throws-early-referenceerror.js
js/src/tests/test262/harness/assert-throws-early-syntaxerror.js
js/src/tests/test262/language/directive-prologue/10.1.1-15-s.js
js/src/tests/test262/language/directive-prologue/10.1.1-16-s.js
js/src/tests/test262/language/directive-prologue/10.1.1-17-s.js
js/src/tests/test262/language/directive-prologue/10.1.1-18-s.js
js/src/tests/test262/language/directive-prologue/10.1.1-19-s.js
js/src/tests/test262/language/directive-prologue/10.1.1-2-s.js
js/src/tests/test262/language/directive-prologue/10.1.1-20-s.js
js/src/tests/test262/language/directive-prologue/10.1.1-21-s.js
js/src/tests/test262/language/directive-prologue/10.1.1-22-s.js
js/src/tests/test262/language/directive-prologue/10.1.1-23-s.js
js/src/tests/test262/language/directive-prologue/10.1.1-24-s.js
js/src/tests/test262/language/directive-prologue/10.1.1-25-s.js
js/src/tests/test262/language/directive-prologue/10.1.1-26-s.js
js/src/tests/test262/language/directive-prologue/10.1.1-27-s.js
js/src/tests/test262/language/literals/string/7.8.4-1-s.js
js/src/tests/test262/language/literals/string/7.8.4-10-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-11-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-12-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-13-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-14-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-15-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-16-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-17-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-18-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-19-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-1gs-strict.js
js/src/tests/test262/language/literals/string/7.8.4-2-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-20-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-21-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-22-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-23-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-24-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-25-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-26-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-27-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-28-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-29-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-3-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-30-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-31-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-32-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-33-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-4-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-5-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-6-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-7-s-strict.js
js/src/tests/test262/language/literals/string/7.8.4-8-s-strict.js
testing/web-platform/tests/resources/chromium/chooser_service.mojom.js
testing/web-platform/tests/resources/chromium/chooser_service.mojom.js.headers
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -629,31 +629,31 @@ name = "ena"
 version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "encoding_c"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "encoding_rs 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "encoding_rs 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "encoding_glue"
 version = "0.1.0"
 dependencies = [
- "encoding_rs 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "encoding_rs 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "nserror 0.1.0",
  "nsstring 0.1.0",
 ]
 
 [[package]]
 name = "encoding_rs"
-version = "0.8.4"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "simd 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "env_logger"
@@ -1453,17 +1453,17 @@ dependencies = [
  "nsstring 0.1.0",
 ]
 
 [[package]]
 name = "nsstring"
 version = "0.1.0"
 dependencies = [
  "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "encoding_rs 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "encoding_rs 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "nsstring-gtest"
 version = "0.1.0"
 dependencies = [
  "nsstring 0.1.0",
 ]
@@ -2679,17 +2679,17 @@ dependencies = [
 "checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a"
 "checksum docopt 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d8acd393692c503b168471874953a2531df0e9ab77d0b6bbc582395743300a4a"
 "checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
 "checksum dtoa-short 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "068d4026697c1a18f0b0bb8cfcad1b0c151b90d8edb9bf4c235ad68128920d1d"
 "checksum dwrote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b26e30aaa6bf31ec830db15fec14ed04f0f2ecfcc486ecfce88c55d3389b237f"
 "checksum either 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18785c1ba806c258137c937e44ada9ee7e69a37e3c72077542cd2f069d78562a"
 "checksum ena 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cabe5a5078ac8c506d3e4430763b1ba9b609b1286913e7d08e581d1c2de9b7e5"
 "checksum encoding_c 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "769ecb8b33323998e482b218c0d13cd64c267609023b4b7ec3ee740714c318ee"
-"checksum encoding_rs 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88a1b66a0d28af4b03a8c8278c6dcb90e6e600d89c14500a9e7a02e64b9ee3ac"
+"checksum encoding_rs 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2a91912d6f37c6a8fef8a2316a862542d036f13c923ad518b5aca7bcaac7544c"
 "checksum env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0561146661ae44c579e993456bc76d11ce1e0c7d745e57b2fa7146b6e49fa2ad"
 "checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
 "checksum euclid 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "70a2ebdf55fb9d6329046e026329a55ef8fbaae5ea833f56e170beb3125a8a5f"
 "checksum failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7efb22686e4a466b1ec1a15c2898f91fa9cb340452496dca654032de20ff95b9"
 "checksum failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "946d0e98a50d9831f5d589038d2ca7f8f455b1c21028c0db0e84116a12696426"
 "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.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1483,20 +1483,23 @@ pref("browser.ping-centre.telemetry", tr
 pref("browser.ping-centre.log", false);
 pref("browser.ping-centre.staging.endpoint", "https://onyx_tiles.stage.mozaws.net/v3/links/ping-centre");
 pref("browser.ping-centre.production.endpoint", "https://tiles.services.mozilla.com/v3/links/ping-centre");
 
 // Enable GMP support in the addon manager.
 pref("media.gmp-provider.enabled", true);
 
 pref("browser.contentblocking.enabled", true);
+pref("browser.contentblocking.cookies-site-data.ui.reject-trackers.recommended", true);
 #ifdef NIGHTLY_BUILD
 pref("browser.contentblocking.ui.enabled", true);
+pref("browser.contentblocking.cookies-site-data.ui.reject-trackers.enabled", true);
 #else
 pref("browser.contentblocking.ui.enabled", false);
+pref("browser.contentblocking.cookies-site-data.ui.reject-trackers.enabled", false);
 #endif
 #ifdef NIGHTLY_BUILD
 pref("browser.contentblocking.reportBreakage.enabled", true);
 #else
 pref("browser.contentblocking.reportBreakage.enabled", false);
 #endif
 pref("browser.contentblocking.reportBreakage.url", "https://tracking-protection-issues.herokuapp.com/new");
 
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -194,25 +194,29 @@
                               command="cmd_CustomizeToolbars"/>
                   </menupopup>
                 </menu>
                 <menu id="viewSidebarMenuMenu"
                       label="&viewSidebarMenu.label;"
                       accesskey="&viewSidebarMenu.accesskey;">
                   <menupopup id="viewSidebarMenu">
                     <menuitem id="menu_bookmarksSidebar"
+                              type="checkbox"
                               key="viewBookmarksSidebarKb"
-                              observes="viewBookmarksSidebar"/>
+                              oncommand="SidebarUI.toggle('viewBookmarksSidebar');"
+                              label="&bookmarksButton.label;"/>
                     <menuitem id="menu_historySidebar"
+                              type="checkbox"
                               key="key_gotoHistory"
-                              observes="viewHistorySidebar"
+                              oncommand="SidebarUI.toggle('viewHistorySidebar');"
                               label="&historyButton.label;"/>
                     <menuitem id="menu_tabsSidebar"
+                              type="checkbox"
                               class="sync-ui-item"
-                              observes="viewTabsSidebar"
+                              oncommand="SidebarUI.toggle('viewTabsSidebar');"
                               label="&syncedTabs.sidebar.label;"/>
                   </menupopup>
                 </menu>
                 <menuseparator/>
                 <menu id="viewFullZoomMenu" label="&fullZoom.label;"
                       accesskey="&fullZoom.accesskey;"
                       onpopupshowing="FullZoom.updateMenu();">
                   <menupopup>
@@ -480,35 +484,35 @@
                         key="key_openAddons"
                         command="Tools:Addons"/>
 
               <!-- only one of sync-setup, sync-unverifieditem, sync-syncnowitem or sync-reauthitem will be showing at once -->
               <menuitem id="sync-setup"
                         class="sync-ui-item"
                         label="&syncSignIn.label;"
                         accesskey="&syncSignIn.accesskey;"
-                        observes="sync-setup-state"
+                        hidden="true"
                         oncommand="gSync.openPrefs('menubar')"/>
               <menuitem id="sync-unverifieditem"
                         class="sync-ui-item"
                         label="&syncSignIn.label;"
                         accesskey="&syncSignIn.accesskey;"
-                        observes="sync-unverified-state"
+                        hidden="true"
                         oncommand="gSync.openPrefs('menubar')"/>
               <menuitem id="sync-syncnowitem"
                         class="sync-ui-item"
                         label="&syncSyncNowItem.label;"
                         accesskey="&syncSyncNowItem.accesskey;"
-                        observes="sync-syncnow-state"
+                        hidden="true"
                         oncommand="gSync.doSync(event);"/>
               <menuitem id="sync-reauthitem"
                         class="sync-ui-item"
                         label="&syncReAuthItem.label;"
                         accesskey="&syncReAuthItem.accesskey;"
-                        observes="sync-reauth-state"
+                        hidden="true"
                         oncommand="gSync.openSignInAgainPage('menubar');"/>
               <menuseparator id="devToolsSeparator"/>
               <menu id="webDeveloperMenu"
                     label="&webDeveloperMenu.label;"
                     accesskey="&webDeveloperMenu.accesskey;">
                 <menupopup id="menuWebDeveloperPopup">
                   <menuitem id="menu_pageSource"
                             label="&pageSourceCmd.label;"
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -117,45 +117,20 @@
              label="&zoomWindow.label;"
              oncommand="zoomWindow();" />
 #endif
   </commandset>
 
 #include ../../components/places/content/placesCommands.inc.xul
 
   <broadcasterset id="mainBroadcasterSet">
-    <broadcaster id="viewBookmarksSidebar" autoCheck="false" label="&bookmarksButton.label;"
-                 type="checkbox" group="sidebar"
-                 sidebarurl="chrome://browser/content/places/bookmarksSidebar.xul"
-                 oncommand="SidebarUI.toggle('viewBookmarksSidebar');"/>
-
-    <broadcaster id="viewHistorySidebar" autoCheck="false" sidebartitle="&historyButton.label;"
-                 type="checkbox" group="sidebar"
-                 sidebarurl="chrome://browser/content/places/historySidebar.xul"
-                 oncommand="SidebarUI.toggle('viewHistorySidebar');"/>
-
     <broadcaster id="isImage"/>
     <broadcaster id="canViewSource"/>
     <broadcaster id="isFrameImage"/>
 
-    <!-- Sync broadcasters -->
-    <!-- A broadcaster of a number of attributes suitable for "sync now" UI -
-        A 'syncstatus' attribute is set while actively syncing, and the label
-        attribute which changes from "sync now" to "syncing" etc. -->
-    <broadcaster id="sync-status" onmouseover="gSync.refreshSyncButtonsTooltip();"/>
-    <!-- broadcasters of the "hidden" attribute to reflect setup state for
-         menus -->
-    <broadcaster id="sync-setup-state" hidden="true"/>
-    <broadcaster id="sync-unverified-state" hidden="true"/>
-    <broadcaster id="sync-syncnow-state" hidden="true"/>
-    <broadcaster id="sync-reauth-state" hidden="true"/>
-    <broadcaster id="viewTabsSidebar" autoCheck="false" sidebartitle="&syncedTabs.sidebar.label;"
-                 type="checkbox" group="sidebar"
-                 sidebarurl="chrome://browser/content/syncedtabs/sidebar.xhtml"
-                 oncommand="SidebarUI.toggle('viewTabsSidebar');"/>
     <broadcaster id="workOfflineMenuitemState"/>
     <broadcaster id="devtoolsMenuBroadcaster_RecordExecution"
                  label="&devtoolsRecordExecution.label;"
                  command="Tools:RecordExecution"/>
     <broadcaster id="devtoolsMenuBroadcaster_SaveRecording"
                  label="&devtoolsSaveRecording.label;"
                  command="Tools:SaveRecording"/>
     <broadcaster id="devtoolsMenuBroadcaster_ReplayExecution"
@@ -284,37 +259,37 @@
     <key id="addBookmarkAsKb" key="&bookmarkThisPageCmd.commandkey;" command="Browser:AddBookmarkAs" modifiers="accel"/>
 # Accel+Shift+A-F are reserved on GTK
 #ifndef MOZ_WIDGET_GTK
     <key id="bookmarkAllTabsKb" key="&bookmarkThisPageCmd.commandkey;" oncommand="PlacesCommandHook.bookmarkPages(PlacesCommandHook.uniqueCurrentPages);" modifiers="accel,shift"/>
     <key id="manBookmarkKb" key="&bookmarksCmd.commandkey;" command="Browser:ShowAllBookmarks" modifiers="accel,shift"/>
 #else
     <key id="manBookmarkKb" key="&bookmarksGtkCmd.commandkey;" command="Browser:ShowAllBookmarks" modifiers="accel,shift"/>
 #endif
-    <key id="viewBookmarksSidebarKb" key="&bookmarksCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
+    <key id="viewBookmarksSidebarKb" key="&bookmarksCmd.commandkey;" oncommand="SidebarUI.toggle('viewBookmarksSidebar');" modifiers="accel"/>
 #ifdef XP_WIN
 # Cmd+I is conventially mapped to Info on MacOS X, thus it should not be
 # overridden for other purposes there.
-    <key id="viewBookmarksSidebarWinKb" key="&bookmarksWinCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
+    <key id="viewBookmarksSidebarWinKb" key="&bookmarksWinCmd.commandkey;" oncommand="SidebarUI.toggle('viewBookmarksSidebar');" modifiers="accel"/>
 #endif
 
     <key id="key_stop" keycode="VK_ESCAPE" command="Browser:Stop"/>
 
 #ifdef XP_MACOSX
     <key id="key_stop_mac" modifiers="accel" key="&stopCmd.macCommandKey;" command="Browser:Stop"/>
 #endif
 
     <key id="key_gotoHistory"
          key="&historySidebarCmd.commandKey;"
 #ifdef XP_MACOSX
          modifiers="accel,shift"
 #else
          modifiers="accel"
 #endif
-         command="viewHistorySidebar"/>
+         oncommand="SidebarUI.toggle('viewHistorySidebar');"/>
 
     <key id="key_fullZoomReduce"  key="&fullZoomReduceCmd.commandkey;"   command="cmd_fullZoomReduce"  modifiers="accel"/>
     <key                          key="&fullZoomReduceCmd.commandkey2;"  command="cmd_fullZoomReduce"  modifiers="accel"/>
     <key id="key_fullZoomEnlarge" key="&fullZoomEnlargeCmd.commandkey;"  command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key                          key="&fullZoomEnlargeCmd.commandkey2;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key                          key="&fullZoomEnlargeCmd.commandkey3;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key id="key_fullZoomReset"   key="&fullZoomResetCmd.commandkey;"    command="cmd_fullZoomReset"   modifiers="accel"/>
     <key                          key="&fullZoomResetCmd.commandkey2;"   command="cmd_fullZoomReset"   modifiers="accel"/>
--- a/browser/base/content/browser-sidebar.js
+++ b/browser/base/content/browser-sidebar.js
@@ -1,30 +1,45 @@
 /* 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/. */
 
 /**
  * SidebarUI controls showing and hiding the browser sidebar.
- *
- * @note
- * Some of these methods take a commandID argument - we expect to find a
- * xul:broadcaster element with the specified ID.
- * The following attributes on that element may be used and/or modified:
- *  - id           (required) the string to match commandID. The convention
- *                 is to use this naming scheme: 'view<sidebar-name>Sidebar'.
- *  - sidebarurl   (required) specifies the URL to load in this sidebar.
- *  - sidebartitle or label (in that order) specify the title to
- *                 display on the sidebar.
- *  - checked      indicates whether the sidebar is currently displayed.
- *                 Note that this attribute is updated when
- *                 the sidebar's visibility is changed.
- *  - group        this attribute must be set to "sidebar".
  */
 var SidebarUI = {
+  get sidebars() {
+    if (this._sidebars) {
+      return this._sidebars;
+    }
+    return this._sidebars = new Map([
+      ["viewBookmarksSidebar", {
+        title: document.getElementById("sidebar-switcher-bookmarks")
+                       .getAttribute("label"),
+        url: "chrome://browser/content/places/bookmarksSidebar.xul",
+        menuId: "menu_bookmarksSidebar",
+        buttonId: "sidebar-switcher-bookmarks",
+      }],
+      ["viewHistorySidebar", {
+        title: document.getElementById("sidebar-switcher-history")
+                       .getAttribute("label"),
+        url: "chrome://browser/content/places/historySidebar.xul",
+        menuId: "menu_historySidebar",
+        buttonId: "sidebar-switcher-history",
+      }],
+      ["viewTabsSidebar", {
+        title: document.getElementById("sidebar-switcher-tabs")
+                       .getAttribute("label"),
+        url: "chrome://browser/content/syncedtabs/sidebar.xhtml",
+        menuId: "menu_tabsSidebar",
+        buttonId: "sidebar-switcher-tabs",
+      }],
+    ]);
+  },
+
   // Avoid getting the browser element from init() to avoid triggering the
   // <browser> constructor during startup if the sidebar is hidden.
   get browser() {
     if (this._browser)
       return this._browser;
     return this._browser = document.getElementById("sidebar");
   },
   POSITION_START_PREF: "sidebar.position_start",
@@ -221,17 +236,17 @@ var SidebarUI = {
 
     if (sourceUI._box.hidden) {
       // just hidden means we have adopted the hidden state.
       return true;
     }
 
     // dynamically generated sidebars will fail this check, but we still
     // consider it adopted.
-    if (!document.getElementById(commandID)) {
+    if (!this.sidebars.has(commandID)) {
       return true;
     }
 
     this._box.setAttribute("width", sourceUI._box.boxObject.width);
     this.showInitially(commandID);
 
     return true;
   },
@@ -261,17 +276,17 @@ var SidebarUI = {
 
     // If we're not adopting settings from a parent window, set them now.
     let wasOpen = this._box.getAttribute("checked");
     if (!wasOpen) {
       return;
     }
 
     let commandID = this._box.getAttribute("sidebarcommand");
-    if (commandID && document.getElementById(commandID)) {
+    if (commandID && this.sidebars.has(commandID)) {
       this.showInitially(commandID);
     } else {
       this._box.removeAttribute("checked");
       // Remove the |sidebarcommand| attribute, because the element it
       // refers to no longer exists, so we should assume this sidebar
       // panel has been uninstalled. (249883)
       // We use setAttribute rather than removeAttribute so it persists
       // correctly.
@@ -309,169 +324,138 @@ var SidebarUI = {
   /**
    * True if the sidebar is currently open.
    */
   get isOpen() {
     return !this._box.hidden;
   },
 
   /**
-   * The ID of the current sidebar (ie, the ID of the broadcaster being used).
+   * The ID of the current sidebar.
    */
   get currentID() {
     return this.isOpen ? this._box.getAttribute("sidebarcommand") : "";
   },
 
   get title() {
     return this._title.value;
   },
 
   set title(value) {
     this._title.value = value;
   },
 
-  getBroadcasterById(id) {
-    let sidebarBroadcaster = document.getElementById(id);
-    if (sidebarBroadcaster && sidebarBroadcaster.localName == "broadcaster") {
-      return sidebarBroadcaster;
-    }
-    return null;
-  },
-
   /**
    * Toggle the visibility of the sidebar. If the sidebar is hidden or is open
    * with a different commandID, then the sidebar will be opened using the
    * specified commandID. Otherwise the sidebar will be hidden.
    *
-   * @param  {string}  commandID     ID of the xul:broadcaster element to use.
+   * @param  {string}  commandID     ID of the sidebar.
    * @param  {DOMNode} [triggerNode] Node, usually a button, that triggered the
    *                                 visibility toggling of the sidebar.
    * @return {Promise}
    */
   toggle(commandID = this.lastOpenedId, triggerNode) {
     // First priority for a default value is this.lastOpenedId which is set during show()
     // and not reset in hide(), unlike currentID. If show() hasn't been called and we don't
     // have a persisted command either, or the command doesn't exist anymore, then
     // fallback to a default sidebar.
     if (!commandID) {
       commandID = this._box.getAttribute("sidebarcommand");
     }
-    if (!commandID || !this.getBroadcasterById(commandID)) {
+    if (!commandID || !this.sidebars.has(commandID)) {
       commandID = this.DEFAULT_SIDEBAR_ID;
     }
 
     if (this.isOpen && commandID == this.currentID) {
       this.hide(triggerNode);
       return Promise.resolve();
     }
     return this.show(commandID, triggerNode);
   },
 
-  _loadSidebarExtension(sidebarBroadcaster) {
-    let extensionId = sidebarBroadcaster.getAttribute("extensionId");
+  _loadSidebarExtension(commandID) {
+    let sidebar = this.sidebars.get(commandID);
+    let {extensionId} = sidebar;
     if (extensionId) {
-      let extensionUrl = sidebarBroadcaster.getAttribute("panel");
-      let browserStyle = sidebarBroadcaster.getAttribute("browserStyle");
-      SidebarUI.browser.contentWindow.loadPanel(extensionId, extensionUrl, browserStyle);
+      SidebarUI.browser.contentWindow.loadPanel(extensionId, sidebar.panel,
+                                                sidebar.browserStyle);
     }
   },
 
   /**
-   * Show the sidebar, using the parameters from the specified broadcaster.
-   * @see SidebarUI note.
+   * Show the sidebar.
    *
    * This wraps the internal method, including a ping to telemetry.
    *
-   * @param {string}  commandID     ID of the xul:broadcaster element to use.
+   * @param {string}  commandID     ID of the sidebar to use.
    * @param {DOMNode} [triggerNode] Node, usually a button, that triggered the
    *                                showing of the sidebar.
    */
   show(commandID, triggerNode) {
-    return this._show(commandID).then((sidebarBroadcaster) => {
-      this._loadSidebarExtension(sidebarBroadcaster);
+    return this._show(commandID).then(() => {
+      this._loadSidebarExtension(commandID);
 
       if (triggerNode) {
         updateToggleControlLabel(triggerNode);
       }
 
       this._fireFocusedEvent();
     });
   },
 
   /**
    * Show the sidebar, without firing the focused event or logging telemetry.
    * This is intended to be used when the sidebar is opened automatically
    * when a window opens (not triggered by user interaction).
    *
-   * @param {string} commandID ID of the xul:broadcaster element to use.
+   * @param {string} commandID ID of the sidebar.
    */
   showInitially(commandID) {
-    return this._show(commandID).then((sidebarBroadcaster) => {
-      this._loadSidebarExtension(sidebarBroadcaster);
+    return this._show(commandID).then(() => {
+      this._loadSidebarExtension(commandID);
     });
   },
 
   /**
    * Implementation for show. Also used internally for sidebars that are shown
    * when a window is opened and we don't want to ping telemetry.
    *
-   * @param {string} commandID ID of the xul:broadcaster element to use.
+   * @param {string} commandID ID of the sidebar.
    */
   _show(commandID) {
-    return new Promise((resolve, reject) => {
-      let sidebarBroadcaster = this.getBroadcasterById(commandID);
-      if (!sidebarBroadcaster) {
-        reject(new Error("Invalid sidebar broadcaster specified: " + commandID));
-        return;
-      }
-
-      let broadcasters = document.querySelectorAll("broadcaster[group=sidebar]");
-      for (let broadcaster of broadcasters) {
-        if (broadcaster != sidebarBroadcaster) {
-          broadcaster.removeAttribute("checked");
-        } else {
-          sidebarBroadcaster.setAttribute("checked", "true");
-        }
-      }
+    return new Promise(resolve => {
+      this.selectMenuItem(commandID);
 
       this._box.hidden = this._splitter.hidden = false;
       this.setPosition();
 
       this.hideSwitcherPanel();
 
       this._box.setAttribute("checked", "true");
-      this._box.setAttribute("sidebarcommand", sidebarBroadcaster.id);
-      this.lastOpenedId = sidebarBroadcaster.id;
-
-      let title = sidebarBroadcaster.getAttribute("sidebartitle") ||
-                  sidebarBroadcaster.getAttribute("label");
+      this._box.setAttribute("sidebarcommand", commandID);
+      this.lastOpenedId = commandID;
 
-      // When loading a web page in the sidebar there is no title set on the
-      // broadcaster, as it is instead set by openWebPanel. Don't clear out
-      // the title in this case.
-      if (title) {
-        this.title = title;
-      }
-
-      let url = sidebarBroadcaster.getAttribute("sidebarurl");
+      let {url, title} = this.sidebars.get(commandID);
+      this.title = title;
       this.browser.setAttribute("src", url); // kick off async load
 
       if (this.browser.contentDocument.location.href != url) {
         this.browser.addEventListener("load", event => {
           // We're handling the 'load' event before it bubbles up to the usual
           // (non-capturing) event handlers. Let it bubble up before resolving.
           setTimeout(() => {
-            resolve(sidebarBroadcaster);
+            resolve();
 
             // Now that the currentId is updated, fire a show event.
             this._fireShowEvent();
           }, 0);
         }, {capture: true, once: true});
       } else {
-        resolve(sidebarBroadcaster);
+        resolve();
 
         // Now that the currentId is updated, fire a show event.
         this._fireShowEvent();
       }
 
       let selBrowser = gBrowser.selectedBrowser;
       selBrowser.messageManager.sendAsyncMessage("Sidebar:VisibilityChange",
         {commandID, isOpen: true}
@@ -488,43 +472,56 @@ var SidebarUI = {
   hide(triggerNode) {
     if (!this.isOpen) {
       return;
     }
 
     this.hideSwitcherPanel();
 
     let commandID = this._box.getAttribute("sidebarcommand");
-    let sidebarBroadcaster = document.getElementById(commandID);
-
-    if (sidebarBroadcaster.getAttribute("checked") != "true") {
-      return;
-    }
+    this.selectMenuItem("");
 
     // Replace the document currently displayed in the sidebar with about:blank
     // so that we can free memory by unloading the page. We need to explicitly
     // create a new content viewer because the old one doesn't get destroyed
     // until about:blank has loaded (which does not happen as long as the
     // element is hidden).
     this.browser.setAttribute("src", "about:blank");
     this.browser.docShell.createAboutBlankContentViewer(null);
 
-    sidebarBroadcaster.removeAttribute("checked");
     this._box.removeAttribute("checked");
     this._box.hidden = this._splitter.hidden = true;
 
     let selBrowser = gBrowser.selectedBrowser;
     selBrowser.focus();
     selBrowser.messageManager.sendAsyncMessage("Sidebar:VisibilityChange",
       {commandID, isOpen: false}
     );
     if (triggerNode) {
       updateToggleControlLabel(triggerNode);
     }
   },
+
+  /**
+   * Sets the checked state only on the menu items of the specified sidebar, or
+   * none if the argument is an empty string.
+   */
+  selectMenuItem(commandID) {
+    for (let [id, {menuId, buttonId}] of this.sidebars) {
+      let menu = document.getElementById(menuId);
+      let button = document.getElementById(buttonId);
+      if (id == commandID) {
+        menu.setAttribute("checked", "true");
+        button.setAttribute("checked", "true");
+      } else {
+        menu.removeAttribute("checked");
+        button.removeAttribute("checked");
+      }
+    }
+  },
 };
 
 // Add getters related to the position here, since we will want them
 // available for both startDelayedLoad and init.
 XPCOMUtils.defineLazyPreferenceGetter(SidebarUI, "_positionStart",
   SidebarUI.POSITION_START_PREF, true, SidebarUI.setPosition.bind(SidebarUI));
 XPCOMUtils.defineLazyGetter(SidebarUI, "isRTL", () => {
   return getComputedStyle(document.documentElement).direction == "rtl";
--- a/browser/base/content/browser-sync.js
+++ b/browser/base/content/browser-sync.js
@@ -116,28 +116,30 @@ var gSync = {
 
     this._definePrefGetters();
 
     if (!this.SYNC_ENABLED) {
       this.onSyncDisabled();
       return;
     }
 
-    // initial label for the sync buttons.
-    let statusBroadcaster = document.getElementById("sync-status");
-    if (!statusBroadcaster) {
+    // Label for the sync buttons, also set on the icon for accessibility.
+    let syncIcon = document.getElementById("appMenu-fxa-icon");
+    if (!syncIcon) {
       // We are in a window without our elements - just abort now, without
       // setting this._initialized, so we don't attempt to remove observers.
       return;
     }
-    statusBroadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncnow.label"));
-    // We start with every broadcasters hidden, so that we don't need to init
+    let syncNow = document.getElementById("PanelUI-remotetabs-syncnow");
+    let label = this.syncStrings.GetStringFromName("syncnow.label");
+    syncIcon.setAttribute("label", label);
+    syncNow.setAttribute("label", label);
+    // We start with every menuitem hidden, so that we don't need to init
     // the sync UI on windows like pageInfo.xul (see bug 1384856).
-    let setupBroadcaster = document.getElementById("sync-setup-state");
-    setupBroadcaster.hidden = false;
+    document.getElementById("sync-setup").hidden = false;
 
     for (let topic of this._obs) {
       Services.obs.addObserver(this, topic, true);
     }
 
     this._generateNodeGetters();
 
     this._maybeUpdateUIState();
@@ -180,17 +182,17 @@ var gSync = {
         }
         this.onClientsSynced();
         break;
     }
   },
 
   updateAllUI(state) {
     this.updatePanelPopup(state);
-    this.updateStateBroadcasters(state);
+    this.updateState(state);
     this.updateSyncButtonsTooltip(state);
     this.updateSyncStatus(state);
   },
 
   updatePanelPopup(state) {
     let defaultLabel = this.appMenuStatus.getAttribute("defaultlabel");
     // The localization string is for the signed in text, but it's the default text as well
     let defaultTooltiptext = this.appMenuStatus.getAttribute("signedinTooltiptext");
@@ -238,40 +240,35 @@ var gSync = {
         if (this.appMenuAvatar.style.listStyleImage === bgImage) {
           this.appMenuAvatar.style.removeProperty("list-style-image");
         }
       };
       img.src = state.avatarURL;
     }
   },
 
-  updateStateBroadcasters(state) {
-    const status = state.status;
-
-    // Start off with a clean slate
-    document.getElementById("sync-reauth-state").hidden = true;
-    document.getElementById("sync-setup-state").hidden = true;
-    document.getElementById("sync-syncnow-state").hidden = true;
-    document.getElementById("sync-unverified-state").hidden = true;
-
-    if (status == UIState.STATUS_LOGIN_FAILED) {
-      // unhiding this element makes the menubar show the login failure state.
-      document.getElementById("sync-reauth-state").hidden = false;
-    } else if (status == UIState.STATUS_NOT_CONFIGURED) {
-      document.getElementById("sync-setup-state").hidden = false;
-    } else if (status == UIState.STATUS_NOT_VERIFIED) {
-      document.getElementById("sync-unverified-state").hidden = false;
-    } else {
-      document.getElementById("sync-syncnow-state").hidden = false;
+  updateState(state) {
+    for (let [status, menuId, boxId] of [
+      [UIState.STATUS_NOT_CONFIGURED, "sync-setup",
+                                      "PanelUI-remotetabs-setupsync"],
+      [UIState.STATUS_LOGIN_FAILED,   "sync-reauthitem",
+                                      "PanelUI-remotetabs-reauthsync"],
+      [UIState.STATUS_NOT_VERIFIED,   "sync-unverifieditem",
+                                      "PanelUI-remotetabs-unverified"],
+      [UIState.STATUS_SIGNED_IN,      "sync-syncnowitem",
+                                      "PanelUI-remotetabs-main"],
+    ]) {
+      document.getElementById(menuId).hidden =
+        document.getElementById(boxId).hidden = (status != state.status);
     }
   },
 
   updateSyncStatus(state) {
-    const broadcaster = document.getElementById("sync-status");
-    const syncingUI = broadcaster.getAttribute("syncstatus") == "active";
+    let syncNow = document.getElementById("PanelUI-remotetabs-syncnow");
+    const syncingUI = syncNow.getAttribute("syncstatus") == "active";
     if (state.syncing != syncingUI) { // Do we need to update the UI?
       state.syncing ? this.onActivityStart() : this.onActivityStop();
     }
   },
 
   onMenuPanelCommand() {
     switch (this.appMenuContainer.getAttribute("fxastatus")) {
     case "signedin":
@@ -563,29 +560,39 @@ var gSync = {
                                            "disabled", !enabled || null);
   },
 
   // Functions called by observers
   onActivityStart() {
     clearTimeout(this._syncAnimationTimer);
     this._syncStartTime = Date.now();
 
-    let broadcaster = document.getElementById("sync-status");
-    broadcaster.setAttribute("syncstatus", "active");
-    broadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncingtabs.label"));
-    broadcaster.setAttribute("disabled", "true");
+    let label = this.syncStrings.GetStringFromName("syncingtabs.label");
+    let syncIcon = document.getElementById("appMenu-fxa-icon");
+    let syncNow = document.getElementById("PanelUI-remotetabs-syncnow");
+    syncIcon.setAttribute("syncstatus", "active");
+    syncIcon.setAttribute("label", label);
+    syncIcon.setAttribute("disabled", "true");
+    syncNow.setAttribute("syncstatus", "active");
+    syncNow.setAttribute("label", label);
+    syncNow.setAttribute("disabled", "true");
   },
 
   _onActivityStop() {
     if (!gBrowser)
       return;
-    let broadcaster = document.getElementById("sync-status");
-    broadcaster.removeAttribute("syncstatus");
-    broadcaster.removeAttribute("disabled");
-    broadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncnow.label"));
+    let label = this.syncStrings.GetStringFromName("syncnow.label");
+    let syncIcon = document.getElementById("appMenu-fxa-icon");
+    let syncNow = document.getElementById("PanelUI-remotetabs-syncnow");
+    syncIcon.removeAttribute("syncstatus");
+    syncIcon.removeAttribute("disabled");
+    syncIcon.setAttribute("label", label);
+    syncNow.removeAttribute("syncstatus");
+    syncNow.removeAttribute("disabled");
+    syncNow.setAttribute("label", label);
     Services.obs.notifyObservers(null, "test:browser-sync:activity-stop");
   },
 
   onActivityStop() {
     let now = Date.now();
     let syncDuration = now - this._syncStartTime;
 
     if (syncDuration < MIN_STATUS_ANIMATION_DURATION) {
@@ -640,18 +647,17 @@ var gSync = {
     }
   },
 
   refreshSyncButtonsTooltip() {
     const state = UIState.get();
     this.updateSyncButtonsTooltip(state);
   },
 
-  /* Update the tooltip for the sync-status broadcaster (which will update the
-     Sync Toolbar button and the Sync spinner in the FxA hamburger area.)
+  /* Update the tooltip for the sync icon in the main menu and in Synced Tabs.
      If Sync is configured, the tooltip is when the last sync occurred,
      otherwise the tooltip reflects the fact that Sync needs to be
      (re-)configured.
   */
   updateSyncButtonsTooltip(state) {
     const status = state.status;
 
     // This is a little messy as the Sync buttons are 1/2 Sync related and
@@ -667,22 +673,25 @@ var gSync = {
     } else if (status == UIState.STATUS_LOGIN_FAILED) {
       // "need to reconnect/re-enter your password"
       tooltiptext = this.fxaStrings.formatStringFromName("reconnectDescription", [state.email], 1);
     } else {
       // Sync appears configured - format the "last synced at" time.
       tooltiptext = this.formatLastSyncDate(state.lastSync);
     }
 
-    let broadcaster = document.getElementById("sync-status");
-    if (broadcaster) {
+    let syncIcon = document.getElementById("appMenu-fxa-icon");
+    if (syncIcon) {
+      let syncNow = document.getElementById("PanelUI-remotetabs-syncnow");
       if (tooltiptext) {
-        broadcaster.setAttribute("tooltiptext", tooltiptext);
+        syncIcon.setAttribute("tooltiptext", tooltiptext);
+        syncNow.setAttribute("tooltiptext", tooltiptext);
       } else {
-        broadcaster.removeAttribute("tooltiptext");
+        syncIcon.removeAttribute("tooltiptext");
+        syncNow.removeAttribute("tooltiptext");
       }
     }
   },
 
   get relativeTimeFormat() {
     delete this.relativeTimeFormat;
     return this.relativeTimeFormat = new Services.intl.RelativeTimeFormat(undefined, {style: "short"});
   },
@@ -691,22 +700,22 @@ var gSync = {
     if (!date) { // Date can be null before the first sync!
       return null;
     }
     const relativeDateStr = this.relativeTimeFormat.formatBestUnit(date);
     return this.syncStrings.formatStringFromName("lastSync2.label", [relativeDateStr], 1);
   },
 
   onClientsSynced() {
-    let broadcaster = document.getElementById("sync-syncnow-state");
-    if (broadcaster) {
+    let element = document.getElementById("PanelUI-remotetabs-main");
+    if (element) {
       if (Weave.Service.clientsEngine.stats.numClients > 1) {
-        broadcaster.setAttribute("devices-status", "multi");
+        element.setAttribute("devices-status", "multi");
       } else {
-        broadcaster.setAttribute("devices-status", "single");
+        element.setAttribute("devices-status", "single");
       }
     }
   },
 
   onSyncDisabled() {
     const toHide = [...document.querySelectorAll(".sync-ui-item")];
     for (const item of toHide) {
       item.hidden = true;
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -316,37 +316,32 @@ xmlns="http://www.w3.org/1999/xhtml"
            class="cui-widget-panel"
            role="group"
            type="arrow"
            hidden="true"
            flip="slide"
            orient="vertical"
            position="bottomcenter topleft">
       <toolbarbutton id="sidebar-switcher-bookmarks"
+                     type="checkbox"
+                     label="&bookmarksButton.label;"
                      class="subviewbutton subviewbutton-iconic"
                      key="viewBookmarksSidebarKb"
-                     observes="viewBookmarksSidebar"
-                     oncommand="SidebarUI.show('viewBookmarksSidebar');">
-        <observes element="viewBookmarksSidebar" attribute="checked"/>
-      </toolbarbutton>
+                     oncommand="SidebarUI.show('viewBookmarksSidebar');"/>
       <toolbarbutton id="sidebar-switcher-history"
+                     type="checkbox"
                      label="&historyButton.label;"
                      class="subviewbutton subviewbutton-iconic"
                      key="key_gotoHistory"
-                     observes="viewHistorySidebar"
-                     oncommand="SidebarUI.show('viewHistorySidebar');">
-        <observes element="viewHistorySidebar" attribute="checked"/>
-      </toolbarbutton>
+                     oncommand="SidebarUI.show('viewHistorySidebar');"/>
       <toolbarbutton id="sidebar-switcher-tabs"
+                     type="checkbox"
                      label="&syncedTabs.sidebar.label;"
                      class="subviewbutton subviewbutton-iconic sync-ui-item"
-                     observes="viewTabsSidebar"
-                     oncommand="SidebarUI.show('viewTabsSidebar');">
-        <observes element="viewTabsSidebar" attribute="checked"/>
-      </toolbarbutton>
+                     oncommand="SidebarUI.show('viewTabsSidebar');"/>
       <toolbarseparator/>
       <!-- Extension toolbarbuttons go here. -->
       <toolbarseparator id="sidebar-extensions-separator"/>
       <toolbarbutton id="sidebar-reverse-position"
                      class="subviewbutton"
                      oncommand="SidebarUI.reversePosition()"/>
       <toolbarseparator/>
       <toolbarbutton label="&sidebarMenuClose.label;"
@@ -1215,17 +1210,17 @@ xmlns="http://www.w3.org/1999/xhtml"
 
   <deck id="content-deck" flex="1">
     <hbox flex="1" id="browser">
       <vbox id="browser-border-start" hidden="true" layer="true"/>
       <vbox id="sidebar-box" hidden="true" class="chromeclass-extrachrome">
         <sidebarheader id="sidebar-header" align="center">
           <toolbarbutton id="sidebar-switcher-target" flex="1" class="tabbable">
             <image id="sidebar-icon" consumeanchor="sidebar-switcher-target"/>
-            <label id="sidebar-title" persist="value" crop="end" flex="1" control="sidebar"/>
+            <label id="sidebar-title" crop="end" flex="1" control="sidebar"/>
             <image id="sidebar-switcher-arrow"/>
           </toolbarbutton>
           <image id="sidebar-throbber"/>
 # To ensure the button label's intrinsic width doesn't expand the sidebar
 # if the label is long, the button needs flex=1.
 # To ensure the button doesn't expand unnecessarily for short labels, the
 # spacer should significantly out-flex the button.
           <spacer flex="1000"/>
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -381,17 +381,17 @@ support-files =
 [browser_save_link-perwindowpb.js]
 skip-if = (e10s && debug && os == "win") || verify # Bug 1280505
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_save_private_link_perwindowpb.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_save_link_when_window_navigates.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_save_video.js]
-skip-if = (os == 'mac') || (verify && (os == 'mac'))
+skip-if = (os == 'mac') || (verify && (os == 'mac')) || (os == 'win' && debug) || (os =='linux') #Bug 1212419
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_save_video_frame.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_scope.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_contentSearchUI.js]
 support-files =
   contentSearchUI.html
@@ -471,18 +471,18 @@ support-files =
   file_about_child.html
   file_about_parent.html
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_e10s_switchbrowser.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_e10s_about_process.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_e10s_chrome_process.js]
+skip-if = (os == 'linux' && debug) || (os == 'mac' && debug) # Bug 1444565, Bug 1457887
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
-skip-if = (os == 'linux' && debug) || (os == 'osx' && debug) # Bug 1444565
 [browser_e10s_javascript.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_blockHPKP.js]
 skip-if = verify && !debug
 uses-unsafe-cpows = true
 tags = psm
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_windowactivation.js]
--- a/browser/base/content/test/sidebar/browser.ini
+++ b/browser/base/content/test/sidebar/browser.ini
@@ -1,5 +1,6 @@
 [DEFAULT]
 
 [browser_sidebar_adopt.js]
+[browser_sidebar_keys.js]
 [browser_sidebar_move.js]
 [browser_sidebar_switcher.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/sidebar/browser_sidebar_keys.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+async function testSidebarKeyToggle(key, options, expectedSidebarId) {
+  let promiseShown = BrowserTestUtils.waitForEvent(window, "SidebarShown");
+  EventUtils.synthesizeKey(key, options);
+  await promiseShown;
+  Assert.equal(document.getElementById("sidebar-box")
+                       .getAttribute("sidebarcommand"), expectedSidebarId);
+  EventUtils.synthesizeKey(key, options);
+  Assert.ok(!SidebarUI.isOpen);
+}
+
+add_task(async function test_sidebar_keys() {
+  registerCleanupFunction(() => SidebarUI.hide());
+
+  await testSidebarKeyToggle("b", { accelKey: true }, "viewBookmarksSidebar");
+  if (AppConstants.platform == "win") {
+    await testSidebarKeyToggle("i", { accelKey: true }, "viewBookmarksSidebar");
+  }
+
+  let options = { accelKey: true, shiftKey: AppConstants.platform == "macosx" };
+  await testSidebarKeyToggle("h", options, "viewHistorySidebar");
+});
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -104,16 +104,17 @@ support-files =
 [browser_urlbarPlaceholder.js]
 support-files =
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
 [browser_urlbarPrivateBrowsingWindowChange.js]
 [browser_urlbarRaceWithTabs.js]
 [browser_urlbarRevert.js]
 [browser_urlbarSearchFunction.js]
+skip-if = true # Bug 1482494
 [browser_urlbarSearchSingleWordNotification.js]
 [browser_urlbarSearchSuggestions.js]
 support-files =
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
 [browser_urlbarSearchSuggestions_opt-out.js]
 support-files =
   searchSuggestionEngine.xml
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -196,22 +196,19 @@
             <toolbarbutton id="appMenu-fxa-label"
                            class="subviewbutton subviewbutton-iconic"
                            label="&fxaSignIn.label;"
                            fxabrandname="&syncBrand.fxAccount.label;"/>
           </hbox>
           <toolbarseparator orient="vertical"/>
           <toolbarbutton id="appMenu-fxa-icon"
                          class="subviewbutton subviewbutton-iconic"
+                         onmouseover="gSync.refreshSyncButtonsTooltip();"
                          oncommand="gSync.doSync();"
-                         closemenu="none">
-            <observes element="sync-status" attribute="syncstatus"/>
-            <observes element="sync-status" attribute="tooltiptext"/>
-            <observes element="sync-status" attribute="onmouseover"/>
-          </toolbarbutton>
+                         closemenu="none"/>
         </toolbaritem>
         <toolbarseparator class="sync-ui-item"/>
         <toolbaritem closemenu="none">
           <toolbarbutton id="appMenu-tp-label"
                          tooltiptext="&trackingProtection.tooltip;"
                          class="subviewbutton subviewbutton-iconic"
                          oncommand="ContentBlocking.openPreferences('appMenu-trackingprotection'); PanelUI.hide();"
                          label="&trackingProtection.title;"/>
@@ -373,19 +370,17 @@
     </panelview>
     <panelview id="PanelUI-history" flex="1">
       <vbox class="panel-subview-body">
         <toolbarbutton id="appMenuViewHistorySidebar"
                        label="&appMenuHistory.viewSidebar.label;"
                        type="checkbox"
                        class="subviewbutton subviewbutton-iconic"
                        key="key_gotoHistory"
-                       oncommand="SidebarUI.toggle('viewHistorySidebar'); PanelUI.hide();">
-          <observes element="viewHistorySidebar" attribute="checked"/>
-        </toolbarbutton>
+                       oncommand="SidebarUI.toggle('viewHistorySidebar');"/>
         <toolbarbutton id="appMenuClearRecentHistory"
                        label="&appMenuHistory.clearRecent.label;"
                        class="subviewbutton subviewbutton-iconic"
                        command="Tools:Sanitize"/>
         <toolbarseparator/>
         <toolbarbutton id="appMenuRecentlyClosedTabs"
                        label="&historyUndoMenu.label;"
                        class="subviewbutton subviewbutton-iconic subviewbutton-nav"
@@ -416,28 +411,28 @@
     <panelview id="appMenu-library-recentlyClosedTabs"/>
     <panelview id="appMenu-library-recentlyClosedWindows"/>
 
     <panelview id="PanelUI-remotetabs" flex="1" class="PanelUI-subView"
                descriptionheightworkaround="true">
       <vbox class="panel-subview-body">
         <!-- this widget has 3 boxes in the body, but only 1 is ever visible -->
         <!-- When Sync is ready to sync -->
-        <vbox id="PanelUI-remotetabs-main" observes="sync-syncnow-state">
+        <vbox id="PanelUI-remotetabs-main" hidden="true">
           <vbox id="PanelUI-remotetabs-buttons">
             <toolbarbutton id="PanelUI-remotetabs-view-sidebar"
                            class="subviewbutton subviewbutton-iconic"
-                           observes="viewTabsSidebar"
-                           label="&appMenuRemoteTabs.sidebar.label;"/>
+                           label="&appMenuRemoteTabs.sidebar.label;"
+                           oncommand="SidebarUI.toggle('viewTabsSidebar');"/>
             <toolbarbutton id="PanelUI-remotetabs-view-managedevices"
                            class="subviewbutton subviewbutton-iconic"
                            label="&appMenuRemoteTabs.managedevices.label;"
                            oncommand="gSync.openDevicesManagementPage('syncedtabs-menupanel');"/>
             <toolbarbutton id="PanelUI-remotetabs-syncnow"
-                           observes="sync-status"
+                           onmouseover="gSync.refreshSyncButtonsTooltip();"
                            class="subviewbutton subviewbutton-iconic"
                            oncommand="gSync.doSync();"
                            closemenu="none"/>
             <menuseparator id="PanelUI-remotetabs-separator"/>
           </vbox>
           <deck id="PanelUI-remotetabs-deck">
             <!-- Sync is ready to Sync and the "tabs" engine is enabled -->
             <vbox id="PanelUI-remotetabs-tabspane">
@@ -484,42 +479,41 @@
         </vbox>
         <!-- a box to ensure contained boxes are centered horizonally -->
         <hbox pack="center" flex="1">
           <!-- When Sync is not configured -->
           <vbox id="PanelUI-remotetabs-setupsync"
                 flex="1"
                 align="center"
                 class="PanelUI-remotetabs-instruction-box"
-                observes="sync-setup-state">
+                hidden="true">
             <image class="fxaSyncIllustration"/>
             <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
             <toolbarbutton class="PanelUI-remotetabs-button"
                            label="&appMenuRemoteTabs.signin.label;"
                            oncommand="gSync.openPrefs('synced-tabs');"/>
           </vbox>
-          <!-- When Sync needs re-authentication. This uses the exact same messaging
-               as "Sync is not configured" but remains a separate box so we get
-               the goodness of observing broadcasters to manage the hidden states -->
+          <!-- When Sync needs re-authentication -->
           <vbox id="PanelUI-remotetabs-reauthsync"
                 flex="1"
                 align="center"
                 class="PanelUI-remotetabs-instruction-box"
-                observes="sync-reauth-state">
+                hidden="true">
             <image class="fxaSyncIllustrationIssue"/>
             <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
             <toolbarbutton class="PanelUI-remotetabs-button"
                            label="&appMenuRemoteTabs.signin.label;"
                            oncommand="gSync.openPrefs('synced-tabs');"/>
           </vbox>
+          <!-- When Sync needs verification -->
           <vbox id="PanelUI-remotetabs-unverified"
                 flex="1"
                 align="center"
                 class="PanelUI-remotetabs-instruction-box"
-                observes="sync-unverified-state">
+                hidden="true">
             <image class="fxaSyncIllustrationIssue"/>
             <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.unverified.label;</label>
             <toolbarbutton class="PanelUI-remotetabs-button"
                            label="&appMenuRemoteTabs.opensyncprefs.label;"
                            oncommand="gSync.openPrefs('synced-tabs');"/>
           </vbox>
         </hbox>
       </vbox>
--- a/browser/components/enterprisepolicies/tests/browser/browser_policy_cookie_settings.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_cookie_settings.js
@@ -83,30 +83,26 @@ async function test_cookie_settings({
   is(Services.prefs.prefIsLocked("network.cookie.cookieBehavior"), cookieSettingsLocked,
      "Cookie behavior pref lock status should be what is expected");
   is(Services.prefs.prefIsLocked("network.cookie.lifetimePolicy"), cookieSettingsLocked,
      "Cookie lifetime pref lock status should be what is expected");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
   // eslint-disable-next-line no-shadow
   await ContentTask.spawn(tab.linkedBrowser, {cookiesEnabled, cookieSettingsLocked}, async function({cookiesEnabled, cookieSettingsLocked}) {
-    let acceptThirdPartyLabel = content.document.getElementById("acceptThirdPartyLabel");
-    let acceptThirdPartyMenu = content.document.getElementById("acceptThirdPartyMenu");
-    let keepUntilLabel = content.document.getElementById("keepUntil");
-    let keepUntilMenu = content.document.getElementById("keepCookiesUntil");
+    content.setTimeout(() => {
+      let keepUntilLabel = content.document.getElementById("keepUntil");
+      let keepUntilMenu = content.document.getElementById("keepCookiesUntil");
 
-    let expectControlsDisabled = !cookiesEnabled || cookieSettingsLocked;
-    is(acceptThirdPartyLabel.disabled, expectControlsDisabled,
-       "\"Accept Third Party Cookies\" Label disabled status should match expected");
-    is(acceptThirdPartyMenu.disabled, expectControlsDisabled,
-       "\"Accept Third Party Cookies\" Menu disabled status should match expected");
-    is(keepUntilLabel.disabled, expectControlsDisabled,
-       "\"Keep Cookies Until\" Label disabled status should match expected");
-    is(keepUntilMenu.disabled, expectControlsDisabled,
-       "\"Keep Cookies Until\" Menu disabled status should match expected");
+      let expectControlsDisabled = !cookiesEnabled || cookieSettingsLocked;
+      is(keepUntilLabel.disabled, expectControlsDisabled,
+         "\"Keep Cookies Until\" Label disabled status should match expected");
+      is(keepUntilMenu.disabled, expectControlsDisabled,
+         "\"Keep Cookies Until\" Menu disabled status should match expected");
+    }, 0);
   });
   BrowserTestUtils.removeTab(tab);
 
   if (rejectTrackers) {
     tab = await BrowserTestUtils.addTab(gBrowser, "http://example.net/browser/browser/components/enterprisepolicies/tests/browser/page.html");
     let browser = gBrowser.getBrowserForTab(tab);
     await BrowserTestUtils.browserLoaded(browser);
     await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
--- a/browser/components/extensions/parent/ext-sidebarAction.js
+++ b/browser/components/extensions/parent/ext-sidebarAction.js
@@ -111,22 +111,19 @@ this.sidebarAction = class extends Exten
       let menu = document.getElementById(this.menuId);
       if (menu) {
         menu.remove();
       }
       let button = document.getElementById(this.buttonId);
       if (button) {
         button.remove();
       }
-      let broadcaster = document.getElementById(this.id);
-      if (broadcaster) {
-        broadcaster.remove();
-      }
       let header = document.getElementById("sidebar-switcher-target");
       header.removeEventListener("SidebarShown", this.updateHeader);
+      SidebarUI.sidebars.delete(this.id);
     }
     windowTracker.removeOpenListener(this.windowOpenListener);
     windowTracker.removeCloseListener(this.windowCloseListener);
   }
 
   build() {
     this.tabContext.on("tab-select", // eslint-disable-line mozilla/balanced-listeners
                        (evt, tab) => { this.updateWindow(tab.ownerGlobal); });
@@ -139,56 +136,52 @@ this.sidebarAction = class extends Exten
           SidebarUI.lastOpenedId == this.id) {
         SidebarUI.show(this.id);
       }
     }
   }
 
   createMenuItem(window, details) {
     let {document, SidebarUI} = window;
+    let keyId = `ext-key-id-${this.id}`;
 
-    // Use of the broadcaster allows browser-sidebar.js to properly manage the
-    // checkmarks in the menus.
-    let broadcaster = document.createXULElement("broadcaster");
-    broadcaster.setAttribute("id", this.id);
-    broadcaster.setAttribute("autoCheck", "false");
-    broadcaster.setAttribute("type", "checkbox");
-    broadcaster.setAttribute("group", "sidebar");
-    broadcaster.setAttribute("label", details.title);
-    broadcaster.setAttribute("sidebarurl", sidebarURL);
-    broadcaster.setAttribute("panel", details.panel);
-    if (this.browserStyle) {
-      broadcaster.setAttribute("browserStyle", "true");
-    }
-    broadcaster.setAttribute("extensionId", this.extension.id);
-    let id = `ext-key-id-${this.id}`;
-    broadcaster.setAttribute("key", id);
-
-    // oncommand gets attached to menuitem, so we use the observes attribute to
-    // get the command id we pass to SidebarUI.
-    broadcaster.setAttribute("oncommand", "SidebarUI.toggle(this.getAttribute('observes'))");
+    SidebarUI.sidebars.set(this.id, {
+      title: details.title,
+      url: sidebarURL,
+      menuId: this.menuId,
+      buttonId: this.buttonId,
+      // The following properties are specific to extensions
+      extensionId: this.extension.id,
+      panel: details.panel,
+      browserStyle: this.browserStyle,
+    });
 
     let header = document.getElementById("sidebar-switcher-target");
     header.addEventListener("SidebarShown", this.updateHeader);
 
     // Insert a menuitem for View->Show Sidebars.
     let menuitem = document.createXULElement("menuitem");
     menuitem.setAttribute("id", this.menuId);
-    menuitem.setAttribute("observes", this.id);
+    menuitem.setAttribute("type", "checkbox");
+    menuitem.setAttribute("label", details.title);
+    menuitem.setAttribute("oncommand", `SidebarUI.toggle("${this.id}");`);
     menuitem.setAttribute("class", "menuitem-iconic webextension-menuitem");
+    menuitem.setAttribute("key", keyId);
     this.setMenuIcon(menuitem, details);
 
     // Insert a toolbarbutton for the sidebar dropdown selector.
     let toolbarbutton = document.createXULElement("toolbarbutton");
     toolbarbutton.setAttribute("id", this.buttonId);
-    toolbarbutton.setAttribute("observes", this.id);
+    toolbarbutton.setAttribute("type", "checkbox");
+    toolbarbutton.setAttribute("label", details.title);
+    toolbarbutton.setAttribute("oncommand", `SidebarUI.show("${this.id}");`);
     toolbarbutton.setAttribute("class", "subviewbutton subviewbutton-iconic webextension-menuitem");
+    toolbarbutton.setAttribute("key", keyId);
     this.setMenuIcon(toolbarbutton, details);
 
-    document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
     document.getElementById("viewSidebarMenu").appendChild(menuitem);
     let separator = document.getElementById("sidebar-extensions-separator");
     separator.parentNode.insertBefore(toolbarbutton, separator);
     SidebarUI.updateShortcut({button: toolbarbutton});
 
     return menuitem;
   }
 
@@ -198,71 +191,67 @@ this.sidebarAction = class extends Exten
 
     menuitem.setAttribute("style", `
       --webextension-menuitem-image: url("${getIcon(16)}");
       --webextension-menuitem-image-2x: url("${getIcon(32)}");
     `);
   }
 
   /**
-   * Update the broadcaster and menuitem `node` with the tab context data
-   * in `tabData`.
+   * Update the menu items with the tab context data in `tabData`.
    *
    * @param {ChromeWindow} window
    *        Browser chrome window.
    * @param {object} tabData
    *        Tab specific sidebar configuration.
    */
   updateButton(window, tabData) {
     let {document, SidebarUI} = window;
     let title = tabData.title || this.extension.name;
     let menu = document.getElementById(this.menuId);
     if (!menu) {
       menu = this.createMenuItem(window, tabData);
     }
 
-    // Update the broadcaster first, it will update both menus.
-    let broadcaster = document.getElementById(this.id);
-    broadcaster.setAttribute("tooltiptext", title);
-    broadcaster.setAttribute("label", title);
-
-    let urlChanged = tabData.panel !== broadcaster.getAttribute("panel");
+    let urlChanged = tabData.panel !== SidebarUI.sidebars.get(this.id).panel;
     if (urlChanged) {
-      broadcaster.setAttribute("panel", tabData.panel);
+      SidebarUI.sidebars.get(this.id).panel = tabData.panel;
     }
 
+    menu.setAttribute("label", title);
     this.setMenuIcon(menu, tabData);
 
     let button = document.getElementById(this.buttonId);
+    button.setAttribute("label", title);
     this.setMenuIcon(button, tabData);
 
     // Update the sidebar if this extension is the current sidebar.
     if (SidebarUI.currentID === this.id) {
       SidebarUI.title = title;
       let header = document.getElementById("sidebar-switcher-target");
       this.setMenuIcon(header, tabData);
       if (SidebarUI.isOpen && urlChanged) {
         SidebarUI.show(this.id);
       }
     }
   }
 
   /**
-   * Update the broadcaster and menuitem for a given window.
+   * Update the menu items for a given window.
    *
    * @param {ChromeWindow} window
    *        Browser chrome window.
    */
   updateWindow(window) {
     let nativeTab = window.gBrowser.selectedTab;
     this.updateButton(window, this.tabContext.get(nativeTab));
   }
 
   /**
-   * Update the broadcaster and menuitem when the extension changes the icon,
+   * Update the menu items when the extension changes the icon,
    * title, url, etc. If it only changes a parameter for a single tab, `target`
    * will be that tab. If it only changes a parameter for a single window,
    * `target` will be that window. Otherwise `target` will be null.
    *
    * @param {XULElement|ChromeWindow|null} target
    *        Browser tab or browser chrome window, may be null.
    */
   updateOnChange(target) {
--- a/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js
@@ -83,19 +83,16 @@ async function runTests(options) {
 
   let sidebarActionId;
   function checkDetails(details, windowId) {
     let {document} = Services.wm.getOuterWindowWithId(windowId);
     if (!sidebarActionId) {
       sidebarActionId = `${makeWidgetId(extension.id)}-sidebar-action`;
     }
 
-    let command = document.getElementById(sidebarActionId);
-    ok(command, "command exists");
-
     let menuId = `menu_${sidebarActionId}`;
     let menu = document.getElementById(menuId);
     ok(menu, "menu exists");
 
     let title = details.title || options.manifest.name;
 
     is(getListStyleImage(menu), details.icon, "icon URL is correct");
     is(menu.getAttribute("label"), title, "image label is correct");
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -18,16 +18,22 @@ ChromeUtils.defineModuleGetter(this, "Lo
 ChromeUtils.defineModuleGetter(this, "SiteDataManager",
   "resource:///modules/SiteDataManager.jsm");
 
 ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "contentBlockingUiEnabled",
                                       "browser.contentblocking.ui.enabled");
 
+XPCOMUtils.defineLazyPreferenceGetter(this, "contentBlockingCookiesAndSiteDataRejectTrackersRecommended",
+                                      "browser.contentblocking.cookies-site-data.ui.reject-trackers.recommended");
+
+XPCOMUtils.defineLazyPreferenceGetter(this, "contentBlockingCookiesAndSiteDataRejectTrackersEnabled",
+                                      "browser.contentblocking.cookies-site-data.ui.reject-trackers.enabled");
+
 XPCOMUtils.defineLazyPreferenceGetter(this, "contentBlockingEnabled",
                                       "browser.contentblocking.enabled");
 
 const PREF_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
 
 const TRACKING_PROTECTION_KEY = "websites.trackingProtectionMode";
 const TRACKING_PROTECTION_PREFS = ["privacy.trackingprotection.enabled",
                                    "privacy.trackingprotection.pbmode.enabled"];
@@ -246,40 +252,50 @@ var gPrivacyPane = {
 
   /**
    * Sets up the UI for the number of days of history to keep, and updates the
    * label of the "Clear Now..." button.
    */
   init() {
     this._updateSanitizeSettingsButton();
     this.initializeHistoryMode();
+    this.initAutoplay();
     this.updateAutoplayMediaControlsVisibility();
     this.updateHistoryModePane();
     this.updatePrivacyMicroControls();
     this.initAutoStartPrivateBrowsingReverter();
     this._initAutocomplete();
 
     /* Initialize Content Blocking / Tracking Protection */
 
     if (contentBlockingUiEnabled) {
       this.initContentBlocking();
     } else {
       this._initTrackingProtection();
     }
 
     this.trackingProtectionReadPrefs();
+    this.networkCookieBehaviorReadPrefs();
     this._initTrackingProtectionExtensionControl();
 
     this.updateContentBlockingVisibility();
 
     Preferences.get("privacy.trackingprotection.enabled").on("change",
       gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane));
     Preferences.get("privacy.trackingprotection.pbmode.enabled").on("change",
       gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane));
 
+    // Watch all of the prefs that the new Cookies & Site Data UI depends on
+    Preferences.get("network.cookie.cookieBehavior").on("change",
+      gPrivacyPane.networkCookieBehaviorReadPrefs.bind(gPrivacyPane));
+    Preferences.get("network.cookie.lifetimePolicy").on("change",
+      gPrivacyPane.networkCookieBehaviorReadPrefs.bind(gPrivacyPane));
+    Preferences.get("browser.privatebrowsing.autostart").on("change",
+      gPrivacyPane.networkCookieBehaviorReadPrefs.bind(gPrivacyPane));
+
     setEventListener("trackingProtectionExceptions", "command",
       gPrivacyPane.showTrackingProtectionExceptions);
 
     Preferences.get("privacy.sanitize.sanitizeOnShutdown").on("change",
       gPrivacyPane._updateSanitizeSettingsButton.bind(gPrivacyPane));
     Preferences.get("browser.privatebrowsing.autostart").on("change",
       gPrivacyPane.updatePrivacyMicroControls.bind(gPrivacyPane));
     Preferences.get("media.autoplay.enabled.ask-permission").on("change",
@@ -470,31 +486,43 @@ var gPrivacyPane = {
     }
   },
 
   /**
    * Changes the visibility of elements in the TP/CB section depending on the
    * content blocking UI pref.
    */
   updateContentBlockingVisibility() {
+    // First, update the content blocking UI.
     let visibleState = {
       "contentBlockingHeader": true,
       "contentBlockingDescription": true,
       "contentBlockingLearnMore": true,
       "contentBlockingRestoreDefaults": true,
       "contentBlockingCheckboxContainer": true,
       "contentBlockingCategories": true,
 
       "trackingProtectionHeader": false,
       "trackingProtectionDescription": false,
       "trackingProtectionBox": false,
     };
     for (let id in visibleState) {
       document.getElementById(id).hidden = contentBlockingUiEnabled != visibleState[id];
     }
+
+    // Allow turning off the "(recommended)" label using a pref
+    let blockCookiesFromTrackers = document.getElementById("blockCookiesFromTrackers");
+    if (contentBlockingCookiesAndSiteDataRejectTrackersRecommended) {
+      document.l10n.setAttributes(blockCookiesFromTrackers, "sitedata-block-trackers-option-recommended");
+    }
+
+    // Allow hiding the Reject Trackers option based on a pref
+    if (!contentBlockingCookiesAndSiteDataRejectTrackersEnabled) {
+      blockCookiesFromTrackers.remove();
+    }
   },
 
   /**
    * Updates the preferences UI to reflect the browser.contentblocking.enabled pref.
    * This affects the button to toggle the pref and the disabled state of the dependent controls.
    */
   updateContentBlockingToggle() {
     let onOrOff = contentBlockingEnabled ? "on" : "off";
@@ -561,16 +589,61 @@ var gPrivacyPane = {
     } else if (pbmPref.value) {
       tpControl.value = "private";
     } else {
       tpControl.value = "never";
     }
   },
 
   /**
+   * Selects the right items of the new Cookies & Site Data UI.
+   */
+  networkCookieBehaviorReadPrefs() {
+    let behavior = Preferences.get("network.cookie.cookieBehavior").value;
+    let blockCookiesCtrl = document.getElementById("blockCookies");
+    let blockCookiesLabel = document.getElementById("blockCookiesLabel");
+    let blockCookiesMenu = document.getElementById("blockCookiesMenu");
+    let keepUntilLabel = document.getElementById("keepUntil");
+    let keepUntilMenu = document.getElementById("keepCookiesUntil");
+
+    let blockCookies = (behavior != 0);
+    let cookieBehaviorLocked = Services.prefs.prefIsLocked("network.cookie.cookieBehavior");
+    let blockCookiesControlsDisabled = !blockCookies || cookieBehaviorLocked;
+    blockCookiesLabel.disabled = blockCookiesMenu.disabled = blockCookiesControlsDisabled;
+
+    let completelyBlockCookies = (behavior == 2);
+    let privateBrowsing = Preferences.get("browser.privatebrowsing.autostart").value;
+    let cookieExpirationLocked = Services.prefs.prefIsLocked("network.cookie.lifetimePolicy");
+    let keepUntilControlsDisabled = privateBrowsing || completelyBlockCookies || cookieExpirationLocked;
+    keepUntilLabel.disabled = keepUntilMenu.disabled = keepUntilControlsDisabled;
+
+    switch (behavior) {
+      case Ci.nsICookieService.BEHAVIOR_ACCEPT:
+        blockCookiesCtrl.value = "allow";
+        break;
+      case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN:
+        blockCookiesCtrl.value = "disallow";
+        blockCookiesMenu.value = "all-third-parties";
+        break;
+      case Ci.nsICookieService.BEHAVIOR_REJECT:
+        blockCookiesCtrl.value = "disallow";
+        blockCookiesMenu.value = "always";
+        break;
+      case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN:
+        blockCookiesCtrl.value = "disallow";
+        blockCookiesMenu.value = "unvisited";
+        break;
+      case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER:
+        blockCookiesCtrl.value = "disallow";
+        blockCookiesMenu.value = "trackers";
+        break;
+    }
+  },
+
+  /**
    * Sets the pref values based on the selected item of the radiogroup.
    */
   trackingProtectionWritePrefs() {
     let enabledPref = Preferences.get("privacy.trackingprotection.enabled");
     let pbmPref = Preferences.get("privacy.trackingprotection.pbmode.enabled");
     let tpControl;
     if (contentBlockingUiEnabled) {
       tpControl = document.getElementById("trackingProtectionMenu");
@@ -713,17 +786,16 @@ var gPrivacyPane = {
   /**
    * Update the privacy micro-management controls based on the
    * value of the private browsing auto-start preference.
    */
   updatePrivacyMicroControls() {
     // Set "Keep cookies until..." to "I close Nightly" and disable the setting
     // when we're in auto private mode (or reset it back otherwise).
     document.getElementById("keepCookiesUntil").value = this.readKeepCookiesUntil();
-    this.readAcceptCookies();
 
     let clearDataSettings = document.getElementById("clearDataSettings");
 
     if (document.getElementById("historyMode").value == "custom") {
       let disabled = Preferences.get("browser.privatebrowsing.autostart").value;
       this.dependentControls.forEach(function(aElement) {
         let control = document.getElementById(aElement);
         let preferenceId = control.getAttribute("preference");
@@ -919,87 +991,78 @@ var gPrivacyPane = {
 
     // network.cookie.lifetimePolicy can be set to any value, but we just
     // support ACCEPT_SESSION and ACCEPT_NORMALLY. Let's force ACCEPT_NORMALLY.
     return Ci.nsICookieService.ACCEPT_NORMALLY;
   },
 
   /**
    * Reads the network.cookie.cookieBehavior preference value and
-   * enables/disables the rest of the cookie UI accordingly.
+   * enables/disables the rest of the new cookie & site data UI accordingly.
    *
-   * Returns "0" if cookies are accepted and "2" if they are entirely disabled.
+   * Returns "allow" if cookies are accepted and "disallow" if they are entirely
+   * disabled.
    */
-  readAcceptCookies() {
+  readBlockCookies() {
+    // enable the rest of the UI for anything other than "accept all cookies"
     let pref = Preferences.get("network.cookie.cookieBehavior");
-    let acceptThirdPartyLabel = document.getElementById("acceptThirdPartyLabel");
-    let acceptThirdPartyMenu = document.getElementById("acceptThirdPartyMenu");
-    let keepUntilLabel = document.getElementById("keepUntil");
-    let keepUntilMenu = document.getElementById("keepCookiesUntil");
-
-    // enable the rest of the UI for anything other than "disable all cookies"
-    let acceptCookies = (pref.value != 2);
-    let cookieBehaviorLocked = Services.prefs.prefIsLocked("network.cookie.cookieBehavior");
-    const acceptThirdPartyControlsDisabled = !acceptCookies || cookieBehaviorLocked;
-
-    acceptThirdPartyLabel.disabled = acceptThirdPartyMenu.disabled = acceptThirdPartyControlsDisabled;
-
-    let privateBrowsing = Preferences.get("browser.privatebrowsing.autostart").value;
-    let cookieExpirationLocked = Services.prefs.prefIsLocked("network.cookie.lifetimePolicy");
-    const keepUntilControlsDisabled = privateBrowsing || !acceptCookies || cookieExpirationLocked;
-    keepUntilLabel.disabled = keepUntilMenu.disabled = keepUntilControlsDisabled;
+    let blockCookies = (pref.value != 0);
 
     // Our top-level setting is a radiogroup that only sets "enable all"
     // and "disable all", so convert the pref value accordingly.
-    return acceptCookies ? "0" : "2";
+    return blockCookies ? "disallow" : "allow";
   },
 
   /**
    * Updates the "accept third party cookies" menu based on whether the
    * "accept cookies" or "block cookies" radio buttons are selected.
    */
-  writeAcceptCookies() {
-    var accept = document.getElementById("acceptCookies");
-    var acceptThirdPartyMenu = document.getElementById("acceptThirdPartyMenu");
+  writeBlockCookies() {
+    let block = document.getElementById("blockCookies");
+    let blockCookiesMenu = document.getElementById("blockCookiesMenu");
 
-    // if we're enabling cookies, automatically select 'accept third party always'
-    if (accept.value == "0")
-      acceptThirdPartyMenu.selectedIndex = 0;
+    // if we're disabling cookies, automatically select 'third-party trackers'
+    if (block.value == "disallow") {
+      blockCookiesMenu.selectedIndex = 0;
+      return this.writeBlockCookiesFrom();
+    }
 
-    return parseInt(accept.value, 10);
+    return Ci.nsICookieService.BEHAVIOR_ACCEPT;
   },
 
   /**
-   * Converts between network.cookie.cookieBehavior and the third-party cookie UI
+   * Converts between network.cookie.cookieBehavior and the new third-party cookies UI
    */
-  readAcceptThirdPartyCookies() {
-    var pref = Preferences.get("network.cookie.cookieBehavior");
+  readBlockCookiesFrom() {
+    let pref = Preferences.get("network.cookie.cookieBehavior");
     switch (pref.value) {
-      case 0:
+      case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN:
+        return "all-third-parties";
+      case Ci.nsICookieService.BEHAVIOR_REJECT:
         return "always";
-      case 1:
-        return "never";
-      case 2:
-        return "never";
-      case 3:
-        return "visited";
+      case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN:
+        return "unvisited";
+      case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER:
+        return "trackers";
       default:
         return undefined;
     }
   },
 
-  writeAcceptThirdPartyCookies() {
-    var accept = document.getElementById("acceptThirdPartyMenu").selectedItem;
-    switch (accept.value) {
+  writeBlockCookiesFrom() {
+    let block = document.getElementById("blockCookiesMenu").selectedItem;
+    switch (block.value) {
+      case "trackers":
+        return Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER;
+      case "unvisited":
+        return Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN;
       case "always":
-        return 0;
-      case "visited":
-        return 3;
-      case "never":
-        return 1;
+        return Ci.nsICookieService.BEHAVIOR_REJECT;
+      case "all-third-parties":
+        return Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN;
       default:
         return undefined;
     }
   },
 
   /**
    * Displays fine-grained, per-site preferences for cookies.
    */
@@ -1102,16 +1165,22 @@ var gPrivacyPane = {
       Services.telemetry
         .getHistogramById("WEB_NOTIFICATION_EXCEPTIONS_OPENED").add();
     } catch (e) { }
   },
 
 
   // MEDIA
 
+  initAutoplay() {
+    let url = Services.urlFormatter.formatURLPref("app.support.baseURL") +
+      "block-autoplay";
+    document.getElementById("autoplayLearnMoreLink").setAttribute("href", url);
+  },
+
   /**
    * The checkbox enabled sets the pref to BLOCKED
    */
   toggleAutoplayMedia(event) {
     let blocked = event.target.checked ? Ci.nsIAutoplay.BLOCKED : Ci.nsIAutoplay.ALLOWED;
     Services.prefs.setIntPref("media.autoplay.default", blocked);
   },
 
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -167,64 +167,62 @@
 
   <hbox data-subcategory="sitedata" align="baseline">
     <vbox flex="1">
       <description class="description-with-side-element" flex="1">
         <html:span id="totalSiteDataSize" class="tail-with-learn-more"></html:span>
         <label id="siteDataLearnMoreLink"
           class="learnMore text-link" data-l10n-id="sitedata-learn-more"/>
       </description>
-      <radiogroup id="acceptCookies"
+      <radiogroup id="blockCookies"
                   preference="network.cookie.cookieBehavior"
-                  onsyncfrompreference="return gPrivacyPane.readAcceptCookies();"
-                  onsynctopreference="return gPrivacyPane.writeAcceptCookies();">
-        <hbox id="cookiesBox">
-          <radio value="0"
-                 data-l10n-id="sitedata-accept-cookies-option"
-                 flex="1" />
+                  onsyncfrompreference="return gPrivacyPane.readBlockCookies();"
+                  onsynctopreference="return gPrivacyPane.writeBlockCookies();">
+        <radio value="allow"
+               data-l10n-id="sitedata-allow-cookies-option"
+               flex="1" />
+        <radio value="disallow"
+               data-l10n-id="sitedata-disallow-cookies-option"
+               flex="1" />
+        <hbox id="blockThirdPartyRow"
+              class="indent"
+              align="center">
+          <label id="blockCookiesLabel" control="blockCookiesMenu"
+                 data-l10n-id="sitedata-block-desc"/>
+          <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
+          <hbox>
+            <menulist id="blockCookiesMenu" preference="network.cookie.cookieBehavior"
+                      onsyncfrompreference="return gPrivacyPane.readBlockCookiesFrom();"
+                      onsynctopreference="return gPrivacyPane.writeBlockCookiesFrom();">
+              <menupopup>
+                <menuitem id="blockCookiesFromTrackers" data-l10n-id="sitedata-block-trackers-option" value="trackers"/>
+                <menuitem data-l10n-id="sitedata-block-unvisited-option" value="unvisited"/>
+                <menuitem data-l10n-id="sitedata-block-all-third-parties-option" value="all-third-parties"/>
+                <menuitem data-l10n-id="sitedata-block-always-option" value="always"/>
+              </menupopup>
+            </menulist>
+          </hbox>
         </hbox>
         <hbox id="keepRow"
-              class="indent"
               align="center">
           <label id="keepUntil"
                  control="keepCookiesUntil"
                  data-l10n-id="sitedata-keep-until"/>
           <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
           <hbox>
             <menulist id="keepCookiesUntil"
                       onsyncfrompreference="return gPrivacyPane.readKeepCookiesUntil();"
                       preference="network.cookie.lifetimePolicy">
               <menupopup>
                 <menuitem data-l10n-id="sitedata-keep-until-expire" value="0"/>
                 <menuitem data-l10n-id="sitedata-keep-until-closed" value="2"/>
               </menupopup>
             </menulist>
           </hbox>
         </hbox>
-        <hbox id="acceptThirdPartyRow"
-              class="indent"
-              align="center">
-          <label id="acceptThirdPartyLabel" control="acceptThirdPartyMenu"
-                 data-l10n-id="sitedata-accept-third-party-desc"/>
-          <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
-          <hbox>
-            <menulist id="acceptThirdPartyMenu" preference="network.cookie.cookieBehavior"
-            onsyncfrompreference="return gPrivacyPane.readAcceptThirdPartyCookies();"
-            onsynctopreference="return gPrivacyPane.writeAcceptThirdPartyCookies();">
-              <menupopup>
-                <menuitem data-l10n-id="sitedata-accept-third-party-always-option" value="always"/>
-                <menuitem data-l10n-id="sitedata-accept-third-party-visited-option" value="visited"/>
-                <menuitem data-l10n-id="sitedata-accept-third-party-never-option" value="never"/>
-              </menupopup>
-            </menulist>
-          </hbox>
-        </hbox>
-        <radio data-l10n-id="sitedata-block-cookies-option"
-               value="2"
-               flex="1" />
       </radiogroup>
     </vbox>
     <vbox>
       <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
       <hbox>
         <button id="clearSiteDataButton"
             class="accessory-button"
             icon="clear"
@@ -455,20 +453,20 @@
 <hbox id="permissionsCategory"
       class="subcategory"
       hidden="true"
       data-category="panePrivacy">
   <label class="header-name" flex="1" data-l10n-id="permissions-header"/>
 </hbox>
 
 <!-- Permissions -->
-<groupbox id="permissionsGroup" data-category="panePrivacy" hidden="true">
+<groupbox id="permissionsGroup" data-category="panePrivacy" hidden="true" data-subcategory="permissions">
   <caption class="search-header" hidden="true"><label data-l10n-id="permissions-header"/></caption>
 
-  <grid data-subcategory="permissions">
+  <grid>
     <columns>
       <column flex="1"/>
       <column/>
     </columns>
     <rows>
       <row id="locationSettingsRow" align="center">
         <description flex="1">
           <image class="geo-icon permission-icon" />
@@ -581,17 +579,17 @@
     <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
     <hbox>
       <button id="autoplayMediaPolicyButton"
               class="accessory-button"
               data-l10n-id="permissions-block-autoplay-media-exceptions"
               search-l10n-ids="permissions-address,
                                permissions-button-cancel.label,
                                permissions-button-ok.label,
-                               permissions-exceptions-autoplay-media-window.title,
+                               permissions-exceptions-autoplay-media-window2.title,
                                permissions-exceptions-autoplay-media-desc2
                                " />
     </hbox>
   </hbox>
 
   <hbox>
     <checkbox id="popupPolicy" preference="dom.disable_open_during_load"
               data-l10n-id="permissions-block-popups"
@@ -657,26 +655,28 @@
                 preference="media.autoplay.default">
         <menupopup>
           <!-- Defined in dom/media/nsIAutoplay.idl -->
           <menuitem data-l10n-id="autoplay-option-allow" value="0"/>
           <menuitem data-l10n-id="autoplay-option-ask" value="2"/>
           <menuitem data-l10n-id="autoplay-option-dont" value="1"/>
         </menupopup>
       </menulist>
+      <label id="autoplayLearnMoreLink" class="learnMore text-link"
+             data-l10n-id="permissions-autoplay-link"/>
     </hbox>
 
     <hbox pack="end">
       <button id="autoplayMediaPolicyComboboxButton"
               class="accessory-button"
               data-l10n-id="permissions-block-autoplay-media-exceptions"
               search-l10n-ids="permissions-address,
                                permissions-button-cancel.label,
                                permissions-button-ok.label,
-                               permissions-exceptions-autoplay-media-window.title,
+                               permissions-exceptions-autoplay-media-window2.title,
                                permissions-exceptions-autoplay-media-desc2
                                " />
     </hbox>
   </hbox>
 
 </groupbox>
 
 <!-- Firefox Data Collection and Use -->
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -63,21 +63,20 @@ skip-if = !e10s
 skip-if = e10s
 [browser_permissions_urlFieldHidden.js]
 [browser_proxy_backup.js]
 [browser_privacypane.js]
 run-if = nightly_build
 # browser_privacypane.js only has Browser Error collection tests currently,
 # which is disabled outside Nightly. Remove this once non-Nightly tests are
 # added.
-[browser_privacypane_1.js]
+[browser_privacypane_2.js]
 [browser_privacypane_3.js]
 [browser_privacypane_4.js]
 [browser_privacypane_5.js]
-[browser_privacypane_8.js]
 [browser_sanitizeOnShutdown_prefLocked.js]
 [browser_searchShowSuggestionsFirst.js]
 [browser_searchsuggestions.js]
 [browser_security-1.js]
 [browser_security-2.js]
 [browser_spotlight.js]
 [browser_site_login_exceptions.js]
 [browser_site_autoplay_media_exceptions.js]
deleted file mode 100644
--- a/browser/components/preferences/in-content/tests/browser_privacypane_1.js
+++ /dev/null
@@ -1,16 +0,0 @@
-let rootDir = getRootDirectory(gTestPath);
-let jar = getJar(rootDir);
-if (jar) {
-  let tmpdir = extractJarToTmp(jar);
-  rootDir = "file://" + tmpdir.path + "/";
-}
-/* import-globals-from privacypane_tests_perwindow.js */
-Services.scriptloader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
-
-run_test_subset([
-  test_pane_visibility,
-  test_dependent_elements,
-  test_dependent_cookie_elements,
-  test_dependent_clearonclose_elements,
-  test_dependent_prefs,
-]);
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_2.js
@@ -0,0 +1,20 @@
+let rootDir = getRootDirectory(gTestPath);
+let jar = getJar(rootDir);
+if (jar) {
+  let tmpdir = extractJarToTmp(jar);
+  rootDir = "file://" + tmpdir.path + "/";
+}
+/* import-globals-from privacypane_tests_perwindow.js */
+Services.scriptloader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
+
+SpecialPowers.pushPrefEnv({"set":
+  [["browser.contentblocking.cookies-site-data.ui.reject-trackers.enabled", false]]
+});
+
+run_test_subset([
+  test_pane_visibility,
+  test_dependent_elements,
+  test_dependent_cookie_elements,
+  test_dependent_clearonclose_elements,
+  test_dependent_prefs,
+]);
--- a/browser/components/preferences/in-content/tests/browser_privacypane_3.js
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_3.js
@@ -8,9 +8,11 @@ if (jar) {
 Services.scriptloader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
 
 run_test_subset([
   test_custom_retention("rememberHistory", "remember"),
   test_custom_retention("rememberHistory", "custom"),
   test_custom_retention("rememberForms", "custom"),
   test_custom_retention("rememberForms", "custom"),
   test_historymode_retention("remember", "custom"),
+  test_custom_retention("alwaysClear", "remember"),
+  test_custom_retention("alwaysClear", "custom"),
 ]);
--- a/browser/components/preferences/in-content/tests/browser_privacypane_4.js
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_4.js
@@ -1,24 +1,20 @@
-requestLongerTimeout(2);
-
 let rootDir = getRootDirectory(gTestPath);
 let jar = getJar(rootDir);
 if (jar) {
   let tmpdir = extractJarToTmp(jar);
   rootDir = "file://" + tmpdir.path + "/";
 }
 /* import-globals-from privacypane_tests_perwindow.js */
 Services.scriptloader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
-let runtime = Services.appInfo;
+
+SpecialPowers.pushPrefEnv({"set":
+  [["browser.contentblocking.cookies-site-data.ui.reject-trackers.enabled", false]]
+});
 
 run_test_subset([
-  test_custom_retention("acceptCookies", "remember"),
-  test_custom_retention("acceptCookies", "custom"),
-  test_custom_retention("acceptThirdPartyMenu", "custom", "visited"),
-  test_custom_retention("acceptThirdPartyMenu", "custom", "always"),
-  test_custom_retention("keepCookiesUntil", "custom", 1),
-  test_custom_retention("keepCookiesUntil", "custom", 2),
-  test_custom_retention("keepCookiesUntil", "custom", 0),
-  test_custom_retention("alwaysClear", "custom"),
-  test_custom_retention("alwaysClear", "custom"),
-  test_historymode_retention("remember", "custom"),
+  test_pane_visibility,
+  test_dependent_elements,
+  test_dependent_cookie_elements,
+  test_dependent_clearonclose_elements,
+  test_dependent_prefs,
 ]);
deleted file mode 100644
--- a/browser/components/preferences/in-content/tests/browser_privacypane_8.js
+++ /dev/null
@@ -1,25 +0,0 @@
-let rootDir = getRootDirectory(gTestPath);
-let jar = getJar(rootDir);
-if (jar) {
-  let tmpdir = extractJarToTmp(jar);
-  rootDir = "file://" + tmpdir.path + "/";
-}
-/* import-globals-from privacypane_tests_perwindow.js */
-Services.scriptloader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
-
-run_test_subset([
-  // history mode should be initialized to remember
-  test_historymode_retention("remember", undefined),
-
-  // history mode should remain remember; toggle acceptCookies checkbox
-  test_custom_retention("acceptCookies", "remember"),
-
-  // history mode should now be custom; set history mode to dontremember
-  test_historymode_retention("dontremember", "custom"),
-
-  // history mode should remain custom; set history mode to remember
-  test_historymode_retention("remember", "custom"),
-
-  // history mode should now be remember
-  test_historymode_retention("remember", "remember"),
-]);
--- a/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js
+++ b/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js
@@ -49,19 +49,17 @@ function test_dependent_elements(win) {
     win.document.getElementById("keepUntil"),
     win.document.getElementById("keepCookiesUntil"),
     win.document.getElementById("alwaysClear"),
   ];
   controls.forEach(function(control) {
     ok(control, "the dependent controls should exist");
   });
   let independents = [
-    win.document.getElementById("acceptCookies"),
-    win.document.getElementById("acceptThirdPartyLabel"),
-    win.document.getElementById("acceptThirdPartyMenu")
+    win.document.getElementById("blockCookies")
   ];
   independents.forEach(function(control) {
     ok(control, "the independent controls should exist");
   });
   let cookieexceptions = win.document.getElementById("cookieExceptions");
   ok(cookieexceptions, "the cookie exceptions button should exist");
   let keepuntil = win.document.getElementById("keepCookiesUntil");
   ok(keepuntil, "the keep cookies until menulist should exist");
@@ -113,49 +111,63 @@ function test_dependent_elements(win) {
   controlChanged(historymode);
   expect_disabled(false);
   check_independents(false);
 }
 
 function test_dependent_cookie_elements(win) {
   let keepUntil = win.document.getElementById("keepUntil");
   let keepCookiesUntil = win.document.getElementById("keepCookiesUntil");
-  let acceptThirdPartyLabel = win.document.getElementById("acceptThirdPartyLabel");
-  let acceptThirdPartyMenu = win.document.getElementById("acceptThirdPartyMenu");
+  let blockCookiesLabel = win.document.getElementById("blockCookiesLabel");
+  let blockCookiesMenu = win.document.getElementById("blockCookiesMenu");
 
-  let controls = [acceptThirdPartyLabel, acceptThirdPartyMenu, keepUntil, keepCookiesUntil];
+  let controls = [blockCookiesLabel, blockCookiesMenu, keepUntil, keepCookiesUntil];
   controls.forEach(function(control) {
     ok(control, "the dependent cookie controls should exist");
   });
-  let acceptcookies = win.document.getElementById("acceptCookies");
-  ok(acceptcookies, "the accept cookies checkbox should exist");
+  let blockcookies = win.document.getElementById("blockCookies");
+  ok(blockcookies, "the block cookies checkbox should exist");
 
   function expect_disabled(disabled, c = controls) {
     c.forEach(function(control) {
       is(control.disabled, disabled,
         control.getAttribute("id") + " should " + (disabled ? "" : "not ") + "be disabled");
     });
   }
 
-  acceptcookies.value = "2";
-  controlChanged(acceptcookies);
-  expect_disabled(true);
+  blockcookies.value = "disallow";
+  controlChanged(blockcookies);
+  expect_disabled(false);
+
+  blockcookies.value = "allow";
+  controlChanged(blockcookies);
+  expect_disabled(true, [blockCookiesLabel, blockCookiesMenu]);
+  expect_disabled(false, [keepUntil, keepCookiesUntil]);
 
-  acceptcookies.value = "1";
-  controlChanged(acceptcookies);
+  blockCookiesMenu.value = "always";
+  controlChanged(blockCookiesMenu);
+  expect_disabled(true, [keepUntil, keepCookiesUntil]);
+  expect_disabled(false, [blockCookiesLabel, blockCookiesMenu]);
+
+  if (win.contentBlockingCookiesAndSiteDataRejectTrackersEnabled) {
+    blockCookiesMenu.value = "trackers";
+  } else {
+    blockCookiesMenu.value = "unvisited";
+  }
+  controlChanged(blockCookiesMenu);
   expect_disabled(false);
 
   let historymode = win.document.getElementById("historyMode");
 
   // The History mode setting for "never remember history" should still
   // disable the "keep cookies until..." menu.
   historymode.value = "dontremember";
   controlChanged(historymode);
   expect_disabled(true, [keepUntil, keepCookiesUntil]);
-  expect_disabled(false, [acceptThirdPartyLabel, acceptThirdPartyMenu]);
+  expect_disabled(false, [blockCookiesLabel, blockCookiesMenu]);
 
   historymode.value = "remember";
   controlChanged(historymode);
   expect_disabled(false);
 }
 
 function test_dependent_clearonclose_elements(win) {
   let historymode = win.document.getElementById("historyMode");
--- a/browser/components/preferences/permissions.js
+++ b/browser/components/preferences/permissions.js
@@ -22,17 +22,17 @@ const permissionExceptionsL10n = {
     window: "permissions-exceptions-saved-logins-window",
     description: "permissions-exceptions-saved-logins-desc",
   },
   "install": {
     window: "permissions-exceptions-addons-window",
     description: "permissions-exceptions-addons-desc",
   },
   "autoplay-media": {
-    window: "permissions-exceptions-autoplay-media-window",
+    window: "permissions-exceptions-autoplay-media-window2",
     description: "permissions-exceptions-autoplay-media-desc2",
   },
 };
 
 function Permission(principal, type, capability) {
   this.principal = principal;
   this.origin = principal.origin;
   this.type = type;
--- a/browser/components/translation/test/browser.ini
+++ b/browser/components/translation/test/browser.ini
@@ -6,11 +6,11 @@ support-files =
   fixtures/result-da39a3ee5e.txt
   fixtures/result-yandex-d448894848.json
 
 [browser_translation_bing.js]
 [browser_translation_yandex.js]
 skip-if = os == 'win' && !e10s # bug 1374446
 [browser_translation_telemetry.js]
 [browser_translation_infobar.js]
-skip-if = (verify && debug && (os == 'mac'))
+skip-if = (verify && debug && (os == 'mac')) || (os == 'mac') || (os == 'linux') # Bug 1316953
 [browser_translation_exceptions.js]
-skip-if = (verify && debug && (os == 'mac'))
+skip-if = (verify && debug && (os == 'mac')) || (os == 'linux') || (os == 'mac') # Bug 1387666
--- a/browser/locales/en-US/browser/preferences/permissions.ftl
+++ b/browser/locales/en-US/browser/preferences/permissions.ftl
@@ -93,18 +93,18 @@ permissions-exceptions-saved-logins-desc
 
 permissions-exceptions-addons-window =
     .title = Allowed Websites - Add-ons Installation
     .style = { permissions-window.style }
 permissions-exceptions-addons-desc = You can specify which websites are allowed to install add-ons. Type the exact address of the site you want to allow and then click Allow.
 
 ## Exceptions - Autoplay Media
 
-permissions-exceptions-autoplay-media-window =
-    .title = Allowed Websites - Autoplay
+permissions-exceptions-autoplay-media-window2 =
+    .title = Exceptions - Autoplay
     .style = { permissions-window.style }
 permissions-exceptions-autoplay-media-desc2 = You can specify which websites are always or never allowed to autoplay media with sound. Type the address of the site you want to manage and then click Block or Allow.
 
 ## Site Permissions - Notifications
 
 permissions-site-notification-window =
     .title = Settings - Notification Permissions
     .style = { permissions-window.style }
--- a/browser/locales/en-US/browser/preferences/preferences.ftl
+++ b/browser/locales/en-US/browser/preferences/preferences.ftl
@@ -728,41 +728,47 @@ sitedata-total-size-calculating = Calculating site data and cache size…
 
 # Variables:
 #   $value (Number) - Value of the unit (for example: 4.6, 500)
 #   $unit (String) - Name of the unit (for example: "bytes", "KB")
 sitedata-total-size = Your stored cookies, site data and cache are currently using { $value } { $unit } of disk space.
 
 sitedata-learn-more = Learn more
 
-sitedata-accept-cookies-option =
-    .label = Accept cookies and site data from websites (recommended)
-    .accesskey = A
-
-sitedata-block-cookies-option =
-    .label = Block cookies and site data (may cause websites to break)
-    .accesskey = B
-
 sitedata-keep-until = Keep until
     .accesskey = u
 
 sitedata-keep-until-expire =
     .label = They expire
 sitedata-keep-until-closed =
     .label = { -brand-short-name } is closed
 
-sitedata-accept-third-party-desc = Accept third-party cookies and site data
-    .accesskey = y
+sitedata-allow-cookies-option =
+    .label = Accept cookies and site data
+    .accesskey = A
+
+sitedata-disallow-cookies-option =
+    .label = Block cookies and site data
+    .accesskey = B
 
-sitedata-accept-third-party-always-option =
-    .label = Always
-sitedata-accept-third-party-visited-option =
-    .label = From visited
-sitedata-accept-third-party-never-option =
-    .label = Never
+# This label means 'type of content that is blocked', and is followed by a drop-down list with content types below.
+# The list items are the strings named sitedata-block-*-option*.
+sitedata-block-desc = Type blocked
+    .accesskey = T
+
+sitedata-block-trackers-option-recommended =
+    .label = Third party trackers (recommended)
+sitedata-block-trackers-option =
+    .label = Third party trackers
+sitedata-block-unvisited-option =
+    .label = Cookies from unvisited websites
+sitedata-block-all-third-parties-option =
+    .label = All third-party cookies
+sitedata-block-always-option =
+    .label = All cookies (may cause websites to break)
 
 sitedata-clear =
     .label = Clear Data…
     .accesskey = l
 
 sitedata-settings =
     .label = Manage Data…
     .accesskey = M
@@ -901,16 +907,18 @@ permissions-block-autoplay-media-excepti
 
 autoplay-option-ask =
     .label = Always Ask
 autoplay-option-allow =
     .label = Allow Autoplay
 autoplay-option-dont =
     .label = Don't Autoplay
 
+permissions-autoplay-link = Learn more
+
 permissions-block-popups =
     .label = Block pop-up windows
     .accesskey = B
 
 permissions-block-popups-exceptions =
     .label = Exceptions…
     .accesskey = E
 
--- a/browser/modules/PermissionUI.jsm
+++ b/browser/modules/PermissionUI.jsm
@@ -793,26 +793,29 @@ function AutoplayPermissionPrompt(reques
 AutoplayPermissionPrompt.prototype = {
   __proto__: PermissionPromptForRequestPrototype,
 
   get permissionKey() {
     return "autoplay-media";
   },
 
   get popupOptions() {
+    let learnMoreURL =
+      Services.urlFormatter.formatURLPref("app.support.baseURL") + "block-autoplay";
     let checkbox = {
       show: !PrivateBrowsingUtils.isWindowPrivate(this.browser.ownerGlobal) &&
         !this.principal.URI.schemeIs("file")
     };
     if (checkbox.show) {
       checkbox.checked = true;
       checkbox.label = gBrowserBundle.GetStringFromName("autoplay.remember");
     }
     return {
       checkbox,
+      learnMoreURL,
       displayURI: false,
       name: this.principal.URI.hostPort,
     };
   },
 
   get notificationID() {
     return "autoplay-media";
   },
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -406,17 +406,18 @@ button > hbox > label {
 }
 
 #historyButtons {
   display: flex;
   flex-direction: column;
   justify-content: space-between;
 }
 
-#acceptCookies {
+#blockCookies,
+#keepRow {
   margin-top: 1.5em;
 }
 
 /* Collapse the non-active vboxes in decks to use only the height the
    active vbox needs */
 #historyPane:not([selectedIndex="1"]) > #historyDontRememberPane,
 #historyPane:not([selectedIndex="2"]) > #historyCustomPane,
 #weavePrefsDeck:not([selectedIndex="1"]) > #hasFxaAccount,
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -111,16 +111,17 @@ subsuite = clipboard
 [browser_net_cookies_sorted.js]
 skip-if = (verify && debug && os == 'win')
 [browser_net_copy_as_curl.js]
 subsuite = clipboard
 [browser_net_cors_requests.js]
 [browser_net_cyrillic-01.js]
 [browser_net_cyrillic-02.js]
 [browser_net_frame.js]
+skip-if = (os == 'mac') # Bug 1479782
 [browser_net_header-docs.js]
 [browser_net_filter-01.js]
 [browser_net_filter-02.js]
 [browser_net_filter-03.js]
 [browser_net_filter-04.js]
 [browser_net_filter-autocomplete.js]
 [browser_net_filter-flags.js]
 [browser_net_footer-summary.js]
--- a/devtools/server/actors/addon/console.js
+++ b/devtools/server/actors/addon/console.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-var { ConsoleAPIListener } = require("devtools/server/actors/webconsole/listeners");
+var { ConsoleAPIListener } = require("devtools/server/actors/webconsole/listeners/console-api");
 var { update } = require("devtools/shared/DevToolsUtils");
 
 loader.lazyRequireGetter(this, "WebConsoleActor", "devtools/server/actors/webconsole", true);
 
 const { extend } = require("devtools/shared/extend");
 const { ActorClassWithSpec, Actor } = require("devtools/shared/protocol");
 const { addonConsoleSpec } = require("devtools/shared/specs/addon/console");
 
--- a/devtools/server/actors/moz.build
+++ b/devtools/server/actors/moz.build
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
     'addon',
     'canvas',
     'emulation',
     'highlighters',
     'inspector',
+    'network-monitor',
     'object',
     'replay',
     'targets',
     'utils',
     'webconsole',
     'worker',
 ]
 
--- a/devtools/server/actors/network-monitor.js
+++ b/devtools/server/actors/network-monitor.js
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Actor, ActorClassWithSpec } = require("devtools/shared/protocol");
 const { networkMonitorSpec } = require("devtools/shared/specs/network-monitor");
 
-loader.lazyRequireGetter(this, "NetworkMonitor", "devtools/shared/webconsole/network-monitor", true);
+loader.lazyRequireGetter(this, "NetworkObserver", "devtools/server/actors/network-monitor/network-observer", true);
 loader.lazyRequireGetter(this, "NetworkEventActor", "devtools/server/actors/network-event", true);
 
 const NetworkMonitorActor = ActorClassWithSpec(networkMonitorSpec, {
   _netEvents: new Map(),
   _networkEventActorsByURL: new Map(),
 
   /**
    * NetworkMonitorActor is instanciated from WebConsoleActor.startListeners
@@ -36,18 +36,18 @@ const NetworkMonitorActor = ActorClassWi
   initialize(conn, filters, parentID, messageManager) {
     Actor.prototype.initialize.call(this, conn);
 
     this.parentID = parentID;
     this.messageManager = messageManager;
 
     // Immediately start watching for new request according to `filters`.
     // NetworkMonitor will call `onNetworkEvent` method.
-    this.netMonitor = new NetworkMonitor(filters, this);
-    this.netMonitor.init();
+    this.observer = new NetworkObserver(filters, this);
+    this.observer.init();
 
     this.stackTraces = new Set();
     this.onStackTraceAvailable = this.onStackTraceAvailable.bind(this);
     this.messageManager.addMessageListener("debug:request-stack-available",
       this.onStackTraceAvailable);
     this.onRequestContent = this.onRequestContent.bind(this);
     this.messageManager.addMessageListener("debug:request-content",
       this.onRequestContent);
@@ -66,19 +66,19 @@ const NetworkMonitorActor = ActorClassWi
     if (data.actorID == this.parentID) {
       this.destroy();
     }
   },
 
   destroy() {
     Actor.prototype.destroy.call(this);
 
-    if (this.netMonitor) {
-      this.netMonitor.destroy();
-      this.netMonitor = null;
+    if (this.observer) {
+      this.observer.destroy();
+      this.observer = null;
     }
 
     this.stackTraces.clear();
     if (this.messageManager) {
       this.messageManager.removeMessageListener("debug:request-stack-available",
         this.onStackTraceAvailable);
       this.messageManager.removeMessageListener("debug:request-content",
         this.onRequestContent);
@@ -136,20 +136,20 @@ const NetworkMonitorActor = ActorClassWi
     this.messageManager.sendAsyncMessage("debug:request-content", {
       url,
       content,
     });
   },
 
   onSetPreference({ data }) {
     if ("saveRequestAndResponseBodies" in data) {
-      this.netMonitor.saveRequestAndResponseBodies = data.saveRequestAndResponseBodies;
+      this.observer.saveRequestAndResponseBodies = data.saveRequestAndResponseBodies;
     }
     if ("throttleData" in data) {
-      this.netMonitor.throttleData = data.throttleData;
+      this.observer.throttleData = data.throttleData;
     }
   },
 
   onGetNetworkEventActor({ data }) {
     const actor = this.getNetworkEventActor(data.channelId);
     this.messageManager.sendAsyncMessage("debug:get-network-event-actor", {
       channelId: data.channelId,
       actor: actor.form()
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/network-monitor/channel-event-sink.js
@@ -0,0 +1,84 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {Cc, Ci, Cm, Cr, components} = require("chrome");
+const ChromeUtils = require("ChromeUtils");
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+
+/**
+ * This is a nsIChannelEventSink implementation that monitors channel redirects and
+ * informs the registered StackTraceCollector about the old and new channels.
+ */
+const SINK_CLASS_DESCRIPTION = "NetworkMonitor Channel Event Sink";
+const SINK_CLASS_ID = components.ID("{e89fa076-c845-48a8-8c45-2604729eba1d}");
+const SINK_CONTRACT_ID = "@mozilla.org/network/monitor/channeleventsink;1";
+const SINK_CATEGORY_NAME = "net-channel-event-sinks";
+
+function ChannelEventSink() {
+  this.wrappedJSObject = this;
+  this.collectors = new Set();
+}
+
+ChannelEventSink.prototype = {
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIChannelEventSink]),
+
+  registerCollector(collector) {
+    this.collectors.add(collector);
+  },
+
+  unregisterCollector(collector) {
+    this.collectors.delete(collector);
+
+    if (this.collectors.size == 0) {
+      ChannelEventSinkFactory.unregister();
+    }
+  },
+
+  // eslint-disable-next-line no-shadow
+  asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
+    for (const collector of this.collectors) {
+      try {
+        collector.onChannelRedirect(oldChannel, newChannel, flags);
+      } catch (ex) {
+        console.error("StackTraceCollector.onChannelRedirect threw an exception", ex);
+      }
+    }
+    callback.onRedirectVerifyCallback(Cr.NS_OK);
+  }
+};
+
+const ChannelEventSinkFactory = XPCOMUtils.generateSingletonFactory(ChannelEventSink);
+
+ChannelEventSinkFactory.register = function() {
+  const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+  if (registrar.isCIDRegistered(SINK_CLASS_ID)) {
+    return;
+  }
+
+  registrar.registerFactory(SINK_CLASS_ID,
+                            SINK_CLASS_DESCRIPTION,
+                            SINK_CONTRACT_ID,
+                            ChannelEventSinkFactory);
+
+  XPCOMUtils.categoryManager.addCategoryEntry(SINK_CATEGORY_NAME, SINK_CONTRACT_ID,
+    SINK_CONTRACT_ID, false, true);
+};
+
+ChannelEventSinkFactory.unregister = function() {
+  const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+  registrar.unregisterFactory(SINK_CLASS_ID, ChannelEventSinkFactory);
+
+  XPCOMUtils.categoryManager.deleteCategoryEntry(SINK_CATEGORY_NAME, SINK_CONTRACT_ID,
+    false);
+};
+
+ChannelEventSinkFactory.getService = function() {
+  // Make sure the ChannelEventSink service is registered before accessing it
+  ChannelEventSinkFactory.register();
+
+  return Cc[SINK_CONTRACT_ID].getService(Ci.nsIChannelEventSink).wrappedJSObject;
+};
+exports.ChannelEventSinkFactory = ChannelEventSinkFactory;
copy from devtools/server/actors/moz.build
copy to devtools/server/actors/network-monitor/moz.build
--- a/devtools/server/actors/moz.build
+++ b/devtools/server/actors/network-monitor/moz.build
@@ -1,106 +1,12 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-DIRS += [
-    'addon',
-    'canvas',
-    'emulation',
-    'highlighters',
-    'inspector',
-    'object',
-    'replay',
-    'targets',
-    'utils',
-    'webconsole',
-    'worker',
-]
-
 DevToolsModules(
-    'accessibility-parent.js',
-    'accessibility.js',
-    'actor-registry.js',
-    'animation-type-longhand.js',
-    'animation.js',
-    'array-buffer.js',
-    'breakpoint.js',
-    'call-watcher.js',
-    'canvas.js',
-    'common.js',
-    'css-properties.js',
-    'csscoverage.js',
-    'device.js',
-    'emulation.js',
-    'environment.js',
-    'errordocs.js',
-    'frame.js',
-    'framerate.js',
-    'gcli.js',
-    'heap-snapshot-file.js',
-    'highlighters.css',
-    'highlighters.js',
-    'layout.js',
-    'memory.js',
-    'network-event.js',
-    'network-monitor.js',
-    'object.js',
-    'pause-scoped.js',
-    'perf.js',
-    'performance-recording.js',
-    'performance.js',
-    'preference.js',
-    'pretty-print-worker.js',
-    'process.js',
-    'promises.js',
-    'reflow.js',
-    'root.js',
-    'source.js',
-    'storage.js',
-    'string.js',
-    'styles.js',
-    'stylesheets.js',
-    'thread.js',
-    'timeline.js',
-    'webaudio.js',
-    'webbrowser.js',
-    'webconsole.js',
-    'webgl.js',
+    'channel-event-sink.js',
+    'network-observer.js',
+    'network-response-listener.js',
+    'stack-trace-collector.js',
 )
-
-with Files('animation.js'):
-    BUG_COMPONENT = ('DevTools', 'Animation Inspector')
-
-with Files('breakpoint.js'):
-    BUG_COMPONENT = ('DevTools', 'Debugger')
-
-with Files('css-properties.js'):
-    BUG_COMPONENT = ('DevTools', 'CSS Rules Inspector')
-
-with Files('csscoverage.js'):
-    BUG_COMPONENT = ('DevTools', 'Graphics Commandline and Toolbar')
-
-with Files('memory.js'):
-    BUG_COMPONENT = ('DevTools', 'Memory')
-
-with Files('performance*'):
-    BUG_COMPONENT = ('DevTools', 'Performance Tools (Profiler/Timeline)')
-
-with Files('source.js'):
-    BUG_COMPONENT = ('DevTools', 'Debugger')
-
-with Files('storage.js'):
-    BUG_COMPONENT = ('DevTools', 'Storage Inspector')
-
-with Files('stylesheets.js'):
-    BUG_COMPONENT = ('DevTools', 'Style Editor')
-
-with Files('webaudio.js'):
-    BUG_COMPONENT = ('DevTools', 'Web Audio Editor')
-
-with Files('webconsole.js'):
-    BUG_COMPONENT = ('DevTools', 'Console')
-
-with Files('webgl.js'):
-    BUG_COMPONENT = ('DevTools', 'WebGL Shader Editor')
rename from devtools/shared/webconsole/network-monitor.js
rename to devtools/server/actors/network-monitor/network-observer.js
--- a/devtools/shared/webconsole/network-monitor.js
+++ b/devtools/server/actors/network-monitor/network-observer.js
@@ -1,35 +1,31 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft= javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {Cc, Ci, Cm, Cr, components} = require("chrome");
-const ChromeUtils = require("ChromeUtils");
+const {Cc, Ci} = require("chrome");
 const Services = require("Services");
-const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+const flags = require("devtools/shared/flags");
 
 loader.lazyRequireGetter(this, "NetworkHelper",
-                         "devtools/shared/webconsole/network-helper");
+  "devtools/shared/webconsole/network-helper");
 loader.lazyRequireGetter(this, "DevToolsUtils",
-                         "devtools/shared/DevToolsUtils");
-loader.lazyRequireGetter(this, "flags",
-                         "devtools/shared/flags");
+  "devtools/shared/DevToolsUtils");
 loader.lazyRequireGetter(this, "NetworkThrottleManager",
-                         "devtools/shared/webconsole/throttle", true);
-loader.lazyRequireGetter(this, "CacheEntry",
-                         "devtools/shared/platform/cache-entry", true);
-loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
+  "devtools/shared/webconsole/throttle", true);
 loader.lazyServiceGetter(this, "gActivityDistributor",
-                         "@mozilla.org/network/http-activity-distributor;1",
-                         "nsIHttpActivityDistributor");
+  "@mozilla.org/network/http-activity-distributor;1",
+  "nsIHttpActivityDistributor");
+loader.lazyRequireGetter(this, "NetworkResponseListener",
+  "devtools/server/actors/network-monitor/network-response-listener", true);
 
 // Network logging
 
 // The maximum uint32 value.
 const PR_UINT32_MAX = 4294967295;
 
 // HTTP status codes.
 const HTTP_MOVED_PERMANENTLY = 301;
@@ -39,17 +35,17 @@ const HTTP_TEMPORARY_REDIRECT = 307;
 
 /**
  * Check if a given network request should be logged by a network monitor
  * based on the specified filters.
  *
  * @param nsIHttpChannel channel
  *        Request to check.
  * @param filters
- *        NetworkMonitor filters to match against.
+ *        NetworkObserver filters to match against.
  * @return boolean
  *         True if the network request should be logged, false otherwise.
  */
 function matchRequest(channel, filters) {
   // Log everything if no filter is specified
   if (!filters.outerWindowID && !filters.window) {
     return true;
   }
@@ -93,672 +89,17 @@ function matchRequest(channel, filters) 
         // outerWindowID getter from browser.xml (non-remote <xul:browser>) may
         // throw when closing a tab while resources are still loading.
       }
     }
   }
 
   return false;
 }
-
-/**
- * This is a nsIChannelEventSink implementation that monitors channel redirects and
- * informs the registered StackTraceCollector about the old and new channels.
- */
-const SINK_CLASS_DESCRIPTION = "NetworkMonitor Channel Event Sink";
-const SINK_CLASS_ID = components.ID("{e89fa076-c845-48a8-8c45-2604729eba1d}");
-const SINK_CONTRACT_ID = "@mozilla.org/network/monitor/channeleventsink;1";
-const SINK_CATEGORY_NAME = "net-channel-event-sinks";
-
-function ChannelEventSink() {
-  this.wrappedJSObject = this;
-  this.collectors = new Set();
-}
-
-ChannelEventSink.prototype = {
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIChannelEventSink]),
-
-  registerCollector(collector) {
-    this.collectors.add(collector);
-  },
-
-  unregisterCollector(collector) {
-    this.collectors.delete(collector);
-
-    if (this.collectors.size == 0) {
-      ChannelEventSinkFactory.unregister();
-    }
-  },
-
-  // eslint-disable-next-line no-shadow
-  asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
-    for (const collector of this.collectors) {
-      try {
-        collector.onChannelRedirect(oldChannel, newChannel, flags);
-      } catch (ex) {
-        console.error("StackTraceCollector.onChannelRedirect threw an exception", ex);
-      }
-    }
-    callback.onRedirectVerifyCallback(Cr.NS_OK);
-  }
-};
-
-const ChannelEventSinkFactory = XPCOMUtils.generateSingletonFactory(ChannelEventSink);
-
-ChannelEventSinkFactory.register = function() {
-  const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
-  if (registrar.isCIDRegistered(SINK_CLASS_ID)) {
-    return;
-  }
-
-  registrar.registerFactory(SINK_CLASS_ID,
-                            SINK_CLASS_DESCRIPTION,
-                            SINK_CONTRACT_ID,
-                            ChannelEventSinkFactory);
-
-  XPCOMUtils.categoryManager.addCategoryEntry(SINK_CATEGORY_NAME, SINK_CONTRACT_ID,
-    SINK_CONTRACT_ID, false, true);
-};
-
-ChannelEventSinkFactory.unregister = function() {
-  const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
-  registrar.unregisterFactory(SINK_CLASS_ID, ChannelEventSinkFactory);
-
-  XPCOMUtils.categoryManager.deleteCategoryEntry(SINK_CATEGORY_NAME, SINK_CONTRACT_ID,
-    false);
-};
-
-ChannelEventSinkFactory.getService = function() {
-  // Make sure the ChannelEventSink service is registered before accessing it
-  ChannelEventSinkFactory.register();
-
-  return Cc[SINK_CONTRACT_ID].getService(Ci.nsIChannelEventSink).wrappedJSObject;
-};
-
-function StackTraceCollector(filters, netmonitors) {
-  this.filters = filters;
-  this.stacktracesById = new Map();
-  this.netmonitors = netmonitors;
-}
-
-StackTraceCollector.prototype = {
-  init() {
-    Services.obs.addObserver(this, "http-on-opening-request");
-    ChannelEventSinkFactory.getService().registerCollector(this);
-    this.onGetStack = this.onGetStack.bind(this);
-    for (const { messageManager } of this.netmonitors) {
-      messageManager.addMessageListener("debug:request-stack", this.onGetStack);
-    }
-  },
-
-  destroy() {
-    Services.obs.removeObserver(this, "http-on-opening-request");
-    ChannelEventSinkFactory.getService().unregisterCollector(this);
-    for (const { messageManager } of this.netmonitors) {
-      messageManager.removeMessageListener("debug:request-stack", this.onGetStack);
-    }
-  },
-
-  _saveStackTrace(channel, stacktrace) {
-    for (const { messageManager } of this.netmonitors) {
-      messageManager.sendAsyncMessage("debug:request-stack-available", {
-        channelId: channel.channelId,
-        stacktrace: stacktrace && stacktrace.length > 0
-      });
-    }
-    this.stacktracesById.set(channel.channelId, stacktrace);
-  },
-
-  observe(subject) {
-    const channel = subject.QueryInterface(Ci.nsIHttpChannel);
-
-    if (!matchRequest(channel, this.filters)) {
-      return;
-    }
-
-    // Convert the nsIStackFrame XPCOM objects to a nice JSON that can be
-    // passed around through message managers etc.
-    let frame = components.stack;
-    const stacktrace = [];
-    if (frame && frame.caller) {
-      frame = frame.caller;
-      while (frame) {
-        stacktrace.push({
-          filename: frame.filename,
-          lineNumber: frame.lineNumber,
-          columnNumber: frame.columnNumber,
-          functionName: frame.name,
-          asyncCause: frame.asyncCause,
-        });
-        frame = frame.caller || frame.asyncCaller;
-      }
-    }
-
-    this._saveStackTrace(channel, stacktrace);
-  },
-
-  // eslint-disable-next-line no-shadow
-  onChannelRedirect(oldChannel, newChannel, flags) {
-    // We can be called with any nsIChannel, but are interested only in HTTP channels
-    try {
-      oldChannel.QueryInterface(Ci.nsIHttpChannel);
-      newChannel.QueryInterface(Ci.nsIHttpChannel);
-    } catch (ex) {
-      return;
-    }
-
-    const oldId = oldChannel.channelId;
-    const stacktrace = this.stacktracesById.get(oldId);
-    if (stacktrace) {
-      this._saveStackTrace(newChannel, stacktrace);
-    }
-  },
-
-  getStackTrace(channelId) {
-    const trace = this.stacktracesById.get(channelId);
-    this.stacktracesById.delete(channelId);
-    return trace;
-  },
-
-  onGetStack(msg) {
-    const messageManager = msg.target;
-    const channelId = msg.data;
-    const stack = this.getStackTrace(channelId);
-    messageManager.sendAsyncMessage("debug:request-stack", {
-      channelId,
-      stack,
-    });
-  },
-};
-
-exports.StackTraceCollector = StackTraceCollector;
-
-/**
- * The network response listener implements the nsIStreamListener and
- * nsIRequestObserver interfaces. This is used within the NetworkMonitor feature
- * to get the response body of the request.
- *
- * The code is mostly based on code listings from:
- *
- *   http://www.softwareishard.com/blog/firebug/
- *      nsitraceablechannel-intercept-http-traffic/
- *
- * @constructor
- * @param object owner
- *        The response listener owner. This object needs to hold the
- *        |openResponses| object.
- * @param object httpActivity
- *        HttpActivity object associated with this request. See NetworkMonitor
- *        for more information.
- */
-function NetworkResponseListener(owner, httpActivity) {
-  this.owner = owner;
-  this.receivedData = "";
-  this.httpActivity = httpActivity;
-  this.bodySize = 0;
-  // Indicates if the response had a size greater than response body limit.
-  this.truncated = false;
-  // Note that this is really only needed for the non-e10s case.
-  // See bug 1309523.
-  const channel = this.httpActivity.channel;
-  this._wrappedNotificationCallbacks = channel.notificationCallbacks;
-  channel.notificationCallbacks = this;
-}
-
-NetworkResponseListener.prototype = {
-  QueryInterface:
-    ChromeUtils.generateQI([Ci.nsIStreamListener, Ci.nsIInputStreamCallback,
-                            Ci.nsIRequestObserver, Ci.nsIInterfaceRequestor]),
-
-  // nsIInterfaceRequestor implementation
-
-  /**
-   * This object implements nsIProgressEventSink, but also needs to forward
-   * interface requests to the notification callbacks of other objects.
-   */
-  getInterface(iid) {
-    if (iid.equals(Ci.nsIProgressEventSink)) {
-      return this;
-    }
-    if (this._wrappedNotificationCallbacks) {
-      return this._wrappedNotificationCallbacks.getInterface(iid);
-    }
-    throw Cr.NS_ERROR_NO_INTERFACE;
-  },
-
-  /**
-   * Forward notifications for interfaces this object implements, in case other
-   * objects also implemented them.
-   */
-  _forwardNotification(iid, method, args) {
-    if (!this._wrappedNotificationCallbacks) {
-      return;
-    }
-    try {
-      const impl = this._wrappedNotificationCallbacks.getInterface(iid);
-      impl[method].apply(impl, args);
-    } catch (e) {
-      if (e.result != Cr.NS_ERROR_NO_INTERFACE) {
-        throw e;
-      }
-    }
-  },
-
-  /**
-   * This NetworkResponseListener tracks the NetworkMonitor.openResponses object
-   * to find the associated uncached headers.
-   * @private
-   */
-  _foundOpenResponse: false,
-
-  /**
-   * If the channel already had notificationCallbacks, hold them here internally
-   * so that we can forward getInterface requests to that object.
-   */
-  _wrappedNotificationCallbacks: null,
-
-  /**
-   * The response listener owner.
-   */
-  owner: null,
-
-  /**
-   * The response will be written into the outputStream of this nsIPipe.
-   * Both ends of the pipe must be blocking.
-   */
-  sink: null,
-
-  /**
-   * The HttpActivity object associated with this response.
-   */
-  httpActivity: null,
-
-  /**
-   * Stores the received data as a string.
-   */
-  receivedData: null,
-
-  /**
-   * The uncompressed, decoded response body size.
-   */
-  bodySize: null,
-
-  /**
-   * Response size on the wire, potentially compressed / encoded.
-   */
-  transferredSize: null,
-
-  /**
-   * The nsIRequest we are started for.
-   */
-  request: null,
-
-  /**
-   * Set the async listener for the given nsIAsyncInputStream. This allows us to
-   * wait asynchronously for any data coming from the stream.
-   *
-   * @param nsIAsyncInputStream stream
-   *        The input stream from where we are waiting for data to come in.
-   * @param nsIInputStreamCallback listener
-   *        The input stream callback you want. This is an object that must have
-   *        the onInputStreamReady() method. If the argument is null, then the
-   *        current callback is removed.
-   * @return void
-   */
-  setAsyncListener: function(stream, listener) {
-    // Asynchronously wait for the stream to be readable or closed.
-    stream.asyncWait(listener, 0, 0, Services.tm.mainThread);
-  },
-
-  /**
-   * Stores the received data, if request/response body logging is enabled. It
-   * also does limit the number of stored bytes, based on the
-   * `devtools.netmonitor.responseBodyLimit` pref.
-   *
-   * Learn more about nsIStreamListener at:
-   * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIStreamListener
-   *
-   * @param nsIRequest request
-   * @param nsISupports context
-   * @param nsIInputStream inputStream
-   * @param unsigned long offset
-   * @param unsigned long count
-   */
-  onDataAvailable: function(request, context, inputStream, offset, count) {
-    this._findOpenResponse();
-    const data = NetUtil.readInputStreamToString(inputStream, count);
-
-    this.bodySize += count;
-
-    if (!this.httpActivity.discardResponseBody) {
-      const limit = Services.prefs.getIntPref("devtools.netmonitor.responseBodyLimit");
-      if (this.receivedData.length <= limit || limit == 0) {
-        this.receivedData +=
-          NetworkHelper.convertToUnicode(data, request.contentCharset);
-      }
-      if (this.receivedData.length > limit && limit > 0) {
-        this.receivedData = this.receivedData.substr(0, limit);
-        this.truncated = true;
-      }
-    }
-  },
-
-  /**
-   * See documentation at
-   * https://developer.mozilla.org/En/NsIRequestObserver
-   *
-   * @param nsIRequest request
-   * @param nsISupports context
-   */
-  onStartRequest: function(request) {
-    // Converter will call this again, we should just ignore that.
-    if (this.request) {
-      return;
-    }
-
-    this.request = request;
-    this._getSecurityInfo();
-    this._findOpenResponse();
-    // We need to track the offset for the onDataAvailable calls where
-    // we pass the data from our pipe to the converter.
-    this.offset = 0;
-
-    const channel = this.request;
-
-    // Bug 1372115 - We should load bytecode cached requests from cache as the actual
-    // channel content is going to be optimized data that reflects platform internals
-    // instead of the content user expects (i.e. content served by HTTP server)
-    // Note that bytecode cached is one example, there may be wasm or other usecase in
-    // future.
-    let isOptimizedContent = false;
-    try {
-      if (channel instanceof Ci.nsICacheInfoChannel) {
-        isOptimizedContent = channel.alternativeDataType;
-      }
-    } catch (e) {
-      // Accessing `alternativeDataType` for some SW requests throws.
-    }
-    if (isOptimizedContent) {
-      let charset;
-      try {
-        charset = this.request.contentCharset;
-      } catch (e) {
-        // Accessing the charset sometimes throws NS_ERROR_NOT_AVAILABLE when
-        // reloading the page
-      }
-      if (!charset) {
-        charset = this.httpActivity.charset;
-      }
-      NetworkHelper.loadFromCache(this.httpActivity.url, charset,
-                                  this._onComplete.bind(this));
-      return;
-    }
-
-    // In the multi-process mode, the conversion happens on the child
-    // side while we can only monitor the channel on the parent
-    // side. If the content is gzipped, we have to unzip it
-    // ourself. For that we use the stream converter services.  Do not
-    // do that for Service workers as they are run in the child
-    // process.
-    if (!this.httpActivity.fromServiceWorker &&
-        channel instanceof Ci.nsIEncodedChannel &&
-        channel.contentEncodings &&
-        !channel.applyConversion) {
-      const encodingHeader = channel.getResponseHeader("Content-Encoding");
-      const scs = Cc["@mozilla.org/streamConverters;1"]
-        .getService(Ci.nsIStreamConverterService);
-      const encodings = encodingHeader.split(/\s*\t*,\s*\t*/);
-      let nextListener = this;
-      const acceptedEncodings = ["gzip", "deflate", "br", "x-gzip", "x-deflate"];
-      for (const i in encodings) {
-        // There can be multiple conversions applied
-        const enc = encodings[i].toLowerCase();
-        if (acceptedEncodings.indexOf(enc) > -1) {
-          this.converter = scs.asyncConvertData(enc, "uncompressed",
-                                                nextListener, null);
-          nextListener = this.converter;
-        }
-      }
-      if (this.converter) {
-        this.converter.onStartRequest(this.request, null);
-      }
-    }
-    // Asynchronously wait for the data coming from the request.
-    this.setAsyncListener(this.sink.inputStream, this);
-  },
-
-  /**
-   * Parse security state of this request and report it to the client.
-   */
-  _getSecurityInfo: DevToolsUtils.makeInfallible(function() {
-    // Many properties of the securityInfo (e.g., the server certificate or HPKP
-    // status) are not available in the content process and can't be even touched safely,
-    // because their C++ getters trigger assertions. This function is called in content
-    // process for synthesized responses from service workers, in the parent otherwise.
-    if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
-      return;
-    }
-
-    // Take the security information from the original nsIHTTPChannel instead of
-    // the nsIRequest received in onStartRequest. If response to this request
-    // was a redirect from http to https, the request object seems to contain
-    // security info for the https request after redirect.
-    const secinfo = this.httpActivity.channel.securityInfo;
-    const info = NetworkHelper.parseSecurityInfo(secinfo, this.httpActivity);
-
-    this.httpActivity.owner.addSecurityInfo(info);
-  }),
-
-  /**
-   * Fetches cache information from CacheEntry
-   * @private
-   */
-  _fetchCacheInformation: function() {
-    const httpActivity = this.httpActivity;
-    CacheEntry.getCacheEntry(this.request, (descriptor) => {
-      httpActivity.owner.addResponseCache({
-        responseCache: descriptor
-      });
-    });
-  },
-
-  /**
-   * Handle the onStopRequest by closing the sink output stream.
-   *
-   * For more documentation about nsIRequestObserver go to:
-   * https://developer.mozilla.org/En/NsIRequestObserver
-   */
-  onStopRequest: function() {
-    // Bug 1429365: onStopRequest may be called after onComplete for resources loaded
-    // from bytecode cache.
-    if (!this.httpActivity) {
-      return;
-    }
-    this._findOpenResponse();
-    this.sink.outputStream.close();
-  },
-
-  // nsIProgressEventSink implementation
-
-  /**
-   * Handle progress event as data is transferred.  This is used to record the
-   * size on the wire, which may be compressed / encoded.
-   */
-  onProgress: function(request, context, progress, progressMax) {
-    this.transferredSize = progress;
-    // Need to forward as well to keep things like Download Manager's progress
-    // bar working properly.
-    this._forwardNotification(Ci.nsIProgressEventSink, "onProgress", arguments);
-  },
-
-  onStatus: function() {
-    this._forwardNotification(Ci.nsIProgressEventSink, "onStatus", arguments);
-  },
-
-  /**
-   * Find the open response object associated to the current request. The
-   * NetworkMonitor._httpResponseExaminer() method saves the response headers in
-   * NetworkMonitor.openResponses. This method takes the data from the open
-   * response object and puts it into the HTTP activity object, then sends it to
-   * the remote Web Console instance.
-   *
-   * @private
-   */
-  _findOpenResponse: function() {
-    if (!this.owner || this._foundOpenResponse) {
-      return;
-    }
-
-    const channel = this.httpActivity.channel;
-    const openResponse = this.owner.openResponses.get(channel);
-    if (!openResponse) {
-      return;
-    }
-    this._foundOpenResponse = true;
-    this.owner.openResponses.delete(channel);
-
-    this.httpActivity.owner.addResponseHeaders(openResponse.headers);
-    this.httpActivity.owner.addResponseCookies(openResponse.cookies);
-  },
-
-  /**
-   * Clean up the response listener once the response input stream is closed.
-   * This is called from onStopRequest() or from onInputStreamReady() when the
-   * stream is closed.
-   * @return void
-   */
-  onStreamClose: function() {
-    if (!this.httpActivity) {
-      return;
-    }
-    // Remove our listener from the request input stream.
-    this.setAsyncListener(this.sink.inputStream, null);
-
-    this._findOpenResponse();
-    if (this.request.fromCache || this.httpActivity.responseStatus == 304) {
-      this._fetchCacheInformation();
-    }
-
-    if (!this.httpActivity.discardResponseBody && this.receivedData.length) {
-      this._onComplete(this.receivedData);
-    } else if (!this.httpActivity.discardResponseBody &&
-               this.httpActivity.responseStatus == 304) {
-      // Response is cached, so we load it from cache.
-      let charset;
-      try {
-        charset = this.request.contentCharset;
-      } catch (e) {
-        // Accessing the charset sometimes throws NS_ERROR_NOT_AVAILABLE when
-        // reloading the page
-      }
-      if (!charset) {
-        charset = this.httpActivity.charset;
-      }
-      NetworkHelper.loadFromCache(this.httpActivity.url, charset,
-                                  this._onComplete.bind(this));
-    } else {
-      this._onComplete();
-    }
-  },
-
-  /**
-   * Handler for when the response completes. This function cleans up the
-   * response listener.
-   *
-   * @param string [data]
-   *        Optional, the received data coming from the response listener or
-   *        from the cache.
-   */
-  _onComplete: function(data) {
-    const response = {
-      mimeType: "",
-      text: data || "",
-    };
-
-    response.size = this.bodySize;
-    response.transferredSize = this.transferredSize + this.httpActivity.headersSize;
-
-    try {
-      response.mimeType = this.request.contentType;
-    } catch (ex) {
-      // Ignore.
-    }
-
-    if (!response.mimeType ||
-        !NetworkHelper.isTextMimeType(response.mimeType)) {
-      response.encoding = "base64";
-      try {
-        response.text = btoa(response.text);
-      } catch (err) {
-        // Ignore.
-      }
-    }
-
-    if (response.mimeType && this.request.contentCharset) {
-      response.mimeType += "; charset=" + this.request.contentCharset;
-    }
-
-    this.receivedData = "";
-
-    this.httpActivity.owner.addResponseContent(
-      response,
-      {
-        discardResponseBody: this.httpActivity.discardResponseBody,
-        truncated: this.truncated
-      }
-    );
-
-    this._wrappedNotificationCallbacks = null;
-    this.httpActivity = null;
-    this.sink = null;
-    this.inputStream = null;
-    this.converter = null;
-    this.request = null;
-    this.owner = null;
-  },
-
-  /**
-   * The nsIInputStreamCallback for when the request input stream is ready -
-   * either it has more data or it is closed.
-   *
-   * @param nsIAsyncInputStream stream
-   *        The sink input stream from which data is coming.
-   * @returns void
-   */
-  onInputStreamReady: function(stream) {
-    if (!(stream instanceof Ci.nsIAsyncInputStream) || !this.httpActivity) {
-      return;
-    }
-
-    let available = -1;
-    try {
-      // This may throw if the stream is closed normally or due to an error.
-      available = stream.available();
-    } catch (ex) {
-      // Ignore.
-    }
-
-    if (available != -1) {
-      if (available != 0) {
-        if (this.converter) {
-          this.converter.onDataAvailable(this.request, null, stream,
-                                         this.offset, available);
-        } else {
-          this.onDataAvailable(this.request, null, stream, this.offset,
-                               available);
-        }
-      }
-      this.offset += available;
-      this.setAsyncListener(stream, this);
-    } else {
-      this.onStreamClose();
-      this.offset = 0;
-    }
-  },
-};
+exports.matchRequest = matchRequest;
 
 /**
  * The network monitor uses the nsIHttpActivityDistributor to monitor network
  * requests. The nsIObserverService is also used for monitoring
  * http-on-examine-response notifications. All network request information is
  * routed to the remote Web Console.
  *
  * @constructor
@@ -773,33 +114,33 @@ NetworkResponseListener.prototype = {
  * @param object owner
  *        The network monitor owner. This object needs to hold:
  *        - onNetworkEvent(requestInfo)
  *          This method is invoked once for every new network request and it is
  *          given the initial network request information as an argument.
  *          onNetworkEvent() must return an object which holds several add*()
  *          methods which are used to add further network request/response information.
  */
-function NetworkMonitor(filters, owner) {
+function NetworkObserver(filters, owner) {
   this.filters = filters;
   this.owner = owner;
   this.openRequests = new Map();
   this.openResponses = new Map();
   this._httpResponseExaminer =
     DevToolsUtils.makeInfallible(this._httpResponseExaminer).bind(this);
   this._httpModifyExaminer =
     DevToolsUtils.makeInfallible(this._httpModifyExaminer).bind(this);
   this._serviceWorkerRequest = this._serviceWorkerRequest.bind(this);
   this._throttleData = null;
   this._throttler = null;
 }
 
-exports.NetworkMonitor = NetworkMonitor;
+exports.NetworkObserver = NetworkObserver;
 
-NetworkMonitor.prototype = {
+NetworkObserver.prototype = {
   filters: null,
 
   httpTransactionCodes: {
     0x5001: "REQUEST_HEADER",
     0x5002: "REQUEST_BODY_SENT",
     0x5003: "RESPONSE_START",
     0x5004: "RESPONSE_HEADER",
     0x5005: "RESPONSE_COMPLETE",
@@ -1744,232 +1085,16 @@ NetworkMonitor.prototype = {
     this.openRequests.clear();
     this.openResponses.clear();
     this.owner = null;
     this.filters = null;
     this._throttler = null;
   },
 };
 
-/**
- * A WebProgressListener that listens for location changes.
- *
- * This progress listener is used to track file loads and other kinds of
- * location changes.
- *
- * @constructor
- * @param object window
- *        The window for which we need to track location changes.
- * @param object owner
- *        The listener owner which needs to implement two methods:
- *        - onFileActivity(aFileURI)
- *        - onLocationChange(aState, aTabURI, aPageTitle)
- */
-function ConsoleProgressListener(window, owner) {
-  this.window = window;
-  this.owner = owner;
-}
-exports.ConsoleProgressListener = ConsoleProgressListener;
-
-ConsoleProgressListener.prototype = {
-  /**
-   * Constant used for startMonitor()/stopMonitor() that tells you want to
-   * monitor file loads.
-   */
-  MONITOR_FILE_ACTIVITY: 1,
-
-  /**
-   * Constant used for startMonitor()/stopMonitor() that tells you want to
-   * monitor page location changes.
-   */
-  MONITOR_LOCATION_CHANGE: 2,
-
-  /**
-   * Tells if you want to monitor file activity.
-   * @private
-   * @type boolean
-   */
-  _fileActivity: false,
-
-  /**
-   * Tells if you want to monitor location changes.
-   * @private
-   * @type boolean
-   */
-  _locationChange: false,
-
-  /**
-   * Tells if the console progress listener is initialized or not.
-   * @private
-   * @type boolean
-   */
-  _initialized: false,
-
-  _webProgress: null,
-
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener,
-                                          Ci.nsISupportsWeakReference]),
-
-  /**
-   * Initialize the ConsoleProgressListener.
-   * @private
-   */
-  _init: function() {
-    if (this._initialized) {
-      return;
-    }
-
-    this._webProgress = this.window.docShell.QueryInterface(Ci.nsIWebProgress);
-    this._webProgress.addProgressListener(this,
-                                          Ci.nsIWebProgress.NOTIFY_STATE_ALL);
-
-    this._initialized = true;
-  },
-
-  /**
-   * Start a monitor/tracker related to the current nsIWebProgressListener
-   * instance.
-   *
-   * @param number monitor
-   *        Tells what you want to track. Available constants:
-   *        - this.MONITOR_FILE_ACTIVITY
-   *          Track file loads.
-   *        - this.MONITOR_LOCATION_CHANGE
-   *          Track location changes for the top window.
-   */
-  startMonitor: function(monitor) {
-    switch (monitor) {
-      case this.MONITOR_FILE_ACTIVITY:
-        this._fileActivity = true;
-        break;
-      case this.MONITOR_LOCATION_CHANGE:
-        this._locationChange = true;
-        break;
-      default:
-        throw new Error("ConsoleProgressListener: unknown monitor type " +
-                        monitor + "!");
-    }
-    this._init();
-  },
-
-  /**
-   * Stop a monitor.
-   *
-   * @param number monitor
-   *        Tells what you want to stop tracking. See this.startMonitor() for
-   *        the list of constants.
-   */
-  stopMonitor: function(monitor) {
-    switch (monitor) {
-      case this.MONITOR_FILE_ACTIVITY:
-        this._fileActivity = false;
-        break;
-      case this.MONITOR_LOCATION_CHANGE:
-        this._locationChange = false;
-        break;
-      default:
-        throw new Error("ConsoleProgressListener: unknown monitor type " +
-                        monitor + "!");
-    }
-
-    if (!this._fileActivity && !this._locationChange) {
-      this.destroy();
-    }
-  },
-
-  onStateChange: function(progress, request, state, status) {
-    if (!this.owner) {
-      return;
-    }
-
-    if (this._fileActivity) {
-      this._checkFileActivity(progress, request, state, status);
-    }
-
-    if (this._locationChange) {
-      this._checkLocationChange(progress, request, state, status);
-    }
-  },
-
-  /**
-   * Check if there is any file load, given the arguments of
-   * nsIWebProgressListener.onStateChange. If the state change tells that a file
-   * URI has been loaded, then the remote Web Console instance is notified.
-   * @private
-   */
-  _checkFileActivity: function(progress, request, state, status) {
-    if (!(state & Ci.nsIWebProgressListener.STATE_START)) {
-      return;
-    }
-
-    let uri = null;
-    if (request instanceof Ci.imgIRequest) {
-      const imgIRequest = request.QueryInterface(Ci.imgIRequest);
-      uri = imgIRequest.URI;
-    } else if (request instanceof Ci.nsIChannel) {
-      const nsIChannel = request.QueryInterface(Ci.nsIChannel);
-      uri = nsIChannel.URI;
-    }
-
-    if (!uri || !uri.schemeIs("file") && !uri.schemeIs("ftp")) {
-      return;
-    }
-
-    this.owner.onFileActivity(uri.spec);
-  },
-
-  /**
-   * Check if the current window.top location is changing, given the arguments
-   * of nsIWebProgressListener.onStateChange. If that is the case, the remote
-   * Web Console instance is notified.
-   * @private
-   */
-  _checkLocationChange: function(progress, request, state) {
-    const isStart = state & Ci.nsIWebProgressListener.STATE_START;
-    const isStop = state & Ci.nsIWebProgressListener.STATE_STOP;
-    const isNetwork = state & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
-    const isWindow = state & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
-
-    // Skip non-interesting states.
-    if (!isNetwork || !isWindow || progress.DOMWindow != this.window) {
-      return;
-    }
-
-    if (isStart && request instanceof Ci.nsIChannel) {
-      this.owner.onLocationChange("start", request.URI.spec, "");
-    } else if (isStop) {
-      this.owner.onLocationChange("stop", this.window.location.href,
-                                  this.window.document.title);
-    }
-  },
-
-  /**
-   * Destroy the ConsoleProgressListener.
-   */
-  destroy: function() {
-    if (!this._initialized) {
-      return;
-    }
-
-    this._initialized = false;
-    this._fileActivity = false;
-    this._locationChange = false;
-
-    try {
-      this._webProgress.removeProgressListener(this);
-    } catch (ex) {
-      // This can throw during browser shutdown.
-    }
-
-    this._webProgress = null;
-    this.window = null;
-    this.owner = null;
-  },
-};
-
 function gSequenceId() {
   return gSequenceId.n++;
 }
 gSequenceId.n = 1;
 
 /**
  * Convert a nsIContentPolicy constant to a display string
  */
copy from devtools/shared/webconsole/network-monitor.js
copy to devtools/server/actors/network-monitor/network-response-listener.js
--- a/devtools/shared/webconsole/network-monitor.js
+++ b/devtools/server/actors/network-monitor/network-response-listener.js
@@ -1,31 +1,30 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft= javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {Cc, Ci, Cm, Cr, components} = require("chrome");
+const {Cc, Ci, Cr} = require("chrome");
 const ChromeUtils = require("ChromeUtils");
 const Services = require("Services");
-const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
 
 loader.lazyRequireGetter(this, "NetworkHelper",
                          "devtools/shared/webconsole/network-helper");
 loader.lazyRequireGetter(this, "DevToolsUtils",
                          "devtools/shared/DevToolsUtils");
-loader.lazyRequireGetter(this, "flags",
-                         "devtools/shared/flags");
 loader.lazyRequireGetter(this, "NetworkThrottleManager",
                          "devtools/shared/webconsole/throttle", true);
 loader.lazyRequireGetter(this, "CacheEntry",
                          "devtools/shared/platform/cache-entry", true);
+loader.lazyRequireGetter(this, "matchRequest",
+                         "devtools/server/actors/network-monitor/network-observer", true);
 loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
 loader.lazyServiceGetter(this, "gActivityDistributor",
                          "@mozilla.org/network/http-activity-distributor;1",
                          "nsIHttpActivityDistributor");
 
 // Network logging
 
 // The maximum uint32 value.
@@ -33,281 +32,49 @@ const PR_UINT32_MAX = 4294967295;
 
 // HTTP status codes.
 const HTTP_MOVED_PERMANENTLY = 301;
 const HTTP_FOUND = 302;
 const HTTP_SEE_OTHER = 303;
 const HTTP_TEMPORARY_REDIRECT = 307;
 
 /**
- * Check if a given network request should be logged by a network monitor
- * based on the specified filters.
- *
- * @param nsIHttpChannel channel
- *        Request to check.
- * @param filters
- *        NetworkMonitor filters to match against.
- * @return boolean
- *         True if the network request should be logged, false otherwise.
- */
-function matchRequest(channel, filters) {
-  // Log everything if no filter is specified
-  if (!filters.outerWindowID && !filters.window) {
-    return true;
-  }
-
-  // Ignore requests from chrome or add-on code when we are monitoring
-  // content.
-  // TODO: one particular test (browser_styleeditor_fetch-from-cache.js) needs
-  // the flags.testing check. We will move to a better way to serve
-  // its needs in bug 1167188, where this check should be removed.
-  if (!flags.testing && channel.loadInfo &&
-      channel.loadInfo.loadingDocument === null &&
-      channel.loadInfo.loadingPrincipal ===
-      Services.scriptSecurityManager.getSystemPrincipal()) {
-    return false;
-  }
-
-  if (filters.window) {
-    // Since frames support, this.window may not be the top level content
-    // frame, so that we can't only compare with win.top.
-    let win = NetworkHelper.getWindowForRequest(channel);
-    while (win) {
-      if (win == filters.window) {
-        return true;
-      }
-      if (win.parent == win) {
-        break;
-      }
-      win = win.parent;
-    }
-  }
-
-  if (filters.outerWindowID) {
-    const topFrame = NetworkHelper.getTopFrameForRequest(channel);
-    // topFrame is typically null for some chrome requests like favicons
-    if (topFrame) {
-      try {
-        if (topFrame.outerWindowID == filters.outerWindowID) {
-          return true;
-        }
-      } catch (e) {
-        // outerWindowID getter from browser.xml (non-remote <xul:browser>) may
-        // throw when closing a tab while resources are still loading.
-      }
-    }
-  }
-
-  return false;
-}
-
-/**
- * This is a nsIChannelEventSink implementation that monitors channel redirects and
- * informs the registered StackTraceCollector about the old and new channels.
- */
-const SINK_CLASS_DESCRIPTION = "NetworkMonitor Channel Event Sink";
-const SINK_CLASS_ID = components.ID("{e89fa076-c845-48a8-8c45-2604729eba1d}");
-const SINK_CONTRACT_ID = "@mozilla.org/network/monitor/channeleventsink;1";
-const SINK_CATEGORY_NAME = "net-channel-event-sinks";
-
-function ChannelEventSink() {
-  this.wrappedJSObject = this;
-  this.collectors = new Set();
-}
-
-ChannelEventSink.prototype = {
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIChannelEventSink]),
-
-  registerCollector(collector) {
-    this.collectors.add(collector);
-  },
-
-  unregisterCollector(collector) {
-    this.collectors.delete(collector);
-
-    if (this.collectors.size == 0) {
-      ChannelEventSinkFactory.unregister();
-    }
-  },
-
-  // eslint-disable-next-line no-shadow
-  asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
-    for (const collector of this.collectors) {
-      try {
-        collector.onChannelRedirect(oldChannel, newChannel, flags);
-      } catch (ex) {
-        console.error("StackTraceCollector.onChannelRedirect threw an exception", ex);
-      }
-    }
-    callback.onRedirectVerifyCallback(Cr.NS_OK);
-  }
-};
-
-const ChannelEventSinkFactory = XPCOMUtils.generateSingletonFactory(ChannelEventSink);
-
-ChannelEventSinkFactory.register = function() {
-  const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
-  if (registrar.isCIDRegistered(SINK_CLASS_ID)) {
-    return;
-  }
-
-  registrar.registerFactory(SINK_CLASS_ID,
-                            SINK_CLASS_DESCRIPTION,
-                            SINK_CONTRACT_ID,
-                            ChannelEventSinkFactory);
-
-  XPCOMUtils.categoryManager.addCategoryEntry(SINK_CATEGORY_NAME, SINK_CONTRACT_ID,
-    SINK_CONTRACT_ID, false, true);
-};
-
-ChannelEventSinkFactory.unregister = function() {
-  const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
-  registrar.unregisterFactory(SINK_CLASS_ID, ChannelEventSinkFactory);
-
-  XPCOMUtils.categoryManager.deleteCategoryEntry(SINK_CATEGORY_NAME, SINK_CONTRACT_ID,
-    false);
-};
-
-ChannelEventSinkFactory.getService = function() {
-  // Make sure the ChannelEventSink service is registered before accessing it
-  ChannelEventSinkFactory.register();
-
-  return Cc[SINK_CONTRACT_ID].getService(Ci.nsIChannelEventSink).wrappedJSObject;
-};
-
-function StackTraceCollector(filters, netmonitors) {
-  this.filters = filters;
-  this.stacktracesById = new Map();
-  this.netmonitors = netmonitors;
-}
-
-StackTraceCollector.prototype = {
-  init() {
-    Services.obs.addObserver(this, "http-on-opening-request");
-    ChannelEventSinkFactory.getService().registerCollector(this);
-    this.onGetStack = this.onGetStack.bind(this);
-    for (const { messageManager } of this.netmonitors) {
-      messageManager.addMessageListener("debug:request-stack", this.onGetStack);
-    }
-  },
-
-  destroy() {
-    Services.obs.removeObserver(this, "http-on-opening-request");
-    ChannelEventSinkFactory.getService().unregisterCollector(this);
-    for (const { messageManager } of this.netmonitors) {
-      messageManager.removeMessageListener("debug:request-stack", this.onGetStack);
-    }
-  },
-
-  _saveStackTrace(channel, stacktrace) {
-    for (const { messageManager } of this.netmonitors) {
-      messageManager.sendAsyncMessage("debug:request-stack-available", {
-        channelId: channel.channelId,
-        stacktrace: stacktrace && stacktrace.length > 0
-      });
-    }
-    this.stacktracesById.set(channel.channelId, stacktrace);
-  },
-
-  observe(subject) {
-    const channel = subject.QueryInterface(Ci.nsIHttpChannel);
-
-    if (!matchRequest(channel, this.filters)) {
-      return;
-    }
-
-    // Convert the nsIStackFrame XPCOM objects to a nice JSON that can be
-    // passed around through message managers etc.
-    let frame = components.stack;
-    const stacktrace = [];
-    if (frame && frame.caller) {
-      frame = frame.caller;
-      while (frame) {
-        stacktrace.push({
-          filename: frame.filename,
-          lineNumber: frame.lineNumber,
-          columnNumber: frame.columnNumber,
-          functionName: frame.name,
-          asyncCause: frame.asyncCause,
-        });
-        frame = frame.caller || frame.asyncCaller;
-      }
-    }
-
-    this._saveStackTrace(channel, stacktrace);
-  },
-
-  // eslint-disable-next-line no-shadow
-  onChannelRedirect(oldChannel, newChannel, flags) {
-    // We can be called with any nsIChannel, but are interested only in HTTP channels
-    try {
-      oldChannel.QueryInterface(Ci.nsIHttpChannel);
-      newChannel.QueryInterface(Ci.nsIHttpChannel);
-    } catch (ex) {
-      return;
-    }
-
-    const oldId = oldChannel.channelId;
-    const stacktrace = this.stacktracesById.get(oldId);
-    if (stacktrace) {
-      this._saveStackTrace(newChannel, stacktrace);
-    }
-  },
-
-  getStackTrace(channelId) {
-    const trace = this.stacktracesById.get(channelId);
-    this.stacktracesById.delete(channelId);
-    return trace;
-  },
-
-  onGetStack(msg) {
-    const messageManager = msg.target;
-    const channelId = msg.data;
-    const stack = this.getStackTrace(channelId);
-    messageManager.sendAsyncMessage("debug:request-stack", {
-      channelId,
-      stack,
-    });
-  },
-};
-
-exports.StackTraceCollector = StackTraceCollector;
-
-/**
  * The network response listener implements the nsIStreamListener and
- * nsIRequestObserver interfaces. This is used within the NetworkMonitor feature
+ * nsIRequestObserver interfaces. This is used within the NetworkObserver feature
  * to get the response body of the request.
  *
  * The code is mostly based on code listings from:
  *
  *   http://www.softwareishard.com/blog/firebug/
  *      nsitraceablechannel-intercept-http-traffic/
  *
  * @constructor
  * @param object owner
  *        The response listener owner. This object needs to hold the
  *        |openResponses| object.
  * @param object httpActivity
- *        HttpActivity object associated with this request. See NetworkMonitor
+ *        HttpActivity object associated with this request. See NetworkObserver
  *        for more information.
  */
 function NetworkResponseListener(owner, httpActivity) {
   this.owner = owner;
   this.receivedData = "";
   this.httpActivity = httpActivity;
   this.bodySize = 0;
   // Indicates if the response had a size greater than response body limit.
   this.truncated = false;
   // Note that this is really only needed for the non-e10s case.
   // See bug 1309523.
   const channel = this.httpActivity.channel;
   this._wrappedNotificationCallbacks = channel.notificationCallbacks;
   channel.notificationCallbacks = this;
 }
 
+exports.NetworkResponseListener = NetworkResponseListener;
+
 NetworkResponseListener.prototype = {
   QueryInterface:
     ChromeUtils.generateQI([Ci.nsIStreamListener, Ci.nsIInputStreamCallback,
                             Ci.nsIRequestObserver, Ci.nsIInterfaceRequestor]),
 
   // nsIInterfaceRequestor implementation
 
   /**
@@ -338,17 +105,17 @@ NetworkResponseListener.prototype = {
     } catch (e) {
       if (e.result != Cr.NS_ERROR_NO_INTERFACE) {
         throw e;
       }
     }
   },
 
   /**
-   * This NetworkResponseListener tracks the NetworkMonitor.openResponses object
+   * This NetworkResponseListener tracks the NetworkObserver.openResponses object
    * to find the associated uncached headers.
    * @private
    */
   _foundOpenResponse: false,
 
   /**
    * If the channel already had notificationCallbacks, hold them here internally
    * so that we can forward getInterface requests to that object.
@@ -590,18 +357,18 @@ NetworkResponseListener.prototype = {
   },
 
   onStatus: function() {
     this._forwardNotification(Ci.nsIProgressEventSink, "onStatus", arguments);
   },
 
   /**
    * Find the open response object associated to the current request. The
-   * NetworkMonitor._httpResponseExaminer() method saves the response headers in
-   * NetworkMonitor.openResponses. This method takes the data from the open
+   * NetworkObserver._httpResponseExaminer() method saves the response headers in
+   * NetworkObserver.openResponses. This method takes the data from the open
    * response object and puts it into the HTTP activity object, then sends it to
    * the remote Web Console instance.
    *
    * @private
    */
   _findOpenResponse: function() {
     if (!this.owner || this._foundOpenResponse) {
       return;
@@ -773,33 +540,33 @@ NetworkResponseListener.prototype = {
  * @param object owner
  *        The network monitor owner. This object needs to hold:
  *        - onNetworkEvent(requestInfo)
  *          This method is invoked once for every new network request and it is
  *          given the initial network request information as an argument.
  *          onNetworkEvent() must return an object which holds several add*()
  *          methods which are used to add further network request/response information.
  */
-function NetworkMonitor(filters, owner) {
+function NetworkObserver(filters, owner) {
   this.filters = filters;
   this.owner = owner;
   this.openRequests = new Map();
   this.openResponses = new Map();
   this._httpResponseExaminer =
     DevToolsUtils.makeInfallible(this._httpResponseExaminer).bind(this);
   this._httpModifyExaminer =
     DevToolsUtils.makeInfallible(this._httpModifyExaminer).bind(this);
   this._serviceWorkerRequest = this._serviceWorkerRequest.bind(this);
   this._throttleData = null;
   this._throttler = null;
 }
 
-exports.NetworkMonitor = NetworkMonitor;
+exports.NetworkObserver = NetworkObserver;
 
-NetworkMonitor.prototype = {
+NetworkObserver.prototype = {
   filters: null,
 
   httpTransactionCodes: {
     0x5001: "REQUEST_HEADER",
     0x5002: "REQUEST_BODY_SENT",
     0x5003: "RESPONSE_START",
     0x5004: "RESPONSE_HEADER",
     0x5005: "RESPONSE_COMPLETE",
@@ -1744,232 +1511,16 @@ NetworkMonitor.prototype = {
     this.openRequests.clear();
     this.openResponses.clear();
     this.owner = null;
     this.filters = null;
     this._throttler = null;
   },
 };
 
-/**
- * A WebProgressListener that listens for location changes.
- *
- * This progress listener is used to track file loads and other kinds of
- * location changes.
- *
- * @constructor
- * @param object window
- *        The window for which we need to track location changes.
- * @param object owner
- *        The listener owner which needs to implement two methods:
- *        - onFileActivity(aFileURI)
- *        - onLocationChange(aState, aTabURI, aPageTitle)
- */
-function ConsoleProgressListener(window, owner) {
-  this.window = window;
-  this.owner = owner;
-}
-exports.ConsoleProgressListener = ConsoleProgressListener;
-
-ConsoleProgressListener.prototype = {
-  /**
-   * Constant used for startMonitor()/stopMonitor() that tells you want to
-   * monitor file loads.
-   */
-  MONITOR_FILE_ACTIVITY: 1,
-
-  /**
-   * Constant used for startMonitor()/stopMonitor() that tells you want to
-   * monitor page location changes.
-   */
-  MONITOR_LOCATION_CHANGE: 2,
-
-  /**
-   * Tells if you want to monitor file activity.
-   * @private
-   * @type boolean
-   */
-  _fileActivity: false,
-
-  /**
-   * Tells if you want to monitor location changes.
-   * @private
-   * @type boolean
-   */
-  _locationChange: false,
-
-  /**
-   * Tells if the console progress listener is initialized or not.
-   * @private
-   * @type boolean
-   */
-  _initialized: false,
-
-  _webProgress: null,
-
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener,
-                                          Ci.nsISupportsWeakReference]),
-
-  /**
-   * Initialize the ConsoleProgressListener.
-   * @private
-   */
-  _init: function() {
-    if (this._initialized) {
-      return;
-    }
-
-    this._webProgress = this.window.docShell.QueryInterface(Ci.nsIWebProgress);
-    this._webProgress.addProgressListener(this,
-                                          Ci.nsIWebProgress.NOTIFY_STATE_ALL);
-
-    this._initialized = true;
-  },
-
-  /**
-   * Start a monitor/tracker related to the current nsIWebProgressListener
-   * instance.
-   *
-   * @param number monitor
-   *        Tells what you want to track. Available constants:
-   *        - this.MONITOR_FILE_ACTIVITY
-   *          Track file loads.
-   *        - this.MONITOR_LOCATION_CHANGE
-   *          Track location changes for the top window.
-   */
-  startMonitor: function(monitor) {
-    switch (monitor) {
-      case this.MONITOR_FILE_ACTIVITY:
-        this._fileActivity = true;
-        break;
-      case this.MONITOR_LOCATION_CHANGE:
-        this._locationChange = true;
-        break;
-      default:
-        throw new Error("ConsoleProgressListener: unknown monitor type " +
-                        monitor + "!");
-    }
-    this._init();
-  },
-
-  /**
-   * Stop a monitor.
-   *
-   * @param number monitor
-   *        Tells what you want to stop tracking. See this.startMonitor() for
-   *        the list of constants.
-   */
-  stopMonitor: function(monitor) {
-    switch (monitor) {
-      case this.MONITOR_FILE_ACTIVITY:
-        this._fileActivity = false;
-        break;
-      case this.MONITOR_LOCATION_CHANGE:
-        this._locationChange = false;
-        break;
-      default:
-        throw new Error("ConsoleProgressListener: unknown monitor type " +
-                        monitor + "!");
-    }
-
-    if (!this._fileActivity && !this._locationChange) {
-      this.destroy();
-    }
-  },
-
-  onStateChange: function(progress, request, state, status) {
-    if (!this.owner) {
-      return;
-    }
-
-    if (this._fileActivity) {
-      this._checkFileActivity(progress, request, state, status);
-    }
-
-    if (this._locationChange) {
-      this._checkLocationChange(progress, request, state, status);
-    }
-  },
-
-  /**
-   * Check if there is any file load, given the arguments of
-   * nsIWebProgressListener.onStateChange. If the state change tells that a file
-   * URI has been loaded, then the remote Web Console instance is notified.
-   * @private
-   */
-  _checkFileActivity: function(progress, request, state, status) {
-    if (!(state & Ci.nsIWebProgressListener.STATE_START)) {
-      return;
-    }
-
-    let uri = null;
-    if (request instanceof Ci.imgIRequest) {
-      const imgIRequest = request.QueryInterface(Ci.imgIRequest);
-      uri = imgIRequest.URI;
-    } else if (request instanceof Ci.nsIChannel) {
-      const nsIChannel = request.QueryInterface(Ci.nsIChannel);
-      uri = nsIChannel.URI;
-    }
-
-    if (!uri || !uri.schemeIs("file") && !uri.schemeIs("ftp")) {
-      return;
-    }
-
-    this.owner.onFileActivity(uri.spec);
-  },
-
-  /**
-   * Check if the current window.top location is changing, given the arguments
-   * of nsIWebProgressListener.onStateChange. If that is the case, the remote
-   * Web Console instance is notified.
-   * @private
-   */
-  _checkLocationChange: function(progress, request, state) {
-    const isStart = state & Ci.nsIWebProgressListener.STATE_START;
-    const isStop = state & Ci.nsIWebProgressListener.STATE_STOP;
-    const isNetwork = state & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
-    const isWindow = state & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
-
-    // Skip non-interesting states.
-    if (!isNetwork || !isWindow || progress.DOMWindow != this.window) {
-      return;
-    }
-
-    if (isStart && request instanceof Ci.nsIChannel) {
-      this.owner.onLocationChange("start", request.URI.spec, "");
-    } else if (isStop) {
-      this.owner.onLocationChange("stop", this.window.location.href,
-                                  this.window.document.title);
-    }
-  },
-
-  /**
-   * Destroy the ConsoleProgressListener.
-   */
-  destroy: function() {
-    if (!this._initialized) {
-      return;
-    }
-
-    this._initialized = false;
-    this._fileActivity = false;
-    this._locationChange = false;
-
-    try {
-      this._webProgress.removeProgressListener(this);
-    } catch (ex) {
-      // This can throw during browser shutdown.
-    }
-
-    this._webProgress = null;
-    this.window = null;
-    this.owner = null;
-  },
-};
-
 function gSequenceId() {
   return gSequenceId.n++;
 }
 gSequenceId.n = 1;
 
 /**
  * Convert a nsIContentPolicy constant to a display string
  */
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/network-monitor/stack-trace-collector.js
@@ -0,0 +1,115 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {Ci, components} = require("chrome");
+const Services = require("Services");
+
+loader.lazyRequireGetter(this, "ChannelEventSinkFactory",
+                         "devtools/server/actors/network-monitor/channel-event-sink",
+                         true);
+loader.lazyRequireGetter(this, "matchRequest",
+                         "devtools/server/actors/network-monitor/network-observer",
+                         true);
+
+function StackTraceCollector(filters, netmonitors) {
+  this.filters = filters;
+  this.stacktracesById = new Map();
+  this.netmonitors = netmonitors;
+}
+
+StackTraceCollector.prototype = {
+  init() {
+    Services.obs.addObserver(this, "http-on-opening-request");
+    ChannelEventSinkFactory.getService().registerCollector(this);
+    this.onGetStack = this.onGetStack.bind(this);
+    for (const { messageManager } of this.netmonitors) {
+      messageManager.addMessageListener("debug:request-stack", this.onGetStack);
+    }
+  },
+
+  destroy() {
+    Services.obs.removeObserver(this, "http-on-opening-request");
+    ChannelEventSinkFactory.getService().unregisterCollector(this);
+    for (const { messageManager } of this.netmonitors) {
+      messageManager.removeMessageListener("debug:request-stack", this.onGetStack);
+    }
+  },
+
+  _saveStackTrace(channel, stacktrace) {
+    for (const { messageManager } of this.netmonitors) {
+      messageManager.sendAsyncMessage("debug:request-stack-available", {
+        channelId: channel.channelId,
+        stacktrace: stacktrace && stacktrace.length > 0
+      });
+    }
+    this.stacktracesById.set(channel.channelId, stacktrace);
+  },
+
+  observe(subject) {
+    const channel = subject.QueryInterface(Ci.nsIHttpChannel);
+
+    if (!matchRequest(channel, this.filters)) {
+      return;
+    }
+
+    // Convert the nsIStackFrame XPCOM objects to a nice JSON that can be
+    // passed around through message managers etc.
+    let frame = components.stack;
+    const stacktrace = [];
+    if (frame && frame.caller) {
+      frame = frame.caller;
+      while (frame) {
+        stacktrace.push({
+          filename: frame.filename,
+          lineNumber: frame.lineNumber,
+          columnNumber: frame.columnNumber,
+          functionName: frame.name,
+          asyncCause: frame.asyncCause,
+        });
+        frame = frame.caller || frame.asyncCaller;
+      }
+    }
+
+    this._saveStackTrace(channel, stacktrace);
+  },
+
+  // eslint-disable-next-line no-shadow
+  onChannelRedirect(oldChannel, newChannel, flags) {
+    // We can be called with any nsIChannel, but are interested only in HTTP channels
+    try {
+      oldChannel.QueryInterface(Ci.nsIHttpChannel);
+      newChannel.QueryInterface(Ci.nsIHttpChannel);
+    } catch (ex) {
+      return;
+    }
+
+    const oldId = oldChannel.channelId;
+    const stacktrace = this.stacktracesById.get(oldId);
+    if (stacktrace) {
+      this._saveStackTrace(newChannel, stacktrace);
+    }
+  },
+
+  getStackTrace(channelId) {
+    const trace = this.stacktracesById.get(channelId);
+    this.stacktracesById.delete(channelId);
+    return trace;
+  },
+
+  onGetStack(msg) {
+    const messageManager = msg.target;
+    const channelId = msg.data;
+    const stack = this.getStackTrace(channelId);
+    messageManager.sendAsyncMessage("debug:request-stack", {
+      channelId,
+      stack,
+    });
+  },
+};
+
+exports.StackTraceCollector = StackTraceCollector;
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -15,18 +15,18 @@ const { ActorPool } = require("devtools/
 const { ThreadActor } = require("devtools/server/actors/thread");
 const { ObjectActor } = require("devtools/server/actors/object");
 const { LongStringActor } = require("devtools/server/actors/object/long-string");
 const { createValueGrip, stringIsLong } = require("devtools/server/actors/object/utils");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const ErrorDocs = require("devtools/server/actors/errordocs");
 
 loader.lazyRequireGetter(this, "NetworkMonitorActor", "devtools/server/actors/network-monitor", true);
-loader.lazyRequireGetter(this, "ConsoleProgressListener", "devtools/shared/webconsole/network-monitor", true);
-loader.lazyRequireGetter(this, "StackTraceCollector", "devtools/shared/webconsole/network-monitor", true);
+loader.lazyRequireGetter(this, "ConsoleProgressListener", "devtools/server/actors/webconsole/listeners/console-progress", true);
+loader.lazyRequireGetter(this, "StackTraceCollector", "devtools/server/actors/network-monitor/stack-trace-collector", true);
 loader.lazyRequireGetter(this, "JSPropertyProvider", "devtools/shared/webconsole/js-property-provider", true);
 loader.lazyRequireGetter(this, "Parser", "resource://devtools/shared/Parser.jsm", true);
 loader.lazyRequireGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm", true);
 loader.lazyRequireGetter(this, "WebConsoleCommands", "devtools/server/actors/webconsole/utils", true);
 loader.lazyRequireGetter(this, "addWebConsoleCommands", "devtools/server/actors/webconsole/utils", true);
 loader.lazyRequireGetter(this, "formatCommand", "devtools/server/actors/webconsole/commands", true);
 loader.lazyRequireGetter(this, "isCommand", "devtools/server/actors/webconsole/commands", true);
 loader.lazyRequireGetter(this, "validCommands", "devtools/server/actors/webconsole/commands", true);
@@ -37,21 +37,21 @@ loader.lazyRequireGetter(this, "Environm
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 
 // Overwrite implemented listeners for workers so that we don't attempt
 // to load an unsupported module.
 if (isWorker) {
   loader.lazyRequireGetter(this, "ConsoleAPIListener", "devtools/server/actors/webconsole/worker-listeners", true);
   loader.lazyRequireGetter(this, "ConsoleServiceListener", "devtools/server/actors/webconsole/worker-listeners", true);
 } else {
-  loader.lazyRequireGetter(this, "ConsoleAPIListener", "devtools/server/actors/webconsole/listeners", true);
-  loader.lazyRequireGetter(this, "ConsoleServiceListener", "devtools/server/actors/webconsole/listeners", true);
-  loader.lazyRequireGetter(this, "ConsoleReflowListener", "devtools/server/actors/webconsole/listeners", true);
-  loader.lazyRequireGetter(this, "ContentProcessListener", "devtools/server/actors/webconsole/listeners", true);
-  loader.lazyRequireGetter(this, "DocumentEventsListener", "devtools/server/actors/webconsole/listeners", true);
+  loader.lazyRequireGetter(this, "ConsoleAPIListener", "devtools/server/actors/webconsole/listeners/console-api", true);
+  loader.lazyRequireGetter(this, "ConsoleServiceListener", "devtools/server/actors/webconsole/listeners/console-service", true);
+  loader.lazyRequireGetter(this, "ConsoleReflowListener", "devtools/server/actors/webconsole/listeners/console-reflow", true);
+  loader.lazyRequireGetter(this, "ContentProcessListener", "devtools/server/actors/webconsole/listeners/content-process", true);
+  loader.lazyRequireGetter(this, "DocumentEventsListener", "devtools/server/actors/webconsole/listeners/document-events", true);
 }
 
 function isObject(value) {
   return Object(value) === value;
 }
 
 /**
  * The WebConsoleActor implements capabilities needed for the Web Console
rename from devtools/server/actors/webconsole/listeners.js
rename to devtools/server/actors/webconsole/listeners/console-api.js
--- a/devtools/server/actors/webconsole/listeners.js
+++ b/devtools/server/actors/webconsole/listeners/console-api.js
@@ -1,185 +1,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {Cc, Ci, components} = require("chrome");
+const {Cc, Ci} = require("chrome");
 const {isWindowIncluded} = require("devtools/shared/layout/utils");
 const Services = require("Services");
 const ChromeUtils = require("ChromeUtils");
 const {CONSOLE_WORKER_IDS, WebConsoleUtils} = require("devtools/server/actors/webconsole/utils");
 
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-
-// Process script used to forward console calls from content processes to parent process
-const CONTENT_PROCESS_SCRIPT = "resource://devtools/server/actors/webconsole/content-process-forward.js";
-
-// The page errors listener
-
-/**
- * The nsIConsoleService listener. This is used to send all of the console
- * messages (JavaScript, CSS and more) to the remote Web Console instance.
- *
- * @constructor
- * @param nsIDOMWindow [window]
- *        Optional - the window object for which we are created. This is used
- *        for filtering out messages that belong to other windows.
- * @param object listener
- *        The listener object must have one method:
- *        - onConsoleServiceMessage(). This method is invoked with one argument,
- *        the nsIConsoleMessage, whenever a relevant message is received.
- */
-function ConsoleServiceListener(window, listener) {
-  this.window = window;
-  this.listener = listener;
-}
-exports.ConsoleServiceListener = ConsoleServiceListener;
-
-ConsoleServiceListener.prototype =
-{
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIConsoleListener]),
-
-  /**
-   * The content window for which we listen to page errors.
-   * @type nsIDOMWindow
-   */
-  window: null,
-
-  /**
-   * The listener object which is notified of messages from the console service.
-   * @type object
-   */
-  listener: null,
-
-  /**
-   * Initialize the nsIConsoleService listener.
-   */
-  init: function() {
-    Services.console.registerListener(this);
-  },
-
-  /**
-   * The nsIConsoleService observer. This method takes all the script error
-   * messages belonging to the current window and sends them to the remote Web
-   * Console instance.
-   *
-   * @param nsIConsoleMessage message
-   *        The message object coming from the nsIConsoleService.
-   */
-  observe: function(message) {
-    if (!this.listener) {
-      return;
-    }
-
-    if (this.window) {
-      if (!(message instanceof Ci.nsIScriptError) ||
-          !message.outerWindowID ||
-          !this.isCategoryAllowed(message.category)) {
-        return;
-      }
-
-      const errorWindow = Services.wm.getOuterWindowWithId(message.outerWindowID);
-      if (!errorWindow || !isWindowIncluded(this.window, errorWindow)) {
-        return;
-      }
-    }
-
-    this.listener.onConsoleServiceMessage(message);
-  },
-
-  /**
-   * Check if the given message category is allowed to be tracked or not.
-   * We ignore chrome-originating errors as we only care about content.
-   *
-   * @param string category
-   *        The message category you want to check.
-   * @return boolean
-   *         True if the category is allowed to be logged, false otherwise.
-   */
-  isCategoryAllowed: function(category) {
-    if (!category) {
-      return false;
-    }
-
-    switch (category) {
-      case "XPConnect JavaScript":
-      case "component javascript":
-      case "chrome javascript":
-      case "chrome registration":
-      case "XBL":
-      case "XBL Prototype Handler":
-      case "XBL Content Sink":
-      case "xbl javascript":
-        return false;
-    }
-
-    return true;
-  },
-
-  /**
-   * Get the cached page errors for the current inner window and its (i)frames.
-   *
-   * @param boolean [includePrivate=false]
-   *        Tells if you want to also retrieve messages coming from private
-   *        windows. Defaults to false.
-   * @return array
-   *         The array of cached messages. Each element is an nsIScriptError or
-   *         an nsIConsoleMessage
-   */
-  getCachedMessages: function(includePrivate = false) {
-    const errors = Services.console.getMessageArray() || [];
-
-    // if !this.window, we're in a browser console. Still need to filter
-    // private messages.
-    if (!this.window) {
-      return errors.filter((error) => {
-        if (error instanceof Ci.nsIScriptError) {
-          if (!includePrivate && error.isFromPrivateWindow) {
-            return false;
-          }
-        }
-
-        return true;
-      });
-    }
-
-    const ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
-
-    return errors.filter((error) => {
-      if (error instanceof Ci.nsIScriptError) {
-        if (!includePrivate && error.isFromPrivateWindow) {
-          return false;
-        }
-        if (ids &&
-            (!ids.includes(error.innerWindowID) ||
-             !this.isCategoryAllowed(error.category))) {
-          return false;
-        }
-      } else if (ids && ids[0]) {
-        // If this is not an nsIScriptError and we need to do window-based
-        // filtering we skip this message.
-        return false;
-      }
-
-      return true;
-    });
-  },
-
-  /**
-   * Remove the nsIConsoleService listener.
-   */
-  destroy: function() {
-    Services.console.unregisterListener(this);
-    this.listener = this.window = null;
-  },
-};
-
 // The window.console API observer
 
 /**
  * The window.console API observer. This allows the window.console API messages
  * to be sent to the remote Web Console instance.
  *
  * @constructor
  * @param nsIDOMWindow window
@@ -360,208 +195,8 @@ ConsoleAPIListener.prototype =
   /**
    * Destroy the console API listener.
    */
   destroy: function() {
     Services.obs.removeObserver(this, "console-api-log-event");
     this.window = this.owner = null;
   },
 };
-
-/**
- * A ReflowObserver that listens for reflow events from the page.
- * Implements nsIReflowObserver.
- *
- * @constructor
- * @param object window
- *        The window for which we need to track reflow.
- * @param object owner
- *        The listener owner which needs to implement:
- *        - onReflowActivity(reflowInfo)
- */
-
-function ConsoleReflowListener(window, listener) {
-  this.docshell = window.docShell;
-  this.listener = listener;
-  this.docshell.addWeakReflowObserver(this);
-}
-
-exports.ConsoleReflowListener = ConsoleReflowListener;
-
-ConsoleReflowListener.prototype =
-{
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIReflowObserver,
-                                          Ci.nsISupportsWeakReference]),
-  docshell: null,
-  listener: null,
-
-  /**
-   * Forward reflow event to listener.
-   *
-   * @param DOMHighResTimeStamp start
-   * @param DOMHighResTimeStamp end
-   * @param boolean interruptible
-   */
-  sendReflow: function(start, end, interruptible) {
-    const frame = components.stack.caller.caller;
-
-    let filename = frame ? frame.filename : null;
-
-    if (filename) {
-      // Because filename could be of the form "xxx.js -> xxx.js -> xxx.js",
-      // we only take the last part.
-      filename = filename.split(" ").pop();
-    }
-
-    this.listener.onReflowActivity({
-      interruptible: interruptible,
-      start: start,
-      end: end,
-      sourceURL: filename,
-      sourceLine: frame ? frame.lineNumber : null,
-      functionName: frame ? frame.name : null
-    });
-  },
-
-  /**
-   * On uninterruptible reflow
-   *
-   * @param DOMHighResTimeStamp start
-   * @param DOMHighResTimeStamp end
-   */
-  reflow: function(start, end) {
-    this.sendReflow(start, end, false);
-  },
-
-  /**
-   * On interruptible reflow
-   *
-   * @param DOMHighResTimeStamp start
-   * @param DOMHighResTimeStamp end
-   */
-  reflowInterruptible: function(start, end) {
-    this.sendReflow(start, end, true);
-  },
-
-  /**
-   * Unregister listener.
-   */
-  destroy: function() {
-    this.docshell.removeWeakReflowObserver(this);
-    this.listener = this.docshell = null;
-  },
-};
-
-/**
- * Forward console message calls from content processes to the parent process.
- * Used by Browser console and toolbox to see messages from all processes.
- *
- * @constructor
- * @param object owner
- *        The listener owner which needs to implement:
- *        - onConsoleAPICall(message)
- */
-function ContentProcessListener(listener) {
-  this.listener = listener;
-
-  Services.ppmm.addMessageListener("Console:Log", this);
-  Services.ppmm.loadProcessScript(CONTENT_PROCESS_SCRIPT, true);
-}
-
-exports.ContentProcessListener = ContentProcessListener;
-
-ContentProcessListener.prototype = {
-  receiveMessage(message) {
-    const logMsg = message.data;
-    logMsg.wrappedJSObject = logMsg;
-    this.listener.onConsoleAPICall(logMsg);
-  },
-
-  destroy() {
-    // Tell the content processes to stop listening and forwarding messages
-    Services.ppmm.broadcastAsyncMessage("DevTools:StopForwardingContentProcessMessage");
-
-    Services.ppmm.removeMessageListener("Console:Log", this);
-    Services.ppmm.removeDelayedProcessScript(CONTENT_PROCESS_SCRIPT);
-
-    this.listener = null;
-  }
-};
-
-/**
- * Forward `DOMContentLoaded` and `load` events with precise timing
- * of when events happened according to window.performance numbers.
- *
- * @constructor
- * @param object console
- *        The web console actor.
- */
-function DocumentEventsListener(console) {
-  this.console = console;
-
-  this.onWindowReady = this.onWindowReady.bind(this);
-  this.onContentLoaded = this.onContentLoaded.bind(this);
-  this.onLoad = this.onLoad.bind(this);
-  this.listen();
-}
-
-exports.DocumentEventsListener = DocumentEventsListener;
-
-DocumentEventsListener.prototype = {
-  listen() {
-    EventEmitter.on(this.console.parentActor, "window-ready", this.onWindowReady);
-    this.onWindowReady({ window: this.console.window, isTopLevel: true });
-  },
-
-  onWindowReady({ window, isTopLevel }) {
-    // Ignore iframes
-    if (!isTopLevel) {
-      return;
-    }
-
-    const { readyState } = window.document;
-    if (readyState != "interactive" && readyState != "complete") {
-      window.addEventListener("DOMContentLoaded", this.onContentLoaded, { once: true });
-    } else {
-      this.onContentLoaded({ target: window.document });
-    }
-    if (readyState != "complete") {
-      window.addEventListener("load", this.onLoad, { once: true });
-    } else {
-      this.onLoad({ target: window.document });
-    }
-  },
-
-  onContentLoaded(event) {
-    const window = event.target.defaultView;
-    const packet = {
-      from: this.console.actorID,
-      type: "documentEvent",
-      name: "dom-interactive",
-      // milliseconds since the UNIX epoch, when the parser finished its work
-      // on the main document, that is when its Document.readyState changes to
-      // 'interactive' and the corresponding readystatechange event is thrown
-      time: window.performance.timing.domInteractive
-    };
-    this.console.conn.send(packet);
-  },
-
-  onLoad(event) {
-    const window = event.target.defaultView;
-    const packet = {
-      from: this.console.actorID,
-      type: "documentEvent",
-      name: "dom-complete",
-      // milliseconds since the UNIX epoch, when the parser finished its work
-      // on the main document, that is when its Document.readyState changes to
-      // 'complete' and the corresponding readystatechange event is thrown
-      time: window.performance.timing.domComplete
-    };
-    this.console.conn.send(packet);
-  },
-
-  destroy() {
-    EventEmitter.off(this.console.parentActor, "window-ready", this.onWindowReady);
-
-    this.listener = null;
-  }
-};
-
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/webconsole/listeners/console-progress.js
@@ -0,0 +1,224 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Ci } = require("chrome");
+const ChromeUtils = require("ChromeUtils");
+
+/**
+ * A WebProgressListener that listens for location changes.
+ *
+ * This progress listener is used to track file loads and other kinds of
+ * location changes.
+ *
+ * @constructor
+ * @param object window
+ *        The window for which we need to track location changes.
+ * @param object owner
+ *        The listener owner which needs to implement two methods:
+ *        - onFileActivity(aFileURI)
+ *        - onLocationChange(aState, aTabURI, aPageTitle)
+ */
+function ConsoleProgressListener(window, owner) {
+  this.window = window;
+  this.owner = owner;
+}
+exports.ConsoleProgressListener = ConsoleProgressListener;
+
+ConsoleProgressListener.prototype = {
+  /**
+   * Constant used for startMonitor()/stopMonitor() that tells you want to
+   * monitor file loads.
+   */
+  MONITOR_FILE_ACTIVITY: 1,
+
+  /**
+   * Constant used for startMonitor()/stopMonitor() that tells you want to
+   * monitor page location changes.
+   */
+  MONITOR_LOCATION_CHANGE: 2,
+
+  /**
+   * Tells if you want to monitor file activity.
+   * @private
+   * @type boolean
+   */
+  _fileActivity: false,
+
+  /**
+   * Tells if you want to monitor location changes.
+   * @private
+   * @type boolean
+   */
+  _locationChange: false,
+
+  /**
+   * Tells if the console progress listener is initialized or not.
+   * @private
+   * @type boolean
+   */
+  _initialized: false,
+
+  _webProgress: null,
+
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener,
+                                          Ci.nsISupportsWeakReference]),
+
+  /**
+   * Initialize the ConsoleProgressListener.
+   * @private
+   */
+  _init: function() {
+    if (this._initialized) {
+      return;
+    }
+
+    this._webProgress = this.window.docShell.QueryInterface(Ci.nsIWebProgress);
+    this._webProgress.addProgressListener(this,
+                                          Ci.nsIWebProgress.NOTIFY_STATE_ALL);
+
+    this._initialized = true;
+  },
+
+  /**
+   * Start a monitor/tracker related to the current nsIWebProgressListener
+   * instance.
+   *
+   * @param number monitor
+   *        Tells what you want to track. Available constants:
+   *        - this.MONITOR_FILE_ACTIVITY
+   *          Track file loads.
+   *        - this.MONITOR_LOCATION_CHANGE
+   *          Track location changes for the top window.
+   */
+  startMonitor: function(monitor) {
+    switch (monitor) {
+      case this.MONITOR_FILE_ACTIVITY:
+        this._fileActivity = true;
+        break;
+      case this.MONITOR_LOCATION_CHANGE:
+        this._locationChange = true;
+        break;
+      default:
+        throw new Error("ConsoleProgressListener: unknown monitor type " +
+                        monitor + "!");
+    }
+    this._init();
+  },
+
+  /**
+   * Stop a monitor.
+   *
+   * @param number monitor
+   *        Tells what you want to stop tracking. See this.startMonitor() for
+   *        the list of constants.
+   */
+  stopMonitor: function(monitor) {
+    switch (monitor) {
+      case this.MONITOR_FILE_ACTIVITY:
+        this._fileActivity = false;
+        break;
+      case this.MONITOR_LOCATION_CHANGE:
+        this._locationChange = false;
+        break;
+      default:
+        throw new Error("ConsoleProgressListener: unknown monitor type " +
+                        monitor + "!");
+    }
+
+    if (!this._fileActivity && !this._locationChange) {
+      this.destroy();
+    }
+  },
+
+  onStateChange: function(progress, request, state, status) {
+    if (!this.owner) {
+      return;
+    }
+
+    if (this._fileActivity) {
+      this._checkFileActivity(progress, request, state, status);
+    }
+
+    if (this._locationChange) {
+      this._checkLocationChange(progress, request, state, status);
+    }
+  },
+
+  /**
+   * Check if there is any file load, given the arguments of
+   * nsIWebProgressListener.onStateChange. If the state change tells that a file
+   * URI has been loaded, then the remote Web Console instance is notified.
+   * @private
+   */
+  _checkFileActivity: function(progress, request, state, status) {
+    if (!(state & Ci.nsIWebProgressListener.STATE_START)) {
+      return;
+    }
+
+    let uri = null;
+    if (request instanceof Ci.imgIRequest) {
+      const imgIRequest = request.QueryInterface(Ci.imgIRequest);
+      uri = imgIRequest.URI;
+    } else if (request instanceof Ci.nsIChannel) {
+      const nsIChannel = request.QueryInterface(Ci.nsIChannel);
+      uri = nsIChannel.URI;
+    }
+
+    if (!uri || !uri.schemeIs("file") && !uri.schemeIs("ftp")) {
+      return;
+    }
+
+    this.owner.onFileActivity(uri.spec);
+  },
+
+  /**
+   * Check if the current window.top location is changing, given the arguments
+   * of nsIWebProgressListener.onStateChange. If that is the case, the remote
+   * Web Console instance is notified.
+   * @private
+   */
+  _checkLocationChange: function(progress, request, state) {
+    const isStart = state & Ci.nsIWebProgressListener.STATE_START;
+    const isStop = state & Ci.nsIWebProgressListener.STATE_STOP;
+    const isNetwork = state & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+    const isWindow = state & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
+
+    // Skip non-interesting states.
+    if (!isNetwork || !isWindow || progress.DOMWindow != this.window) {
+      return;
+    }
+
+    if (isStart && request instanceof Ci.nsIChannel) {
+      this.owner.onLocationChange("start", request.URI.spec, "");
+    } else if (isStop) {
+      this.owner.onLocationChange("stop", this.window.location.href,
+                                  this.window.document.title);
+    }
+  },
+
+  /**
+   * Destroy the ConsoleProgressListener.
+   */
+  destroy: function() {
+    if (!this._initialized) {
+      return;
+    }
+
+    this._initialized = false;
+    this._fileActivity = false;
+    this._locationChange = false;
+
+    try {
+      this._webProgress.removeProgressListener(this);
+    } catch (ex) {
+      // This can throw during browser shutdown.
+    }
+
+    this._webProgress = null;
+    this.window = null;
+    this.owner = null;
+  },
+};
copy from devtools/server/actors/webconsole/listeners.js
copy to devtools/server/actors/webconsole/listeners/console-reflow.js
--- a/devtools/server/actors/webconsole/listeners.js
+++ b/devtools/server/actors/webconsole/listeners/console-reflow.js
@@ -1,375 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {Cc, Ci, components} = require("chrome");
-const {isWindowIncluded} = require("devtools/shared/layout/utils");
-const Services = require("Services");
+const {Ci, components} = require("chrome");
 const ChromeUtils = require("ChromeUtils");
-const {CONSOLE_WORKER_IDS, WebConsoleUtils} = require("devtools/server/actors/webconsole/utils");
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-
-// Process script used to forward console calls from content processes to parent process
-const CONTENT_PROCESS_SCRIPT = "resource://devtools/server/actors/webconsole/content-process-forward.js";
-
-// The page errors listener
-
-/**
- * The nsIConsoleService listener. This is used to send all of the console
- * messages (JavaScript, CSS and more) to the remote Web Console instance.
- *
- * @constructor
- * @param nsIDOMWindow [window]
- *        Optional - the window object for which we are created. This is used
- *        for filtering out messages that belong to other windows.
- * @param object listener
- *        The listener object must have one method:
- *        - onConsoleServiceMessage(). This method is invoked with one argument,
- *        the nsIConsoleMessage, whenever a relevant message is received.
- */
-function ConsoleServiceListener(window, listener) {
-  this.window = window;
-  this.listener = listener;
-}
-exports.ConsoleServiceListener = ConsoleServiceListener;
-
-ConsoleServiceListener.prototype =
-{
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIConsoleListener]),
-
-  /**
-   * The content window for which we listen to page errors.
-   * @type nsIDOMWindow
-   */
-  window: null,
-
-  /**
-   * The listener object which is notified of messages from the console service.
-   * @type object
-   */
-  listener: null,
-
-  /**
-   * Initialize the nsIConsoleService listener.
-   */
-  init: function() {
-    Services.console.registerListener(this);
-  },
-
-  /**
-   * The nsIConsoleService observer. This method takes all the script error
-   * messages belonging to the current window and sends them to the remote Web
-   * Console instance.
-   *
-   * @param nsIConsoleMessage message
-   *        The message object coming from the nsIConsoleService.
-   */
-  observe: function(message) {
-    if (!this.listener) {
-      return;
-    }
-
-    if (this.window) {
-      if (!(message instanceof Ci.nsIScriptError) ||
-          !message.outerWindowID ||
-          !this.isCategoryAllowed(message.category)) {
-        return;
-      }
-
-      const errorWindow = Services.wm.getOuterWindowWithId(message.outerWindowID);
-      if (!errorWindow || !isWindowIncluded(this.window, errorWindow)) {
-        return;
-      }
-    }
-
-    this.listener.onConsoleServiceMessage(message);
-  },
-
-  /**
-   * Check if the given message category is allowed to be tracked or not.
-   * We ignore chrome-originating errors as we only care about content.
-   *
-   * @param string category
-   *        The message category you want to check.
-   * @return boolean
-   *         True if the category is allowed to be logged, false otherwise.
-   */
-  isCategoryAllowed: function(category) {
-    if (!category) {
-      return false;
-    }
-
-    switch (category) {
-      case "XPConnect JavaScript":
-      case "component javascript":
-      case "chrome javascript":
-      case "chrome registration":
-      case "XBL":
-      case "XBL Prototype Handler":
-      case "XBL Content Sink":
-      case "xbl javascript":
-        return false;
-    }
-
-    return true;
-  },
-
-  /**
-   * Get the cached page errors for the current inner window and its (i)frames.
-   *
-   * @param boolean [includePrivate=false]
-   *        Tells if you want to also retrieve messages coming from private
-   *        windows. Defaults to false.
-   * @return array
-   *         The array of cached messages. Each element is an nsIScriptError or
-   *         an nsIConsoleMessage
-   */
-  getCachedMessages: function(includePrivate = false) {
-    const errors = Services.console.getMessageArray() || [];
-
-    // if !this.window, we're in a browser console. Still need to filter
-    // private messages.
-    if (!this.window) {
-      return errors.filter((error) => {
-        if (error instanceof Ci.nsIScriptError) {
-          if (!includePrivate && error.isFromPrivateWindow) {
-            return false;
-          }
-        }
-
-        return true;
-      });
-    }
-
-    const ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
-
-    return errors.filter((error) => {
-      if (error instanceof Ci.nsIScriptError) {
-        if (!includePrivate && error.isFromPrivateWindow) {
-          return false;
-        }
-        if (ids &&
-            (!ids.includes(error.innerWindowID) ||
-             !this.isCategoryAllowed(error.category))) {
-          return false;
-        }
-      } else if (ids && ids[0]) {
-        // If this is not an nsIScriptError and we need to do window-based
-        // filtering we skip this message.
-        return false;
-      }
-
-      return true;
-    });
-  },
-
-  /**
-   * Remove the nsIConsoleService listener.
-   */
-  destroy: function() {
-    Services.console.unregisterListener(this);
-    this.listener = this.window = null;
-  },
-};
-
-// The window.console API observer
-
-/**
- * The window.console API observer. This allows the window.console API messages
- * to be sent to the remote Web Console instance.
- *
- * @constructor
- * @param nsIDOMWindow window
- *        Optional - the window object for which we are created. This is used
- *        for filtering out messages that belong to other windows.
- * @param object owner
- *        The owner object must have the following methods:
- *        - onConsoleAPICall(). This method is invoked with one argument, the
- *        Console API message that comes from the observer service, whenever
- *        a relevant console API call is received.
- * @param object filteringOptions
- *        Optional - The filteringOptions that this listener should listen to:
- *        - addonId: filter console messages based on the addonId.
- */
-function ConsoleAPIListener(window, owner, {addonId} = {}) {
-  this.window = window;
-  this.owner = owner;
-  this.addonId = addonId;
-}
-exports.ConsoleAPIListener = ConsoleAPIListener;
-
-ConsoleAPIListener.prototype =
-{
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
-
-  /**
-   * The content window for which we listen to window.console API calls.
-   * @type nsIDOMWindow
-   */
-  window: null,
-
-  /**
-   * The owner object which is notified of window.console API calls. It must
-   * have a onConsoleAPICall method which is invoked with one argument: the
-   * console API call object that comes from the observer service.
-   *
-   * @type object
-   * @see WebConsoleActor
-   */
-  owner: null,
-
-  /**
-   * The addonId that we listen for. If not null then only messages from this
-   * console will be returned.
-   */
-  addonId: null,
-
-  /**
-   * Initialize the window.console API observer.
-   */
-  init: function() {
-    // Note that the observer is process-wide. We will filter the messages as
-    // needed, see CAL_observe().
-    Services.obs.addObserver(this, "console-api-log-event");
-  },
-
-  /**
-   * The console API message observer. When messages are received from the
-   * observer service we forward them to the remote Web Console instance.
-   *
-   * @param object message
-   *        The message object receives from the observer service.
-   * @param string topic
-   *        The message topic received from the observer service.
-   */
-  observe: function(message, topic) {
-    if (!this.owner) {
-      return;
-    }
-
-    // Here, wrappedJSObject is not a security wrapper but a property defined
-    // by the XPCOM component which allows us to unwrap the XPCOM interface and
-    // access the underlying JSObject.
-    const apiMessage = message.wrappedJSObject;
-
-    if (!this.isMessageRelevant(apiMessage)) {
-      return;
-    }
-
-    this.owner.onConsoleAPICall(apiMessage);
-  },
-
-  /**
-   * Given a message, return true if this window should show it and false
-   * if it should be ignored.
-   *
-   * @param message
-   *        The message from the Storage Service
-   * @return bool
-   *         Do we care about this message?
-   */
-  isMessageRelevant: function(message) {
-    const workerType = WebConsoleUtils.getWorkerType(message);
-
-    if (this.window && workerType === "ServiceWorker") {
-      // For messages from Service Workers, message.ID is the
-      // scope, which can be used to determine whether it's controlling
-      // a window.
-      const scope = message.ID;
-
-      if (!this.window.shouldReportForServiceWorkerScope(scope)) {
-        return false;
-      }
-    }
-
-    if (this.window && !workerType) {
-      const msgWindow = Services.wm.getCurrentInnerWindowWithId(message.innerID);
-      if (!msgWindow || !isWindowIncluded(this.window, msgWindow)) {
-        // Not the same window!
-        return false;
-      }
-    }
-
-    if (this.addonId) {
-      // ConsoleAPI.jsm messages contains a consoleID, (and it is currently
-      // used in Addon SDK add-ons), the standard 'console' object
-      // (which is used in regular webpages and in WebExtensions pages)
-      // contains the originAttributes of the source document principal.
-
-      // Filtering based on the originAttributes used by
-      // the Console API object.
-      if (message.addonId == this.addonId) {
-        return true;
-      }
-
-      // Filtering based on the old-style consoleID property used by
-      // the legacy Console JSM module.
-      if (message.consoleID && message.consoleID == `addon/${this.addonId}`) {
-        return true;
-      }
-
-      return false;
-    }
-
-    return true;
-  },
-
-  /**
-   * Get the cached messages for the current inner window and its (i)frames.
-   *
-   * @param boolean [includePrivate=false]
-   *        Tells if you want to also retrieve messages coming from private
-   *        windows. Defaults to false.
-   * @return array
-   *         The array of cached messages.
-   */
-  getCachedMessages: function(includePrivate = false) {
-    let messages = [];
-    const ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
-                              .getService(Ci.nsIConsoleAPIStorage);
-
-    // if !this.window, we're in a browser console. Retrieve all events
-    // for filtering based on privacy.
-    if (!this.window) {
-      messages = ConsoleAPIStorage.getEvents();
-    } else {
-      const ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
-      ids.forEach((id) => {
-        messages = messages.concat(ConsoleAPIStorage.getEvents(id));
-      });
-    }
-
-    CONSOLE_WORKER_IDS.forEach((id) => {
-      messages = messages.concat(ConsoleAPIStorage.getEvents(id));
-    });
-
-    messages = messages.filter(msg => {
-      return this.isMessageRelevant(msg);
-    });
-
-    if (includePrivate) {
-      return messages;
-    }
-
-    return messages.filter((m) => !m.private);
-  },
-
-  /**
-   * Destroy the console API listener.
-   */
-  destroy: function() {
-    Services.obs.removeObserver(this, "console-api-log-event");
-    this.window = this.owner = null;
-  },
-};
 
 /**
  * A ReflowObserver that listens for reflow events from the page.
  * Implements nsIReflowObserver.
  *
  * @constructor
  * @param object window
  *        The window for which we need to track reflow.
@@ -444,124 +85,8 @@ ConsoleReflowListener.prototype =
   /**
    * Unregister listener.
    */
   destroy: function() {
     this.docshell.removeWeakReflowObserver(this);
     this.listener = this.docshell = null;
   },
 };
-
-/**
- * Forward console message calls from content processes to the parent process.
- * Used by Browser console and toolbox to see messages from all processes.
- *
- * @constructor
- * @param object owner
- *        The listener owner which needs to implement:
- *        - onConsoleAPICall(message)
- */
-function ContentProcessListener(listener) {
-  this.listener = listener;
-
-  Services.ppmm.addMessageListener("Console:Log", this);
-  Services.ppmm.loadProcessScript(CONTENT_PROCESS_SCRIPT, true);
-}
-
-exports.ContentProcessListener = ContentProcessListener;
-
-ContentProcessListener.prototype = {
-  receiveMessage(message) {
-    const logMsg = message.data;
-    logMsg.wrappedJSObject = logMsg;
-    this.listener.onConsoleAPICall(logMsg);
-  },
-
-  destroy() {
-    // Tell the content processes to stop listening and forwarding messages
-    Services.ppmm.broadcastAsyncMessage("DevTools:StopForwardingContentProcessMessage");
-
-    Services.ppmm.removeMessageListener("Console:Log", this);
-    Services.ppmm.removeDelayedProcessScript(CONTENT_PROCESS_SCRIPT);
-
-    this.listener = null;
-  }
-};
-
-/**
- * Forward `DOMContentLoaded` and `load` events with precise timing
- * of when events happened according to window.performance numbers.
- *
- * @constructor
- * @param object console
- *        The web console actor.
- */
-function DocumentEventsListener(console) {
-  this.console = console;
-
-  this.onWindowReady = this.onWindowReady.bind(this);
-  this.onContentLoaded = this.onContentLoaded.bind(this);
-  this.onLoad = this.onLoad.bind(this);
-  this.listen();
-}
-
-exports.DocumentEventsListener = DocumentEventsListener;
-
-DocumentEventsListener.prototype = {
-  listen() {
-    EventEmitter.on(this.console.parentActor, "window-ready", this.onWindowReady);
-    this.onWindowReady({ window: this.console.window, isTopLevel: true });
-  },
-
-  onWindowReady({ window, isTopLevel }) {
-    // Ignore iframes
-    if (!isTopLevel) {
-      return;
-    }
-
-    const { readyState } = window.document;
-    if (readyState != "interactive" && readyState != "complete") {
-      window.addEventListener("DOMContentLoaded", this.onContentLoaded, { once: true });
-    } else {
-      this.onContentLoaded({ target: window.document });
-    }
-    if (readyState != "complete") {
-      window.addEventListener("load", this.onLoad, { once: true });
-    } else {
-      this.onLoad({ target: window.document });
-    }
-  },
-
-  onContentLoaded(event) {
-    const window = event.target.defaultView;
-    const packet = {
-      from: this.console.actorID,
-      type: "documentEvent",
-      name: "dom-interactive",
-      // milliseconds since the UNIX epoch, when the parser finished its work
-      // on the main document, that is when its Document.readyState changes to
-      // 'interactive' and the corresponding readystatechange event is thrown
-      time: window.performance.timing.domInteractive
-    };
-    this.console.conn.send(packet);
-  },
-
-  onLoad(event) {
-    const window = event.target.defaultView;
-    const packet = {
-      from: this.console.actorID,
-      type: "documentEvent",
-      name: "dom-complete",
-      // milliseconds since the UNIX epoch, when the parser finished its work
-      // on the main document, that is when its Document.readyState changes to
-      // 'complete' and the corresponding readystatechange event is thrown
-      time: window.performance.timing.domComplete
-    };
-    this.console.conn.send(packet);
-  },
-
-  destroy() {
-    EventEmitter.off(this.console.parentActor, "window-ready", this.onWindowReady);
-
-    this.listener = null;
-  }
-};
-
copy from devtools/server/actors/webconsole/listeners.js
copy to devtools/server/actors/webconsole/listeners/console-service.js
--- a/devtools/server/actors/webconsole/listeners.js
+++ b/devtools/server/actors/webconsole/listeners/console-service.js
@@ -1,24 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {Cc, Ci, components} = require("chrome");
+const {Ci} = require("chrome");
 const {isWindowIncluded} = require("devtools/shared/layout/utils");
 const Services = require("Services");
 const ChromeUtils = require("ChromeUtils");
-const {CONSOLE_WORKER_IDS, WebConsoleUtils} = require("devtools/server/actors/webconsole/utils");
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-
-// Process script used to forward console calls from content processes to parent process
-const CONTENT_PROCESS_SCRIPT = "resource://devtools/server/actors/webconsole/content-process-forward.js";
+const {WebConsoleUtils} = require("devtools/server/actors/webconsole/utils");
 
 // The page errors listener
 
 /**
  * The nsIConsoleService listener. This is used to send all of the console
  * messages (JavaScript, CSS and more) to the remote Web Console instance.
  *
  * @constructor
@@ -169,399 +164,8 @@ ConsoleServiceListener.prototype =
   /**
    * Remove the nsIConsoleService listener.
    */
   destroy: function() {
     Services.console.unregisterListener(this);
     this.listener = this.window = null;
   },
 };
-
-// The window.console API observer
-
-/**
- * The window.console API observer. This allows the window.console API messages
- * to be sent to the remote Web Console instance.
- *
- * @constructor
- * @param nsIDOMWindow window
- *        Optional - the window object for which we are created. This is used
- *        for filtering out messages that belong to other windows.
- * @param object owner
- *        The owner object must have the following methods:
- *        - onConsoleAPICall(). This method is invoked with one argument, the
- *        Console API message that comes from the observer service, whenever
- *        a relevant console API call is received.
- * @param object filteringOptions
- *        Optional - The filteringOptions that this listener should listen to:
- *        - addonId: filter console messages based on the addonId.
- */
-function ConsoleAPIListener(window, owner, {addonId} = {}) {
-  this.window = window;
-  this.owner = owner;
-  this.addonId = addonId;
-}
-exports.ConsoleAPIListener = ConsoleAPIListener;
-
-ConsoleAPIListener.prototype =
-{
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
-
-  /**
-   * The content window for which we listen to window.console API calls.
-   * @type nsIDOMWindow
-   */
-  window: null,
-
-  /**
-   * The owner object which is notified of window.console API calls. It must
-   * have a onConsoleAPICall method which is invoked with one argument: the
-   * console API call object that comes from the observer service.
-   *
-   * @type object
-   * @see WebConsoleActor
-   */
-  owner: null,
-
-  /**
-   * The addonId that we listen for. If not null then only messages from this
-   * console will be returned.
-   */
-  addonId: null,
-
-  /**
-   * Initialize the window.console API observer.
-   */
-  init: function() {
-    // Note that the observer is process-wide. We will filter the messages as
-    // needed, see CAL_observe().
-    Services.obs.addObserver(this, "console-api-log-event");
-  },
-
-  /**
-   * The console API message observer. When messages are received from the
-   * observer service we forward them to the remote Web Console instance.
-   *
-   * @param object message
-   *        The message object receives from the observer service.
-   * @param string topic
-   *        The message topic received from the observer service.
-   */
-  observe: function(message, topic) {
-    if (!this.owner) {
-      return;
-    }
-
-    // Here, wrappedJSObject is not a security wrapper but a property defined
-    // by the XPCOM component which allows us to unwrap the XPCOM interface and
-    // access the underlying JSObject.
-    const apiMessage = message.wrappedJSObject;
-
-    if (!this.isMessageRelevant(apiMessage)) {
-      return;
-    }
-
-    this.owner.onConsoleAPICall(apiMessage);
-  },
-
-  /**
-   * Given a message, return true if this window should show it and false
-   * if it should be ignored.
-   *
-   * @param message
-   *        The message from the Storage Service
-   * @return bool
-   *         Do we care about this message?
-   */
-  isMessageRelevant: function(message) {
-    const workerType = WebConsoleUtils.getWorkerType(message);
-
-    if (this.window && workerType === "ServiceWorker") {
-      // For messages from Service Workers, message.ID is the
-      // scope, which can be used to determine whether it's controlling
-      // a window.
-      const scope = message.ID;
-
-      if (!this.window.shouldReportForServiceWorkerScope(scope)) {
-        return false;
-      }
-    }
-
-    if (this.window && !workerType) {
-      const msgWindow = Services.wm.getCurrentInnerWindowWithId(message.innerID);
-      if (!msgWindow || !isWindowIncluded(this.window, msgWindow)) {
-        // Not the same window!
-        return false;
-      }
-    }
-
-    if (this.addonId) {
-      // ConsoleAPI.jsm messages contains a consoleID, (and it is currently
-      // used in Addon SDK add-ons), the standard 'console' object
-      // (which is used in regular webpages and in WebExtensions pages)
-      // contains the originAttributes of the source document principal.
-
-      // Filtering based on the originAttributes used by
-      // the Console API object.
-      if (message.addonId == this.addonId) {
-        return true;
-      }
-
-      // Filtering based on the old-style consoleID property used by
-      // the legacy Console JSM module.
-      if (message.consoleID && message.consoleID == `addon/${this.addonId}`) {
-        return true;
-      }
-
-      return false;
-    }
-
-    return true;
-  },
-
-  /**
-   * Get the cached messages for the current inner window and its (i)frames.
-   *
-   * @param boolean [includePrivate=false]
-   *        Tells if you want to also retrieve messages coming from private
-   *        windows. Defaults to false.
-   * @return array
-   *         The array of cached messages.
-   */
-  getCachedMessages: function(includePrivate = false) {
-    let messages = [];
-    const ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
-                              .getService(Ci.nsIConsoleAPIStorage);
-
-    // if !this.window, we're in a browser console. Retrieve all events
-    // for filtering based on privacy.
-    if (!this.window) {
-      messages = ConsoleAPIStorage.getEvents();
-    } else {
-      const ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
-      ids.forEach((id) => {
-        messages = messages.concat(ConsoleAPIStorage.getEvents(id));
-      });
-    }
-
-    CONSOLE_WORKER_IDS.forEach((id) => {
-      messages = messages.concat(ConsoleAPIStorage.getEvents(id));
-    });
-
-    messages = messages.filter(msg => {
-      return this.isMessageRelevant(msg);
-    });
-
-    if (includePrivate) {
-      return messages;
-    }
-
-    return messages.filter((m) => !m.private);
-  },
-
-  /**
-   * Destroy the console API listener.
-   */
-  destroy: function() {
-    Services.obs.removeObserver(this, "console-api-log-event");
-    this.window = this.owner = null;
-  },
-};
-
-/**
- * A ReflowObserver that listens for reflow events from the page.
- * Implements nsIReflowObserver.
- *
- * @constructor
- * @param object window
- *        The window for which we need to track reflow.
- * @param object owner
- *        The listener owner which needs to implement:
- *        - onReflowActivity(reflowInfo)
- */
-
-function ConsoleReflowListener(window, listener) {
-  this.docshell = window.docShell;
-  this.listener = listener;
-  this.docshell.addWeakReflowObserver(this);
-}
-
-exports.ConsoleReflowListener = ConsoleReflowListener;
-
-ConsoleReflowListener.prototype =
-{
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIReflowObserver,
-                                          Ci.nsISupportsWeakReference]),
-  docshell: null,
-  listener: null,
-
-  /**
-   * Forward reflow event to listener.
-   *
-   * @param DOMHighResTimeStamp start
-   * @param DOMHighResTimeStamp end
-   * @param boolean interruptible
-   */
-  sendReflow: function(start, end, interruptible) {
-    const frame = components.stack.caller.caller;
-
-    let filename = frame ? frame.filename : null;
-
-    if (filename) {
-      // Because filename could be of the form "xxx.js -> xxx.js -> xxx.js",
-      // we only take the last part.
-      filename = filename.split(" ").pop();
-    }
-
-    this.listener.onReflowActivity({
-      interruptible: interruptible,
-      start: start,
-      end: end,
-      sourceURL: filename,
-      sourceLine: frame ? frame.lineNumber : null,
-      functionName: frame ? frame.name : null
-    });
-  },
-
-  /**
-   * On uninterruptible reflow
-   *
-   * @param DOMHighResTimeStamp start
-   * @param DOMHighResTimeStamp end
-   */
-  reflow: function(start, end) {
-    this.sendReflow(start, end, false);
-  },
-
-  /**
-   * On interruptible reflow
-   *
-   * @param DOMHighResTimeStamp start
-   * @param DOMHighResTimeStamp end
-   */
-  reflowInterruptible: function(start, end) {
-    this.sendReflow(start, end, true);
-  },
-
-  /**
-   * Unregister listener.
-   */
-  destroy: function() {
-    this.docshell.removeWeakReflowObserver(this);
-    this.listener = this.docshell = null;
-  },
-};
-
-/**
- * Forward console message calls from content processes to the parent process.
- * Used by Browser console and toolbox to see messages from all processes.
- *
- * @constructor
- * @param object owner
- *        The listener owner which needs to implement:
- *        - onConsoleAPICall(message)
- */
-function ContentProcessListener(listener) {
-  this.listener = listener;
-
-  Services.ppmm.addMessageListener("Console:Log", this);
-  Services.ppmm.loadProcessScript(CONTENT_PROCESS_SCRIPT, true);
-}
-
-exports.ContentProcessListener = ContentProcessListener;
-
-ContentProcessListener.prototype = {
-  receiveMessage(message) {
-    const logMsg = message.data;
-    logMsg.wrappedJSObject = logMsg;
-    this.listener.onConsoleAPICall(logMsg);
-  },
-
-  destroy() {
-    // Tell the content processes to stop listening and forwarding messages
-    Services.ppmm.broadcastAsyncMessage("DevTools:StopForwardingContentProcessMessage");
-
-    Services.ppmm.removeMessageListener("Console:Log", this);
-    Services.ppmm.removeDelayedProcessScript(CONTENT_PROCESS_SCRIPT);
-
-    this.listener = null;
-  }
-};
-
-/**
- * Forward `DOMContentLoaded` and `load` events with precise timing
- * of when events happened according to window.performance numbers.
- *
- * @constructor
- * @param object console
- *        The web console actor.
- */
-function DocumentEventsListener(console) {
-  this.console = console;
-
-  this.onWindowReady = this.onWindowReady.bind(this);
-  this.onContentLoaded = this.onContentLoaded.bind(this);
-  this.onLoad = this.onLoad.bind(this);
-  this.listen();
-}
-
-exports.DocumentEventsListener = DocumentEventsListener;
-
-DocumentEventsListener.prototype = {
-  listen() {
-    EventEmitter.on(this.console.parentActor, "window-ready", this.onWindowReady);
-    this.onWindowReady({ window: this.console.window, isTopLevel: true });
-  },
-
-  onWindowReady({ window, isTopLevel }) {
-    // Ignore iframes
-    if (!isTopLevel) {
-      return;
-    }
-
-    const { readyState } = window.document;
-    if (readyState != "interactive" && readyState != "complete") {
-      window.addEventListener("DOMContentLoaded", this.onContentLoaded, { once: true });
-    } else {
-      this.onContentLoaded({ target: window.document });
-    }
-    if (readyState != "complete") {
-      window.addEventListener("load", this.onLoad, { once: true });
-    } else {
-      this.onLoad({ target: window.document });
-    }
-  },
-
-  onContentLoaded(event) {
-    const window = event.target.defaultView;
-    const packet = {
-      from: this.console.actorID,
-      type: "documentEvent",
-      name: "dom-interactive",
-      // milliseconds since the UNIX epoch, when the parser finished its work
-      // on the main document, that is when its Document.readyState changes to
-      // 'interactive' and the corresponding readystatechange event is thrown
-      time: window.performance.timing.domInteractive
-    };
-    this.console.conn.send(packet);
-  },
-
-  onLoad(event) {
-    const window = event.target.defaultView;
-    const packet = {
-      from: this.console.actorID,
-      type: "documentEvent",
-      name: "dom-complete",
-      // milliseconds since the UNIX epoch, when the parser finished its work
-      // on the main document, that is when its Document.readyState changes to
-      // 'complete' and the corresponding readystatechange event is thrown
-      time: window.performance.timing.domComplete
-    };
-    this.console.conn.send(packet);
-  },
-
-  destroy() {
-    EventEmitter.off(this.console.parentActor, "window-ready", this.onWindowReady);
-
-    this.listener = null;
-  }
-};
-
copy from devtools/server/actors/webconsole/listeners.js
copy to devtools/server/actors/webconsole/listeners/content-process.js
--- a/devtools/server/actors/webconsole/listeners.js
+++ b/devtools/server/actors/webconsole/listeners/content-process.js
@@ -1,460 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {Cc, Ci, components} = require("chrome");
-const {isWindowIncluded} = require("devtools/shared/layout/utils");
 const Services = require("Services");
-const ChromeUtils = require("ChromeUtils");
-const {CONSOLE_WORKER_IDS, WebConsoleUtils} = require("devtools/server/actors/webconsole/utils");
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 
 // Process script used to forward console calls from content processes to parent process
 const CONTENT_PROCESS_SCRIPT = "resource://devtools/server/actors/webconsole/content-process-forward.js";
 
-// The page errors listener
-
-/**
- * The nsIConsoleService listener. This is used to send all of the console
- * messages (JavaScript, CSS and more) to the remote Web Console instance.
- *
- * @constructor
- * @param nsIDOMWindow [window]
- *        Optional - the window object for which we are created. This is used
- *        for filtering out messages that belong to other windows.
- * @param object listener
- *        The listener object must have one method:
- *        - onConsoleServiceMessage(). This method is invoked with one argument,
- *        the nsIConsoleMessage, whenever a relevant message is received.
- */
-function ConsoleServiceListener(window, listener) {
-  this.window = window;
-  this.listener = listener;
-}
-exports.ConsoleServiceListener = ConsoleServiceListener;
-
-ConsoleServiceListener.prototype =
-{
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIConsoleListener]),
-
-  /**
-   * The content window for which we listen to page errors.
-   * @type nsIDOMWindow
-   */
-  window: null,
-
-  /**
-   * The listener object which is notified of messages from the console service.
-   * @type object
-   */
-  listener: null,
-
-  /**
-   * Initialize the nsIConsoleService listener.
-   */
-  init: function() {
-    Services.console.registerListener(this);
-  },
-
-  /**
-   * The nsIConsoleService observer. This method takes all the script error
-   * messages belonging to the current window and sends them to the remote Web
-   * Console instance.
-   *
-   * @param nsIConsoleMessage message
-   *        The message object coming from the nsIConsoleService.
-   */
-  observe: function(message) {
-    if (!this.listener) {
-      return;
-    }
-
-    if (this.window) {
-      if (!(message instanceof Ci.nsIScriptError) ||
-          !message.outerWindowID ||
-          !this.isCategoryAllowed(message.category)) {
-        return;
-      }
-
-      const errorWindow = Services.wm.getOuterWindowWithId(message.outerWindowID);
-      if (!errorWindow || !isWindowIncluded(this.window, errorWindow)) {
-        return;
-      }
-    }
-
-    this.listener.onConsoleServiceMessage(message);
-  },
-
-  /**
-   * Check if the given message category is allowed to be tracked or not.
-   * We ignore chrome-originating errors as we only care about content.
-   *
-   * @param string category
-   *        The message category you want to check.
-   * @return boolean
-   *         True if the category is allowed to be logged, false otherwise.
-   */
-  isCategoryAllowed: function(category) {
-    if (!category) {
-      return false;
-    }
-
-    switch (category) {
-      case "XPConnect JavaScript":
-      case "component javascript":
-      case "chrome javascript":
-      case "chrome registration":
-      case "XBL":
-      case "XBL Prototype Handler":
-      case "XBL Content Sink":
-      case "xbl javascript":
-        return false;
-    }
-
-    return true;
-  },
-
-  /**
-   * Get the cached page errors for the current inner window and its (i)frames.
-   *
-   * @param boolean [includePrivate=false]
-   *        Tells if you want to also retrieve messages coming from private
-   *        windows. Defaults to false.
-   * @return array
-   *         The array of cached messages. Each element is an nsIScriptError or
-   *         an nsIConsoleMessage
-   */
-  getCachedMessages: function(includePrivate = false) {
-    const errors = Services.console.getMessageArray() || [];
-
-    // if !this.window, we're in a browser console. Still need to filter
-    // private messages.
-    if (!this.window) {
-      return errors.filter((error) => {
-        if (error instanceof Ci.nsIScriptError) {
-          if (!includePrivate && error.isFromPrivateWindow) {
-            return false;
-          }
-        }
-
-        return true;
-      });
-    }
-
-    const ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
-
-    return errors.filter((error) => {
-      if (error instanceof Ci.nsIScriptError) {
-        if (!includePrivate && error.isFromPrivateWindow) {
-          return false;
-        }
-        if (ids &&
-            (!ids.includes(error.innerWindowID) ||
-             !this.isCategoryAllowed(error.category))) {
-          return false;
-        }
-      } else if (ids && ids[0]) {
-        // If this is not an nsIScriptError and we need to do window-based
-        // filtering we skip this message.
-        return false;
-      }
-
-      return true;
-    });
-  },
-
-  /**
-   * Remove the nsIConsoleService listener.
-   */
-  destroy: function() {
-    Services.console.unregisterListener(this);
-    this.listener = this.window = null;
-  },
-};
-
-// The window.console API observer
-
-/**
- * The window.console API observer. This allows the window.console API messages
- * to be sent to the remote Web Console instance.
- *
- * @constructor
- * @param nsIDOMWindow window
- *        Optional - the window object for which we are created. This is used
- *        for filtering out messages that belong to other windows.
- * @param object owner
- *        The owner object must have the following methods:
- *        - onConsoleAPICall(). This method is invoked with one argument, the
- *        Console API message that comes from the observer service, whenever
- *        a relevant console API call is received.
- * @param object filteringOptions
- *        Optional - The filteringOptions that this listener should listen to:
- *        - addonId: filter console messages based on the addonId.
- */
-function ConsoleAPIListener(window, owner, {addonId} = {}) {
-  this.window = window;
-  this.owner = owner;
-  this.addonId = addonId;
-}
-exports.ConsoleAPIListener = ConsoleAPIListener;
-
-ConsoleAPIListener.prototype =
-{
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
-
-  /**
-   * The content window for which we listen to window.console API calls.
-   * @type nsIDOMWindow
-   */
-  window: null,
-
-  /**
-   * The owner object which is notified of window.console API calls. It must
-   * have a onConsoleAPICall method which is invoked with one argument: the
-   * console API call object that comes from the observer service.
-   *
-   * @type object
-   * @see WebConsoleActor
-   */
-  owner: null,
-
-  /**
-   * The addonId that we listen for. If not null then only messages from this
-   * console will be returned.
-   */
-  addonId: null,
-
-  /**
-   * Initialize the window.console API observer.
-   */
-  init: function() {
-    // Note that the observer is process-wide. We will filter the messages as
-    // needed, see CAL_observe().
-    Services.obs.addObserver(this, "console-api-log-event");
-  },
-
-  /**
-   * The console API message observer. When messages are received from the
-   * observer service we forward them to the remote Web Console instance.
-   *
-   * @param object message
-   *        The message object receives from the observer service.
-   * @param string topic
-   *        The message topic received from the observer service.
-   */
-  observe: function(message, topic) {
-    if (!this.owner) {
-      return;
-    }
-
-    // Here, wrappedJSObject is not a security wrapper but a property defined
-    // by the XPCOM component which allows us to unwrap the XPCOM interface and
-    // access the underlying JSObject.
-    const apiMessage = message.wrappedJSObject;
-
-    if (!this.isMessageRelevant(apiMessage)) {
-      return;
-    }
-
-    this.owner.onConsoleAPICall(apiMessage);
-  },
-
-  /**
-   * Given a message, return true if this window should show it and false
-   * if it should be ignored.
-   *
-   * @param message
-   *        The message from the Storage Service
-   * @return bool
-   *         Do we care about this message?
-   */
-  isMessageRelevant: function(message) {
-    const workerType = WebConsoleUtils.getWorkerType(message);
-
-    if (this.window && workerType === "ServiceWorker") {
-      // For messages from Service Workers, message.ID is the
-      // scope, which can be used to determine whether it's controlling
-      // a window.
-      const scope = message.ID;
-
-      if (!this.window.shouldReportForServiceWorkerScope(scope)) {
-        return false;
-      }
-    }
-
-    if (this.window && !workerType) {
-      const msgWindow = Services.wm.getCurrentInnerWindowWithId(message.innerID);
-      if (!msgWindow || !isWindowIncluded(this.window, msgWindow)) {
-        // Not the same window!
-        return false;
-      }
-    }
-
-    if (this.addonId) {
-      // ConsoleAPI.jsm messages contains a consoleID, (and it is currently
-      // used in Addon SDK add-ons), the standard 'console' object
-      // (which is used in regular webpages and in WebExtensions pages)
-      // contains the originAttributes of the source document principal.
-
-      // Filtering based on the originAttributes used by
-      // the Console API object.
-      if (message.addonId == this.addonId) {
-        return true;
-      }
-
-      // Filtering based on the old-style consoleID property used by
-      // the legacy Console JSM module.
-      if (message.consoleID && message.consoleID == `addon/${this.addonId}`) {
-        return true;
-      }
-
-      return false;
-    }
-
-    return true;
-  },
-
-  /**
-   * Get the cached messages for the current inner window and its (i)frames.
-   *
-   * @param boolean [includePrivate=false]
-   *        Tells if you want to also retrieve messages coming from private
-   *        windows. Defaults to false.
-   * @return array
-   *         The array of cached messages.
-   */
-  getCachedMessages: function(includePrivate = false) {
-    let messages = [];
-    const ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
-                              .getService(Ci.nsIConsoleAPIStorage);
-
-    // if !this.window, we're in a browser console. Retrieve all events
-    // for filtering based on privacy.
-    if (!this.window) {
-      messages = ConsoleAPIStorage.getEvents();
-    } else {
-      const ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
-      ids.forEach((id) => {
-        messages = messages.concat(ConsoleAPIStorage.getEvents(id));
-      });
-    }
-
-    CONSOLE_WORKER_IDS.forEach((id) => {
-      messages = messages.concat(ConsoleAPIStorage.getEvents(id));
-    });
-
-    messages = messages.filter(msg => {
-      return this.isMessageRelevant(msg);
-    });
-
-    if (includePrivate) {
-      return messages;
-    }
-
-    return messages.filter((m) => !m.private);
-  },
-
-  /**
-   * Destroy the console API listener.
-   */
-  destroy: function() {
-    Services.obs.removeObserver(this, "console-api-log-event");
-    this.window = this.owner = null;
-  },
-};
-
-/**
- * A ReflowObserver that listens for reflow events from the page.
- * Implements nsIReflowObserver.
- *
- * @constructor
- * @param object window
- *        The window for which we need to track reflow.
- * @param object owner
- *        The listener owner which needs to implement:
- *        - onReflowActivity(reflowInfo)
- */
-
-function ConsoleReflowListener(window, listener) {
-  this.docshell = window.docShell;
-  this.listener = listener;
-  this.docshell.addWeakReflowObserver(this);
-}
-
-exports.ConsoleReflowListener = ConsoleReflowListener;
-
-ConsoleReflowListener.prototype =
-{
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIReflowObserver,
-                                          Ci.nsISupportsWeakReference]),
-  docshell: null,
-  listener: null,
-
-  /**
-   * Forward reflow event to listener.
-   *
-   * @param DOMHighResTimeStamp start
-   * @param DOMHighResTimeStamp end
-   * @param boolean interruptible
-   */
-  sendReflow: function(start, end, interruptible) {
-    const frame = components.stack.caller.caller;
-
-    let filename = frame ? frame.filename : null;
-
-    if (filename) {
-      // Because filename could be of the form "xxx.js -> xxx.js -> xxx.js",
-      // we only take the last part.
-      filename = filename.split(" ").pop();
-    }
-
-    this.listener.onReflowActivity({
-      interruptible: interruptible,
-      start: start,
-      end: end,
-      sourceURL: filename,
-      sourceLine: frame ? frame.lineNumber : null,
-      functionName: frame ? frame.name : null
-    });
-  },
-
-  /**
-   * On uninterruptible reflow
-   *
-   * @param DOMHighResTimeStamp start
-   * @param DOMHighResTimeStamp end
-   */
-  reflow: function(start, end) {
-    this.sendReflow(start, end, false);
-  },
-
-  /**
-   * On interruptible reflow
-   *
-   * @param DOMHighResTimeStamp start
-   * @param DOMHighResTimeStamp end
-   */
-  reflowInterruptible: function(start, end) {
-    this.sendReflow(start, end, true);
-  },
-
-  /**
-   * Unregister listener.
-   */
-  destroy: function() {
-    this.docshell.removeWeakReflowObserver(this);
-    this.listener = this.docshell = null;
-  },
-};
-
 /**
  * Forward console message calls from content processes to the parent process.
  * Used by Browser console and toolbox to see messages from all processes.
  *
  * @constructor
  * @param object owner
  *        The listener owner which needs to implement:
  *        - onConsoleAPICall(message)
@@ -480,88 +39,8 @@ ContentProcessListener.prototype = {
     Services.ppmm.broadcastAsyncMessage("DevTools:StopForwardingContentProcessMessage");
 
     Services.ppmm.removeMessageListener("Console:Log", this);
     Services.ppmm.removeDelayedProcessScript(CONTENT_PROCESS_SCRIPT);
 
     this.listener = null;
   }
 };
-
-/**
- * Forward `DOMContentLoaded` and `load` events with precise timing
- * of when events happened according to window.performance numbers.
- *
- * @constructor
- * @param object console
- *        The web console actor.
- */
-function DocumentEventsListener(console) {
-  this.console = console;
-
-  this.onWindowReady = this.onWindowReady.bind(this);
-  this.onContentLoaded = this.onContentLoaded.bind(this);
-  this.onLoad = this.onLoad.bind(this);
-  this.listen();
-}
-
-exports.DocumentEventsListener = DocumentEventsListener;
-
-DocumentEventsListener.prototype = {
-  listen() {
-    EventEmitter.on(this.console.parentActor, "window-ready", this.onWindowReady);
-    this.onWindowReady({ window: this.console.window, isTopLevel: true });
-  },
-
-  onWindowReady({ window, isTopLevel }) {
-    // Ignore iframes
-    if (!isTopLevel) {
-      return;
-    }
-
-    const { readyState } = window.document;
-    if (readyState != "interactive" && readyState != "complete") {
-      window.addEventListener("DOMContentLoaded", this.onContentLoaded, { once: true });
-    } else {
-      this.onContentLoaded({ target: window.document });
-    }
-    if (readyState != "complete") {
-      window.addEventListener("load", this.onLoad, { once: true });
-    } else {
-      this.onLoad({ target: window.document });
-    }
-  },
-
-  onContentLoaded(event) {
-    const window = event.target.defaultView;
-    const packet = {
-      from: this.console.actorID,
-      type: "documentEvent",
-      name: "dom-interactive",
-      // milliseconds since the UNIX epoch, when the parser finished its work
-      // on the main document, that is when its Document.readyState changes to
-      // 'interactive' and the corresponding readystatechange event is thrown
-      time: window.performance.timing.domInteractive
-    };
-    this.console.conn.send(packet);
-  },
-
-  onLoad(event) {
-    const window = event.target.defaultView;
-    const packet = {
-      from: this.console.actorID,
-      type: "documentEvent",
-      name: "dom-complete",
-      // milliseconds since the UNIX epoch, when the parser finished its work
-      // on the main document, that is when its Document.readyState changes to
-      // 'complete' and the corresponding readystatechange event is thrown
-      time: window.performance.timing.domComplete
-    };
-    this.console.conn.send(packet);
-  },
-
-  destroy() {
-    EventEmitter.off(this.console.parentActor, "window-ready", this.onWindowReady);
-
-    this.listener = null;
-  }
-};
-
copy from devtools/server/actors/webconsole/listeners.js
copy to devtools/server/actors/webconsole/listeners/document-events.js
--- a/devtools/server/actors/webconsole/listeners.js
+++ b/devtools/server/actors/webconsole/listeners/document-events.js
@@ -1,496 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {Cc, Ci, components} = require("chrome");
-const {isWindowIncluded} = require("devtools/shared/layout/utils");
-const Services = require("Services");
-const ChromeUtils = require("ChromeUtils");
-const {CONSOLE_WORKER_IDS, WebConsoleUtils} = require("devtools/server/actors/webconsole/utils");
-
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 
-// Process script used to forward console calls from content processes to parent process
-const CONTENT_PROCESS_SCRIPT = "resource://devtools/server/actors/webconsole/content-process-forward.js";
-
-// The page errors listener
-
-/**
- * The nsIConsoleService listener. This is used to send all of the console
- * messages (JavaScript, CSS and more) to the remote Web Console instance.
- *
- * @constructor
- * @param nsIDOMWindow [window]
- *        Optional - the window object for which we are created. This is used
- *        for filtering out messages that belong to other windows.
- * @param object listener
- *        The listener object must have one method:
- *        - onConsoleServiceMessage(). This method is invoked with one argument,
- *        the nsIConsoleMessage, whenever a relevant message is received.
- */
-function ConsoleServiceListener(window, listener) {
-  this.window = window;
-  this.listener = listener;
-}
-exports.ConsoleServiceListener = ConsoleServiceListener;
-
-ConsoleServiceListener.prototype =
-{
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIConsoleListener]),
-
-  /**
-   * The content window for which we listen to page errors.
-   * @type nsIDOMWindow
-   */
-  window: null,
-
-  /**
-   * The listener object which is notified of messages from the console service.
-   * @type object
-   */
-  listener: null,
-
-  /**
-   * Initialize the nsIConsoleService listener.
-   */
-  init: function() {
-    Services.console.registerListener(this);
-  },
-
-  /**
-   * The nsIConsoleService observer. This method takes all the script error
-   * messages belonging to the current window and sends them to the remote Web
-   * Console instance.
-   *
-   * @param nsIConsoleMessage message
-   *        The message object coming from the nsIConsoleService.
-   */
-  observe: function(message) {
-    if (!this.listener) {
-      return;
-    }
-
-    if (this.window) {
-      if (!(message instanceof Ci.nsIScriptError) ||
-          !message.outerWindowID ||
-          !this.isCategoryAllowed(message.category)) {
-        return;
-      }
-
-      const errorWindow = Services.wm.getOuterWindowWithId(message.outerWindowID);
-      if (!errorWindow || !isWindowIncluded(this.window, errorWindow)) {
-        return;
-      }
-    }
-
-    this.listener.onConsoleServiceMessage(message);
-  },
-
-  /**
-   * Check if the given message category is allowed to be tracked or not.
-   * We ignore chrome-originating errors as we only care about content.
-   *
-   * @param string category
-   *        The message category you want to check.
-   * @return boolean
-   *         True if the category is allowed to be logged, false otherwise.
-   */
-  isCategoryAllowed: function(category) {
-    if (!category) {
-      return false;
-    }
-
-    switch (category) {
-      case "XPConnect JavaScript":
-      case "component javascript":
-      case "chrome javascript":
-      case "chrome registration":
-      case "XBL":
-      case "XBL Prototype Handler":
-      case "XBL Content Sink":
-      case "xbl javascript":
-        return false;
-    }
-
-    return true;
-  },
-
-  /**
-   * Get the cached page errors for the current inner window and its (i)frames.
-   *
-   * @param boolean [includePrivate=false]
-   *        Tells if you want to also retrieve messages coming from private
-   *        windows. Defaults to false.
-   * @return array
-   *         The array of cached messages. Each element is an nsIScriptError or
-   *         an nsIConsoleMessage
-   */
-  getCachedMessages: function(includePrivate = false) {
-    const errors = Services.console.getMessageArray() || [];
-
-    // if !this.window, we're in a browser console. Still need to filter
-    // private messages.
-    if (!this.window) {
-      return errors.filter((error) => {
-        if (error instanceof Ci.nsIScriptError) {
-          if (!includePrivate && error.isFromPrivateWindow) {
-            return false;
-          }
-        }
-
-        return true;
-      });
-    }
-
-    const ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
-
-    return errors.filter((error) => {
-      if (error instanceof Ci.nsIScriptError) {
-        if (!includePrivate && error.isFromPrivateWindow) {
-          return false;
-        }
-        if (ids &&
-            (!ids.includes(error.innerWindowID) ||
-             !this.isCategoryAllowed(error.category))) {
-          return false;
-        }
-      } else if (ids && ids[0]) {
-        // If this is not an nsIScriptError and we need to do window-based
-        // filtering we skip this message.
-        return false;
-      }
-
-      return true;
-    });
-  },
-
-  /**
-   * Remove the nsIConsoleService listener.
-   */
-  destroy: function() {
-    Services.console.unregisterListener(this);
-    this.listener = this.window = null;
-  },
-};
-
-// The window.console API observer
-
-/**
- * The window.console API observer. This allows the window.console API messages
- * to be sent to the remote Web Console instance.
- *
- * @constructor
- * @param nsIDOMWindow window
- *        Optional - the window object for which we are created. This is used
- *        for filtering out messages that belong to other windows.
- * @param object owner
- *        The owner object must have the following methods:
- *        - onConsoleAPICall(). This method is invoked with one argument, the
- *        Console API message that comes from the observer service, whenever
- *        a relevant console API call is received.
- * @param object filteringOptions
- *        Optional - The filteringOptions that this listener should listen to:
- *        - addonId: filter console messages based on the addonId.
- */
-function ConsoleAPIListener(window, owner, {addonId} = {}) {
-  this.window = window;
-  this.owner = owner;
-  this.addonId = addonId;
-}
-exports.ConsoleAPIListener = ConsoleAPIListener;
-
-ConsoleAPIListener.prototype =
-{
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
-
-  /**
-   * The content window for which we listen to window.console API calls.
-   * @type nsIDOMWindow
-   */
-  window: null,
-
-  /**
-   * The owner object which is notified of window.console API calls. It must
-   * have a onConsoleAPICall method which is invoked with one argument: the
-   * console API call object that comes from the observer service.
-   *
-   * @type object
-   * @see WebConsoleActor
-   */
-  owner: null,
-
-  /**
-   * The addonId that we listen for. If not null then only messages from this
-   * console will be returned.
-   */
-  addonId: null,
-
-  /**
-   * Initialize the window.console API observer.
-   */
-  init: function() {
-    // Note that the observer is process-wide. We will filter the messages as
-    // needed, see CAL_observe().
-    Services.obs.addObserver(this, "console-api-log-event");
-  },
-
-  /**
-   * The console API message observer. When messages are received from the
-   * observer service we forward them to the remote Web Console instance.
-   *
-   * @param object message
-   *        The message object receives from the observer service.
-   * @param string topic
-   *        The message topic received from the observer service.
-   */
-  observe: function(message, topic) {
-    if (!this.owner) {
-      return;
-    }
-
-    // Here, wrappedJSObject is not a security wrapper but a property defined
-    // by the XPCOM component which allows us to unwrap the XPCOM interface and
-    // access the underlying JSObject.
-    const apiMessage = message.wrappedJSObject;
-
-    if (!this.isMessageRelevant(apiMessage)) {
-      return;
-    }
-
-    this.owner.onConsoleAPICall(apiMessage);
-  },
-
-  /**
-   * Given a message, return true if this window should show it and false
-   * if it should be ignored.
-   *
-   * @param message
-   *        The message from the Storage Service
-   * @return bool
-   *         Do we care about this message?
-   */
-  isMessageRelevant: function(message) {
-    const workerType = WebConsoleUtils.getWorkerType(message);
-
-    if (this.window && workerType === "ServiceWorker") {
-      // For messages from Service Workers, message.ID is the
-      // scope, which can be used to determine whether it's controlling
-      // a window.
-      const scope = message.ID;
-
-      if (!this.window.shouldReportForServiceWorkerScope(scope)) {
-        return false;
-      }
-    }
-
-    if (this.window && !workerType) {
-      const msgWindow = Services.wm.getCurrentInnerWindowWithId(message.innerID);
-      if (!msgWindow || !isWindowIncluded(this.window, msgWindow)) {
-        // Not the same window!
-        return false;
-      }
-    }
-
-    if (this.addonId) {
-      // ConsoleAPI.jsm messages contains a consoleID, (and it is currently
-      // used in Addon SDK add-ons), the standard 'console' object
-      // (which is used in regular webpages and in WebExtensions pages)
-      // contains the originAttributes of the source document principal.
-
-      // Filtering based on the originAttributes used by
-      // the Console API object.
-      if (message.addonId == this.addonId) {
-        return true;
-      }
-
-      // Filtering based on the old-style consoleID property used by
-      // the legacy Console JSM module.
-      if (message.consoleID && message.consoleID == `addon/${this.addonId}`) {
-        return true;
-      }
-
-      return false;
-    }
-
-    return true;
-  },
-
-  /**
-   * Get the cached messages for the current inner window and its (i)frames.
-   *
-   * @param boolean [includePrivate=false]
-   *        Tells if you want to also retrieve messages coming from private
-   *        windows. Defaults to false.
-   * @return array
-   *         The array of cached messages.
-   */
-  getCachedMessages: function(includePrivate = false) {
-    let messages = [];
-    const ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
-                              .getService(Ci.nsIConsoleAPIStorage);
-
-    // if !this.window, we're in a browser console. Retrieve all events
-    // for filtering based on privacy.
-    if (!this.window) {
-      messages = ConsoleAPIStorage.getEvents();
-    } else {
-      const ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
-      ids.forEach((id) => {
-        messages = messages.concat(ConsoleAPIStorage.getEvents(id));
-      });
-    }
-
-    CONSOLE_WORKER_IDS.forEach((id) => {
-      messages = messages.concat(ConsoleAPIStorage.getEvents(id));
-    });
-
-    messages = messages.filter(msg => {
-      return this.isMessageRelevant(msg);
-    });
-
-    if (includePrivate) {
-      return messages;
-    }
-
-    return messages.filter((m) => !m.private);
-  },
-
-  /**
-   * Destroy the console API listener.
-   */
-  destroy: function() {
-    Services.obs.removeObserver(this, "console-api-log-event");
-    this.window = this.owner = null;
-  },
-};
-
-/**
- * A ReflowObserver that listens for reflow events from the page.
- * Implements nsIReflowObserver.
- *
- * @constructor
- * @param object window
- *        The window for which we need to track reflow.
- * @param object owner
- *        The listener owner which needs to implement:
- *        - onReflowActivity(reflowInfo)
- */
-
-function ConsoleReflowListener(window, listener) {
-  this.docshell = window.docShell;
-  this.listener = listener;
-  this.docshell.addWeakReflowObserver(this);
-}
-
-exports.ConsoleReflowListener = ConsoleReflowListener;
-
-ConsoleReflowListener.prototype =
-{
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIReflowObserver,
-                                          Ci.nsISupportsWeakReference]),
-  docshell: null,
-  listener: null,
-
-  /**
-   * Forward reflow event to listener.
-   *
-   * @param DOMHighResTimeStamp start
-   * @param DOMHighResTimeStamp end
-   * @param boolean interruptible
-   */
-  sendReflow: function(start, end, interruptible) {
-    const frame = components.stack.caller.caller;
-
-    let filename = frame ? frame.filename : null;
-
-    if (filename) {
-      // Because filename could be of the form "xxx.js -> xxx.js -> xxx.js",
-      // we only take the last part.
-      filename = filename.split(" ").pop();
-    }
-
-    this.listener.onReflowActivity({
-      interruptible: interruptible,
-      start: start,
-      end: end,
-      sourceURL: filename,
-      sourceLine: frame ? frame.lineNumber : null,
-      functionName: frame ? frame.name : null
-    });
-  },
-
-  /**
-   * On uninterruptible reflow
-   *
-   * @param DOMHighResTimeStamp start
-   * @param DOMHighResTimeStamp end
-   */
-  reflow: function(start, end) {
-    this.sendReflow(start, end, false);
-  },
-
-  /**
-   * On interruptible reflow
-   *
-   * @param DOMHighResTimeStamp start
-   * @param DOMHighResTimeStamp end
-   */
-  reflowInterruptible: function(start, end) {
-    this.sendReflow(start, end, true);
-  },
-
-  /**
-   * Unregister listener.
-   */
-  destroy: function() {
-    this.docshell.removeWeakReflowObserver(this);
-    this.listener = this.docshell = null;
-  },
-};
-
-/**
- * Forward console message calls from content processes to the parent process.
- * Used by Browser console and toolbox to see messages from all processes.
- *
- * @constructor
- * @param object owner
- *        The listener owner which needs to implement:
- *        - onConsoleAPICall(message)
- */
-function ContentProcessListener(listener) {
-  this.listener = listener;
-
-  Services.ppmm.addMessageListener("Console:Log", this);
-  Services.ppmm.loadProcessScript(CONTENT_PROCESS_SCRIPT, true);
-}
-
-exports.ContentProcessListener = ContentProcessListener;
-
-ContentProcessListener.prototype = {
-  receiveMessage(message) {
-    const logMsg = message.data;
-    logMsg.wrappedJSObject = logMsg;
-    this.listener.onConsoleAPICall(logMsg);
-  },
-
-  destroy() {
-    // Tell the content processes to stop listening and forwarding messages
-    Services.ppmm.broadcastAsyncMessage("DevTools:StopForwardingContentProcessMessage");
-
-    Services.ppmm.removeMessageListener("Console:Log", this);
-    Services.ppmm.removeDelayedProcessScript(CONTENT_PROCESS_SCRIPT);
-
-    this.listener = null;
-  }
-};
-
 /**
  * Forward `DOMContentLoaded` and `load` events with precise timing
  * of when events happened according to window.performance numbers.
  *
  * @constructor
  * @param object console
  *        The web console actor.
  */
@@ -559,9 +79,8 @@ DocumentEventsListener.prototype = {
   },
 
   destroy() {
     EventEmitter.off(this.console.parentActor, "window-ready", this.onWindowReady);
 
     this.listener = null;
   }
 };
-
copy from devtools/server/actors/webconsole/moz.build
copy to devtools/server/actors/webconsole/listeners/moz.build
--- a/devtools/server/actors/webconsole/moz.build
+++ b/devtools/server/actors/webconsole/listeners/moz.build
@@ -1,15 +1,14 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
-    'commands.js',
-    'content-process-forward.js',
-    'listeners.js',
-    'message-manager-mock.js',
-    'screenshot.js',
-    'utils.js',
-    'worker-listeners.js',
+    'console-api.js',
+    'console-progress.js',
+    'console-reflow.js',
+    'console-service.js',
+    'content-process.js',
+    'document-events.js',
 )
--- a/devtools/server/actors/webconsole/moz.build
+++ b/devtools/server/actors/webconsole/moz.build
@@ -1,15 +1,18 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+DIRS += [
+    'listeners',
+]
+
 DevToolsModules(
     'commands.js',
     'content-process-forward.js',
-    'listeners.js',
     'message-manager-mock.js',
     'screenshot.js',
     'utils.js',
     'worker-listeners.js',
 )
--- a/devtools/shared/tests/unit/test_console_filtering.js
+++ b/devtools/shared/tests/unit/test_console_filtering.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const { console, ConsoleAPI } = require("resource://gre/modules/Console.jsm");
-const { ConsoleAPIListener } = require("devtools/server/actors/webconsole/listeners");
+const { ConsoleAPIListener } = require("devtools/server/actors/webconsole/listeners/console-api");
 
 var seenMessages = 0;
 var seenTypes = 0;
 
 var callback = {
   onConsoleAPICall: function(message) {
     if (message.consoleID && message.consoleID == "addon/foo") {
       Assert.equal(message.level, "warn");
--- a/devtools/shared/tests/unit/test_eventemitter_basic.js
+++ b/devtools/shared/tests/unit/test_eventemitter_basic.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const {
   ConsoleAPIListener
-} = require("devtools/server/actors/webconsole/listeners");
+} = require("devtools/server/actors/webconsole/listeners/console-api");
 const EventEmitter = require("devtools/shared/event-emitter");
 const hasMethod = (target, method) =>
   method in target && typeof target[method] === "function";
 
 /**
  * Each method of this object is a test; tests can be synchronous or asynchronous:
  *
  * 1. Plain functions are synchronous tests.
--- a/devtools/shared/tests/unit/test_eventemitter_static.js
+++ b/devtools/shared/tests/unit/test_eventemitter_static.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const {
   ConsoleAPIListener
-} = require("devtools/server/actors/webconsole/listeners");
+} = require("devtools/server/actors/webconsole/listeners/console-api");
 const { on, once, off, emit, count, handler } = require("devtools/shared/event-emitter");
 
 const pass = (message) => ok(true, message);
 const fail = (message) => ok(false, message);
 
 /**
  * Each method of this object is a test; tests can be synchronous or asynchronous:
  *
--- a/devtools/shared/webconsole/moz.build
+++ b/devtools/shared/webconsole/moz.build
@@ -7,12 +7,11 @@
 if CONFIG['OS_TARGET'] != 'Android':
     MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
     XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 
 DevToolsModules(
     'client.js',
     'js-property-provider.js',
     'network-helper.js',
-    'network-monitor.js',
     'screenshot-helper.js',
     'throttle.js',
 )
--- a/devtools/shared/webconsole/test/test_cached_messages.html
+++ b/devtools/shared/webconsole/test/test_cached_messages.html
@@ -92,18 +92,20 @@ function doConsoleCalls() {
       arguments: ["foobarBaz-warn", { type: "object", actor: /[a-z]/ }],
     },
   ];
 }
 </script>
 
 <script class="testbody" type="text/javascript">
 SimpleTest.waitForExplicitFinish();
-var {ConsoleServiceListener, ConsoleAPIListener} =
-  require("devtools/server/actors/webconsole/listeners");
+var {ConsoleServiceListener} =
+  require("devtools/server/actors/webconsole/listeners/console-service");
+var {ConsoleAPIListener} =
+  require("devtools/server/actors/webconsole/listeners/console-api");
 
 let consoleAPIListener, consoleServiceListener;
 let consoleAPICalls = 0;
 let pageErrors = 0;
 
 let handlers = {
   onConsoleAPICall: function onConsoleAPICall(message) {
     for (let msg of expectedConsoleCalls) {
--- a/dom/base/test/gtest/TestMimeType.cpp
+++ b/dom/base/test/gtest/TestMimeType.cpp
@@ -207,26 +207,33 @@ TEST(MimeType, DuplicateParameter2)
   UniquePtr<MimeType> parsed = MimeType::Parse(in);
   ASSERT_TRUE(parsed) << "Parsing succeeded";
   nsString out;
   parsed->Serialize(out);
   ASSERT_TRUE(out.Equals(NS_LITERAL_STRING("text/html;charset=\"()\""))) <<
     "Duplicate parameter #2";
 }
 
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4819)
+#endif
 TEST(MimeType, NonAlphanumericParametersAreQuoted)
 {
   const auto in = NS_LITERAL_STRING("text/html;test=\x00FF\\;charset=gbk");
   UniquePtr<MimeType> parsed = MimeType::Parse(in);
   ASSERT_TRUE(parsed) << "Parsing succeeded";
   nsString out;
   parsed->Serialize(out);
   ASSERT_TRUE(out.Equals(NS_LITERAL_STRING("text/html;test=\"\x00FF\\\\\";charset=gbk"))) <<
     "Non-alphanumeric parameters are quoted";
 }
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
 
 TEST(MimeType, ParameterQuotedIfHasLeadingWhitespace1)
 {
   const auto in = NS_LITERAL_STRING("text/html;charset= g\\\"bk");
   UniquePtr<MimeType> parsed = MimeType::Parse(in);
   ASSERT_TRUE(parsed) << "Parsing succeeded";
   nsAutoString out;
   parsed->Serialize(out);
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -1907,16 +1907,27 @@ CompositorBridgeParent::DeallocPWebRende
     if (it != sIndirectLayerTrees.end()) {
       it->second.mWrBridge = nullptr;
     }
   }
   parent->Release(); // IPDL reference
   return true;
 }
 
+void
+CompositorBridgeParent::NotifyMemoryPressure()
+{
+  if (mWrBridge) {
+    RefPtr<wr::WebRenderAPI> api = mWrBridge->GetWebRenderAPI();
+    if (api) {
+      api->NotifyMemoryPressure();
+    }
+  }
+}
+
 RefPtr<WebRenderBridgeParent>
 CompositorBridgeParent::GetWebRenderBridgeParent() const
 {
   return mWrBridge;
 }
 
 Maybe<TimeStamp>
 CompositorBridgeParent::GetTestingTimeStamp() const
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -170,16 +170,18 @@ public:
   virtual bool IsRemote() const {
     return false;
   }
 
   virtual void ForceComposeToTarget(gfx::DrawTarget* aTarget, const gfx::IntRect* aRect = nullptr) {
     MOZ_CRASH();
   }
 
+  virtual void NotifyMemoryPressure() {}
+
 protected:
   ~CompositorBridgeParentBase() override;
 
   bool mCanSend;
 
 private:
   RefPtr<CompositorManagerParent> mCompositorManager;
 };
@@ -231,16 +233,18 @@ public:
   mozilla::ipc::IPCResult RecvCheckContentOnlyTDR(const uint32_t& sequenceNum, bool* isContentOnlyTDR) override { return IPC_OK(); }
 
   // Unused for chrome <-> compositor communication (which this class does).
   // @see CrossProcessCompositorBridgeParent::RecvRequestNotifyAfterRemotePaint
   mozilla::ipc::IPCResult RecvRequestNotifyAfterRemotePaint() override { return IPC_OK(); };
 
   mozilla::ipc::IPCResult RecvAllPluginsCaptured() override;
 
+  virtual void NotifyMemoryPressure() override;
+
   void ActorDestroy(ActorDestroyReason why) override;
 
   void ShadowLayersUpdated(LayerTransactionParent* aLayerTree,
                            const TransactionInfo& aInfo,
                            bool aHitTestUpdate) override;
   void ScheduleComposite(LayerTransactionParent* aLayerTree) override;
   bool SetTestSampleTime(const LayersId& aId,
                          const TimeStamp& aTime) override;
--- a/gfx/layers/ipc/CompositorManagerParent.cpp
+++ b/gfx/layers/ipc/CompositorManagerParent.cpp
@@ -283,10 +283,21 @@ CompositorManagerParent::RecvAddSharedSu
 
 mozilla::ipc::IPCResult
 CompositorManagerParent::RecvRemoveSharedSurface(const wr::ExternalImageId& aId)
 {
   SharedSurfacesParent::Remove(aId);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+CompositorManagerParent::RecvNotifyMemoryPressure()
+{
+  nsTArray<PCompositorBridgeParent*> compositorBridges;
+  ManagedPCompositorBridgeParent(compositorBridges);
+  for (auto bridge : compositorBridges) {
+    static_cast<CompositorBridgeParentBase*>(bridge)->NotifyMemoryPressure();
+  }
+  return IPC_OK();
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/CompositorManagerParent.h
+++ b/gfx/layers/ipc/CompositorManagerParent.h
@@ -39,16 +39,18 @@ public:
                                           const CompositorOptions& aOptions,
                                           bool aUseExternalSurfaceSize,
                                           const gfx::IntSize& aSurfaceSize);
 
   mozilla::ipc::IPCResult RecvAddSharedSurface(const wr::ExternalImageId& aId,
                                                const SurfaceDescriptorShared& aDesc) override;
   mozilla::ipc::IPCResult RecvRemoveSharedSurface(const wr::ExternalImageId& aId) override;
 
+  virtual mozilla::ipc::IPCResult RecvNotifyMemoryPressure() override;
+
   void BindComplete();
   void ActorDestroy(ActorDestroyReason aReason) override;
 
   bool DeallocPCompositorBridgeParent(PCompositorBridgeParent* aActor) override;
   PCompositorBridgeParent* AllocPCompositorBridgeParent(const CompositorBridgeOptions& aOpt) override;
 
 private:
   static StaticRefPtr<CompositorManagerParent> sInstance;
--- a/gfx/layers/ipc/PCompositorManager.ipdl
+++ b/gfx/layers/ipc/PCompositorManager.ipdl
@@ -70,12 +70,14 @@ parent:
    * - A "same process widget" PCompositorBridge is requested by the combined
    *   GPU/UI process for each "top level browser window" as above.
    * See gfx/layers/ipc/PCompositorBridge.ipdl for more details.
    */
   async PCompositorBridge(CompositorBridgeOptions options);
 
   async AddSharedSurface(ExternalImageId aId, SurfaceDescriptorShared aDesc);
   async RemoveSharedSurface(ExternalImageId aId);
+
+  async NotifyMemoryPressure();
 };
 
 } // layers
 } // mozilla
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -466,16 +466,22 @@ FontPrefChanged(const char* aPref, void*
 
 void
 gfxPlatform::OnMemoryPressure(layers::MemoryPressureReason aWhy)
 {
     Factory::PurgeAllCaches();
     gfxGradientCache::PurgeAllCaches();
     PurgeSkiaFontCache();
     PurgeSkiaGPUCache();
+    if (XRE_IsParentProcess()) {
+      layers::CompositorManagerChild* manager = CompositorManagerChild::GetInstance();
+      if (manager) {
+        manager->SendNotifyMemoryPressure();
+      }
+    }
 }
 
 gfxPlatform::gfxPlatform()
   : mHasVariationFontSupport(false)
   , mAzureCanvasBackendCollector(this, &gfxPlatform::GetAzureBackendInfo)
   , mApzSupportCollector(this, &gfxPlatform::GetApzSupportInfo)
   , mTilesInfoCollector(this, &gfxPlatform::GetTilesSupportInfo)
   , mCompositorBackend(layers::LayersBackend::LAYERS_NONE)
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -498,16 +498,22 @@ WebRenderAPI::Resume()
     // implies that all frame data have been processed when the renderer runs this event.
     RunOnRenderThread(std::move(event));
 
     task.Wait();
     return result;
 }
 
 void
+WebRenderAPI::NotifyMemoryPressure()
+{
+  wr_api_notify_memory_pressure(mDocHandle);
+}
+
+void
 WebRenderAPI::WakeSceneBuilder()
 {
     wr_api_wake_scene_builder(mDocHandle);
 }
 
 void
 WebRenderAPI::FlushSceneBuilder()
 {
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -201,16 +201,18 @@ public:
   void ClearAllCaches();
 
   void Pause();
   bool Resume();
 
   void WakeSceneBuilder();
   void FlushSceneBuilder();
 
+  void NotifyMemoryPressure();
+
   wr::WrIdNamespace GetNamespace();
   uint32_t GetMaxTextureSize() const { return mMaxTextureSize; }
   bool GetUseANGLE() const { return mUseANGLE; }
   bool GetUseDComp() const { return mUseDComp; }
   layers::SyncHandle GetSyncHandle() const { return mSyncHandle; }
 
   void Capture();
 
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1024,16 +1024,21 @@ pub unsafe extern "C" fn wr_api_delete(d
 }
 
 /// cbindgen:postfix=WR_DESTRUCTOR_SAFE_FUNC
 #[no_mangle]
 pub unsafe extern "C" fn wr_api_shut_down(dh: &mut DocumentHandle) {
     dh.api.shut_down();
 }
 
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_notify_memory_pressure(dh: &mut DocumentHandle) {
+    dh.api.notify_memory_pressure();
+}
+
 /// cbindgen:postfix=WR_DESTRUCTOR_SAFE_FUNC
 #[no_mangle]
 pub unsafe extern "C" fn wr_api_clear_all_caches(dh: &mut DocumentHandle) {
     dh.api.send_debug_cmd(DebugCommand::ClearCaches(ClearCache::all()));
 }
 
 fn make_transaction(do_async: bool) -> Transaction {
     let mut transaction = Transaction::new();
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -1093,16 +1093,20 @@ WR_INLINE
 bool wr_api_hit_test(DocumentHandle *aDh,
                      WorldPoint aPoint,
                      WrPipelineId *aOutPipelineId,
                      uint64_t *aOutScrollId,
                      uint16_t *aOutHitInfo)
 WR_FUNC;
 
 WR_INLINE
+void wr_api_notify_memory_pressure(DocumentHandle *aDh)
+WR_FUNC;
+
+WR_INLINE
 void wr_api_send_external_event(DocumentHandle *aDh,
                                 uintptr_t aEvt)
 WR_DESTRUCTOR_SAFE_FUNC;
 
 WR_INLINE
 void wr_api_send_transaction(DocumentHandle *aDh,
                              Transaction *aTransaction,
                              bool aIsAsync)
--- a/js/src/devtools/automation/cgc-jstests-slow.txt
+++ b/js/src/devtools/automation/cgc-jstests-slow.txt
@@ -58,20 +58,20 @@ non262/regress/regress-360969-02.js
 non262/regress/regress-360969-03.js
 non262/regress/regress-360969-04.js
 non262/regress/regress-360969-05.js
 non262/regress/regress-360969-06.js
 non262/extensions/regress-477187.js
 non262/regress/regress-452498-052-a.js
 non262/extensions/clone-complex-object.js
 non262/extensions/clone-object-deep.js
-test262/built-ins/RegExp/CharacterClassEscapes/character-class-word-class-escape-flags-u.js
+test262/built-ins/RegExp/CharacterClassEscapes/character-class-digit-class-escape-flags-u.js
+test262/built-ins/RegExp/CharacterClassEscapes/character-class-digit-class-escape-plus-quantifier-flags-u.js
+test262/built-ins/RegExp/CharacterClassEscapes/character-class-non-digit-class-escape-flags-u.js
 test262/built-ins/RegExp/CharacterClassEscapes/character-class-non-digit-class-escape-plus-quantifier-flags-u.js
-test262/built-ins/RegExp/CharacterClassEscapes/character-class-non-digit-class-escape-flags-u.js
-test262/built-ins/RegExp/CharacterClassEscapes/character-class-whitespace-class-escape-flags-u.js
+test262/built-ins/RegExp/CharacterClassEscapes/character-class-non-whitespace-class-escape-flags-u.js
 test262/built-ins/RegExp/CharacterClassEscapes/character-class-non-whitespace-class-escape-plus-quantifier-flags-u.js
-test262/built-ins/RegExp/CharacterClassEscapes/character-class-digit-class-escape-flags-u.js
+test262/built-ins/RegExp/CharacterClassEscapes/character-class-non-word-class-escape-flags-u.js
 test262/built-ins/RegExp/CharacterClassEscapes/character-class-non-word-class-escape-plus-quantifier-flags-u.js
-test262/built-ins/RegExp/CharacterClassEscapes/character-class-digit-class-escape-plus-quantifier-flags-u.js
+test262/built-ins/RegExp/CharacterClassEscapes/character-class-whitespace-class-escape-flags-u.js
+test262/built-ins/RegExp/CharacterClassEscapes/character-class-whitespace-class-escape-plus-quantifier-flags-u.js
+test262/built-ins/RegExp/CharacterClassEscapes/character-class-word-class-escape-flags-u.js
 test262/built-ins/RegExp/CharacterClassEscapes/character-class-word-class-escape-plus-quantifier-flags-u.js
-test262/built-ins/RegExp/CharacterClassEscapes/character-class-whitespace-class-escape-plus-quantifier-flags-u.js
-test262/built-ins/RegExp/CharacterClassEscapes/character-class-non-whitespace-class-escape-flags-u.js
-test262/built-ins/RegExp/CharacterClassEscapes/character-class-non-word-class-escape-flags-u.js
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1293575.js
@@ -0,0 +1,9 @@
+
+function f(y) {
+    y = 123456;
+    for (var x = 0; x < 9; ++x) {
+        z = arguments.callee.arguments;
+        assertEq(z[0], Math);
+    }
+}
+f(Math);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/cacheir/bug1483183.js
@@ -0,0 +1,8 @@
+if (!('stackTest' in this))
+    quit();
+
+stackTest(new Function(`
+newGlobal({
+  sameZoneAs: []
+}).frame;
+`));
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -791,34 +791,42 @@ InitFromBailout(JSContext* cx, size_t fr
 
         size_t thisvOffset = builder.framePushed() + JitFrameLayout::offsetOfThis();
         builder.valuePointerAtStackOffset(thisvOffset).set(thisv);
 
         MOZ_ASSERT(iter.numAllocations() >= CountArgSlots(script, fun));
         JitSpew(JitSpew_BaselineBailouts, "      frame slots %u, nargs %zu, nfixed %zu",
                 iter.numAllocations(), fun->nargs(), script->nfixed());
 
-        if (frameNo == 0) {
-            // This is the first frame. Store the formals in a Vector until we
-            // are done. Due to UCE and phi elimination, we could store an
-            // UndefinedValue() here for formals we think are unused, but
-            // locals may still reference the original argument slot
+        bool argsObjAliasesFormals = script->argsObjAliasesFormals();
+        if (frameNo == 0 && !argsObjAliasesFormals) {
+            // This is the first (outermost) frame and we don't have an
+            // arguments object aliasing the formals. Store the formals in a
+            // Vector until we are done. Due to UCE and phi elimination, we
+            // could store an UndefinedValue() here for formals we think are
+            // unused, but locals may still reference the original argument slot
             // (MParameter/LArgument) and expect the original Value.
             MOZ_ASSERT(startFrameFormals.empty());
             if (!startFrameFormals.resize(fun->nargs()))
                 return false;
         }
 
         for (uint32_t i = 0; i < fun->nargs(); i++) {
             Value arg = iter.read();
             JitSpew(JitSpew_BaselineBailouts, "      arg %d = %016" PRIx64,
                         (int) i, *((uint64_t*) &arg));
             if (frameNo > 0) {
                 size_t argOffset = builder.framePushed() + JitFrameLayout::offsetOfActualArg(i);
                 builder.valuePointerAtStackOffset(argOffset).set(arg);
+            } else if (argsObjAliasesFormals) {
+                // When the arguments object aliases the formal arguments, then
+                // JSOP_SETARG mutates the argument object. In such cases, the
+                // list of arguments reported by the snapshot are only aliases
+                // of argument object slots which are optimized to only store
+                // differences compared to arguments which are on the stack.
             } else {
                 startFrameFormals[i].set(arg);
             }
         }
     }
 
     for (uint32_t i = 0; i < script->nfixed(); i++) {
         Value slot = iter.read();
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -1136,18 +1136,20 @@ GetPropIRGenerator::tryAttachCrossCompar
     // If we allowed different zones we would have to wrap strings.
     if (unwrapped->compartment()->zone() != cx_->compartment()->zone())
         return false;
 
     // Take the unwrapped object's global, and wrap in a
     // this-compartment wrapper. This is what will be stored in the IC
     // keep the compartment alive.
     RootedObject wrappedTargetGlobal(cx_, &unwrapped->nonCCWGlobal());
-    if (!cx_->compartment()->wrap(cx_, &wrappedTargetGlobal))
-        return false;
+    if (!cx_->compartment()->wrap(cx_, &wrappedTargetGlobal)) {
+        cx_->clearPendingException();
+        return false;
+    }
 
     bool isWindowProxy = false;
     RootedShape shape(cx_);
     RootedNativeObject holder(cx_);
 
     // Enter realm of target since some checks have side-effects
     // such as de-lazifying type info.
     {
@@ -4024,18 +4026,16 @@ SetPropIRGenerator::tryAttachWindowProxy
 
     trackAttached("WindowProxySlot");
     return true;
 }
 
 bool
 SetPropIRGenerator::tryAttachAddSlotStub(HandleObjectGroup oldGroup, HandleShape oldShape)
 {
-    AutoAssertNoPendingException aanpe(cx_);
-
     ValOperandId objValId(writer.setInputOperandId(0));
     ValOperandId rhsValId;
     if (cacheKind_ == CacheKind::SetProp) {
         rhsValId = ValOperandId(writer.setInputOperandId(1));
     } else {
         MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
         MOZ_ASSERT(setElemKeyValueId().id() == 1);
         writer.setInputOperandId(1);
@@ -4463,17 +4463,16 @@ CallIRGenerator::tryAttachStringSplit()
     if (args_[0].toString()->isAtom() && args_[1].toString()->isAtom())
         return false;
 
     // Get the object group to use for this location.
     RootedObjectGroup group(cx_, ObjectGroupRealm::getStringSplitStringGroup(cx_));
     if (!group)
         return false;
 
-    AutoAssertNoPendingException aanpe(cx_);
     Int32OperandId argcId(writer.setInputOperandId(0));
 
     // Ensure argc == 1.
     writer.guardSpecificInt32Immediate(argcId, 2);
 
     // 2 arguments.  Stack-layout here is (bottom to top):
     //
     //  3: Callee
@@ -4539,17 +4538,16 @@ CallIRGenerator::tryAttachArrayPush()
 
     MOZ_ASSERT(!thisarray->getElementsHeader()->isFrozen(),
                "Extensible arrays should not have frozen elements");
     MOZ_ASSERT(thisarray->lengthIsWritable());
 
     // After this point, we can generate code fine.
 
     // Generate code.
-    AutoAssertNoPendingException aanpe(cx_);
     Int32OperandId argcId(writer.setInputOperandId(0));
 
     // Ensure argc == 1.
     writer.guardSpecificInt32Immediate(argcId, 1);
 
     // 1 argument only.  Stack-layout here is (bottom to top):
     //
     //  2: Callee
@@ -4618,17 +4616,16 @@ CallIRGenerator::tryAttachArrayJoin()
     // And the array is packed.
     if (thisarray->getDenseInitializedLength() != thisarray->length())
         return false;
 
     // We don't need to worry about indexed properties because we can perform
     // hole check manually.
 
     // Generate code.
-    AutoAssertNoPendingException aanpe(cx_);
     Int32OperandId argcId(writer.setInputOperandId(0));
 
     // if 0 arguments:
     //  1: Callee
     //  0: ThisValue <-- Top of stack.
     //
     // if 1 argument:
     //  2: Callee
@@ -4667,16 +4664,18 @@ CallIRGenerator::tryAttachArrayJoin()
 
     trackAttached("ArrayJoin");
     return true;
 }
 
 bool
 CallIRGenerator::tryAttachStub()
 {
+    AutoAssertNoPendingException aanpe(cx_);
+
     // Only optimize on JSOP_CALL or JSOP_CALL_IGNORES_RV.  No fancy business for now.
     if ((op_ != JSOP_CALL) && (op_ != JSOP_CALL_IGNORES_RV))
         return false;
 
     // Only optimize when the mode is Specialized.
     if (mode_ != ICState::Mode::Specialized)
         return false;
 
@@ -5225,21 +5224,23 @@ GetIntrinsicIRGenerator::trackAttached(c
         sp.valueProperty("val", val_);
     }
 #endif
 }
 
 bool
 GetIntrinsicIRGenerator::tryAttachStub()
 {
+    AutoAssertNoPendingException aanpe(cx_);
     writer.loadValueResult(val_);
     writer.returnFromIC();
     trackAttached("GetIntrinsic");
     return true;
 }
+
 UnaryArithIRGenerator::UnaryArithIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, ICState::Mode mode,
                                              JSOp op, HandleValue val, HandleValue res)
   : IRGenerator(cx, script, pc, CacheKind::UnaryArith, mode),
     op_(op),
     val_(val),
     res_(res)
 { }
 
@@ -5252,16 +5253,17 @@ UnaryArithIRGenerator::trackAttached(con
         sp.valueProperty("res", res_);
     }
 #endif
 }
 
 bool
 UnaryArithIRGenerator::tryAttachStub()
 {
+    AutoAssertNoPendingException aanpe(cx_);
     if (tryAttachInt32())
         return true;
     if (tryAttachNumber())
         return true;
 
     trackAttached(IRGenerator::NotAttached);
     return false;
 }
@@ -5338,17 +5340,17 @@ BinaryArithIRGenerator::trackAttached(co
         sp.valueProperty("lhs", lhs_);
     }
 #endif
 }
 
 bool
 BinaryArithIRGenerator::tryAttachStub()
 {
-
+    AutoAssertNoPendingException aanpe(cx_);
     // Attempt common case first
     if (tryAttachInt32())
         return true;
     if (tryAttachBooleanWithInt32())
         return true;
     if (tryAttachDoubleWithInt32())
         return true;
 
@@ -5686,16 +5688,17 @@ NewObjectIRGenerator::trackAttached(cons
         sp.opcodeProperty("op", op_);
     }
 #endif
 }
 
 bool
 NewObjectIRGenerator::tryAttachStub()
 {
+    AutoAssertNoPendingException aanpe(cx_);
     if (!templateObject_->is<UnboxedPlainObject>() &&
         templateObject_->as<PlainObject>().hasDynamicSlots())
     {
         trackAttached(IRGenerator::NotAttached);
         return false;
     }
 
     // Don't attach stub if group is pretenured, as the stub
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -1,19 +1,31 @@
 # Manifest entries for imported test suites whose individual test cases
 # we don't want to change.
 
 # Skip the folder with tests for the scripts
 skip include test/jstests.list
 
 skip script non262/String/normalize-generateddata-input.js # input data for other test
 
-# Times out on arm and cgc builds.
+# Timeouts on arm and cgc builds.
 slow script test262/built-ins/decodeURI/S15.1.3.1_A2.5_T1.js
 slow script test262/built-ins/decodeURIComponent/S15.1.3.2_A2.5_T1.js
+slow script test262/built-ins/RegExp/CharacterClassEscapes/character-class-digit-class-escape-flags-u.js
+slow script test262/built-ins/RegExp/CharacterClassEscapes/character-class-digit-class-escape-plus-quantifier-flags-u.js
+slow script test262/built-ins/RegExp/CharacterClassEscapes/character-class-non-digit-class-escape-flags-u.js
+slow script test262/built-ins/RegExp/CharacterClassEscapes/character-class-non-digit-class-escape-plus-quantifier-flags-u.js
+slow script test262/built-ins/RegExp/CharacterClassEscapes/character-class-non-whitespace-class-escape-flags-u.js
+slow script test262/built-ins/RegExp/CharacterClassEscapes/character-class-non-whitespace-class-escape-plus-quantifier-flags-u.js
+slow script test262/built-ins/RegExp/CharacterClassEscapes/character-class-non-word-class-escape-flags-u.js
+slow script test262/built-ins/RegExp/CharacterClassEscapes/character-class-non-word-class-escape-plus-quantifier-flags-u.js
+slow script test262/built-ins/RegExp/CharacterClassEscapes/character-class-whitespace-class-escape-flags-u.js
+slow script test262/built-ins/RegExp/CharacterClassEscapes/character-class-whitespace-class-escape-plus-quantifier-flags-u.js
+slow script test262/built-ins/RegExp/CharacterClassEscapes/character-class-word-class-escape-flags-u.js
+slow script test262/built-ins/RegExp/CharacterClassEscapes/character-class-word-class-escape-plus-quantifier-flags-u.js
 
 
 #################################################################
 # Tests disabled due to intentional alternative implementations #
 #################################################################
 
 # Legacy "caller" and "arguments" implemented as accessor properties on Function.prototype.
 skip script test262/built-ins/Function/prototype/restricted-property-arguments.js
@@ -373,33 +385,44 @@ skip script test262/language/expressions
 skip script test262/language/expressions/prefix-increment/target-cover-newtarget.js
 skip script test262/language/expressions/prefix-increment/non-simple.js
 skip script test262/language/expressions/prefix-decrement/target-cover-yieldexpr.js
 skip script test262/language/expressions/prefix-decrement/target-newtarget.js
 skip script test262/language/expressions/prefix-decrement/target-cover-newtarget.js
 skip script test262/language/expressions/prefix-decrement/non-simple.js
 skip script test262/language/asi/S7.9_A5.7_T1.js
 
-# Dependent on evalInWorker, setSharedArrayBuffer, and
-# getSharedArrayBuffer, plus the test cases can't actually run in the
-# browser even if that were fixed, https://bugzil.la/1349863
-skip-if(!xulRuntime.shell) script test262/built-ins/Atomics/wait/negative-timeout.js
-skip-if(!xulRuntime.shell) script test262/built-ins/Atomics/wait/was-woken.js
-skip-if(!xulRuntime.shell) script test262/built-ins/Atomics/wait/did-timeout.js
-skip-if(!xulRuntime.shell) script test262/built-ins/Atomics/wait/good-views.js
-skip-if(!xulRuntime.shell) script test262/built-ins/Atomics/wait/no-spurious-wakeup.js
-skip-if(!xulRuntime.shell) script test262/built-ins/Atomics/wait/nan-timeout.js
-skip-if(!xulRuntime.shell) script test262/built-ins/Atomics/wake/wake-all.js
-skip-if(!xulRuntime.shell) script test262/built-ins/Atomics/wake/wake-zero.js
-skip-if(!xulRuntime.shell) script test262/built-ins/Atomics/wake/wake-negative.js
-skip-if(!xulRuntime.shell) script test262/built-ins/Atomics/wake/wake-nan.js
-skip-if(!xulRuntime.shell) script test262/built-ins/Atomics/wake/wake-two.js
-skip-if(!xulRuntime.shell) script test262/built-ins/Atomics/wake/wake-in-order.js
-skip-if(!xulRuntime.shell) script test262/built-ins/Atomics/wake/wake-one.js
-skip-if(!xulRuntime.shell) script test262/built-ins/Atomics/wake/wake-all-on-loc.js
+# Dependent on evalInWorker, setSharedArrayBuffer, and getSharedArrayBuffer; plus:
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1349863 - Enable test262 agent tests in browser
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1470490 - Rename Atomics.wake to Atomics.notify
+skip include test262/built-ins/Atomics/notify/jstests.list
+skip script test262/built-ins/Atomics/wait/false-for-timeout-agent.js
+skip script test262/built-ins/Atomics/wait/nan-for-timeout.js
+skip script test262/built-ins/Atomics/wait/negative-timeout-agent.js
+skip script test262/built-ins/Atomics/wait/no-spurious-wakeup-no-operation.js
+skip script test262/built-ins/Atomics/wait/no-spurious-wakeup-on-add.js
+skip script test262/built-ins/Atomics/wait/no-spurious-wakeup-on-and.js
+skip script test262/built-ins/Atomics/wait/no-spurious-wakeup-on-compareExchange.js
+skip script test262/built-ins/Atomics/wait/no-spurious-wakeup-on-exchange.js
+skip script test262/built-ins/Atomics/wait/no-spurious-wakeup-on-or.js
+skip script test262/built-ins/Atomics/wait/no-spurious-wakeup-on-store.js
+skip script test262/built-ins/Atomics/wait/no-spurious-wakeup-on-sub.js
+skip script test262/built-ins/Atomics/wait/no-spurious-wakeup-on-xor.js
+skip script test262/built-ins/Atomics/wait/null-for-timeout-agent.js
+skip script test262/built-ins/Atomics/wait/object-for-timeout-agent.js
+skip script test262/built-ins/Atomics/wait/poisoned-object-for-timeout-throws-agent.js
+skip script test262/built-ins/Atomics/wait/symbol-for-index-throws-agent.js
+skip script test262/built-ins/Atomics/wait/symbol-for-timeout-throws-agent.js
+skip script test262/built-ins/Atomics/wait/symbol-for-value-throws-agent.js
+skip script test262/built-ins/Atomics/wait/true-for-timeout-agent.js
+skip script test262/built-ins/Atomics/wait/undefined-for-timeout.js
+skip script test262/built-ins/Atomics/wait/undefined-index-defaults-to-zero.js
+skip script test262/built-ins/Atomics/wait/wait-index-value-not-equal.js
+skip script test262/built-ins/Atomics/wait/waiterlist-block-indexedposition-wake.js
+skip script test262/built-ins/Atomics/wait/waiterlist-order-of-operations-is-fifo.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1346081
 skip script test262/intl402/NumberFormat/prototype/format/format-fraction-digits.js
 skip script test262/intl402/NumberFormat/prototype/format/format-significant-digits.js
 
 # Hoisted block-level function named "arguments" not initialized with undefined per B.3.3.1
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1339123
 skip script test262/annexB/language/function-code/block-decl-func-skip-arguments.js
@@ -431,23 +454,69 @@ skip script test262/built-ins/Function/p
 skip script test262/built-ins/Function/prototype/toString/well-known-intrinsic-object-functions.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1462745
 skip script test262/annexB/language/function-code/block-decl-nested-blocks-with-fun-decl.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1406171
 skip script test262/built-ins/Reflect/ownKeys/return-on-corresponding-order-large-index.js
 
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1469019
+skip script test262/built-ins/Object/fromEntries/to-property-key.js
+skip script test262/built-ins/Object/fromEntries/iterator-closed-for-string-entry.js.js
+skip script test262/built-ins/Object/fromEntries/supports-symbols.js
+skip script test262/built-ins/Object/fromEntries/evaluation-order.js
+skip script test262/built-ins/Object/fromEntries/length.js
+skip script test262/built-ins/Object/fromEntries/uses-keys-not-iterator.js
+skip script test262/built-ins/Object/fromEntries/iterator-not-closed-for-throwing-next.js
+skip script test262/built-ins/Object/fromEntries/uses-define-semantics.js
+skip script test262/built-ins/Object/fromEntries/key-order.js
+skip script test262/built-ins/Object/fromEntries/iterator-closed-for-null-entry.js.js
+skip script test262/built-ins/Object/fromEntries/simple-properties.js
+skip script test262/built-ins/Object/fromEntries/empty-iterable.js
+skip script test262/built-ins/Object/fromEntries/iterator-closed-for-throwing-entry-accessor.js
+skip script test262/built-ins/Object/fromEntries/string-entry-object-succeeds.js
+skip script test262/built-ins/Object/fromEntries/iterator-not-closed-for-throwing-done-accessor.js
+skip script test262/built-ins/Object/fromEntries/prototype.js
+skip script test262/built-ins/Object/fromEntries/iterator-closed-for-throwing-entry-key-tostring.js
+skip script test262/built-ins/Object/fromEntries/name.js
+
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1291407
+skip script test262/intl402/ListFormat/prototype/toStringTag/toString.js
+skip script test262/intl402/ListFormat/prototype/toStringTag/toStringTag.js
+
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1473228
+skip script test262/intl402/RelativeTimeFormat/prototype/toStringTag/toString.js
 skip script test262/intl402/RelativeTimeFormat/prototype/toStringTag/toStringTag.js
 
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1473228
+skip script test262/intl402/Segmenter/prototype/toStringTag/toString.js
+skip script test262/intl402/Segmenter/prototype/toStringTag/toStringTag.js
+
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1473229
-skip script test262/intl402/RelativeTimeFormat/prototype/formatToParts/length.js
-skip script test262/intl402/RelativeTimeFormat/prototype/formatToParts/name.js
-skip script test262/intl402/RelativeTimeFormat/prototype/formatToParts/prop-desc.js
+skip include test262/intl402/RelativeTimeFormat/prototype/formatToParts/jstests.list
+
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1483374
+skip script test262/intl402/NumberFormat/prototype/format/format-negative-numbers.js
+
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1483545
+skip script test262/intl402/RelativeTimeFormat/prototype/format/en-us-numeric-always.js
+skip script test262/intl402/RelativeTimeFormat/prototype/format/en-us-numeric-auto.js
+skip script test262/intl402/RelativeTimeFormat/prototype/format/en-us-style-short.js
+skip script test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-long.js
+skip script test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-narrow.js
+
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1473230
+skip script test262/intl402/RelativeTimeFormat/prototype/format/unit-plural.js
+
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1483547
+skip script test262/intl402/RelativeTimeFormat/prototype/format/pl-pl-style-short.js
+
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1483548
+skip script test262/intl402/RelativeTimeFormat/prototype/format/value-non-finite.js
 
 
 ###########################################################
 # Tests disabled due to issues in test262 importer script #
 ###########################################################
 
 # test262 importer merges all includes in a per directory shell.js file, breaking this harness test case.
 skip script test262/harness/detachArrayBuffer.js
--- a/js/src/tests/test262/GIT-INFO
+++ b/js/src/tests/test262/GIT-INFO
@@ -1,7 +1,5 @@
-commit e9a5a7f918c0f9c4a9f1224d25a6d55147953225
-Author: test262-automation <40212386+test262-automation@users.noreply.github.com>
-Date:   Tue Jul 3 15:59:58 2018 -0400
+commit 60b9467630a7b4899058e3ad74eb88c3ecb08a40
+Author: Kevin Gibbons <bakkot@gmail.com>
+Date:   Fri Aug 10 23:16:46 2018 -0700
 
-    [javascriptcore-test262-automation] changes from git@github.com:WebKit/webkit.git at sha 949e26452cfa153a7f4afe593da97e2fe9e1b706 on Tue Jul 03 2018 14:35:15 GMT-0400 (Eastern Daylight Time) (#1620)
-    
-    * [javascriptcore-test262-automation] changes from git@github.com:WebKit/webkit.git at sha 949e26452cfa153a7f4afe593da97e2fe9e1b706 on Tue Jul 03 2018 14:35:15 GMT-0400 (Eastern Daylight Time)
+    Two more simple tests
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/bad-range.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')||!this.hasOwnProperty('SharedArrayBuffer')) -- Atomics,SharedArrayBuffer is not enabled unconditionally
+// Copyright (C) 2017 Mozilla Corporation.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Test range checking of Atomics.notify on arrays that allow atomic operations
+info: |
+  Atomics.notify( typedArray, index, count )
+
+  1. Let buffer be ? ValidateSharedIntegerTypedArray(typedArray, true).
+  ..
+
+includes: [testAtomics.js]
+features: [ArrayBuffer, Atomics, DataView, SharedArrayBuffer, Symbol, TypedArray]
+---*/
+
+const i32a = new Int32Array(
+  new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 4)
+);
+
+testWithAtomicsOutOfBoundsIndices(function(IdxGen) {
+  assert.throws(RangeError, function() {
+    Atomics.notify(i32a, IdxGen(i32a), 0);
+  }, '`Atomics.notify(i32a, IdxGen(i32a), 0)` throws RangeError');
+});
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/bigint/bad-range.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')||!this.hasOwnProperty('BigInt')||!this.hasOwnProperty('SharedArrayBuffer')) -- Atomics,BigInt,SharedArrayBuffer is not enabled unconditionally
+// Copyright (C) 2018 Rick Waldron. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Test range checking of Atomics.notify on arrays that allow atomic operations
+info: |
+  Atomics.notify( typedArray, index, count )
+
+  1. Let buffer be ? ValidateSharedIntegerTypedArray(typedArray, true).
+  ..
+
+includes: [testAtomics.js]
+features: [ArrayBuffer, Atomics, BigInt, DataView, SharedArrayBuffer, Symbol, TypedArray]
+---*/
+
+const i64a = new BigInt64Array(
+  new SharedArrayBuffer(BigInt64Array.BYTES_PER_ELEMENT * 8)
+);
+
+testWithAtomicsOutOfBoundsIndices(function(IdxGen) {
+  assert.throws(RangeError, function() {
+    Atomics.notify(i64a, IdxGen(i64a), 0);
+  }, '`Atomics.notify(i64a, IdxGen(i64a), 0)` throws RangeError');
+});
+
+reportCompare(0, 0);
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/bigint/non-bigint64-typedarray-throws.js
@@ -0,0 +1,37 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')||!this.hasOwnProperty('BigInt')) -- Atomics,BigInt is not enabled unconditionally
+// Copyright (C) 2018 Amal Hussein. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Throws a TypeError if typedArray arg is not an BigInt64Array
+info: |
+  Atomics.notify( typedArray, index, count )
+
+  1.Let buffer be ? ValidateSharedIntegerTypedArray(typedArray, true).
+    ...
+      5.If onlyInt32 is true, then
+        If typeName is not "BigInt64Array", throw a TypeError exception.
+features: [Atomics, BigInt, TypedArray]
+---*/
+
+const i64a = new BigUint64Array(
+  new SharedArrayBuffer(BigUint64Array.BYTES_PER_ELEMENT * 8)
+);
+
+const poisoned = {
+  valueOf: function() {
+    throw new Test262Error('should not evaluate this code');
+  }
+};
+
+assert.throws(TypeError, function() {
+  Atomics.wait(i64a, 0, 0);
+}, '`Atomics.wait(i64a, 0, 0)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  Atomics.wait(i64a, poisoned, poisoned);
+}, '`Atomics.wait(i64a, poisoned, poisoned)` throws TypeError');
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/bigint/non-shared-bufferdata-throws.js
@@ -0,0 +1,36 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')||!this.hasOwnProperty('BigInt')) -- Atomics,BigInt is not enabled unconditionally
+// Copyright (C) 2018 Amal Hussein. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-atomics.notify
+description: >
+  Throws a TypeError if typedArray.buffer is not a SharedArrayBuffer
+info: |
+  Atomics.notify( typedArray, index, count )
+
+  1.Let buffer be ? ValidateSharedIntegerTypedArray(typedArray, true).
+    ...
+      9.If IsSharedArrayBuffer(buffer) is false, throw a TypeError exception.
+        ...
+          4.If bufferData is a Data Block, return false.
+features: [ArrayBuffer, Atomics, BigInt, TypedArray]
+---*/
+const i64a = new BigInt64Array(
+  new ArrayBuffer(BigInt64Array.BYTES_PER_ELEMENT * 8)
+);
+
+const poisoned = {
+  valueOf: function() {
+    throw new Test262Error('should not evaluate this code');
+  }
+};
+
+assert.throws(TypeError, function() {
+  Atomics.notify(i64a, 0, 0);
+}, '`Atomics.notify(i64a, 0, 0)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  Atomics.notify(i64a, poisoned, poisoned);
+}, '`Atomics.notify(i64a, poisoned, poisoned)` throws TypeError');
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/bigint/notify-all-on-loc.js
@@ -0,0 +1,92 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')||!this.hasOwnProperty('BigInt')||!this.hasOwnProperty('SharedArrayBuffer')) -- Atomics,BigInt,SharedArrayBuffer is not enabled unconditionally
+// Copyright (C) 2017 Mozilla Corporation.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Test that Atomics.notify notifies all waiters on a location, but does not
+  notify waiters on other locations.
+includes: [atomicsHelper.js]
+features: [Atomics, BigInt, SharedArrayBuffer, TypedArray]
+---*/
+
+const WAIT_INDEX = 0;             // Waiters on this will be woken
+const WAIT_FAKE = 1;              // Waiters on this will not be woken
+const RUNNING = 2;                // Accounting of live agents
+const NOTIFY_INDEX = 3;             // Accounting for too early timeouts
+const NUMAGENT = 3;
+const TIMEOUT_AGENT_MESSAGES = 2; // Number of messages for the timeout agent
+const BUFFER_SIZE = 4;
+
+// Long timeout to ensure the agent doesn't timeout before the main agent calls
+// `Atomics.notify`.
+const TIMEOUT = $262.agent.timeouts.long;
+
+for (var i = 0; i < NUMAGENT; i++) {
+  $262.agent.start(`
+    $262.agent.receiveBroadcast(function(sab) {
+      const i64a = new BigInt64Array(sab);
+      Atomics.add(i64a, ${RUNNING}, 1n);
+
+      $262.agent.report("A " + Atomics.wait(i64a, ${WAIT_INDEX}, 0n));
+      $262.agent.leaving();
+    });
+  `);
+}
+
+$262.agent.start(`
+  $262.agent.receiveBroadcast(function(sab) {
+    const i64a = new BigInt64Array(sab);
+    Atomics.add(i64a, ${RUNNING}, 1n);
+
+    // This will always time out.
+    $262.agent.report("B " + Atomics.wait(i64a, ${WAIT_FAKE}, 0n, ${TIMEOUT}));
+
+    // If this value is not 1n, then the agent timeout before the main agent
+    // called Atomics.notify.
+    const result = Atomics.load(i64a, ${NOTIFY_INDEX}) === 1n
+                   ? "timeout after Atomics.notify"
+                   : "timeout before Atomics.notify";
+    $262.agent.report("W " + result);
+
+    $262.agent.leaving();
+  });
+`);
+
+const i64a = new BigInt64Array(
+  new SharedArrayBuffer(BigInt64Array.BYTES_PER_ELEMENT * BUFFER_SIZE)
+);
+
+$262.agent.broadcast(i64a.buffer);
+
+// Wait for agents to be running.
+$262.agent.waitUntil(i64a, RUNNING, BigInt(NUMAGENT + 1));
+
+// Try to yield control to ensure the agent actually started to wait. If we
+// don't, we risk sending the notification before agents are sleeping, and we hang.
+$262.agent.tryYield();
+
+// Notify all waiting on WAIT_INDEX, should be 3 always, they won't time out.
+assert.sameValue(
+  Atomics.notify(i64a, WAIT_INDEX),
+  NUMAGENT,
+  'Atomics.notify(i64a, WAIT_INDEX) returns the value of `NUMAGENT`'
+);
+
+Atomics.store(i64a, NOTIFY_INDEX, 1n);
+
+const reports = [];
+for (var i = 0; i < NUMAGENT  + TIMEOUT_AGENT_MESSAGES; i++) {
+  reports.push($262.agent.getReport());
+}
+reports.sort();
+
+for (var i = 0; i < NUMAGENT; i++) {
+  assert.sameValue(reports[i], 'A ok', 'The value of reports[i] is "A ok"');
+}
+assert.sameValue(reports[NUMAGENT], 'B timed-out', 'The value of reports[NUMAGENT] is "B timed-out"');
+assert.sameValue(reports[NUMAGENT + 1], "W timeout after Atomics.notify",
+                 'The value of reports[NUMAGENT + 1] is "W timeout after Atomics.notify"');
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/bigint/null-bufferdata-throws.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')||!this.hasOwnProperty('BigInt')) -- Atomics,BigInt is not enabled unconditionally
+// Copyright (C) 2018 Amal Hussein. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-atomics.notify
+description: >
+  A null value for bufferData throws a TypeError
+info: |
+  Atomics.notify( typedArray, index, count )
+
+  1.Let buffer be ? ValidateSharedIntegerTypedArray(typedArray, true).
+    ...
+      9.If IsSharedArrayBuffer(buffer) is false, throw a TypeError exception.
+        ...
+          3.If bufferData is null, return false.
+includes: [detachArrayBuffer.js]
+features: [ArrayBuffer, Atomics, BigInt, TypedArray]
+---*/
+
+const i64a = new BigInt64Array(
+  new ArrayBuffer(BigInt64Array.BYTES_PER_ELEMENT * 8)
+);
+const poisoned = {
+  valueOf: function() {
+    throw new Test262Error('should not evaluate this code');
+  }
+};
+
+try {
+  $DETACHBUFFER(i64a.buffer); // Detaching a non-shared ArrayBuffer sets the [[ArrayBufferData]] value to null
+} catch (error) {
+  $ERROR(`An unexpected error occurred when detaching ArrayBuffer: ${error.message}`);
+}
+
+assert.throws(TypeError, function() {
+  Atomics.notify(i64a, poisoned, poisoned);
+}, '`Atomics.notify(i64a, poisoned, poisoned)` throws TypeError');
+
+reportCompare(0, 0);
new file mode 100644
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/count-boundary-cases.js
@@ -0,0 +1,64 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')||!this.hasOwnProperty('SharedArrayBuffer')) -- Atomics,SharedArrayBuffer is not enabled unconditionally
+// Copyright (C) 2017 Mozilla Corporation.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Allowed boundary cases for 'count' argument to Atomics.notify
+info: |
+  Atomics.notify( typedArray, index, count )
+
+  ...
+  3. If count is undefined, let c be +∞.
+  4. Else,
+    a. Let intCount be ? ToInteger(count).
+  ...
+
+  ToInteger ( argument )
+
+  1. Let number be ? ToNumber(argument).
+  2. If number is NaN, return +0.
+  3. If number is +0, -0, +∞, or -∞, return number.
+  4. Return the number value that is the same sign as number
+      and whose magnitude is floor(abs(number)).
+
+features: [Atomics, SharedArrayBuffer, TypedArray]
+---*/
+
+const i32a = new Int32Array(
+  new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 4)
+);
+
+assert.sameValue(
+  Atomics.notify(i32a, 0, -3),
+  0,
+  'Atomics.notify(i32a, 0, -3) returns 0'
+);
+assert.sameValue(
+  Atomics.notify(i32a, 0, Number.POSITIVE_INFINITY),
+  0,
+  'Atomics.notify(i32a, 0, Number.POSITIVE_INFINITY) returns 0'
+);
+assert.sameValue(
+  Atomics.notify(i32a, 0, undefined),
+  0,
+  'Atomics.notify(i32a, 0, undefined) returns 0'
+);
+assert.sameValue(
+  Atomics.notify(i32a, 0, '33'),
+  0,
+  'Atomics.notify(i32a, 0, \'33\') returns 0'
+);
+assert.sameValue(
+  Atomics.notify(i32a, 0, { valueOf: 8 }),
+  0,
+  'Atomics.notify(i32a, 0, {valueOf: 8}) returns 0'
+);
+assert.sameValue(
+  Atomics.notify(i32a, 0),
+  0,
+  'Atomics.notify(i32a, 0) returns 0'
+);
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/count-defaults-to-infinity-missing.js
@@ -0,0 +1,69 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')||!this.hasOwnProperty('SharedArrayBuffer')) -- Atomics,SharedArrayBuffer is not enabled unconditionally
+// Copyright (C) 2018 Amal Hussein.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Default to +Infinity when missing 'count' argument to Atomics.notify
+info: |
+  Atomics.notify( typedArray, index, count )
+
+  ...
+  3. If count is undefined, let c be +∞.
+  ...
+
+includes: [atomicsHelper.js]
+features: [Atomics, SharedArrayBuffer, TypedArray]
+---*/
+
+const RUNNING = 0; // Index to notify agent has started.
+const WAIT_INDEX = 1; // Index all agents are waiting on.
+const BUFFER_SIZE = 2;
+
+const NUMAGENT = 4; // Total number of agents started
+
+for (var i = 0; i < NUMAGENT; i++) {
+  $262.agent.start(`
+    $262.agent.receiveBroadcast(function(sab) {
+      const i32a = new Int32Array(sab);
+      Atomics.add(i32a, ${RUNNING}, 1);
+
+      // Wait until restarted by main thread.
+      var status = Atomics.wait(i32a, ${WAIT_INDEX}, 0);
+
+      // Report wait status and then exit the agent.
+      var name = String.fromCharCode(0x41 + ${i}); // "A", "B", "C", or "D"
+      $262.agent.report(name + " " + status);
+      $262.agent.leaving();
+    });
+  `);
+}
+
+const i32a = new Int32Array(
+  new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * BUFFER_SIZE)
+);
+
+$262.agent.broadcast(i32a.buffer);
+$262.agent.waitUntil(i32a, RUNNING, NUMAGENT);
+
+// An agent may have been interrupted between reporting its initial report
+// and the `Atomics.wait` call. Try to yield control to ensure the agent
+// actually started to wait.
+$262.agent.tryYield();
+
+assert.sameValue(Atomics.notify(i32a, WAIT_INDEX /*, count missing */), NUMAGENT,
+                 'Atomics.notify(i32a, WAIT_INDEX /*, count missing */) returns the value of `NUMAGENT`');
+
+const reports = [];
+for (var i = 0; i < NUMAGENT; i++) {
+  reports.push($262.agent.getReport());
+}
+reports.sort();
+
+assert.sameValue(reports[0], 'A ok', 'The value of reports[0] is "A ok"');
+assert.sameValue(reports[1], 'B ok', 'The value of reports[1] is "B ok"');
+assert.sameValue(reports[2], 'C ok', 'The value of reports[2] is "C ok"');
+assert.sameValue(reports[3], 'D ok', 'The value of reports[3] is "D ok"');
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/count-defaults-to-infinity-undefined.js
@@ -0,0 +1,67 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')||!this.hasOwnProperty('SharedArrayBuffer')) -- Atomics,SharedArrayBuffer is not enabled unconditionally
+// Copyright (C) 2018 Amal Hussein.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Undefined count arg should result in an infinite count
+info: |
+  Atomics.notify( typedArray, index, count )
+
+  3.If count is undefined, let c be +∞.
+
+includes: [atomicsHelper.js]
+features: [Atomics, SharedArrayBuffer, TypedArray]
+---*/
+
+const RUNNING = 0; // Index to notify agent has started.
+const WAIT_INDEX = 1; // Index all agents are waiting on.
+const BUFFER_SIZE = 2;
+
+const NUMAGENT = 4; // Total number of agents started
+
+for (var i = 0; i < NUMAGENT; i++) {
+  $262.agent.start(`
+    $262.agent.receiveBroadcast(function(sab) {
+      const i32a = new Int32Array(sab);
+      Atomics.add(i32a, ${RUNNING}, 1);
+
+      // Wait until restarted by main thread.
+      var status = Atomics.wait(i32a, ${WAIT_INDEX}, 0);
+
+      // Report wait status and then exit the agent.
+      var name = String.fromCharCode(0x41 + ${i}); // "A", "B", "C", or "D"
+      $262.agent.report(name + " " + status);
+      $262.agent.leaving();
+    });
+  `);
+}
+
+const i32a = new Int32Array(
+  new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * BUFFER_SIZE)
+);
+
+$262.agent.broadcast(i32a.buffer);
+$262.agent.waitUntil(i32a, RUNNING, NUMAGENT);
+
+// An agent may have been interrupted between reporting its initial report
+// and the `Atomics.wait` call. Try to yield control to ensure the agent
+// actually started to wait.
+$262.agent.tryYield();
+
+assert.sameValue(Atomics.notify(i32a, WAIT_INDEX, undefined), NUMAGENT,
+                 'Atomics.notify(i32a, WAIT_INDEX, undefined) returns the value of `NUMAGENT`');
+
+const reports = [];
+for (var i = 0; i < NUMAGENT; i++) {
+  reports.push($262.agent.getReport());
+}
+reports.sort();
+
+assert.sameValue(reports[0], 'A ok', 'The value of reports[0] is "A ok"');
+assert.sameValue(reports[1], 'B ok', 'The value of reports[1] is "B ok"');
+assert.sameValue(reports[2], 'C ok', 'The value of reports[2] is "C ok"');
+assert.sameValue(reports[3], 'D ok', 'The value of reports[3] is "D ok"');
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/count-from-nans.js
@@ -0,0 +1,36 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')||!this.hasOwnProperty('SharedArrayBuffer')) -- Atomics,SharedArrayBuffer is not enabled unconditionally
+// Copyright (C) 2018 Rick Waldron.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  NaNs are converted to 0 for 'count' argument to Atomics.notify
+info: |
+  Atomics.notify( typedArray, index, count )
+
+  ...
+  3. If count is undefined, let c be +∞.
+  4. Else,
+    a. Let intCount be ? ToInteger(count).
+  ...
+
+  ToInteger ( argument )
+
+  ...
+  2. If number is NaN, return +0.
+  ...
+
+includes: [nans.js, atomicsHelper.js]
+features: [Atomics, SharedArrayBuffer, TypedArray]
+---*/
+
+const i32a = new Int32Array(
+  new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 4)
+);
+
+NaNs.forEach(nan => {
+  assert.sameValue(Atomics.notify(i32a, 0, nan), 0, 'Atomics.notify(i32a, 0, nan) returns 0');
+});
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/count-symbol-throws.js
@@ -0,0 +1,29 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')||!this.hasOwnProperty('SharedArrayBuffer')) -- Atomics,SharedArrayBuffer is not enabled unconditionally
+// Copyright (C) 2018 Rick Waldron.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Return abrupt when symbol passed for 'count' argument to Atomics.notify
+info: |
+  Atomics.notify( typedArray, index, count )
+
+  ...
+  3. If count is undefined, let c be +∞.
+  4. Else,
+    a. Let intCount be ? ToInteger(count).
+  ...
+
+features: [Atomics, SharedArrayBuffer, TypedArray]
+---*/
+
+const i32a = new Int32Array(
+  new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 4)
+);
+
+assert.throws(TypeError, function() {
+  Atomics.notify(i32a, 0, Symbol());
+}, '`Atomics.notify(i32a, 0, Symbol())` throws TypeError');
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/count-tointeger-throws-then-wake-throws.js
@@ -0,0 +1,35 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')||!this.hasOwnProperty('SharedArrayBuffer')) -- Atomics,SharedArrayBuffer is not enabled unconditionally
+// Copyright (C) 2018 Rick Waldron.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Return abrupt when ToInteger throws an exception on 'count' argument to Atomics.notify
+info: |
+  Atomics.notify( typedArray, index, count )
+
+  ...
+  3. If count is undefined, let c be +∞.
+  4. Else,
+    a. Let intCount be ? ToInteger(count).
+  ...
+
+features: [Atomics, SharedArrayBuffer, TypedArray]
+---*/
+
+const i32a = new Int32Array(
+  new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 4)
+);
+
+const poisoned = {
+  valueOf: function() {
+    throw new Test262Error('should not evaluate this code');
+  }
+};
+
+assert.throws(Test262Error, function() {
+  Atomics.notify(i32a, 0, poisoned);
+}, '`Atomics.notify(i32a, 0, poisoned)` throws Test262Error');
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/descriptor.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')) -- Atomics is not enabled unconditionally
+// Copyright (C) 2017 Mozilla Corporation. All rights reserved.
+// This code is governed by the license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: Testing descriptor property of Atomics.notify
+includes: [propertyHelper.js]
+features: [Atomics]
+---*/
+
+verifyProperty(Atomics, 'notify', {
+  enumerable: false,
+  writable: true,
+  configurable: true,
+});
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/length.js
@@ -0,0 +1,35 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')) -- Atomics is not enabled unconditionally
+// Copyright (C) 2015 André Bargull. All rights reserved.
+// Copyright (C) 2017 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Atomics.notify.length is 3.
+info: |
+  Atomics.notify ( ia, index, count )
+
+  17 ECMAScript Standard Built-in Objects:
+    Every built-in Function object, including constructors, has a length
+    property whose value is an integer. Unless otherwise specified, this
+    value is equal to the largest number of named arguments shown in the
+    subclause headings for the function description, including optional
+    parameters. However, rest parameters shown using the form “...name”
+    are not included in the default argument count.
+
+    Unless otherwise specified, the length property of a built-in Function
+    object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
+    [[Configurable]]: true }.
+includes: [propertyHelper.js]
+features: [Atomics]
+---*/
+
+verifyProperty(Atomics.notify, 'length', {
+  value: 3,
+  enumerable: false,
+  writable: false,
+  configurable: true,
+});
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/name.js
@@ -0,0 +1,21 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')) -- Atomics is not enabled unconditionally
+// Copyright (C) 2015 André Bargull. All rights reserved.
+// Copyright (C) 2017 Mozilla Corporation. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Atomics.notify.name is "notify".
+includes: [propertyHelper.js]
+features: [Atomics]
+---*/
+
+verifyProperty(Atomics.notify, 'name', {
+  value: 'notify',
+  enumerable: false,
+  writable: false,
+  configurable: true,
+});
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/negative-count.js
@@ -0,0 +1,40 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')||!this.hasOwnProperty('SharedArrayBuffer')) -- Atomics,SharedArrayBuffer is not enabled unconditionally
+// Copyright (C) 2017 Mozilla Corporation.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Test that Atomics.notify notifies zero waiters if the count is negative
+includes: [atomicsHelper.js]
+features: [Atomics, SharedArrayBuffer, TypedArray]
+---*/
+
+const RUNNING = 1;
+const TIMEOUT = $262.agent.timeouts.long;
+
+$262.agent.start(`
+  $262.agent.receiveBroadcast(function(sab) {
+    const i32a = new Int32Array(sab);
+    Atomics.add(i32a, ${RUNNING}, 1);
+
+    $262.agent.report(Atomics.wait(i32a, 0, 0, ${TIMEOUT}));
+    $262.agent.leaving();
+  });
+`);
+
+const i32a = new Int32Array(
+  new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 4)
+);
+
+$262.agent.broadcast(i32a.buffer);
+$262.agent.waitUntil(i32a, RUNNING, 1);
+
+// Try to yield control to ensure the agent actually started to wait.
+$262.agent.tryYield();
+
+assert.sameValue(Atomics.notify(i32a, 0, -1), 0, 'Atomics.notify(i32a, 0, -1) returns 0'); // Don't actually notify it
+
+assert.sameValue($262.agent.getReport(), 'timed-out', '$262.agent.getReport() returns "timed-out"');
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/negative-index-throws.js
@@ -0,0 +1,43 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')||!this.hasOwnProperty('SharedArrayBuffer')) -- Atomics,SharedArrayBuffer is not enabled unconditionally
+// Copyright (C) 2018 Amal Hussein. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Throws a RangeError is index < 0
+info: |
+  Atomics.notify( typedArray, index, count )
+
+  2.Let i be ? ValidateAtomicAccess(typedArray, index).
+    ...
+      2.Let accessIndex be ? ToIndex(requestIndex).
+        ...
+        2.b If integerIndex < 0, throw a RangeError exception
+features: [Atomics, SharedArrayBuffer, TypedArray]
+---*/
+
+const i32a = new Int32Array(
+  new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 4)
+);
+
+const poisoned = {
+  valueOf: function() {
+    throw new Test262Error('should not evaluate this code');
+  }
+};
+
+assert.throws(RangeError, function() {
+  Atomics.notify(i32a, -Infinity, poisoned);
+}, '`Atomics.notify(i32a, -Infinity, poisoned)` throws RangeError');
+assert.throws(RangeError, function() {
+  Atomics.notify(i32a, -7.999, poisoned);
+}, '`Atomics.notify(i32a, -7.999, poisoned)` throws RangeError');
+assert.throws(RangeError, function() {
+  Atomics.notify(i32a, -1, poisoned);
+}, '`Atomics.notify(i32a, -1, poisoned)` throws RangeError');
+assert.throws(RangeError, function() {
+  Atomics.notify(i32a, -300, poisoned);
+}, '`Atomics.notify(i32a, -300, poisoned)` throws RangeError');
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/non-int32-typedarray-throws.js
@@ -0,0 +1,81 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')) -- Atomics is not enabled unconditionally
+// Copyright (C) 2018 Amal Hussein. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Throws a TypeError if typedArray arg is not an Int32Array
+info: |
+  Atomics.notify( typedArray, index, count )
+
+  1.Let buffer be ? ValidateSharedIntegerTypedArray(typedArray, true).
+    ...
+      5.If onlyInt32 is true, then
+        If typeName is not "Int32Array", throw a TypeError exception.
+features: [Atomics, Float32Array, Float64Array, Int8Array, TypedArray, Uint16Array, Uint8Array, Uint8ClampedArray]
+---*/
+
+const poisoned = {
+  valueOf: function() {
+    throw new Test262Error('should not evaluate this code');
+  }
+};
+
+assert.throws(TypeError, function() {
+  const view = new Float64Array(
+    new SharedArrayBuffer(Float64Array.BYTES_PER_ELEMENT * 8)
+  );
+  Atomics.notify(view, poisoned, poisoned);
+}, '`const view = new Float64Array( new SharedArrayBuffer(Float64Array.BYTES_PER_ELEMENT * 8) ); Atomics.notify(view, poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  const view = new Float32Array(
+    new SharedArrayBuffer(Float32Array.BYTES_PER_ELEMENT * 4)
+  );
+  Atomics.notify(view, poisoned, poisoned);
+}, '`const view = new Float32Array( new SharedArrayBuffer(Float32Array.BYTES_PER_ELEMENT * 4) ); Atomics.notify(view, poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  const view = new Int16Array(
+    new SharedArrayBuffer(Int16Array.BYTES_PER_ELEMENT * 2)
+  );
+  Atomics.notify(view, poisoned, poisoned);
+}, '`const view = new Int16Array( new SharedArrayBuffer(Int16Array.BYTES_PER_ELEMENT * 2) ); Atomics.notify(view, poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  const view = new Int8Array(
+    new SharedArrayBuffer(Int8Array.BYTES_PER_ELEMENT)
+  );
+  Atomics.notify(view, poisoned, poisoned);
+}, '`const view = new Int8Array( new SharedArrayBuffer(Int8Array.BYTES_PER_ELEMENT) ); Atomics.notify(view, poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  const view = new Uint32Array(
+    new SharedArrayBuffer(Uint32Array.BYTES_PER_ELEMENT * 4)
+  );
+  Atomics.notify(new Uint32Array(),  poisoned, poisoned);
+}, '`const view = new Uint32Array( new SharedArrayBuffer(Uint32Array.BYTES_PER_ELEMENT * 4) ); Atomics.notify(new Uint32Array(), poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  const view = new Uint16Array(
+    new SharedArrayBuffer(Uint16Array.BYTES_PER_ELEMENT * 2)
+  );
+  Atomics.notify(view, poisoned, poisoned);
+}, '`const view = new Uint16Array( new SharedArrayBuffer(Uint16Array.BYTES_PER_ELEMENT * 2) ); Atomics.notify(view, poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  const view = new Uint8Array(
+    new SharedArrayBuffer(Uint8Array.BYTES_PER_ELEMENT)
+  );
+  Atomics.notify(view, poisoned, poisoned);
+}, '`const view = new Uint8Array( new SharedArrayBuffer(Uint8Array.BYTES_PER_ELEMENT) ); Atomics.notify(view, poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  const view = new Uint8ClampedArray(
+    new SharedArrayBuffer(Uint8ClampedArray.BYTES_PER_ELEMENT)
+  );
+  Atomics.notify(view, poisoned, poisoned);
+}, '`const view = new Uint8ClampedArray( new SharedArrayBuffer(Uint8ClampedArray.BYTES_PER_ELEMENT) ); Atomics.notify(view, poisoned, poisoned)` throws TypeError');
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/non-shared-bufferdata-throws.js
@@ -0,0 +1,37 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')) -- Atomics is not enabled unconditionally
+// Copyright (C) 2018 Amal Hussein. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-atomics.notify
+description: >
+  Throws a TypeError if typedArray.buffer is not a SharedArrayBuffer
+info: |
+  Atomics.notify( typedArray, index, count )
+
+  1.Let buffer be ? ValidateSharedIntegerTypedArray(typedArray, true).
+    ...
+      9.If IsSharedArrayBuffer(buffer) is false, throw a TypeError exception.
+        ...
+          4.If bufferData is a Data Block, return false.
+features: [ArrayBuffer, Atomics, TypedArray]
+---*/
+
+const i32a = new Int32Array(
+  new ArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 4)
+);
+
+const poisoned = {
+  valueOf: function() {
+    throw new Test262Error('should not evaluate this code');
+  }
+};
+
+assert.throws(TypeError, function() {
+  Atomics.notify(i32a, 0, 0);
+}, '`Atomics.notify(i32a, 0, 0)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  Atomics.notify(i32a, poisoned, poisoned);
+}, '`Atomics.notify(i32a, poisoned, poisoned)` throws TypeError');
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/non-shared-bufferdatate-non-shared-int-views.js
@@ -0,0 +1,45 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')) -- Atomics is not enabled unconditionally
+// Copyright (C) 2017 Mozilla Corporation.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Test Atomics.notify on non-shared integer TypedArrays
+includes: [testTypedArray.js]
+features: [ArrayBuffer, Atomics, TypedArray]
+---*/
+
+const nonsab = new ArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 4);
+
+const poisoned = {
+  valueOf: function() {
+    throw new Test262Error('should not evaluate this code');
+  }
+};
+
+assert.throws(TypeError, function() {
+  Atomics.notify(new Int16Array(nonsab), poisoned, poisoned);
+}, '`Atomics.notify(new Int16Array(nonsab), poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  Atomics.notify(new Int8Array(nonsab), poisoned, poisoned);
+}, '`Atomics.notify(new Int8Array(nonsab), poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  Atomics.notify(new Uint32Array(nonsab),  poisoned, poisoned);
+}, '`Atomics.notify(new Uint32Array(nonsab), poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  Atomics.notify(new Uint16Array(nonsab), poisoned, poisoned);
+}, '`Atomics.notify(new Uint16Array(nonsab), poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  Atomics.notify(new Uint8Array(nonsab), poisoned, poisoned);
+}, '`Atomics.notify(new Uint8Array(nonsab), poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  Atomics.notify(new Uint8ClampedArray(nonsab), poisoned, poisoned);
+}, '`Atomics.notify(new Uint8ClampedArray(nonsab), poisoned, poisoned)` throws TypeError');
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/non-shared-int-views.js
@@ -0,0 +1,45 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')||!this.hasOwnProperty('SharedArrayBuffer')) -- Atomics,SharedArrayBuffer is not enabled unconditionally
+// Copyright (C) 2017 Mozilla Corporation.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Test Atomics.notify on non-shared integer TypedArrays
+includes: [testTypedArray.js]
+features: [Atomics, SharedArrayBuffer, TypedArray]
+---*/
+
+const sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 4);
+
+const poisoned = {
+  valueOf: function() {
+    throw new Test262Error('should not evaluate this code');
+  }
+};
+
+assert.throws(TypeError, function() {
+  Atomics.notify(new Int16Array(sab), poisoned, poisoned);
+}, '`Atomics.notify(new Int16Array(sab), poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  Atomics.notify(new Int8Array(sab), poisoned, poisoned);
+}, '`Atomics.notify(new Int8Array(sab), poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  Atomics.notify(new Uint32Array(sab),  poisoned, poisoned);
+}, '`Atomics.notify(new Uint32Array(sab), poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  Atomics.notify(new Uint16Array(sab), poisoned, poisoned);
+}, '`Atomics.notify(new Uint16Array(sab), poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  Atomics.notify(new Uint8Array(sab), poisoned, poisoned);
+}, '`Atomics.notify(new Uint8Array(sab), poisoned, poisoned)` throws TypeError');
+
+assert.throws(TypeError, function() {
+  Atomics.notify(new Uint8ClampedArray(sab), poisoned, poisoned);
+}, '`Atomics.notify(new Uint8ClampedArray(sab), poisoned, poisoned)` throws TypeError');
+
+reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/built-ins/Atomics/notify/non-views.js
@@ -0,0 +1,19 @@
+// |reftest| skip-if(!this.hasOwnProperty('Atomics')||!this.hasOwnProperty('SharedArrayBuffer')) -- Atomics,SharedArrayBuffer is not enabled unconditionally
+// Copyright (C) 2017 Mozilla Corporation.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-atomics.notify
+description: >
+  Test Atomics.notify on view values other than TypedArrays
+includes: [testAtomics.js]
+features: [ArrayBuffer, Atomics, DataView, SharedArrayBuffer, Symbol, TypedArray]
+---*/
+
+testWithAtomicsNo