Bug 1411531 - Web Console to ES6 classes r?nchevobbe draft
authorMichael Ratcliffe <mratcliffe@mozilla.com>
Wed, 25 Oct 2017 13:33:15 +0100
changeset 686238 0399772cfce244c1b577c1193b916fe58178c080
parent 686237 5321fb41338a56b65877cc000c578e26ae8bea11
child 737336 cca367da74d75ed4b5fda096f8d5f54c68ce4af3
push id86135
push userbmo:mratcliffe@mozilla.com
push dateWed, 25 Oct 2017 16:15:16 +0000
reviewersnchevobbe
bugs1411531
milestone58.0a1
Bug 1411531 - Web Console to ES6 classes r?nchevobbe MozReview-Commit-ID: BBtCmeyMBaH
devtools/client/webconsole/net/components/cookies-tab.js
devtools/client/webconsole/net/components/headers-tab.js
devtools/client/webconsole/net/components/net-info-body.js
devtools/client/webconsole/net/components/net-info-group-list.js
devtools/client/webconsole/net/components/net-info-group.js
devtools/client/webconsole/net/components/net-info-params.js
devtools/client/webconsole/net/components/params-tab.js
devtools/client/webconsole/net/components/post-tab.js
devtools/client/webconsole/net/components/response-tab.js
devtools/client/webconsole/net/components/size-limit.js
devtools/client/webconsole/net/components/spinner.js
devtools/client/webconsole/net/components/stacktrace-tab.js
devtools/client/webconsole/new-console-output/components/ConsoleOutput.js
devtools/client/webconsole/new-console-output/components/ConsoleTable.js
devtools/client/webconsole/new-console-output/components/FilterBar.js
devtools/client/webconsole/new-console-output/components/Message.js
devtools/client/webconsole/new-console-output/components/MessageContainer.js
--- a/devtools/client/webconsole/net/components/cookies-tab.js
+++ b/devtools/client/webconsole/net/components/cookies-tab.js
@@ -1,50 +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 React = require("devtools/client/shared/vendor/react");
-const NetInfoGroupList = React.createFactory(require("./net-info-group-list"));
-const Spinner = React.createFactory(require("./spinner"));
-
-// Shortcuts
-const DOM = React.DOM;
-const PropTypes = React.PropTypes;
+const { Component, createFactory, DOM, PropTypes } =
+  require("devtools/client/shared/vendor/react");
+const NetInfoGroupList = createFactory(require("./net-info-group-list"));
+const Spinner = createFactory(require("./spinner"));
 
 /**
  * This template represents 'Cookies' tab displayed when the user
  * expands network log in the Console panel. It's responsible for rendering
  * sent and received cookies.
  */
-var CookiesTab = React.createClass({
-  propTypes: {
-    actions: PropTypes.shape({
-      requestData: PropTypes.func.isRequired
-    }),
-    data: PropTypes.object.isRequired,
-  },
-
-  displayName: "CookiesTab",
+class CookiesTab extends Component {
+  static get propTypes() {
+    return {
+      actions: PropTypes.shape({
+        requestData: PropTypes.func.isRequired
+      }),
+      data: PropTypes.object.isRequired,
+    };
+  }
 
   componentDidMount() {
     let { actions, data } = this.props;
     let requestCookies = data.request.cookies;
     let responseCookies = data.response.cookies;
 
     // TODO: use async action objects as soon as Redux is in place
     if (!requestCookies || !requestCookies.length) {
       actions.requestData("requestCookies");
     }
 
     if (!responseCookies || !responseCookies.length) {
       actions.requestData("responseCookies");
     }
-  },
+  }
 
   render() {
     let { actions, data: file } = this.props;
     let requestCookies = file.request.cookies;
     let responseCookies = file.response.cookies;
 
     // The cookie panel displays two groups of cookies:
     // 1) Response Cookies
@@ -64,12 +61,12 @@ var CookiesTab = React.createClass({
         DOM.div({className: "panelContent"},
           NetInfoGroupList({
             groups: groups
           })
         )
       )
     );
   }
-});
+}
 
 // Exports from this module
 module.exports = CookiesTab;
--- a/devtools/client/webconsole/net/components/headers-tab.js
+++ b/devtools/client/webconsole/net/components/headers-tab.js
@@ -1,51 +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 NetInfoGroupList = React.createFactory(require("./net-info-group-list"));
-const Spinner = React.createFactory(require("./spinner"));
-
-// Shortcuts
-const DOM = React.DOM;
-const PropTypes = React.PropTypes;
+const { Component, createFactory, DOM, PropTypes } =
+  require("devtools/client/shared/vendor/react");
+const NetInfoGroupList = createFactory(require("./net-info-group-list"));
+const Spinner = createFactory(require("./spinner"));
 
 /**
  * This template represents 'Headers' tab displayed when the user
  * expands network log in the Console panel. It's responsible for rendering
  * request and response HTTP headers.
  */
-var HeadersTab = React.createClass({
-  propTypes: {
-    actions: PropTypes.shape({
-      requestData: PropTypes.func.isRequired
-    }),
-    data: PropTypes.object.isRequired,
-  },
-
-  displayName: "HeadersTab",
+class HeadersTab extends Component {
+  static get propTypes() {
+    return {
+      actions: PropTypes.shape({
+        requestData: PropTypes.func.isRequired
+      }),
+      data: PropTypes.object.isRequired,
+    };
+  }
 
   componentDidMount() {
     let { actions, data } = this.props;
     let requestHeaders = data.request.headers;
     let responseHeaders = data.response.headers;
 
     // Request headers if they are not available yet.
     // TODO: use async action objects as soon as Redux is in place
     if (!requestHeaders) {
       actions.requestData("requestHeaders");
     }
 
     if (!responseHeaders) {
       actions.requestData("responseHeaders");
     }
-  },
+  }
 
   render() {
     let { data } = this.props;
     let requestHeaders = data.request.headers;
     let responseHeaders = data.response.headers;
 
     // TODO: Another groups to implement:
     // 1) Cached Headers
@@ -68,12 +65,12 @@ var HeadersTab = React.createClass({
     return (
       DOM.div({className: "headersTabBox"},
         DOM.div({className: "panelContent"},
           NetInfoGroupList({groups: groups})
         )
       )
     );
   }
-});
+}
 
 // Exports from this module
 module.exports = HeadersTab;
--- a/devtools/client/webconsole/net/components/net-info-body.js
+++ b/devtools/client/webconsole/net/components/net-info-body.js
@@ -1,86 +1,92 @@
 /* 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 { Component, createFactory, PropTypes } =
+  require("devtools/client/shared/vendor/react");
 const { createFactories } = require("devtools/client/shared/react-utils");
 const { Tabs, TabPanel } = createFactories(require("devtools/client/shared/components/tabs/Tabs"));
 
 // Network
-const HeadersTab = React.createFactory(require("./headers-tab"));
-const ResponseTab = React.createFactory(require("./response-tab"));
-const ParamsTab = React.createFactory(require("./params-tab"));
-const CookiesTab = React.createFactory(require("./cookies-tab"));
-const PostTab = React.createFactory(require("./post-tab"));
-const StackTraceTab = React.createFactory(require("./stacktrace-tab"));
+const HeadersTab = createFactory(require("./headers-tab"));
+const ResponseTab = createFactory(require("./response-tab"));
+const ParamsTab = createFactory(require("./params-tab"));
+const CookiesTab = createFactory(require("./cookies-tab"));
+const PostTab = createFactory(require("./post-tab"));
+const StackTraceTab = createFactory(require("./stacktrace-tab"));
 const NetUtils = require("../utils/net");
 
-// Shortcuts
-const PropTypes = React.PropTypes;
 
 /**
  * This template renders the basic Network log info body. It's not
  * visible by default, the user needs to expand the network log
  * to see it.
  *
  * This is the set of tabs displaying details about network events:
  * 1) Headers - request and response headers
  * 2) Params - URL parameters
  * 3) Response - response body
  * 4) Cookies - request and response cookies
  * 5) Post - posted data
  */
-var NetInfoBody = React.createClass({
-  propTypes: {
-    tabActive: PropTypes.number.isRequired,
-    actions: PropTypes.object.isRequired,
-    data: PropTypes.shape({
-      request: PropTypes.object.isRequired,
-      response: PropTypes.object.isRequired
-    }),
-    // Service to enable the source map feature.
-    sourceMapService: PropTypes.object,
-  },
+class NetInfoBody extends Component {
+  static get propTypes() {
+    return {
+      tabActive: PropTypes.number.isRequired,
+      actions: PropTypes.object.isRequired,
+      data: PropTypes.shape({
+        request: PropTypes.object.isRequired,
+        response: PropTypes.object.isRequired
+      }),
+      // Service to enable the source map feature.
+      sourceMapService: PropTypes.object,
+    };
+  }
 
-  displayName: "NetInfoBody",
-
-  getDefaultProps() {
+  static get defaultProps() {
     return {
       tabActive: 0
     };
-  },
+  }
 
-  getInitialState() {
-    return {
+  constructor(props) {
+    super(props);
+
+    this.state = {
       data: {
         request: {},
         response: {}
       },
-      tabActive: this.props.tabActive,
+      tabActive: props.tabActive,
     };
-  },
+
+    this.onTabChanged = this.onTabChanged.bind(this);
+    this.hasCookies = this.hasCookies.bind(this);
+    this.hasStackTrace = this.hasStackTrace.bind(this);
+    this.getTabPanels = this.getTabPanels.bind(this);
+  }
 
   onTabChanged(index) {
     this.setState({tabActive: index});
-  },
+  }
 
   hasCookies() {
     let {request, response} = this.state.data;
     return this.state.hasCookies ||
       NetUtils.getHeaderValue(request.headers, "Cookie") ||
       NetUtils.getHeaderValue(response.headers, "Set-Cookie");
-  },
+  }
 
   hasStackTrace() {
     let {cause} = this.state.data;
     return cause && cause.stacktrace && cause.stacktrace.length > 0;
-  },
+  }
 
   getTabPanels() {
     let { actions, sourceMapService } = this.props;
     let data = this.state.data;
     let {request} = data;
 
     // Flags for optional tabs. Some tabs are visible only if there
     // are data to display.
@@ -158,25 +164,25 @@ var NetInfoBody = React.createClass({
             actions: actions,
             sourceMapService: sourceMapService,
           })
         )
       );
     }
 
     return panels;
-  },
+  }
 
   render() {
     let tabActive = this.state.tabActive;
     let tabPanels = this.getTabPanels();
     return (
       Tabs({
         tabActive: tabActive,
         onAfterChange: this.onTabChanged},
         tabPanels
       )
     );
   }
-});
+}
 
 // Exports from this module
 module.exports = NetInfoBody;
--- a/devtools/client/webconsole/net/components/net-info-group-list.js
+++ b/devtools/client/webconsole/net/components/net-info-group-list.js
@@ -1,30 +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 React = require("devtools/client/shared/vendor/react");
-const NetInfoGroup = React.createFactory(require("./net-info-group"));
-
-// Shortcuts
-const DOM = React.DOM;
-const PropTypes = React.PropTypes;
+const { Component, createFactory, DOM, PropTypes } =
+  require("devtools/client/shared/vendor/react");
+const NetInfoGroup = createFactory(require("./net-info-group"));
 
 /**
  * This template is responsible for rendering sections/groups inside tabs.
  * It's used e.g to display Response and Request headers as separate groups.
  */
-var NetInfoGroupList = React.createClass({
-  propTypes: {
-    groups: PropTypes.array.isRequired,
-  },
-
-  displayName: "NetInfoGroupList",
+class NetInfoGroupList extends Component {
+  static get propTypes() {
+    return {
+      groups: PropTypes.array.isRequired,
+    };
+  }
 
   render() {
     let groups = this.props.groups;
 
     // Filter out empty groups.
     groups = groups.filter(group => {
       return group && ((group.params && group.params.length) || group.content);
     });
@@ -36,12 +33,12 @@ var NetInfoGroupList = React.createClass
     });
 
     return (
       DOM.div({className: "netInfoGroupList"},
         groups
       )
     );
   }
-});
+}
 
 // Exports from this module
 module.exports = NetInfoGroupList;
--- a/devtools/client/webconsole/net/components/net-info-group.js
+++ b/devtools/client/webconsole/net/components/net-info-group.js
@@ -1,53 +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 React = require("devtools/client/shared/vendor/react");
-const NetInfoParams = React.createFactory(require("./net-info-params"));
-
-// Shortcuts
-const DOM = React.DOM;
-const PropTypes = React.PropTypes;
+const { Component, createFactory, DOM, PropTypes } =
+  require("devtools/client/shared/vendor/react");
+const NetInfoParams = createFactory(require("./net-info-params"));
 
 /**
  * This template represents a group of data within a tab. For example,
  * Headers tab has two groups 'Request Headers' and 'Response Headers'
  * The Response tab can also have two groups 'Raw Data' and 'JSON'
  */
-var NetInfoGroup = React.createClass({
-  propTypes: {
-    type: PropTypes.string.isRequired,
-    name: PropTypes.string.isRequired,
-    params: PropTypes.array,
-    content: PropTypes.element,
-    open: PropTypes.bool
-  },
+class NetInfoGroup extends Component {
+  static get propTypes() {
+    return {
+      type: PropTypes.string.isRequired,
+      name: PropTypes.string.isRequired,
+      params: PropTypes.array,
+      content: PropTypes.element,
+      open: PropTypes.bool
+    };
+  }
 
-  displayName: "NetInfoGroup",
-
-  getDefaultProps() {
+  static get defaultProps() {
     return {
       open: true,
     };
-  },
+  }
+
+  constructor(props) {
+    super(props);
 
-  getInitialState() {
-    return {
-      open: this.props.open,
+    this.state = {
+      open: props.open,
     };
-  },
+
+    this.onToggle = this.onToggle.bind(this);
+  }
 
   onToggle(event) {
     this.setState({
       open: !this.state.open
     });
-  },
+  }
 
   render() {
     let content = this.props.content;
 
     if (!content && this.props.params) {
       content = NetInfoParams({
         params: this.props.params
       });
@@ -69,12 +70,12 @@ var NetInfoGroup = React.createClass({
           this.props.name
         ),
         DOM.div({className: "netInfoGroupContent"},
           content
         )
       )
     );
   }
-});
+}
 
 // Exports from this module
 module.exports = NetInfoGroup;
--- a/devtools/client/webconsole/net/components/net-info-params.js
+++ b/devtools/client/webconsole/net/components/net-info-params.js
@@ -1,32 +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 React = require("devtools/client/shared/vendor/react");
-
-// Shortcuts
-const DOM = React.DOM;
-const PropTypes = React.PropTypes;
+const { Component, DOM, PropTypes } =
+  require("devtools/client/shared/vendor/react");
 
 /**
  * This template renders list of parameters within a group.
  * It's essentially a list of name + value pairs.
  */
-var NetInfoParams = React.createClass({
-  displayName: "NetInfoParams",
-
-  propTypes: {
-    params: PropTypes.arrayOf(PropTypes.shape({
-      name: PropTypes.string.isRequired,
-      value: PropTypes.string.isRequired
-    })).isRequired,
-  },
+class NetInfoParams extends Component {
+  static get propTypes() {
+    return {
+      params: PropTypes.arrayOf(PropTypes.shape({
+        name: PropTypes.string.isRequired,
+        value: PropTypes.string.isRequired
+      })).isRequired,
+    };
+  }
 
   render() {
     let params = this.props.params || [];
 
     params.sort(function (a, b) {
       return a.name > b.name ? 1 : -1;
     });
 
@@ -47,12 +44,12 @@ var NetInfoParams = React.createClass({
     return (
       DOM.table({cellPadding: 0, cellSpacing: 0},
         DOM.tbody({},
           rows
         )
       )
     );
   }
-});
+}
 
 // Exports from this module
 module.exports = NetInfoParams;
--- a/devtools/client/webconsole/net/components/params-tab.js
+++ b/devtools/client/webconsole/net/components/params-tab.js
@@ -1,41 +1,38 @@
 /* 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 NetInfoParams = React.createFactory(require("./net-info-params"));
-
-// Shortcuts
-const DOM = React.DOM;
-const PropTypes = React.PropTypes;
+const { Component, createFactory, DOM, PropTypes } =
+  require("devtools/client/shared/vendor/react");
+const NetInfoParams = createFactory(require("./net-info-params"));
 
 /**
  * This template represents 'Params' tab displayed when the user
  * expands network log in the Console panel. It's responsible for
  * displaying URL parameters (query string).
  */
-var ParamsTab = React.createClass({
-  propTypes: {
-    data: PropTypes.shape({
-      request: PropTypes.object.isRequired
-    })
-  },
-
-  displayName: "ParamsTab",
+class ParamsTab extends Component {
+  static get propTypes() {
+    return {
+      data: PropTypes.shape({
+        request: PropTypes.object.isRequired
+      })
+    };
+  }
 
   render() {
     let data = this.props.data;
 
     return (
       DOM.div({className: "paramsTabBox"},
         DOM.div({className: "panelContent"},
           NetInfoParams({params: data.request.queryString})
         )
       )
     );
   }
-});
+}
 
 // Exports from this module
 module.exports = ParamsTab;
--- a/devtools/client/webconsole/net/components/post-tab.js
+++ b/devtools/client/webconsole/net/components/post-tab.js
@@ -1,62 +1,74 @@
 /* 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 { Component, createFactory, DOM, PropTypes } =
+  require("devtools/client/shared/vendor/react");
 
-const TreeView = React.createFactory(require("devtools/client/shared/components/tree/TreeView"));
+const TreeView =
+  createFactory(require("devtools/client/shared/components/tree/TreeView"));
 
-const { REPS, MODE, parseURLEncodedText } = require("devtools/client/shared/components/reps/reps");
+const { REPS, MODE, parseURLEncodedText } =
+  require("devtools/client/shared/components/reps/reps");
 const { Rep } = REPS;
 
 // Network
-const NetInfoParams = React.createFactory(require("./net-info-params"));
-const NetInfoGroupList = React.createFactory(require("./net-info-group-list"));
-const Spinner = React.createFactory(require("./spinner"));
-const SizeLimit = React.createFactory(require("./size-limit"));
+const NetInfoParams = createFactory(require("./net-info-params"));
+const NetInfoGroupList = createFactory(require("./net-info-group-list"));
+const Spinner = createFactory(require("./spinner"));
+const SizeLimit = createFactory(require("./size-limit"));
 const NetUtils = require("../utils/net");
 const Json = require("../utils/json");
 
-// Shortcuts
-const DOM = React.DOM;
-const PropTypes = React.PropTypes;
-
 /**
  * This template represents 'Post' tab displayed when the user
  * expands network log in the Console panel. It's responsible for
  * displaying posted data (HTTP post body).
  */
-var PostTab = React.createClass({
-  propTypes: {
-    data: PropTypes.shape({
-      request: PropTypes.object.isRequired
-    }),
-    actions: PropTypes.object.isRequired
-  },
+class PostTab extends Component {
+  static get propTypes() {
+    return {
+      data: PropTypes.shape({
+        request: PropTypes.object.isRequired
+      }),
+      actions: PropTypes.object.isRequired
+    };
+  }
 
-  displayName: "PostTab",
+  constructor(props) {
+    super(props);
+    this.isJson = this.isJson.bind(this);
+    this.parseJson = this.parseJson.bind(this);
+    this.renderJson = this.renderJson.bind(this);
+    this.parseXml = this.parseXml.bind(this);
+    this.isXml = this.isXml.bind(this);
+    this.renderXml = this.renderXml.bind(this);
+    this.renderMultiPart = this.renderMultiPart.bind(this);
+    this.renderUrlEncoded = this.renderUrlEncoded.bind(this);
+    this.renderRawData = this.renderRawData.bind(this);
+  }
 
   isJson(file) {
     let text = file.request.postData.text;
     let value = NetUtils.getHeaderValue(file.request.headers, "content-type");
     return Json.isJSON(value, text);
-  },
+  }
 
   parseJson(file) {
     let postData = file.request.postData;
     if (!postData) {
       return null;
     }
 
     let jsonString = new String(postData.text);
     return Json.parseJSONString(jsonString);
-  },
+  }
 
   /**
    * Render JSON post data as an expandable tree.
    */
   renderJson(file) {
     let text = file.request.postData.text;
     if (!text || isLongString(text)) {
       return null;
@@ -78,42 +90,42 @@ var PostTab = React.createClass({
         object: json,
         mode: MODE.TINY,
         renderValue: props => Rep(Object.assign({}, props, {
           cropLimit: 50,
         })),
       }),
       name: Locale.$STR("jsonScopeName")
     };
-  },
+  }
 
   parseXml(file) {
     let text = file.request.postData.text;
     if (isLongString(text)) {
       return null;
     }
 
     return NetUtils.parseXml({
       mimeType: NetUtils.getHeaderValue(file.request.headers, "content-type"),
       text: text,
     });
-  },
+  }
 
   isXml(file) {
     if (isLongString(file.request.postData.text)) {
       return false;
     }
 
     let value = NetUtils.getHeaderValue(file.request.headers, "content-type");
     if (!value) {
       return false;
     }
 
     return NetUtils.isHTML(value);
-  },
+  }
 
   renderXml(file) {
     let text = file.request.postData.text;
     if (!text || isLongString(text)) {
       return null;
     }
 
     if (!this.isXml(file)) {
@@ -122,34 +134,34 @@ var PostTab = React.createClass({
 
     let doc = this.parseXml(file);
     if (!doc) {
       return null;
     }
 
     // Proper component for rendering XML should be used (see bug 1247392)
     return null;
-  },
+  }
 
   /**
    * Multipart post data are parsed and nicely rendered
    * as an expandable tree of individual parts.
    */
   renderMultiPart(file) {
     let text = file.request.postData.text;
     if (!text || isLongString(text)) {
       return;
     }
 
     if (NetUtils.isMultiPartRequest(file)) {
       // TODO: render multi part request (bug: 1247423)
     }
 
     return;
-  },
+  }
 
   /**
    * URL encoded post data are nicely rendered as a list
    * of parameters.
    */
   renderUrlEncoded(file) {
     let text = file.request.postData.text;
     if (!text || isLongString(text)) {
@@ -163,17 +175,17 @@ var PostTab = React.createClass({
     let lines = text.split("\n");
     let params = parseURLEncodedText(lines[lines.length - 1]);
 
     return {
       key: "url-encoded",
       content: NetInfoParams({params: params}),
       name: Locale.$STR("netRequest.params")
     };
-  },
+  }
 
   renderRawData(file) {
     let text = file.request.postData.text;
 
     let group;
 
     // The post body might reached the limit, so check if we are
     // dealing with a long string.
@@ -197,26 +209,26 @@ var PostTab = React.createClass({
         name: Locale.$STR("netRequest.rawData"),
         content: DOM.div({className: "netInfoResponseContent"},
           sanitize(text)
         )
       };
     }
 
     return group;
-  },
+  }
 
   componentDidMount() {
     let { actions, data: file } = this.props;
 
     if (!file.request.postData) {
       // TODO: use async action objects as soon as Redux is in place
       actions.requestData("requestPostData");
     }
-  },
+  }
 
   render() {
     let { actions, data: file } = this.props;
 
     if (file.discardRequestBody) {
       return DOM.span({className: "netInfoBodiesDiscarded"},
         Locale.$STR("netRequest.requestBodyDiscarded")
       );
@@ -252,17 +264,17 @@ var PostTab = React.createClass({
         DOM.div({className: "panelContent"},
           NetInfoGroupList({
             groups: groups
           })
         )
       )
     );
   }
-});
+}
 
 // Helpers
 
 /**
  * Workaround for a "not well-formed" error that react
  * reports when there's multipart data passed to render.
  */
 function sanitize(text) {
--- a/devtools/client/webconsole/net/components/response-tab.js
+++ b/devtools/client/webconsole/net/components/response-tab.js
@@ -1,95 +1,106 @@
 /* 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 { Component, createFactory, DOM, PropTypes } =
+  require("devtools/client/shared/vendor/react");
 
 // Reps
-const TreeView = React.createFactory(require("devtools/client/shared/components/tree/TreeView"));
+const TreeView = createFactory(require("devtools/client/shared/components/tree/TreeView"));
 const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
 const { Rep } = REPS;
 
 // Network
-const SizeLimit = React.createFactory(require("./size-limit"));
-const NetInfoGroupList = React.createFactory(require("./net-info-group-list"));
-const Spinner = React.createFactory(require("./spinner"));
+const SizeLimit = createFactory(require("./size-limit"));
+const NetInfoGroupList = createFactory(require("./net-info-group-list"));
+const Spinner = createFactory(require("./spinner"));
 const Json = require("../utils/json");
 const NetUtils = require("../utils/net");
 
-// Shortcuts
-const DOM = React.DOM;
-const PropTypes = React.PropTypes;
-
 /**
  * This template represents 'Response' tab displayed when the user
  * expands network log in the Console panel. It's responsible for
  * rendering HTTP response body.
  *
  * In case of supported response mime-type (e.g. application/json,
  * text/xml, etc.), the response is parsed using appropriate parser
  * and rendered accordingly.
  */
-var ResponseTab = React.createClass({
-  propTypes: {
-    data: PropTypes.shape({
-      request: PropTypes.object.isRequired,
-      response: PropTypes.object.isRequired
-    }),
-    actions: PropTypes.object.isRequired
-  },
+class ResponseTab extends Component {
+  static get propTypes() {
+    return {
+      data: PropTypes.shape({
+        request: PropTypes.object.isRequired,
+        response: PropTypes.object.isRequired
+      }),
+      actions: PropTypes.object.isRequired
+    };
+  }
 
-  displayName: "ResponseTab",
+  constructor(props) {
+    super(props);
+    this.isJson = this.isJson.bind(this);
+    this.parseJson = this.parseJson.bind(this);
+    this.isImage = this.isImage.bind(this);
+    this.isXml = this.isXml.bind(this);
+    this.parseXml = this.parseXml.bind(this);
+    this.renderJson = this.renderJson.bind(this);
+    this.renderImage = this.renderImage.bind(this);
+    this.renderXml = this.renderXml.bind(this);
+    this.renderFormattedResponse = this.renderFormattedResponse.bind(this);
+    this.renderRawResponse = this.renderRawResponse.bind(this);
+  }
 
   // Response Types
 
   isJson(content) {
     if (isLongString(content.text)) {
       return false;
     }
 
     return Json.isJSON(content.mimeType, content.text);
-  },
+  }
 
   parseJson(file) {
     let content = file.response.content;
     if (isLongString(content.text)) {
       return null;
     }
 
     let jsonString = new String(content.text);
     return Json.parseJSONString(jsonString);
-  },
+  }
 
   isImage(content) {
     if (isLongString(content.text)) {
       return false;
     }
 
     return NetUtils.isImage(content.mimeType);
-  },
+  }
 
   isXml(content) {
     if (isLongString(content.text)) {
       return false;
     }
 
     return NetUtils.isHTML(content.mimeType);
-  },
+  }
 
   parseXml(file) {
     let content = file.response.content;
     if (isLongString(content.text)) {
       return null;
     }
 
     return NetUtils.parseXml(content);
-  },
+  }
 
   // Rendering
 
   renderJson(file) {
     let content = file.response.content;
     if (!this.isJson(content)) {
       return null;
     }
@@ -106,46 +117,46 @@ var ResponseTab = React.createClass({
         object: json,
         mode: MODE.TINY,
         renderValue: props => Rep(Object.assign({}, props, {
           cropLimit: 50,
         })),
       }),
       name: Locale.$STR("jsonScopeName")
     };
-  },
+  }
 
   renderImage(file) {
     let content = file.response.content;
     if (!this.isImage(content)) {
       return null;
     }
 
     let dataUri = "data:" + content.mimeType + ";base64," + content.text;
     return {
       key: "image",
       content: DOM.img({src: dataUri}),
       name: Locale.$STR("netRequest.image")
     };
-  },
+  }
 
   renderXml(file) {
     let content = file.response.content;
     if (!this.isXml(content)) {
       return null;
     }
 
     let doc = this.parseXml(file);
     if (!doc) {
       return null;
     }
 
     // Proper component for rendering XML should be used (see bug 1247392)
     return null;
-  },
+  }
 
   /**
    * If full response text is available, let's try to parse and
    * present nicely according to the underlying format.
    */
   renderFormattedResponse(file) {
     let content = file.response.content;
     if (typeof content.text == "object") {
@@ -161,17 +172,17 @@ var ResponseTab = React.createClass({
     if (group) {
       return group;
     }
 
     group = this.renderXml(file);
     if (group) {
       return group;
     }
-  },
+  }
 
   renderRawResponse(file) {
     let group;
     let content = file.response.content;
 
     // The response might reached the limit, so check if we are
     // dealing with a long string.
     if (typeof content.text == "object") {
@@ -194,27 +205,27 @@ var ResponseTab = React.createClass({
         name: Locale.$STR("netRequest.rawData"),
         content: DOM.div({className: "netInfoResponseContent"},
           content.text
         )
       };
     }
 
     return group;
-  },
+  }
 
   componentDidMount() {
     let { actions, data: file } = this.props;
     let content = file.response.content;
 
     if (!content || typeof (content.text) == "undefined") {
       // TODO: use async action objects as soon as Redux is in place
       actions.requestData("responseContent");
     }
-  },
+  }
 
   /**
    * The response panel displays two groups:
    *
    * 1) Formatted response (in case of supported format, e.g. JSON, XML, etc.)
    * 2) Raw response data (always displayed if not discarded)
    */
   render() {
@@ -260,17 +271,17 @@ var ResponseTab = React.createClass({
         DOM.div({className: "panelContent"},
           NetInfoGroupList({
             groups: groups
           })
         )
       )
     );
   }
-});
+}
 
 // Helpers
 
 function isLongString(text) {
   return typeof text == "object";
 }
 
 // Exports from this module
--- a/devtools/client/webconsole/net/components/size-limit.js
+++ b/devtools/client/webconsole/net/components/size-limit.js
@@ -1,45 +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 React = require("devtools/client/shared/vendor/react");
-
-// Shortcuts
-const DOM = React.DOM;
-const PropTypes = React.PropTypes;
+const { Component, DOM, PropTypes } =
+  require("devtools/client/shared/vendor/react");
 
 /**
  * This template represents a size limit notification message
  * used e.g. in the Response tab when response body exceeds
  * size limit. The message contains a link allowing the user
  * to fetch the rest of the data from the backend (debugger server).
  */
-var SizeLimit = React.createClass({
-  propTypes: {
-    data: PropTypes.object.isRequired,
-    message: PropTypes.string.isRequired,
-    link: PropTypes.string.isRequired,
-    actions: PropTypes.shape({
-      resolveString: PropTypes.func.isRequired
-    }),
-  },
+class SizeLimit extends Component {
+  static get propTypes() {
+    return {
+      data: PropTypes.object.isRequired,
+      message: PropTypes.string.isRequired,
+      link: PropTypes.string.isRequired,
+      actions: PropTypes.shape({
+        resolveString: PropTypes.func.isRequired
+      }),
+    };
+  }
 
-  displayName: "SizeLimit",
+  constructor(props) {
+    super(props);
+    this.onClickLimit = this.onClickLimit.bind(this);
+  }
 
   // Event Handlers
 
   onClickLimit(event) {
     let actions = this.props.actions;
     let content = this.props.data;
 
     actions.resolveString(content, "text");
-  },
+  }
 
   // Rendering
 
   render() {
     let message = this.props.message;
     let link = this.props.link;
     let reLink = /^(.*)\{\{link\}\}(.*$)/;
     let m = message.match(reLink);
@@ -51,12 +53,12 @@ var SizeLimit = React.createClass({
             className: "objectLink",
             onClick: this.onClickLimit},
               link
           ),
           DOM.span({}, m[2])
         )
     );
   }
-});
+}
 
 // Exports from this module
 module.exports = SizeLimit;
--- a/devtools/client/webconsole/net/components/spinner.js
+++ b/devtools/client/webconsole/net/components/spinner.js
@@ -1,26 +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 React = require("devtools/client/shared/vendor/react");
-
-// Shortcuts
-const DOM = React.DOM;
+const { Component, DOM } = require("devtools/client/shared/vendor/react");
 
 /**
  * This template represents a throbber displayed when the UI
  * is waiting for data coming from the backend (debugging server).
  */
-var Spinner = React.createClass({
-  displayName: "Spinner",
-
+class Spinner extends Component {
   render() {
     return (
       DOM.div({className: "devtools-throbber"})
     );
   }
-});
+}
 
 // Exports from this module
 module.exports = Spinner;
--- a/devtools/client/webconsole/net/components/stacktrace-tab.js
+++ b/devtools/client/webconsole/net/components/stacktrace-tab.js
@@ -1,31 +1,33 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const { PropTypes, createClass, createFactory } = require("devtools/client/shared/vendor/react");
-const StackTrace = createFactory(require("devtools/client/shared/components/StackTrace"));
-
-const StackTraceTab = createClass({
-  displayName: "StackTraceTab",
+const { PropTypes, Component, createFactory } =
+  require("devtools/client/shared/vendor/react");
+const StackTrace =
+  createFactory(require("devtools/client/shared/components/StackTrace"));
 
-  propTypes: {
-    data: PropTypes.object.isRequired,
-    actions: PropTypes.shape({
-      onViewSourceInDebugger: PropTypes.func.isRequired
-    }),
-    // Service to enable the source map feature.
-    sourceMapService: PropTypes.object,
-  },
+class StackTraceTab extends Component {
+  static get propTypes() {
+    return {
+      data: PropTypes.object.isRequired,
+      actions: PropTypes.shape({
+        onViewSourceInDebugger: PropTypes.func.isRequired
+      }),
+      // Service to enable the source map feature.
+      sourceMapService: PropTypes.object,
+    };
+  }
 
   render() {
     let { stacktrace } = this.props.data.cause;
     let { actions, sourceMapService } = this.props;
     let onViewSourceInDebugger = actions.onViewSourceInDebugger.bind(actions);
 
     return StackTrace({ stacktrace, onViewSourceInDebugger, sourceMapService });
   }
-});
+}
 
 // Exports from this module
 module.exports = StackTraceTab;
--- a/devtools/client/webconsole/new-console-output/components/ConsoleOutput.js
+++ b/devtools/client/webconsole/new-console-output/components/ConsoleOutput.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 {
-  createClass,
+  Component,
   createFactory,
   DOM: dom,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const {
   getAllMessagesById,
@@ -19,45 +19,49 @@ const {
   getVisibleMessages,
   getAllRepeatById,
 } = require("devtools/client/webconsole/new-console-output/selectors/messages");
 const MessageContainer = createFactory(require("devtools/client/webconsole/new-console-output/components/MessageContainer").MessageContainer);
 const {
   MESSAGE_TYPE,
 } = require("devtools/client/webconsole/new-console-output/constants");
 
-const ConsoleOutput = createClass({
-
-  displayName: "ConsoleOutput",
+class ConsoleOutput extends Component {
+  static get propTypes() {
+    return {
+      messages: PropTypes.object.isRequired,
+      messagesUi: PropTypes.object.isRequired,
+      serviceContainer: PropTypes.shape({
+        attachRefToHud: PropTypes.func.isRequired,
+        openContextMenu: PropTypes.func.isRequired,
+        sourceMapService: PropTypes.object,
+      }),
+      dispatch: PropTypes.func.isRequired,
+      timestampsVisible: PropTypes.bool,
+      messagesTableData: PropTypes.object.isRequired,
+      messagesRepeat: PropTypes.object.isRequired,
+      networkMessagesUpdate: PropTypes.object.isRequired,
+      visibleMessages: PropTypes.array.isRequired,
+      networkMessageActiveTabId: PropTypes.string.isRequired,
+    };
+  }
 
-  propTypes: {
-    messages: PropTypes.object.isRequired,
-    messagesUi: PropTypes.object.isRequired,
-    serviceContainer: PropTypes.shape({
-      attachRefToHud: PropTypes.func.isRequired,
-      openContextMenu: PropTypes.func.isRequired,
-      sourceMapService: PropTypes.object,
-    }),
-    dispatch: PropTypes.func.isRequired,
-    timestampsVisible: PropTypes.bool,
-    messagesTableData: PropTypes.object.isRequired,
-    messagesRepeat: PropTypes.object.isRequired,
-    networkMessagesUpdate: PropTypes.object.isRequired,
-    visibleMessages: PropTypes.array.isRequired,
-    networkMessageActiveTabId: PropTypes.string.isRequired,
-  },
+  constructor(props) {
+    super(props);
+    this.onContextMenu = this.onContextMenu.bind(this);
+  }
 
   componentDidMount() {
     // Do the scrolling in the nextTick since this could hit console startup performances.
     // See https://bugzilla.mozilla.org/show_bug.cgi?id=1355869
     setTimeout(() => {
       scrollToBottom(this.outputNode);
     }, 0);
     this.props.serviceContainer.attachRefToHud("outputScroller", this.outputNode);
-  },
+  }
 
   componentWillUpdate(nextProps, nextState) {
     const outputNode = this.outputNode;
     if (!outputNode || !outputNode.lastChild) {
       // Force a scroll to bottom when messages are added to an empty console.
       // This makes the console stay pinned to the bottom if a batch of messages
       // are added after a page refresh (Bug 1402237).
       this.shouldScrollBottom = true;
@@ -73,29 +77,29 @@ const ConsoleOutput = createClass({
     // We need to scroll to the bottom if:
     // - the number of messages displayed changed
     //   and we are already scrolled to the bottom
     // - the number of messages in the store changed
     //   and the new message is an evaluation result.
     this.shouldScrollBottom =
       (messagesDelta > 0 && nextProps.messages.last().type === MESSAGE_TYPE.RESULT) ||
       (visibleMessagesDelta > 0 && isScrolledToBottom(lastChild, outputNode));
-  },
+  }
 
   componentDidUpdate() {
     if (this.shouldScrollBottom) {
       scrollToBottom(this.outputNode);
     }
-  },
+  }
 
   onContextMenu(e) {
     this.props.serviceContainer.openContextMenu(e);
     e.stopPropagation();
     e.preventDefault();
-  },
+  }
 
   render() {
     let {
       dispatch,
       visibleMessages,
       messages,
       messagesUi,
       messagesTableData,
@@ -126,17 +130,17 @@ const ConsoleOutput = createClass({
         onContextMenu: this.onContextMenu,
         ref: node => {
           this.outputNode = node;
         },
       }, messageNodes
       )
     );
   }
-});
+}
 
 function scrollToBottom(node) {
   node.scrollTop = node.scrollHeight;
 }
 
 function isScrolledToBottom(outputNode, scrollNode) {
   let lastNodeHeight = outputNode.lastChild ?
                        outputNode.lastChild.clientHeight : 0;
--- a/devtools/client/webconsole/new-console-output/components/ConsoleTable.js
+++ b/devtools/client/webconsole/new-console-output/components/ConsoleTable.js
@@ -1,63 +1,68 @@
 /* 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,
+  Component,
   createFactory,
   DOM: dom,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
 const ObjectClient = require("devtools/shared/client/object-client");
 const actions = require("devtools/client/webconsole/new-console-output/actions/messages");
 const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
 const { MODE } = require("devtools/client/shared/components/reps/reps");
 const GripMessageBody = createFactory(require("devtools/client/webconsole/new-console-output/components/GripMessageBody"));
 
 const TABLE_ROW_MAX_ITEMS = 1000;
 const TABLE_COLUMN_MAX_ITEMS = 10;
 
-const ConsoleTable = createClass({
-
-  displayName: "ConsoleTable",
+class ConsoleTable extends Component {
+  static get propTypes() {
+    return {
+      dispatch: PropTypes.func.isRequired,
+      parameters: PropTypes.array.isRequired,
+      serviceContainer: PropTypes.shape({
+        hudProxy: PropTypes.object.isRequired,
+      }),
+      id: PropTypes.string.isRequired,
+      tableData: PropTypes.object,
+    };
+  }
 
-  propTypes: {
-    dispatch: PropTypes.func.isRequired,
-    parameters: PropTypes.array.isRequired,
-    serviceContainer: PropTypes.shape({
-      hudProxy: PropTypes.object.isRequired,
-    }),
-    id: PropTypes.string.isRequired,
-    tableData: PropTypes.object,
-  },
+  constructor(props) {
+    super(props);
+    this.getHeaders = this.getHeaders.bind(this);
+    this.getRows = this.getRows.bind(this);
+  }
 
-  componentWillMount: function () {
+  componentWillMount() {
     const {id, dispatch, serviceContainer, parameters} = this.props;
 
     if (!Array.isArray(parameters) || parameters.length === 0) {
       return;
     }
 
     const client = new ObjectClient(serviceContainer.hudProxy.client, parameters[0]);
     let dataType = getParametersDataType(parameters);
 
     // Get all the object properties.
     dispatch(actions.messageTableDataGet(id, client, dataType));
-  },
+  }
 
-  getHeaders: function (columns) {
+  getHeaders(columns) {
     let headerItems = [];
     columns.forEach((value, key) => headerItems.push(dom.th({}, value)));
     return headerItems;
-  },
+  }
 
-  getRows: function (columns, items) {
+  getRows(columns, items) {
     const {
       dispatch,
       serviceContainer,
     } = this.props;
 
     return items.map(item => {
       let cells = [];
       columns.forEach((value, key) => {
@@ -71,19 +76,19 @@ const ConsoleTable = createClass({
               serviceContainer,
               dispatch,
             })
           )
         );
       });
       return dom.tr({}, cells);
     });
-  },
+  }
 
-  render: function () {
+  render() {
     const {parameters, tableData} = this.props;
     const headersGrip = parameters[1];
     const headers = headersGrip && headersGrip.preview ? headersGrip.preview.items : null;
 
     // if tableData is nullable, we don't show anything.
     if (!tableData) {
       return null;
     }
@@ -96,17 +101,17 @@ const ConsoleTable = createClass({
 
     return (
       dom.table({className: "new-consoletable devtools-monospace"},
         dom.thead({}, this.getHeaders(columns)),
         dom.tbody({}, this.getRows(columns, items))
       )
     );
   }
-});
+}
 
 function getParametersDataType(parameters = null) {
   if (!Array.isArray(parameters) || parameters.length === 0) {
     return null;
   }
   return parameters[0].class;
 }
 
--- a/devtools/client/webconsole/new-console-output/components/FilterBar.js
+++ b/devtools/client/webconsole/new-console-output/components/FilterBar.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 {
-  createClass,
+  Component,
   DOM: dom,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { getAllFilters } = require("devtools/client/webconsole/new-console-output/selectors/filters");
 const { getFilteredMessagesCount } = require("devtools/client/webconsole/new-console-output/selectors/messages");
 const { getAllUi } = require("devtools/client/webconsole/new-console-output/selectors/ui");
 const actions = require("devtools/client/webconsole/new-console-output/actions/index");
@@ -18,30 +18,48 @@ const { PluralForm } = require("devtools
 const {
   DEFAULT_FILTERS,
   FILTERS,
 } = require("../constants");
 
 const FilterButton = require("devtools/client/webconsole/new-console-output/components/FilterButton");
 const FilterCheckbox = require("devtools/client/webconsole/new-console-output/components/FilterCheckbox");
 
-const FilterBar = createClass({
-
-  displayName: "FilterBar",
+class FilterBar extends Component {
+  static get propTypes() {
+    return {
+      dispatch: PropTypes.func.isRequired,
+      filter: PropTypes.object.isRequired,
+      serviceContainer: PropTypes.shape({
+        attachRefToHud: PropTypes.func.isRequired,
+      }).isRequired,
+      filterBarVisible: PropTypes.bool.isRequired,
+      persistLogs: PropTypes.bool.isRequired,
+      filteredMessagesCount: PropTypes.object.isRequired,
+    };
+  }
 
-  propTypes: {
-    dispatch: PropTypes.func.isRequired,
-    filter: PropTypes.object.isRequired,
-    serviceContainer: PropTypes.shape({
-      attachRefToHud: PropTypes.func.isRequired,
-    }).isRequired,
-    filterBarVisible: PropTypes.bool.isRequired,
-    persistLogs: PropTypes.bool.isRequired,
-    filteredMessagesCount: PropTypes.object.isRequired,
-  },
+  constructor(props) {
+    super(props);
+    this.onClickMessagesClear = this.onClickMessagesClear.bind(this);
+    this.onClickFilterBarToggle = this.onClickFilterBarToggle.bind(this);
+    this.onClickRemoveAllFilters = this.onClickRemoveAllFilters.bind(this);
+    this.onClickRemoveTextFilter = this.onClickRemoveTextFilter.bind(this);
+    this.onSearchInput = this.onSearchInput.bind(this);
+    this.onChangePersistToggle = this.onChangePersistToggle.bind(this);
+    this.renderFiltersConfigBar = this.renderFiltersConfigBar.bind(this);
+    this.renderFilteredMessagesBar = this.renderFilteredMessagesBar.bind(this);
+  }
+
+  componentDidMount() {
+    this.props.serviceContainer.attachRefToHud(
+      "filterBox",
+      this.wrapperNode.querySelector(".text-filter")
+    );
+  }
 
   shouldComponentUpdate(nextProps, nextState) {
     if (nextProps.filter !== this.props.filter) {
       return true;
     }
 
     if (nextProps.filterBarVisible !== this.props.filterBarVisible) {
       return true;
@@ -54,46 +72,41 @@ const FilterBar = createClass({
     if (
       JSON.stringify(nextProps.filteredMessagesCount)
       !== JSON.stringify(this.props.filteredMessagesCount)
     ) {
       return true;
     }
 
     return false;
-  },
-
-  componentDidMount() {
-    this.props.serviceContainer.attachRefToHud("filterBox",
-      this.wrapperNode.querySelector(".text-filter"));
-  },
+  }
 
-  onClickMessagesClear: function () {
+  onClickMessagesClear() {
     this.props.dispatch(actions.messagesClear());
-  },
+  }
 
-  onClickFilterBarToggle: function () {
+  onClickFilterBarToggle() {
     this.props.dispatch(actions.filterBarToggle());
-  },
+  }
 
-  onClickRemoveAllFilters: function () {
+  onClickRemoveAllFilters() {
     this.props.dispatch(actions.defaultFiltersReset());
-  },
+  }
 
-  onClickRemoveTextFilter: function () {
+  onClickRemoveTextFilter() {
     this.props.dispatch(actions.filterTextSet(""));
-  },
+  }
 
-  onSearchInput: function (e) {
+  onSearchInput(e) {
     this.props.dispatch(actions.filterTextSet(e.target.value));
-  },
+  }
 
-  onChangePersistToggle: function () {
+  onChangePersistToggle() {
     this.props.dispatch(actions.persistToggle());
-  },
+  }
 
   renderFiltersConfigBar() {
     const {
       dispatch,
       filter,
       filteredMessagesCount,
     } = this.props;
 
@@ -162,17 +175,17 @@ const FilterBar = createClass({
       }),
       FilterButton({
         active: filter[FILTERS.NET],
         label: l10n.getStr("webconsole.requestsFilterButton.label"),
         filterKey: FILTERS.NET,
         dispatch
       }),
     );
-  },
+  }
 
   renderFilteredMessagesBar() {
     const {
       filteredMessagesCount
     } = this.props;
     const {
       global,
     } = filteredMessagesCount;
@@ -196,17 +209,17 @@ const FilterBar = createClass({
         className: "filter-message-text",
         title,
       }, label),
       dom.button({
         className: "devtools-button reset-filters-button",
         onClick: this.onClickRemoveAllFilters
       }, l10n.getStr("webconsole.resetFiltersButton.label"))
     );
-  },
+  }
 
   render() {
     const {
       filter,
       filterBarVisible,
       persistLogs,
       filteredMessagesCount,
     } = this.props;
@@ -259,17 +272,17 @@ const FilterBar = createClass({
         className: "webconsole-filteringbar-wrapper",
         ref: node => {
           this.wrapperNode = node;
         }
       }, ...children
       )
     );
   }
-});
+}
 
 function mapStateToProps(state) {
   let uiState = getAllUi(state);
   return {
     filter: getAllFilters(state),
     filterBarVisible: uiState.filterBarVisible,
     persistLogs: uiState.persistLogs,
     filteredMessagesCount: getFilteredMessagesCount(state),
--- a/devtools/client/webconsole/new-console-output/components/Message.js
+++ b/devtools/client/webconsole/new-console-output/components/Message.js
@@ -3,107 +3,113 @@
 /* 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 {
-  createClass,
+  Component,
   createFactory,
   DOM: dom,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
 const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
 const actions = require("devtools/client/webconsole/new-console-output/actions/index");
 const {MESSAGE_SOURCE} = require("devtools/client/webconsole/new-console-output/constants");
 const CollapseButton = require("devtools/client/webconsole/new-console-output/components/CollapseButton");
 const MessageIndent = require("devtools/client/webconsole/new-console-output/components/MessageIndent").MessageIndent;
 const MessageIcon = require("devtools/client/webconsole/new-console-output/components/MessageIcon");
 const MessageRepeat = require("devtools/client/webconsole/new-console-output/components/MessageRepeat");
 const FrameView = createFactory(require("devtools/client/shared/components/Frame"));
 const StackTrace = createFactory(require("devtools/client/shared/components/StackTrace"));
 
-const Message = createClass({
-  displayName: "Message",
+class Message extends Component {
+  static get propTypes() {
+    return {
+      open: PropTypes.bool,
+      collapsible: PropTypes.bool,
+      collapseTitle: PropTypes.string,
+      source: PropTypes.string.isRequired,
+      type: PropTypes.string.isRequired,
+      level: PropTypes.string.isRequired,
+      indent: PropTypes.number.isRequired,
+      topLevelClasses: PropTypes.array.isRequired,
+      messageBody: PropTypes.any.isRequired,
+      repeat: PropTypes.any,
+      frame: PropTypes.any,
+      attachment: PropTypes.any,
+      stacktrace: PropTypes.any,
+      messageId: PropTypes.string,
+      scrollToMessage: PropTypes.bool,
+      exceptionDocURL: PropTypes.string,
+      parameters: PropTypes.object,
+      request: PropTypes.object,
+      dispatch: PropTypes.func,
+      timeStamp: PropTypes.number,
+      timestampsVisible: PropTypes.bool.isRequired,
+      serviceContainer: PropTypes.shape({
+        emitNewMessage: PropTypes.func.isRequired,
+        onViewSourceInDebugger: PropTypes.func,
+        onViewSourceInScratchpad: PropTypes.func,
+        onViewSourceInStyleEditor: PropTypes.func,
+        openContextMenu: PropTypes.func.isRequired,
+        openLink: PropTypes.func.isRequired,
+        sourceMapService: PropTypes.any,
+      }),
+      notes: PropTypes.arrayOf(PropTypes.shape({
+        messageBody: PropTypes.string.isRequired,
+        frame: PropTypes.any,
+      })),
+    };
+  }
 
-  propTypes: {
-    open: PropTypes.bool,
-    collapsible: PropTypes.bool,
-    collapseTitle: PropTypes.string,
-    source: PropTypes.string.isRequired,
-    type: PropTypes.string.isRequired,
-    level: PropTypes.string.isRequired,
-    indent: PropTypes.number.isRequired,
-    topLevelClasses: PropTypes.array.isRequired,
-    messageBody: PropTypes.any.isRequired,
-    repeat: PropTypes.any,
-    frame: PropTypes.any,
-    attachment: PropTypes.any,
-    stacktrace: PropTypes.any,
-    messageId: PropTypes.string,
-    scrollToMessage: PropTypes.bool,
-    exceptionDocURL: PropTypes.string,
-    parameters: PropTypes.object,
-    request: PropTypes.object,
-    dispatch: PropTypes.func,
-    timeStamp: PropTypes.number,
-    timestampsVisible: PropTypes.bool.isRequired,
-    serviceContainer: PropTypes.shape({
-      emitNewMessage: PropTypes.func.isRequired,
-      onViewSourceInDebugger: PropTypes.func,
-      onViewSourceInScratchpad: PropTypes.func,
-      onViewSourceInStyleEditor: PropTypes.func,
-      openContextMenu: PropTypes.func.isRequired,
-      openLink: PropTypes.func.isRequired,
-      sourceMapService: PropTypes.any,
-    }),
-    notes: PropTypes.arrayOf(PropTypes.shape({
-      messageBody: PropTypes.string.isRequired,
-      frame: PropTypes.any,
-    })),
-  },
-
-  getDefaultProps: function () {
+  static get defaultProps() {
     return {
       indent: 0
     };
-  },
+  }
+
+  constructor(props) {
+    super(props);
+    this.onLearnMoreClick = this.onLearnMoreClick.bind(this);
+    this.onContextMenu = this.onContextMenu.bind(this);
+  }
 
   componentDidMount() {
     if (this.messageNode) {
       if (this.props.scrollToMessage) {
         this.messageNode.scrollIntoView();
       }
       // Event used in tests. Some message types don't pass it in because existing tests
       // did not emit for them.
       if (this.props.serviceContainer) {
         this.props.serviceContainer.emitNewMessage(
           this.messageNode, this.props.messageId, this.props.timeStamp);
       }
     }
-  },
+  }
 
-  onLearnMoreClick: function () {
+  onLearnMoreClick() {
     let {exceptionDocURL} = this.props;
     this.props.serviceContainer.openLink(exceptionDocURL);
-  },
+  }
 
   onContextMenu(e) {
     let { serviceContainer, source, request, messageId } = this.props;
     let messageInfo = {
       source,
       request,
       messageId,
     };
     serviceContainer.openContextMenu(e, messageInfo);
     e.stopPropagation();
     e.preventDefault();
-  },
+  }
 
   render() {
     const {
       messageId,
       open,
       collapsible,
       collapseTitle,
       source,
@@ -256,11 +262,11 @@ const Message = createClass({
         // Add a newline for formatting when copying to the clipboard.
         "\n",
         // If an attachment is displayed, the final newline is handled by the attachment.
         attachment,
         ...notesNodes
       )
     );
   }
-});
+}
 
 module.exports = Message;
--- a/devtools/client/webconsole/new-console-output/components/MessageContainer.js
+++ b/devtools/client/webconsole/new-console-output/components/MessageContainer.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";
 
 // React & Redux
 const {
-  createClass,
+  Component,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
 
 const {
   MESSAGE_SOURCE,
   MESSAGE_TYPE
 } = require("devtools/client/webconsole/new-console-output/constants");
 
@@ -21,59 +21,59 @@ const componentMap = new Map([
   ["ConsoleApiCall", require("./message-types/ConsoleApiCall")],
   ["ConsoleCommand", require("./message-types/ConsoleCommand")],
   ["DefaultRenderer", require("./message-types/DefaultRenderer")],
   ["EvaluationResult", require("./message-types/EvaluationResult")],
   ["NetworkEventMessage", require("./message-types/NetworkEventMessage")],
   ["PageError", require("./message-types/PageError")]
 ]);
 
-const MessageContainer = createClass({
-  displayName: "MessageContainer",
+class MessageContainer extends Component {
+  static get propTypes() {
+    return {
+      messageId: PropTypes.string.isRequired,
+      open: PropTypes.bool.isRequired,
+      serviceContainer: PropTypes.object.isRequired,
+      tableData: PropTypes.object,
+      timestampsVisible: PropTypes.bool.isRequired,
+      repeat: PropTypes.number,
+      networkMessageUpdate: PropTypes.object,
+      getMessage: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    messageId: PropTypes.string.isRequired,
-    open: PropTypes.bool.isRequired,
-    serviceContainer: PropTypes.object.isRequired,
-    tableData: PropTypes.object,
-    timestampsVisible: PropTypes.bool.isRequired,
-    repeat: PropTypes.number,
-    networkMessageUpdate: PropTypes.object,
-    getMessage: PropTypes.func.isRequired,
-  },
-
-  getDefaultProps: function () {
+  static get defaultProps() {
     return {
       open: false,
     };
-  },
+  }
 
   shouldComponentUpdate(nextProps, nextState) {
     const repeatChanged = this.props.repeat !== nextProps.repeat;
     const openChanged = this.props.open !== nextProps.open;
     const tableDataChanged = this.props.tableData !== nextProps.tableData;
     const timestampVisibleChanged =
       this.props.timestampsVisible !== nextProps.timestampsVisible;
     const networkMessageUpdateChanged =
       this.props.networkMessageUpdate !== nextProps.networkMessageUpdate;
 
     return repeatChanged
       || openChanged
       || tableDataChanged
       || timestampVisibleChanged
       || networkMessageUpdateChanged;
-  },
+  }
 
   render() {
     const message = this.props.getMessage();
 
     let MessageComponent = getMessageComponent(message);
     return MessageComponent(Object.assign({message}, this.props));
   }
-});
+}
 
 function getMessageComponent(message) {
   if (!message) {
     return componentMap.get("DefaultRenderer");
   }
 
   switch (message.source) {
     case MESSAGE_SOURCE.CONSOLE_API: