Merge mozilla-central to beta. a=merge, l10n=me on a CLOSED TREE
authorRyan VanderMeulen <ryanvm@gmail.com>
Sun, 21 Jan 2018 09:48:17 -0500
changeset 454438 bcabf2a78927ec1399f07f5f52329bca480c2fda
parent 454288 ff9dec21d6b9ffac3660ee0a489658cb998df27f (current diff)
parent 454437 9fe69ff0762da191fbd2b9fc0718e96f1ab4137e (diff)
child 454460 0ae49758d6c5114514a2cffedf6ce4cd472aac0d
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone59.0
Merge mozilla-central to beta. a=merge, l10n=me on a CLOSED TREE
devtools/client/inspector/animation/actions/element-picker.js
devtools/client/inspector/animation/actions/sidebar.js
devtools/client/inspector/animation/reducers/element-picker.js
devtools/client/inspector/animation/reducers/sidebar.js
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-canvas-css.html
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-canvas-css.js
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-css-loader.css
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-css-loader.css^headers^
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-css-loader.html
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-css-parser.css
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-css-parser.html
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-empty-getelementbyid.html
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-empty-getelementbyid.js
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-html.html
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-image.html
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-image.jpg
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-imagemap.html
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-malformedxml-external.html
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-malformedxml-external.xml
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-malformedxml.xhtml
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-svg.xhtml
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-workers.html
devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-workers.js
gfx/webrender/res/brush_image.glsl
layout/reftests/flexbox/flexbox-paint-ordering-3-ref.html
layout/reftests/flexbox/flexbox-paint-ordering-3.html
servo/tests/unit/gfx/Cargo.toml
servo/tests/unit/gfx/lib.rs
servo/tests/unit/gfx/text_util.rs
servo/tests/unit/layout/Cargo.toml
servo/tests/unit/layout/lib.rs
servo/tests/unit/layout/size_of.rs
servo/tests/unit/msg/Cargo.toml
servo/tests/unit/msg/lib.rs
servo/tests/unit/msg/size_of.rs
servo/tests/unit/net/Cargo.toml
servo/tests/unit/net/chrome_loader.rs
servo/tests/unit/net/cookie.rs
servo/tests/unit/net/cookie_http_state.rs
servo/tests/unit/net/cookie_http_state_utils.py
servo/tests/unit/net/data_loader.rs
servo/tests/unit/net/fetch.rs
servo/tests/unit/net/file_loader.rs
servo/tests/unit/net/filemanager_thread.rs
servo/tests/unit/net/hsts.rs
servo/tests/unit/net/http_loader.rs
servo/tests/unit/net/lib.rs
servo/tests/unit/net/mime_classifier.rs
servo/tests/unit/net/parsable_mime/application/font-woff/test.wof
servo/tests/unit/net/parsable_mime/application/ogg/small.ogg
servo/tests/unit/net/parsable_mime/application/pdf/test.pdf
servo/tests/unit/net/parsable_mime/application/postscript/test.ps
servo/tests/unit/net/parsable_mime/application/vnd.ms-fontobject/vnd.ms-fontobject
servo/tests/unit/net/parsable_mime/application/x-gzip/test.gz
servo/tests/unit/net/parsable_mime/application/x-rar-compressed/test.rar
servo/tests/unit/net/parsable_mime/application/zip/test.zip
servo/tests/unit/net/parsable_mime/audio/aiff/test.aif
servo/tests/unit/net/parsable_mime/audio/basic/test.au
servo/tests/unit/net/parsable_mime/audio/midi/test.mid
servo/tests/unit/net/parsable_mime/audio/mpeg/test.mp3
servo/tests/unit/net/parsable_mime/audio/wave/test.wav
servo/tests/unit/net/parsable_mime/image/bmp/test.bmp
servo/tests/unit/net/parsable_mime/image/gif/test87a
servo/tests/unit/net/parsable_mime/image/gif/test89a.gif
servo/tests/unit/net/parsable_mime/image/jpeg/test.jpg
servo/tests/unit/net/parsable_mime/image/png/test.png
servo/tests/unit/net/parsable_mime/image/webp/test.webp
servo/tests/unit/net/parsable_mime/image/x-icon/test.ico
servo/tests/unit/net/parsable_mime/image/x-icon/test_cursor.ico
servo/tests/unit/net/parsable_mime/text/html/text_html_a_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_a_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_a_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_a_3e_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_b_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_b_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_b_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_b_3e_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_body_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_body_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_body_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_body_3e_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_br_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_br_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_br_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_br_3e_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_comment_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_comment_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_comment_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_comment_3e_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_div_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_div_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_div_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_div_3e_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_doctype_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_doctype_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_doctype_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_doctype_3e_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_font_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_font_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_font_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_font_3e_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_h1_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_h1_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_h1_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_h1_3e_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_head_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_head_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_head_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_head_3e_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_iframe_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_iframe_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_iframe_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_iframe_3e_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_p_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_p_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_p_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_p_3e_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_page_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_page_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_page_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_page_3e_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_script_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_script_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_script_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_script_3e_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_style_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_style_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_style_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_style_3e_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_table_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_table_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_table_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_table_3e_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_title_20.html
servo/tests/unit/net/parsable_mime/text/html/text_html_title_20_u.html
servo/tests/unit/net/parsable_mime/text/html/text_html_title_3e.html
servo/tests/unit/net/parsable_mime/text/html/text_html_title_3e_u.html
servo/tests/unit/net/parsable_mime/text/plain/utf16bebom.txt
servo/tests/unit/net/parsable_mime/text/plain/utf16lebom.txt
servo/tests/unit/net/parsable_mime/text/plain/utf8bom.txt
servo/tests/unit/net/parsable_mime/text/xml/feed.atom
servo/tests/unit/net/parsable_mime/text/xml/feed.rss
servo/tests/unit/net/parsable_mime/text/xml/rdf_rss.xml
servo/tests/unit/net/parsable_mime/text/xml/rdf_rss_ko_1.xml
servo/tests/unit/net/parsable_mime/text/xml/rdf_rss_ko_2.xml
servo/tests/unit/net/parsable_mime/text/xml/rdf_rss_ko_3.xml
servo/tests/unit/net/parsable_mime/text/xml/rdf_rss_ko_4.xml
servo/tests/unit/net/parsable_mime/text/xml/test.xml
servo/tests/unit/net/parsable_mime/unknown/binary_file
servo/tests/unit/net/parsable_mime/unknown/open_type
servo/tests/unit/net/parsable_mime/unknown/true_type.ttf
servo/tests/unit/net/parsable_mime/unknown/true_type_collection.ttc
servo/tests/unit/net/parsable_mime/video/avi/test.avi
servo/tests/unit/net/parsable_mime/video/mp4/test.mp4
servo/tests/unit/net/parsable_mime/video/webm/test.webm
servo/tests/unit/net/resource_thread.rs
servo/tests/unit/net/subresource_integrity.rs
servo/tests/unit/net/test.jpeg
servo/tests/unit/net_traits/Cargo.toml
servo/tests/unit/net_traits/image.rs
servo/tests/unit/net_traits/lib.rs
servo/tests/unit/net_traits/pub_domains.rs
servo/tests/unit/servo_config/Cargo.toml
servo/tests/unit/servo_config/lib.rs
servo/tests/unit/servo_config/opts.rs
servo/tests/unit/servo_config/prefs.rs
servo/tests/unit/servo_remutex/Cargo.toml
servo/tests/unit/servo_remutex/lib.rs
testing/talos/talos/tests/canvasmark/HelveticaNeueLTStd-Lt.otf
testing/talos/talos/tests/canvasmark/HelveticaNeueLTStd-Md.otf
testing/talos/talos/tests/canvasmark/canvasmark.manifest
testing/talos/talos/tests/canvasmark/images/asteroid1.png
testing/talos/talos/tests/canvasmark/images/asteroid2.png
testing/talos/talos/tests/canvasmark/images/asteroid3.png
testing/talos/talos/tests/canvasmark/images/asteroid4.png
testing/talos/talos/tests/canvasmark/images/bg3_1.jpg
testing/talos/talos/tests/canvasmark/images/canvasmark2013.jpg
testing/talos/talos/tests/canvasmark/images/enemyship1.png
testing/talos/talos/tests/canvasmark/images/fruit.jpg
testing/talos/talos/tests/canvasmark/images/player.png
testing/talos/talos/tests/canvasmark/images/texture5.png
testing/talos/talos/tests/canvasmark/index.html
testing/talos/talos/tests/canvasmark/license.txt
testing/talos/talos/tests/canvasmark/ostrich-black-webfont.woff
testing/talos/talos/tests/canvasmark/scripts/canvasmark_v6.js
testing/talos/talos/tests/canvasmark/scripts/jquery-1.4.2.min.js
testing/talos/talos/tests/canvasmark/scripts/k3d-min.js
testing/talos/talos/tests/canvasmark/scripts/mathlib-min.js
toolkit/components/resistfingerprinting/nsRFPService.cpp
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -257,16 +257,20 @@
     </emItem>
     <emItem blockID="i862" id="{CA8C84C6-3918-41b1-BE77-049B2BDD887C}">
       <prefs>
         <pref>browser.startup.homepage</pref>
         <pref>browser.search.defaultenginename</pref>
       </prefs>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
+    <emItem blockID="5bf72f70-a611-4845-af3f-d4dabe8862b6" id="/^(\{1490068c-d8b7-4bd2-9621-a648942b312c\})|(\{d47ebc8a-c1ea-4a42-9ca3-f723fff034bd\})|(\{83d6f65c-7fc0-47d0-9864-a488bfcaa376\})|(\{e804fa4c-08e0-4dae-a237-8680074eba07\})|(\{ea618d26-780e-4f0f-91fd-2a6911064204\})|(\{ce93dcc7-f911-4098-8238-7f023dcdfd0d\})|(\{7eaf96aa-d4e7-41b0-9f12-775c2ac7f7c0\})|(\{b019c485-2a48-4f5b-be13-a7af94bc1a3e\})|(\{9b8a3057-8bf4-4a9e-b94b-867e4e71a50c\})|(\{eb3ebb14-6ced-4f60-9800-85c3de3680a4\})|(\{01f409a5-d617-47be-a574-d54325fe05d1\})$/">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
     <emItem blockID="i882" id="69ffxtbr@PackageTracer_69.com">
       <prefs>
         <pref>browser.startup.homepage</pref>
         <pref>browser.search.defaultenginename</pref>
       </prefs>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="i706" id="thefoxonlybetter@quicksaver">
@@ -968,44 +972,24 @@
     <emItem blockID="i44" id="sigma@labs.mozilla">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="i96" id="youtubeee@youtuber3.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
-    <emItem blockID="i564" id="/^(firefox@vebergreat\.net|EFGLQA@78ETGYN-0W7FN789T87\.COM)$/">
-      <prefs/>
-      <versionRange minVersion="0" maxVersion="*" severity="1"/>
-    </emItem>
-    <emItem blockID="i500" id="{2aab351c-ad56-444c-b935-38bffe18ad26}">
-      <prefs/>
-      <versionRange minVersion="0" maxVersion="*" severity="3"/>
-    </emItem>
     <emItem blockID="i97" id="support3_en@adobe122.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
-    <emItem blockID="i439" id="{d2cf9842-af95-48cd-b873-bfbb48cd7f5e}">
-      <prefs/>
-      <versionRange minVersion="0" maxVersion="*" severity="1"/>
-    </emItem>
     <emItem blockID="i576" id="newmoz@facebook.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
-    <emItem blockID="i46" id="{841468a1-d7f4-4bd3-84e6-bb0f13a06c64}">
-      <prefs/>
-      <versionRange minVersion="0.1" maxVersion="*" severity="1">
-        <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
-          <versionRange maxVersion="9.0" minVersion="9.0a1"/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
     <emItem blockID="i776" id="g@uzcERQ6ko.net">
       <prefs>
         <pref>browser.startup.homepage</pref>
         <pref>browser.search.defaultenginename</pref>
       </prefs>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="i494" id="/^({e9df9360-97f8-4690-afe6-996c80790da4}|{687578b9-7132-4a7a-80e4-30ee31099e03}|{46a3135d-3683-48cf-b94c-82655cbc0e8a}|{49c795c2-604a-4d18-aeb1-b3eba27e5ea2}|{7473b6bd-4691-4744-a82b-7854eb3d70b6}|{96f454ea-9d38-474f-b504-56193e00c1a5})$/">
@@ -1057,16 +1041,20 @@
         </targetApplication>
       </versionRange>
       <versionRange minVersion="1.5.7.5" maxVersion="1.5.7.5" severity="1"/>
     </emItem>
     <emItem blockID="i515" id="/^({bf9194c2-b86d-4ebc-9b53-1c08b6ff779e}|{61a83e16-7198-49c6-8874-3e4e8faeb4f3}|{f0af464e-5167-45cf-9cf0-66b396d1918c}|{5d9968c3-101c-4944-ba71-72d77393322d}|{01e86e69-a2f8-48a0-b068-83869bdba3d0})$/">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
+    <emItem blockID="4ca8206f-bc2a-4428-9439-7f3142dc08db" id="/^(\{ac06c6b2-3fd6-45ee-9237-6235aa347215\})|(\{d461cc1b-8a36-4ff0-b330-1824c148f326\})|(\{d1ab5ebd-9505-481d-a6cd-6b9db8d65977\})|(\{07953f60-447e-4f53-a5ef-ed060487f616\})|(\{2d3c5a5a-8e6f-4762-8aff-b24953fe1cc9\})|(\{f82b3ad5-e590-4286-891f-05adf5028d2f\})|(\{f96245ad-3bb0-46c5-8ca9-2917d69aa6ca\})|(\{2f53e091-4b16-4b60-9cae-69d0c55b2e78\})|(\{18868c3a-a209-41a6-855d-f99f782d1606\})|(\{47352fbf-80d9-4b70-9398-fb7bffa3da53\})$/">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
     <emItem blockID="i596" id="{b99c8534-7800-48fa-bd71-519a46cdc7e1}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="i461" id="{8E9E3331-D360-4f87-8803-52DE43566502}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
@@ -1253,16 +1241,20 @@
           <versionRange maxVersion="*" minVersion="56.0a1"/>
         </targetApplication>
       </versionRange>
     </emItem>
     <emItem blockID="i1034" id="a88a77ahjjfjakckmmabsy278djasi@jetpack">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
+    <emItem blockID="8088b39a-3e6d-4a17-a22f-3f95c0464bd6" id="{5b0f6d3c-10fd-414c-a135-dffd26d7de0f}">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
     <emItem blockID="9abc7502-bd6f-40d7-b035-abe721345360" id="{368eb817-31b4-4be9-a761-b67598faf9fa}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
     <emItem blockID="i562" id="iobitapps@mybrowserbar.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
@@ -2150,16 +2142,36 @@
     <emItem blockID="i117" id="{ce7e73df-6a44-4028-8079-5927a588c948}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="1.0.8" severity="1"/>
     </emItem>
     <emItem blockID="i258" id="helperbar@helperbar.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="1.0" severity="1"/>
     </emItem>
+    <emItem blockID="i564" id="/^(firefox@vebergreat\.net|EFGLQA@78ETGYN-0W7FN789T87\.COM)$/">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="1"/>
+    </emItem>
+    <emItem blockID="i500" id="{2aab351c-ad56-444c-b935-38bffe18ad26}">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+    <emItem blockID="i439" id="{d2cf9842-af95-48cd-b873-bfbb48cd7f5e}">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="1"/>
+    </emItem>
+    <emItem blockID="i46" id="{841468a1-d7f4-4bd3-84e6-bb0f13a06c64}">
+      <prefs/>
+      <versionRange minVersion="0.1" maxVersion="*" severity="1">
+        <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+          <versionRange maxVersion="9.0" minVersion="9.0a1"/>
+        </targetApplication>
+      </versionRange>
+    </emItem>
   </emItems>
   <pluginItems>
     <pluginItem blockID="p416">
       <match exp="JavaAppletPlugin\.plugin" name="filename"/>
       <versionRange maxVersion="Java 6 Update 45" minVersion="Java 6 Update 42" severity="0" vulnerabilitystatus="1">
         <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
           <versionRange maxVersion="*" minVersion="17.0"/>
         </targetApplication>
--- a/browser/base/content/browser-media.js
+++ b/browser/base/content/browser-media.js
@@ -31,17 +31,17 @@ var gEMEHandler = {
     if (keySystem == "com.widevine.alpha" &&
         Services.prefs.getPrefType("media.gmp-widevinecdm.visible")) {
       return Services.prefs.getBoolPref("media.gmp-widevinecdm.visible");
     }
     return true;
   },
   getEMEDisabledFragment(msgId) {
     let mainMessage = gNavigatorBundle.getString("emeNotifications.drmContentDisabled.message");
-    let [prefix, suffix] = mainMessage.split(/%(?:\$\d)?S/).map(s => document.createTextNode(s));
+    let [prefix, suffix] = mainMessage.split(/%(?:1\$)?S/).map(s => document.createTextNode(s));
     let text = gNavigatorBundle.getString("emeNotifications.drmContentDisabled.learnMoreLabel");
     let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
     let link = document.createElement("label");
     link.className = "text-link";
     link.setAttribute("href", baseURL + "drm-content");
     link.textContent = text;
 
     let fragment = document.createDocumentFragment();
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -256,19 +256,16 @@ function getClipboardHelper() {
         return Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper);
     } catch (e) {
         // do nothing, later code will handle the error
         return null;
     }
 }
 const gClipboardHelper = getClipboardHelper();
 
-// Interface for image loading content
-const nsIImageLoadingContent = Components.interfaces.nsIImageLoadingContent;
-
 // namespaces, don't need all of these yet...
 const XLinkNS  = "http://www.w3.org/1999/xlink";
 const XULNS    = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const XMLNS    = "http://www.w3.org/XML/1998/namespace";
 const XHTMLNS  = "http://www.w3.org/1999/xhtml";
 const XHTML2NS = "http://www.w3.org/2002/06/xhtml2";
 
 const XHTMLNSre  = "^http\:\/\/www\.w3\.org\/1999\/xhtml$";
--- a/browser/base/content/test/general/browser_bug553455.js
+++ b/browser/base/content/test/general/browser_bug553455.js
@@ -239,17 +239,17 @@ async function test_disabledInstall() {
   let triggers = encodeURIComponent(JSON.stringify({
     "XPI": "amosigned.xpi"
   }));
   BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
   let panel = await notificationPromise;
 
   let notification = panel.childNodes[0];
   is(notification.button.label, "Enable", "Should have seen the right button");
-  is(notification.getAttribute("startlabel"),
+  is(notification.getAttribute("label"),
      "Software installation is currently disabled. Click Enable and try again.");
 
   let closePromise = waitForNotificationClose();
   // Click on Enable
   EventUtils.synthesizeMouseAtCenter(notification.button, {});
   await closePromise;
 
   try {
@@ -270,17 +270,17 @@ async function test_blockedInstall() {
   }));
   BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
   let panel = await notificationPromise;
 
   let notification = panel.childNodes[0];
   is(notification.button.label, "Allow", "Should have seen the right button");
   is(notification.getAttribute("origin"), "example.com",
      "Should have seen the right origin host");
-  is(notification.getAttribute("startlabel"),
+  is(notification.getAttribute("label"),
      gApp + " prevented this site from asking you to install software on your computer.",
      "Should have seen the right message");
 
   let dialogPromise = waitForInstallDialog();
   // Click on Allow
   EventUtils.synthesizeMouse(notification.button, 20, 10, {});
   // Notification should have changed to progress notification
   ok(PopupNotifications.isPanelOpen, "Notification should still be open");
@@ -289,17 +289,17 @@ async function test_blockedInstall() {
   let installDialog = await dialogPromise;
 
   notificationPromise = waitForNotification("addon-install-restart");
   acceptInstallDialog(installDialog);
   panel = await notificationPromise;
 
   notification = panel.childNodes[0];
   is(notification.button.label, "Restart Now", "Should have seen the right button");
-  is(notification.getAttribute("startlabel"),
+  is(notification.getAttribute("label"),
      "XPI Test will be installed after you restart " + gApp + ".",
      "Should have seen the right message");
 
   let installs = await getInstalls();
   is(installs.length, 1, "Should be one pending install");
   installs[0].cancel();
   await removeTab();
 },
@@ -326,17 +326,17 @@ async function test_whitelistedInstall()
      "tab selected in response to the addon-install-confirmation notification");
 
   let notificationPromise = waitForNotification("addon-install-restart");
   acceptInstallDialog(installDialog);
   let panel = await notificationPromise;
 
   let notification = panel.childNodes[0];
   is(notification.button.label, "Restart Now", "Should have seen the right button");
-  is(notification.getAttribute("startlabel"),
+  is(notification.getAttribute("label"),
      "XPI Test will be installed after you restart " + gApp + ".",
      "Should have seen the right message");
 
   let installs = await getInstalls();
   is(installs.length, 1, "Should be one pending install");
   installs[0].cancel();
 
   Services.perms.remove(makeURI("http://example.com/"), "install");
@@ -352,17 +352,17 @@ async function test_failedDownload() {
   let triggers = encodeURIComponent(JSON.stringify({
     "XPI": "missing.xpi"
   }));
   BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
   await progressPromise;
   let panel = await failPromise;
 
   let notification = panel.childNodes[0];
-  is(notification.getAttribute("startlabel"),
+  is(notification.getAttribute("label"),
      "The add-on could not be downloaded because of a connection failure.",
      "Should have seen the right message");
 
   Services.perms.remove(makeURI("http://example.com/"), "install");
   await removeTab();
 },
 
 async function test_corruptFile() {
@@ -374,17 +374,17 @@ async function test_corruptFile() {
   let triggers = encodeURIComponent(JSON.stringify({
     "XPI": "corrupt.xpi"
   }));
   BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
   await progressPromise;
   let panel = await failPromise;
 
   let notification = panel.childNodes[0];
-  is(notification.getAttribute("startlabel"),
+  is(notification.getAttribute("label"),
      "The add-on downloaded from this site could not be installed " +
      "because it appears to be corrupt.",
      "Should have seen the right message");
 
   Services.perms.remove(makeURI("http://example.com/"), "install");
   await removeTab();
 },
 
@@ -397,17 +397,17 @@ async function test_incompatible() {
   let triggers = encodeURIComponent(JSON.stringify({
     "XPI": "incompatible.xpi"
   }));
   BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
   await progressPromise;
   let panel = await failPromise;
 
   let notification = panel.childNodes[0];
-  is(notification.getAttribute("startlabel"),
+  is(notification.getAttribute("label"),
      "XPI Test could not be installed because it is not compatible with " +
      gApp + " " + gVersion + ".",
      "Should have seen the right message");
 
   Services.perms.remove(makeURI("http://example.com/"), "install");
   await removeTab();
 },
 
@@ -536,17 +536,17 @@ async function test_allUnverified() {
   let triggers = encodeURIComponent(JSON.stringify({
     "Extension XPI": "restartless-unsigned.xpi"
   }));
   BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
   await progressPromise;
   let installDialog = await dialogPromise;
 
   let notification = document.getElementById("addon-install-confirmation-notification");
-  let message = notification.getAttribute("startlabel");
+  let message = notification.getAttribute("label");
   is(message, "Caution: This site would like to install an unverified add-on in " + gApp + ". Proceed at your own risk.");
 
   let container = document.getElementById("addon-install-confirmation-content");
   is(container.childNodes.length, 1, "Should be one item listed");
   is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
   is(container.childNodes[0].childNodes.length, 1, "Shouldn't have the unverified marker");
 
   let notificationPromise = waitForNotification("addon-installed");
@@ -574,17 +574,17 @@ async function test_url() {
   let installDialog = await dialogPromise;
 
   let notificationPromise = waitForNotification("addon-install-restart");
   acceptInstallDialog(installDialog);
   let panel = await notificationPromise;
 
   let notification = panel.childNodes[0];
   is(notification.button.label, "Restart Now", "Should have seen the right button");
-  is(notification.getAttribute("startlabel"),
+  is(notification.getAttribute("label"),
      "XPI Test will be installed after you restart " + gApp + ".",
      "Should have seen the right message");
 
   let installs = await getInstalls();
   is(installs.length, 1, "Should be one pending install");
   installs[0].cancel();
 
   await removeTab();
@@ -611,17 +611,17 @@ async function test_localFile() {
   gBrowser.loadURI(path);
   await failPromise;
 
   // Wait for the browser code to add the failure notification
   await waitForSingleNotification();
 
   let notification = PopupNotifications.panel.childNodes[0];
   is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
-  is(notification.getAttribute("startlabel"),
+  is(notification.getAttribute("label"),
      "This add-on could not be installed because it appears to be corrupt.",
      "Should have seen the right message");
 
   await removeTab();
 },
 
 async function test_tabClose() {
   if (!Services.prefs.getBoolPref("xpinstall.customConfirmationUI", false)) {
@@ -700,17 +700,17 @@ async function test_urlBar() {
   let installDialog = await dialogPromise;
 
   let notificationPromise = waitForNotification("addon-install-restart");
   acceptInstallDialog(installDialog);
   let panel = await notificationPromise;
 
   let notification = panel.childNodes[0];
   is(notification.button.label, "Restart Now", "Should have seen the right button");
-  is(notification.getAttribute("startlabel"),
+  is(notification.getAttribute("label"),
      "XPI Test will be installed after you restart " + gApp + ".",
      "Should have seen the right message");
 
   let installs = await getInstalls();
   is(installs.length, 1, "Should be one pending install");
   installs[0].cancel();
 
   await removeTab();
@@ -726,17 +726,17 @@ async function test_wrongHost() {
 
   let progressPromise = waitForProgressNotification();
   let notificationPromise = waitForNotification("addon-install-failed");
   gBrowser.loadURI(TESTROOT + "corrupt.xpi");
   await progressPromise;
   let panel = await notificationPromise;
 
   let notification = panel.childNodes[0];
-  is(notification.getAttribute("startlabel"),
+  is(notification.getAttribute("label"),
      "The add-on downloaded from this site could not be installed " +
      "because it appears to be corrupt.",
      "Should have seen the right message");
 
   await removeTab();
 },
 
 async function test_reload() {
@@ -753,17 +753,17 @@ async function test_reload() {
   let installDialog = await dialogPromise;
 
   let notificationPromise = waitForNotification("addon-install-restart");
   acceptInstallDialog(installDialog);
   let panel = await notificationPromise;
 
   let notification = panel.childNodes[0];
   is(notification.button.label, "Restart Now", "Should have seen the right button");
-  is(notification.getAttribute("startlabel"),
+  is(notification.getAttribute("label"),
      "XPI Test will be installed after you restart " + gApp + ".",
      "Should have seen the right message");
 
   function testFail() {
     ok(false, "Reloading should not have hidden the notification");
   }
   PopupNotifications.panel.addEventListener("popuphiding", testFail);
   let requestedUrl = TESTROOT2 + "enabled.html";
@@ -794,17 +794,17 @@ async function test_theme() {
   let installDialog = await dialogPromise;
 
   let notificationPromise = waitForNotification("addon-install-restart");
   acceptInstallDialog(installDialog);
   let panel = await notificationPromise;
 
   let notification = panel.childNodes[0];
   is(notification.button.label, "Restart Now", "Should have seen the right button");
-  is(notification.getAttribute("startlabel"),
+  is(notification.getAttribute("label"),
      "Theme Test will be installed after you restart " + gApp + ".",
      "Should have seen the right message");
 
   let addon = await new Promise(resolve => {
     AddonManager.getAddonByID("{972ce4c6-7e08-4474-a285-3208198ce6fd}", function(result) {
       resolve(result);
     });
   });
--- a/browser/base/content/test/popupNotifications/head.js
+++ b/browser/base/content/test/popupNotifications/head.js
@@ -200,19 +200,19 @@ function checkPopup(popup, notifyObj) {
                                                      "popup-notification-icon");
   if (notifyObj.id == "geolocation") {
     isnot(icon.boxObject.width, 0, "icon for geo displayed");
     ok(popup.anchorNode.classList.contains("notification-anchor-icon"),
        "notification anchored to icon");
   }
 
   if (typeof notifyObj.message == "string") {
-    is(notification.getAttribute("startlabel"), notifyObj.message, "message matches");
+    is(notification.getAttribute("label"), notifyObj.message, "message matches");
   } else {
-    is(notification.getAttribute("startlabel"), notifyObj.message.start, "message matches");
+    is(notification.getAttribute("label"), notifyObj.message.start, "message matches");
     is(notification.getAttribute("hostname"), notifyObj.message.host, "message matches");
     is(notification.getAttribute("endlabel"), notifyObj.message.end, "message matches");
   }
 
   is(notification.id, notifyObj.id + "-notification", "id matches");
   if (notifyObj.mainAction) {
     is(notification.getAttribute("buttonlabel"), notifyObj.mainAction.label,
        "main action label matches");
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -274,32 +274,33 @@ this.pageAction = class extends Extensio
 
         isShown(details) {
           let tab = tabTracker.getTab(details.tabId);
           return pageAction.getProperty(tab, "show");
         },
 
         setTitle(details) {
           let tab = tabTracker.getTab(details.tabId);
-
-          // Clear the tab-specific title when given a null string.
-          pageAction.setProperty(tab, "title", details.title || null);
+          pageAction.setProperty(tab, "title", details.title);
         },
 
         getTitle(details) {
           let tab = tabTracker.getTab(details.tabId);
 
           let title = pageAction.getProperty(tab, "title");
           return Promise.resolve(title);
         },
 
         setIcon(details) {
           let tab = tabTracker.getTab(details.tabId);
 
           let icon = IconDetails.normalize(details, extension, context);
+          if (!Object.keys(icon).length) {
+            icon = null;
+          }
           pageAction.setProperty(tab, "icon", icon);
         },
 
         setPopup(details) {
           let tab = tabTracker.getTab(details.tabId);
 
           // Note: Chrome resolves arguments to setIcon relative to the calling
           // context, but resolves arguments to setPopup relative to the extension
--- a/browser/components/extensions/ext-sidebarAction.js
+++ b/browser/components/extensions/ext-sidebarAction.js
@@ -4,20 +4,16 @@
 
 // The ext-* files are imported into the same scopes.
 /* import-globals-from ext-browser.js */
 /* globals WINDOW_ID_CURRENT */
 
 Cu.import("resource://gre/modules/ExtensionParent.jsm");
 
 var {
-  ExtensionError,
-} = ExtensionUtils;
-
-var {
   IconDetails,
 } = ExtensionParent;
 
 var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 // WeakMap[Extension -> SidebarAction]
 let sidebarActionMap = new WeakMap();
 
@@ -51,23 +47,24 @@ this.sidebarAction = class extends Exten
     this.browserStyle = options.browser_style || options.browser_style === null;
 
     this.defaults = {
       enabled: true,
       title: options.default_title || extension.name,
       icon: IconDetails.normalize({path: options.default_icon}, extension),
       panel: options.default_panel || "",
     };
+    this.globals = Object.create(this.defaults);
 
-    this.tabContext = new TabContext(tab => Object.create(this.defaults),
+    this.tabContext = new TabContext(tab => Object.create(this.globals),
                                      extension);
 
     // We need to ensure our elements are available before session restore.
     this.windowOpenListener = (window) => {
-      this.createMenuItem(window, this.defaults);
+      this.createMenuItem(window, this.globals);
     };
     windowTracker.addOpenListener(this.windowOpenListener);
 
     this.updateHeader = (event) => {
       let window = event.target.ownerGlobal;
       let details = this.tabContext.get(window.gBrowser.selectedTab);
       let header = window.document.getElementById("sidebar-switcher-target");
       if (window.SidebarUI.currentID === this.id) {
@@ -286,40 +283,44 @@ this.sidebarAction = class extends Exten
    * @param {XULElement|null} nativeTab
    *        Webextension tab object, may be null.
    * @param {string} prop
    *        String property to retrieve ["icon", "title", or "panel"].
    * @param {string} value
    *        Value for property.
    */
   setProperty(nativeTab, prop, value) {
+    let values;
     if (nativeTab === null) {
-      this.defaults[prop] = value;
-    } else if (value !== null) {
-      this.tabContext.get(nativeTab)[prop] = value;
+      values = this.globals;
     } else {
-      delete this.tabContext.get(nativeTab)[prop];
+      values = this.tabContext.get(nativeTab);
+    }
+    if (value === null) {
+      delete values[prop];
+    } else {
+      values[prop] = value;
     }
 
     this.updateOnChange(nativeTab);
   }
 
   /**
-   * Retrieve a property from the tab or defaults if tab is null.
+   * Retrieve a property from the tab or globals if tab is null.
    *
    * @param {XULElement|null} nativeTab
    *        Browser tab object, may be null.
    * @param {string} prop
    *        String property to retrieve ["icon", "title", or "panel"]
    * @returns {string} value
    *          Value for prop.
    */
   getProperty(nativeTab, prop) {
     if (nativeTab === null) {
-      return this.defaults[prop];
+      return this.globals[prop];
     }
     return this.tabContext.get(nativeTab)[prop];
   }
 
   /**
    * Triggers this sidebar action for the given window, with the same effects as
    * if it were toggled via menu or toolbarbutton by a user.
    *
@@ -376,53 +377,48 @@ this.sidebarAction = class extends Exten
       }
       return null;
     }
 
     return {
       sidebarAction: {
         async setTitle(details) {
           let nativeTab = getTab(details.tabId);
-
-          let title = details.title;
-          // Clear the tab-specific title when given a null string.
-          if (nativeTab && title === "") {
-            title = null;
-          }
-          sidebarAction.setProperty(nativeTab, "title", title);
+          sidebarAction.setProperty(nativeTab, "title", details.title);
         },
 
         getTitle(details) {
           let nativeTab = getTab(details.tabId);
 
           let title = sidebarAction.getProperty(nativeTab, "title");
           return Promise.resolve(title);
         },
 
         async setIcon(details) {
           let nativeTab = getTab(details.tabId);
 
           let icon = IconDetails.normalize(details, extension, context);
+          if (!Object.keys(icon).length) {
+            icon = null;
+          }
           sidebarAction.setProperty(nativeTab, "icon", icon);
         },
 
         async setPanel(details) {
           let nativeTab = getTab(details.tabId);
 
           let url;
-          // Clear the tab-specific url when given a null string.
-          if (nativeTab && details.panel === "") {
+          // Clear the url when given null or empty string.
+          if (!details.panel) {
             url = null;
-          } else if (details.panel !== "") {
+          } else {
             url = context.uri.resolve(details.panel);
             if (!context.checkLoadURL(url)) {
               return Promise.reject({message: `Access denied for URL ${url}`});
             }
-          } else {
-            throw new ExtensionError("Invalid url for sidebar panel.");
           }
 
           sidebarAction.setProperty(nativeTab, "panel", url);
         },
 
         getPanel(details) {
           let nativeTab = getTab(details.tabId);
 
--- a/browser/components/extensions/schemas/page_action.json
+++ b/browser/components/extensions/schemas/page_action.json
@@ -119,17 +119,23 @@
         "type": "function",
         "description": "Sets the title of the page action. This is displayed in a tooltip over the page action.",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
-              "title": {"type": "string", "description": "The tooltip string."}
+              "title": {
+                "choices": [
+                  {"type": "string"},
+                  {"type": "null"}
+                ],
+                "description": "The tooltip string."
+              }
             }
           }
         ]
       },
       {
         "name": "getTitle",
         "type": "function",
         "description": "Gets the title of the page action.",
@@ -211,17 +217,20 @@
         "description": "Sets the html document to be opened as a popup when the user clicks on the page action's icon.",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
               "popup": {
-                "type": "string",
+                "choices": [
+                  {"type": "string"},
+                  {"type": "null"}
+                ],
                 "description": "The html file to show in a popup.  If set to the empty string (''), no popup is shown."
               }
             }
           }
         ]
       },
       {
         "name": "getPopup",
--- a/browser/components/extensions/schemas/sidebar_action.json
+++ b/browser/components/extensions/schemas/sidebar_action.json
@@ -59,17 +59,20 @@
         "description": "Sets the title of the sidebar action. This shows up in the tooltip.",
         "async": true,
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "title": {
-                "type": "string",
+                "choices": [
+                  {"type": "string"},
+                  {"type": "null"}
+                ],
                 "description": "The string the sidebar action should display when moused over."
               },
               "tabId": {
                 "type": "integer",
                 "optional": true,
                 "description": "Sets the sidebar title for the tab specified by tabId. Automatically resets when the tab is closed."
               }
             }
@@ -151,17 +154,20 @@
             "properties": {
               "tabId": {
                 "type": "integer",
                 "optional": true,
                 "minimum": 0,
                 "description": "Sets the sidebar url for the tab specified by tabId. Automatically resets when the tab is closed."
               },
               "panel": {
-                "type": "string",
+                "choices": [
+                  {"type": "string"},
+                  {"type": "null"}
+                ],
                 "description": "The url to the html file to show in a sidebar.  If set to the empty string (''), no sidebar is shown."
               }
             }
           }
         ]
       },
       {
         "name": "getPanel",
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
@@ -468,34 +468,34 @@ add_task(async function testPropertyRemo
     "files": {
       "default.png": imageBuffer,
       "i1.png": imageBuffer,
       "i2.png": imageBuffer,
       "i3.png": imageBuffer,
     },
 
     getTests: function(tabs, expectGlobals) {
-      let contextUri = browser.runtime.getURL("_generated_background_page.html");
+      let defaultIcon = "chrome://browser/content/extension.svg";
       let details = [
         {"icon": browser.runtime.getURL("default.png"),
          "popup": browser.runtime.getURL("default.html"),
          "title": "Default Title",
          "badge": "",
          "badgeBackgroundColor": [0xd9, 0x00, 0x00, 0xFF]},
         {"icon": browser.runtime.getURL("i1.png"),
          "popup": browser.runtime.getURL("p1.html"),
          "title": "t1",
          "badge": "b1",
          "badgeBackgroundColor": [0x11, 0x11, 0x11, 0xFF]},
         {"icon": browser.runtime.getURL("i2.png"),
          "popup": browser.runtime.getURL("p2.html"),
          "title": "t2",
          "badge": "b2",
          "badgeBackgroundColor": [0x22, 0x22, 0x22, 0xFF]},
-        {"icon": contextUri,
+        {"icon": defaultIcon,
          "popup": "",
          "title": "",
          "badge": "",
          "badgeBackgroundColor": [0x22, 0x22, 0x22, 0xFF]},
         {"icon": browser.runtime.getURL("i3.png"),
          "popup": browser.runtime.getURL("p3.html"),
          "title": "t3",
          "badge": "b3",
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
@@ -47,29 +47,30 @@ add_task(async function testTabSwitchCon
       },
 
       "default.png": imageBuffer,
       "1.png": imageBuffer,
       "2.png": imageBuffer,
     },
 
     getTests: function(tabs) {
+      let defaultIcon = "chrome://browser/content/extension.svg";
       let details = [
         {"icon": browser.runtime.getURL("default.png"),
          "popup": browser.runtime.getURL("default.html"),
          "title": "Default T\u00edtulo \u263a"},
         {"icon": browser.runtime.getURL("1.png"),
          "popup": browser.runtime.getURL("default.html"),
          "title": "Default T\u00edtulo \u263a"},
         {"icon": browser.runtime.getURL("2.png"),
          "popup": browser.runtime.getURL("2.html"),
          "title": "Title 2"},
-        {"icon": browser.runtime.getURL("2.png"),
-         "popup": browser.runtime.getURL("2.html"),
-         "title": "Default T\u00edtulo \u263a"},
+        {"icon": defaultIcon,
+         "popup": "",
+         "title": ""},
       ];
 
       let promiseTabLoad = details => {
         return new Promise(resolve => {
           browser.tabs.onUpdated.addListener(function listener(tabId, changed) {
             if (tabId == details.id && changed.url == details.url) {
               browser.tabs.onUpdated.removeListener(listener);
               resolve();
@@ -119,21 +120,31 @@ add_task(async function testTabSwitchCon
 
           let promise = promiseTabLoad({id: tabs[1], url: "about:blank?0#ref"});
           browser.tabs.update(tabs[1], {url: "about:blank?0#ref"});
           await promise;
 
           expect(details[2]);
         },
         expect => {
-          browser.test.log("Clear the title. Expect default title.");
+          browser.test.log("Set empty string values. Expect empty strings but default icon.");
+          browser.pageAction.setIcon({tabId: tabs[1], path: ""});
+          browser.pageAction.setPopup({tabId: tabs[1], popup: ""});
           browser.pageAction.setTitle({tabId: tabs[1], title: ""});
 
           expect(details[3]);
         },
+        expect => {
+          browser.test.log("Clear the values. Expect default ones.");
+          browser.pageAction.setIcon({tabId: tabs[1], path: null});
+          browser.pageAction.setPopup({tabId: tabs[1], popup: null});
+          browser.pageAction.setTitle({tabId: tabs[1], title: null});
+
+          expect(details[0]);
+        },
         async expect => {
           browser.test.log("Navigate to a new page. Expect icon hidden.");
 
           // TODO: This listener should not be necessary, but the |tabs.update|
           // callback currently fires too early in e10s windows.
           let promise = promiseTabLoad({id: tabs[1], url: "about:blank?1"});
 
           browser.tabs.update(tabs[1], {url: "about:blank?1"});
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_title.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_title.js
@@ -59,16 +59,19 @@ add_task(async function testTabSwitchCon
         {"icon": browser.runtime.getURL("1.png"),
          "popup": browser.runtime.getURL("default.html"),
          "title": "Default T\u00edtulo \u263a"},
         {"icon": browser.runtime.getURL("2.png"),
          "popup": browser.runtime.getURL("2.html"),
          "title": "Title 2"},
         {"icon": browser.runtime.getURL("2.png"),
          "popup": browser.runtime.getURL("2.html"),
+         "title": ""},
+        {"icon": browser.runtime.getURL("2.png"),
+         "popup": browser.runtime.getURL("2.html"),
          "title": "Default T\u00edtulo \u263a"},
       ];
 
       let promiseTabLoad = details => {
         return new Promise(resolve => {
           browser.tabs.onUpdated.addListener(function listener(tabId, changed) {
             if (tabId == details.id && changed.url == details.url) {
               browser.tabs.onUpdated.removeListener(listener);
@@ -119,21 +122,27 @@ add_task(async function testTabSwitchCon
           let promise = promiseTabLoad({id: tabs[1], url: "about:blank?0#ref"});
 
           browser.tabs.update(tabs[1], {url: "about:blank?0#ref"});
 
           await promise;
           expect(details[2]);
         },
         expect => {
-          browser.test.log("Clear the title. Expect default title.");
+          browser.test.log("Set empty title. Expect empty title.");
           browser.pageAction.setTitle({tabId: tabs[1], title: ""});
 
           expect(details[3]);
         },
+        expect => {
+          browser.test.log("Clear the title. Expect default title.");
+          browser.pageAction.setTitle({tabId: tabs[1], title: null});
+
+          expect(details[4]);
+        },
         async expect => {
           browser.test.log("Navigate to a new page. Expect icon hidden.");
 
           // TODO: This listener should not be necessary, but the |tabs.update|
           // callback currently fires too early in e10s windows.
           let promise = promiseTabLoad({id: tabs[1], url: "about:blank?1"});
 
           browser.tabs.update(tabs[1], {url: "about:blank?1"});
@@ -191,16 +200,19 @@ add_task(async function testDefaultTitle
     getTests: function(tabs) {
       let details = [
         {"title": "Foo Extension",
          "popup": "",
          "icon": browser.runtime.getURL("icon.png")},
         {"title": "Foo Title",
          "popup": "",
          "icon": browser.runtime.getURL("icon.png")},
+        {"title": "",
+         "popup": "",
+         "icon": browser.runtime.getURL("icon.png")},
       ];
 
       return [
         expect => {
           browser.test.log("Initial state. No icon visible.");
           expect(null);
         },
         async expect => {
@@ -209,16 +221,21 @@ add_task(async function testDefaultTitle
           expect(details[0]);
         },
         expect => {
           browser.test.log("Change the title. Expect new title.");
           browser.pageAction.setTitle({tabId: tabs[0], title: "Foo Title"});
           expect(details[1]);
         },
         expect => {
+          browser.test.log("Set empty title. Expect empty title.");
+          browser.pageAction.setTitle({tabId: tabs[0], title: ""});
+          expect(details[2]);
+        },
+        expect => {
           browser.test.log("Clear the title. Expect extension title.");
-          browser.pageAction.setTitle({tabId: tabs[0], title: ""});
+          browser.pageAction.setTitle({tabId: tabs[0], title: null});
           expect(details[0]);
         },
       ];
     },
   });
 });
--- a/browser/components/extensions/test/browser/browser_ext_sidebarAction.js
+++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction.js
@@ -29,21 +29,22 @@ let extData = {
         browser.test.sendMessage("sidebar");
       };
     },
   },
 
   background: function() {
     browser.test.onMessage.addListener(async ({msg, data}) => {
       if (msg === "set-panel") {
-        await browser.sidebarAction.setPanel({panel: ""}).then(() => {
-          browser.test.notifyFail("empty panel settable");
-        }).catch(() => {
-          browser.test.notifyPass("unable to set empty panel");
-        });
+        await browser.sidebarAction.setPanel({panel: null});
+        browser.test.assertEq(
+          await browser.sidebarAction.getPanel({}),
+          browser.runtime.getURL("sidebar.html"),
+          "Global panel can be reverted to the default."
+        );
       } else if (msg === "isOpen") {
         let {arg = {}, result} = data;
         let isOpen = await browser.sidebarAction.isOpen(arg);
         browser.test.assertEq(result, isOpen, "expected value from isOpen");
       }
       browser.test.sendMessage("done");
     });
   },
@@ -91,17 +92,16 @@ add_task(async function sidebar_two_side
 
 add_task(async function sidebar_empty_panel() {
   let extension = ExtensionTestUtils.loadExtension(extData);
   await extension.startup();
   // Test sidebar is opened on install
   await extension.awaitMessage("sidebar");
   ok(!document.getElementById("sidebar-box").hidden, "sidebar box is visible in first window");
   await sendMessage(extension, "set-panel");
-  await extension.awaitFinish();
   await extension.unload();
 });
 
 add_task(async function sidebar_isOpen() {
   info("Load extension1");
   let extension1 = ExtensionTestUtils.loadExtension(extData);
   await extension1.startup();
 
--- a/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js
@@ -293,17 +293,17 @@ add_task(async function testTabSwitchCon
           browser.test.log("Change tab panel.");
           let tabId = tabs[0];
           await browser.sidebarAction.setPanel({tabId, panel: "2.html"});
           expect(details[6]);
         },
         async expect => {
           browser.test.log("Revert tab panel.");
           let tabId = tabs[0];
-          await browser.sidebarAction.setPanel({tabId, panel: ""});
+          await browser.sidebarAction.setPanel({tabId, panel: null});
           expect(details[4]);
         },
       ];
     },
   });
 });
 
 add_task(async function testDefaultTitle() {
@@ -319,72 +319,176 @@ add_task(async function testDefaultTitle
       "permissions": ["tabs"],
     },
 
     files: {
       "sidebar.html": sidebar,
       "icon.png": imageBuffer,
     },
 
-    getTests: function(tabs, expectDefaults) {
+    getTests: function(tabs, expectGlobals) {
       let details = [
         {"title": "Foo Extension",
          "panel": browser.runtime.getURL("sidebar.html"),
          "icon": browser.runtime.getURL("icon.png")},
         {"title": "Foo Title",
          "panel": browser.runtime.getURL("sidebar.html"),
          "icon": browser.runtime.getURL("icon.png")},
         {"title": "Bar Title",
          "panel": browser.runtime.getURL("sidebar.html"),
          "icon": browser.runtime.getURL("icon.png")},
-        {"title": "",
-         "panel": browser.runtime.getURL("sidebar.html"),
-         "icon": browser.runtime.getURL("icon.png")},
       ];
 
       return [
         async expect => {
-          browser.test.log("Initial state. Expect extension title as default title.");
+          browser.test.log("Initial state. Expect default extension title.");
 
-          await expectDefaults(details[0]);
+          await expectGlobals(details[0]);
           expect(details[0]);
         },
         async expect => {
-          browser.test.log("Change the title. Expect new title.");
+          browser.test.log("Change the tab title. Expect new title.");
           browser.sidebarAction.setTitle({tabId: tabs[0], title: "Foo Title"});
 
-          await expectDefaults(details[0]);
+          await expectGlobals(details[0]);
           expect(details[1]);
         },
         async expect => {
-          browser.test.log("Change the default. Expect same properties.");
+          browser.test.log("Change the global title. Expect same properties.");
           browser.sidebarAction.setTitle({title: "Bar Title"});
 
-          await expectDefaults(details[2]);
+          await expectGlobals(details[2]);
           expect(details[1]);
         },
         async expect => {
-          browser.test.log("Clear the title. Expect new default title.");
-          browser.sidebarAction.setTitle({tabId: tabs[0], title: ""});
+          browser.test.log("Clear the tab title. Expect new global title.");
+          browser.sidebarAction.setTitle({tabId: tabs[0], title: null});
 
-          await expectDefaults(details[2]);
+          await expectGlobals(details[2]);
           expect(details[2]);
         },
         async expect => {
-          browser.test.log("Set default title to null string. Expect null string from API, extension title in UI.");
-          browser.sidebarAction.setTitle({title: ""});
+          browser.test.log("Clear the global title. Expect default title.");
+          browser.sidebarAction.setTitle({title: null});
 
-          await expectDefaults(details[3]);
-          expect(details[3]);
+          await expectGlobals(details[0]);
+          expect(details[0]);
         },
         async expect => {
           browser.test.assertRejects(
             browser.sidebarAction.setPanel({panel: "about:addons"}),
             /Access denied for URL about:addons/,
             "unable to set panel to about:addons");
 
-          await expectDefaults(details[3]);
-          expect(details[3]);
+          await expectGlobals(details[0]);
+          expect(details[0]);
         },
       ];
     },
   });
 });
+
+add_task(async function testPropertyRemoval() {
+  await runTests({
+    manifest: {
+      "name": "Foo Extension",
+
+      "sidebar_action": {
+        "default_icon": "default.png",
+        "default_panel": "default.html",
+        "default_title": "Default Title",
+      },
+
+      "permissions": ["tabs"],
+    },
+
+    files: {
+      "default.html": sidebar,
+      "p1.html": sidebar,
+      "p2.html": sidebar,
+      "p3.html": sidebar,
+      "default.png": imageBuffer,
+      "i1.png": imageBuffer,
+      "i2.png": imageBuffer,
+      "i3.png": imageBuffer,
+    },
+
+    getTests: function(tabs, expectGlobals) {
+      let defaultIcon = "chrome://browser/content/extension.svg";
+      let details = [
+        {"icon": browser.runtime.getURL("default.png"),
+         "panel": browser.runtime.getURL("default.html"),
+         "title": "Default Title"},
+        {"icon": browser.runtime.getURL("i1.png"),
+         "panel": browser.runtime.getURL("p1.html"),
+         "title": "t1"},
+        {"icon": browser.runtime.getURL("i2.png"),
+         "panel": browser.runtime.getURL("p2.html"),
+         "title": "t2"},
+        {"icon": defaultIcon,
+         "panel": browser.runtime.getURL("p1.html"),
+         "title": ""},
+        {"icon": browser.runtime.getURL("i3.png"),
+         "panel": browser.runtime.getURL("p3.html"),
+         "title": "t3"},
+      ];
+
+      return [
+        async expect => {
+          browser.test.log("Initial state, expect default properties.");
+          await expectGlobals(details[0]);
+          expect(details[0]);
+        },
+        async expect => {
+          browser.test.log("Set global values, expect the new values.");
+          browser.sidebarAction.setIcon({path: "i1.png"});
+          browser.sidebarAction.setPanel({panel: "p1.html"});
+          browser.sidebarAction.setTitle({title: "t1"});
+          await expectGlobals(details[1]);
+          expect(details[1]);
+        },
+        async expect => {
+          browser.test.log("Set tab values, expect the new values.");
+          let tabId = tabs[0];
+          browser.sidebarAction.setIcon({tabId, path: "i2.png"});
+          browser.sidebarAction.setPanel({tabId, panel: "p2.html"});
+          browser.sidebarAction.setTitle({tabId, title: "t2"});
+          await expectGlobals(details[1]);
+          expect(details[2]);
+        },
+        async expect => {
+          browser.test.log("Set empty tab values.");
+          let tabId = tabs[0];
+          browser.sidebarAction.setIcon({tabId, path: ""});
+          browser.sidebarAction.setPanel({tabId, panel: ""});
+          browser.sidebarAction.setTitle({tabId, title: ""});
+          await expectGlobals(details[1]);
+          expect(details[3]);
+        },
+        async expect => {
+          browser.test.log("Remove tab values, expect global values.");
+          let tabId = tabs[0];
+          browser.sidebarAction.setIcon({tabId, path: null});
+          browser.sidebarAction.setPanel({tabId, panel: null});
+          browser.sidebarAction.setTitle({tabId, title: null});
+          await expectGlobals(details[1]);
+          expect(details[1]);
+        },
+        async expect => {
+          browser.test.log("Change global values, expect the new values.");
+          browser.sidebarAction.setIcon({path: "i3.png"});
+          browser.sidebarAction.setPanel({panel: "p3.html"});
+          browser.sidebarAction.setTitle({title: "t3"});
+          await expectGlobals(details[4]);
+          expect(details[4]);
+        },
+        async expect => {
+          browser.test.log("Remove global values, expect defaults.");
+          browser.sidebarAction.setIcon({path: null});
+          browser.sidebarAction.setPanel({panel: null});
+          browser.sidebarAction.setTitle({title: null});
+          await expectGlobals(details[0]);
+          expect(details[0]);
+        },
+      ];
+    },
+  });
+});
--- a/browser/components/extensions/test/browser/browser_ext_user_events.js
+++ b/browser/components/extensions/test/browser/browser_ext_user_events.js
@@ -13,27 +13,28 @@ add_task(async function testSources() {
           browser.test.sendMessage("request", {success: true, result});
         } catch (err) {
           browser.test.sendMessage("request", {success: false, errmsg: err.message});
         }
       }
 
       let tabs = await browser.tabs.query({active: true, currentWindow: true});
       await browser.pageAction.show(tabs[0].id);
-      browser.test.sendMessage("page-action-shown");
 
       browser.pageAction.onClicked.addListener(request);
       browser.browserAction.onClicked.addListener(request);
 
       browser.contextMenus.create({
         id: "menu",
         title: "test user events",
         contexts: ["page"],
       });
       browser.contextMenus.onClicked.addListener(request);
+
+      browser.test.sendMessage("actions-ready");
     },
 
     manifest: {
       browser_action: {default_title: "test"},
       page_action: {default_title: "test"},
       permissions: ["contextMenus"],
       optional_permissions: ["cookies"],
     },
@@ -44,18 +45,18 @@ add_task(async function testSources() {
     ok(result.success, `request() did not throw when called from ${what}`);
     is(result.result, true, `request() succeeded when called from ${what}`);
   }
 
   // Remove Sidebar button to prevent pushing extension button to overflow menu
   CustomizableUI.removeWidgetFromArea("sidebar-button");
 
   await extension.startup();
+  await extension.awaitMessage("actions-ready");
 
-  await extension.awaitMessage("page-action-shown");
   clickPageAction(extension);
   await check("page action click");
 
   clickBrowserAction(extension);
   await check("browser action click");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
   gBrowser.selectedTab = tab;
--- a/browser/components/migration/EdgeProfileMigrator.js
+++ b/browser/components/migration/EdgeProfileMigrator.js
@@ -390,17 +390,18 @@ EdgeProfileMigrator.prototype.getLastUse
     });
   });
   datePromises.push(new Promise(resolve => {
     let typedURLs = new Map();
     try {
       typedURLs = MSMigrationUtils.getTypedURLs(kEdgeRegistryRoot);
     } catch (ex) {}
     let times = [0, ...typedURLs.values()];
-    resolve(Math.max.apply(Math, times));
+    // dates is an array of PRTimes, which are in microseconds - convert to milliseconds
+    resolve(Math.max.apply(Math, times) / 1000);
   }));
   return Promise.all(datePromises).then(dates => {
     return new Date(Math.max.apply(Math, dates));
   });
 };
 
 /* Somewhat counterintuitively, this returns:
  * - |null| to indicate "There is only 1 (default) profile" (on win10+)
--- a/browser/components/migration/IEProfileMigrator.js
+++ b/browser/components/migration/IEProfileMigrator.js
@@ -365,17 +365,18 @@ IEProfileMigrator.prototype.getLastUsedD
     });
   });
   datePromises.push(new Promise(resolve => {
     let typedURLs = new Map();
     try {
       typedURLs = MSMigrationUtils.getTypedURLs("Software\\Microsoft\\Internet Explorer");
     } catch (ex) {}
     let dates = [0, ...typedURLs.values()];
-    resolve(Math.max.apply(Math, dates));
+    // dates is an array of PRTimes, which are in microseconds - convert to milliseconds
+    resolve(Math.max.apply(Math, dates) / 1000);
   }));
   return Promise.all(datePromises).then(dates => {
     return new Date(Math.max.apply(Math, dates));
   });
 };
 
 Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
   get: function IE_get_sourceHomePageURL() {
--- a/browser/components/migration/MSMigrationUtils.jsm
+++ b/browser/components/migration/MSMigrationUtils.jsm
@@ -667,17 +667,18 @@ function getTypedURLs(registryKeyPath) {
                            registryKeyPath + "\\TypedURLsTime",
                            Ci.nsIWindowsRegKey.ACCESS_READ);
     } catch (ex) {
       typedURLTimeKey = null;
     }
     let entryName;
     for (let entry = 1; typedURLKey.hasValue((entryName = "url" + entry)); entry++) {
       let url = typedURLKey.readStringValue(entryName);
-      let timeTyped = 0;
+      // If we can't get a date for whatever reason, default to 6 months ago
+      let timeTyped = Date.now() - 31536000 / 2;
       if (typedURLTimeKey && typedURLTimeKey.hasValue(entryName)) {
         let urlTime = "";
         try {
           urlTime = typedURLTimeKey.readBinaryValue(entryName);
         } catch (ex) {
           Cu.reportError("Couldn't read url time for " + entryName);
         }
         if (urlTime.length == 8) {
@@ -687,26 +688,30 @@ function getTypedURLs(registryKeyPath) {
             if (c.length == 1)
               c = "0" + c;
             urlTimeHex.unshift(c);
           }
           try {
             let hi = parseInt(urlTimeHex.slice(0, 4).join(""), 16);
             let lo = parseInt(urlTimeHex.slice(4, 8).join(""), 16);
             // Convert to seconds since epoch:
-            timeTyped = cTypes.fileTimeToSecondsSinceEpoch(hi, lo);
-            // Callers expect PRTime, which is microseconds since epoch:
-            timeTyped *= 1000 * 1000;
+            let secondsSinceEpoch = cTypes.fileTimeToSecondsSinceEpoch(hi, lo);
+
+            // If the date is very far in the past, just use the default
+            if (secondsSinceEpoch > Date.now() / 1000000) {
+              // Callers expect PRTime, which is microseconds since epoch:
+              timeTyped = secondsSinceEpoch * 1000;
+            }
           } catch (ex) {
             // Ignore conversion exceptions. Callers will have to deal
-            // with the fallback value (0).
+            // with the fallback value.
           }
         }
       }
-      typedURLs.set(url, timeTyped);
+      typedURLs.set(url, timeTyped * 1000);
     }
   } catch (ex) {
     Cu.reportError("Error reading typed URL history: " + ex);
   } finally {
     if (typedURLKey) {
       typedURLKey.close();
     }
     if (typedURLTimeKey) {
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -435,17 +435,17 @@ BrowserGlue.prototype = {
           Object.defineProperty(this, "AlertsService", {
             value: subject.wrappedJSObject
           });
         } else if (data == "places-browser-init-complete") {
           if (this._placesBrowserInitComplete) {
             Services.obs.notifyObservers(null, "places-browser-init-complete");
           }
         } else if (data == "migrateMatchBucketsPrefForUIVersion60") {
-          this._migrateMatchBucketsPrefForUIVersion60();
+          this._migrateMatchBucketsPrefForUIVersion60(true);
         }
         break;
       case "initial-migration-will-import-default-bookmarks":
         this._migrationImportsDefaultBookmarks = true;
         break;
       case "initial-migration-did-import-default-bookmarks":
         this._initPlaces(true);
         break;
@@ -2346,28 +2346,48 @@ BrowserGlue.prototype = {
                         .add(promptCount);
     } catch (ex) { /* Don't break the default prompt if telemetry is broken. */ }
 
     if (willPrompt) {
       DefaultBrowserCheck.prompt(RecentWindow.getMostRecentBrowserWindow());
     }
   },
 
-  _migrateMatchBucketsPrefForUIVersion60() {
+  _migrateMatchBucketsPrefForUIVersion60(forceCheck = false) {
+    function check() {
+      if (CustomizableUI.getPlacementOfWidget("search-container")) {
+        Services.prefs.setCharPref(prefName,
+                                   "general:5,suggestion:Infinity");
+      }
+    }
     let prefName = "browser.urlbar.matchBuckets";
     let pref = Services.prefs.getCharPref(prefName, "");
     if (!pref) {
       // Set the pref based on the search bar's current placement.  If it's
       // placed (the urlbar and search bar are not unified), then set the pref
       // (so that history results will come before search suggestions).  If it's
       // not placed (the urlbar and search bar are unified), then leave the pref
       // cleared so that UnifiedComplete.js uses the default value (so that
       // search suggestions will come before history results).
-      if (CustomizableUI.getPlacementOfWidget("search-container")) {
-        Services.prefs.setCharPref(prefName, "general:5,suggestion:Infinity");
+      if (forceCheck) {
+        // This is the case when this is called by the test.
+        check();
+      } else {
+        // This is the normal, non-test case.  At this point the first window
+        // has not been set up yet, so use a CUI listener to get the placement
+        // when the nav-bar is first registered.
+        let listener = {
+          onAreaNodeRegistered(area, container) {
+            if (CustomizableUI.AREA_NAVBAR == area) {
+              check();
+              CustomizableUI.removeListener(listener);
+            }
+          },
+        };
+        CustomizableUI.addListener(listener);
       }
     }
     // Else, the pref has already been set.  Normally this pref does not exist.
     // Either the user customized it, or they were enrolled in the Shield study
     // in Firefox 57 that effectively already migrated the pref.  Either way,
     // leave it at its current value.
   },
 
--- a/browser/components/preferences/in-content/findInPage.js
+++ b/browser/components/preferences/in-content/findInPage.js
@@ -298,17 +298,17 @@ var gSearchResultsPane = {
         let strings = this.strings;
 
         document.getElementById("sorry-message").textContent = AppConstants.platform == "win" ?
           strings.getFormattedString("searchResults.sorryMessageWin", [this.query]) :
           strings.getFormattedString("searchResults.sorryMessageUnix", [this.query]);
         let helpUrl = Services.urlFormatter.formatURLPref("app.support.baseURL") + "preferences";
         let brandName = document.getElementById("bundleBrand").getString("brandShortName");
         let helpString = strings.getString("searchResults.needHelp3");
-        let helpItems = helpString.split(/%(?:\$1)?S/);
+        let helpItems = helpString.split(/%(?:1\$)?S/);
         let helpContainer = document.getElementById("need-help");
         helpContainer.innerHTML = "";
         helpContainer.appendChild(document.createTextNode(helpItems[0]));
         let link = document.createElement("label");
         link.className = "text-link";
         link.setAttribute("href", helpUrl);
         link.textContent = strings.getFormattedString("searchResults.needHelpSupportLink", [brandName]);
         helpContainer.appendChild(link);
--- a/browser/components/resistfingerprinting/test/browser/browser_performanceAPI.js
+++ b/browser/components/resistfingerprinting/test/browser/browser_performanceAPI.js
@@ -55,16 +55,38 @@ let isRounded = (x, expectedPrecision) =
 
   ok(false, "Looming Test Failure, Additional Debugging Info: Expected Precision: " + expectedPrecision + " Measured Value: " + x +
     " Rounded Vaue: " + rounded + " Fuzzy1: " + Math.abs(rounded - x + expectedPrecision) +
     " Fuzzy 2: " + Math.abs(rounded - x));
 
   return false;
 };
 
+let setupTest = async function(tab, resistFingerprinting, reduceTimerPrecision, expectedPrecision, runTests, workerCall) {
+  await SpecialPowers.pushPrefEnv({"set":
+    [["privacy.resistFingerprinting", resistFingerprinting],
+     ["privacy.reduceTimerPrecision", reduceTimerPrecision],
+     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+     ]
+  });
+  // No matter what we set the precision to, if we're in ResistFingerprinting mode
+  // we use the larger of the precision pref and the constant 100ms
+  if (resistFingerprinting) {
+    expectedPrecision = expectedPrecision < 100 ? 100 : expectedPrecision;
+  }
+  await ContentTask.spawn(tab.linkedBrowser, {
+      list: PERFORMANCE_TIMINGS,
+      precision: expectedPrecision,
+      isRoundedFunc: isRounded.toString(),
+      workerCall
+    },
+    runTests);
+};
+// ================================================================================================
+// ================================================================================================
 add_task(async function runRPTests() {
   let tab = await BrowserTestUtils.openNewForegroundTab(
     gBrowser, TEST_PATH + "file_dummy.html");
 
   let runTests = async function(data) {
     let timerlist = data.list;
     let expectedPrecision = data.precision;
     // eslint beleives that isrounded is available in this scope, but if you
@@ -86,98 +108,25 @@ add_task(async function runRPTests() {
 
     // Check that no entries for performance.getEntries/getEntriesByType/getEntriesByName.
     is(content.performance.getEntries().length, 0, "For resistFingerprinting, there should be no entries for performance.getEntries()");
     is(content.performance.getEntriesByType("resource").length, 0, "For resistFingerprinting, there should be no entries for performance.getEntriesByType()");
     is(content.performance.getEntriesByName("Test", "mark").length, 0, "For resistFingerprinting, there should be no entries for performance.getEntriesByName()");
 
   };
 
-  let expectedPrecision = 100;
-  await SpecialPowers.pushPrefEnv({"set":
-    // Run one set of tests with both true to confirm p.rP overrides p.rTP
-    [["privacy.resistFingerprinting", true],
-     ["privacy.reduceTimerPrecision", true],
-     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-     ]
-  });
-  await ContentTask.spawn(tab.linkedBrowser, { list: PERFORMANCE_TIMINGS, precision: expectedPrecision, isRoundedFunc: isRounded.toString() }, runTests);
-
-  expectedPrecision = 13;
-  await SpecialPowers.pushPrefEnv({"set":
-    // Run one set of tests with both true to confirm p.rP overrides p.rTP
-    [["privacy.resistFingerprinting", true],
-     ["privacy.reduceTimerPrecision", true],
-     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-     ]
-  });
-  await ContentTask.spawn(tab.linkedBrowser, { list: PERFORMANCE_TIMINGS, precision: expectedPrecision, isRoundedFunc: isRounded.toString() }, runTests);
-
-  expectedPrecision = .13;
-  await SpecialPowers.pushPrefEnv({"set":
-    // Run one set of tests with both true to confirm p.rP overrides p.rTP
-    [["privacy.resistFingerprinting", true],
-     ["privacy.reduceTimerPrecision", true],
-     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-     ]
-  });
-  await ContentTask.spawn(tab.linkedBrowser, { list: PERFORMANCE_TIMINGS, precision: expectedPrecision, isRoundedFunc: isRounded.toString() }, runTests);
+  await setupTest(tab, true, true, 100, runTests);
+  await setupTest(tab, true, false, 13, runTests);
+  await setupTest(tab, true, false, .13, runTests);
 
   await BrowserTestUtils.removeTab(tab);
 });
 
-add_task(async function runRPTestsForWorker() {
-  let tab = await BrowserTestUtils.openNewForegroundTab(
-    gBrowser, TEST_PATH + "file_dummy.html");
-
-  let runTest = async function(expectedPrecision) {
-    await new Promise(resolve => {
-      let worker = new content.Worker("file_workerPerformance.js");
-      worker.onmessage = function(e) {
-        if (e.data.type == "status") {
-          ok(e.data.status, e.data.msg);
-        } else if (e.data.type == "finish") {
-          resolve();
-        } else {
-          ok(false, "Unknown message type");
-          resolve();
-        }
-      };
-      worker.postMessage({type: "runRPTests", precision: expectedPrecision});
-    });
-  };
-
-  let expectedPrecision = 100;
-    // Run one set of tests with both true to confirm p.rP overrides p.rTP
-  await SpecialPowers.pushPrefEnv({"set":
-    [["privacy.resistFingerprinting", true],
-     ["privacy.reduceTimerPrecision", true],
-     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]]
-  });
-  await ContentTask.spawn(tab.linkedBrowser, expectedPrecision, runTest);
-
-  expectedPrecision = 13;
-  await SpecialPowers.pushPrefEnv({"set":
-    [["privacy.resistFingerprinting", true],
-     ["privacy.reduceTimerPrecision", false],
-     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]]
-  });
-  await ContentTask.spawn(tab.linkedBrowser, expectedPrecision, runTest);
-
-  expectedPrecision = .13;
-  await SpecialPowers.pushPrefEnv({"set":
-    [["privacy.resistFingerprinting", true],
-     ["privacy.reduceTimerPrecision", false],
-     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]]
-  });
-  await ContentTask.spawn(tab.linkedBrowser, expectedPrecision, runTest);
-
-  await BrowserTestUtils.removeTab(tab);
-});
-
+// ================================================================================================
+// ================================================================================================
 add_task(async function runRTPTests() {
   let tab = await BrowserTestUtils.openNewForegroundTab(
     gBrowser, TEST_PATH + "file_dummy.html");
 
   let runTests = async function(data) {
     let timerlist = data.list;
     let expectedPrecision = data.precision;
     // eslint beleives that isrounded is available in this scope, but if you
@@ -207,84 +156,57 @@ add_task(async function runRTPTests() {
     }
     is(content.performance.getEntriesByType("mark").length, 2, "For reduceTimerPrecision, there should be 2 entries for performance.getEntriesByType()");
     is(content.performance.getEntriesByName("Test", "mark").length, 1, "For reduceTimerPrecision, there should be 1 entry for performance.getEntriesByName()");
     content.performance.clearMarks();
     content.performance.clearMeasures();
     content.performance.clearResourceTimings();
   };
 
-  let expectedPrecision = 100;
-  await SpecialPowers.pushPrefEnv({"set":
-    [["privacy.resistFingerprinting", false],
-     ["privacy.reduceTimerPrecision", true],
-     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-    ]
-    });
-  await ContentTask.spawn(tab.linkedBrowser, { list: PERFORMANCE_TIMINGS, precision: expectedPrecision, isRoundedFunc: isRounded.toString() }, runTests);
-
-  expectedPrecision = 13;
-  await SpecialPowers.pushPrefEnv({"set":
-    [["privacy.resistFingerprinting", false],
-     ["privacy.reduceTimerPrecision", true],
-     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-    ]
-  });
-  await ContentTask.spawn(tab.linkedBrowser, { list: PERFORMANCE_TIMINGS, precision: expectedPrecision, isRoundedFunc: isRounded.toString() }, runTests);
-
-  expectedPrecision = .13;
-  await SpecialPowers.pushPrefEnv({"set":
-    [["privacy.resistFingerprinting", false],
-     ["privacy.reduceTimerPrecision", true],
-     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-    ]
-  });
-  await ContentTask.spawn(tab.linkedBrowser, { list: PERFORMANCE_TIMINGS, precision: expectedPrecision, isRoundedFunc: isRounded.toString() }, runTests);
+  await setupTest(tab, false, true, 100, runTests);
+  await setupTest(tab, false, true, 13, runTests);
+  await setupTest(tab, false, true, .13, runTests);
 
   await BrowserTestUtils.removeTab(tab);
 });
 
-add_task(async function runRTPTestsForWorker() {
-  let tab = await BrowserTestUtils.openNewForegroundTab(
-    gBrowser, TEST_PATH + "file_dummy.html");
-
-  let runTest = async function(expectedPrecision) {
+// ================================================================================================
+// ================================================================================================
+let runWorkerTest = async function(data) {
+  let expectedPrecision = data.precision;
+  let workerCall = data.workerCall;
     await new Promise(resolve => {
       let worker = new content.Worker("file_workerPerformance.js");
       worker.onmessage = function(e) {
         if (e.data.type == "status") {
           ok(e.data.status, e.data.msg);
         } else if (e.data.type == "finish") {
           resolve();
         } else {
           ok(false, "Unknown message type");
           resolve();
         }
       };
-      worker.postMessage({type: "runRTPTests", precision: expectedPrecision});
+    worker.postMessage({type: workerCall, precision: expectedPrecision});
     });
   };
 
-  let expectedPrecision = 100;
-  await SpecialPowers.pushPrefEnv({"set":
-    [["privacy.resistFingerprinting", false],
-     ["privacy.reduceTimerPrecision", true],
-     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]]
-  });
-  await ContentTask.spawn(tab.linkedBrowser, expectedPrecision, runTest);
+add_task(async function runRPTestsForWorker() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(
+    gBrowser, TEST_PATH + "file_dummy.html");
+
+  await setupTest(tab, true, true, 100, runWorkerTest, "runRPTests");
+  await setupTest(tab, true, false, 13, runWorkerTest, "runRPTests");
+  await setupTest(tab, true, true, .13, runWorkerTest, "runRPTests");
 
-  expectedPrecision = 13;
-  await SpecialPowers.pushPrefEnv({"set":
-    [["privacy.resistFingerprinting", false],
-     ["privacy.reduceTimerPrecision", true],
-     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]]
+  await BrowserTestUtils.removeTab(tab);
   });
-  await ContentTask.spawn(tab.linkedBrowser, expectedPrecision, runTest);
 
-  expectedPrecision = .13;
-  await SpecialPowers.pushPrefEnv({"set":
-    [["privacy.resistFingerprinting", false],
-     ["privacy.reduceTimerPrecision", true],
-     ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]]
-  });
-  await ContentTask.spawn(tab.linkedBrowser, expectedPrecision, runTest);
+add_task(async function runRTPTestsForWorker() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(
+    gBrowser, TEST_PATH + "file_dummy.html");
+
+  await setupTest(tab, false, true, 100, runWorkerTest, "runRTPTests");
+  await setupTest(tab, false, true, 13, runWorkerTest, "runRTPTests");
+  await setupTest(tab, false, true, .13, runWorkerTest, "runRTPTests");
+
   await BrowserTestUtils.removeTab(tab);
 });
--- a/browser/components/resistfingerprinting/test/mochitest/file_animation_api.html
+++ b/browser/components/resistfingerprinting/test/mochitest/file_animation_api.html
@@ -61,24 +61,24 @@
     const testDiv = document.getElementById("testDiv");
     const animation = testDiv.animate({ opacity: [0, 1] }, 100000);
     animation.play();
 
     waitForCondition(
       () => animation.currentTime > 100,
         () => {
           opener.ok(isRounded(animation.startTime),
-             "pref: " + opener.currentPref + " - animation.startTime with precision " + expectedPrecision + " is not rounded: " + animation.startTime);
+             "pref: " + opener.prefName + " - animation.startTime with precision " + expectedPrecision + " is not rounded: " + animation.startTime);
           opener.ok(isRounded(animation.currentTime),
-             "pref: " + opener.currentPref + " - animation.currentTime with precision " + expectedPrecision + " is not rounded: " + animation.currentTime);
+             "pref: " + opener.prefName + " - animation.currentTime with precision " + expectedPrecision + " is not rounded: " + animation.currentTime);
           opener.ok(isRounded(animation.timeline.currentTime),
-             "pref: " + opener.currentPref + " - animation.timeline.currentTime with precision " + expectedPrecision + " is not rounded: " + animation.timeline.currentTime);
+             "pref: " + opener.prefName + " - animation.timeline.currentTime with precision " + expectedPrecision + " is not rounded: " + animation.timeline.currentTime);
           if (document.timeline) {
             opener.ok(isRounded(document.timeline.currentTime),
-               "pref: " + opener.currentPref + " - document.timeline.currentTime with precision " + expectedPrecision + " is not rounded: " + document.timeline.currentTime);
+               "pref: " + opener.prefName + " - document.timeline.currentTime with precision " + expectedPrecision + " is not rounded: " + document.timeline.currentTime);
           }
           opener.done();
           window.close();
         },
         "animation failed to start");
   }
 </script>
 </head>
--- a/browser/components/resistfingerprinting/test/mochitest/test_animation_api.html
+++ b/browser/components/resistfingerprinting/test/mochitest/test_animation_api.html
@@ -8,135 +8,71 @@ https://bugzilla.mozilla.org/show_bug.cg
   <title>Test for Bug 1382545</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
   /** Test for Bug 1382545 **/
   SimpleTest.waitForExplicitFinish();
 
-  var currentPref = "";
+  // Used by file_animation_api.html
+  var prefName = "";
   var expectedPrecision = 0;
-  window.onload = () => {
-    currentPref = "privacy.resistFingerprinting";
-    expectedPrecision = 100000;
-    SpecialPowers.pushPrefEnv({"set":
-      [
-        ["privacy.resistFingerprinting", true],
-        ["dom.animations-api.core.enabled", true],
-        ["privacy.reduceTimerPrecision", false],
-        ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
-      ]
-    }, runTest);
-  };
+  var resistFingerprinting = false;
+  var reduceTimerPrecision = false;
 
   function runTest() {
+    // No matter what we set the precision to, if we're in ResistFingerprinting mode
+    // we use the larger of the precision pref and the constant 100ms
+    if (resistFingerprinting) {
+      expectedPrecision = expectedPrecision < 100000 ? 100000 : expectedPrecision;
+    }
     window.open("file_animation_api.html");
   }
 
-  let completed = 0;
-  const numTests = 8;
-  function done() {
-    completed++;
-    if (completed == numTests) {
-      SimpleTest.finish();
-    } else {
-      nextTest();
-    }
+  function setupTest(rfp, rtp, ep) {
+    // Set globals
+    expectedPrecision = ep;
+    resistFingerprinting = rfp;
+    reduceTimerPrecision = rtp;
+    prefName = "";
+    prefName += resistFingerprinting ? "privacy.resistFingerprinting " : "";
+    prefName += reduceTimerPrecision ? "privacy.reduceTimerPrecision " : "";
+    SpecialPowers.pushPrefEnv({"set":
+      [
+        ["dom.animations-api.core.enabled", true],
+        ["privacy.resistFingerprinting", resistFingerprinting],
+        ["privacy.reduceTimerPrecision", reduceTimerPrecision],
+        ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
+      ]
+    }, runTest);
+
   }
 
-  function nextTest() {
-    // ----------------------------------------------
-    if (completed == 1) {
-      currentPref = "privacy.reduceTimerPrecision";
-      expectedPrecision = 100000;
-      SpecialPowers.pushPrefEnv({"set":
-        [
-          ["privacy.resistFingerprinting", false],
-          ["dom.animations-api.core.enabled", true],
-          ["privacy.reduceTimerPrecision", true],
-          ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
-        ]
-      }, runTest);
-    // ----------------------------------------------
-    } else if (completed == 2) {
-      currentPref = "privacy.resistFingerprinting";
-      expectedPrecision = 50000;
-      SpecialPowers.pushPrefEnv({"set":
-        [
-          ["privacy.resistFingerprinting", true],
-          ["dom.animations-api.core.enabled", true],
-          ["privacy.reduceTimerPrecision", false],
-          ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
-        ]
-      }, runTest);
-    // ----------------------------------------------
-    } else if (completed == 3) {
-      currentPref = "privacy.reduceTimerPrecision";
-      expectedPrecision = 50000;
-      SpecialPowers.pushPrefEnv({"set":
-        [
-          ["privacy.resistFingerprinting", false],
-          ["dom.animations-api.core.enabled", true],
-          ["privacy.reduceTimerPrecision", true],
-          ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
-        ]
-      }, runTest);
-    // ----------------------------------------------
-    } else if (completed == 4) {
-      currentPref = "privacy.resistFingerprinting";
-      expectedPrecision = 100;
-      SpecialPowers.pushPrefEnv({"set":
-        [
-          ["privacy.resistFingerprinting", true],
-          ["dom.animations-api.core.enabled", true],
-          ["privacy.reduceTimerPrecision", false],
-          ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
-        ]
-      }, runTest);
-    // ----------------------------------------------
-    } else if (completed == 5) {
-      currentPref = "privacy.reduceTimerPrecision";
-      expectedPrecision = 100;
-      SpecialPowers.pushPrefEnv({"set":
-        [
-          ["privacy.resistFingerprinting", false],
-          ["dom.animations-api.core.enabled", true],
-          ["privacy.reduceTimerPrecision", true],
-          ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
-        ]
-      }, runTest);
-    // ----------------------------------------------
-    } else if (completed == 6) {
-      currentPref = "privacy.resistFingerprinting";
-      expectedPrecision = 13;
-      SpecialPowers.pushPrefEnv({"set":
-        [
-          ["privacy.resistFingerprinting", true],
-          ["dom.animations-api.core.enabled", true],
-          ["privacy.reduceTimerPrecision", false],
-          ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
-        ]
-      }, runTest);
-    // ----------------------------------------------
-    } else if (completed == 7) {
-      currentPref = "privacy.reduceTimerPrecision";
-      expectedPrecision = 13;
-      SpecialPowers.pushPrefEnv({"set":
-        [
-          ["privacy.resistFingerprinting", false],
-          ["dom.animations-api.core.enabled", true],
-          ["privacy.reduceTimerPrecision", true],
-          ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
-        ]
-      }, runTest);
-    // ----------------------------------------------
+  var testIndx = 0;
+  var testSequence = [
+    [true, false, 100000],
+    [false, true, 100000],
+    [true, false, 50000],
+    [false, true, 50000],
+    [true, false, 100],
+    [false, true, 100],
+    [true, true, 13],
+    [false, true, 13],
+  ];
+
+  window.onload = () => {
+    setupTest(testSequence[testIndx][0], testSequence[testIndx][1], testSequence[testIndx][2]);
+  };
+
+  function done() {
+    testIndx++;
+    if (testIndx == testSequence.length) {
+      SimpleTest.finish();
     } else {
-      ok(false, "I seem to have asked for " + numTests +
-         " tests, but don't know how to run them all.");
-      SimpleTest.finish();
+      setupTest(testSequence[testIndx][0], testSequence[testIndx][1], testSequence[testIndx][2]);
     }
   }
   </script>
 </head>
 <body>
 </body>
 </html>
--- a/browser/components/resistfingerprinting/test/mochitest/test_reduce_time_precision.html
+++ b/browser/components/resistfingerprinting/test/mochitest/test_reduce_time_precision.html
@@ -33,16 +33,21 @@ https://trac.torproject.org/projects/tor
   // Known ways to generate time stamps, in milliseconds
   const timeStampCodes = [
     "performance.now()",
     "new Date().getTime()",
     "new Event(\"\").timeStamp",
     "new File([], \"\").lastModified",
     "new File([], \"\").lastModifiedDate.getTime()",
   ];
+  // These are measured in seconds, so we need to scale them up
+  var timeStampCodesDOM = timeStampCodes.concat([
+    "audioContext.currentTime * 1000",
+    "canvasStream.currentTime * 1000",
+  ]);
 
   let isRounded = (x, expectedPrecision) => {
     let rounded = (Math.floor(x / expectedPrecision) * expectedPrecision);
     // First we do the perfectly normal check that should work just fine
     if (rounded === x || x === 0)
       return true;
 
     // When we're diving by non-whole numbers, we may not get perfect
@@ -64,29 +69,31 @@ https://trac.torproject.org/projects/tor
 
     ok(false, "Looming Test Failure, Additional Debugging Info: Expected Precision: " + expectedPrecision + " Measured Value: " + x +
       " Rounded Vaue: " + rounded + " Fuzzy1: " + Math.abs(rounded - x + expectedPrecision) +
       " Fuzzy 2: " + Math.abs(rounded - x));
 
     return false;
   };
 
+  // ================================================================================================
+  // ================================================================================================
   async function checkWorker(worker, prefname, expectedPrecision) {
     // The child worker will send the results back.
     let checkWorkerTimeStamps = () => new Promise(function(resolve) {
       let onMessage = function(event) {
         worker.removeEventListener("message", onMessage);
 
         let timeStamps = event.data;
         for (let i = 0; i < timeStampCodes.length; i++) {
           let timeStamp = timeStamps[i];
           ok(isRounded(timeStamp, expectedPrecision),
             "pref: " + prefname + " - '" +
              "'" + timeStampCodes[i] +
-             "' should be rounded to nearest " + expectedPrecision + " us in workers; saw " +
+             "' should be rounded to nearest " + expectedPrecision + " ms in workers; saw " +
              timeStamp);
         }
         resolve();
       };
       worker.addEventListener("message", onMessage);
     });
 
     // Send the codes to its child worker.
@@ -95,207 +102,98 @@ https://trac.torproject.org/projects/tor
     // First, check the child's results.
     await checkWorkerTimeStamps();
     // Then, check the grandchild's results.
     await checkWorkerTimeStamps();
 
     worker.terminate();
   }
 
-  function checkTimestamps(pref, expectedPrecision) {
-    // These are measured in seconds, so we need to scale them up
-    let timeStampCodesDOM = timeStampCodes.concat([
-      "audioContext.currentTime * 1000",
-      "canvasStream.currentTime * 1000",
-    ]);
+  async function testWorker(resistFingerprinting, reduceTimerPrecision, expectedPrecision) {
+    let prefname = "";
+    prefname += resistFingerprinting ? "privacy.resistFingerprinting " : "";
+    prefname += reduceTimerPrecision ? "privacy.reduceTimerPrecision " : "";
+    // Create one worker before setting the pref, and one after, in order to
+    // check that the resolution is updated whether or not the worker was
+    // already started
+    let worker1 = new Worker("worker_child.js");
+    await SpecialPowers.pushPrefEnv({
+      "set": [["privacy.resistFingerprinting", resistFingerprinting],
+              ["privacy.reduceTimerPrecision", reduceTimerPrecision],
+              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+              ]});
+
+    // No matter what we set the precision to, if we're in ResistFingerprinting mode
+    // we use the larger of the precision pref and the constant 100ms
+    if (resistFingerprinting) {
+      expectedPrecision = expectedPrecision < 100 ? 100 : expectedPrecision;
+    }
+
+    let worker2 = new Worker("worker_child.js");
+    // Allow ~550 ms to elapse, so we can get non-zero
+    // time values for all elements.
+    await new Promise(resolve => window.setTimeout(resolve, 550));
+    await checkWorker(worker1, prefname, expectedPrecision);
+    await checkWorker(worker2, prefname, expectedPrecision);
+  }
+
+  add_task(async function testWorkerRFP() {
+    await testWorker(true, false, 100);
+    await testWorker(true, false, 13);
+    await testWorker(true, false, .13);
+  });
+
+  add_task(async function testWorkerRTP() {
+    await testWorker(false, true, 100);
+    await testWorker(false, true, 13);
+    await testWorker(false, true, .13);
+  });
+
+  // ================================================================================================
+  // ================================================================================================
+  async function testDOM(resistFingerprinting, reduceTimerPrecision, expectedPrecision) {
+    let prefname = "";
+    prefname += resistFingerprinting ? "privacy.resistFingerprinting " : "";
+    prefname += reduceTimerPrecision ? "privacy.reduceTimerPrecision " : "";
+
+    await SpecialPowers.pushPrefEnv({
+      "set": [["privacy.resistFingerprinting", resistFingerprinting],
+              ["privacy.reduceTimerPrecision", reduceTimerPrecision],
+              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
+              ]});
+
+    // No matter what we set the precision to, if we're in ResistFingerprinting mode
+    // we use the larger of the precision pref and the constant 100ms
+    if (resistFingerprinting) {
+      expectedPrecision = expectedPrecision < 100 ? 100 : expectedPrecision;
+    }
+
     // Loop through each timeStampCode, evaluate it,
     // and check if it is rounded
     for (let timeStampCode of timeStampCodesDOM) {
       let timeStamp = eval(timeStampCode);
       ok(isRounded(timeStamp, expectedPrecision),
-        "pref: " + pref + " - '" +
+        "pref: " + prefname + " - '" +
          "'" + timeStampCode +
          "' should be rounded to nearest " +
-         expectedPrecision + " us; saw " +
+         expectedPrecision + " ms; saw " +
          timeStamp);
     }
   }
 
-
-  add_task(async function testWorkerRFP1() {
-    // Create one worker before setting the pref, and one after, in order to
-    // check that the resolution is updated whether or not the worker was
-    // already started
-    let expectedPrecision = 100;
-    let worker1 = new Worker("worker_child.js");
-    await SpecialPowers.pushPrefEnv({
-      "set": [["privacy.resistFingerprinting", true],
-              ["privacy.reduceTimerPrecision", false],
-              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-              ]});
-    let worker2 = new Worker("worker_child.js");
-    // Allow ~550 ms to elapse, so we can get non-zero
-    // time values for all elements.
-    await new Promise(resolve => window.setTimeout(resolve, 550));
-    await checkWorker(worker1, "privacy.resistFingerprinting", expectedPrecision);
-    await checkWorker(worker2, "privacy.resistFingerprinting", expectedPrecision);
-  });
-
-  add_task(async function testWorkerRFP2() {
-    // Create one worker before setting the pref, and one after, in order to
-    // check that the resolution is updated whether or not the worker was
-    // already started
-    let expectedPrecision = 13;
-    let worker1 = new Worker("worker_child.js");
-    await SpecialPowers.pushPrefEnv({
-      "set": [["privacy.resistFingerprinting", true],
-              ["privacy.reduceTimerPrecision", false],
-              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-              ]});
-    let worker2 = new Worker("worker_child.js");
-    // Allow ~550 ms to elapse, so we can get non-zero
-    // time values for all elements.
-    await new Promise(resolve => window.setTimeout(resolve, 550));
-    await checkWorker(worker1, "privacy.resistFingerprinting", expectedPrecision);
-    await checkWorker(worker2, "privacy.resistFingerprinting", expectedPrecision);
-  });
-
-  add_task(async function testWorkerRFP3() {
-    // Create one worker before setting the pref, and one after, in order to
-    // check that the resolution is updated whether or not the worker was
-    // already started
-    let expectedPrecision = .13;
-    let worker1 = new Worker("worker_child.js");
-    await SpecialPowers.pushPrefEnv({
-      "set": [["privacy.resistFingerprinting", true],
-              ["privacy.reduceTimerPrecision", false],
-              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-              ]});
-    let worker2 = new Worker("worker_child.js");
-    // Allow ~550 ms to elapse, so we can get non-zero
-    // time values for all elements.
-    await new Promise(resolve => window.setTimeout(resolve, 550));
-    await checkWorker(worker1, "privacy.resistFingerprinting", expectedPrecision);
-    await checkWorker(worker2, "privacy.resistFingerprinting", expectedPrecision);
-  });
-
-  add_task(async function testWorkerRTP1() {
-    // Create one worker before setting the pref, and one after, in order to
-    // check that the resolution is updated whether or not the worker was
-    // already started
-    let expectedPrecision = 100;
-    let worker1 = new Worker("worker_child.js");
-    await SpecialPowers.pushPrefEnv({
-      "set": [["privacy.resistFingerprinting", false],
-              ["privacy.reduceTimerPrecision", true],
-              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-              ]});
-    let worker2 = new Worker("worker_child.js");
-    // Allow ~550 ms to elapse, so we can get non-zero
-    // time values for all elements.
-    await new Promise(resolve => window.setTimeout(resolve, 550));
-    await checkWorker(worker1, "privacy.reduceTimerPrecision", expectedPrecision);
-    await checkWorker(worker2, "privacy.reduceTimerPrecision", expectedPrecision);
+  add_task(async function testDOMRFP() {
+    await testDOM(true, true, 100);
+    await testDOM(true, false, 13);
+    await testDOM(true, false, .13);
   });
 
-  add_task(async function testWorkerRTP2() {
-    // Create one worker before setting the pref, and one after, in order to
-    // check that the resolution is updated whether or not the worker was
-    // already started
-    let expectedPrecision = 13;
-    let worker1 = new Worker("worker_child.js");
-    await SpecialPowers.pushPrefEnv({
-      "set": [["privacy.resistFingerprinting", false],
-              ["privacy.reduceTimerPrecision", true],
-              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-              ]});
-    let worker2 = new Worker("worker_child.js");
-    // Allow ~550 ms to elapse, so we can get non-zero
-    // time values for all elements.
-    await new Promise(resolve => window.setTimeout(resolve, 550));
-    await checkWorker(worker1, "privacy.reduceTimerPrecision", expectedPrecision);
-    await checkWorker(worker2, "privacy.reduceTimerPrecision", expectedPrecision);
-  });
-
-  add_task(async function testWorkerRTP3() {
-    // Create one worker before setting the pref, and one after, in order to
-    // check that the resolution is updated whether or not the worker was
-    // already started
-    let expectedPrecision = .13;
-    let worker1 = new Worker("worker_child.js");
-    await SpecialPowers.pushPrefEnv({
-      "set": [["privacy.resistFingerprinting", false],
-              ["privacy.reduceTimerPrecision", true],
-              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-              ]});
-    let worker2 = new Worker("worker_child.js");
-    // Allow ~550 ms to elapse, so we can get non-zero
-    // time values for all elements.
-    await new Promise(resolve => window.setTimeout(resolve, 550));
-    await checkWorker(worker1, "privacy.reduceTimerPrecision", expectedPrecision);
-    await checkWorker(worker2, "privacy.reduceTimerPrecision", expectedPrecision);
-  });
-
-  add_task(async function testDOMRFP1() {
-    let expectedPrecision = 100;
-    await SpecialPowers.pushPrefEnv({
-      "set": [["privacy.resistFingerprinting", true],
-              ["privacy.reduceTimerPrecision", false],
-              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-              ]});
-    checkTimestamps("privacy.resistFingerprinting", expectedPrecision);
+  add_task(async function testDOMRTP() {
+    await testDOM(false, true, 100);
+    await testDOM(false, true, 13);
+    await testDOM(false, true, .13);
   });
 
-  add_task(async function testDOMRFP2() {
-    let expectedPrecision = 13;
-    await SpecialPowers.pushPrefEnv({
-      "set": [["privacy.resistFingerprinting", true],
-              ["privacy.reduceTimerPrecision", false],
-              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-              ]});
-    checkTimestamps("privacy.resistFingerprinting", expectedPrecision);
-  });
-
-  add_task(async function testDOMRFP3() {
-    let expectedPrecision = .13;
-    await SpecialPowers.pushPrefEnv({
-      "set": [["privacy.resistFingerprinting", true],
-              ["privacy.reduceTimerPrecision", false],
-              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-              ]});
-    checkTimestamps("privacy.resistFingerprinting", expectedPrecision);
-  });
-
-  add_task(async function testDOMRTP1() {
-    let expectedPrecision = 100;
-    await SpecialPowers.pushPrefEnv({
-      "set": [["privacy.resistFingerprinting", false],
-              ["privacy.reduceTimerPrecision", true],
-              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-              ]});
-    checkTimestamps("privacy.reduceTimerPrecision", expectedPrecision);
-  });
-
-  add_task(async function testDOMRTP2() {
-    let expectedPrecision = 13;
-    await SpecialPowers.pushPrefEnv({
-      "set": [["privacy.resistFingerprinting", false],
-              ["privacy.reduceTimerPrecision", true],
-              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-              ]});
-    checkTimestamps("privacy.reduceTimerPrecision", expectedPrecision);
-  });
-
-  add_task(async function testDOMRTP3() {
-    let expectedPrecision = .13;
-    await SpecialPowers.pushPrefEnv({
-      "set": [["privacy.resistFingerprinting", false],
-              ["privacy.reduceTimerPrecision", true],
-              ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
-              ]});
-    checkTimestamps("privacy.reduceTimerPrecision", expectedPrecision);
-  });
 
 </script>
 
 
 </body>
 </html>
--- a/browser/modules/ContextMenu.jsm
+++ b/browser/modules/ContextMenu.jsm
@@ -449,18 +449,17 @@ class ContextMenu {
     if (("complete" in aTarget) && !aTarget.complete) {
       return true;
     }
 
     if (aTarget.currentURI.schemeIs("javascript")) {
       return true;
     }
 
-    let request = aTarget.QueryInterface(Ci.nsIImageLoadingContent)
-                         .getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+    let request = aTarget.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
 
     if (!request) {
       return true;
     }
 
     return false;
   }
 
--- a/build/autoconf/sanitize.m4
+++ b/build/autoconf/sanitize.m4
@@ -2,20 +2,16 @@ dnl This Source Code Form is subject to 
 dnl License, v. 2.0. If a copy of the MPL was not distributed with this
 dnl file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 AC_DEFUN([MOZ_CONFIG_SANITIZE], [
 
 dnl ========================================================
 dnl = Use Address Sanitizer
 dnl ========================================================
-MOZ_ARG_ENABLE_BOOL(address-sanitizer,
-[  --enable-address-sanitizer       Enable Address Sanitizer (default=no)],
-    MOZ_ASAN=1,
-    MOZ_ASAN= )
 if test -n "$MOZ_ASAN"; then
     MOZ_LLVM_HACKS=1
     if test -n "$CLANG_CL"; then
         # Look for the ASan runtime binary
         if test "$CPU_ARCH" = "x86_64"; then
           MOZ_CLANG_RT_ASAN_LIB=clang_rt.asan_dynamic-x86_64.dll
         else
           MOZ_CLANG_RT_ASAN_LIB=clang_rt.asan_dynamic-i386.dll
--- a/build/macosx/cross-mozconfig.common
+++ b/build/macosx/cross-mozconfig.common
@@ -26,17 +26,18 @@ FLAGS="-target x86_64-apple-darwin11 -B 
 
 export CC="$topsrcdir/clang/bin/clang $FLAGS"
 export CXX="$topsrcdir/clang/bin/clang++ $FLAGS"
 export CPP="$topsrcdir/clang/bin/clang $FLAGS -E"
 export LLVMCONFIG=$topsrcdir/clang/bin/llvm-config
 export LDFLAGS="-Wl,-syslibroot,$CROSS_SYSROOT -Wl,-dead_strip"
 export BINDGEN_CFLAGS="$FLAGS"
 export TOOLCHAIN_PREFIX=$CROSS_CCTOOLS_PATH/bin/x86_64-apple-darwin11-
-export DSYMUTIL=$topsrcdir/llvm-dsymutil/bin/llvm-dsymutil
+export DSYMUTIL=$topsrcdir/build/macosx/llvm-dsymutil
+mk_add_options "export REAL_DSYMUTIL=$topsrcdir/llvm-dsymutil/bin/llvm-dsymutil"
 export MKFSHFS=$topsrcdir/hfsplus-tools/newfs_hfs
 export DMG_TOOL=$topsrcdir/dmg/dmg
 export HFS_TOOL=$topsrcdir/dmg/hfsplus
 
 export HOST_CC="$topsrcdir/clang/bin/clang"
 export HOST_CXX="$topsrcdir/clang/bin/clang++"
 export HOST_CPP="$topsrcdir/clang/bin/clang -E"
 export HOST_CFLAGS="-g"
new file mode 100755
--- /dev/null
+++ b/build/macosx/llvm-dsymutil
@@ -0,0 +1,75 @@
+#!/bin/sh
+# 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/.
+
+"$REAL_DSYMUTIL" "$@"
+ret=$?
+if [ $ret -ne 139 ]; then
+  exit $ret
+fi
+
+echo "$REAL_DSYMUTIL crashed. Trying to get a reduced testcase." >&2
+tmpdir=$(mktemp -d)
+trap "rm -rf $tmpdir" EXIT
+
+# Get the library file name from the command line arguments. We assume
+# it's the last argument that doesn't start with a dash.
+for arg in "$@"; do
+  case "$arg" in
+  -*)
+    ;;
+  *)
+    lib="$arg"
+    ;;
+  esac
+done
+
+last_obj=$("$REAL_DSYMUTIL" --verbose "$@" 2> /dev/null | sed -n "/trying to open/s/trying to open '\(.*\)'/\1/p" | tail -1)
+
+case "$last_obj" in
+"")
+  echo "Could not produce a reduced testcase. Aborting." >&2
+  # Ideally, we'd produce an archive with every .o and .a involved, but so
+  # far, this case has never happened, so, meh.
+  exit 139
+  ;;
+*.a\(*.o\))
+  # The crash likely happened while reading one particular object in a library.
+  # Create a new library with just that one object.
+  archive=$(readlink -f "${last_obj%(*}")
+  obj="${last_obj#*.a(}"
+  obj="${obj%)}"
+  (cd "$tmpdir"; ar x "$archive" "$obj")
+  mkdir -p $tmpdir/crasher/$(dirname "$archive")
+  (cd "$tmpdir"; ar cr "$tmpdir/crasher/$archive" "$obj")
+  rm "$tmpdir/$obj"
+  ;;
+*)
+  # The crash likely happened while reading one particular object.
+  obj=$(readlink -f "$last_obj")
+  mkdir -p "$tmpdir/crasher/$(dirname "$obj")"
+  cp "$obj" "$tmpdir/crasher/$obj"
+  ;;
+esac
+cp "$lib" "$tmpdir/crasher"
+cat > "$tmpdir/crasher/run-me.sh" <<EOF
+#!/bin/sh
+DSYMUTIL="\${DSYMUTIL:-llvm-dsymutil}"
+dir="\$(dirname \$0)"
+\$DSYMUTIL -oso-prepend-path="\$dir" "\$dir/$(basename "$lib")"
+exit \$?
+EOF
+chmod +x "$tmpdir/crasher/run-me.sh"
+(cd "$tmpdir"/crasher; DSYMUTIL=/builds/worker/workspace/build/src/llvm-dsymutil/bin/llvm-dsymutil ./run-me.sh > /dev/null 2>&1)
+if [ $? -eq 139 ]; then
+  echo "Could reproduce with a reduced testcase. Creating an artifact." >&2
+  mkdir -p "$HOME/artifacts"
+  artifact=dsymutil-crasher.tar.xz
+  tar -Jcf "$HOME/artifacts/$artifact" -C "$tmpdir" crasher/
+  echo "Check the $artifact artifact." >&2
+else
+  echo "Could not reproduce with a reduced testcase. Sorry." >&2
+fi
+
+exit 139
--- a/build/moz.configure/old.configure
+++ b/build/moz.configure/old.configure
@@ -165,17 +165,16 @@ def old_configure_options(*options):
     return depends(prepare_configure, extra_old_configure_args, all_options,
                    *options)
 
 
 @old_configure_options(
     '--cache-file',
     '--datadir',
     '--enable-accessibility',
-    '--enable-address-sanitizer',
     '--enable-alsa',
     '--enable-bundled-fonts',
     '--enable-content-sandbox',
     '--enable-cookies',
     '--enable-cpp-rtti',
     '--enable-crashreporter',
     '--enable-dbus',
     '--enable-debug-js-modules',
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -1292,29 +1292,80 @@ include('windows.configure', when=is_win
 fxc = check_prog('FXC', ('fxc.exe', 'fxc2.exe'), when=depends(target)
                  (lambda t: t.kernel == 'WINNT'))
 wine = check_prog('WINE', ['wine'], when=depends(target, host)
                   (lambda t, h: t.kernel == 'WINNT' and h.kernel == 'Linux'))
 
 # Security Hardening
 # ==============================================================
 
+js_option('--enable-address-sanitizer', help='Enable Address Sanitizer')
+
+
+@depends_if('--enable-address-sanitizer')
+def asan(value):
+    return True
+
+
+add_old_configure_assignment('MOZ_ASAN', asan)
+
+
 option('--enable-hardening', env='MOZ_SECURITY_HARDENING',
        help='Enables security hardening compiler options')
 
 
-@depends('--enable-hardening', c_compiler)
-def security_hardening_cflags(value, c_compiler):
-    if value and c_compiler.type in ['gcc', 'clang']:
-        return '-fstack-protector-strong'
+@depends('--enable-hardening', '--enable-address-sanitizer',
+         '--enable-optimize', c_compiler, target)
+def security_hardening_cflags(hardening_flag, asan, optimize, c_compiler, target):
+    compiler_is_gccish = c_compiler.type in ('gcc', 'clang')
+
+    flags = []
+    js_flags = []
+
+    # FORTIFY_SOURCE ------------------------------------
+    # If hardening is explicitly enabled, or not explicitly disabled
+    if hardening_flag.origin == "default" or hardening_flag:
+        # Require optimization for FORTIFY_SOURCE. See Bug 1417452
+        # Also, undefine it before defining it just in case a distro adds it, see Bug 1418398
+        if compiler_is_gccish and optimize and not asan:
+            # Don't enable FORTIFY_SOURCE on Android on the top-level, but do enable in js/
+            if target.os != 'Android':
+                flags.append("-U_FORTIFY_SOURCE")
+                flags.append("-D_FORTIFY_SOURCE=2")
+            js_flags.append("-U_FORTIFY_SOURCE")
+            js_flags.append("-D_FORTIFY_SOURCE=2")
+
+    # If ASAN _is_ on, undefine FOTIFY_SOURCE just to be safe
+    if asan:
+        flags.append("-U_FORTIFY_SOURCE")
+        js_flags.append("-U_FORTIFY_SOURCE")
+
+    # fstack-protector ------------------------------------
+    # Enable only if --enable-hardening is passed and ASAN is
+    # not on as ASAN will catch the crashes for us
+    if hardening_flag and compiler_is_gccish and not asan:
+        flags.append("-fstack-protector-strong")
+
+    # fno-common -----------------------------------------
+    # Do not merge variables for ASAN; can detect some subtle bugs
+    if asan:
+        flags.append("-fno-common")
+
+    return namespace(
+        flags=flags,
+        js_flags=js_flags,
+    )
 
 
-add_old_configure_assignment('HARDENING_CFLAGS', security_hardening_cflags)
+add_old_configure_assignment('MOZ_HARDENING_CFLAGS', security_hardening_cflags.flags)
+add_old_configure_assignment('MOZ_HARDENING_CFLAGS_JS', security_hardening_cflags.js_flags)
 imply_option('--enable-pie', depends_if('--enable-hardening')(lambda v: v))
 
+# ==============================================================
+
 option(env='RUSTFLAGS',
        nargs=1,
        help='Rust compiler flags')
 set_config('RUSTFLAGS', depends('RUSTFLAGS')(lambda flags: flags))
 
 
 imply_option('--enable-release', mozilla_official)
 imply_option('--enable-release', depends_if('MOZ_AUTOMATION')(lambda x: True))
--- a/build/unix/build-binutils/build-binutils.sh
+++ b/build/unix/build-binutils/build-binutils.sh
@@ -9,17 +9,17 @@ if [ -z "$root_dir" -o ! -d "$root_dir" 
 fi
 cd $root_dir
 
 if test -z $TMPDIR; then
   TMPDIR=/tmp/
 fi
 
 # Download the source of the specified version of binutils
-wget -c -P $TMPDIR ftp://ftp.gnu.org/gnu/binutils/binutils-${binutils_version}.tar.bz2 || exit 1
+wget -c --progress=dot:mega -P $TMPDIR ftp://ftp.gnu.org/gnu/binutils/binutils-${binutils_version}.tar.bz2 || exit 1
 tar xjf $TMPDIR/binutils-${binutils_version}.tar.bz2
 
 # Build binutils
 mkdir binutils-objdir
 cd binutils-objdir
 
 ../binutils-$binutils_version/configure --prefix /tools/binutils/ --enable-gold --enable-plugins --disable-nls || exit 1
 make $make_flags || exit 1
--- a/build/unix/build-gcc/download-tools.sh
+++ b/build/unix/build-gcc/download-tools.sh
@@ -12,17 +12,17 @@ if test -z $TMPDIR; then
 fi
 
 mkdir $root_dir/gpg
 GPG="gpg --homedir $root_dir/gpg"
 
 > $root_dir/downloads
 
 download() {
-  wget -c -P $TMPDIR $1/$2
+  wget -c --progress=dot:mega -P $TMPDIR $1/$2
   (cd $TMPDIR; sha256sum $2) >> $root_dir/downloads
 }
 
 download_and_check() {
   download $1 ${2%.*}
-  wget -c -P $TMPDIR $1/$2
+  wget -c --progress=dot:mega -P $TMPDIR $1/$2
   $GPG --verify $TMPDIR/$2 $TMPDIR/${2%.*}
-}
\ No newline at end of file
+}
--- a/build/unix/build-gtk3/build-gtk3.sh
+++ b/build/unix/build-gtk3/build-gtk3.sh
@@ -33,17 +33,17 @@ make_flags=-j$(nproc)
 yum install -y libtool-ltdl-devel libtool-ltdl-devel.i686 json-c-devel json-c-devel.i686 libsndfile-devel libsndfile-devel.i686
 
 build() {
 	name=$1
 	shift
 	pkg=$(echo $name | tr '+-' '__')
 	version=$(eval echo \$${pkg}_version)
 	url=$(eval echo \$${pkg}_url)
-	wget -c -P $root_dir $url
+	wget -c --progress=dot:mega -P $root_dir $url
 	tar -axf $root_dir/$name-$version.tar.*
 	mkdir -p build/$name
 	cd build/$name
 	eval ../../$name-$version/configure --disable-static $* $configure_args --libdir=/usr/local/$lib
 	make $make_flags
 	make install
 	find /usr/local/$lib -name \*.la -delete
 	cd ../..
--- a/build/unix/build-hfsplus/build-hfsplus.sh
+++ b/build/unix/build-hfsplus/build-hfsplus.sh
@@ -19,17 +19,17 @@ if test -z $TMPDIR; then
 fi
 
 # Set an md5 check file to validate input
 echo "${md5sum} *${TMPDIR}/${filename}" > $TMPDIR/hfsplus.MD5
 
 # Most-upstream is https://opensource.apple.com/source/diskdev_cmds/
 
 # Download the source of the specified version of hfsplus
-wget -c -P $TMPDIR http://pkgs.fedoraproject.org/repo/pkgs/hfsplus-tools/${filename}/${md5sum}/${filename} || exit 1
+wget -c --progress=dot:mega -P $TMPDIR http://pkgs.fedoraproject.org/repo/pkgs/hfsplus-tools/${filename}/${md5sum}/${filename} || exit 1
 md5sum -c $TMPDIR/hfsplus.MD5 || exit 1
 mkdir hfsplus-source
 tar xzf $TMPDIR/${filename} -C hfsplus-source --strip-components=1
 
 # Build
 cd hfsplus-source
 # We want to statically link against libcrypto. On CentOS, that requires zlib
 # and libdl, because of FIPS functions pulling in more than necessary from
--- a/caps/NullPrincipalURI.cpp
+++ b/caps/NullPrincipalURI.cpp
@@ -252,18 +252,18 @@ NullPrincipalURI::GetSpecIgnoringRef(nsA
 
 NS_IMETHODIMP
 NullPrincipalURI::GetHasRef(bool* _result)
 {
   *_result = false;
   return NS_OK;
 }
 
-NS_IMETHODIMP
-NullPrincipalURI::SetSpec(const nsACString& aSpec)
+nsresult
+NullPrincipalURI::SetSpecInternal(const nsACString& aSpec)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 NullPrincipalURI::GetUsername(nsACString& _username)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
--- a/caps/NullPrincipalURI.h
+++ b/caps/NullPrincipalURI.h
@@ -73,17 +73,20 @@ public:
     NS_IMETHOD Finalize(nsIURI** aURI) override
     {
       mURI.forget(aURI);
       return NS_OK;
     }
 
     NS_IMETHOD SetSpec(const nsACString & aSpec, nsIURIMutator** aMutator) override
     {
-      NS_ADDREF(*aMutator = this);
+      if (aMutator) {
+        nsCOMPtr<nsIURIMutator> mutator = this;
+        mutator.forget(aMutator);
+      }
       return NS_ERROR_NOT_IMPLEMENTED;
     }
 
     explicit Mutator() { }
   private:
     virtual ~Mutator() { }
 
     friend class NullPrincipalURI;
--- a/caps/OriginAttributes.cpp
+++ b/caps/OriginAttributes.cpp
@@ -171,18 +171,18 @@ public:
   {
     MOZ_ASSERT(aOriginAttributes);
     // If mPrivateBrowsingId is passed in as >0 and is not present in the suffix,
     // then it will remain >0 when it should be 0 according to the suffix. Set to 0 before
     // iterating to fix this.
     mOriginAttributes->mPrivateBrowsingId = 0;
   }
 
-  bool URLParamsIterator(const nsString& aName,
-                         const nsString& aValue) override
+  bool URLParamsIterator(const nsAString& aName,
+                         const nsAString& aValue) override
   {
     if (aName.EqualsLiteral("appId")) {
       nsresult rv;
       int64_t val  = aValue.ToInteger64(&rv);
       NS_ENSURE_SUCCESS(rv, false);
       NS_ENSURE_TRUE(val <= UINT32_MAX, false);
       mOriginAttributes->mAppId = static_cast<uint32_t>(val);
 
@@ -246,21 +246,18 @@ OriginAttributes::PopulateFromSuffix(con
   if (aStr.IsEmpty()) {
     return true;
   }
 
   if (aStr[0] != '^') {
     return false;
   }
 
-  URLParams params;
-  params.ParseInput(Substring(aStr, 1, aStr.Length() - 1));
-
   PopulateFromSuffixIterator iterator(this);
-  return params.ForEach(iterator);
+  return URLParams::Parse(Substring(aStr, 1, aStr.Length() - 1), iterator);
 }
 
 bool
 OriginAttributes::PopulateFromOrigin(const nsACString& aOrigin,
                                      nsACString& aOriginNoSuffix)
 {
   // RFindChar is only available on nsCString.
   nsCString origin(aOrigin);
--- a/devtools/client/debugger/new/debugger.css
+++ b/devtools/client/debugger/new/debugger.css
@@ -556,16 +556,20 @@ button:focus {
   display: flex;
   position: relative;
   flex: 1;
   background-color: var(--theme-tab-toolbar-background);
   height: calc(100% - 1px);
   overflow: hidden;
 }
 
+.theme-dark .editor-pane {
+  background-color: var(--theme-toolbar-background);
+}
+
 .editor-container {
   width: 100%;
 }
 
 .search-container {
   position: absolute;
   top: 0;
   left: 0;
@@ -3204,17 +3208,17 @@ html .breakpoints-list .breakpoint.pause
  * 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/>. */
 
 :root {
   --accordion-header-background: var(--theme-toolbar-background);
 }
 
 :root.theme-dark {
-  --accordion-header-background: #141416;
+  --accordion-header-background: #222225;
 }
 
 .accordion {
   background-color: var(--theme-sidebar-background);
   width: 100%;
 }
 
 .accordion ._header {
@@ -3293,17 +3297,17 @@ html .breakpoints-list .breakpoint.pause
   background-color: var(--theme-toolbar-background);
 }
 
 html[dir="rtl"] .command-bar {
   border-right: 1px solid var(--theme-splitter-color);
 }
 
 .theme-dark .command-bar {
-  background-color: var(--theme-tab-toolbar-background);
+  background-color: var(--theme-toolbar-background);
 }
 
 img.pause,
 img.stepOver,
 img.stepIn,
 img.stepOut,
 img.resume {
   background-color: var(--theme-body-color);
--- a/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse_keyboard.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse_keyboard.js
@@ -4,37 +4,39 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the debugger panes collapse properly.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
 
-function test() {
-  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
-    Task.spawn(function* () {
-      let doc = aPanel.panelWin.document;
-      let panel = doc.getElementById("instruments-pane");
-      let button = doc.getElementById("instruments-pane-toggle");
-      ok(panel.classList.contains("pane-collapsed"),
-          "The instruments panel is initially in collapsed state");
+async function test() {
+  let [aTab,, aPanel] = await initDebugger(TAB_URL);
+
+  let doc = aPanel.panelWin.document;
+  let panel = doc.getElementById("instruments-pane");
+  let button = doc.getElementById("instruments-pane-toggle");
+  ok(panel.classList.contains("pane-collapsed"),
+     "The instruments panel is initially in collapsed state");
 
-      yield togglePane(button, "Press on the toggle button to expand", panel, "VK_RETURN");
-      ok(!panel.classList.contains("pane-collapsed"),
-          "The instruments panel is in the expanded state");
+  await togglePane(button, "Press on the toggle button to expand", panel, "VK_RETURN");
+  ok(!panel.classList.contains("pane-collapsed"),
+     "The instruments panel is in the expanded state");
 
-      yield togglePane(button, "Press on the toggle button to collapse", panel, "VK_SPACE");
-      ok(panel.classList.contains("pane-collapsed"),
-        "The instruments panel is in the collapsed state");
+  await togglePane(button, "Press on the toggle button to collapse", panel, "VK_SPACE");
+  ok(panel.classList.contains("pane-collapsed"),
+     "The instruments panel is in the collapsed state");
 
-      closeDebuggerAndFinish(aPanel);
-    });
-  });
+  closeDebuggerAndFinish(aPanel);
 }
 
-function* togglePane(button, message, pane, keycode) {
+async function togglePane(button, message, pane, keycode) {
   let onTransitionEnd = once(pane, "transitionend");
   info(message);
   button.focus();
   EventUtils.synthesizeKey(keycode, {});
-  yield onTransitionEnd;
+  await onTransitionEnd;
+
+  // Wait for the next event tick to make sure all transitionend event
+  // handlers finish.
+  await waitForTick();
 }
--- a/devtools/client/inspector/animation/actions/animations.js
+++ b/devtools/client/inspector/animation/actions/animations.js
@@ -1,19 +1,65 @@
 /* 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 { UPDATE_ANIMATIONS } = require("./index");
+const {
+  UPDATE_ANIMATIONS,
+  UPDATE_DETAIL_VISIBILITY,
+  UPDATE_ELEMENT_PICKER_ENABLED,
+  UPDATE_SELECTED_ANIMATION,
+  UPDATE_SIDEBAR_SIZE
+} = require("./index");
 
 module.exports = {
   /**
    * Update the list of animation in the animation inspector.
    */
   updateAnimations(animations) {
     return {
       type: UPDATE_ANIMATIONS,
       animations,
     };
+  },
+
+  /**
+   * Update visibility of detail pane.
+   */
+  updateDetailVisibility(detailVisibility) {
+    return {
+      type: UPDATE_DETAIL_VISIBILITY,
+      detailVisibility,
+    };
+  },
+
+  /**
+   * Update the state of element picker in animation inspector.
+   */
+  updateElementPickerEnabled(elementPickerEnabled) {
+    return {
+      type: UPDATE_ELEMENT_PICKER_ENABLED,
+      elementPickerEnabled,
+    };
+  },
+
+  /**
+   * Update selected animation.
+   */
+  updateSelectedAnimation(selectedAnimation) {
+    return {
+      type: UPDATE_SELECTED_ANIMATION,
+      selectedAnimation,
+    };
+  },
+
+  /**
+   * Update the sidebar size.
+   */
+  updateSidebarSize(sidebarSize) {
+    return {
+      type: UPDATE_SIDEBAR_SIZE,
+      sidebarSize,
+    };
   }
 };
deleted file mode 100644
--- a/devtools/client/inspector/animation/actions/element-picker.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const { UPDATE_ELEMENT_PICKER_ENABLED } = require("./index");
-
-module.exports = {
-  /**
-   * Update the state of element picker in animation inspector.
-   */
-  updateElementPickerEnabled(isEnabled) {
-    return {
-      type: UPDATE_ELEMENT_PICKER_ENABLED,
-      isEnabled,
-    };
-  }
-};
--- a/devtools/client/inspector/animation/actions/index.js
+++ b/devtools/client/inspector/animation/actions/index.js
@@ -6,15 +6,21 @@
 
 const { createEnum } = require("devtools/client/shared/enum");
 
 createEnum([
 
   // Update the list of animation.
   "UPDATE_ANIMATIONS",
 
+  // Update visibility of detail pane.
+  "UPDATE_DETAIL_VISIBILITY",
+
   // Update state of the picker enabled.
   "UPDATE_ELEMENT_PICKER_ENABLED",
 
+  // Update selected animation.
+  "UPDATE_SELECTED_ANIMATION",
+
   // Update sidebar size.
   "UPDATE_SIDEBAR_SIZE",
 
 ], module.exports);
--- a/devtools/client/inspector/animation/actions/moz.build
+++ b/devtools/client/inspector/animation/actions/moz.build
@@ -1,10 +1,8 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'animations.js',
-    'element-picker.js',
     'index.js',
-    'sidebar.js',
 )
deleted file mode 100644
--- a/devtools/client/inspector/animation/actions/sidebar.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const { UPDATE_SIDEBAR_SIZE } = require("./index");
-
-module.exports = {
-  /**
-   * Update the sidebar size.
-   */
-  updateSidebarSize(size) {
-    return {
-      type: UPDATE_SIDEBAR_SIZE,
-      size,
-    };
-  }
-};
--- a/devtools/client/inspector/animation/animation.js
+++ b/devtools/client/inspector/animation/animation.js
@@ -7,28 +7,34 @@
 const { AnimationsFront } = require("devtools/shared/fronts/animation");
 const { createElement, createFactory } = require("devtools/client/shared/vendor/react");
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
 
 const EventEmitter = require("devtools/shared/event-emitter");
 
 const App = createFactory(require("./components/App"));
 
-const { updateAnimations } = require("./actions/animations");
-const { updateElementPickerEnabled } = require("./actions/element-picker");
-const { updateSidebarSize } = require("./actions/sidebar");
+const {
+  updateAnimations,
+  updateDetailVisibility,
+  updateElementPickerEnabled,
+  updateSelectedAnimation,
+  updateSidebarSize
+} = require("./actions/animations");
 const { isAllAnimationEqual } = require("./utils/utils");
 
 class AnimationInspector {
   constructor(inspector, win) {
     this.inspector = inspector;
     this.win = win;
 
     this.getAnimatedPropertyMap = this.getAnimatedPropertyMap.bind(this);
     this.getNodeFromActor = this.getNodeFromActor.bind(this);
+    this.selectAnimation = this.selectAnimation.bind(this);
+    this.setDetailVisibility = this.setDetailVisibility.bind(this);
     this.simulateAnimation = this.simulateAnimation.bind(this);
     this.toggleElementPicker = this.toggleElementPicker.bind(this);
     this.update = this.update.bind(this);
     this.onElementPickerStarted = this.onElementPickerStarted.bind(this);
     this.onElementPickerStopped = this.onElementPickerStopped.bind(this);
     this.onSidebarResized = this.onSidebarResized.bind(this);
     this.onSidebarSelect = this.onSidebarSelect.bind(this);
 
@@ -47,16 +53,18 @@ class AnimationInspector {
     const {
       onHideBoxModelHighlighter,
     } = this.inspector.getPanel("boxmodel").getComponentProps();
 
     const {
       emit: emitEventForTest,
       getAnimatedPropertyMap,
       getNodeFromActor,
+      selectAnimation,
+      setDetailVisibility,
       simulateAnimation,
       toggleElementPicker,
     } = this;
 
     const target = this.inspector.target;
     this.animationsFront = new AnimationsFront(target.client, target.form);
 
     const provider = createElement(Provider,
@@ -67,16 +75,18 @@ class AnimationInspector {
       },
       App(
         {
           emitEventForTest,
           getAnimatedPropertyMap,
           getNodeFromActor,
           onHideBoxModelHighlighter,
           onShowBoxModelHighlighterForNode,
+          selectAnimation,
+          setDetailVisibility,
           setSelectedNode,
           simulateAnimation,
           toggleElementPicker,
         }
       )
     );
     this.provider = provider;
 
@@ -215,21 +225,31 @@ class AnimationInspector {
     const animations =
       selection.isConnected() && selection.isElementNode()
       ? await this.animationsFront.getAnimationPlayersForNode(selection.nodeFront)
       : [];
 
     if (!this.animations || !isAllAnimationEqual(animations, this.animations)) {
       this.inspector.store.dispatch(updateAnimations(animations));
       this.animations = animations;
+      // If number of displayed animations is one, we select the animation automatically.
+      this.selectAnimation(animations.length === 1 ? animations[0] : null);
     }
 
     done();
   }
 
+  selectAnimation(animation) {
+    this.inspector.store.dispatch(updateSelectedAnimation(animation));
+  }
+
+  setDetailVisibility(isVisible) {
+    this.inspector.store.dispatch(updateDetailVisibility(isVisible));
+  }
+
   onElementPickerStarted() {
     this.inspector.store.dispatch(updateElementPickerEnabled(true));
   }
 
   onElementPickerStopped() {
     this.inspector.store.dispatch(updateElementPickerEnabled(false));
   }
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/components/AnimatedPropertyItem.js
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
+class AnimatedPropertyItem extends PureComponent {
+  static get propTypes() {
+    return {
+      property: PropTypes.string.isRequired,
+      values: PropTypes.array.isRequired,
+    };
+  }
+
+  render() {
+    return dom.li(
+      {
+        className: "animated-property-item"
+      }
+    );
+  }
+}
+
+module.exports = AnimatedPropertyItem;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/components/AnimatedPropertyList.js
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
+const AnimatedPropertyItem = createFactory(require("./AnimatedPropertyItem"));
+
+class AnimatedPropertyList extends PureComponent {
+  static get propTypes() {
+    return {
+      animation: PropTypes.object.isRequired,
+      emitEventForTest: PropTypes.func.isRequired,
+      getAnimatedPropertyMap: PropTypes.func.isRequired,
+    };
+  }
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      animatedPropertyMap: null
+    };
+  }
+
+  componentDidMount() {
+    this.updateKeyframesList(this.props.animation);
+  }
+
+  componentWillReceiveProps(nextProps) {
+    this.updateKeyframesList(nextProps.animation);
+  }
+
+  async updateKeyframesList(animation) {
+    const {
+      getAnimatedPropertyMap,
+      emitEventForTest,
+    } = this.props;
+    const animatedPropertyMap = await getAnimatedPropertyMap(animation);
+
+    this.setState({ animatedPropertyMap });
+
+    emitEventForTest("animation-keyframes-rendered");
+  }
+
+  render() {
+    const { animatedPropertyMap } = this.state;
+
+    if (!animatedPropertyMap) {
+      return null;
+    }
+
+    return dom.ul(
+      {
+        className: "animated-property-list"
+      },
+      [...animatedPropertyMap.entries()].map(([property, values]) => {
+        return AnimatedPropertyItem(
+          {
+            property,
+            values,
+          }
+        );
+      })
+    );
+  }
+}
+
+module.exports = AnimatedPropertyList;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/components/AnimatedPropertyListContainer.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
+const AnimatedPropertyList = createFactory(require("./AnimatedPropertyList"));
+const AnimatedPropertyListHeader = createFactory(require("./AnimatedPropertyListHeader"));
+
+class AnimatedPropertyListContainer extends PureComponent {
+  static get propTypes() {
+    return {
+      animation: PropTypes.object.isRequired,
+      emitEventForTest: PropTypes.func.isRequired,
+      getAnimatedPropertyMap: PropTypes.func.isRequired,
+    };
+  }
+
+  render() {
+    const {
+      animation,
+      emitEventForTest,
+      getAnimatedPropertyMap,
+    } = this.props;
+
+    return dom.div(
+      {
+        className: "animated-property-list-container"
+      },
+      AnimatedPropertyListHeader(),
+      AnimatedPropertyList(
+        {
+          animation,
+          emitEventForTest,
+          getAnimatedPropertyMap,
+        }
+      )
+    );
+  }
+}
+
+module.exports = AnimatedPropertyListContainer;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/components/AnimatedPropertyListHeader.js
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+
+const KeyframesProgressTickList = createFactory(require("./KeyframesProgressTickList"));
+
+class AnimatedPropertyListHeader extends PureComponent {
+  render() {
+    return dom.div(
+      {
+        className: "animated-property-list-header devtools-toolbar"
+      },
+      KeyframesProgressTickList()
+    );
+  }
+}
+
+module.exports = AnimatedPropertyListHeader;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/components/AnimationDetailContainer.js
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
+const AnimationDetailHeader = createFactory(require("./AnimationDetailHeader"));
+const AnimatedPropertyListContainer =
+  createFactory(require("./AnimatedPropertyListContainer"));
+
+class AnimationDetailContainer extends PureComponent {
+  static get propTypes() {
+    return {
+      animation: PropTypes.object.isRequired,
+      emitEventForTest: PropTypes.func.isRequired,
+      getAnimatedPropertyMap: PropTypes.func.isRequired,
+      setDetailVisibility: PropTypes.func.isRequired,
+    };
+  }
+
+  render() {
+    const {
+      animation,
+      emitEventForTest,
+      getAnimatedPropertyMap,
+      setDetailVisibility,
+    } = this.props;
+
+    return dom.div(
+      {
+        className: "animation-detail-container"
+      },
+      animation ?
+        AnimationDetailHeader(
+          {
+            animation,
+            setDetailVisibility,
+          }
+        )
+      :
+        null,
+      animation ?
+        AnimatedPropertyListContainer(
+          {
+            animation,
+            emitEventForTest,
+            getAnimatedPropertyMap,
+          }
+        )
+      :
+        null
+    );
+  }
+}
+
+const mapStateToProps = state => {
+  return {
+    animation: state.animations.selectedAnimation,
+  };
+};
+
+module.exports = connect(mapStateToProps)(AnimationDetailContainer);
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/components/AnimationDetailHeader.js
@@ -0,0 +1,49 @@
+/* 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 { PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
+const { getFormattedTitle } = require("../utils/l10n");
+
+class AnimationDetailHeader extends PureComponent {
+  static get propTypes() {
+    return {
+      animation: PropTypes.object.isRequired,
+      setDetailVisibility: PropTypes.func.isRequired,
+    };
+  }
+
+  onClick() {
+    const { setDetailVisibility } = this.props;
+    setDetailVisibility(false);
+  }
+
+  render() {
+    const { animation } = this.props;
+
+    return dom.div(
+      {
+        className: "animation-detail-header devtools-toolbar",
+      },
+      dom.div(
+        {
+          className: "animation-detail-title",
+        },
+        getFormattedTitle(animation.state)
+      ),
+      dom.button(
+        {
+          className: "animation-detail-close-button devtools-button",
+          onClick: this.onClick.bind(this),
+        }
+      )
+    );
+  }
+}
+
+module.exports = AnimationDetailHeader;
--- a/devtools/client/inspector/animation/components/AnimationItem.js
+++ b/devtools/client/inspector/animation/components/AnimationItem.js
@@ -1,69 +1,107 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 const AnimationTarget = createFactory(require("./AnimationTarget"));
 const SummaryGraph = createFactory(require("./graph/SummaryGraph"));
 
 class AnimationItem extends PureComponent {
   static get propTypes() {
     return {
       animation: PropTypes.object.isRequired,
       emitEventForTest: PropTypes.func.isRequired,
       getAnimatedPropertyMap: PropTypes.func.isRequired,
       getNodeFromActor: PropTypes.func.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
+      selectAnimation: PropTypes.func.isRequired,
+      selectedAnimation: PropTypes.object.isRequired,
       setSelectedNode: PropTypes.func.isRequired,
       simulateAnimation: PropTypes.func.isRequired,
       timeScale: PropTypes.object.isRequired,
     };
   }
 
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      isSelected: false,
+    };
+  }
+
+  componentWillReceiveProps(nextProps) {
+    const { animation } = this.props;
+
+    this.setState({
+      isSelected: nextProps.selectedAnimation &&
+                  animation.actorID === nextProps.selectedAnimation.actorID
+    });
+  }
+
+  shouldComponentUpdate(nextProps, nextState) {
+    return this.state.isSelected !== nextState.isSelected ||
+           this.props.animation !== nextProps.animation ||
+           this.props.timeScale !== nextProps.timeScale;
+  }
+
   render() {
     const {
       animation,
       emitEventForTest,
       getAnimatedPropertyMap,
       getNodeFromActor,
       onHideBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
+      selectAnimation,
       setSelectedNode,
       simulateAnimation,
       timeScale,
     } = this.props;
+    const {
+      isSelected,
+    } = this.state;
 
     return dom.li(
       {
-        className: `animation-item ${ animation.state.type }`
+        className: `animation-item ${ animation.state.type } ` +
+                   (isSelected ? "selected" : ""),
       },
       AnimationTarget(
         {
           animation,
           emitEventForTest,
           getNodeFromActor,
           onHideBoxModelHighlighter,
           onShowBoxModelHighlighterForNode,
           setSelectedNode,
         }
       ),
       SummaryGraph(
         {
           animation,
           emitEventForTest,
           getAnimatedPropertyMap,
+          selectAnimation,
           simulateAnimation,
           timeScale,
         }
       )
     );
   }
 }
 
-module.exports = AnimationItem;
+const mapStateToProps = state => {
+  return {
+    selectedAnimation: state.animations.selectedAnimation,
+  };
+};
+
+module.exports = connect(mapStateToProps)(AnimationItem);
--- a/devtools/client/inspector/animation/components/AnimationList.js
+++ b/devtools/client/inspector/animation/components/AnimationList.js
@@ -14,30 +14,32 @@ class AnimationList extends PureComponen
   static get propTypes() {
     return {
       animations: PropTypes.arrayOf(PropTypes.object).isRequired,
       emitEventForTest: PropTypes.func.isRequired,
       getAnimatedPropertyMap: PropTypes.func.isRequired,
       getNodeFromActor: PropTypes.func.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
+      selectAnimation: PropTypes.func.isRequired,
       setSelectedNode: PropTypes.func.isRequired,
       simulateAnimation: PropTypes.func.isRequired,
       timeScale: PropTypes.object.isRequired,
     };
   }
 
   render() {
     const {
       animations,
       emitEventForTest,
       getAnimatedPropertyMap,
       getNodeFromActor,
       onHideBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
+      selectAnimation,
       setSelectedNode,
       simulateAnimation,
       timeScale,
     } = this.props;
 
     return dom.ul(
       {
         className: "animation-list"
@@ -46,16 +48,17 @@ class AnimationList extends PureComponen
         AnimationItem(
           {
             animation,
             emitEventForTest,
             getAnimatedPropertyMap,
             getNodeFromActor,
             onHideBoxModelHighlighter,
             onShowBoxModelHighlighterForNode,
+            selectAnimation,
             setSelectedNode,
             simulateAnimation,
             timeScale,
           }
         )
       )
     );
   }
--- a/devtools/client/inspector/animation/components/AnimationListContainer.js
+++ b/devtools/client/inspector/animation/components/AnimationListContainer.js
@@ -18,29 +18,31 @@ class AnimationListContainer extends Pur
   static get propTypes() {
     return {
       animations: PropTypes.arrayOf(PropTypes.object).isRequired,
       emitEventForTest: PropTypes.func.isRequired,
       getAnimatedPropertyMap: PropTypes.func.isRequired,
       getNodeFromActor: PropTypes.func.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
+      selectAnimation: PropTypes.func.isRequired,
       setSelectedNode: PropTypes.func.isRequired,
       simulateAnimation: PropTypes.func.isRequired,
     };
   }
 
   render() {
     const {
       animations,
       emitEventForTest,
       getAnimatedPropertyMap,
       getNodeFromActor,
       onHideBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
+      selectAnimation,
       setSelectedNode,
       simulateAnimation,
     } = this.props;
     const timeScale = new TimeScale(animations);
 
     return dom.div(
       {
         className: "animation-list-container"
@@ -53,16 +55,17 @@ class AnimationListContainer extends Pur
       AnimationList(
         {
           animations,
           emitEventForTest,
           getAnimatedPropertyMap,
           getNodeFromActor,
           onHideBoxModelHighlighter,
           onShowBoxModelHighlighterForNode,
+          selectAnimation,
           setSelectedNode,
           simulateAnimation,
           timeScale,
         }
       )
     );
   }
 }
--- a/devtools/client/inspector/animation/components/AnimationTimelineTickList.js
+++ b/devtools/client/inspector/animation/components/AnimationTimelineTickList.js
@@ -90,13 +90,13 @@ class AnimationTimelineTickList extends 
       },
       tickList.map(tickItem => AnimationTimelineTickItem(tickItem))
     );
   }
 }
 
 const mapStateToProps = state => {
   return {
-    sidebarWidth: state.animationSidebar.width
+    sidebarWidth: state.animations.sidebarSize.width
   };
 };
 
 module.exports = connect(mapStateToProps)(AnimationTimelineTickList);
--- a/devtools/client/inspector/animation/components/App.js
+++ b/devtools/client/inspector/animation/components/App.js
@@ -4,71 +4,102 @@
 
 "use strict";
 
 const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
+const AnimationDetailContainer = createFactory(require("./AnimationDetailContainer"));
 const AnimationListContainer = createFactory(require("./AnimationListContainer"));
 const NoAnimationPanel = createFactory(require("./NoAnimationPanel"));
+const SplitBox = createFactory(require("devtools/client/shared/components/splitter/SplitBox"));
 
 class App extends PureComponent {
   static get propTypes() {
     return {
       animations: PropTypes.arrayOf(PropTypes.object).isRequired,
+      detailVisibility: PropTypes.bool.isRequired,
       emitEventForTest: PropTypes.func.isRequired,
       getAnimatedPropertyMap: PropTypes.func.isRequired,
       getNodeFromActor: PropTypes.func.isRequired,
       onHideBoxModelHighlighter: PropTypes.func.isRequired,
       onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
+      selectAnimation: PropTypes.func.isRequired,
+      setDetailVisibility: PropTypes.func.isRequired,
       setSelectedNode: PropTypes.func.isRequired,
       simulateAnimation: PropTypes.func.isRequired,
       toggleElementPicker: PropTypes.func.isRequired,
     };
   }
 
   shouldComponentUpdate(nextProps, nextState) {
     return this.props.animations.length !== 0 || nextProps.animations.length !== 0;
   }
 
   render() {
     const {
       animations,
+      detailVisibility,
       emitEventForTest,
       getAnimatedPropertyMap,
       getNodeFromActor,
       onHideBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
+      selectAnimation,
+      setDetailVisibility,
       setSelectedNode,
       simulateAnimation,
       toggleElementPicker,
     } = this.props;
 
     return dom.div(
       {
-        id: "animation-container"
+        id: "animation-container",
+        className: detailVisibility ? "animation-detail-visible" : "",
       },
       animations.length ?
-      AnimationListContainer(
-        {
-          animations,
-          emitEventForTest,
-          getAnimatedPropertyMap,
-          getNodeFromActor,
-          onHideBoxModelHighlighter,
-          onShowBoxModelHighlighterForNode,
-          setSelectedNode,
-          simulateAnimation,
-        }
-      )
+      SplitBox({
+        className: "animation-container-splitter",
+        endPanel: AnimationDetailContainer(
+          {
+            emitEventForTest,
+            getAnimatedPropertyMap,
+            setDetailVisibility,
+          }
+        ),
+        endPanelControl: true,
+        initialHeight: "50%",
+        splitterSize: 1,
+        startPanel: AnimationListContainer(
+          {
+            animations,
+            emitEventForTest,
+            getAnimatedPropertyMap,
+            getNodeFromActor,
+            onHideBoxModelHighlighter,
+            onShowBoxModelHighlighterForNode,
+            selectAnimation,
+            setSelectedNode,
+            simulateAnimation,
+          }
+        ),
+        vert: false,
+      })
       :
       NoAnimationPanel(
         {
           toggleElementPicker
         }
       )
     );
   }
 }
 
-module.exports = connect(state => state)(App);
+const mapStateToProps = state => {
+  return {
+    animations: state.animations.animations,
+    detailVisibility: state.animations.detailVisibility,
+  };
+};
+
+module.exports = connect(mapStateToProps)(App);
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/components/KeyframesProgressTickItem.js
@@ -0,0 +1,37 @@
+/* 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 { PureComponent } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+
+class KeyframesProgressTickItem extends PureComponent {
+  static get propTypes() {
+    return {
+      direction: PropTypes.string.isRequired,
+      position: PropTypes.number.isRequired,
+      progressTickLabel: PropTypes.string.isRequired,
+    };
+  }
+
+  render() {
+    const {
+      direction,
+      position,
+      progressTickLabel,
+    } = this.props;
+
+    return dom.div(
+      {
+        className: `keyframes-progress-tick-item ${ direction }`,
+        style: { [direction]: `${ position }%` }
+      },
+      progressTickLabel
+    );
+  }
+}
+
+module.exports = KeyframesProgressTickItem;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/components/KeyframesProgressTickList.js
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+
+const KeyframesProgressTickItem = createFactory(require("./KeyframesProgressTickItem"));
+const { getFormatStr } = require("../utils/l10n");
+
+class KeyframesProgressTickList extends PureComponent {
+  render() {
+    return dom.div(
+      {
+        className: "keyframes-progress-tick-list"
+      },
+      [0, 50, 100].map(progress => {
+        const direction = progress === 100 ? "right" : "left";
+        const position = progress === 100 ? 0 : progress;
+        const progressTickLabel =
+          getFormatStr("detail.propertiesHeader.percentage", progress);
+
+        return KeyframesProgressTickItem(
+          {
+            direction,
+            position,
+            progressTickLabel,
+          }
+        );
+      })
+    );
+  }
+}
+
+module.exports = KeyframesProgressTickList;
--- a/devtools/client/inspector/animation/components/NoAnimationPanel.js
+++ b/devtools/client/inspector/animation/components/NoAnimationPanel.js
@@ -11,47 +11,47 @@ const { connect } = require("devtools/cl
 const { LocalizationHelper } = require("devtools/shared/l10n");
 
 const L10N =
   new LocalizationHelper("devtools/client/locales/animationinspector.properties");
 
 class NoAnimationPanel extends PureComponent {
   static get propTypes() {
     return {
-      elementPicker: PropTypes.object.isRequired,
+      elementPickerEnabled: PropTypes.bool.isRequired,
       toggleElementPicker: PropTypes.func.isRequired,
     };
   }
 
   shouldComponentUpdate(nextProps, nextState) {
-    return this.props.elementPicker.isEnabled != nextProps.elementPicker.isEnabled;
+    return this.props.elementPickerEnabled != nextProps.elementPickerEnabled;
   }
 
   render() {
-    const { elementPicker, toggleElementPicker } = this.props;
+    const { elementPickerEnabled, toggleElementPicker } = this.props;
 
     return dom.div(
       {
         className: "animation-error-message devtools-sidepanel-no-result"
       },
       dom.p(
         null,
         L10N.getStr("panel.noAnimation")
       ),
       dom.button(
         {
-          className: "animation-element-picker devtools-button"
-                     + (elementPicker.isEnabled ? " checked" : ""),
+          className: "animation-element-picker devtools-button" +
+                     (elementPickerEnabled ? " checked" : ""),
           "data-standalone": true,
           onClick: toggleElementPicker
         }
       )
     );
   }
 }
 
 const mapStateToProps = state => {
   return {
-    elementPicker: state.animationElementPicker
+    elementPickerEnabled: state.animations.elementPickerEnabled
   };
 };
 
 module.exports = connect(mapStateToProps)(NoAnimationPanel);
--- a/devtools/client/inspector/animation/components/graph/SummaryGraph.js
+++ b/devtools/client/inspector/animation/components/graph/SummaryGraph.js
@@ -8,29 +8,40 @@ const { createFactory, PureComponent } =
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 const AnimationName = createFactory(require("./AnimationName"));
 const DelaySign = createFactory(require("./DelaySign"));
 const EndDelaySign = createFactory(require("./EndDelaySign"));
 const SummaryGraphPath = createFactory(require("./SummaryGraphPath"));
 
-const { getFormatStr, getStr, numberWithDecimals } = require("../../utils/l10n");
+const { getFormattedTitle, getFormatStr, getStr, numberWithDecimals } = require("../../utils/l10n");
 
 class SummaryGraph extends PureComponent {
   static get propTypes() {
     return {
       animation: PropTypes.object.isRequired,
       emitEventForTest: PropTypes.func.isRequired,
       getAnimatedPropertyMap: PropTypes.func.isRequired,
+      selectAnimation: PropTypes.func.isRequired,
       simulateAnimation: PropTypes.func.isRequired,
       timeScale: PropTypes.object.isRequired,
     };
   }
 
+  constructor(props) {
+    super(props);
+
+    this.onClick = this.onClick.bind(this);
+  }
+
+  onClick() {
+    this.props.selectAnimation(this.props.animation);
+  }
+
   getTitleText(state) {
     const getTime =
       time => getFormatStr("player.timeLabel", numberWithDecimals(time / 1000, 2));
 
     let text = "";
 
     // Adding the name.
     text += getFormattedTitle(state);
@@ -130,16 +141,17 @@ class SummaryGraph extends PureComponent
       simulateAnimation,
       timeScale,
     } = this.props;
 
     return dom.div(
       {
         className: "animation-summary-graph" +
                    (animation.state.isRunningOnCompositor ? " compositor" : ""),
+        onClick: this.onClick,
         title: this.getTitleText(animation.state),
       },
       SummaryGraphPath(
         {
           animation,
           emitEventForTest,
           getAnimatedPropertyMap,
           simulateAnimation,
@@ -171,34 +183,9 @@ class SummaryGraph extends PureComponent
           }
         )
       :
       null
     );
   }
 }
 
-/**
- * Get a formatted title for this animation. This will be either:
- * "%S", "%S : CSS Transition", "%S : CSS Animation",
- * "%S : Script Animation", or "Script Animation", depending
- * if the server provides the type, what type it is and if the animation
- * has a name.
- *
- * @param {Object} state
- */
-function getFormattedTitle(state) {
-  // Older servers don't send a type, and only know about
-  // CSSAnimations and CSSTransitions, so it's safe to use
-  // just the name.
-  if (!state.type) {
-    return state.name;
-  }
-
-  // Script-generated animations may not have a name.
-  if (state.type === "scriptanimation" && !state.name) {
-    return getStr("timeline.scriptanimation.unnamedLabel");
-  }
-
-  return getFormatStr(`timeline.${state.type}.nameLabel`, state.name);
-}
-
 module.exports = SummaryGraph;
--- a/devtools/client/inspector/animation/components/moz.build
+++ b/devtools/client/inspector/animation/components/moz.build
@@ -2,18 +2,26 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
     'graph'
 ]
 
 DevToolsModules(
+    'AnimatedPropertyItem.js',
+    'AnimatedPropertyList.js',
+    'AnimatedPropertyListContainer.js',
+    'AnimatedPropertyListHeader.js',
+    'AnimationDetailContainer.js',
+    'AnimationDetailHeader.js',
     'AnimationItem.js',
     'AnimationList.js',
     'AnimationListContainer.js',
     'AnimationListHeader.js',
     'AnimationTarget.js',
     'AnimationTimelineTickItem.js',
     'AnimationTimelineTickList.js',
     'App.js',
+    'KeyframesProgressTickItem.js',
+    'KeyframesProgressTickList.js',
     'NoAnimationPanel.js',
 )
--- a/devtools/client/inspector/animation/reducers/animations.js
+++ b/devtools/client/inspector/animation/reducers/animations.js
@@ -1,20 +1,64 @@
 /* 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 { UPDATE_ANIMATIONS } = require("../actions/index");
+const {
+  UPDATE_ANIMATIONS,
+  UPDATE_DETAIL_VISIBILITY,
+  UPDATE_ELEMENT_PICKER_ENABLED,
+  UPDATE_SELECTED_ANIMATION,
+  UPDATE_SIDEBAR_SIZE,
+} = require("../actions/index");
 
-const INITIAL_ANIMATIONS = [];
+const INITIAL_STATE = {
+  animations: [],
+  detailVisibility: false,
+  elementPickerEnabled: false,
+  selectedAnimation: null,
+  sidebarSize: {
+    height: 0,
+    width: 0,
+  },
+};
 
 const reducers = {
-  [UPDATE_ANIMATIONS](_, { animations }) {
-    return animations;
-  }
+  [UPDATE_ANIMATIONS](state, { animations }) {
+    return Object.assign({}, state, {
+      animations,
+    });
+  },
+
+  [UPDATE_DETAIL_VISIBILITY](state, { detailVisibility }) {
+    return Object.assign({}, state, {
+      detailVisibility
+    });
+  },
+
+  [UPDATE_ELEMENT_PICKER_ENABLED](state, { elementPickerEnabled }) {
+    return Object.assign({}, state, {
+      elementPickerEnabled
+    });
+  },
+
+  [UPDATE_SELECTED_ANIMATION](state, { selectedAnimation }) {
+    const detailVisibility = !!selectedAnimation;
+
+    return Object.assign({}, state, {
+      detailVisibility,
+      selectedAnimation
+    });
+  },
+
+  [UPDATE_SIDEBAR_SIZE](state, { sidebarSize }) {
+    return Object.assign({}, state, {
+      sidebarSize
+    });
+  },
 };
 
-module.exports = function (animations = INITIAL_ANIMATIONS, action) {
+module.exports = function (state = INITIAL_STATE, action) {
   const reducer = reducers[action.type];
-  return reducer ? reducer(animations, action) : animations;
+  return reducer ? reducer(state, action) : state;
 };
deleted file mode 100644
--- a/devtools/client/inspector/animation/reducers/element-picker.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const { UPDATE_ELEMENT_PICKER_ENABLED } = require("../actions/index");
-
-const INITIAL_STATE = { isEnabled: false };
-
-const reducers = {
-  [UPDATE_ELEMENT_PICKER_ENABLED](state, { isEnabled }) {
-    return Object.assign({}, state, {
-      isEnabled
-    });
-  }
-};
-
-module.exports = function (state = INITIAL_STATE, action) {
-  const reducer = reducers[action.type];
-  return reducer ? reducer(state, action) : state;
-};
--- a/devtools/client/inspector/animation/reducers/moz.build
+++ b/devtools/client/inspector/animation/reducers/moz.build
@@ -1,9 +1,7 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'animations.js',
-    'element-picker.js',
-    'sidebar.js',
 )
deleted file mode 100644
--- a/devtools/client/inspector/animation/reducers/sidebar.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const { UPDATE_SIDEBAR_SIZE } = require("../actions/index");
-
-const INITIAL_SIZE = {
-  width: 0,
-  height: 0
-};
-
-const reducers = {
-  [UPDATE_SIDEBAR_SIZE](_, { size }) {
-    return size;
-  }
-};
-
-module.exports = function (size = INITIAL_SIZE, action) {
-  const reducer = reducers[action.type];
-  return reducer ? reducer(size, action) : size;
-};
--- a/devtools/client/inspector/animation/test/browser.ini
+++ b/devtools/client/inspector/animation/test/browser.ini
@@ -6,16 +6,20 @@ support-files =
   doc_simple_animation.html
   head.js
   !/devtools/client/framework/test/shared-head.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/test-actor-registry.js
   !/devtools/client/shared/test/test-actor.js
 
+[browser_animation_animated-property-list.js]
+[browser_animation_animation-detail_close-button.js]
+[browser_animation_animation-detail_title.js]
+[browser_animation_animation-detail_visibility.js]
 [browser_animation_animation-list.js]
 [browser_animation_animation-target.js]
 [browser_animation_animation-timeline-tick.js]
 [browser_animation_empty_on_invalid_nodes.js]
 [browser_animation_inspector_exists.js]
 [browser_animation_summary-graph_animation-name.js]
 [browser_animation_summary-graph_compositor.js]
 [browser_animation_summary-graph_computed-timing-path.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_animated-property-list.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test following animated property list test.
+// 1. Existence for animated property list.
+// 2. Number of animated property item.
+
+const TEST_CASES = [
+  {
+    target: ".animated",
+    expectedNumber: 1,
+  },
+  {
+    target: ".compositor-notall",
+    expectedNumber: 3,
+  },
+];
+
+add_task(async function () {
+  await addTab(URL_ROOT + "doc_simple_animation.html");
+  const { inspector, panel } = await openAnimationInspector();
+
+  info("Checking animated property list and items existence at initial");
+  ok(!panel.querySelector(".animated-property-list"),
+     "The animated-property-list should not be in the DOM at initial");
+
+  for (const testCase of TEST_CASES) {
+    info(`Checking animated-property-list and items existence at ${ testCase.target }`);
+    const animatedNode = await getNodeFront(testCase.target, inspector);
+    await selectNodeAndWaitForAnimations(animatedNode, inspector);
+    ok(panel.querySelector(".animated-property-list"),
+       `The animated-property-list should be in the DOM at ${ testCase.target }`);
+    const itemEls =
+      panel.querySelectorAll(".animated-property-list .animated-property-item");
+    is(itemEls.length, testCase.expectedNumber,
+       `The number of animated-property-list should be ${ testCase.expectedNumber } `
+       + `at ${ testCase.target }`);
+
+    if (itemEls.length < 2) {
+      continue;
+    }
+
+    info(`Checking the background color for `
+         + `the animated property item at ${ testCase.target }`);
+    const evenColor = panel.ownerGlobal.getComputedStyle(itemEls[0]).backgroundColor;
+    const oddColor = panel.ownerGlobal.getComputedStyle(itemEls[1]).backgroundColor;
+    isnot(evenColor, oddColor,
+          "Background color of an even animated property item "
+          + "should be different from odd");
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_animation-detail_close-button.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that whether close button in header of animation detail works.
+
+add_task(async function () {
+  await addTab(URL_ROOT + "doc_multi_timings.html");
+  const { animationInspector, panel } = await openAnimationInspector();
+
+  info("Checking close button in header of animation detail");
+  await clickOnAnimation(animationInspector, panel, 0);
+  const detailEl = panel.querySelector("#animation-container .controlled");
+  const win = panel.ownerGlobal;
+  isnot(win.getComputedStyle(detailEl).display, "none",
+    "detailEl should be visibled before clicking close button");
+  clickOnDetailCloseButton(panel);
+  is(win.getComputedStyle(detailEl).display, "none",
+    "detailEl should be unvisibled after clicking close button");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_animation-detail_title.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that whether title in header of animations detail.
+
+const TEST_CASES = [
+  {
+    target: ".cssanimation-normal",
+    expectedTitle: "cssanimation - CSS Animation",
+  },
+  {
+    target: ".delay-positive",
+    expectedTitle: "test-delay-animation - Script Animation",
+  },
+  {
+    target: ".easing-step",
+    expectedTitle: "Script Animation",
+  },
+];
+
+add_task(async function () {
+  await addTab(URL_ROOT + "doc_multi_timings.html");
+  const { inspector, panel } = await openAnimationInspector();
+
+  info("Checking title in each header of animation detail");
+
+  for (const testCase of TEST_CASES) {
+    info(`Checking title at ${ testCase.target }`);
+    const animatedNode = await getNodeFront(testCase.target, inspector);
+    await selectNodeAndWaitForAnimations(animatedNode, inspector);
+    const titleEl = panel.querySelector(".animation-detail-title");
+    is(titleEl.textContent, testCase.expectedTitle,
+       `Title of "${ testCase.target }" should be "${ testCase.expectedTitle }"`);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_animation-detail_visibility.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that whether animations detail could be displayed if there is selected animation.
+
+add_task(async function () {
+  await addTab(URL_ROOT + "doc_multi_timings.html");
+  const { animationInspector, inspector, panel } = await openAnimationInspector();
+
+  info("Checking animation detail visibility if animation was unselected");
+  const detailEl = panel.querySelector("#animation-container .controlled");
+  ok(detailEl, "The detail pane should be in the DOM");
+  const win = panel.ownerGlobal;
+  is(win.getComputedStyle(detailEl).display, "none", "detailEl should be unvisibled");
+
+  info("Checking animation detail visibility if animation was selected by click");
+  await clickOnAnimation(animationInspector, panel, 0);
+  isnot(win.getComputedStyle(detailEl).display, "none", "detailEl should be visibled");
+
+  info("Checking animation detail visibility when choose node which has animations");
+  const htmlNode = await getNodeFront("html", inspector);
+  await selectNodeAndWaitForAnimations(htmlNode, inspector);
+  is(win.getComputedStyle(detailEl).display, "none",
+     "detailEl should be unvisibled after choose html node");
+
+  info("Checking animation detail visibility when choose node which has an animation");
+  const animatedNode = await getNodeFront(".cssanimation-normal", inspector);
+  await selectNodeAndWaitForAnimations(animatedNode, inspector);
+  isnot(win.getComputedStyle(detailEl).display, "none",
+        "detailEl should be visibled after choose .cssanimation-normal node");
+});
--- a/devtools/client/inspector/animation/test/head.js
+++ b/devtools/client/inspector/animation/test/head.js
@@ -65,32 +65,70 @@ const enableAnimationFeatures = function
       ["layout.css.frames-timing.enabled", true],
     ]}, resolve);
   });
 };
 
 /**
  * Add a new test tab in the browser and load the given url.
  *
- * @param {String} url The url to be loaded in the new tab
+ * @param {String} url
+ *        The url to be loaded in the new tab
  * @return a promise that resolves to the tab object when the url is loaded
  */
 const _addTab = addTab;
 addTab = async function (url) {
   await enableAnimationFeatures();
   const tab = await _addTab(url);
   const browser = tab.linkedBrowser;
   info("Loading the helper frame script " + FRAME_SCRIPT_URL);
   browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
   info("Loading the helper frame script " + COMMON_FRAME_SCRIPT_URL);
   browser.messageManager.loadFrameScript(COMMON_FRAME_SCRIPT_URL, false);
   return tab;
 };
 
 /**
+ * Click on an animation in the timeline to select it.
+ *
+ * @param {AnimationInspector} animationInspector.
+ * @param {AnimationsPanel} panel
+ *        The panel instance.
+ * @param {Number} index
+ *        The index of the animation to click on.
+ */
+const clickOnAnimation = async function (animationInspector, panel, index) {
+  info("Click on animation " + index + " in the timeline");
+  const summaryGraphEl = panel.querySelectorAll(".animation-summary-graph")[index];
+  // Scroll to show the timeBlock since the element may be out of displayed area.
+  summaryGraphEl.scrollIntoView(false);
+  const bounds = summaryGraphEl.getBoundingClientRect();
+  const x = bounds.width / 2;
+  const y = bounds.height / 2;
+  EventUtils.synthesizeMouse(summaryGraphEl, x, y, {}, summaryGraphEl.ownerGlobal);
+
+  await waitForAnimationDetail(animationInspector);
+};
+
+/**
+ * Click on close button for animation detail pane.
+ *
+ * @param {AnimationsPanel} panel
+ *        The panel instance.
+ */
+const clickOnDetailCloseButton = function (panel) {
+  info("Click on close button for animation detail pane");
+  const buttonEl = panel.querySelector(".animation-detail-close-button");
+  const bounds = buttonEl.getBoundingClientRect();
+  const x = bounds.width / 2;
+  const y = bounds.height / 2;
+  EventUtils.synthesizeMouse(buttonEl, x, y, {}, buttonEl.ownerGlobal);
+};
+
+/**
  * Set the inspector's current selection to a node or to the first match of the
  * given css selector and wait for the animations to be displayed
  *
  * @param {String|NodeFront}
  *        data The node to select
  * @param {InspectorPanel} inspector
  *        The instance of InspectorPanel currently loaded in the toolbox
  * @param {String} reason
@@ -127,20 +165,32 @@ const setSidebarWidth = async function (
  * Wait for rendering.
  *
  * @param {AnimationInspector} animationInspector
  */
 const waitForRendering = async function (animationInspector) {
   await Promise.all([
     waitForAllAnimationTargets(animationInspector),
     waitForAllSummaryGraph(animationInspector),
+    waitForAnimationDetail(animationInspector),
   ]);
 };
 
 /**
+ * Wait for rendering of animation keyframes.
+ *
+ * @param {AnimationInspector} inspector
+ */
+const waitForAnimationDetail = async function (animationInspector) {
+  if (animationInspector.animations.length === 1) {
+    await animationInspector.once("animation-keyframes-rendered");
+  }
+};
+
+/**
  * Wait for all AnimationTarget components to be fully loaded
  * (fetched their related actor and rendered).
  *
  * @param {AnimationInspector} animationInspector
  */
 const waitForAllAnimationTargets = async function (animationInspector) {
   for (let i = 0; i < animationInspector.animations.length; i++) {
     await animationInspector.once("animation-target-rendered");
--- a/devtools/client/inspector/animation/utils/l10n.js
+++ b/devtools/client/inspector/animation/utils/l10n.js
@@ -3,13 +3,39 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const L10N =
   new LocalizationHelper("devtools/client/locales/animationinspector.properties");
 
+/**
+ * Get a formatted title for this animation. This will be either:
+ * "%S", "%S : CSS Transition", "%S : CSS Animation",
+ * "%S : Script Animation", or "Script Animation", depending
+ * if the server provides the type, what type it is and if the animation
+ * has a name.
+ *
+ * @param {Object} state
+ */
+function getFormattedTitle(state) {
+  // Older servers don't send a type, and only know about
+  // CSSAnimations and CSSTransitions, so it's safe to use
+  // just the name.
+  if (!state.type) {
+    return state.name;
+  }
+
+  // Script-generated animations may not have a name.
+  if (state.type === "scriptanimation" && !state.name) {
+    return L10N.getStr("timeline.scriptanimation.unnamedLabel");
+  }
+
+  return L10N.getFormatStr(`timeline.${state.type}.nameLabel`, state.name);
+}
+
 module.exports = {
   getFormatStr: (...args) => L10N.getFormatStr(...args),
+  getFormattedTitle,
   getStr: (...args) => L10N.getStr(...args),
   numberWithDecimals: (...args) => L10N.numberWithDecimals(...args),
 };
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -299,16 +299,17 @@ CssComputedView.prototype = {
   },
 
   /**
    * Get the type of a given node in the computed-view
    *
    * @param {DOMNode} node
    *        The node which we want information about
    * @return {Object} The type information object contains the following props:
+   * - view {String} Always "computed" to indicate the computed view.
    * - type {String} One of the VIEW_NODE_XXX_TYPE const in
    *   client/inspector/shared/node-types
    * - value {Object} Depends on the type of the node
    * returns null if the node isn't anything we care about
    */
   getNodeInfo: function (node) {
     if (!node) {
       return null;
@@ -382,17 +383,21 @@ CssComputedView.prototype = {
       type = VIEW_NODE_VALUE_TYPE;
     } else if (isHref) {
       type = VIEW_NODE_IMAGE_URL_TYPE;
       value.url = node.href;
     } else {
       return null;
     }
 
-    return {type, value};
+    return {
+      view: "computed",
+      type,
+      value,
+    };
   },
 
   _createPropertyViews: function () {
     if (this._createViewsPromise) {
       return this._createViewsPromise;
     }
 
     this.refreshSourceFilter();
--- a/devtools/client/inspector/layout/components/Accordion.css
+++ b/devtools/client/inspector/layout/components/Accordion.css
@@ -10,17 +10,17 @@
  * any changes to the existing styles.
  */
 
 :root {
   --accordion-header-background: var(--theme-toolbar-background);
 }
 
 :root.theme-dark {
-  --accordion-header-background: #141416;
+  --accordion-header-background: #222225;
 }
 
 .accordion {
   background-color: var(--theme-sidebar-background);
   width: 100%;
 }
 
 .accordion ._header {
--- a/devtools/client/inspector/reducers.js
+++ b/devtools/client/inspector/reducers.js
@@ -3,20 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // This file exposes the Redux reducers of the box model, grid and grid highlighter
 // settings.
 
 exports.animations = require("devtools/client/inspector/animation/reducers/animations");
-exports.animationElementPicker =
-  require("devtools/client/inspector/animation/reducers/element-picker");
-exports.animationSidebar =
-  require("devtools/client/inspector/animation/reducers/sidebar");
 exports.boxModel = require("devtools/client/inspector/boxmodel/reducers/box-model");
 exports.changes = require("devtools/client/inspector/changes/reducers/changes");
 exports.events = require("devtools/client/inspector/events/reducers/events");
 exports.extensionsSidebar = require("devtools/client/inspector/extensions/reducers/sidebar");
 exports.flexboxes = require("devtools/client/inspector/flexbox/reducers/flexboxes");
 exports.fontOptions = require("devtools/client/inspector/fonts/reducers/font-options");
 exports.fonts = require("devtools/client/inspector/fonts/reducers/fonts");
 exports.grids = require("devtools/client/inspector/grids/reducers/grids");
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -285,16 +285,17 @@ CssRuleView.prototype = {
   }),
 
   /**
    * Get the type of a given node in the rule-view
    *
    * @param {DOMNode} node
    *        The node which we want information about
    * @return {Object} The type information object contains the following props:
+   * - view {String} Always "rule" to indicate the rule view.
    * - type {String} One of the VIEW_NODE_XXX_TYPE const in
    *   client/inspector/shared/node-types
    * - value {Object} Depends on the type of the node
    * returns null of the node isn't anything we care about
    */
   getNodeInfo: function (node) {
     if (!node) {
       return null;
@@ -378,17 +379,21 @@ CssRuleView.prototype = {
                classes.contains("ruleview-rule-source-label")) {
       type = VIEW_NODE_LOCATION_TYPE;
       let rule = this._getRuleEditorForNode(node).rule;
       value = (rule.sheet && rule.sheet.href) ? rule.sheet.href : rule.title;
     } else {
       return null;
     }
 
-    return {type, value};
+    return {
+      view: "rule",
+      type,
+      value,
+    };
   },
 
   /**
    * Retrieve the RuleEditor instance that should be stored on
    * the offset parent of the node
    */
   _getRuleEditorForNode: function (node) {
     if (!node.offsetParent) {
--- a/devtools/client/inspector/rules/test/browser_rules_cssom.js
+++ b/devtools/client/inspector/rules/test/browser_rules_cssom.js
@@ -10,13 +10,20 @@
 const TEST_URI = URL_ROOT + "doc_cssom.html";
 
 add_task(function* () {
   yield addTab(TEST_URI);
   let {inspector, view} = yield openRuleView();
   yield selectNode("#target", inspector);
 
   let elementStyle = view._elementStyle;
-  let rule = elementStyle.rules[1];
+  let rule;
 
-  is(rule.textProps.length, 1, "rule should have one property");
+  rule = elementStyle.rules[1];
+  is(rule.textProps.length, 1, "rule 1 should have one property");
   is(rule.textProps[0].name, "color", "the property should be 'color'");
+  is(rule.ruleLine, -1, "the property has no source line");
+
+  rule = elementStyle.rules[2];
+  is(rule.textProps.length, 1, "rule 2 should have one property");
+  is(rule.textProps[0].name, "font-weight", "the property should be 'font-weight'");
+  is(rule.ruleLine, -1, "the property has no source line");
 });
--- a/devtools/client/inspector/rules/test/doc_cssom.html
+++ b/devtools/client/inspector/rules/test/doc_cssom.html
@@ -4,16 +4,19 @@
 <head>
   <title>CSSOM test</title>
 
   <script>
     "use strict";
     window.onload = function () {
       let x = document.styleSheets[0];
       x.insertRule("div { color: seagreen; }", 1);
+
+      // Add a rule with a leading newline, to test that inspector can handle it.
+      x.insertRule("\ndiv { font-weight: bold; }", 1);
     };
   </script>
 
   <style>
     span { }
   </style>
 </head>
 <body>
--- a/devtools/client/inspector/shared/highlighters-overlay.js
+++ b/devtools/client/inspector/shared/highlighters-overlay.js
@@ -67,18 +67,24 @@ class HighlightersOverlay {
 
     // Add inspector events, not specific to a given view.
     this.inspector.on("markupmutation", this.onMarkupMutation);
     this.inspector.target.on("will-navigate", this.onWillNavigate);
 
     EventEmitter.decorate(this);
   }
 
-  get isRuleView() {
-    return this.inspector.sidebar.getCurrentTabID() == "ruleview";
+  /**
+   * Returns whether `node` is somewhere inside the DOM of the rule view.
+   *
+   * @param {DOMNode} node
+   * @return {Boolean}
+   */
+  isRuleView(node) {
+    return !!node.closest("#ruleview-panel");
   }
 
   /**
    * Add the highlighters overlay to the view. This will start tracking mouse events
    * and display highlighters when needed.
    *
    * @param  {CssRuleView|CssComputedView|LayoutView} view
    *         Either the rule-view or computed-view panel to add the highlighters overlay.
@@ -675,83 +681,91 @@ class HighlightersOverlay {
   /**
    * Is the current hovered node a css transform property value in the
    * computed-view.
    *
    * @param  {Object} nodeInfo
    * @return {Boolean}
    */
   _isComputedViewTransform(nodeInfo) {
-    let isTransform = nodeInfo.type === VIEW_NODE_VALUE_TYPE &&
-                      nodeInfo.value.property === "transform";
-    return !this.isRuleView && isTransform;
+    if (nodeInfo.view != "computed") {
+      return false;
+    }
+    return nodeInfo.type === VIEW_NODE_VALUE_TYPE &&
+           nodeInfo.value.property === "transform";
   }
 
   /**
    * Is the current clicked node a flex display property value in the
    * rule-view.
    *
    * @param  {DOMNode} node
    * @return {Boolean}
    */
   _isRuleViewDisplayFlex(node) {
-    return this.isRuleView && node.classList.contains("ruleview-flex");
+    return this.isRuleView(node) && node.classList.contains("ruleview-flex");
   }
 
   /**
    * Is the current clicked node a grid display property value in the
    * rule-view.
    *
    * @param  {DOMNode} node
    * @return {Boolean}
    */
   _isRuleViewDisplayGrid(node) {
-    return this.isRuleView && node.classList.contains("ruleview-grid");
+    return this.isRuleView(node) && node.classList.contains("ruleview-grid");
   }
 
   /**
    * Does the current clicked node have the shapes highlighter toggle in the
    * rule-view.
    *
    * @param  {DOMNode} node
    * @return {Boolean}
    */
   _isRuleViewShape(node) {
-    return this.isRuleView && node.classList.contains("ruleview-shape");
+    return this.isRuleView(node) && node.classList.contains("ruleview-shape");
   }
 
   /**
    * Is the current hovered node a css transform property value in the rule-view.
    *
    * @param  {Object} nodeInfo
    * @return {Boolean}
    */
   _isRuleViewTransform(nodeInfo) {
+    if (nodeInfo.view != "rule") {
+      return false;
+    }
     let isTransform = nodeInfo.type === VIEW_NODE_VALUE_TYPE &&
                       nodeInfo.value.property === "transform";
     let isEnabled = nodeInfo.value.enabled &&
                     !nodeInfo.value.overridden &&
                     !nodeInfo.value.pseudoElement;
-    return this.isRuleView && isTransform && isEnabled;
+    return isTransform && isEnabled;
   }
 
   /**
    * Is the current hovered node a highlightable shape point in the rule-view.
    *
    * @param  {Object} nodeInfo
    * @return {Boolean}
    */
   isRuleViewShapePoint(nodeInfo) {
+    if (nodeInfo.view != "rule") {
+      return false;
+    }
     let isShape = nodeInfo.type === VIEW_NODE_SHAPE_POINT_TYPE &&
                   (nodeInfo.value.property === "clip-path" ||
                   nodeInfo.value.property === "shape-outside");
     let isEnabled = nodeInfo.value.enabled &&
                     !nodeInfo.value.overridden &&
                     !nodeInfo.value.pseudoElement;
-    return this.isRuleView && isShape && isEnabled && nodeInfo.value.toggleActive &&
+    return isShape && isEnabled && nodeInfo.value.toggleActive &&
            !this.state.shapes.options.transformMode;
   }
 
   onClick(event) {
     if (this._isRuleViewDisplayGrid(event.target)) {
       event.stopPropagation();
 
       let { store } = this.inspector;
@@ -783,17 +797,17 @@ class HighlightersOverlay {
       return;
     }
 
     // Only one highlighter can be displayed at a time, hide the currently shown.
     this._hideHoveredHighlighter();
 
     this._lastHovered = event.target;
 
-    let view = this.isRuleView ?
+    let view = this.isRuleView(this._lastHovered) ?
       this.inspector.getPanel("ruleview").view :
       this.inspector.getPanel("computedview").computedView;
     let nodeInfo = view.getNodeInfo(event.target);
     if (!nodeInfo) {
       return;
     }
 
     if (this.isRuleViewShapePoint(nodeInfo)) {
@@ -826,17 +840,17 @@ class HighlightersOverlay {
   onMouseOut(event) {
     // Only hide the highlighter if the mouse leaves the currently hovered node.
     if (!this._lastHovered ||
         (event && this._lastHovered.contains(event.relatedTarget))) {
       return;
     }
 
     // Otherwise, hide the highlighter.
-    let view = this.isRuleView ?
+    let view = this.isRuleView(this._lastHovered) ?
       this.inspector.getPanel("ruleview").view :
       this.inspector.getPanel("computedview").computedView;
     let nodeInfo = view.getNodeInfo(this._lastHovered);
     if (nodeInfo && this.isRuleViewShapePoint(nodeInfo)) {
       this.hoverPointShapesHighlighter(this.inspector.selection.nodeFront, null);
       this.emit("hover-shape-point", null);
     }
     this._lastHovered = null;
--- a/devtools/client/jsonview/test/head.js
+++ b/devtools/client/jsonview/test/head.js
@@ -64,19 +64,31 @@ async function addJsonViewTab(url, {
       },
     }, Ci.nsIWebProgress.NOTIFY_LOCATION);
   })]);
 
   // Load devtools/shared/frame-script-utils.js
   getFrameScript();
   let rootDir = getRootDirectory(gTestPath);
 
+  // Catch RequireJS errors (usually timeouts)
+  let error = tabLoaded.then(() => new Promise((resolve, reject) => {
+    let {requirejs} = content.wrappedJSObject;
+    if (requirejs) {
+      requirejs.onError = err => {
+        info(err);
+        ok(false, "RequireJS error");
+        reject(err);
+      };
+    }
+  }));
+
   let data = {rootDir, appReadyState, docReadyState};
   // eslint-disable-next-line no-shadow
-  await ContentTask.spawn(browser, data, async function (data) {
+  await Promise.race([error, ContentTask.spawn(browser, data, async function (data) {
     // Check if there is a JSONView object.
     let {JSONView} = content.window.wrappedJSObject;
     if (!JSONView) {
       throw new Error("The JSON Viewer did not load.");
     }
 
     // Load frame script with helpers for JSON View tests.
     let frameScriptUrl = data.rootDir + "doc_frame_script.js";
@@ -101,17 +113,17 @@ async function addJsonViewTab(url, {
 
     // Wait until the app readyState suffices.
     while (appReadyStates.indexOf(JSONView.readyState) < appReadyIndex) {
       info(`AppReadyState is "${JSONView.readyState}". Await "${data.appReadyState}"`);
       await new Promise(resolve => {
         content.addEventListener("AppReadyStateChange", resolve, {once: true});
       });
     }
-  });
+  })]);
 
   return tab;
 }
 
 /**
  * Expanding a node in the JSON tree
  */
 function clickJsonNode(selector) {
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -2,17 +2,17 @@
 	if(typeof exports === 'object' && typeof module === 'object')
 		module.exports = factory(require("devtools/client/shared/vendor/react-dom-factories"), require("devtools/client/shared/vendor/lodash"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react"));
 	else if(typeof define === 'function' && define.amd)
 		define(["devtools/client/shared/vendor/react-dom-factories", "devtools/client/shared/vendor/lodash", "devtools/client/shared/vendor/react-prop-types", "devtools/client/shared/vendor/react"], factory);
 	else {
 		var a = typeof exports === 'object' ? factory(require("devtools/client/shared/vendor/react-dom-factories"), require("devtools/client/shared/vendor/lodash"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react")) : factory(root["devtools/client/shared/vendor/react-dom-factories"], root["devtools/client/shared/vendor/lodash"], root["devtools/client/shared/vendor/react-prop-types"], root["devtools/client/shared/vendor/react"]);
 		for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
 	}
-})(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_58__, __WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_6__) {
+})(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_56__, __WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_7__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
 /******/
 /******/ 	// The require function
 /******/ 	function __webpack_require__(moduleId) {
 /******/
 /******/ 		// Check if module is in cache
@@ -65,17 +65,17 @@ return /******/ (function(modules) { // 
 /******/
 /******/ 	// Object.prototype.hasOwnProperty.call
 /******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
 /******/
 /******/ 	// __webpack_public_path__
 /******/ 	__webpack_require__.p = "/assets/build";
 /******/
 /******/ 	// Load entry module and return exports
-/******/ 	return __webpack_require__(__webpack_require__.s = 18);
+/******/ 	return __webpack_require__(__webpack_require__.s = 19);
 /******/ })
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
@@ -518,50 +518,50 @@ module.exports = {
 
 "use strict";
 
 
 /* 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/. */
 
-__webpack_require__(19);
+__webpack_require__(20);
 
 // Load all existing rep templates
-const Undefined = __webpack_require__(20);
-const Null = __webpack_require__(21);
-const StringRep = __webpack_require__(7);
-const LongStringRep = __webpack_require__(22);
-const Number = __webpack_require__(23);
+const Undefined = __webpack_require__(21);
+const Null = __webpack_require__(22);
+const StringRep = __webpack_require__(6);
+const LongStringRep = __webpack_require__(23);
+const Number = __webpack_require__(24);
 const ArrayRep = __webpack_require__(10);
-const Obj = __webpack_require__(24);
-const SymbolRep = __webpack_require__(25);
-const InfinityRep = __webpack_require__(26);
-const NaNRep = __webpack_require__(27);
-const Accessor = __webpack_require__(28);
+const Obj = __webpack_require__(25);
+const SymbolRep = __webpack_require__(26);
+const InfinityRep = __webpack_require__(27);
+const NaNRep = __webpack_require__(28);
+const Accessor = __webpack_require__(29);
 
 // DOM types (grips)
-const Attribute = __webpack_require__(29);
-const DateTime = __webpack_require__(30);
-const Document = __webpack_require__(31);
-const Event = __webpack_require__(32);
-const Func = __webpack_require__(33);
+const Attribute = __webpack_require__(30);
+const DateTime = __webpack_require__(31);
+const Document = __webpack_require__(32);
+const Event = __webpack_require__(33);
+const Func = __webpack_require__(34);
 const PromiseRep = __webpack_require__(38);
 const RegExp = __webpack_require__(39);
 const StyleSheet = __webpack_require__(40);
 const CommentNode = __webpack_require__(41);
 const ElementNode = __webpack_require__(42);
 const TextNode = __webpack_require__(43);
 const ErrorRep = __webpack_require__(44);
 const Window = __webpack_require__(45);
 const ObjectWithText = __webpack_require__(46);
 const ObjectWithURL = __webpack_require__(47);
-const GripArray = __webpack_require__(12);
-const GripMap = __webpack_require__(13);
-const GripMapEntry = __webpack_require__(14);
+const GripArray = __webpack_require__(13);
+const GripMap = __webpack_require__(14);
+const GripMapEntry = __webpack_require__(15);
 const Grip = __webpack_require__(8);
 
 // List of all registered template.
 // XXX there should be a way for extensions to register a new
 // or modify an existing rep.
 let reps = [RegExp, StyleSheet, Event, DateTime, CommentNode, ElementNode, TextNode, Attribute, LongStringRep, Func, PromiseRep, ArrayRep, Document, Window, ObjectWithText, ObjectWithURL, ErrorRep, GripArray, GripMap, GripMapEntry, Grip, Undefined, Null, StringRep, Number, SymbolRep, InfinityRep, NaNRep, Accessor];
 
 /**
@@ -734,22 +734,16 @@ function PropRep(props) {
   }, equal), Rep(Object.assign({}, props))];
 }
 
 // Exports from this module
 module.exports = wrapRender(PropRep);
 
 /***/ }),
 /* 6 */
-/***/ (function(module, exports) {
-
-module.exports = __WEBPACK_EXTERNAL_MODULE_6__;
-
-/***/ }),
-/* 7 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -939,16 +933,22 @@ function supportsObject(object, noGrip =
 // Exports from this module
 
 module.exports = {
   rep: wrapRender(StringRep),
   supportsObject
 };
 
 /***/ }),
+/* 7 */
+/***/ (function(module, exports) {
+
+module.exports = __WEBPACK_EXTERNAL_MODULE_7__;
+
+/***/ }),
 /* 8 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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
@@ -1263,27 +1263,27 @@ module.exports = Grip;
 
 /***/ }),
 /* 9 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
-var _svgInlineReact = __webpack_require__(34);
+var _svgInlineReact = __webpack_require__(11);
 
 var _svgInlineReact2 = _interopRequireDefault(_svgInlineReact);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-const React = __webpack_require__(6);
+const React = __webpack_require__(7);
 const PropTypes = __webpack_require__(2);
 
 
 const svg = {
   "open-inspector": __webpack_require__(36),
   "jump-definition": __webpack_require__(37)
 };
 
@@ -1456,16 +1456,114 @@ module.exports = {
 
 /***/ }),
 /* 11 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
+Object.defineProperty(exports, "__esModule", {
+    value: true
+});
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+var _react = __webpack_require__(7);
+
+var _react2 = _interopRequireDefault(_react);
+
+var _propTypes = __webpack_require__(2);
+
+var _util = __webpack_require__(35);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var process = process || { env: {} };
+
+var InlineSVG = function (_React$Component) {
+    _inherits(InlineSVG, _React$Component);
+
+    function InlineSVG() {
+        _classCallCheck(this, InlineSVG);
+
+        return _possibleConstructorReturn(this, (InlineSVG.__proto__ || Object.getPrototypeOf(InlineSVG)).apply(this, arguments));
+    }
+
+    _createClass(InlineSVG, [{
+        key: 'componentWillReceiveProps',
+        value: function componentWillReceiveProps(_ref) {
+            var children = _ref.children;
+
+            if ("production" !== process.env.NODE_ENV && children != null) {
+                console.info('<InlineSVG />: `children` prop will be ignored.');
+            }
+        }
+    }, {
+        key: 'render',
+        value: function render() {
+            var Element = void 0,
+                __html = void 0,
+                svgProps = void 0;
+
+            var _props = this.props,
+                element = _props.element,
+                raw = _props.raw,
+                src = _props.src,
+                otherProps = _objectWithoutProperties(_props, ['element', 'raw', 'src']);
+
+            if (raw === true) {
+                Element = 'svg';
+                svgProps = (0, _util.extractSVGProps)(src);
+                __html = (0, _util.getSVGFromSource)(src).innerHTML;
+            }
+            __html = __html || src;
+            Element = Element || element;
+            svgProps = svgProps || {};
+
+            return _react2.default.createElement(Element, _extends({}, svgProps, otherProps, { src: null, children: null,
+                dangerouslySetInnerHTML: { __html: __html } }));
+        }
+    }]);
+
+    return InlineSVG;
+}(_react2.default.Component);
+
+exports.default = InlineSVG;
+
+
+InlineSVG.defaultProps = {
+    element: 'i',
+    raw: false,
+    src: ''
+};
+
+InlineSVG.propTypes = {
+    src: _propTypes.string.isRequired,
+    element: _propTypes.string,
+    raw: _propTypes.bool
+};
+
+/***/ }),
+/* 12 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
 /* 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/. */
 
 module.exports = {
   ELEMENT_NODE: 1,
   ATTRIBUTE_NODE: 2,
   TEXT_NODE: 3,
@@ -1484,17 +1582,17 @@ module.exports = {
   DOCUMENT_POSITION_PRECEDING: 0x02,
   DOCUMENT_POSITION_FOLLOWING: 0x04,
   DOCUMENT_POSITION_CONTAINS: 0x08,
   DOCUMENT_POSITION_CONTAINED_BY: 0x10,
   DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 0x20
 };
 
 /***/ }),
-/* 12 */
+/* 13 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -1702,17 +1800,17 @@ maxLengthMap.set(MODE.LONG, 10);
 module.exports = {
   rep: wrapRender(GripArray),
   supportsObject,
   maxLengthMap,
   getLength
 };
 
 /***/ }),
-/* 13 */
+/* 14 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -1910,17 +2008,17 @@ maxLengthMap.set(MODE.LONG, 10);
 module.exports = {
   rep: wrapRender(GripMap),
   supportsObject,
   maxLengthMap,
   getLength
 };
 
 /***/ }),
-/* 14 */
+/* 15 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -1988,38 +2086,38 @@ function createGripMapEntry(key, value) 
 // Exports from this module
 module.exports = {
   rep: wrapRender(GripMapEntry),
   createGripMapEntry,
   supportsObject
 };
 
 /***/ }),
-/* 15 */
+/* 16 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-const client = __webpack_require__(16);
-const loadProperties = __webpack_require__(57);
-const node = __webpack_require__(17);
+const client = __webpack_require__(17);
+const loadProperties = __webpack_require__(55);
+const node = __webpack_require__(18);
 
 module.exports = {
   client,
   loadProperties,
   node
 };
 
 /***/ }),
-/* 16 */
+/* 17 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 async function enumIndexedProperties(objectClient, start, end) {
   try {
     const { iterator } = await objectClient.enumProperties({ ignoreNonIndexedProperties: true });
@@ -2084,32 +2182,32 @@ module.exports = {
   enumEntries,
   enumIndexedProperties,
   enumNonIndexedProperties,
   enumSymbols,
   getPrototype
 };
 
 /***/ }),
-/* 17 */
+/* 18 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-const { get, has } = __webpack_require__(58);
+const { get, has } = __webpack_require__(56);
 const { maybeEscapePropertyName } = __webpack_require__(0);
 const ArrayRep = __webpack_require__(10);
-const GripArrayRep = __webpack_require__(12);
-const GripMap = __webpack_require__(13);
-const GripMapEntryRep = __webpack_require__(14);
+const GripArrayRep = __webpack_require__(13);
+const GripMap = __webpack_require__(14);
+const GripMapEntryRep = __webpack_require__(15);
 
 const MAX_NUMERICAL_PROPERTIES = 100;
 
 const NODE_TYPES = {
   BUCKET: Symbol("[n…n]"),
   DEFAULT_PROPERTIES: Symbol("[default properties]"),
   ENTRIES: Symbol("<entries>"),
   GET: Symbol("<get>"),
@@ -2719,30 +2817,30 @@ module.exports = {
   setNodeChildren,
   sortProperties,
   NODE_TYPES,
   // Export for testing purpose.
   SAFE_PATH_PREFIX
 };
 
 /***/ }),
-/* 18 */
+/* 19 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const { MODE } = __webpack_require__(3);
 const { REPS, getRep } = __webpack_require__(4);
 const ObjectInspector = __webpack_require__(48);
-const ObjectInspectorUtils = __webpack_require__(15);
+const ObjectInspectorUtils = __webpack_require__(16);
 
 const {
   parseURLEncodedText,
   parseURLParams,
   maybeEscapePropertyName,
   getGripPreviewItems
 } = __webpack_require__(0);
 
@@ -2754,23 +2852,23 @@ module.exports = {
   parseURLEncodedText,
   parseURLParams,
   getGripPreviewItems,
   ObjectInspector,
   ObjectInspectorUtils
 };
 
 /***/ }),
-/* 19 */
+/* 20 */
 /***/ (function(module, exports) {
 
 // removed by extract-text-webpack-plugin
 
 /***/ }),
-/* 20 */
+/* 21 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -2802,17 +2900,17 @@ function supportsObject(object, noGrip =
 // Exports from this module
 
 module.exports = {
   rep: wrapRender(Undefined),
   supportsObject
 };
 
 /***/ }),
-/* 21 */
+/* 22 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -2844,17 +2942,17 @@ function supportsObject(object, noGrip =
 // Exports from this module
 
 module.exports = {
   rep: wrapRender(Null),
   supportsObject
 };
 
 /***/ }),
-/* 22 */
+/* 23 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -2921,17 +3019,17 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(LongStringRep),
   supportsObject
 };
 
 /***/ }),
-/* 23 */
+/* 24 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -2973,17 +3071,17 @@ function supportsObject(object, noGrip =
 // Exports from this module
 
 module.exports = {
   rep: wrapRender(Number),
   supportsObject
 };
 
 /***/ }),
-/* 24 */
+/* 25 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -3151,17 +3249,17 @@ function supportsObject(object) {
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(ObjectRep),
   supportsObject
 };
 
 /***/ }),
-/* 25 */
+/* 26 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -3203,17 +3301,17 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(SymbolRep),
   supportsObject
 };
 
 /***/ }),
-/* 26 */
+/* 27 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -3251,17 +3349,17 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(InfinityRep),
   supportsObject
 };
 
 /***/ }),
-/* 27 */
+/* 28 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -3288,17 +3386,17 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(NaNRep),
   supportsObject
 };
 
 /***/ }),
-/* 28 */
+/* 29 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -3358,17 +3456,17 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(Accessor),
   supportsObject
 };
 
 /***/ }),
-/* 29 */
+/* 30 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -3379,17 +3477,17 @@ const dom = __webpack_require__(1);
 const { span } = dom;
 
 // Reps
 const {
   getGripType,
   isGrip,
   wrapRender
 } = __webpack_require__(0);
-const { rep: StringRep } = __webpack_require__(7);
+const { rep: StringRep } = __webpack_require__(6);
 
 /**
  * Renders DOM attribute
  */
 Attribute.propTypes = {
   object: PropTypes.object.isRequired
 };
 
@@ -3419,17 +3517,17 @@ function supportsObject(grip, noGrip = f
 }
 
 module.exports = {
   rep: wrapRender(Attribute),
   supportsObject
 };
 
 /***/ }),
-/* 30 */
+/* 31 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -3486,17 +3584,17 @@ function supportsObject(grip, noGrip = f
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(DateTime),
   supportsObject
 };
 
 /***/ }),
-/* 31 */
+/* 32 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -3553,17 +3651,17 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(Document),
   supportsObject
 };
 
 /***/ }),
-/* 32 */
+/* 33 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -3660,17 +3758,17 @@ function supportsObject(grip, noGrip = f
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(Event),
   supportsObject
 };
 
 /***/ }),
-/* 33 */
+/* 34 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -3821,114 +3919,16 @@ function supportsObject(grip, noGrip = f
 module.exports = {
   rep: wrapRender(FunctionRep),
   supportsObject,
   // exported for testing purpose.
   getFunctionName
 };
 
 /***/ }),
-/* 34 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
-    value: true
-});
-
-var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
-
-var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
-
-var _react = __webpack_require__(6);
-
-var _react2 = _interopRequireDefault(_react);
-
-var _propTypes = __webpack_require__(2);
-
-var _util = __webpack_require__(35);
-
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
-
-function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
-
-function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
-
-function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
-
-function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
-
-var process = process || { env: {} };
-
-var InlineSVG = function (_React$Component) {
-    _inherits(InlineSVG, _React$Component);
-
-    function InlineSVG() {
-        _classCallCheck(this, InlineSVG);
-
-        return _possibleConstructorReturn(this, (InlineSVG.__proto__ || Object.getPrototypeOf(InlineSVG)).apply(this, arguments));
-    }
-
-    _createClass(InlineSVG, [{
-        key: 'componentWillReceiveProps',
-        value: function componentWillReceiveProps(_ref) {
-            var children = _ref.children;
-
-            if ("production" !== process.env.NODE_ENV && children != null) {
-                console.info('<InlineSVG />: `children` prop will be ignored.');
-            }
-        }
-    }, {
-        key: 'render',
-        value: function render() {
-            var Element = void 0,
-                __html = void 0,
-                svgProps = void 0;
-
-            var _props = this.props,
-                element = _props.element,
-                raw = _props.raw,
-                src = _props.src,
-                otherProps = _objectWithoutProperties(_props, ['element', 'raw', 'src']);
-
-            if (raw === true) {
-                Element = 'svg';
-                svgProps = (0, _util.extractSVGProps)(src);
-                __html = (0, _util.getSVGFromSource)(src).innerHTML;
-            }
-            __html = __html || src;
-            Element = Element || element;
-            svgProps = svgProps || {};
-
-            return _react2.default.createElement(Element, _extends({}, svgProps, otherProps, { src: null, children: null,
-                dangerouslySetInnerHTML: { __html: __html } }));
-        }
-    }]);
-
-    return InlineSVG;
-}(_react2.default.Component);
-
-exports.default = InlineSVG;
-
-
-InlineSVG.defaultProps = {
-    element: 'i',
-    raw: false,
-    src: ''
-};
-
-InlineSVG.propTypes = {
-    src: _propTypes.string.isRequired,
-    element: _propTypes.string,
-    raw: _propTypes.bool
-};
-
-/***/ }),
 /* 35 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
     value: true
@@ -4242,17 +4242,17 @@ module.exports = {
 const PropTypes = __webpack_require__(2);
 const {
   isGrip,
   cropString,
   cropMultipleLines,
   wrapRender
 } = __webpack_require__(0);
 const { MODE } = __webpack_require__(3);
-const nodeConstants = __webpack_require__(11);
+const nodeConstants = __webpack_require__(12);
 const dom = __webpack_require__(1);
 const { span } = dom;
 
 /**
  * Renders DOM comment node.
  */
 CommentNode.propTypes = {
   object: PropTypes.object.isRequired,
@@ -4307,19 +4307,19 @@ module.exports = {
 // ReactJS
 const PropTypes = __webpack_require__(2);
 
 // Utils
 const {
   isGrip,
   wrapRender
 } = __webpack_require__(0);
-const { rep: StringRep } = __webpack_require__(7);
+const { rep: StringRep } = __webpack_require__(6);
 const { MODE } = __webpack_require__(3);
-const nodeConstants = __webpack_require__(11);
+const nodeConstants = __webpack_require__(12);
 const Svg = __webpack_require__(9);
 
 const dom = __webpack_require__(1);
 const { span } = dom;
 
 /**
  * Renders DOM element node.
  */
@@ -4711,17 +4711,17 @@ module.exports = {
 const PropTypes = __webpack_require__(2);
 
 // Reps
 const {
   isGrip,
   wrapRender
 } = __webpack_require__(0);
 
-const String = __webpack_require__(7).rep;
+const String = __webpack_require__(6).rep;
 
 const dom = __webpack_require__(1);
 const { span } = dom;
 
 /**
  * Renders a grip object with textual data.
  */
 ObjectWithText.propTypes = {
@@ -4839,36 +4839,36 @@ var _devtoolsComponents = __webpack_requ
 var _devtoolsComponents2 = _interopRequireDefault(_devtoolsComponents);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-const { Component, createFactory } = __webpack_require__(6);
+const { Component, createFactory } = __webpack_require__(7);
 const PropTypes = __webpack_require__(2);
 const dom = __webpack_require__(1);
 
 const Tree = createFactory(_devtoolsComponents2.default.Tree);
-__webpack_require__(55);
-
-const classnames = __webpack_require__(56);
+__webpack_require__(53);
+
+const classnames = __webpack_require__(54);
 
 const {
   REPS: {
     Rep,
     Grip
   }
 } = __webpack_require__(4);
 const {
   MODE
 } = __webpack_require__(3);
 
-const Utils = __webpack_require__(15);
+const Utils = __webpack_require__(16);
 
 const {
   getChildren,
   getClosestGripNode,
   getParent,
   getValue,
   nodeHasAccessors,
   nodeHasProperties,
@@ -4937,17 +4937,25 @@ class ObjectInspector extends Component 
     self.getRoots = this.getRoots.bind(this);
   }
 
   shouldComponentUpdate(nextProps, nextState) {
     const {
       expandedPaths,
       loadedProperties
     } = this.state;
-    return this.props.roots !== nextProps.roots || expandedPaths.size !== nextState.expandedPaths.size || loadedProperties.size !== nextState.loadedProperties.size || [...expandedPaths].some(key => !nextState.expandedPaths.has(key));
+
+    if (this.props.roots !== nextProps.roots) {
+      // Since the roots changed, we assume the properties did as well. Thus we can clear
+      // the cachedNodes to avoid bugs and memory leaks.
+      this.cachedNodes.clear();
+      return true;
+    }
+
+    return expandedPaths.size !== nextState.expandedPaths.size || loadedProperties.size !== nextState.loadedProperties.size || [...expandedPaths].some(key => !nextState.expandedPaths.has(key));
   }
 
   componentWillUnmount() {
     const { releaseActor } = this.props;
     if (typeof releaseActor !== "function") {
       return;
     }
 
@@ -5234,43 +5242,43 @@ module.exports = {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
-var _react = __webpack_require__(6);
+var _react = __webpack_require__(7);
 
 var _react2 = _interopRequireDefault(_react);
 
 var _reactDomFactories = __webpack_require__(1);
 
 var _reactDomFactories2 = _interopRequireDefault(_reactDomFactories);
 
 var _propTypes = __webpack_require__(2);
 
 var _propTypes2 = _interopRequireDefault(_propTypes);
 
-var _svgInlineReact = __webpack_require__(51);
+var _svgInlineReact = __webpack_require__(11);
 
 var _svgInlineReact2 = _interopRequireDefault(_svgInlineReact);
 
-var _arrow = __webpack_require__(53);
+var _arrow = __webpack_require__(51);
 
 var _arrow2 = _interopRequireDefault(_arrow);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 const { Component, createFactory, createElement } = _react2.default; /* 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/. */
 
-__webpack_require__(54);
+__webpack_require__(52);
 
 const AUTO_EXPAND_DEPTH = 0; // depth
 
 /**
  * An arrow that displays whether its node is expanded (▼) or collapsed
  * (▶). When its node has no children, it is hidden.
  */
 class ArrowExpander extends Component {
@@ -6019,187 +6027,34 @@ class Tree extends Component {
     }, nodes);
   }
 }
 
 exports.default = Tree;
 
 /***/ }),
 /* 51 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
-    value: true
-});
-
-var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
-
-var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
-
-var _react = __webpack_require__(6);
-
-var _react2 = _interopRequireDefault(_react);
-
-var _propTypes = __webpack_require__(2);
-
-var _util = __webpack_require__(52);
-
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
-
-function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
-
-function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
-
-function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
-
-function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
-
-var process = process || { env: {} };
-
-var InlineSVG = function (_React$Component) {
-    _inherits(InlineSVG, _React$Component);
-
-    function InlineSVG() {
-        _classCallCheck(this, InlineSVG);
-
-        return _possibleConstructorReturn(this, (InlineSVG.__proto__ || Object.getPrototypeOf(InlineSVG)).apply(this, arguments));
-    }
-
-    _createClass(InlineSVG, [{
-        key: 'componentWillReceiveProps',
-        value: function componentWillReceiveProps(_ref) {
-            var children = _ref.children;
-
-            if ("production" !== process.env.NODE_ENV && children != null) {
-                console.info('<InlineSVG />: `children` prop will be ignored.');
-            }
-        }
-    }, {
-        key: 'render',
-        value: function render() {
-            var Element = void 0,
-                __html = void 0,
-                svgProps = void 0;
-
-            var _props = this.props,
-                element = _props.element,
-                raw = _props.raw,
-                src = _props.src,
-                otherProps = _objectWithoutProperties(_props, ['element', 'raw', 'src']);
-
-            if (raw === true) {
-                Element = 'svg';
-                svgProps = (0, _util.extractSVGProps)(src);
-                __html = (0, _util.getSVGFromSource)(src).innerHTML;
-            }
-            __html = __html || src;
-            Element = Element || element;
-            svgProps = svgProps || {};
-
-            return _react2.default.createElement(Element, _extends({}, svgProps, otherProps, { src: null, children: null,
-                dangerouslySetInnerHTML: { __html: __html } }));
-        }
-    }]);
-
-    return InlineSVG;
-}(_react2.default.Component);
-
-exports.default = InlineSVG;
-
-
-InlineSVG.defaultProps = {
-    element: 'i',
-    raw: false,
-    src: ''
-};
-
-InlineSVG.propTypes = {
-    src: _propTypes.string.isRequired,
-    element: _propTypes.string,
-    raw: _propTypes.bool
-};
-
-/***/ }),
-/* 52 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
-    value: true
-});
-exports.convertReactSVGDOMProperty = convertReactSVGDOMProperty;
-exports.startsWith = startsWith;
-exports.serializeAttrs = serializeAttrs;
-exports.getSVGFromSource = getSVGFromSource;
-exports.extractSVGProps = extractSVGProps;
-// Transform DOM prop/attr names applicable to `<svg>` element but react-limited
-
-function convertReactSVGDOMProperty(str) {
-    return str.replace(/[-|:]([a-z])/g, function (g) {
-        return g[1].toUpperCase();
-    });
-}
-
-function startsWith(str, substring) {
-    return str.indexOf(substring) === 0;
-}
-
-var DataPropPrefix = 'data-';
-// Serialize `Attr` objects in `NamedNodeMap`
-function serializeAttrs(map) {
-    var ret = {};
-    for (var prop, i = 0; i < map.length; i++) {
-        var key = map[i].name;
-        if (!startsWith(key, DataPropPrefix)) {
-            prop = convertReactSVGDOMProperty(key);
-        }
-        ret[prop] = map[i].value;
-    }
-    return ret;
-}
-
-function getSVGFromSource(src) {
-    var svgContainer = document.createElement('div');
-    svgContainer.innerHTML = src;
-    var svg = svgContainer.firstElementChild;
-    svg.remove(); // deref from parent element
-    return svg;
-}
-
-// get <svg /> element props
-function extractSVGProps(src) {
-    var map = getSVGFromSource(src).attributes;
-    return map.length > 0 ? serializeAttrs(map) : null;
-}
-
-/***/ }),
-/* 53 */
 /***/ (function(module, exports) {
 
 module.exports = "<!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8 13.4c-.5 0-.9-.2-1.2-.6L.4 5.2C0 4.7-.1 4.3.2 3.7S1 3 1.6 3h12.8c.6 0 1.2.1 1.4.7.3.6.2 1.1-.2 1.6l-6.4 7.6c-.3.4-.7.5-1.2.5z\"></path></svg>"
 
 /***/ }),
-/* 54 */
+/* 52 */
 /***/ (function(module, exports) {
 
 // removed by extract-text-webpack-plugin
 
 /***/ }),
-/* 55 */
+/* 53 */
 /***/ (function(module, exports) {
 
 // removed by extract-text-webpack-plugin
 
 /***/ }),
-/* 56 */
+/* 54 */
 /***/ (function(module, exports, __webpack_require__) {
 
 var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
   Copyright (c) 2016 Jed Watson.
   Licensed under the MIT License (MIT), see
   http://jedwatson.github.io/classnames
 */
 /* global define */
@@ -6244,49 +6099,49 @@ var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBP
 				__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
 	} else {
 		window.classNames = classNames;
 	}
 }());
 
 
 /***/ }),
-/* 57 */
+/* 55 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const {
   enumEntries,
   enumIndexedProperties,
   enumNonIndexedProperties,
   getPrototype,
   enumSymbols
-} = __webpack_require__(16);
+} = __webpack_require__(17);
 
 const {
   getClosestGripNode,
   getClosestNonBucketNode,
   getValue,
   nodeHasAccessors,
   nodeHasAllEntriesInPreview,
   nodeHasProperties,
   nodeIsBucket,
   nodeIsDefaultProperties,
   nodeIsEntries,
   nodeIsMapEntry,
   nodeIsPrimitive,
   nodeIsProxy,
   nodeNeedsNumericalBuckets
-} = __webpack_require__(17);
+} = __webpack_require__(18);
 
 function loadItemProperties(item, createObjectClient, loadedProperties) {
   const [start, end] = item.meta ? [item.meta.startIndex, item.meta.endIndex] : [];
 
   let objectClient;
   const getObjectClient = () => {
     if (objectClient) {
       return objectClient;
@@ -6381,16 +6236,16 @@ module.exports = {
   shouldLoadItemEntries,
   shouldLoadItemIndexedProperties,
   shouldLoadItemNonIndexedProperties,
   shouldLoadItemPrototype,
   shouldLoadItemSymbols
 };
 
 /***/ }),
-/* 58 */
+/* 56 */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_58__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_56__;
 
 /***/ })
 /******/ ]);
 });
\ No newline at end of file
--- a/devtools/client/shared/test/browser_treeWidget_keyboard_interaction.js
+++ b/devtools/client/shared/test/browser_treeWidget_keyboard_interaction.js
@@ -6,26 +6,26 @@
 
 // Tests that keyboard interaction works fine with the tree widget
 
 const TEST_URI = "data:text/html;charset=utf-8,<head>" +
   "<link rel='stylesheet' type='text/css' href='chrome://devtools/skin/widg" +
   "ets.css'></head><body><div></div><span></span></body>";
 const {TreeWidget} = require("devtools/client/shared/widgets/TreeWidget");
 
-add_task(function* () {
-  yield addTab("about:blank");
-  let [host, win, doc] = yield createHost("bottom", TEST_URI);
+add_task(async function () {
+  await addTab("about:blank");
+  let [host, win, doc] = await createHost("bottom", TEST_URI);
 
   let tree = new TreeWidget(doc.querySelector("div"), {
     defaultType: "store"
   });
 
   populateTree(tree, doc);
-  yield testKeyboardInteraction(tree, win);
+  await testKeyboardInteraction(tree, win);
 
   tree.destroy();
   host.destroy();
   gBrowser.removeCurrentTab();
 });
 
 function populateTree(tree, doc) {
   tree.add([{
@@ -78,147 +78,150 @@ function populateTree(tree, doc) {
 function click(node) {
   let win = node.ownerDocument.defaultView;
   executeSoon(() => EventUtils.synthesizeMouseAtCenter(node, {}, win));
 }
 
 /**
  * Tests if pressing navigation keys on the tree items does the expected behavior
  */
-function* testKeyboardInteraction(tree, win) {
+async function testKeyboardInteraction(tree, win) {
   info("Testing keyboard interaction with the tree");
   let event;
   let pass = (e, d, a) => event.resolve([e, d, a]);
 
   info("clicking on first top level item");
   let node = tree.root.children.firstChild.firstChild;
   event = defer();
+  // The select event handler will be called before the click event hasn't
+  // fully finished, so wait for both of them.
+  let clicked = once(node, "click");
   tree.once("select", pass);
   click(node);
-  yield event.promise;
+  await Promise.all([event.promise, clicked]);
   node = tree.root.children.firstChild.nextSibling.firstChild;
   // node should not have selected class
   ok(!node.classList.contains("theme-selected"), "Node should not have selected class");
   ok(!node.hasAttribute("expanded"), "Node is not expanded");
 
   info("Pressing down key to select next item");
   event = defer();
   tree.once("select", pass);
   EventUtils.sendKey("DOWN", win);
-  let [name, data, attachment] = yield event.promise;
+  let [name, data, attachment] = await event.promise;
   is(name, "select", "Select event was fired after pressing down");
   is(data[0], "level1", "Correct item was selected after pressing down");
   ok(!attachment, "null attachment was emitted");
   ok(node.classList.contains("theme-selected"), "Node has selected class");
   ok(node.hasAttribute("expanded"), "Node is expanded now");
 
   info("Pressing down key again to select next item");
   event = defer();
   tree.once("select", pass);
   EventUtils.sendKey("DOWN", win);
-  [name, data, attachment] = yield event.promise;
+  [name, data, attachment] = await event.promise;
   is(data.length, 2, "Correct level item was selected after second down keypress");
   is(data[0], "level1", "Correct parent level");
   is(data[1], "level2", "Correct second level");
 
   info("Pressing down key again to select next item");
   event = defer();
   tree.once("select", pass);
   EventUtils.sendKey("DOWN", win);
-  [name, data, attachment] = yield event.promise;
+  [name, data, attachment] = await event.promise;
   is(data.length, 3, "Correct level item was selected after third down keypress");
   is(data[0], "level1", "Correct parent level");
   is(data[1], "level2", "Correct second level");
   is(data[2], "level3", "Correct third level");
 
   info("Pressing down key again to select next item");
   event = defer();
   tree.once("select", pass);
   EventUtils.sendKey("DOWN", win);
-  [name, data, attachment] = yield event.promise;
+  [name, data, attachment] = await event.promise;
   is(data.length, 2, "Correct level item was selected after fourth down keypress");
   is(data[0], "level1", "Correct parent level");
   is(data[1], "level2-1", "Correct second level");
 
   // pressing left to check expand collapse feature.
   // This does not emit any event, so listening for keypress
   tree.root.children.addEventListener("keypress", function () {
     // executeSoon so that other listeners on the same method are executed first
     executeSoon(() => event.resolve(null));
   }, {once: true});
   info("Pressing left key to collapse the item");
   event = defer();
   node = tree._selectedLabel;
   ok(node.hasAttribute("expanded"), "Item is expanded before left keypress");
   EventUtils.sendKey("LEFT", win);
-  yield event.promise;
+  await event.promise;
 
   ok(!node.hasAttribute("expanded"), "Item is not expanded after left keypress");
 
   // pressing left on collapsed item should select the previous item
 
   info("Pressing left key on collapsed item to select previous");
   tree.once("select", pass);
   event = defer();
   // parent node should have no effect of this keypress
   node = tree.root.children.firstChild.nextSibling.firstChild;
   ok(node.hasAttribute("expanded"), "Parent is expanded");
   EventUtils.sendKey("LEFT", win);
-  [name, data] = yield event.promise;
+  [name, data] = await event.promise;
   is(data.length, 3, "Correct level item was selected after second left keypress");
   is(data[0], "level1", "Correct parent level");
   is(data[1], "level2", "Correct second level");
   is(data[2], "level3", "Correct third level");
   ok(node.hasAttribute("expanded"), "Parent is still expanded after left keypress");
 
   // pressing down again
 
   info("Pressing down key to select next item");
   event = defer();
   tree.once("select", pass);
   EventUtils.sendKey("DOWN", win);
-  [name, data, attachment] = yield event.promise;
+  [name, data, attachment] = await event.promise;
   is(data.length, 2, "Correct level item was selected after fifth down keypress");
   is(data[0], "level1", "Correct parent level");
   is(data[1], "level2-1", "Correct second level");
 
   // collapsing the item to check expand feature.
 
   tree.root.children.addEventListener("keypress", function () {
     executeSoon(() => event.resolve(null));
   }, {once: true});
   info("Pressing left key to collapse the item");
   event = defer();
   node = tree._selectedLabel;
   ok(node.hasAttribute("expanded"), "Item is expanded before left keypress");
   EventUtils.sendKey("LEFT", win);
-  yield event.promise;
+  await event.promise;
   ok(!node.hasAttribute("expanded"), "Item is collapsed after left keypress");
 
   // pressing right should expand this now.
 
   tree.root.children.addEventListener("keypress", function () {
     executeSoon(() => event.resolve(null));
   }, {once: true});
   info("Pressing right key to expend the collapsed item");
   event = defer();
   node = tree._selectedLabel;
   ok(!node.hasAttribute("expanded"), "Item is collapsed before right keypress");
   EventUtils.sendKey("RIGHT", win);
-  yield event.promise;
+  await event.promise;
   ok(node.hasAttribute("expanded"), "Item is expanded after right keypress");
 
   // selecting last item node to test edge navigation case
 
   tree.selectedItem = ["level1.1", "level2", "level3"];
   node = tree._selectedLabel;
   // pressing down again should not change selection
   event = defer();
   tree.root.children.addEventListener("keypress", function () {
     executeSoon(() => event.resolve(null));
   }, {once: true});
   info("Pressing down key on last item of the tree");
   EventUtils.sendKey("DOWN", win);
-  yield event.promise;
+  await event.promise;
 
   ok(tree.isSelected(["level1.1", "level2", "level3"]),
      "Last item is still selected after pressing down on last item of the tree");
 }
--- a/devtools/client/themes/animation.css
+++ b/devtools/client/themes/animation.css
@@ -4,36 +4,47 @@
 
 /* Animation-inspector specific theme variables */
 
 :root {
   --animation-even-background-color: rgba(0, 0, 0, 0.05);
   --command-pick-image: url(chrome://devtools/skin/images/command-pick.svg);
   --graph-right-offset: 10px;
   --sidebar-width: 200px;
+  --tick-line-style: 0.5px solid rgba(128, 136, 144, 0.5);
 }
 
 :root.theme-dark {
   --animation-even-background-color: rgba(255, 255, 255, 0.05);
 }
 
 :root.theme-firebug {
   --command-pick-image: url(chrome://devtools/skin/images/firebug/command-pick.svg);
 }
 
 /* Root element of animation inspector */
 #animation-container {
   height: 100%;
 }
 
+#animation-container .uncontrolled {
+  overflow: hidden;
+}
+
+#animation-container:not(.animation-detail-visible) .controlled {
+  display: none;
+}
+
 /* Animation List Container */
 .animation-list-container {
   display: flex;
   flex-direction: column;
   height: 100%;
+  overflow: hidden;
+  width: 100%;
 }
 
 /* Animation List Header */
 .animation-list-header {
   display: flex;
   justify-content: flex-end;
   padding: 0;
 }
@@ -41,26 +52,26 @@
 /* Animation Timeline Tick List */
 .animation-timeline-tick-list {
   margin-right: var(--graph-right-offset);
   position: relative;
   width: calc(100% - var(--sidebar-width) - var(--graph-right-offset));
 }
 
 .animation-timeline-tick-item {
-  border-left: 0.5px solid rgba(128, 136, 144, .5);
+  border-left: var(--tick-line-style);
   height: 100vh;
   position: absolute;
 }
 
 /* Animation List */
 .animation-list {
   flex: 1;
   list-style-type: none;
-  margin-top: 0;
+  margin: 0;
   overflow: auto;
   padding: 0;
 }
 
 /* Animation Item */
 .animation-item {
   display: flex;
   height: 30px;
@@ -80,16 +91,20 @@
   --effect-timing-graph-color: var(--theme-highlight-bluegrey);
 }
 
 .animation-item.scriptanimation {
   --computed-timing-graph-color: var(--theme-graphs-green);
   --effect-timing-graph-color: var(--theme-highlight-green);
 }
 
+.animation-item.selected {
+  background-color: var(--theme-selection-background-hover);
+}
+
 /* Animation Target */
 .animation-target {
   align-items: center;
   display: flex;
   height: 100%;
   padding-left: 4px;
   width: var(--sidebar-width);
 }
@@ -210,16 +225,91 @@
   paint-order: stroke;
   stroke: var(--theme-body-background);
   stroke-linejoin: round;
   stroke-opacity: .5;
   stroke-width: 4;
   text-anchor: end;
 }
 
+/* Animation Detail */
+.animation-detail-container {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  overflow: hidden;
+  width: 100%;
+}
+
+.animation-detail-header {
+  display: flex;
+}
+
+.animation-detail-title {
+  flex: 1;
+  white-space: nowrap;
+}
+
+.animation-detail-close-button::before {
+  background-image: url(chrome://devtools/skin/images/close.svg);
+}
+
+/* Animated Property List Container */
+.animated-property-list-container {
+  display: flex;
+  flex: 1;
+  flex-direction: column;
+  overflow-y: auto;
+}
+
+/* Animated Property List Header */
+.animated-property-list-header {
+  display: flex;
+  justify-content: flex-end;
+  padding: 0;
+}
+
+/* Keyframes Progress Tick List */
+.keyframes-progress-tick-list {
+  margin-right: var(--graph-right-offset);
+  position: absolute;
+  width: calc(100% - var(--sidebar-width) - var(--graph-right-offset));
+}
+
+.keyframes-progress-tick-item {
+  height: 100vh;
+  position: absolute;
+}
+
+.keyframes-progress-tick-item.left {
+  border-left: var(--tick-line-style);
+}
+
+.keyframes-progress-tick-item.right {
+  border-right: var(--tick-line-style);
+}
+
+/* Animated Property List */
+.animated-property-list {
+  flex: 1;
+  list-style-type: none;
+  margin-top: 0;
+  overflow-y: auto;
+  padding: 0;
+}
+
+/* Animated Property Item */
+.animated-property-item {
+  height: 30px;
+}
+
+.animated-property-item:nth-child(2n+1) {
+  background-color: var(--animation-even-background-color);
+}
+
 /* No Animation Panel */
 .animation-error-message {
   overflow: auto;
 }
 
 .animation-error-message > p {
   white-space: pre;
 }
--- a/devtools/client/themes/markup.css
+++ b/devtools/client/themes/markup.css
@@ -1,22 +1,22 @@
 /* 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/. */
 
 :root {
-  --markup-hidden-attr-name-color: #CA60AC;
+  --markup-hidden-attr-name-color: #BA89B8;
   --markup-hidden-attr-value-color: #5C6D87;
   --markup-hidden-punctuation-color: #909090;
   --markup-hidden-tag-color: #97A4B3;
   --markup-outline: var(--theme-splitter-color);
 }
 
 .theme-dark:root {
-  --markup-hidden-attr-name-color: #CC8EC8;
+  --markup-hidden-attr-name-color: #B07EB3;
   --markup-hidden-attr-value-color: #9893A3;
   --markup-hidden-punctuation-color: #909090;
   --markup-hidden-tag-color: #AFB5BF;
   --markup-outline: var(--theme-selection-background);
 }
 
 * {
   padding: 0;
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -9,17 +9,17 @@
   --rule-header-background-color: var(--theme-toolbar-background);
   --rule-flex-toggle-color: var(--grey-90);
   --rule-shape-toggle-color: var(--grey-90);
 }
 
 :root.theme-dark {
   --rule-highlight-background-color: #521C76;
   --rule-overridden-item-border-color: var(--theme-content-color1);
-  --rule-header-background-color: #141416;
+  --rule-header-background-color: #222225;
   --rule-flex-toggle-color: var(--grey-10);
   --rule-shape-toggle-color: var(--grey-10);
 }
 
 :root.theme-firebug {
   --rule-highlight-background-color: var(--theme-highlight-yellow);
   --rule-property-name: darkgreen;
   --rule-property-value: darkblue;
--- a/devtools/client/themes/variables.css
+++ b/devtools/client/themes/variables.css
@@ -98,49 +98,49 @@
   --theme-command-line-image-focus: url(chrome://devtools/skin/images/commandline-icon.svg#light-theme-focus);
 
   --theme-codemirror-gutter-background: #f4f4f4;
   --theme-messageCloseButtonFilter: invert(0);
 }
 
 :root.theme-dark {
   --theme-body-background: var(--grey-80);
-  --theme-sidebar-background: var(--grey-90);
+  --theme-sidebar-background: #1B1B1D;
   --theme-contrast-background: #ffb35b;
 
   /* Toolbar */
   --theme-tab-toolbar-background: var(--grey-90);
-  --theme-toolbar-background: var(--grey-90);
+  --theme-toolbar-background: #1B1B1D;
   --theme-toolbar-color: var(--grey-40);
   --theme-toolbar-selected-color: white;
   --theme-toolbar-checked-color: #75BFFF;
   --theme-toolbar-highlighted-color: var(--green-50);
   --theme-toolbar-background-hover: #20232B;
-  --theme-toolbar-background-alt: #2F343E;
+  --theme-toolbar-background-alt: #1B1B1D;
   --theme-toolbar-hover: #252526;
   --theme-toolbar-hover-active: #252526;
 
   /* Selection */
   --theme-selection-background: #204E8A;
   --theme-selection-background-hover: #353B48;
   --theme-selection-color: #ffffff;
 
   /* Border color that splits the toolbars/panels/headers.
    * This needs to be sync with commandline.css and commandline-browser.css. */
   --theme-splitter-color: #3c3c3d;
 
   --theme-comment: #939393;
   --theme-comment-alt: #939393;
 
-  --theme-body-color: var(--grey-40);
+  --theme-body-color: #909090;
   --theme-body-color-alt: var(--grey-50);
   --theme-body-color-inactive: var(--grey-40);
   --theme-content-color1: var(--grey-30);
   --theme-content-color2: var(--grey-40);
-  --theme-content-color3: #939393;
+  --theme-content-color3: #58575c;
 
   --theme-highlight-green: #86DE74;
   --theme-highlight-blue: #75BFFF;
   --theme-highlight-purple: #B98EFF;
   --theme-highlight-red: #FF7DE9;
   --theme-highlight-yellow: #FFF89E;
 
   /* These theme-highlight color variables have not been photonized. */
--- a/devtools/client/webconsole/new-console-output/test/components/console-api-call.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/console-api-call.test.js
@@ -265,17 +265,17 @@ describe("ConsoleAPICall component:", ()
       const secondElementStyle = elements.eq(1).prop("style");
       // Allowed styles are applied accordingly on the second element.
       expect(secondElementStyle.color).toBe(`red`);
       // Forbidden styles are not applied.
       expect(secondElementStyle.background).toBe(undefined);
     });
 
     it("toggle the group when the collapse button is clicked", () => {
-      const store = setupStore([]);
+      const store = setupStore();
       store.dispatch = sinon.spy();
       const message = stubPreparedMessages.get("console.group('bar')");
 
       let wrapper = mount(Provider({store},
         ConsoleApiCall({
           message,
           open: true,
           dispatch: store.dispatch,
--- a/devtools/client/webconsole/new-console-output/test/components/evaluation-result.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/evaluation-result.test.js
@@ -60,17 +60,17 @@ describe("EvaluationResult component:", 
     const message = stubPreparedMessages.get("cd(document)");
     const wrapper = render(EvaluationResult({ message, serviceContainer }));
 
     expect(wrapper.find(".message-body").text())
       .toBe("Cannot cd() to the given window. Invalid argument.");
   });
 
   it("displays a [Learn more] link", () => {
-    const store = setupStore([]);
+    const store = setupStore();
 
     const message = stubPreparedMessages.get("asdf()");
 
     serviceContainer.openLink = sinon.spy();
     const wrapper = mount(Provider({store},
       EvaluationResult({message, serviceContainer})
     ));
 
--- a/devtools/client/webconsole/new-console-output/test/components/filter-bar.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/filter-bar.test.js
@@ -26,17 +26,17 @@ const serviceContainer = require("devtoo
 const ServicesMock = require("Services");
 
 describe("FilterBar component:", () => {
   afterEach(() => {
     ServicesMock.prefs.testHelpers.clearPrefs();
   });
 
   it("initial render", () => {
-    const store = setupStore([]);
+    const store = setupStore();
 
     const wrapper = render(Provider({store}, FilterBar({ serviceContainer })));
     const toolbar = wrapper.find(
       ".devtools-toolbar.webconsole-filterbar-primary"
     );
 
     // Clear button
     const clearButton = toolbar.children().eq(0);
@@ -164,17 +164,17 @@ describe("FilterBar component:", () => {
     expect(toolbar.exists()).toBeTruthy();
 
     const message = toolbar.find(".filter-message-text");
     expect(message.text()).toBe("10 items hidden by filters");
     expect(message.prop("title")).toBe("error: 2, warn: 2, log: 2, info: 2, debug: 2");
   });
 
   it("does not display the number of hidden messages when there are no messages", () => {
-    const store = setupStore([]);
+    const store = setupStore();
     const wrapper = mount(Provider({store}, FilterBar({ serviceContainer })));
     const toolbar = wrapper.find(".webconsole-filterbar-filtered-messages");
     expect(toolbar.exists()).toBeFalsy();
   });
 
   it("does not display the number of hidden non-default filters (CSS, Network,…)", () => {
     const store = setupStore([
       "Unknown property ‘such-unknown-property’.  Declaration dropped.",
@@ -189,17 +189,17 @@ describe("FilterBar component:", () => {
     expect(filters[FILTERS.NET]).toBe(false);
     expect(filters[FILTERS.NETXHR]).toBe(false);
 
     const toolbar = wrapper.find(".webconsole-filterbar-filtered-messages");
     expect(toolbar.exists()).toBeFalsy();
   });
 
   it("displays filter bar when button is clicked", () => {
-    const store = setupStore([]);
+    const store = setupStore();
 
     expect(getAllUi(store.getState()).filterBarVisible).toBe(false);
     expect(ServicesMock.prefs.getBoolPref(PREFS.UI.FILTER_BAR), false);
 
     const wrapper = mount(Provider({store}, FilterBar({ serviceContainer })));
     wrapper.find(".devtools-filter-icon").simulate("click");
 
     expect(getAllUi(store.getState()).filterBarVisible).toBe(true);
@@ -231,37 +231,37 @@ describe("FilterBar component:", () => {
     ];
 
     secondaryBar.children().forEach((child, index) => {
       expect(child.html()).toEqual(shallow(buttons[index]).html());
     });
   });
 
   it("fires MESSAGES_CLEAR action when clear button is clicked", () => {
-    const store = setupStore([]);
+    const store = setupStore();
     store.dispatch = sinon.spy();
 
     const wrapper = mount(Provider({store}, FilterBar({ serviceContainer })));
     wrapper.find(".devtools-clear-icon").simulate("click");
     const call = store.dispatch.getCall(0);
     expect(call.args[0]).toEqual({
       type: MESSAGES_CLEAR
     });
   });
 
   it("sets filter text when text is typed", () => {
-    const store = setupStore([]);
+    const store = setupStore();
 
     const wrapper = mount(Provider({store}, FilterBar({ serviceContainer })));
     wrapper.find(".devtools-plaininput").simulate("input", { target: { value: "a" } });
     expect(store.getState().filters.text).toBe("a");
   });
 
   it("toggles persist logs when checkbox is clicked", () => {
-    const store = setupStore([]);
+    const store = setupStore();
 
     expect(getAllUi(store.getState()).persistLogs).toBe(false);
     expect(ServicesMock.prefs.getBoolPref(PREFS.UI.PERSIST), false);
 
     const wrapper = mount(Provider({store}, FilterBar({ serviceContainer })));
     wrapper.find(".filter-checkbox input").simulate("change");
 
     expect(getAllUi(store.getState()).persistLogs).toBe(true);
--- a/devtools/client/webconsole/new-console-output/test/components/page-error.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/page-error.test.js
@@ -65,17 +65,17 @@ describe("PageError component:", () => {
     const message = stubPreparedMessages.get("TypeError longString message");
     const wrapper = render(PageError({ message, serviceContainer }));
 
     const text = wrapper.find(".message-body").text();
     expect(text.startsWith("Error: Long error Long error")).toBe(true);
   });
 
   it("displays a [Learn more] link", () => {
-    const store = setupStore([]);
+    const store = setupStore();
 
     const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
 
     serviceContainer.openLink = sinon.spy();
     const wrapper = mount(Provider({store},
       PageError({message, serviceContainer})
     ));
 
@@ -99,17 +99,17 @@ describe("PageError component:", () => {
     expect(wrapper.find(".theme-twisty.open").length).toBe(1);
 
     // There should be five stacktrace items.
     const frameLinks = wrapper.find(`.stack-trace span.frame-link`);
     expect(frameLinks.length).toBe(5);
   });
 
   it("toggle the stacktrace when the collapse button is clicked", () => {
-    const store = setupStore([]);
+    const store = setupStore();
     store.dispatch = sinon.spy();
     const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
 
     let wrapper = mount(Provider({store},
       PageError({
         message,
         open: true,
         dispatch: store.dispatch,
--- a/devtools/client/webconsole/new-console-output/test/helpers.js
+++ b/devtools/client/webconsole/new-console-output/test/helpers.js
@@ -4,60 +4,64 @@
 "use strict";
 
 let ReactDOM = require("devtools/client/shared/vendor/react-dom");
 let React = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const { createElement } = React;
 const TestUtils = ReactDOM.TestUtils;
 
-const actions = require("devtools/client/webconsole/new-console-output/actions/index");
+const reduxActions = require("devtools/client/webconsole/new-console-output/actions/index");
 const { configureStore } = require("devtools/client/webconsole/new-console-output/store");
 const { IdGenerator } = require("devtools/client/webconsole/new-console-output/utils/id-generator");
 const { stubPackets } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
 const {
   getAllMessagesById,
 } = require("devtools/client/webconsole/new-console-output/selectors/messages");
 
 /**
  * Prepare actions for use in testing.
  */
 function setupActions() {
   // Some actions use dependency injection. This helps them avoid using state in
   // a hard-to-test way. We need to inject stubbed versions of these dependencies.
-  const wrappedActions = Object.assign({}, actions);
+  const wrappedActions = Object.assign({}, reduxActions);
 
   const idGenerator = new IdGenerator();
   wrappedActions.messagesAdd = (packets) => {
-    return actions.messagesAdd(packets, idGenerator);
+    return reduxActions.messagesAdd(packets, idGenerator);
   };
 
   return {
-    ...actions,
-    messagesAdd: packets => actions.messagesAdd(packets, idGenerator)
+    ...reduxActions,
+    messagesAdd: packets => reduxActions.messagesAdd(packets, idGenerator)
   };
 }
 
 /**
  * Prepare the store for use in testing.
  */
-function setupStore(input = [], hud, options, wrappedActions) {
+function setupStore(input = [], {
+  storeOptions,
+  actions,
+  hud,
+} = {}) {
   if (!hud) {
     hud = {
       proxy: {
         releaseActor: () => {}
       }
     };
   }
-  const store = configureStore(hud, options);
+  const store = configureStore(hud, storeOptions);
 
   // Add the messages from the input commands to the store.
-  const messagesAdd = wrappedActions
-    ? wrappedActions.messagesAdd
-    : actions.messagesAdd;
+  const messagesAdd = actions
+    ? actions.messagesAdd
+    : reduxActions.messagesAdd;
   store.dispatch(messagesAdd(input.map(cmd => stubPackets.get(cmd))));
 
   return store;
 }
 
 function renderComponent(component, props) {
   const el = createElement(component, props, {});
   // By default, renderIntoDocument() won't work for stateless components, but
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -20,35 +20,16 @@ support-files =
   test_hsts-invalid-headers.sjs
   test-autocomplete-in-stackframe.html
   test-batching.html
   test-bug_923281_console_log_filter.html
   test-bug_923281_test1.js
   test-bug_923281_test2.js
   test-bug_939783_console_trace_duplicates.html
   test-bug-585956-console-trace.html
-  test-bug-595934-canvas-css.html
-  test-bug-595934-canvas-css.js
-  test-bug-595934-css-loader.css
-  test-bug-595934-css-loader.css^headers^
-  test-bug-595934-css-loader.html
-  test-bug-595934-css-parser.css
-  test-bug-595934-css-parser.html
-  test-bug-595934-empty-getelementbyid.html
-  test-bug-595934-empty-getelementbyid.js
-  test-bug-595934-html.html
-  test-bug-595934-image.html
-  test-bug-595934-image.jpg
-  test-bug-595934-imagemap.html
-  test-bug-595934-malformedxml-external.html
-  test-bug-595934-malformedxml-external.xml
-  test-bug-595934-malformedxml.xhtml
-  test-bug-595934-svg.xhtml
-  test-bug-595934-workers.html
-  test-bug-595934-workers.js
   test-bug-599725-response-headers.sjs
   test-bug-601177-log-levels.html
   test-bug-601177-log-levels.js
   test-bug-630733-response-redirect-headers.sjs
   test-bug-632275-getters.html
   test-bug-644419-log-limits.html
   test-bug-646025-console-file-location.html
   test-bug-658368-time-methods.html
@@ -132,16 +113,35 @@ support-files =
   test-inspect-cross-domain-objects-top.html
   test-jsterm-dollar.html
   test-location-debugger-link-console-log.js
   test-location-debugger-link-errors.js
   test-location-debugger-link.html
   test-location-styleeditor-link-1.css
   test-location-styleeditor-link-2.css
   test-location-styleeditor-link.html
+  test-message-categories-canvas-css.html
+  test-message-categories-canvas-css.js
+  test-message-categories-css-loader.css
+  test-message-categories-css-loader.css^headers^
+  test-message-categories-css-loader.html
+  test-message-categories-css-parser.css
+  test-message-categories-css-parser.html
+  test-message-categories-empty-getelementbyid.html
+  test-message-categories-empty-getelementbyid.js
+  test-message-categories-html.html
+  test-message-categories-image.html
+  test-message-categories-image.jpg
+  test-message-categories-imagemap.html
+  test-message-categories-malformedxml-external.html
+  test-message-categories-malformedxml-external.xml
+  test-message-categories-malformedxml.xhtml
+  test-message-categories-svg.xhtml
+  test-message-categories-workers.html
+  test-message-categories-workers.js
   test-mixedcontent-securityerrors.html
   test-mutation.html
   test-network-exceptions.html
   test-network-request.html
   test-network.html
   test-observe-http-ajax.html
   test-own-console.html
   test-property-provider.html
@@ -309,18 +309,16 @@ skip-if = true #	Bug 1404831
 [browser_webconsole_location_scratchpad_link.js]
 [browser_webconsole_location_styleeditor_link.js]
 [browser_webconsole_logErrorInPage.js]
 [browser_webconsole_longstring_expand.js]
 skip-if = true #	Bug 1403448
 [browser_webconsole_longstring_hang.js]
 skip-if = true #	Bug 1403448
 [browser_webconsole_message_categories.js]
-skip-if = true #	Bug 1404384
-# old console skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
 [browser_webconsole_mixedcontent.js]
 tags = mcb
 skip-if = true #	Bug 1404886
 [browser_webconsole_multiple_windows_and_tabs.js]
 [browser_webconsole_network_attach.js]
 [browser_webconsole_network_exceptions.js]
 [browser_webconsole_network_messages_expand.js]
 [browser_webconsole_network_messages_openinnet.js]
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_message_categories.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_message_categories.js
@@ -1,212 +1,143 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// See Bug 595934.
+// Check that messages are logged and observed with the correct category. See Bug 595934.
 
 const TEST_URI = "data:text/html;charset=utf-8,Web Console test for " +
                  "bug 595934 - message categories coverage.";
 const TESTS_PATH = "http://example.com/browser/devtools/client/webconsole/" +
-                   "test/";
+                   "new-console-output/test/mochitest/";
 const TESTS = [
   {
     // #0
-    file: "test-bug-595934-css-loader.html",
+    file: "test-message-categories-css-loader.html",
     category: "CSS Loader",
     matchString: "text/css",
   },
   {
     // #1
-    file: "test-bug-595934-imagemap.html",
+    file: "test-message-categories-imagemap.html",
     category: "Layout: ImageMap",
     matchString: "shape=\"rect\"",
   },
   {
     // #2
-    file: "test-bug-595934-html.html",
+    file: "test-message-categories-html.html",
     category: "HTML",
     matchString: "multipart/form-data",
     onload: function () {
-      let form = content.document.querySelector("form");
-      form.submit();
+      ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+        let form = content.document.querySelector("form");
+        form.submit();
+      });
     },
   },
   {
     // #3
-    file: "test-bug-595934-workers.html",
+    file: "test-message-categories-workers.html",
     category: "Web Worker",
     matchString: "fooBarWorker",
   },
   {
     // #4
-    file: "test-bug-595934-malformedxml.xhtml",
+    file: "test-message-categories-malformedxml.xhtml",
     category: "malformed-xml",
     matchString: "no root element found",
   },
   {
     // #5
-    file: "test-bug-595934-svg.xhtml",
+    file: "test-message-categories-svg.xhtml",
     category: "SVG",
     matchString: "fooBarSVG",
   },
   {
     // #6
-    file: "test-bug-595934-css-parser.html",
+    file: "test-message-categories-css-parser.html",
     category: "CSS Parser",
     matchString: "foobarCssParser",
   },
   {
     // #7
-    file: "test-bug-595934-malformedxml-external.html",
+    file: "test-message-categories-malformedxml-external.html",
     category: "malformed-xml",
     matchString: "</html>",
   },
   {
     // #8
-    file: "test-bug-595934-empty-getelementbyid.html",
+    file: "test-message-categories-empty-getelementbyid.html",
     category: "DOM",
     matchString: "getElementById",
   },
   {
     // #9
-    file: "test-bug-595934-canvas-css.html",
+    file: "test-message-categories-canvas-css.html",
     category: "CSS Parser",
     matchString: "foobarCanvasCssParser",
   },
   {
     // #10
-    file: "test-bug-595934-image.html",
+    file: "test-message-categories-image.html",
     category: "Image",
     matchString: "corrupt",
+    // This message is not displayed in the main console in e10s. Bug 1431731
+    skipInE10s: true,
   },
 ];
 
-var pos = -1;
-
-var foundCategory = false;
-var foundText = false;
-var pageLoaded = false;
-var pageError = false;
-var output = null;
-var jsterm = null;
-var hud = null;
-var testEnded = false;
-
-var TestObserver = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+add_task(async function () {
+  requestLongerTimeout(2);
 
-  observe: function testObserve(subject) {
-    if (testEnded || !(subject instanceof Ci.nsIScriptError)) {
-      return;
-    }
-
-    let expectedCategory = TESTS[pos].category;
-
-    info("test #" + pos + " console observer got " + subject.category +
-         ", is expecting " + expectedCategory);
-
-    if (subject.category == expectedCategory) {
-      foundCategory = true;
-      startNextTest();
-    } else {
-      info("unexpected message was: " + subject.sourceName + ":" +
-           subject.lineNumber + "; " + subject.errorMessage);
-    }
-  }
-};
+  await pushPref("devtools.webconsole.filter.css", true);
+  await pushPref("devtools.webconsole.filter.net", true);
 
-function consoleOpened(hudConsole) {
-  hud = hudConsole;
-  output = hud.outputNode;
-  jsterm = hud.jsterm;
-
-  Services.console.registerListener(TestObserver);
-
-  registerCleanupFunction(testEnd);
-
-  testNext();
-}
-
-function testNext() {
-  jsterm.clearOutput();
-  foundCategory = false;
-  foundText = false;
-  pageLoaded = false;
-  pageError = false;
+  let hud = await openNewTabAndConsole(TEST_URI);
+  for (let i = 0; i < TESTS.length; i++) {
+    let test = TESTS[i];
+    info("Running test #" + i);
+    await runTest(test, hud);
+  }
+});
 
-  pos++;
-  info("testNext: #" + pos);
-  if (pos < TESTS.length) {
-    test = TESTS[pos];
+async function runTest(test, hud) {
+  let {file, category, matchString, onload, skipInE10s} = test;
 
-    waitForMessages({
-      webconsole: hud,
-      messages: [{
-        name: "message for test #" + pos + ": '" + test.matchString + "'",
-        text: test.matchString,
-      }],
-    }).then(() => {
-      foundText = true;
-      startNextTest();
-    });
+  if (skipInE10s && Services.appinfo.browserTabsRemoteAutostart) {
+    return;
+  }
 
-    let testLocation = TESTS_PATH + test.file;
-    gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
-      if (content.location.href != testLocation) {
+  let onMessageLogged = waitForMessage(hud, matchString);
+
+  let onMessageObserved = new Promise(resolve => {
+    Services.console.registerListener(function listener(subject) {
+      if (!(subject instanceof Ci.nsIScriptError)) {
         return;
       }
-      gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
 
-      pageLoaded = true;
-      test.onload && test.onload(evt);
-
-      if (test.expectError) {
-        content.addEventListener("error", function () {
-          pageError = true;
-          startNextTest();
-        }, {once: true});
-        // On e10s, the exception is triggered in child process
-        // and is ignored by test harness
-        if (!Services.appinfo.browserTabsRemoteAutostart) {
-          expectUncaughtException();
-        }
-      } else {
-        pageError = true;
+      if (subject.category != category) {
+        return;
       }
 
-      startNextTest();
-    }, true);
+      ok(true, "Expected category [" + category + "] received in observer");
+      Services.console.unregisterListener(listener);
+      resolve();
+    });
+  });
 
-    BrowserTestUtils.loadURI(gBrowser.selectedBrowser, testLocation);
-  } else {
-    testEnded = true;
-    finishTest();
-  }
-}
+  info("Load test file " + file);
+  await loadDocument(TESTS_PATH + file);
 
-function testEnd() {
-  if (!testEnded) {
-    info("foundCategory " + foundCategory + " foundText " + foundText +
-         " pageLoaded " + pageLoaded + " pageError " + pageError);
+  // Call test specific callback if defined
+  if (onload) {
+    onload();
   }
 
-  Services.console.unregisterListener(TestObserver);
-  hud = TestObserver = output = jsterm = null;
-}
+  info("Wait for log message to be observed with the correct category");
+  await onMessageObserved;
 
-function startNextTest() {
-  if (!testEnded && foundCategory && foundText && pageLoaded && pageError) {
-    testNext();
-  }
+  info("Wait for log message to be displayed in the hud");
+  await onMessageLogged;
 }
-
-function test() {
-  requestLongerTimeout(2);
-
-  loadTab(TEST_URI).then(() => {
-    openConsole().then(consoleOpened);
-  });
-}
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-canvas-css.html
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-canvas-css.html
--- a/devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-canvas-css.html
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-canvas-css.html
@@ -2,16 +2,16 @@
 <html lang="en">
   <head>
     <meta charset="utf-8">
     <title>Web Console test for bug 595934 - category: CSS Parser (with
       Canvas)</title>
     <!-- Any copyright is dedicated to the Public Domain.
          http://creativecommons.org/publicdomain/zero/1.0/ -->
     <script type="text/javascript"
-            src="test-bug-595934-canvas-css.js"></script>
+            src="test-message-categories-canvas-css.js"></script>
   </head>
   <body>
     <p>Web Console test for bug 595934 - category "CSS Parser" (with
     Canvas).</p>
     <p><canvas width="200" height="200">Canvas support is required!</canvas></p>
   </body>
 </html>
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-canvas-css.js
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-canvas-css.js
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-css-loader.css
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-css-loader.css
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-css-loader.css^headers^
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-css-loader.css^headers^
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-css-loader.html
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-css-loader.html
--- a/devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-css-loader.html
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-css-loader.html
@@ -1,13 +1,13 @@
 <!DOCTYPE html>
 <html lang="en">
   <head>
     <meta charset="utf-8">
     <title>Web Console test for bug 595934 - category: CSS Loader</title>
 <!-- Any copyright is dedicated to the Public Domain.
      http://creativecommons.org/publicdomain/zero/1.0/ -->
-    <link rel="stylesheet" href="test-bug-595934-css-loader.css">
+    <link rel="stylesheet" href="test-message-categories-css-loader.css">
   </head>
   <body>
     <p>Web Console test for bug 595934 - category "CSS Loader".</p>
   </body>
 </html>
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-css-parser.css
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-css-parser.css
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-css-parser.html
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-css-parser.html
--- a/devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-css-parser.html
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-css-parser.html
@@ -1,14 +1,14 @@
 <!DOCTYPE html>
 <html lang="en">
   <head>
     <meta charset="utf-8">
     <title>Web Console test for bug 595934 - category: CSS Parser</title>
 <!-- Any copyright is dedicated to the Public Domain.
      http://creativecommons.org/publicdomain/zero/1.0/ -->
      <link rel="stylesheet" type="text/css"
-     href="test-bug-595934-css-parser.css">
+     href="test-message-categories-css-parser.css">
   </head>
   <body>
     <p>Web Console test for bug 595934 - category "CSS Parser".</p>
   </body>
 </html>
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-empty-getelementbyid.html
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-empty-getelementbyid.html
--- a/devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-empty-getelementbyid.html
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-empty-getelementbyid.html
@@ -2,15 +2,15 @@
 <html lang="en">
   <head>
     <meta charset="utf-8">
     <title>Web Console test for bug 595934 - category: DOM.
     (empty getElementById())</title>
 <!-- Any copyright is dedicated to the Public Domain.
      http://creativecommons.org/publicdomain/zero/1.0/ -->
     <script type="text/javascript"
-      src="test-bug-595934-empty-getelementbyid.js"></script>
+      src="test-message-categories-empty-getelementbyid.js"></script>
   </head>
   <body>
     <p>Web Console test for bug 595934 - category "DOM"
     (empty getElementById()).</p>
   </body>
 </html>
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-empty-getelementbyid.js
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-empty-getelementbyid.js
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-html.html
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-html.html
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-image.html
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-image.html
--- a/devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-image.html
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-image.html
@@ -3,13 +3,13 @@
   <head>
     <meta charset="utf-8">
     <title>Web Console test for bug 595934 - category: Image</title>
 <!-- Any copyright is dedicated to the Public Domain.
      http://creativecommons.org/publicdomain/zero/1.0/ -->
   </head>
   <body>
     <p>Web Console test for bug 595934 - category Image.</p>
-    <p><img src="test-bug-595934-image.jpg" alt="corrupted image"></p>
+    <p><img src="test-message-categories-image.jpg" alt="corrupted image"></p>
   </body>
 </html>
 
 
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-image.jpg
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-image.jpg
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-imagemap.html
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-imagemap.html
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-malformedxml-external.html
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-malformedxml-external.html
--- a/devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-malformedxml-external.html
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-malformedxml-external.html
@@ -3,17 +3,17 @@
   <head>
     <meta charset="utf-8">
     <title>Web Console test for bug 595934 - category: malformed-xml.
       (external file)</title>
 <!-- Any copyright is dedicated to the Public Domain.
      http://creativecommons.org/publicdomain/zero/1.0/ -->
      <script type="text/javascript"><!--
        var req = new XMLHttpRequest();
-       req.open("GET", "test-bug-595934-malformedxml-external.xml", true);
+       req.open("GET", "test-message-categories-malformedxml-external.xml", true);
        req.send(null);
      // --></script>
   </head>
   <body>
     <p>Web Console test for bug 595934 - category "malformed-xml"
     (external file).</p>
   </body>
 </html>
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-malformedxml-external.xml
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-malformedxml-external.xml
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-malformedxml.xhtml
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-malformedxml.xhtml
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-svg.xhtml
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-svg.xhtml
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-workers.html
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-workers.html
--- a/devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-workers.html
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-workers.html
@@ -5,14 +5,14 @@
       javascript</title>
     <!-- Any copyright is dedicated to the Public Domain.
          http://creativecommons.org/publicdomain/zero/1.0/ -->
   </head>
   <body>
     <p id="foobar">Web Console test for bug 595934 - category "DOM Worker
     javascript".</p>
     <script type="text/javascript">
-      var myWorker = new Worker("test-bug-595934-workers.js");
+      var myWorker = new Worker("test-message-categories-workers.js");
       myWorker.postMessage("hello world");
     </script>
   </body>
 </html>
 
rename from devtools/client/webconsole/new-console-output/test/mochitest/test-bug-595934-workers.js
rename to devtools/client/webconsole/new-console-output/test/mochitest/test-message-categories-workers.js
--- a/devtools/client/webconsole/new-console-output/test/store/filters.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/filters.test.js
@@ -199,17 +199,17 @@ describe("Filtering", () => {
   describe("Combined filters", () => {
     // @TODO add test
     it("filters");
   });
 });
 
 describe("Clear filters", () => {
   it("clears all filters", () => {
-    const store = setupStore([]);
+    const store = setupStore();
 
     // Setup test case
     store.dispatch(actions.filterToggle(FILTERS.ERROR));
     store.dispatch(actions.filterToggle(FILTERS.CSS));
     store.dispatch(actions.filterToggle(FILTERS.NET));
     store.dispatch(actions.filterToggle(FILTERS.NETXHR));
     store.dispatch(actions.filterTextSet("foobar"));
 
@@ -261,17 +261,17 @@ describe("Clear filters", () => {
       [PREFS.FILTER.NETXHR]: false,
       [PREFS.FILTER.WARN]: true,
     });
   });
 });
 
 describe("Resets filters", () => {
   it("resets default filters value to their original one.", () => {
-    const store = setupStore([]);
+    const store = setupStore();
 
     // Setup test case
     store.dispatch(actions.filterToggle(FILTERS.ERROR));
     store.dispatch(actions.filterToggle(FILTERS.LOG));
     store.dispatch(actions.filterToggle(FILTERS.CSS));
     store.dispatch(actions.filterToggle(FILTERS.NET));
     store.dispatch(actions.filterToggle(FILTERS.NETXHR));
     store.dispatch(actions.filterTextSet("foobar"));
--- a/devtools/client/webconsole/new-console-output/test/store/messages.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/messages.test.js
@@ -31,36 +31,36 @@ describe("Message reducer:", () => {
   let actions;
 
   before(() => {
     actions = setupActions();
   });
 
   describe("messagesById", () => {
     it("adds a message to an empty store", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       const packet = stubPackets.get("console.log('foobar', 'test')");
       const message = stubPreparedMessages.get("console.log('foobar', 'test')");
       dispatch(actions.messagesAdd([packet]));
 
       expect(getFirstMessage(getState())).toEqual(message);
     });
 
     it("increments repeat on a repeating log message", () => {
       const key1 = "console.log('foobar', 'test')";
-      const { dispatch, getState } = setupStore([key1, key1]);
+      const { dispatch, getState } = setupStore([key1, key1], {actions});
 
       const packet = clonePacket(stubPackets.get(key1));
+      const packet2 = clonePacket(packet);
 
       // Repeat ID must be the same even if the timestamp is different.
       packet.message.timeStamp = 1;
-      dispatch(actions.messagesAdd([packet]));
-      packet.message.timeStamp = 2;
-      dispatch(actions.messagesAdd([packet]));
+      packet2.message.timeStamp = 2;
+      dispatch(actions.messagesAdd([packet, packet2]));
 
       const messages = getAllMessagesById(getState());
 
       expect(messages.size).toBe(1);
       const repeat = getAllRepeatById(getState());
       expect(repeat[getFirstMessage(getState()).id]).toBe(4);
     });
 
@@ -179,21 +179,22 @@ describe("Message reducer:", () => {
     it("cleans the repeatsById object when messages are pruned", () => {
       const { dispatch, getState } = setupStore(
         [
           "console.log('foobar', 'test')",
           "console.log('foobar', 'test')",
           "console.log(undefined)",
           "console.log(undefined)",
         ],
-        null, {
-          logLimit: 2
-        },
-        actions
-      );
+        {
+          actions,
+          storeOptions: {
+            logLimit: 2
+          }
+        });
 
       // Check that we have the expected data.
       let repeats = getAllRepeatById(getState());
       expect(Object.keys(repeats).length).toBe(2);
       const lastMessageId = getLastMessage(getState()).id;
 
       // This addition will prune the first message out of the store.
       let packet = stubPackets.get("console.log('foobar', 'test')");
@@ -210,17 +211,17 @@ describe("Message reducer:", () => {
       packet = stubPackets.get("console.log(undefined)");
       dispatch(actions.messagesAdd([packet]));
 
       // repeatById should now be empty.
       expect(getAllRepeatById(getState())).toEqual({});
     });
 
     it("properly limits number of messages", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       const logLimit = 1000;
       const packet = clonePacket(stubPackets.get("console.log(undefined)"));
 
       for (let i = 1; i <= logLimit + 2; i++) {
         packet.message.arguments = [`message num ${i}`];
         dispatch(actions.messagesAdd([packet]));
       }
@@ -228,17 +229,17 @@ describe("Message reducer:", () => {
       const messages = getAllMessagesById(getState());
       expect(messages.size).toBe(logLimit);
       expect(getFirstMessage(getState()).parameters[0]).toBe(`message num 3`);
       expect(getLastMessage(getState()).parameters[0])
         .toBe(`message num ${logLimit + 2}`);
     });
 
     it("properly limits number of messages when there are nested groups", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       const logLimit = 1000;
 
       const packet = clonePacket(stubPackets.get("console.log(undefined)"));
       const packetGroup = clonePacket(stubPackets.get("console.group('bar')"));
       const packetGroupEnd = clonePacket(stubPackets.get("console.groupEnd()"));
 
       packetGroup.message.arguments = [`group-1`];
@@ -274,17 +275,17 @@ describe("Message reducer:", () => {
 
       // The groups were cleaned up.
       const groups = getGroupsById(getState());
       expect(groups.size).toBe(0);
     });
 
     it("properly limits number of groups", () => {
       const logLimit = 100;
-      const { dispatch, getState } = setupStore([], null, {logLimit});
+      const { dispatch, getState } = setupStore([], {storeOptions: {logLimit}});
 
       const packet = clonePacket(stubPackets.get("console.log(undefined)"));
       const packetGroup = clonePacket(stubPackets.get("console.group('bar')"));
       const packetGroupEnd = clonePacket(stubPackets.get("console.groupEnd()"));
 
       for (let i = 0; i < logLimit + 2; i++) {
         dispatch(actions.messagesAdd([packetGroup]));
         packet.message.arguments = [`message-${i}-a`];
@@ -304,17 +305,17 @@ describe("Message reducer:", () => {
       expect(groups.size).toBe(logLimit);
 
       expect(messages.get(visibleMessages[1]).parameters[0]).toBe(`message-2-a`);
       expect(getLastMessage(getState()).parameters[0]).toBe(`message-${logLimit + 1}-b`);
     });
 
     it("properly limits number of collapsed groups", () => {
       const logLimit = 100;
-      const { dispatch, getState } = setupStore([], null, {logLimit});
+      const { dispatch, getState } = setupStore([], {storeOptions: {logLimit}});
 
       const packet = clonePacket(stubPackets.get("console.log(undefined)"));
       const packetGroupCollapsed = clonePacket(
         stubPackets.get("console.groupCollapsed('foo')"));
       const packetGroupEnd = clonePacket(stubPackets.get("console.groupEnd()"));
 
       for (let i = 0; i < logLimit + 2; i++) {
         packetGroupCollapsed.message.arguments = [`group-${i}`];
@@ -340,47 +341,47 @@ describe("Message reducer:", () => {
       const visibleMessages = getVisibleMessages(getState());
       expect(visibleMessages.length).toBe(logLimit);
       const lastVisibleMessageId = visibleMessages[visibleMessages.length - 1];
       expect(messages.get(lastVisibleMessageId).parameters[0])
         .toBe(`group-${logLimit + 1}`);
     });
 
     it("does not add null messages to the store", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       const message = stubPackets.get("console.time('bar')");
       dispatch(actions.messagesAdd([message]));
 
       const messages = getAllMessagesById(getState());
       expect(messages.size).toBe(0);
     });
 
     it("adds console.table call with unsupported type as console.log", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       const packet = stubPackets.get("console.table('bar')");
       dispatch(actions.messagesAdd([packet]));
 
       const tableMessage = getLastMessage(getState());
       expect(tableMessage.level).toEqual(MESSAGE_TYPE.LOG);
     });
 
     it("adds console.group messages to the store", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       const message = stubPackets.get("console.group('bar')");
       dispatch(actions.messagesAdd([message]));
 
       const messages = getAllMessagesById(getState());
       expect(messages.size).toBe(1);
     });
 
     it("adds messages in console.group to the store", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       const groupPacket = stubPackets.get("console.group('bar')");
       const groupEndPacket = stubPackets.get("console.groupEnd('bar')");
       const logPacket = stubPackets.get("console.log('foobar', 'test')");
 
       const packets = [
         groupPacket,
         logPacket,
@@ -411,64 +412,64 @@ describe("Message reducer:", () => {
 
       const messages = getAllMessagesById(getState());
       const visibleMessages = getVisibleMessages(getState());
       expect(messages.size).toBe(messageCount);
       expect(visibleMessages.length).toBe(messageCount);
     });
 
     it("sets groupId property as expected", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       dispatch(actions.messagesAdd([
         stubPackets.get("console.group('bar')"),
         stubPackets.get("console.log('foobar', 'test')")
       ]));
 
       const messages = getAllMessagesById(getState());
       expect(messages.size).toBe(2);
       expect(getLastMessage(getState()).groupId).toBe(getFirstMessage(getState()).id);
     });
 
     it("does not display console.groupEnd messages to the store", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       const message = stubPackets.get("console.groupEnd('bar')");
       dispatch(actions.messagesAdd([message]));
 
       const messages = getAllMessagesById(getState());
       expect(messages.size).toBe(0);
     });
 
     it("filters out message added after a console.groupCollapsed message", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       dispatch(actions.messagesAdd([
         stubPackets.get("console.groupCollapsed('foo')"),
         stubPackets.get("console.log('foobar', 'test')"),
       ]));
 
       const messages = getVisibleMessages(getState());
       expect(messages.length).toBe(1);
     });
 
     it("adds console.dirxml call as console.log", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       const packet = stubPackets.get("console.dirxml(window)");
       dispatch(actions.messagesAdd([packet]));
 
       const dirxmlMessage = getLastMessage(getState());
       expect(dirxmlMessage.level).toEqual(MESSAGE_TYPE.LOG);
     });
   });
 
   describe("expandedMessageIds", () => {
     it("opens console.trace messages when they are added", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       const message = stubPackets.get("console.trace()");
       dispatch(actions.messagesAdd([message]));
 
       const expanded = getAllMessagesUiById(getState());
       expect(expanded.length).toBe(1);
       expect(expanded[0]).toBe(getFirstMessage(getState()).id);
     });
@@ -486,18 +487,20 @@ describe("Message reducer:", () => {
 
       const expanded = getAllMessagesUiById(getState());
       expect(expanded.length).toBe(0);
     });
 
     it("cleans the messages UI list when messages are pruned", () => {
       const { dispatch, getState } = setupStore(
         ["console.trace()", "console.log(undefined)", "console.trace()"],
-        null, {
-          logLimit: 3
+        {
+          storeOptions: {
+            logLimit: 3
+          }
         }
       );
 
       // Check that we have the expected data.
       let expanded = getAllMessagesUiById(getState());
       expect(expanded.length).toBe(2);
       expect(expanded[0]).toBe(getFirstMessage(getState()).id);
       const lastMessageId = getLastMessage(getState()).id;
@@ -519,28 +522,28 @@ describe("Message reducer:", () => {
       packet = stubPackets.get("console.log(undefined)");
       dispatch(actions.messagesAdd([packet]));
 
       // expandedMessageIds should now be empty.
       expect(getAllMessagesUiById(getState()).length).toBe(0);
     });
 
     it("opens console.group messages when they are added", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       const message = stubPackets.get("console.group('bar')");
       dispatch(actions.messagesAdd([message]));
 
       const expanded = getAllMessagesUiById(getState());
       expect(expanded.length).toBe(1);
       expect(expanded[0]).toBe(getFirstMessage(getState()).id);
     });
 
     it("does not open console.groupCollapsed messages when they are added", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       const message = stubPackets.get("console.groupCollapsed('foo')");
       dispatch(actions.messagesAdd([message]));
 
       const expanded = getAllMessagesUiById(getState());
       expect(expanded.length).toBe(0);
     });
 
@@ -581,33 +584,35 @@ describe("Message reducer:", () => {
 
       expanded = getAllMessagesUiById(getState());
       expect(expanded.length).toBe(0);
     });
   });
 
   describe("currentGroup", () => {
     it("sets the currentGroup when console.group message is added", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       const packet = stubPackets.get("console.group('bar')");
       dispatch(actions.messagesAdd([packet]));
 
       const currentGroup = getCurrentGroup(getState());
       expect(currentGroup).toBe(getFirstMessage(getState()).id);
     });
 
     it("sets currentGroup to expected value when console.groupEnd is added", () => {
       const { dispatch, getState } = setupStore([
         "console.group('bar')",
-        "console.groupCollapsed('foo')"
+        "console.groupCollapsed('foo')",
+        "console.group('bar')",
+        "console.groupEnd('bar')",
       ]);
 
       let currentGroup = getCurrentGroup(getState());
-      expect(currentGroup).toBe(getLastMessage(getState()).id);
+      expect(currentGroup).toBe(getMessageAt(getState(), 1).id);
 
       const endFooPacket = stubPackets.get("console.groupEnd('foo')");
       dispatch(actions.messagesAdd([endFooPacket]));
       currentGroup = getCurrentGroup(getState());
       expect(currentGroup).toBe(getFirstMessage(getState()).id);
 
       const endBarPacket = stubPackets.get("console.groupEnd('bar')");
       dispatch(actions.messagesAdd([endBarPacket]));
@@ -624,17 +629,17 @@ describe("Message reducer:", () => {
 
       const currentGroup = getCurrentGroup(getState());
       expect(currentGroup).toBe(null);
     });
   });
 
   describe("groupsById", () => {
     it("adds the group with expected array when console.group message is added", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       const barPacket = stubPackets.get("console.group('bar')");
       dispatch(actions.messagesAdd([barPacket]));
 
       let groupsById = getGroupsById(getState());
       expect(groupsById.size).toBe(1);
       expect(groupsById.has(getFirstMessage(getState()).id)).toBe(true);
       expect(groupsById.get(getFirstMessage(getState()).id)).toEqual([]);
@@ -670,46 +675,67 @@ describe("Message reducer:", () => {
           "console.group('bar')",
           "console.group()",
           "console.groupEnd()",
           "console.groupEnd('bar')",
           "console.group('bar')",
           "console.groupEnd('bar')",
           "console.log('foobar', 'test')",
         ],
-        null, {
-          logLimit: 3
+        {
+          actions,
+          storeOptions: {
+            logLimit: 3
+          }
         }
       );
 
+      /*
+       * Here is the initial state of the console:
+       * ▶︎ bar
+       *   ▶︎ noLabel
+       * ▶︎ bar
+       * foobar test
+      */
+
       // Check that we have the expected data.
       let groupsById = getGroupsById(getState());
       expect(groupsById.size).toBe(3);
 
       // This addition will prune the first group (and its child group) out of the store.
+      /*
+       * ▶︎ bar
+       * foobar test
+       * undefined
+      */
       let packet = stubPackets.get("console.log(undefined)");
       dispatch(actions.messagesAdd([packet]));
 
       groupsById = getGroupsById(getState());
 
-      // There should be only the id of the last console.trace message.
+      // There should be only the id of the last console.group message.
       expect(groupsById.size).toBe(1);
 
       // This additions will prune the last group message out of the store.
+      /*
+       * foobar test
+       * undefined
+       * foobar test
+      */
       packet = stubPackets.get("console.log('foobar', 'test')");
       dispatch(actions.messagesAdd([packet]));
 
       // groupsById should now be empty.
       expect(getGroupsById(getState()).size).toBe(0);
     });
   });
 
   describe("networkMessagesUpdateById", () => {
     it("adds the network update message when network update action is called", () => {
-      const { dispatch, getState } = setupStore([]);
+      const { dispatch, getState } = setupStore();
 
       let packet = clonePacket(stubPackets.get("GET request"));
       let updatePacket = clonePacket(stubPackets.get("GET request update"));
 
       packet.actor = "message1";
       updatePacket.networkInfo.actor = "message1";
       dispatch(actions.messagesAdd([packet]));
       dispatch(
@@ -744,18 +770,20 @@ describe("Message reducer:", () => {
 
       dispatch(actions.messagesClear());
 
       networkUpdates = getAllNetworkMessagesUpdateById(getState());
       expect(Object.keys(networkUpdates).length).toBe(0);
     });
 
     it("cleans the networkMessagesUpdateById property when messages are pruned", () => {
-      const { dispatch, getState } = setupStore([], null, {
-        logLimit: 3
+      const { dispatch, getState } = setupStore([], {
+        storeOptions: {
+          logLimit: 3
+        }
       });
 
       // Add 3 network messages and their updates
       let packet = clonePacket(stubPackets.get("XHR GET request"));
       let updatePacket = clonePacket(stubPackets.get("XHR GET request update"));
       packet.actor = "message1";
       updatePacket.networkInfo.actor = "message1";
       dispatch(actions.messagesAdd([packet]));
@@ -831,18 +859,20 @@ describe("Message reducer:", () => {
       expect(table.get(getFirstMessage(getState()).id)).toBe(data);
 
       dispatch(actions.messagesClear());
 
       expect(getAllMessagesTableDataById(getState()).size).toBe(0);
     });
 
     it("cleans the messagesTableDataById property when messages are pruned", () => {
-      const { dispatch, getState } = setupStore([], null, {
-        logLimit: 2
+      const { dispatch, getState } = setupStore([], {
+        storeOptions: {
+          logLimit: 2
+        }
       });
 
       // Add 2 table message and their data.
       dispatch(actions.messagesAdd([stubPackets.get("console.table(['a', 'b', 'c'])")]));
       dispatch(actions.messagesAdd(
         [stubPackets.get("console.table(['red', 'green', 'blue']);")]));
 
       let messages = getAllMessagesById(getState());
@@ -870,18 +900,20 @@ describe("Message reducer:", () => {
     });
   });
 
   describe("messagesAdd", () => {
     it("still log repeated message over logLimit, but only repeated ones", () => {
       // Log two distinct messages
       const key1 = "console.log('foobar', 'test')";
       const key2 = "console.log(undefined)";
-      const { dispatch, getState } = setupStore([key1, key2], null, {
-        logLimit: 2
+      const { dispatch, getState } = setupStore([key1, key2], {
+        storeOptions: {
+          logLimit: 2
+        }
       });
 
       // Then repeat the last one two times and log the first one again
       const packet1 = clonePacket(stubPackets.get(key2));
       const packet2 = clonePacket(stubPackets.get(key2));
       const packet3 = clonePacket(stubPackets.get(key1));
 
       // Repeat ID must be the same even if the timestamp is different.
--- a/devtools/client/webconsole/new-console-output/test/store/network-messages.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/network-messages.test.js
@@ -19,17 +19,17 @@ describe("Network message reducer:", () 
   let getState;
   let dispatch;
 
   before(() => {
     actions = setupActions();
   });
 
   beforeEach(() => {
-    const store = setupStore([]);
+    const store = setupStore();
 
     getState = store.getState;
     dispatch = store.dispatch;
 
     let packet = clonePacket(stubPackets.get("GET request"));
     let updatePacket = clonePacket(stubPackets.get("GET request update"));
 
     packet.actor = "message1";
--- a/devtools/client/webconsole/new-console-output/test/store/release-actors.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/release-actors.test.js
@@ -19,22 +19,25 @@ describe("Release actor enhancer:", () =
     actions = setupActions();
   });
 
   describe("Client proxy", () => {
     it("releases backend actors when limit reached adding a single message", () => {
       const logLimit = 100;
       let releasedActors = [];
       const { dispatch, getState } = setupStore([], {
-        proxy: {
-          releaseActor: (actor) => {
-            releasedActors.push(actor);
+        storeOptions: {logLimit},
+        hud: {
+          proxy: {
+            releaseActor: (actor) => {
+              releasedActors.push(actor);
+            }
           }
         }
-      }, { logLimit });
+      });
 
       // Add a log message.
       dispatch(actions.messagesAdd([
         stubPackets.get("console.log('myarray', ['red', 'green', 'blue'])")]));
 
       const firstMessage = getFirstMessage(getState());
       const firstMessageActor = firstMessage.parameters[1].actor;
 
@@ -58,22 +61,25 @@ describe("Release actor enhancer:", () =
       expect(releasedActors).toInclude(secondMessageActor);
       expect(releasedActors).toInclude(thirdMessageActor);
     });
 
     it("releases backend actors when limit reached adding multiple messages", () => {
       const logLimit = 100;
       let releasedActors = [];
       const { dispatch, getState } = setupStore([], {
-        proxy: {
-          releaseActor: (actor) => {
-            releasedActors.push(actor);
+        storeOptions: {logLimit},
+        hud: {
+          proxy: {
+            releaseActor: (actor) => {
+              releasedActors.push(actor);
+            }
           }
         }
-      }, { logLimit });
+      });
 
       // Add a log message.
       dispatch(actions.messagesAdd([
         stubPackets.get("console.log('myarray', ['red', 'green', 'blue'])")]));
 
       const firstMessage = getFirstMessage(getState());
       const firstMessageActor = firstMessage.parameters[1].actor;
 
@@ -104,19 +110,21 @@ describe("Release actor enhancer:", () =
       expect(releasedActors).toInclude(firstMessageActor);
       expect(releasedActors).toInclude(secondMessageActor);
       expect(releasedActors).toInclude(thirdMessageActor);
     });
 
     it("properly releases backend actors after clear", () => {
       let releasedActors = [];
       const { dispatch, getState } = setupStore([], {
-        proxy: {
-          releaseActor: (actor) => {
-            releasedActors.push(actor);
+        hud: {
+          proxy: {
+            releaseActor: (actor) => {
+              releasedActors.push(actor);
+            }
           }
         }
       });
 
       // Add a log message.
       dispatch(actions.messagesAdd([
         stubPackets.get("console.log('myarray', ['red', 'green', 'blue'])")]));
 
--- a/devtools/client/webconsole/test/browser_console_keyboard_accessibility.js
+++ b/devtools/client/webconsole/test/browser_console_keyboard_accessibility.js
@@ -2,31 +2,31 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Check that basic keyboard shortcuts work in the web console.
 
 "use strict";
 
-add_task(function* () {
+add_task(async function () {
   const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
                    "test/test-console.html";
 
-  yield loadTab(TEST_URI);
+  await loadTab(TEST_URI);
 
-  let hud = yield openConsole();
+  let hud = await openConsole();
   ok(hud, "Web Console opened");
 
   info("dump some spew into the console for scrolling");
   hud.jsterm.execute("(function() { for (var i = 0; i < 100; i++) { " +
                      "console.log('foobarz' + i);" +
                      "}})();");
 
-  yield waitForMessages({
+  await waitForMessages({
     webconsole: hud,
     messages: [{
       text: "foobarz99",
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_LOG,
     }],
   });
 
@@ -56,17 +56,21 @@ add_task(function* () {
     let clearShortcut;
     if (Services.appinfo.OS === "Darwin") {
       clearShortcut = WCUL10n.getStr("webconsole.clear.keyOSX");
     } else {
       clearShortcut = WCUL10n.getStr("webconsole.clear.key");
     }
     synthesizeKeyShortcut(clearShortcut);
   });
-  yield hud.jsterm.once("messages-cleared");
+  await hud.jsterm.once("messages-cleared");
+
+  // Wait for the next event tick to make sure keyup for the shortcut above
+  // finishes.  Otherwise the 2 shortcuts are mixed.
+  await new Promise(executeSoon);
 
   is(hud.outputNode.textContent.indexOf("foobarz1"), -1, "output cleared");
   is(hud.jsterm.inputNode.getAttribute("focused"), "true",
      "jsterm input is focused");
 
   info("try ctrl-f to focus filter");
   synthesizeKeyShortcut(WCUL10n.getStr("webconsole.find.key"));
   ok(!hud.jsterm.inputNode.getAttribute("focused"),
--- a/devtools/client/webconsole/test/browser_webconsole_clear_method.js
+++ b/devtools/client/webconsole/test/browser_webconsole_clear_method.js
@@ -3,76 +3,80 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Check that calls to console.clear from a script delete the messages
 // previously logged.
 
 "use strict";
 
-add_task(function* () {
+add_task(async function () {
   const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
                    "test/test-console-clear.html";
 
-  yield loadTab(TEST_URI);
-  let hud = yield openConsole();
+  await loadTab(TEST_URI);
+  let hud = await openConsole();
   ok(hud, "Web Console opened");
 
   info("Check the console.clear() done on page load has been processed.");
-  yield waitForLog("Console was cleared", hud);
+  await waitForLog("Console was cleared", hud);
   ok(hud.outputNode.textContent.includes("Console was cleared"),
     "console.clear() message is displayed");
   ok(!hud.outputNode.textContent.includes("log1"), "log1 not displayed");
   ok(!hud.outputNode.textContent.includes("log2"), "log2 not displayed");
 
   info("Logging two messages log3, log4");
   ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
     content.wrappedJSObject.console.log("log3");
     content.wrappedJSObject.console.log("log4");
   });
 
-  yield waitForLog("log3", hud);
-  yield waitForLog("log4", hud);
+  await waitForLog("log3", hud);
+  await waitForLog("log4", hud);
 
   ok(hud.outputNode.textContent.includes("Console was cleared"),
     "console.clear() message is still displayed");
   ok(hud.outputNode.textContent.includes("log3"), "log3 is displayed");
   ok(hud.outputNode.textContent.includes("log4"), "log4 is displayed");
 
   info("Open the variables view sidebar for 'objFromPage'");
-  yield openSidebar("objFromPage", { a: 1 }, hud);
+  await openSidebar("objFromPage", { a: 1 }, hud);
   let sidebarClosed = hud.jsterm.once("sidebar-closed");
 
   info("Call console.clear from the page");
   ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
     content.wrappedJSObject.console.clear();
   });
 
   // Cannot wait for "Console was cleared" here because such a message is
-  // already present and would yield immediately.
+  // already present and would resolve immediately.
   info("Wait for variables view sidebar to be closed after console.clear()");
-  yield sidebarClosed;
+  await sidebarClosed;
+
+  // Wait for the next event tick to make sure the remaining part of the
+  // test is not executed before the message is actually flushed.
+  await new Promise(executeSoon);
 
   ok(!hud.outputNode.textContent.includes("log3"), "log3 not displayed");
   ok(!hud.outputNode.textContent.includes("log4"), "log4 not displayed");
   ok(hud.outputNode.textContent.includes("Console was cleared"),
     "console.clear() message is still displayed");
   is(hud.outputNode.textContent.split("Console was cleared").length, 2,
     "console.clear() message is only displayed once");
 
   info("Logging one messages log5");
   ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
     content.wrappedJSObject.console.log("log5");
   });
-  yield waitForLog("log5", hud);
+  await waitForLog("log5", hud);
 
   info("Close and reopen the webconsole.");
-  yield closeConsole(gBrowser.selectedTab);
-  hud = yield openConsole();
-  yield waitForLog("Console was cleared", hud);
+  await closeConsole(gBrowser.selectedTab);
+  hud = await openConsole();
+  await waitForLog("Console was cleared", hud);
 
   ok(hud.outputNode.textContent.includes("Console was cleared"),
     "console.clear() message is still displayed");
   ok(!hud.outputNode.textContent.includes("log1"), "log1 not displayed");
   ok(!hud.outputNode.textContent.includes("log2"), "log1 not displayed");
   ok(!hud.outputNode.textContent.includes("log3"), "log3 not displayed");
   ok(!hud.outputNode.textContent.includes("log4"), "log4 not displayed");
   ok(hud.outputNode.textContent.includes("log5"), "log5 still displayed");
@@ -82,18 +86,18 @@ add_task(function* () {
  * Wait for a single message to be logged in the provided webconsole instance
  * with the category CATEGORY_WEBDEV and the SEVERITY_LOG severity.
  *
  * @param {String} message
  *        The expected messaged.
  * @param {WebConsole} webconsole
  *        WebConsole instance in which the message should be logged.
  */
-function* waitForLog(message, webconsole, options) {
-  yield waitForMessages({
+function waitForLog(message, webconsole, options) {
+  return waitForMessages({
     webconsole: webconsole,
     messages: [{
       text: message,
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_LOG,
     }],
   });
 }
@@ -105,27 +109,27 @@ function* waitForLog(message, webconsole
  * @param {String} objName
  *        The name of the object to open in the sidebar.
  * @param {Object} expectedObj
  *        The properties that should be displayed in the variables view.
  * @param {WebConsole} webconsole
  *        WebConsole instance in which the message should be logged.
  *
  */
-function* openSidebar(objName, expectedObj, webconsole) {
-  let msg = yield webconsole.jsterm.execute(objName);
+async function openSidebar(objName, expectedObj, webconsole) {
+  let msg = await webconsole.jsterm.execute(objName);
   ok(msg, "output message found");
 
   let anchor = msg.querySelector("a");
   let body = msg.querySelector(".message-body");
   ok(anchor, "object anchor");
   ok(body, "message body");
 
-  yield EventUtils.synthesizeMouse(anchor, 2, 2, {}, webconsole.iframeWindow);
+  await EventUtils.synthesizeMouse(anchor, 2, 2, {}, webconsole.iframeWindow);
 
-  let vviewVar = yield webconsole.jsterm.once("variablesview-fetched");
+  let vviewVar = await webconsole.jsterm.once("variablesview-fetched");
   let vview = vviewVar._variablesView;
   ok(vview, "variables view object exists");
 
-  yield findVariableViewProperties(vviewVar, [
+  await findVariableViewProperties(vviewVar, [
     expectedObj,
   ], { webconsole: webconsole });
 }
--- a/devtools/server/actors/highlighters/flexbox.js
+++ b/devtools/server/actors/highlighters/flexbox.js
@@ -12,37 +12,37 @@ const {
   drawRect,
   getCurrentMatrix,
   updateCanvasElement,
   updateCanvasPosition,
 } = require("./utils/canvas");
 const {
   CanvasFrameAnonymousContentHelper,
   createNode,
+  getComputedStyle,
 } = require("./utils/markup");
 const {
   getAdjustedQuads,
   getDisplayPixelRatio,
   setIgnoreLayoutChanges,
 } = require("devtools/shared/layout/utils");
 
 const FLEXBOX_LINES_PROPERTIES = {
   "edge": {
-    lineDash: [0, 0],
-    alpha: 1,
+    lineDash: [2, 2]
   },
   "item": {
-    lineDash: [2, 2],
-    alpha: 1,
-  },
+    lineDash: [0, 0]
+  }
 };
 
 const FLEXBOX_CONTAINER_PATTERN_WIDTH = 14; // px
 const FLEXBOX_CONTAINER_PATTERN_HEIGHT = 14; // px
 const FLEXBOX_CONTAINER_PATTERN_LINE_DISH = [5, 3]; // px
+const BASIS_FILL_COLOR = "rgb(109, 184, 255, 0.4)";
 
 /**
  * Cached used by `FlexboxHighlighter.getFlexContainerPattern`.
  */
 const gCachedFlexboxPattern = new Map();
 
 const FLEXBOX = "flexbox";
 
@@ -252,60 +252,99 @@ class FlexboxHighlighter extends AutoRef
   onWillNavigate({ isTopLevel }) {
     this.clearCache();
 
     if (isTopLevel) {
       this.hide();
     }
   }
 
-  renderFlexContainer() {
+  renderFlexContainerBorder() {
+    if (!this.currentQuads.content || !this.currentQuads.content[0]) {
+      return;
+    }
+
+    let { devicePixelRatio } = this.win;
+    let lineWidth = getDisplayPixelRatio(this.win) * 2;
+    let offset = (lineWidth / 2) % 1;
+    let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
+    let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
+
+    this.ctx.save();
+    this.ctx.translate(offset - canvasX, offset - canvasY);
+    this.ctx.setLineDash(FLEXBOX_LINES_PROPERTIES.edge.lineDash);
+    this.ctx.lineWidth = lineWidth;
+    this.ctx.strokeStyle = DEFAULT_COLOR;
+
+    let { bounds } = this.currentQuads.content[0];
+    drawRect(this.ctx, 0, 0, bounds.width, bounds.height, this.currentMatrix);
+
+    this.ctx.stroke();
+    this.ctx.restore();
+  }
+
+  renderFlexContainerFill() {
     if (!this.currentQuads.content || !this.currentQuads.content[0]) {
       return;
     }
 
     let { devicePixelRatio } = this.win;
     let lineWidth = getDisplayPixelRatio(this.win);
     let offset = (lineWidth / 2) % 1;
-
     let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
     let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
 
     this.ctx.save();
     this.ctx.translate(offset - canvasX, offset - canvasY);
     this.ctx.setLineDash(FLEXBOX_LINES_PROPERTIES.edge.lineDash);
-    this.ctx.globalAlpha = FLEXBOX_LINES_PROPERTIES.edge.alpha;
-    this.ctx.lineWidth = lineWidth;
+    this.ctx.lineWidth = 0;
     this.ctx.strokeStyle = DEFAULT_COLOR;
     this.ctx.fillStyle = this.getFlexContainerPattern(devicePixelRatio);
 
     let { bounds } = this.currentQuads.content[0];
     drawRect(this.ctx, 0, 0, bounds.width, bounds.height, this.currentMatrix);
 
     this.ctx.fill();
     this.ctx.stroke();
     this.ctx.restore();
   }
 
+  /**
+   * Renders the flex basis for a given flex item.
+   */
+  renderFlexItemBasis(flexItem, left, top, right, bottom, boundsWidth) {
+    let computedStyle = getComputedStyle(flexItem);
+    let basis = computedStyle.getPropertyValue("flex-basis");
+
+    if (basis.endsWith("px")) {
+      right = left + parseFloat(basis);
+    } else if (basis.endsWith("%")) {
+      basis = parseFloat(basis) / 100 * boundsWidth;
+      right = left + basis;
+    }
+
+    this.ctx.fillStyle = BASIS_FILL_COLOR;
+    drawRect(this.ctx, left, top, right, bottom, this.currentMatrix);
+    this.ctx.fill();
+  }
+
   renderFlexItems() {
     if (!this.currentQuads.content || !this.currentQuads.content[0]) {
       return;
     }
 
     let { devicePixelRatio } = this.win;
     let lineWidth = getDisplayPixelRatio(this.win);
     let offset = (lineWidth / 2) % 1;
-
     let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
     let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
 
     this.ctx.save();
     this.ctx.translate(offset - canvasX, offset - canvasY);
     this.ctx.setLineDash(FLEXBOX_LINES_PROPERTIES.item.lineDash);
-    this.ctx.globalAlpha = FLEXBOX_LINES_PROPERTIES.item.alpha;
     this.ctx.lineWidth = lineWidth;
     this.ctx.strokeStyle = DEFAULT_COLOR;
 
     let { bounds } = this.currentQuads.content[0];
     let flexItems = this.currentNode.children;
 
     // TODO: Utilize the platform API that will be implemented in Bug 1414290 to
     // retrieve the flex item properties.
@@ -320,16 +359,64 @@ class FlexboxHighlighter extends AutoRef
       let left = flexItemBounds.left - bounds.left;
       let top = flexItemBounds.top - bounds.top;
       let right = flexItemBounds.right - bounds.left;
       let bottom = flexItemBounds.bottom - bounds.top;
 
       clearRect(this.ctx, left, top, right, bottom, this.currentMatrix);
       drawRect(this.ctx, left, top, right, bottom, this.currentMatrix);
       this.ctx.stroke();
+
+      this.renderFlexItemBasis(flexItem, left, top, right, bottom, bounds.width);
+    }
+
+    this.ctx.restore();
+  }
+
+  renderFlexLines() {
+    if (!this.currentQuads.content || !this.currentQuads.content[0]) {
+      return;
+    }
+
+    let { devicePixelRatio } = this.win;
+    let lineWidth = getDisplayPixelRatio(this.win);
+    let offset = (lineWidth / 2) % 1;
+    let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
+    let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
+
+    this.ctx.save();
+    this.ctx.translate(offset - canvasX, offset - canvasY);
+    this.ctx.lineWidth = lineWidth;
+    this.ctx.strokeStyle = DEFAULT_COLOR;
+
+    let { bounds } = this.currentQuads.content[0];
+    let computedStyle = getComputedStyle(this.currentNode);
+    let flexLines = this.currentNode.getAsFlexContainer().getLines();
+
+    for (let flexLine of flexLines) {
+      let { crossStart, crossSize } = flexLine;
+
+      if (computedStyle.getPropertyValue("flex-direction") === "column" ||
+          computedStyle.getPropertyValue("flex-direction") === "column-reverse") {
+        clearRect(this.ctx, crossStart, 0, crossStart + crossSize, bounds.height,
+          this.currentMatrix);
+        drawRect(this.ctx, crossStart, 0, crossStart, bounds.height, this.currentMatrix);
+        this.ctx.stroke();
+        drawRect(this.ctx, crossStart + crossSize, 0, crossStart + crossSize,
+          bounds.height, this.currentMatrix);
+        this.ctx.stroke();
+      } else {
+        clearRect(this.ctx, 0, crossStart, bounds.width, crossStart + crossSize,
+          this.currentMatrix);
+        drawRect(this.ctx, 0, crossStart, bounds.width, crossStart, this.currentMatrix);
+        this.ctx.stroke();
+        drawRect(this.ctx, 0, crossStart + crossSize, bounds.width,
+          crossStart + crossSize, this.currentMatrix);
+        this.ctx.stroke();
+      }
     }
 
     this.ctx.restore();
   }
 
   _update() {
     setIgnoreLayoutChanges(true);
 
@@ -347,18 +434,20 @@ class FlexboxHighlighter extends AutoRef
     updateCanvasElement(this.canvas, this._canvasPosition, this.win.devicePixelRatio);
 
     // Update the current matrix used in our canvas' rendering
     let { currentMatrix, hasNodeTransformations } = getCurrentMatrix(this.currentNode,
       this.win);
     this.currentMatrix = currentMatrix;
     this.hasNodeTransformations = hasNodeTransformations;
 
-    this.renderFlexContainer();
+    this.renderFlexContainerFill();
+    this.renderFlexLines();
     this.renderFlexItems();
+    this.renderFlexContainerBorder();
 
     this._showFlexbox();
 
     root.setAttribute("style",
       `position: absolute; width: ${width}px; height: ${height}px; overflow: hidden`);
 
     setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement);
     return true;
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -997,20 +997,20 @@ var StyleRuleActor = protocol.ActorClass
     return this.pageStyle;
   },
 
   // True if this rule supports as-authored styles, meaning that the
   // rule text can be rewritten using setRuleText.
   get canSetRuleText() {
     return this.type === ELEMENT_STYLE ||
            (this._parentSheet &&
-            // If a rule does not have source, then it has been modified via
-            // CSSOM; and we should fall back to non-authored editing.
+            // If a rule has been modified via CSSOM, then we should fall
+            // back to non-authored editing.
             // https://bugzilla.mozilla.org/show_bug.cgi?id=1224121
-            this.sheetActor.allRulesHaveSource() &&
+            !this.sheetActor.hasRulesModifiedByCSSOM() &&
             // Special case about:PreferenceStyleSheet, as it is generated on
             // the fly and the URI is not registered with the about:handler
             // https://bugzilla.mozilla.org/show_bug.cgi?id=935803#c37
             this._parentSheet.href !== "about:PreferenceStyleSheet");
   },
 
   getDocument: function (sheet) {
     if (sheet.ownerNode) {
@@ -1687,17 +1687,17 @@ function getSelectorOffsets(initialText,
  * @param {Number} column (1-indexed)
  * @return {object} An object of the form {offset: number, text: string},
  *                  where the offset is the offset into the input string
  *                  where the text starts, and where text is the text.
  */
 function getTextAtLineColumn(text, line, column) {
   let offset;
   if (line > 1) {
-    let rx = new RegExp("(?:.*(?:\\r\\n|\\n|\\r|\\f)){" + (line - 1) + "}");
+    let rx = new RegExp("(?:[^\\r\\n\\f]*(?:\\r\\n|\\n|\\r|\\f)){" + (line - 1) + "}");
     offset = rx.exec(text)[0].length;
   } else {
     offset = 0;
   }
   offset += column - 1;
   return {offset: offset, text: text.substr(offset) };
 }
 
--- a/devtools/server/actors/stylesheets.js
+++ b/devtools/server/actors/stylesheets.js
@@ -232,37 +232,21 @@ var StyleSheetActor = protocol.ActorClas
     if (parentStyleSheet.ownerNode) {
       this.ownerDocument = parentStyleSheet.ownerNode.ownerDocument;
     } else {
       this.ownerDocument = parentActor.window;
     }
   },
 
   /**
-   * Test whether all the rules in this sheet have associated source.
-   * @return {Boolean} true if all the rules have source; false if
-   *         some rule was created via CSSOM.
+   * Test whether this sheet has been modified by CSSOM.
+   * @return {Boolean} true if changed by CSSOM.
    */
-  allRulesHaveSource: function () {
-    let rules;
-    try {
-      rules = this.rawSheet.cssRules;
-    } catch (e) {
-      // sheet isn't loaded yet
-      return true;
-    }
-
-    for (let i = 0; i < rules.length; i++) {
-      let rule = rules[i];
-      if (InspectorUtils.getRelativeRuleLine(rule) === 0) {
-        return false;
-      }
-    }
-
-    return true;
+  hasRulesModifiedByCSSOM: function () {
+    return InspectorUtils.hasRulesModifiedByCSSOM(this.rawSheet);
   },
 
   /**
    * Get the raw stylesheet's cssRules once the sheet has been loaded.
    *
    * @return {Promise}
    *         Promise that resolves with a CSSRuleList
    */
--- a/devtools/server/tests/browser/browser_canvasframe_helper_04.js
+++ b/devtools/server/tests/browser/browser_canvasframe_helper_04.js
@@ -76,16 +76,21 @@ add_task(async function () {
     await onDocMouseDown;
     is(mouseDownHandled, 1, "The mousedown event was handled once before navigation");
 
     info("Navigating to a new page");
     let loaded = once(this, "load");
     content.location = url2;
     await loaded;
 
+    // Wait for the next event tick to make sure the remaining part of the
+    // test is not executed in the microtask checkpoint for load event
+    // itself.  Otherwise the synthesizeMouseDown doesn't work.
+    await new Promise(r => setTimeout(r, 0));
+
     // Update to the new document we just loaded
     doc = content.document;
 
     info("Try to access the element again");
     is(el.getAttribute("class"), "child-element",
       "The attribute is correct after navigation");
     is(el.getTextContent(), "test content",
       "The text content is correct after navigation");
--- a/docshell/shistory/nsSHEntryShared.cpp
+++ b/docshell/shistory/nsSHEntryShared.cpp
@@ -150,26 +150,23 @@ nsSHEntryShared::SetContentViewer(nsICon
   // non-null content viewer, the entry shouldn't have been tracked either.
   MOZ_ASSERT(!GetExpirationState()->IsTracked());
   mContentViewer = aViewer;
 
   if (mContentViewer) {
     // mSHistory is only set for root entries, but in general bfcache only
     // applies to root entries as well. BFCache for subframe navigation has been
     // disabled since 2005 in bug 304860.
-    nsCOMPtr<nsISHistoryInternal> shistory = do_QueryReferent(mSHistory);
-    if (shistory) {
+    if (nsCOMPtr<nsISHistoryInternal> shistory = do_QueryReferent(mSHistory)) {
       shistory->AddToExpirationTracker(this);
     }
 
-    nsCOMPtr<nsIDOMDocument> domDoc;
-    mContentViewer->GetDOMDocument(getter_AddRefs(domDoc));
     // Store observed document in strong pointer in case it is removed from
     // the contentviewer
-    mDocument = do_QueryInterface(domDoc);
+    mDocument = mContentViewer->GetDocument();
     if (mDocument) {
       mDocument->SetBFCacheEntry(this);
       mDocument->AddMutationObserver(this);
     }
   }
 
   return NS_OK;
 }
--- a/dom/base/BodyUtil.cpp
+++ b/dom/base/BodyUtil.cpp
@@ -41,27 +41,27 @@ PushOverLine(nsACString::const_iterator&
     ++aStart; // advance to after CRLF
     return true;
   }
 
   return false;
 }
 
 class MOZ_STACK_CLASS FillFormIterator final
-  : public URLSearchParams::ForEachIterator
+  : public URLParams::ForEachIterator
 {
 public:
   explicit FillFormIterator(FormData* aFormData)
     : mFormData(aFormData)
   {
     MOZ_ASSERT(aFormData);
   }
 
-  bool URLParamsIterator(const nsString& aName,
-                         const nsString& aValue) override
+  bool URLParamsIterator(const nsAString& aName,
+                         const nsAString& aValue) override
   {
     ErrorResult rv;
     mFormData->Append(aName, aValue, rv);
     MOZ_ASSERT(!rv.Failed());
     return true;
   }
 
 private:
@@ -464,22 +464,19 @@ BodyUtil::ConsumeFormData(nsIGlobalObjec
   NS_NAMED_LITERAL_CSTRING(urlDataMimeType, "application/x-www-form-urlencoded");
   bool isValidUrlEncodedMimeType = StringBeginsWith(aMimeType, urlDataMimeType);
 
   if (isValidUrlEncodedMimeType && aMimeType.Length() > urlDataMimeType.Length()) {
     isValidUrlEncodedMimeType = aMimeType[urlDataMimeType.Length()] == ';';
   }
 
   if (isValidUrlEncodedMimeType) {
-    URLParams params;
-    params.ParseInput(aStr);
-
     RefPtr<FormData> fd = new FormData(aParent);
     FillFormIterator iterator(fd);
-    DebugOnly<bool> status = params.ForEach(iterator);
+    DebugOnly<bool> status = URLParams::Parse(aStr, iterator);
     MOZ_ASSERT(status);
 
     return fd.forget();
   }
 
   aRv.ThrowTypeError<MSG_BAD_FORMDATA>();
   return nullptr;
 }
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1477,16 +1477,17 @@ nsIDocument::nsIDocument()
     mHasScrollLinkedEffect(false),
     mFrameRequestCallbacksScheduled(false),
     mIsTopLevelContentDocument(false),
     mIsContentDocument(false),
     mDidCallBeginLoad(false),
     mBufferingCSPViolations(false),
     mAllowPaymentRequest(false),
     mEncodingMenuDisabled(false),
+    mIsSVGGlyphsDocument(false),
     mIsScopedStyleEnabled(eScopedStyle_Unknown),
     mCompatMode(eCompatibility_FullStandards),
     mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
     mStyleBackendType(StyleBackendType::None),
 #ifdef MOZILLA_INTERNAL_API
     mVisibilityState(dom::VisibilityState::Hidden),
 #else
     mDummy(0),
--- a/dom/base/nsIImageLoadingContent.idl
+++ b/dom/base/nsIImageLoadingContent.idl
@@ -35,99 +35,88 @@ native Visibility(mozilla::Visibility);
  * the currently loaded image will start a "pending" request which will
  * become current only when the image is loaded.  It is the responsibility of
  * observers to check which request they are getting notifications for.
  *
  * Please make sure to update the MozImageLoadingContent WebIDL
  * interface to mirror this interface when changing it.
  */
 
+// We can't make this interface noscript yet, because there is JS code doing
+// "instanceof Ci.nsIImageLoadingContent" and using the nsIImageLoadingContent
+// constants.
 [scriptable, builtinclass, uuid(0357123d-9224-4d12-a47e-868c32689777)]
 interface nsIImageLoadingContent : imgINotificationObserver
 {
   /**
    * Request types.  Image loading content nodes attempt to do atomic
    * image changes when the image url is changed.  This means that
    * when the url changes the new image load will start, but the old
    * image will remain the "current" request until the new image is
    * fully loaded.  At that point, the old "current" request will be
    * discarded and the "pending" request will become "current".
    */
   const long UNKNOWN_REQUEST = -1;
   const long CURRENT_REQUEST = 0;
   const long PENDING_REQUEST = 1;
 
   /**
-   * loadingEnabled is used to enable and disable loading in
+   * setLoadingEnabled is used to enable and disable loading in
    * situations where loading images is unwanted.  Note that enabling
    * loading will *not* automatically trigger an image load.
    */
-  attribute boolean loadingEnabled;
+  [notxpcom, nostdcall] void setLoadingEnabled(in boolean aEnabled);
 
   /**
    * Returns the image blocking status (@see nsIContentPolicy).  This
    * will always be an nsIContentPolicy REJECT_* status for cases when
    * the image was blocked.  This status always refers to the
    * CURRENT_REQUEST load.
    */
-  readonly attribute short imageBlockingStatus;
+  [noscript] readonly attribute short imageBlockingStatus;
 
   /**
    * Used to register an image decoder observer.  Typically, this will
    * be a proxy for a frame that wants to paint the image.
    * Notifications from ongoing image loads will be passed to all
    * registered observers.  Notifications for all request types,
    * current and pending, will be passed through.
    *
    * @param aObserver the observer to register
-   *
-   * @throws NS_ERROR_OUT_OF_MEMORY
    */
-  [notxpcom] nsresult addNativeObserver(in imgINotificationObserver aObserver);
+  [notxpcom, nostdcall] void addNativeObserver(in imgINotificationObserver aObserver);
 
   /**
    * Used to unregister an image decoder observer.
    *
    * @param aObserver the observer to unregister
    */
-  [notxpcom] nsresult removeNativeObserver(in imgINotificationObserver aObserver);
+  [notxpcom, nostdcall] void removeNativeObserver(in imgINotificationObserver aObserver);
 
   /**
-   * Same as addNativeObserver but intended for scripted observers or observers
-   * from another or without a document.
-   */
-  void addObserver(in imgINotificationObserver aObserver);
-
-  /**
-   * Same as removeNativeObserver but intended for scripted observers or
-   * observers from another or without a document.
-   */
-  void removeObserver(in imgINotificationObserver aObserver);
-  
-  /**
    * Accessor to get the image requests
    *
    * @param aRequestType a value saying which request is wanted
    *
    * @return the imgIRequest object (may be null, even when no error
    * is thrown)
    *
    * @throws NS_ERROR_UNEXPECTED if the request type requested is not
    * known
    */
-  imgIRequest getRequest(in long aRequestType);
+  [noscript] imgIRequest getRequest(in long aRequestType);
 
   /**
    * Call this function when the request was blocked by any of the
    * security policies enforced.
    *
    * @param aContentDecision the decision returned from nsIContentPolicy
    *                         (any of the types REJECT_*)
    */
-  void setBlockedRequest(in int16_t aContentDecision);
+  [notxpcom, nostdcall] void setBlockedRequest(in int16_t aContentDecision);
 
   /**
    * @return true if the current request's size is available.
    */
   [noscript, notxpcom] boolean currentRequestHasSize();
 
   /**
    * Used to notify the image loading content node that a frame has been
@@ -147,60 +136,52 @@ interface nsIImageLoadingContent : imgIN
    * interface of an observer)
    *
    * @param aRequest the request whose type we want to know
    *
    * @return an enum value saying what type this request is
    *
    * @throws NS_ERROR_UNEXPECTED if aRequest is not known
    */
-  long getRequestType(in imgIRequest aRequest);
+  [noscript] long getRequestType(in imgIRequest aRequest);
 
   /**
    * Gets the URI of the current request, if available.
    * Otherwise, returns the last URI that this content tried to load, or
    * null if there haven't been any such attempts.
    */
-  readonly attribute nsIURI currentURI;
+  [noscript] readonly attribute nsIURI currentURI;
 
   /**
    * loadImageWithChannel allows data from an existing channel to be
    * used as the image data for this content node.
    *
    * @param aChannel the channel that will deliver the data
    *
    * @return a stream listener to pump the image data into
    *
    * @see imgILoader::loadImageWithChannel
    *
    * @throws NS_ERROR_NULL_POINTER if aChannel is null
    */
-  nsIStreamListener loadImageWithChannel(in nsIChannel aChannel);
+  [noscript] nsIStreamListener loadImageWithChannel(in nsIChannel aChannel);
 
   /**
-   * forceReload forces reloading of the image pointed to by currentURI
-   *
-   * @param aNotify [optional] request should notify, defaults to true
-   * @throws NS_ERROR_NOT_AVAILABLE if there is no current URI to reload
+   * Enables/disables image state forcing. When |aForce| is true, we force
+   * nsImageLoadingContent::ImageState() to return |aState|. Call again with |aForce|
+   * as false to revert ImageState() to its original behaviour.
    */
-  [optional_argc] void forceReload([optional] in boolean aNotify /* = true */);
-
-  /**
-   * Enables/disables image state forcing. When |aForce| is PR_TRUE, we force
-   * nsImageLoadingContent::ImageState() to return |aState|. Call again with |aForce|
-   * as PR_FALSE to revert ImageState() to its original behaviour.
-   */
-  void forceImageState(in boolean aForce, in unsigned long long aState);
+  [notxpcom, nostdcall] void forceImageState(in boolean aForce, in unsigned long long aState);
 
   /**
    * The intrinsic size and width of this content. May differ from actual image
    * size due to things like responsive image density.
    */
-  readonly attribute unsigned long    naturalWidth;
-  readonly attribute unsigned long    naturalHeight;
+  [noscript] readonly attribute unsigned long    naturalWidth;
+  [noscript] readonly attribute unsigned long    naturalHeight;
 
   /**
    * Called by layout to announce when the frame associated with this content
    * has changed its visibility state.
    *
    * @param aNewVisibility    The new visibility state.
    * @param aNonvisibleAction A requested action if the frame has become
    *                          nonvisible. If Nothing(), no action is
--- a/dom/base/nsImageLoadingContent.cpp
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -326,30 +326,22 @@ nsImageLoadingContent::OnImageIsAnimated
 
   return NS_OK;
 }
 
 /*
  * nsIImageLoadingContent impl
  */
 
-NS_IMETHODIMP
-nsImageLoadingContent::GetLoadingEnabled(bool *aLoadingEnabled)
-{
-  *aLoadingEnabled = mLoadingEnabled;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
+void
 nsImageLoadingContent::SetLoadingEnabled(bool aLoadingEnabled)
 {
   if (nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) {
     mLoadingEnabled = aLoadingEnabled;
   }
-  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsImageLoadingContent::GetImageBlockingStatus(int16_t* aStatus)
 {
   NS_PRECONDITION(aStatus, "Null out param");
   *aStatus = ImageBlockingStatus();
   return NS_OK;
@@ -383,54 +375,56 @@ ReplayImageStatus(imgIRequest* aRequest,
   if (status & imgIRequest::STATUS_DECODE_COMPLETE) {
     aObserver->Notify(aRequest, imgINotificationObserver::DECODE_COMPLETE, nullptr);
   }
   if (status & imgIRequest::STATUS_LOAD_COMPLETE) {
     aObserver->Notify(aRequest, imgINotificationObserver::LOAD_COMPLETE, nullptr);
   }
 }
 
-NS_IMETHODIMP
+void
 nsImageLoadingContent::AddNativeObserver(imgINotificationObserver* aObserver)
 {
-  NS_ENSURE_ARG_POINTER(aObserver);
+  if (NS_WARN_IF(!aObserver)) {
+    return;
+  }
 
   if (!mObserverList.mObserver) {
     // Don't touch the linking of the list!
     mObserverList.mObserver = aObserver;
 
     ReplayImageStatus(mCurrentRequest, aObserver);
     ReplayImageStatus(mPendingRequest, aObserver);
 
-    return NS_OK;
+    return;
   }
 
   // otherwise we have to create a new entry
 
   ImageObserver* observer = &mObserverList;
   while (observer->mNext) {
     observer = observer->mNext;
   }
 
   observer->mNext = new ImageObserver(aObserver);
   ReplayImageStatus(mCurrentRequest, aObserver);
   ReplayImageStatus(mPendingRequest, aObserver);
-
-  return NS_OK;
 }
 
-NS_IMETHODIMP
+void
 nsImageLoadingContent::RemoveNativeObserver(imgINotificationObserver* aObserver)
 {
-  NS_ENSURE_ARG_POINTER(aObserver);
+  if (NS_WARN_IF(!aObserver)) {
+    return;
+  }
 
   if (mObserverList.mObserver == aObserver) {
     mObserverList.mObserver = nullptr;
     // Don't touch the linking of the list!
-    return NS_OK;
+    return;
   }
 
   // otherwise have to find it and splice it out
   ImageObserver* observer = &mObserverList;
   while (observer->mNext && observer->mNext->mObserver != aObserver) {
     observer = observer->mNext;
   }
 
@@ -443,79 +437,80 @@ nsImageLoadingContent::RemoveNativeObser
     oldObserver->mNext = nullptr;  // so we don't destroy them all
     delete oldObserver;
   }
 #ifdef DEBUG
   else {
     NS_WARNING("Asked to remove nonexistent observer");
   }
 #endif
-  return NS_OK;
 }
 
-NS_IMETHODIMP
+void
 nsImageLoadingContent::AddObserver(imgINotificationObserver* aObserver)
 {
-  NS_ENSURE_ARG_POINTER(aObserver);
+  if (NS_WARN_IF(!aObserver)) {
+    return;
+  }
 
   nsresult rv = NS_OK;
   RefPtr<imgRequestProxy> currentReq;
   if (mCurrentRequest) {
     // Scripted observers may not belong to the same document as us, so when we
     // create the imgRequestProxy, we shouldn't use any. This allows the request
     // to dispatch notifications from the correct scheduler group.
     rv = mCurrentRequest->Clone(aObserver, nullptr, getter_AddRefs(currentReq));
     if (NS_FAILED(rv)) {
-      return rv;
+      return;
     }
   }
 
   RefPtr<imgRequestProxy> pendingReq;
<