Bug 1417512 - Performance Tools to ES6 Classes, prop-types and react-dom-factories r?gregtatum draft
authorMichael Ratcliffe <mratcliffe@mozilla.com>
Wed, 15 Nov 2017 17:00:45 +0000
changeset 698380 d201de1b73d4e3e3a185c3eecc6c25e338f827c5
parent 698261 45715ece25fcb064eee4f977ebd842d44a87f22b
child 740366 f0ae5613d5add82e1f09a0fe547c9383677e9827
push id89275
push userbmo:mratcliffe@mozilla.com
push dateWed, 15 Nov 2017 17:23:08 +0000
reviewersgregtatum
bugs1417512
milestone59.0a1
Bug 1417512 - Performance Tools to ES6 Classes, prop-types and react-dom-factories r?gregtatum MozReview-Commit-ID: A1F79OQpVGO
devtools/client/dom/content/components/dom-tree.js
devtools/client/dom/content/components/main-frame.js
devtools/client/dom/content/components/main-toolbar.js
devtools/client/performance/components/jit-optimizations-item.js
devtools/client/performance/components/jit-optimizations.js
devtools/client/performance/components/recording-button.js
devtools/client/performance/components/recording-controls.js
devtools/client/performance/components/recording-list-item.js
devtools/client/performance/components/recording-list.js
devtools/client/performance/components/waterfall-header.js
devtools/client/performance/components/waterfall-tree-row.js
devtools/client/performance/components/waterfall-tree.js
devtools/client/performance/components/waterfall.js
--- a/devtools/client/dom/content/components/dom-tree.js
+++ b/devtools/client/dom/content/components/dom-tree.js
@@ -1,63 +1,67 @@
 /* -*- 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";
 
 // React & Redux
-const React = require("devtools/client/shared/vendor/react");
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
-const TreeView = React.createFactory(require("devtools/client/shared/components/tree/TreeView"));
-
+const TreeView = createFactory(require("devtools/client/shared/components/tree/TreeView"));
 // Reps
 const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
 const { Rep } = REPS;
+
 const Grip = REPS.Grip;
-
 // DOM Panel
 const { GripProvider } = require("../grip-provider");
+
 const { DomDecorator } = require("../dom-decorator");
 
-// Shortcuts
-const PropTypes = React.PropTypes;
-
 /**
  * Renders DOM panel tree.
  */
-var DomTree = React.createClass({
-  displayName: "DomTree",
+class DomTree extends Component {
+  static get propTypes() {
+    return {
+      dispatch: PropTypes.func.isRequired,
+      filter: PropTypes.string,
+      grips: PropTypes.object,
+      object: PropTypes.any,
+      openLink: PropTypes.func,
+    };
+  }
 
-  propTypes: {
-    dispatch: PropTypes.func.isRequired,
-    filter: PropTypes.string,
-    grips: PropTypes.object,
-    object: PropTypes.any,
-    openLink: PropTypes.func,
-  },
+  constructor(props) {
+    super(props);
+    this.onFilter = this.onFilter.bind(this);
+  }
 
   /**
    * Filter DOM properties. Return true if the object
    * should be visible in the tree.
    */
-  onFilter: function (object) {
+  onFilter(object) {
     if (!this.props.filter) {
       return true;
     }
 
     return (object.name && object.name.indexOf(this.props.filter) > -1);
-  },
+  }
 
   /**
    * Render DOM panel content
    */
-  render: function () {
+  render() {
     let {
       dispatch,
       grips,
       object,
       openLink,
     } = this.props;
 
     let columns = [{
@@ -82,17 +86,17 @@ var DomTree = React.createClass({
         object,
         onFilter: this.onFilter,
         openLink,
         provider: new GripProvider(grips, dispatch),
         renderValue,
       })
     );
   }
-});
+}
 
 const mapStateToProps = (state) => {
   return {
     grips: state.grips,
     filter: state.filter
   };
 };
 
--- a/devtools/client/dom/content/components/main-frame.js
+++ b/devtools/client/dom/content/components/main-frame.js
@@ -1,46 +1,47 @@
 /* -*- 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/. */
- /* globals DomProvider */
+/* globals DomProvider */
 
 "use strict";
 
 // React & Redux
-const React = require("devtools/client/shared/vendor/react");
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
 const { connect } = require("devtools/client/shared/vendor/react-redux");
-
 // DOM Panel
-const DomTree = React.createFactory(require("./dom-tree"));
-const MainToolbar = React.createFactory(require("./main-toolbar"));
+const DomTree = createFactory(require("./dom-tree"));
 
+const MainToolbar = createFactory(require("./main-toolbar"));
 // Shortcuts
-const { div } = React.DOM;
-const PropTypes = React.PropTypes;
+const { div } = dom;
 
 /**
  * Renders basic layout of the DOM panel. The DOM panel content consists
  * from two main parts: toolbar and tree.
  */
-var MainFrame = React.createClass({
-  displayName: "MainFrame",
-
-  propTypes: {
-    dispatch: PropTypes.func.isRequired,
-    filter: PropTypes.string,
-    object: PropTypes.any,
-  },
+class MainFrame extends Component {
+  static get propTypes() {
+    return {
+      dispatch: PropTypes.func.isRequired,
+      filter: PropTypes.string,
+      object: PropTypes.any,
+    };
+  }
 
   /**
    * Render DOM panel content
    */
-  render: function () {
+  render() {
     let {
       filter,
       object,
     } = this.props;
 
     return (
       div({className: "mainFrame"},
         MainToolbar({
@@ -52,17 +53,17 @@ var MainFrame = React.createClass({
             filter,
             object,
             openLink: url => DomProvider.openLink(url),
           })
         )
       )
     );
   }
-});
+}
 
 // Transform state into props
 // Note: use https://github.com/faassen/reselect for better performance.
 const mapStateToProps = (state) => {
   return {
     filter: state.filter
   };
 };
--- a/devtools/client/dom/content/components/main-toolbar.js
+++ b/devtools/client/dom/content/components/main-toolbar.js
@@ -1,54 +1,59 @@
 /* -*- 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";
 
 // React
-const React = require("devtools/client/shared/vendor/react");
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
 const { l10n } = require("../utils");
-
 // Reps
 const { createFactories } = require("devtools/client/shared/react-utils");
+
 const { Toolbar, ToolbarButton } = createFactories(require("devtools/client/jsonview/components/reps/Toolbar"));
 
 // DOM Panel
-const SearchBox = React.createFactory(require("devtools/client/shared/components/SearchBox"));
-
+const SearchBox = createFactory(require("devtools/client/shared/components/SearchBox"));
 // Actions
 const { fetchProperties } = require("../actions/grips");
+
 const { setVisibilityFilter } = require("../actions/filter");
 
-// Shortcuts
-const PropTypes = React.PropTypes;
-
 /**
  * This template is responsible for rendering a toolbar
  * within the 'Headers' panel.
  */
-var MainToolbar = React.createClass({
-  displayName: "MainToolbar",
-
-  propTypes: {
-    object: PropTypes.any.isRequired,
-    dispatch: PropTypes.func.isRequired,
-  },
+class MainToolbar extends Component {
+  static get propTypes() {
+    return {
+      object: PropTypes.any.isRequired,
+      dispatch: PropTypes.func.isRequired,
+    };
+  }
 
-  onRefresh: function () {
-    this.props.dispatch(fetchProperties(this.props.object));
-  },
+  constructor(props) {
+    super(props);
+    this.onRefresh = this.onRefresh.bind(this);
+    this.onSearch = this.onSearch.bind(this);
+  }
 
-  onSearch: function (value) {
+  onRefresh() {
+    this.props.dispatch(fetchProperties(this.props.object));
+  }
+
+  onSearch(value) {
     this.props.dispatch(setVisibilityFilter(value));
-  },
+  }
 
-  render: function () {
+  render() {
     return (
       Toolbar({},
         ToolbarButton({
           className: "refresh devtools-button",
           id: "dom-refresh-button",
           title: l10n.getStr("dom.refresh"),
           onClick: this.onRefresh
         }),
@@ -56,12 +61,12 @@ var MainToolbar = React.createClass({
           delay: 250,
           onChange: this.onSearch,
           placeholder: l10n.getStr("dom.filterDOMPanel"),
           type: "filter"
         })
       )
     );
   }
-});
+}
 
 // Exports from this module
 module.exports = MainToolbar;
--- a/devtools/client/performance/components/jit-optimizations-item.js
+++ b/devtools/client/performance/components/jit-optimizations-item.js
@@ -2,18 +2,20 @@
  * 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 { LocalizationHelper } = require("devtools/shared/l10n");
 const STRINGS_URI = "devtools/client/locales/jit-optimizations.properties";
 const L10N = new LocalizationHelper(STRINGS_URI);
 
-const {PluralForm} = require("devtools/shared/plural-form");
-const { DOM: dom, PropTypes, createClass, createFactory } = require("devtools/client/shared/vendor/react");
+const { PluralForm } = require("devtools/shared/plural-form");
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const Frame = createFactory(require("devtools/client/shared/components/Frame"));
 const PROPNAME_MAX_LENGTH = 4;
 // If TREE_ROW_HEIGHT changes, be sure to change `var(--jit-tree-row-height)`
 // in `devtools/client/themes/jit-optimizations.css`
 const TREE_ROW_HEIGHT = 14;
 
 const OPTIMIZATION_ITEM_TYPES = ["site", "attempts", "types", "attempt", "type",
                                  "observedtype"];
@@ -25,30 +27,41 @@ const OPTIMIZATION_ITEM_TYPES = ["site",
  */
 const {
   JITOptimizations, hasSuccessfulOutcome, isSuccessfulOutcome
 } = require("devtools/client/performance/modules/logic/jit");
 const OPTIMIZATION_FAILURE = L10N.getStr("jit.optimizationFailure");
 const JIT_SAMPLES = L10N.getStr("jit.samples");
 const JIT_TYPES = L10N.getStr("jit.types");
 const JIT_ATTEMPTS = L10N.getStr("jit.attempts");
+
 /* eslint-enable no-unused-vars */
 
-const JITOptimizationsItem = createClass({
-  displayName: "JITOptimizationsItem",
+class JITOptimizationsItem extends Component {
+  static get propTypes() {
+    return {
+      onViewSourceInDebugger: PropTypes.func.isRequired,
+      frameData: PropTypes.object.isRequired,
+      type: PropTypes.oneOf(OPTIMIZATION_ITEM_TYPES).isRequired,
+      depth: PropTypes.number.isRequired,
+      arrow: PropTypes.element.isRequired,
+      item: PropTypes.object,
+      focused: PropTypes.bool
+    };
+  }
 
-  propTypes: {
-    onViewSourceInDebugger: PropTypes.func.isRequired,
-    frameData: PropTypes.object.isRequired,
-    type: PropTypes.oneOf(OPTIMIZATION_ITEM_TYPES).isRequired,
-    depth: PropTypes.number.isRequired,
-    arrow: PropTypes.element.isRequired,
-    item: PropTypes.object,
-    focused: PropTypes.bool
-  },
+  constructor(props) {
+    super(props);
+    this._renderSite = this._renderSite.bind(this);
+    this._renderAttempts = this._renderAttempts.bind(this);
+    this._renderTypes = this._renderTypes.bind(this);
+    this._renderAttempt = this._renderAttempt.bind(this);
+    this._renderType = this._renderType.bind(this);
+    this._renderObservedType = this._renderObservedType.bind(this);
+  }
 
   _renderSite({ item: site, onViewSourceInDebugger, frameData }) {
     let attempts = site.data.attempts;
     let lastStrategy = attempts[attempts.length - 1].strategy;
     let propString = "";
     let propertyName = site.data.propertyName;
 
     // Display property name if it exists
@@ -76,45 +89,45 @@ const JITOptimizationsItem = createClass
     });
     let children = [text, frame];
 
     if (!hasSuccessfulOutcome(site)) {
       children.unshift(dom.span({ className: "opt-icon warning" }));
     }
 
     return dom.span({ className: "optimization-site" }, ...children);
-  },
+  }
 
   _renderAttempts({ item: attempts }) {
     return dom.span({ className: "optimization-attempts" },
       `${JIT_ATTEMPTS} (${attempts.length})`
     );
-  },
+  }
 
   _renderTypes({ item: types }) {
     return dom.span({ className: "optimization-types" },
       `${JIT_TYPES} (${types.length})`
     );
-  },
+  }
 
   _renderAttempt({ item: attempt }) {
     let success = isSuccessfulOutcome(attempt.outcome);
     let { strategy, outcome } = attempt;
     return dom.span({ className: "optimization-attempt" },
       dom.span({ className: "optimization-strategy" }, strategy),
       " → ",
       dom.span({ className: `optimization-outcome ${success ? "success" : "failure"}` },
                outcome)
     );
-  },
+  }
 
   _renderType({ item: type }) {
     return dom.span({ className: "optimization-ion-type" },
                     `${type.site}:${type.mirType}`);
-  },
+  }
 
   _renderObservedType({ onViewSourceInDebugger, item: type }) {
     let children = [
       dom.span({ className: "optimization-observed-type-keyed" },
         `${type.keyedBy}${type.name ? ` → ${type.name}` : ""}`)
     ];
 
     // If we have a line and location, make a link to the debugger
@@ -130,17 +143,17 @@ const JITOptimizationsItem = createClass
         })
       );
     // Otherwise if we just have a location, it's probably just a memory location.
     } else if (type.location) {
       children.push(`@${type.location}`);
     }
 
     return dom.span({ className: "optimization-observed-type" }, ...children);
-  },
+  }
 
   render() {
     /* eslint-disable no-unused-vars */
     /**
      * TODO - Re-enable this eslint rule. The JIT tool is a work in progress, and these
      *        undefined variables may represent intended functionality.
      */
     let {
@@ -168,12 +181,12 @@ const JITOptimizationsItem = createClass
     return dom.div(
       {
         className: `optimization-tree-item optimization-tree-item-${type}`,
         style: { marginInlineStart: depth * TREE_ROW_HEIGHT }
       },
       arrow,
       content
     );
-  },
-});
+  }
+}
 
 module.exports = JITOptimizationsItem;
--- a/devtools/client/performance/components/jit-optimizations.js
+++ b/devtools/client/performance/components/jit-optimizations.js
@@ -3,17 +3,19 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const STRINGS_URI = "devtools/client/locales/jit-optimizations.properties";
 const L10N = new LocalizationHelper(STRINGS_URI);
 
 const { assert } = require("devtools/shared/DevToolsUtils");
-const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const Tree = createFactory(require("../../shared/components/Tree"));
 const OptimizationsItem = createFactory(require("./jit-optimizations-item"));
 const FrameView = createFactory(require("../../shared/components/Frame"));
 const JIT_TITLE = L10N.getStr("jit.title");
 // If TREE_ROW_HEIGHT changes, be sure to change `var(--jit-tree-row-height)`
 // in `devtools/client/themes/jit-optimizations.css`
 const TREE_ROW_HEIGHT = 14;
 
@@ -53,47 +55,52 @@ const optimizationSiteModel = {
   line: PropTypes.number.isRequired,
   column: PropTypes.number.isRequired,
   data: PropTypes.shape({
     attempts: PropTypes.arrayOf(optimizationAttemptModel).isRequired,
     types: PropTypes.arrayOf(optimizationIonTypeModel).isRequired,
   }).isRequired,
 };
 
-const JITOptimizations = createClass({
-  displayName: "JITOptimizations",
+class JITOptimizations extends Component {
+  static get propTypes() {
+    return {
+      onViewSourceInDebugger: PropTypes.func.isRequired,
+      frameData: PropTypes.object.isRequired,
+      optimizationSites: PropTypes.arrayOf(optimizationSiteModel).isRequired,
+      autoExpandDepth: PropTypes.number,
+    };
+  }
 
-  propTypes: {
-    onViewSourceInDebugger: PropTypes.func.isRequired,
-    frameData: PropTypes.object.isRequired,
-    optimizationSites: PropTypes.arrayOf(optimizationSiteModel).isRequired,
-    autoExpandDepth: PropTypes.number,
-  },
-
-  getDefaultProps() {
+  static get defaultProps() {
     return {
       autoExpandDepth: 0
     };
-  },
+  }
 
-  getInitialState() {
-    return {
+  constructor(props) {
+    super(props);
+
+    this.state = {
       expanded: new Set()
     };
-  },
+
+    this._createHeader = this._createHeader.bind(this);
+    this._createTree = this._createTree.bind(this);
+  }
 
   /**
    * Frame data generated from `frameNode.getInfo()`, or an empty
    * object, as well as a handler for clicking on the frame component.
    *
    * @param {?Object} .frameData
    * @param {Function} .onViewSourceInDebugger
    * @return {ReactElement}
    */
-  _createHeader: function ({ frameData, onViewSourceInDebugger }) {
+  _createHeader({ frameData, onViewSourceInDebugger }) {
     let { isMetaCategory, url, line } = frameData;
     let name = isMetaCategory ? frameData.categoryData.label :
                frameData.functionName || "";
 
     // Simulate `SavedFrame`s interface
     let frame = { source: url, line: +line, functionDisplayName: name };
 
     // Neither Meta Category nodes, or the lack of a selected frame node,
@@ -109,17 +116,17 @@ const JITOptimizations = createClass({
       });
     }
 
     return dom.div({ className: "optimization-header" },
       dom.span({ className: "header-title" }, JIT_TITLE),
       dom.span({ className: "header-function-name" }, name),
       frameComponent
     );
-  },
+  }
 
   _createTree(props) {
     let {
       autoExpandDepth,
       frameData,
       onViewSourceInDebugger,
       optimizationSites: sites
     } = this.props;
@@ -230,19 +237,19 @@ const JITOptimizations = createClass({
           depth,
           focused,
           arrow,
           expanded,
           type: getRowType(item),
           frameData,
         }),
     });
-  },
+  }
 
   render() {
     let header = this._createHeader(this.props);
     let tree = this._createTree(this.props);
 
     return dom.div({}, header, tree);
   }
-});
+}
 
 module.exports = JITOptimizations;
--- a/devtools/client/performance/components/recording-button.js
+++ b/devtools/client/performance/components/recording-button.js
@@ -1,25 +1,27 @@
 /* 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 {L10N} = require("devtools/client/performance/modules/global");
-const {DOM, createClass, PropTypes} = require("devtools/client/shared/vendor/react");
-const {button} = DOM;
+const { L10N } = require("devtools/client/performance/modules/global");
+const { Component } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const { button } = dom;
 
-module.exports = createClass({
-  displayName: "Recording Button",
-
-  propTypes: {
-    onRecordButtonClick: PropTypes.func.isRequired,
-    isRecording: PropTypes.bool,
-    isLocked: PropTypes.bool
-  },
+class RecordingButton extends Component {
+  static get propTypes() {
+    return {
+      onRecordButtonClick: PropTypes.func.isRequired,
+      isRecording: PropTypes.bool,
+      isLocked: PropTypes.bool
+    };
+  }
 
   render() {
     let {
       onRecordButtonClick,
       isRecording,
       isLocked
     } = this.props;
 
@@ -35,9 +37,11 @@ module.exports = createClass({
         onClick: onRecordButtonClick,
         "data-standalone": "true",
         "data-text-only": "true",
         disabled: isLocked
       },
       isRecording ? L10N.getStr("recordings.stop") : L10N.getStr("recordings.start")
     );
   }
-});
+}
+
+module.exports = RecordingButton;
--- a/devtools/client/performance/components/recording-controls.js
+++ b/devtools/client/performance/components/recording-controls.js
@@ -1,27 +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/. */
 "use strict";
 
-const {L10N} = require("devtools/client/performance/modules/global");
-const {DOM, createClass, PropTypes} = require("devtools/client/shared/vendor/react");
-const {div, button} = DOM;
-
-module.exports = createClass({
-  displayName: "Recording Controls",
+const { L10N } = require("devtools/client/performance/modules/global");
+const { Component } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const { div, button } = dom;
 
-  propTypes: {
-    onClearButtonClick: PropTypes.func.isRequired,
-    onRecordButtonClick: PropTypes.func.isRequired,
-    onImportButtonClick: PropTypes.func.isRequired,
-    isRecording: PropTypes.bool,
-    isLocked: PropTypes.bool
-  },
+class RecordingControls extends Component {
+  static get propTypes() {
+    return {
+      onClearButtonClick: PropTypes.func.isRequired,
+      onRecordButtonClick: PropTypes.func.isRequired,
+      onImportButtonClick: PropTypes.func.isRequired,
+      isRecording: PropTypes.bool,
+      isLocked: PropTypes.bool
+    };
+  }
 
   render() {
     let {
       onClearButtonClick,
       onRecordButtonClick,
       onImportButtonClick,
       isRecording,
       isLocked
@@ -54,9 +56,11 @@ module.exports = createClass({
             className: "devtools-button",
             title: L10N.getStr("recordings.import.tooltip"),
             onClick: onImportButtonClick
           })
         )
       )
     );
   }
-});
+}
+
+module.exports = RecordingControls;
--- a/devtools/client/performance/components/recording-list-item.js
+++ b/devtools/client/performance/components/recording-list-item.js
@@ -1,29 +1,31 @@
 /* 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 {div, li, span, button} = DOM;
-const {L10N} = require("devtools/client/performance/modules/global");
-
-module.exports = createClass({
-  displayName: "Recording List Item",
+const { Component } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const { div, li, span, button } = dom;
+const { L10N } = require("devtools/client/performance/modules/global");
 
-  propTypes: {
-    label: PropTypes.string.isRequired,
-    duration: PropTypes.string,
-    onSelect: PropTypes.func.isRequired,
-    onSave: PropTypes.func.isRequired,
-    isLoading: PropTypes.bool,
-    isSelected: PropTypes.bool,
-    isRecording: PropTypes.bool
-  },
+class RecordingListItem extends Component {
+  static get propTypes() {
+    return {
+      label: PropTypes.string.isRequired,
+      duration: PropTypes.string,
+      onSelect: PropTypes.func.isRequired,
+      onSave: PropTypes.func.isRequired,
+      isLoading: PropTypes.bool,
+      isSelected: PropTypes.bool,
+      isRecording: PropTypes.bool
+    };
+  }
 
   render() {
     const {
       label,
       duration,
       onSelect,
       onSave,
       isLoading,
@@ -51,9 +53,11 @@ module.exports = createClass({
           span({ className: "recording-list-item-duration" }, durationText),
           button({ className: "recording-list-item-save", onClick: onSave },
             L10N.getStr("recordingsList.saveLabel")
           )
         )
       )
     );
   }
-});
+}
+
+module.exports = RecordingListItem;
--- a/devtools/client/performance/components/recording-list.js
+++ b/devtools/client/performance/components/recording-list.js
@@ -1,28 +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 {DOM, createClass, PropTypes} = require("devtools/client/shared/vendor/react");
-const {L10N} = require("devtools/client/performance/modules/global");
-const {ul, div} = DOM;
+const { Component } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const { L10N } = require("devtools/client/performance/modules/global");
+const { ul, div } = dom;
 
-module.exports = createClass({
-  displayName: "Recording List",
-
-  propTypes: {
-    items: PropTypes.arrayOf(PropTypes.object).isRequired,
-    itemComponent: PropTypes.func.isRequired
-  },
+class RecordingList extends Component {
+  static get propTypes() {
+    return {
+      items: PropTypes.arrayOf(PropTypes.object).isRequired,
+      itemComponent: PropTypes.func.isRequired
+    };
+  }
 
   render() {
     const {
       items,
       itemComponent: Item,
     } = this.props;
 
     return items.length > 0
       ? ul({ className: "recording-list" }, ...items.map(Item))
       : div({ className: "recording-list-empty" }, L10N.getStr("noRecordingsText"));
   }
-});
+}
+
+module.exports = RecordingList;
--- a/devtools/client/performance/components/waterfall-header.js
+++ b/devtools/client/performance/components/waterfall-header.js
@@ -3,17 +3,18 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /**
  * The "waterfall ticks" view, a header for the markers displayed in the waterfall.
  */
 
-const { DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const { L10N } = require("../modules/global");
 const { TickUtils } = require("../modules/waterfall-ticks");
 
 const WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
 const WATERFALL_HEADER_TICKS_SPACING_MIN = 50; // px
 const WATERFALL_HEADER_TEXT_PADDING = 3; // px
 
 function WaterfallHeader(props) {
--- a/devtools/client/performance/components/waterfall-tree-row.js
+++ b/devtools/client/performance/components/waterfall-tree-row.js
@@ -3,17 +3,18 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /**
  * A single row (node) in the waterfall tree
  */
 
-const { DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const { MarkerBlueprintUtils } = require("../modules/marker-blueprint-utils");
 
 const LEVEL_INDENT = 10; // px
 const ARROW_NODE_OFFSET = -14; // px
 const WATERFALL_MARKER_TIMEBAR_WIDTH_MIN = 5; // px
 
 function buildMarkerSidebar(blueprint, props) {
   const { marker, level, sidebarWidth } = props;
--- a/devtools/client/performance/components/waterfall-tree.js
+++ b/devtools/client/performance/components/waterfall-tree.js
@@ -1,14 +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 { createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const Tree = createFactory(require("devtools/client/shared/components/Tree"));
 const WaterfallTreeRow = createFactory(require("./waterfall-tree-row"));
 
 // Keep in sync with var(--waterfall-tree-row-height) in performance.css
 const WATERFALL_TREE_ROW_HEIGHT = 15; // px
 
 /**
  * Checks if a given marker is in the specified time range.
@@ -33,40 +34,53 @@ function isMarkerInRange(e, start, end) 
     (mStart < start && mEnd > end) ||
     // overlap start
     (mStart < start && mEnd >= start && mEnd <= end) ||
     // overlap end
     (mEnd > end && mStart >= start && mStart <= end)
   );
 }
 
-const WaterfallTree = createClass({
-  displayName: "WaterfallTree",
+class WaterfallTree extends Component {
+  static get propTypes() {
+    return {
+      marker: PropTypes.object.isRequired,
+      startTime: PropTypes.number.isRequired,
+      endTime: PropTypes.number.isRequired,
+      dataScale: PropTypes.number.isRequired,
+      sidebarWidth: PropTypes.number.isRequired,
+      waterfallWidth: PropTypes.number.isRequired,
+      onFocus: PropTypes.func,
+    };
+  }
 
-  propTypes: {
-    marker: PropTypes.object.isRequired,
-    startTime: PropTypes.number.isRequired,
-    endTime: PropTypes.number.isRequired,
-    dataScale: PropTypes.number.isRequired,
-    sidebarWidth: PropTypes.number.isRequired,
-    waterfallWidth: PropTypes.number.isRequired,
-    onFocus: PropTypes.func,
-  },
+  constructor(props) {
+    super(props);
 
-  getInitialState() {
-    return {
+    this.state = {
       focused: null,
       expanded: new Set()
     };
-  },
+
+    this._getRoots = this._getRoots.bind(this);
+    this._getParent = this._getParent.bind(this);
+    this._getChildren = this._getChildren.bind(this);
+    this._getKey = this._getKey.bind(this);
+    this._isExpanded = this._isExpanded.bind(this);
+    this._onExpand = this._onExpand.bind(this);
+    this._onCollapse = this._onCollapse.bind(this);
+    this._onFocus = this._onFocus.bind(this);
+    this._filter = this._filter.bind(this);
+    this._renderItem = this._renderItem.bind(this);
+  }
 
   _getRoots(node) {
     let roots = this.props.marker.submarkers || [];
     return roots.filter(this._filter);
-  },
+  }
 
   /**
    * Find the parent node of 'node' with a depth-first search of the marker tree
    */
   _getParent(node) {
     function findParent(marker) {
       if (marker.submarkers) {
         for (let submarker of marker.submarkers) {
@@ -85,83 +99,83 @@ const WaterfallTree = createClass({
     }
 
     let rootMarker = this.props.marker;
     let parent = findParent(rootMarker);
 
     // We are interested only in parent markers that are rendered,
     // which rootMarker is not. Return null if the parent is rootMarker.
     return parent !== rootMarker ? parent : null;
-  },
+  }
 
   _getChildren(node) {
     let submarkers = node.submarkers || [];
     return submarkers.filter(this._filter);
-  },
+  }
 
   _getKey(node) {
     return `marker-${node.index}`;
-  },
+  }
 
   _isExpanded(node) {
     return this.state.expanded.has(node);
-  },
+  }
 
   _onExpand(node) {
     this.setState(state => {
       let expanded = new Set(state.expanded);
       expanded.add(node);
       return { expanded };
     });
-  },
+  }
 
   _onCollapse(node) {
     this.setState(state => {
       let expanded = new Set(state.expanded);
       expanded.delete(node);
       return { expanded };
     });
-  },
+  }
 
   _onFocus(node) {
     this.setState({ focused: node });
     if (this.props.onFocus) {
       this.props.onFocus(node);
     }
-  },
+  }
 
   _filter(node) {
     let { startTime, endTime } = this.props;
     return isMarkerInRange(node, startTime, endTime);
-  },
+  }
 
   _renderItem(marker, level, focused, arrow, expanded) {
     let { startTime, dataScale, sidebarWidth } = this.props;
     return WaterfallTreeRow({
       marker,
       level,
       arrow,
       expanded,
       focused,
       startTime,
       dataScale,
       sidebarWidth
     });
-  },
+  }
 
   render() {
     return Tree({
       getRoots: this._getRoots,
       getParent: this._getParent,
       getChildren: this._getChildren,
       getKey: this._getKey,
       isExpanded: this._isExpanded,
       onExpand: this._onExpand,
       onCollapse: this._onCollapse,
       onFocus: this._onFocus,
       renderItem: this._renderItem,
       focused: this.state.focused,
       itemHeight: WATERFALL_TREE_ROW_HEIGHT
     });
   }
-});
+}
 
 module.exports = WaterfallTree;
--- a/devtools/client/performance/components/waterfall.js
+++ b/devtools/client/performance/components/waterfall.js
@@ -3,17 +3,19 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 /**
  * This file contains the "waterfall" view, essentially a detailed list
  * of all the markers in the timeline data.
  */
 
-const { DOM: dom, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
+const { createFactory } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const WaterfallHeader = createFactory(require("./waterfall-header"));
 const WaterfallTree = createFactory(require("./waterfall-tree"));
 
 function Waterfall(props) {
   return dom.div(
     { className: "waterfall-markers" },
     WaterfallHeader(props),
     WaterfallTree(props)