Merge mozilla-central to mozilla-inbound. r=merge a=merge CLOSED TREE
authorshindli <shindli@mozilla.com>
Thu, 23 Nov 2017 00:17:19 +0200
changeset 393233 c01eab6a9e8065c42b69ef4a5e9616cdb90bdace
parent 393180 ffc12802d5585e08de1a9ae4f2939e05bbea5767 (current diff)
parent 393232 960f50c2e0a991ab2ab313132e69fb2c96cb7866 (diff)
child 393234 6c2e5d5828a5d7a089dd340f5a2cd250459a40f6
push id97613
push usershindli@mozilla.com
push dateWed, 22 Nov 2017 22:22:51 +0000
treeherdermozilla-inbound@c01eab6a9e80 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound. r=merge a=merge CLOSED TREE
browser/extensions/formautofill/content/addressReferences.js
dom/tests/mochitest/webcomponents/test_document_register_lifecycle.html
dom/tests/mochitest/webcomponents/test_document_register_stack.html
mobile/android/chrome/content/browser.js
servo/etc/rustdoc-with-private
servo/tests/compiletest/helper/Cargo.toml
servo/tests/compiletest/helper/lib.rs
servo/tests/compiletest/plugin/Cargo.toml
servo/tests/compiletest/plugin/compile-fail/arc_rc_must_not_derive_malloc_size_of.rs
servo/tests/compiletest/plugin/compile-fail/deny_public_fields.rs
servo/tests/compiletest/plugin/compile-fail/trustedpromise_mustnot_deriveclone.rs
servo/tests/compiletest/plugin/compile-fail/unrooted_must_root.rs
servo/tests/compiletest/plugin/lib.rs
widget/ContentCache.cpp
widget/ContentCache.h
--- a/.gitignore
+++ b/.gitignore
@@ -2,17 +2,17 @@
 
 # Filenames that should be ignored wherever they appear
 *~
 *.pyc
 *.pyo
 TAGS
 tags
 ID
-!/browser/extensions/activity-stream/prerendered/id/
+!/browser/extensions/activity-stream/prerendered/locales/id/
 !/browser/extensions/screenshots/webextension/_locales/id/
 .DS_Store*
 *.pdb
 *.egg-info
 
 # Vim swap files.
 .*.sw[a-z]
 
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-bug 1413336 - if PSM xpcshell tests have been run locally, these changes will break the tests without a clobber (see bug 1416332)
+Bug 1419814 - a11y IDL changes causing build failures
--- a/browser/base/content/test/general/browser_bug581253.js
+++ b/browser/base/content/test/general/browser_bug581253.js
@@ -19,16 +19,17 @@ add_task(async function test_remove_book
   await PlacesUtils.bookmarks.insert({
     parentGuid: PlacesUtils.bookmarks.unfiledGuid,
     title: "",
     url: testURL,
   });
 
   Assert.ok(await PlacesUtils.bookmarks.fetch({url: testURL}), "the test url is bookmarked");
 
+  // eslint-disable-next-line mozilla/no-cpows-in-tests
   content.location = testURL;
 
   await BrowserTestUtils.waitForCondition(
     () => BookmarkingUI.status == BookmarkingUI.STATUS_STARRED,
     "star button indicates that the page is bookmarked");
 
   PlacesUtils.tagging.tagURI(makeURI(testURL), [testTag]);
 
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -25,16 +25,20 @@ var gExceptionPaths = [
 
   // https://github.com/mozilla/activity-stream/issues/3053
   "resource://activity-stream/data/content/tippytop/images/",
   // https://github.com/mozilla/activity-stream/issues/3758
   "resource://activity-stream/prerendered/",
 
   // browser/extensions/pdfjs/content/build/pdf.js#1999
   "resource://pdf.js/web/images/",
+
+  // Exclude all the metadata paths under the country metadata folder because these
+  // paths will be concatenated in FormAutofillUtils.jsm based on different country/region.
+  "resource://formautofill/addressmetadata/",
 ];
 
 // These are not part of the omni.ja file, so we find them only when running
 // the test on a non-packaged build.
 if (AppConstants.platform == "macosx")
   gExceptionPaths.push("resource://gre/res/cursors/");
 
 var whitelist = [
--- a/browser/components/newtab/tests/browser/browser_packaged_as_locales.js
+++ b/browser/components/newtab/tests/browser/browser_packaged_as_locales.js
@@ -33,20 +33,27 @@ add_task(async function test_default_loc
   const url = await getUrlForLocale("de-UNKNOWN");
   Assert.equal(url, DEFAULT_URL.replace("en-US", "de"));
 });
 
 /**
  * Tests that all activity stream packaged locales can be referenced / accessed
  */
 add_task(async function test_all_packaged_locales() {
+  let gotID = false;
   const listing = await (await fetch("resource://activity-stream/prerendered/")).text();
   for (const line of listing.split("\n").slice(2)) {
     const [file, , , type] = line.split(" ").slice(1);
     if (type === "DIRECTORY") {
       const locale = file.replace("/", "");
       if (locale !== "static") {
         const url = await getUrlForLocale(locale);
         Assert[locale === "en-US" ? "equal" : "notEqual"](url, DEFAULT_URL, `can reference "${locale}" files`);
+
+        // Specially remember if we saw an ID locale packaged as it can be
+        // easily ignored by source control, e.g., .gitignore
+        gotID |= locale === "id";
       }
     }
   }
+
+  Assert.ok(gotID, `"id" locale packaged and not ignored`);
 });
--- a/browser/components/preferences/in-content/tests/browser_advanced_update.js
+++ b/browser/components/preferences/in-content/tests/browser_advanced_update.js
@@ -109,16 +109,17 @@ add_task(async function() {
   gBrowser.removeCurrentTab();
 });
 
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
   let doc = gBrowser.selectedBrowser.contentDocument;
 
   let showBtn = doc.getElementById("showUpdateHistory");
+  // eslint-disable-next-line mozilla/no-cpows-in-tests
   let dialogOverlay = content.gSubDialog._preloadDialog._overlay;
 
   // XXX: For unknown reasons, this mock cannot be loaded by
   // XPCOMUtils.defineLazyServiceGetter() called in aboutDialog-appUpdater.js.
   // It is registered here so that we could assert update history subdialog
   // without stopping the preferences advanced pane from loading.
   // See bug 1361929.
   mockUpdateManager.register();
--- a/browser/components/preferences/in-content/tests/browser_cookies_dialog.js
+++ b/browser/components/preferences/in-content/tests/browser_cookies_dialog.js
@@ -35,16 +35,17 @@ add_task(async function testDeleteCookie
   if (AppConstants.platform == "macosx") {
     EventUtils.synthesizeKey("VK_BACK_SPACE", {});
   } else {
     EventUtils.synthesizeKey("VK_DELETE", {});
   }
 
   await waitForCondition(() => tree.view.rowCount == 0);
 
+  // eslint-disable-next-line mozilla/no-cpows-in-tests
   is_element_visible(content.gSubDialog._dialogs[0]._box,
     "Subdialog is visible after deleting an element");
 
 });
 
 add_task(async function removeTab() {
   gBrowser.removeCurrentTab();
 });
--- a/browser/components/preferences/in-content/tests/browser_password_management.js
+++ b/browser/components/preferences/in-content/tests/browser_password_management.js
@@ -61,11 +61,12 @@ add_task(async function test_deletePassw
   if (AppConstants.platform == "macosx") {
     EventUtils.synthesizeKey("VK_BACK_SPACE", {});
   } else {
     EventUtils.synthesizeKey("VK_DELETE", {});
   }
 
   await waitForCondition(() => tree.view.rowCount == 0);
 
+  // eslint-disable-next-line mozilla/no-cpows-in-tests
   is_element_visible(content.gSubDialog._dialogs[0]._box,
     "Subdialog is visible after deleting an element");
 });
--- a/browser/components/preferences/in-content/tests/browser_siteData.js
+++ b/browser/components/preferences/in-content/tests/browser_siteData.js
@@ -142,16 +142,17 @@ add_task(async function() {
   // eslint-disable-next-line mozilla/no-cpows-in-tests
   await waitForEvent(gBrowser.selectedBrowser.contentWindow, "test-indexedDB-done");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
   let updatedPromise = promiseSiteDataManagerSitesUpdated();
   await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
   await updatedPromise;
   await openSiteDataSettingsDialog();
+  // eslint-disable-next-line mozilla/no-cpows-in-tests
   let dialog = content.gSubDialog._topDialog;
   let dialogFrame = dialog._frame;
   let frameDoc = dialogFrame.contentDocument;
 
   let siteItems = frameDoc.getElementsByTagName("richlistitem");
   is(siteItems.length, 2, "Should list sites using quota usage or appcache");
 
   let appcacheSite = frameDoc.querySelector(`richlistitem[host="${TEST_OFFLINE_HOST}"]`);
--- a/browser/components/preferences/in-content/tests/browser_siteData3.js
+++ b/browser/components/preferences/in-content/tests/browser_siteData3.js
@@ -132,16 +132,17 @@ add_task(async function() {
     },
   ];
 
   let updatePromise = promiseSiteDataManagerSitesUpdated();
   await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
   await updatePromise;
   await openSiteDataSettingsDialog();
 
+  // eslint-disable-next-line mozilla/no-cpows-in-tests
   let dialog = content.gSubDialog._topDialog;
   let dialogFrame = dialog._frame;
   let frameDoc = dialogFrame.contentDocument;
   let hostCol = frameDoc.getElementById("hostCol");
   let usageCol = frameDoc.getElementById("usageCol");
   let statusCol = frameDoc.getElementById("statusCol");
   let sitesList = frameDoc.getElementById("sitesList");
 
--- a/browser/components/preferences/in-content/tests/browser_site_login_exceptions.js
+++ b/browser/components/preferences/in-content/tests/browser_site_login_exceptions.js
@@ -66,11 +66,12 @@ add_task(async function deleteALoginExce
   if (AppConstants.platform == "macosx") {
     EventUtils.synthesizeKey("VK_BACK_SPACE", {});
   } else {
     EventUtils.synthesizeKey("VK_DELETE", {});
   }
 
   await waitForCondition(() => tree.view.rowCount == 0);
 
+  // eslint-disable-next-line mozilla/no-cpows-in-tests
   is_element_visible(content.gSubDialog._dialogs[0]._box,
     "Subdialog is visible after deleting an element");
 });
--- a/browser/components/preferences/in-content/tests/browser_subdialogs.js
+++ b/browser/components/preferences/in-content/tests/browser_subdialogs.js
@@ -174,16 +174,17 @@ add_task(async function check_reopening_
   await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
     function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.acceptDialog(); },
     "accept", 1);
 });
 
 add_task(async function check_opening_while_closing() {
   await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
   info("closing");
+  // eslint-disable-next-line mozilla/no-cpows-in-tests
   content.window.gSubDialog._topDialog.close();
   info("reopening immediately after calling .close()");
   await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
   await close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
     function() { content.window.gSubDialog._topDialog._frame.contentDocument.documentElement.acceptDialog(); },
     "accept", 1);
 
 });
--- a/browser/components/search/test/browser_abouthome_behavior.js
+++ b/browser/components/search/test/browser_abouthome_behavior.js
@@ -34,16 +34,17 @@ function test() {
       if (event.originalTarget != tab.linkedBrowser.contentDocumentAsCPOW ||
           event.target.location.href == "about:blank") {
         info("skipping spurious load event");
         return;
       }
       tab.linkedBrowser.removeEventListener("load", load, true);
 
       // Observe page setup
+      // eslint-disable-next-line mozilla/no-cpows-in-tests
       let doc = gBrowser.contentDocumentAsCPOW;
       gMutationObserver = new MutationObserver(function(mutations) {
         for (let mutation of mutations) {
           if (mutation.attributeName == "searchEngineName") {
             // Re-add the listener, and perform a search
             gBrowser.addProgressListener(listener);
             gMutationObserver.disconnect();
             gMutationObserver = null;
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/prerendered/locales/id/activity-stream-prerendered.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<html lang="id" dir="ltr">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="Content-Security-Policy-Report-Only" content="script-src 'unsafe-inline'; img-src http: https: data: blob:; style-src 'unsafe-inline'; child-src 'none'; object-src 'none'; report-uri https://tiles.services.mozilla.com/v4/links/activity-stream/csp">
+    <link rel="icon" type="image/png" id="favicon" href="chrome://branding/content/icon32.png"/>
+    <link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
+    <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
+  </head>
+  <body class="activity-stream">
+    <div id="root"><div class="outer-wrapper fixed-to-top" data-reactroot="" data-reactid="1" data-react-checksum="-19032572"><main data-reactid="2"><div class="search-wrapper" data-reactid="3"><label for="newtab-search-text" class="search-label" data-reactid="4"><span class="sr-only" data-reactid="5"><span data-reactid="6">Cari di Web</span></span></label><input type="search" id="newtab-search-text" maxlength="256" placeholder="Cari di Web" title="Cari di Web" data-reactid="7"/><button id="searchSubmit" class="search-button" title="Cari" data-reactid="8"><span class="sr-only" data-reactid="9"><span data-reactid="10">Cari</span></span></button></div><div class="body-wrapper" data-reactid="11"><section class="collapsible-section top-sites animation-enabled" data-reactid="12"><div class="section-top-bar" data-reactid="13"><h3 class="section-title" data-reactid="14"><span class="click-target" data-reactid="15"><span class="icon icon-small-spacer icon-topsites" data-reactid="16"></span><span data-reactid="17">Situs Teratas</span><span class="icon icon-arrowhead-down" data-reactid="18"></span></span></h3><span class="section-info-option" data-reactid="19"><img class="info-option-icon" title="Info" aria-haspopup="true" aria-controls="info-option" aria-expanded="false" role="note" tabindex="0" data-reactid="20"/><div class="info-option" data-reactid="21"><div class="info-option-header" role="heading" data-reactid="22"><span data-reactid="23">Situs Teratas</span></div><p class="info-option-body" data-reactid="24"><span data-reactid="25">Mengakses situs web yang paling sering Anda kunjungi.</span></p><div class="info-option-manage" data-reactid="26"><button data-reactid="27"><span data-reactid="28">Preferensi Tab Baru</span></button></div></div></span></div><div class="section-body" data-reactid="29"><ul class="top-sites-list" data-reactid="30"><li class="top-site-outer placeholder" data-reactid="31"><a data-reactid="32"><div class="tile" aria-hidden="true" data-reactid="33"><div class="screenshot" style="background-image:none;" data-reactid="34"></div></div><div class="title " data-reactid="35"><span dir="auto" data-reactid="36"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="37"><a data-reactid="38"><div class="tile" aria-hidden="true" data-reactid="39"><div class="screenshot" style="background-image:none;" data-reactid="40"></div></div><div class="title " data-reactid="41"><span dir="auto" data-reactid="42"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="43"><a data-reactid="44"><div class="tile" aria-hidden="true" data-reactid="45"><div class="screenshot" style="background-image:none;" data-reactid="46"></div></div><div class="title " data-reactid="47"><span dir="auto" data-reactid="48"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="49"><a data-reactid="50"><div class="tile" aria-hidden="true" data-reactid="51"><div class="screenshot" style="background-image:none;" data-reactid="52"></div></div><div class="title " data-reactid="53"><span dir="auto" data-reactid="54"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="55"><a data-reactid="56"><div class="tile" aria-hidden="true" data-reactid="57"><div class="screenshot" style="background-image:none;" data-reactid="58"></div></div><div class="title " data-reactid="59"><span dir="auto" data-reactid="60"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="61"><a data-reactid="62"><div class="tile" aria-hidden="true" data-reactid="63"><div class="screenshot" style="background-image:none;" data-reactid="64"></div></div><div class="title " data-reactid="65"><span dir="auto" data-reactid="66"></span></div></a></li></ul><div class="edit-topsites-wrapper" data-reactid="67"><div class="edit-topsites-button" data-reactid="68"><button class="edit" title="Ubahsuai bagian Situs Teratas Anda" data-reactid="69"><span data-reactid="70">Sunting</span></button></div></div></div></section><div class="sections-list" data-reactid="71"><section class="collapsible-section section animation-enabled" data-reactid="72"><div class="section-top-bar" data-reactid="73"><h3 class="section-title" data-reactid="74"><span class="click-target" data-reactid="75"><span class="icon icon-small-spacer icon-pocket" data-reactid="76"></span><span data-reactid="77">Disarankan oleh Pocket</span><span class="icon icon-arrowhead-down" data-reactid="78"></span></span></h3></div><div class="section-body" data-reactid="79"><ul class="section-list" style="padding:0;" data-reactid="80"><li class="card-outer placeholder" data-reactid="81"><a data-reactid="82"><div class="card" data-reactid="83"><div class="card-details no-image" data-reactid="84"><div class="card-text no-context no-description no-host-name no-image" data-reactid="85"><h4 class="card-title" dir="auto" data-reactid="86"></h4><p class="card-description" dir="auto" data-reactid="87"></p></div><div class="card-context" data-reactid="88"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="89"><a data-reactid="90"><div class="card" data-reactid="91"><div class="card-details no-image" data-reactid="92"><div class="card-text no-context no-description no-host-name no-image" data-reactid="93"><h4 class="card-title" dir="auto" data-reactid="94"></h4><p class="card-description" dir="auto" data-reactid="95"></p></div><div class="card-context" data-reactid="96"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="97"><a data-reactid="98"><div class="card" data-reactid="99"><div class="card-details no-image" data-reactid="100"><div class="card-text no-context no-description no-host-name no-image" data-reactid="101"><h4 class="card-title" dir="auto" data-reactid="102"></h4><p class="card-description" dir="auto" data-reactid="103"></p></div><div class="card-context" data-reactid="104"></div></div></div></a></li></ul><div class="topic" data-reactid="105"><span data-reactid="106"><span data-reactid="107">Topik Populer:</span></span><ul data-reactid="108"></ul></div></div></section><section class="collapsible-section section animation-enabled" data-reactid="109"><div class="section-top-bar" data-reactid="110"><h3 class="section-title" data-reactid="111"><span class="click-target" data-reactid="112"><span class="icon icon-small-spacer icon-highlights" data-reactid="113"></span><span data-reactid="114">Sorotan</span><span class="icon icon-arrowhead-down" data-reactid="115"></span></span></h3></div><div class="section-body" data-reactid="116"><ul class="section-list" style="padding:0;" data-reactid="117"><li class="card-outer placeholder" data-reactid="118"><a data-reactid="119"><div class="card" data-reactid="120"><div class="card-details no-image" data-reactid="121"><div class="card-text no-context no-description no-host-name no-image" data-reactid="122"><h4 class="card-title" dir="auto" data-reactid="123"></h4><p class="card-description" dir="auto" data-reactid="124"></p></div><div class="card-context" data-reactid="125"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="126"><a data-reactid="127"><div class="card" data-reactid="128"><div class="card-details no-image" data-reactid="129"><div class="card-text no-context no-description no-host-name no-image" data-reactid="130"><h4 class="card-title" dir="auto" data-reactid="131"></h4><p class="card-description" dir="auto" data-reactid="132"></p></div><div class="card-context" data-reactid="133"></div></div></div></a></li><li class="card-outer placeholder" data-reactid="134"><a data-reactid="135"><div class="card" data-reactid="136"><div class="card-details no-image" data-reactid="137"><div class="card-text no-context no-description no-host-name no-image" data-reactid="138"><h4 class="card-title" dir="auto" data-reactid="139"></h4><p class="card-description" dir="auto" data-reactid="140"></p></div><div class="card-context" data-reactid="141"></div></div></div></a></li></ul></div></section></div></div><!-- react-empty: 142 --></main></div></div>
+    <div id="snippets-container">
+      <div id="snippets"></div>
+    </div>
+    <script>
+// Don't directly load the following scripts as part of html to let the page
+// finish loading to render the content sooner.
+for (const src of [
+  "resource://activity-stream/prerendered/static/activity-stream-initial-state.js",
+  "chrome://browser/content/contentSearchUI.js",
+  "resource://activity-stream/vendor/react.js",
+  "resource://activity-stream/vendor/react-dom.js",
+  "resource://activity-stream/vendor/react-intl.js",
+  "resource://activity-stream/vendor/redux.js",
+  "resource://activity-stream/vendor/react-redux.js",
+  "resource://activity-stream/prerendered/id/activity-stream-strings.js",
+  "resource://activity-stream/data/content/activity-stream.bundle.js"
+]) {
+  // These dynamically inserted scripts by default are async, but we need them
+  // to load in the desired order (i.e., bundle last).
+  const script = document.body.appendChild(document.createElement("script"));
+  script.async = false;
+  script.src = src;
+}
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/prerendered/locales/id/activity-stream-strings.js
@@ -0,0 +1,98 @@
+// Note - this is a generated file.
+window.gActivityStreamStrings = {
+  "newtab_page_title": "Tab Baru",
+  "default_label_loading": "Memuat…",
+  "header_top_sites": "Situs Teratas",
+  "header_stories": "Cerita Utama",
+  "header_highlights": "Sorotan",
+  "header_visit_again": "Kunjungi Lagi",
+  "header_bookmarks": "Markah Terbaru",
+  "header_recommended_by": "Disarankan oleh {provider}",
+  "header_bookmarks_placeholder": "Anda belum memiliki markah.",
+  "header_stories_from": "dari",
+  "type_label_visited": "Dikunjungi",
+  "type_label_bookmarked": "Dimarkahi",
+  "type_label_synced": "Disinkronkan dari perangkat lain",
+  "type_label_recommended": "Trending",
+  "type_label_open": "Buka",
+  "type_label_topic": "Topik",
+  "type_label_now": "Sekarang",
+  "menu_action_bookmark": "Markah",
+  "menu_action_remove_bookmark": "Hapus Markah",
+  "menu_action_copy_address": "Salin Alamat",
+  "menu_action_email_link": "Emailkan Tautan…",
+  "menu_action_open_new_window": "Buka di Jendela Baru",
+  "menu_action_open_private_window": "Buka di Jendela Penjelajahan Pribadi Baru",
+  "menu_action_dismiss": "Tutup",
+  "menu_action_delete": "Hapus dari Riwayat",
+  "menu_action_pin": "Semat",
+  "menu_action_unpin": "Lepas",
+  "confirm_history_delete_p1": "Yakin ingin menghapus setiap bagian dari laman ini dari riwayat Anda?",
+  "confirm_history_delete_notice_p2": "Tindakan ini tidak bisa diurungkan.",
+  "menu_action_save_to_pocket": "Simpan ke Pocket",
+  "search_for_something_with": "Cari {search_term} lewat:",
+  "search_button": "Cari",
+  "search_header": "Pencarian {search_engine_name}",
+  "search_web_placeholder": "Cari di Web",
+  "search_settings": "Ubah Pengaturan Pencarian",
+  "section_info_option": "Info",
+  "section_info_send_feedback": "Kirim Umpan Balik",
+  "section_info_privacy_notice": "Kebijakan Privasi",
+  "section_disclaimer_topstories": "The most interesting stories on the web, selected based on what you read. From Pocket, now part of Mozilla.",
+  "section_disclaimer_topstories_linktext": "Learn how it works.",
+  "section_disclaimer_topstories_buttontext": "Okay, got it",
+  "welcome_title": "Selamat datang di tab baru",
+  "welcome_body": "Firefox akan menggunakan ruang ini untuk menampilkan markah, artikel, video, dan laman yang baru-baru ini dikunjungi, yang paling relevan agar Anda bisa kembali mengunjunginya dengan mudah.",
+  "welcome_label": "Mengidentifikasi Sorotan Anda",
+  "time_label_less_than_minute": "<1 mnt",
+  "time_label_minute": "{number} mnt",
+  "time_label_hour": "{number} jam",
+  "time_label_day": "{number} hr",
+  "settings_pane_button_label": "Ubahsuai laman Tab Baru Anda",
+  "settings_pane_header": "Preferensi Tab Baru",
+  "settings_pane_body2": "Pilih apa yang Anda lihat di halaman ini.",
+  "settings_pane_search_header": "Pencarian",
+  "settings_pane_search_body": "Cari Web dari tab baru Anda.",
+  "settings_pane_topsites_header": "Situs Teratas",
+  "settings_pane_topsites_body": "Mengakses situs web yang paling sering Anda kunjungi.",
+  "settings_pane_topsites_options_showmore": "Tampilkan dua baris",
+  "settings_pane_bookmarks_header": "Markah Terbaru",
+  "settings_pane_bookmarks_body": "Markah Anda dibuat di lokasi yang praktis.",
+  "settings_pane_visit_again_header": "Kunjungi Lagi",
+  "settings_pane_visit_again_body": "Firefox akan menunjukkan bagian dari riwayat penjelajahan yang mungkin ingin Anda ingat atau kunjungi lagi.",
+  "settings_pane_highlights_header": "Sorotan",
+  "settings_pane_highlights_body2": "Temukan jalan kembali ke hal menarik yang baru saja Anda kunjungi atau dimarkah.",
+  "settings_pane_highlights_options_bookmarks": "Markah",
+  "settings_pane_highlights_options_visited": "Situs Terkunjungi",
+  "settings_pane_snippets_header": "Catatan Kecil",
+  "settings_pane_snippets_body": "Baca info pendek terbaru dari Mozilla tentang Firefox, budaya internet dan beberapa meme acak.",
+  "settings_pane_done_button": "Selesai",
+  "settings_pane_topstories_options_sponsored": "Show Sponsored Stories",
+  "edit_topsites_button_text": "Sunting",
+  "edit_topsites_button_label": "Ubahsuai bagian Situs Teratas Anda",
+  "edit_topsites_showmore_button": "Tampilkan lainnya",
+  "edit_topsites_showless_button": "Tampilkan lebih sedikit",
+  "edit_topsites_done_button": "Selesai",
+  "edit_topsites_pin_button": "Sematkan situs ini",
+  "edit_topsites_unpin_button": "Lepaskan situs ini",
+  "edit_topsites_edit_button": "Sunting situs ini",
+  "edit_topsites_dismiss_button": "Abaikan situs ini",
+  "edit_topsites_add_button": "Tambah",
+  "topsites_form_add_header": "Situs Pilihan Baru",
+  "topsites_form_edit_header": "Ubah Situs Pilihan",
+  "topsites_form_title_placeholder": "Masukkan judul",
+  "topsites_form_url_placeholder": "Ketik atau tempel URL",
+  "topsites_form_add_button": "Tambah",
+  "topsites_form_save_button": "Simpan",
+  "topsites_form_cancel_button": "Batalkan",
+  "topsites_form_url_validation": "URL valid diperlukan",
+  "pocket_read_more": "Topik Populer:",
+  "pocket_read_even_more": "Lihat Cerita Lainnya",
+  "pocket_feedback_header": "Yang terbaik dari Web, dikurasi lebih dari 25 juta orang.",
+  "pocket_description": "Temukan konten berkualitas tinggi yang mungkin Anda lewatkan dengan bantuan Pocket, yang sekarang menjadi bagian dari Mozilla.",
+  "highlights_empty_state": "Mulai menjelajah, dan kami akan menampilkan beberapa artikel bagus, video, dan halaman lain yang baru saja Anda kunjungi atau termarkah di sini.",
+  "topstories_empty_state": "Maaf Anda tercegat. Periksa lagi nanti untuk lebih banyak cerita terbaik dari {provider}. Tidak mau menunggu? Pilih topik populer untuk menemukan lebih banyak cerita hebat dari seluruh web.",
+  "manual_migration_explanation2": "Coba Firefox dengan markah, riwayat, dan sandi dari peramban lain.",
+  "manual_migration_cancel_button": "Tidak, Terima kasih",
+  "manual_migration_import_button": "Impor Sekarang"
+};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/prerendered/locales/id/activity-stream.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html lang="id" dir="ltr">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="Content-Security-Policy-Report-Only" content="script-src 'unsafe-inline'; img-src http: https: data: blob:; style-src 'unsafe-inline'; child-src 'none'; object-src 'none'; report-uri https://tiles.services.mozilla.com/v4/links/activity-stream/csp">
+    <link rel="icon" type="image/png" id="favicon" href="chrome://branding/content/icon32.png"/>
+    <link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
+    <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
+  </head>
+  <body class="activity-stream">
+    <div id="root"></div>
+    <div id="snippets-container">
+      <div id="snippets"></div>
+    </div>
+    <script>
+// Don't directly load the following scripts as part of html to let the page
+// finish loading to render the content sooner.
+for (const src of [
+  "chrome://browser/content/contentSearchUI.js",
+  "resource://activity-stream/vendor/react.js",
+  "resource://activity-stream/vendor/react-dom.js",
+  "resource://activity-stream/vendor/react-intl.js",
+  "resource://activity-stream/vendor/redux.js",
+  "resource://activity-stream/vendor/react-redux.js",
+  "resource://activity-stream/prerendered/id/activity-stream-strings.js",
+  "resource://activity-stream/data/content/activity-stream.bundle.js"
+]) {
+  // These dynamically inserted scripts by default are async, but we need them
+  // to load in the desired order (i.e., bundle last).
+  const script = document.body.appendChild(document.createElement("script"));
+  script.async = false;
+  script.src = src;
+}
+    </script>
+  </body>
+</html>
--- a/browser/extensions/formautofill/FormAutofillHeuristics.jsm
+++ b/browser/extensions/formautofill/FormAutofillHeuristics.jsm
@@ -34,16 +34,17 @@ class FieldScanner {
    *
    * @param {Array.DOMElement} elements
    *        The elements from a form for each parser.
    */
   constructor(elements) {
     this._elementsWeakRef = Cu.getWeakReference(elements);
     this.fieldDetails = [];
     this._parsingIndex = 0;
+    this._sections = [];
   }
 
   get _elements() {
     return this._elementsWeakRef.get();
   }
 
   /**
    * This cursor means the index of the element which is waiting for parsing.
@@ -93,16 +94,41 @@ class FieldScanner {
 
     return this.fieldDetails[index];
   }
 
   get parsingFinished() {
     return this.parsingIndex >= this._elements.length;
   }
 
+  _pushToSection(name, fieldDetail) {
+    for (let section of this._sections) {
+      if (section.name == name) {
+        section.fieldDetails.push(fieldDetail);
+        return;
+      }
+    }
+    this._sections.push({
+      name,
+      fieldDetails: [fieldDetail],
+    });
+  }
+
+  getSectionFieldDetails(allowDuplicates) {
+    // TODO: [Bug 1416664] If there is only one section which is not defined by
+    // `autocomplete` attribute, the sections should be classified by the
+    // heuristics.
+    return this._sections.map(section => {
+      if (allowDuplicates) {
+        return section.fieldDetails;
+      }
+      return this._trimFieldDetails(section.fieldDetails);
+    });
+  }
+
   /**
    * This function will prepare an autocomplete info object with getInfo
    * function and push the detail to fieldDetails property. Any duplicated
    * detail will be marked as _duplicated = true for the parser.
    *
    * Any element without the related detail will be used for adding the detail
    * to the end of field details.
    */
@@ -131,16 +157,28 @@ class FieldScanner {
     // Store the association between the field metadata and the element.
     if (this.findSameField(info) != -1) {
       // A field with the same identifier already exists.
       log.debug("Not collecting a field matching another with the same info:", info);
       fieldInfo._duplicated = true;
     }
 
     this.fieldDetails.push(fieldInfo);
+    this._pushToSection(this._getSectionName(fieldInfo), fieldInfo);
+  }
+
+  _getSectionName(info) {
+    let names = [];
+    if (info.section) {
+      names.push(info.section);
+    }
+    if (info.addressType) {
+      names.push(info.addressType);
+    }
+    return names.length ? names.join(" ") : "-moz-section-default";
   }
 
   /**
    * When a field detail should be changed its fieldName after parsing, use
    * this function to update the fieldName which is at a specific index.
    *
    * @param {number} index
    *        The index indicates a field detail to be updated.
@@ -165,22 +203,28 @@ class FieldScanner {
                                        f.addressType == info.addressType &&
                                        f.contactType == info.contactType &&
                                        f.fieldName == info.fieldName);
   }
 
   /**
    * Provide the field details without invalid field name and duplicated fields.
    *
+   * @param   {Array<Object>} fieldDetails
+   *          The field details for trimming.
    * @returns {Array<Object>}
    *          The array with the field details without invalid field name and
    *          duplicated fields.
    */
-  get trimmedFieldDetail() {
-    return this.fieldDetails.filter(f => f.fieldName && !f._duplicated);
+  _trimFieldDetails(fieldDetails) {
+    return fieldDetails.filter(f => f.fieldName && !f._duplicated);
+  }
+
+  getFieldDetails(allowDuplicates) {
+    return allowDuplicates ? this.fieldDetails : this._trimFieldDetails(this.fieldDetails);
   }
 
   elementExisting(index) {
     return index < this._elements.length;
   }
 }
 
 this.LabelUtils = {
@@ -621,26 +665,20 @@ this.FormAutofillHeuristics = {
       }
     }
 
     LabelUtils.clearLabelMap();
 
     if (!this._sectionEnabled) {
       // When the section feature is disabled, `getFormInfo` should provide a
       // single section result.
-      return [allowDuplicates ? fieldScanner.fieldDetails : fieldScanner.trimmedFieldDetail];
+      return [fieldScanner.getFieldDetails(allowDuplicates)];
     }
 
-    return this._groupingFields(fieldScanner, allowDuplicates);
-  },
-
-  _groupingFields(fieldScanner, allowDuplicates) {
-    // TODO [Bug 1415077] This function should be able to handle the section
-    // part of autocomplete attr.
-    return [allowDuplicates ? fieldScanner.fieldDetails : fieldScanner.trimmedFieldDetail];
+    return fieldScanner.getSectionFieldDetails(allowDuplicates);
   },
 
   _regExpTableHashValue(...signBits) {
     return signBits.reduce((p, c, i) => p | !!c << i, 0);
   },
 
   _setRegExpListCache(regexps, b0, b1, b2) {
     if (!this._regexpList) {
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -1,25 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-this.EXPORTED_SYMBOLS = ["FormAutofillUtils"];
+this.EXPORTED_SYMBOLS = ["FormAutofillUtils", "AddressDataLoader"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
-const ADDRESS_REFERENCES = "chrome://formautofill/content/addressReferences.js";
+const ADDRESS_METADATA_PATH = "resource://formautofill/addressmetadata/";
+const ADDRESS_REFERENCES = "addressReferences.js";
+const ADDRESS_REFERENCES_EXT = "addressReferencesExt.js";
 
-// TODO: We only support US in MVP. We are going to support more countries in
-//       bug 1370193.
-const ALTERNATIVE_COUNTRY_NAMES = {
-  "US": ["US", "United States of America", "United States", "America", "U.S.", "USA", "U.S.A.", "U.S.A"],
-};
+// TODO: This list should become a pref in Bug 1413494
+const SUPPORTED_COUNTRY_LIST = ["US"];
 
 const ADDRESSES_COLLECTION_NAME = "addresses";
 const CREDITCARDS_COLLECTION_NAME = "creditCards";
 const ADDRESSES_FIRST_TIME_USE_PREF = "extensions.formautofill.firstTimeUse";
 const ENABLED_AUTOFILL_ADDRESSES_PREF = "extensions.formautofill.addresses.enabled";
 const CREDITCARDS_USED_STATUS_PREF = "extensions.formautofill.creditCards.used";
 const AUTOFILL_CREDITCARDS_AVAILABLE_PREF = "extensions.formautofill.creditCards.available";
 const ENABLED_AUTOFILL_CREDITCARDS_PREF = "extensions.formautofill.creditCards.enabled";
@@ -38,16 +37,87 @@ const FIELD_STATES = {
 
 // The maximum length of data to be saved in a single field for preventing DoS
 // attacks that fill the user's hard drive(s).
 const MAX_FIELD_VALUE_LENGTH = 200;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
+let AddressDataLoader = {
+  // Status of address data loading. We'll load all the countries with basic level 1
+  // information while requesting conutry information, and set country to true.
+  // Level 1 Set is for recording which country's level 1/level 2 data is loaded,
+  // since we only load this when getCountryAddressData called with level 1 parameter.
+  _dataLoaded: {
+    country: false,
+    level1: new Set(),
+  },
+  /**
+   * Load address data and extension script into a sandbox from different paths.
+   * @param   {string} path
+   *          The path for address data and extension script. It could be root of the address
+   *          metadata folder(addressmetadata/) or under specific country(addressmetadata/TW/).
+   * @returns {object}
+   *          A sandbox that contains address data object with properties from extension.
+   */
+  _loadScripts(path) {
+    let sandbox = {};
+    let extSandbox = {};
+
+    try {
+      sandbox = FormAutofillUtils.loadDataFromScript(path + ADDRESS_REFERENCES);
+      extSandbox = FormAutofillUtils.loadDataFromScript(path + ADDRESS_REFERENCES_EXT);
+    } catch (e) {
+      // Will return only address references if extension loading failed or empty sandbox if
+      // address references loading failed.
+      return sandbox;
+    }
+
+    if (extSandbox.addressDataExt) {
+      for (let key in extSandbox.addressDataExt) {
+        Object.assign(sandbox.addressData[key], extSandbox.addressDataExt[key]);
+      }
+    }
+    return sandbox;
+  },
+  /**
+   * We'll cache addressData in the loader once the data loaded from scripts.
+   * It'll become the example below after loading addressReferences with extension:
+   * addressData: {
+                   "data/US": {"lang": "en", ...// Data defined in libaddressinput metadata
+   *                           "alternative_names": ... // Data defined in extension }
+   *               "data/CA": {} // Other supported country metadata
+   *               "data/TW": {} // Other supported country metadata
+   *               "data/TW/台北市": {} // Other supported country level 1 metadata
+   *              }
+   * @param   {string} country
+   * @param   {string} level1
+   * @returns {object}
+   */
+  getData(country, level1 = null) {
+    // Load the addressData if needed
+    if (!this._dataLoaded.country) {
+      this._addressData = this._loadScripts(ADDRESS_METADATA_PATH).addressData;
+      this._dataLoaded.country = true;
+    }
+    if (!level1) {
+      return this._addressData[`data/${country}`];
+    }
+    // If level1 is set, load addressReferences under country folder with specific
+    // country/level 1 for level 2 information.
+    if (!this._dataLoaded.level1.has(country)) {
+      Object.assign(this._addressData,
+                    this._loadScripts(`${ADDRESS_METADATA_PATH}${country}/`).addressData);
+      this._dataLoaded.level1.add(country);
+    }
+    return this._addressData[`data/${country}/${level1}`];
+  },
+};
+
 this.FormAutofillUtils = {
   get AUTOFILL_FIELDS_THRESHOLD() { return 3; },
   get isAutofillEnabled() { return this.isAutofillAddressesEnabled || this.isAutofillCreditCardsEnabled; },
   get isAutofillCreditCardsEnabled() { return this.isAutofillCreditCardsAvailable && this._isAutofillCreditCardsEnabled; },
 
   ADDRESSES_COLLECTION_NAME,
   CREDITCARDS_COLLECTION_NAME,
   ENABLED_AUTOFILL_ADDRESSES_PREF,
@@ -89,17 +159,17 @@ this.FormAutofillUtils = {
     "cc-given-name": "creditCard",
     "cc-additional-name": "creditCard",
     "cc-family-name": "creditCard",
     "cc-number": "creditCard",
     "cc-exp-month": "creditCard",
     "cc-exp-year": "creditCard",
     "cc-exp": "creditCard",
   },
-  _addressDataLoaded: false,
+
   _collators: {},
   _reAlternativeCountryNames: {},
 
   isAddressField(fieldName) {
     return !!this._fieldNameInfo[fieldName] && !this.isCreditCardField(fieldName);
   },
 
   isCreditCardField(fieldName) {
@@ -216,28 +286,25 @@ this.FormAutofillUtils = {
 
   loadDataFromScript(url, sandbox = {}) {
     let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
                          .getService(Ci.mozIJSSubScriptLoader);
     scriptLoader.loadSubScript(url, sandbox, "utf-8");
     return sandbox;
   },
 
-  /**
-   * Get country address data. Fallback to US if not found.
-   * @param   {string} country
-   * @returns {object}
-   */
-  getCountryAddressData(country) {
-    // Load the addressData if needed
-    if (!this._addressDataLoaded) {
-      Object.assign(this, this.loadDataFromScript(ADDRESS_REFERENCES));
-      this._addressDataLoaded = true;
+  // Get country address data and fallback to US if not found.
+  // See AddressDataLoader.getData for more details of addressData structure.
+  getCountryAddressData(country, level1 = null) {
+    let metadata = AddressDataLoader.getData(country, level1);
+    if (!metadata) {
+      metadata = level1 ? null : AddressDataLoader.getData("US");
     }
-    return this.addressData[`data/${country}`] || this.addressData["data/US"];
+
+    return metadata;
   },
 
   /**
    * Get the collators based on the specified country.
    * @param   {string} country The specified country.
    * @returns {array} An array containing several collator objects.
    */
   getCollators(country) {
@@ -306,22 +373,23 @@ this.FormAutofillUtils = {
    * Use alternative country name list to identify a country code from a
    * specified country name.
    * @param   {string} countryName A country name to be identified
    * @param   {string} [countrySpecified] A country code indicating that we only
    *                                      search its alternative names if specified.
    * @returns {string} The matching country code.
    */
   identifyCountryCode(countryName, countrySpecified) {
-    let countries = countrySpecified ? [countrySpecified] : Object.keys(ALTERNATIVE_COUNTRY_NAMES);
+    let countries = countrySpecified ? [countrySpecified] : SUPPORTED_COUNTRY_LIST;
 
     for (let country of countries) {
       let collators = this.getCollators(country);
 
-      let alternativeCountryNames = ALTERNATIVE_COUNTRY_NAMES[country];
+      let metadata = this.getCountryAddressData(country);
+      let alternativeCountryNames = metadata.alternative_names || [metadata.name];
       let reAlternativeCountryNames = this._reAlternativeCountryNames[country];
       if (!reAlternativeCountryNames) {
         reAlternativeCountryNames = this._reAlternativeCountryNames[country] = [];
       }
 
       for (let i = 0; i < alternativeCountryNames.length; i++) {
         let name = alternativeCountryNames[i];
         let reName = reAlternativeCountryNames[i];
@@ -440,17 +508,17 @@ this.FormAutofillUtils = {
           let optionText = this.identifyValue(keys, names, option.text, collators);
           if (identifiedValue === optionValue || identifiedValue === optionText || pattern.test(option.value)) {
             return option;
           }
         }
         break;
       }
       case "country": {
-        if (ALTERNATIVE_COUNTRY_NAMES[value]) {
+        if (this.getCountryAddressData(value).alternative_names) {
           for (let option of selectEl.options) {
             if (this.identifyCountryCode(option.text, value) || this.identifyCountryCode(option.value, value)) {
               return option;
             }
           }
         }
         break;
       }
rename from browser/extensions/formautofill/content/addressReferences.js
rename to browser/extensions/formautofill/addressmetadata/addressReferences.js
--- a/browser/extensions/formautofill/content/addressReferences.js
+++ b/browser/extensions/formautofill/addressmetadata/addressReferences.js
@@ -5,11 +5,16 @@
 /* exported addressData */
 /* eslint max-len: 0 */
 
 "use strict";
 
 // The data below is initially copied from
 // https://chromium-i18n.appspot.com/ssl-aggregate-address
 
+// WARNING: DO NOT change any value or add additional properties in addressData.
+// We only accept the metadata of the supported countries that is copied from libaddressinput directly.
+// Please edit addressReferencesExt.js instead if you want to add new property as complement
+// or overwrite the existing properties.
+
 var addressData = {
   "data/US": {"lang": "en", "upper": "CS", "sub_zipexs": "35000,36999~99500,99999~96799~85000,86999~71600,72999~34000,34099~09000,09999~96200,96699~90000,96199~80000,81999~06000,06999~19700,19999~20000,56999~32000,34999~30000,39901~96910,96932~96700,96899~83200,83999~60000,62999~46000,47999~50000,52999~66000,67999~40000,42799~70000,71599~03900,04999~96960,96979~20600,21999~01000,05544~48000,49999~96941,96944~55000,56799~38600,39799~63000,65999~59000,59999~68000,69999~88900,89999~03000,03899~07000,08999~87000,88499~10000,00544~27000,28999~58000,58999~96950,96952~43000,45999~73000,74999~97000,97999~96940~15000,19699~00600,00999~02800,02999~29000,29999~57000,57999~37000,38599~75000,73344~84000,84999~05000,05999~00800,00899~20100,24699~98000,99499~24700,26999~53000,54999~82000,83414", "zipex": "95014,22162-1010", "name": "UNITED STATES", "zip": "(\\d{5})(?:[ \\-](\\d{4}))?", "zip_name_type": "zip", "fmt": "%N%n%O%n%A%n%C, %S %Z", "state_name_type": "state", "id": "data/US", "languages": "en", "sub_keys": "AL~AK~AS~AZ~AR~AA~AE~AP~CA~CO~CT~DE~DC~FL~GA~GU~HI~ID~IL~IN~IA~KS~KY~LA~ME~MH~MD~MA~MI~FM~MN~MS~MO~MT~NE~NV~NH~NJ~NM~NY~NC~ND~MP~OH~OK~OR~PW~PA~PR~RI~SC~SD~TN~TX~UT~VT~VI~VA~WA~WV~WI~WY", "key": "US", "posturl": "https://tools.usps.com/go/ZipLookupAction!input.action", "require": "ACSZ", "sub_names": "Alabama~Alaska~American Samoa~Arizona~Arkansas~Armed Forces (AA)~Armed Forces (AE)~Armed Forces (AP)~California~Colorado~Connecticut~Delaware~District of Columbia~Florida~Georgia~Guam~Hawaii~Idaho~Illinois~Indiana~Iowa~Kansas~Kentucky~Louisiana~Maine~Marshall Islands~Maryland~Massachusetts~Michigan~Micronesia~Minnesota~Mississippi~Missouri~Montana~Nebraska~Nevada~New Hampshire~New Jersey~New Mexico~New York~North Carolina~North Dakota~Northern Mariana Islands~Ohio~Oklahoma~Oregon~Palau~Pennsylvania~Puerto Rico~Rhode Island~South Carolina~South Dakota~Tennessee~Texas~Utah~Vermont~Virgin Islands~Virginia~Washington~West Virginia~Wisconsin~Wyoming", "sub_zips": "3[56]~99[5-9]~96799~8[56]~71[6-9]|72~340~09~96[2-6]~9[0-5]|96[01]~8[01]~06~19[7-9]~20[02-5]|569~3[23]|34[1-9]~3[01]|398|39901~969([1-2]\\d|3[12])~967[0-8]|9679[0-8]|968~83[2-9]~6[0-2]~4[67]~5[0-2]~6[67]~4[01]|42[0-7]~70|71[0-5]~039|04~969[67]~20[6-9]|21~01|02[0-7]|05501|05544~4[89]~9694[1-4]~55|56[0-7]~38[6-9]|39[0-7]~6[3-5]~59~6[89]~889|89~03[0-8]~0[78]~87|88[0-4]~1[0-4]|06390|00501|00544~2[78]~58~9695[0-2]~4[3-5]~7[34]~97~969(39|40)~1[5-8]|19[0-6]~00[679]~02[89]~29~57~37|38[0-5]~7[5-9]|885|73301|73344~84~05~008~201|2[23]|24[0-6]~98|99[0-4]~24[7-9]|2[56]~5[34]~82|83[01]|83414"},
 };
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/addressmetadata/addressReferencesExt.js
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* exported addressDataExt */
+/* eslint max-len: 0 */
+
+"use strict";
+
+// "addressDataExt" uses the same key as "addressData" in "addressReferences.js" and contains
+//  contains the information we need but absent in "libaddressinput" such as alternative names.
+
+// TODO: We only support the alternative name of US in MVP. We are going to support more countries in
+//       bug 1370193.
+var addressDataExt = {
+  "data/US": {"alternative_names": ["US", "United States of America", "United States", "America", "U.S.", "USA", "U.S.A.", "U.S.A"]},
+};
--- a/browser/extensions/formautofill/content/formautofill.xml
+++ b/browser/extensions/formautofill/content/formautofill.xml
@@ -241,23 +241,20 @@
       <method name="_adjustAcItem">
         <body>
         <![CDATA[
           /* global Cu */
           this._adjustAutofillItemLayout();
           this.setAttribute("formautofillattached", "true");
 
           let {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm", {});
+          // TODO: The "Short" suffix is pointless now as normal version string is no longer needed,
+          // we should consider removing the suffix if possible when the next time locale change.
           let buttonTextBundleKey = AppConstants.platform == "macosx" ?
-            "autocompleteFooterOptionOSX" : "autocompleteFooterOption";
-          // If the popup shows up with small layout, we should use short string to
-          // have a better fit in the box.
-          if (this._itemBox.getAttribute("size") == "small") {
-            buttonTextBundleKey += "Short";
-          }
+            "autocompleteFooterOptionOSXShort" : "autocompleteFooterOptionShort";
           let buttonText = this._stringBundle.GetStringFromName(buttonTextBundleKey);
           this._optionButton.textContent = buttonText;
 
           let value = JSON.parse(this.getAttribute("ac-value"));
 
           this._allFieldCategories = value.categories;
           this._focusedCategory = value.focusedCategory;
           this.showWarningText = this._allFieldCategories && this._focusedCategory;
--- a/browser/extensions/formautofill/jar.mn
+++ b/browser/extensions/formautofill/jar.mn
@@ -1,16 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 [features/formautofill@mozilla.org] chrome.jar:
 % resource formautofill %res/
   res/ (*.jsm)
   res/phonenumberutils/ (phonenumberutils/*.jsm)
+  res/addressmetadata/ (addressmetadata/*)
 
 % content formautofill %content/
   content/ (content/*)
 
 % skin formautofill classic/1.0 %skin/linux/ os=LikeUnix
 % skin formautofill classic/1.0 %skin/osx/ os=Darwin
 % skin formautofill classic/1.0 %skin/windows/ os=WINNT
 % skin formautofill-shared classic/1.0 %skin/shared/
--- a/browser/extensions/formautofill/locales/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locales/en-US/formautofill.properties
@@ -46,23 +46,18 @@ neverSaveCreditCardAccessKey = N
 updateCreditCardMessage = Would you like to update your credit card with this new information?
 createCreditCardLabel = Create New Credit Card
 createCreditCardAccessKey = C
 updateCreditCardLabel = Update Credit Card
 updateCreditCardAccessKey = U
 # LOCALIZATION NOTE (openAutofillMessagePanel): Tooltip label for Form Autofill doorhanger icon on address bar.
 openAutofillMessagePanel = Open Form Autofill message panel
 
-# LOCALIZATION NOTE (autocompleteFooterOption, autocompleteFooterOptionOSX): Used as a label for the button,
-# displayed at the bottom of the drop down suggestion, to open Form Autofill browser preferences.
-autocompleteFooterOption = Form Autofill Options
-autocompleteFooterOptionOSX = Form Autofill Preferences
-# LOCALIZATION NOTE (autocompleteFooterOptionShort, autocompleteFooterOptionOSXShort): Used as a label for the button,
-# displayed at the bottom of the drop down suggestion, to open Form Autofill browser preferences. This version is used
-# instead of autocompleteFooterOption* when the menu width is below 185px.
+# LOCALIZATION NOTE ( (autocompleteFooterOptionShort, autocompleteFooterOptionOSXShort): Used as a label for the button,
+# displayed at the bottom of the dropdown suggestion, to open Form Autofill browser preferences.
 autocompleteFooterOptionShort = More Options
 autocompleteFooterOptionOSXShort = Preferences
 # LOCALIZATION NOTE (category.address, category.name, category.organization2, category.tel, category.email):
 # Used in autofill drop down suggestion to indicate what other categories Form Autofill will attempt to fill.
 category.address = address
 category.name = name
 category.organization2 = organization
 category.tel = phone
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/fixtures/multiple_section.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Form Autofill Demo Page</title>
+</head>
+<body>
+  <h1>Form Autofill Demo Page</h1>
+  <form>
+    <label>Name: <input id="name" autocomplete="name"></label><br/>
+    <label>Organization: <input id="organization" autocomplete="organization"></label><br/>
+
+    <br/>
+    <label>Street Address: <input id="street-address" autocomplete="shipping street-address"></label><br/>
+    <label>Address Level 2: <input id="address-level2" autocomplete="shipping address-level2"></label><br/>
+    <label>Address Level 1: <input id="address-level1" autocomplete="shipping address-level1"></label><br/>
+    <label>Postal Code: <input id="postal-code" autocomplete="shipping postal-code"></label><br/>
+    <label>Country: <input id="country" autocomplete="shipping country"></label><br/>
+
+    <br/>
+    <label>Street Address: <input id="street-address" autocomplete="billing street-address"></label><br/>
+    <label>Address Level 2: <input id="address-level2" autocomplete="billing address-level2"></label><br/>
+    <label>Address Level 1: <input id="address-level1" autocomplete="billing address-level1"></label><br/>
+    <label>Postal Code: <input id="postal-code" autocomplete="billing postal-code"></label><br/>
+    <label>Country: <input id="country" autocomplete="billing country"></label><br/>
+
+    <br/>
+    <label>Street Address: <input id="street-address" autocomplete="section-my street-address"></label><br/>
+    <label>Address Level 2: <input id="address-level2" autocomplete="section-my address-level2"></label><br/>
+    <label>Address Level 1: <input id="address-level1" autocomplete="section-my address-level1"></label><br/>
+    <label>Postal Code: <input id="postal-code" autocomplete="section-my postal-code"></label><br/>
+    <label>Country: <input id="country" autocomplete="section-my country"></label><br/>
+
+    <br/>
+    <label>Telephone: <input id="tel" autocomplete="work tel"></label><br/>
+    <label>Email: <input id="email" autocomplete="work email"></label><br/>
+    <br/>
+    <label>Telephone: <input id="tel" autocomplete="home tel"></label><br/>
+    <label>Email: <input id="email" autocomplete="home email"></label><br/>
+    <p>
+      <input type="submit" value="Submit">
+      <button type="reset">Reset</button>
+    </p>
+  </form>
+
+</body>
+</html>
+
--- a/browser/extensions/formautofill/test/unit/head.js
+++ b/browser/extensions/formautofill/test/unit/head.js
@@ -73,16 +73,33 @@ async function initProfileStorage(fileNa
   for (let record of records) {
     do_check_true(profileStorage[collectionName].add(record));
     await onChanged;
   }
   await profileStorage._saveImmediately();
   return profileStorage;
 }
 
+function verifySectionFieldDetails(sections, expectedResults) {
+  Assert.equal(sections.length, expectedResults.length, "Expected section count.");
+  sections.forEach((sectionInfo, sectionIndex) => {
+    let expectedSectionInfo = expectedResults[sectionIndex];
+    do_print("FieldName Prediction Results: " + sectionInfo.map(i => i.fieldName));
+    do_print("FieldName Expected Results:   " + expectedSectionInfo.map(i => i.fieldName));
+    Assert.equal(sectionInfo.length, expectedSectionInfo.length, "Expected field count.");
+
+    sectionInfo.forEach((field, fieldIndex) => {
+      let expectedField = expectedSectionInfo[fieldIndex];
+      delete field._reason;
+      delete field.elementWeakRef;
+      Assert.deepEqual(field, expectedField);
+    });
+  });
+}
+
 function runHeuristicsTest(patterns, fixturePathPrefix) {
   Cu.import("resource://formautofill/FormAutofillHeuristics.jsm");
   Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
   patterns.forEach(testPattern => {
     add_task(async function() {
       do_print("Starting test fixture: " + testPattern.fixturePath);
       let file = do_get_file(fixturePathPrefix + testPattern.fixturePath);
@@ -96,31 +113,17 @@ function runHeuristicsTest(patterns, fix
           forms.push(formLike);
         }
       }
 
       Assert.equal(forms.length, testPattern.expectedResult.length, "Expected form count.");
 
       forms.forEach((form, formIndex) => {
         let sections = FormAutofillHeuristics.getFormInfo(form);
-        if (testPattern.expectedResult[formIndex].length == 0) {
-          return;
-        }
-        // TODO [Bug 1415077] the test should be able to support traversing all
-        // sections.
-        let formInfo = sections[0];
-        do_print("FieldName Prediction Results: " + formInfo.map(i => i.fieldName));
-        do_print("FieldName Expected Results:   " + testPattern.expectedResult[formIndex].map(i => i.fieldName));
-        Assert.equal(formInfo.length, testPattern.expectedResult[formIndex].length, "Expected field count.");
-        formInfo.forEach((field, fieldIndex) => {
-          let expectedField = testPattern.expectedResult[formIndex][fieldIndex];
-          delete field._reason;
-          expectedField.elementWeakRef = field.elementWeakRef;
-          Assert.deepEqual(field, expectedField);
-        });
+        verifySectionFieldDetails(sections, testPattern.expectedResult[formIndex]);
       });
     });
   });
 }
 
 /**
  * Returns the Sync change counter for a profile storage record. Synced records
  * store additional metadata for tracking changes and resolving merge conflicts.
@@ -168,17 +171,17 @@ function objectMatches(object, fields) {
   }
   return ObjectUtils.deepEqual(actual, fields);
 }
 
 add_task(async function head_initialize() {
   Services.prefs.setStringPref("extensions.formautofill.available", "on");
   Services.prefs.setBoolPref("extensions.formautofill.creditCards.available", true);
   Services.prefs.setBoolPref("extensions.formautofill.heuristics.enabled", true);
-  Services.prefs.setBoolPref("extensions.formautofill.section.enabled", false);
+  Services.prefs.setBoolPref("extensions.formautofill.section.enabled", true);
   Services.prefs.setBoolPref("dom.forms.autocomplete.formautofill", true);
 
   // Clean up after every test.
   do_register_cleanup(function head_cleanup() {
     Services.prefs.clearUserPref("extensions.formautofill.available");
     Services.prefs.clearUserPref("extensions.formautofill.creditCards.available");
     Services.prefs.clearUserPref("extensions.formautofill.heuristics.enabled");
     Services.prefs.clearUserPref("extensions.formautofill.section.enabled");
--- a/browser/extensions/formautofill/test/unit/heuristics/test_basic.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/test_basic.js
@@ -1,38 +1,38 @@
 /* global runHeuristicsTest */
 
 "use strict";
 
 runHeuristicsTest([
   {
     fixturePath: "autocomplete_basic.html",
     expectedResult: [
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line3"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
-      ],
+      ]],
     ],
   },
 ], "../../fixtures/");
 
--- a/browser/extensions/formautofill/test/unit/heuristics/test_cc_exp.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/test_cc_exp.js
@@ -1,36 +1,36 @@
 /* global runHeuristicsTest */
 
 "use strict";
 
 runHeuristicsTest([
   {
     fixturePath: "heuristics_cc_exp.html",
     expectedResult: [
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
-      ],
+      ]],
     ],
   },
 ], "../../fixtures/");
 
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/heuristics/test_multiple_section.js
@@ -0,0 +1,43 @@
+/* global runHeuristicsTest */
+
+"use strict";
+
+runHeuristicsTest([
+  {
+    fixturePath: "multiple_section.html",
+    expectedResult: [
+      [
+        [
+          {"section": "", "addressType": "", "contactType": "", "fieldName": "name"},
+          {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
+          {"section": "", "addressType": "", "contactType": "work", "fieldName": "tel"},
+          {"section": "", "addressType": "", "contactType": "work", "fieldName": "email"},
+          {"section": "", "addressType": "", "contactType": "home", "fieldName": "tel"},
+          {"section": "", "addressType": "", "contactType": "home", "fieldName": "email"},
+        ],
+        [
+          {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
+          {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2"},
+          {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level1"},
+          {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "postal-code"},
+          {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country"},
+        ],
+        [
+          {"section": "", "addressType": "billing", "contactType": "", "fieldName": "street-address"},
+          {"section": "", "addressType": "billing", "contactType": "", "fieldName": "address-level2"},
+          {"section": "", "addressType": "billing", "contactType": "", "fieldName": "address-level1"},
+          {"section": "", "addressType": "billing", "contactType": "", "fieldName": "postal-code"},
+          {"section": "", "addressType": "billing", "contactType": "", "fieldName": "country"},
+        ],
+        [
+          {"section": "section-my", "addressType": "", "contactType": "", "fieldName": "street-address"},
+          {"section": "section-my", "addressType": "", "contactType": "", "fieldName": "address-level2"},
+          {"section": "section-my", "addressType": "", "contactType": "", "fieldName": "address-level1"},
+          {"section": "section-my", "addressType": "", "contactType": "", "fieldName": "postal-code"},
+          {"section": "section-my", "addressType": "", "contactType": "", "fieldName": "country"},
+        ],
+      ],
+    ],
+  },
+], "../../fixtures/");
+
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_BestBuy.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_BestBuy.js
@@ -1,56 +1,56 @@
 /* global runHeuristicsTest */
 
 "use strict";
 
 runHeuristicsTest([
   {
     fixturePath: "Checkout_ShippingAddress.html",
     expectedResult: [
-      [], // Search form
-      [
+      [[]], // Search form
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
-      ],
-      [ // Sign up
+      ]],
+      [[ // Sign up
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
-      [ // unknown
+      ]],
+      [[ // unknown
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
-      ],
+      ]],
     ],
   }, {
     fixturePath: "Checkout_Payment.html",
     expectedResult: [
-      [], // Search form
-      [ // Sign up
+      [[]], // Search form
+      [[ // Sign up
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
-      ],
-      [
+      ]],
+      [[
         // unknown
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
-      ],
+      ]],
     ],
   }, {
     fixturePath: "SignIn.html",
     expectedResult: [
-      [ // Sign in
+      [[ // Sign in
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
     ],
   },
 ], "../../../fixtures/third_party/BestBuy/");
 
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_CDW.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_CDW.js
@@ -1,40 +1,40 @@
 /* global runHeuristicsTest */
 
 "use strict";
 
 runHeuristicsTest([
   {
     fixturePath: "Checkout_ShippingInfo.html",
     expectedResult: [
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
 
         // FIXME: bug 1392932 - misdetect ZIP ext string
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         // The below "tel-extension" is correct and removed due to the
         // duplicated field above.
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
-      ],
+      ]],
       [],
     ],
   }, {
     fixturePath: "Checkout_BillingPaymentInfo.html",
     expectedResult: [
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
@@ -42,21 +42,21 @@ runHeuristicsTest([
         // FIXME: bug 1392932 - misdetect ZIP ext string
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
 
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
-      ],
+      ]],
       [],
     ],
   }, {
     fixturePath: "Checkout_Logon.html",
     expectedResult: [
       [],
-      [],
+      [[]],
       [],
     ],
   },
 ], "../../../fixtures/third_party/CDW/");
 
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_CostCo.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_CostCo.js
@@ -3,123 +3,123 @@
 "use strict";
 
 runHeuristicsTest([
   {
     fixturePath: "ShippingAddress.html",
     expectedResult: [
       [],
       [],
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "additional-name"}, // middle-name initial
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "additional-name"}, // middle-name initial
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
       [],
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
       [],
     ],
   }, {
     fixturePath: "Payment.html",
     expectedResult: [
-      [
+      [[
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
 
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"}, // ac-off
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
-      ],
+      ]],
       [],
-      [],
-      [
+      [[]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "additional-name"}, // middle-name initial
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "additional-name"}, // middle-name initial
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
       [],
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
       [],
     ],
   }, {
     fixturePath: "SignIn.html",
     expectedResult: [
-      [],
+      [[]],
       [],
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
       [],
-      [ // Forgot password
+      [[ // Forgot password
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "password"},
-      ],
-      [ // Sign up
+      ]],
+      [[ // Sign up
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
       [],
     ],
   },
 ], "../../../fixtures/third_party/CostCo/");
 
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_HomeDepot.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_HomeDepot.js
@@ -2,42 +2,45 @@
 
 "use strict";
 
 runHeuristicsTest([
   {
     fixturePath: "Checkout_ShippingPayment.html",
     expectedResult: [
       [
-        {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
-        {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
-        {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
-        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
-        {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
-        {"section": "", "addressType": "billing", "contactType": "", "fieldName": "street-address"}, // <select>
+        [
+          {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
+          {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
+          {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
+          {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+          {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
+          {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
 
-        // FIXME: bug 1392944 - the uncommented cc-exp-month and cc-exp-year are
-        // both invisible <input> elements, and the following two <select>
-        // elements are the correct ones. BTW, they are both applied
-        // autocomplete attr.
-        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
-        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
-        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
+          // FIXME: bug 1392944 - the uncommented cc-exp-month and cc-exp-year are
+          // both invisible <input> elements, and the following two <select>
+          // elements are the correct ones. BTW, they are both applied
+          // autocomplete attr.
+          {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
+          {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
+          {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
+//        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
+//        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
 
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
+//        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
+        ], [
+          {"section": "", "addressType": "billing", "contactType": "", "fieldName": "street-address"}, // <select>
+        ],
       ],
     ],
   }, {
     fixturePath: "SignIn.html",
     expectedResult: [
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
     ],
   },
 ], "../../../fixtures/third_party/HomeDepot/");
 
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Macys.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Macys.js
@@ -1,65 +1,65 @@
 /* global runHeuristicsTest */
 
 "use strict";
 
 runHeuristicsTest([
   {
     fixturePath: "Checkout_ShippingAddress.html",
     expectedResult: [
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
-      ],
+      ]],
       [
 /*
 */
       ],
     ],
   }, {
     fixturePath: "Checkout_Payment.html",
     expectedResult: [
-      [
+      [[
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"}, // ac-off
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"}, // ac-off
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
       [],
     ],
   }, {
     fixturePath: "SignIn.html",
     expectedResult: [
-      [ // Sign in
+      [[ // Sign in
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "password"},
-      ],
-      [ // Forgot password
+      ]],
+      [[ // Forgot password
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
-      [],
+      ]],
+      [[]],
       [],
       [],
     ],
   },
 ], "../../../fixtures/third_party/Macys/");
 
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_NewEgg.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_NewEgg.js
@@ -1,65 +1,65 @@
 /* global runHeuristicsTest */
 
 "use strict";
 
 runHeuristicsTest([
   {
     fixturePath: "ShippingInfo.html",
     expectedResult: [
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
       [],
     ],
   }, {
     fixturePath: "BillingInfo.html",
     expectedResult: [
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
-      ],
+      ]],
       [],
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
-      ],
+      ]],
     ],
   }, {
     fixturePath: "Login.html",
     expectedResult: [
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
       [],
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
     ],
   },
 ], "../../../fixtures/third_party/NewEgg/");
 
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_OfficeDepot.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_OfficeDepot.js
@@ -1,39 +1,39 @@
 /* global runHeuristicsTest */
 
 "use strict";
 
 runHeuristicsTest([
   {
     fixturePath: "ShippingAddress.html",
     expectedResult: [
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
 
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
 
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
       [],
     ],
   }, {
     fixturePath: "Payment.html",
     expectedResult: [
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
@@ -45,20 +45,20 @@ runHeuristicsTest([
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
 
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
 
         // FIXME: bug 1392950 - the membership number should not be detected
         // as cc-number.
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
-      ],
+      ]],
     ],
   }, {
     fixturePath: "SignIn.html",
     expectedResult: [
-      [ // ac-off
+      [[ // ac-off
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
     ],
   },
 ], "../../../fixtures/third_party/OfficeDepot/");
 
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_QVC.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_QVC.js
@@ -1,59 +1,57 @@
 /* global runHeuristicsTest */
 
 "use strict";
 
 runHeuristicsTest([
   {
     fixturePath: "YourInformation.html",
     expectedResult: [
-      [
+      [[
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-month"}, // select
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-day"}, // select
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-year"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"},
 
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
 
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
     ],
   }, {
     fixturePath: "PaymentMethod.html",
     expectedResult: [
-      [
+      [[
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-month"}, // select
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-day"}, // select
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-year"}, // select
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"}, // select
 
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"}, // ac-off
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
 
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
     ],
   }, {
     fixturePath: "SignIn.html",
     expectedResult: [
-      [
-        // Unknown
-      ],
-      [ // Sign in
+      [[]],
+      [[ // Sign in
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
       [],
     ],
   },
 ], "../../../fixtures/third_party/QVC/");
 
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Sears.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Sears.js
@@ -2,95 +2,95 @@
 
 "use strict";
 
 runHeuristicsTest([
   {
     fixturePath: "ShippingAddress.html",
     expectedResult: [
       [],
-      [], // search form, ac-off
-      [ // ac-off
+      [[]], // search form, ac-off
+      [[ // ac-off
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
-      [ // check-out, ac-off
+      ]],
+      [[ // check-out, ac-off
 /*
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
 */
-      ],
+      ]],
       [ // ac-off
 /*
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "new-password"},
 */
       ],
-      [ // ac-off
+      [[ // ac-off
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
-      [ // ac-off
+      ]],
+      [[ // ac-off
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
     ],
   }, {
     fixturePath: "PaymentOptions.html",
     expectedResult: [
       [],
-      [], // search
-      [ // credit card
+      [[]], // search
+      [[ // credit card
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
         // FIXME: bug 1392958 - Cardholder name field should be detected as cc-name
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
-      ],
-      [ // Another billing address
+      ]],
+      [[ // Another billing address
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
-      ],
-      [ // check out
+      ]],
+      [[ // check out
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
 
         // FIXME: bug 1392950 - the bank routing number should not be detected
         // as cc-number.
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
 
         // FIXME: bug 1392934 - this should be detected as address-level1 since
         // it's for Driver's license or state identification.
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"},
 
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-month"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-day"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-year"},
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      ],
+      ]],
     ],
   },
 ], "../../../fixtures/third_party/Sears/");
 
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Staples.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Staples.js
@@ -1,52 +1,52 @@
 /* global runHeuristicsTest */
 
 "use strict";
 
 runHeuristicsTest([
   {
     fixturePath: "Basic.html",
     expectedResult: [
-      [ // ac-off
+      [[ // ac-off
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
-      ],
+      ]],
     ],
   }, {
     fixturePath: "Basic_ac_on.html",
     expectedResult: [
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "organization"},
-      ],
+      ]],
     ],
   }, {
     fixturePath: "PaymentBilling.html",
     expectedResult: [
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
-      ],
+      ]],
     ],
   }, {
     fixturePath: "PaymentBilling_ac_on.html",
     expectedResult: [
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
-      ],
+      ]],
     ],
   },
 ], "../../../fixtures/third_party/Staples/");
 
--- a/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Walmart.js
+++ b/browser/extensions/formautofill/test/unit/heuristics/third_party/test_Walmart.js
@@ -1,66 +1,67 @@
 /* global runHeuristicsTest */
 
 "use strict";
 
 runHeuristicsTest([
   {
     fixturePath: "Checkout.html",
     expectedResult: [
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
-      ],
-      [],
-      [
+      ]],
+      [[]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "password"}, // ac-off
-      ],
-      [
+      ]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "email"}, // ac-off
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "password"},
 //      {"section": "", "addressType": "", "contactType": "", "fieldName": "password"}, // ac-off
-      ],
+      ]],
     ],
   }, {
     fixturePath: "Payment.html",
     expectedResult: [
-      [
-      ],
+      [[]],
       [
-        {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "given-name"},
-        {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "family-name"},
-        {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "cc-number"},
-        {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
-        {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
-        // FIXME bug 1392932 - the following field shouldn't be recognized as
-        // "tel-extension".
-        // The wrong prediction is caused by the name attr "brwsrAutofillText"
-        // which matches the regexp "ext\\b".
-        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
-//      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
-        {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "tel"},
+        [
+          {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "given-name"},
+          {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "family-name"},
+          {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "cc-number"},
+          {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
+          {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
+//        {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "cc-csc"},
+          {"section": "section-payment", "addressType": "", "contactType": "", "fieldName": "tel"},
+        ], [
+          // FIXME bug 1392932 - the following field shouldn't be recognized as
+          // "tel-extension".
+          // The wrong prediction is caused by the name attr "brwsrAutofillText"
+          // which matches the regexp "ext\\b".
+          {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
+        ],
       ],
     ],
   }, {
     fixturePath: "Shipping.html",
     expectedResult: [
-      [
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
-      ],
-      [
-      ],
-      [
+      ]],
+      [[]],
+      [[
         {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line2"},
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"}, // city
         {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level1"}, // state
         {"section": "", "addressType": "", "contactType": "", "fieldName": "postal-code"},
-      ],
+      ]],
     ],
   },
 ], "../../../fixtures/third_party/Walmart/");
 
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_addressDataLoader.js
@@ -0,0 +1,49 @@
+"use strict";
+
+Cu.import("resource://formautofill/FormAutofillUtils.jsm");
+
+add_task(async function test_initalState() {
+  // addressData should not exist
+  Assert.equal(AddressDataLoader._addressData, undefined);
+  // Verify _dataLoaded state
+  Assert.equal(AddressDataLoader._dataLoaded.country, false);
+  Assert.equal(AddressDataLoader._dataLoaded.level1.size, 0);
+});
+
+add_task(async function test_loadDataState() {
+  sinon.spy(AddressDataLoader, "_loadScripts");
+  let metadata = FormAutofillUtils.getCountryAddressData("US");
+  Assert.ok(AddressDataLoader._addressData, "addressData exists");
+  // Verify _dataLoaded state
+  Assert.equal(AddressDataLoader._dataLoaded.country, true);
+  Assert.equal(AddressDataLoader._dataLoaded.level1.size, 0);
+  // _loadScripts should be called
+  sinon.assert.called(AddressDataLoader._loadScripts);
+  // Verify metadata
+  Assert.equal(metadata.id, "data/US");
+  Assert.ok(metadata.alternative_names,
+            "US alternative names should be loaded from extension");
+  AddressDataLoader._loadScripts.reset();
+
+  // Load data without country
+  let newMetadata = FormAutofillUtils.getCountryAddressData();
+  // _loadScripts should not be called
+  sinon.assert.notCalled(AddressDataLoader._loadScripts);
+  Assert.deepEqual(metadata, newMetadata, "metadata should be US if country is not specified");
+  AddressDataLoader._loadScripts.reset();
+
+  // Load level 1 data that does not exist
+  let undefinedMetadata = FormAutofillUtils.getCountryAddressData("US", "CA");
+  // _loadScripts should be called
+  sinon.assert.called(AddressDataLoader._loadScripts);
+  Assert.equal(undefinedMetadata, undefined, "metadata should be undefined");
+  Assert.ok(AddressDataLoader._dataLoaded.level1.has("US"),
+               "level 1 state array should be set even there's no valid metadata");
+  AddressDataLoader._loadScripts.reset();
+
+  // Load level 1 data again
+  undefinedMetadata = FormAutofillUtils.getCountryAddressData("US", "AS");
+  Assert.equal(undefinedMetadata, undefined, "metadata should be undefined");
+  // _loadScripts should not be called
+  sinon.assert.notCalled(AddressDataLoader._loadScripts);
+});
--- a/browser/extensions/formautofill/test/unit/test_collectFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_collectFormFields.js
@@ -7,26 +7,28 @@
 Cu.import("resource://formautofill/FormAutofillHandler.jsm");
 
 const TESTCASES = [
   {
     description: "Form without autocomplete property",
     document: `<form><input id="given-name"><input id="family-name">
                <input id="street-addr"><input id="city"><select id="country"></select>
                <input id='email'><input id="phone"></form>`,
-    addressFieldDetails: [
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
-    ],
-    creditCardFieldDetails: [],
+    sections: [{
+      addressFieldDetails: [
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+      ],
+      creditCardFieldDetails: [],
+    }],
     validFieldDetails: [
       {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
@@ -43,31 +45,33 @@ const TESTCASES = [
                <select id="country" autocomplete="country"></select>
                <input id="email" autocomplete="email">
                <input id="tel" autocomplete="tel">
                <input id="cc-number" autocomplete="cc-number">
                <input id="cc-name" autocomplete="cc-name">
                <input id="cc-exp-month" autocomplete="cc-exp-month">
                <input id="cc-exp-year" autocomplete="cc-exp-year">
                </form>`,
-    addressFieldDetails: [
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
-    ],
-    creditCardFieldDetails: [
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
-    ],
+    sections: [{
+      addressFieldDetails: [
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+      ],
+      creditCardFieldDetails: [
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
+      ],
+    }],
     validFieldDetails: [
       {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
@@ -81,26 +85,28 @@ const TESTCASES = [
     description: "An address form with autocomplete properties and 2 tokens",
     document: `<form><input id="given-name" autocomplete="shipping given-name">
                <input id="family-name" autocomplete="shipping family-name">
                <input id="street-addr" autocomplete="shipping street-address">
                <input id="city" autocomplete="shipping address-level2">
                <input id="country" autocomplete="shipping country">
                <input id='email' autocomplete="shipping email">
                <input id="tel" autocomplete="shipping tel"></form>`,
-    addressFieldDetails: [
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
-    ],
-    creditCardFieldDetails: [],
+    sections: [{
+      addressFieldDetails: [
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
+      ],
+      creditCardFieldDetails: [],
+    }],
     validFieldDetails: [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
@@ -110,26 +116,28 @@ const TESTCASES = [
     description: "Form with autocomplete properties and profile is partly matched",
     document: `<form><input id="given-name" autocomplete="shipping given-name">
                <input id="family-name" autocomplete="shipping family-name">
                <input id="street-addr" autocomplete="shipping street-address">
                <input autocomplete="shipping address-level2">
                <select autocomplete="shipping country"></select>
                <input id='email' autocomplete="shipping email">
                <input id="tel" autocomplete="shipping tel"></form>`,
-    addressFieldDetails: [
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
-    ],
-    creditCardFieldDetails: [],
+    sections: [{
+      addressFieldDetails: [
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
+      ],
+      creditCardFieldDetails: [],
+    }],
     validFieldDetails: [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
@@ -138,108 +146,122 @@ const TESTCASES = [
   {
     description: "It's a valid address and credit card form.",
     document: `<form>
                <input id="given-name" autocomplete="shipping given-name">
                <input id="family-name" autocomplete="shipping family-name">
                <input id="street-addr" autocomplete="shipping street-address">
                <input id="cc-number" autocomplete="shipping cc-number">
                </form>`,
-    addressFieldDetails: [
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
-    ],
-    creditCardFieldDetails: [
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "cc-number"},
-    ],
+    sections: [{
+      addressFieldDetails: [
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
+      ],
+      creditCardFieldDetails: [
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "cc-number"},
+      ],
+    }],
     validFieldDetails: [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "cc-number"},
     ],
   },
   {
     description: "An invalid address form due to less than 3 fields.",
     document: `<form>
                <input id="given-name" autocomplete="shipping given-name">
                <input autocomplete="shipping address-level2">
                </form>`,
-    addressFieldDetails: [],
-    creditCardFieldDetails: [],
+    sections: [{
+      addressFieldDetails: [],
+      creditCardFieldDetails: [],
+    }],
     validFieldDetails: [],
   },
   {
     description: "An invalid credit card form due to omitted cc-number.",
     document: `<form>
                <input id="cc-name" autocomplete="cc-name">
                <input id="cc-exp-month" autocomplete="cc-exp-month">
                <input id="cc-exp-year" autocomplete="cc-exp-year">
                </form>`,
-    addressFieldDetails: [],
-    creditCardFieldDetails: [],
+    sections: [{
+      addressFieldDetails: [],
+      creditCardFieldDetails: [],
+    }],
     validFieldDetails: [],
   },
   {
     description: "An invalid credit card form due to non-autocomplete-attr cc-number and omitted cc-exp-*.",
     document: `<form>
                <input id="cc-name" autocomplete="cc-name">
                <input id="cc-number" name="card-number">
                </form>`,
-    addressFieldDetails: [],
-    creditCardFieldDetails: [],
+    sections: [{
+      addressFieldDetails: [],
+      creditCardFieldDetails: [],
+    }],
     validFieldDetails: [],
   },
   {
     description: "A valid credit card form with autocomplete-attr cc-number only.",
     document: `<form>
                <input id="cc-number" autocomplete="cc-number">
                </form>`,
-    addressFieldDetails: [],
-    creditCardFieldDetails: [
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
-    ],
+    sections: [{
+      addressFieldDetails: [],
+      creditCardFieldDetails: [
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
+      ],
+    }],
     validFieldDetails: [
       {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
     ],
   },
   {
     description: "A valid credit card form with non-autocomplete-attr cc-number and cc-exp.",
     document: `<form>
                <input id="cc-number" name="card-number">
                <input id="cc-exp" autocomplete="cc-exp">
                </form>`,
-    addressFieldDetails: [],
-    creditCardFieldDetails: [
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
-    ],
+    sections: [{
+      addressFieldDetails: [],
+      creditCardFieldDetails: [
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
+      ],
+    }],
     validFieldDetails: [
       {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp"},
     ],
     ids: [
       "cc-number",
       "cc-exp",
     ],
   },
   {
     description: "A valid credit card form with non-autocomplete-attr cc-number and cc-exp-month/cc-exp-year.",
     document: `<form>
                <input id="cc-number" name="card-number">
                <input id="cc-exp-month" autocomplete="cc-exp-month">
                <input id="cc-exp-year" autocomplete="cc-exp-year">
                </form>`,
-    addressFieldDetails: [],
-    creditCardFieldDetails: [
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
-    ],
+    sections: [{
+      addressFieldDetails: [],
+      creditCardFieldDetails: [
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
+      ],
+    }],
     validFieldDetails: [
       {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
     ],
     ids: [
       "cc-number",
       "cc-exp-month",
@@ -259,30 +281,32 @@ const TESTCASES = [
                  <input id="billingSuffix" name="phone" maxlength="4">
 
                  <input id="otherCC" name="phone" maxlength="3">
                  <input id="otherAC" name="phone" maxlength="3">
                  <input id="otherPrefix" name="phone" maxlength="3">
                  <input id="otherSuffix" name="phone" maxlength="4">
                </form>`,
     allowDuplicates: true,
-    addressFieldDetails: [
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-country-code"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
-    ],
-    creditCardFieldDetails: [],
+    sections: [{
+      addressFieldDetails: [
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-country-code"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
+      ],
+      creditCardFieldDetails: [],
+    }],
     validFieldDetails: [
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
@@ -295,69 +319,73 @@ const TESTCASES = [
       "shippingAC", "shippingPrefix", "shippingSuffix", "shippingTelExt",
       "billingAC", "billingPrefix", "billingSuffix",
       "otherCC", "otherAC", "otherPrefix", "otherSuffix",
     ],
   },
   {
     description: "Dedup the same field names of the different telephone fields.",
     document: `<form>
-                 <input id="i1" autocomplete="shipping given-name">
-                 <input id="i2" autocomplete="shipping family-name">
-                 <input id="i3" autocomplete="shipping street-address">
-                 <input id="i4" autocomplete="shipping email">
+                 <input id="i1" autocomplete="given-name">
+                 <input id="i2" autocomplete="family-name">
+                 <input id="i3" autocomplete="street-address">
+                 <input id="i4" autocomplete="email">
 
                  <input id="homePhone" maxlength="10">
                  <input id="mobilePhone" maxlength="10">
                  <input id="officePhone" maxlength="10">
                </form>`,
-    addressFieldDetails: [
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
-    ],
-    creditCardFieldDetails: [],
+    sections: [{
+      addressFieldDetails: [
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+      ],
+      creditCardFieldDetails: [],
+    }],
     validFieldDetails: [
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
     ],
     ids: ["i1", "i2", "i3", "i4", "homePhone"],
   },
   {
     description: "The duplicated phones of a single one and a set with ac, prefix, suffix.",
     document: `<form>
                  <input id="i1" autocomplete="shipping given-name">
                  <input id="i2" autocomplete="shipping family-name">
                  <input id="i3" autocomplete="shipping street-address">
                  <input id="i4" autocomplete="shipping email">
                  <input id="singlePhone" autocomplete="shipping tel">
                  <input id="shippingAreaCode" autocomplete="shipping tel-area-code">
                  <input id="shippingPrefix" autocomplete="shipping tel-local-prefix">
                  <input id="shippingSuffix" autocomplete="shipping tel-local-suffix">
                </form>`,
-    addressFieldDetails: [
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
+    sections: [{
+      addressFieldDetails: [
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
 
-      // NOTES: Ideally, there is only one full telephone field(s) in a form for
-      // this case. We can see if there is any better solution later.
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
+        // NOTES: Ideally, there is only one full telephone field(s) in a form for
+        // this case. We can see if there is any better solution later.
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
 
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-area-code"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-prefix"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-suffix"},
-    ],
-    creditCardFieldDetails: [],
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-area-code"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-prefix"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-suffix"},
+      ],
+      creditCardFieldDetails: [],
+    }],
     validFieldDetails: [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-area-code"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-prefix"},
@@ -370,22 +398,24 @@ const TESTCASES = [
     description: "Always adopt the info from autocomplete attribute.",
     document: `<form>
                  <input id="given-name" autocomplete="shipping given-name">
                  <input id="family-name" autocomplete="shipping family-name">
                  <input id="dummyAreaCode" autocomplete="shipping tel" maxlength="3">
                  <input id="dummyPrefix" autocomplete="shipping tel" maxlength="3">
                  <input id="dummySuffix" autocomplete="shipping tel" maxlength="4">
                </form>`,
-    addressFieldDetails: [
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
-    ],
-    creditCardFieldDetails: [],
+    sections: [{
+      addressFieldDetails: [
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
+        {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
+      ],
+      creditCardFieldDetails: [],
+    }],
     validFieldDetails: [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
     ],
     ids: ["given-name", "family-name", "dummyAreaCode"],
   },
 ];
@@ -426,26 +456,30 @@ for (let tc of TESTCASES) {
         handlerDetails.forEach((detail, index) => {
           Assert.equal(detail.fieldName, testCaseDetails[index].fieldName, "fieldName");
           Assert.equal(detail.section, testCaseDetails[index].section, "section");
           Assert.equal(detail.addressType, testCaseDetails[index].addressType, "addressType");
           Assert.equal(detail.contactType, testCaseDetails[index].contactType, "contactType");
           Assert.equal(detail.elementWeakRef.get(), testCaseDetails[index].elementWeakRef.get(), "DOM reference");
         });
       }
-      [
-        testcase.addressFieldDetails,
-        testcase.creditCardFieldDetails,
-        testcase.validFieldDetails,
-      ].forEach(details => setElementWeakRef(details));
+      for (let i = 0; i < testcase.sections.length; i++) {
+        let section = testcase.sections[i];
+        [
+          section.addressFieldDetails,
+          section.creditCardFieldDetails,
+        ].forEach(details => setElementWeakRef(details));
+      }
+      setElementWeakRef(testcase.validFieldDetails);
 
       let handler = new FormAutofillHandler(formLike);
       let validFieldDetails = handler.collectFormFields(testcase.allowDuplicates);
 
-      // TODO [Bug 1415077] We can assume all test cases with only one section
-      // should be filled. Eventually, the test needs to verify the filling
-      // feature in a multiple section case.
-      verifyDetails(handler.sections[0].address.fieldDetails, testcase.addressFieldDetails);
-      verifyDetails(handler.sections[0].creditCard.fieldDetails, testcase.creditCardFieldDetails);
+      Assert.equal(handler.sections.length, testcase.sections.length);
+      for (let i = 0; i < handler.sections.length; i++) {
+        let section = handler.sections[i];
+        verifyDetails(section.address.fieldDetails, testcase.sections[i].addressFieldDetails);
+        verifyDetails(section.creditCard.fieldDetails, testcase.sections[i].creditCardFieldDetails);
+      }
       verifyDetails(validFieldDetails, testcase.validFieldDetails);
     });
   })();
 }
--- a/browser/extensions/formautofill/test/unit/xpcshell.ini
+++ b/browser/extensions/formautofill/test/unit/xpcshell.ini
@@ -1,28 +1,30 @@
 [DEFAULT]
 firefox-appdir = browser
 head = head.js
 support-files =
   ../fixtures/**
 
 [heuristics/test_basic.js]
 [heuristics/test_cc_exp.js]
+[heuristics/test_multiple_section.js]
 [heuristics/third_party/test_BestBuy.js]
 [heuristics/third_party/test_CDW.js]
 [heuristics/third_party/test_CostCo.js]
 [heuristics/third_party/test_HomeDepot.js]
 [heuristics/third_party/test_Macys.js]
 [heuristics/third_party/test_NewEgg.js]
 [heuristics/third_party/test_OfficeDepot.js]
 [heuristics/third_party/test_QVC.js]
 [heuristics/third_party/test_Sears.js]
 [heuristics/third_party/test_Staples.js]
 [heuristics/third_party/test_Walmart.js]
 [test_activeStatus.js]
+[test_addressDataLoader.js]
 [test_addressRecords.js]
 [test_autofillFormFields.js]
 [test_collectFormFields.js]
 [test_createRecords.js]
 [test_creditCardRecords.js]
 [test_extractLabelStrings.js]
 [test_findLabelElements.js]
 [test_getAdaptedProfiles.js]
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_skip_tour.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_skip_tour.js
@@ -31,12 +31,13 @@ add_task(async function test_hide_skip_b
   resetOnboardingDefaultState();
   Preferences.set("browser.onboarding.skip-tour-button.hide", true);
 
   let tab = await openTab(ABOUT_NEWTAB_URL);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
   await promiseOnboardingOverlayOpened(tab.linkedBrowser);
 
+  // eslint-disable-next-line mozilla/no-cpows-in-tests
   ok(!content.document.querySelector("#onboarding-skip-tour-button"), "should not render the skip button");
 
   await BrowserTestUtils.removeTab(tab);
 });
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -607,16 +607,28 @@ tabbrowser {
 .tabbrowser-tab::after,
 .tabbrowser-tab::before {
   border-left: 1px solid;
   margin-top: 5px;
   margin-bottom: 4px;
   opacity: 0.3;
 }
 
+/* Move the ::before pseudo-element on tabs 1px to the left
+ * to avoid resizing the tab when the pseudo-element is removed again
+ * (this currently happens when a tab is dragged and dropped).
+ *
+ * Note that this doesn't perfectly solve the issue (dragged tabs
+ * may still resize very slightly) on some DPI settings with uneven
+ * scaling factors on Windows, because of bug 477157.
+ */
+.tabbrowser-tab::before {
+  margin-inline-start: -1px;
+}
+
 %ifdef CAN_DRAW_IN_TITLEBAR
 %ifdef MENUBAR_CAN_AUTOHIDE
 :root[tabsintitlebar]:not([extradragspace]) #toolbar-menubar[autohide=true] + #TabsToolbar > #tabbrowser-tabs > .tabbrowser-tab::after,
 %else
 :root[tabsintitlebar]:not([extradragspace]) .tabbrowser-tab::after,
 %endif
 %endif
 /* Show full height tab separators on hover. */
--- a/build/moz.configure/old.configure
+++ b/build/moz.configure/old.configure
@@ -285,22 +285,16 @@ def old_configure_options(*options):
     '--with-system-png',
     '--with-system-zlib',
     '--with-thumb',
     '--with-thumb-interwork',
     '--with-unify-dist',
     '--with-user-appdir',
     '--x-includes',
     '--x-libraries',
-
-    # Below are the configure flags used by comm-central.
-    '--enable-ldap',
-    '--enable-mapi',
-    '--enable-calendar',
-    '--enable-incomplete-external-linkage',
 )
 @imports(_from='__builtin__', _import='compile')
 @imports(_from='__builtin__', _import='open')
 @imports('logging')
 @imports('os')
 @imports('subprocess')
 @imports('sys')
 @imports(_from='mozbuild.shellutil', _import='quote')
--- a/devtools/client/jsonview/converter-child.js
+++ b/devtools/client/jsonview/converter-child.js
@@ -19,17 +19,17 @@ loader.lazyGetter(this, "debug", functio
 
 const childProcessMessageManager =
   Cc["@mozilla.org/childprocessmessagemanager;1"]
     .getService(Ci.nsISyncMessageSender);
 const BinaryInput = CC("@mozilla.org/binaryinputstream;1",
                        "nsIBinaryInputStream", "setInputStream");
 const BufferStream = CC("@mozilla.org/io/arraybuffer-input-stream;1",
                        "nsIArrayBufferInputStream", "setData");
-const encodingLength = 2;
+const encodingLength = 0;
 
 // Localization
 loader.lazyGetter(this, "jsonViewStrings", () => {
   return Services.strings.createBundle(
     "chrome://devtools/locale/jsonview.properties");
 });
 
 /**
@@ -140,32 +140,19 @@ Converter.prototype = {
     this.listener.onStopRequest(request, context, statusCode);
     this.listener = null;
     this.decoder = null;
     this.data = null;
   },
 
   // Determines the encoding of the response.
   determineEncoding: function (request, context, flush = false) {
-    // Determine the encoding using the bytes in encodingArray, defaulting to UTF-8.
-    // An initial byte order mark character (U+FEFF) does the trick.
-    // If there is no BOM, since the first character of valid JSON will be ASCII,
-    // the pattern of nulls in the first two bytes can be used instead.
-    //  - UTF-16BE:  00 xx  or  FE FF
-    //  - UTF-16LE:  xx 00  or  FF FE
-    //  - UTF-8:  anything else.
+    // Always use UTF-8
     let encoding = "UTF-8";
     let bytes = this.encodingArray;
-    if (bytes.length >= 2) {
-      if (!bytes[0] && bytes[1] || bytes[0] == 0xFE && bytes[1] == 0xFF) {
-        encoding = "UTF-16BE";
-      } else if (bytes[0] && !bytes[1] || bytes[0] == 0xFF && bytes[1] == 0xFE) {
-        encoding = "UTF-16LE";
-      }
-    }
 
     // Create a decoder for that encoding.
     this.decoder = new TextDecoder(encoding);
     this.data.encoding = encoding;
 
     // Decode and insert the bytes in encodingArray, and remove it.
     let buffer = new Uint8Array(bytes).buffer;
     this.decodeAndInsertBuffer(request, context, buffer, flush);
--- a/devtools/client/jsonview/test/browser.ini
+++ b/devtools/client/jsonview/test/browser.ini
@@ -32,16 +32,17 @@ skip-if = (os == 'linux' && bits == 32 &
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_jsonview_copy_rawdata.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_jsonview_csp_json.js]
 [browser_jsonview_empty_object.js]
 [browser_jsonview_encoding.js]
+skip-if = true # bug 1419416, remove UTF-16 support
 [browser_jsonview_filter.js]
 [browser_jsonview_invalid_json.js]
 [browser_jsonview_manifest.js]
 [browser_jsonview_nojs.js]
 [browser_jsonview_nul.js]
 [browser_jsonview_object-type.js]
 [browser_jsonview_row_selection.js]
 [browser_jsonview_save_json.js]
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -322,16 +322,22 @@ charts.cacheDisabled=Empty cache
 charts.totalSize=Size: %S KB
 
 # LOCALIZATION NOTE (charts.totalSeconds): Semi-colon list of plural forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # This is the label displayed in the performance analysis view for the
 # total requests time, in seconds.
 charts.totalSeconds=Time: #1 second;Time: #1 seconds
 
+# LOCALIZATION NOTE (charts.totalSecondsNonBlocking): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# This is the label displayed in the performance analysis view for the
+# total requests time (non-blocking), in seconds.
+charts.totalSecondsNonBlocking=Non blocking time: #1 second;Non blocking time: #1 seconds
+
 # LOCALIZATION NOTE (charts.totalCached): This is the label displayed
 # in the performance analysis view for total cached responses.
 charts.totalCached=Cached responses: %S
 
 # LOCALIZATION NOTE (charts.totalCount): This is the label displayed
 # in the performance analysis view for total requests.
 charts.totalCount=Total requests: %S
 
@@ -347,16 +353,21 @@ charts.type=Type
 # in the header column in the performance analysis view for transferred
 # size of the request.
 charts.transferred=Transferred
 
 # LOCALIZATION NOTE (charts.totalCount): This is the label displayed
 # in the header column in the performance analysis view for time of request.
 charts.time=Time
 
+# LOCALIZATION NOTE (charts.nonBlockingTime): This is the label displayed
+# in the header column in the performance analysis view for non blocking
+# time of request.
+charts.nonBlockingTime=Non blocking time
+
 # LOCALIZATION NOTE (netRequest.headers): A label used for Headers tab
 # This tab displays list of HTTP headers
 netRequest.headers=Headers
 
 # LOCALIZATION NOTE (netRequest.response): A label used for Response tab
 # This tab displays HTTP response body
 netRequest.response=Response
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/.babelrc
@@ -0,0 +1,5 @@
+{
+  "plugins": [
+    "transform-object-rest-spread"
+  ]
+}
--- a/devtools/client/netmonitor/package.json
+++ b/devtools/client/netmonitor/package.json
@@ -16,15 +16,16 @@
     "jszip": "^3.1.3",
     "react": "=15.6.1",
     "react-dom": "=15.6.1",
     "react-redux": "=5.0.3",
     "redux": "^3.6.0",
     "reselect": "^3.0.1"
   },
   "devDependencies": {
+    "babel-plugin-transform-object-rest-spread": "^6.26.0",
     "babel-register": "^6.24.0",
     "file-loader": "^0.10.1"
   },
   "scripts": {
     "start": "node bin/dev-server"
   }
 }
--- a/devtools/client/netmonitor/src/components/StatisticsPanel.js
+++ b/devtools/client/netmonitor/src/components/StatisticsPanel.js
@@ -121,41 +121,50 @@ class StatisticsPanel extends Component 
       title,
       header: {
         cached: "",
         count: "",
         label: L10N.getStr("charts.type"),
         size: L10N.getStr("charts.size"),
         transferredSize: L10N.getStr("charts.transferred"),
         time: L10N.getStr("charts.time"),
+        nonBlockingTime: L10N.getStr("charts.nonBlockingTime"),
       },
       data,
       strings: {
         size: (value) =>
           L10N.getFormatStr("charts.sizeKB", getSizeWithDecimals(value / 1024)),
         transferredSize: (value) =>
           L10N.getFormatStr("charts.transferredSizeKB",
             getSizeWithDecimals(value / 1024)),
         time: (value) =>
           L10N.getFormatStr("charts.totalS", getTimeWithDecimals(value / 1000)),
+        nonBlockingTime: (value) =>
+          L10N.getFormatStr("charts.totalS", getTimeWithDecimals(value / 1000)),
       },
       totals: {
         cached: (total) => L10N.getFormatStr("charts.totalCached", total),
         count: (total) => L10N.getFormatStr("charts.totalCount", total),
         size: (total) =>
           L10N.getFormatStr("charts.totalSize", getSizeWithDecimals(total / 1024)),
         transferredSize: total =>
           L10N.getFormatStr("charts.totalTransferredSize",
             getSizeWithDecimals(total / 1024)),
         time: (total) => {
           let seconds = total / 1000;
           let string = getTimeWithDecimals(seconds);
           return PluralForm.get(seconds,
             L10N.getStr("charts.totalSeconds")).replace("#1", string);
         },
+        nonBlockingTime: (total) => {
+          let seconds = total / 1000;
+          let string = getTimeWithDecimals(seconds);
+          return PluralForm.get(seconds,
+            L10N.getStr("charts.totalSecondsNonBlocking")).replace("#1", string);
+        },
       },
       sorted: true,
     });
 
     chart.on("click", (_, { label }) => {
       // Reset FilterButtons and enable one filter exclusively
       this.props.closeStatistics();
       this.props.enableRequestFilterTypeOnly(label);
@@ -174,16 +183,17 @@ class StatisticsPanel extends Component 
   sanitizeChartDataSource(requests, emptyCache) {
     const data = FILTER_TAGS.map((type) => ({
       cached: 0,
       count: 0,
       label: type,
       size: 0,
       transferredSize: 0,
       time: 0,
+      nonBlockingTime: 0,
     }));
 
     for (let request of requests) {
       let type;
 
       if (Filters.html(request)) {
         // "html"
         type = 0;
@@ -213,16 +223,19 @@ class StatisticsPanel extends Component 
         // "other"
         type = 8;
       }
 
       if (emptyCache || !this.responseIsFresh(request)) {
         data[type].time += request.totalTime || 0;
         data[type].size += request.contentSize || 0;
         data[type].transferredSize += request.transferredSize || 0;
+        let nonBlockingTime =
+           request.eventTimings.totalTime - request.eventTimings.timings.blocked;
+        data[type].nonBlockingTime += nonBlockingTime || 0;
       } else {
         data[type].cached++;
       }
       data[type].count++;
     }
 
     return data.filter(e => e.count > 0);
   }
--- a/devtools/client/netmonitor/src/connector/firefox-connector.js
+++ b/devtools/client/netmonitor/src/connector/firefox-connector.js
@@ -134,17 +134,19 @@ class FirefoxConnector {
       }
       window.off(EVENTS.PAYLOAD_READY, listener);
       this.onReloaded();
     };
     window.on(EVENTS.PAYLOAD_READY, listener);
   }
 
   onReloaded() {
-    this.panel.emit("reloaded");
+    if (this.panel) {
+      this.panel.emit("reloaded");
+    }
   }
 
   /**
    * Display any network events already in the cache.
    */
   displayCachedEvents() {
     for (let networkInfo of this.webConsoleClient.getNetworkEvents()) {
       // First add the request to the timeline.
--- a/devtools/client/netmonitor/yarn.lock
+++ b/devtools/client/netmonitor/yarn.lock
@@ -584,16 +584,23 @@ babel-plugin-transform-flow-strip-types@
 
 babel-plugin-transform-object-rest-spread@^6.22.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz#875d6bc9be761c58a2ae3feee5dc4895d8c7f921"
   dependencies:
     babel-plugin-syntax-object-rest-spread "^6.8.0"
     babel-runtime "^6.22.0"
 
+babel-plugin-transform-object-rest-spread@^6.26.0:
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06"
+  dependencies:
+    babel-plugin-syntax-object-rest-spread "^6.8.0"
+    babel-runtime "^6.26.0"
+
 babel-plugin-transform-runtime@^6.7.5:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz#88490d446502ea9b8e7efb0fe09ec4d99479b1ee"
   dependencies:
     babel-runtime "^6.22.0"
 
 babel-plugin-transform-strict-mode@^6.24.1:
   version "6.24.1"
@@ -4433,19 +4440,19 @@ require-directory@^2.1.1:
 require-from-string@^1.1.0:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418"
 
 require-main-filename@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
 
-reselect@^2.5.4:
-  version "2.5.4"
-  resolved "https://registry.yarnpkg.com/reselect/-/reselect-2.5.4.tgz#b7d23fdf00b83fa7ad0279546f8dbbbd765c7047"
+reselect@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147"
 
 resolve@1.1.7:
   version "1.1.7"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
 
 resolve@^1.2.0:
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5"
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -297,16 +297,19 @@ pref("devtools.webconsole.timestampMessa
 
 // Web Console automatic multiline mode: |true| if you want incomplete statements
 // to automatically trigger multiline editing (equivalent to shift + enter).
 pref("devtools.webconsole.autoMultiline", true);
 
 // Enable the new webconsole frontend
 pref("devtools.webconsole.new-frontend-enabled", true);
 
+// Enable the webconsole sidebar toggle
+pref("devtools.webconsole.sidebarToggle", false);
+
 // Enable client-side mapping service for source maps
 pref("devtools.source-map.client-service.enabled", true);
 
 // The number of lines that are displayed in the web console.
 pref("devtools.hud.loglimit", 10000);
 
 // The number of lines that are displayed in the old web console for the Net,
 // CSS, JS and Web Developer categories. These defaults should be kept in sync
--- a/devtools/client/shared/components/reps/reps.css
+++ b/devtools/client/shared/components/reps/reps.css
@@ -251,17 +251,17 @@
   user-select: none;
 }
 
 .tree button {
   display: block;
 }
 
 .tree .tree-node[data-expandable="true"] {
-  cursor: pointer;
+  cursor: default;
 }
 
 .tree .tree-node:not(.focused):hover {
   background-color: var(--tree-node-hover-background-color);
 }
 
 .tree .tree-node.focused {
   color: var(--tree-node-focus-color);
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -1,18 +1,18 @@
 (function webpackUniversalModuleDefinition(root, factory) {
 	if(typeof exports === 'object' && typeof module === 'object')
-		module.exports = factory(require("devtools/client/shared/vendor/react"), require("devtools/client/shared/vendor/lodash"));
+		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", "devtools/client/shared/vendor/lodash"], factory);
+		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"), require("devtools/client/shared/vendor/lodash")) : factory(root["devtools/client/shared/vendor/react"], root["devtools/client/shared/vendor/lodash"]);
+		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_0__, __WEBPACK_EXTERNAL_MODULE_49__) {
+})(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_53__, __WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_6__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
 /******/
 /******/ 	// The require function
 /******/ 	function __webpack_require__(moduleId) {
 /******/
 /******/ 		// Check if module is in cache
@@ -65,40 +65,36 @@ 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 = 15);
+/******/ 	return __webpack_require__(__webpack_require__.s = 16);
 /******/ })
 /************************************************************************/
 /******/ ([
 /* 0 */
-/***/ (function(module, exports) {
-
-module.exports = __WEBPACK_EXTERNAL_MODULE_0__;
-
-/***/ }),
-/* 1 */
 /***/ (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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
 const validProtocols = /^(http|https|ftp|data|javascript|resource|chrome):/i;
 const tokenSplitRegex = /(\s|\'|\"|\\)+/;
+const dom = __webpack_require__(1);
+const { span } = dom;
+
 /**
  * Returns true if the given object is a grip (see RDP protocol)
  */
 function isGrip(object) {
   return object && object.actor;
 }
 
 function escapeNewLines(value) {
@@ -334,17 +330,17 @@ function splitURLTrue(url) {
  * fallback rep if the render fails.
  */
 function wrapRender(renderMethod) {
   const wrappedFunction = function (props) {
     try {
       return renderMethod.call(this, props);
     } catch (e) {
       console.error(e);
-      return React.DOM.span({
+      return span({
         className: "objectBox objectBox-failure",
         title: "This object could not be rendered, " + "please file a bug on bugzilla.mozilla.org"
       },
       /* Labels have to be hardcoded for reps, see Bug 1317038. */
       "Invalid object");
     }
   };
   wrappedFunction.propTypes = renderMethod.propTypes;
@@ -488,17 +484,29 @@ module.exports = {
   getURLDisplayString,
   maybeEscapePropertyName,
   getGripPreviewItems,
   getGripType,
   tokenSplitRegex
 };
 
 /***/ }),
+/* 1 */
+/***/ (function(module, exports) {
+
+module.exports = __WEBPACK_EXTERNAL_MODULE_1__;
+
+/***/ }),
 /* 2 */
+/***/ (function(module, exports) {
+
+module.exports = __WEBPACK_EXTERNAL_MODULE_2__;
+
+/***/ }),
+/* 3 */
 /***/ (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/. */
@@ -507,61 +515,61 @@ module.exports = {
   MODE: {
     TINY: Symbol("TINY"),
     SHORT: Symbol("SHORT"),
     LONG: Symbol("LONG")
   }
 };
 
 /***/ }),
-/* 3 */
+/* 4 */
 /***/ (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/. */
 
-__webpack_require__(16);
+__webpack_require__(17);
 
 // Load all existing rep templates
-const Undefined = __webpack_require__(17);
-const Null = __webpack_require__(18);
-const StringRep = __webpack_require__(5);
-const LongStringRep = __webpack_require__(19);
-const Number = __webpack_require__(20);
-const ArrayRep = __webpack_require__(7);
-const Obj = __webpack_require__(21);
-const SymbolRep = __webpack_require__(22);
-const InfinityRep = __webpack_require__(23);
-const NaNRep = __webpack_require__(24);
-const Accessor = __webpack_require__(25);
+const Undefined = __webpack_require__(18);
+const Null = __webpack_require__(19);
+const StringRep = __webpack_require__(7);
+const LongStringRep = __webpack_require__(20);
+const Number = __webpack_require__(21);
+const ArrayRep = __webpack_require__(9);
+const Obj = __webpack_require__(22);
+const SymbolRep = __webpack_require__(23);
+const InfinityRep = __webpack_require__(24);
+const NaNRep = __webpack_require__(25);
+const Accessor = __webpack_require__(26);
 
 // DOM types (grips)
-const Attribute = __webpack_require__(26);
-const DateTime = __webpack_require__(27);
-const Document = __webpack_require__(28);
-const Event = __webpack_require__(29);
-const Func = __webpack_require__(30);
-const PromiseRep = __webpack_require__(31);
-const RegExp = __webpack_require__(32);
-const StyleSheet = __webpack_require__(33);
-const CommentNode = __webpack_require__(34);
-const ElementNode = __webpack_require__(35);
-const TextNode = __webpack_require__(37);
-const ErrorRep = __webpack_require__(38);
-const Window = __webpack_require__(39);
-const ObjectWithText = __webpack_require__(40);
-const ObjectWithURL = __webpack_require__(41);
-const GripArray = __webpack_require__(11);
-const GripMap = __webpack_require__(12);
-const GripMapEntry = __webpack_require__(13);
-const Grip = __webpack_require__(6);
+const Attribute = __webpack_require__(27);
+const DateTime = __webpack_require__(28);
+const Document = __webpack_require__(29);
+const Event = __webpack_require__(30);
+const Func = __webpack_require__(31);
+const PromiseRep = __webpack_require__(32);
+const RegExp = __webpack_require__(33);
+const StyleSheet = __webpack_require__(34);
+const CommentNode = __webpack_require__(35);
+const ElementNode = __webpack_require__(36);
+const TextNode = __webpack_require__(40);
+const ErrorRep = __webpack_require__(41);
+const Window = __webpack_require__(42);
+const ObjectWithText = __webpack_require__(43);
+const ObjectWithURL = __webpack_require__(44);
+const GripArray = __webpack_require__(12);
+const GripMap = __webpack_require__(13);
+const GripMapEntry = __webpack_require__(14);
+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];
 
 /**
  * Generic rep that is using for rendering native JS types or an object.
@@ -646,67 +654,68 @@ module.exports = {
     Undefined,
     Window
   },
   // Exporting for tests
   getRep
 };
 
 /***/ }),
-/* 4 */
+/* 5 */
 /***/ (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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 const {
   maybeEscapePropertyName,
   wrapRender
-} = __webpack_require__(1);
-const { MODE } = __webpack_require__(2);
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+const { MODE } = __webpack_require__(3);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Property for Obj (local JS objects), Grip (remote JS objects)
  * and GripMap (remote JS maps and weakmaps) reps.
  * It's used to render object properties.
  */
 PropRep.propTypes = {
   // Property name.
-  name: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object]).isRequired,
+  name: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
   // Equal character rendered between property name and value.
-  equal: React.PropTypes.string,
+  equal: PropTypes.string,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
-  mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
-  onDOMNodeMouseOver: React.PropTypes.func,
-  onDOMNodeMouseOut: React.PropTypes.func,
-  onInspectIconClick: React.PropTypes.func,
+  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+  onDOMNodeMouseOver: PropTypes.func,
+  onDOMNodeMouseOut: PropTypes.func,
+  onInspectIconClick: PropTypes.func,
   // Normally a PropRep will quote a property name that isn't valid
   // when unquoted; but this flag can be used to suppress the
   // quoting.
-  suppressQuotes: React.PropTypes.bool
+  suppressQuotes: PropTypes.bool
 };
 
 /**
  * Function that given a name, a delimiter and an object returns an array
  * of React elements representing an object property (e.g. `name: value`)
  *
  * @param {Object} props
  * @return {Array} Array of React elements.
  */
 function PropRep(props) {
-  const Grip = __webpack_require__(6);
-  const { Rep } = __webpack_require__(3);
+  const Grip = __webpack_require__(8);
+  const { Rep } = __webpack_require__(4);
 
   let {
     name,
     mode,
     equal,
     suppressQuotes
   } = props;
 
@@ -731,56 +740,62 @@ function PropRep(props) {
     "className": "objectEqual"
   }, equal), Rep(Object.assign({}, props))];
 }
 
 // Exports from this module
 module.exports = wrapRender(PropRep);
 
 /***/ }),
-/* 5 */
+/* 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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 
 const {
   containsURL,
   isURL,
   escapeString,
   getGripType,
   rawCropString,
   sanitizeString,
   wrapRender,
   tokenSplitRegex
-} = __webpack_require__(1);
-
-// Shortcuts
-const { a, span } = React.DOM;
+} = __webpack_require__(0);
+
+const dom = __webpack_require__(1);
+const { a, span } = dom;
 
 /**
  * Renders a string. String value is enclosed within quotes.
  */
 StringRep.propTypes = {
-  useQuotes: React.PropTypes.bool,
-  escapeWhitespace: React.PropTypes.bool,
-  style: React.PropTypes.object,
-  object: React.PropTypes.string.isRequired,
-  member: React.PropTypes.any,
-  cropLimit: React.PropTypes.number,
-  openLink: React.PropTypes.func,
-  className: React.PropTypes.string,
-  omitLinkHref: React.PropTypes.bool
+  useQuotes: PropTypes.bool,
+  escapeWhitespace: PropTypes.bool,
+  style: PropTypes.object,
+  object: PropTypes.string.isRequired,
+  member: PropTypes.any,
+  cropLimit: PropTypes.number,
+  openLink: PropTypes.func,
+  className: PropTypes.string,
+  omitLinkHref: PropTypes.bool
 };
 
 function StringRep(props) {
   let {
     className,
     cropLimit,
     object: text,
     member,
@@ -852,53 +867,55 @@ function supportsObject(object, noGrip =
 // Exports from this module
 
 module.exports = {
   rep: wrapRender(StringRep),
   supportsObject
 };
 
 /***/ }),
-/* 6 */
+/* 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
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // ReactJS
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
+
 // Dependencies
 const {
   isGrip,
   wrapRender
-} = __webpack_require__(1);
-const PropRep = __webpack_require__(4);
-const { MODE } = __webpack_require__(2);
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+const PropRep = __webpack_require__(5);
+const { MODE } = __webpack_require__(3);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders generic grip. Grip is client representation
  * of remote JS object and is used as an input object
  * for this rep component.
  */
 GripRep.propTypes = {
-  object: React.PropTypes.object.isRequired,
+  object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
-  mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
-  isInterestingProp: React.PropTypes.func,
-  title: React.PropTypes.string,
-  onDOMNodeMouseOver: React.PropTypes.func,
-  onDOMNodeMouseOut: React.PropTypes.func,
-  onInspectIconClick: React.PropTypes.func,
-  noGrip: React.PropTypes.bool
+  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+  isInterestingProp: PropTypes.func,
+  title: PropTypes.string,
+  onDOMNodeMouseOver: PropTypes.func,
+  onDOMNodeMouseOut: PropTypes.func,
+  onInspectIconClick: PropTypes.func,
+  noGrip: PropTypes.bool
 };
 
 const DEFAULT_TITLE = "Object";
 
 function GripRep(props) {
   let {
     mode = MODE.SHORT,
     object
@@ -970,17 +987,17 @@ function safePropIterator(props, object,
   } catch (err) {
     console.error(err);
   }
   return [];
 }
 
 function propIterator(props, object, max) {
   if (object.preview && Object.keys(object.preview).includes("wrappedValue")) {
-    const { Rep } = __webpack_require__(3);
+    const { Rep } = __webpack_require__(4);
 
     return [Rep({
       object: object.preview.wrappedValue,
       mode: props.mode || MODE.TINY,
       defaultRep: Grip
     })];
   }
 
@@ -1168,47 +1185,46 @@ let Grip = {
   supportsObject,
   maxLengthMap
 };
 
 // Exports from this module
 module.exports = Grip;
 
 /***/ }),
-/* 7 */
+/* 9 */
 /***/ (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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
+const dom = __webpack_require__(1);
+const PropTypes = __webpack_require__(2);
 const {
   wrapRender
-} = __webpack_require__(1);
-const { MODE } = __webpack_require__(2);
-
-const ModePropType = React.PropTypes.oneOf(
+} = __webpack_require__(0);
+const { MODE } = __webpack_require__(3);
+const { span } = dom;
+
+const ModePropType = PropTypes.oneOf(
 // @TODO Change this to Object.values once it's supported in Node's version of V8
 Object.keys(MODE).map(key => MODE[key]));
 
-// Shortcuts
-const DOM = React.DOM;
-
 /**
  * Renders an array. The array is enclosed by left and right bracket
  * and the max number of rendered items depends on the current mode.
  */
 ArrayRep.propTypes = {
   mode: ModePropType,
-  object: React.PropTypes.array.isRequired
+  object: PropTypes.array.isRequired
 };
 
 function ArrayRep(props) {
   let {
     object,
     mode = MODE.SHORT
   } = props;
 
@@ -1218,33 +1234,33 @@ function ArrayRep(props) {
     return space ? { left: "[ ", right: " ]" } : { left: "[", right: "]" };
   };
 
   if (mode === MODE.TINY) {
     let isEmpty = object.length === 0;
     if (isEmpty) {
       items = [];
     } else {
-      items = [DOM.span({
+      items = [span({
         className: "more-ellipsis",
         title: "more…"
       }, "…")];
     }
     brackets = needSpace(false);
   } else {
     items = arrayIterator(props, object, maxLengthMap.get(mode));
     brackets = needSpace(items.length > 0);
   }
 
-  return DOM.span({
-    className: "objectBox objectBox-array" }, DOM.span({
+  return span({
+    className: "objectBox objectBox-array" }, span({
     className: "arrayLeftBracket"
-  }, brackets.left), ...items, DOM.span({
+  }, brackets.left), ...items, span({
     className: "arrayRightBracket"
-  }, brackets.right), DOM.span({
+  }, brackets.right), span({
     className: "arrayProperties",
     role: "group" }));
 }
 
 function arrayIterator(props, array, max) {
   let items = [];
 
   for (let i = 0; i < array.length && i < max; i++) {
@@ -1262,43 +1278,43 @@ function arrayIterator(props, array, max
       item = ItemRep(Object.assign({}, props, config, {
         object: exc
       }));
     }
     items.push(item);
   }
 
   if (array.length > max) {
-    items.push(DOM.span({
+    items.push(span({
       className: "more-ellipsis",
       title: "more…"
     }, "…"));
   }
 
   return items;
 }
 
 /**
  * Renders array item. Individual values are separated by a comma.
  */
 ItemRep.propTypes = {
-  object: React.PropTypes.any.isRequired,
-  delim: React.PropTypes.string.isRequired,
+  object: PropTypes.any.isRequired,
+  delim: PropTypes.string.isRequired,
   mode: ModePropType
 };
 
 function ItemRep(props) {
-  const { Rep } = __webpack_require__(3);
+  const { Rep } = __webpack_require__(4);
 
   let {
     object,
     delim,
     mode
   } = props;
-  return DOM.span({}, Rep(Object.assign({}, props, {
+  return span({}, Rep(Object.assign({}, props, {
     object: object,
     mode: mode
   })), delim);
 }
 
 function getLength(object) {
   return object.length;
 }
@@ -1315,17 +1331,17 @@ maxLengthMap.set(MODE.LONG, 10);
 module.exports = {
   rep: wrapRender(ArrayRep),
   supportsObject,
   maxLengthMap,
   getLength
 };
 
 /***/ }),
-/* 8 */
+/* 10 */
 /***/ (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/. */
@@ -1349,242 +1365,96 @@ module.exports = {
   DOCUMENT_POSITION_PRECEDING: 0x02,
   DOCUMENT_POSITION_FOLLOWING: 0x04,
   DOCUMENT_POSITION_CONTAINS: 0x08,
   DOCUMENT_POSITION_CONTAINED_BY: 0x10,
   DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 0x20
 };
 
 /***/ }),
-/* 9 */
+/* 11 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
+var _svgInlineReact = __webpack_require__(37);
+
+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__(0);
-const InlineSVG = __webpack_require__(10);
+const React = __webpack_require__(6);
+const PropTypes = __webpack_require__(2);
+
 
 const svg = {
-  "open-inspector": __webpack_require__(36)
+  "open-inspector": __webpack_require__(39)
 };
 
 Svg.propTypes = {
-  className: React.PropTypes.string
+  className: PropTypes.string
 };
 
 function Svg(name, props) {
   if (!svg[name]) {
     throw new Error("Unknown SVG: " + name);
   }
   let className = name;
   if (props && props.className) {
     className = `${name} ${props.className}`;
   }
   if (name === "subSettings") {
     className = "";
   }
   props = Object.assign({}, props, { className, src: svg[name] });
-  return React.createElement(InlineSVG, props);
+  return React.createElement(_svgInlineReact2.default, props);
 }
 
 module.exports = Svg;
 
 /***/ }),
-/* 10 */
-/***/ (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 _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
-
-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 _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 _react = __webpack_require__(0);
-
-var _react2 = _interopRequireDefault(_react);
-
-var DOMParser = typeof window !== 'undefined' && window.DOMParser;
-var process = process || {};
-process.env = process.env || {};
-var parserAvailable = typeof DOMParser !== 'undefined' && DOMParser.prototype != null && DOMParser.prototype.parseFromString != null;
-
-function isParsable(src) {
-    // kinda naive but meh, ain't gonna use full-blown parser for this
-    return parserAvailable && typeof src === 'string' && src.trim().substr(0, 4) === '<svg';
-}
-
-// parse SVG string using `DOMParser`
-function parseFromSVGString(src) {
-    var parser = new DOMParser();
-    return parser.parseFromString(src, "image/svg+xml");
-}
-
-// Transform DOM prop/attr names applicable to `<svg>` element but react-limited
-function switchSVGAttrToReactProp(propName) {
-    switch (propName) {
-        case 'class':
-            return 'className';
-        default:
-            return propName;
-    }
-}
-
-var InlineSVG = (function (_React$Component) {
-    _inherits(InlineSVG, _React$Component);
-
-    _createClass(InlineSVG, null, [{
-        key: 'defaultProps',
-        value: {
-            element: 'i',
-            raw: false,
-            src: ''
-        },
-        enumerable: true
-    }, {
-        key: 'propTypes',
-        value: {
-            src: _react2['default'].PropTypes.string.isRequired,
-            element: _react2['default'].PropTypes.string,
-            raw: _react2['default'].PropTypes.bool
-        },
-        enumerable: true
-    }]);
-
-    function InlineSVG(props) {
-        _classCallCheck(this, InlineSVG);
-
-        _get(Object.getPrototypeOf(InlineSVG.prototype), 'constructor', this).call(this, props);
-        this._extractSVGProps = this._extractSVGProps.bind(this);
-    }
-
-    // Serialize `Attr` objects in `NamedNodeMap`
-
-    _createClass(InlineSVG, [{
-        key: '_serializeAttrs',
-        value: function _serializeAttrs(map) {
-            var ret = {};
-            var prop = undefined;
-            for (var i = 0; i < map.length; i++) {
-                prop = switchSVGAttrToReactProp(map[i].name);
-                ret[prop] = map[i].value;
-            }
-            return ret;
-        }
-
-        // get <svg /> element props
-    }, {
-        key: '_extractSVGProps',
-        value: function _extractSVGProps(src) {
-            var map = parseFromSVGString(src).documentElement.attributes;
-            return map.length > 0 ? this._serializeAttrs(map) : null;
-        }
-
-        // get content inside <svg> element.
-    }, {
-        key: '_stripSVG',
-        value: function _stripSVG(src) {
-            return parseFromSVGString(src).documentElement.innerHTML;
-        }
-    }, {
-        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 = undefined,
-                __html = undefined,
-                svgProps = undefined;
-            var _props = this.props;
-            var element = _props.element;
-            var raw = _props.raw;
-            var src = _props.src;
-
-            var otherProps = _objectWithoutProperties(_props, ['element', 'raw', 'src']);
-
-            if (raw === true && isParsable(src)) {
-                Element = 'svg';
-                svgProps = this._extractSVGProps(src);
-                __html = this._stripSVG(src);
-            }
-            __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;
-module.exports = exports['default'];
-
-/***/ }),
-/* 11 */
+/* 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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 const {
   getGripType,
   isGrip,
   wrapRender
-} = __webpack_require__(1);
-const { MODE } = __webpack_require__(2);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+const { MODE } = __webpack_require__(3);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders an array. The array is enclosed by left and right bracket
  * and the max number of rendered items depends on the current mode.
  */
 GripArray.propTypes = {
-  object: React.PropTypes.object.isRequired,
+  object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
-  mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
-  provider: React.PropTypes.object,
-  onDOMNodeMouseOver: React.PropTypes.func,
-  onDOMNodeMouseOut: React.PropTypes.func,
-  onInspectIconClick: React.PropTypes.func
+  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+  provider: PropTypes.object,
+  onDOMNodeMouseOver: PropTypes.func,
+  onDOMNodeMouseOut: PropTypes.func,
+  onInspectIconClick: PropTypes.func
 };
 
 function GripArray(props) {
   let {
     object,
     mode = MODE.SHORT
   } = props;
 
@@ -1657,17 +1527,17 @@ function getPreviewItems(grip) {
   if (!grip.preview) {
     return null;
   }
 
   return grip.preview.items || grip.preview.childNodes || [];
 }
 
 function arrayIterator(props, grip, max) {
-  let { Rep } = __webpack_require__(3);
+  let { Rep } = __webpack_require__(4);
 
   let items = [];
   const gripLength = getLength(grip);
 
   if (!gripLength) {
     return items;
   }
 
@@ -1749,50 +1619,51 @@ maxLengthMap.set(MODE.LONG, 10);
 module.exports = {
   rep: wrapRender(GripArray),
   supportsObject,
   maxLengthMap,
   getLength
 };
 
 /***/ }),
-/* 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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 const {
   isGrip,
   wrapRender
-} = __webpack_require__(1);
-const PropRep = __webpack_require__(4);
-const { MODE } = __webpack_require__(2);
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+const PropRep = __webpack_require__(5);
+const { MODE } = __webpack_require__(3);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders an map. A map is represented by a list of its
  * entries enclosed in curly brackets.
  */
 GripMap.propTypes = {
-  object: React.PropTypes.object,
+  object: PropTypes.object,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
-  mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
-  isInterestingEntry: React.PropTypes.func,
-  onDOMNodeMouseOver: React.PropTypes.func,
-  onDOMNodeMouseOut: React.PropTypes.func,
-  onInspectIconClick: React.PropTypes.func,
-  title: React.PropTypes.string
+  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+  isInterestingEntry: PropTypes.func,
+  onDOMNodeMouseOver: PropTypes.func,
+  onDOMNodeMouseOut: PropTypes.func,
+  onInspectIconClick: PropTypes.func,
+  title: PropTypes.string
 };
 
 function GripMap(props) {
   let {
     mode,
     object
   } = props;
 
@@ -1956,45 +1827,46 @@ maxLengthMap.set(MODE.LONG, 10);
 module.exports = {
   rep: wrapRender(GripMap),
   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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 // Shortcuts
-const { span } = React.DOM;
+const dom = __webpack_require__(1);
+const { span } = dom;
 const {
   wrapRender
-} = __webpack_require__(1);
-const PropRep = __webpack_require__(4);
-const { MODE } = __webpack_require__(2);
+} = __webpack_require__(0);
+const PropRep = __webpack_require__(5);
+const { MODE } = __webpack_require__(3);
 /**
  * Renders an map entry. A map entry is represented by its key, a column and its value.
  */
 GripMapEntry.propTypes = {
-  object: React.PropTypes.object,
+  object: PropTypes.object,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
-  mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
-  onDOMNodeMouseOver: React.PropTypes.func,
-  onDOMNodeMouseOut: React.PropTypes.func,
-  onInspectIconClick: React.PropTypes.func
+  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+  onDOMNodeMouseOver: PropTypes.func,
+  onDOMNodeMouseOut: PropTypes.func,
+  onInspectIconClick: PropTypes.func
 };
 
 function GripMapEntry(props) {
   const {
     object
   } = props;
 
   const {
@@ -2033,32 +1905,32 @@ function createGripMapEntry(key, value) 
 // Exports from this module
 module.exports = {
   rep: wrapRender(GripMapEntry),
   createGripMapEntry,
   supportsObject
 };
 
 /***/ }),
-/* 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/. */
 
-const { get, has } = __webpack_require__(49);
-const { maybeEscapePropertyName } = __webpack_require__(1);
-const ArrayRep = __webpack_require__(7);
-const GripArrayRep = __webpack_require__(11);
-const GripMap = __webpack_require__(12);
-const GripMapEntryRep = __webpack_require__(13);
+const { get, has } = __webpack_require__(53);
+const { maybeEscapePropertyName } = __webpack_require__(0);
+const ArrayRep = __webpack_require__(9);
+const GripArrayRep = __webpack_require__(12);
+const GripMap = __webpack_require__(13);
+const GripMapEntryRep = __webpack_require__(14);
 
 const MAX_NUMERICAL_PROPERTIES = 100;
 
 const NODE_TYPES = {
   BUCKET: Symbol("[n…n]"),
   DEFAULT_PROPERTIES: Symbol("[default properties]"),
   ENTRIES: Symbol("<entries>"),
   GET: Symbol("<get>"),
@@ -2710,77 +2582,75 @@ module.exports = {
   shouldLoadItemSymbols,
   sortProperties,
   NODE_TYPES,
   // Export for testing purpose.
   SAFE_PATH_PREFIX
 };
 
 /***/ }),
-/* 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 { MODE } = __webpack_require__(2);
-const { REPS, getRep } = __webpack_require__(3);
-const ObjectInspector = __webpack_require__(42);
-const ObjectInspectorUtils = __webpack_require__(14);
+const { MODE } = __webpack_require__(3);
+const { REPS, getRep } = __webpack_require__(4);
+const ObjectInspector = __webpack_require__(45);
+const ObjectInspectorUtils = __webpack_require__(15);
 
 const {
   parseURLEncodedText,
   parseURLParams,
   maybeEscapePropertyName,
   getGripPreviewItems
-} = __webpack_require__(1);
+} = __webpack_require__(0);
 
 module.exports = {
   REPS,
   getRep,
   MODE,
   maybeEscapePropertyName,
   parseURLEncodedText,
   parseURLParams,
   getGripPreviewItems,
   ObjectInspector,
   ObjectInspectorUtils
 };
 
 /***/ }),
-/* 16 */
+/* 17 */
 /***/ (function(module, exports) {
 
 // removed by extract-text-webpack-plugin
 
 /***/ }),
-/* 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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
-
 const {
   getGripType,
   wrapRender
-} = __webpack_require__(1);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders undefined value
  */
 const Undefined = function () {
   return span({ className: "objectBox objectBox-undefined" }, "undefined");
 };
 
@@ -2795,33 +2665,30 @@ function supportsObject(object, noGrip =
 // Exports from this module
 
 module.exports = {
   rep: wrapRender(Undefined),
   supportsObject
 };
 
 /***/ }),
-/* 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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
-
-const { wrapRender } = __webpack_require__(1);
-
-// Shortcuts
-const { span } = React.DOM;
+const { wrapRender } = __webpack_require__(0);
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders null value
  */
 function Null(props) {
   return span({ className: "objectBox objectBox-null" }, "null");
 }
 
@@ -2840,47 +2707,48 @@ function supportsObject(object, noGrip =
 // Exports from this module
 
 module.exports = {
   rep: wrapRender(Null),
   supportsObject
 };
 
 /***/ }),
-/* 19 */
+/* 20 */
 /***/ (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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 const {
   escapeString,
   sanitizeString,
   isGrip,
   wrapRender
-} = __webpack_require__(1);
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders a long string grip.
  */
 LongStringRep.propTypes = {
-  useQuotes: React.PropTypes.bool,
-  escapeWhitespace: React.PropTypes.bool,
-  style: React.PropTypes.object,
-  cropLimit: React.PropTypes.number.isRequired,
-  member: React.PropTypes.string,
-  object: React.PropTypes.object.isRequired
+  useQuotes: PropTypes.bool,
+  escapeWhitespace: PropTypes.bool,
+  style: PropTypes.object,
+  cropLimit: PropTypes.number.isRequired,
+  member: PropTypes.string,
+  object: PropTypes.object.isRequired
 };
 
 function LongStringRep(props) {
   let {
     cropLimit,
     member,
     object,
     style,
@@ -2916,42 +2784,42 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(LongStringRep),
   supportsObject
 };
 
 /***/ }),
-/* 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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 
 const {
   getGripType,
   wrapRender
-} = __webpack_require__(1);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders a number
  */
 Number.propTypes = {
-  object: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.number, React.PropTypes.bool]).isRequired
+  object: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.bool]).isRequired
 };
 
 function Number(props) {
   let value = props.object;
 
   return span({ className: "objectBox objectBox-number" }, stringify(value));
 }
 
@@ -2968,47 +2836,48 @@ function supportsObject(object, noGrip =
 // Exports from this module
 
 module.exports = {
   rep: wrapRender(Number),
   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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 const {
   wrapRender
-} = __webpack_require__(1);
-const PropRep = __webpack_require__(4);
-const { MODE } = __webpack_require__(2);
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+const PropRep = __webpack_require__(5);
+const { MODE } = __webpack_require__(3);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 const DEFAULT_TITLE = "Object";
 
 /**
  * Renders an object. An object is represented by a list of its
  * properties enclosed in curly brackets.
  */
 ObjectRep.propTypes = {
-  object: React.PropTypes.object.isRequired,
+  object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
-  mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
-  title: React.PropTypes.string
+  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+  title: PropTypes.string
 };
 
 function ObjectRep(props) {
   let object = props.object;
   let propsArray = safePropIterator(props, object);
 
   if (props.mode === MODE.TINY) {
     const tinyModeItems = [];
@@ -3169,42 +3038,42 @@ function supportsObject(object) {
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(ObjectRep),
   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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 
 const {
   getGripType,
   wrapRender
-} = __webpack_require__(1);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders a symbol.
  */
 SymbolRep.propTypes = {
-  object: React.PropTypes.object.isRequired
+  object: PropTypes.object.isRequired
 };
 
 function SymbolRep(props) {
   let {
     className = "objectBox objectBox-symbol",
     object
   } = props;
   let { name } = object;
@@ -3218,42 +3087,42 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(SymbolRep),
   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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 
 const {
   getGripType,
   wrapRender
-} = __webpack_require__(1);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders a Infinity object
  */
 InfinityRep.propTypes = {
-  object: React.PropTypes.object.isRequired
+  object: PropTypes.object.isRequired
 };
 
 function InfinityRep(props) {
   const {
     object
   } = props;
 
   return span({ className: "objectBox objectBox-number" }, object.type);
@@ -3266,36 +3135,34 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(InfinityRep),
   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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
-
 const {
   getGripType,
   wrapRender
-} = __webpack_require__(1);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders a NaN object
  */
 function NaNRep(props) {
   return span({ className: "objectBox objectBox-nan" }, "NaN");
 }
 
@@ -3305,43 +3172,42 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(NaNRep),
   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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
+const dom = __webpack_require__(1);
+const PropTypes = __webpack_require__(2);
 const {
   wrapRender
-} = __webpack_require__(1);
-const { MODE } = __webpack_require__(2);
-// Shortcuts
-const {
-  span
-} = React.DOM;
+} = __webpack_require__(0);
+const { MODE } = __webpack_require__(3);
+const { span } = dom;
+
 /**
  * Renders an object. An object is represented by a list of its
  * properties enclosed in curly brackets.
  */
 Accessor.propTypes = {
-  object: React.PropTypes.object.isRequired,
-  mode: React.PropTypes.oneOf(Object.values(MODE))
+  object: PropTypes.object.isRequired,
+  mode: PropTypes.oneOf(Object.values(MODE))
 };
 
 function Accessor(props) {
   const {
     object
   } = props;
 
   const accessors = [];
@@ -3376,45 +3242,44 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(Accessor),
   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/. */
 
 // ReactJS
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 // Reps
 const {
   getGripType,
   isGrip,
   wrapRender
-} = __webpack_require__(1);
-const { rep: StringRep } = __webpack_require__(5);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+const { rep: StringRep } = __webpack_require__(7);
 
 /**
  * Renders DOM attribute
  */
 Attribute.propTypes = {
-  object: React.PropTypes.object.isRequired
+  object: PropTypes.object.isRequired
 };
 
 function Attribute(props) {
   let {
     object
   } = props;
   let value = object.preview.value;
 
@@ -3438,44 +3303,44 @@ function supportsObject(grip, noGrip = f
 }
 
 module.exports = {
   rep: wrapRender(Attribute),
   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/. */
 
 // ReactJS
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 
 // Reps
 const {
   getGripType,
   isGrip,
   wrapRender
-} = __webpack_require__(1);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Used to render JS built-in Date() object.
  */
 DateTime.propTypes = {
-  object: React.PropTypes.object.isRequired
+  object: PropTypes.object.isRequired
 };
 
 function DateTime(props) {
   let grip = props.object;
   let date;
   try {
     date = span({
       "data-link-actor-id": grip.actor,
@@ -3505,65 +3370,65 @@ function supportsObject(grip, noGrip = f
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(DateTime),
   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/. */
 
 // ReactJS
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 
 // Reps
 const {
   getGripType,
   isGrip,
   getURLDisplayString,
   wrapRender
-} = __webpack_require__(1);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders DOM document object.
  */
 Document.propTypes = {
-  object: React.PropTypes.object.isRequired
+  object: PropTypes.object.isRequired
 };
 
 function Document(props) {
   let grip = props.object;
-
+  const location = getLocation(grip);
   return span({
     "data-link-actor-id": grip.actor,
     className: "objectBox objectBox-document"
-  }, getTitle(grip), span({ className: "location" }, getLocation(grip)));
+  }, getTitle(grip), location ? " " : null, location ? span({ className: "location" }, location) : null);
 }
 
 function getLocation(grip) {
   let location = grip.preview.location;
-  return location ? getURLDisplayString(location) : "";
+  return location ? getURLDisplayString(location) : null;
 }
 
 function getTitle(grip) {
   return span({
     className: "objectTitle"
-  }, grip.class + " ");
+  }, grip.class);
 }
 
 // Registration
 function supportsObject(object, noGrip = false) {
   if (noGrip === true || !isGrip(object)) {
     return false;
   }
 
@@ -3572,48 +3437,48 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(Document),
   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/. */
 
 // ReactJS
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 
 // Reps
 const {
   isGrip,
   wrapRender
-} = __webpack_require__(1);
-
-const { MODE } = __webpack_require__(2);
-const { rep } = __webpack_require__(6);
+} = __webpack_require__(0);
+
+const { MODE } = __webpack_require__(3);
+const { rep } = __webpack_require__(8);
 
 /**
  * Renders DOM event objects.
  */
 Event.propTypes = {
-  object: React.PropTypes.object.isRequired,
+  object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
-  mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
-  onDOMNodeMouseOver: React.PropTypes.func,
-  onDOMNodeMouseOut: React.PropTypes.func,
-  onInspectIconClick: React.PropTypes.func
+  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+  onDOMNodeMouseOver: PropTypes.func,
+  onDOMNodeMouseOut: PropTypes.func,
+  onInspectIconClick: PropTypes.func
 };
 
 function Event(props) {
   // Use `Object.assign` to keep `props` without changes because:
   // 1. JSON.stringify/JSON.parse is slow.
   // 2. Immutable.js is planned for the future.
   let gripProps = Object.assign({}, props, {
     title: getTitle(props)
@@ -3679,47 +3544,47 @@ function supportsObject(grip, noGrip = f
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(Event),
   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/. */
 
 // ReactJS
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 
 // Reps
 const {
   getGripType,
   isGrip,
   cropString,
   wrapRender
-} = __webpack_require__(1);
-const { MODE } = __webpack_require__(2);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+const { MODE } = __webpack_require__(3);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * This component represents a template for Function objects.
  */
 FunctionRep.propTypes = {
-  object: React.PropTypes.object.isRequired,
-  parameterNames: React.PropTypes.array
+  object: PropTypes.object.isRequired,
+  parameterNames: PropTypes.array
 };
 
 function FunctionRep(props) {
   let grip = props.object;
 
   return span({
     "data-link-actor-id": grip.actor,
     className: "objectBox objectBox-function",
@@ -3785,63 +3650,64 @@ function supportsObject(grip, noGrip = f
 // Exports from this module
 
 module.exports = {
   rep: wrapRender(FunctionRep),
   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/. */
 
 // ReactJS
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 // Dependencies
 const {
   getGripType,
   isGrip,
   wrapRender
-} = __webpack_require__(1);
-
-const PropRep = __webpack_require__(4);
-const { MODE } = __webpack_require__(2);
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+
+const PropRep = __webpack_require__(5);
+const { MODE } = __webpack_require__(3);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders a DOM Promise object.
  */
 PromiseRep.propTypes = {
-  object: React.PropTypes.object.isRequired,
+  object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
-  mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
-  onDOMNodeMouseOver: React.PropTypes.func,
-  onDOMNodeMouseOut: React.PropTypes.func,
-  onInspectIconClick: React.PropTypes.func
+  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+  onDOMNodeMouseOver: PropTypes.func,
+  onDOMNodeMouseOut: PropTypes.func,
+  onInspectIconClick: PropTypes.func
 };
 
 function PromiseRep(props) {
   const object = props.object;
   const { promiseState } = object;
 
   const config = {
     "data-link-actor-id": object.actor,
     className: "objectBox objectBox-object"
   };
 
   if (props.mode === MODE.TINY) {
-    let { Rep } = __webpack_require__(3);
+    let { Rep } = __webpack_require__(4);
 
     return span(config, getTitle(object), span({
       className: "objectLeftBrace"
     }, " { "), Rep({ object: promiseState.state }), span({
       className: "objectRightBrace"
     }, " }"));
   }
 
@@ -3894,47 +3760,50 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(PromiseRep),
   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/. */
 
 // ReactJS
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 
 // Reps
 const {
   getGripType,
   isGrip,
   wrapRender
-} = __webpack_require__(1);
+} = __webpack_require__(0);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders a grip object with regular expression.
  */
 RegExp.propTypes = {
-  object: React.PropTypes.object.isRequired
+  object: PropTypes.object.isRequired
 };
 
 function RegExp(props) {
   let { object } = props;
 
-  return React.DOM.span({
+  return span({
     "data-link-actor-id": object.actor,
     className: "objectBox objectBox-regexp regexpSource"
   }, getSource(object));
 }
 
 function getSource(grip) {
   return grip.displayString;
 }
@@ -3950,45 +3819,45 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(RegExp),
   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/. */
 
 // ReactJS
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 
 // Reps
 const {
   getGripType,
   isGrip,
   getURLDisplayString,
   wrapRender
-} = __webpack_require__(1);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders a grip representing CSSStyleSheet
  */
 StyleSheet.propTypes = {
-  object: React.PropTypes.object.isRequired
+  object: PropTypes.object.isRequired
 };
 
 function StyleSheet(props) {
   let grip = props.object;
 
   return span({
     "data-link-actor-id": grip.actor,
     className: "objectBox objectBox-object"
@@ -4018,47 +3887,46 @@ function supportsObject(object, noGrip =
 // Exports from this module
 
 module.exports = {
   rep: wrapRender(StyleSheet),
   supportsObject
 };
 
 /***/ }),
-/* 34 */
+/* 35 */
 /***/ (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/. */
 
 // Dependencies
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 const {
   isGrip,
   cropString,
   cropMultipleLines,
   wrapRender
-} = __webpack_require__(1);
-const { MODE } = __webpack_require__(2);
-const nodeConstants = __webpack_require__(8);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+const { MODE } = __webpack_require__(3);
+const nodeConstants = __webpack_require__(10);
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders DOM comment node.
  */
 CommentNode.propTypes = {
-  object: React.PropTypes.object.isRequired,
+  object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
-  mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
+  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
 };
 
 function CommentNode(props) {
   let {
     object,
     mode = MODE.SHORT
   } = props;
 
@@ -4085,52 +3953,52 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(CommentNode),
   supportsObject
 };
 
 /***/ }),
-/* 35 */
+/* 36 */
 /***/ (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/. */
 
 // ReactJS
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 
 // Utils
 const {
   isGrip,
   wrapRender
-} = __webpack_require__(1);
-const { rep: StringRep } = __webpack_require__(5);
-const { MODE } = __webpack_require__(2);
-const nodeConstants = __webpack_require__(8);
-const Svg = __webpack_require__(9);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+const { rep: StringRep } = __webpack_require__(7);
+const { MODE } = __webpack_require__(3);
+const nodeConstants = __webpack_require__(10);
+const Svg = __webpack_require__(11);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders DOM element node.
  */
 ElementNode.propTypes = {
-  object: React.PropTypes.object.isRequired,
+  object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
-  mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
-  onDOMNodeMouseOver: React.PropTypes.func,
-  onDOMNodeMouseOut: React.PropTypes.func,
-  onInspectIconClick: React.PropTypes.func
+  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+  onDOMNodeMouseOver: PropTypes.func,
+  onDOMNodeMouseOut: PropTypes.func,
+  onInspectIconClick: PropTypes.func
 };
 
 function ElementNode(props) {
   let {
     object,
     mode,
     onDOMNodeMouseOver,
     onDOMNodeMouseOut,
@@ -4217,57 +4085,210 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(ElementNode),
   supportsObject
 };
 
 /***/ }),
-/* 36 */
+/* 37 */
+/***/ (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__(38);
+
+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
+};
+
+/***/ }),
+/* 38 */
+/***/ (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;
+}
+
+/***/ }),
+/* 39 */
 /***/ (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,3L12,3L12,7L14,7L14,8L12,8L12,12L8,12L8,14L7,14L7,12L3,12L3,8L1,8L1,7L3,7L3,3L7,3L7,1L8,1L8,3ZM10,10L10,5L5,5L5,10L10,10Z\"></path></svg>"
 
 /***/ }),
-/* 37 */
+/* 40 */
 /***/ (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/. */
 
 // ReactJS
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 
 // Reps
 const {
   isGrip,
   cropString,
   wrapRender
-} = __webpack_require__(1);
-const { MODE } = __webpack_require__(2);
-const Svg = __webpack_require__(9);
-
-// Shortcuts
-const DOM = React.DOM;
+} = __webpack_require__(0);
+const { MODE } = __webpack_require__(3);
+const Svg = __webpack_require__(11);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders DOM #text node.
  */
 TextNode.propTypes = {
-  object: React.PropTypes.object.isRequired,
+  object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
-  mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
-  onDOMNodeMouseOver: React.PropTypes.func,
-  onDOMNodeMouseOut: React.PropTypes.func,
-  onInspectIconClick: React.PropTypes.func
+  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+  onDOMNodeMouseOver: PropTypes.func,
+  onDOMNodeMouseOut: PropTypes.func,
+  onInspectIconClick: PropTypes.func
 };
 
 function TextNode(props) {
   let {
     object: grip,
     mode = MODE.SHORT,
     onDOMNodeMouseOver,
     onDOMNodeMouseOut,
@@ -4301,29 +4322,29 @@ function TextNode(props) {
         // TODO: Localize this with "openNodeInInspector" when Bug 1317038 lands
         title: "Click to select the node in the inspector",
         onClick: e => onInspectIconClick(grip, e)
       });
     }
   }
 
   if (mode === MODE.TINY) {
-    return DOM.span(baseConfig, getTitle(grip), inspectIcon);
-  }
-
-  return DOM.span(baseConfig, getTitle(grip), DOM.span({ className: "nodeValue" }, " ", `"${getTextContent(grip)}"`), inspectIcon);
+    return span(baseConfig, getTitle(grip), inspectIcon);
+  }
+
+  return span(baseConfig, getTitle(grip), span({ className: "nodeValue" }, " ", `"${getTextContent(grip)}"`), inspectIcon);
 }
 
 function getTextContent(grip) {
   return cropString(grip.preview.textContent);
 }
 
 function getTitle(grip) {
   const title = "#text";
-  return DOM.span({}, title);
+  return span({}, title);
 }
 
 // Registration
 function supportsObject(grip, noGrip = false) {
   if (noGrip === true || !isGrip(grip)) {
     return false;
   }
 
@@ -4332,46 +4353,46 @@ function supportsObject(grip, noGrip = f
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(TextNode),
   supportsObject
 };
 
 /***/ }),
-/* 38 */
+/* 41 */
 /***/ (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/. */
 
 // ReactJS
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 // Utils
 const {
   getGripType,
   isGrip,
   wrapRender
-} = __webpack_require__(1);
-const { MODE } = __webpack_require__(2);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+const { MODE } = __webpack_require__(3);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders Error objects.
  */
 ErrorRep.propTypes = {
-  object: React.PropTypes.object.isRequired,
+  object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
-  mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
+  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
 };
 
 function ErrorRep(props) {
   let object = props.object;
   let preview = object.preview;
   let name = preview && preview.name ? preview.name : "Error";
 
   let content = props.mode === MODE.TINY ? name : `${name}: ${preview.message}`;
@@ -4401,49 +4422,49 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(ErrorRep),
   supportsObject
 };
 
 /***/ }),
-/* 39 */
+/* 42 */
 /***/ (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/. */
 
 // ReactJS
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 
 // Reps
 const {
   getGripType,
   isGrip,
   getURLDisplayString,
   wrapRender
-} = __webpack_require__(1);
-
-const { MODE } = __webpack_require__(2);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+
+const { MODE } = __webpack_require__(3);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders a grip representing a window.
  */
 WindowRep.propTypes = {
   // @TODO Change this to Object.values once it's supported in Node's version of V8
-  mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
-  object: React.PropTypes.object.isRequired
+  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+  object: PropTypes.object.isRequired
 };
 
 function WindowRep(props) {
   let {
     mode,
     object
   } = props;
 
@@ -4479,43 +4500,43 @@ function supportsObject(object, noGrip =
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(WindowRep),
   supportsObject
 };
 
 /***/ }),
-/* 40 */
+/* 43 */
 /***/ (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/. */
 
 // ReactJS
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 
 // Reps
 const {
   isGrip,
   wrapRender
-} = __webpack_require__(1);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders a grip object with textual data.
  */
 ObjectWithText.propTypes = {
-  object: React.PropTypes.object.isRequired
+  object: PropTypes.object.isRequired
 };
 
 function ObjectWithText(props) {
   let grip = props.object;
   return span({
     "data-link-actor-id": grip.actor,
     className: "objectBox objectBox-" + getType(grip)
   }, span({ className: "objectPropValue" }, getDescription(grip)));
@@ -4540,44 +4561,44 @@ function supportsObject(grip, noGrip = f
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(ObjectWithText),
   supportsObject
 };
 
 /***/ }),
-/* 41 */
+/* 44 */
 /***/ (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/. */
 
 // ReactJS
-const React = __webpack_require__(0);
+const PropTypes = __webpack_require__(2);
 
 // Reps
 const {
   isGrip,
   getURLDisplayString,
   wrapRender
-} = __webpack_require__(1);
-
-// Shortcuts
-const { span } = React.DOM;
+} = __webpack_require__(0);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
 
 /**
  * Renders a grip object with URL data.
  */
 ObjectWithURL.propTypes = {
-  object: React.PropTypes.object.isRequired
+  object: PropTypes.object.isRequired
 };
 
 function ObjectWithURL(props) {
   let grip = props.object;
   return span({
     "data-link-actor-id": grip.actor,
     className: "objectBox objectBox-" + getType(grip)
   }, getTitle(grip), span({ className: "objectPropValue" }, getDescription(grip)));
@@ -4606,55 +4627,50 @@ function supportsObject(grip, noGrip = f
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(ObjectWithURL),
   supportsObject
 };
 
 /***/ }),
-/* 42 */
+/* 45 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
-var _devtoolsComponents = __webpack_require__(43);
+var _devtoolsComponents = __webpack_require__(46);
 
 var _devtoolsComponents2 = _interopRequireDefault(_devtoolsComponents);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
-
 /* 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,
-  DOM: dom,
-  PropTypes
-} = __webpack_require__(0);
+const { Component, createFactory } = __webpack_require__(6);
+const PropTypes = __webpack_require__(2);
+const dom = __webpack_require__(1);
 
 const Tree = createFactory(_devtoolsComponents2.default.Tree);
-__webpack_require__(47);
-
-const classnames = __webpack_require__(48);
+__webpack_require__(51);
+
+const classnames = __webpack_require__(52);
 
 const {
   REPS: {
     Rep,
     Grip
   }
-} = __webpack_require__(3);
+} = __webpack_require__(4);
 const {
   MODE
-} = __webpack_require__(2);
+} = __webpack_require__(3);
 
 const {
   getChildren,
   getClosestGripNode,
   getParent,
   getValue,
   nodeHasAccessors,
   nodeHasProperties,
@@ -4668,25 +4684,25 @@ const {
   nodeIsPrototype,
   nodeIsSetter,
   nodeIsWindow,
   shouldLoadItemEntries,
   shouldLoadItemIndexedProperties,
   shouldLoadItemNonIndexedProperties,
   shouldLoadItemPrototype,
   shouldLoadItemSymbols
-} = __webpack_require__(14);
+} = __webpack_require__(15);
 
 const {
   enumEntries,
   enumIndexedProperties,
   enumNonIndexedProperties,
   getPrototype,
   enumSymbols
-} = __webpack_require__(50);
+} = __webpack_require__(54);
 
 // This implements a component that renders an interactive inspector
 // for looking at JavaScript objects. It expects descriptions of
 // objects from the protocol, and will dynamically fetch child
 // properties as objects are expanded.
 //
 // If you want to inspect a single object, pass the name and the
 // protocol descriptor of it:
@@ -4732,18 +4748,17 @@ class ObjectInspector extends Component 
     self.getRoots = this.getRoots.bind(this);
   }
 
   shouldComponentUpdate(nextProps, nextState) {
     const {
       expandedPaths,
       loadedProperties
     } = this.state;
-
-    return expandedPaths.size !== nextState.expandedPaths.size || loadedProperties.size !== nextState.loadedProperties.size || [...expandedPaths].some(key => !nextState.expandedPaths.has(key));
+    return this.props.roots !== nextProps.roots || 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;
     }
 
@@ -4776,124 +4791,120 @@ class ObjectInspector extends Component 
 
   /**
    * This function is responsible for expanding/collapsing a given node,
    * which also means that it will check if we need to fetch properties,
    * entries, prototype and symbols for the said node. If we do, it will call
    * the appropriate ObjectClient functions, and change the state of the component
    * with the results it gets from those functions.
    */
-  setExpanded(item, expand) {
-    var _this = this;
-
-    return _asyncToGenerator(function* () {
-      if (nodeIsPrimitive(item)) {
-        return;
+  async setExpanded(item, expand) {
+    if (nodeIsPrimitive(item)) {
+      return;
+    }
+
+    const {
+      loadedProperties
+    } = this.state;
+
+    const key = this.getKey(item);
+
+    this.setState((prevState, props) => {
+      const newPaths = new Set(prevState.expandedPaths);
+      if (expand === true) {
+        newPaths.add(key);
+      } else {
+        newPaths.delete(key);
       }
-
-      const {
-        loadedProperties
-      } = _this.state;
-
-      const key = _this.getKey(item);
-
-      _this.setState(function (prevState, props) {
-        const newPaths = new Set(prevState.expandedPaths);
-        if (expand === true) {
-          newPaths.add(key);
-        } else {
-          newPaths.delete(key);
-        }
-        return {
-          expandedPaths: newPaths
-        };
-      });
-
-      if (expand === true) {
-        const gripItem = getClosestGripNode(item);
-        const value = getValue(gripItem);
-
-        const path = item.path;
-        const [start, end] = item.meta ? [item.meta.startIndex, item.meta.endIndex] : [];
-
-        let promises = [];
-        let objectClient;
-        const getObjectClient = function () {
-          if (objectClient) {
-            return objectClient;
-          }
-          return _this.props.createObjectClient(value);
-        };
-
-        if (shouldLoadItemIndexedProperties(item, loadedProperties)) {
-          promises.push(enumIndexedProperties(getObjectClient(), start, end));
-        }
-
-        if (shouldLoadItemNonIndexedProperties(item, loadedProperties)) {
-          promises.push(enumNonIndexedProperties(getObjectClient(), start, end));
-        }
-
-        if (shouldLoadItemEntries(item, loadedProperties)) {
-          promises.push(enumEntries(getObjectClient(), start, end));
+      return {
+        expandedPaths: newPaths
+      };
+    });
+
+    if (expand === true) {
+      const gripItem = getClosestGripNode(item);
+      const value = getValue(gripItem);
+
+      const path = item.path;
+      const [start, end] = item.meta ? [item.meta.startIndex, item.meta.endIndex] : [];
+
+      let promises = [];
+      let objectClient;
+      const getObjectClient = () => {
+        if (objectClient) {
+          return objectClient;
         }
-
-        if (shouldLoadItemPrototype(item, loadedProperties)) {
-          promises.push(getPrototype(getObjectClient()));
-        }
-
-        if (shouldLoadItemSymbols(item, loadedProperties)) {
-          promises.push(enumSymbols(getObjectClient(), start, end));
-        }
-
-        if (promises.length > 0) {
-          // Set the loading state with the pending promises.
-          _this.setState(function (prevState, props) {
-            const nextLoading = new Map(prevState.loading);
-            nextLoading.set(path, promises);
-            return {
-              loading: nextLoading
-            };
+        return this.props.createObjectClient(value);
+      };
+
+      if (shouldLoadItemIndexedProperties(item, loadedProperties)) {
+        promises.push(enumIndexedProperties(getObjectClient(), start, end));
+      }
+
+      if (shouldLoadItemNonIndexedProperties(item, loadedProperties)) {
+        promises.push(enumNonIndexedProperties(getObjectClient(), start, end));
+      }
+
+      if (shouldLoadItemEntries(item, loadedProperties)) {
+        promises.push(enumEntries(getObjectClient(), start, end));
+      }
+
+      if (shouldLoadItemPrototype(item, loadedProperties)) {
+        promises.push(getPrototype(getObjectClient()));
+      }
+
+      if (shouldLoadItemSymbols(item, loadedProperties)) {
+        promises.push(enumSymbols(getObjectClient(), start, end));
+      }
+
+      if (promises.length > 0) {
+        // Set the loading state with the pending promises.
+        this.setState((prevState, props) => {
+          const nextLoading = new Map(prevState.loading);
+          nextLoading.set(path, promises);
+          return {
+            loading: nextLoading
+          };
+        });
+
+        const responses = await Promise.all(promises);
+
+        // Let's loop through the responses to build a single response object.
+        const response = responses.reduce((accumulator, res) => {
+          Object.entries(res).forEach(([k, v]) => {
+            if (accumulator.hasOwnProperty(k)) {
+              if (Array.isArray(accumulator[k])) {
+                accumulator[k].push(...v);
+              } else if (typeof accumulator[k] === "object") {
+                accumulator[k] = Object.assign({}, accumulator[k], v);
+              }
+            } else {
+              accumulator[k] = v;
+            }
           });
-
-          const responses = yield Promise.all(promises);
-
-          // Let's loop through the responses to build a single response object.
-          const response = responses.reduce(function (accumulator, res) {
-            Object.entries(res).forEach(function ([k, v]) {
-              if (accumulator.hasOwnProperty(k)) {
-                if (Array.isArray(accumulator[k])) {
-                  accumulator[k].push(...v);
-                } else if (typeof accumulator[k] === "object") {
-                  accumulator[k] = Object.assign({}, accumulator[k], v);
-                }
-              } else {
-                accumulator[k] = v;
-              }
-            });
-            return accumulator;
-          }, {});
-
-          _this.setState(function (prevState, props) {
-            const nextLoading = new Map(prevState.loading);
-            nextLoading.delete(path);
-
-            const isRoot = _this.props.roots.some(function (root) {
-              const rootValue = getValue(root);
-              return rootValue && rootValue.actor === value.actor;
-            });
-
-            return {
-              actors: isRoot ? prevState.actors : new Set(prevState.actors).add(value.actor),
-              loadedProperties: new Map(prevState.loadedProperties).set(path, response),
-              loading: nextLoading
-            };
+          return accumulator;
+        }, {});
+
+        this.setState((prevState, props) => {
+          const nextLoading = new Map(prevState.loading);
+          nextLoading.delete(path);
+
+          const isRoot = this.props.roots.some(root => {
+            const rootValue = getValue(root);
+            return rootValue && rootValue.actor === value.actor;
           });
-        }
+
+          return {
+            actors: isRoot ? prevState.actors : new Set(prevState.actors).add(value.actor),
+            loadedProperties: new Map(prevState.loadedProperties).set(path, response),
+            loading: nextLoading
+          };
+        });
       }
-    })();
+    }
   }
 
   focusItem(item) {
     if (!this.props.disabledFocus && this.state.focusedItem !== item) {
       this.setState({
         focusedItem: item
       });
 
@@ -5052,68 +5063,68 @@ ObjectInspector.propTypes = {
   onFocus: PropTypes.func,
   onDoubleClick: PropTypes.func,
   onLabelClick: PropTypes.func
 };
 
 module.exports = ObjectInspector;
 
 /***/ }),
-/* 43 */
+/* 46 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
-var _tree = __webpack_require__(44);
+var _tree = __webpack_require__(47);
 
 var _tree2 = _interopRequireDefault(_tree);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 exports.default = {
   Tree: _tree2.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/. */
 
 /***/ }),
-/* 44 */
+/* 47 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
-var _react = __webpack_require__(0);
+var _react = __webpack_require__(6);
 
 var _react2 = _interopRequireDefault(_react);
 
-var _svgInlineReact = __webpack_require__(10);
+var _svgInlineReact = __webpack_require__(48);
 
 var _svgInlineReact2 = _interopRequireDefault(_svgInlineReact);
 
-var _arrow = __webpack_require__(45);
+var _arrow = __webpack_require__(49);
 
 var _arrow2 = _interopRequireDefault(_arrow);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 const { DOM: dom, createClass, createFactory, createElement, PropTypes } = _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__(46);
+__webpack_require__(50);
 
 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.
  */
 const ArrowExpander = createFactory(createClass({
@@ -5479,22 +5490,31 @@ const Tree = createClass({
   getInitialState() {
     return {
       seen: new Set()
     };
   },
 
   componentDidMount() {
     this._autoExpand();
+    if (this.props.focused) {
+      this._scrollNodeIntoView(this.props.focused);
+    }
   },
 
   componentWillReceiveProps(nextProps) {
     this._autoExpand();
   },
 
+  componentDidUpdate(prevProps, prevState) {
+    if (prevProps.focused !== this.props.focused) {
+      this._scrollNodeIntoView(this.props.focused);
+    }
+  },
+
   _autoExpand() {
     if (!this.props.autoExpandDepth) {
       return;
     }
 
     // Automatically expand the first autoExpandDepth levels for new items. Do
     // not use the usual DFS infrastructure because we don't want to ignore
     // collapsed nodes.
@@ -5612,52 +5632,76 @@ const Tree = createClass({
     if (this.props.onCollapse) {
       this.props.onCollapse(item);
     }
   }),
 
   /**
    * Sets the passed in item to be the focused item.
    *
-   * @param {Number} index
-   *        The index of the item in a full DFS traversal (ignoring collapsed
-   *        nodes). Ignored if `item` is undefined.
-   *
    * @param {Object|undefined} item
    *        The item to be focused, or undefined to focus no item.
+   *
+   * @param {Object|undefined} options
+   *        An options object which can contain:
+   *          - dir: "up" or "down" to indicate if we should scroll the element to the
+   *                 top or the bottom of the scrollable container when the element is
+   *                 off canvas.
    */
-  _focus(index, item) {
-    // TODO: Revisit how we should handle focus without having fixed item height.
-    // if (item !== undefined) {
-    //   const itemStartPosition = index * this.props.itemHeight;
-    //   const itemEndPosition = (index + 1) * this.props.itemHeight;
-
-    //   // Note that if the height of the viewport (this.state.height) is less than
-    //   // `this.props.itemHeight`, we could accidentally try and scroll both up and
-    //   // down in a futile attempt to make both the item's start and end positions
-    //   // visible. Instead, give priority to the start of the item by checking its
-    //   // position first, and then using an "else if", rather than a separate "if",
-    //   // for the end position.
-    //   if (this.state.scroll > itemStartPosition) {
-    //     this.refs.tree.scrollTop = itemStartPosition;
-    //   } else if ((this.state.scroll + this.state.height) < itemEndPosition) {
-    //     this.refs.tree.scrollTop = itemEndPosition - this.state.height;
-    //   }
-    // }
-
+  _focus(item, options) {
+    this._scrollNodeIntoView(item, options);
     if (this.props.onFocus) {
       this.props.onFocus(item);
     }
   },
 
   /**
+   * Sets the passed in item to be the focused item.
+   *
+   * @param {Object|undefined} item
+   *        The item to be scrolled to.
+   *
+   * @param {Object|undefined} options
+   *        An options object which can contain:
+   *          - dir: "up" or "down" to indicate if we should scroll the element to the
+   *                 top or the bottom of the scrollable container when the element is
+   *                 off canvas.
+   */
+  _scrollNodeIntoView(item, options = {}) {
+    if (item !== undefined) {
+      const treeElement = this.refs.tree;
+      const element = document.getElementById(this.props.getKey(item));
+      if (element) {
+        const { top, bottom } = element.getBoundingClientRect();
+        const closestScrolledParent = node => {
+          if (node == null) {
+            return null;
+          }
+
+          if (node.scrollHeight > node.clientHeight) {
+            return node;
+          }
+          return closestScrolledParent(node.parentNode);
+        };
+        const scrolledParent = closestScrolledParent(treeElement);
+        const isVisible = !scrolledParent || top >= 0 && bottom <= scrolledParent.clientHeight;
+
+        if (!isVisible) {
+          let scrollToTop = !options.alignTo && top < 0 || options.alignTo === "top";
+          element.scrollIntoView(scrollToTop);
+        }
+      }
+    }
+  },
+
+  /**
    * Sets the state to have no focused item.
    */
   _onBlur() {
-    this._focus(0, undefined);
+    this._focus(undefined);
   },
 
   /**
    * Handles key down events in the tree's container.
    *
    * @param {Event} e
    */
   _onKeyDown(e) {
@@ -5685,98 +5729,95 @@ const Tree = createClass({
         if (this.props.isExpanded(this.props.focused) && this._nodeIsExpandable(this.props.focused)) {
           this._onCollapse(this.props.focused);
         } else {
           this._focusParentNode();
         }
         return;
 
       case "ArrowRight":
-        if (!this.props.isExpanded(this.props.focused)) {
+        if (this._nodeIsExpandable(this.props.focused) && !this.props.isExpanded(this.props.focused)) {
           this._onExpand(this.props.focused);
         } else {
           this._focusNextNode();
         }
     }
   },
 
   /**
    * Sets the previous node relative to the currently focused item, to focused.
    */
   _focusPrevNode: oncePerAnimationFrame(function () {
     // Start a depth first search and keep going until we reach the currently
     // focused node. Focus the previous node in the DFS, if it exists. If it
     // doesn't exist, we're at the first node already.
 
     let prev;
-    let prevIndex;
 
     const traversal = this._dfsFromRoots();
     const length = traversal.length;
     for (let i = 0; i < length; i++) {
       const item = traversal[i].item;
       if (item === this.props.focused) {
         break;
       }
       prev = item;
-      prevIndex = i;
     }
-
     if (prev === undefined) {
       return;
     }
 
-    this._focus(prevIndex, prev);
+    this._focus(prev, { alignTo: "top" });
   }),
 
   /**
    * Handles the down arrow key which will focus either the next child
    * or sibling row.
    */
   _focusNextNode: oncePerAnimationFrame(function () {
     // Start a depth first search and keep going until we reach the currently
     // focused node. Focus the next node in the DFS, if it exists. If it
     // doesn't exist, we're at the last node already.
-
     const traversal = this._dfsFromRoots();
     const length = traversal.length;
     let i = 0;
 
     while (i < length) {
       if (traversal[i].item === this.props.focused) {
         break;
       }
       i++;
     }
 
     if (i + 1 < traversal.length) {
-      this._focus(i + 1, traversal[i + 1].item);
+      this._focus(traversal[i + 1].item, { alignTo: "bottom" });
     }
   }),
 
   /**
    * Handles the left arrow key, going back up to the current rows'
    * parent row.
    */
   _focusParentNode: oncePerAnimationFrame(function () {
     const parent = this.props.getParent(this.props.focused);
     if (!parent) {
+      this._focusPrevNode(this.props.focused);
       return;
     }
 
     const traversal = this._dfsFromRoots();
     const length = traversal.length;
     let parentIndex = 0;
     for (; parentIndex < length; parentIndex++) {
       if (traversal[parentIndex].item === parent) {
         break;
       }
     }
 
-    this._focus(parentIndex, parent);
+    this._focus(parent, { alignTo: "top" });
   }),
 
   _nodeIsExpandable: function (item) {
     return this.props.isExpandable ? this.props.isExpandable(item) : !!this.props.getChildren(item).length;
   },
 
   render() {
     const traversal = this._dfsFromRoots();
@@ -5795,17 +5836,17 @@ const Tree = createClass({
         depth,
         renderItem: this.props.renderItem,
         focused: focused === item,
         expanded: this.props.isExpanded(item),
         isExpandable: this._nodeIsExpandable(item),
         onExpand: this._onExpand,
         onCollapse: this._onCollapse,
         onClick: e => {
-          this._focus(i, item);
+          this._focus(item);
           if (this.props.isExpanded(item)) {
             this.props.onCollapse(item);
           } else {
             this.props.onExpand(item, e.altKey);
           }
         }
       });
     });
@@ -5828,17 +5869,17 @@ const Tree = createClass({
           return;
         }
 
         let { explicitOriginalTarget } = nativeEvent;
         // Only set default focus to the first tree node if the focus came
         // from outside the tree (e.g. by tabbing to the tree from other
         // external elements).
         if (explicitOriginalTarget !== this.refs.tree && !this.refs.tree.contains(explicitOriginalTarget)) {
-          this._focus(0, traversal[0].item);
+          this._focus(traversal[0].item);
         }
       },
       onBlur: this._onBlur,
       onClick: () => {
         // Focus should always remain on the tree container itself.
         this.refs.tree.focus();
       },
       "aria-label": this.props.label,
@@ -5847,35 +5888,236 @@ const Tree = createClass({
       style
     }, nodes);
   }
 });
 
 exports.default = Tree;
 
 /***/ }),
-/* 45 */
+/* 48 */
+/***/ (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 _get = function get(_x, _x2, _x3) {
+    var _again = true;_function: while (_again) {
+        var object = _x,
+            property = _x2,
+            receiver = _x3;_again = false;if (object === null) object = Function.prototype;var desc = Object.getOwnPropertyDescriptor(object, property);if (desc === undefined) {
+            var parent = Object.getPrototypeOf(object);if (parent === null) {
+                return undefined;
+            } else {
+                _x = parent;_x2 = property;_x3 = receiver;_again = true;desc = parent = undefined;continue _function;
+            }
+        } else if ('value' in desc) {
+            return desc.value;
+        } else {
+            var getter = desc.get;if (getter === undefined) {
+                return undefined;
+            }return getter.call(receiver);
+        }
+    }
+};
+
+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 _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 _react = __webpack_require__(6);
+
+var _react2 = _interopRequireDefault(_react);
+
+var DOMParser = typeof window !== 'undefined' && window.DOMParser;
+var process = process || {};
+process.env = process.env || {};
+var parserAvailable = typeof DOMParser !== 'undefined' && DOMParser.prototype != null && DOMParser.prototype.parseFromString != null;
+
+function isParsable(src) {
+    // kinda naive but meh, ain't gonna use full-blown parser for this
+    return parserAvailable && typeof src === 'string' && src.trim().substr(0, 4) === '<svg';
+}
+
+// parse SVG string using `DOMParser`
+function parseFromSVGString(src) {
+    var parser = new DOMParser();
+    return parser.parseFromString(src, "image/svg+xml");
+}
+
+// Transform DOM prop/attr names applicable to `<svg>` element but react-limited
+function switchSVGAttrToReactProp(propName) {
+    switch (propName) {
+        case 'class':
+            return 'className';
+        default:
+            return propName;
+    }
+}
+
+var InlineSVG = function (_React$Component) {
+    _inherits(InlineSVG, _React$Component);
+
+    _createClass(InlineSVG, null, [{
+        key: 'defaultProps',
+        value: {
+            element: 'i',
+            raw: false,
+            src: ''
+        },
+        enumerable: true
+    }, {
+        key: 'propTypes',
+        value: {
+            src: _react2['default'].PropTypes.string.isRequired,
+            element: _react2['default'].PropTypes.string,
+            raw: _react2['default'].PropTypes.bool
+        },
+        enumerable: true
+    }]);
+
+    function InlineSVG(props) {
+        _classCallCheck(this, InlineSVG);
+
+        _get(Object.getPrototypeOf(InlineSVG.prototype), 'constructor', this).call(this, props);
+        this._extractSVGProps = this._extractSVGProps.bind(this);
+    }
+
+    // Serialize `Attr` objects in `NamedNodeMap`
+
+    _createClass(InlineSVG, [{
+        key: '_serializeAttrs',
+        value: function _serializeAttrs(map) {
+            var ret = {};
+            var prop = undefined;
+            for (var i = 0; i < map.length; i++) {
+                prop = switchSVGAttrToReactProp(map[i].name);
+                ret[prop] = map[i].value;
+            }
+            return ret;
+        }
+
+        // get <svg /> element props
+    }, {
+        key: '_extractSVGProps',
+        value: function _extractSVGProps(src) {
+            var map = parseFromSVGString(src).documentElement.attributes;
+            return map.length > 0 ? this._serializeAttrs(map) : null;
+        }
+
+        // get content inside <svg> element.
+    }, {
+        key: '_stripSVG',
+        value: function _stripSVG(src) {
+            return parseFromSVGString(src).documentElement.innerHTML;
+        }
+    }, {
+        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 = undefined,
+                __html = undefined,
+                svgProps = undefined;
+            var _props = this.props;
+            var element = _props.element;
+            var raw = _props.raw;
+            var src = _props.src;
+
+            var otherProps = _objectWithoutProperties(_props, ['element', 'raw', 'src']);
+
+            if (raw === true && isParsable(src)) {
+                Element = 'svg';
+                svgProps = this._extractSVGProps(src);
+                __html = this._stripSVG(src);
+            }
+            __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;
+module.exports = exports['default'];
+
+/***/ }),
+/* 49 */
 /***/ (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>"
 
 /***/ }),
-/* 46 */
+/* 50 */
 /***/ (function(module, exports) {
 
 // removed by extract-text-webpack-plugin
 
 /***/ }),
-/* 47 */
+/* 51 */
 /***/ (function(module, exports) {
 
 // removed by extract-text-webpack-plugin
 
 /***/ }),
-/* 48 */
+/* 52 */
 /***/ (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 */
@@ -5920,113 +6162,81 @@ var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBP
 				__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
 	} else {
 		window.classNames = classNames;
 	}
 }());
 
 
 /***/ }),
-/* 49 */
+/* 53 */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_49__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_53__;
 
 /***/ }),
-/* 50 */
+/* 54 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
-let enumIndexedProperties = (() => {
-  var _ref = _asyncToGenerator(function* (objectClient, start, end) {
-    try {
-      const { iterator } = yield objectClient.enumProperties({ ignoreNonIndexedProperties: true });
-      const response = yield iteratorSlice(iterator, start, end);
-      return response;
-    } catch (e) {
-      console.error("Error in enumIndexedProperties", e);
-      return {};
-    }
-  });
-
-  return function enumIndexedProperties(_x, _x2, _x3) {
-    return _ref.apply(this, arguments);
-  };
-})(); /* 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/. */
-
-let enumNonIndexedProperties = (() => {
-  var _ref2 = _asyncToGenerator(function* (objectClient, start, end) {
-    try {
-      const { iterator } = yield objectClient.enumProperties({ ignoreIndexedProperties: true });
-      const response = yield iteratorSlice(iterator, start, end);
-      return response;
-    } catch (e) {
-      console.error("Error in enumNonIndexedProperties", e);
-      return {};
-    }
-  });
-
-  return function enumNonIndexedProperties(_x4, _x5, _x6) {
-    return _ref2.apply(this, arguments);
-  };
-})();
-
-let enumEntries = (() => {
-  var _ref3 = _asyncToGenerator(function* (objectClient, start, end) {
-    try {
-      const { iterator } = yield objectClient.enumEntries();
-      const response = yield iteratorSlice(iterator, start, end);
-      return response;
-    } catch (e) {
-      console.error("Error in enumEntries", e);
-      return {};
-    }
-  });
-
-  return function enumEntries(_x7, _x8, _x9) {
-    return _ref3.apply(this, arguments);
-  };
-})();
-
-let enumSymbols = (() => {
-  var _ref4 = _asyncToGenerator(function* (objectClient, start, end) {
-    try {
-      const { iterator } = yield objectClient.enumSymbols();
-      const response = yield iteratorSlice(iterator, start, end);
-      return response;
-    } catch (e) {
-      console.error("Error in enumSymbols", e);
-      return {};
-    }
-  });
-
-  return function enumSymbols(_x10, _x11, _x12) {
-    return _ref4.apply(this, arguments);
-  };
-})();
-
-let getPrototype = (() => {
-  var _ref5 = _asyncToGenerator(function* (objectClient) {
-    if (typeof objectClient.getPrototype !== "function") {
-      console.error("objectClient.getPrototype is not a function");
-      return Promise.resolve({});
-    }
-    return objectClient.getPrototype();
-  });
-
-  return function getPrototype(_x13) {
-    return _ref5.apply(this, arguments);
-  };
-})();
-
-function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
+async function enumIndexedProperties(objectClient, start, end) {
+  try {
+    const { iterator } = await objectClient.enumProperties({ ignoreNonIndexedProperties: true });
+    const response = await iteratorSlice(iterator, start, end);
+    return response;
+  } catch (e) {
+    console.error("Error in enumIndexedProperties", e);
+    return {};
+  }
+} /* 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/. */
+
+async function enumNonIndexedProperties(objectClient, start, end) {
+  try {
+    const { iterator } = await objectClient.enumProperties({ ignoreIndexedProperties: true });
+    const response = await iteratorSlice(iterator, start, end);
+    return response;
+  } catch (e) {
+    console.error("Error in enumNonIndexedProperties", e);
+    return {};
+  }
+}
+
+async function enumEntries(objectClient, start, end) {
+  try {
+    const { iterator } = await objectClient.enumEntries();
+    const response = await iteratorSlice(iterator, start, end);
+    return response;
+  } catch (e) {
+    console.error("Error in enumEntries", e);
+    return {};
+  }
+}
+
+async function enumSymbols(objectClient, start, end) {
+  try {
+    const { iterator } = await objectClient.enumSymbols();
+    const response = await iteratorSlice(iterator, start, end);
+    return response;
+  } catch (e) {
+    console.error("Error in enumSymbols", e);
+    return {};
+  }
+}
+
+async function getPrototype(objectClient) {
+  if (typeof objectClient.getPrototype !== "function") {
+    console.error("objectClient.getPrototype is not a function");
+    return Promise.resolve({});
+  }
+  return objectClient.getPrototype();
+}
 
 function iteratorSlice(iterator, start, end) {
   start = start || 0;
   const count = end ? end - start + 1 : iterator.count;
   return iterator.slice(start, count);
 }
 
 module.exports = {
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -815,16 +815,17 @@ a.learn-more-link.webconsole-learn-more-
   ------------------------------------------------------------------------------------
   |                                X items hidden by filters  - Reset Filters button |
   ------------------------------------------------------------------------------------
   | Error - Warning - Log - Info - Debug - CSS - Network - XHR                       |
   ------------------------------------------------------------------------------------
 */
 .webconsole-filteringbar-wrapper {
   display: flex;
+  grid-row: 1 / 2;
   /* Wrap so the "Hidden messages" bar can go to its own row if needed */
   flex-wrap: wrap;
   --primary-toolbar-height: 29px;
 }
 
 .webconsole-filterbar-primary {
   display: flex;
   /* We want the toolbar (which contain the text search input) to fit
@@ -1132,16 +1133,17 @@ a.learn-more-link.webconsole-learn-more-
 /* Layout */
 
 .webconsole-output {
   flex: 1;
   direction: ltr;
   overflow: auto;
   -moz-user-select: text;
   position: relative;
+  grid-row: 2 / 3;
 }
 
 html,
 body {
   height: 100%;
   margin: 0;
   padding: 0;
 }
@@ -1157,18 +1159,19 @@ body {
 }
 
 body #output-container {
   flex: 1;
   overflow: hidden;
 }
 
 .webconsole-output-wrapper {
-  display: flex;
-  flex-direction: column;
+  display: grid;
+  grid-template-columns: 1fr auto;
+  grid-template-rows: auto 1fr;
   height: 100%;
 }
 
 /* Object Inspector */
 .webconsole-output-wrapper .object-inspector.tree {
   /*
    * Make the arrow the same color and approximately the same size of the twisty icon.
    * Should be properly fixed in https://bugzilla.mozilla.org/show_bug.cgi?id=1307937.
@@ -1180,8 +1183,16 @@ body #output-container {
   --tree-indent-border-color: var(--console-output-indent-border-color);
   --tree-node-hover-background-color : var(--object-inspector-hover-background);
   display: inline-block;
 }
 
 .theme-dark .webconsole-output-wrapper .object-inspector.tree {
   --arrow-fill-color: #7F7E81;
 }
+
+.sidebar {
+  display: flex;
+  grid-row: 1 / -1;
+  grid-column: -1 / -2;
+  min-width: 200px;
+  background-color: var(--theme-sidebar-background);
+}
--- a/devtools/client/webconsole/local-dev/index.js
+++ b/devtools/client/webconsole/local-dev/index.js
@@ -40,16 +40,17 @@ pref("devtools.webconsole.filter.debug",
 pref("devtools.webconsole.filter.css", false);
 pref("devtools.webconsole.filter.net", false);
 pref("devtools.webconsole.filter.netxhr", false);
 pref("devtools.webconsole.ui.filterbar", false);
 pref("devtools.webconsole.inputHistoryCount", 50);
 pref("devtools.webconsole.persistlog", false);
 pref("devtools.webconsole.timestampMessages", false);
 pref("devtools.webconsole.autoMultiline", true);
+pref("devtools.webconsole.sidebarToggle", true);
 
 const NewConsoleOutputWrapper = require("../new-console-output/new-console-output-wrapper");
 const NewWebConsoleFrame = require("../new-webconsole").NewWebConsoleFrame;
 
 // Copied from netmonitor/index.js:
 window.addEventListener("DOMContentLoaded", () => {
   for (let link of document.head.querySelectorAll("link")) {
     link.href = link.href.replace(/(resource|chrome)\:\/\//, "/");
--- a/devtools/client/webconsole/new-console-output/actions/ui.js
+++ b/devtools/client/webconsole/new-console-output/actions/ui.js
@@ -10,16 +10,17 @@ const { getAllUi } = require("devtools/c
 const Services = require("Services");
 
 const {
   FILTER_BAR_TOGGLE,
   INITIALIZE,
   PERSIST_TOGGLE,
   PREFS,
   SELECT_NETWORK_MESSAGE_TAB,
+  SIDEBAR_TOGGLE,
   TIMESTAMPS_TOGGLE,
 } = require("devtools/client/webconsole/new-console-output/constants");
 
 function filterBarToggle(show) {
   return (dispatch, getState) => {
     dispatch({
       type: FILTER_BAR_TOGGLE,
     });
@@ -53,15 +54,22 @@ function selectNetworkMessageTab(id) {
 }
 
 function initialize() {
   return {
     type: INITIALIZE
   };
 }
 
+function sidebarToggle(show) {
+  return {
+    type: SIDEBAR_TOGGLE,
+  };
+}
+
 module.exports = {
   filterBarToggle,
   initialize,
   persistToggle,
   selectNetworkMessageTab,
+  sidebarToggle,
   timestampsToggle,
 };
--- a/devtools/client/webconsole/new-console-output/components/FilterBar.js
+++ b/devtools/client/webconsole/new-console-output/components/FilterBar.js
@@ -27,23 +27,25 @@ class FilterBar extends Component {
       dispatch: PropTypes.func.isRequired,
       filter: PropTypes.object.isRequired,
       serviceContainer: PropTypes.shape({
         attachRefToHud: PropTypes.func.isRequired,
       }).isRequired,
       filterBarVisible: PropTypes.bool.isRequired,
       persistLogs: PropTypes.bool.isRequired,
       filteredMessagesCount: PropTypes.object.isRequired,
+      sidebarToggle: PropTypes.bool,
     };
   }
 
   constructor(props) {
     super(props);
     this.onClickMessagesClear = this.onClickMessagesClear.bind(this);
     this.onClickFilterBarToggle = this.onClickFilterBarToggle.bind(this);
+    this.onClickSidebarToggle = this.onClickSidebarToggle.bind(this);
     this.onClickRemoveAllFilters = this.onClickRemoveAllFilters.bind(this);
     this.onClickRemoveTextFilter = this.onClickRemoveTextFilter.bind(this);
     this.onSearchInput = this.onSearchInput.bind(this);
     this.onChangePersistToggle = this.onChangePersistToggle.bind(this);
     this.renderFiltersConfigBar = this.renderFiltersConfigBar.bind(this);
     this.renderFilteredMessagesBar = this.renderFilteredMessagesBar.bind(this);
   }
 
@@ -80,16 +82,20 @@ class FilterBar extends Component {
   onClickMessagesClear() {
     this.props.dispatch(actions.messagesClear());
   }
 
   onClickFilterBarToggle() {
     this.props.dispatch(actions.filterBarToggle());
   }
 
+  onClickSidebarToggle() {
+    this.props.dispatch(actions.sidebarToggle());
+  }
+
   onClickRemoveAllFilters() {
     this.props.dispatch(actions.defaultFiltersReset());
   }
 
   onClickRemoveTextFilter() {
     this.props.dispatch(actions.filterTextSet(""));
   }
 
@@ -215,16 +221,17 @@ class FilterBar extends Component {
   }
 
   render() {
     const {
       filter,
       filterBarVisible,
       persistLogs,
       filteredMessagesCount,
+      sidebarToggle,
     } = this.props;
 
     let children = [
       dom.div({
         className: "devtools-toolbar webconsole-filterbar-primary",
         key: "primary-bar",
       },
         dom.button({
@@ -248,17 +255,24 @@ class FilterBar extends Component {
           placeholder: l10n.getStr("webconsole.filterInput.placeholder"),
           onInput: this.onSearchInput
         }),
         FilterCheckbox({
           label: l10n.getStr("webconsole.enablePersistentLogs.label"),
           title: l10n.getStr("webconsole.enablePersistentLogs.tooltip"),
           onChange: this.onChangePersistToggle,
           checked: persistLogs,
-        })
+        }),
+        sidebarToggle ?
+          dom.button({
+            className: "devtools-button",
+            title: l10n.getStr("webconsole.toggleFilterButton.tooltip"),
+            onClick: this.onClickSidebarToggle
+          }, "Toggle Sidebar")
+          : null,
       )
     ];
 
     if (filteredMessagesCount.global > 0) {
       children.push(this.renderFilteredMessagesBar());
     }
 
     if (filterBarVisible) {
@@ -279,12 +293,13 @@ class FilterBar extends Component {
 
 function mapStateToProps(state) {
   let uiState = getAllUi(state);
   return {
     filter: getAllFilters(state),
     filterBarVisible: uiState.filterBarVisible,
     persistLogs: uiState.persistLogs,
     filteredMessagesCount: getFilteredMessagesCount(state),
+    sidebarToggle: state.prefs.sidebarToggle,
   };
 }
 
 module.exports = connect(mapStateToProps)(FilterBar);
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/SideBar.js
@@ -0,0 +1,39 @@
+/* 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 { Component } = 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");
+
+class SideBar extends Component {
+  static get propTypes() {
+    return {
+      sidebarVisible: PropTypes.bool
+    };
+  }
+
+  render() {
+    let {
+      sidebarVisible,
+    } = this.props;
+
+    return (
+      sidebarVisible ?
+        dom.aside({
+          className: "sidebar",
+        }, "Sidebar WIP")
+        : null
+    );
+  }
+}
+
+function mapStateToProps(state, props) {
+  return {
+    sidebarVisible: state.ui.sidebarVisible,
+  };
+}
+
+module.exports = connect(mapStateToProps)(SideBar);
--- a/devtools/client/webconsole/new-console-output/components/moz.build
+++ b/devtools/client/webconsole/new-console-output/components/moz.build
@@ -14,10 +14,11 @@ DevToolsModules(
     'FilterBar.js',
     'FilterButton.js',
     'FilterCheckbox.js',
     'GripMessageBody.js',
     'Message.js',
     'MessageContainer.js',
     'MessageIcon.js',
     'MessageIndent.js',
-    'MessageRepeat.js'
+    'MessageRepeat.js',
+    'SideBar.js'
 )
--- a/devtools/client/webconsole/new-console-output/constants.js
+++ b/devtools/client/webconsole/new-console-output/constants.js
@@ -19,16 +19,17 @@ const actionTypes = {
   MESSAGE_TABLE_RECEIVE: "MESSAGE_TABLE_RECEIVE",
   MESSAGES_ADD: "MESSAGES_ADD",
   MESSAGES_CLEAR: "MESSAGES_CLEAR",
   NETWORK_MESSAGE_UPDATE: "NETWORK_MESSAGE_UPDATE",
   NETWORK_UPDATE_REQUEST: "NETWORK_UPDATE_REQUEST",
   PERSIST_TOGGLE: "PERSIST_TOGGLE",
   REMOVED_ACTORS_CLEAR: "REMOVED_ACTORS_CLEAR",
   SELECT_NETWORK_MESSAGE_TAB: "SELECT_NETWORK_MESSAGE_TAB",
+  SIDEBAR_TOGGLE: "SIDEBAR_TOGGLE",
   TIMESTAMPS_TOGGLE: "TIMESTAMPS_TOGGLE",
 };
 
 const prefs = {
   PREFS: {
     FILTER: {
       ERROR: "devtools.webconsole.filter.error",
       WARN: "devtools.webconsole.filter.warn",
@@ -37,16 +38,17 @@ const prefs = {
       DEBUG: "devtools.webconsole.filter.debug",
       CSS: "devtools.webconsole.filter.css",
       NET: "devtools.webconsole.filter.net",
       NETXHR: "devtools.webconsole.filter.netxhr",
     },
     UI: {
       FILTER_BAR: "devtools.webconsole.ui.filterbar",
       PERSIST: "devtools.webconsole.persistlog",
+      SIDEBAR_TOGGLE: "devtools.webconsole.sidebarToggle",
     }
   }
 };
 
 const FILTERS = {
   CSS: "css",
   DEBUG: "debug",
   ERROR: "error",
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -10,16 +10,17 @@ const { Provider } = require("devtools/c
 
 const actions = require("devtools/client/webconsole/new-console-output/actions/index");
 const { createContextMenu } = require("devtools/client/webconsole/new-console-output/utils/context-menu");
 const { configureStore } = require("devtools/client/webconsole/new-console-output/store");
 
 const EventEmitter = require("devtools/shared/old-event-emitter");
 const ConsoleOutput = createFactory(require("devtools/client/webconsole/new-console-output/components/ConsoleOutput"));
 const FilterBar = createFactory(require("devtools/client/webconsole/new-console-output/components/FilterBar"));
+const SideBar = createFactory(require("devtools/client/webconsole/new-console-output/components/SideBar"));
 
 let store = null;
 
 function NewConsoleOutputWrapper(parentNode, jsterm, toolbox, owner, document) {
   EventEmitter.decorate(this);
 
   this.parentNode = parentNode;
   this.jsterm = jsterm;
@@ -173,34 +174,39 @@ NewConsoleOutputWrapper.prototype = {
             let onInspectorUpdated = inspector.once("inspector-updated");
             let onNodeFrontSet = this.toolbox.selection.setNodeFront(front, "console");
 
             return Promise.all([onNodeFrontSet, onInspectorUpdated]);
           }
         });
       }
 
-      let childComponent = ConsoleOutput({
+      let consoleOutput = ConsoleOutput({
         serviceContainer,
         onFirstMeaningfulPaint: resolve
       });
 
       let filterBar = FilterBar({
         serviceContainer: {
           attachRefToHud
         }
       });
 
+      let sideBar = SideBar({
+        serviceContainer
+      });
+
       let provider = createElement(
         Provider,
         { store },
         dom.div(
           {className: "webconsole-output-wrapper"},
           filterBar,
-          childComponent
+          consoleOutput,
+          sideBar
       ));
       this.body = ReactDOM.render(provider, this.parentNode);
 
       this.jsterm.focus();
     });
   },
 
   dispatchMessageAdd: function (message, waitForResponse) {
--- a/devtools/client/webconsole/new-console-output/reducers/prefs.js
+++ b/devtools/client/webconsole/new-console-output/reducers/prefs.js
@@ -2,17 +2,18 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const Immutable = require("devtools/client/shared/vendor/immutable");
 const PrefState = Immutable.Record({
-  logLimit: 1000
+  logLimit: 1000,
+  sidebarToggle: false
 });
 
 function prefs(state = new PrefState(), action) {
   return state;
 }
 
 exports.PrefState = PrefState;
 exports.prefs = prefs;
--- a/devtools/client/webconsole/new-console-output/reducers/ui.js
+++ b/devtools/client/webconsole/new-console-output/reducers/ui.js
@@ -5,42 +5,46 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {
   FILTER_BAR_TOGGLE,
   INITIALIZE,
   PERSIST_TOGGLE,
   SELECT_NETWORK_MESSAGE_TAB,
+  SIDEBAR_TOGGLE,
   TIMESTAMPS_TOGGLE,
 } = require("devtools/client/webconsole/new-console-output/constants");
 const Immutable = require("devtools/client/shared/vendor/immutable");
 
 const {
   PANELS,
 } = require("devtools/client/netmonitor/src/constants");
 
 const UiState = Immutable.Record({
   filterBarVisible: false,
   initialized: false,
   networkMessageActiveTabId: PANELS.HEADERS,
   persistLogs: false,
+  sidebarVisible: false,
   timestampsVisible: true,
 });
 
 function ui(state = new UiState(), action) {
   switch (action.type) {
     case FILTER_BAR_TOGGLE:
       return state.set("filterBarVisible", !state.filterBarVisible);
     case PERSIST_TOGGLE:
       return state.set("persistLogs", !state.persistLogs);
     case TIMESTAMPS_TOGGLE:
       return state.set("timestampsVisible", action.visible);
     case SELECT_NETWORK_MESSAGE_TAB:
       return state.set("networkMessageActiveTabId", action.id);
+    case SIDEBAR_TOGGLE:
+      return state.set("sidebarVisible", !state.sidebarVisible);
     case INITIALIZE:
       return state.set("initialized", true);
   }
 
   return state;
 }
 
 module.exports = {
--- a/devtools/client/webconsole/new-console-output/store.js
+++ b/devtools/client/webconsole/new-console-output/store.js
@@ -37,18 +37,20 @@ const {
 /**
  * Create and configure store for the Console panel. This is the place
  * where various enhancers and middleware can be registered.
  */
 function configureStore(hud, options = {}) {
   const logLimit = options.logLimit
     || Math.max(Services.prefs.getIntPref("devtools.hud.loglimit"), 1);
 
+  const sidebarToggle = Services.prefs.getBoolPref(PREFS.UI.SIDEBAR_TOGGLE);
+
   const initialState = {
-    prefs: new PrefState({ logLimit }),
+    prefs: new PrefState({ logLimit, sidebarToggle }),
     filters: new FilterState({
       error: Services.prefs.getBoolPref(PREFS.FILTER.ERROR),
       warn: Services.prefs.getBoolPref(PREFS.FILTER.WARN),
       info: Services.prefs.getBoolPref(PREFS.FILTER.INFO),
       debug: Services.prefs.getBoolPref(PREFS.FILTER.DEBUG),
       log: Services.prefs.getBoolPref(PREFS.FILTER.LOG),
       css: Services.prefs.getBoolPref(PREFS.FILTER.CSS),
       net: Services.prefs.getBoolPref(PREFS.FILTER.NET),
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -355,17 +355,16 @@ skip-if = true # Bug 1408942
 [browser_webconsole_netlogging_reset_filter.js]
 skip-if = true #	Bug 1405636
 [browser_webconsole_network_attach.js]
 [browser_webconsole_network_exceptions.js]
 skip-if = true # Bug 1408943
 [browser_webconsole_network_messages_expand.js]
 [browser_webconsole_network_messages_openinnet.js]
 [browser_webconsole_network_requests_from_chrome.js]
-skip-if = true # Bug 1408944
 [browser_webconsole_nodes_highlight.js]
 [browser_webconsole_nodes_select.js]
 [browser_webconsole_notifications.js]
 skip-if = true #	Bug 1405637
 [browser_webconsole_object_inspector.js]
 [browser_webconsole_object_inspector_entries.js]
 [browser_webconsole_observer_notifications.js]
 [browser_webconsole_optimized_out_vars.js]
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_requests_from_chrome.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_network_requests_from_chrome.js
@@ -5,48 +5,45 @@
 
 // Tests that network requests from chrome don't cause the Web Console to
 // throw exceptions. See Bug 597136.
 
 "use strict";
 
 const TEST_URI = "http://example.com/";
 
-var good = true;
-var listener = {
-  QueryInterface: XPCOMUtils.generateQI([ Ci.nsIObserver ]),
-  observe: function (subject) {
-    if (subject instanceof Ci.nsIScriptError &&
-        subject.category === "XPConnect JavaScript" &&
-        subject.sourceName.includes("webconsole")) {
-      good = false;
+add_task(async function () {
+  // Start a listener on the console service.
+  let good = true;
+  const listener = {
+    QueryInterface: XPCOMUtils.generateQI([ Ci.nsIObserver ]),
+    observe: function (subject) {
+      if (subject instanceof Ci.nsIScriptError &&
+          subject.category === "XPConnect JavaScript" &&
+          subject.sourceName.includes("webconsole")) {
+        good = false;
+      }
     }
-  }
-};
-
-var xhr;
-
-function test() {
+  };
   Services.console.registerListener(listener);
 
   // trigger a lazy-load of the HUD Service
   HUDService;
 
-  xhr = new XMLHttpRequest();
-  xhr.addEventListener("load", xhrComplete);
-  xhr.open("GET", TEST_URI, true);
-  xhr.send(null);
-}
+  await sendRequestFromChrome();
 
-function xhrComplete() {
-  xhr.removeEventListener("load", xhrComplete);
-  window.setTimeout(checkForException, 0);
-}
-
-function checkForException() {
-  ok(good, "no exception was thrown when sending a network request from a " +
-     "chrome window");
+  ok(good, "No exception was thrown when sending a network request from a chrome window");
 
   Services.console.unregisterListener(listener);
-  listener = xhr = null;
+});
+
+function sendRequestFromChrome() {
+  return new Promise(resolve => {
+    const xhr = new XMLHttpRequest();
 
-  finishTest();
+    xhr.addEventListener("load", () => {
+      window.setTimeout(resolve, 0);
+    }, { once: true });
+
+    xhr.open("GET", TEST_URI, true);
+    xhr.send(null);
+  });
 }
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/store/ui.test.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const expect = require("expect");
+
+const actions = require("devtools/client/webconsole/new-console-output/actions/index");
+const { setupStore } = require("devtools/client/webconsole/new-console-output/test/helpers");
+
+describe("Testing UI", () => {
+  let store;
+
+  beforeEach(() => {
+    store = setupStore();
+  });
+
+  describe("Toggle sidebar", () => {
+    it("sidebar is toggled on and off", () => {
+      store.dispatch(actions.sidebarToggle());
+      expect(store.getState().ui.sidebarVisible).toEqual(true);
+      store.dispatch(actions.sidebarToggle());
+      expect(store.getState().ui.sidebarVisible).toEqual(false);
+    });
+  });
+});
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object.js
@@ -1929,65 +1929,43 @@ DebuggerServer.ObjectActorPreviewers.Obj
       lineNumber: hooks.createValueGrip(rawObj.lineNumber),
       columnNumber: hooks.createValueGrip(rawObj.columnNumber),
     };
 
     return true;
   },
 
   function PseudoArray({obj, hooks}, grip, rawObj) {
-    let length;
+    // An object is considered a pseudo-array if all the following apply:
+    // - All its properties are array indices except, optionally, a "length" property.
+    // - At least it has the "0" array index.
+    // - The array indices are consecutive.
+    // - The value of "length", if present, is the number of array indices.
 
     let keys = obj.getOwnPropertyNames();
-    if (keys.length == 0) {
-      return false;
-    }
-
-    // We don't want to represent Objects as sparse arrays, so every property
-    // should match its index, or be the length property.
-    if (keys.some((key, i) => parseInt(key, 10) !== i && key !== "length")) {
+    let {length} = keys;
+    if (length === 0) {
       return false;
     }
 
-    // Pseudo-arrays should only have array indices and, optionally, a "length" property.
-    // Since integer indices are sorted first, check if the last property is "length".
-    if (keys[keys.length - 1] === "length") {
-      keys.pop();
-      length = DevToolsUtils.getProperty(obj, "length");
-    } else {
-      // Otherwise, let length be the (presumably) greatest array index plus 1.
-      length = +keys[keys.length - 1] + 1;
+    // Array indices should be sorted at the beginning, from smallest to largest.
+    // Other properties should be at the end, so check if the last one is "length".
+    if (keys[length - 1] === "length") {
+      --length;
+      if (length === 0 || length !== DevToolsUtils.getProperty(obj, "length")) {
+        return false;
+      }
     }
 
-    // If they are no numeric keys, or if the length does not represent the actual
-    // object length, or is not a valid array length, i.e. is a Uint32 number,
-    // do not label the object as ArrayLike.
-    if (
-      keys.length === 0 ||
-      keys.length !== length ||
-      typeof length !== "number" ||
-      length >>> 0 !== length
-    ) {
+    // Check that the last key is the array index expected at that position.
+    let lastKey = keys[length - 1];
+    if (!isArrayIndex(lastKey) || +lastKey !== length - 1) {
       return false;
     }
 
-    // Ensure all keys are increasing array indices smaller than length. The order is not
-    // guaranteed for exotic objects but, in most cases, big array indices and properties
-    // which are not integer indices should be at the end. Then, iterating backwards
-    // allows us to return earlier when the object is not completely a pseudo-array.
-    let prev = length;
-    for (let i = keys.length - 1; i >= 0; --i) {
-      let key = keys[i];
-      let numKey = key >>> 0; // ToUint32(key)
-      if (numKey + "" !== key || numKey >= prev) {
-        return false;
-      }
-      prev = numKey;
-    }
-
     grip.preview = {
       kind: "ArrayLike",
       length: length,
     };
 
     // Avoid recursive object grips.
     if (hooks.getGripDepth() > 1) {
       return true;
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -60,16 +60,17 @@ TextComposition::TextComposition(nsPresC
   , mCompositionStartOffset(0)
   , mTargetClauseOffsetInComposition(0)
   , mIsSynthesizedForTests(aCompositionEvent->mFlags.mIsSynthesizedForTests)
   , mIsComposing(false)
   , mIsEditorHandlingEvent(false)
   , mIsRequestingCommit(false)
   , mIsRequestingCancel(false)
   , mRequestedToCommitOrCancel(false)
+  , mHasReceivedCommitEvent(false)
   , mWasNativeCompositionEndEventDiscarded(false)
   , mAllowControlCharacters(
       Preferences::GetBool("dom.compositionevent.allow_control_characters",
                            false))
   , mWasCompositionStringEmpty(true)
 {
   MOZ_ASSERT(aCompositionEvent->mNativeIMEContext.IsValid());
 }
@@ -241,16 +242,20 @@ void
 TextComposition::DispatchCompositionEvent(
                    WidgetCompositionEvent* aCompositionEvent,
                    nsEventStatus* aStatus,
                    EventDispatchingCallback* aCallBack,
                    bool aIsSynthesized)
 {
   mWasCompositionStringEmpty = mString.IsEmpty();
 
+  if (aCompositionEvent->IsFollowedByCompositionEnd()) {
+    mHasReceivedCommitEvent = true;
+  }
+
   // If this instance has requested to commit or cancel composition but
   // is not synthesizing commit event, that means that the IME commits or
   // cancels the composition asynchronously.  Typically, iBus behaves so.
   // Then, synthesized events which were dispatched immediately after
   // the request has already committed our editor's composition string and
   // told it to web apps.  Therefore, we should ignore the delayed events.
   if (mRequestedToCommitOrCancel && !aIsSynthesized) {
     *aStatus = nsEventStatus_eConsumeNoDefault;
@@ -558,20 +563,22 @@ TextComposition::DispatchCompositionEven
     new CompositionEventDispatcher(this, mNode, aEventMessage, aData,
                                    aIsSynthesizingCommit));
 }
 
 nsresult
 TextComposition::RequestToCommit(nsIWidget* aWidget, bool aDiscard)
 {
   // If this composition is already requested to be committed or canceled,
-  // we don't need to request it again because even if the first request
-  // failed, new request won't success, probably.  And we shouldn't synthesize
-  // events for committing or canceling composition twice or more times.
-  if (mRequestedToCommitOrCancel) {
+  // or has already finished in IME, we don't need to request it again because
+  // request from this instance shouldn't cause committing nor canceling current
+  // composition in IME, and even if the first request failed, new request
+  // won't success, probably.  And we shouldn't synthesize events for
+  // committing or canceling composition twice or more times.
+  if (!CanRequsetIMEToCommitOrCancelComposition()) {
     return NS_OK;
   }
 
   RefPtr<TextComposition> kungFuDeathGrip(this);
   const nsAutoString lastData(mLastData);
 
   {
     AutoRestore<bool> saveRequestingCancel(mIsRequestingCancel);
--- a/dom/events/TextComposition.h
+++ b/dom/events/TextComposition.h
@@ -96,16 +96,25 @@ public:
 
   /**
    * Request to commit (or cancel) the composition to IME.  This method should
    * be called only by IMEStateManager::NotifyIME().
    */
   nsresult RequestToCommit(nsIWidget* aWidget, bool aDiscard);
 
   /**
+   * IsRequestingCommitOrCancelComposition() returns true if the instance is
+   * requesting widget to commit or cancel composition.
+   */
+  bool IsRequestingCommitOrCancelComposition() const
+  {
+    return mIsRequestingCancel || mIsRequestingCommit;
+  }
+
+  /**
    * Send a notification to IME.  It depends on the IME or platform spec what
    * will occur (or not occur).
    */
   nsresult NotifyIME(widget::IMEMessage aMessage);
 
   /**
    * the offset of first composition string
    */
@@ -242,20 +251,24 @@ private:
   // requesting commit or canceling the composition.  In other words, while
   // one of these values is true, we're handling the request.
   bool mIsRequestingCommit;
   bool mIsRequestingCancel;
 
   // mRequestedToCommitOrCancel is true *after* we requested IME to commit or
   // cancel the composition.  In other words, we already requested of IME that
   // it commits or cancels current composition.
-  // NOTE: Before this is set true, both mIsRequestingCommit and
-  //       mIsRequestingCancel are set false.
+  // NOTE: Before this is set to true, both mIsRequestingCommit and
+  //       mIsRequestingCancel are set to false.
   bool mRequestedToCommitOrCancel;
 
+  // Before this dispatches commit event into the tree, this is set to true.
+  // So, this means if native IME already commits the composition.
+  bool mHasReceivedCommitEvent;
+
   // mWasNativeCompositionEndEventDiscarded is true if this composition was
   // requested commit or cancel itself but native compositionend event is
   // discarded by PresShell due to not safe to dispatch events.
   bool mWasNativeCompositionEndEventDiscarded;
 
   // Allow control characters appear in composition string.
   // When this is false, control characters except
   // CHARACTER TABULATION (horizontal tab) are removed from
@@ -274,23 +287,38 @@ private:
     , mCompositionStartOffset(0)
     , mTargetClauseOffsetInComposition(0)
     , mIsSynthesizedForTests(false)
     , mIsComposing(false)
     , mIsEditorHandlingEvent(false)
     , mIsRequestingCommit(false)
     , mIsRequestingCancel(false)
     , mRequestedToCommitOrCancel(false)
+    , mHasReceivedCommitEvent(false)
     , mWasNativeCompositionEndEventDiscarded(false)
     , mAllowControlCharacters(false)
     , mWasCompositionStringEmpty(true)
   {}
   TextComposition(const TextComposition& aOther);
 
   /**
+   * If we're requesting IME to commit or cancel composition, or we've already
+   * requested it, or we've already known this composition has been ended in
+   * IME, we don't need to request commit nor cancel composition anymore and
+   * shouldn't do so if we're in content process for not committing/canceling
+   * "current" composition in native IME.  So, when this returns true,
+   * RequestIMEToCommit() does nothing.
+   */
+  bool CanRequsetIMEToCommitOrCancelComposition() const
+  {
+    return !mIsRequestingCommit && !mIsRequestingCancel &&
+           !mRequestedToCommitOrCancel && !mHasReceivedCommitEvent;
+  }
+
+  /**
    * GetEditorBase() returns EditorBase pointer of mEditorBaseWeak.
    */
   already_AddRefed<EditorBase> GetEditorBase() const;
 
   /**
    * HasEditor() returns true if mEditorBaseWeak holds EditorBase instance
    * which is alive.  Otherwise, false.
    */
--- a/dom/tests/mochitest/webcomponents/mochitest.ini
+++ b/dom/tests/mochitest/webcomponents/mochitest.ini
@@ -5,45 +5,41 @@ support-files =
 
 [test_bug900724.html]
 [test_bug1017896.html]
 [test_bug1176757.html]
 [test_bug1276240.html]
 [test_content_element.html]
 skip-if = true # Triggers assertions about flattened tree inconsistencies and it's going away in bug 1418002.
 [test_custom_element_callback_innerhtml.html]
-skip-if = true # disabled - See bug 1390396
 [test_custom_element_htmlconstructor.html]
 skip-if = os == 'android' # bug 1323645
 support-files =
   htmlconstructor_autonomous_tests.js
   htmlconstructor_builtin_tests.js
 [test_custom_element_in_shadow.html]
-skip-if = true # disabled - See bug 1390396
 [test_custom_element_register_invalid_callbacks.html]
 [test_custom_element_throw_on_dynamic_markup_insertion.html]
 [test_custom_element_get.html]
 [test_custom_element_when_defined.html]
 [test_custom_element_upgrade.html]
 support-files =
   test_upgrade_page.html
   upgrade_tests.js
+[test_custom_element_lifecycle.html]
+[test_custom_element_stack.html]
 [test_nested_content_element.html]
 [test_dest_insertion_points.html]
 [test_fallback_dest_insertion_points.html]
 [test_detached_style.html]
 [test_dynamic_content_element_matching.html]
 [test_document_adoptnode.html]
 [test_document_importnode.html]
 [test_document_register.html]
-[test_document_register_lifecycle.html]
-skip-if = true # disabled - See bug 1390396
 [test_document_register_parser.html]
-[test_document_register_stack.html]
-skip-if = true # disabled - See bug 1390396
 [test_document_shared_registry.html]
 [test_event_retarget.html]
 [test_event_stopping.html]
 [test_template.html]
 [test_template_xhtml.html]
 [test_template_custom_elements.html]
 [test_shadowroot.html]
 [test_shadowroot_inert_element.html]
--- a/dom/tests/mochitest/webcomponents/test_custom_element_callback_innerhtml.html
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_callback_innerhtml.html
@@ -1,34 +1,30 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=1102502
 -->
 <head>
-  <title>Test for attached callback for element created in the document by the parser</title>
+  <title>Test for connected callback for element created in the document by the parser</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1102502">Bug 1102502</a>
 <div id="container"></div>
 
 <script>
 
 SimpleTest.waitForExplicitFinish();
 
 var connectedCallbackCount = 0;
 
 var p = Object.create(HTMLElement.prototype);
 
-p.createdCallback = function() {
-  ok(true, "createdCallback called.");
-};
-
 p.connectedCallback = function() {
   ok(true, "connectedCallback should be called when the parser creates an element in the document.");
   connectedCallbackCount++;
   // connectedCallback should be called twice, once for the element created for innerHTML and
   // once for the element created in this document.
   if (connectedCallbackCount == 2) {
     SimpleTest.finish();
   }
--- a/dom/tests/mochitest/webcomponents/test_custom_element_in_shadow.html
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_in_shadow.html
@@ -11,122 +11,110 @@ https://bugzilla.mozilla.org/show_bug.cg
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1087460">Bug 1087460</a>
 <div id="container"></div>
 
 <script>
 
 // Test callback for custom element when used after registration.
 
-var createdCallbackCount = 0;
-var attachedCallbackCount = 0;
-var detachedCallbackCount = 0;
+var connectedCallbackCount = 0;
+var disconnectedCallbackCount = 0;
 var attributeChangedCallbackCount = 0;
 
-var p1 = Object.create(HTMLElement.prototype);
+class Foo extends HTMLElement
+{
+  connectedCallback() {
+    connectedCallbackCount++;
+  }
 
-p1.createdCallback = function() {
-  createdCallbackCount++;
-};
-
-p1.attachedCallback = function() {
-  attachedCallbackCount++;
-};
+  disconnectedCallback() {
+    disconnectedCallbackCount++;
+  }
 
-p1.detachedCallback = function() {
-  detachedCallbackCount++;
-};
+  attributeChangedCallback(aName, aOldValue, aNewValue) {
+    attributeChangedCallbackCount++;
+  }
 
-p1.attributeChangedCallback = function(name, oldValue, newValue) {
-  attributeChangedCallbackCount++;
-};
+  static get observedAttributes() {
+    return ["data-foo"];
+  }
+}
 
-document.registerElement("x-foo", { prototype: p1 });
+customElements.define("x-foo", Foo);
 
 var container = document.getElementById("container");
 var shadow = container.createShadowRoot();
-
-is(createdCallbackCount, 0, "createdCallback should not be called more than once in this test.");
 var customElem = document.createElement("x-foo");
-is(createdCallbackCount, 1, "createdCallback should be called after creating custom element.");
 
 is(attributeChangedCallbackCount, 0, "attributeChangedCallback should not be called after just creating an element.");
 customElem.setAttribute("data-foo", "bar");
 is(attributeChangedCallbackCount, 1, "attributeChangedCallback should be called after setting an attribute.");
 
-is(attachedCallbackCount, 0, "attachedCallback should not be called on an element that is not in a document/composed document.");
+is(connectedCallbackCount, 0, "connectedCallback should not be called on an element that is not in a document/composed document.");
 shadow.appendChild(customElem);
-is(attachedCallbackCount, 1, "attachedCallback should be called after attaching custom element to the composed document.");
+is(connectedCallbackCount, 1, "connectedCallback should be called after attaching custom element to the composed document.");
 
-is(detachedCallbackCount, 0, "detachedCallback should not be called without detaching custom element.");
+is(disconnectedCallbackCount, 0, "disconnectedCallback should not be called without detaching custom element.");
 shadow.removeChild(customElem);
-is(detachedCallbackCount, 1, "detachedCallback should be called after detaching custom element from the composed document.");
+is(disconnectedCallbackCount, 1, "disconnectedCallback should be called after detaching custom element from the composed document.");
 
 // Test callback for custom element already in the composed doc when created.
 
-createdCallbackCount = 0;
-attachedCallbackCount = 0;
-detachedCallbackCount = 0;
+connectedCallbackCount = 0;
+disconnectedCallbackCount = 0;
 attributeChangedCallbackCount = 0;
 
 shadow.innerHTML = "<x-foo></x-foo>";
-is(createdCallbackCount, 1, "createdCallback should be called after creating a custom element.");
-is(attachedCallbackCount, 1, "attachedCallback should be called after creating an element in the composed document.");
+is(connectedCallbackCount, 1, "connectedCallback should be called after creating an element in the composed document.");
 
 shadow.innerHTML = "";
-is(detachedCallbackCount, 1, "detachedCallback should be called after detaching custom element from the composed document.");
+is(disconnectedCallbackCount, 1, "disconnectedCallback should be called after detaching custom element from the composed document.");
 
 // Test callback for custom element in shadow DOM when host attached/detached to/from document.
 
-createdCallbackCount = 0;
-attachedCallbackCount = 0;
-detachedCallbackCount = 0;
+connectedCallbackCount = 0;
+disconnectedCallbackCount = 0;
 attributeChangedCallbackCount = 0;
 
 var host = document.createElement("div");
 shadow = host.createShadowRoot();
 customElem = document.createElement("x-foo");
 
-is(attachedCallbackCount, 0, "attachedCallback should not be called on newly created element.");
+is(connectedCallbackCount, 0, "connectedCallback should not be called on newly created element.");
 shadow.appendChild(customElem);
-is(attachedCallbackCount, 0, "attachedCallback should not be called on attaching to a tree that is not in the composed document.");
+is(connectedCallbackCount, 0, "connectedCallback should not be called on attaching to a tree that is not in the composed document.");
 
-is(attachedCallbackCount, 0, "detachedCallback should not be called.");
+is(disconnectedCallbackCount, 0, "disconnectedCallback should not be called.");
 shadow.removeChild(customElem);
-is(detachedCallbackCount, 0, "detachedCallback should not be called when detaching from a tree that is not in the composed document.");
+is(disconnectedCallbackCount, 0, "disconnectedCallback should not be called when detaching from a tree that is not in the composed document.");
 
 shadow.appendChild(customElem);
-is(attachedCallbackCount, 0, "attachedCallback should still not be called after reattaching to a shadow tree that is not in the composed document.");
+is(connectedCallbackCount, 0, "connectedCallback should still not be called after reattaching to a shadow tree that is not in the composed document.");
 
 container.appendChild(host);
-is(attachedCallbackCount, 1, "attachedCallback should be called after host is inserted into document.");
+is(connectedCallbackCount, 1, "connectedCallback should be called after host is inserted into document.");
 
 container.removeChild(host);
-is(detachedCallbackCount, 1, "detachedCallback should be called after host is removed from document.");
+is(disconnectedCallbackCount, 1, "disconnectedCallback should be called after host is removed from document.");
 
 // Test callback for custom element for upgraded element.
 
-createdCallbackCount = 0;
-attachedCallbackCount = 0;
-detachedCallbackCount = 0;
+connectedCallbackCount = 0;
+disconnectedCallbackCount = 0;
 attributeChangedCallbackCount = 0;
 
 shadow = container.shadowRoot;
 shadow.innerHTML = "<x-bar></x-bar>";
 
 var p2 = Object.create(HTMLElement.prototype);
 
-p2.createdCallback = function() {
-  createdCallbackCount++;
-};
-
-p2.attachedCallback = function() {
-  attachedCallbackCount++;
+p2.connectedCallback = function() {
+  connectedCallbackCount++;
 };
 
 document.registerElement("x-bar", { prototype: p2 });
-is(createdCallbackCount, 1, "createdCallback should be called after registering element.");
-is(attachedCallbackCount, 1, "attachedCallback should be called after upgrading element in composed document.");
+is(connectedCallbackCount, 1, "connectedCallback should be called after upgrading element in composed document.");
 
 </script>
 
 </body>
 </html>
rename from dom/tests/mochitest/webcomponents/test_document_register_lifecycle.html
rename to dom/tests/mochitest/webcomponents/test_custom_element_lifecycle.html
--- a/dom/tests/mochitest/webcomponents/test_document_register_lifecycle.html
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_lifecycle.html
@@ -18,295 +18,273 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 var container = document.getElementById("container");
 
 // Tests callbacks after registering element type that is already in the document.
 // create element in document -> register -> remove from document
 function testRegisterUnresolved() {
   var helloElem = document.getElementById("hello");
 
-  var createdCallbackCalled = false;
-  var attachedCallbackCalled = false;
-  var detachedCallbackCalled = false;
+  var connectedCallbackCalled = false;
+  var disconnectedCallbackCalled = false;
 
   var p = Object.create(HTMLElement.prototype);
-  p.createdCallback = function() {
-    is(helloElem.__proto__, p, "Prototype should be adjusted just prior to invoking the created callback.");
-    is(createdCallbackCalled, false, "Created callback should only be called once in this tests.");
+  p.connectedCallback = function() {
+    is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
     is(this, helloElem, "The 'this' value should be the custom element.");
-    createdCallbackCalled = true;
+    connectedCallbackCalled = true;
   };
 
-  p.attachedCallback = function() {
-    is(createdCallbackCalled, true, "Created callback should be called before attached");
-    is(attachedCallbackCalled, false, "attached callback should only be called once in this test.");
-    is(this, helloElem, "The 'this' value should be the custom element.");
-    attachedCallbackCalled = true;
-  };
-
-  p.detachedCallback = function() {
-    is(attachedCallbackCalled, true, "attached callback should be called before detached");
-    is(detachedCallbackCalled, false, "detached callback should only be called once in this test.");
-    detachedCallbackCalled = true;
+  p.disconnectedCallback = function() {
+    is(connectedCallbackCalled, true, "Connected callback should be called before detached");
+    is(disconnectedCallbackCalled, false, "Disconnected callback should only be called once in this test.");
+    disconnectedCallbackCalled = true;
     is(this, helloElem, "The 'this' value should be the custom element.");
     runNextTest();
   };
 
   p.attributeChangedCallback = function(name, oldValue, newValue) {
     ok(false, "attributeChanged callback should never be called in this test.");
   };
 
   document.registerElement("x-hello", { prototype: p });
-  is(createdCallbackCalled, true, "created callback should be called when control returns to script from user agent code");
 
-  // Remove element from document to trigger detached callback.
+  // Remove element from document to trigger disconnected callback.
   container.removeChild(helloElem);
 }
 
 // Tests callbacks after registering an extended element type that is already in the document.
 // create element in document -> register -> remove from document
 function testRegisterUnresolvedExtended() {
   var buttonElem = document.getElementById("extbutton");
 
-  var createdCallbackCalled = false;
-  var attachedCallbackCalled = false;
-  var detachedCallbackCalled = false;
+  var connectedCallbackCalled = false;
+  var disconnectedCallbackCalled = false;
 
   var p = Object.create(HTMLButtonElement.prototype);
-  p.createdCallback = function() {
-    is(buttonElem.__proto__, p, "Prototype should be adjusted just prior to invoking the created callback.");
-    is(createdCallbackCalled, false, "Created callback should only be called once in this tests.");
+  p.connectedCallback = function() {
+    is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
     is(this, buttonElem, "The 'this' value should be the custom element.");
-    createdCallbackCalled = true;
+    connectedCallbackCalled = true;
   };
 
-  p.attachedCallback = function() {
-    is(createdCallbackCalled, true, "Created callback should be called before attached");
-    is(attachedCallbackCalled, false, "attached callback should only be called once in this test.");
-    is(this, buttonElem, "The 'this' value should be the custom element.");
-    attachedCallbackCalled = true;
-  };
-
-  p.detachedCallback = function() {
-    is(attachedCallbackCalled, true, "attached callback should be called before detached");
-    is(detachedCallbackCalled, false, "detached callback should only be called once in this test.");
-    detachedCallbackCalled = true;
+  p.disconnectedCallback = function() {
+    is(connectedCallbackCalled, true, "Connected callback should be called before detached");
+    is(disconnectedCallbackCalled, false, "Disconnected callback should only be called once in this test.");
+    disconnectedCallbackCalled = true;
     is(this, buttonElem, "The 'this' value should be the custom element.");
     runNextTest();
   };
 
   p.attributeChangedCallback = function(name, oldValue, newValue) {
     ok(false, "attributeChanged callback should never be called in this test.");
   };
 
   document.registerElement("x-button", { prototype: p, extends: "button" });
-  is(createdCallbackCalled, true, "created callback should be called when control returns to script from user agent code");
 
-  // Remove element from document to trigger detached callback.
+  // Remove element from document to trigger disconnected callback.
   container.removeChild(buttonElem);
 }
 
 function testInnerHTML() {
-  var createdCallbackCalled = false;
+  var connectedCallbackCalled = false;
 
   var p = Object.create(HTMLElement.prototype);
-  p.createdCallback = function() {
-    is(createdCallbackCalled, false, "created callback should only be called once in this test.");
-    createdCallbackCalled = true;
+  p.connectedCallback = function() {
+    is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
+    connectedCallbackCalled = true;
   };
 
   document.registerElement("x-inner-html", { prototype: p });
   var div = document.createElement(div);
+  document.documentElement.appendChild(div);
   div.innerHTML = '<x-inner-html></x-inner-html>';
-  is(createdCallbackCalled, true, "created callback should be called after setting innerHTML.");
+  is(connectedCallbackCalled, true, "Connected callback should be called after setting innerHTML.");
   runNextTest();
 }
 
 function testInnerHTMLExtended() {
-  var createdCallbackCalled = false;
+  var connectedCallbackCalled = false;
 
   var p = Object.create(HTMLButtonElement.prototype);
-  p.createdCallback = function() {
-    is(createdCallbackCalled, false, "created callback should only be called once in this test.");
-    createdCallbackCalled = true;
+  p.connectedCallback = function() {
+    is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
+    connectedCallbackCalled = true;
   };
 
   document.registerElement("x-inner-html-extended", { prototype: p, extends: "button" });
   var div = document.createElement(div);
+  document.documentElement.appendChild(div);
   div.innerHTML = '<button is="x-inner-html-extended"></button>';
-  is(createdCallbackCalled, true, "created callback should be called after setting innerHTML.");
+  is(connectedCallbackCalled, true, "Connected callback should be called after setting innerHTML.");
   runNextTest();
 }
 
 function testInnerHTMLUpgrade() {
-  var createdCallbackCalled = false;
+  var connectedCallbackCalled = false;
 
   var div = document.createElement(div);
+  document.documentElement.appendChild(div);
   div.innerHTML = '<x-inner-html-upgrade></x-inner-html-upgrade>';
 
   var p = Object.create(HTMLElement.prototype);
-  p.createdCallback = function() {
-    is(createdCallbackCalled, false, "created callback should only be called once in this test.");
-    createdCallbackCalled = true;
+  p.connectedCallback = function() {
+    is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
+    connectedCallbackCalled = true;
   };
 
   document.registerElement("x-inner-html-upgrade", { prototype: p });
-  is(createdCallbackCalled, true, "created callback should be called after registering.");
+  is(connectedCallbackCalled, true, "Connected callback should be called after registering.");
   runNextTest();
 }
 
 function testInnerHTMLExtendedUpgrade() {
-  var createdCallbackCalled = false;
+  var connectedCallbackCalled = false;
 
   var div = document.createElement(div);
+  document.documentElement.appendChild(div);
   div.innerHTML = '<button is="x-inner-html-extended-upgrade"></button>';
 
   var p = Object.create(HTMLButtonElement.prototype);
-  p.createdCallback = function() {
-    is(createdCallbackCalled, false, "created callback should only be called once in this test.");
-    createdCallbackCalled = true;
+  p.connectedCallback = function() {
+    is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
+    connectedCallbackCalled = true;
   };
 
   document.registerElement("x-inner-html-extended-upgrade", { prototype: p, extends: "button" });
-  is(createdCallbackCalled, true, "created callback should be called after registering.");
+  is(connectedCallbackCalled, true, "Connected callback should be called after registering.");
   runNextTest();
 }
 
 // Test callback when creating element after registering an element type.
 // register -> create element -> insert into document -> remove from document
 function testRegisterResolved() {
-  var createdCallbackCalled = false;
-  var attachedCallbackCalled = false;
-  var detachedCallbackCalled = false;
-
-  var createdCallbackThis;
+  var connectedCallbackCalled = false;
+  var disconnectedCallbackCalled = false;
 
   var p = Object.create(HTMLElement.prototype);
-  p.createdCallback = function() {
-    is(createdCallbackCalled, false, "Created callback should only be called once in this test.");
-    createdCallbackThis = this;
-    createdCallbackCalled = true;
+  p.connectedCallback = function() {
+    is(connectedCallbackCalled, false, "Connected callback should only be called on in this test.");
+    is(this, createdElement, "The 'this' value should be the custom element.");
+    connectedCallbackCalled = true;
   };
 
-  p.attachedCallback = function() {
-    is(createdCallbackCalled, true, "created callback should be called before attached callback.");
-    is(attachedCallbackCalled, false, "attached callback should only be called on in this test.");
+  p.disconnectedCallback = function() {
+    is(connectedCallbackCalled, true, "Connected callback should be called before detached");
+    is(disconnectedCallbackCalled, false, "Disconnected callback should only be called once in this test.");
     is(this, createdElement, "The 'this' value should be the custom element.");
-    attachedCallbackCalled = true;
-  };
-
-  p.detachedCallback = function() {
-    is(attachedCallbackCalled, true, "attached callback should be called before detached");
-    is(detachedCallbackCalled, false, "detached callback should only be called once in this test.");
-    is(this, createdElement, "The 'this' value should be the custom element.");
-    detachedCallbackCalled = true;
+    disconnectedCallbackCalled = true;
     runNextTest();
   };
 
   p.attributeChangedCallback = function() {
     ok(false, "attributeChanged callback should never be called in this test.");
   };
 
   document.registerElement("x-resolved", { prototype: p });
-  is(createdCallbackCalled, false, "Created callback should not be called when custom element instance has not been created.");
 
   var createdElement = document.createElement("x-resolved");
-  is(createdCallbackThis, createdElement, "The 'this' value in the created callback should be the custom element.");
   is(createdElement.__proto__, p, "Prototype of custom element should be the registered prototype.");
 
   // Insert element into document to trigger attached callback.
   container.appendChild(createdElement);
 
   // Remove element from document to trigger detached callback.
   container.removeChild(createdElement);
 }
 
 // Callbacks should always be the same ones when registered.
 function testChangingCallback() {
-  var p = Object.create(HTMLElement.prototype);
   var callbackCalled = false;
-  p.attributeChangedCallback = function(name, oldValue, newValue) {
-    is(callbackCalled, false, "Callback should only be called once in this test.");
-    callbackCalled = true;
-    runNextTest();
-  };
+
+  class TestCallback extends HTMLElement
+  {
+    attributeChangedCallback(aName, aOldValue, aNewValue) {
+      is(callbackCalled, false, "Callback should only be called once in this test.");
+      callbackCalled = true;
+      runNextTest();
+    }
 
-  document.registerElement("x-test-callback", { prototype: p });
+    static get observedAttributes() {
+      return ["data-foo"];
+    }
+  }
 
-  p.attributeChangedCallback = function(name, oldValue, newValue) {
+  customElements.define("x-test-callback", TestCallback);
+
+  TestCallback.prototype.attributeChangedCallback = function(name, oldValue, newValue) {
     ok(false, "Only callbacks at registration should be called.");
   };
 
   var elem = document.createElement("x-test-callback");
-  elem.setAttribute("foo", "bar");
+  elem.setAttribute("data-foo", "bar");
 }
 
 function testAttributeChanged() {
-  var createdCallbackCalled = false;
-
   var createdElement;
-  var createdCallbackThis;
-
-  var p = Object.create(HTMLElement.prototype);
-  p.createdCallback = function() {
-    is(createdCallbackCalled, false, "Created callback should only be called once in this test.");
-    createdCallbackThis = this;
-    createdCallbackCalled = true;
-  };
-
   // Sequence of callback arguments that we expect from attribute changed callback
   // after changing attributes values in a specific order.
   var expectedCallbackArguments = [
     // [oldValue, newValue]
     [null, "newvalue"], // Setting the attribute value to "newvalue"
     ["newvalue", "nextvalue"], // Changing the attribute value from "newvalue" to "nextvalue"
     ["nextvalue", ""], // Changing the attribute value from "nextvalue" to empty string
     ["", null], // Removing the attribute.
   ];
 
-  p.attributeChangedCallback = function(name, oldValue, newValue) {
-    is(createdCallbackCalled, true, "created callback should be called before attribute changed.");
-    is(this, createdElement, "The 'this' value should be the custom element.");
-    ok(expectedCallbackArguments.length > 0, "Attribute changed callback should not be called more than expected.");
+  class AttrChange extends HTMLElement
+  {
+    attributeChangedCallback(name, oldValue, newValue) {
+      is(this, createdElement, "The 'this' value should be the custom element.");
+      ok(expectedCallbackArguments.length > 0, "Attribute changed callback should not be called more than expected.");
 
-    is(name, "changeme", "name arugment in attribute changed callback should be the name of the changed attribute.");
+      is(name, "changeme", "name arugment in attribute changed callback should be the name of the changed attribute.");
 
-    var expectedArgs = expectedCallbackArguments.shift();
-    is(oldValue, expectedArgs[0], "The old value argument should match the expected value.");
-    is(newValue, expectedArgs[1], "The new value argument should match the expected value.");
+      var expectedArgs = expectedCallbackArguments.shift();
+      is(oldValue, expectedArgs[0], "The old value argument should match the expected value.");
+      is(newValue, expectedArgs[1], "The new value argument should match the expected value.");
 
-    if (expectedCallbackArguments.length === 0) {
-      // Done with the attribute changed callback test.
-      runNextTest();
+      if (expectedCallbackArguments.length === 0) {
+        // Done with the attribute changed callback test.
+        runNextTest();
+      }
     }
-  };
 
-  document.registerElement("x-attrchange", { prototype: p });
+    static get observedAttributes() {
+      return ["changeme"];
+    }
+  }
 
-  var createdElement = document.createElement("x-attrchange");
-  is(createdCallbackThis, createdElement, "The 'this' value in the created callback should be the custom element.");
+  customElements.define("x-attrchange", AttrChange);
+
+  createdElement = document.createElement("x-attrchange");
   createdElement.setAttribute("changeme", "newvalue");
   createdElement.setAttribute("changeme", "nextvalue");
   createdElement.setAttribute("changeme", "");
   createdElement.removeAttribute("changeme");
 }
 
 function testAttributeChangedExtended() {
-  var p = Object.create(HTMLButtonElement.prototype);
-  var callbackCalled = 0;
-  p.attributeChangedCallback = function(name, oldValue, newValue) {
-    callbackCalled++;
-    if (callbackCalled > 2) {
-      is(false, "Got unexpected attribute changed callback.");
-    } else if (callbackCalled === 2) {
+  var callbackCalled = false;
+
+  class ExtnededAttributeChange extends HTMLButtonElement
+  {
+    attributeChangedCallback(name, oldValue, newValue) {
+      is(callbackCalled, false, "Callback should only be called once in this test.");
+      callbackCalled = true;
       runNextTest();
     }
-  };
 
-  document.registerElement("x-extended-attribute-change", { prototype: p, extends: "button" });
+    static get observedAttributes() {
+      return ["foo"];
+    }
+  }
+
+  customElements.define("x-extended-attribute-change", ExtnededAttributeChange,
+                        { extends: "button" });
 
   var elem = document.createElement("button", {is: "x-extended-attribute-change"});
   elem.setAttribute("foo", "bar");
 }
 
 // Creates a custom element that is an upgrade candidate (no registration)
 // and mutate the element in ways that would call callbacks for registered
 // elements.
@@ -341,37 +319,37 @@ function testNotInDocEnterLeave() {
   var divNotInDoc = document.createElement("div");
   divNotInDoc.appendChild(createdElement);
   divNotInDoc.removeChild(createdElement);
 
   runNextTest();
 }
 
 function testEnterLeaveView() {
-  var attachedCallbackCalled = false;
-  var detachedCallbackCalled = false;
+  var connectedCallbackCalled = false;
+  var disconnectedCallbackCalled = false;
 
   var p = Object.create(HTMLElement.prototype);
-  p.attachedCallback = function() {
-    is(attachedCallbackCalled, false, "attached callback should only be called on in this test.");
-    attachedCallbackCalled = true;
+  p.connectedCallback = function() {
+    is(connectedCallbackCalled, false, "Connected callback should only be called on in this test.");
+    connectedCallbackCalled = true;
   };
 
-  p.detachedCallback = function() {
-    is(attachedCallbackCalled, true, "attached callback should be called before detached");
-    is(detachedCallbackCalled, false, "detached callback should only be called once in this test.");
-    detachedCallbackCalled = true;
+  p.disconnectedCallback = function() {
+    is(connectedCallbackCalled, true, "Connected callback should be called before detached");
+    is(disconnectedCallbackCalled, false, "Disconnected callback should only be called once in this test.");
+    disconnectedCallbackCalled = true;
     runNextTest();
   };
 
   var div = document.createElement("div");
   document.registerElement("x-element-in-div", { prototype: p });
   var customElement = document.createElement("x-element-in-div");
   div.appendChild(customElement);
-  is(attachedCallbackCalled, false, "Appending a custom element to a node that is not in the document should not call the attached callback.");
+  is(connectedCallbackCalled, false, "Appending a custom element to a node that is not in the document should not call the connected callback.");
 
   container.appendChild(div);
   container.removeChild(div);
 }
 
 var testFunctions = [
   testRegisterUnresolved,
   testRegisterUnresolvedExtended,
@@ -387,16 +365,17 @@ var testFunctions = [
   testNotInDocEnterLeave,
   testEnterLeaveView,
   SimpleTest.finish
 ];
 
 function runNextTest() {
   if (testFunctions.length > 0) {
     var nextTestFunction = testFunctions.shift();
+    info(`Start ${nextTestFunction.name} ...`);
     nextTestFunction();
   }
 }
 
 SimpleTest.waitForExplicitFinish();
 
 runNextTest();
 
rename from dom/tests/mochitest/webcomponents/test_document_register_stack.html
rename to dom/tests/mochitest/webcomponents/test_custom_element_stack.html
--- a/dom/tests/mochitest/webcomponents/test_document_register_stack.html
+++ b/dom/tests/mochitest/webcomponents/test_custom_element_stack.html
@@ -11,139 +11,124 @@ https://bugzilla.mozilla.org/show_bug.cg
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129">Bug 783129</a>
 <div id="container">
 </div>
 <script>
 
 var container = document.getElementById("container");
 
-// Test changing attributes in the created callback on an element
-// created after registration.
-function testChangeAttributeInCreatedCallback() {
-  var createdCallbackCalled = false;
+function testChangeAttributeInEnteredViewCallback() {
   var attributeChangedCallbackCalled = false;
-
-  var p = Object.create(HTMLElement.prototype);
-  p.createdCallback = function() {
-    is(createdCallbackCalled, false, "Created callback should be called before attached callback.");
-    createdCallbackCalled = true;
-    is(attributeChangedCallbackCalled, false, "Attribute changed callback should not have been called prior to setting the attribute.");
-    this.setAttribute("foo", "bar");
-    is(attributeChangedCallbackCalled, true, "While element is being created, element should be added to the current element callback queue.");
-    runNextTest();
-  };
-
-  p.attributeChangedCallback = function(name, oldValue, newValue) {
-    is(createdCallbackCalled, true, "attributeChanged callback should be called after the created callback because it was enqueued during created callback.");
-    is(attributeChangedCallbackCalled, false, "attributeChanged callback should only be called once in this tests.");
-    is(newValue, "bar", "The new value should be 'bar'");
-    attributeChangedCallbackCalled = true;
-  };
+  var connectedCallbackCalled = false;
 
-  document.registerElement("x-one", { prototype: p });
-  document.createElement("x-one");
-}
-
-function testChangeAttributeInEnteredViewCallback() {
-  var p = Object.create(HTMLElement.prototype);
-  var attributeChangedCallbackCalled = false;
-  var attachedCallbackCalled = false;
+  class Two extends HTMLElement
+  {
+    connectedCallback() {
+      is(connectedCallbackCalled, false, "Connected callback should be called only once in this test.");
+      connectedCallbackCalled = true;
+      is(attributeChangedCallbackCalled, false, "Attribute changed callback should not be called before changing attribute.");
+      this.setAttribute("foo", "bar");
+      is(attributeChangedCallbackCalled, true, "Transition from user-agent implementation to script should result in attribute changed callback being called.");
+      runNextTest();
+    }
 
-  p.attachedCallback = function() {
-    is(attachedCallbackCalled, false, "attached callback should be called only once in this test.");
-    attachedCallbackCalled = true;
-    is(attributeChangedCallbackCalled, false, "Attribute changed callback should not be called before changing attribute.");
-    this.setAttribute("foo", "bar");
-    is(attributeChangedCallbackCalled, true, "Transition from user-agent implementation to script should result in attribute changed callback being called.");
-    runNextTest();
-  };
+    attributeChangedCallback() {
+      is(connectedCallbackCalled, true, "Connected callback should have been called prior to attribute changed callback.");
+      is(attributeChangedCallbackCalled, false, "Attribute changed callback should only be called once in this tests.");
+      attributeChangedCallbackCalled = true;
+    }
 
-  p.attributeChangedCallback = function() {
-    is(attachedCallbackCalled, true, "attached callback should have been called prior to attribute changed callback.");
-    is(attributeChangedCallbackCalled, false, "attributeChanged callback should only be called once in this tests.");
-    attributeChangedCallbackCalled = true;
-  };
+    static get observedAttributes() {
+      return ["foo"];
+    }
+  }
 
-  document.registerElement("x-two", { prototype: p });
+  customElements.define("x-two", Two);
   var elem = document.createElement("x-two");
 
   var container = document.getElementById("container");
   container.appendChild(elem);
 }
 
 function testLeaveViewInEnteredViewCallback() {
   var p = Object.create(HTMLElement.prototype);
-  var attachedCallbackCalled = false;
-  var detachedCallbackCalled = false;
+  var connectedCallbackCalled = false;
+  var disconnectedCallbackCalled = false;
   var container = document.getElementById("container");
 
-  p.attachedCallback = function() {
+  p.connectedCallback = function() {
     is(this.parentNode, container, "Parent node should the container in which the node was appended.");
-    is(attachedCallbackCalled, false, "attached callback should be called only once in this test.");
-    attachedCallbackCalled = true;
-    is(detachedCallbackCalled, false, "detached callback should not be called prior to removing element from document.");
+    is(connectedCallbackCalled, false, "Connected callback should be called only once in this test.");
+    connectedCallbackCalled = true;
+    is(disconnectedCallbackCalled, false, "Disconnected callback should not be called prior to removing element from document.");
     container.removeChild(this);
-    is(detachedCallbackCalled, true, "Transition from user-agent implementation to script should run left view callback.");
+    is(disconnectedCallbackCalled, true, "Transition from user-agent implementation to script should run left view callback.");
     runNextTest();
   };
 
-  p.detachedCallback = function() {
-    is(detachedCallbackCalled, false, "The detached callback should only be called once in this test.");
-    is(attachedCallbackCalled, true, "The attached callback should be called prior to detached callback.");
-    detachedCallbackCalled = true;
+  p.disconnectedCallback = function() {
+    is(disconnectedCallbackCalled, false, "The disconnected callback should only be called once in this test.");
+    is(connectedCallbackCalled, true, "The connected callback should be called prior to disconnected callback.");
+    disconnectedCallbackCalled = true;
   };
 
   document.registerElement("x-three", { prototype: p });
   var elem = document.createElement("x-three");
 
   container.appendChild(elem);
 }
 
 function testStackedAttributeChangedCallback() {
-  var p = Object.create(HTMLElement.prototype);
   var attributeChangedCallbackCount = 0;
 
   var attributeSequence = ["foo", "bar", "baz"];
 
-  p.attributeChangedCallback = function(attrName, oldValue, newValue) {
-    if (newValue == "baz") {
-      return;
+  class Four extends HTMLElement
+  {
+    attributeChangedCallback(attrName, oldValue, newValue) {
+      if (newValue == "baz") {
+        return;
+      }
+
+      var nextAttribute = attributeSequence.shift();
+      ok(true, nextAttribute);
+      // Setting this attribute will call this function again, when
+      // control returns to the script, the last attribute in the sequence should
+      // be set on the element.
+      this.setAttribute("foo", nextAttribute);
+      is(this.getAttribute("foo"), "baz", "The last value in the sequence should be the value of the attribute.");
+
+      attributeChangedCallbackCount++;
+      if (attributeChangedCallbackCount == 3) {
+        runNextTest();
+      }
     }
 
-    var nextAttribute = attributeSequence.shift();
-    ok(true, nextAttribute);
-    // Setting this attribute will call this function again, when
-    // control returns to the script, the last attribute in the sequence should
-    // be set on the element.
-    this.setAttribute("foo", nextAttribute);
-    is(this.getAttribute("foo"), "baz", "The last value in the sequence should be the value of the attribute.");
+    static get observedAttributes() {
+      return ["foo"];
+    }
+  }
 
-    attributeChangedCallbackCount++;
-    if (attributeChangedCallbackCount == 3) {
-      runNextTest();
-    }
-  };
-
-  document.registerElement("x-four", { prototype: p });
+  customElements.define("x-four", Four);
   var elem = document.createElement("x-four");
   elem.setAttribute("foo", "changeme");
 }
 
 var testFunctions = [
-  testChangeAttributeInCreatedCallback,
   testChangeAttributeInEnteredViewCallback,
   testLeaveViewInEnteredViewCallback,
   testStackedAttributeChangedCallback,
   SimpleTest.finish
 ];
 
 function runNextTest() {
   if (testFunctions.length > 0) {
     var nextTestFunction = testFunctions.shift();
+    info(`Start ${nextTestFunction.name} ...`);
     nextTestFunction();
   }
 }
 
 SimpleTest.waitForExplicitFinish();
 
 runNextTest();
 
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -4430,17 +4430,19 @@ Tab.prototype = {
 
     let fixedURI = aLocationURI;
     try {
       fixedURI = URIFixup.createExposableURI(aLocationURI);
     } catch (ex) { }
 
     // In restricted profiles, we refuse to let you open various urls.
     if (!ParentalControls.isAllowed(ParentalControls.BROWSE, fixedURI)) {
-      aRequest.cancel(Cr.NS_BINDING_ABORTED);
+      if (aRequest) {
+        aRequest.cancel(Cr.NS_BINDING_ABORTED);
+      }
 
       this.browser.docShell.displayLoadError(Cr.NS_ERROR_UNKNOWN_PROTOCOL, fixedURI, null);
     }
 
     let contentType = contentWin.document.contentType;
 
     // If fixedURI matches browser.lastURI, we assume this isn't a real location
     // change but rather a spurious addition like a wyciwyg URI prefix. See Bug 747883.
@@ -4495,17 +4497,17 @@ Tab.prototype = {
       }
     }
 
     // Update the page actions URI for helper apps.
     if (BrowserApp.selectedTab == this) {
       ExternalApps.updatePageActionUri(fixedURI);
     }
 
-    if (Components.isSuccessCode(aRequest.status) &&
+    if ((!aRequest || Components.isSuccessCode(aRequest.status)) &&
         !fixedURI.displaySpec.startsWith("about:neterror") && !this.isSearch) {
       // If this won't end up in an error page and the user isn't searching,
       // don't retain the typed entry.
       this.userRequested = "";
     }
 
     let message = {
       type: "Content:LocationChange",
--- a/mobile/android/themes/geckoview/videocontrols.css
+++ b/mobile/android/themes/geckoview/videocontrols.css
@@ -171,17 +171,17 @@
 }
 
 .positionLabel, .durationLabel {
   font-family: 'Roboto', Helvetica, Arial, sans-serif;
   font-size: 16px;
   color: white;
 }
 
-.statusOverlay {
+.statusOverlay[error] {
   -moz-box-align: center;
   -moz-box-pack: center;
   background-color: rgb(50,50,50);
 }
 
 .statusIcon {
   margin-bottom: 28px;
   width: 36px;
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/action/test_process_install_manifest.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import os
+
+import mozunit
+
+from unittest import expectedFailure
+
+from mozpack.copier import (
+    FileCopier,
+    FileRegistry,
+)
+from mozpack.manifests import (
+    InstallManifest,
+    UnreadableInstallManifest,
+)
+from mozpack.test.test_files import TestWithTmpDir
+
+import mozbuild.action.process_install_manifest as process_install_manifest
+
+
+class TestGenerateManifest(TestWithTmpDir):
+    """
+    Unit tests for process_install_manifest.py.
+    """
+
+    def test_process_manifest(self):
+        source = self.tmppath('source')
+        os.mkdir(source)
+        os.mkdir('%s/base' % source)
+        os.mkdir('%s/base/foo' % source)
+        os.mkdir('%s/base2' % source)
+
+        with open('%s/base/foo/file1' % source, 'a'):
+            pass
+
+        with open('%s/base/foo/file2' % source, 'a'):
+            pass
+
+        with open('%s/base2/file3' % source, 'a'):
+            pass
+
+        m = InstallManifest()
+        m.add_pattern_link('%s/base' % source, '**', '')
+        m.add_link('%s/base2/file3' % source, 'foo/file3')
+
+        p = self.tmppath('m')
+        m.write(path=p)
+
+        dest = self.tmppath('dest')
+        track = self.tmppath('track')
+
+        for i in range(2):
+            process_install_manifest.process_manifest(dest, [p], track)
+
+            self.assertTrue(os.path.exists(self.tmppath('dest/foo/file1')))
+            self.assertTrue(os.path.exists(self.tmppath('dest/foo/file2')))
+            self.assertTrue(os.path.exists(self.tmppath('dest/foo/file3')))
+
+    @expectedFailure
+    def test_process_manifest2(self):
+        self.test_process_manifest()
+        m = InstallManifest()
+        p = self.tmppath('m')
+        m.write(path=p)
+
+        dest = self.tmppath('dest')
+        track = self.tmppath('track')
+
+        for i in range(2):
+            process_install_manifest.process_manifest(dest, [p], track)
+
+            self.assertFalse(os.path.exists(self.tmppath('dest/foo/file1')))
+            self.assertFalse(os.path.exists(self.tmppath('dest/foo/file2')))
+            self.assertFalse(os.path.exists(self.tmppath('dest/foo/file3')))
+
+if __name__ == '__main__':
+    mozunit.main()
--- a/python/mozbuild/mozbuild/test/python.ini
+++ b/python/mozbuild/mozbuild/test/python.ini
@@ -1,11 +1,12 @@
 [action/test_buildlist.py]
 [action/test_generate_browsersearch.py]
 [action/test_langpack_manifest.py]
+[action/test_process_install_manifest.py]
 [action/test_package_fennec_apk.py]
 [backend/test_build.py]
 [backend/test_configenvironment.py]
 [backend/test_fastermake.py]
 [backend/test_partialconfigenvironment.py]
 [backend/test_recursivemake.py]
 [backend/test_test_manifest.py]
 [backend/test_visualstudio.py]
--- a/security/manager/ssl/StaticHPKPins.h
+++ b/security/manager/ssl/StaticHPKPins.h
@@ -1154,9 +1154,9 @@ static const TransportSecurityPreload kP
   { "za.search.yahoo.com", false, true, false, -1, &kPinset_yahoo },
   { "zh.search.yahoo.com", false, true, false, -1, &kPinset_yahoo },
 };
 
 // Pinning Preload List Length = 481;
 
 static const int32_t kUnknownId = -1;
 
-static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1519760737593000);
+static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1519846476952000);
--- a/security/manager/ssl/nsSTSPreloadList.errors
+++ b/security/manager/ssl/nsSTSPreloadList.errors
@@ -1,126 +1,90 @@
 01100010011001010111001101110100.com: could not connect to host
 04sun.com: could not connect to host
 06se.com: could not connect to host
 07733.win: could not connect to host
 0day.su: could not connect to host
 18888msc.com: could not connect to host
+1nian.vip: could not connect to host
 1q365a.com: could not connect to host
 24hrs.shopping: could not connect to host
-2c-b.com: could not connect to host
-2c-d.com: could not connect to host
-2c-e.com: could not connect to host
-2c-t-2.com: could not connect to host
-2c-t-7.com: could not connect to host
-2c-t-8.com: could not connect to host
 47tech.com: could not connect to host
+4d2.xyz: could not connect to host
 4loc.us: could not connect to host
-4vf.de: could not connect to host
 4x4.lk: could not connect to host
 68277.me: could not connect to host
 692b8c32.de: could not connect to host
 8560.be: could not connect to host
 87577.com: could not connect to host
 8887999.com: could not connect to host
 8ack.de: could not connect to host
 8ballbombom.uk: could not connect to host
 8t88.biz: could not connect to host
 91-freedom.com: could not connect to host
 9ss6.com: could not connect to host
-a-little-linux-box.at: could not connect to host
 aaronmcguire.me: could not connect to host
+aberdeenalmeras.com: could not connect to host
 abloop.com: could not connect to host
-abolicionistas.com: could not connect to host
 abolition.co: could not connect to host
-abolition.net: could not connect to host
-abolitionism.ca: could not connect to host
-abolitionism.co.uk: could not connect to host
-abolitionism.com: could not connect to host
-abolitionism.in: could not connect to host
-abolitionism.net: could not connect to host
-abolitionism.us: could not connect to host
-abolitionist-project.com: could not connect to host
-abolitionist-society.com: could not connect to host
-abolitionist.ca: could not connect to host
-abolitionist.co.uk: could not connect to host
-abolitionist.com: could not connect to host
-abolitionist.in: could not connect to host
-abolitionist.net: could not connect to host
-abolitionist.us: could not connect to host
-abolitionistparty.com: could not connect to host
-abolitionistproject.com: could not connect to host
-abolitionistsociety.com: could not connect to host
-abolitionniste.com: could not connect to host
-abolizionista.com: could not connect to host
 accwing.com: could not connect to host
+achterstieg.dedyn.io: could not connect to host
 ackis.duckdns.org: could not connect to host
 acrossgw.com: could not connect to host
 adamgold.net: could not connect to host
-adrafinil.wiki: could not connect to host
 aevpn.org: could not connect to host
 affily.io: could not connect to host
+agowa.eu: could not connect to host
+agowa338.de: could not connect to host
+ajetaci.cz: could not connect to host
 akiba-server.info: could not connect to host
-akihito.com: could not connect to host
 akita-stream.com: could not connect to host
 akoww.de: could not connect to host
+akritikos.info: could not connect to host
 akul.co.in: could not connect to host
 al-f.net: could not connect to host
 alasta.info: could not connect to host
 alauda-home.de: could not connect to host
-aldous-huxley.com: could not connect to host
 alexey-shamara.ru: could not connect to host
 alexhaydock.co.uk: could not connect to host
 alexmol.tk: could not connect to host
 alexperry.io: could not connect to host
 alphie.me: could not connect to host
 altahrim.net: could not connect to host
 ameho.me: could not connect to host
 americandistribuidora.com: could not connect to host
-amineptine.com: could not connect to host
-amphetamines.org: could not connect to host
 amua.fr: could not connect to host
-analgesia.net: could not connect to host
 anastasia-shamara.ru: could not connect to host
 andreas-kluge.eu: could not connect to host
 andreaskluge.eu: could not connect to host
 andrei-coman.com: could not connect to host
 angrydragonproductions.com: could not connect to host
-animal-liberation.com: could not connect to host
-animal-rights.com: could not connect to host
 annetaan.fi: could not connect to host
 annonasoftware.com: could not connect to host
-anticopyright.com: could not connect to host
-antispeciesism.com: could not connect to host
-antispeciesist.com: could not connect to host
 anttitenhunen.com: could not connect to host
-anxiolytics.com: could not connect to host
 aojiao.org: could not connect to host
 apkoyunlar.club: could not connect to host
 apparels24.com: could not connect to host
 appdrinks.com: could not connect to host
 apple.ax: could not connect to host
 arawaza.biz: could not connect to host
 arawaza.info: could not connect to host
 arcadiaeng.com: could not connect to host
 arent.kz: could not connect to host
-argot.com: could not connect to host
 arksan.com.tr: could not connect to host
 arne-petersen.net: could not connect to host
 artisense.de: could not connect to host
 artyland.ru: could not connect to host
-aryan-nation.com: could not connect to host
 aseith.com: could not connect to host
 askmagicconch.com: could not connect to host
 asphaltfruehling.de: could not connect to host
 assdecoeur.org: could not connect to host
 asthon.cn: could not connect to host
 at1.co: could not connect to host
 athi.pl: could not connect to host
-atomism.com: could not connect to host
 ausec.ch: could not connect to host
 austinsutphin.com: could not connect to host
 australiancattle.dog: could not connect to host
 autostop-occasions.be: could not connect to host
 autozane.com: could not connect to host
 awan.tech: could not connect to host
 awf0.xyz: could not connect to host
 axolsoft.com: could not connect to host
@@ -140,140 +104,108 @@ bellavistaoutdoor.com: could not connect
 belua.com: could not connect to host
 benjamin-horvath.com: could not connect to host
 benjamin-suess.de: could not connect to host
 benzou-space.com: could not connect to host
 berduri.com: could not connect to host
 berthelier.me: could not connect to host
 bey.io: could not connect to host
 biathloncup.ru: could not connect to host
-billdestler.com: could not connect to host
-binding-problem.com: could not connect to host
 bingcheung.com: could not connect to host
 binimo.com: could not connect to host
-binsp.net: could not connect to host
-biohappiness.com: could not connect to host
-biointelligence-explosion.com: could not connect to host
-biometrics.es: could not connect to host
-biopsychiatry.com: could not connect to host
 bip.gov.sa: could not connect to host
 bitmessage.ch: could not connect to host
 bizeau.ch: could not connect to host
-bizniskatalog.mk: could not connect to host
 bjtxl.cn: could not connect to host
 blackdiam.net: could not connect to host
 blackgamelp.de: could not connect to host
 blackscytheconsulting.com: could not connect to host
 blinkenlight.co.uk: could not connect to host
 blinkenlight.com.au: could not connect to host
 blog.gparent.org: could not connect to host
-bloodsports.org: could not connect to host
-bltc.co.uk: could not connect to host
-bltc.com: could not connect to host
-bltc.net: could not connect to host
-bltc.org: could not connect to host
-bltc.org.uk: could not connect to host
-blueperil.de: could not connect to host
 blumen-garage.de: could not connect to host
 bm-i.ch: could not connect to host
 bodrumfarm.com: could not connect to host
 bolwerk.com.br: could not connect to host
 bonesserver.com: could not connect to host
 bookluk.com: could not connect to host
-boomsaki.com: could not connect to host
-boomsakis.com: could not connect to host
 borisbesemer.com: could not connect to host
 brage.info: could not connect to host
-brahmins.com: could not connect to host
 braintensive.com: could not connect to host
 brettabel.com: could not connect to host
-britishbeef.com: could not connect to host
-britishmeat.com: could not connect to host
-brompton-cocktail.com: could not connect to host
 brookframework.org: could not connect to host
+brrr.fr: could not connect to host
 bsktweetup.info: could not connect to host
 bsuess.de: could not connect to host
-buckypaper.com: could not connect to host
 buka.jp: could not connect to host
-bunny-rabbits.com: could not connect to host
-bupropion.com: could not connect to host
 burlesquemakeup.com: could not connect to host
-bushbaby.com: could not connect to host
 businessfurs.info: could not connect to host
 businessmodeler.se: could not connect to host
 buyingsellingflorida.com: could not connect to host
 buyshoe.org: could not connect to host
 bvexplained.co.uk: could not connect to host
 by1898.com: could not connect to host
 bypass.kr: could not connect to host
-cacao-chocolate.com: could not connect to host
 cafesg.net: could not connect to host
 caipai.fm: could not connect to host
 calculatoaresecondhand.xyz: could not connect to host
 callabs.net: could not connect to host
 callsigns.ca: could not connect to host
-canadiantouristboard.com: could not connect to host
 cancelmyprofile.com: could not connect to host
-candidasa.com: could not connect to host
-cannabis-marijuana.com: could not connect to host
 carlandfaith.com: could not connect to host
 carloshmm.stream: could not connect to host
-carringtonrealtygroup.com: could not connect to host
 casinoreal.com: could not connect to host
 catcontent.cloud: could not connect to host
 caughtredhanded.co.nz: could not connect to host
 cctld.com: could not connect to host
 cee.io: could not connect to host
-cegfw.com: could not connect to host
 cencalvia.org: could not connect to host
 centos.pub: could not connect to host
 challengeskins.com: could not connect to host
 chaouby.com: could not connect to host
-charles-darwin.com: could not connect to host
 charmyadesara.com: could not connect to host
 charonsecurity.com: could not connect to host
 cheah.xyz: could not connect to host
 cheesefusion.com: could not connect to host
 childrendeservebetter.org: could not connect to host
-chimpanzee.net: could not connect to host
 china-line.org: could not connect to host
+chinternet.xyz: could not connect to host
 chosenplaintext.org: could not connect to host
-christopherpritchard.co.uk: could not connect to host
 cio.gov: could not connect to host
 cirope.com: could not connect to host
 cjtkfan.club: could not connect to host
 clearchatsandbox.com: could not connect to host
 clearviewwealthprojector.com.au: could not connect to host
 cloudbleed.info: could not connect to host
 cloudimproved.com: could not connect to host
 cloudimprovedtest.com: could not connect to host
 clycat.ru: could not connect to host
 cnlic.com: could not connect to host
 co-yutaka.com: could not connect to host
 coco-cool.fr: could not connect to host
-codeine.co.uk: could not connect to host
 codenlife.xyz: could not connect to host
 codercross.com: could not connect to host
 codymoniz.com: could not connect to host
+coincoin.eu.org: could not connect to host
 colleencornez.com: could not connect to host
 communityflow.info: could not connect to host
 compartir.party: could not connect to host
 comprehensiveihc.com: could not connect to host
 conception.sk: could not connect to host
 conniesacademy.com: could not connect to host
 corinnanese.de: could not connect to host
 cosmeticosdelivery.com.br: could not connect to host
 cosplayer.com: could not connect to host
 cousincouples.com: could not connect to host
+coverdat.com: could not connect to host
 cpaneltips.com: could not connect to host
 crackpfer.de: could not connect to host
 creativecommonscatpictures.com: could not connect to host
 cristianhares.com: could not connect to host
 criticalaim.com: could not connect to host
-cryothanasia.com: could not connect to host
 cryptopartynewcastle.org: could not connect to host
 cryptoshot.pw: could not connect to host
 crystalmachine.net: could not connect to host
 csgo77.com: could not connect to host
 ctj.im: could not connect to host
 cubela.tech: could not connect to host
 customfilmworks.com: could not connect to host
 cyber-computer.club: could not connect to host
@@ -281,144 +213,118 @@ cyberpeace.nl: could not connect to host
 cypherpunk.ws: could not connect to host
 d-bood.site: could not connect to host
 dahlberg.cologne: could not connect to host
 daniel-stahl.net: could not connect to host
 danielmarquard.com: could not connect to host
 darkdestiny.ch: could not connect to host
 data-detox.com: could not connect to host
 datorb.com: could not connect to host
-dave-pearce.com: could not connect to host
-davepearce.com: could not connect to host
-david-pearce.com: could not connect to host
-davidpearce.com: could not connect to host
-davidpearce.org: could not connect to host
 davidscherzer.at: could not connect to host
 davros.eu: could not connect to host
 davros.ru: could not connect to host
 dawnsonb.com: could not connect to host
 days.one: could not connect to host
 dbcom.ru: could not connect to host
 dbox.ga: could not connect to host
 de-servers.de: could not connect to host
 decoyrouting.com: could not connect to host
 deloittequant.com: could not connect to host
 demotivatorbi.ru: could not connect to host
-deontology.com: could not connect to host
 derchris.me: could not connect to host
 derivativeshub.pro: could not connect to host
 dermacarecomplex.com: could not connect to host
-designer-drug.com: could not connect to host
 detecte-fuite.ch: could not connect to host
 detecte.ch: could not connect to host
 detectefuite.ch: could not connect to host
 dev-talk.eu: could not connect to host
 devafterdark.com: could not connect to host
 devkid.net: could not connect to host
 devops.moe: could not connect to host
 devpsy.info: could not connect to host
-diamorphine.com: could not connect to host
 dick.red: could not connect to host
 diemogebhardt.com: could not connect to host
 digioccumss.ddns.net: could not connect to host
 digiworks.se: could not connect to host
 diguass.us: could not connect to host
 dijks.com: could not connect to host
 dirtycat.ru: could not connect to host
 disadattamentolavorativo.it: could not connect to host
 disco-crazy-world.de: could not connect to host
 ditch.ch: could not connect to host
 djangogolf.com: could not connect to host
 dlyl888.com: could not connect to host
-dmdre.com: could not connect to host
 dojifish.space: could not connect to host
-dolorism.com: could not connect to host
 dolphin-hosting.com: could not connect to host
 domengrad.ru: could not connect to host
 dostavkakurierom.ru: could not connect to host
 doyoulyft.com: could not connect to host
+drdim.ru: could not connect to host
 dreamaholic.club: could not connect to host
 dreaming.solutions: could not connect to host
 drighes.com: could not connect to host
+drinkplanet.eu: could not connect to host
 drizz.com.br: could not connect to host
-drlazarina.net: could not connect to host
 dronexpertos.com: could not connect to host
 droomhuis-in-zuid-holland-kopen.nl: could not connect to host
-dsm5.com: could not connect to host
+drtti.io: could not connect to host
 dubrovskiy.net: could not connect to host
 dubrovskiy.pro: could not connect to host
 duch.cloud: could not connect to host
 duelsow.eu: could not connect to host
 duks.com.br: could not connect to host
 duo.money: could not connect to host
-dynorphin.com: could not connect to host
-dynorphins.com: could not connect to host
-dysthymia.com: could not connect to host
 e-wishlist.net: could not connect to host
 eagleridgecampground.com: could not connect to host
 eatfitoutlet.com.br: could not connect to host
+eb-net.de: could not connect to host
 ebonyriddle.com: could not connect to host
 eeb98.com: could not connect to host
-effective-altruist.com: could not connect to host
 ehuber.info: could not connect to host
 elaxy-online.de: could not connect to host
-elephants.net: could not connect to host
 elisabeth-strunz.de: could not connect to host
 elonbase.com: could not connect to host
 elsword.moe: could not connect to host
-empathogen.com: could not connect to host
-empathogens.com: could not connect to host
-emperor-penguin.com: could not connect to host
-emperor-penguins.com: could not connect to host
 endspamwith.us: could not connect to host
 engg.ca: could not connect to host
 enginx.net: could not connect to host
-entactogen.com: could not connect to host
-entactogens.com: could not connect to host
-entheogens.com: could not connect to host
 epulsar.ru: could not connect to host
 er-music.com: could not connect to host
-erinaceinae.com: could not connect to host
 erspro.net: could not connect to host
-erythroxylum-coca.com: could not connect to host
 estan.cn: could not connect to host
 etzi.myds.me: could not connect to host
 eurostrategy.vn.ua: could not connect to host
 ev-zertifikate.de: could not connect to host
 eveshaiwu.com: could not connect to host
 exceed.global: could not connect to host
 expowerhps.com: could not connect to host
 expressemotion.net: could not connect to host
+expressmarket.ru: could not connect to host
 eytosh.net: could not connect to host
 faber.org.ru: could not connect to host
 fabian-kluge.de: could not connect to host
 facebook.ax: could not connect to host
 facilitrak.com: could not connect to host
 faithwatch.org: could not connect to host
 falkus.net: could not connect to host
+familie-sander.rocks: could not connect to host
 farm24.co.uk: could not connect to host
-faroes.net: could not connect to host
-faroes.org: could not connect to host
 feedstringer.com: could not connect to host
 feirlane.org: could not connect to host
-femtomind.com: could not connect to host
 fernangp.com: could not connect to host
-festival.house: could not connect to host
 findmybottleshop.com.au: could not connect to host
 finstererlebnis.de: could not connect to host
 firebaseio.com: could not connect to host
 firexarxa.de: could not connect to host
 first-time-offender.com: could not connect to host
-firstchoicecandy.com: could not connect to host
 fixmyglitch.com: could not connect to host
-flopy.club: could not connect to host
 florian-schlachter.de: could not connect to host
+flosch.at: could not connect to host
 flow.su: could not connect to host
 flugplatz-edvc.de: could not connect to host
-fluoxetine.net: could not connect to host
 flygpost.com: could not connect to host
 foodserve.in: could not connect to host
 forcamp.ga: could not connect to host
 forglemmigej.net: could not connect to host
 fortuna-loessnitz.de: could not connect to host
 foshanshequ.com: could not connect to host
 fossewayflowers.co.uk: could not connect to host
 fossewayflowers.com: could not connect to host
@@ -451,266 +357,225 @@ funksteckdosen24.de: could not connect t
 futbolvivo.tv: could not connect to host
 futos.de: could not connect to host
 g4w.co: could not connect to host
 gabriele-kluge.de: could not connect to host
 gaiserik.com: could not connect to host
 gala.kiev.ua: could not connect to host
 gam3rs.de: could not connect to host
 game-gentle.com: could not connect to host
-gameconservation.org.uk: could not connect to host
 gamishou.fr: could not connect to host
 gasbarkenora.com: could not connect to host
 gasnews.net: could not connect to host
 gasser-daniel.ch: could not connect to host
-gauche.com: could not connect to host
-gayforgenji.com: could not connect to host
 gaygeeks.de: could not connect to host
 gdevpenze.ru: could not connect to host
 gdhzcgs.com: could not connect to host
 ge1.me: could not connect to host
 geeks.berlin: could not connect to host
 gehrke.nrw: could not connect to host
-gene-drive.com: could not connect to host
-gene-drives.com: could not connect to host
-general-anaesthesia.com: could not connect to host
-general-anaesthetics.com: could not connect to host
-general-anesthesia.com: could not connect to host
 generationnext.pl: could not connect to host
 geneve.guide: could not connect to host
-george-orwell.com: could not connect to host
 georgescarryout.com: could not connect to host
 getgeek.dk: could not connect to host
 getgeek.ee: could not connect to host
 getgeek.es: could not connect to host
 getgeek.fi: could not connect to host
 getgeek.fr: could not connect to host
 getgeek.io: could not connect to host
 getgeek.no: could not connect to host
 getgeek.nu: could not connect to host
 getgeek.pl: could not connect to host
 getwarden.net: could not connect to host
 gevaulug.fr: could not connect to host
 gfoss.gr: could not connect to host
 ggss.cf: could not connect to host
 ggx.us: could not connect to host
-giant-panda.com: could not connect to host
-giant-tortoise.com: could not connect to host
-gigantism.com: could not connect to host
 gina-architektur.design: could not connect to host
-giraffes.org: could not connect to host
 girlsgonesporty.com: could not connect to host
 glasner.photo: could not connect to host
-glbg.eu: could not connect to host
-glyxins.com: could not connect to host
 gmantra.org: could not connect to host
 gnom.me: could not connect to host
 godrive.ga: could not connect to host
 goiaspropaganda.com.br: could not connect to host
 google: could not connect to host
 google.ax: could not connect to host
 goranrango.ch: could not connect to host
 gottfridsberg.org: could not connect to host
 goukon.ru: could not connect to host
+govtjobs.blog: could not connect to host
 gozadentro.com: could not connect to host
 gozel.com.tr: could not connect to host
-gradients.com: could not connect to host
 gradsm-ci.net: could not connect to host
 grandmasfridge.org: could not connect to host
 granth.io: could not connect to host
 gratisonlinesex.com: could not connect to host
 greboid.co.uk: could not connect to host
 greboid.com: could not connect to host
 greditsoft.com: could not connect to host
 greenroach.ru: could not connect to host
 gregmartyn.com: could not connect to host
 greuel.online: could not connect to host
 gritte.net: could not connect to host
 grizzlys.com: could not connect to host
 grossberger-ge.org: could not connect to host
 gugaltika-ipb.org: could not connect to host
+gunhunter.com: could not connect to host
+gus.moe: could not connect to host
 gvt2.com: could not connect to host
 gvt3.com: could not connect to host
 h3artbl33d.nl: could not connect to host
 hackerchai.com: could not connect to host
+hadouk.in: could not connect to host
 halcyonsbastion.com: could not connect to host
-hallucinogen.com: could not connect to host
-hallucinogens.org: could not connect to host
 hasabig.wang: could not connect to host
 hasalittle.wang: could not connect to host
-hashish.net: could not connect to host
 haze.network: could not connect to host
+hbbet.com: could not connect to host
+hd-only.org: could not connect to host
 hdy.nz: could not connect to host
 hearty.ink: could not connect to host
-hedonism.org: could not connect to host
-hedonistic-imperative.com: could not connect to host
-hedonistic.org: could not connect to host
-hedonium.com: could not connect to host
-hedweb.co.uk: could not connect to host
-hedweb.com: could not connect to host
-hedweb.net: could not connect to host
-hedweb.org: could not connect to host
 heisenberg.co: could not connect to host
 hejsupport.se: could not connect to host
 hellomouse.tk: could not connect to host
 helsingfors.guide: could not connect to host
 henriknoerr.com: could not connect to host
 hentaimaster.net: could not connect to host
-herbweb.net: could not connect to host
-herbweb.org: could not connect to host
 here.ml: could not connect to host
 heroin.org.uk: could not connect to host
 hexobind.com: could not connect to host
 hg881.com: could not connect to host
 hintermeier-rae.at: could not connect to host
-hippopotamuses.org: could not connect to host
 hiraku.me: could not connect to host
 hirte-digital.de: could not connect to host
 hjes.com.ve: could not connect to host
 hloe0xff.ru: could not connect to host
 hobaugh.social: could not connect to host
 hohm.in: could not connect to host
 home-work-jobs.com: could not connect to host
 hoodoo.io: could not connect to host
 hoodoo.tech: could not connect to host
-hopesb.org: could not connect to host
 horvathd.eu: could not connect to host
-house-sparrow.com: could not connect to host
 hr98.tk: could not connect to host
 hserver.top: could not connect to host
 hudingyuan.cn: could not connect to host
 hukkatavara.com: could not connect to host
-human-clone.com: could not connect to host
-humanzee.com: could not connect to host
-huntingdonlifesciences.com: could not connect to host
 huwjones.me: could not connect to host
-huxley.net: could not connect to host
 hydra.zone: could not connect to host
 hydrante.ch: could not connect to host
-hyperalgesia.com: could not connect to host
-hypersomnia.com: could not connect to host
-hyperthymia.com: could not connect to host
 ibase.com: could not connect to host
+ibps.blog: could not connect to host
 ictpro.info: could not connect to host
 idcrane.com: could not connect to host
+iemb.cf: could not connect to host
 ifoss.me: could not connect to host
 ifxnet.com: could not connect to host
 ignace72.eu: could not connect to host
 ikenmeyer.com: could not connect to host
 ikenmeyer.eu: could not connect to host
 ileat.com: could not connect to host
 imlinan.cn: could not connect to host
 imlinan.com: could not connect to host
 imlinan.info: could not connect to host
 imlinan.net: could not connect to host
 imperdintechnologies.com: could not connect to host
-indian-elephant.com: could not connect to host
 inexpensivecomputers.net: could not connect to host
 informatik.zone: could not connect to host
 injust.me: could not connect to host
 inondation.ch: could not connect to host
 inscript.pl: could not connect to host
 installgentoo.net: could not connect to host
-intelligence-explosion.com: could not connect to host
 investorloanshub.com: could not connect to host
 irvinepa.org: could not connect to host
 is-sw.net: could not connect to host
 isamiok.com: could not connect to host
 itpro-mg.de: could not connect to host
 itproject.guru: could not connect to host
 its-schindler.de: could not connect to host
 ivanpolchenko.com: could not connect to host
+j0ng.xyz: could not connect to host
 jaaxypro.com: could not connect to host
 jakincode.army: could not connect to host
-jamesevans.is: could not connect to host
 japan4you.org: could not connect to host
 jaredfraser.com: could not connect to host
 javascriptlab.fr: could not connect to host
 jbrowndesign.me: could not connect to host
 jccrew.org: could not connect to host
 jcyz.cf: could not connect to host
-jdtic.com: could not connect to host
 jean-remy.ch: could not connect to host
-jeannecalment.com: could not connect to host
 jens.hk: could not connect to host
-jeremybentham.com: could not connect to host
 jhburton.co.uk: could not connect to host
 jie.dance: could not connect to host
 jobmedic.com: could not connect to host
 joecod.es: could not connect to host
 jonathansanchez.pro: could not connect to host
+jonpads.com: could not connect to host
 joostbovee.nl: could not connect to host
-jorovik.com: could not connect to host
 juliawebber.co.za: could not connect to host
 just-pools.co.za: could not connect to host
 justmy.website: could not connect to host
 juventusmania1897.com: could not connect to host
-kabus.org: could not connect to host
 kaika-facilitymanagement.de: could not connect to host
+kaileymslusser.com: could not connect to host
+kaisev.net: could not connect to host
 kamikaichimaru.com: could not connect to host
 kanaanonline.org: could not connect to host
-kangaroos.org: could not connect to host
 kapo.info: could not connect to host
 karanlyons.com: could not connect to host
 karuneshjohri.com: could not connect to host
 kawaiiku.com: could not connect to host
 kawaiiku.de: could not connect to host
 kbb-ev.de: could not connect to host
 kela.jp: could not connect to host
-kemptown.co.uk: could not connect to host
-kemptown.com: could not connect to host
-kemptown.net: could not connect to host
-kenrogers.co: could not connect to host
 kenvix.com: could not connect to host
-ketamine.co.uk: could not connect to host
-keys.fedoraproject.org: could not connect to host
 kieranweightman.me: could not connect to host
 kikuzuki.org: could not connect to host
 kinepolis-studio.ga: could not connect to host
 kitchenaccessories.pro: could not connect to host
 kj1396.net: could not connect to host
 kjchernov.info: could not connect to host
 kjoglum.me: could not connect to host
 kngk-azs.ru: could not connect to host
-knightsbridge.net: could not connect to host
+kngkng.com: could not connect to host
 knownsec.cf: could not connect to host
-koalas.org: could not connect to host
 koketteriet.se: could not connect to host
 kollawat.me: could not connect to host
 konicaprinterdriver.com: could not connect to host
 konventseliten.se: could not connect to host
 kopfsalat.eu: could not connect to host
 kousaku.jp: could not connect to host
 kozmik.co: could not connect to host
+kpebetka.net: could not connect to host
 kriptosec.com: could not connect to host
 kteen.info: could not connect to host
 kylling.io: could not connect to host
 l18.io: could not connect to host
 laboutiquemarocaineduconvoyeur.ma: could not connect to host
 lacasa.fr: could not connect to host
 lachawoj.de: could not connect to host
-lachlan.com: could not connect to host
 ladylikeit.com: could not connect to host
 lanonfire.com: could not connect to host
 lathamlabs.com: could not connect to host
 lathamlabs.net: could not connect to host
 lathamlabs.org: could not connect to host
 lavapot.com: could not connect to host
+lavval.com: could not connect to host
 lawformt.com: could not connect to host
 lazulu.com: could not connect to host
 lcht.ch: could not connect to host
 lcti.biz: could not connect to host
 ldcraft.pw: could not connect to host
 lebal.se: could not connect to host
 leedev.org: could not connect to host
 legaltip.eu: could not connect to host
 legitaxi.com: could not connect to host
 leifdreizler.com: could not connect to host
 leninalbertop.com.ve: could not connect to host
 leonardcamacho.me: could not connect to host
+lessets-graphiques.com: could not connect to host
 leticiagomeztagle.com: could not connect to host
 leveredge.net: could not connect to host
 lezdomsm.com: could not connect to host
 lheinrich.org: could not connect to host
 lianye1.cc: could not connect to host
 lianye2.cc: could not connect to host
 lianye3.cc: could not connect to host
 lianye4.cc: could not connect to host
@@ -718,160 +583,134 @@ lianye5.cc: could not connect to host
 lianye6.cc: could not connect to host
 libertas-tech.com: could not connect to host
 lifenexto.com: could not connect to host
 likenosis.com: could not connect to host
 lingerieonline.com.br: could not connect to host
 linksanitizer.com: could not connect to host
 linksextremist.at: could not connect to host
 linuxcommand.ru: could not connect to host
-linuxwebservertips.in: could not connect to host
 linvx.org: could not connect to host
 lissabon.guide: could not connect to host
+littlelundgrenladies.com: could not connect to host
 littleservice.cn: could not connect to host
 litz.ca: could not connect to host
 litzenberger.ca: could not connect to host
 liukang.tech: could not connect to host
 livnev.me: could not connect to host
+livrariahugodesaovitor.com.br: could not connect to host
 lobosdomain.no-ip.info: could not connect to host
 locker3.com: could not connect to host
 logcat.info: could not connect to host
 logic8.ml: could not connect to host
+lojavirtualfct.com.br: could not connect to host
 lovelytimes.net: could not connect to host
 luav.org: could not connect to host
 lukasunger.cz: could not connect to host
 lukasunger.net: could not connect to host
+luxinmo.com: could not connect to host
 luxonetwork.com: could not connect to host
 m4g.ru: could not connect to host
 maartenterpstra.xyz: could not connect to host
-macaws.org: could not connect to host
 macedopesca.com.br: could not connect to host
 madrants.net: could not connect to host
-maff.co.uk: could not connect to host
 magnacumlaude.co: could not connect to host
 mail4geek.com: could not connect to host
 malgraph.net: could not connect to host
-malinator.net: could not connect to host
-mammals.net: could not connect to host
-manatees.net: could not connect to host
-manavgabhawala.com: could not connect to host
 mare92.cz: could not connect to host
 marketingdesignu.cz: could not connect to host
 martin-mattel.com: could not connect to host
 marxist.party: could not connect to host
-mastd.me: could not connect to host
 mastodon.my: could not connect to host
-materialism.com: could not connect to host
 mathijskingma.nl: could not connect to host
 mattwb65.com: could not connect to host
<