Merge mozilla-central to beta. a=merge, l10n=me on a CLOSED TREE DEVEDITION_58_0b1_RELEASE FIREFOX_58_0b1_BUILD1 FIREFOX_58_0b1_RELEASE
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 02 Nov 2017 20:37:50 -0400
changeset 692973 1c336e874ae849a9f0b6d12bb2858c4af5120779
parent 692972 403fe46f7f820dde5a71082e3b092eaf65c0d743 (current diff)
parent 692325 b2f459b88cab5525c785a7fa70a01be3e9cdcb23 (diff)
child 692974 e65a20ee3a063aa94ea61059b80dce6bc065fa91
push id87647
push userbmo:jkt@mozilla.com
push dateFri, 03 Nov 2017 18:34:26 +0000
reviewersmerge
milestone58.0
Merge mozilla-central to beta. a=merge, l10n=me on a CLOSED TREE
.hgtags
security/manager/ssl/tests/unit/test_signed_apps/sha1_and_sha256_manifest_sha1_signature_file.zip
security/manager/ssl/tests/unit/test_signed_apps/sha1_and_sha256_manifest_sha256_signature_file.zip
security/manager/ssl/tests/unit/test_signed_apps/sha1_manifest_sha1_and_sha256_signature_file.zip
security/manager/ssl/tests/unit/test_signed_apps/sha256_manifest_sha1_and_sha256_signature_file.zip
security/manager/ssl/tests/unit/test_signed_apps/signed_app.zip
security/manager/ssl/tests/unit/test_signed_apps/signed_app_sha1_and_sha256.zip
security/manager/ssl/tests/unit/test_signed_apps/signed_app_sha256.zip
security/manager/ssl/tests/unit/test_signed_apps/signed_app_sha256_manifest.zip
security/manager/ssl/tests/unit/test_signed_apps/signed_app_sha256_signature_file.zip
services/common/tests/unit/test_async_querySpinningly.js
--- a/.hgtags
+++ b/.hgtags
@@ -642,12 +642,13 @@ 86534d5daeef8066928eef910d6d5c60442b24b0
 86534d5daeef8066928eef910d6d5c60442b24b0 FENNEC_57_0b11_RELEASE
 013f57c8692e2da5d49288d77a78aa20db9dca24 FIREFOX_57_0b12_BUILD2
 013f57c8692e2da5d49288d77a78aa20db9dca24 FIREFOX_57_0b12_RELEASE
 aa8f74f0ad60ab35955e539ac8e1164fe00a7fd1 FIREFOX_57_0b13_BUILD1
 aa8f74f0ad60ab35955e539ac8e1164fe00a7fd1 FIREFOX_57_0b13_RELEASE
 aa8f74f0ad60ab35955e539ac8e1164fe00a7fd1 FENNEC_57_0b13_BUILD1
 aa8f74f0ad60ab35955e539ac8e1164fe00a7fd1 FENNEC_57_0b13_RELEASE
 14581cf06e7fb0556dffebed82fe81b21e1fc68d FIREFOX_RELEASE_57_BASE
+d4134557db7c4aee5dd836efe4dfee7716ebc47e FIREFOX_RELEASE_56_END
 9f12a9fab080f2d363d7424e25b9ffe85ebc3414 FIREFOX_AURORA_28_BASE
 f7e9777221a34f9f23c2e4933307eb38b621b679 FIREFOX_NIGHTLY_57_END
 40a14ca1cf04499f398e4cb8ba359b39eae4e216 FIREFOX_BETA_58_BASE
 b7c372f8a26c510c30a0e49b89801508cb2116dc FIREFOX_BETA_57_END
--- a/accessible/windows/msaa/Compatibility.cpp
+++ b/accessible/windows/msaa/Compatibility.cpp
@@ -163,17 +163,17 @@ uint32_t Compatibility::sConsumers = Com
 
 void
 Compatibility::Init()
 {
   // Note we collect some AT statistics/telemetry here for convenience.
 
   HMODULE jawsHandle = ::GetModuleHandleW(L"jhook");
   if (jawsHandle)
-    sConsumers |= (IsModuleVersionLessThan(jawsHandle, 18, 4315)) ?
+    sConsumers |= (IsModuleVersionLessThan(jawsHandle, 20, 0)) ?
                    OLDJAWS : JAWS;
 
   if (::GetModuleHandleW(L"gwm32inc"))
     sConsumers |= WE;
 
   if (::GetModuleHandleW(L"dolwinhk"))
     sConsumers |= DOLPHIN;
 
--- a/browser/components/extensions/ext-menus.js
+++ b/browser/components/extensions/ext-menus.js
@@ -233,17 +233,17 @@ var gMenuBuilder = {
           }
         }
         // Select the clicked radio item.
         item.checked = true;
       }
 
       item.tabManager.addActiveTabPermission();
 
-      let tab = item.tabManager.convert(contextData.tab);
+      let tab = contextData.tab && item.tabManager.convert(contextData.tab);
       let info = item.getClickInfo(contextData, wasChecked);
 
       const map = {shiftKey: "Shift", altKey: "Alt", metaKey: "Command", ctrlKey: "Ctrl"};
       info.modifiers = Object.keys(map).filter(key => event[key]).map(key => map[key]);
       if (event.ctrlKey && AppConstants.platform === "macosx") {
         info.modifiers.push("MacCtrl");
       }
 
@@ -668,17 +668,18 @@ this.menusInternal = class extends Exten
           let root = gRootItems.get(extension);
           if (root) {
             root.remove();
           }
         },
 
         onClicked: new EventManager(context, "menusInternal.onClicked", fire => {
           let listener = (event, info, tab) => {
-            context.withPendingBrowser(tab.linkedBrowser,
+            let {linkedBrowser} = tab || tabTracker.activeTab;
+            context.withPendingBrowser(linkedBrowser,
                                        () => fire.sync(info, tab));
           };
 
           extension.on("webext-menu-menuitem-click", listener);
           return () => {
             extension.off("webext-menu-menuitem-click", listener);
           };
         }).api(),
--- a/browser/components/extensions/test/browser/browser_ext_sidebarAction_contextMenu.js
+++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction_contextMenu.js
@@ -31,16 +31,19 @@ let extData = {
     },
   },
 
   background: function() {
     browser.contextMenus.create({
       id: "clickme-page",
       title: "Click me!",
       contexts: ["all"],
+      onclick(info, tab) {
+        browser.test.sendMessage("menu-click", tab);
+      },
     });
   },
 };
 
 let contextMenuItems = {
   "context-navigation": "hidden",
   "context-sep-navigation": "hidden",
   "context-viewsource": "",
@@ -54,17 +57,21 @@ add_task(async function sidebar_contextm
   let extension = ExtensionTestUtils.loadExtension(extData);
   await extension.startup();
   // Test sidebar is opened on install
   await extension.awaitMessage("sidebar");
 
   let contentAreaContextMenu = await openContextMenuInSidebar();
   let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
   is(item.length, 1, "contextMenu item for page was found");
+
+  item[0].click();
   await closeContextMenu(contentAreaContextMenu);
+  let tab = await extension.awaitMessage("menu-click");
+  is(tab, null, "tab argument is optional, and missing in clicks from sidebars");
 
   await extension.unload();
 });
 
 
 add_task(async function sidebar_contextmenu_hidden_items() {
   let extension = ExtensionTestUtils.loadExtension(extData);
   await extension.startup();
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -2912,19 +2912,21 @@ var JawsScreenReaderVersionCheck = {
   },
 
   onWindowsRestored() {
     Services.tm.dispatchToMainThread(() => this._checkVersionAndPrompt());
   },
 
   _checkVersionAndPrompt() {
     // Make sure we only prompt for versions of JAWS we do not
-    // support and never prompt if e10s is disabled.
+    // support and never prompt if e10s is disabled or if we're on
+    // nightly.
     if (!Services.appinfo.shouldBlockIncompatJaws ||
-        !Services.appinfo.browserTabsRemoteAutostart) {
+        !Services.appinfo.browserTabsRemoteAutostart ||
+        AppConstants.NIGHTLY_BUILD) {
       return;
     }
 
     let win = RecentWindow.getMostRecentBrowserWindow();
     if (!win || !win.gBrowser || !win.gBrowser.selectedBrowser) {
       Services.console.logStringMessage(
           "Content access support for older versions of JAWS is disabled " +
           "due to compatibility issues with this version of Firefox.");
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -308,52 +308,47 @@ class AutofillRecords {
    * @param {boolean} [options.sourceSync = false]
    *        Did sync generate this addition?
    * @returns {string}
    *          The GUID of the newly added item..
    */
   add(record, {sourceSync = false} = {}) {
     this.log.debug("add:", record);
 
+    let recordToSave = this._cloneAndCleanUp(record);
+
     if (sourceSync) {
       // Remove tombstones for incoming items that were changed on another
       // device. Local deletions always lose to avoid data loss.
-      let index = this._findIndexByGUID(record.guid, {
+      let index = this._findIndexByGUID(recordToSave.guid, {
         includeDeleted: true,
       });
       if (index > -1) {
         let existing = this.data[index];
         if (existing.deleted) {
           this.data.splice(index, 1);
         } else {
-          throw new Error(`Record ${record.guid} already exists`);
+          throw new Error(`Record ${recordToSave.guid} already exists`);
         }
       }
-      let recordToSave = this._clone(record);
-      return this._saveRecord(recordToSave, {sourceSync});
-    }
+    } else if (!recordToSave.deleted) {
+      this._normalizeRecord(recordToSave);
+
+      recordToSave.guid = this._generateGUID();
+      recordToSave.version = this.version;
 
-    if (record.deleted) {
-      return this._saveRecord(record);
+      // Metadata
+      let now = Date.now();
+      recordToSave.timeCreated = now;
+      recordToSave.timeLastModified = now;
+      recordToSave.timeLastUsed = 0;
+      recordToSave.timesUsed = 0;
     }
 
-    let recordToSave = this._clone(record);
-    this._normalizeRecord(recordToSave);
-
-    recordToSave.guid = this._generateGUID();
-    recordToSave.version = this.version;
-
-    // Metadata
-    let now = Date.now();
-    recordToSave.timeCreated = now;
-    recordToSave.timeLastModified = now;
-    recordToSave.timeLastUsed = 0;
-    recordToSave.timesUsed = 0;
-
-    return this._saveRecord(recordToSave);
+    return this._saveRecord(recordToSave, {sourceSync});
   }
 
   _saveRecord(record, {sourceSync = false} = {}) {
     if (!record.guid) {
       throw new Error("Record missing GUID");
     }
 
     let recordToSave;
@@ -423,17 +418,17 @@ class AutofillRecords {
       let oldValue = recordFound[field];
       let newValue = recordToUpdate[field];
 
       // Resume the old field value in the perserve case
       if (preserveOldProperties && newValue === undefined) {
         newValue = oldValue;
       }
 
-      if (!newValue) {
+      if (newValue === undefined || newValue === "") {
         delete recordFound[field];
       } else {
         recordFound[field] = newValue;
       }
 
       this._maybeStoreLastSyncedField(recordFound, field, oldValue);
     }
 
--- a/browser/extensions/formautofill/test/unit/test_addressRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_addressRecords.js
@@ -34,17 +34,17 @@ const TEST_ADDRESS_3 = {
 
 const TEST_ADDRESS_4 = {
   "given-name": "Timothy",
   "additional-name": "John",
   "family-name": "Berners-Lee",
   organization: "World Wide Web Consortium",
 };
 
-const TEST_ADDRESS_FOR_UPDATE = {
+const TEST_ADDRESS_WITH_EMPTY_FIELD = {
   "name": "Tim Berners",
   "street-address": "",
 };
 
 const TEST_ADDRESS_WITH_INVALID_FIELD = {
   "street-address": "Another Address",
   invalidField: "INVALID",
 };
@@ -294,16 +294,22 @@ add_task(async function test_add() {
 
   do_check_neq(addresses[0].guid, undefined);
   do_check_eq(addresses[0].version, 1);
   do_check_neq(addresses[0].timeCreated, undefined);
   do_check_eq(addresses[0].timeLastModified, addresses[0].timeCreated);
   do_check_eq(addresses[0].timeLastUsed, 0);
   do_check_eq(addresses[0].timesUsed, 0);
 
+  // Empty string should be deleted before saving.
+  profileStorage.addresses.add(TEST_ADDRESS_WITH_EMPTY_FIELD);
+  let address = profileStorage.addresses.data[2];
+  do_check_eq(address.name, TEST_ADDRESS_WITH_EMPTY_FIELD.name);
+  do_check_eq(address["street-address"], undefined);
+
   Assert.throws(() => profileStorage.addresses.add(TEST_ADDRESS_WITH_INVALID_FIELD),
     /"invalidField" is not a valid field\./);
 });
 
 add_task(async function test_update() {
   let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
                                                 [TEST_ADDRESS_1, TEST_ADDRESS_2]);
 
@@ -328,31 +334,37 @@ add_task(async function test_update() {
   let address = profileStorage.addresses.get(guid, {rawData: true});
 
   do_check_eq(address.country, undefined);
   do_check_neq(address.timeLastModified, timeLastModified);
   do_check_record_matches(address, TEST_ADDRESS_3);
   do_check_eq(getSyncChangeCounter(profileStorage.addresses, guid), 1);
 
   // Test preserveOldProperties parameter and field with empty string.
-  profileStorage.addresses.update(guid, TEST_ADDRESS_FOR_UPDATE, true);
+  profileStorage.addresses.update(guid, TEST_ADDRESS_WITH_EMPTY_FIELD, true);
   await onChanged;
   await profileStorage._saveImmediately();
 
   profileStorage.addresses.pullSyncChanges(); // force sync metadata, which we check below.
 
   address = profileStorage.addresses.get(guid, {rawData: true});
 
   do_check_eq(address["given-name"], "Tim");
   do_check_eq(address["family-name"], "Berners");
   do_check_eq(address["street-address"], undefined);
   do_check_eq(address["postal-code"], "12345");
   do_check_neq(address.timeLastModified, timeLastModified);
   do_check_eq(getSyncChangeCounter(profileStorage.addresses, guid), 2);
 
+  // Empty string should be deleted while updating.
+  profileStorage.addresses.update(profileStorage.addresses.data[0].guid, TEST_ADDRESS_WITH_EMPTY_FIELD);
+  address = profileStorage.addresses.data[0];
+  do_check_eq(address.name, TEST_ADDRESS_WITH_EMPTY_FIELD.name);
+  do_check_eq(address["street-address"], undefined);
+
   Assert.throws(
     () => profileStorage.addresses.update("INVALID_GUID", TEST_ADDRESS_3),
     /No matching record\./
   );
 
   Assert.throws(
     () => profileStorage.addresses.update(guid, TEST_ADDRESS_WITH_INVALID_FIELD),
     /"invalidField" is not a valid field\./
--- a/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
@@ -23,16 +23,22 @@ const TEST_CREDIT_CARD_2 = {
 };
 
 const TEST_CREDIT_CARD_3 = {
   "cc-number": "9999888877776666",
   "cc-exp-month": 1,
   "cc-exp-year": 2000,
 };
 
+const TEST_CREDIT_CARD_WITH_EMPTY_FIELD = {
+  "cc-name": "",
+  "cc-number": "1234123412341234",
+  "cc-exp-month": 1,
+};
+
 const TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR = {
   "cc-number": "1234123412341234",
   "cc-exp-month": 1,
   "cc-exp-year": 12,
 };
 
 const TEST_CREDIT_CARD_WITH_INVALID_FIELD = {
   "cc-name": "John Doe",
@@ -165,16 +171,22 @@ add_task(async function test_add() {
 
   do_check_neq(creditCards[0].guid, undefined);
   do_check_eq(creditCards[0].version, 1);
   do_check_neq(creditCards[0].timeCreated, undefined);
   do_check_eq(creditCards[0].timeLastModified, creditCards[0].timeCreated);
   do_check_eq(creditCards[0].timeLastUsed, 0);
   do_check_eq(creditCards[0].timesUsed, 0);
 
+  // Empty string should be deleted before saving.
+  profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_EMPTY_FIELD);
+  let creditCard = profileStorage.creditCards.data[2];
+  do_check_eq(creditCard["cc-exp-month"], TEST_CREDIT_CARD_WITH_EMPTY_FIELD["cc-exp-month"]);
+  do_check_eq(creditCard["cc-name"], undefined);
+
   Assert.throws(() => profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_FIELD),
     /"invalidField" is not a valid field\./);
 });
 
 add_task(async function test_update() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
   await prepareTestCreditCards(path);
 
@@ -197,16 +209,22 @@ add_task(async function test_update() {
   await profileStorage.initialize();
 
   let creditCard = profileStorage.creditCards.get(guid);
 
   do_check_eq(creditCard["cc-name"], undefined);
   do_check_neq(creditCard.timeLastModified, timeLastModified);
   do_check_credit_card_matches(creditCard, TEST_CREDIT_CARD_3);
 
+  // Empty string should be deleted while updating.
+  profileStorage.creditCards.update(profileStorage.creditCards.data[0].guid, TEST_CREDIT_CARD_WITH_EMPTY_FIELD);
+  creditCard = profileStorage.creditCards.data[0];
+  do_check_eq(creditCard["cc-exp-month"], TEST_CREDIT_CARD_WITH_EMPTY_FIELD["cc-exp-month"]);
+  do_check_eq(creditCard["cc-name"], undefined);
+
   Assert.throws(
     () => profileStorage.creditCards.update("INVALID_GUID", TEST_CREDIT_CARD_3),
     /No matching record\./
   );
 
   Assert.throws(
     () => profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_WITH_INVALID_FIELD),
     /"invalidField" is not a valid field\./
--- a/build/docs/toolchains.rst
+++ b/build/docs/toolchains.rst
@@ -46,17 +46,17 @@ 2. Select ``Programming Languages`` -> `
 3. Under ``Windows and Web Development`` uncheck everything except
    ``Universal Windows App Development Tools`` and the items under it
    (should be ``Tools (1.3.1)...`` and the ``Windows 10 SDK``).
 
 Once Visual Studio 2015 Community has been installed, from a checkout
 of mozilla-central, run something like the following to produce a ZIP
 archive::
 
-   $ ./mach python build/windows_toolchain.py create-zip vs2015u3
+   $ ./mach python build/windows_toolchain.py create-zip vs2017_15.4.2
 
 The produced archive will be the argument to ``create-zip`` + ``.zip``.
 
 Firefox for Android with Gradle
 ===============================
 
 To build Firefox for Android with Gradle in automation, archives
 containing both the Gradle executable and a Maven repository
--- a/build/windows_toolchain.py
+++ b/build/windows_toolchain.py
@@ -19,154 +19,171 @@ import sys
 from mozpack.files import (
     FileFinder,
 )
 from mozpack.mozjar import (
     JarWriter,
 )
 import mozpack.path as mozpath
 
+SDK_RELEASE = '10.0.15063.0'
 
-# mozpack.match patterns for files under "Microsoft Visual Studio 14.0".
-VS_PATTERNS = [
-    {
-        'pattern': 'DIA SDK/bin/**',
-        'ignore': (
-            'DIA SDK/bin/arm/**',
-        ),
-    },
-    {
-        'pattern': 'DIA SDK/idl/**',
-    },
-    {
-        'pattern': 'DIA SDK/include/**',
-    },
+PATTERNS = [
     {
-        'pattern': 'DIA SDK/lib/**',
-        'ignore': (
-            'DIA SDK/lib/arm/**',
-        ),
-    },
-    # ATL is needed by Breakpad.
-    {
-        'pattern': 'VC/atlmfc/include/**',
-    },
-    {
-        'pattern': 'VC/atlmfc/lib/atls.*',
-    },
-    {
-        'pattern': 'VC/atlmfc/lib/amd64/atls.*',
-    },
-    {
-        'pattern': 'VC/bin/**',
-        # We only care about compiling on amd64 for amd64 or x86 targets.
-        'ignore': (
-            'VC/bin/amd64_arm/**',
-            'VC/bin/arm/**',
-            'VC/bin/x86_arm/**',
-            'VC/bin/x86_amd64/**',
-        ),
-    },
-    {
-        'pattern': 'VC/include/**',
+        'srcdir': '%(vs_path)s/DIA SDK',
+        'dstdir': 'DIA SDK',
+        'files': [
+            {
+                'pattern': 'bin/**',
+                'ignore': (
+                    'bin/arm/**',
+                ),
+            },
+            {
+                'pattern': 'idl/**',
+            },
+            {
+                'pattern': 'include/**',
+            },
+            {
+                'pattern': 'lib/**',
+                'ignore': (
+                    'lib/arm/**',
+                ),
+            },
+        ],
     },
     {
-        'pattern': 'VC/lib/**',
-        'ignore': (
-            'VC/lib/arm/**',
-            'VC/lib/onecore/**',
-            'VC/lib/store/**',
-        ),
-    },
-    {
-        'pattern': 'VC/redist/x64/Microsoft.VC140.CRT/**',
-    },
-    {
-        'pattern': 'VC/redist/x86/Microsoft.VC140.CRT/**',
-    },
-]
-
-SDK_RELEASE = '10.0.14393.0'
-
-# Files from the Windows 10 SDK to install.
-SDK_PATTERNS = [
-    {
-        'pattern': 'bin/x64/**',
+        'srcdir': '%(vs_path)s/VC/Tools/MSVC/14.11.25503',
+        'dstdir': 'VC',
+        'files': [
+            # ATL is needed by Breakpad.
+            {
+                'pattern': 'atlmfc/include/**',
+            },
+            {
+                'pattern': 'atlmfc/lib/x86/atls.*',
+            },
+            {
+                'pattern': 'atlmfc/lib/x64/atls.*',
+            },
+            {
+                'pattern': 'bin/Hostx64/**',
+            },
+            # 32-bit PGO-instrumented builds require 32-bit pgort140.dll.
+            {
+                'pattern': 'bin/Hostx86/x86/pgort140.dll',
+            },
+            {
+                'pattern': 'include/**',
+            },
+            {
+                'pattern': 'lib/**',
+                'ignore': (
+                    'lib/onecore/**',
+                    'lib/x64/store/**',
+                    'lib/x86/store/**',
+                ),
+            },
+        ],
     },
     {
-        'pattern': 'Include/%s/**' % SDK_RELEASE,
-    },
-    {
-        'pattern': 'Lib/%s/ucrt/x64/**' % SDK_RELEASE,
-    },
-    {
-        'pattern': 'Lib/%s/ucrt/x86/**' % SDK_RELEASE,
+        'srcdir': '%(vs_path)s/VC/Redist/MSVC/14.11.25325',
+        'dstdir': 'VC/redist',
+        'files': [
+            {
+                'pattern': 'x64/Microsoft.VC141.CRT/**',
+            },
+            {
+                'pattern': 'x86/Microsoft.VC141.CRT/**',
+            },
+        ],
     },
     {
-        'pattern': 'Lib/%s/um/x64/**' % SDK_RELEASE,
-    },
-    {
-        'pattern': 'Lib/%s/um/x86/**' % SDK_RELEASE,
-    },
-    {
-        'pattern': 'Redist/D3D/**',
-    },
-    {
-        'pattern': 'Redist/ucrt/DLLs/x64/**',
-    },
-    {
-        'pattern': 'Redist/ucrt/DLLs/x86/**',
+        'srcdir': '%(sdk_path)s',
+        'dstdir': 'SDK',
+        'files': [
+            {
+                'pattern': 'bin/%s/x64/**' % SDK_RELEASE,
+            },
+            {
+                'pattern': 'Include/%s/**' % SDK_RELEASE,
+            },
+            {
+                'pattern': 'Lib/%s/ucrt/x64/**' % SDK_RELEASE,
+            },
+            {
+                'pattern': 'Lib/%s/ucrt/x86/**' % SDK_RELEASE,
+            },
+            {
+                'pattern': 'Lib/%s/um/x64/**' % SDK_RELEASE,
+            },
+            {
+                'pattern': 'Lib/%s/um/x86/**' % SDK_RELEASE,
+            },
+            {
+                'pattern': 'Redist/D3D/**',
+            },
+            {
+                'pattern': 'Redist/ucrt/DLLs/x64/**',
+            },
+            {
+                'pattern': 'Redist/ucrt/DLLs/x86/**',
+            },
+        ],
     },
 ]
 
 
 def find_vs_paths():
     """Resolve source locations of files.
 
     Returns a 2-tuple of (Visual Studio Path, SDK Path).
     """
     pf = os.environ.get('ProgramFiles(x86)')
     if not pf:
         raise Exception('No "ProgramFiles(x86)" environment variable. '
                         'Not running on 64-bit Windows?')
 
-    vs_path = os.path.join(pf, 'Microsoft Visual Studio 14.0')
+    vs_path = os.path.join(pf, 'Microsoft Visual Studio', '2017', 'Community')
     if not os.path.exists(vs_path):
-        raise Exception('%s does not exist; Visual Studio 2015 not installed?' %
+        raise Exception('%s does not exist; Visual Studio 2017 not installed?' %
                         vs_path)
 
     sdk_path = os.path.join(pf, 'Windows Kits', '10')
     if not os.path.exists(sdk_path):
         raise Exception('%s does not exist; Windows 10 SDK not installed?' %
                         sdk_path)
 
+    sdk_fullver_path = os.path.join(sdk_path, 'Include', SDK_RELEASE)
+    if not os.path.exists(sdk_fullver_path):
+        raise Exception('%s does not exist; Wrong SDK version installed?' %
+                        sdk_fullver_path)
+
     return vs_path, sdk_path
 
 
 def resolve_files():
     """Resolve the files that constitute a standalone toolchain.
 
     This is a generator of (dest path, file) where the destination
     path is relative and the file instance is a BaseFile from mozpack.
     """
     vs_path, sdk_path = find_vs_paths()
 
-    for entry in VS_PATTERNS:
-        finder = FileFinder(vs_path, ignore=entry.get('ignore', []))
-        for p, f in finder.find(entry['pattern']):
-            assert p.startswith(('VC/', 'DIA SDK/'))
-
-            yield p.encode('utf-8'), f
-
-    for entry in SDK_PATTERNS:
-        finder = FileFinder(sdk_path, ignore=entry.get('ignore', []))
-        for p, f in finder.find(entry['pattern']):
-            relpath = 'SDK/%s' % p
-
-            yield relpath.encode('utf-8'), f
+    for entry in PATTERNS:
+        fullpath = entry['srcdir'] % {
+            'vs_path': vs_path,
+            'sdk_path': sdk_path,
+        }
+        for pattern in entry['files']:
+            finder = FileFinder(fullpath, ignore=pattern.get('ignore', []))
+            for p, f in finder.find(pattern['pattern']):
+                dstpath = '%s/%s' % (entry['dstdir'], p)
+                yield dstpath.encode('utf-8'), f
 
 
 def resolve_files_and_hash(manifest):
     """Resolve files and hash their data.
 
     This is a generator of 3-tuples of (relpath, data, mode).
 
     As data is read, the manifest is populated with metadata.
@@ -196,17 +213,17 @@ def format_manifest(manifest):
     return b'\n'.join(sha256_lines)
 
 
 def write_zip(zip_path, prefix=None):
     """Write toolchain data to a zip file."""
     if isinstance(prefix, unicode): # noqa Special case for Python 2
         prefix = prefix.encode('utf-8')
 
-    with JarWriter(file=zip_path, optimize=False, compress=5) as zip:
+    with JarWriter(file=zip_path, optimize=False, compress_level=5) as zip:
         manifest = {}
         for p, data, mode in resolve_files_and_hash(manifest):
             print(p)
             if prefix:
                 p = mozpath.join(prefix, p)
 
             zip.add(p, data, mode=mode)
 
--- a/devtools/client/aboutdebugging/components/Aboutdebugging.js
+++ b/devtools/client/aboutdebugging/components/Aboutdebugging.js
@@ -1,17 +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/. */
 
 /* eslint-env browser */
 
 "use strict";
 
-const { createFactory, createClass, DOM: dom, PropTypes } =
+const { createFactory, Component, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 const Services = require("Services");
 
 const PanelMenu = createFactory(require("./PanelMenu"));
 
 loader.lazyGetter(this, "AddonsPanel",
   () => createFactory(require("./addons/Panel")));
 loader.lazyGetter(this, "TabsPanel",
@@ -41,51 +41,56 @@ const panels = [{
   id: "workers",
   name: Strings.GetStringFromName("workers"),
   icon: "chrome://devtools/skin/images/debugging-workers.svg",
   component: WorkersPanel
 }];
 
 const defaultPanelId = "addons";
 
-module.exports = createClass({
-  displayName: "AboutDebuggingApp",
+class AboutDebuggingApp extends Component {
+  static get propTypes() {
+    return {
+      client: PropTypes.instanceOf(DebuggerClient).isRequired,
+      telemetry: PropTypes.instanceOf(Telemetry).isRequired
+    };
+  }
 
-  propTypes: {
-    client: PropTypes.instanceOf(DebuggerClient).isRequired,
-    telemetry: PropTypes.instanceOf(Telemetry).isRequired
-  },
+  constructor(props) {
+    super(props);
 
-  getInitialState() {
-    return {
+    this.state = {
       selectedPanelId: defaultPanelId
     };
-  },
+
+    this.onHashChange = this.onHashChange.bind(this);
+    this.selectPanel = this.selectPanel.bind(this);
+  }
 
   componentDidMount() {
     window.addEventListener("hashchange", this.onHashChange);
     this.onHashChange();
     this.props.telemetry.toolOpened("aboutdebugging");
-  },
+  }
 
   componentWillUnmount() {
     window.removeEventListener("hashchange", this.onHashChange);
     this.props.telemetry.toolClosed("aboutdebugging");
     this.props.telemetry.destroy();
-  },
+  }
 
   onHashChange() {
     this.setState({
       selectedPanelId: window.location.hash.substr(1) || defaultPanelId
     });
-  },
+  }
 
   selectPanel(panelId) {
     window.location.hash = "#" + panelId;
-  },
+  }
 
   render() {
     let { client } = this.props;
     let { selectedPanelId } = this.state;
     let selectPanel = this.selectPanel;
     let selectedPanel = panels.find(p => p.id == selectedPanelId);
     let panel;
 
@@ -103,9 +108,11 @@ module.exports = createClass({
       );
     }
 
     return dom.div({ className: "app" },
       PanelMenu({ panels, selectedPanelId, selectPanel }),
       dom.div({ className: "main-content" }, panel)
     );
   }
-});
+}
+
+module.exports = AboutDebuggingApp;
--- a/devtools/client/aboutdebugging/components/PanelHeader.js
+++ b/devtools/client/aboutdebugging/components/PanelHeader.js
@@ -1,24 +1,26 @@
 /* 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 { createClass, DOM: dom, PropTypes } =
+const { Component, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 
-module.exports = createClass({
-  displayName: "PanelHeader",
-
-  propTypes: {
-    id: PropTypes.string.isRequired,
-    name: PropTypes.string.isRequired
-  },
+class PanelHeader extends Component {
+  static get propTypes() {
+    return {
+      id: PropTypes.string.isRequired,
+      name: PropTypes.string.isRequired
+    };
+  }
 
   render() {
     let { name, id } = this.props;
 
     return dom.div({ className: "header" },
       dom.h1({ id, className: "header-name" }, name));
-  },
-});
+  }
+}
+
+module.exports = PanelHeader;
--- a/devtools/client/aboutdebugging/components/PanelMenu.js
+++ b/devtools/client/aboutdebugging/components/PanelMenu.js
@@ -1,41 +1,43 @@
 /* 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 { createClass, createFactory, DOM: dom, PropTypes } =
+const { Component, createFactory, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 const PanelMenuEntry = createFactory(require("./PanelMenuEntry"));
 
-module.exports = createClass({
-  displayName: "PanelMenu",
-
-  propTypes: {
-    panels: PropTypes.arrayOf(PropTypes.shape({
-      id: PropTypes.string.isRequired,
-      name: PropTypes.string.isRequired,
-      icon: PropTypes.string.isRequired,
-      component: PropTypes.func.isRequired
-    })).isRequired,
-    selectPanel: PropTypes.func.isRequired,
-    selectedPanelId: PropTypes.string
-  },
+class PanelMenu extends Component {
+  static get propTypes() {
+    return {
+      panels: PropTypes.arrayOf(PropTypes.shape({
+        id: PropTypes.string.isRequired,
+        name: PropTypes.string.isRequired,
+        icon: PropTypes.string.isRequired,
+        component: PropTypes.func.isRequired
+      })).isRequired,
+      selectPanel: PropTypes.func.isRequired,
+      selectedPanelId: PropTypes.string
+    };
+  }
 
   render() {
     let { panels, selectedPanelId, selectPanel } = this.props;
     let panelLinks = panels.map(({ id, name, icon }) => {
       let selected = id == selectedPanelId;
       return PanelMenuEntry({
         id,
         name,
         icon,
         selected,
         selectPanel
       });
     });
 
     // "categories" id used for styling purposes
     return dom.div({ id: "categories", role: "tablist" }, panelLinks);
-  },
-});
+  }
+}
+
+module.exports = PanelMenu;
--- a/devtools/client/aboutdebugging/components/PanelMenuEntry.js
+++ b/devtools/client/aboutdebugging/components/PanelMenuEntry.js
@@ -1,31 +1,36 @@
 /* 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 { createClass, DOM: dom, PropTypes } =
+const { Component, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 
-module.exports = createClass({
-  displayName: "PanelMenuEntry",
+class PanelMenuEntry extends Component {
+  static get propTypes() {
+    return {
+      icon: PropTypes.string.isRequired,
+      id: PropTypes.string.isRequired,
+      name: PropTypes.string.isRequired,
+      selected: PropTypes.bool,
+      selectPanel: PropTypes.func.isRequired
+    };
+  }
 
-  propTypes: {
-    icon: PropTypes.string.isRequired,
-    id: PropTypes.string.isRequired,
-    name: PropTypes.string.isRequired,
-    selected: PropTypes.bool,
-    selectPanel: PropTypes.func.isRequired
-  },
+  constructor(props) {
+    super(props);
+    this.onClick = this.onClick.bind(this);
+  }
 
   onClick() {
     this.props.selectPanel(this.props.id);
-  },
+  }
 
   render() {
     let { id, name, icon, selected } = this.props;
 
     // Here .category, .category-icon, .category-name classnames are used to
     // apply common styles defined.
     let className = "category" + (selected ? " selected" : "");
     return dom.button({
@@ -33,9 +38,11 @@ module.exports = createClass({
       "aria-controls": id + "-panel",
       className,
       onClick: this.onClick,
       tabIndex: "0",
       role: "tab" },
     dom.img({ className: "category-icon", src: icon, role: "presentation" }),
     dom.div({ className: "category-name" }, name));
   }
-});
+}
+
+module.exports = PanelMenuEntry;
--- a/devtools/client/aboutdebugging/components/TargetList.js
+++ b/devtools/client/aboutdebugging/components/TargetList.js
@@ -1,41 +1,41 @@
 /* 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 { createClass, DOM: dom, PropTypes } =
+const { Component, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 const Services = require("Services");
 
 loader.lazyRequireGetter(this, "DebuggerClient",
   "devtools/shared/client/debugger-client", true);
 
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 
 const LocaleCompare = (a, b) => {
   return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
 };
 
-module.exports = createClass({
-  displayName: "TargetList",
-
-  propTypes: {
-    client: PropTypes.instanceOf(DebuggerClient).isRequired,
-    debugDisabled: PropTypes.bool,
-    error: PropTypes.node,
-    id: PropTypes.string.isRequired,
-    name: PropTypes.string,
-    sort: PropTypes.bool,
-    targetClass: PropTypes.func.isRequired,
-    targets: PropTypes.arrayOf(PropTypes.object).isRequired
-  },
+class TargetList extends Component {
+  static get propTypes() {
+    return {
+      client: PropTypes.instanceOf(DebuggerClient).isRequired,
+      debugDisabled: PropTypes.bool,
+      error: PropTypes.node,
+      id: PropTypes.string.isRequired,
+      name: PropTypes.string,
+      sort: PropTypes.bool,
+      targetClass: PropTypes.func.isRequired,
+      targets: PropTypes.arrayOf(PropTypes.object).isRequired
+    };
+  }
 
   render() {
     let { client, debugDisabled, error, targetClass, targets, sort } = this.props;
     if (sort) {
       targets = targets.sort(LocaleCompare);
     }
     targets = targets.map(target => {
       return targetClass({ client, target, debugDisabled });
@@ -47,10 +47,12 @@ module.exports = createClass({
     } else if (targets.length > 0) {
       content = dom.ul({ className: "target-list" }, targets);
     } else {
       content = dom.p(null, Strings.GetStringFromName("nothing"));
     }
 
     return dom.div({ id: this.props.id, className: "targets" },
       dom.h2(null, this.props.name), content);
-  },
-});
+  }
+}
+
+module.exports = TargetList;
--- a/devtools/client/aboutdebugging/components/addons/Controls.js
+++ b/devtools/client/aboutdebugging/components/addons/Controls.js
@@ -6,45 +6,52 @@
 /* globals AddonManager */
 
 "use strict";
 
 loader.lazyImporter(this, "AddonManager",
   "resource://gre/modules/AddonManager.jsm");
 
 const { Cc, Ci } = require("chrome");
-const { createFactory, createClass, DOM: dom, PropTypes } =
+const { createFactory, Component, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 const Services = require("Services");
 const AddonsInstallError = createFactory(require("./InstallError"));
 
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 
 const MORE_INFO_URL = "https://developer.mozilla.org/docs/Tools" +
                       "/about:debugging#Enabling_add-on_debugging";
 
-module.exports = createClass({
-  displayName: "AddonsControls",
+class AddonsControls extends Component {
+  static get propTypes() {
+    return {
+      debugDisabled: PropTypes.bool
+    };
+  }
 
-  propTypes: {
-    debugDisabled: PropTypes.bool
-  },
+  constructor(props) {
+    super(props);
 
-  getInitialState() {
-    return {
+    this.state = {
       installError: null,
     };
-  },
+
+    this.onEnableAddonDebuggingChange = this.onEnableAddonDebuggingChange.bind(this);
+    this.loadAddonFromFile = this.loadAddonFromFile.bind(this);
+    this.retryInstall = this.retryInstall.bind(this);
+    this.installAddon = this.installAddon.bind(this);
+  }
 
   onEnableAddonDebuggingChange(event) {
     let enabled = event.target.checked;
     Services.prefs.setBoolPref("devtools.chrome.enabled", enabled);
     Services.prefs.setBoolPref("devtools.debugger.remote-enabled", enabled);
-  },
+  }
 
   loadAddonFromFile() {
     this.setState({ installError: null });
     let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
     fp.init(window,
       Strings.GetStringFromName("selectAddonFromFile2"),
       Ci.nsIFilePicker.modeOpen);
     fp.open(res => {
@@ -55,33 +62,33 @@ module.exports = createClass({
       // AddonManager.installTemporaryAddon accepts either
       // addon directory or final xpi file.
       if (!file.isDirectory() && !file.leafName.endsWith(".xpi")) {
         file = file.parent;
       }
 
       this.installAddon(file);
     });
-  },
+  }
 
   retryInstall() {
     this.setState({ installError: null });
     this.installAddon(this.state.lastInstallErrorFile);
-  },
+  }
 
   installAddon(file) {
     AddonManager.installTemporaryAddon(file)
       .then(() => {
         this.setState({ lastInstallErrorFile: null });
       })
       .catch(e => {
         console.error(e);
         this.setState({ installError: e.message, lastInstallErrorFile: file });
       });
-  },
+  }
 
   render() {
     let { debugDisabled } = this.props;
 
     return dom.div({ className: "addons-top" },
       dom.div({ className: "addons-controls" },
         dom.div({ className: "addons-options toggle-container-with-text" },
           dom.input({
@@ -105,9 +112,11 @@ module.exports = createClass({
           onClick: this.loadAddonFromFile,
         }, Strings.GetStringFromName("loadTemporaryAddon"))
       ),
       AddonsInstallError({
         error: this.state.installError,
         retryInstall: this.retryInstall,
       }));
   }
-});
+}
+
+module.exports = AddonsControls;
--- a/devtools/client/aboutdebugging/components/addons/InstallError.js
+++ b/devtools/client/aboutdebugging/components/addons/InstallError.js
@@ -1,29 +1,29 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env browser */
 "use strict";
 
-const { createClass, DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react");
+const { Component, DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react");
 
 const Services = require("Services");
 
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 
-module.exports = createClass({
-  displayName: "AddonsInstallError",
-
-  propTypes: {
-    error: PropTypes.string,
-    retryInstall: PropTypes.func,
-  },
+class AddonsInstallError extends Component {
+  static get propTypes() {
+    return {
+      error: PropTypes.string,
+      retryInstall: PropTypes.func,
+    };
+  }
 
   render() {
     if (!this.props.error) {
       return null;
     }
     let text = `There was an error during installation: ${this.props.error}`;
     return dom.div(
       { className: "addons-install-error" },
@@ -31,9 +31,11 @@ module.exports = createClass({
         {},
         dom.div({ className: "warning" }),
         dom.span({}, text),
       ),
       dom.button(
         { className: "addons-install-retry", onClick: this.props.retryInstall },
         Strings.GetStringFromName("retryTemporaryInstall")));
   }
-});
+}
+
+module.exports = AddonsInstallError;
--- a/devtools/client/aboutdebugging/components/addons/Panel.js
+++ b/devtools/client/aboutdebugging/components/addons/Panel.js
@@ -1,17 +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/. */
 
 "use strict";
 
 const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
 const { Management } = require("resource://gre/modules/Extension.jsm");
-const { createFactory, createClass, DOM: dom, PropTypes } =
+const { createFactory, Component, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 const Services = require("Services");
 
 const AddonsControls = createFactory(require("./Controls"));
 const AddonTarget = createFactory(require("./Target"));
 const PanelHeader = createFactory(require("../PanelHeader"));
 const TargetList = createFactory(require("../TargetList"));
 
@@ -22,63 +22,72 @@ const Strings = Services.strings.createB
   "chrome://devtools/locale/aboutdebugging.properties");
 
 const ExtensionIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
 const CHROME_ENABLED_PREF = "devtools.chrome.enabled";
 const REMOTE_ENABLED_PREF = "devtools.debugger.remote-enabled";
 const WEB_EXT_URL = "https://developer.mozilla.org/Add-ons" +
                     "/WebExtensions/Getting_started_with_web-ext";
 
-module.exports = createClass({
-  displayName: "AddonsPanel",
+class AddonsPanel extends Component {
+  static get propTypes() {
+    return {
+      client: PropTypes.instanceOf(DebuggerClient).isRequired,
+      id: PropTypes.string.isRequired
+    };
+  }
 
-  propTypes: {
-    client: PropTypes.instanceOf(DebuggerClient).isRequired,
-    id: PropTypes.string.isRequired
-  },
+  constructor(props) {
+    super(props);
 
-  getInitialState() {
-    return {
+    this.state = {
       extensions: [],
       debugDisabled: false,
     };
-  },
+
+    this.updateDebugStatus = this.updateDebugStatus.bind(this);
+    this.updateAddonsList = this.updateAddonsList.bind(this);
+    this.onInstalled = this.onInstalled.bind(this);
+    this.onUninstalled = this.onUninstalled.bind(this);
+    this.onEnabled = this.onEnabled.bind(this);
+    this.onDisabled = this.onDisabled.bind(this);
+  }
 
   componentDidMount() {
     AddonManager.addAddonListener(this);
     // Listen to startup since that's when errors and warnings
     // get populated on the extension.
     Management.on("startup", this.updateAddonsList);
 
     Services.prefs.addObserver(CHROME_ENABLED_PREF,
       this.updateDebugStatus);
     Services.prefs.addObserver(REMOTE_ENABLED_PREF,
       this.updateDebugStatus);
 
     this.updateDebugStatus();
     this.updateAddonsList();
-  },
+  }
 
   componentWillUnmount() {
     AddonManager.removeAddonListener(this);
     Management.off("startup", this.updateAddonsList);
 
     Services.prefs.removeObserver(CHROME_ENABLED_PREF,
       this.updateDebugStatus);
     Services.prefs.removeObserver(REMOTE_ENABLED_PREF,
       this.updateDebugStatus);
-  },
+  }
 
   updateDebugStatus() {
     let debugDisabled =
       !Services.prefs.getBoolPref(CHROME_ENABLED_PREF) ||
       !Services.prefs.getBoolPref(REMOTE_ENABLED_PREF);
 
     this.setState({ debugDisabled });
-  },
+  }
 
   updateAddonsList() {
     this.props.client.listAddons()
       .then(({addons}) => {
         let extensions = addons.filter(addon => addon.debuggable).map(addon => {
           return {
             name: addon.name,
             icon: addon.iconURL || ExtensionIcon,
@@ -90,45 +99,45 @@ module.exports = createClass({
             warnings: addon.warnings,
           };
         });
 
         this.setState({ extensions });
       }, error => {
         throw new Error("Client error while listing addons: " + error);
       });
-  },
+  }
 
   /**
    * Mandatory callback as AddonManager listener.
    */
   onInstalled() {
     this.updateAddonsList();
-  },
+  }
 
   /**
    * Mandatory callback as AddonManager listener.
    */
   onUninstalled() {
     this.updateAddonsList();
-  },
+  }
 
   /**
    * Mandatory callback as AddonManager listener.
    */
   onEnabled() {
     this.updateAddonsList();
-  },
+  }
 
   /**
    * Mandatory callback as AddonManager listener.
    */
   onDisabled() {
     this.updateAddonsList();
-  },
+  }
 
   render() {
     let { client, id } = this.props;
     let { debugDisabled, extensions: targets } = this.state;
     let installedName = Strings.GetStringFromName("extensions");
     let temporaryName = Strings.GetStringFromName("temporaryExtensions");
     let targetClass = AddonTarget;
 
@@ -172,9 +181,11 @@ module.exports = createClass({
         targets: installedTargets,
         client,
         debugDisabled,
         targetClass,
         sort: true
       })
     ));
   }
-});
+}
+
+module.exports = AddonsPanel;
--- a/devtools/client/aboutdebugging/components/addons/Target.js
+++ b/devtools/client/aboutdebugging/components/addons/Target.js
@@ -1,17 +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/. */
 
 /* eslint-env browser */
 
 "use strict";
 
-const { createClass, DOM: dom, PropTypes } =
+const { Component, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 const { debugAddon, isTemporaryID, parseFileUri, uninstallAddon } =
   require("../../modules/addon");
 const Services = require("Services");
 
 loader.lazyImporter(this, "BrowserToolboxProcess",
   "resource://devtools/client/framework/ToolboxProcess.jsm");
 
@@ -117,55 +117,62 @@ function infoMessages(target) {
 function warningMessages(warnings = []) {
   return warnings.map((warning) => {
     return dom.li(
       { className: "addon-target-warning-message addon-target-message" },
       warning);
   });
 }
 
-module.exports = createClass({
-  displayName: "AddonTarget",
+class AddonTarget extends Component {
+  static get propTypes() {
+    return {
+      client: PropTypes.instanceOf(DebuggerClient).isRequired,
+      debugDisabled: PropTypes.bool,
+      target: PropTypes.shape({
+        addonActor: PropTypes.string.isRequired,
+        addonID: PropTypes.string.isRequired,
+        icon: PropTypes.string,
+        name: PropTypes.string.isRequired,
+        temporarilyInstalled: PropTypes.bool,
+        url: PropTypes.string,
+        warnings: PropTypes.array,
+      }).isRequired
+    };
+  }
 
-  propTypes: {
-    client: PropTypes.instanceOf(DebuggerClient).isRequired,
-    debugDisabled: PropTypes.bool,
-    target: PropTypes.shape({
-      addonActor: PropTypes.string.isRequired,
-      addonID: PropTypes.string.isRequired,
-      icon: PropTypes.string,
-      name: PropTypes.string.isRequired,
-      temporarilyInstalled: PropTypes.bool,
-      url: PropTypes.string,
-      warnings: PropTypes.array,
-    }).isRequired
-  },
+  constructor(props) {
+    super(props);
+    this.debug = this.debug.bind(this);
+    this.uninstall = this.uninstall.bind(this);
+    this.reload = this.reload.bind(this);
+  }
 
   debug() {
     let { target } = this.props;
     debugAddon(target.addonID);
-  },
+  }
 
   uninstall() {
     let { target } = this.props;
     uninstallAddon(target.addonID);
-  },
+  }
 
   reload() {
     let { client, target } = this.props;
     // This function sometimes returns a partial promise that only
     // implements then().
     client.request({
       to: target.addonActor,
       type: "reload"
     }).then(() => {}, error => {
       throw new Error(
         "Error reloading addon " + target.addonID + ": " + error);
     });
-  },
+  }
 
   render() {
     let { target, debugDisabled } = this.props;
 
     return dom.li(
       { className: "addon-target-container", "data-addon-id": target.addonID },
       dom.div({ className: "target" },
         dom.img({
@@ -200,9 +207,11 @@ module.exports = createClass({
           ? dom.button({
             className: "uninstall-button addon-target-button",
             onClick: this.uninstall,
           }, Strings.GetStringFromName("remove"))
           : null,
       ),
     );
   }
-});
+}
+
+module.exports = AddonTarget;
--- a/devtools/client/aboutdebugging/components/tabs/Panel.js
+++ b/devtools/client/aboutdebugging/components/tabs/Panel.js
@@ -1,54 +1,58 @@
 /* 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/. */
 
 /* eslint-env browser */
 
 "use strict";
 
-const { createClass, createFactory, DOM: dom, PropTypes } =
+const { Component, createFactory, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 const Services = require("Services");
 
 const PanelHeader = createFactory(require("../PanelHeader"));
 const TargetList = createFactory(require("../TargetList"));
 const TabTarget = createFactory(require("./Target"));
 
 loader.lazyRequireGetter(this, "DebuggerClient",
   "devtools/shared/client/debugger-client", true);
 
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 
-module.exports = createClass({
-  displayName: "TabsPanel",
+class TabsPanel extends Component {
+  static get propTypes() {
+    return {
+      client: PropTypes.instanceOf(DebuggerClient).isRequired,
+      id: PropTypes.string.isRequired
+    };
+  }
 
-  propTypes: {
-    client: PropTypes.instanceOf(DebuggerClient).isRequired,
-    id: PropTypes.string.isRequired
-  },
+  constructor(props) {
+    super(props);
 
-  getInitialState() {
-    return {
+    this.state = {
       tabs: []
     };
-  },
+
+    this.update = this.update.bind(this);
+  }
 
   componentDidMount() {
     let { client } = this.props;
     client.addListener("tabListChanged", this.update);
     this.update();
-  },
+  }
 
   componentWillUnmount() {
     let { client } = this.props;
     client.removeListener("tabListChanged", this.update);
-  },
+  }
 
   update() {
     this.props.client.mainRoot.listTabs().then(({ tabs }) => {
       // Filter out closed tabs (represented as `null`).
       tabs = tabs.filter(tab => !!tab);
       tabs.forEach(tab => {
         // FIXME Also try to fetch low-res favicon. But we should use actor
         // support for this to get the high-res one (bug 1061654).
@@ -63,17 +67,17 @@ module.exports = createClass({
           }
           tab.icon = prePath + "/favicon.ico";
         } else {
           tab.icon = "chrome://devtools/skin/images/globe.svg";
         }
       });
       this.setState({ tabs });
     });
-  },
+  }
 
   render() {
     let { client, id } = this.props;
     let { tabs } = this.state;
 
     return dom.div({
       id: id + "-panel",
       className: "panel",
@@ -90,9 +94,11 @@ module.exports = createClass({
         id: "tabs",
         name: Strings.GetStringFromName("tabs"),
         sort: false,
         targetClass: TabTarget,
         targets: tabs
       })
     ));
   }
-});
+}
+
+module.exports = TabsPanel;
--- a/devtools/client/aboutdebugging/components/tabs/Target.js
+++ b/devtools/client/aboutdebugging/components/tabs/Target.js
@@ -1,39 +1,44 @@
 /* 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/. */
 
 /* eslint-env browser */
 
 "use strict";
 
-const { createClass, DOM: dom, PropTypes } =
+const { Component, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 const Services = require("Services");
 
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 
-module.exports = createClass({
-  displayName: "TabTarget",
+class TabTarget extends Component {
+  static get propTypes() {
+    return {
+      target: PropTypes.shape({
+        icon: PropTypes.string,
+        outerWindowID: PropTypes.number.isRequired,
+        title: PropTypes.string,
+        url: PropTypes.string.isRequired
+      }).isRequired
+    };
+  }
 
-  propTypes: {
-    target: PropTypes.shape({
-      icon: PropTypes.string,
-      outerWindowID: PropTypes.number.isRequired,
-      title: PropTypes.string,
-      url: PropTypes.string.isRequired
-    }).isRequired
-  },
+  constructor(props) {
+    super(props);
+    this.debug = this.debug.bind(this);
+  }
 
   debug() {
     let { target } = this.props;
     window.open("about:devtools-toolbox?type=tab&id=" + target.outerWindowID);
-  },
+  }
 
   render() {
     let { target } = this.props;
 
     return dom.div({ className: "target-container" },
       dom.img({
         className: "target-icon",
         role: "presentation",
@@ -45,9 +50,11 @@ module.exports = createClass({
           target.title || target.url)
       ),
       dom.button({
         className: "debug-button",
         onClick: this.debug,
       }, Strings.GetStringFromName("debug"))
     );
   }
-});
+}
+
+module.exports = TabTarget;
--- a/devtools/client/aboutdebugging/components/workers/MultiE10sWarning.js
+++ b/devtools/client/aboutdebugging/components/workers/MultiE10sWarning.js
@@ -3,43 +3,46 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env browser */
 
 "use strict";
 
 loader.lazyImporter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
-const { createClass, DOM: dom } =
+const { Component, DOM: dom } =
   require("devtools/client/shared/vendor/react");
 const Services = require("Services");
 const { Ci } = require("chrome");
 
 loader.lazyImporter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 loader.lazyRequireGetter(this, "DebuggerClient",
   "devtools/shared/client/debugger-client", true);
 
 const Strings = Services.strings.createBundle("chrome://devtools/locale/aboutdebugging.properties");
 const MULTI_OPT_OUT_PREF = "dom.ipc.multiOptOut";
 
-module.exports = createClass({
-  displayName: "multiE10SWarning",
+class multiE10SWarning extends Component {
+  constructor(props) {
+    super(props);
+    this.onUpdatePreferenceClick = this.onUpdatePreferenceClick.bind(this);
+  }
 
   onUpdatePreferenceClick() {
     let message = Strings.GetStringFromName("multiProcessWarningConfirmUpdate2");
     if (window.confirm(message)) {
       // Disable multi until at least the next experiment.
       Services.prefs.setIntPref(MULTI_OPT_OUT_PREF,
                                 Services.appinfo.E10S_MULTI_EXPERIMENT);
       // Restart the browser.
       Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
     }
-  },
+  }
 
   render() {
     return dom.div(
       {
         className: "service-worker-multi-process"
       },
       dom.div(
         {},
@@ -53,10 +56,12 @@ module.exports = createClass({
       dom.button(
         {
           className: "update-button",
           onClick: this.onUpdatePreferenceClick,
         },
         Strings.GetStringFromName("multiProcessWarningUpdateLink2")
       )
     );
-  },
-});
+  }
+}
+
+module.exports = multiE10SWarning;
--- a/devtools/client/aboutdebugging/components/workers/Panel.js
+++ b/devtools/client/aboutdebugging/components/workers/Panel.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 /* globals window */
 "use strict";
 
 loader.lazyImporter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 const { Ci } = require("chrome");
-const { createClass, createFactory, DOM: dom, PropTypes } =
+const { Component, createFactory, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 const { getWorkerForms } = require("../../modules/worker");
 const Services = require("Services");
 
 const PanelHeader = createFactory(require("../PanelHeader"));
 const TargetList = createFactory(require("../TargetList"));
 const WorkerTarget = createFactory(require("./Target"));
 const MultiE10SWarning = createFactory(require("./MultiE10sWarning"));
@@ -29,34 +29,35 @@ const Strings = Services.strings.createB
   "chrome://devtools/locale/aboutdebugging.properties");
 
 const WorkerIcon = "chrome://devtools/skin/images/debugging-workers.svg";
 const MORE_INFO_URL = "https://developer.mozilla.org/en-US/docs/Tools/about%3Adebugging" +
                       "#Service_workers_not_compatible";
 const PROCESS_COUNT_PREF = "dom.ipc.processCount";
 const MULTI_OPTOUT_PREF = "dom.ipc.multiOptOut";
 
-module.exports = createClass({
-  displayName: "WorkersPanel",
-
-  propTypes: {
-    client: PropTypes.instanceOf(DebuggerClient).isRequired,
-    id: PropTypes.string.isRequired
-  },
+class WorkersPanel extends Component {
+  static get propTypes() {
+    return {
+      client: PropTypes.instanceOf(DebuggerClient).isRequired,
+      id: PropTypes.string.isRequired
+    };
+  }
 
-  getInitialState() {
-    return {
-      workers: {
-        service: [],
-        shared: [],
-        other: []
-      },
-      processCount: 1,
-    };
-  },
+  constructor(props) {
+    super(props);
+
+    this.updateMultiE10S = this.updateMultiE10S.bind(this);
+    this.updateWorkers = this.updateWorkers.bind(this);
+    this.getRegistrationForWorker = this.getRegistrationForWorker.bind(this);
+    this.isE10S = this.isE10S.bind(this);
+    this.renderServiceWorkersError = this.renderServiceWorkersError.bind(this);
+
+    this.state = this.initialState;
+  }
 
   componentDidMount() {
     let client = this.props.client;
     client.addListener("workerListChanged", this.updateWorkers);
     client.addListener("serviceWorkerRegistrationListChanged", this.updateWorkers);
     client.addListener("processListChanged", this.updateWorkers);
     client.addListener("registration-changed", this.updateWorkers);
 
@@ -72,38 +73,49 @@ module.exports = createClass({
     // - In all cases, we don't have to manually check which pref changed to
     //   what. The platform code in nsIXULRuntime.maxWebProcessCount does all
     //   of that for us.
     Services.prefs.addObserver(PROCESS_COUNT_PREF, this.updateMultiE10S);
     Services.prefs.addObserver(MULTI_OPTOUT_PREF, this.updateMultiE10S);
 
     this.updateMultiE10S();
     this.updateWorkers();
-  },
+  }
 
   componentWillUnmount() {
     let client = this.props.client;
     client.removeListener("processListChanged", this.updateWorkers);
     client.removeListener("serviceWorkerRegistrationListChanged", this.updateWorkers);
     client.removeListener("workerListChanged", this.updateWorkers);
     client.removeListener("registration-changed", this.updateWorkers);
 
     Services.prefs.removeObserver(PROCESS_COUNT_PREF, this.updateMultiE10S);
     Services.prefs.removeObserver(MULTI_OPTOUT_PREF, this.updateMultiE10S);
-  },
+  }
+
+  get initialState() {
+    return {
+      workers: {
+        service: [],
+        shared: [],
+        other: []
+      },
+      processCount: 1,
+    };
+  }
 
   updateMultiE10S() {
     // We watch the pref but set the state based on
     // nsIXULRuntime.maxWebProcessCount.
     let processCount = Services.appinfo.maxWebProcessCount;
     this.setState({ processCount });
-  },
+  }
 
   updateWorkers() {
-    let workers = this.getInitialState().workers;
+    let workers = this.initialState.workers;
 
     getWorkerForms(this.props.client).then(forms => {
       forms.registrations.forEach(form => {
         workers.service.push({
           icon: WorkerIcon,
           name: form.url,
           url: form.url,
           scope: form.scope,
@@ -151,30 +163,30 @@ module.exports = createClass({
       });
 
       // XXX: Filter out the service worker registrations for which we couldn't
       // find the scriptSpec.
       workers.service = workers.service.filter(reg => !!reg.url);
 
       this.setState({ workers });
     });
-  },
+  }
 
   getRegistrationForWorker(form, registrations) {
     for (let registration of registrations) {
       if (registration.scope === form.scope) {
         return registration;
       }
     }
     return null;
-  },
+  }
 
   isE10S() {
     return Services.appinfo.browserTabsRemoteAutostart;
-  },
+  }
 
   renderServiceWorkersError() {
     let isWindowPrivate = PrivateBrowsingUtils.isContentWindowPrivate(window);
     let isPrivateBrowsingMode = PrivateBrowsingUtils.permanentPrivateBrowsing;
     let isServiceWorkerDisabled = !Services.prefs
                                     .getBoolPref("dom.serviceWorkers.enabled");
 
     let isDisabled = isWindowPrivate || isPrivateBrowsingMode || isServiceWorkerDisabled;
@@ -195,17 +207,17 @@ module.exports = createClass({
       dom.a(
         {
           href: MORE_INFO_URL,
           target: "_blank"
         },
         Strings.GetStringFromName("configurationIsNotCompatible.learnMore")
       ),
     );
-  },
+  }
 
   render() {
     let { client, id } = this.props;
     let { workers, processCount } = this.state;
 
     let isE10S = Services.appinfo.browserTabsRemoteAutostart;
     let isMultiE10S = isE10S && processCount > 1;
 
@@ -250,9 +262,11 @@ module.exports = createClass({
           name: Strings.GetStringFromName("otherWorkers"),
           sort: true,
           targetClass: WorkerTarget,
           targets: workers.other
         })
       )
     );
   }
-});
+}
+
+module.exports = WorkersPanel;
--- a/devtools/client/aboutdebugging/components/workers/ServiceWorkerTarget.js
+++ b/devtools/client/aboutdebugging/components/workers/ServiceWorkerTarget.js
@@ -1,161 +1,175 @@
 /* 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/. */
 
 /* eslint-env browser */
 
 "use strict";
 
-const { createClass, DOM: dom, PropTypes } =
+const { Component, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 const { debugWorker } = require("../../modules/worker");
 const Services = require("Services");
 
 loader.lazyRequireGetter(this, "DebuggerClient",
   "devtools/shared/client/debugger-client", true);
 
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 
-module.exports = createClass({
-  displayName: "ServiceWorkerTarget",
+class ServiceWorkerTarget extends Component {
+  static get propTypes() {
+    return {
+      client: PropTypes.instanceOf(DebuggerClient).isRequired,
+      debugDisabled: PropTypes.bool,
+      target: PropTypes.shape({
+        active: PropTypes.bool,
+        fetch: PropTypes.bool.isRequired,
+        icon: PropTypes.string,
+        name: PropTypes.string.isRequired,
+        url: PropTypes.string,
+        scope: PropTypes.string.isRequired,
+        // registrationActor can be missing in e10s.
+        registrationActor: PropTypes.string,
+        workerActor: PropTypes.string
+      }).isRequired
+    };
+  }
 
-  propTypes: {
-    client: PropTypes.instanceOf(DebuggerClient).isRequired,
-    debugDisabled: PropTypes.bool,
-    target: PropTypes.shape({
-      active: PropTypes.bool,
-      fetch: PropTypes.bool.isRequired,
-      icon: PropTypes.string,
-      name: PropTypes.string.isRequired,
-      url: PropTypes.string,
-      scope: PropTypes.string.isRequired,
-      // registrationActor can be missing in e10s.
-      registrationActor: PropTypes.string,
-      workerActor: PropTypes.string
-    }).isRequired
-  },
+  constructor(props) {
+    super(props);
 
-  getInitialState() {
-    return {
+    this.state = {
       pushSubscription: null
     };
-  },
+
+    this.debug = this.debug.bind(this);
+    this.push = this.push.bind(this);
+    this.start = this.start.bind(this);
+    this.unregister = this.unregister.bind(this);
+    this.onPushSubscriptionModified = this.onPushSubscriptionModified.bind(this);
+    this.updatePushSubscription = this.updatePushSubscription.bind(this);
+    this.isRunning = this.isRunning.bind(this);
+    this.isActive = this.isActive.bind(this);
+    this.getServiceWorkerStatus = this.getServiceWorkerStatus.bind(this);
+    this.renderButtons = this.renderButtons.bind(this);
+    this.renderUnregisterLink = this.renderUnregisterLink.bind(this);
+  }
 
   componentDidMount() {
     let { client } = this.props;
     client.addListener("push-subscription-modified", this.onPushSubscriptionModified);
     this.updatePushSubscription();
-  },
+  }
 
   componentDidUpdate(oldProps, oldState) {
     let wasActive = oldProps.target.active;
     if (!wasActive && this.isActive()) {
       // While the service worker isn't active, any calls to `updatePushSubscription`
       // won't succeed. If we just became active, make sure we didn't miss a push
       // subscription change by updating it now.
       this.updatePushSubscription();
     }
-  },
+  }
 
   componentWillUnmount() {
     let { client } = this.props;
     client.removeListener("push-subscription-modified", this.onPushSubscriptionModified);
-  },
+  }
 
   debug() {
     if (!this.isRunning()) {
       // If the worker is not running, we can't debug it.
       return;
     }
 
     let { client, target } = this.props;
     debugWorker(client, target.workerActor);
-  },
+  }
 
   push() {
     if (!this.isActive() || !this.isRunning()) {
       // If the worker is not running, we can't push to it.
       // If the worker is not active, the registration might be unavailable and the
       // push will not succeed.
       return;
     }
 
     let { client, target } = this.props;
     client.request({
       to: target.workerActor,
       type: "push"
     });
-  },
+  }
 
   start() {
     if (!this.isActive() || this.isRunning()) {
       // If the worker is not active or if it is already running, we can't start it.
       return;
     }
 
     let { client, target } = this.props;
     client.request({
       to: target.registrationActor,
       type: "start"
     });
-  },
+  }
 
   unregister() {
     let { client, target } = this.props;
     client.request({
       to: target.registrationActor,
       type: "unregister"
     });
-  },
+  }
 
   onPushSubscriptionModified(type, data) {
     let { target } = this.props;
     if (data.from === target.registrationActor) {
       this.updatePushSubscription();
     }
-  },
+  }
 
   updatePushSubscription() {
     if (!this.props.target.registrationActor) {
       // A valid registrationActor is needed to retrieve the push subscription.
       return;
     }
 
     let { client, target } = this.props;
     client.request({
       to: target.registrationActor,
       type: "getPushSubscription"
     }, ({ subscription }) => {
       this.setState({ pushSubscription: subscription });
     });
-  },
+  }
 
   isRunning() {
     // We know the target is running if it has a worker actor.
     return !!this.props.target.workerActor;
-  },
+  }
 
   isActive() {
     return this.props.target.active;
-  },
+  }
 
   getServiceWorkerStatus() {
     if (this.isActive() && this.isRunning()) {
       return "running";
     } else if (this.isActive()) {
       return "stopped";
     }
     // We cannot get service worker registrations unless the registration is in
     // ACTIVE state. Unable to know the actual state ("installing", "waiting"), we
     // display a custom state "registering" for now. See Bug 1153292.
     return "registering";
-  },
+  }
 
   renderButtons() {
     let pushButton = dom.button({
       className: "push-button",
       onClick: this.push,
       disabled: this.props.debugDisabled
     }, Strings.GetStringFromName("push"));
 
@@ -174,29 +188,29 @@ module.exports = createClass({
     if (this.isRunning()) {
       if (this.isActive()) {
         return [pushButton, debugButton];
       }
       // Only debug button is available if the service worker is not active.
       return debugButton;
     }
     return startButton;
-  },
+  }
 
   renderUnregisterLink() {
     if (!this.isActive()) {
       // If not active, there might be no registrationActor available.
       return null;
     }
 
     return dom.a({
       onClick: this.unregister,
       className: "unregister-link",
     }, Strings.GetStringFromName("unregister"));
-  },
+  }
 
   render() {
     let { target } = this.props;
     let { pushSubscription } = this.state;
     let status = this.getServiceWorkerStatus();
 
     let fetch = target.fetch ? Strings.GetStringFromName("listeningForFetchEvents") :
       Strings.GetStringFromName("notListeningForFetchEvents");
@@ -235,9 +249,11 @@ module.exports = createClass({
             }, target.scope),
             this.renderUnregisterLink()
           )
         )
       ),
       this.renderButtons()
     );
   }
-});
+}
+
+module.exports = ServiceWorkerTarget;
--- a/devtools/client/aboutdebugging/components/workers/Target.js
+++ b/devtools/client/aboutdebugging/components/workers/Target.js
@@ -1,44 +1,49 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env browser */
 
 "use strict";
 
-const { createClass, DOM: dom, PropTypes } =
+const { Component, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 const { debugWorker } = require("../../modules/worker");
 const Services = require("Services");
 
 loader.lazyRequireGetter(this, "DebuggerClient",
   "devtools/shared/client/debugger-client", true);
 
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 
-module.exports = createClass({
-  displayName: "WorkerTarget",
+class WorkerTarget extends Component {
+  static get propTypes() {
+    return {
+      client: PropTypes.instanceOf(DebuggerClient).isRequired,
+      debugDisabled: PropTypes.bool,
+      target: PropTypes.shape({
+        icon: PropTypes.string,
+        name: PropTypes.string.isRequired,
+        workerActor: PropTypes.string
+      }).isRequired
+    };
+  }
 
-  propTypes: {
-    client: PropTypes.instanceOf(DebuggerClient).isRequired,
-    debugDisabled: PropTypes.bool,
-    target: PropTypes.shape({
-      icon: PropTypes.string,
-      name: PropTypes.string.isRequired,
-      workerActor: PropTypes.string
-    }).isRequired
-  },
+  constructor(props) {
+    super(props);
+    this.debug = this.debug.bind(this);
+  }
 
   debug() {
     let { client, target } = this.props;
     debugWorker(client, target.workerActor);
-  },
+  }
 
   render() {
     let { target, debugDisabled } = this.props;
 
     return dom.li({ className: "target-container" },
       dom.img({
         className: "target-icon",
         role: "presentation",
@@ -49,9 +54,11 @@ module.exports = createClass({
       ),
       dom.button({
         className: "debug-button",
         onClick: this.debug,
         disabled: debugDisabled
       }, Strings.GetStringFromName("debug"))
     );
   }
-});
+}
+
+module.exports = WorkerTarget;
--- a/devtools/client/memory/app.js
+++ b/devtools/client/memory/app.js
@@ -1,17 +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/. */
 
 "use strict";
 
 const { assert } = require("devtools/shared/DevToolsUtils");
 const { appinfo } = require("Services");
-const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
+const { DOM: dom, Component, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { censusDisplays, labelDisplays, treeMapDisplays, diffingState, viewState } = require("./constants");
 const { toggleRecordingAllocationStacks } = require("./actions/allocations");
 const { setCensusDisplayAndRefresh } = require("./actions/census-display");
 const { setLabelDisplayAndRefresh } = require("./actions/label-display");
 const { setTreeMapDisplayAndRefresh } = require("./actions/tree-map-display");
 
 const {
@@ -46,49 +46,59 @@ const {
 const { changeViewAndRefresh, popViewAndRefresh } = require("./actions/view");
 const { resizeShortestPaths } = require("./actions/sizes");
 const Toolbar = createFactory(require("./components/Toolbar"));
 const List = createFactory(require("./components/List"));
 const SnapshotListItem = createFactory(require("./components/SnapshotListItem"));
 const Heap = createFactory(require("./components/Heap"));
 const { app: appModel } = require("./models");
 
-const MemoryApp = createClass({
-  displayName: "MemoryApp",
-
-  propTypes: appModel,
+class MemoryApp extends Component {
+  static get propTypes() {
+    return appModel;
+  }
 
-  childContextTypes: {
-    front: PropTypes.any,
-    heapWorker: PropTypes.any,
-    toolbox: PropTypes.any,
-  },
+  static get childContextTypes() {
+    return {
+      front: PropTypes.any,
+      heapWorker: PropTypes.any,
+      toolbox: PropTypes.any,
+    };
+  }
 
-  getDefaultProps() {
+  static get defaultProps() {
     return {};
-  },
+  }
+
+  constructor(props) {
+    super(props);
+    this.onKeyDown = this.onKeyDown.bind(this);
+    this._getCensusDisplays = this._getCensusDisplays.bind(this);
+    this._getLabelDisplays = this._getLabelDisplays.bind(this);
+    this._getTreeMapDisplays = this._getTreeMapDisplays.bind(this);
+  }
 
   getChildContext() {
     return {
       front: this.props.front,
       heapWorker: this.props.heapWorker,
       toolbox: this.props.toolbox,
     };
-  },
+  }
 
   componentDidMount() {
     // Attach the keydown listener directly to the window. When an element that
     // has the focus (such as a tree node) is removed from the DOM, the focus
     // falls back to the body.
     window.addEventListener("keydown", this.onKeyDown);
-  },
+  }
 
   componentWillUnmount() {
     window.removeEventListener("keydown", this.onKeyDown);
-  },
+  }
 
   onKeyDown(e) {
     let { snapshots, dispatch, heapWorker } = this.props;
     const selectedSnapshot = snapshots.find(s => s.selected);
     const selectedIndex = snapshots.indexOf(selectedSnapshot);
 
     let isOSX = appinfo.OS == "Darwin";
     let isAccelKey = (isOSX && e.metaKey) || (!isOSX && e.ctrlKey);
@@ -101,56 +111,56 @@ const MemoryApp = createClass({
     }
 
     // On ACCEL+DOWN, select next snapshot.
     if (isAccelKey && e.key === "ArrowDown") {
       let nextIndex = Math.min(snapshots.length - 1, selectedIndex + 1);
       let nextSnapshotId = snapshots[nextIndex].id;
       dispatch(selectSnapshotAndRefresh(heapWorker, nextSnapshotId));
     }
-  },
+  }
 
   _getCensusDisplays() {
     const customDisplays = getCustomCensusDisplays();
     const custom = Object.keys(customDisplays).reduce((arr, key) => {
       arr.push(customDisplays[key]);
       return arr;
     }, []);
 
     return [
       censusDisplays.coarseType,
       censusDisplays.allocationStack,
       censusDisplays.invertedAllocationStack,
     ].concat(custom);
-  },
+  }
 
   _getLabelDisplays() {
     const customDisplays = getCustomLabelDisplays();
     const custom = Object.keys(customDisplays).reduce((arr, key) => {
       arr.push(customDisplays[key]);
       return arr;
     }, []);
 
     return [
       labelDisplays.coarseType,
       labelDisplays.allocationStack,
     ].concat(custom);
-  },
+  }
 
   _getTreeMapDisplays() {
     const customDisplays = getCustomTreeMapDisplays();
     const custom = Object.keys(customDisplays).reduce((arr, key) => {
       arr.push(customDisplays[key]);
       return arr;
     }, []);
 
     return [
       treeMapDisplays.coarseType
     ].concat(custom);
-  },
+  }
 
   render() {
     let {
       dispatch,
       snapshots,
       front,
       heapWorker,
       allocations,
@@ -312,18 +322,18 @@ const MemoryApp = createClass({
               dispatch(resizeShortestPaths(newSize));
             },
             sizes,
             view,
           })
         )
       )
     );
-  },
-});
+  }
+}
 
 /**
  * Passed into react-redux's `connect` method that is called on store change
  * and passed to components.
  */
 function mapStateToProps(state) {
   return state;
 }
--- a/devtools/client/memory/components/Census.js
+++ b/devtools/client/memory/components/Census.js
@@ -1,32 +1,32 @@
 /* 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 { createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
+const { Component, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
 const Tree = createFactory(require("devtools/client/shared/components/Tree"));
 const CensusTreeItem = createFactory(require("./CensusTreeItem"));
 const { TREE_ROW_HEIGHT } = require("../constants");
 const { censusModel, diffingModel } = require("../models");
 
-module.exports = createClass({
-  displayName: "Census",
-
-  propTypes: {
-    census: censusModel,
-    onExpand: PropTypes.func.isRequired,
-    onCollapse: PropTypes.func.isRequired,
-    onFocus: PropTypes.func.isRequired,
-    onViewSourceInDebugger: PropTypes.func.isRequired,
-    onViewIndividuals: PropTypes.func.isRequired,
-    diffing: diffingModel,
-  },
+class Census extends Component {
+  static get propTypes() {
+    return {
+      census: censusModel,
+      onExpand: PropTypes.func.isRequired,
+      onCollapse: PropTypes.func.isRequired,
+      onFocus: PropTypes.func.isRequired,
+      onViewSourceInDebugger: PropTypes.func.isRequired,
+      onViewIndividuals: PropTypes.func.isRequired,
+      diffing: diffingModel,
+    };
+  }
 
   render() {
     let {
       census,
       onExpand,
       onCollapse,
       onFocus,
       diffing,
@@ -72,9 +72,11 @@ module.exports = createClass({
           inverted: census.display.inverted,
           onViewIndividuals,
         }),
       getRoots: () => report.children || [],
       getKey: node => node.id,
       itemHeight: TREE_ROW_HEIGHT,
     });
   }
-});
+}
+
+module.exports = Census;
--- a/devtools/client/memory/components/CensusHeader.js
+++ b/devtools/client/memory/components/CensusHeader.js
@@ -1,24 +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";
 
-const { DOM: dom, createClass } = require("devtools/client/shared/vendor/react");
+const { DOM: dom, Component } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../utils");
 const models = require("../models");
 
-module.exports = createClass({
-  displayName: "CensusHeader",
-
-  propTypes: {
-    diffing: models.diffingModel,
-  },
+class CensusHeader extends Component {
+  static get propTypes() {
+    return {
+      diffing: models.diffingModel,
+    };
+  }
 
   render() {
     let individualsCell;
     if (!this.props.diffing) {
       individualsCell = dom.span({
         className: "heap-tree-item-field heap-tree-item-individuals"
       });
     }
@@ -66,9 +66,11 @@ module.exports = createClass({
         {
           className: "heap-tree-item-name",
           title: L10N.getStr("heapview.field.name.tooltip"),
         },
         L10N.getStr("heapview.field.name")
       )
     );
   }
-});
+}
+
+module.exports = CensusHeader;
--- a/devtools/client/memory/components/CensusTreeItem.js
+++ b/devtools/client/memory/components/CensusTreeItem.js
@@ -1,49 +1,54 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { isSavedFrame } = require("devtools/shared/DevToolsUtils");
 const {
   DOM: dom,
-  createClass,
+  Component,
   createFactory,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
 const { L10N, formatNumber, formatPercent } = require("../utils");
 const Frame = createFactory(require("devtools/client/shared/components/Frame"));
 const { TREE_ROW_HEIGHT } = require("../constants");
 const models = require("../models");
 
-module.exports = createClass({
-  displayName: "CensusTreeItem",
+class CensusTreeItem extends Component {
+  static get propTypes() {
+    return {
+      arrow: PropTypes.any,
+      depth: PropTypes.number.isRequired,
+      diffing: models.app.diffing,
+      expanded: PropTypes.bool.isRequired,
+      focused: PropTypes.bool.isRequired,
+      getPercentBytes: PropTypes.func.isRequired,
+      getPercentCount: PropTypes.func.isRequired,
+      inverted: PropTypes.bool,
+      item: PropTypes.object.isRequired,
+      onViewIndividuals: PropTypes.func.isRequired,
+      onViewSourceInDebugger: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    arrow: PropTypes.any,
-    depth: PropTypes.number.isRequired,
-    diffing: models.app.diffing,
-    expanded: PropTypes.bool.isRequired,
-    focused: PropTypes.bool.isRequired,
-    getPercentBytes: PropTypes.func.isRequired,
-    getPercentCount: PropTypes.func.isRequired,
-    inverted: PropTypes.bool,
-    item: PropTypes.object.isRequired,
-    onViewIndividuals: PropTypes.func.isRequired,
-    onViewSourceInDebugger: PropTypes.func.isRequired,
-  },
+  constructor(props) {
+    super(props);
+    this.toLabel = this.toLabel.bind(this);
+  }
 
   shouldComponentUpdate(nextProps, nextState) {
     return this.props.item != nextProps.item
       || this.props.depth != nextProps.depth
       || this.props.expanded != nextProps.expanded
       || this.props.focused != nextProps.focused
       || this.props.diffing != nextProps.diffing;
-  },
+  }
 
   toLabel(name, linkToDebugger) {
     if (isSavedFrame(name)) {
       return Frame({
         frame: name,
         onClick: () => linkToDebugger(name),
         showFunctionName: true,
         showHost: true,
@@ -58,17 +63,17 @@ module.exports = createClass({
       return L10N.getStr("tree-item.nostack");
     }
 
     if (name === "noFilename") {
       return L10N.getStr("tree-item.nofilename");
     }
 
     return String(name);
-  },
+  }
 
   render() {
     let {
       item,
       depth,
       arrow,
       focused,
       getPercentBytes,
@@ -145,10 +150,12 @@ module.exports = createClass({
           className: "heap-tree-item-field heap-tree-item-name",
           style: { marginInlineStart: depth * TREE_ROW_HEIGHT }
         },
         arrow,
         pointer,
         this.toLabel(item.name, onViewSourceInDebugger)
       )
     );
-  },
-});
+  }
+}
+
+module.exports = CensusTreeItem;
--- a/devtools/client/memory/components/DominatorTree.js
+++ b/devtools/client/memory/components/DominatorTree.js
@@ -1,42 +1,42 @@
 /* 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 { DOM: dom, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
+const { DOM: dom, Component, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
 const { assert } = require("devtools/shared/DevToolsUtils");
 const { createParentMap } = require("devtools/shared/heapsnapshot/CensusUtils");
 const Tree = createFactory(require("devtools/client/shared/components/Tree"));
 const DominatorTreeItem = createFactory(require("./DominatorTreeItem"));
 const { L10N } = require("../utils");
 const { TREE_ROW_HEIGHT, dominatorTreeState } = require("../constants");
 const { dominatorTreeModel } = require("../models");
 const DominatorTreeLazyChildren = require("../dominator-tree-lazy-children");
 
 const DOMINATOR_TREE_AUTO_EXPAND_DEPTH = 3;
 
 /**
  * A throbber that represents a subtree in the dominator tree that is actively
  * being incrementally loaded and fetched from the `HeapAnalysesWorker`.
  */
-const DominatorTreeSubtreeFetching = createFactory(createClass({
-  displayName: "DominatorTreeSubtreeFetching",
-
-  propTypes: {
-    depth: PropTypes.number.isRequired,
-    focused: PropTypes.bool.isRequired,
-  },
+class DominatorTreeSubtreeFetchingClass extends Component {
+  static get propTypes() {
+    return {
+      depth: PropTypes.number.isRequired,
+      focused: PropTypes.bool.isRequired,
+    };
+  }
 
   shouldComponentUpdate(nextProps, nextState) {
     return this.props.depth !== nextProps.depth
       || this.props.focused !== nextProps.focused;
-  },
+  }
 
   render() {
     let {
       depth,
       focused,
     } = this.props;
 
     return dom.div(
@@ -46,36 +46,36 @@ const DominatorTreeSubtreeFetching = cre
       dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" }),
       dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" }),
       dom.span({
         className: "heap-tree-item-field heap-tree-item-name devtools-throbber",
         style: { marginInlineStart: depth * TREE_ROW_HEIGHT }
       })
     );
   }
-}));
+}
 
 /**
  * A link to fetch and load more siblings in the dominator tree, when there are
  * already many loaded above.
  */
-const DominatorTreeSiblingLink = createFactory(createClass({
-  displayName: "DominatorTreeSiblingLink",
-
-  propTypes: {
-    depth: PropTypes.number.isRequired,
-    focused: PropTypes.bool.isRequired,
-    item: PropTypes.instanceOf(DominatorTreeLazyChildren).isRequired,
-    onLoadMoreSiblings: PropTypes.func.isRequired,
-  },
+class DominatorTreeSiblingLinkClass extends Component {
+  static get propTypes() {
+    return {
+      depth: PropTypes.number.isRequired,
+      focused: PropTypes.bool.isRequired,
+      item: PropTypes.instanceOf(DominatorTreeLazyChildren).isRequired,
+      onLoadMoreSiblings: PropTypes.func.isRequired,
+    };
+  }
 
   shouldComponentUpdate(nextProps, nextState) {
     return this.props.depth !== nextProps.depth
       || this.props.focused !== nextProps.focused;
-  },
+  }
 
   render() {
     let {
       depth,
       focused,
       item,
       onLoadMoreSiblings,
     } = this.props;
@@ -95,42 +95,39 @@ const DominatorTreeSiblingLink = createF
           {
             onClick: () => onLoadMoreSiblings(item)
           },
           L10N.getStr("tree-item.load-more")
         )
       )
     );
   }
-}));
-
-/**
- * The actual dominator tree rendered as an expandable and collapsible tree.
- */
-module.exports = createClass({
-  displayName: "DominatorTree",
+}
 
-  propTypes: {
-    dominatorTree: dominatorTreeModel.isRequired,
-    onLoadMoreSiblings: PropTypes.func.isRequired,
-    onViewSourceInDebugger: PropTypes.func.isRequired,
-    onExpand: PropTypes.func.isRequired,
-    onCollapse: PropTypes.func.isRequired,
-    onFocus: PropTypes.func.isRequired,
-  },
+class DominatorTree extends Component {
+  static get propTypes() {
+    return {
+      dominatorTree: dominatorTreeModel.isRequired,
+      onLoadMoreSiblings: PropTypes.func.isRequired,
+      onViewSourceInDebugger: PropTypes.func.isRequired,
+      onExpand: PropTypes.func.isRequired,
+      onCollapse: PropTypes.func.isRequired,
+      onFocus: PropTypes.func.isRequired,
+    };
+  }
 
   shouldComponentUpdate(nextProps, nextState) {
     // Safe to use referential equality here because all of our mutations on
     // dominator tree models use immutableUpdate in a persistent manner. The
     // exception to the rule are mutations of the expanded set, however we take
     // care that the dominatorTree model itself is still re-allocated when
     // mutations to the expanded set occur. Because of the re-allocations, we
     // can continue using referential equality here.
     return this.props.dominatorTree !== nextProps.dominatorTree;
-  },
+  }
 
   render() {
     const { dominatorTree, onViewSourceInDebugger, onLoadMoreSiblings } = this.props;
 
     const parentMap = createParentMap(dominatorTree.root, node => node.nodeId);
 
     return Tree({
       key: "dominator-tree-tree",
@@ -211,9 +208,14 @@ module.exports = createClass({
         });
       },
       getRoots: () => [dominatorTree.root],
       getKey: node =>
         node instanceof DominatorTreeLazyChildren ? node.key() : node.nodeId,
       itemHeight: TREE_ROW_HEIGHT,
     });
   }
-});
+}
+
+const DominatorTreeSubtreeFetching = createFactory(DominatorTreeSubtreeFetchingClass);
+const DominatorTreeSiblingLink = createFactory(DominatorTreeSiblingLinkClass);
+
+module.exports = DominatorTree;
--- a/devtools/client/memory/components/DominatorTreeHeader.js
+++ b/devtools/client/memory/components/DominatorTreeHeader.js
@@ -1,21 +1,21 @@
 /* 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 { DOM: dom, createClass } = require("devtools/client/shared/vendor/react");
+const { DOM: dom, Component } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../utils");
 
-module.exports = createClass({
-  displayName: "DominatorTreeHeader",
-
-  propTypes: { },
+class DominatorTreeHeader extends Component {
+  static get propTypes() {
+    return { };
+  }
 
   render() {
     return dom.div(
       {
         className: "header"
       },
 
       dom.span(
@@ -38,9 +38,11 @@ module.exports = createClass({
         {
           className: "heap-tree-item-name",
           title: L10N.getStr("dominatortree.field.label.tooltip"),
         },
         L10N.getStr("dominatortree.field.label")
       )
     );
   }
-});
+}
+
+module.exports = DominatorTreeHeader;
--- a/devtools/client/memory/components/DominatorTreeItem.js
+++ b/devtools/client/memory/components/DominatorTreeItem.js
@@ -1,47 +1,47 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { assert, isSavedFrame } = require("devtools/shared/DevToolsUtils");
-const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
+const { DOM: dom, Component, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
 const { L10N, formatNumber, formatPercent } = require("../utils");
 const Frame = createFactory(require("devtools/client/shared/components/Frame"));
 const { TREE_ROW_HEIGHT } = require("../constants");
 
-const Separator = createFactory(createClass({
-  displayName: "Separator",
-
+class SeparatorClass extends Component {
   render() {
     return dom.span({ className: "separator" }, "›");
   }
-}));
+}
 
-module.exports = createClass({
-  displayName: "DominatorTreeItem",
+const Separator = createFactory(SeparatorClass);
 
-  propTypes: {
-    item: PropTypes.object.isRequired,
-    depth: PropTypes.number.isRequired,
-    arrow: PropTypes.object,
-    expanded: PropTypes.bool.isRequired,
-    focused: PropTypes.bool.isRequired,
-    getPercentSize: PropTypes.func.isRequired,
-    onViewSourceInDebugger: PropTypes.func.isRequired,
-  },
+class DominatorTreeItem extends Component {
+  static get propTypes() {
+    return {
+      item: PropTypes.object.isRequired,
+      depth: PropTypes.number.isRequired,
+      arrow: PropTypes.object,
+      expanded: PropTypes.bool.isRequired,
+      focused: PropTypes.bool.isRequired,
+      getPercentSize: PropTypes.func.isRequired,
+      onViewSourceInDebugger: PropTypes.func.isRequired,
+    };
+  }
 
   shouldComponentUpdate(nextProps, nextState) {
     return this.props.item != nextProps.item
       || this.props.depth != nextProps.depth
       || this.props.expanded != nextProps.expanded
       || this.props.focused != nextProps.focused;
-  },
+  }
 
   render() {
     let {
       item,
       depth,
       arrow,
       focused,
       getPercentSize,
@@ -136,10 +136,12 @@ module.exports = createClass({
           style: { marginInlineStart: depth * TREE_ROW_HEIGHT }
         },
         arrow,
         label,
         dom.span({ className: "heap-tree-item-address" },
                  `@ 0x${item.nodeId.toString(16)}`)
       )
     );
-  },
-});
+  }
+}
+
+module.exports = DominatorTreeItem;
--- a/devtools/client/memory/components/Heap.js
+++ b/devtools/client/memory/components/Heap.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { DOM: dom, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
+const { DOM: dom, Component, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
 const { assert, safeErrorString } = require("devtools/shared/DevToolsUtils");
 const Census = createFactory(require("./Census"));
 const CensusHeader = createFactory(require("./CensusHeader"));
 const DominatorTree = createFactory(require("./DominatorTree"));
 const DominatorTreeHeader = createFactory(require("./DominatorTreeHeader"));
 const TreeMap = createFactory(require("./TreeMap"));
 const HSplitBox = createFactory(require("devtools/client/shared/components/HSplitBox"));
 const Individuals = createFactory(require("./Individuals"));
@@ -175,39 +175,51 @@ function getError(snapshot, diffing, ind
 
 /**
  * Main view for the memory tool.
  *
  * The Heap component contains several panels for different states; an initial
  * state of only a button to take a snapshot, loading states, the census view
  * tree, the dominator tree, etc.
  */
-module.exports = createClass({
-  displayName: "Heap",
+class Heap extends Component {
+  static get propTypes() {
+    return {
+      onSnapshotClick: PropTypes.func.isRequired,
+      onLoadMoreSiblings: PropTypes.func.isRequired,
+      onCensusExpand: PropTypes.func.isRequired,
+      onCensusCollapse: PropTypes.func.isRequired,
+      onDominatorTreeExpand: PropTypes.func.isRequired,
+      onDominatorTreeCollapse: PropTypes.func.isRequired,
+      onCensusFocus: PropTypes.func.isRequired,
+      onDominatorTreeFocus: PropTypes.func.isRequired,
+      onShortestPathsResize: PropTypes.func.isRequired,
+      snapshot: snapshotModel,
+      onViewSourceInDebugger: PropTypes.func.isRequired,
+      onPopView: PropTypes.func.isRequired,
+      individuals: models.individuals,
+      onViewIndividuals: PropTypes.func.isRequired,
+      onFocusIndividual: PropTypes.func.isRequired,
+      diffing: diffingModel,
+      view: models.view.isRequired,
+      sizes: PropTypes.object.isRequired,
+    };
+  }
 
-  propTypes: {
-    onSnapshotClick: PropTypes.func.isRequired,
-    onLoadMoreSiblings: PropTypes.func.isRequired,
-    onCensusExpand: PropTypes.func.isRequired,
-    onCensusCollapse: PropTypes.func.isRequired,
-    onDominatorTreeExpand: PropTypes.func.isRequired,
-    onDominatorTreeCollapse: PropTypes.func.isRequired,
-    onCensusFocus: PropTypes.func.isRequired,
-    onDominatorTreeFocus: PropTypes.func.isRequired,
-    onShortestPathsResize: PropTypes.func.isRequired,
-    snapshot: snapshotModel,
-    onViewSourceInDebugger: PropTypes.func.isRequired,
-    onPopView: PropTypes.func.isRequired,
-    individuals: models.individuals,
-    onViewIndividuals: PropTypes.func.isRequired,
-    onFocusIndividual: PropTypes.func.isRequired,
-    diffing: diffingModel,
-    view: models.view.isRequired,
-    sizes: PropTypes.object.isRequired,
-  },
+  constructor(props) {
+    super(props);
+    this._renderHeapView = this._renderHeapView.bind(this);
+    this._renderInitial = this._renderInitial.bind(this);
+    this._renderStatus = this._renderStatus.bind(this);
+    this._renderError = this._renderError.bind(this);
+    this._renderCensus = this._renderCensus.bind(this);
+    this._renderTreeMap = this._renderTreeMap.bind(this);
+    this._renderIndividuals = this._renderIndividuals.bind(this);
+    this._renderDominatorTree = this._renderDominatorTree.bind(this);
+  }
 
   /**
    * Render the heap view's container panel with the given contents inside of
    * it.
    *
    * @param {snapshotState|diffingState|dominatorTreeState} state
    * @param {...Any} contents
    */
@@ -220,50 +232,50 @@ module.exports = createClass({
       dom.div(
         {
           className: "heap-view-panel",
           "data-state": state,
         },
         ...contents
       )
     );
-  },
+  }
 
   _renderInitial(onSnapshotClick) {
     return this._renderHeapView("initial", dom.button(
       {
         className: "devtools-button take-snapshot",
         onClick: onSnapshotClick,
         "data-standalone": true,
       },
       L10N.getStr("take-snapshot")
     ));
-  },
+  }
 
   _renderStatus(state, statusText, diffing) {
     let throbber = "";
     if (shouldDisplayThrobber(diffing)) {
       throbber = "devtools-throbber";
     }
 
     return this._renderHeapView(state, dom.span(
       {
         className: `snapshot-status ${throbber}`
       },
       statusText
     ));
-  },
+  }
 
   _renderError(state, statusText, error) {
     return this._renderHeapView(
       state,
       dom.span({ className: "snapshot-status error" }, statusText),
       dom.pre({}, safeErrorString(error))
     );
-  },
+  }
 
   _renderCensus(state, census, diffing, onViewSourceInDebugger, onViewIndividuals) {
     assert(census.report, "Should not render census that does not have a report");
 
     if (!census.report.children) {
       const censusFilterMsg = census.filter ? L10N.getStr("heapview.none-match")
                                             : L10N.getStr("heapview.empty");
       const msg = diffing ? L10N.getStr("heapview.no-difference")
@@ -288,24 +300,24 @@ module.exports = createClass({
       diffing,
       census,
       onExpand: node => this.props.onCensusExpand(census, node),
       onCollapse: node => this.props.onCensusCollapse(census, node),
       onFocus: node => this.props.onCensusFocus(census, node),
     }));
 
     return this._renderHeapView(state, ...contents);
-  },
+  }
 
   _renderTreeMap(state, treeMap) {
     return this._renderHeapView(
       state,
       TreeMap({ treeMap })
     );
-  },
+  }
 
   _renderIndividuals(state, individuals, dominatorTree, onViewSourceInDebugger) {
     assert(individuals.state === individualsState.FETCHED,
            "Should have fetched individuals");
     assert(dominatorTree && dominatorTree.root,
            "Should have a dominator tree and its root");
 
     const tree = dom.div(
@@ -350,17 +362,17 @@ module.exports = createClass({
       ),
       HSplitBox({
         start: tree,
         end: shortestPaths,
         startWidth: this.props.sizes.shortestPathsSize,
         onResize: this.props.onShortestPathsResize,
       })
     );
-  },
+  }
 
   _renderDominatorTree(state, onViewSourceInDebugger, dominatorTree, onLoadMoreSiblings) {
     const tree = dom.div(
       {
         className: "vbox",
         style: {
           overflowY: "auto"
         }
@@ -386,17 +398,17 @@ module.exports = createClass({
       state,
       HSplitBox({
         start: tree,
         end: shortestPaths,
         startWidth: this.props.sizes.shortestPathsSize,
         onResize: this.props.onShortestPathsResize,
       })
     );
-  },
+  }
 
   render() {
     let {
       snapshot,
       diffing,
       onSnapshotClick,
       onLoadMoreSiblings,
       onViewSourceInDebugger,
@@ -449,10 +461,12 @@ module.exports = createClass({
            "If we aren't in progress, looking at a census, or diffing, then we " +
            "must be looking at a dominator tree");
     assert(!diffing, "Should not have diffing");
     assert(snapshot.dominatorTree, "Should have a dominator tree");
 
     return this._renderDominatorTree(state, onViewSourceInDebugger,
                                      snapshot.dominatorTree,
                                      onLoadMoreSiblings);
-  },
-});
+  }
+}
+
+module.exports = Heap;
--- a/devtools/client/memory/components/Individuals.js
+++ b/devtools/client/memory/components/Individuals.js
@@ -1,32 +1,32 @@
 /* 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 { createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
+const { Component, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
 const Tree = createFactory(require("devtools/client/shared/components/Tree"));
 const DominatorTreeItem = createFactory(require("./DominatorTreeItem"));
 const { TREE_ROW_HEIGHT } = require("../constants");
 const models = require("../models");
 
 /**
  * The list of individuals in a census group.
  */
-module.exports = createClass({
-  displayName: "Individuals",
-
-  propTypes: {
-    onViewSourceInDebugger: PropTypes.func.isRequired,
-    onFocus: PropTypes.func.isRequired,
-    individuals: models.individuals,
-    dominatorTree: models.dominatorTreeModel,
-  },
+class Individuals extends Component {
+  static get propTypes() {
+    return {
+      onViewSourceInDebugger: PropTypes.func.isRequired,
+      onFocus: PropTypes.func.isRequired,
+      individuals: models.individuals,
+      dominatorTree: models.dominatorTreeModel,
+    };
+  }
 
   render() {
     const {
       individuals,
       dominatorTree,
       onViewSourceInDebugger,
       onFocus,
     } = this.props;
@@ -52,9 +52,11 @@ module.exports = createClass({
           onViewSourceInDebugger,
         });
       },
       getRoots: () => individuals.nodes,
       getKey: node => node.nodeId,
       itemHeight: TREE_ROW_HEIGHT,
     });
   }
-});
+}
+
+module.exports = Individuals;
--- a/devtools/client/memory/components/IndividualsHeader.js
+++ b/devtools/client/memory/components/IndividualsHeader.js
@@ -1,21 +1,21 @@
 /* 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 { DOM: dom, createClass } = require("devtools/client/shared/vendor/react");
+const { DOM: dom, Component } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../utils");
 
-module.exports = createClass({
-  displayName: "IndividualsHeader",
-
-  propTypes: { },
+class IndividualsHeader extends Component {
+  static get propTypes() {
+    return { };
+  }
 
   render() {
     return dom.div(
       {
         className: "header"
       },
 
       dom.span(
@@ -38,9 +38,11 @@ module.exports = createClass({
         {
           className: "heap-tree-item-name",
           title: L10N.getStr("individuals.field.node.tooltip"),
         },
         L10N.getStr("individuals.field.node")
       )
     );
   }
-});
+}
+
+module.exports = IndividualsHeader;
--- a/devtools/client/memory/components/List.js
+++ b/devtools/client/memory/components/List.js
@@ -1,37 +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 { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
+const { DOM: dom, Component, PropTypes } = require("devtools/client/shared/vendor/react");
 
 /**
  * Generic list component that takes another react component to represent
  * the children nodes as `itemComponent`, and a list of items to render
  * as that component with a click handler.
  */
-module.exports = createClass({
-  displayName: "List",
-
-  propTypes: {
-    itemComponent: PropTypes.any.isRequired,
-    onClick: PropTypes.func,
-    items: PropTypes.array.isRequired,
-  },
+class List extends Component {
+  static get propTypes() {
+    return {
+      itemComponent: PropTypes.any.isRequired,
+      onClick: PropTypes.func,
+      items: PropTypes.array.isRequired,
+    };
+  }
 
   render() {
     let { items, onClick, itemComponent: Item } = this.props;
 
     return (
       dom.ul({ className: "list" }, ...items.map((item, index) => {
         return Item(Object.assign({}, this.props, {
           key: index,
           item,
           index,
           onClick: () => onClick(item),
         }));
       }))
     );
   }
-});
+}
+
+module.exports = List;
--- a/devtools/client/memory/components/ShortestPaths.js
+++ b/devtools/client/memory/components/ShortestPaths.js
@@ -1,17 +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/. */
 
 "use strict";
 
 const {
   DOM: dom,
-  createClass,
+  Component,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { isSavedFrame } = require("devtools/shared/DevToolsUtils");
 const { getSourceNames } = require("devtools/client/shared/source-utils");
 const { L10N } = require("../utils");
 
 const GRAPH_DEFAULTS = {
   translate: [20, 20],
@@ -45,51 +45,53 @@ function stringifyLabel(label, id) {
     } else {
       sanitized[i] = "" + piece;
     }
   }
 
   return `${sanitized.join(" › ")} @ 0x${id.toString(16)}`;
 }
 
-module.exports = createClass({
-  displayName: "ShortestPaths",
+class ShortestPaths extends Component {
+  static get propTypes() {
+    return {
+      graph: PropTypes.shape({
+        nodes: PropTypes.arrayOf(PropTypes.object),
+        edges: PropTypes.arrayOf(PropTypes.object),
+      }),
+    };
+  }
 
-  propTypes: {
-    graph: PropTypes.shape({
-      nodes: PropTypes.arrayOf(PropTypes.object),
-      edges: PropTypes.arrayOf(PropTypes.object),
-    }),
-  },
-
-  getInitialState() {
-    return { zoom: null };
-  },
+  constructor(props) {
+    super(props);
+    this.state = { zoom: null };
+    this._renderGraph = this._renderGraph.bind(this);
+  }
 
   componentDidMount() {
     if (this.props.graph) {
       this._renderGraph(this.refs.container, this.props.graph);
     }
-  },
+  }
 
   shouldComponentUpdate(nextProps) {
     return this.props.graph != nextProps.graph;
-  },
+  }
 
   componentDidUpdate() {
     if (this.props.graph) {
       this._renderGraph(this.refs.container, this.props.graph);
     }
-  },
+  }
 
   componentWillUnmount() {
     if (this.state.zoom) {
       this.state.zoom.on("zoom", null);
     }
-  },
+  }
 
   _renderGraph(container, { nodes, edges }) {
     if (!container.firstChild) {
       const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
       svg.setAttribute("id", "graph-svg");
       svg.setAttribute("xlink", "http://www.w3.org/1999/xlink");
       svg.style.width = "100%";
       svg.style.height = "100%";
@@ -139,17 +141,17 @@ module.exports = createClass({
 
     const { translate, scale } = GRAPH_DEFAULTS;
     zoom.scale(scale);
     zoom.translate(translate);
     target.attr("transform", `translate(${translate}) scale(${scale})`);
 
     const layout = dagreD3.layout();
     renderer.layout(layout).run(graph, target);
-  },
+  }
 
   render() {
     let contents;
     if (this.props.graph) {
       // Let the componentDidMount or componentDidUpdate method draw the graph
       // with DagreD3. We just provide the container for the graph here.
       contents = dom.div({
         ref: "container",
@@ -177,10 +179,12 @@ module.exports = createClass({
         {
           id: "shortest-paths-header",
           className: "header",
         },
         L10N.getStr("shortest-paths.header")
       ),
       contents
     );
-  },
-});
+  }
+}
+
+module.exports = ShortestPaths;
--- a/devtools/client/memory/components/SnapshotListItem.js
+++ b/devtools/client/memory/components/SnapshotListItem.js
@@ -1,37 +1,37 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
+const { DOM: dom, Component, PropTypes } = require("devtools/client/shared/vendor/react");
 const {
   L10N,
   getSnapshotTitle,
   getSnapshotTotals,
   getStatusText,
   snapshotIsDiffable,
   getSavedCensus
 } = require("../utils");
 const { diffingState } = require("../constants");
 const { snapshot: snapshotModel, app: appModel } = require("../models");
 
-module.exports = createClass({
-  displayName: "SnapshotListItem",
-
-  propTypes: {
-    onClick: PropTypes.func.isRequired,
-    onSave: PropTypes.func.isRequired,
-    onDelete: PropTypes.func.isRequired,
-    item: snapshotModel.isRequired,
-    index: PropTypes.number.isRequired,
-    diffing: appModel.diffing,
-  },
+class SnapshotListItem extends Component {
+  static get propTypes() {
+    return {
+      onClick: PropTypes.func.isRequired,
+      onSave: PropTypes.func.isRequired,
+      onDelete: PropTypes.func.isRequired,
+      item: snapshotModel.isRequired,
+      index: PropTypes.number.isRequired,
+      diffing: appModel.diffing,
+    };
+  }
 
   render() {
     let { item: snapshot, onClick, onSave, onDelete, diffing } = this.props;
     let className = `snapshot-list-item ${snapshot.selected ? " selected" : ""}`;
     let statusText = getStatusText(snapshot.state);
     let wantThrobber = !!statusText;
     let title = getSnapshotTitle(snapshot);
 
@@ -107,9 +107,11 @@ module.exports = createClass({
         ),
         dom.span({ className: "snapshot-info" },
           details,
           saveLink
         )
       )
     );
   }
-});
+}
+
+module.exports = SnapshotListItem;
--- a/devtools/client/memory/components/Toolbar.js
+++ b/devtools/client/memory/components/Toolbar.js
@@ -1,54 +1,54 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { assert } = require("devtools/shared/DevToolsUtils");
-const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
+const { DOM: dom, Component, PropTypes } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../utils");
 const models = require("../models");
 const { viewState } = require("../constants");
 
-module.exports = createClass({
-  displayName: "Toolbar",
-
-  propTypes: {
-    censusDisplays: PropTypes.arrayOf(PropTypes.shape({
-      displayName: PropTypes.string.isRequired,
-    })).isRequired,
-    censusDisplay: PropTypes.shape({
-      displayName: PropTypes.string.isRequired,
-    }).isRequired,
-    onTakeSnapshotClick: PropTypes.func.isRequired,
-    onImportClick: PropTypes.func.isRequired,
-    onClearSnapshotsClick: PropTypes.func.isRequired,
-    onCensusDisplayChange: PropTypes.func.isRequired,
-    onToggleRecordAllocationStacks: PropTypes.func.isRequired,
-    allocations: models.allocations,
-    filterString: PropTypes.string,
-    setFilterString: PropTypes.func.isRequired,
-    diffing: models.diffingModel,
-    onToggleDiffing: PropTypes.func.isRequired,
-    view: models.view.isRequired,
-    onViewChange: PropTypes.func.isRequired,
-    labelDisplays: PropTypes.arrayOf(PropTypes.shape({
-      displayName: PropTypes.string.isRequired,
-    })).isRequired,
-    labelDisplay: PropTypes.shape({
-      displayName: PropTypes.string.isRequired,
-    }).isRequired,
-    onLabelDisplayChange: PropTypes.func.isRequired,
-    treeMapDisplays: PropTypes.arrayOf(PropTypes.shape({
-      displayName: PropTypes.string.isRequired,
-    })).isRequired,
-    onTreeMapDisplayChange: PropTypes.func.isRequired,
-    snapshots: PropTypes.arrayOf(models.snapshot).isRequired,
-  },
+class Toolbar extends Component {
+  static get propTypes() {
+    return {
+      censusDisplays: PropTypes.arrayOf(PropTypes.shape({
+        displayName: PropTypes.string.isRequired,
+      })).isRequired,
+      censusDisplay: PropTypes.shape({
+        displayName: PropTypes.string.isRequired,
+      }).isRequired,
+      onTakeSnapshotClick: PropTypes.func.isRequired,
+      onImportClick: PropTypes.func.isRequired,
+      onClearSnapshotsClick: PropTypes.func.isRequired,
+      onCensusDisplayChange: PropTypes.func.isRequired,
+      onToggleRecordAllocationStacks: PropTypes.func.isRequired,
+      allocations: models.allocations,
+      filterString: PropTypes.string,
+      setFilterString: PropTypes.func.isRequired,
+      diffing: models.diffingModel,
+      onToggleDiffing: PropTypes.func.isRequired,
+      view: models.view.isRequired,
+      onViewChange: PropTypes.func.isRequired,
+      labelDisplays: PropTypes.arrayOf(PropTypes.shape({
+        displayName: PropTypes.string.isRequired,
+      })).isRequired,
+      labelDisplay: PropTypes.shape({
+        displayName: PropTypes.string.isRequired,
+      }).isRequired,
+      onLabelDisplayChange: PropTypes.func.isRequired,
+      treeMapDisplays: PropTypes.arrayOf(PropTypes.shape({
+        displayName: PropTypes.string.isRequired,
+      })).isRequired,
+      onTreeMapDisplayChange: PropTypes.func.isRequired,
+      snapshots: PropTypes.arrayOf(models.snapshot).isRequired,
+    };
+  }
 
   render() {
     let {
       onTakeSnapshotClick,
       onImportClick,
       onClearSnapshotsClick,
       onCensusDisplayChange,
       censusDisplays,
@@ -293,9 +293,11 @@ module.exports = createClass({
           L10N.getStr("checkbox.recordAllocationStacks")
         ),
 
         viewSelect,
         viewToolbarOptions
       )
     );
   }
-});
+}
+
+module.exports = Toolbar;
--- a/devtools/client/memory/components/TreeMap.js
+++ b/devtools/client/memory/components/TreeMap.js
@@ -1,71 +1,76 @@
 /* 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 { DOM: dom, createClass } = require("devtools/client/shared/vendor/react");
+const { DOM: dom, Component } = require("devtools/client/shared/vendor/react");
 const { treeMapModel } = require("../models");
 const startVisualization = require("./tree-map/start");
 
-module.exports = createClass({
-  displayName: "TreeMap",
+class TreeMap extends Component {
+  static get propTypes() {
+    return {
+      treeMap: treeMapModel
+    };
+  }
 
-  propTypes: {
-    treeMap: treeMapModel
-  },
-
-  getInitialState() {
-    return {};
-  },
+  constructor(props) {
+    super(props);
+    this.state = {};
+    this._stopVisualization = this._stopVisualization.bind(this);
+    this._startVisualization = this._startVisualization.bind(this);
+  }
 
   componentDidMount() {
     const { treeMap } = this.props;
     if (treeMap && treeMap.report) {
       this._startVisualization();
     }
-  },
+  }
 
   shouldComponentUpdate(nextProps) {
     const oldTreeMap = this.props.treeMap;
     const newTreeMap = nextProps.treeMap;
     return oldTreeMap !== newTreeMap;
-  },
+  }
 
   componentDidUpdate(prevProps) {
     this._stopVisualization();
 
     if (this.props.treeMap && this.props.treeMap.report) {
       this._startVisualization();
     }
-  },
+  }
 
   componentWillUnmount() {
     if (this.state.stopVisualization) {
       this.state.stopVisualization();
     }
-  },
+  }
 
   _stopVisualization() {
     if (this.state.stopVisualization) {
       this.state.stopVisualization();
       this.setState({ stopVisualization: null });
     }
-  },
+  }
 
   _startVisualization() {
     const { container } = this.refs;
     const { report } = this.props.treeMap;
     const stopVisualization = startVisualization(container, report);
     this.setState({ stopVisualization });
-  },
+  }
 
   render() {
     return dom.div(
       {
         ref: "container",
         className: "tree-map-container"
       }
     );
   }
-});
+}
+
+module.exports = TreeMap;
--- a/devtools/client/shared/components/AutoCompletePopup.js
+++ b/devtools/client/shared/components/AutoCompletePopup.js
@@ -1,72 +1,79 @@
 /* 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 { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
-
-module.exports = createClass({
-  displayName: "AutocompletePopup",
+const { DOM: dom, Component, PropTypes } = require("devtools/client/shared/vendor/react");
 
-  propTypes: {
-    /**
-     * autocompleteProvider takes search-box's entire input text as `filter` argument
-     * ie. "is:cached pr"
-     * returned value is array of objects like below
-     * [{value: "is:cached protocol", displayValue: "protocol"}[, ...]]
-     * `value` is used to update the search-box input box for given item
-     * `displayValue` is used to render the autocomplete list
-     */
-    autocompleteProvider: PropTypes.func.isRequired,
-    filter: PropTypes.string.isRequired,
-    onItemSelected: PropTypes.func.isRequired,
-  },
+class AutocompletePopup extends Component {
+  static get propTypes() {
+    return {
+      /**
+       * autocompleteProvider takes search-box's entire input text as `filter` argument
+       * ie. "is:cached pr"
+       * returned value is array of objects like below
+       * [{value: "is:cached protocol", displayValue: "protocol"}[, ...]]
+       * `value` is used to update the search-box input box for given item
+       * `displayValue` is used to render the autocomplete list
+       */
+      autocompleteProvider: PropTypes.func.isRequired,
+      filter: PropTypes.string.isRequired,
+      onItemSelected: PropTypes.func.isRequired,
+    };
+  }
 
-  getInitialState() {
-    return this.computeState(this.props);
-  },
+  constructor(props, context) {
+    super(props, context);
+    this.state = this.computeState(props);
+    this.computeState = this.computeState.bind(this);
+    this.jumpToTop = this.jumpToTop.bind(this);
+    this.jumpToBottom = this.jumpToBottom.bind(this);
+    this.jumpBy = this.jumpBy.bind(this);
+    this.select = this.select.bind(this);
+    this.onMouseDown = this.onMouseDown.bind(this);
+  }
 
   componentWillReceiveProps(nextProps) {
     if (this.props.filter === nextProps.filter) {
       return;
     }
     this.setState(this.computeState(nextProps));
-  },
+  }
 
   componentDidUpdate() {
     if (this.refs.selected) {
       this.refs.selected.scrollIntoView(false);
     }
-  },
+  }
 
   computeState({ autocompleteProvider, filter }) {
     let list = autocompleteProvider(filter);
     let selectedIndex = list.length == 1 ? 0 : -1;
 
     return { list, selectedIndex };
-  },
+  }
 
   /**
    * Use this method to select the top-most item
    * This method is public, called outside of the autocomplete-popup component.
    */
   jumpToTop() {
     this.setState({ selectedIndex: 0 });
-  },
+  }
 
   /**
    * Use this method to select the bottom-most item
    * This method is public.
    */
   jumpToBottom() {
     this.setState({ selectedIndex: this.state.list.length - 1 });
-  },
+  }
 
   /**
    * Increment the selected index with the provided increment value. Will cycle to the
    * beginning/end of the list if the index exceeds the list boundaries.
    * This method is public.
    *
    * @param {number} increment - No. of hops in the direction
    */
@@ -76,32 +83,32 @@ module.exports = createClass({
     if (increment > 0) {
       // Positive cycling
       nextIndex = nextIndex > list.length - 1 ? 0 : nextIndex;
     } else if (increment < 0) {
       // Inverse cycling
       nextIndex = nextIndex < 0 ? list.length - 1 : nextIndex;
     }
     this.setState({selectedIndex: nextIndex});
-  },
+  }
 
   /**
    * Submit the currently selected item to the onItemSelected callback
    * This method is public.
    */
   select() {
     if (this.refs.selected) {
       this.props.onItemSelected(this.refs.selected.dataset.value);
     }
-  },
+  }
 
   onMouseDown(e) {
     e.preventDefault();
     this.setState({ selectedIndex: Number(e.target.dataset.index) }, this.select);
-  },
+  }
 
   render() {
     let { list } = this.state;
 
     return list.length > 0 && dom.div(
       { className: "devtools-autocomplete-popup devtools-monospace" },
       dom.ul(
         { className: "devtools-autocomplete-listbox" },
@@ -119,9 +126,11 @@ module.exports = createClass({
             className: itemClassList.join(" "),
             ref: isSelected ? "selected" : null,
             onMouseDown: this.onMouseDown,
           }, item.displayValue);
         })
       )
     );
   }
-});
+}
+
+module.exports = AutocompletePopup;
--- a/devtools/client/shared/components/Frame.js
+++ b/devtools/client/shared/components/Frame.js
@@ -1,106 +1,112 @@
 /* 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 { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
+const { DOM: dom, Component, PropTypes } = require("devtools/client/shared/vendor/react");
 const { getSourceNames, parseURL,
         isScratchpadScheme, getSourceMappedFile } = require("devtools/client/shared/source-utils");
 const { LocalizationHelper } = require("devtools/shared/l10n");
 
 const l10n = new LocalizationHelper("devtools/client/locales/components.properties");
 const webl10n = new LocalizationHelper("devtools/client/locales/webconsole.properties");
 
-module.exports = createClass({
-  displayName: "Frame",
+class Frame extends Component {
+  static get propTypes() {
+    return {
+      // SavedFrame, or an object containing all the required properties.
+      frame: PropTypes.shape({
+        functionDisplayName: PropTypes.string,
+        source: PropTypes.string.isRequired,
+        line: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
+        column: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
+      }).isRequired,
+      // Clicking on the frame link -- probably should link to the debugger.
+      onClick: PropTypes.func.isRequired,
+      // Option to display a function name before the source link.
+      showFunctionName: PropTypes.bool,
+      // Option to display a function name even if it's anonymous.
+      showAnonymousFunctionName: PropTypes.bool,
+      // Option to display a host name after the source link.
+      showHost: PropTypes.bool,
+      // Option to display a host name if the filename is empty or just '/'
+      showEmptyPathAsHost: PropTypes.bool,
+      // Option to display a full source instead of just the filename.
+      showFullSourceUrl: PropTypes.bool,
+      // Service to enable the source map feature for console.
+      sourceMapService: PropTypes.object,
+    };
+  }
 
-  propTypes: {
-    // SavedFrame, or an object containing all the required properties.
-    frame: PropTypes.shape({
-      functionDisplayName: PropTypes.string,
-      source: PropTypes.string.isRequired,
-      line: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
-      column: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
-    }).isRequired,
-    // Clicking on the frame link -- probably should link to the debugger.
-    onClick: PropTypes.func.isRequired,
-    // Option to display a function name before the source link.
-    showFunctionName: PropTypes.bool,
-    // Option to display a function name even if it's anonymous.
-    showAnonymousFunctionName: PropTypes.bool,
-    // Option to display a host name after the source link.
-    showHost: PropTypes.bool,
-    // Option to display a host name if the filename is empty or just '/'
-    showEmptyPathAsHost: PropTypes.bool,
-    // Option to display a full source instead of just the filename.
-    showFullSourceUrl: PropTypes.bool,
-    // Service to enable the source map feature for console.
-    sourceMapService: PropTypes.object,
-  },
-
-  getDefaultProps() {
+  static get defaultProps() {
     return {
       showFunctionName: false,
       showAnonymousFunctionName: false,
       showHost: false,
       showEmptyPathAsHost: false,
       showFullSourceUrl: false,
     };
-  },
+  }
+
+  constructor(props) {
+    super(props);
+    this._locationChanged = this._locationChanged.bind(this);
+    this.getSourceForClick = this.getSourceForClick.bind(this);
+  }
 
   componentWillMount() {
     if (this.props.sourceMapService) {
       const { source, line, column } = this.props.frame;
       this.props.sourceMapService.subscribe(source, line, column,
                                             this._locationChanged);
     }
-  },
+  }
 
   componentWillUnmount() {
     if (this.props.sourceMapService) {
       const { source, line, column } = this.props.frame;
       this.props.sourceMapService.unsubscribe(source, line, column,
                                               this._locationChanged);
     }
-  },
+  }
 
   _locationChanged(isSourceMapped, url, line, column) {
     let newState = {
       isSourceMapped,
     };
     if (isSourceMapped) {
       newState.frame = {
         source: url,
         line,
         column,
         functionDisplayName: this.props.frame.functionDisplayName,
       };
     }
 
     this.setState(newState);
-  },
+  }
 
   /**
    * Utility method to convert the Frame object model to the
    * object model required by the onClick callback.
    * @param Frame frame
    * @returns {{url: *, line: *, column: *, functionDisplayName: *}}
    */
   getSourceForClick(frame) {
     const { source, line, column } = frame;
     return {
       url: source,
       line,
       column,
       functionDisplayName: this.props.frame.functionDisplayName,
     };
-  },
+  }
 
   render() {
     let frame, isSourceMapped;
     let {
       onClick,
       showFunctionName,
       showAnonymousFunctionName,
       showHost,
@@ -230,9 +236,11 @@ module.exports = createClass({
       elements.push(dom.span({
         key: "host",
         className: "frame-link-host",
       }, host));
     }
 
     return dom.span(attributes, ...elements);
   }
-});
+}
+
+module.exports = Frame;
--- a/devtools/client/shared/components/HSplitBox.js
+++ b/devtools/client/shared/components/HSplitBox.js
@@ -20,105 +20,111 @@
 //     |                       e                     |
 //     |                       r                     |
 //     |                       |                     |
 //     |                       |                     |
 //     +-----------------------+---------------------+
 
 const {
   DOM: dom,
-  createClass,
+  Component,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { assert } = require("devtools/shared/DevToolsUtils");
 
-module.exports = createClass({
-  displayName: "HSplitBox",
+class HSplitBox extends Component {
+  static get propTypes() {
+    return {
+      // The contents of the start pane.
+      start: PropTypes.any.isRequired,
 
-  propTypes: {
-    // The contents of the start pane.
-    start: PropTypes.any.isRequired,
-
-    // The contents of the end pane.
-    end: PropTypes.any.isRequired,
+      // The contents of the end pane.
+      end: PropTypes.any.isRequired,
 
-    // The relative width of the start pane, expressed as a number between 0 and
-    // 1. The relative width of the end pane is 1 - startWidth. For example,
-    // with startWidth = .5, both panes are of equal width; with startWidth =
-    // .25, the start panel will take up 1/4 width and the end panel will take
-    // up 3/4 width.
-    startWidth: PropTypes.number,
+      // The relative width of the start pane, expressed as a number between 0 and
+      // 1. The relative width of the end pane is 1 - startWidth. For example,
+      // with startWidth = .5, both panes are of equal width; with startWidth =
+      // .25, the start panel will take up 1/4 width and the end panel will take
+      // up 3/4 width.
+      startWidth: PropTypes.number,
 
-    // A minimum css width value for the start and end panes.
-    minStartWidth: PropTypes.any,
-    minEndWidth: PropTypes.any,
+      // A minimum css width value for the start and end panes.
+      minStartWidth: PropTypes.any,
+      minEndWidth: PropTypes.any,
 
-    // A callback fired when the user drags the splitter to resize the relative
-    // pane widths. The function is passed the startWidth value that would put
-    // the splitter underneath the users mouse.
-    onResize: PropTypes.func.isRequired,
-  },
+      // A callback fired when the user drags the splitter to resize the relative
+      // pane widths. The function is passed the startWidth value that would put
+      // the splitter underneath the users mouse.
+      onResize: PropTypes.func.isRequired,
+    };
+  }
 
-  getDefaultProps() {
+  static get defaultProps() {
     return {
       startWidth: 0.5,
       minStartWidth: "20px",
       minEndWidth: "20px",
     };
-  },
+  }
 
-  getInitialState() {
-    return {
+  constructor(props) {
+    super(props);
+
+    this.state = {
       mouseDown: false
     };
-  },
+
+    this._onMouseDown = this._onMouseDown.bind(this);
+    this._onMouseUp = this._onMouseUp.bind(this);
+    this._onMouseMove = this._onMouseMove.bind(this);
+  }
 
   componentDidMount() {
     document.defaultView.top.addEventListener("mouseup", this._onMouseUp);
     document.defaultView.top.addEventListener("mousemove", this._onMouseMove);
-  },
+  }
 
   componentWillUnmount() {
     document.defaultView.top.removeEventListener("mouseup", this._onMouseUp);
     document.defaultView.top.removeEventListener("mousemove", this._onMouseMove);
-  },
+  }
 
   _onMouseDown(event) {
     if (event.button !== 0) {
       return;
     }
 
     this.setState({ mouseDown: true });
     event.preventDefault();
-  },
+  }
 
   _onMouseUp(event) {
     if (event.button !== 0 || !this.state.mouseDown) {
       return;
     }
 
     this.setState({ mouseDown: false });
     event.preventDefault();
-  },
+  }
 
   _onMouseMove(event) {
     if (!this.state.mouseDown) {
       return;
     }
 
     const rect = this.refs.box.getBoundingClientRect();
     const { left, right } = rect;
     const width = right - left;
     const direction = this.refs.box.ownerDocument.dir;
     const relative = direction == "rtl" ? right - event.clientX
                                         : event.clientX - left;
     this.props.onResize(relative / width);
 
     event.preventDefault();
-  },
+  }
 
   render() {
     /* eslint-disable no-shadow */
     const { start, end, startWidth, minStartWidth, minEndWidth } = this.props;
     assert(startWidth => 0 && startWidth <= 1,
            "0 <= this.props.startWidth <= 1");
     /* eslint-enable */
     return dom.div(
@@ -144,9 +150,11 @@ module.exports = createClass({
         {
           className: "h-split-box-pane",
           style: { flex: 1 - startWidth, minWidth: minEndWidth },
         },
         end
       )
     );
   }
-});
+}
+
+module.exports = HSplitBox;
--- a/devtools/client/shared/components/NotificationBox.js
+++ b/devtools/client/shared/components/NotificationBox.js
@@ -5,17 +5,17 @@
 "use strict";
 
 const React = require("devtools/client/shared/vendor/react");
 const Immutable = require("devtools/client/shared/vendor/immutable");
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const l10n = new LocalizationHelper("devtools/client/locales/components.properties");
 
 // Shortcuts
-const { PropTypes, createClass, DOM } = React;
+const { PropTypes, Component, DOM } = React;
 const { div, span, button } = DOM;
 
 // Priority Levels
 const PriorityLevels = {
   PRIORITY_INFO_LOW: 1,
   PRIORITY_INFO_MEDIUM: 2,
   PRIORITY_INFO_HIGH: 3,
   PRIORITY_WARNING_LOW: 4,
@@ -29,82 +29,91 @@ const PriorityLevels = {
 
 /**
  * This component represents Notification Box - HTML alternative for
  * <xul:notificationbox> binding.
  *
  * See also MDN for more info about <xul:notificationbox>:
  * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/notificationbox
  */
-var NotificationBox = createClass({
-  displayName: "NotificationBox",
-
-  propTypes: {
-    // List of notifications appended into the box.
-    notifications: PropTypes.arrayOf(PropTypes.shape({
-      // label to appear on the notification.
-      label: PropTypes.string.isRequired,
-
-      // Value used to identify the notification
-      value: PropTypes.string.isRequired,
-
-      // URL of image to appear on the notification. If "" then an icon
-      // appropriate for the priority level is used.
-      image: PropTypes.string.isRequired,
-
-      // Notification priority; see Priority Levels.
-      priority: PropTypes.number.isRequired,
-
-      // Array of button descriptions to appear on the notification.
-      buttons: PropTypes.arrayOf(PropTypes.shape({
-        // Function to be called when the button is activated.
-        // This function is passed three arguments:
-        // 1) the NotificationBox component the button is associated with
-        // 2) the button description as passed to appendNotification.
-        // 3) the element which was the target of the button press event.
-        // If the return value from this function is not True, then the
-        // notification is closed. The notification is also not closed
-        // if an error is thrown.
-        callback: PropTypes.func.isRequired,
-
-        // The label to appear on the button.
+class NotificationBox extends Component {
+  static get propTypes() {
+    return {
+      // List of notifications appended into the box.
+      notifications: PropTypes.arrayOf(PropTypes.shape({
+        // label to appear on the notification.
         label: PropTypes.string.isRequired,
 
-        // The accesskey attribute set on the <button> element.
-        accesskey: PropTypes.string,
+        // Value used to identify the notification
+        value: PropTypes.string.isRequired,
+
+        // URL of image to appear on the notification. If "" then an icon
+        // appropriate for the priority level is used.
+        image: PropTypes.string.isRequired,
+
+        // Notification priority; see Priority Levels.
+        priority: PropTypes.number.isRequired,
+
+        // Array of button descriptions to appear on the notification.
+        buttons: PropTypes.arrayOf(PropTypes.shape({
+          // Function to be called when the button is activated.
+          // This function is passed three arguments:
+          // 1) the NotificationBox component the button is associated with
+          // 2) the button description as passed to appendNotification.
+          // 3) the element which was the target of the button press event.
+          // If the return value from this function is not True, then the
+          // notification is closed. The notification is also not closed
+          // if an error is thrown.
+          callback: PropTypes.func.isRequired,
+
+          // The label to appear on the button.
+          label: PropTypes.string.isRequired,
+
+          // The accesskey attribute set on the <button> element.
+          accesskey: PropTypes.string,
+        })),
+
+        // A function to call to notify you of interesting things that happen
+        // with the notification box.
+        eventCallback: PropTypes.func,
       })),
 
-      // A function to call to notify you of interesting things that happen
-      // with the notification box.
-      eventCallback: PropTypes.func,
-    })),
+      // Message that should be shown when hovering over the close button
+      closeButtonTooltip: PropTypes.string
+    };
+  }
 
-    // Message that should be shown when hovering over the close button
-    closeButtonTooltip: PropTypes.string
-  },
-
-  getDefaultProps() {
+  static get defaultProps() {
     return {
       closeButtonTooltip: l10n.getStr("notificationBox.closeTooltip")
     };
-  },
+  }
 
-  getInitialState() {
-    return {
+  constructor(props) {
+    super(props);
+
+    this.state = {
       notifications: new Immutable.OrderedMap()
     };
-  },
+
+    this.appendNotification = this.appendNotification.bind(this);
+    this.removeNotification = this.removeNotification.bind(this);
+    this.getNotificationWithValue = this.getNotificationWithValue.bind(this);
+    this.getCurrentNotification = this.getCurrentNotification.bind(this);
+    this.close = this.close.bind(this);
+    this.renderButton = this.renderButton.bind(this);
+    this.renderNotification = this.renderNotification.bind(this);
+  }
 
   /**
    * Create a new notification and display it. If another notification is
    * already present with a higher priority, the new notification will be
    * added behind it. See `propTypes` for arguments description.
    */
-  appendNotification(label, value, image, priority, buttons = [],
-    eventCallback) {
+  appendNotification(label, value, image, priority, buttons = [], eventCallback) {
     // Priority level must be within expected interval
     // (see priority levels at the top of this file).
     if (priority < PriorityLevels.PRIORITY_INFO_LOW ||
       priority > PriorityLevels.PRIORITY_CRITICAL_BLOCK) {
       throw new Error("Invalid notification priority " + priority);
     }
 
     // Custom image URL is not supported yet.
@@ -132,24 +141,24 @@ var NotificationBox = createClass({
     // High priorities must be on top.
     notifications = notifications.sortBy((val, key) => {
       return -val.priority;
     });
 
     this.setState({
       notifications: notifications
     });
-  },
+  }
 
   /**
    * Remove specific notification from the list.
    */
   removeNotification(notification) {
     this.close(this.state.notifications.get(notification.value));
-  },
+  }
 
   /**
    * Returns an object that represents a notification. It can be
    * used to close it.
    */
   getNotificationWithValue(value) {
     let notification = this.state.notifications.get(value);
     if (!notification) {
@@ -158,38 +167,38 @@ var NotificationBox = createClass({
 
     // Return an object that can be used to remove the notification
     // later (using `removeNotification` method) or directly close it.
     return Object.assign({}, notification, {
       close: () => {
         this.close(notification);
       }
     });
-  },
+  }
 
   getCurrentNotification() {
     return this.state.notifications.first();
-  },
+  }
 
   /**
    * Close specified notification.
    */
   close(notification) {
     if (!notification) {
       return;
     }
 
     if (notification.eventCallback) {
       notification.eventCallback("removed");
     }
 
     this.setState({
       notifications: this.state.notifications.remove(notification.value)
     });
-  },
+  }
 
   /**
    * Render a button. A notification can have a set of custom buttons.
    * These are used to execute custom callback.
    */
   renderButton(props, notification) {
     let onClick = event => {
       if (props.callback) {
@@ -205,17 +214,17 @@ var NotificationBox = createClass({
       button({
         key: props.label,
         className: "notification-button",
         accesskey: props.accesskey,
         onClick: onClick},
         props.label
       )
     );
-  },
+  }
 
   /**
    * Render a notification.
    */
   renderNotification(notification) {
     return (
       div({
         key: notification.value,
@@ -236,28 +245,28 @@ var NotificationBox = createClass({
           div({
             className: "messageCloseButton",
             title: this.props.closeButtonTooltip,
             onClick: this.close.bind(this, notification)}
           )
         )
       )
     );
-  },
+  }
 
   /**
    * Render the top (highest priority) notification. Only one
    * notification is rendered at a time.
    */
   render() {
     let notification = this.state.notifications.first();
     let content = notification ?
       this.renderNotification(notification) :
       null;
 
     return div({className: "notificationbox"},
       content
     );
-  },
-});
+  }
+}
 
 module.exports.NotificationBox = NotificationBox;
 module.exports.PriorityLevels = PriorityLevels;
--- a/devtools/client/shared/components/SearchBox.js
+++ b/devtools/client/shared/components/SearchBox.js
@@ -1,66 +1,71 @@
 /* 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/. */
 
 /* global window */
 
 "use strict";
 
-const { DOM: dom, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
+const { DOM: dom, Component, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
 const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
 const AutocompletePopup = createFactory(require("devtools/client/shared/components/AutoCompletePopup"));
 
-/**
- * A generic search box component for use across devtools
- */
-module.exports = createClass({
-  displayName: "SearchBox",
+class SearchBox extends Component {
+  static get propTypes() {
+    return {
+      delay: PropTypes.number,
+      keyShortcut: PropTypes.string,
+      onChange: PropTypes.func,
+      placeholder: PropTypes.string,
+      type: PropTypes.string,
+      autocompleteProvider: PropTypes.func,
+    };
+  }
 
-  propTypes: {
-    delay: PropTypes.number,
-    keyShortcut: PropTypes.string,
-    onChange: PropTypes.func,
-    placeholder: PropTypes.string,
-    type: PropTypes.string,
-    autocompleteProvider: PropTypes.func,
-  },
+  constructor(props) {
+    super(props);
 
-  getInitialState() {
-    return {
+    this.state = {
       value: "",
       focused: false,
     };
-  },
+
+    this.onChange = this.onChange.bind(this);
+    this.onClearButtonClick = this.onClearButtonClick.bind(this);
+    this.onFocus = this.onFocus.bind(this);
+    this.onBlur = this.onBlur.bind(this);
+    this.onKeyDown = this.onKeyDown.bind(this);
+  }
 
   componentDidMount() {
     if (!this.props.keyShortcut) {
       return;
     }
 
     this.shortcuts = new KeyShortcuts({
       window
     });
     this.shortcuts.on(this.props.keyShortcut, (name, event) => {
       event.preventDefault();
       this.refs.input.focus();
     });
-  },
+  }
 
   componentWillUnmount() {
     if (this.shortcuts) {
       this.shortcuts.destroy();
     }
 
     // Clean up an existing timeout.
     if (this.searchTimeout) {
       clearTimeout(this.searchTimeout);
     }
-  },
+  }
 
   onChange() {
     if (this.state.value !== this.refs.input.value) {
       this.setState({
         focused: true,
         value: this.refs.input.value,
       });
     }
@@ -76,30 +81,30 @@ module.exports = createClass({
     }
 
     // Execute the search after a timeout. It makes the UX
     // smoother if the user is typing quickly.
     this.searchTimeout = setTimeout(() => {
       this.searchTimeout = null;
       this.props.onChange(this.state.value);
     }, this.props.delay);
-  },
+  }
 
   onClearButtonClick() {
     this.refs.input.value = "";
     this.onChange();
-  },
+  }
 
   onFocus() {
     this.setState({ focused: true });
-  },
+  }
 
   onBlur() {
     this.setState({ focused: false });
-  },
+  }
 
   onKeyDown(e) {
     let { autocomplete } = this.refs;
     if (!autocomplete || autocomplete.state.list.length <= 0) {
       return;
     }
 
     switch (e.key) {
@@ -126,17 +131,17 @@ module.exports = createClass({
         break;
       case "Home":
         autocomplete.jumpToTop();
         break;
       case "End":
         autocomplete.jumpToBottom();
         break;
     }
-  },
+  }
 
   render() {
     let {
       type = "search",
       placeholder,
       autocompleteProvider,
     } = this.props;
     let { value } = this.state;
@@ -170,9 +175,11 @@ module.exports = createClass({
         ref: "autocomplete",
         onItemSelected: (itemValue) => {
           this.setState({ value: itemValue });
           this.onChange();
         }
       })
     );
   }
-});
+}
+
+module.exports = SearchBox;
--- a/devtools/client/shared/components/SidebarToggle.js
+++ b/devtools/client/shared/components/SidebarToggle.js
@@ -1,54 +1,58 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { DOM, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
+const { DOM, Component, PropTypes } = require("devtools/client/shared/vendor/react");
 
 // Shortcuts
 const { button } = DOM;
 
 /**
  * Sidebar toggle button. This button is used to exapand
  * and collapse Sidebar.
  */
-var SidebarToggle = createClass({
-  displayName: "SidebarToggle",
+class SidebarToggle extends Component {
+  static get propTypes() {
+    return {
+      // Set to true if collapsed.
+      collapsed: PropTypes.bool.isRequired,
+      // Tooltip text used when the button indicates expanded state.
+      collapsePaneTitle: PropTypes.string.isRequired,
+      // Tooltip text used when the button indicates collapsed state.
+      expandPaneTitle: PropTypes.string.isRequired,
+      // Click callback
+      onClick: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    // Set to true if collapsed.
-    collapsed: PropTypes.bool.isRequired,
-    // Tooltip text used when the button indicates expanded state.
-    collapsePaneTitle: PropTypes.string.isRequired,
-    // Tooltip text used when the button indicates collapsed state.
-    expandPaneTitle: PropTypes.string.isRequired,
-    // Click callback
-    onClick: PropTypes.func.isRequired,
-  },
+  constructor(props) {
+    super(props);
 
-  getInitialState: function () {
-    return {
-      collapsed: this.props.collapsed,
+    this.state = {
+      collapsed: props.collapsed,
     };
-  },
+
+    this.onClick = this.onClick.bind(this);
+  }
 
   // Events
 
-  onClick: function (event) {
+  onClick(event) {
     this.props.onClick(event);
-  },
+  }
 
   // Rendering
 
-  render: function () {
+  render() {
     let title = this.state.collapsed ?
       this.props.expandPaneTitle :
       this.props.collapsePaneTitle;
 
     let classNames = ["devtools-button", "sidebar-toggle"];
     if (this.state.collapsed) {
       classNames.push("pane-collapsed");
     }
@@ -56,11 +60,11 @@ var SidebarToggle = createClass({
     return (
       button({
         className: classNames.join(" "),
         title: title,
         onClick: this.onClick
       })
     );
   }
-});
+}
 
 module.exports = SidebarToggle;
--- a/devtools/client/shared/components/StackTrace.js
+++ b/devtools/client/shared/components/StackTrace.js
@@ -1,48 +1,48 @@
 /* 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 React = require("devtools/client/shared/vendor/react");
-const { DOM: dom, createClass, createFactory, PropTypes } = React;
+const { DOM: dom, Component, createFactory, PropTypes } = React;
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const Frame = createFactory(require("./Frame"));
 
 const l10n = new LocalizationHelper("devtools/client/locales/webconsole.properties");
 
-const AsyncFrame = createFactory(createClass({
-  displayName: "AsyncFrame",
-
-  propTypes: {
-    asyncCause: PropTypes.string.isRequired
-  },
+class AsyncFrameClass extends Component {
+  static get propTypes() {
+    return {
+      asyncCause: PropTypes.string.isRequired
+    };
+  }
 
   render() {
     let { asyncCause } = this.props;
 
     return dom.span(
       { className: "frame-link-async-cause" },
       l10n.getFormatStr("stacktrace.asyncStack", asyncCause)
     );
   }
-}));
-
-const StackTrace = createClass({
-  displayName: "StackTrace",
+}
 
-  propTypes: {
-    stacktrace: PropTypes.array.isRequired,
-    onViewSourceInDebugger: PropTypes.func.isRequired,
-    onViewSourceInScratchpad: PropTypes.func,
-    // Service to enable the source map feature.
-    sourceMapService: PropTypes.object,
-  },
+class StackTrace extends Component {
+  static get propTypes() {
+    return {
+      stacktrace: PropTypes.array.isRequired,
+      onViewSourceInDebugger: PropTypes.func.isRequired,
+      onViewSourceInScratchpad: PropTypes.func,
+      // Service to enable the source map feature.
+      sourceMapService: PropTypes.object,
+    };
+  }
 
   render() {
     let {
       stacktrace,
       onViewSourceInDebugger,
       onViewSourceInScratchpad,
       sourceMapService,
     } = this.props;
@@ -72,11 +72,13 @@ const StackTrace = createClass({
           ? onViewSourceInScratchpad
           : onViewSourceInDebugger,
         sourceMapService,
       }), "\n");
     });
 
     return dom.div({ className: "stack-trace" }, frames);
   }
-});
+}
+
+const AsyncFrame = createFactory(AsyncFrameClass);
 
 module.exports = StackTrace;
--- a/devtools/client/shared/components/Tree.js
+++ b/devtools/client/shared/components/Tree.js
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 /* eslint-env browser */
 "use strict";
 
 const React = require("devtools/client/shared/vendor/react");
-const { DOM: dom, createClass, createFactory, PropTypes } = React;
+const { DOM: dom, Component, createFactory, PropTypes } = React;
 
 const AUTO_EXPAND_DEPTH = 0;
 const NUMBER_OF_OFFSCREEN_ITEMS = 1;
 
 /**
  * A fast, generic, expandable and collapsible tree component.
  *
  * This tree component is fast: it can handle trees with *many* items. It only
@@ -92,168 +92,186 @@ const NUMBER_OF_OFFSCREEN_ITEMS = 1;
  *           },
  *
  *           onExpand: item => dispatchExpandActionToRedux(item),
  *           onCollapse: item => dispatchCollapseActionToRedux(item),
  *         });
  *       }
  *     });
  */
-module.exports = createClass({
-  displayName: "Tree",
-
-  propTypes: {
-    // Required props
+class Tree extends Component {
+  static get propTypes() {
+    return {
+      // Required props
 
-    // A function to get an item's parent, or null if it is a root.
-    //
-    // Type: getParent(item: Item) -> Maybe<Item>
-    //
-    // Example:
-    //
-    //     // The parent of this item is stored in its `parent` property.
-    //     getParent: item => item.parent
-    getParent: PropTypes.func.isRequired,
+      // A function to get an item's parent, or null if it is a root.
+      //
+      // Type: getParent(item: Item) -> Maybe<Item>
+      //
+      // Example:
+      //
+      //     // The parent of this item is stored in its `parent` property.
+      //     getParent: item => item.parent
+      getParent: PropTypes.func.isRequired,
 
-    // A function to get an item's children.
-    //
-    // Type: getChildren(item: Item) -> [Item]
-    //
-    // Example:
-    //
-    //     // This item's children are stored in its `children` property.
-    //     getChildren: item => item.children
-    getChildren: PropTypes.func.isRequired,
+      // A function to get an item's children.
+      //
+      // Type: getChildren(item: Item) -> [Item]
+      //
+      // Example:
+      //
+      //     // This item's children are stored in its `children` property.
+      //     getChildren: item => item.children
+      getChildren: PropTypes.func.isRequired,
 
-    // A function which takes an item and ArrowExpander component instance and
-    // returns a component, or text, or anything else that React considers
-    // renderable.
-    //
-    // Type: renderItem(item: Item,
-    //                  depth: Number,
-    //                  isFocused: Boolean,
-    //                  arrow: ReactComponent,
-    //                  isExpanded: Boolean) -> ReactRenderable
-    //
-    // Example:
-    //
-    //     renderItem: (item, depth, isFocused, arrow, isExpanded) => {
-    //       let className = "my-tree-item";
-    //       if (isFocused) {
-    //         className += " focused";
-    //       }
-    //       return dom.div(
-    //         {
-    //           className,
-    //           style: { marginLeft: depth * 10 + "px" }
-    //         },
-    //         arrow,
-    //         dom.span({ className: "my-tree-item-label" }, item.label)
-    //       );
-    //     },
-    renderItem: PropTypes.func.isRequired,
+      // A function which takes an item and ArrowExpander component instance and
+      // returns a component, or text, or anything else that React considers
+      // renderable.
+      //
+      // Type: renderItem(item: Item,
+      //                  depth: Number,
+      //                  isFocused: Boolean,
+      //                  arrow: ReactComponent,
+      //                  isExpanded: Boolean) -> ReactRenderable
+      //
+      // Example:
+      //
+      //     renderItem: (item, depth, isFocused, arrow, isExpanded) => {
+      //       let className = "my-tree-item";
+      //       if (isFocused) {
+      //         className += " focused";
+      //       }
+      //       return dom.div(
+      //         {
+      //           className,
+      //           style: { marginLeft: depth * 10 + "px" }
+      //         },
+      //         arrow,
+      //         dom.span({ className: "my-tree-item-label" }, item.label)
+      //       );
+      //     },
+      renderItem: PropTypes.func.isRequired,
 
-    // A function which returns the roots of the tree (forest).
-    //
-    // Type: getRoots() -> [Item]
-    //
-    // Example:
-    //
-    //     // In this case, we only have one top level, root item. You could
-    //     // return multiple items if you have many top level items in your
-    //     // tree.
-    //     getRoots: () => [this.props.rootOfMyTree]
-    getRoots: PropTypes.func.isRequired,
+      // A function which returns the roots of the tree (forest).
+      //
+      // Type: getRoots() -> [Item]
+      //
+      // Example:
+      //
+      //     // In this case, we only have one top level, root item. You could
+      //     // return multiple items if you have many top level items in your
+      //     // tree.
+      //     getRoots: () => [this.props.rootOfMyTree]
+      getRoots: PropTypes.func.isRequired,
 
-    // A function to get a unique key for the given item. This helps speed up
-    // React's rendering a *TON*.
-    //
-    // Type: getKey(item: Item) -> String
-    //
-    // Example:
-    //
-    //     getKey: item => `my-tree-item-${item.uniqueId}`
-    getKey: PropTypes.func.isRequired,
+      // A function to get a unique key for the given item. This helps speed up
+      // React's rendering a *TON*.
+      //
+      // Type: getKey(item: Item) -> String
+      //
+      // Example:
+      //
+      //     getKey: item => `my-tree-item-${item.uniqueId}`
+      getKey: PropTypes.func.isRequired,
 
-    // A function to get whether an item is expanded or not. If an item is not
-    // expanded, then it must be collapsed.
-    //
-    // Type: isExpanded(item: Item) -> Boolean
-    //
-    // Example:
-    //
-    //     isExpanded: item => item.expanded,
-    isExpanded: PropTypes.func.isRequired,
+      // A function to get whether an item is expanded or not. If an item is not
+      // expanded, then it must be collapsed.
+      //
+      // Type: isExpanded(item: Item) -> Boolean
+      //
+      // Example:
+      //
+      //     isExpanded: item => item.expanded,
+      isExpanded: PropTypes.func.isRequired,
 
-    // The height of an item in the tree including margin and padding, in
-    // pixels.
-    itemHeight: PropTypes.number.isRequired,
+      // The height of an item in the tree including margin and padding, in
+      // pixels.
+      itemHeight: PropTypes.number.isRequired,
 
-    // Optional props
+      // Optional props
 
-    // The currently focused item, if any such item exists.
-    focused: PropTypes.any,
+      // The currently focused item, if any such item exists.
+      focused: PropTypes.any,
 
-    // Handle when a new item is focused.
-    onFocus: PropTypes.func,
+      // Handle when a new item is focused.
+      onFocus: PropTypes.func,
 
-    // The depth to which we should automatically expand new items.
-    autoExpandDepth: PropTypes.number,
+      // The depth to which we should automatically expand new items.
+      autoExpandDepth: PropTypes.number,
 
-    // Note: the two properties below are mutually exclusive. Only one of the
-    // label properties is necessary.
-    // ID of an element whose textual content serves as an accessible label for
-    // a tree.
-    labelledby: PropTypes.string,
-    // Accessibility label for a tree widget.
-    label: PropTypes.string,
+      // Note: the two properties below are mutually exclusive. Only one of the
+      // label properties is necessary.
+      // ID of an element whose textual content serves as an accessible label for
+      // a tree.
+      labelledby: PropTypes.string,
+      // Accessibility label for a tree widget.
+      label: PropTypes.string,
 
-    // Optional event handlers for when items are expanded or collapsed. Useful
-    // for dispatching redux events and updating application state, maybe lazily
-    // loading subtrees from a worker, etc.
-    //
-    // Type:
-    //     onExpand(item: Item)
-    //     onCollapse(item: Item)
-    //
-    // Example:
-    //
-    //     onExpand: item => dispatchExpandActionToRedux(item)
-    onExpand: PropTypes.func,
-    onCollapse: PropTypes.func,
-  },
+      // Optional event handlers for when items are expanded or collapsed. Useful
+      // for dispatching redux events and updating application state, maybe lazily
+      // loading subtrees from a worker, etc.
+      //
+      // Type:
+      //     onExpand(item: Item)
+      //     onCollapse(item: Item)
+      //
+      // Example:
+      //
+      //     onExpand: item => dispatchExpandActionToRedux(item)
+      onExpand: PropTypes.func,
+      onCollapse: PropTypes.func,
+    };
+  }
 
-  getDefaultProps() {
+  static get defaultProps() {
     return {
       autoExpandDepth: AUTO_EXPAND_DEPTH,
     };
-  },
+  }
 
-  getInitialState() {
-    return {
+  constructor(props) {
+    super(props);
+
+    this.state = {
       scroll: 0,
       height: window.innerHeight,
       seen: new Set(),
     };
-  },
+
+    this._onExpand = oncePerAnimationFrame(this._onExpand).bind(this);
+    this._onCollapse = oncePerAnimationFrame(this._onCollapse).bind(this);
+    this._onScroll = oncePerAnimationFrame(this._onScroll).bind(this);
+    this._focusPrevNode = oncePerAnimationFrame(this._focusPrevNode).bind(this);
+    this._focusNextNode = oncePerAnimationFrame(this._focusNextNode).bind(this);
+    this._focusParentNode = oncePerAnimationFrame(this._focusParentNode).bind(this);
+
+    this._autoExpand = this._autoExpand.bind(this);
+    this._preventArrowKeyScrolling = this._preventArrowKeyScrolling.bind(this);
+    this._updateHeight = this._updateHeight.bind(this);
+    this._dfs = this._dfs.bind(this);
+    this._dfsFromRoots = this._dfsFromRoots.bind(this);
+    this._focus = this._focus.bind(this);
+    this._onBlur = this._onBlur.bind(this);
+    this._onKeyDown = this._onKeyDown.bind(this);
+  }
 
   componentDidMount() {
     window.addEventListener("resize", this._updateHeight);
     this._autoExpand();
     this._updateHeight();
-  },
+  }
 
   componentWillReceiveProps(nextProps) {
     this._autoExpand();
     this._updateHeight();
-  },
+  }
 
   componentWillUnmount() {
     window.removeEventListener("resize", this._updateHeight);
-  },
+  }
 
   _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
@@ -274,17 +292,17 @@ module.exports = createClass({
       }
     };
 
     const roots = this.props.getRoots();
     const length = roots.length;
     for (let i = 0; i < length; i++) {
       autoExpand(roots[i], 0);
     }
-  },
+  }
 
   _preventArrowKeyScrolling(e) {
     switch (e.key) {
       case "ArrowUp":
       case "ArrowDown":
       case "ArrowLeft":
       case "ArrowRight":
         e.preventDefault();
@@ -293,26 +311,26 @@ module.exports = createClass({
           if (e.nativeEvent.preventDefault) {
             e.nativeEvent.preventDefault();
           }
           if (e.nativeEvent.stopPropagation) {
             e.nativeEvent.stopPropagation();
           }
         }
     }
-  },
+  }
 
   /**
    * Updates the state's height based on clientHeight.
    */
   _updateHeight() {
     this.setState({
       height: this.refs.tree.clientHeight
     });
-  },
+  }
 
   /**
    * Perform a pre-order depth-first search from item.
    */
   _dfs(item, maxDepth = Infinity, traversal = [], _depth = 0) {
     traversal.push({ item, depth: _depth });
 
     if (!this.props.isExpanded(item)) {
@@ -327,63 +345,63 @@ module.exports = createClass({
 
     const children = this.props.getChildren(item);
     const length = children.length;
     for (let i = 0; i < length; i++) {
       this._dfs(children[i], maxDepth, traversal, nextDepth);
     }
 
     return traversal;
-  },
+  }
 
   /**
    * Perform a pre-order depth-first search over the whole forest.
    */
   _dfsFromRoots(maxDepth = Infinity) {
     const traversal = [];
 
     const roots = this.props.getRoots();
     const length = roots.length;
     for (let i = 0; i < length; i++) {
       this._dfs(roots[i], maxDepth, traversal);
     }
 
     return traversal;
-  },
+  }
 
   /**
    * Expands current row.
    *
    * @param {Object} item
    * @param {Boolean} expandAllChildren
    */
-  _onExpand: oncePerAnimationFrame(function (item, expandAllChildren) {
+  _onExpand(item, expandAllChildren) {
     if (this.props.onExpand) {
       this.props.onExpand(item);
 
       if (expandAllChildren) {
         const children = this._dfs(item);
         const length = children.length;
         for (let i = 0; i < length; i++) {
           this.props.onExpand(children[i].item);
         }
       }
     }
-  }),
+  }
 
   /**
    * Collapses current row.
    *
    * @param {Object} item
    */
-  _onCollapse: oncePerAnimationFrame(function (item) {
+  _onCollapse(item) {
     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.
    *
@@ -406,37 +424,37 @@ module.exports = createClass({
       } else if ((this.state.scroll + this.state.height) < itemEndPosition) {
         this.refs.tree.scrollTo(0, itemEndPosition - this.state.height);
       }
     }
 
     if (this.props.onFocus) {
       this.props.onFocus(item);
     }
-  },
+  }
 
   /**
    * Sets the state to have no focused item.
    */
   _onBlur() {
     this._focus(0, undefined);
-  },
+  }
 
   /**
    * Fired on a scroll within the tree's container, updates
    * the stored position of the view port to handle virtual view rendering.
    *
    * @param {Event} e
    */
-  _onScroll: oncePerAnimationFrame(function (e) {
+  _onScroll(e) {
     this.setState({
       scroll: Math.max(this.refs.tree.scrollTop, 0),
       height: this.refs.tree.clientHeight
     });
-  }),
+  }
 
   /**
    * Handles key down events in the tree's container.
    *
    * @param {Event} e
    */
   _onKeyDown(e) {
     if (this.props.focused == null) {
@@ -471,22 +489,22 @@ module.exports = createClass({
       case "ArrowRight":
         if (!this.props.isExpanded(this.props.focused)) {
           this._onExpand(this.props.focused);
         } else {
           this._focusNextNode();
         }
         break;
     }
-  },
+  }
 
   /**
    * Sets the previous node relative to the currently focused item, to focused.
    */
-  _focusPrevNode: oncePerAnimationFrame(function () {
+  _focusPrevNode() {
     // 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();
@@ -500,23 +518,23 @@ module.exports = createClass({
       prevIndex = i;
     }
 
     if (prev === undefined) {
       return;
     }
 
     this._focus(prevIndex, prev);
-  }),
+  }
 
   /**
    * Handles the down arrow key which will focus either the next child
    * or sibling row.
    */
-  _focusNextNode: oncePerAnimationFrame(function () {
+  _focusNextNode() {
     // 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;
 
@@ -525,39 +543,39 @@ module.exports = createClass({
         break;
       }
       i++;
     }
 
     if (i + 1 < traversal.length) {
       this._focus(i + 1, traversal[i + 1].item);
     }
-  }),
+  }
 
   /**
    * Handles the left arrow key, going back up to the current rows'
    * parent row.
    */
-  _focusParentNode: oncePerAnimationFrame(function () {
+  _focusParentNode() {
     const parent = this.props.getParent(this.props.focused);
     if (!parent) {
       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);
-  }),
+  }
 
   render() {
     const traversal = this._dfsFromRoots();
 
     // 'begin' and 'end' are the index of the first (at least partially) visible item
     // and the index after the last (at least partially) visible item, respectively.
     // `NUMBER_OF_OFFSCREEN_ITEMS` is removed from `begin` and added to `end` so that
     // the top and bottom of the page are filled with the `NUMBER_OF_OFFSCREEN_ITEMS`
@@ -651,38 +669,38 @@ module.exports = createClass({
         style: {
           padding: 0,
           margin: 0
         }
       },
       nodes
     );
   }
-});
+}
 
 /**
  * An arrow that displays whether its node is expanded (▼) or collapsed
  * (▶). When its node has no children, it is hidden.
  */
-const ArrowExpander = createFactory(createClass({
-  displayName: "ArrowExpander",
-
-  propTypes: {
-    item: PropTypes.any.isRequired,
-    visible: PropTypes.bool.isRequired,
-    expanded: PropTypes.bool.isRequired,
-    onCollapse: PropTypes.func.isRequired,
-    onExpand: PropTypes.func.isRequired,
-  },
+class ArrowExpanderClass extends Component {
+  static get propTypes() {
+    return {
+      item: PropTypes.any.isRequired,
+      visible: PropTypes.bool.isRequired,
+      expanded: PropTypes.bool.isRequired,
+      onCollapse: PropTypes.func.isRequired,
+      onExpand: PropTypes.func.isRequired,
+    };
+  }
 
   shouldComponentUpdate(nextProps, nextState) {
     return this.props.item !== nextProps.item
       || this.props.visible !== nextProps.visible
       || this.props.expanded !== nextProps.expanded;
-  },
+  }
 
   render() {
     const attrs = {
       className: "arrow theme-twisty",
       onClick: this.props.expanded
         ? () => this.props.onCollapse(this.props.item)
         : e => this.props.onExpand(this.props.item, e.altKey)
     };
@@ -694,34 +712,36 @@ const ArrowExpander = createFactory(crea
     if (!this.props.visible) {
       attrs.style = {
         visibility: "hidden"
       };
     }
 
     return dom.div(attrs);
   }
-}));
+}
 
-const TreeNode = createFactory(createClass({
-  propTypes: {
-    id: PropTypes.any.isRequired,
-    focused: PropTypes.bool.isRequired,
-    item: PropTypes.any.isRequired,
-    expanded: PropTypes.bool.isRequired,
-    hasChildren: PropTypes.bool.isRequired,
-    onExpand: PropTypes.func.isRequired,
-    index: PropTypes.number.isRequired,
-    first: PropTypes.bool,
-    last: PropTypes.bool,
-    onClick: PropTypes.func,
-    onCollapse: PropTypes.func.isRequired,
-    depth: PropTypes.number.isRequired,
-    renderItem: PropTypes.func.isRequired,
-  },
+class TreeNodeClass extends Component {
+  static get propTypes() {
+    return {
+      id: PropTypes.any.isRequired,
+      focused: PropTypes.bool.isRequired,
+      item: PropTypes.any.isRequired,
+      expanded: PropTypes.bool.isRequired,
+      hasChildren: PropTypes.bool.isRequired,
+      onExpand: PropTypes.func.isRequired,
+      index: PropTypes.number.isRequired,
+      first: PropTypes.bool,
+      last: PropTypes.bool,
+      onClick: PropTypes.func,
+      onCollapse: PropTypes.func.isRequired,
+      depth: PropTypes.number.isRequired,
+      renderItem: PropTypes.func.isRequired,
+    };
+  }
 
   render() {
     const arrow = ArrowExpander({
       item: this.props.item,
       expanded: this.props.expanded,
       visible: this.props.hasChildren,
       onExpand: this.props.onExpand,
       onCollapse: this.props.onCollapse,
@@ -764,17 +784,20 @@ const TreeNode = createFactory(createCla
 
       this.props.renderItem(this.props.item,
                             this.props.depth,
                             this.props.focused,
                             arrow,
                             this.props.expanded),
     );
   }
-}));
+}
+
+const ArrowExpander = createFactory(ArrowExpanderClass);
+const TreeNode = createFactory(TreeNodeClass);
 
 /**
  * Create a function that calls the given function `fn` only once per animation
  * frame.
  *
  * @param {Function} fn
  * @returns {Function}
  */
@@ -789,8 +812,10 @@ function oncePerAnimationFrame(fn) {
 
     animationId = requestAnimationFrame(() => {
       fn.call(this, ...argsToPass);
       animationId = null;
       argsToPass = null;
     });
   };
 }
+
+module.exports = Tree;
--- a/devtools/client/shared/components/splitter/Draggable.js
+++ b/devtools/client/shared/components/splitter/Draggable.js
@@ -1,54 +1,61 @@
 /* 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 React = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
-const { DOM: dom, PropTypes } = React;
-
-const Draggable = React.createClass({
-  displayName: "Draggable",
+const { Component, DOM: dom, PropTypes } = React;
 
-  propTypes: {
-    onMove: PropTypes.func.isRequired,
-    onStart: PropTypes.func,
-    onStop: PropTypes.func,
-    style: PropTypes.object,
-    className: PropTypes.string
-  },
+class Draggable extends Component {
+  static get propTypes() {
+    return {
+      onMove: PropTypes.func.isRequired,
+      onStart: PropTypes.func,
+      onStop: PropTypes.func,
+      style: PropTypes.object,
+      className: PropTypes.string
+    };
+  }
+
+  constructor(props) {
+    super(props);
+    this.startDragging = this.startDragging.bind(this);
+    this.onMove = this.onMove.bind(this);
+    this.onUp = this.onUp.bind(this);
+  }
 
   startDragging(ev) {
     ev.preventDefault();
     const doc = ReactDOM.findDOMNode(this).ownerDocument;
     doc.addEventListener("mousemove", this.onMove);
     doc.addEventListener("mouseup", this.onUp);
     this.props.onStart && this.props.onStart();
-  },
+  }
 
   onMove(ev) {
     ev.preventDefault();
     // Use viewport coordinates so, moving mouse over iframes
     // doesn't mangle (relative) coordinates.
     this.props.onMove(ev.clientX, ev.clientY);
-  },
+  }
 
   onUp(ev) {
     ev.preventDefault();
     const doc = ReactDOM.findDOMNode(this).ownerDocument;
     doc.removeEventListener("mousemove", this.onMove);
     doc.removeEventListener("mouseup", this.onUp);
     this.props.onStop && this.props.onStop();
-  },
+  }
 
   render() {
     return dom.div({
       style: this.props.style,
       className: this.props.className,
       onMouseDown: this.startDragging
     });
   }
-});
+}
 
 module.exports = Draggable;
--- a/devtools/client/shared/components/splitter/SplitBox.js
+++ b/devtools/client/shared/components/splitter/SplitBox.js
@@ -2,92 +2,98 @@
  * 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 React = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const Draggable = React.createFactory(require("devtools/client/shared/components/splitter/Draggable"));
-const { DOM: dom, PropTypes } = React;
+const { Component, DOM: dom, PropTypes } = React;
 
 /**
  * This component represents a Splitter. The splitter supports vertical
  * as well as horizontal mode.
  */
-const SplitBox = React.createClass({
-  displayName: "SplitBox",
+class SplitBox extends Component {
+  static get propTypes() {
+    return {
+      // Custom class name. You can use more names separated by a space.
+      className: PropTypes.string,
+      // Initial size of controlled panel.
+      initialSize: PropTypes.string,
+      // Initial width of controlled panel.
+      initialWidth: PropTypes.string,
+      // Initial height of controlled panel.
+      initialHeight: PropTypes.string,
+      // Left/top panel
+      startPanel: PropTypes.any,
+      // Min panel size.
+      minSize: PropTypes.string,
+      // Max panel size.
+      maxSize: PropTypes.string,
+      // Right/bottom panel
+      endPanel: PropTypes.any,
+      // True if the right/bottom panel should be controlled.
+      endPanelControl: PropTypes.bool,
+      // Size of the splitter handle bar.
+      splitterSize: PropTypes.string,
+      // True if the splitter bar is vertical (default is vertical).
+      vert: PropTypes.bool,
+      // Style object.
+      style: PropTypes.object,
+    };
+  }
 
-  propTypes: {
-    // Custom class name. You can use more names separated by a space.
-    className: PropTypes.string,
-    // Initial size of controlled panel.
-    initialSize: PropTypes.string,
-    // Initial width of controlled panel.
-    initialWidth: PropTypes.string,
-    // Initial height of controlled panel.
-    initialHeight: PropTypes.string,
-    // Left/top panel
-    startPanel: PropTypes.any,
-    // Min panel size.
-    minSize: PropTypes.string,
-    // Max panel size.
-    maxSize: PropTypes.string,
-    // Right/bottom panel
-    endPanel: PropTypes.any,
-    // True if the right/bottom panel should be controlled.
-    endPanelControl: PropTypes.bool,
-    // Size of the splitter handle bar.
-    splitterSize: PropTypes.string,
-    // True if the splitter bar is vertical (default is vertical).
-    vert: PropTypes.bool,
-    // Style object.
-    style: PropTypes.object,
-  },
-
-  getDefaultProps() {
+  static get defaultProps() {
     return {
       splitterSize: 5,
       vert: true,
       endPanelControl: false
     };
-  },
+  }
+
+  constructor(props) {
+    super(props);
 
-  /**
-   * The state stores the current orientation (vertical or horizontal)
-   * and the current size (width/height). All these values can change
-   * during the component's life time.
-   */
-  getInitialState() {
-    return {
-      vert: this.props.vert,
-      width: this.props.initialWidth || this.props.initialSize,
-      height: this.props.initialHeight || this.props.initialSize
+    /**
+     * The state stores the current orientation (vertical or horizontal)
+     * and the current size (width/height). All these values can change
+     * during the component's life time.
+     */
+    this.state = {
+      vert: props.vert,
+      width: props.initialWidth || props.initialSize,
+      height: props.initialHeight || props.initialSize
     };
-  },
+
+    this.onStartMove = this.onStartMove.bind(this);
+    this.onStopMove = this.onStopMove.bind(this);
+    this.onMove = this.onMove.bind(this);
+  }
 
   componentWillReceiveProps(nextProps) {
     let { vert } = nextProps;
 
     if (vert !== this.props.vert) {
       this.setState({ vert });
     }
-  },
+  }
 
   shouldComponentUpdate(nextProps, nextState) {
     return nextState.width != this.state.width ||
       nextState.height != this.state.height ||
       nextState.vert != this.state.vert ||
       nextProps.startPanel != this.props.startPanel ||
       nextProps.endPanel != this.props.endPanel ||
       nextProps.endPanelControl != this.props.endPanelControl ||
       nextProps.minSize != this.props.minSize ||
       nextProps.maxSize != this.props.maxSize ||
       nextProps.splitterSize != this.props.splitterSize;
-  },
+  }
 
   // Dragging Events
 
   /**
    * Set 'resizing' cursor on entire document during splitter dragging.
    * This avoids cursor-flickering that happens when the mouse leaves
    * the splitter bar area (happens frequently).
    */
@@ -97,25 +103,25 @@ const SplitBox = React.createClass({
     let defaultCursor = doc.documentElement.style.cursor;
     doc.documentElement.style.cursor = (this.state.vert ? "ew-resize" : "ns-resize");
 
     splitBox.classList.add("dragging");
 
     this.setState({
       defaultCursor: defaultCursor
     });
-  },
+  }
 
   onStopMove() {
     const splitBox = ReactDOM.findDOMNode(this);
     const doc = splitBox.ownerDocument;
     doc.documentElement.style.cursor = this.state.defaultCursor;
 
     splitBox.classList.remove("dragging");
-  },
+  }
 
   /**
    * Adjust size of the controlled panel. Depending on the current
    * orientation we either remember the width or height of
    * the splitter box.
    */
   onMove(x, y) {
     const node = ReactDOM.findDOMNode(this);
@@ -144,17 +150,17 @@ const SplitBox = React.createClass({
       size = endPanelControl ?
         (node.offsetTop + node.offsetHeight) - y :
         y - node.offsetTop;
 
       this.setState({
         height: size
       });
     }
-  },
+  }
 
   // Rendering
 
   render() {
     const vert = this.state.vert;
     const { startPanel, endPanel, endPanelControl, minSize,
       maxSize, splitterSize } = this.props;
 
@@ -221,11 +227,11 @@ const SplitBox = React.createClass({
           dom.div({
             className: endPanelControl ? "controlled" : "uncontrolled",
             style: rightPanelStyle},
             endPanel
           ) : null
       )
     );
   }
-});
+}
 
 module.exports = SplitBox;
--- a/devtools/client/shared/components/tabs/TabBar.js
+++ b/devtools/client/shared/components/tabs/TabBar.js
@@ -3,87 +3,100 @@
 /* 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/. */
 
 /* eslint-env browser */
 
 "use strict";
 
-const { DOM, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
+const { DOM, Component, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
 const Tabs = createFactory(require("devtools/client/shared/components/tabs/Tabs").Tabs);
 
 const Menu = require("devtools/client/framework/menu");
 const MenuItem = require("devtools/client/framework/menu-item");
 
 // Shortcuts
 const { div } = DOM;
 
 /**
  * Renders Tabbar component.
  */
-let Tabbar = createClass({
-  displayName: "Tabbar",
+class Tabbar extends Component {
+  static get propTypes() {
+    return {
+      children: PropTypes.array,
+      menuDocument: PropTypes.object,
+      onSelect: PropTypes.func,
+      showAllTabsMenu: PropTypes.bool,
+      activeTabId: PropTypes.string,
+      renderOnlySelected: PropTypes.bool,
+    };
+  }
 
-  propTypes: {
-    children: PropTypes.array,
-    menuDocument: PropTypes.object,
-    onSelect: PropTypes.func,
-    showAllTabsMenu: PropTypes.bool,
-    activeTabId: PropTypes.string,
-    renderOnlySelected: PropTypes.bool,
-  },
-
-  getDefaultProps: function () {
+  static get defaultProps() {
     return {
       menuDocument: window.parent.document,
       showAllTabsMenu: false,
     };
-  },
+  }
 
-  getInitialState: function () {
-    let { activeTabId, children = [] } = this.props;
+  constructor(props, context) {
+    super(props, context);
+    let { activeTabId, children = [] } = props;
     let tabs = this.createTabs(children);
     let activeTab = tabs.findIndex((tab, index) => tab.id === activeTabId);
 
-    return {
+    this.state = {
       activeTab: activeTab === -1 ? 0 : activeTab,
       tabs,
     };
-  },
 
-  componentWillReceiveProps: function (nextProps) {
+    this.createTabs = this.createTabs.bind(this);
+    this.addTab = this.addTab.bind(this);
+    this.toggleTab = this.toggleTab.bind(this);
+    this.removeTab = this.removeTab.bind(this);
+    this.select = this.select.bind(this);
+    this.getTabIndex = this.getTabIndex.bind(this);
+    this.getTabId = this.getTabId.bind(this);
+    this.getCurrentTabId = this.getCurrentTabId.bind(this);
+    this.onTabChanged = this.onTabChanged.bind(this);
+    this.onAllTabsMenuClick = this.onAllTabsMenuClick.bind(this);
+    this.renderTab = this.renderTab.bind(this);
+  }
+
+  componentWillReceiveProps(nextProps) {
     let { activeTabId, children = [] } = nextProps;
     let tabs = this.createTabs(children);
     let activeTab = tabs.findIndex((tab, index) => tab.id === activeTabId);
 
     if (activeTab !== this.state.activeTab ||
         (children !== this.props.children)) {
       this.setState({
         activeTab: activeTab === -1 ? 0 : activeTab,
         tabs,
       });
     }
-  },
+  }
 
-  createTabs: function (children) {
+  createTabs(children) {
     return children
       .filter((panel) => panel)
       .map((panel, index) =>
         Object.assign({}, children[index], {
           id: panel.props.id || index,
           panel,
           title: panel.props.title,
         })
       );
-  },
+  }
 
   // Public API
 
-  addTab: function (id, title, selected = false, panel, url, index = -1) {
+  addTab(id, title, selected = false, panel, url, index = -1) {
     let tabs = this.state.tabs.slice();
 
     if (index >= 0) {
       tabs.splice(index, 0, {id, title, panel, url});
     } else {
       tabs.push({id, title, panel, url});
     }
 
@@ -95,35 +108,35 @@ let Tabbar = createClass({
       newState.activeTab = index >= 0 ? index : tabs.length - 1;
     }
 
     this.setState(newState, () => {
       if (this.props.onSelect && selected) {
         this.props.onSelect(id);
       }
     });
-  },
+  }
 
-  toggleTab: function (tabId, isVisible) {
+  toggleTab(tabId, isVisible) {
     let index = this.getTabIndex(tabId);
     if (index < 0) {
       return;
     }
 
     let tabs = this.state.tabs.slice();
     tabs[index] = Object.assign({}, tabs[index], {
       isVisible: isVisible
     });
 
     this.setState(Object.assign({}, this.state, {
       tabs: tabs,
     }));
-  },
+  }
 
-  removeTab: function (tabId) {
+  removeTab(tabId) {
     let index = this.getTabIndex(tabId);
     if (index < 0) {
       return;
     }
 
     let tabs = this.state.tabs.slice();
     tabs.splice(index, 1);
 
@@ -132,68 +145,68 @@ let Tabbar = createClass({
     if (activeTab >= tabs.length) {
       activeTab = tabs.length - 1;
     }
 
     this.setState(Object.assign({}, this.state, {
       tabs,
       activeTab,
     }));
-  },
+  }
 
-  select: function (tabId) {
+  select(tabId) {
     let index = this.getTabIndex(tabId);
     if (index < 0) {
       return;
     }
 
     let newState = Object.assign({}, this.state, {
       activeTab: index,
     });
 
     this.setState(newState, () => {
       if (this.props.onSelect) {
         this.props.onSelect(tabId);
       }
     });
-  },
+  }
 
   // Helpers
 
-  getTabIndex: function (tabId) {
+  getTabIndex(tabId) {
     let tabIndex = -1;
     this.state.tabs.forEach((tab, index) => {
       if (tab.id === tabId) {
         tabIndex = index;
       }
     });
     return tabIndex;
-  },
+  }
 
-  getTabId: function (index) {
+  getTabId(index) {
     return this.state.tabs[index].id;
-  },
+  }
 
-  getCurrentTabId: function () {
+  getCurrentTabId() {
     return this.state.tabs[this.state.activeTab].id;
-  },
+  }
 
   // Event Handlers
 
-  onTabChanged: function (index) {
+  onTabChanged(index) {
     this.setState({
       activeTab: index
     });
 
     if (this.props.onSelect) {
       this.props.onSelect(this.state.tabs[index].id);
     }
-  },
+  }
 
-  onAllTabsMenuClick: function (event) {
+  onAllTabsMenuClick(event) {
     let menu = new Menu();
     let target = event.target;
 
     // Generate list of menu items from the list of tabs.
     this.state.tabs.forEach((tab) => {
       menu.append(new MenuItem({
         label: tab.title,
         type: "checkbox",
@@ -209,45 +222,45 @@ let Tabbar = createClass({
     // https://bugzilla.mozilla.org/show_bug.cgi?id=1274551
     let rect = target.getBoundingClientRect();
     let screenX = target.ownerDocument.defaultView.mozInnerScreenX;
     let screenY = target.ownerDocument.defaultView.mozInnerScreenY;
     menu.popup(rect.left + screenX, rect.bottom + screenY,
       { doc: this.props.menuDocument });
 
     return menu;
-  },
+  }
 
   // Rendering
 
-  renderTab: function (tab) {
+  renderTab(tab) {
     if (typeof tab.panel === "function") {
       return tab.panel({
         key: tab.id,
         title: tab.title,
         id: tab.id,
         url: tab.url,
       });
     }
 
     return tab.panel;
-  },
+  }
 
-  render: function () {
+  render() {
     let tabs = this.state.tabs.map((tab) => this.renderTab(tab));
 
     return (
       div({className: "devtools-sidebar-tabs"},
         Tabs({
           onAllTabsMenuClick: this.onAllTabsMenuClick,
           renderOnlySelected: this.props.renderOnlySelected,
           showAllTabsMenu: this.props.showAllTabsMenu,
           tabActive: this.state.activeTab,
           onAfterChange: this.onTabChanged,
         },
           tabs
         )
       )
     );
-  },
-});
+  }
+}
 
 module.exports = Tabbar;
--- a/devtools/client/shared/components/tabs/Tabs.js
+++ b/devtools/client/shared/components/tabs/Tabs.js
@@ -3,17 +3,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/. */
 
 "use strict";
 
 define(function (require, exports, module) {
   const React = require("devtools/client/shared/vendor/react");
-  const { DOM } = React;
+  const { Component, DOM } = React;
   const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
 
   /**
    * Renders simple 'tab' widget.
    *
    * Based on ReactSimpleTabs component
    * https://github.com/pedronauck/react-simpletabs
    *
@@ -26,89 +26,99 @@ define(function (require, exports, modul
    *      <li class='tabs-menu-item'>Tab #2</li>
    *    </ul>
    *  </nav>
    *  <div class='panels'>
    *    The content of active panel here
    *  </div>
    * <div>
    */
-  let Tabs = React.createClass({
-    displayName: "Tabs",
+  class Tabs extends Component {
+    static get propTypes() {
+      return {
+        className: React.PropTypes.oneOfType([
+          React.PropTypes.array,
+          React.PropTypes.string,
+          React.PropTypes.object
+        ]),
+        tabActive: React.PropTypes.number,
+        onMount: React.PropTypes.func,
+        onBeforeChange: React.PropTypes.func,
+        onAfterChange: React.PropTypes.func,
+        children: React.PropTypes.oneOfType([
+          React.PropTypes.array,
+          React.PropTypes.element
+        ]).isRequired,
+        showAllTabsMenu: React.PropTypes.bool,
+        onAllTabsMenuClick: React.PropTypes.func,
 
-    propTypes: {
-      className: React.PropTypes.oneOfType([
-        React.PropTypes.array,
-        React.PropTypes.string,
-        React.PropTypes.object
-      ]),
-      tabActive: React.PropTypes.number,
-      onMount: React.PropTypes.func,
-      onBeforeChange: React.PropTypes.func,
-      onAfterChange: React.PropTypes.func,
-      children: React.PropTypes.oneOfType([
-        React.PropTypes.array,
-        React.PropTypes.element
-      ]).isRequired,
-      showAllTabsMenu: React.PropTypes.bool,
-      onAllTabsMenuClick: React.PropTypes.func,
+        // Set true will only render selected panel on DOM. It's complete
+        // opposite of the created array, and it's useful if panels content
+        // is unpredictable and update frequently.
+        renderOnlySelected: React.PropTypes.bool,
+      };
+    }
 
-      // Set true will only render selected panel on DOM. It's complete
-      // opposite of the created array, and it's useful if panels content
-      // is unpredictable and update frequently.
-      renderOnlySelected: React.PropTypes.bool,
-    },
-
-    getDefaultProps: function () {
+    static get defaultProps() {
       return {
         tabActive: 0,
         showAllTabsMenu: false,
         renderOnlySelected: false,
       };
-    },
+    }
 
-    getInitialState: function () {
-      return {
-        tabActive: this.props.tabActive,
+    constructor(props) {
+      super(props);
+
+      this.state = {
+        tabActive: props.tabActive,
 
         // This array is used to store an information whether a tab
         // at specific index has already been created (e.g. selected
         // at least once).
         // If yes, it's rendered even if not currently selected.
         // This is because in some cases we don't want to re-create
         // tab content when it's being unselected/selected.
         // E.g. in case of an iframe being used as a tab-content
         // we want the iframe to stay in the DOM.
         created: [],
 
         // True if tabs can't fit into available horizontal space.
         overflow: false,
       };
-    },
 
-    componentDidMount: function () {
+      this.onOverflow = this.onOverflow.bind(this);
+      this.onUnderflow = this.onUnderflow.bind(this);
+      this.onKeyDown = this.onKeyDown.bind(this);
+      this.onClickTab = this.onClickTab.bind(this);
+      this.setActive = this.setActive.bind(this);
+      this.renderMenuItems = this.renderMenuItems.bind(this);
+      this.renderPanels = this.renderPanels.bind(this);
+    }
+
+    componentDidMount() {
       let node = findDOMNode(this);
       node.addEventListener("keydown", this.onKeyDown);
 
       // Register overflow listeners to manage visibility
       // of all-tabs-menu. This menu is displayed when there
       // is not enough h-space to render all tabs.
       // It allows the user to select a tab even if it's hidden.
       if (this.props.showAllTabsMenu) {
         node.addEventListener("overflow", this.onOverflow);
         node.addEventListener("underflow", this.onUnderflow);
       }
 
       let index = this.state.tabActive;
       if (this.props.onMount) {
         this.props.onMount(index);
       }
-    },
+    }
 
-    componentWillReceiveProps: function (nextProps) {
+    componentWillReceiveProps(nextProps) {
       let { children, tabActive } = nextProps;
 
       // Check type of 'tabActive' props to see if it's valid
       // (it's 0-based index).
       if (typeof tabActive === "number") {
         let panels = children.filter((panel) => panel);
 
         // Reset to index 0 if index overflows the range of panel array
@@ -118,47 +128,47 @@ define(function (require, exports, modul
         let created = [...this.state.created];
         created[tabActive] = true;
 
         this.setState({
           created,
           tabActive,
         });
       }
-    },
+    }
 
-    componentWillUnmount: function () {
+    componentWillUnmount() {
       let node = findDOMNode(this);
       node.removeEventListener("keydown", this.onKeyDown);
 
       if (this.props.showAllTabsMenu) {
         node.removeEventListener("overflow", this.onOverflow);
         node.removeEventListener("underflow", this.onUnderflow);
       }
-    },
+    }
 
     // DOM Events
 
-    onOverflow: function (event) {
+    onOverflow(event) {
       if (event.target.classList.contains("tabs-menu")) {
         this.setState({
           overflow: true
         });
       }
-    },
+    }
 
-    onUnderflow: function (event) {
+    onUnderflow(event) {
       if (event.target.classList.contains("tabs-menu")) {
         this.setState({
           overflow: false
         });
       }
-    },
+    }
 
-    onKeyDown: function (event) {
+    onKeyDown(event) {
       // Bail out if the focus isn't on a tab.
       if (!event.target.closest(".tabs-menu-item")) {
         return;
       }
 
       let tabActive = this.state.tabActive;
       let tabCount = this.props.children.length;
 
@@ -169,29 +179,29 @@ define(function (require, exports, modul
         case "ArrowLeft":
           tabActive = Math.max(0, tabActive - 1);
           break;
       }
 
       if (this.state.tabActive != tabActive) {
         this.setActive(tabActive);
       }
-    },
+    }
 
-    onClickTab: function (index, event) {
+    onClickTab(index, event) {
       this.setActive(index);
 
       if (event) {
         event.preventDefault();
       }
-    },
+    }
 
     // API
 
-    setActive: function (index) {
+    setActive(index) {
       let onAfterChange = this.props.onAfterChange;
       let onBeforeChange = this.props.onBeforeChange;
 
       if (onBeforeChange) {
         let cancel = onBeforeChange(index);
         if (cancel) {
           return;
         }
@@ -212,21 +222,21 @@ define(function (require, exports, modul
         if (selectedTab) {
           selectedTab.focus();
         }
 
         if (onAfterChange) {
           onAfterChange(index);
         }
       });
-    },
+    }
 
     // Rendering
 
-    renderMenuItems: function () {
+    renderMenuItems() {
       if (!this.props.children) {
         throw new Error("There must be at least one Tab");
       }
 
       if (!Array.isArray(this.props.children)) {
         this.props.children = [this.props.children];
       }
 
@@ -294,19 +304,19 @@ define(function (require, exports, modul
       return (
         DOM.nav({className: "tabs-navigation"},
           DOM.ul({className: "tabs-menu", role: "tablist"},
             tabs
           ),
           allTabsMenu
         )
       );
-    },
+    }
 
-    renderPanels: function () {
+    renderPanels() {
       let { children, renderOnlySelected } = this.props;
 
       if (!children) {
         throw new Error("There must be at least one Tab");
       }
 
       if (!Array.isArray(children)) {
         children = [children];
@@ -354,45 +364,45 @@ define(function (require, exports, modul
           );
         });
 
       return (
         DOM.div({className: "panels"},
           panels
         )
       );
-    },
+    }
 
-    render: function () {
+    render() {
       return (
         DOM.div({ className: ["tabs", this.props.className].join(" ") },
           this.renderMenuItems(),
           this.renderPanels()
         )
       );
-    },
-  });
+    }
+  }
 
   /**
    * Renders simple tab 'panel'.
    */
-  let Panel = React.createClass({
-    displayName: "Panel",
+  class Panel extends Component {
+    static get propTypes() {
+      return {
+        title: React.PropTypes.string.isRequired,
+        children: React.PropTypes.oneOfType([
+          React.PropTypes.array,
+          React.PropTypes.element
+        ]).isRequired
+      };
+    }
 
-    propTypes: {
-      title: React.PropTypes.string.isRequired,
-      children: React.PropTypes.oneOfType([
-        React.PropTypes.array,
-        React.PropTypes.element
-      ]).isRequired
-    },
-
-    render: function () {
+    render() {
       return DOM.div({className: "tab-panel"},
         this.props.children
       );
     }
-  });
+  }
 
   // Exports from this module
   exports.TabPanel = Panel;
   exports.Tabs = Tabs;
 });
--- a/devtools/client/shared/components/test/mochitest/test_tabs_menu.html
+++ b/devtools/client/shared/components/test/mochitest/test_tabs_menu.html
@@ -21,18 +21,18 @@ Test all-tabs menu.
 </head>
 <body>
 <pre id="test">
 <script src="head.js" type="application/javascript"></script>
 <script type="application/javascript">
 window.onload = Task.async(function* () {
   try {
     const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
-    const React = browserRequire("devtools/client/shared/vendor/react");
-    const Tabbar = React.createFactory(browserRequire("devtools/client/shared/components/tabs/TabBar"));
+    const { Component, createFactory, DOM } = browserRequire("devtools/client/shared/vendor/react");
+    const Tabbar = createFactory(browserRequire("devtools/client/shared/components/tabs/TabBar"));
 
     // Create container for the TabBar. Set smaller width
     // to ensure that tabs won't fit and the all-tabs menu
     // needs to appear.
     const tabBarBox = document.createElement("div");
     tabBarBox.style.width = "200px";
     tabBarBox.style.height = "200px";
     tabBarBox.style.border = "1px solid lightgray";
@@ -40,22 +40,24 @@ window.onload = Task.async(function* () 
 
     // Render the tab-bar.
     const tabbar = Tabbar({
       showAllTabsMenu: true,
     });
 
     const tabbarReact = ReactDOM.render(tabbar, tabBarBox);
 
+    class TabPanelClass extends Component {
+      render() {
+        return DOM.div({}, "content");
+      }
+    }
+
     // Test panel.
-    let TabPanel = React.createFactory(React.createClass({
-      render: function () {
-        return React.DOM.div({}, "content");
-      }
-    }));
+    let TabPanel = createFactory(TabPanelClass);
 
     // Create a few panels.
     yield addTabWithPanel(1);
     yield addTabWithPanel(2);
     yield addTabWithPanel(3);
     yield addTabWithPanel(4);
     yield addTabWithPanel(5);
 
--- a/devtools/client/shared/components/tree/LabelCell.js
+++ b/devtools/client/shared/components/tree/LabelCell.js
@@ -3,36 +3,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/. */
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
-  const React = require("devtools/client/shared/vendor/react");
-
-  // Shortcuts
-  const { td, span } = React.DOM;
-  const PropTypes = React.PropTypes;
+  const { Component, DOM: dom, PropTypes } =
+    require("devtools/client/shared/vendor/react");
 
   /**
    * Render the default cell used for toggle buttons
    */
-  let LabelCell = React.createClass({
-    displayName: "LabelCell",
-
+  class LabelCell extends Component {
     // See the TreeView component for details related
     // to the 'member' object.
-    propTypes: {
-      id: PropTypes.string.isRequired,
-      member: PropTypes.object.isRequired
-    },
+    static get propTypes() {
+      return {
+        id: PropTypes.string.isRequired,
+        member: PropTypes.object.isRequired
+      };
+    }
 
-    render: function () {
+    render() {
       let id = this.props.id;
       let member = this.props.member;
       let level = member.level || 0;
 
       // Compute indentation dynamically. The deeper the item is
       // inside the hierarchy, the bigger is the left padding.
       let rowStyle = {
         "paddingInlineStart": (level * 16) + "px",
@@ -44,30 +41,30 @@ define(function (require, exports, modul
       } else if (member.hasChildren) {
         iconClassList.push("theme-twisty");
       }
       if (member.open) {
         iconClassList.push("open");
       }
 
       return (
-        td({
+        dom.td({
           className: "treeLabelCell",
           key: "default",
           style: rowStyle,
           role: "presentation"},
-          span({
+          dom.span({
             className: iconClassList.join(" "),
             role: "presentation"
           }),
-          span({
+          dom.span({
             className: "treeLabel " + member.type + "Label",
             "aria-labelledby": id,
             "data-level": level
           }, member.name)
         )
       );
     }
-  });
+  }
 
   // Exports from this module
   module.exports = LabelCell;
 });
--- a/devtools/client/shared/components/tree/TreeCell.js
+++ b/devtools/client/shared/components/tree/TreeCell.js
@@ -3,80 +3,83 @@
 /* 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";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   const React = require("devtools/client/shared/vendor/react");
-
-  // Shortcuts
+  const { Component, PropTypes } = React;
   const { input, span, td } = React.DOM;
-  const PropTypes = React.PropTypes;
 
   /**
    * This template represents a cell in TreeView row. It's rendered
    * using <td> element (the row is <tr> and the entire tree is <table>).
    */
-  let TreeCell = React.createClass({
-    displayName: "TreeCell",
-
+  class TreeCell extends Component {
     // See TreeView component for detailed property explanation.
-    propTypes: {
-      value: PropTypes.any,
-      decorator: PropTypes.object,
-      id: PropTypes.string.isRequired,
-      member: PropTypes.object.isRequired,
-      renderValue: PropTypes.func.isRequired,
-      enableInput: PropTypes.bool,
-    },
+    static get propTypes() {
+      return {
+        value: PropTypes.any,
+        decorator: PropTypes.object,
+        id: PropTypes.string.isRequired,
+        member: PropTypes.object.isRequired,
+        renderValue: PropTypes.func.isRequired,
+        enableInput: PropTypes.bool,
+      };
+    }
 
-    getInitialState: function () {
-      return {
+    constructor(props) {
+      super(props);
+
+      this.state = {
         inputEnabled: false,
       };
-    },
+
+      this.getCellClass = this.getCellClass.bind(this);
+      this.updateInputEnabled = this.updateInputEnabled.bind(this);
+    }
 
     /**
      * Optimize cell rendering. Rerender cell content only if
      * the value or expanded state changes.
      */
-    shouldComponentUpdate: function (nextProps, nextState) {
+    shouldComponentUpdate(nextProps, nextState) {
       return (this.props.value != nextProps.value) ||
         (this.state !== nextState) ||
         (this.props.member.open != nextProps.member.open);
-    },
+    }
 
-    getCellClass: function (object, id) {
+    getCellClass(object, id) {
       let decorator = this.props.decorator;
       if (!decorator || !decorator.getCellClass) {
         return [];
       }
 
       // Decorator can return a simple string or array of strings.
       let classNames = decorator.getCellClass(object, id);
       if (!classNames) {
         return [];
       }
 
       if (typeof classNames == "string") {
         classNames = [classNames];
       }
 
       return classNames;
-    },
+    }
 
-    updateInputEnabled: function (evt) {
+    updateInputEnabled(evt) {
       this.setState(Object.assign({}, this.state, {
         inputEnabled: evt.target.nodeName.toLowerCase() !== "input",
       }));
-    },
+    }
 
-    render: function () {
+    render() {
       let {
         member,
         id,
         value,
         decorator,
         renderValue,
         enableInput,
       } = this.props;
@@ -122,17 +125,17 @@ define(function (require, exports, modul
         td({
           className: classNames.join(" "),
           role: "presentation"
         },
           cellElement
         )
       );
     }
-  });
+  }
 
   // Default value rendering.
   let defaultRenderValue = props => {
     return (
       props.object + ""
     );
   };
 
--- a/devtools/client/shared/components/tree/TreeHeader.js
+++ b/devtools/client/shared/components/tree/TreeHeader.js
@@ -2,68 +2,70 @@
 /* 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";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
-  // ReactJS
   const React = require("devtools/client/shared/vendor/react");
-
-  // Shortcuts
+  const { Component, PropTypes } = React;
   const { thead, tr, td, div } = React.DOM;
-  const PropTypes = React.PropTypes;
 
   /**
    * This component is responsible for rendering tree header.
    * It's based on <thead> element.
    */
-  let TreeHeader = React.createClass({
-    displayName: "TreeHeader",
-
+  class TreeHeader extends Component {
     // See also TreeView component for detailed info about properties.
-    propTypes: {
-      // Custom tree decorator
-      decorator: PropTypes.object,
-      // True if the header should be visible
-      header: PropTypes.bool,
-      // Array with column definition
-      columns: PropTypes.array
-    },
+    static get propTypes() {
+      return {
+        // Custom tree decorator
+        decorator: PropTypes.object,
+        // True if the header should be visible
+        header: PropTypes.bool,
+        // Array with column definition
+        columns: PropTypes.array
+      };
+    }
 
-    getDefaultProps: function () {
+    static get defaultProps() {
       return {
         columns: [{
           id: "default"
         }]
       };
-    },
+    }
 
-    getHeaderClass: function (colId) {
+    constructor(props) {
+      super(props);
+      this.getHeaderClass = this.getHeaderClass.bind(this);
+    }
+
+    getHeaderClass(colId) {
       let decorator = this.props.decorator;
       if (!decorator || !decorator.getHeaderClass) {
         return [];
       }
 
       // Decorator can return a simple string or array of strings.
       let classNames = decorator.getHeaderClass(colId);
       if (!classNames) {
         return [];
       }
 
       if (typeof classNames == "string") {
         classNames = [classNames];
       }
 
       return classNames;
-    },
+    }
 
-    render: function () {
+    render() {
       let cells = [];
       let visible = this.props.header;
 
       // Render the rest of the columns (if any)
       this.props.columns.forEach(col => {
         let cellStyle = {
           "width": col.width ? col.width : "",
         };
@@ -92,13 +94,13 @@ define(function (require, exports, modul
         thead({
           role: "presentation"
         }, tr({
           className: visible ? "treeHeaderRow" : "",
           role: "presentation"
         }, cells))
       );
     }
-  });
+  }
 
   // Exports from this module
   module.exports = TreeHeader;
 });
--- a/devtools/client/shared/components/tree/TreeRow.js
+++ b/devtools/client/shared/components/tree/TreeRow.js
@@ -2,124 +2,126 @@
 /* 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";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
-  // ReactJS
   const React = require("devtools/client/shared/vendor/react");
-  const ReactDOM = require("devtools/client/shared/vendor/react-dom");
+  const { Component, createFactory, PropTypes } = React;
+  const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
+  const { tr } = React.DOM;
 
   // Tree
-  const TreeCell = React.createFactory(require("./TreeCell"));
-  const LabelCell = React.createFactory(require("./LabelCell"));
+  const TreeCell = createFactory(require("./TreeCell"));
+  const LabelCell = createFactory(require("./LabelCell"));
 
   // Scroll
   const { scrollIntoViewIfNeeded } = require("devtools/client/shared/scroll");
 
-  // Shortcuts
-  const { tr } = React.DOM;
-  const PropTypes = React.PropTypes;
-
   /**
    * This template represents a node in TreeView component. It's rendered
    * using <tr> element (the entire tree is one big <table>).
    */
-  let TreeRow = React.createClass({
-    displayName: "TreeRow",
-
+  class TreeRow extends Component {
     // See TreeView component for more details about the props and
     // the 'member' object.
-    propTypes: {
-      member: PropTypes.shape({
-        object: PropTypes.obSject,
-        name: PropTypes.sring,
-        type: PropTypes.string.isRequired,
-        rowClass: PropTypes.string.isRequired,
-        level: PropTypes.number.isRequired,
-        hasChildren: PropTypes.bool,
-        value: PropTypes.any,
-        open: PropTypes.bool.isRequired,
-        path: PropTypes.string.isRequired,
-        hidden: PropTypes.bool,
-        selected: PropTypes.bool,
-      }),
-      decorator: PropTypes.object,
-      renderCell: PropTypes.object,
-      renderLabelCell: PropTypes.object,
-      columns: PropTypes.array.isRequired,
-      id: PropTypes.string.isRequired,
-      provider: PropTypes.object.isRequired,
-      onClick: PropTypes.func.isRequired,
-      onMouseOver: PropTypes.func,
-      onMouseOut: PropTypes.func
-    },
+    static get propTypes() {
+      return {
+        member: PropTypes.shape({
+          object: PropTypes.obSject,
+          name: PropTypes.sring,
+          type: PropTypes.string.isRequired,
+          rowClass: PropTypes.string.isRequired,
+          level: PropTypes.number.isRequired,
+          hasChildren: PropTypes.bool,
+          value: PropTypes.any,
+          open: PropTypes.bool.isRequired,
+          path: PropTypes.string.isRequired,
+          hidden: PropTypes.bool,
+          selected: PropTypes.bool,
+        }),
+        decorator: PropTypes.object,
+        renderCell: PropTypes.object,
+        renderLabelCell: PropTypes.object,
+        columns: PropTypes.array.isRequired,
+        id: PropTypes.string.isRequired,
+        provider: PropTypes.object.isRequired,
+        onClick: PropTypes.func.isRequired,
+        onMouseOver: PropTypes.func,
+        onMouseOut: PropTypes.func
+      };
+    }
+
+    constructor(props) {
+      super(props);
+      this.getRowClass = this.getRowClass.bind(this);
+    }
 
     componentWillReceiveProps(nextProps) {
       // I don't like accessing the underlying DOM elements directly,
       // but this optimization makes the filtering so damn fast!
       // The row doesn't have to be re-rendered, all we really need
       // to do is toggling a class name.
       // The important part is that DOM elements don't need to be
       // re-created when they should appear again.
       if (nextProps.member.hidden != this.props.member.hidden) {
-        let row = ReactDOM.findDOMNode(this);
+        let row = findDOMNode(this);
         row.classList.toggle("hidden");
       }
-    },
+    }
 
     /**
      * Optimize row rendering. If props are the same do not render.
      * This makes the rendering a lot faster!
      */
-    shouldComponentUpdate: function (nextProps) {
+    shouldComponentUpdate(nextProps) {
       let props = ["name", "open", "value", "loading", "selected", "hasChildren"];
       for (let p in props) {
         if (nextProps.member[props[p]] != this.props.member[props[p]]) {
           return true;
         }
       }
 
       return false;
-    },
+    }
 
-    componentDidUpdate: function () {
+    componentDidUpdate() {
       if (this.props.member.selected) {
-        let row = ReactDOM.findDOMNode(this);
+        let row = findDOMNode(this);
         // Because this is called asynchronously, context window might be
         // already gone.
         if (row.ownerDocument.defaultView) {
           scrollIntoViewIfNeeded(row);
         }
       }
-    },
+    }
 
-    getRowClass: function (object) {
+    getRowClass(object) {
       let decorator = this.props.decorator;
       if (!decorator || !decorator.getRowClass) {
         return [];
       }
 
       // Decorator can return a simple string or array of strings.
       let classNames = decorator.getRowClass(object);
       if (!classNames) {
         return [];
       }
 
       if (typeof classNames == "string") {
         classNames = [classNames];
       }
 
       return classNames;
-    },
+    }
 
-    render: function () {
+    render() {
       let member = this.props.member;
       let decorator = this.props.decorator;
       let props = {
         id: this.props.id,
         role: "treeitem",
         "aria-level": member.level,
         "aria-selected": !!member.selected,
         onClick: this.props.onClick,
@@ -193,17 +195,17 @@ define(function (require, exports, modul
         }
       });
 
       // Render tree row
       return (
         tr(props, cells)
       );
     }
-  });
+  }
 
   // Helpers
 
   let RenderCell = props => {
     return TreeCell(props);
   };
 
   let RenderLabelCell = props => {
--- a/devtools/client/shared/components/tree/TreeView.js
+++ b/devtools/client/shared/components/tree/TreeView.js
@@ -2,27 +2,32 @@
 /* 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";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
-  // ReactJS
-  const React = require("devtools/client/shared/vendor/react");
+  const { cloneElement, Component, createFactory, DOM: dom, PropTypes } =
+    require("devtools/client/shared/vendor/react");
 
   // Reps
   const { ObjectProvider } = require("./ObjectProvider");
-  const TreeRow = React.createFactory(require("./TreeRow"));
-  const TreeHeader = React.createFactory(require("./TreeHeader"));
+  const TreeRow = createFactory(require("./TreeRow"));
+  const TreeHeader = createFactory(require("./TreeHeader"));
 
-  // Shortcuts
-  const DOM = React.DOM;
-  const PropTypes = React.PropTypes;
+  const defaultProps = {
+    object: null,
+    renderRow: null,
+    provider: ObjectProvider,
+    expandedNodes: new Set(),
+    expandableStrings: true,
+    columns: []
+  };
 
   /**
    * This component represents a tree view with expandable/collapsible nodes.
    * The tree is rendered using <table> element where every node is represented
    * by <tr> element. The tree is one big table where nodes (rows) are properly
    * indented from the left to mimic hierarchical structure of the data.
    *
    * The tree can have arbitrary number of columns and so, might be use
@@ -48,137 +53,192 @@ define(function (require, exports, modul
    *   getCellClass: function(object, colId);
    *   getHeaderClass: function(colId);
    *   renderValue: function(object, colId);
    *   renderRow: function(object);
    *   renderCell: function(object, colId);
    *   renderLabelCell: function(object);
    * }
    */
-  let TreeView = React.createClass({
-    displayName: "TreeView",
-
+  class TreeView extends Component {
     // The only required property (not set by default) is the input data
     // object that is used to puputate the tree.
-    propTypes: {
-      // The input data object.
-      object: PropTypes.any,
-      className: PropTypes.string,
-      label: PropTypes.string,
-      // Data provider (see also the interface above)
-      provider: PropTypes.shape({
-        getChildren: PropTypes.func,
-        hasChildren: PropTypes.func,
-        getLabel: PropTypes.func,
-        getValue: PropTypes.func,
-        getKey: PropTypes.func,
-        getType: PropTypes.func,
-      }).isRequired,
-      // Tree decorator (see also the interface above)
-      decorator: PropTypes.shape({
-        getRowClass: PropTypes.func,
-        getCellClass: PropTypes.func,
-        getHeaderClass: PropTypes.func,
+    static get propTypes() {
+      return {
+        // The input data object.
+        object: PropTypes.any,
+        className: PropTypes.string,
+        label: PropTypes.string,
+        // Data provider (see also the interface above)
+        provider: PropTypes.shape({
+          getChildren: PropTypes.func,
+          hasChildren: PropTypes.func,
+          getLabel: PropTypes.func,
+          getValue: PropTypes.func,
+          getKey: PropTypes.func,
+          getType: PropTypes.func,
+        }).isRequired,
+        // Tree decorator (see also the interface above)
+        decorator: PropTypes.shape({
+          getRowClass: PropTypes.func,
+          getCellClass: PropTypes.func,
+          getHeaderClass: PropTypes.func,
+          renderValue: PropTypes.func,
+          renderRow: PropTypes.func,
+          renderCell: PropTypes.func,
+          renderLabelCell: PropTypes.func,
+        }),
+        // Custom tree row (node) renderer
+        renderRow: PropTypes.func,
+        // Custom cell renderer
+        renderCell: PropTypes.func,
+        // Custom value renderef
         renderValue: PropTypes.func,
-        renderRow: PropTypes.func,
-        renderCell: PropTypes.func,
+        // Custom tree label (including a toggle button) renderer
         renderLabelCell: PropTypes.func,
-      }),
-      // Custom tree row (node) renderer
-      renderRow: PropTypes.func,
-      // Custom cell renderer
-      renderCell: PropTypes.func,
-      // Custom value renderef
-      renderValue: PropTypes.func,
-      // Custom tree label (including a toggle button) renderer
-      renderLabelCell: PropTypes.func,
-      // Set of expanded nodes
-      expandedNodes: PropTypes.object,
-      // Custom filtering callback
-      onFilter: PropTypes.func,
-      // Custom sorting callback
-      onSort: PropTypes.func,
-      // A header is displayed if set to true
-      header: PropTypes.bool,
-      // Long string is expandable by a toggle button
-      expandableStrings: PropTypes.bool,
-      // Array of columns
-      columns: PropTypes.arrayOf(PropTypes.shape({
-        id: PropTypes.string.isRequired,
-        title: PropTypes.string,
-        width: PropTypes.string
-      }))
-    },
+        // Set of expanded nodes
+        expandedNodes: PropTypes.object,
+        // Custom filtering callback
+        onFilter: PropTypes.func,
+        // Custom sorting callback
+        onSort: PropTypes.func,
+        // A header is displayed if set to true
+        header: PropTypes.bool,
+        // Long string is expandable by a toggle button
+        expandableStrings: PropTypes.bool,
+        // Array of columns
+        columns: PropTypes.arrayOf(PropTypes.shape({
+          id: PropTypes.string.isRequired,
+          title: PropTypes.string,
+          width: PropTypes.string
+        }))
+      };
+    }
 
-    getDefaultProps: function () {
-      return {
-        object: null,
-        renderRow: null,
-        provider: ObjectProvider,
-        expandedNodes: new Set(),
-        expandableStrings: true,
-        columns: []
-      };
-    },
+    static get defaultProps() {
+      return defaultProps;
+    }
 
-    getInitialState: function () {
-      return {
-        expandedNodes: this.props.expandedNodes,
-        columns: ensureDefaultColumn(this.props.columns),
+    constructor(props) {
+      super(props);
+
+      this.state = {
+        expandedNodes: props.expandedNodes,
+        columns: ensureDefaultColumn(props.columns),
         selected: null
       };
-    },
 
-    componentWillReceiveProps: function (nextProps) {
+      this.toggle = this.toggle.bind(this);
+      this.isExpanded = this.isExpanded.bind(this);
+      this.onKeyDown = this.onKeyDown.bind(this);
+      this.onKeyUp = this.onKeyUp.bind(this);
+      this.onClickRow = this.onClickRow.bind(this);
+      this.getSelectedRow = this.getSelectedRow.bind(this);
+      this.selectRow = this.selectRow.bind(this);
+      this.isSelected = this.isSelected.bind(this);
+      this.onFilter = this.onFilter.bind(this);
+      this.onSort = this.onSort.bind(this);
+      this.getMembers = this.getMembers.bind(this);
+      this.renderRows = this.renderRows.bind(this);
+    }
+
+    componentWillReceiveProps(nextProps) {
       let { expandedNodes } = nextProps;
       this.setState(Object.assign({}, this.state, {
         expandedNodes,
       }));
-    },
+    }
 
-    componentDidUpdate: function () {
+    componentDidUpdate() {
       let selected = this.getSelectedRow(this.rows);
       if (!selected && this.rows.length > 0) {
         // TODO: Do better than just selecting the first row again. We want to
         // select (in order) previous, next or parent in case when selected
         // row is removed.
         this.selectRow(this.rows[0].props.member.path);
       }
-    },
+    }
+
+    static subPath(path, subKey) {
+      return path + "/" + String(subKey).replace(/[\\/]/g, "\\$&");
+    }
+
+    /**
+     * Creates a set with the paths of the nodes that should be expanded by default
+     * according to the passed options.
+     * @param {Object} The root node of the tree.
+     * @param {Object} [optional] An object with the following optional parameters:
+     *   - maxLevel: nodes nested deeper than this level won't be expanded.
+     *   - maxNodes: maximum number of nodes that can be expanded. The traversal is
+           breadth-first, so expanding nodes nearer to the root will be preferred.
+           Sibling nodes will either be all expanded or none expanded.
+     * }
+     */
+    static getExpandedNodes(rootObj, { maxLevel = Infinity, maxNodes = Infinity } = {}) {
+      let expandedNodes = new Set();
+      let queue = [{
+        object: rootObj,
+        level: 1,
+        path: ""
+      }];
+      while (queue.length) {
+        let {object, level, path} = queue.shift();
+        if (Object(object) !== object) {
+          continue;
+        }
+        let keys = Object.keys(object);
+        if (expandedNodes.size + keys.length > maxNodes) {
+          // Avoid having children half expanded.
+          break;
+        }
+        for (let key of keys) {
+          let nodePath = TreeView.subPath(path, key);
+          expandedNodes.add(nodePath);
+          if (level < maxLevel) {
+            queue.push({
+              object: object[key],
+              level: level + 1,
+              path: nodePath
+            });
+          }
+        }
+      }
+      return expandedNodes;
+    }
 
     // Node expand/collapse
 
-    toggle: function (nodePath) {
+    toggle(nodePath) {
       let nodes = this.state.expandedNodes;
       if (this.isExpanded(nodePath)) {
         nodes.delete(nodePath);
       } else {
         nodes.add(nodePath);
       }
 
       // Compute new state and update the tree.
       this.setState(Object.assign({}, this.state, {
         expandedNodes: nodes
       }));
-    },
+    }
 
-    isExpanded: function (nodePath) {
+    isExpanded(nodePath) {
       return this.state.expandedNodes.has(nodePath);
-    },
+    }
 
     // Event Handlers
 
-    onKeyDown: function (event) {
+    onKeyDown(event) {
       if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(
         event.key)) {
         event.preventDefault();
       }
-    },
+    }
 
-    onKeyUp: function (event) {
+    onKeyUp(event) {
       let row = this.getSelectedRow(this.rows);
       if (!row) {
         return;
       }
 
       let index = this.rows.indexOf(row);
       switch (event.key) {
         case "ArrowRight":
@@ -204,67 +264,67 @@ define(function (require, exports, modul
             this.selectRow(previousRow.props.member.path);
           }
           break;
         default:
           return;
       }
 
       event.preventDefault();
-    },
+    }
 
-    onClickRow: function (nodePath, event) {
+    onClickRow(nodePath, event) {
       event.stopPropagation();
       let cell = event.target.closest("td");
       if (cell && cell.classList.contains("treeLabelCell")) {
         this.toggle(nodePath);
       }
       this.selectRow(nodePath);
-    },
+    }
 
-    getSelectedRow: function (rows) {
+    getSelectedRow(rows) {
       if (!this.state.selected || rows.length === 0) {
         return null;
       }
       return rows.find(row => this.isSelected(row.props.member.path));
-    },
+    }
 
-    selectRow: function (nodePath) {
+    selectRow(nodePath) {
       this.setState(Object.assign({}, this.state, {
         selected: nodePath
       }));
-    },
+    }
 
-    isSelected: function (nodePath) {
+    isSelected(nodePath) {
       return nodePath === this.state.selected;
-    },
+    }
 
     // Filtering & Sorting
 
     /**
      * Filter out nodes that don't correspond to the current filter.
      * @return {Boolean} true if the node should be visible otherwise false.
      */
-    onFilter: function (object) {
+    onFilter(object) {
       let onFilter = this.props.onFilter;
       return onFilter ? onFilter(object) : true;
-    },
+    }
 
-    onSort: function (parent, children) {
+    onSort(parent, children) {
       let onSort = this.props.onSort;
       return onSort ? onSort(parent, children) : children;
-    },
+    }
 
     // Members
 
     /**
      * Return children node objects (so called 'members') for given
      * parent object.
      */
-    getMembers: function (parent, level, path) {
+    getMembers(parent, level, path) {
       // Strings don't have children. Note that 'long' strings are using
       // the expander icon (+/-) to display the entire original value,
       // but there are no child items.
       if (typeof parent == "string") {
         return [];
       }
 
       let { expandableStrings, provider } = this.props;
@@ -315,22 +375,22 @@ define(function (require, exports, modul
           // Node path
           path: nodePath,
           // True if the node is hidden (used for filtering)
           hidden: !this.onFilter(child),
           // True if the node is selected with keyboard
           selected: this.isSelected(nodePath)
         };
       });
-    },
+    }
 
     /**
      * Render tree rows/nodes.
      */
-    renderRows: function (parent, level = 0, path = "") {
+    renderRows(parent, level = 0, path = "") {
       let rows = [];
       let decorator = this.props.decorator;
       let renderRow = this.props.renderRow || TreeRow;
 
       // Get children for given parent node, iterate over them and render
       // a row for every one. Use row template (a component) from properties.
       // If the return value is non-array, the children are being loaded
       // asynchronously.
@@ -362,27 +422,27 @@ define(function (require, exports, modul
             member.path);
 
           // If children needs to be asynchronously fetched first,
           // set 'loading' property to the parent row. Otherwise
           // just append children rows to the array of all rows.
           if (!Array.isArray(childRows)) {
             let lastIndex = rows.length - 1;
             props.member.loading = true;
-            rows[lastIndex] = React.cloneElement(rows[lastIndex], props);
+            rows[lastIndex] = cloneElement(rows[lastIndex], props);
           } else {
             rows = rows.concat(childRows);
           }
         }
       });
 
       return rows;
-    },
+    }
 
-    render: function () {
+    render() {
       let root = this.props.object;
       let classNames = ["treeTable"];
       this.rows = [];
 
       // Use custom class name from props.
       let className = this.props.className;
       if (className) {
         classNames.push(...className.split(" "));
@@ -398,83 +458,34 @@ define(function (require, exports, modul
         rows = [];
       }
 
       let props = Object.assign({}, this.props, {
         columns: this.state.columns
       });
 
       return (
-        DOM.table({
+        dom.table({
           className: classNames.join(" "),
           role: "tree",
           tabIndex: 0,
           onKeyDown: this.onKeyDown,
           onKeyUp: this.onKeyUp,
           "aria-label": this.props.label || "",
           "aria-activedescendant": this.state.selected,
           cellPadding: 0,
           cellSpacing: 0},
           TreeHeader(props),
-          DOM.tbody({
+          dom.tbody({
             role: "presentation"
           }, rows)
         )
       );
     }
-  });
-
-  TreeView.subPath = function (path, subKey) {
-    return path + "/" + String(subKey).replace(/[\\/]/g, "\\$&");
-  };
-
-  /**
-   * Creates a set with the paths of the nodes that should be expanded by default
-   * according to the passed options.
-   * @param {Object} The root node of the tree.
-   * @param {Object} [optional] An object with the following optional parameters:
-   *   - maxLevel: nodes nested deeper than this level won't be expanded.
-   *   - maxNodes: maximum number of nodes that can be expanded. The traversal is
-         breadth-first, so expanding nodes nearer to the root will be preferred.
-         Sibling nodes will either be all expanded or none expanded.
-   * }
-   */
-  TreeView.getExpandedNodes = function (rootObj,
-    { maxLevel = Infinity, maxNodes = Infinity } = {}
-  ) {
-    let expandedNodes = new Set();
-    let queue = [{
-      object: rootObj,
-      level: 1,
-      path: ""
-    }];
-    while (queue.length) {
-      let {object, level, path} = queue.shift();
-      if (Object(object) !== object) {
-        continue;
-      }
-      let keys = Object.keys(object);
-      if (expandedNodes.size + keys.length > maxNodes) {
-        // Avoid having children half expanded.
-        break;
-      }
-      for (let key of keys) {
-        let nodePath = TreeView.subPath(path, key);
-        expandedNodes.add(nodePath);
-        if (level < maxLevel) {
-          queue.push({
-            object: object[key],
-            level: level + 1,
-            path: nodePath
-          });
-        }
-      }
-    }
-    return expandedNodes;
-  };
+  }
 
   // Helpers
 
   /**
    * There should always be at least one column (the one with toggle buttons)
    * and this function ensures that it's true.
    */
   function ensureDefaultColumn(columns) {
--- a/devtools/server/actors/highlighters/css-grid.js
+++ b/devtools/server/actors/highlighters/css-grid.js
@@ -8,21 +8,21 @@ const Services = require("Services");
 const { AutoRefreshHighlighter } = require("./auto-refresh");
 const {
   CANVAS_SIZE,
   drawBubbleRect,
   drawLine,
   drawRect,
   drawRoundedRect,
   getBoundsFromPoints,
-  getCanvasPosition,
   getCurrentMatrix,
   getPathDescriptionFromPoints,
   getPointsFromDiagonal,
   updateCanvasElement,
+  updateCanvasPosition,
 } = require("./utils/canvas");
 const {
   CanvasFrameAnonymousContentHelper,
   createNode,
   createSVGNode,
   moveInfobar,
 } = require("./utils/markup");
 const { apply } = require("devtools/shared/layout/dom-matrix-2d");
@@ -171,22 +171,20 @@ class CssGridHighlighter extends AutoRef
     pageListenerTarget.addEventListener("pagehide", this.onPageHide);
 
     // Initialize the <canvas> position to the top left corner of the page
     this._canvasPosition = {
       x: 0,
       y: 0
     };
 
-    // Calling `getCanvasPosition` anyway since the highlighter could be initialized
+    // Calling `updateCanvasPosition` anyway since the highlighter could be initialized
     // on a page that has scrolled already.
-    let { canvasX, canvasY } = getCanvasPosition(this._canvasPosition, this._scroll,
-      this.win, this._winDimensions);
-    this._canvasPosition.x = canvasX;
-    this._canvasPosition.y = canvasY;
+    updateCanvasPosition(this._canvasPosition, this._scroll, this.win,
+      this._winDimensions);
   }
 
   _buildMarkup() {
     let container = createNode(this.win, {
       attributes: {
         "class": "highlighter-container"
       }
     });
@@ -767,17 +765,17 @@ class CssGridHighlighter extends AutoRef
       getBoundsFromPoints([{x, y}, {x, y}, {x, y}, {x, y}]), this.win);
   }
 
   /**
    * The <canvas>'s position needs to be updated if the page scrolls too much, in order
    * to give the illusion that it always covers the viewport.
    */
   _scrollUpdate() {
-    let { hasUpdated } = getCanvasPosition(this._canvasPosition, this._scroll, this.win,
+    let hasUpdated = updateCanvasPosition(this._canvasPosition, this._scroll, this.win,
       this._winDimensions);
 
     if (hasUpdated) {
       this._update();
     }
   }
 
   getFirstRowLinePos(fragment) {
--- a/devtools/server/actors/highlighters/flexbox.js
+++ b/devtools/server/actors/highlighters/flexbox.js
@@ -2,19 +2,19 @@
  * 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 { AutoRefreshHighlighter } = require("./auto-refresh");
 const {
   CANVAS_SIZE,
-  getCanvasPosition,
   getCurrentMatrix,
   updateCanvasElement,
+  updateCanvasPosition,
 } = require("./utils/canvas");
 const {
   CanvasFrameAnonymousContentHelper,
   createNode,
 } = require("./utils/markup");
 const {
   setIgnoreLayoutChanges,
 } = require("devtools/shared/layout/utils");
@@ -37,22 +37,20 @@ class FlexboxHighlighter extends AutoRef
     pageListenerTarget.addEventListener("pagehide", this.onPageHide);
 
     // Initialize the <canvas> position to the top left corner of the page
     this._canvasPosition = {
       x: 0,
       y: 0
     };
 
-    // Calling `getCanvasPosition` anyway since the highlighter could be initialized
+    // Calling `updateCanvasPosition` anyway since the highlighter could be initialized
     // on a page that has scrolled already.
-    let { canvasX, canvasY } = getCanvasPosition(this._canvasPosition, this._scroll,
-      this.win, this._winDimensions);
-    this._canvasPosition.x = canvasX;
-    this._canvasPosition.y = canvasY;
+    updateCanvasPosition(this._canvasPosition, this._scroll, this.win,
+      this._winDimensions);
   }
 
   _buildMarkup() {
     let container = createNode(this.win, {
       attributes: {
         "class": "highlighter-container"
       }
     });
@@ -121,17 +119,17 @@ class FlexboxHighlighter extends AutoRef
     this.getElement("canvas").setAttribute("hidden", "true");
   }
 
   /**
    * The <canvas>'s position needs to be updated if the page scrolls too much, in order
    * to give the illusion that it always covers the viewport.
    */
   _scrollUpdate() {
-    let { hasUpdated } = getCanvasPosition(this._canvasPosition, this._scroll, this.win,
+    let hasUpdated = updateCanvasPosition(this._canvasPosition, this._scroll, this.win,
       this._winDimensions);
 
     if (hasUpdated) {
       this._update();
     }
   }
 
   _show() {
--- a/devtools/server/actors/highlighters/utils/canvas.js
+++ b/devtools/server/actors/highlighters/utils/canvas.js
@@ -224,89 +224,16 @@ function getBoundsFromPoints(points) {
   bounds.y = bounds.top;
   bounds.width = bounds.right - bounds.left;
   bounds.height = bounds.bottom - bounds.top;
 
   return bounds;
 }
 
 /**
- * Calculates and returns the <canvas>'s position in accordance with the page's scroll,
- * document's size, canvas size, and viewport's size. This is called when a page's scroll
- * is detected.
- *
- * @param  {Object} canvasPosition
- *         A pointer object {x, y} representing the <canvas> position to the top left
- *         corner of the page.
- * @param  {Object} scrollPosition
- *         A pointer object {x, y} representing the window's pageXOffset and pageYOffset.
- * @param  {Window} window
- *         The window object.
- * @param  {Object} windowDimensions
- *         An object {width, height} representing the window's dimensions for the
- *         `window` given.
- * @return {Object} An object with the following properties:
- *         - {Boolean} hasUpdated
- *           true if the <canvas> position was updated and false otherwise.
- *         - {Number} canvasX
- *           The canvas' x position.
- *         - {Number} canvasY
- *           The canvas' y position.
- */
-function getCanvasPosition(canvasPosition, scrollPosition, window, windowDimensions) {
-  let { x: canvasX, y: canvasY } = canvasPosition;
-  let { x: scrollX, y: scrollY } = scrollPosition;
-  let cssCanvasSize = CANVAS_SIZE / window.devicePixelRatio;
-  let viewportSize = getViewportDimensions(window);
-  let { height, width } = windowDimensions;
-  let canvasWidth = cssCanvasSize;
-  let canvasHeight = cssCanvasSize;
-  let hasUpdated = false;
-
-  // Those values indicates the relative horizontal and vertical space the page can
-  // scroll before we have to reposition the <canvas>; they're 1/4 of the delta between
-  // the canvas' size and the viewport's size: that's because we want to consider both
-  // sides (top/bottom, left/right; so 1/2 for each side) and also we don't want to
-  // shown the edges of the canvas in case of fast scrolling (to avoid showing undraw
-  // areas, therefore another 1/2 here).
-  let bufferSizeX = (canvasWidth - viewportSize.width) >> 2;
-  let bufferSizeY = (canvasHeight - viewportSize.height) >> 2;
-
-  // Defines the boundaries for the canvas.
-  let leftBoundary = 0;
-  let rightBoundary = width - canvasWidth;
-  let topBoundary = 0;
-  let bottomBoundary = height - canvasHeight;
-
-  // Defines the thresholds that triggers the canvas' position to be updated.
-  let leftThreshold = scrollX - bufferSizeX;
-  let rightThreshold = scrollX - canvasWidth + viewportSize.width + bufferSizeX;
-  let topThreshold = scrollY - bufferSizeY;
-  let bottomThreshold = scrollY - canvasHeight + viewportSize.height + bufferSizeY;
-
-  if (canvasX < rightBoundary && canvasX < rightThreshold) {
-    canvasX = Math.min(leftThreshold, rightBoundary);
-    hasUpdated = true;
-  } else if (canvasX > leftBoundary && canvasX > leftThreshold) {
-    canvasX = Math.max(rightThreshold, leftBoundary);
-    hasUpdated = true;
-  }
-
-  if (canvasY < bottomBoundary && canvasY < bottomThreshold) {
-    canvasY = Math.min(topThreshold, bottomBoundary);
-    hasUpdated = true;
-  } else if (canvasY > topBoundary && canvasY > topThreshold) {
-    canvasY = Math.max(bottomThreshold, topBoundary);
-    hasUpdated = true;
-  }
-
-  return { canvasX, canvasY, hasUpdated };
-}
-
-/**
  * Returns the current matrices for both canvas drawing and SVG taking into account the
  * following transformations, in this order:
  *   1. The scale given by the display pixel ratio.
  *   2. The translation to the top left corner of the element.
  *   3. The scale given by the current zoom.
  *   4. The translation given by the top and left padding of the element.
  *   5. Any CSS transformation applied directly to the element (only 2D
  *      transformation; the 3D transformation are flattened, see `dom-matrix-2d` module
@@ -422,19 +349,90 @@ function updateCanvasElement(canvas, can
 
   // Resize the canvas taking the dpr into account so as to have crisp lines, and
   // translating it to give the perception that it always covers the viewport.
   canvas.setAttribute("style",
     `width: ${size}px; height: ${size}px; transform: translate(${x}px, ${y}px);`);
   canvas.getCanvasContext("2d").clearRect(0, 0, CANVAS_SIZE, CANVAS_SIZE);
 }
 
+/**
+ * Calculates and returns the <canvas>'s position in accordance with the page's scroll,
+ * document's size, canvas size, and viewport's size. This is called when a page's scroll
+ * is detected.
+ *
+ * @param  {Object} canvasPosition
+ *         A pointer object {x, y} representing the <canvas> position to the top left
+ *         corner of the page.
+ * @param  {Object} scrollPosition
+ *         A pointer object {x, y} representing the window's pageXOffset and pageYOffset.
+ * @param  {Window} window
+ *         The window object.
+ * @param  {Object} windowDimensions
+ *         An object {width, height} representing the window's dimensions for the
+ *         `window` given.
+ * @return {Boolean} true if the <canvas> position was updated and false otherwise.
+ */
+function updateCanvasPosition(canvasPosition, scrollPosition, window, windowDimensions) {
+  let { x: canvasX, y: canvasY } = canvasPosition;
+  let { x: scrollX, y: scrollY } = scrollPosition;
+  let cssCanvasSize = CANVAS_SIZE / window.devicePixelRatio;
+  let viewportSize = getViewportDimensions(window);
+  let { height, width } = windowDimensions;
+  let canvasWidth = cssCanvasSize;
+  let canvasHeight = cssCanvasSize;
+  let hasUpdated = false;
+
+  // Those values indicates the relative horizontal and vertical space the page can
+  // scroll before we have to reposition the <canvas>; they're 1/4 of the delta between
+  // the canvas' size and the viewport's size: that's because we want to consider both
+  // sides (top/bottom, left/right; so 1/2 for each side) and also we don't want to
+  // shown the edges of the canvas in case of fast scrolling (to avoid showing undraw
+  // areas, therefore another 1/2 here).
+  let bufferSizeX = (canvasWidth - viewportSize.width) >> 2;
+  let bufferSizeY = (canvasHeight - viewportSize.height) >> 2;
+
+  // Defines the boundaries for the canvas.
+  let leftBoundary = 0;
+  let rightBoundary = width - canvasWidth;
+  let topBoundary = 0;
+  let bottomBoundary = height - canvasHeight;
+
+  // Defines the thresholds that triggers the canvas' position to be updated.
+  let leftThreshold = scrollX - bufferSizeX;
+  let rightThreshold = scrollX - canvasWidth + viewportSize.width + bufferSizeX;
+  let topThreshold = scrollY - bufferSizeY;
+  let bottomThreshold = scrollY - canvasHeight + viewportSize.height + bufferSizeY;
+
+  if (canvasX < rightBoundary && canvasX < rightThreshold) {
+    canvasX = Math.min(leftThreshold, rightBoundary);
+    hasUpdated = true;
+  } else if (canvasX > leftBoundary && canvasX > leftThreshold) {
+    canvasX = Math.max(rightThreshold, leftBoundary);
+    hasUpdated = true;
+  }
+
+  if (canvasY < bottomBoundary && canvasY < bottomThreshold) {
+    canvasY = Math.min(topThreshold, bottomBoundary);
+    hasUpdated = true;
+  } else if (canvasY > topBoundary && canvasY > topThreshold) {
+    canvasY = Math.max(bottomThreshold, topBoundary);
+    hasUpdated = true;
+  }
+
+  // Update the canvas position with the calculated canvasX and canvasY positions.
+  canvasPosition.x = canvasX;
+  canvasPosition.y = canvasY;
+
+  return hasUpdated;
+}
+
 exports.CANVAS_SIZE = CANVAS_SIZE;
 exports.drawBubbleRect = drawBubbleRect;
 exports.drawLine = drawLine;
 exports.drawRect = drawRect;
 exports.drawRoundedRect = drawRoundedRect;
 exports.getBoundsFromPoints = getBoundsFromPoints;
-exports.getCanvasPosition = getCanvasPosition;
 exports.getCurrentMatrix = getCurrentMatrix;
 exports.getPathDescriptionFromPoints = getPathDescriptionFromPoints;
 exports.getPointsFromDiagonal = getPointsFromDiagonal;
 exports.updateCanvasElement = updateCanvasElement;
+exports.updateCanvasPosition = updateCanvasPosition;
--- a/dom/media/CanvasCaptureMediaStream.cpp
+++ b/dom/media/CanvasCaptureMediaStream.cpp
@@ -65,16 +65,27 @@ public:
       mSourceStream->AppendToTrack(mTrackId, &segment);
     }
 
     if (mEnded) {
       mSourceStream->EndAllTrackAndFinish();
     }
   }
 
+  void NotifyEvent(MediaStreamGraph* aGraph, MediaStreamGraphEvent aEvent) override
+  {
+    if (aEvent == MediaStreamGraphEvent::EVENT_REMOVED) {
+      EndStream();
+      mSourceStream->EndAllTrackAndFinish();
+
+      MutexAutoLock lock(mMutex);
+      mImage = nullptr;
+    }
+  }
+
 protected:
   ~StreamListener() { }
 
 private:
   Atomic<bool> mEnded;
   const RefPtr<SourceMediaStream> mSourceStream;
   const TrackID mTrackId;
   const PrincipalHandle mPrincipalHandle;
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -5,33 +5,36 @@
 
 #include "DOMMediaStream.h"
 
 #include "AudioCaptureStream.h"
 #include "AudioChannelAgent.h"
 #include "AudioStreamTrack.h"
 #include "Layers.h"
 #include "MediaStreamGraph.h"
+#include "MediaStreamGraphImpl.h"
 #include "MediaStreamListener.h"
 #include "VideoStreamTrack.h"
 #include "mozilla/dom/AudioNode.h"
 #include "mozilla/dom/AudioTrack.h"
 #include "mozilla/dom/AudioTrackList.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "mozilla/dom/LocalMediaStreamBinding.h"
 #include "mozilla/dom/MediaStreamBinding.h"
 #include "mozilla/dom/MediaStreamTrackEvent.h"
+#include "mozilla/dom/Promise.h"
 #include "mozilla/dom/VideoTrack.h"
 #include "mozilla/dom/VideoTrackList.h"
 #include "mozilla/media/MediaUtils.h"
 #include "nsContentUtils.h"
 #include "nsIScriptError.h"
 #include "nsIUUIDGenerator.h"
 #include "nsPIDOMWindow.h"
+#include "nsProxyRelease.h"
 #include "nsRFPService.h"
 #include "nsServiceManagerUtils.h"
 
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
 // GetTickCount() and conflicts with NS_DECL_NSIDOMMEDIASTREAM, containing
 // currentTime getter.
 #ifdef GetCurrentTime
 #undef GetCurrentTime
@@ -333,19 +336,18 @@ private:
 };
 
 class DOMMediaStream::PlaybackTrackListener : public MediaStreamTrackConsumer
 {
 public:
   explicit PlaybackTrackListener(DOMMediaStream* aStream) :
     mStream(aStream) {}
 
-  NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PlaybackTrackListener,
-                                           MediaStreamTrackConsumer)
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PlaybackTrackListener)
+  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(PlaybackTrackListener)
 
   void NotifyEnded(MediaStreamTrack* aTrack) override
   {
     if (!mStream) {
       MOZ_ASSERT(false);
       return;
     }
 
@@ -359,25 +361,19 @@ public:
   }
 
 protected:
   virtual ~PlaybackTrackListener() {}
 
   RefPtr<DOMMediaStream> mStream;
 };
 
-NS_IMPL_ADDREF_INHERITED(DOMMediaStream::PlaybackTrackListener,
-                         MediaStreamTrackConsumer)
-NS_IMPL_RELEASE_INHERITED(DOMMediaStream::PlaybackTrackListener,
-                          MediaStreamTrackConsumer)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMMediaStream::PlaybackTrackListener)
-NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackConsumer)
-NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMMediaStream::PlaybackTrackListener,
-                                   MediaStreamTrackConsumer,
-                                   mStream)
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMMediaStream::PlaybackTrackListener, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMMediaStream::PlaybackTrackListener, Release)
+NS_IMPL_CYCLE_COLLECTION(DOMMediaStream::PlaybackTrackListener, mStream)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMMediaStream)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMMediaStream,
                                                 DOMEventTargetHelper)
   tmp->Destroy();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwnedTracks)
@@ -575,16 +571,78 @@ DOMMediaStream::CurrentTime()
 {
   if (!mPlaybackStream) {
     return 0.0;
   }
   return nsRFPService::ReduceTimePrecisionAsSecs(mPlaybackStream->
     StreamTimeToSeconds(mPlaybackStream->GetCurrentTime() - mLogicalStreamStartTime));
 }
 
+already_AddRefed<Promise>
+DOMMediaStream::CountUnderlyingStreams(const GlobalObject& aGlobal, ErrorResult& aRv)
+{
+  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!window) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!go) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  RefPtr<Promise> p = Promise::Create(go, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  MediaStreamGraph* graph = MediaStreamGraph::GetInstanceIfExists(window);
+  if (!graph) {
+    p->MaybeResolve(0);
+    return p.forget();
+  }
+
+  auto* graphImpl = static_cast<MediaStreamGraphImpl*>(graph);
+
+  class Counter : public ControlMessage
+  {
+  public:
+    Counter(MediaStreamGraphImpl* aGraph,
+            const RefPtr<Promise>& aPromise)
+      : ControlMessage(nullptr)
+      , mGraph(aGraph)
+      , mPromise(MakeAndAddRef<nsMainThreadPtrHolder<Promise>>("DOMMediaStream::Counter::mPromise", aPromise))
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+    }
+
+    void Run() override
+    {
+      nsMainThreadPtrHandle<Promise>& promise = mPromise;
+      uint32_t streams = mGraph->mStreams.Length() +
+                         mGraph->mSuspendedStreams.Length();
+      mGraph->DispatchToMainThreadAfterStreamStateUpdate(
+        NewRunnableFrom([promise, streams]() mutable {
+          promise->MaybeResolve(streams);
+          return NS_OK;
+        }));
+    }
+
+  private:
+    // mGraph owns this Counter instance and decides its lifetime.
+    MediaStreamGraphImpl* mGraph;
+    nsMainThreadPtrHandle<Promise> mPromise;
+  };
+  graphImpl->AppendMessage(MakeUnique<Counter>(graphImpl, p));
+
+  return p.forget();
+}
+
 void
 DOMMediaStream::GetId(nsAString& aID) const
 {
   aID = mID;
 }
 
 void
 DOMMediaStream::GetAudioTracks(nsTArray<RefPtr<AudioStreamTrack> >& aTracks) const
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -355,16 +355,19 @@ public:
 
   static already_AddRefed<DOMMediaStream>
   Constructor(const dom::GlobalObject& aGlobal,
               const dom::Sequence<OwningNonNull<MediaStreamTrack>>& aTracks,
               ErrorResult& aRv);
 
   double CurrentTime();
 
+  static already_AddRefed<dom::Promise>
+  CountUnderlyingStreams(const dom::GlobalObject& aGlobal, ErrorResult& aRv);
+
   void GetId(nsAString& aID) const;
 
   void GetAudioTracks(nsTArray<RefPtr<AudioStreamTrack> >& aTracks) const;
   void GetVideoTracks(nsTArray<RefPtr<VideoStreamTrack> >& aTracks) const;
   void GetTracks(nsTArray<RefPtr<MediaStreamTrack> >& aTracks) const;
   MediaStreamTrack* GetTrackById(const nsAString& aId) const;
   void AddTrack(MediaStreamTrack& aTrack);
   void RemoveTrack(MediaStreamTrack& aTrack);
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -54,16 +54,18 @@ LazyLogModule gMediaStreamGraphLog("Medi
 enum SourceMediaStream::TrackCommands : uint32_t {
   TRACK_CREATE = TrackEventCommand::TRACK_EVENT_CREATED,
   TRACK_END = TrackEventCommand::TRACK_EVENT_ENDED,
   TRACK_UNUSED = TrackEventCommand::TRACK_EVENT_UNUSED,
 };
 
 /**
  * A hash table containing the graph instances, one per document.
+ *
+ * The key is a hash of nsPIDOMWindowInner, see `WindowToHash`.
  */
 static nsDataHashtable<nsUint32HashKey, MediaStreamGraphImpl*> gGraphs;
 
 MediaStreamGraphImpl::~MediaStreamGraphImpl()
 {
   NS_ASSERTION(IsEmpty(),
                "All streams should have been destroyed by messages from the main thread");
   LOG(LogLevel::Debug, ("MediaStreamGraph %p destroyed", this));
@@ -1584,43 +1586,46 @@ public:
       MOZ_ASSERT(false,
         "AudioCallbackDriver took too long to shut down and we let shutdown"
         " continue - freezing and leaking");
 
       // The timer fired, so we may be deeper in shutdown now.  Block any further
       // teardown and just leak, for safety.
       return NS_OK;
     }
+
+    for (MediaStream* stream : mGraph->AllStreams()) {
+      // Clean up all MediaSegments since we cannot release Images too
+      // late during shutdown. Also notify listeners that they were removed
+      // so they can clean up any gfx resources.
+      if (SourceMediaStream* source = stream->AsSourceStream()) {
+        // Finishing a SourceStream prevents new data from being appended.
+        source->Finish();
+      }
+      stream->GetStreamTracks().Clear();
+      stream->RemoveAllListenersImpl();
+    }
+
     mGraph->mForceShutdownTicket = nullptr;
 
     // We can't block past the final LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION
     // stage, since completion of that stage requires all streams to be freed,
     // which requires shutdown to proceed.
 
     // mGraph's thread is not running so it's OK to do whatever here
     if (mGraph->IsEmpty()) {
       // mGraph is no longer needed, so delete it.
       mGraph->Destroy();
     } else {
       // The graph is not empty.  We must be in a forced shutdown, or a
-      // non-realtime graph that has finished processing.  Some later
-      // AppendMessage will detect that the manager has been emptied, and
+      // non-realtime graph that has finished processing. Some later
+      // AppendMessage will detect that the graph has been emptied, and
       // delete it.
       NS_ASSERTION(mGraph->mForceShutDown || !mGraph->mRealtime,
                    "Not in forced shutdown?");
-      for (MediaStream* stream : mGraph->AllStreams()) {
-        // Clean up all MediaSegments since we cannot release Images too
-        // late during shutdown.
-        if (SourceMediaStream* source = stream->AsSourceStream()) {
-          // Finishing a SourceStream prevents new data from being appended.
-          source->Finish();
-        }
-        stream->GetStreamTracks().Clear();
-      }
-
       mGraph->mLifecycleState =
         MediaStreamGraphImpl::LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION;
     }
     return NS_OK;
   }
 private:
   RefPtr<MediaStreamGraphImpl> mGraph;
 };
@@ -2069,21 +2074,33 @@ MediaStream::EnsureTrack(TrackID aTrackI
     track = &mTracks.AddTrack(aTrackId, 0, segment.forget());
   }
   return track;
 }
 
 void
 MediaStream::RemoveAllListenersImpl()
 {
-  for (int32_t i = mListeners.Length() - 1; i >= 0; --i) {
-    RefPtr<MediaStreamListener> listener = mListeners[i].forget();
-    listener->NotifyEvent(GraphImpl(), MediaStreamGraphEvent::EVENT_REMOVED);
+  GraphImpl()->AssertOnGraphThreadOrNotRunning();
+
+  auto streamListeners(mListeners);
+  for (auto& l : streamListeners) {
+    l->NotifyEvent(GraphImpl(), MediaStreamGraphEvent::EVENT_REMOVED);
   }
   mListeners.Clear();
+
+  auto trackListeners(mTrackListeners);
+  for (auto& l : trackListeners) {
+    l.mListener->NotifyRemoved();
+  }
+  mTrackListeners.Clear();
+
+  if (SourceMediaStream* source = AsSourceStream()) {
+    source->RemoveAllDirectListeners();
+  }
 }
 
 void
 MediaStream::DestroyImpl()
 {
   for (int32_t i = mConsumers.Length() - 1; i >= 0; --i) {
     mConsumers[i]->Disconnect();
   }
@@ -3114,16 +3131,28 @@ SourceMediaStream::EndAllTrackAndFinish(
     SourceMediaStream::TrackData* data = &mUpdateTracks[i];
     data->mCommands |= TrackEventCommand::TRACK_EVENT_ENDED;
   }
   mPendingTracks.Clear();
   FinishWithLockHeld();
   // we will call NotifyEvent() to let GetUserMedia know
 }
 
+void
+SourceMediaStream::RemoveAllDirectListeners()
+{
+  GraphImpl()->AssertOnGraphThreadOrNotRunning();
+
+  auto directListeners(mDirectTrackListeners);
+  for (auto& l : directListeners) {
+    l.mListener->NotifyDirectListenerUninstalled();
+  }
+  mDirectTrackListeners.Clear();
+}
+
 SourceMediaStream::~SourceMediaStream()
 {
 }
 
 void
 SourceMediaStream::RegisterForAudioMixing()
 {
   MutexAutoLock lock(mMutex);
@@ -3490,30 +3519,37 @@ uint32_t WindowToHash(nsPIDOMWindowInner
   uint32_t hashkey = 0;
 
   hashkey = AddToHash(hashkey, aWindow);
 
   return hashkey;
 }
 
 MediaStreamGraph*
+MediaStreamGraph::GetInstanceIfExists(nsPIDOMWindowInner* aWindow)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Main thread only");
+
+  uint32_t hashkey = WindowToHash(aWindow);
+
+  MediaStreamGraphImpl* graph = nullptr;
+  gGraphs.Get(hashkey, &graph);
+  return graph;
+}
+
+MediaStreamGraph*
 MediaStreamGraph::GetInstance(MediaStreamGraph::GraphDriverType aGraphDriverRequested,
                               nsPIDOMWindowInner* aWindow)
 {
   NS_ASSERTION(NS_IsMainThread(), "Main thread only");
 
-  MediaStreamGraphImpl* graph = nullptr;
-
-  // We hash the nsPIDOMWindowInner to form a key to the gloabl
-  // MediaStreamGraph hashtable. Effectively, this means there is a graph per
-  // document.
-
-  uint32_t hashkey = WindowToHash(aWindow);
-
-  if (!gGraphs.Get(hashkey, &graph)) {
+  MediaStreamGraphImpl* graph =
+    static_cast<MediaStreamGraphImpl*>(GetInstanceIfExists(aWindow));
+
+  if (!graph) {
     if (!gMediaStreamGraphShutdownBlocker) {
 
       class Blocker : public media::ShutdownBlocker
       {
       public:
         Blocker()
         : media::ShutdownBlocker(NS_LITERAL_STRING(
             "MediaStreamGraph shutdown: blocking on msg thread"))
@@ -3551,16 +3587,17 @@ MediaStreamGraph::GetInstance(MediaStrea
     } else {
       // Uncommon case, only for some old configuration of webspeech.
       mainThread = AbstractThread::MainThread();
     }
     graph = new MediaStreamGraphImpl(aGraphDriverRequested,
                                      CubebUtils::PreferredSampleRate(),
                                      mainThread);
 
+    uint32_t hashkey = WindowToHash(aWindow);
     gGraphs.Put(hashkey, graph);
 
     LOG(LogLevel::Debug,
         ("Starting up MediaStreamGraph %p for window %p", graph, aWindow));
   }
 
   return graph;
 }
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -779,16 +779,22 @@ public:
   }
 
   /**
    * End all tracks and Finish() this stream.  Used to voluntarily revoke access
    * to a LocalMediaStream.
    */
   void EndAllTrackAndFinish();
 
+  /**
+   * Removes all direct listeners and signals to them that they have been
+   * uninstalled.
+   */
+  void RemoveAllDirectListeners();
+
   void RegisterForAudioMixing();
 
   /**
    * Returns true if this SourceMediaStream contains at least one audio track
    * that is in pending state.
    * This is thread safe, and takes the SourceMediaStream mutex.
    */
   bool HasPendingAudioTrack();
@@ -1259,16 +1265,17 @@ public:
   enum GraphDriverType {
     AUDIO_THREAD_DRIVER,
     SYSTEM_THREAD_DRIVER,
     OFFLINE_THREAD_DRIVER
   };
   static const uint32_t AUDIO_CALLBACK_DRIVER_SHUTDOWN_TIMEOUT = 20*1000;
 
   // Main thread only
+  static MediaStreamGraph* GetInstanceIfExists(nsPIDOMWindowInner* aWindow);
   static MediaStreamGraph* GetInstance(GraphDriverType aGraphDriverRequested,
                                        nsPIDOMWindowInner* aWindow);
   static MediaStreamGraph* CreateNonRealtimeInstance(
     TrackRate aSampleRate,
     nsPIDOMWindowInner* aWindowId);
 
   // Return the correct main thread for this graph. This always returns
   // something that is valid. Thread safe.
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -52,24 +52,16 @@ MediaStreamTrackSource::ApplyConstraints
 {
   RefPtr<PledgeVoid> p = new PledgeVoid();
   p->Reject(new MediaStreamError(aWindow,
                                  NS_LITERAL_STRING("OverconstrainedError"),
                                  NS_LITERAL_STRING("")));
   return p.forget();
 }
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamTrackConsumer)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamTrackConsumer)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrackConsumer)
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_CYCLE_COLLECTION_0(MediaStreamTrackConsumer)
-
 /**
  * PrincipalHandleListener monitors changes in PrincipalHandle of the media flowing
  * through the MediaStreamGraph.
  *
  * When the main thread principal for a MediaStreamTrack changes, its principal
  * will be set to the combination of the previous principal and the new one.
  *
  * As a PrincipalHandle change later happens on the MediaStreamGraph thread, we will
@@ -182,27 +174,25 @@ MediaStreamTrack::Destroy()
   }
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrack)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack,
                                                 DOMEventTargetHelper)
   tmp->Destroy();
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumers)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwningStream)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalTrack)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPrincipal)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamTrack,
                                                   DOMEventTargetHelper)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumers)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwningStream)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalTrack)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPrincipal)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_ADDREF_INHERITED(MediaStreamTrack, DOMEventTargetHelper)
@@ -374,20 +364,24 @@ MediaStreamTrack::NotifyPrincipalHandleC
   }
 }
 
 void
 MediaStreamTrack::NotifyEnded()
 {
   MOZ_ASSERT(mReadyState == MediaStreamTrackState::Ended);
 
-  for (int32_t i = mConsumers.Length() - 1; i >= 0; --i) {
-    // Loop backwards by index in case the consumer removes itself in the
-    // callback.
-    mConsumers[i]->NotifyEnded(this);
+  auto consumers(mConsumers);
+  for (const auto& consumer : consumers) {
+    if (consumer) {
+      consumer->NotifyEnded(this);
+    } else {
+      MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
+      mConsumers.RemoveElement(consumer);
+    }
   }
 }
 
 bool
 MediaStreamTrack::AddPrincipalChangeObserver(
   PrincipalChangeObserver<MediaStreamTrack>* aObserver)
 {
   return mPrincipalChangeObservers.AppendElement(aObserver) != nullptr;
@@ -400,22 +394,32 @@ MediaStreamTrack::RemovePrincipalChangeO
   return mPrincipalChangeObservers.RemoveElement(aObserver);
 }
 
 void
 MediaStreamTrack::AddConsumer(MediaStreamTrackConsumer* aConsumer)
 {
   MOZ_ASSERT(!mConsumers.Contains(aConsumer));
   mConsumers.AppendElement(aConsumer);
+
+  // Remove destroyed consumers for cleanliness
+  while (mConsumers.RemoveElement(nullptr)) {
+    MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
+  }
 }
 
 void
 MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer* aConsumer)
 {
   mConsumers.RemoveElement(aConsumer);
+
+  // Remove destroyed consumers for cleanliness
+  while (mConsumers.RemoveElement(nullptr)) {
+    MOZ_ASSERT_UNREACHABLE("A consumer was not explicitly removed");
+  }
 }
 
 already_AddRefed<MediaStreamTrack>
 MediaStreamTrack::Clone()
 {
   // MediaStreamTracks are currently governed by streams, so we need a dummy
   // DOMMediaStream to own our track clone. The dummy will never see any
   // dynamically created tracks (no input stream) so no need for a SourceGetter.
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -9,16 +9,17 @@
 #include "MediaTrackConstraints.h"
 #include "PrincipalChangeObserver.h"
 #include "StreamTracks.h"
 #include "mozilla/CORSMode.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/MediaStreamTrackBinding.h"
 #include "mozilla/dom/MediaTrackSettingsBinding.h"
 #include "mozilla/media/MediaUtils.h"
+#include "mozilla/WeakPtr.h"
 #include "nsError.h"
 #include "nsID.h"
 #include "nsIPrincipal.h"
 
 namespace mozilla {
 
 class DOMMediaStream;
 class MediaEnginePhotoCallback;
@@ -216,31 +217,28 @@ protected:
 
   const MediaSourceEnum mMediaSource;
 };
 
 /**
  * Base class that consumers of a MediaStreamTrack can use to get notifications
  * about state changes in the track.
  */
-class MediaStreamTrackConsumer : public nsISupports
+class MediaStreamTrackConsumer
+  : public SupportsWeakPtr<MediaStreamTrackConsumer>
 {
 public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS(MediaStreamTrackConsumer)
+  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(MediaStreamTrackConsumer)
 
   /**
    * Called when the track's readyState transitions to "ended".
    * Unlike the "ended" event exposed to script this is called for any reason,
    * including MediaStreamTrack::Stop().
    */
   virtual void NotifyEnded(MediaStreamTrack* aTrack) {};
-
-protected:
-  virtual ~MediaStreamTrackConsumer() {}
 };
 
 /**
  * Class representing a track in a DOMMediaStream.
  */
 class MediaStreamTrack : public DOMEventTargetHelper,
                          public MediaStreamTrackSource::Sink
 {
@@ -451,17 +449,17 @@ protected:
    * source as this MediaStreamTrack.
    * aTrackID is the TrackID the new track will have in its owned stream.
    */
   virtual already_AddRefed<MediaStreamTrack> CloneInternal(DOMMediaStream* aOwningStream,
                                                            TrackID aTrackID) = 0;
 
   nsTArray<PrincipalChangeObserver<MediaStreamTrack>*> mPrincipalChangeObservers;
 
-  nsTArray<RefPtr<MediaStreamTrackConsumer>> mConsumers;
+  nsTArray<WeakPtr<MediaStreamTrackConsumer>> mConsumers;
 
   RefPtr<DOMMediaStream> mOwningStream;
   TrackID mTrackID;
   TrackID mInputTrackID;
   RefPtr<MediaStreamTrackSource> mSource;
   RefPtr<MediaStreamTrack> mOriginalTrack;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIPrincipal> mPendingPrincipal;
--- a/dom/media/encoder/MediaEncoder.cpp
+++ b/dom/media/encoder/MediaEncoder.cpp
@@ -627,17 +627,21 @@ MediaEncoder::CreateEncoder(TaskQueue* a
   else if (MediaEncoder::IsWebMEncoderEnabled() &&
       (aMIMEType.EqualsLiteral(VIDEO_WEBM) ||
        (aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK))) {
     if (aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK &&
         MediaDecoder::IsOpusEnabled()) {
       audioEncoder = MakeAndAddRef<OpusTrackEncoder>(aTrackRate);
       NS_ENSURE_TRUE(audioEncoder, nullptr);
     }
-    videoEncoder = MakeAndAddRef<VP8TrackEncoder>(aTrackRate);
+    if (Preferences::GetBool("media.recorder.video.frame_drops", true)) {
+      videoEncoder = MakeAndAddRef<VP8TrackEncoder>(aTrackRate, FrameDroppingMode::ALLOW);
+    } else {
+      videoEncoder = MakeAndAddRef<VP8TrackEncoder>(aTrackRate, FrameDroppingMode::DISALLOW);
+    }
     writer = MakeUnique<WebMWriter>(aTrackTypes);
     NS_ENSURE_TRUE(writer, nullptr);
     NS_ENSURE_TRUE(videoEncoder, nullptr);
     mimeType = NS_LITERAL_STRING(VIDEO_WEBM);
   }
 #endif //MOZ_WEBM_ENCODER
   else if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled() &&
            (aMIMEType.EqualsLiteral(AUDIO_OGG) ||
--- a/dom/media/encoder/TrackEncoder.h
+++ b/dom/media/encoder/TrackEncoder.h
@@ -380,27 +380,33 @@ protected:
   uint32_t mAudioBitrate;
 
   // This may only be accessed on the MSG thread.
   // I.e., in the regular NotifyQueuedChanges for audio to avoid adding data
   // from that callback when the direct one is active.
   bool mDirectConnected;
 };
 
+enum class FrameDroppingMode {
+  ALLOW, // Allowed to drop frames to keep up under load
+  DISALLOW, // Must not drop any frames, even if it means we will OOM
+};
+
 class VideoTrackEncoder : public TrackEncoder
 {
 public:
-  explicit VideoTrackEncoder(TrackRate aTrackRate)
+  VideoTrackEncoder(TrackRate aTrackRate, FrameDroppingMode aFrameDroppingMode)
     : TrackEncoder(aTrackRate)
     , mFrameWidth(0)
     , mFrameHeight(0)
     , mDisplayWidth(0)
     , mDisplayHeight(0)
     , mEncodedTicks(0)
     , mVideoBitrate(0)
+    , mFrameDroppingMode(aFrameDroppingMode)
   {
     mLastChunk.mDuration = 0;
   }
 
   /**
    * Suspends encoding from aTime, i.e., all video frame with a timestamp
    * between aTime and the timestamp of the next Resume() will be dropped.
    */
@@ -546,13 +552,19 @@ protected:
 
   /**
    * The time Suspend was called on the MediaRecorder, so we can calculate the
    * duration on the next Resume().
    */
   TimeStamp mSuspendTime;
 
   uint32_t mVideoBitrate;
+
+  /**
+   * ALLOW to drop frames under load.
+   * DISALLOW to encode all frames, mainly for testing.
+   */
+  FrameDroppingMode mFrameDroppingMode;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/encoder/VP8TrackEncoder.cpp
+++ b/dom/media/encoder/VP8TrackEncoder.cpp
@@ -51,18 +51,19 @@ GetSourceSurface(already_AddRefed<Image>
     surf = img->GetAsSourceSurface();
     return NS_OK;
   });
 
   NS_DispatchToMainThread(runnable, NS_DISPATCH_SYNC);
   return surf.forget();
 }
 
-VP8TrackEncoder::VP8TrackEncoder(TrackRate aTrackRate)
-  : VideoTrackEncoder(aTrackRate)
+VP8TrackEncoder::VP8TrackEncoder(TrackRate aTrackRate,
+                                 FrameDroppingMode aFrameDroppingMode)
+  : VideoTrackEncoder(aTrackRate, aFrameDroppingMode)
   , mEncodedTimestamp(0)
   , mVPXContext(new vpx_codec_ctx_t())
   , mVPXImageWrapper(new vpx_image_t())
 {
   MOZ_COUNT_CTOR(VP8TrackEncoder);
 }
 
 VP8TrackEncoder::~VP8TrackEncoder()
@@ -571,16 +572,20 @@ nsresult VP8TrackEncoder::PrepareRawFram
  * Compares the elapsed time from the beginning of GetEncodedTrack and
  * the processed frame duration in mSourceSegment
  * in order to set the nextEncodeOperation for next target frame.
  */
 VP8TrackEncoder::EncodeOperation
 VP8TrackEncoder::GetNextEncodeOperation(TimeDuration aTimeElapsed,
                                         StreamTime aProcessedDuration)
 {
+  if (mFrameDroppingMode == FrameDroppingMode::DISALLOW) {
+    return ENCODE_NORMAL_FRAME;
+  }
+
   int64_t durationInUsec =
     FramesToUsecs(aProcessedDuration, mTrackRate).value();
   if (aTimeElapsed.ToMicroseconds() > (durationInUsec * SKIP_FRAME_RATIO)) {
     // The encoder is too slow.
     // We should skip next frame to consume the mSourceSegment.
     return SKIP_FRAME;
   } else if (aTimeElapsed.ToMicroseconds() > (durationInUsec * I_FRAME_RATIO)) {
     // The encoder is a little slow.
--- a/dom/media/encoder/VP8TrackEncoder.h
+++ b/dom/media/encoder/VP8TrackEncoder.h
@@ -22,18 +22,19 @@ typedef struct vpx_image vpx_image_t;
  */
 class VP8TrackEncoder : public VideoTrackEncoder
 {
   enum EncodeOperation {
     ENCODE_NORMAL_FRAME, // VP8 track encoder works normally.
     ENCODE_I_FRAME, // The next frame will be encoded as I-Frame.
     SKIP_FRAME, // Skip the next frame.
   };
+
 public:
-  explicit VP8TrackEncoder(TrackRate aTrackRate);
+  VP8TrackEncoder(TrackRate aTrackRate, FrameDroppingMode aFrameDroppingMode);
   virtual ~VP8TrackEncoder();
 
   already_AddRefed<TrackMetadataBase> GetMetadata() final override;
 
   nsresult GetEncodedTrack(EncodedFrameContainer& aData) final override;
 
 protected:
   nsresult Init(int32_t aWidth, int32_t aHeight,
--- a/dom/media/gtest/TestVideoTrackEncoder.cpp
+++ b/dom/media/gtest/TestVideoTrackEncoder.cpp
@@ -187,17 +187,17 @@ struct InitParam {
   int  mWidth;          // frame width
   int  mHeight;         // frame height
 };
 
 class TestVP8TrackEncoder: public VP8TrackEncoder
 {
 public:
   explicit TestVP8TrackEncoder(TrackRate aTrackRate = VIDEO_TRACK_RATE)
-    : VP8TrackEncoder(aTrackRate) {}
+    : VP8TrackEncoder(aTrackRate, FrameDroppingMode::DISALLOW) {}
 
   ::testing::AssertionResult TestInit(const InitParam &aParam)
   {
     nsresult result = Init(aParam.mWidth, aParam.mHeight, aParam.mWidth, aParam.mHeight);
 
     if (((NS_FAILED(result) && aParam.mShouldSucceed)) || (NS_SUCCEEDED(result) && !aParam.mShouldSucceed))
     {
       return ::testing::AssertionFailure()
--- a/dom/media/gtest/TestWebMWriter.cpp
+++ b/dom/media/gtest/TestWebMWriter.cpp
@@ -26,17 +26,17 @@ public:
     return false;
   }
 };
 
 class WebMVP8TrackEncoder: public VP8TrackEncoder
 {
 public:
   explicit WebMVP8TrackEncoder(TrackRate aTrackRate = 90000)
-    : VP8TrackEncoder(aTrackRate) {}
+    : VP8TrackEncoder(aTrackRate, FrameDroppingMode::DISALLOW) {}
 
   bool TestVP8Creation(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth,
                        int32_t aDisplayHeight)
   {
     if (NS_SUCCEEDED(Init(aWidth, aHeight, aDisplayWidth, aDisplayHeight))) {
       return true;
     }
     return false;
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -58,16 +58,17 @@ skip-if = toolkit == 'android' # no scre
 [test_getUserMedia_basicTabshare.html]
 skip-if = toolkit == 'android' # no windowshare on android
 [test_getUserMedia_basicWindowshare.html]
 skip-if = toolkit == 'android' # no windowshare on android
 [test_getUserMedia_basicVideoAudio.html]
 [test_getUserMedia_bug1223696.html]
 [test_getUserMedia_constraints.html]
 [test_getUserMedia_callbacks.html]
+[test_getUserMedia_GC_MediaStream.html]
 [test_getUserMedia_getTrackById.html]
 [test_getUserMedia_gumWithinGum.html]
 [test_getUserMedia_loadedmetadata.html]
 [test_getUserMedia_mediaElementCapture_audio.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_getUserMedia_mediaElementCapture_tracks.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_getUserMedia_mediaElementCapture_video.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_getUserMedia_GC_MediaStream.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  "use strict";
+
+  createHTML({
+    title: "MediaStreams can be garbage collected",
+    bug: "1407542"
+  });
+
+  let SpecialStream = SpecialPowers.wrap(MediaStream);
+
+  async function testGC(stream, numCopies, copy) {
+    let startStreams = await SpecialStream.countUnderlyingStreams();
+
+    let copies = new Array(numCopies).fill(0).map(() => copy(stream));
+    ok(await SpecialStream.countUnderlyingStreams() > startStreams,
+        "MediaStream constructor creates more underlying streams");
+
+    copies = [];
+    await new Promise(r => SpecialPowers.exactGC(r));
+    is(await SpecialStream.countUnderlyingStreams(), startStreams,
+       "MediaStreams should have been collected");
+  }
+
+  runTest(async () => {
+    let gUMStream = await getUserMedia({video: true});
+    info("Testing GC of copy constructor");
+    await testGC(gUMStream, 10, s => new MediaStream(s));
+
+    info("Testing GC of track-array constructor");
+    await testGC(gUMStream, 10, s => new MediaStream(s.getTracks()));
+
+    info("Testing GC of empty constructor plus addTrack");
+    await testGC(gUMStream, 10, s => {
+      let s2 = new MediaStream();
+      s.getTracks().forEach(t => s2.addTrack(t));
+      return s2;
+    });
+
+    info("Testing GC of track-array constructor with cloned tracks");
+    await testGC(gUMStream, 10, s => new MediaStream(s.getTracks().map(t => t.clone())));
+
+    info("Testing GC of empty constructor plus addTrack with cloned tracks");
+    await testGC(gUMStream, 10, s => {
+      let s2 = new MediaStream();
+      s.getTracks().forEach(t => s2.addTrack(t.clone()));
+      return s2;
+    });
+
+    info("Testing GC of cloned stream");
+    await testGC(gUMStream, 10, s => s.clone());
+
+    info("Testing GC of gUM stream");
+    gUMStream = null;
+    await new Promise(r => SpecialPowers.exactGC(r));
+    is(await SpecialStream.countUnderlyingStreams(), 0,
+       "Original gUM stream should be collectable");
+  });
+</script>
+</pre>
+</body>
+</html>
--- a/dom/plugins/test/mochitest/mochitest.ini
+++ b/dom/plugins/test/mochitest/mochitest.ini
@@ -54,17 +54,16 @@ skip-if = !crashreporter
 skip-if = toolkit == 'android' # needs plugin support
 [test_bug852315.html]
 [test_bug854082.html]
 [test_bug863792.html]
 [test_bug967694.html]
 [test_bug985859.html]
 [test_bug986930.html]
 [test_bug1092842.html]
-skip-if = os == "win" && debug # Bug 1408490
 [test_bug1165981.html]
 [test_bug1245545.html]
 [test_bug1307694.html]
 [test_cocoa_focus.html]
 skip-if = toolkit != "cocoa" || e10s # Bug 1194534
 support-files = cocoa_focus.html
 [test_cocoa_window_focus.html]
 skip-if = toolkit != "cocoa" # Bug 1194534
--- a/dom/u2f/U2FTransactionChild.cpp
+++ b/dom/u2f/U2FTransactionChild.cpp
@@ -4,24 +4,16 @@
  * 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/. */
 
 #include "U2FTransactionChild.h"
 
 namespace mozilla {
 namespace dom {
 
-U2FTransactionChild::U2FTransactionChild()
-{
-  // Retain a reference so the task object isn't deleted without IPDL's
-  // knowledge. The reference will be released by
-  // mozilla::ipc::BackgroundChildImpl::DeallocPWebAuthnTransactionChild.
-  NS_ADDREF_THIS();
-}
-
 mozilla::ipc::IPCResult
 U2FTransactionChild::RecvConfirmRegister(const uint64_t& aTransactionId,
                                          nsTArray<uint8_t>&& aRegBuffer)
 {
   RefPtr<U2FManager> mgr = U2FManager::Get();
   MOZ_ASSERT(mgr);
   mgr->FinishRegister(aTransactionId, aRegBuffer);
   return IPC_OK();
--- a/dom/u2f/U2FTransactionChild.h
+++ b/dom/u2f/U2FTransactionChild.h
@@ -2,47 +2,41 @@
 /* vim: set ts=8 sts=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/. */
 
 #ifndef mozilla_dom_U2FTransactionChild_h
 #define mozilla_dom_U2FTransactionChild_h
 
-#include "mozilla/dom/PWebAuthnTransactionChild.h"
+#include "mozilla/dom/WebAuthnTransactionChildBase.h"
 
 /*
  * Child process IPC implementation for U2F API. Receives results of U2F
  * transactions from the parent process, and sends them to the U2FManager
  * to either cancel the transaction, or be formatted and relayed to content.
  */
 
 namespace mozilla {
 namespace dom {
 
-class U2FTransactionChild final : public PWebAuthnTransactionChild
+class U2FTransactionChild final : public WebAuthnTransactionChildBase
 {
 public:
-  NS_INLINE_DECL_REFCOUNTING(U2FTransactionChild);
-  U2FTransactionChild();
-
   mozilla::ipc::IPCResult
   RecvConfirmRegister(const uint64_t& aTransactionId,
                       nsTArray<uint8_t>&& aRegBuffer) override;
 
   mozilla::ipc::IPCResult
   RecvConfirmSign(const uint64_t& aTransactionId,
                   nsTArray<uint8_t>&& aCredentialId,
                   nsTArray<uint8_t>&& aBuffer) override;
 
   mozilla::ipc::IPCResult
   RecvAbort(const uint64_t& aTransactionId, const nsresult& aError) override;
 
   void ActorDestroy(ActorDestroyReason why) override;
-
-private:
-  ~U2FTransactionChild() = default;
 };
 
 }
 }
 
 #endif //mozilla_dom_U2FTransactionChild_h
--- a/dom/webauthn/WebAuthnTransactionChild.cpp
+++ b/dom/webauthn/WebAuthnTransactionChild.cpp
@@ -4,24 +4,16 @@
  * 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/. */
 
 #include "mozilla/dom/WebAuthnTransactionChild.h"
 
 namespace mozilla {
 namespace dom {
 
-WebAuthnTransactionChild::WebAuthnTransactionChild()
-{
-  // Retain a reference so the task object isn't deleted without IPDL's
-  // knowledge. The reference will be released by
-  // mozilla::ipc::BackgroundChildImpl::DeallocPWebAuthnTransactionChild.
-  NS_ADDREF_THIS();
-}
-
 mozilla::ipc::IPCResult
 WebAuthnTransactionChild::RecvConfirmRegister(const uint64_t& aTransactionId,
                                               nsTArray<uint8_t>&& aRegBuffer)
 {
   RefPtr<WebAuthnManager> mgr = WebAuthnManager::Get();
   MOZ_ASSERT(mgr);
   mgr->FinishMakeCredential(aTransactionId, aRegBuffer);
   return IPC_OK();
--- a/dom/webauthn/WebAuthnTransactionChild.h
+++ b/dom/webauthn/WebAuthnTransactionChild.h
@@ -2,48 +2,42 @@
 /* vim: set ts=8 sts=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/. */
 
 #ifndef mozilla_dom_WebAuthnTransactionChild_h
 #define mozilla_dom_WebAuthnTransactionChild_h
 
-#include "mozilla/dom/PWebAuthnTransactionChild.h"
+#include "mozilla/dom/WebAuthnTransactionChildBase.h"
 
 /*
  * Child process IPC implementation for WebAuthn API. Receives results of
  * WebAuthn transactions from the parent process, and sends them to the
  * WebAuthnManager either cancel the transaction, or be formatted and relayed to
  * content.
  */
 
 namespace mozilla {
 namespace dom {
 
-class WebAuthnTransactionChild final : public PWebAuthnTransactionChild
+class WebAuthnTransactionChild final : public WebAuthnTransactionChildBase
 {
 public:
-  NS_INLINE_DECL_REFCOUNTING(WebAuthnTransactionChild);
-  WebAuthnTransactionChild();
-
   mozilla::ipc::IPCResult
   RecvConfirmRegister(const uint64_t& aTransactionId,
                       nsTArray<uint8_t>&& aRegBuffer) override;
 
   mozilla::ipc::IPCResult
   RecvConfirmSign(const uint64_t& aTransactionId,
                   nsTArray<uint8_t>&& aCredentialId,
                   nsTArray<uint8_t>&& aBuffer) override;
 
   mozilla::ipc::IPCResult
   RecvAbort(const uint64_t& aTransactionId, const nsresult& aError) override;
 
   void ActorDestroy(ActorDestroyReason why) override;
-
-private:
-  ~WebAuthnTransactionChild() = default;
 };
 
 }
 }
 
 #endif //mozilla_dom_WebAuthnTransactionChild_h
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/WebAuthnTransactionChildBase.cpp
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+#include "mozilla/dom/WebAuthnTransactionChildBase.h"
+
+namespace mozilla {
+namespace dom {
+
+WebAuthnTransactionChildBase::WebAuthnTransactionChildBase()
+{
+  // Retain a reference so the task object isn't deleted without IPDL's
+  // knowledge. The reference will be released by
+  // mozilla::ipc::BackgroundChildImpl::DeallocPWebAuthnTransactionChild.
+  NS_ADDREF_THIS();
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/WebAuthnTransactionChildBase.h
@@ -0,0 +1,36 @@
+
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+#ifndef mozilla_dom_WebAuthnTransactionChildBase_h
+#define mozilla_dom_WebAuthnTransactionChildBase_h
+
+#include "mozilla/dom/PWebAuthnTransactionChild.h"
+
+/*
+ * A base class to be used by child process IPC implementations for WebAuthn
+ * and U2F. This mostly handles refcounting so we can properly dereference
+ * in mozilla::ipc::BackgroundChildImpl::DeallocPWebAuthnTransactionChild(),
+ * a function that doesn't know which of the two implementations is passed.
+ */
+
+namespace mozilla {
+namespace dom {
+
+class WebAuthnTransactionChildBase : public PWebAuthnTransactionChild
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING(WebAuthnTransactionChildBase);
+  WebAuthnTransactionChildBase();
+
+protected:
+  ~WebAuthnTransactionChildBase() = default;
+};
+
+}
+}
+
+#endif //mozilla_dom_WebAuthnTransactionChildBase_h
--- a/dom/webauthn/moz.build
+++ b/dom/webauthn/moz.build
@@ -18,16 +18,17 @@ EXPORTS.mozilla.dom += [
     'PublicKeyCredential.h',
     'U2FHIDTokenManager.h',
     'U2FSoftTokenManager.h',
     'U2FTokenManager.h',
     'U2FTokenTransport.h',
     'WebAuthnCBORUtil.h',
     'WebAuthnManager.h',
     'WebAuthnTransactionChild.h',
+    'WebAuthnTransactionChildBase.h',
     'WebAuthnTransactionParent.h',
     'WebAuthnUtil.h',
 ]
 
 UNIFIED_SOURCES += [
     'AuthenticatorAssertionResponse.cpp',
     'AuthenticatorAttestationResponse.cpp',
     'AuthenticatorResponse.cpp',
@@ -35,16 +36,17 @@ UNIFIED_SOURCES += [
     'cbor-cpp/src/output_dynamic.cpp',
     'PublicKeyCredential.cpp',
     'U2FHIDTokenManager.cpp',
     'U2FSoftTokenManager.cpp',
     'U2FTokenManager.cpp',
     'WebAuthnCBORUtil.cpp',
     'WebAuthnManager.cpp',
     'WebAuthnTransactionChild.cpp',
+    'WebAuthnTransactionChildBase.cpp',
     'WebAuthnTransactionParent.cpp',
     'WebAuthnUtil.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
--- a/dom/webauthn/u2f-hid-rs/.travis.yml
+++ b/dom/webauthn/u2f-hid-rs/.travis.yml
@@ -1,16 +1,20 @@
 sudo: false
 language: rust
 cache: cargo
 rust:
   - stable
   - beta
   - nightly
 
+matrix:
+  allow_failures:
+    - rust: nightly
+
 addons:
   apt:
     packages:
       - build-essential
       - libudev-dev
 
 before_install:
   - pkg-config --list-all
--- a/dom/webauthn/u2f-hid-rs/src/windows/winapi.rs
+++ b/dom/webauthn/u2f-hid-rs/src/windows/winapi.rs
@@ -190,17 +190,17 @@ impl DeviceInterfaceDetailData {
             cb_size = 4 + 2; // 4-byte uint + default TCHAR size. size_of is inaccurate.
         }
 
         if size < cb_size {
             warn!("DeviceInterfaceDetailData is too small. {}", size);
             return None;
         }
 
-        let mut data = unsafe { libc::malloc(size) as PSP_DEVICE_INTERFACE_DETAIL_DATA_W };
+        let data = unsafe { libc::malloc(size) as PSP_DEVICE_INTERFACE_DETAIL_DATA_W };
         if data.is_null() {
             return None;
         }
 
         // Set total size of the structure.
         unsafe { (*data).cbSize = cb_size as UINT };
 
         // Compute offset of `SP_DEVICE_INTERFACE_DETAIL_DATA_W.DevicePath`.
--- a/dom/webidl/MediaStream.webidl
+++ b/dom/webidl/MediaStream.webidl
@@ -35,9 +35,12 @@ interface MediaStream : EventTarget {
     MediaStreamTrack?          getTrackById (DOMString trackId);
     void                       addTrack (MediaStreamTrack track);
     void                       removeTrack (MediaStreamTrack track);
     MediaStream                clone ();
     readonly    attribute boolean      active;
                 attribute EventHandler onaddtrack;
     //             attribute EventHandler onremovetrack;
     readonly attribute double currentTime;
+
+    [ChromeOnly, Throws]
+    static Promise<long> countUnderlyingStreams();
 };
--- a/editor/reftests/xul/input.css
+++ b/editor/reftests/xul/input.css
@@ -39,17 +39,17 @@ html|input, html|textarea {
 html|input.ac {
   padding: 0 4px !important;
 }
 
 html|input.empty {
   color: graytext;
 }
 
-@media (-moz-windows-default-theme) {
+@media (-moz-windows-default-theme) and (-moz-os-version: windows-win7) {
   :root:not(.winxp) html|input.empty {
     font-style: italic;
   }
 }
 
 html|input.num {
   text-align: end;
 }
--- a/editor/reftests/xul/reftest.list
+++ b/editor/reftests/xul/reftest.list
@@ -1,9 +1,9 @@
-fails-if(Android) fails-if(stylo&&winWidget) skip-if(browserIsRemote&&winWidget) == empty-1.xul empty-ref.xul # Windows: bug 1239170, stylo: bug 1411460
+fails-if(Android) skip-if(browserIsRemote&&winWidget) == empty-1.xul empty-ref.xul # Windows: bug 1239170
 != empty-2.xul empty-ref.xul
 # There is no way to simulate an autocomplete textbox in windows XP/Vista/7/8/10 default theme using CSS.
 # Therefore, the equlity tests below should be marked as failing.
 fails-if(Android) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012]|10\.0)/.test(http.oscpu)) == autocomplete-1.xul autocomplete-ref.xul # bug 783658
 fails-if(Android) fails-if(windowsDefaultTheme&&/^Windows\x20NT\x20(5\.[12]|6\.[012]|10\.0)/.test(http.oscpu)) == emptyautocomplete-1.xul emptyautocomplete-ref.xul # bug 783658
 != emptymultiline-1.xul emptymultiline-ref.xul
 fails-if(Android) == emptymultiline-2.xul emptymultiline-ref.xul # bug 783658
 fails-if(Android) skip-if(browserIsRemote&&winWidget) == emptytextbox-1.xul emptytextbox-ref.xul # Windows: bug 1239170
--- a/ipc/glue/BackgroundChildImpl.cpp
+++ b/ipc/glue/BackgroundChildImpl.cpp
@@ -37,17 +37,17 @@
 #include "mozilla/ipc/IPCStreamAlloc.h"
 #include "mozilla/ipc/PBackgroundTestChild.h"
 #include "mozilla/ipc/PChildToParentStreamChild.h"
 #include "mozilla/ipc/PParentToChildStreamChild.h"
 #include "mozilla/layout/VsyncChild.h"
 #include "mozilla/net/HttpBackgroundChannelChild.h"
 #include "mozilla/net/PUDPSocketChild.h"
 #include "mozilla/dom/network/UDPSocketChild.h"
-#include "mozilla/dom/WebAuthnTransactionChild.h"
+#include "mozilla/dom/WebAuthnTransactionChildBase.h"
 #include "nsID.h"
 #include "nsTraceRefcnt.h"
 
 namespace {
 
 class TestChild final : public mozilla::ipc::PBackgroundTestChild
 {
   friend class mozilla::ipc::BackgroundChildImpl;
@@ -81,17 +81,17 @@ using mozilla::net::PUDPSocketChild;
 
 using mozilla::dom::asmjscache::PAsmJSCacheEntryChild;
 using mozilla::dom::cache::PCacheChild;
 using mozilla::dom::cache::PCacheStorageChild;
 using mozilla::dom::cache::PCacheStreamControlChild;
 using mozilla::dom::LocalStorage;
 using mozilla::dom::StorageDBChild;
 
-using mozilla::dom::WebAuthnTransactionChild;
+using mozilla::dom::WebAuthnTransactionChildBase;
 
 // -----------------------------------------------------------------------------
 // BackgroundChildImpl::ThreadLocal
 // -----------------------------------------------------------------------------
 
 BackgroundChildImpl::
 ThreadLocal::ThreadLocal()
   : mCurrentFileHandle(nullptr)
@@ -602,18 +602,18 @@ BackgroundChildImpl::AllocPWebAuthnTrans
   MOZ_CRASH("PWebAuthnTransaction actor should be manually constructed!");
   return nullptr;
 }
 
 bool
 BackgroundChildImpl::DeallocPWebAuthnTransactionChild(PWebAuthnTransactionChild* aActor)
 {
   MOZ_ASSERT(aActor);
-  RefPtr<dom::WebAuthnTransactionChild> child =
-    dont_AddRef(static_cast<dom::WebAuthnTransactionChild*>(aActor));
+  RefPtr<dom::WebAuthnTransactionChildBase> child =
+    dont_AddRef(static_cast<dom::WebAuthnTransactionChildBase*>(aActor));
   return true;
 }
 
 net::PHttpBackgroundChannelChild*
 BackgroundChildImpl::AllocPHttpBackgroundChannelChild(const uint64_t& aChannelId)
 {
   MOZ_CRASH("PHttpBackgroundChannelChild actor should be manually constructed!");
   return nullptr;
--- a/js/src/builtin/Generator.js
+++ b/js/src/builtin/Generator.js
@@ -1,85 +1,85 @@
 /* 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/. */
 
-function StarGeneratorNext(val) {
-    // The IsSuspendedStarGenerator call below is not necessary for
-    // correctness. It's a performance optimization to check for the
-    // common case with a single call. It's also inlined in Baseline.
+function GeneratorNext(val) {
+    // The IsSuspendedGenerator call below is not necessary for correctness.
+    // It's a performance optimization to check for the common case with a
+    // single call. It's also inlined in Baseline.
 
-    if (!IsSuspendedStarGenerator(this)) {
-        if (!IsObject(this) || !IsStarGeneratorObject(this))
-            return callFunction(CallStarGeneratorMethodIfWrapped, this, val, "StarGeneratorNext");
+    if (!IsSuspendedGenerator(this)) {
+        if (!IsObject(this) || !IsGeneratorObject(this))
+            return callFunction(CallGeneratorMethodIfWrapped, this, val, "GeneratorNext");
 
-        if (StarGeneratorObjectIsClosed(this))
+        if (GeneratorObjectIsClosed(this))
             return { value: undefined, done: true };
 
         if (GeneratorIsRunning(this))
             ThrowTypeError(JSMSG_NESTING_GENERATOR);
     }
 
     try {
         return resumeGenerator(this, val, "next");
     } catch (e) {
-        if (!StarGeneratorObjectIsClosed(this))
+        if (!GeneratorObjectIsClosed(this))
             GeneratorSetClosed(this);
         throw e;
     }
 }
 
-function StarGeneratorThrow(val) {
-    if (!IsSuspendedStarGenerator(this)) {
-        if (!IsObject(this) || !IsStarGeneratorObject(this))
-            return callFunction(CallStarGeneratorMethodIfWrapped, this, val, "StarGeneratorThrow");
+function GeneratorThrow(val) {
+    if (!IsSuspendedGenerator(this)) {
+        if (!IsObject(this) || !IsGeneratorObject(this))
+            return callFunction(CallGeneratorMethodIfWrapped, this, val, "GeneratorThrow");
 
-        if (StarGeneratorObjectIsClosed(this))
+        if (GeneratorObjectIsClosed(this))
             throw val;
 
         if (GeneratorIsRunning(this))
             ThrowTypeError(JSMSG_NESTING_GENERATOR);
     }
 
     try {
         return resumeGenerator(this, val, "throw");
     } catch (e) {
-        if (!StarGeneratorObjectIsClosed(this))
+        if (!GeneratorObjectIsClosed(this))
             GeneratorSetClosed(this);
         throw e;
     }
 }
 
-function StarGeneratorReturn(val) {
-    if (!IsSuspendedStarGenerator(this)) {
-        if (!IsObject(this) || !IsStarGeneratorObject(this))
-            return callFunction(CallStarGeneratorMethodIfWrapped, this, val, "StarGeneratorReturn");
+function GeneratorReturn(val) {
+    if (!IsSuspendedGenerator(this)) {
+        if (!IsObject(this) || !IsGeneratorObject(this))
+            return callFunction(CallGeneratorMethodIfWrapped, this, val, "GeneratorReturn");
 
-        if (StarGeneratorObjectIsClosed(this))
+        if (GeneratorObjectIsClosed(this))
             return { value: val, done: true };
 
         if (GeneratorIsRunning(this))
             ThrowTypeError(JSMSG_NESTING_GENERATOR);
     }
 
     try {
         var rval = { value: val, done: true };
-        return resumeGenerator(this, rval, "close");
+        return resumeGenerator(this, rval, "return");
     } catch (e) {
-        if (!StarGeneratorObjectIsClosed(this))
+        if (!GeneratorObjectIsClosed(this))
             GeneratorSetClosed(this);
         throw e;
     }
 }
 
 function InterpretGeneratorResume(gen, val, kind) {
     // If we want to resume a generator in the interpreter, the script containing
     // the resumeGenerator/JSOP_RESUME also has to run in the interpreter. The
     // forceInterpreter() call below compiles to a bytecode op that prevents us
     // from JITing this script.
     forceInterpreter();
     if (kind === "next")
        return resumeGenerator(gen, val, "next");
     if (kind === "throw")
        return resumeGenerator(gen, val, "throw");
-    assert(kind === "close", "Invalid resume kind");
-    return resumeGenerator(gen, val, "close");
+    assert(kind === "return", "Invalid resume kind");
+    return resumeGenerator(gen, val, "return");
 }
--- a/js/src/builtin/Iterator.js
+++ b/js/src/builtin/Iterator.js
@@ -1,73 +1,7 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function IteratorIdentity() {
     return this;
 }
-
-var LegacyIteratorWrapperMap = new std_WeakMap();
-
-function LegacyIteratorNext(arg) {
-    var iter = callFunction(std_WeakMap_get, LegacyIteratorWrapperMap, this);
-    try {
-        return { value: callContentFunction(iter.next, iter, arg), done: false };
-    } catch (e) {
-        if (e instanceof std_StopIteration)
-            return { value: undefined, done: true };
-        throw e;
-    }
-}
-
-function LegacyIteratorThrow(exn) {
-    var iter = callFunction(std_WeakMap_get, LegacyIteratorWrapperMap, this);
-    try {
-        return { value: callContentFunction(iter.throw, iter, exn), done: false };
-    } catch (e) {
-        if (e instanceof std_StopIteration)
-            return { value: undefined, done: true };
-        throw e;
-    }
-}
-
-function LegacyGeneratorIterator(iter) {
-    callFunction(std_WeakMap_set, LegacyIteratorWrapperMap, this, iter);
-}
-
-var LegacyIteratorsInitialized = std_Object_create(null);
-
-function InitLegacyIterators() {
-    var props = std_Object_create(null);
-
-    props.next = std_Object_create(null);
-    props.next.value = LegacyIteratorNext;
-    props.next.enumerable = false;
-    props.next.configurable = true;
-    props.next.writable = true;
-
-    props[std_iterator] = std_Object_create(null);
-    props[std_iterator].value = IteratorIdentity;
-    props[std_iterator].enumerable = false;
-    props[std_iterator].configurable = true;
-    props[std_iterator].writable = true;
-
-    props.throw = std_Object_create(null);
-    props.throw.value = LegacyIteratorThrow;
-    props.throw.enumerable = false;
-    props.throw.configurable = true;
-    props.throw.writable = true;
-
-    var LegacyGeneratorIteratorProto = std_Object_create(GetIteratorPrototype(), props);
-    MakeConstructible(LegacyGeneratorIterator, LegacyGeneratorIteratorProto);
-
-    LegacyIteratorsInitialized.initialized = true;
-}
-
-function LegacyGeneratorIteratorShim() {
-    var iter = ToObject(this);
-
-    if (!LegacyIteratorsInitialized.initialized)
-        InitLegacyIterators();
-
-    return new LegacyGeneratorIterator(iter);
-}
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -1066,21 +1066,20 @@ ModuleObject::instantiateFunctionDeclara
 
     for (const auto& funDecl : *funDecls) {
         fun = funDecl.fun;
         obj = Lambda(cx, fun, env);
         if (!obj)
             return false;
 
         if (fun->isAsync()) {
-            if (fun->isStarGenerator()) {
+            if (fun->isGenerator())
                 obj = WrapAsyncGenerator(cx, obj.as<JSFunction>());
-            } else {
+            else
                 obj = WrapAsyncFunction(cx, obj.as<JSFunction>());
-            }
         }
 
         if (!obj)
             return false;
 
         value = ObjectValue(*obj);
         if (!SetProperty(cx, env, funDecl.name->asPropertyName(), value))
             return false;
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -350,17 +350,17 @@ js::ObjectToSource(JSContext* cx, Handle
                 if (!buf.append("set "))
                     return false;
             } else if (kind == PropertyKind::Method && fun) {
                 if (IsWrappedAsyncFunction(fun)) {
                     if (!buf.append("async "))
                         return false;
                 }
 
-                if (fun->isStarGenerator()) {
+                if (fun->isGenerator()) {
                     if (!buf.append('*'))
                         return false;
                 }
             }
         }
 
         bool needsBracket = JSID_IS_SYMBOL(id);
         if (needsBracket && !buf.append('['))
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -3422,17 +3422,17 @@ ASTSerializer::identifier(ParseNode* pn,
 }
 
 bool
 ASTSerializer::function(ParseNode* pn, ASTType type, MutableHandleValue dst)
 {
     RootedFunction func(cx, pn->pn_funbox->function());
 
     GeneratorStyle generatorStyle =
-        pn->pn_funbox->isStarGenerator()
+        pn->pn_funbox->isGenerator()
         ? GeneratorStyle::ES6
         : GeneratorStyle::None;
 
     bool isAsync = pn->pn_funbox->isAsync();
     bool isExpression = pn->pn_funbox->isExprBody();
 
     RootedValue id(cx);
     RootedAtom funcAtom(cx, func->explicitName());
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -48,20 +48,16 @@
 // code are installed via the std_functions JSFunctionSpec[] in
 // SelfHosting.cpp.
 //
 // Do not create an alias to a self-hosted builtin, otherwise it will be cloned
 // twice.
 //
 // Symbol is a bare constructor without properties or methods.
 var std_Symbol = Symbol;
-// WeakMap is a bare constructor without properties or methods.
-var std_WeakMap = WeakMap;
-// StopIteration is a bare constructor without properties or methods.
-var std_StopIteration = StopIteration;
 
 
 /********** List specification type **********/
 
 /* Spec: ECMAScript Language Specification, 5.1 edition, 8.8 */
 function List() {
     this.length = 0;
 }
--- a/js/src/builtin/WeakMapObject.cpp
+++ b/js/src/builtin/WeakMapObject.cpp
@@ -69,18 +69,18 @@ WeakMap_get_impl(JSContext* cx, const Ca
             return true;
         }
     }
 
     args.rval().setUndefined();
     return true;
 }
 
-bool
-js::WeakMap_get(JSContext* cx, unsigned argc, Value* vp)
+static bool
+WeakMap_get(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsWeakMap, WeakMap_get_impl>(cx, args);
 }
 
 MOZ_ALWAYS_INLINE bool
 WeakMap_delete_impl(JSContext* cx, const CallArgs& args)
 {
@@ -125,18 +125,18 @@ WeakMap_set_impl(JSContext* cx, const Ca
     Rooted<WeakMapObject*> map(cx, &args.thisv().toObject().as<WeakMapObject>());
 
     if (!WeakCollectionPutEntryInternal(cx, map, key, args.get(1)))
         return false;
     args.rval().set(args.thisv());
     return true;
 }
 
-bool
-js::WeakMap_set(JSContext* cx, unsigned argc, Value* vp)
+static bool
+WeakMap_set(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsWeakMap, WeakMap_set_impl>(cx, args);
 }
 
 bool
 WeakCollectionObject::nondeterministicGetKeys(JSContext* cx, Handle<WeakCollectionObject*> obj,
                                               MutableHandleObject ret)
@@ -290,18 +290,18 @@ const Class WeakMapObject::class_ = {
 static const JSFunctionSpec weak_map_methods[] = {
     JS_FN("has",    WeakMap_has, 1, 0),
     JS_FN("get",    WeakMap_get, 1, 0),
     JS_FN("delete", WeakMap_delete, 1, 0),
     JS_FN("set",    WeakMap_set, 2, 0),
     JS_FS_END
 };
 
-static JSObject*
-InitWeakMapClass(JSContext* cx, HandleObject obj, bool defineMembers)
+JSObject*
+js::InitWeakMapClass(JSContext* cx, HandleObject obj)
 {
     MOZ_ASSERT(obj->isNative());
 
     Handle<GlobalObject*> global = obj.as<GlobalObject>();
 
     RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
     if (!proto)
         return nullptr;
@@ -309,32 +309,17 @@ InitWeakMapClass(JSContext* cx, HandleOb
     RootedFunction ctor(cx, GlobalObject::createConstructor(cx, WeakMap_construct,
                                                             cx->names().WeakMap, 0));
     if (!ctor)
         return nullptr;
 
     if (!LinkConstructorAndPrototype(cx, ctor, proto))
         return nullptr;
 
-    if (defineMembers) {
-        if (!DefinePropertiesAndFunctions(cx, proto, nullptr, weak_map_methods))
-            return nullptr;
-        if (!DefineToStringTag(cx, proto, cx->names().WeakMap))
-            return nullptr;
-    }
+    if (!DefinePropertiesAndFunctions(cx, proto, nullptr, weak_map_methods))
+        return nullptr;
+    if (!DefineToStringTag(cx, proto, cx->names().WeakMap))
+        return nullptr;
 
     if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_WeakMap, ctor, proto))
         return nullptr;
     return proto;
 }
-
-JSObject*
-js::InitWeakMapClass(JSContext* cx, HandleObject obj)
-{
-    return InitWeakMapClass(cx, obj, true);
-}
-
-JSObject*
-js::InitBareWeakMapCtor(JSContext* cx, HandleObject obj)
-{
-    return InitWeakMapClass(cx, obj, false);
-}
-
--- a/js/src/builtin/WeakMapObject.h
+++ b/js/src/builtin/WeakMapObject.h
@@ -27,25 +27,14 @@ class WeakCollectionObject : public Nati
 };
 
 class WeakMapObject : public WeakCollectionObject
 {
   public:
     static const Class class_;
 };
 
-// WeakMap methods exposed so they can be installed in the self-hosting global.
-
-extern bool
-WeakMap_get(JSContext* cx, unsigned argc, Value* vp);
-
-extern bool
-WeakMap_set(JSContext* cx, unsigned argc, Value* vp);
-
-extern JSObject*
-InitBareWeakMapCtor(JSContext* cx, HandleObject obj);
-
 extern JSObject*
 InitWeakMapClass(JSContext* cx, HandleObject obj);
 
 } // namespace js
 
 #endif /* builtin_WeakMapObject_h */
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -725,46 +725,50 @@ frontend::CompileStandaloneFunction(JSCo
                                     const Maybe<uint32_t>& parameterListEnd,
                                     HandleScope enclosingScope /* = nullptr */)
 {
     RootedScope scope(cx, enclosingScope);
     if (!scope)
         scope = &cx->global()->emptyGlobalScope();
 
     BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, scope);
-    return compiler.compileStandaloneFunction(fun, NotGenerator, SyncFunction, parameterListEnd);
+    return compiler.compileStandaloneFunction(fun, GeneratorKind::NotGenerator, SyncFunction,
+                                              parameterListEnd);
 }
 
 bool
 frontend::CompileStandaloneGenerator(JSContext* cx, MutableHandleFunction fun,
                                      const ReadOnlyCompileOptions& options,
                                      JS::SourceBufferHolder& srcBuf,
                                      const Maybe<uint32_t>& parameterListEnd)
 {
     RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
 
     BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope);
-    return compiler.compileStandaloneFunction(fun, StarGenerator, SyncFunction, parameterListEnd);
+    return compiler.compileStandaloneFunction(fun, GeneratorKind::Generator, SyncFunction,
+                                              parameterListEnd);
 }
 
 bool
 frontend::CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun,
                                          const ReadOnlyCompileOptions& options,
                                          JS::SourceBufferHolder& srcBuf,
                                          const Maybe<uint32_t>& parameterListEnd)
 {
     RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
 
     BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope);
-    return compiler.compileStandaloneFunction(fun, NotGenerator, AsyncFunction, parameterListEnd);
+    return compiler.compileStandaloneFunction(fun, GeneratorKind::NotGenerator, AsyncFunction,
+                                              parameterListEnd);
 }
 
 bool
 frontend::CompileStandaloneAsyncGenerator(JSContext* cx, MutableHandleFunction fun,
                                           const ReadOnlyCompileOptions& options,
                                           JS::SourceBufferHolder& srcBuf,
                                           const Maybe<uint32_t>& parameterListEnd)
 {
     RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
 
     BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope);
-    return compiler.compileStandaloneFunction(fun, StarGenerator, AsyncFunction, parameterListEnd);
+    return compiler.compileStandaloneFunction(fun, GeneratorKind::Generator, AsyncFunction,
+                                              parameterListEnd);
 }
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -394,17 +394,17 @@ class BytecodeEmitter::EmitterScope : pu
         return true;
     }
 
     void updateFrameFixedSlots(BytecodeEmitter* bce, const BindingIter& bi) {
         nextFrameSlot_ = bi.nextFrameSlot();
         if (nextFrameSlot_ > bce->maxFixedSlots)
             bce->maxFixedSlots = nextFrameSlot_;
         MOZ_ASSERT_IF(bce->sc->isFunctionBox() &&
-                      (bce->sc->asFunctionBox()->isStarGenerator() ||
+                      (bce->sc->asFunctionBox()->isGenerator() ||
                        bce->sc->asFunctionBox()->isAsync()),
                       bce->maxFixedSlots == 0);
     }
 
     MOZ_MUST_USE bool putNameInCache(BytecodeEmitter* bce, JSAtom* name, NameLocation loc) {
         NameLocationMap& cache = *nameCache_;
         NameLocationMap::AddPtr p = cache.lookupForAdd(name);
         MOZ_ASSERT(!p);
@@ -4826,17 +4826,17 @@ BytecodeEmitter::isRunOnceLambda()
     if (!(parent && parent->emittingRunOnceLambda) &&
         (emitterMode != LazyFunction || !lazyScript->treatAsRunOnce()))
     {
         return false;
     }
 
     FunctionBox* funbox = sc->asFunctionBox();
     return !funbox->argumentsHasLocalBinding() &&
-           !funbox->isStarGenerator() &&
+           !funbox->isGenerator() &&
            !funbox->isAsync() &&
            !funbox->function()->explicitName();
 }
 
 bool
 BytecodeEmitter::emitYieldOp(JSOp op)
 {
     if (op == JSOP_FINALYIELDRVAL)
@@ -8234,17 +8234,17 @@ BytecodeEmitter::emitFunction(ParseNode*
 
     /* Non-hoisted functions simply emit their respective op. */
     if (!pn->functionIsHoisted()) {
         /* JSOP_LAMBDA_ARROW is always preceded by a new.target */
         MOZ_ASSERT(fun->isArrow() == (pn->getOp() == JSOP_LAMBDA_ARROW));
         if (funbox->isAsync()) {
             MOZ_ASSERT(!needsProto);
             return emitAsyncWrapper(index, funbox->needsHomeObject(), fun->isArrow(),
-                                    fun->isStarGenerator());
+                                    fun->isGenerator());
         }
 
         if (fun->isArrow()) {
             if (sc->allowNewTarget()) {
                 if (!emit1(JSOP_NEWTARGET))
                     return false;
             } else {
                 if (!emit1(JSOP_NULL))
@@ -8290,17 +8290,17 @@ BytecodeEmitter::emitFunction(ParseNode*
             if (!module->noteFunctionDeclaration(cx, name, fun))
                 return false;
         } else {
             MOZ_ASSERT(sc->isGlobalContext() || sc->isEvalContext());
             MOZ_ASSERT(pn->getOp() == JSOP_NOP);
             switchToPrologue();
             if (funbox->isAsync()) {
                 if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow(),
-                                      fun->isStarGenerator()))
+                                      fun->isGenerator()))
                 {
                     return false;
                 }
             } else {
                 if (!emitIndex32(JSOP_LAMBDA, index))
                     return false;
             }
             if (!emit1(JSOP_DEFFUN))
@@ -8309,22 +8309,22 @@ BytecodeEmitter::emitFunction(ParseNode*
                 return false;
             switchToMain();
         }
     } else {
         // For functions nested within functions and blocks, make a lambda and
         // initialize the binding name of the function in the current scope.
 
         bool isAsync = funbox->isAsync();
-        bool isStarGenerator = funbox->isStarGenerator();
-        auto emitLambda = [index, isAsync, isStarGenerator](BytecodeEmitter* bce,
-                                                            const NameLocation&, bool) {
+        bool isGenerator = funbox->isGenerator();
+        auto emitLambda = [index, isAsync, isGenerator](BytecodeEmitter* bce,
+                                                        const NameLocation&, bool) {
             if (isAsync) {
                 return bce->emitAsyncWrapper(index, /* needsHomeObject = */ false,
-                                             /* isArrow = */ false, isStarGenerator);
+                                             /* isArrow = */ false, isGenerator);
             }
             return bce->emitIndexOp(JSOP_LAMBDA, index);
         };
 
         if (!emitInitializeName(name, emitLambda))
             return false;
         if (!emit1(JSOP_POP))
             return false;
@@ -8350,17 +8350,17 @@ BytecodeEmitter::emitAsyncWrapperLambda(
             return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow,
-                                  bool isStarGenerator)
+                                  bool isGenerator)
 {
     // needsHomeObject can be true for propertyList for extended class.
     // In that case push both unwrapped and wrapped function, in order to
     // initialize home object of unwrapped function, and set wrapped function
     // as a property.
     //
     //   lambda       // unwrapped
     //   dup          // unwrapped unwrapped
@@ -8382,17 +8382,17 @@ BytecodeEmitter::emitAsyncWrapper(unsign
     //
     // needsHomeObject is false for other cases, push wrapped function only.
     if (!emitAsyncWrapperLambda(index, isArrow))
         return false;
     if (needsHomeObject) {
         if (!emit1(JSOP_DUP))
             return false;
     }
-    if (isStarGenerator) {
+    if (isGenerator) {
         if (!emit1(JSOP_TOASYNCGEN))
             return false;
     } else {
         if (!emit1(JSOP_TOASYNC))
             return false;
     }
     return true;
 }
@@ -8636,17 +8636,17 @@ BytecodeEmitter::emitReturn(ParseNode* p
     }
 
     /* Push a return value */
     if (ParseNode* pn2 = pn->pn_kid) {
         if (!emitTree(pn2))
             return false;
 
         bool isAsyncGenerator = sc->asFunctionBox()->isAsync() &&
-                                sc->asFunctionBox()->isStarGenerator();
+                                sc->asFunctionBox()->isGenerator();
         if (isAsyncGenerator) {
             if (!emitAwait())
                 return false;
         }
     } else {
         /* No explicit return value provided */
         if (!emit1(JSOP_UNDEFINED))
             return false;
@@ -8800,17 +8800,17 @@ BytecodeEmitter::emitAwait(ParseNode* pn
         return false;
     return emitAwait();
 }
 
 bool
 BytecodeEmitter::emitYieldStar(ParseNode* iter)
 {
     MOZ_ASSERT(sc->isFunctionBox());
-    MOZ_ASSERT(sc->asFunctionBox()->isStarGenerator());
+    MOZ_ASSERT(sc->asFunctionBox()->isGenerator());
 
     bool isAsyncGenerator = sc->asFunctionBox()->isAsync();
 
     if (!emitTree(iter))                                  // ITERABLE
         return false;
     if (isAsyncGenerator) {
         if (!emitAsyncIterator())                         // ITER
             return false;
@@ -9357,17 +9357,17 @@ BytecodeEmitter::emitSelfHostedCallFunct
 
     checkTypeSet(callOp);
     return true;
 }
 
 bool
 BytecodeEmitter::emitSelfHostedResumeGenerator(ParseNode* pn)
 {
-    // Syntax: resumeGenerator(gen, value, 'next'|'throw'|'close')
+    // Syntax: resumeGenerator(gen, value, 'next'|'throw'|'return')
     if (pn->pn_count != 4) {
         reportError(pn, JSMSG_MORE_ARGS_NEEDED, "resumeGenerator", "1", "s");
         return false;
     }
 
     ParseNode* funNode = pn->pn_head;  // The resumeGenerator node.
 
     ParseNode* genNode = funNode->pn_next;
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -645,17 +645,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     MOZ_MUST_USE bool emitAwait(ParseNode* pn);
 
     MOZ_MUST_USE bool emitPropLHS(ParseNode* pn);
     MOZ_MUST_USE bool emitPropOp(ParseNode* pn, JSOp op);
     MOZ_MUST_USE bool emitPropIncDec(ParseNode* pn);
 
     MOZ_MUST_USE bool emitAsyncWrapperLambda(unsigned index, bool isArrow);
     MOZ_MUST_USE bool emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow,
-                                       bool isStarGenerator);
+                                       bool isGenerator);
 
     MOZ_MUST_USE bool emitComputedPropertyName(ParseNode* computedPropName);
 
     // Emit bytecode to put operands for a JSOP_GETELEM/CALLELEM/SETELEM/DELELEM
     // opcode onto the stack in the right order. In the case of SETELEM, the
     // value to be assigned must already be pushed.
     enum class EmitElemOption { Get, Set, Call, IncDec, CompoundAssign, Ref };
     MOZ_MUST_USE bool emitElemOperands(ParseNode* pn, EmitElemOption opts);
--- a/js/src/frontend/ParseContext.h
+++ b/js/src/frontend/ParseContext.h
@@ -1,8 +1,14 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
 #ifndef frontend_ParseContext_h
 #define frontend_ParseContext_h
 
 #include "frontend/ErrorReporter.h"
 
 namespace js {
 
 namespace frontend {
@@ -615,31 +621,33 @@ class ParseContext : public Nestable<Par
     bool superScopeNeedsHomeObject() const {
         return superScopeNeedsHomeObject_;
     }
 
     bool useAsmOrInsideUseAsm() const {
         return sc_->isFunctionBox() && sc_->asFunctionBox()->useAsmOrInsideUseAsm();
     }
 
-    // An ES6 generator is marked as a "star generator" before its body is parsed.
+    // A generator is marked as a generator before its body is parsed.
     GeneratorKind generatorKind() const {
-        return sc_->isFunctionBox() ? sc_->asFunctionBox()->generatorKind() : NotGenerator;
+        return sc_->isFunctionBox()
+               ? sc_->asFunctionBox()->generatorKind()
+               : GeneratorKind::NotGenerator;
     }
 
-    bool isStarGenerator() const {
-        return generatorKind() == StarGenerator;
+    bool isGenerator() const {
+        return generatorKind() == GeneratorKind::Generator;
     }
 
     bool isAsync() const {
         return sc_->isFunctionBox() && sc_->asFunctionBox()->isAsync();
     }
 
     bool needsDotGeneratorName() const {
-        return isStarGenerator() || isAsync();
+        return isGenerator() || isAsync();
     }
 
     FunctionAsyncKind asyncKind() const {
         return isAsync() ? AsyncFunction : SyncFunction;
     }
 
     bool isArrowFunction() const {
         return sc_->isFunctionBox() && sc_->asFunctionBox()->function()->isArrow();
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -461,17 +461,17 @@ FunctionBox::FunctionBox(JSContext* cx, 
     functionNode(nullptr),
     bufStart(0),
     bufEnd(0),
     startLine(1),
     startColumn(0),
     toStringStart(toStringStart),
     toStringEnd(0),
     length(0),
-    generatorKindBits_(GeneratorKindAsBits(generatorKind)),
+    generatorKind_(GeneratorKindAsBit(generatorKind)),
     asyncKindBits_(AsyncKindAsBits(asyncKind)),
     isGenexpLambda(false),
     hasDestructuringArgs(false),
     hasParameterExprs(false),
     hasDirectEvalInParameterExpr(false),
     hasDuplicateParameters(false),
     useAsm(false),
     insideUseAsm(false),
@@ -2510,17 +2510,17 @@ Parser<SyntaxParseHandler, char16_t>::fi
 
     fun->initLazyScript(lazy);
     return true;
 }
 
 static YieldHandling
 GetYieldHandling(GeneratorKind generatorKind)
 {
-    if (generatorKind == NotGenerator)
+    if (generatorKind == GeneratorKind::NotGenerator)
         return YieldIsName;
     return YieldIsKeyword;
 }
 
 static AwaitHandling
 GetAwaitHandling(FunctionAsyncKind asyncKind)
 {
     if (asyncKind == SyncFunction)
@@ -2548,17 +2548,17 @@ Parser<FullParseHandler, char16_t>::stan
         MOZ_ASSERT(tt == TOK_ASYNC);
         if (!tokenStream.getToken(&tt, TokenStream::Operand))
             return null();
     }
     MOZ_ASSERT(tt == TOK_FUNCTION);
 
     if (!tokenStream.getToken(&tt))
         return null();
-    if (generatorKind == StarGenerator) {
+    if (generatorKind == GeneratorKind::Generator) {
         MOZ_ASSERT(tt == TOK_MUL);
         if (!tokenStream.getToken(&tt))
             return null();
     }
 
     // Skip function name, if present.
     if (TokenKindIsPossibleIdentifierName(tt)) {
         MOZ_ASSERT(tokenStream.currentName() == fun->explicitName());
@@ -2715,19 +2715,18 @@ Parser<ParseHandler, CharT>::functionBod
                 // the invalid parameter name at the correct source location.
                 pc->newDirectives->setStrict();
                 return null();
             }
         }
     } else {
         MOZ_ASSERT(type == ExpressionBody);
 
-        // Async functions are implemented as star generators, and star
-        // generators are assumed to be statement lists, to prepend initial
-        // `yield`.
+        // Async functions are implemented as generators, and generators are
+        // assumed to be statement lists, to prepend initial `yield`.
         Node stmtList = null();
         if (pc->isAsync()) {
             stmtList = handler.newStatementList(pos());
             if (!stmtList)
                 return null();
         }
 
         Node kid = assignExpr(inHandling, yieldHandling, TripledotProhibited);
@@ -2740,21 +2739,21 @@ Parser<ParseHandler, CharT>::functionBod
 
         if (pc->isAsync()) {
             handler.addStatementToList(stmtList, pn);
             pn = stmtList;
         }
     }
 
     switch (pc->generatorKind()) {
-      case NotGenerator:
+      case GeneratorKind::NotGenerator:
         MOZ_ASSERT_IF(!pc->isAsync(), pc->lastYieldOffset == startYieldOffset);
         break;
 
-      case StarGenerator:
+      case GeneratorKind::Generator:
         MOZ_ASSERT(kind != Arrow);
         MOZ_ASSERT(type == StatementListBody);
         break;
     }
 
     if (pc->needsDotGeneratorName()) {
         MOZ_ASSERT_IF(!pc->isAsync(), type == StatementListBody);
         if (!declareDotGeneratorName())
@@ -2790,27 +2789,26 @@ ParserBase::newFunction(HandleAtom atom,
 
     gc::AllocKind allocKind = gc::AllocKind::FUNCTION;
     JSFunction::Flags flags;
 #ifdef DEBUG
     bool isGlobalSelfHostedBuiltin = false;
 #endif
     switch (kind) {
       case Expression:
-        flags = (generatorKind == NotGenerator && asyncKind == SyncFunction
+        flags = (generatorKind == GeneratorKind::NotGenerator && asyncKind == SyncFunction
                  ? JSFunction::INTERPRETED_LAMBDA
                  : JSFunction::INTERPRETED_LAMBDA_GENERATOR_OR_ASYNC);
         break;
       case Arrow:
         flags = JSFunction::INTERPRETED_LAMBDA_ARROW;
         allocKind = gc::AllocKind::FUNCTION_EXTENDED;
         break;
       case Method:
-        MOZ_ASSERT(generatorKind == NotGenerator || generatorKind == StarGenerator);
-        flags = (generatorKind == NotGenerator && asyncKind == SyncFunction
+        flags = (generatorKind == GeneratorKind::NotGenerator && asyncKind == SyncFunction
                  ? JSFunction::INTERPRETED_METHOD
                  : JSFunction::INTERPRETED_METHOD_GENERATOR_OR_ASYNC);
         allocKind = gc::AllocKind::FUNCTION_EXTENDED;
         break;
       case ClassConstructor:
       case DerivedClassConstructor:
         flags = JSFunction::INTERPRETED_CLASS_CONSTRUCTOR;
         allocKind = gc::AllocKind::FUNCTION_EXTENDED;
@@ -2828,17 +2826,17 @@ ParserBase::newFunction(HandleAtom atom,
       default:
         MOZ_ASSERT(kind == Statement);
 #ifdef DEBUG
         if (options().selfHostingMode && !pc->isFunctionBox()) {
             isGlobalSelfHostedBuiltin = true;
             allocKind = gc::AllocKind::FUNCTION_EXTENDED;
         }
 #endif
-        flags = (generatorKind == NotGenerator && asyncKind == SyncFunction
+        flags = (generatorKind == GeneratorKind::NotGenerator && asyncKind == SyncFunction
                  ? JSFunction::INTERPRETED_NORMAL
                  : JSFunction::INTERPRETED_GENERATOR_OR_ASYNC);
     }
 
     // We store the async wrapper in a slot for later access.
     if (asyncKind == AsyncFunction)
         allocKind = gc::AllocKind::FUNCTION_EXTENDED;
 
@@ -3357,22 +3355,22 @@ Parser<ParseHandler, CharT>::functionDef
     // source extents are recorded and may be skipped.
     if (handler.canSkipLazyInnerFunctions()) {
         if (!skipLazyInnerFunction(pn, toStringStart, kind, tryAnnexB))
             return null();
         return pn;
     }
 
     RootedObject proto(context);
-    if (generatorKind == StarGenerator || asyncKind == AsyncFunction) {
+    if (generatorKind == GeneratorKind::Generator || asyncKind == AsyncFunction) {
         // If we are off thread, the generator meta-objects have
         // already been created by js::StartOffThreadParseTask, so cx will not
         // be necessary.
         JSContext* cx = context->helperThread() ? nullptr : context;
-        proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, context->global());
+        proto = GlobalObject::getOrCreateGeneratorFunctionPrototype(cx, context->global());
         if (!proto)
             return null();
     }
     RootedFunction fun(context, newFunction(funName, kind, generatorKind, asyncKind, proto));
     if (!fun)
         return null();
 
     // Speculatively parse using the directives of the parent parsing context.
@@ -3429,18 +3427,22 @@ Parser<FullParseHandler, char16_t>::tryS
                                                                 Directives* newDirectives)
 {
     // Try a syntax parse for this inner function.
     do {
         // If we're assuming this function is an IIFE, always perform a full
         // parse to avoid the overhead of a lazy syntax-only parse. Although
         // the prediction may be incorrect, IIFEs are common enough that it
         // pays off for lots of code.
-        if (pn->isLikelyIIFE() && generatorKind == NotGenerator && asyncKind == SyncFunction)
+        if (pn->isLikelyIIFE() &&
+            generatorKind == GeneratorKind::NotGenerator &&
+            asyncKind == SyncFunction)
+        {
             break;
+        }
 
         if (!syntaxParser_)
             break;
 
         UsedNameTracker::RewindToken token = usedNames.getRewindToken();
 
         // Move the syntax parser to the current position in the stream.
         TokenStream::Position position(keepAtoms);
@@ -3707,17 +3709,17 @@ Parser<ParseHandler, CharT>::functionFor
     // Parse the function body.
     FunctionBodyType bodyType = StatementListBody;
     TokenKind tt;
     if (!tokenStream.getToken(&tt, TokenStream::Operand))
         return false;
     uint32_t openedPos = 0;
     if (tt != TOK_LC) {
         if (kind != Arrow) {
-            if (funbox->isStarGenerator() || funbox->isAsync() || kind == Method ||
+            if (funbox->isGenerator() || funbox->isAsync() || kind == Method ||
                 kind == GetterNoExpressionClosure || kind == SetterNoExpressionClosure ||
                 IsConstructorKind(kind))
             {
                 error(JSMSG_CURLY_BEFORE_BODY);
                 return false;
             }
 
 #if JS_HAS_EXPR_CLOSURES
@@ -3836,19 +3838,19 @@ Parser<ParseHandler, CharT>::functionStm
             return null();
         }
     }
 
     TokenKind tt;
     if (!tokenStream.getToken(&tt))
         return null();
 
-    GeneratorKind generatorKind = NotGenerator;
+    GeneratorKind generatorKind = GeneratorKind::NotGenerator;
     if (tt == TOK_MUL) {
-        generatorKind = StarGenerator;
+        generatorKind = GeneratorKind::Generator;
         if (!tokenStream.getToken(&tt))
             return null();
     }
 
     RootedPropertyName name(context);
     if (TokenKindIsPossibleIdentifier(tt)) {
         name = bindingIdentifier(yieldHandling);
         if (!name)
@@ -3863,19 +3865,21 @@ Parser<ParseHandler, CharT>::functionStm
     }
 
     // Note the declared name and check for early errors.
     DeclarationKind kind;
     if (declaredInStmt) {
         MOZ_ASSERT(declaredInStmt->kind() != StatementKind::Label);
         MOZ_ASSERT(StatementKindIsBraced(declaredInStmt->kind()));
 
-        kind = !pc->sc()->strict() && generatorKind == NotGenerator && asyncKind == SyncFunction
-               ? DeclarationKind::SloppyLexicalFunction
-               : DeclarationKind::LexicalFunction;
+        kind = (!pc->sc()->strict() &&
+                generatorKind == GeneratorKind::NotGenerator &&
+                asyncKind == SyncFunction)
+                ? DeclarationKind::SloppyLexicalFunction
+                : DeclarationKind::LexicalFunction;
     } else {
         kind = pc->atModuleLevel()
                ? DeclarationKind::ModuleBodyLevelFunction
                : DeclarationKind::BodyLevelFunction;
     }
 
     if (!noteDeclaredName(name, kind, pos()))
         return null();
@@ -3901,23 +3905,23 @@ Parser<ParseHandler, CharT>::functionStm
 template <class ParseHandler, typename CharT>
 typename ParseHandler::Node
 Parser<ParseHandler, CharT>::functionExpr(uint32_t toStringStart, InvokedPrediction invoked,
                                           FunctionAsyncKind asyncKind)
 {
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION));
 
     AutoAwaitIsKeyword<Parser> awaitIsKeyword(this, GetAwaitHandling(asyncKind));
-    GeneratorKind generatorKind = NotGenerator;
+    GeneratorKind generatorKind = GeneratorKind::NotGenerator;
     TokenKind tt;
     if (!tokenStream.getToken(&tt))
         return null();
 
     if (tt == TOK_MUL) {
-        generatorKind = StarGenerator;
+        generatorKind = GeneratorKind::Generator;
         if (!tokenStream.getToken(&tt))
             return null();
     }
 
     YieldHandling yieldHandling = GetYieldHandling(generatorKind);
 
     RootedPropertyName name(context);
     if (TokenKindIsPossibleIdentifier(tt)) {
@@ -6576,66 +6580,58 @@ Parser<ParseHandler, CharT>::returnState
 
 template <class ParseHandler, typename CharT>
 typename ParseHandler::Node
 Parser<ParseHandler, CharT>::yieldExpression(InHandling inHandling)
 {
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_YIELD));
     uint32_t begin = pos().begin;
 
-    switch (pc->generatorKind()) {
-      case StarGenerator:
-      {
-        MOZ_ASSERT(pc->isFunctionBox());
-
-        pc->lastYieldOffset = begin;
-
-        Node exprNode;
-        ParseNodeKind kind = PNK_YIELD;
-        TokenKind tt = TOK_EOF;
-        if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand))
-            return null();
-        switch (tt) {
-          // TOK_EOL is special; it implements the [no LineTerminator here]
-          // quirk in the grammar.
-          case TOK_EOL:
-          // The rest of these make up the complete set of tokens that can
-          // appear after any of the places where AssignmentExpression is used
-          // throughout the grammar.  Conveniently, none of them can also be the
-          // start an expression.
-          case TOK_EOF:
-          case TOK_SEMI:
-          case TOK_RC:
-          case TOK_RB:
-          case TOK_RP:
-          case TOK_COLON:
-          case TOK_COMMA:
-          case TOK_IN:
-            // No value.
-            exprNode = null();
-            tokenStream.addModifierException(TokenStream::NoneIsOperand);
-            break;
-          case TOK_MUL:
-            kind = PNK_YIELD_STAR;
-            tokenStream.consumeKnownToken(TOK_MUL, TokenStream::Operand);
-            MOZ_FALLTHROUGH;
-          default:
-            exprNode = assignExpr(inHandling, YieldIsKeyword, TripledotProhibited);
-            if (!exprNode)
-                return null();
-        }
-        if (kind == PNK_YIELD_STAR)
-            return handler.newYieldStarExpression(begin, exprNode);
-        return handler.newYieldExpression(begin, exprNode);
-      }
-      case NotGenerator:
+    MOZ_ASSERT(pc->isGenerator());
+    MOZ_ASSERT(pc->isFunctionBox());
+
+    pc->lastYieldOffset = begin;
+
+    Node exprNode;
+    ParseNodeKind kind = PNK_YIELD;
+    TokenKind tt = TOK_EOF;
+    if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand))
+        return null();
+    switch (tt) {
+      // TOK_EOL is special; it implements the [no LineTerminator here]
+      // quirk in the grammar.
+      case TOK_EOL:
+      // The rest of these make up the complete set of tokens that can
+      // appear after any of the places where AssignmentExpression is used
+      // throughout the grammar.  Conveniently, none of them can also be the
+      // start an expression.
+      case TOK_EOF:
+      case TOK_SEMI:
+      case TOK_RC:
+      case TOK_RB:
+      case TOK_RP:
+      case TOK_COLON:
+      case TOK_COMMA:
+      case TOK_IN:
+        // No value.
+        exprNode = null();
+        tokenStream.addModifierException(TokenStream::NoneIsOperand);
         break;
-    }
-
-    MOZ_CRASH("yieldExpr");
+      case TOK_MUL:
+        kind = PNK_YIELD_STAR;
+        tokenStream.consumeKnownToken(TOK_MUL, TokenStream::Operand);
+        MOZ_FALLTHROUGH;
+      default:
+        exprNode = assignExpr(inHandling, YieldIsKeyword, TripledotProhibited);
+        if (!exprNode)
+            return null();
+    }
+    if (kind == PNK_YIELD_STAR)
+        return handler.newYieldStarExpression(begin, exprNode);
+    return handler.newYieldExpression(begin, exprNode);
 }
 
 template <class ParseHandler, typename CharT>
 typename ParseHandler::Node
 Parser<ParseHandler, CharT>::withStatement(YieldHandling yieldHandling)
 {
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_WITH));
     uint32_t begin = pos().begin;
@@ -8109,17 +8105,17 @@ Parser<ParseHandler, CharT>::assignExpr(
                 tokenStream.ungetToken();
         }
 
         Node pn = handler.newArrowFunction(pos());
         if (!pn)
             return null();
 
         return functionDefinition(pn, toStringStart, inHandling, yieldHandling, nullptr,
-                                  Arrow, NotGenerator, asyncKind);
+                                  Arrow, GeneratorKind::NotGenerator, asyncKind);
       }
 
       default:
         MOZ_ASSERT(!tokenStream.isCurrentTokenAssignment());
         if (!possibleError) {
             if (!possibleErrorInner.checkForExpressionError())
                 return null();
         } else {
@@ -8379,29 +8375,29 @@ Parser<ParseHandler, CharT>::generatorCo
 
     ParseContext* outerpc = pc;
 
     // If we are off thread, the generator meta-objects have
     // already been created by js::StartOffThreadParseScript, so cx will not
     // be necessary.
     RootedObject proto(context);
     JSContext* cx = context->helperThread() ? nullptr : context;
-    proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, context->global());
+    proto = GlobalObject::getOrCreateGeneratorFunctionPrototype(cx, context->global());
     if (!proto)
         return null();
 
     RootedFunction fun(context, newFunction(/* atom = */ nullptr, Expression,
-                                            StarGenerator, SyncFunction, proto));
+                                            GeneratorKind::Generator, SyncFunction, proto));
     if (!fun)
         return null();
 
     // Create box for fun->object early to root it.
     Directives directives(/* strict = */ outerpc->sc()->strict());
     FunctionBox* genFunbox = newFunctionBox(genfn, fun, /* toStringStart = */ begin, directives,
-                                            StarGenerator, SyncFunction);
+                                            GeneratorKind::Generator, SyncFunction);
     if (!genFunbox)
         return null();
     genFunbox->isGenexpLambda = true;
     genFunbox->initWithEnclosingParseContext(outerpc, Expression);
     genFunbox->setStart(tokenStream, begin);
 
     SourceParseContext genpc(this, genFunbox, /* newDirectives = */ nullptr);
     if (!genpc.init())
@@ -8418,17 +8414,17 @@ Parser<ParseHandler, CharT>::generatorCo
 
     if (!declareDotGeneratorName())
         return null();
 
     Node body = handler.newStatementList(TokenPos(begin, pos().end));
     if (!body)
         return null();
 
-    Node comp = comprehension(StarGenerator);
+    Node comp = comprehension(GeneratorKind::Generator);
     if (!comp)
         return null();
 
     MUST_MATCH_TOKEN_MOD(TOK_RP, TokenStream::Operand, JSMSG_PAREN_IN_PAREN);
 
     uint32_t end = pos().end;
     handler.setBeginPosition(comp, begin);
     handler.setEndPosition(comp, end);
@@ -8579,20 +8575,20 @@ Parser<ParseHandler, CharT>::comprehensi
         return comprehensionIf(comprehensionKind);
 
     uint32_t begin = pos().begin;
 
     Node bodyExpr = assignExpr(InAllowed, YieldIsKeyword, TripledotProhibited);
     if (!bodyExpr)
         return null();
 
-    if (comprehensionKind == NotGenerator)
+    if (comprehensionKind == GeneratorKind::NotGenerator)
         return handler.newArrayPush(begin, bodyExpr);
 
-    MOZ_ASSERT(comprehensionKind == StarGenerator);
+    MOZ_ASSERT(comprehensionKind == GeneratorKind::Generator);
     Node yieldExpr = handler.newYieldExpression(begin, bodyExpr);
     if (!yieldExpr)
         return null();
     yieldExpr = handler.parenthesize(yieldExpr);
 
     return handler.newExprStatement(yieldExpr, pos().end);
 }
 
@@ -8605,29 +8601,29 @@ Parser<ParseHandler, CharT>::comprehensi
     MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR));
 
     uint32_t startYieldOffset = pc->lastYieldOffset;
 
     Node body = comprehensionFor(comprehensionKind);
     if (!body)
         return null();
 
-    if (comprehensionKind != NotGenerator && pc->lastYieldOffset != startYieldOffset) {
+    if (comprehensionKind == GeneratorKind::Generator && pc->lastYieldOffset != startYieldOffset) {
         errorAt(pc->lastYieldOffset, JSMSG_BAD_GENEXP_BODY, js_yield_str);
         return null();
     }
 
     return body;
 }
 
 template <class ParseHandler, typename CharT>
 typename ParseHandler::Node
 Parser<ParseHandler, CharT>::arrayComprehension(uint32_t begin)
 {
-    Node inner = comprehension(NotGenerator);
+    Node inner = comprehension(GeneratorKind::NotGenerator);
     if (!inner)
         return null();
 
     MUST_MATCH_TOKEN_MOD(TOK_RB, TokenStream::Operand, JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION);
 
     Node comp = handler.newList(PNK_ARRAYCOMP, inner);
     if (!comp)
         return null();
@@ -9909,18 +9905,18 @@ Parser<ParseHandler, CharT>::methodDefin
         break;
 
       default:
         MOZ_CRASH("unexpected property type");
     }
 
     GeneratorKind generatorKind = (propType == PropertyType::GeneratorMethod ||
                                    propType == PropertyType::AsyncGeneratorMethod)
-                                  ? StarGenerator
-                                  : NotGenerator;
+                                  ? GeneratorKind::Generator
+                                  : GeneratorKind::NotGenerator;
 
     FunctionAsyncKind asyncKind = (propType == PropertyType::AsyncMethod ||
                                    propType == PropertyType::AsyncGeneratorMethod)
                                   ? AsyncFunction
                                   : SyncFunction;
 
     YieldHandling yieldHandling = GetYieldHandling(generatorKind);
 
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -167,18 +167,18 @@ class ParserBase : public StrictModeGett
                UsedNameTracker& usedNames, LazyScript* lazyOuterFunction);
     ~ParserBase();
 
     const char* getFilename() const { return tokenStream.getFilename(); }
     JSVersion versionNumber() const { return tokenStream.versionNumber(); }
     TokenPos pos() const { return tokenStream.currentToken().pos; }
 
     // Determine whether |yield| is a valid name in the current context.
-    bool yieldExpressionsSupported() {
-        return pc->isStarGenerator();
+    bool yieldExpressionsSupported() const {
+        return pc->isGenerator();
     }
 
     virtual bool strictMode() { return pc->sc()->strict(); }
     bool setLocalStrictMode(bool strict) {
         MOZ_ASSERT(tokenStream.debugHasNoLookahead());
         return pc->sc()->setLocalStrictMode(strict);
     }
 
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -399,17 +399,17 @@ class FunctionBox : public ObjectBox, pu
     uint32_t        bufStart;
     uint32_t        bufEnd;
     uint32_t        startLine;
     uint32_t        startColumn;
     uint32_t        toStringStart;
     uint32_t        toStringEnd;
     uint16_t        length;
 
-    uint8_t         generatorKindBits_;     /* The GeneratorKind of this function. */
+    uint8_t         generatorKind_;         /* The GeneratorKind of this function. */
     uint8_t         asyncKindBits_;         /* The FunctionAsyncKind of this function. */
 
     bool            isGenexpLambda:1;       /* lambda from generator expression */
     bool            hasDestructuringArgs:1; /* parameter list contains destructuring expression */
     bool            hasParameterExprs:1;    /* parameter list contains expressions */
     bool            hasDirectEvalInParameterExpr:1; /* parameter list contains direct eval */
     bool            hasDuplicateParameters:1; /* parameter list contains duplicate names */
     bool            useAsm:1;               /* see useAsmOrInsideUseAsm */
@@ -463,17 +463,17 @@ class FunctionBox : public ObjectBox, pu
                       enclosingScope_ == function()->lazyScript()->enclosingScope());
         return enclosingScope_;
     }
 
     bool needsCallObjectRegardlessOfBindings() const {
         return hasExtensibleScope() ||
                needsHomeObject() ||
                isDerivedClassConstructor() ||
-               isStarGenerator() ||
+               isGenerator() ||
                isAsync();
     }
 
     bool hasExtraBodyVarScope() const {
         return hasParameterExprs &&
                (extraVarScopeBindings_ ||
                 needsExtraBodyVarEnvironmentRegardlessOfBindings());
     }
@@ -482,51 +482,43 @@ class FunctionBox : public ObjectBox, pu
         MOZ_ASSERT(hasParameterExprs);
         return hasExtensibleScope() || needsDotGeneratorName();
     }
 
     bool isLikelyConstructorWrapper() const {
         return usesArguments && usesApply && usesThis && !usesReturn;
     }
 
-    GeneratorKind generatorKind() const { return GeneratorKindFromBits(generatorKindBits_); }
-    bool isStarGenerator() const { return generatorKind() == StarGenerator; }
+    GeneratorKind generatorKind() const { return GeneratorKindFromBit(generatorKind_); }
+    bool isGenerator() const { return generatorKind() == GeneratorKind::Generator; }
     FunctionAsyncKind asyncKind() const { return AsyncKindFromBits(asyncKindBits_); }
 
     bool needsFinalYield() const {
-        return isStarGenerator() || isAsync();
+        return isGenerator() || isAsync();
     }
     bool needsDotGeneratorName() const {
-        return isStarGenerator() || isAsync();
+        return isGenerator() || isAsync();
     }
     bool needsIteratorResult() const {
-        return isStarGenerator();
+        return isGenerator();
     }
 
     bool isAsync() const { return asyncKind() == AsyncFunction; }
     bool isArrow() const { return function()->isArrow(); }
 
     bool hasRest() const { return hasRest_; }
     void setHasRest() {
         hasRest_ = true;
     }
 
     bool isExprBody() const { return isExprBody_; }
     void setIsExprBody() {
         isExprBody_ = true;
     }
 
-    void setGeneratorKind(GeneratorKind kind) {
-        // A generator kind can be set at initialization, or when "yield" is
-        // first seen.  In both cases the transition can only happen from
-        // NotGenerator.
-        MOZ_ASSERT(!isStarGenerator());
-        generatorKindBits_ = GeneratorKindAsBits(kind);
-    }
-
     bool hasExtensibleScope()        const { return funCxFlags.hasExtensibleScope; }
     bool hasThisBinding()            const { return funCxFlags.hasThisBinding; }
     bool argumentsHasLocalBinding()  const { return funCxFlags.argumentsHasLocalBinding; }
     bool definitelyNeedsArgsObj()    const { return funCxFlags.definitelyNeedsArgsObj; }
     bool needsHomeObject()           const { return funCxFlags.needsHomeObject; }
     bool isDerivedClassConstructor() const { return funCxFlags.isDerivedClassConstructor; }
     bool hasInnerFunctions()         const { return funCxFlags.hasInnerFunctions; }
 
@@ -616,16 +608,16 @@ SharedContext::asModuleContext()
 // and invalidate DebugScope proxies for unaliased locals in a generator
 // frame, as the generator frame will be copied out to the heap and released
 // only by GC.
 inline bool
 SharedContext::allBindingsClosedOver()
 {
     return bindingsAccessedDynamically() ||
            (isFunctionBox() &&
-            (asFunctionBox()->isStarGenerator() ||
+            (asFunctionBox()->isGenerator() ||
              asFunctionBox()->isAsync()));
 }
 
 } // namespace frontend
 } // namespace js
 
 #endif /* frontend_SharedContext_h */
--- a/js/src/gc/AllocKind.h
+++ b/js/src/gc/AllocKind.h
@@ -17,102 +17,90 @@
 
 #include <stdint.h>
 
 #include "js/TraceKind.h"
 
 namespace js {
 namespace gc {
 
-/* The GC allocation kinds. */
-// FIXME: uint8_t would make more sense for the underlying type, but causes
-// miscompilations in GCC (fixed in 4.8.5 and 4.9.3). See also bug 1143966.
-enum class AllocKind {
-    FIRST,
-    OBJECT_FIRST = FIRST,
-    FUNCTION = FIRST,
-    FUNCTION_EXTENDED,
-    OBJECT0,
-    OBJECT0_BACKGROUND,
-    OBJECT2,
-    OBJECT2_BACKGROUND,
-    OBJECT4,
-    OBJECT4_BACKGROUND,
-    OBJECT8,
-    OBJECT8_BACKGROUND,
-    OBJECT12,
-    OBJECT12_BACKGROUND,
-    OBJECT16,
-    OBJECT16_BACKGROUND,
-    OBJECT_LIMIT,
-    OBJECT_LAST = OBJECT_LIMIT - 1,
-    SCRIPT,
-    LAZY_SCRIPT,
-    SHAPE,
-    ACCESSOR_SHAPE,
-    BASE_SHAPE,
-    OBJECT_GROUP,
-    FAT_INLINE_STRING,
-    STRING,
-    EXTERNAL_STRING,
-    FAT_INLINE_ATOM,
-    ATOM,
-    SYMBOL,
-    JITCODE,
-    SCOPE,
-    REGEXP_SHARED,
-    LIMIT,
-    LAST = LIMIT - 1
-};
+// The GC allocation kinds.
+//
+// These are defined by macros which enumerate the different allocation kinds
+// and supply the following information:
+//
+//  - the corresponding AllocKind
+//  - their JS::TraceKind
+//  - their C++ base type
+//  - a C++ type of the correct size
+//  - whether they can be finalized on the background thread
+//  - whether they can be allocated in the nursery
 
-// Macro to enumerate the different allocation kinds supplying information about
-// the trace kind, C++ type and allocation size.
 #define FOR_EACH_OBJECT_ALLOCKIND(D) \
- /* AllocKind              TraceKind      TypeName           SizedType */ \
-    D(FUNCTION,            Object,        JSObject,          JSFunction) \
-    D(FUNCTION_EXTENDED,   Object,        JSObject,          FunctionExtended) \
-    D(OBJECT0,             Object,        JSObject,          JSObject_Slots0) \
-    D(OBJECT0_BACKGROUND,  Object,        JSObject,          JSObject_Slots0) \
-    D(OBJECT2,             Object,        JSObject,          JSObject_Slots2) \
-    D(OBJECT2_BACKGROUND,  Object,        JSObject,          JSObject_Slots2) \
-    D(OBJECT4,             Object,        JSObject,          JSObject_Slots4) \
-    D(OBJECT4_BACKGROUND,  Object,        JSObject,          JSObject_Slots4) \
-    D(OBJECT8,             Object,        JSObject,          JSObject_Slots8) \
-    D(OBJECT8_BACKGROUND,  Object,        JSObject,          JSObject_Slots8) \
-    D(OBJECT12,            Object,        JSObject,          JSObject_Slots12) \
-    D(OBJECT12_BACKGROUND, Object,        JSObject,          JSObject_Slots12) \
-    D(OBJECT16,            Object,        JSObject,          JSObject_Slots16) \
-    D(OBJECT16_BACKGROUND, Object,        JSObject,          JSObject_Slots16)
+ /* AllocKind              TraceKind     TypeName           SizedType          BGFinal Nursery */ \
+    D(FUNCTION,            Object,       JSObject,          JSFunction,        true,   true)  \
+    D(FUNCTION_EXTENDED,   Object,       JSObject,          FunctionExtended,  true,   true)  \
+    D(OBJECT0,             Object,       JSObject,          JSObject_Slots0,   false,  false) \
+    D(OBJECT0_BACKGROUND,  Object,       JSObject,          JSObject_Slots0,   true,   true)  \
+    D(OBJECT2,             Object,       JSObject,          JSObject_Slots2,   false,  false) \
+    D(OBJECT2_BACKGROUND,  Object,       JSObject,          JSObject_Slots2,   true,   true)  \
+    D(OBJECT4,             Object,       JSObject,          JSObject_Slots4,   false,  false) \
+    D(OBJECT4_BACKGROUND,  Object,       JSObject,          JSObject_Slots4,   true,   true)  \
+    D(OBJECT8,             Object,       JSObject,          JSObject_Slots8,   false,  false) \
+    D(OBJECT8_BACKGROUND,  Object,       JSObject,          JSObject_Slots8,   true,   true)  \
+    D(OBJECT12,            Object,       JSObject,          JSObject_Slots12,  false,  false) \
+    D(OBJECT12_BACKGROUND, Object,       JSObject,          JSObject_Slots12,  true,   true)  \
+    D(OBJECT16,            Object,       JSObject,          JSObject_Slots16,  false,  false) \
+    D(OBJECT16_BACKGROUND, Object,       JSObject,          JSObject_Slots16,  true,   true)
 
 #define FOR_EACH_NONOBJECT_ALLOCKIND(D) \
- /* AllocKind              TraceKind      TypeName           SizedType */ \
-    D(SCRIPT,              Script,        JSScript,          JSScript) \
-    D(LAZY_SCRIPT,         LazyScript,    js::LazyScript,    js::LazyScript) \
-    D(SHAPE,               Shape,         js::Shape,         js::Shape) \
-    D(ACCESSOR_SHAPE,      Shape,         js::AccessorShape, js::AccessorShape) \
-    D(BASE_SHAPE,          BaseShape,     js::BaseShape,     js::BaseShape) \
-    D(OBJECT_GROUP,        ObjectGroup,   js::ObjectGroup,   js::ObjectGroup) \
-    D(FAT_INLINE_STRING,   String,        JSFatInlineString, JSFatInlineString) \
-    D(STRING,              String,        JSString,          JSString) \
-    D(EXTERNAL_STRING,     String,        JSExternalString,  JSExternalString) \
-    D(FAT_INLINE_ATOM,     String,        js::FatInlineAtom, js::FatInlineAtom) \
-    D(ATOM,                String,        js::NormalAtom,    js::NormalAtom) \
-    D(SYMBOL,              Symbol,        JS::Symbol,        JS::Symbol) \
-    D(JITCODE,             JitCode,       js::jit::JitCode,  js::jit::JitCode) \
-    D(SCOPE,               Scope,         js::Scope,         js::Scope) \
-    D(REGEXP_SHARED,       RegExpShared,  js::RegExpShared,  js::RegExpShared)
+ /* AllocKind              TraceKind     TypeName           SizedType          BGFinal Nursery */ \
+    D(SCRIPT,              Script,       JSScript,          JSScript,          false,  false) \
+    D(LAZY_SCRIPT,         LazyScript,   js::LazyScript,    js::LazyScript,    true,   false) \
+    D(SHAPE,               Shape,        js::Shape,         js::Shape,         true,   false) \
+    D(ACCESSOR_SHAPE,      Shape,        js::AccessorShape, js::AccessorShape, true,   false) \
+    D(BASE_SHAPE,          BaseShape,    js::BaseShape,     js::BaseShape,     true,   false) \
+    D(OBJECT_GROUP,        ObjectGroup,  js::ObjectGroup,   js::ObjectGroup,   true,   false) \
+    D(FAT_INLINE_STRING,   String,       JSFatInlineString, JSFatInlineString, true,   false) \
+    D(STRING,              String,       JSString,          JSString,          true,   false) \
+    D(EXTERNAL_STRING,     String,       JSExternalString,  JSExternalString,  true,   false) \
+    D(FAT_INLINE_ATOM,     String,       js::FatInlineAtom, js::FatInlineAtom, true,   false) \
+    D(ATOM,                String,       js::NormalAtom,    js::NormalAtom,    true,   false) \
+    D(SYMBOL,              Symbol,       JS::Symbol,        JS::Symbol,        true,   false) \
+    D(JITCODE,             JitCode,      js::jit::JitCode,  js::jit::JitCode,  false,  false) \
+    D(SCOPE,               Scope,        js::Scope,         js::Scope,         true,   false) \
+    D(REGEXP_SHARED,       RegExpShared, js::RegExpShared,  js::RegExpShared,  true,   false)
 
 #define FOR_EACH_ALLOCKIND(D) \
     FOR_EACH_OBJECT_ALLOCKIND(D) \
     FOR_EACH_NONOBJECT_ALLOCKIND(D)
 
-static_assert(int(AllocKind::FIRST) == 0, "Various places depend on AllocKind starting at 0, "
-                                          "please audit them carefully!");
-static_assert(int(AllocKind::OBJECT_FIRST) == 0, "Various places depend on AllocKind::OBJECT_FIRST "
-                                                 "being 0, please audit them carefully!");
+enum class AllocKind : uint8_t {
+#define DEFINE_ALLOC_KIND(allocKind, _1, _2, _3, _4, _5) allocKind,
+
+    FOR_EACH_OBJECT_ALLOCKIND(DEFINE_ALLOC_KIND)
+
+    OBJECT_LIMIT,
+    OBJECT_LAST = OBJECT_LIMIT - 1,
+
+    FOR_EACH_NONOBJECT_ALLOCKIND(DEFINE_ALLOC_KIND)
+
+    LIMIT,
+    LAST = LIMIT - 1,
+
+    FIRST = 0,
+    OBJECT_FIRST = FUNCTION // Hardcoded to first object kind.
+
+#undef DEFINE_ALLOC_KIND
+};
+
+static_assert(int(AllocKind::FIRST) == 0,
+              "Various places depend on AllocKind starting at 0");
+static_assert(int(AllocKind::OBJECT_FIRST) == 0,
+              "OBJECT_FIRST must be defined as the first object kind");
 
 inline bool
 IsAllocKind(AllocKind kind)
 {
     return kind >= AllocKind::FIRST && kind <= AllocKind::LIMIT;
 }
 
 inline bool
@@ -168,17 +156,17 @@ template<typename ValueType> using AllAl
 // with each index corresponding to a particular object alloc kind.
 template<typename ValueType> using ObjectAllocKindArray =
     mozilla::EnumeratedArray<AllocKind, AllocKind::OBJECT_LIMIT, ValueType>;
 
 static inline JS::TraceKind
 MapAllocToTraceKind(AllocKind kind)
 {
     static const JS::TraceKind map[] = {
-#define EXPAND_ELEMENT(allocKind, traceKind, type, sizedType) \
+#define EXPAND_ELEMENT(allocKind, traceKind, type, sizedType, bgFinal, nursery) \
         JS::TraceKind::traceKind,
 FOR_EACH_ALLOCKIND(EXPAND_ELEMENT)
 #undef EXPAND_ELEMENT
     };
 
     static_assert(MOZ_ARRAY_LENGTH(map) == size_t(AllocKind::LIMIT),
                   "AllocKind-to-TraceKind mapping must be in sync");
     return map[size_t(kind)];
@@ -190,86 +178,38 @@ FOR_EACH_ALLOCKIND(EXPAND_ELEMENT)
  */
 static const size_t MAX_BACKGROUND_FINALIZE_KINDS =
     size_t(AllocKind::LIMIT) - size_t(AllocKind::OBJECT_LIMIT) / 2;
 
 static inline bool
 IsNurseryAllocable(AllocKind kind)
 {
     MOZ_ASSERT(IsValidAllocKind(kind));
+
     static const bool map[] = {
-        true,      /* AllocKind::FUNCTION */
-        true,      /* AllocKind::FUNCTION_EXTENDED */
-        false,     /* AllocKind::OBJECT0 */
-        true,      /* AllocKind::OBJECT0_BACKGROUND */
-        false,     /* AllocKind::OBJECT2 */
-        true,      /* AllocKind::OBJECT2_BACKGROUND */
-        false,     /* AllocKind::OBJECT4 */
-        true,      /* AllocKind::OBJECT4_BACKGROUND */
-        false,     /* AllocKind::OBJECT8 */
-        true,      /* AllocKind::OBJECT8_BACKGROUND */
-        false,     /* AllocKind::OBJECT12 */
-        true,      /* AllocKind::OBJECT12_BACKGROUND */
-        false,     /* AllocKind::OBJECT16 */
-        true,      /* AllocKind::OBJECT16_BACKGROUND */
-        false,     /* AllocKind::SCRIPT */
-        false,     /* AllocKind::LAZY_SCRIPT */
-        false,     /* AllocKind::SHAPE */
-        false,     /* AllocKind::ACCESSOR_SHAPE */
-        false,     /* AllocKind::BASE_SHAPE */
-        false,     /* AllocKind::OBJECT_GROUP */
-        false,     /* AllocKind::FAT_INLINE_STRING */
-        false,     /* AllocKind::STRING */
-        false,     /* AllocKind::EXTERNAL_STRING */
-        false,     /* AllocKind::FAT_INLINE_ATOM */
-        false,     /* AllocKind::ATOM */
-        false,     /* AllocKind::SYMBOL */
-        false,     /* AllocKind::JITCODE */
-        false,     /* AllocKind::SCOPE */
-        false,     /* AllocKind::REGEXP_SHARED */
+#define DEFINE_NURSERY_ALLOCABLE(_1, _2, _3, _4, _5, nursery) nursery,
+        FOR_EACH_ALLOCKIND(DEFINE_NURSERY_ALLOCABLE)
+#undef DEFINE_NURSERY_ALLOCABLE
     };
+
     JS_STATIC_ASSERT(JS_ARRAY_LENGTH(map) == size_t(AllocKind::LIMIT));
     return map[size_t(kind)];
 }
 
 static inline bool
 IsBackgroundFinalized(AllocKind kind)
 {
     MOZ_ASSERT(IsValidAllocKind(kind));
+
     static const bool map[] = {
-        true,      /* AllocKind::FUNCTION */
-        true,      /* AllocKind::FUNCTION_EXTENDED */
-        false,     /* AllocKind::OBJECT0 */
-        true,      /* AllocKind::OBJECT0_BACKGROUND */
-        false,     /* AllocKind::OBJECT2 */
-        true,      /* AllocKind::OBJECT2_BACKGROUND */
-        false,     /* AllocKind::OBJECT4 */
-        true,      /* AllocKind::OBJECT4_BACKGROUND */
-        false,     /* AllocKind::OBJECT8 */
-        true,      /* AllocKind::OBJECT8_BACKGROUND */
-        false,     /* AllocKind::OBJECT12 */
-        true,      /* AllocKind::OBJECT12_BACKGROUND */
-        false,     /* AllocKind::OBJECT16 */
-        true,      /* AllocKind::OBJECT16_BACKGROUND */
-        false,     /* AllocKind::SCRIPT */
-        true,      /* AllocKind::LAZY_SCRIPT */
-        true,      /* AllocKind::SHAPE */
-        true,      /* AllocKind::ACCESSOR_SHAPE */
-        true,      /* AllocKind::BASE_SHAPE */
-        true,      /* AllocKind::OBJECT_GROUP */
-        true,      /* AllocKind::FAT_INLINE_STRING */
-        true,      /* AllocKind::STRING */
-        true,      /* AllocKind::EXTERNAL_STRING */
-        true,      /* AllocKind::FAT_INLINE_ATOM */
-        true,      /* AllocKind::ATOM */
-        true,      /* AllocKind::SYMBOL */
-        false,     /* AllocKind::JITCODE */
-        true,      /* AllocKind::SCOPE */
-        true,      /* AllocKind::REGEXP_SHARED */
+#define DEFINE_BACKGROUND_FINALIZED(_1, _2, _3, _4, bgFinal, _5) bgFinal,
+        FOR_EACH_ALLOCKIND(DEFINE_BACKGROUND_FINALIZED)
+#undef DEFINE_BG_FINALIZE
     };
+
     JS_STATIC_ASSERT(JS_ARRAY_LENGTH(map) == size_t(AllocKind::LIMIT));
     return map[size_t(kind)];
 }
 
 } /* namespace gc */
 } /* namespace js */
 
 #endif /* gc_AllocKind_h */
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -142,17 +142,17 @@ js::Allocate(JSContext* cx)
     if (!cx->helperThread()) {
         if (!cx->runtime()->gc.checkAllocatorState<allowGC>(cx, kind))
             return nullptr;
     }
 
     return GCRuntime::tryNewTenuredThing<T, allowGC>(cx, kind, thingSize);
 }
 
-#define DECL_ALLOCATOR_INSTANCES(allocKind, traceKind, type, sizedType) \
+#define DECL_ALLOCATOR_INSTANCES(allocKind, traceKind, type, sizedType, bgFinal, nursery) \
     template type* js::Allocate<type, NoGC>(JSContext* cx);\
     template type* js::Allocate<type, CanGC>(JSContext* cx);
 FOR_EACH_NONOBJECT_ALLOCKIND(DECL_ALLOCATOR_INSTANCES)
 #undef DECL_ALLOCATOR_INSTANCES
 
 template <typename T, AllowGC allowGC>
 /* static */ T*
 GCRuntime::tryNewTenuredThing(JSContext* cx, AllocKind kind, size_t thingSize)
--- a/js/src/gc/Cell.h
+++ b/js/src/gc/Cell.h
@@ -37,17 +37,17 @@ CurrentThreadIsIonCompiling();
 #endif
 
 extern void
 TraceManuallyBarrieredGenericPointerEdge(JSTracer* trc, gc::Cell** thingp, const char* name);
 
 namespace gc {
 
 class Arena;
-enum class AllocKind;
+enum class AllocKind : uint8_t;
 struct Chunk;
 class TenuredCell;
 
 // A GC cell is the base class for all GC things.
 struct Cell
 {
   public:
     MOZ_ALWAYS_INLINE bool isTenured() const { return !IsInsideNursery(this); }
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -60,17 +60,17 @@ class JSONPrinter;
 void SetGCZeal(JSRuntime*, uint8_t, uint32_t);
 
 namespace gc {
 class AutoMaybeStartBackgroundAllocation;
 struct Cell;
 class MinorCollectionTracer;
 class RelocationOverlay;
 struct TenureCountCache;
-enum class AllocKind;
+enum class AllocKind : uint8_t;
 class TenuredCell;
 } /* namespace gc */
 
 namespace jit {
 class MacroAssembler;
 } // namespace jit
 
 class TenuringTracer : public JSTracer
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -1784,28 +1784,16 @@ HandleLexicalCheckFailure(JSContext* cx,
     if (!innerScript->failedLexicalCheck())
         innerScript->setFailedLexicalCheck();
 
     InvalidateAfterBailout(cx, outerScript, "lexical check failure");
     if (innerScript->hasIonScript())
         Invalidate(cx, innerScript);
 }
 
-static void
-HandleIterNextNonStringBailout(JSContext* cx, HandleScript outerScript, HandleScript innerScript)
-{
-    JitSpew(JitSpew_IonBailouts, "Non-string iterator value %s:%zu, inlined into %s:%zu",
-            innerScript->filename(), innerScript->lineno(),
-            outerScript->filename(), outerScript->lineno());
-
-    // This should only happen when legacy generators are used.
-    ForbidCompilation(cx, innerScript);
-    InvalidateAfterBailout(cx, outerScript, "non-string iterator value");
-}
-
 static bool
 CopyFromRematerializedFrame(JSContext* cx, JitActivation* act, uint8_t* fp, size_t inlineDepth,
                             BaselineFrame* frame)
 {
     RematerializedFrame* rematFrame = act->lookupRematerializedFrame(fp, inlineDepth);
 
     // We might not have rematerialized a frame if the user never requested a
     // Debugger.Frame for it.
@@ -2036,20 +2024,16 @@ jit::FinishBailoutToBaseline(BaselineBai
       case Bailout_OverflowInvalidate:
         outerScript->setHadOverflowBailout();
         MOZ_FALLTHROUGH;
       case Bailout_DoubleOutput:
       case Bailout_ObjectIdentityOrTypeGuard:
         HandleBaselineInfoBailout(cx, outerScript, innerScript);
         break;
 
-      case Bailout_IterNextNonString:
-        HandleIterNextNonStringBailout(cx, outerScript, innerScript);
-        break;
-
       case Bailout_ArgumentCheck:
         // Do nothing, bailout will resume before the argument monitor ICs.
         break;
       case Bailout_BoundsCheck:
       case Bailout_Detached:
         HandleBoundsCheckFailure(cx, outerScript, innerScript);
         break;
       case Bailout_ShapeGuard:
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -4683,17 +4683,17 @@ BaselineCompiler::emit_JSOP_FINALYIELDRV
 typedef bool (*InterpretResumeFn)(JSContext*, HandleObject, HandleValue, HandlePropertyName,
                                   MutableHandleValue);
 static const VMFunction InterpretResumeInfo =
     FunctionInfo<InterpretResumeFn>(jit::InterpretResume, "InterpretResume");
 
 typedef bool (*GeneratorThrowFn)(JSContext*, BaselineFrame*, Handle<GeneratorObject*>,
                                  HandleValue, uint32_t);
 static const VMFunction GeneratorThrowInfo =
-    FunctionInfo<GeneratorThrowFn>(jit::GeneratorThrowOrClose, "GeneratorThrowOrClose", TailCall);
+    FunctionInfo<GeneratorThrowFn>(jit::GeneratorThrowOrReturn, "GeneratorThrowOrReturn", TailCall);
 
 bool
 BaselineCompiler::emit_JSOP_RESUME()
 {
     GeneratorObject::ResumeKind resumeKind = GeneratorObject::getResumeKind(pc);
 
     frame.syncStack(0);
     masm.checkStackAlignment();
@@ -4866,17 +4866,17 @@ BaselineCompiler::emit_JSOP_RESUME()
                         scratch2);
         masm.loadPtr(BaseIndex(scratch1, scratch2, ScaleFromElemWidth(sizeof(uintptr_t))), scratch1);
 
         // Mark as running and jump to the generator's JIT code.
         masm.storeValue(Int32Value(GeneratorObject::YIELD_AND_AWAIT_INDEX_RUNNING),
                         Address(genObj, GeneratorObject::offsetOfYieldAndAwaitIndexSlot()));
         masm.jump(scratch1);
     } else {
-        MOZ_ASSERT(resumeKind == GeneratorObject::THROW || resumeKind == GeneratorObject::CLOSE);
+        MOZ_ASSERT(resumeKind == GeneratorObject::THROW || resumeKind == GeneratorObject::RETURN);
 
         // Update the frame's frameSize field.
         masm.computeEffectiveAddress(Address(BaselineFrameReg, BaselineFrame::FramePointerOffset),
                                      scratch2);
         masm.movePtr(scratch2, scratch1);
         masm.subStackPtrFrom(scratch2);
         masm.store32(scratch2, Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFrameSize()));
         masm.loadBaselineFramePtr(BaselineFrameReg, scratch2);
@@ -4892,17 +4892,17 @@ BaselineCompiler::emit_JSOP_RESUME()
             return false;
 
         // Create the frame descriptor.
         masm.subStackPtrFrom(scratch1);
         masm.makeFrameDescriptor(scratch1, JitFrame_BaselineJS, ExitFrameLayout::Size());
 
         // Push the frame descriptor and a dummy return address (it doesn't
         // matter what we push here, frame iterators will use the frame pc
-        // set in jit::GeneratorThrowOrClose).
+        // set in jit::GeneratorThrowOrReturn).
         masm.push(scratch1);
 
         // On ARM64, the callee will push the return address.
 #ifndef JS_CODEGEN_ARM64
         masm.push(ImmWord(0));
 #endif
         masm.jump(code);
     }
@@ -4911,18 +4911,18 @@ BaselineCompiler::emit_JSOP_RESUME()
     masm.bind(&interpret);
 
     prepareVMCall();
     if (resumeKind == GeneratorObject::NEXT) {
         pushArg(ImmGCPtr(cx->names().next));
     } else if (resumeKind == GeneratorObject::THROW) {
         pushArg(ImmGCPtr(cx->names().throw_));
     } else {
-        MOZ_ASSERT(resumeKind == GeneratorObject::CLOSE);
-        pushArg(ImmGCPtr(cx->names().close));
+        MOZ_ASSERT(resumeKind == GeneratorObject::RETURN);
+        pushArg(ImmGCPtr(cx->names().return_));
     }
 
     masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), retVal);
     pushArg(retVal);
     pushArg(genObj);
 
     if (!callVM(InterpretResumeInfo))
         return false;
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -2320,23 +2320,23 @@ TryAttachCallStub(JSContext* cx, ICCall_
         }
 
         if (stub->nativeStubCount() >= ICCall_Fallback::MAX_NATIVE_STUBS) {
             JitSpew(JitSpew_BaselineIC,
                     "  Too many Call_Native stubs. TODO: add Call_AnyNative!");
             return true;
         }
 
-        if (fun->native() == intrinsic_IsSuspendedStarGenerator) {
+        if (fun->native() == intrinsic_IsSuspendedGenerator) {
             // This intrinsic only appears in self-hosted code.
             MOZ_ASSERT(op != JSOP_NEW);
             MOZ_ASSERT(argc == 1);
-            JitSpew(JitSpew_BaselineIC, "  Generating Call_IsSuspendedStarGenerator stub");
-
-            ICCall_IsSuspendedStarGenerator::Compiler compiler(cx);
+            JitSpew(JitSpew_BaselineIC, "  Generating Call_IsSuspendedGenerator stub");
+
+            ICCall_IsSuspendedGenerator::Compiler compiler(cx);
             ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
             if (!newStub)
                 return false;
 
             stub->addNewStub(newStub);
             *handled = true;
             return true;
         }
@@ -3426,49 +3426,49 @@ ICCall_ConstStringSplit::Compiler::gener
     // Guard failure path.
     masm.bind(&failureRestoreArgc);
     masm.move32(Imm32(2), R0.scratchReg());
     EmitStubGuardFailure(masm);
     return true;
 }
 
 bool
-ICCall_IsSuspendedStarGenerator::Compiler::generateStubCode(MacroAssembler& masm)
+ICCall_IsSuspendedGenerator::Compiler::generateStubCode(MacroAssembler& masm)
 {
     MOZ_ASSERT(engine_ == Engine::Baseline);
 
-    // The IsSuspendedStarGenerator intrinsic is only called in self-hosted
-    // code, so it's safe to assume we have a single a