Bug 1412023 - Memory Tool to ES6 classes r?gregtatum draft
authorMichael Ratcliffe <mratcliffe@mozilla.com>
Thu, 26 Oct 2017 17:35:47 +0100
changeset 692127 00ea009734c7b9a74e1aeedbf226e82732b67326
parent 692089 40a14ca1cf04499f398e4cb8ba359b39eae4e216
child 738678 0b725bd59f9cd29540dd6cdcac9ae498dce6e81b
push id87413
push userbmo:mratcliffe@mozilla.com
push dateThu, 02 Nov 2017 16:33:18 +0000
reviewersgregtatum
bugs1412023
milestone58.0a1
Bug 1412023 - Memory Tool to ES6 classes r?gregtatum @gregtatum If you have made changes that would make it hard to land this then I can recreate the patch any time. I did need to make a few manual changes to get this to work but it is probably a lot easier for me to regenerate the patch than for you to resolve any merge conflicts. Because the changes are generic and mostly automated you don't need to take too long looking at every detail but you probably want to take a quick look over it. The main thing with a change like this is a green try. MozReview-Commit-ID: 1p3ts7na1YF
devtools/client/memory/app.js
devtools/client/memory/components/Census.js
devtools/client/memory/components/CensusHeader.js
devtools/client/memory/components/CensusTreeItem.js
devtools/client/memory/components/DominatorTree.js
devtools/client/memory/components/DominatorTreeHeader.js
devtools/client/memory/components/DominatorTreeItem.js
devtools/client/memory/components/Heap.js
devtools/client/memory/components/Individuals.js
devtools/client/memory/components/IndividualsHeader.js
devtools/client/memory/components/List.js
devtools/client/memory/components/ShortestPaths.js
devtools/client/memory/components/SnapshotListItem.js
devtools/client/memory/components/Toolbar.js
devtools/client/memory/components/TreeMap.js
--- 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;