Merge autoland to mozilla-central r=merge a=merge
authorshindli <shindli@mozilla.com>
Wed, 22 Nov 2017 23:28:18 +0200
changeset 437753 f5dafe8991b323b824768bc2e9bc0979f2f7f8a8
parent 437690 571fa7413320e0b7ed2cbc2ff1e7d81a1924bd3c (current diff)
parent 437752 7cc1270bf794f1e2354a93fd8d508f121f959bcf (diff)
child 437754 960f50c2e0a991ab2ab313132e69fb2c96cb7866
push id117
push userfmarier@mozilla.com
push dateTue, 28 Nov 2017 20:17:16 +0000
reviewersmerge, merge
milestone59.0a1
Merge autoland to mozilla-central r=merge a=merge
browser/extensions/formautofill/content/addressReferences.js
dom/tests/mochitest/webcomponents/test_document_register_lifecycle.html
dom/tests/mochitest/webcomponents/test_document_register_stack.html
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
--- 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/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/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -436,32 +436,16 @@ dependencies = [
 ]
 
 [[package]]
 name = "color_quant"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "compiletest_helper"
-version = "0.0.1"
-dependencies = [
- "compiletest_rs 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "compiletest_rs"
-version = "0.2.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
 name = "compositing"
 version = "0.0.1"
 dependencies = [
  "euclid 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx_traits 0.0.1",
  "gleam 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "image 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -698,16 +682,23 @@ dependencies = [
 name = "deny_public_fields"
 version = "0.0.1"
 dependencies = [
  "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "synstructure 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "deny_public_fields_tests"
+version = "0.0.1"
+dependencies = [
+ "deny_public_fields 0.0.1",
+]
+
+[[package]]
 name = "device"
 version = "0.0.1"
 source = "git+https://github.com/servo/devices#c3b012b0ac4fbc47d1ebc9bd3fc308f599be4ee4"
 dependencies = [
  "blurdroid 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "blurmac 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "blurmock 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "blurz 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1733,16 +1724,24 @@ name = "malloc_size_of_derive"
 version = "0.0.1"
 dependencies = [
  "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
  "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "synstructure 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "malloc_size_of_tests"
+version = "0.0.1"
+dependencies = [
+ "malloc_size_of 0.0.1",
+ "servo_arc 0.0.1",
+]
+
+[[package]]
 name = "markup5ever"
 version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "string_cache 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2354,26 +2353,16 @@ source = "registry+https://github.com/ru
 dependencies = [
  "binary-space-partition 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "plugin_compiletest"
-version = "0.0.1"
-dependencies = [
- "compiletest_helper 0.0.1",
- "deny_public_fields 0.0.1",
- "script 0.0.1",
- "script_plugins 0.0.1",
-]
-
-[[package]]
 name = "png"
 version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "deflate 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "inflate 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2724,16 +2713,23 @@ dependencies = [
  "webrender_api 0.54.0 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "script_plugins"
 version = "0.0.1"
 
 [[package]]
+name = "script_plugins_tests"
+version = "0.0.1"
+dependencies = [
+ "script_plugins 0.0.1",
+]
+
+[[package]]
 name = "script_tests"
 version = "0.0.1"
 dependencies = [
  "euclid 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "script 0.0.1",
  "servo_url 0.0.1",
  "style 0.0.1",
@@ -2854,28 +2850,29 @@ dependencies = [
 
 [[package]]
 name = "servo"
 version = "0.0.1"
 dependencies = [
  "android_injected_glue 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "backtrace 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "browserhtml 0.1.17 (git+https://github.com/browserhtml/browserhtml?branch=crate)",
- "compiletest_helper 0.0.1",
+ "deny_public_fields_tests 0.0.1",
  "gfx_tests 0.0.1",
  "glutin_app 0.0.1",
  "layout_tests 0.0.1",
  "libservo 0.0.1",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "malloc_size_of_tests 0.0.1",
  "metrics_tests 0.0.1",
  "msg_tests 0.0.1",
  "net_tests 0.0.1",
  "net_traits_tests 0.0.1",
- "plugin_compiletest 0.0.1",
  "profile_tests 0.0.1",
+ "script_plugins_tests 0.0.1",
  "script_tests 0.0.1",
  "servo_config_tests 0.0.1",
  "servo_remutex_tests 0.0.1",
  "sig 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "style_tests 0.0.1",
 ]
 
 [[package]]
@@ -3891,17 +3888,16 @@ dependencies = [
 "checksum clang-sys 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5955eab05fa8e6ff2b353753dc73a0608daa36e472a21c69f2eb51f43f593544"
 "checksum clap 2.27.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b8c532887f1a292d17de05ae858a8fe50a301e196f9ef0ddb7ccd0d1d00f180"
 "checksum clipboard 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd3a9a938558f33ec1baaa6ca631a69c104aafaacbc66868d9ad28cf5f30564f"
 "checksum clipboard-win 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "693b1280c514045382dfdbb78d1594b1b03cdb66320aeb7ebd2bd38d49bae959"
 "checksum cmake 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)" = "9b0ec063cbc2034e27f7166d04aa7aa1b9ed85b6c7c2414fb68aff20d1ebf604"
 "checksum coco 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c06169f5beb7e31c7c67ebf5540b8b472d23e3eade3b2ec7d1f5b504a85f91bd"
 "checksum cocoa 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac0d785ff4faf0ff23d7b5561346bb50dc7ef9a11cb0e65e07ef776b7752938f"
 "checksum color_quant 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a475fc4af42d83d28adf72968d9bcfaf035a1a9381642d8e85d8a04957767b0d"
-"checksum compiletest_rs 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "617b23d0ed4f57b3bcff6b5fe0a78f0010f1efb636298317665a960b6dbc0533"
 "checksum cookie 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30b3493e12a550c2f96be785088d1da8d93189e7237c8a8d0d871bc9070334c3"
 "checksum cookie 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "477eb650753e319be2ae77ec368a58c638f9f0c4d941c39bad95e950fb1d1d0d"
 "checksum core-foundation 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5909502e547762013619f4c4e01cc7393c20fe2d52d7fa471c1210adb2320dc7"
 "checksum core-foundation-sys 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bc9fb3d6cb663e6fd7cf1c63f9b144ee2b1e4a78595a0451dd34bff85b9a3387"
 "checksum core-graphics 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5dc0a78ab2ac23b6ea7b3fe5fe93b227900dc0956979735b8f68032417976dd4"
 "checksum core-text 8.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bcad23756dd1dc4b47bf6a914ace27aadb8fa68889db5837af2308d018d0467c"
 "checksum cssparser 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44313341610282488e1156ad1fedebca51c54766c87a041d0287b10499c04ba1"
 "checksum cssparser-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "079adec4af52bb5275eadd004292028c79eb3c5f5b4ee8086a36d4197032f6df"
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -91,17 +91,16 @@ impl ToCss for NonTSPseudoClass {
                         // Don't include the terminating nul.
                         let value = String::from_utf16(&s[..s.len() - 1]).unwrap();
                         dest.write_str(concat!(":", $k_css, "("))?;
                         dest.write_str(&value)?;
                         return dest.write_char(')')
                     }, )*
                     NonTSPseudoClass::Dir(ref dir) => {
                         dest.write_str(":dir(")?;
-                        // FIXME: This should be escaped as an identifier; see #19231
                         (**dir).to_css(dest)?;
                         return dest.write_char(')')
                     },
                     NonTSPseudoClass::MozAny(ref selectors) => {
                         dest.write_str(":-moz-any(")?;
                         let mut iter = selectors.iter();
                         let first = iter.next().expect(":-moz-any must have at least 1 selector");
                         first.to_css(dest)?;
--- a/servo/components/style/selector_parser.rs
+++ b/servo/components/style/selector_parser.rs
@@ -186,13 +186,14 @@ pub enum Direction {
     Other(Box<str>),
 }
 
 impl ToCss for Direction {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: Write {
         let dir_str = match *self {
             Direction::Rtl => "rtl",
             Direction::Ltr => "ltr",
+            // FIXME: This should be escaped as an identifier; see #19231
             Direction::Other(ref other) => other,
         };
         dest.write_str(dir_str)
     }
 }
--- a/servo/etc/ci/buildbot_steps.yml
+++ b/servo/etc/ci/buildbot_steps.yml
@@ -75,17 +75,16 @@ mac-rel-intermittent:
   - env PKG_CONFIG_PATH=/usr/local/opt/zlib/lib/pkgconfig ./mach build --release
   - ./etc/ci/check_intermittents.sh --log-raw intermittents.log
 
 linux-dev:
   - ./mach clean-nightlies --keep 3 --force
   - ./mach test-tidy --no-progress --all
   - ./mach test-tidy --no-progress --self-test
   - env CC=gcc-5 CXX=g++-5 ./mach build --dev
-  - env ./mach test-compiletest
   - env ./mach test-unit
   - env ./mach package --dev
   - env ./mach build-cef
   - env ./mach build --dev --no-default-features --features default-except-unstable
   - ./mach build-geckolib
   - ./mach test-stylo
   - bash ./etc/ci/lockfile_changed.sh
   - bash ./etc/ci/manifest_changed.sh
--- a/servo/etc/ci/manifest_changed.sh
+++ b/servo/etc/ci/manifest_changed.sh
@@ -14,12 +14,14 @@ echo "About to update manifest."
 # Adding "SKIP_TESTS" to skip tests, it doesn't really skip the tests.
 # It will run "run_wpt" with "'test_list': ['SKIP_TESTS']",
 # and then pass it into wptrunner, which won't be able to find any tests named
 # "SKIP_TESTS", and thus won't run any.
 # Adding "--binary=" to skip looking for a compiled servo binary.
 ./mach test-wpt --manifest-update --binary= SKIP_TESTS
 
 echo "Updated manifest; about to check if any changes were made to it."
+echo "If a diff is present, please run './mach update-manifest' \
+and commit the change."
 
 diff="$(git diff -- tests/*/MANIFEST.json)"
 echo "${diff}"
 [[ -z "${diff}" ]]
--- a/servo/etc/ci/upload_docs.sh
+++ b/servo/etc/ci/upload_docs.sh
@@ -9,17 +9,17 @@
 # GitHub API token must be passed in environment var TOKEN
 
 set -o errexit
 set -o nounset
 set -o pipefail
 
 cd "$(dirname ${0})/../.."
 
-./mach doc
+env CC=gcc-5 CXX=g++-5 ./mach doc
 # etc/doc.servo.org/index.html overwrites $(mach rust-root)/doc/index.html
 # Use recursive copy here to avoid `cp` returning an error code
 # when it encounters directories.
 cp -r etc/doc.servo.org/* target/doc/
 
 ./mach cargo-geckolib doc
 # Use recursive copy here to avoid `cp` returning an error code
 # when it encounters directories.
deleted file mode 100755
--- a/servo/etc/rustdoc-with-private
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-# Skip the strip-private and strip-hidden rustdoc passes
-# https://github.com/rust-lang/rust/issues/15347
-rustdoc --no-defaults --passes collapse-docs --passes unindent-comments --passes strip-priv-imports "$@"
--- a/servo/ports/servo/Cargo.toml
+++ b/servo/ports/servo/Cargo.toml
@@ -9,26 +9,27 @@ publish = false
 
 [[bin]]
 name = "servo"
 path = "main.rs"
 test = false
 bench = false
 
 [dev-dependencies]
-compiletest_helper = {path = "../../tests/compiletest/helper"}
+deny_public_fields_tests = {path = "../../tests/unit/deny_public_fields"}
 gfx_tests = {path = "../../tests/unit/gfx"}
 layout_tests = {path = "../../tests/unit/layout"}
+malloc_size_of_tests = {path = "../../tests/unit/malloc_size_of"}
 metrics_tests = {path = "../../tests/unit/metrics"}
 msg_tests = {path = "../../tests/unit/msg"}
 net_tests = {path = "../../tests/unit/net"}
 net_traits_tests = {path = "../../tests/unit/net_traits"}
-plugin_compiletest = {path = "../../tests/compiletest/plugin"}
 profile_tests = {path = "../../tests/unit/profile"}
 script_tests = {path = "../../tests/unit/script"}
+script_plugins_tests = {path = "../../tests/unit/script_plugins"}
 servo_config_tests = {path = "../../tests/unit/servo_config"}
 servo_remutex_tests = {path = "../../tests/unit/servo_remutex"}
 style_tests = {path = "../../tests/unit/style"}
 
 [features]
 default = ["unstable", "default-except-unstable"]
 default-except-unstable = ["webdriver", "max_log_level"]
 max_log_level = ["log/release_max_level_info"]
--- a/servo/python/servo/command_base.py
+++ b/servo/python/servo/command_base.py
@@ -481,17 +481,17 @@ class CommandBase(object):
         if "ANDROID_NDK" in env:
             env["NDK_HOME"] = env["ANDROID_NDK"]
         if "ANDROID_TOOLCHAIN" in env:
             env["NDK_STANDALONE"] = env["ANDROID_TOOLCHAIN"]
 
         if hosts_file_path:
             env['HOST_FILE'] = hosts_file_path
 
-        env['RUSTDOC'] = path.join(self.context.topdir, 'etc', 'rustdoc-with-private')
+        env['RUSTDOCFLAGS'] = "--document-private-items"
 
         if self.config["build"]["rustflags"]:
             env['RUSTFLAGS'] = env.get('RUSTFLAGS', "") + " " + self.config["build"]["rustflags"]
 
         # Don't run the gold linker if on Windows https://github.com/servo/servo/issues/9499
         if self.config["tools"]["rustc-with-gold"] and sys.platform != "win32":
             if subprocess.call(['which', 'ld.gold'], stdout=PIPE, stderr=PIPE) == 0:
                 env['RUSTFLAGS'] = env.get('RUSTFLAGS', "") + " -C link-args=-fuse-ld=gold"
--- a/servo/python/servo/testing_commands.py
+++ b/servo/python/servo/testing_commands.py
@@ -54,19 +54,16 @@ TEST_SUITES = OrderedDict([
               "include_arg": "include"}),
     ("wpt", {"kwargs": {"release": False},
              "paths": [path.abspath(WEB_PLATFORM_TESTS_PATH),
                        path.abspath(SERVO_TESTS_PATH)],
              "include_arg": "include"}),
     ("unit", {"kwargs": {},
               "paths": [path.abspath(path.join("tests", "unit"))],
               "include_arg": "test_name"}),
-    ("compiletest", {"kwargs": {"release": False},
-                     "paths": [path.abspath(path.join("tests", "compiletest"))],
-                     "include_arg": "test_name"})
 ])
 
 TEST_SUITES_BY_PREFIX = {path: k for k, v in TEST_SUITES.iteritems() if "paths" in v for path in v["paths"]}
 
 
 def create_parser_wpt():
     parser = wptcommandline.create_parser()
     parser.add_argument('--release', default=False, action="store_true",
@@ -112,17 +109,16 @@ class MachCommands(CommandBase):
                      help="Run all test suites")
     def test(self, params, render_mode=DEFAULT_RENDER_MODE, release=False, tidy_all=False,
              no_progress=False, self_test=False, all_suites=False):
         suites = copy.deepcopy(TEST_SUITES)
         suites["tidy"]["kwargs"] = {"all_files": tidy_all, "no_progress": no_progress, "self_test": self_test,
                                     "stylo": False}
         suites["wpt"]["kwargs"] = {"release": release}
         suites["unit"]["kwargs"] = {}
-        suites["compiletest"]["kwargs"] = {"release": release}
 
         selected_suites = OrderedDict()
 
         if params is None:
             if all_suites:
                 params = suites.keys()
             else:
                 print("Specify a test path or suite name, or pass --all to run all test suites.\n\nAvailable suites:")
@@ -297,73 +293,16 @@ class MachCommands(CommandBase):
         env["RUST_BACKTRACE"] = "1"
         env["CARGO_TARGET_DIR"] = path.join(self.context.topdir, "target", "geckolib").encode("UTF-8")
 
         args = (["cargo", "test", "-p", "stylo_tests"] +
                 (["--release"] if release else []) + (test_name or []))
         with cd(path.join("ports", "geckolib")):
             return call(args, env=env)
 
-    @Command('test-compiletest',
-             description='Run compiletests',
-             category='testing')
-    @CommandArgument('--package', '-p', default=None, help="Specific package to test")
-    @CommandArgument('test_name', nargs=argparse.REMAINDER,
-                     help="Only run tests that match this pattern or file path")
-    @CommandArgument('--release', default=False, action="store_true",
-                     help="Run with a release build of servo")
-    def test_compiletest(self, test_name=None, package=None, release=False):
-        if test_name is None:
-            test_name = []
-
-        self.ensure_bootstrapped()
-
-        if package:
-            packages = {package}
-        else:
-            packages = set()
-
-        test_patterns = []
-        for test in test_name:
-            # add package if 'tests/compiletest/<package>'
-            match = re.search("tests/compiletest/(\\w+)/?$", test)
-            if match:
-                packages.add(match.group(1))
-            # add package & test if '<package>/<test>', 'tests/compiletest/<package>/<test>.rs', or similar
-            elif re.search("\\w/\\w", test):
-                tokens = test.split("/")
-                packages.add(tokens[-2])
-                test_prefix = tokens[-1]
-                if test_prefix.endswith(".rs"):
-                    test_prefix = test_prefix[:-3]
-                test_prefix += "::"
-                test_patterns.append(test_prefix)
-            # add test as-is otherwise
-            else:
-                test_patterns.append(test)
-
-        if not packages:
-            packages = set(os.listdir(path.join(self.context.topdir, "tests", "compiletest"))) - set(['.DS_Store'])
-
-        packages.remove("helper")
-
-        args = ["cargo", "test"]
-        for crate in packages:
-            args += ["-p", "%s_compiletest" % crate]
-        args += test_patterns
-
-        env = self.build_env()
-        if release:
-            env["BUILD_MODE"] = "release"
-            args += ["--release"]
-        else:
-            env["BUILD_MODE"] = "debug"
-
-        return call(args, env=env, cwd=self.servo_crate())
-
     @Command('test-content',
              description='Run the content tests',
              category='testing')
     def test_content(self):
         print("Content tests have been replaced by web-platform-tests under "
               "tests/wpt/mozilla/.")
         return 0
 
deleted file mode 100644
--- a/servo/tests/compiletest/helper/Cargo.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[package]
-name = "compiletest_helper"
-version = "0.0.1"
-authors = ["The Servo Project Developers"]
-license = "MPL-2.0"
-
-[lib]
-name = "compiletest_helper"
-path = "lib.rs"
-doctest = false
-
-[dependencies]
-compiletest_rs = "0.2.0"
deleted file mode 100644
--- a/servo/tests/compiletest/helper/lib.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-extern crate compiletest_rs as compiletest;
-
-use std::env;
-use std::path::PathBuf;
-
-pub fn run_mode(mode: &'static str) {
-    let mut config = compiletest::default_config();
-    let cfg_mode = mode.parse().ok().expect("Invalid mode");
-
-    config.mode = cfg_mode;
-    config.src_base = PathBuf::from(format!("{}", mode));
-
-    let mut base_path = env::current_dir().expect("Current directory is invalid");
-    base_path.pop();
-    base_path.pop();
-    base_path.pop();
-
-    let mode = env::var("BUILD_MODE").expect("BUILD_MODE environment variable must be set");
-    let debug_path = base_path.join(PathBuf::from(format!("target/{}", mode)));
-    let deps_path = base_path.join(PathBuf::from(format!("target/{}/deps", mode)));
-
-    config.target_rustcflags = Some(format!("-L {} -L {}",  debug_path.display(), deps_path.display()));
-    compiletest::run_tests(&config);
-}
deleted file mode 100644
--- a/servo/tests/compiletest/plugin/Cargo.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-[package]
-name = "plugin_compiletest"
-version = "0.0.1"
-authors = ["The Servo Project Developers"]
-license = "MPL-2.0"
-
-[lib]
-name = "plugin_compiletest"
-path = "lib.rs"
-doctest = false
-
-[dependencies]
-compiletest_helper = {path = "../helper"}
-deny_public_fields = {path = "../../../components/deny_public_fields"}
-script = {path = "../../../components/script"}
-script_plugins = {path = "../../../components/script_plugins"}
deleted file mode 100644
--- a/servo/tests/compiletest/plugin/compile-fail/arc_rc_must_not_derive_malloc_size_of.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-extern crate malloc_size_of;
-extern crate servo_arc;
-
-use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf};
-
-fn sizeable<T: MallocSizeOf>() {
-}
-
-fn shallow_sizeable<T: MallocShallowSizeOf>() {
-}
-
-fn main() {
-    sizeable::<::servo_arc::Arc<i32>>();
-    //~^ ERROR the trait bound `servo_arc::Arc<i32>: malloc_size_of::MallocSizeOf` is not satisfied
-
-    sizeable::<::std::sync::Arc<i32>>();
-    //~^ ERROR the trait bound `std::sync::Arc<i32>: malloc_size_of::MallocSizeOf` is not satisfied
-
-    sizeable::<::std::rc::Rc<i32>>();
-    //~^ ERROR the trait bound `std::rc::Rc<i32>: malloc_size_of::MallocSizeOf` is not satisfied
-
-    shallow_sizeable::<::servo_arc::Arc<i32>>();
-    //~^ ERROR the trait bound `servo_arc::Arc<i32>: malloc_size_of::MallocShallowSizeOf` is not satisfied
-
-    shallow_sizeable::<::std::sync::Arc<i32>>();
-    //~^ ERROR the trait bound `std::sync::Arc<i32>: malloc_size_of::MallocShallowSizeOf` is not satisfied
-
-    shallow_sizeable::<::std::rc::Rc<i32>>();
-    //~^ ERROR the trait bound `std::rc::Rc<i32>: malloc_size_of::MallocShallowSizeOf` is not satisfied
-}
deleted file mode 100644
--- a/servo/tests/compiletest/plugin/compile-fail/deny_public_fields.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#![allow(dead_code)]
-
-#[macro_use]
-extern crate deny_public_fields;
-
-#[derive(DenyPublicFields)]
-//~^ ERROR proc-macro derive panicked
-//~| HELP Field `v1` should not be public
-struct Foo {
-    pub v1: i32,
-    v2: i32
-}
-
-fn main() {}
deleted file mode 100644
--- a/servo/tests/compiletest/plugin/compile-fail/trustedpromise_mustnot_deriveclone.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-extern crate script;
-
-use script::test::TrustedPromise;
-
-fn cloneable<T: Clone>() {
-}
-
-fn main() {
-    cloneable::<TrustedPromise>();
-    //~^ ERROR the trait bound `script::test::TrustedPromise: std::clone::Clone` is not satisfied
-}
deleted file mode 100644
--- a/servo/tests/compiletest/plugin/compile-fail/unrooted_must_root.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#![allow(dead_code)]
-#![feature(plugin)]
-#![plugin(script_plugins)]
-
-#[must_root]
-struct Foo {
-    v: i32
-}
-
-struct Bar {
-    f: Foo
-    //~^ ERROR Type must be rooted, use #[must_root] on the struct definition to propagate
-}
-
-fn foo1(_: Foo) {} //~ ERROR Type must be rooted
-
-
-fn foo2() -> Foo { //~ ERROR Type must be rooted
-    Foo { v: 10 }
-}
-
-
-fn main() {}
deleted file mode 100644
--- a/servo/tests/compiletest/plugin/lib.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-extern crate compiletest_helper;
-
-#[test]
-fn compile_test() {
-    compiletest_helper::run_mode("compile-fail");
-}
new file mode 100644
--- /dev/null
+++ b/servo/tests/unit/deny_public_fields/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "deny_public_fields_tests"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+license = "MPL-2.0"
+
+[lib]
+path = "lib.rs"
+test = false
+
+[dependencies]
+deny_public_fields = {path = "../../../components/deny_public_fields"}
new file mode 100644
--- /dev/null
+++ b/servo/tests/unit/deny_public_fields/lib.rs
@@ -0,0 +1,33 @@
+/* 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/. */
+
+/**
+```compile_fail
+#[macro_use] extern crate deny_public_fields;
+
+#[derive(DenyPublicFields)]
+struct Foo {
+    pub v1: i32,
+    v2: i32
+}
+
+fn main() {}
+```
+*/
+pub fn deny_public_fields_failing() {}
+
+/**
+```
+#[macro_use] extern crate deny_public_fields;
+
+#[derive(DenyPublicFields)]
+struct Foo {
+    v1: i32,
+    v2: i32
+}
+
+fn main() {}
+```
+*/
+pub fn deny_public_fields_ok() {}
new file mode 100644
--- /dev/null
+++ b/servo/tests/unit/malloc_size_of/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "malloc_size_of_tests"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+license = "MPL-2.0"
+
+[lib]
+path = "lib.rs"
+test = false
+
+[dependencies]
+malloc_size_of = {path = "../../../components/malloc_size_of"}
+servo_arc = {path = "../../../components/servo_arc"}
new file mode 100644
--- /dev/null
+++ b/servo/tests/unit/malloc_size_of/lib.rs
@@ -0,0 +1,109 @@
+/* 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/. */
+
+/**
+```
+extern crate malloc_size_of;
+extern crate servo_arc;
+
+fn sizeable<T: malloc_size_of::MallocSizeOf>() {}
+fn shallow_sizeable<T: malloc_size_of::MallocShallowSizeOf>() {}
+fn cloneable<T: Clone>() {}
+
+fn main() {
+    cloneable::<servo_arc::Arc<i32>>();
+    cloneable::<std::sync::Arc<i32>>();
+    cloneable::<std::rc::Rc<i32>>();
+}
+```
+*/
+pub fn imports_ok() {}
+
+pub mod does_not_impl_malloc_size_of {
+    /**
+    ```compile_fail,E0277
+    extern crate malloc_size_of;
+    extern crate servo_arc;
+
+    fn sizeable<T: malloc_size_of::MallocSizeOf>() {}
+
+    fn main() {
+        sizeable::<servo_arc::Arc<i32>>();
+    }
+    ```
+    */
+    pub fn servo_arc() {}
+
+
+    /**
+    ```compile_fail,E0277
+    extern crate malloc_size_of;
+
+    fn sizeable<T: malloc_size_of::MallocSizeOf>() {}
+
+    fn main() {
+        sizeable::<std::sync::Arc<i32>>();
+    }
+    ```
+    */
+    pub fn std_arc() {}
+
+
+    /**
+    ```compile_fail,E0277
+    extern crate malloc_size_of;
+
+    fn sizeable<T: malloc_size_of::MallocSizeOf>() {}
+
+    fn main() {
+        sizeable::<std::rc::Rc<i32>>();
+    }
+    ```
+    */
+    pub fn rc() {}
+}
+
+pub mod does_not_impl_malloc_shallow_size_of {
+    /**
+    ```compile_fail,E0277
+    extern crate malloc_size_of;
+    extern crate servo_arc;
+
+    fn shallow_sizeable<T: malloc_size_of::MallocShallowSizeOf>() {}
+
+    fn main() {
+        shallow_sizeable::<servo_arc::Arc<i32>>();
+    }
+    ```
+    */
+    pub fn servo_arc() {}
+
+
+    /**
+    ```compile_fail,E0277
+    extern crate malloc_size_of;
+
+    fn shallow_sizeable<T: malloc_size_of::MallocShallowSizeOf>() {}
+
+    fn main() {
+        shallow_sizeable::<std::sync::Arc<i32>>();
+    }
+    ```
+    */
+    pub fn std_arc() {}
+
+
+    /**
+    ```compile_fail,E0277
+    extern crate malloc_size_of;
+
+    fn shallow_sizeable<T: malloc_size_of::MallocShallowSizeOf>() {}
+
+    fn main() {
+        shallow_sizeable::<std::rc::Rc<i32>>();
+    }
+    ```
+    */
+    pub fn rc() {}
+}
--- a/servo/tests/unit/script/Cargo.toml
+++ b/servo/tests/unit/script/Cargo.toml
@@ -2,16 +2,1