Bug 1418274 - Responsive Design Mode to ES6 Classes, prop-types and react-dom-factories r=jryans
authorMichael Ratcliffe <mratcliffe@mozilla.com>
Fri, 17 Nov 2017 12:22:29 +0000
changeset 392953 5351572bb1e7e3037cebe9f7bd90e350c3b2f1e3
parent 392952 12a0db87f30b4f4340ee101f2d28794b7c575ace
child 392954 ac76ac88fc5e7b00c43fc6dd2e183528e6bafd6c
push id32947
push useraciure@mozilla.com
push dateWed, 22 Nov 2017 09:57:59 +0000
treeherdermozilla-central@5378dcb45044 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjryans
bugs1418274
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1418274 - Responsive Design Mode to ES6 Classes, prop-types and react-dom-factories r=jryans MozReview-Commit-ID: 1QWLZwVlyVQ
devtools/client/responsive.html/app.js
devtools/client/responsive.html/components/Browser.js
devtools/client/responsive.html/components/DeviceAdder.js
devtools/client/responsive.html/components/DeviceModal.js
devtools/client/responsive.html/components/DevicePixelRatioSelector.js
devtools/client/responsive.html/components/DeviceSelector.js
devtools/client/responsive.html/components/GlobalToolbar.js
devtools/client/responsive.html/components/NetworkThrottlingSelector.js
devtools/client/responsive.html/components/ResizableViewport.js
devtools/client/responsive.html/components/Viewport.js
devtools/client/responsive.html/components/ViewportDimension.js
devtools/client/responsive.html/components/ViewportToolbar.js
devtools/client/responsive.html/components/Viewports.js
devtools/client/responsive.html/types.js
--- a/devtools/client/responsive.html/app.js
+++ b/devtools/client/responsive.html/app.js
@@ -1,18 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
- /* eslint-env browser */
+/* eslint-env browser */
 
 "use strict";
 
-const { createClass, createFactory, PropTypes, DOM: dom } =
-  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");
 
 const {
   addCustomDevice,
   removeCustomDevice,
   updateDeviceDisplayed,
   updateDeviceModal,
   updatePreferredDevices,
@@ -27,122 +28,142 @@ const {
   resizeViewport,
   rotateViewport,
 } = require("./actions/viewports");
 const DeviceModal = createFactory(require("./components/DeviceModal"));
 const GlobalToolbar = createFactory(require("./components/GlobalToolbar"));
 const Viewports = createFactory(require("./components/Viewports"));
 const Types = require("./types");
 
-let App = createClass({
-  displayName: "App",
+class App extends Component {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      dispatch: PropTypes.func.isRequired,
+      displayPixelRatio: Types.pixelRatio.value.isRequired,
+      networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
+      screenshot: PropTypes.shape(Types.screenshot).isRequired,
+      touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
+      viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
+    };
+  }
 
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    dispatch: PropTypes.func.isRequired,
-    displayPixelRatio: Types.pixelRatio.value.isRequired,
-    networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
-    screenshot: PropTypes.shape(Types.screenshot).isRequired,
-    touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
-    viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
-  },
+  constructor(props) {
+    super(props);
+    this.onAddCustomDevice = this.onAddCustomDevice.bind(this);
+    this.onBrowserMounted = this.onBrowserMounted.bind(this);
+    this.onChangeDevice = this.onChangeDevice.bind(this);
+    this.onChangeNetworkThrottling = this.onChangeNetworkThrottling.bind(this);
+    this.onChangePixelRatio = this.onChangePixelRatio.bind(this);
+    this.onChangeTouchSimulation = this.onChangeTouchSimulation.bind(this);
+    this.onContentResize = this.onContentResize.bind(this);
+    this.onDeviceListUpdate = this.onDeviceListUpdate.bind(this);
+    this.onExit = this.onExit.bind(this);
+    this.onRemoveCustomDevice = this.onRemoveCustomDevice.bind(this);
+    this.onRemoveDeviceAssociation = this.onRemoveDeviceAssociation.bind(this);
+    this.onResizeViewport = this.onResizeViewport.bind(this);
+    this.onRotateViewport = this.onRotateViewport.bind(this);
+    this.onScreenshot = this.onScreenshot.bind(this);
+    this.onUpdateDeviceDisplayed = this.onUpdateDeviceDisplayed.bind(this);
+    this.onUpdateDeviceModal = this.onUpdateDeviceModal.bind(this);
+  }
 
   onAddCustomDevice(device) {
     this.props.dispatch(addCustomDevice(device));
-  },
+  }
 
   onBrowserMounted() {
     window.postMessage({ type: "browser-mounted" }, "*");
-  },
+  }
 
   onChangeDevice(id, device, deviceType) {
     // TODO: Bug 1332754: Move messaging and logic into the action creator so that the
     // message is sent from the action creator and device property changes are sent from
     // there instead of this function.
     window.postMessage({
       type: "change-device",
       device,
     }, "*");
     this.props.dispatch(changeDevice(id, device.name, deviceType));
     this.props.dispatch(changeTouchSimulation(device.touch));
     this.props.dispatch(changePixelRatio(id, device.pixelRatio));
-  },
+  }
 
   onChangeNetworkThrottling(enabled, profile) {
     window.postMessage({
       type: "change-network-throtting",
       enabled,
       profile,
     }, "*");
     this.props.dispatch(changeNetworkThrottling(enabled, profile));
-  },
+  }
 
   onChangePixelRatio(pixelRatio) {
     window.postMessage({
       type: "change-pixel-ratio",
       pixelRatio,
     }, "*");
     this.props.dispatch(changePixelRatio(0, pixelRatio));
-  },
+  }
 
   onChangeTouchSimulation(enabled) {
     window.postMessage({
       type: "change-touch-simulation",
       enabled,
     }, "*");
     this.props.dispatch(changeTouchSimulation(enabled));
-  },
+  }
 
   onContentResize({ width, height }) {
     window.postMessage({
       type: "content-resize",
       width,
       height,
     }, "*");
-  },
+  }
 
   onDeviceListUpdate(devices) {
     updatePreferredDevices(devices);
-  },
+  }
 
   onExit() {
     window.postMessage({ type: "exit" }, "*");
-  },
+  }
 
   onRemoveCustomDevice(device) {
     this.props.dispatch(removeCustomDevice(device));
-  },
+  }
 
   onRemoveDeviceAssociation(id) {
     // TODO: Bug 1332754: Move messaging and logic into the action creator so that device
     // property changes are sent from there instead of this function.
     this.props.dispatch(removeDeviceAssociation(id));
     this.props.dispatch(changeTouchSimulation(false));
     this.props.dispatch(changePixelRatio(id, 0));
-  },
+  }
 
   onResizeViewport(id, width, height) {
     this.props.dispatch(resizeViewport(id, width, height));
-  },
+  }
 
   onRotateViewport(id) {
     this.props.dispatch(rotateViewport(id));
-  },
+  }
 
   onScreenshot() {
     this.props.dispatch(takeScreenshot());
-  },
+  }
 
   onUpdateDeviceDisplayed(device, deviceType, displayed) {
     this.props.dispatch(updateDeviceDisplayed(device, deviceType, displayed));
-  },
+  }
 
   onUpdateDeviceModal(isOpen, modalOpenedFromViewport) {
     this.props.dispatch(updateDeviceModal(isOpen, modalOpenedFromViewport));
-  },
+  }
 
   render() {
     let {
       devices,
       displayPixelRatio,
       networkThrottling,
       screenshot,
       touchSimulation,
@@ -216,13 +237,12 @@ let App = createClass({
         devices,
         onAddCustomDevice,
         onDeviceListUpdate,
         onRemoveCustomDevice,
         onUpdateDeviceDisplayed,
         onUpdateDeviceModal,
       })
     );
-  },
-
-});
+  }
+}
 
 module.exports = connect(state => state)(App);
--- a/devtools/client/responsive.html/components/Browser.js
+++ b/devtools/client/responsive.html/components/Browser.js
@@ -2,44 +2,46 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env browser */
 
 "use strict";
 
 const Services = require("Services");
-const { Task } = require("devtools/shared/task");
 const flags = require("devtools/shared/flags");
-const { DOM: dom, createClass, addons, PropTypes } =
-  require("devtools/client/shared/vendor/react");
+const { PureComponent } = 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 e10s = require("../utils/e10s");
 const message = require("../utils/message");
 const { getToplevelWindow } = require("../utils/window");
 
 const FRAME_SCRIPT = "resource://devtools/client/responsive.html/browser/content.js";
 
-module.exports = createClass({
-
+class Browser extends PureComponent {
   /**
    * This component is not allowed to depend directly on frequently changing
    * data (width, height) due to the use of `dangerouslySetInnerHTML` below.
    * Any changes in props will cause the <iframe> to be removed and added again,
    * throwing away the current state of the page.
    */
-  displayName: "Browser",
+  static get propTypes() {
+    return {
+      swapAfterMount: PropTypes.bool.isRequired,
+      onBrowserMounted: PropTypes.func.isRequired,
+      onContentResize: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    swapAfterMount: PropTypes.bool.isRequired,
-    onBrowserMounted: PropTypes.func.isRequired,
-    onContentResize: PropTypes.func.isRequired,
-  },
-
-  mixins: [ addons.PureRenderMixin ],
+  constructor(props) {
+    super(props);
+    this.onContentResize = this.onContentResize.bind(this);
+  }
 
   /**
    * Before the browser is mounted, listen for `remote-browser-shown` so that we know when
    * the browser is fully ready.  Without waiting for an event such as this, we don't know
    * whether all frame state for the browser is fully initialized (since some happens
    * async after the element is added), and swapping browsers can fail if this state is
    * not ready.
    */
@@ -50,92 +52,92 @@ module.exports = createClass({
         if (frameLoader.ownerElement != browser) {
           return;
         }
         Services.obs.removeObserver(handler, "remote-browser-shown");
         resolve();
       };
       Services.obs.addObserver(handler, "remote-browser-shown");
     });
-  },
+  }
 
   /**
    * Once the browser element has mounted, load the frame script and enable
    * various features, like floating scrollbars.
    */
-  componentDidMount: Task.async(function* () {
+  async componentDidMount() {
     // If we are not swapping browsers after mount, it's safe to start the frame
     // script now.
     if (!this.props.swapAfterMount) {
-      yield this.startFrameScript();
+      await this.startFrameScript();
     }
 
     // Notify manager.js that this browser has mounted, so that it can trigger
     // a swap if needed and continue with the rest of its startup.
-    yield this.browserShown;
+    await this.browserShown;
     this.props.onBrowserMounted();
 
     // If we are swapping browsers after mount, wait for the swap to complete
     // and start the frame script after that.
     if (this.props.swapAfterMount) {
-      yield message.wait(window, "start-frame-script");
-      yield this.startFrameScript();
+      await message.wait(window, "start-frame-script");
+      await this.startFrameScript();
       message.post(window, "start-frame-script:done");
     }
 
     // Stop the frame script when requested in the future.
     message.wait(window, "stop-frame-script").then(() => {
       this.stopFrameScript();
     });
-  }),
+  }
 
   onContentResize(msg) {
     let { onContentResize } = this.props;
     let { width, height } = msg.data;
     onContentResize({
       width,
       height,
     });
-  },
+  }
 
-  startFrameScript: Task.async(function* () {
+  async startFrameScript() {
     let { onContentResize } = this;
     let browser = this.refs.browserContainer.querySelector("iframe.browser");
     let mm = browser.frameLoader.messageManager;
 
     // Notify tests when the content has received a resize event.  This is not
     // quite the same timing as when we _set_ a new size around the browser,
     // since it still needs to do async work before the content is actually
     // resized to match.
     e10s.on(mm, "OnContentResize", onContentResize);
 
     let ready = e10s.once(mm, "ChildScriptReady");
     mm.loadFrameScript(FRAME_SCRIPT, true);
-    yield ready;
+    await ready;
 
     let browserWindow = getToplevelWindow(window);
     let requiresFloatingScrollbars =
       !browserWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
 
-    yield e10s.request(mm, "Start", {
+    await e10s.request(mm, "Start", {
       requiresFloatingScrollbars,
       // Tests expect events on resize to yield on various size changes
       notifyOnResize: flags.testing,
     });
-  }),
+  }
 
-  stopFrameScript: Task.async(function* () {
+  async stopFrameScript() {
     let { onContentResize } = this;
     let browser = this.refs.browserContainer.querySelector("iframe.browser");
     let mm = browser.frameLoader.messageManager;
 
     e10s.off(mm, "OnContentResize", onContentResize);
-    yield e10s.request(mm, "Stop");
+    await e10s.request(mm, "Stop");
     message.post(window, "stop-frame-script:done");
-  }),
+  }
 
   render() {
     return dom.div(
       {
         ref: "browserContainer",
         className: "browser-container",
 
         /**
@@ -155,11 +157,12 @@ module.exports = createClass({
           __html: `<iframe class="browser" mozbrowser="true"
                            remote="true" remoteType="web"
                            noisolation="true" allowfullscreen="true"
                            src="about:blank" width="100%" height="100%">
                    </iframe>`
         }
       }
     );
-  },
+  }
+}
 
-});
+module.exports = Browser;
--- a/devtools/client/responsive.html/components/DeviceAdder.js
+++ b/devtools/client/responsive.html/components/DeviceAdder.js
@@ -1,62 +1,65 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env browser */
 
 "use strict";
 
-const { DOM: dom, createClass, createFactory, PropTypes, addons } =
-  require("devtools/client/shared/vendor/react");
+const { PureComponent, 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 { getFormatStr, getStr } = require("../utils/l10n");
 const Types = require("../types");
 const ViewportDimension = createFactory(require("./ViewportDimension.js"));
 
-module.exports = createClass({
-  displayName: "DeviceAdder",
+class DeviceAdder extends PureComponent {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      viewportTemplate: PropTypes.shape(Types.viewport).isRequired,
+      onAddCustomDevice: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    viewportTemplate: PropTypes.shape(Types.viewport).isRequired,
-    onAddCustomDevice: PropTypes.func.isRequired,
-  },
-
-  mixins: [ addons.PureRenderMixin ],
-
-  getInitialState() {
-    return {};
-  },
+  constructor(props) {
+    super(props);
+    this.state = {};
+    this.onChangeSize = this.onChangeSize.bind(this);
+    this.onDeviceAdderShow = this.onDeviceAdderShow.bind(this);
+    this.onDeviceAdderSave = this.onDeviceAdderSave.bind(this);
+  }
 
   componentWillReceiveProps(nextProps) {
     let {
       width,
       height,
     } = nextProps.viewportTemplate;
 
     this.setState({
       width,
       height,
     });
-  },
+  }
 
   onChangeSize(width, height) {
     this.setState({
       width,
       height,
     });
-  },
+  }
 
   onDeviceAdderShow() {
     this.setState({
       deviceAdderDisplayed: true,
     });
-  },
+  }
 
   onDeviceAdderSave() {
     let {
       devices,
       onAddCustomDevice,
     } = this.props;
 
     if (!this.pixelRatioInput.checkValidity()) {
@@ -73,17 +76,17 @@ module.exports = createClass({
     onAddCustomDevice({
       name: this.nameInput.value,
       width: this.state.width,
       height: this.state.height,
       pixelRatio: parseFloat(this.pixelRatioInput.value),
       userAgent: this.userAgentInput.value,
       touch: this.touchInput.checked,
     });
-  },
+  }
 
   render() {
     let {
       devices,
       viewportTemplate,
     } = this.props;
 
     let {
@@ -243,10 +246,12 @@ module.exports = createClass({
       dom.button(
         {
           id: "device-adder-save",
           onClick: this.onDeviceAdderSave,
         },
         getStr("responsive.deviceAdderSave")
       )
     );
-  },
-});
+  }
+}
+
+module.exports = DeviceAdder;
--- a/devtools/client/responsive.html/components/DeviceModal.js
+++ b/devtools/client/responsive.html/components/DeviceModal.js
@@ -1,45 +1,49 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env browser */
 
 "use strict";
 
-const { DOM: dom, createClass, createFactory, PropTypes, addons } =
-  require("devtools/client/shared/vendor/react");
+const { PureComponent, 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 { getStr, getFormatStr } = require("../utils/l10n");
 const Types = require("../types");
 const DeviceAdder = createFactory(require("./DeviceAdder"));
 
-module.exports = createClass({
-  displayName: "DeviceModal",
+class DeviceModal extends PureComponent {
+  static get propTypes() {
+    return {
+      deviceAdderViewportTemplate: PropTypes.shape(Types.viewport).isRequired,
+      devices: PropTypes.shape(Types.devices).isRequired,
+      onAddCustomDevice: PropTypes.func.isRequired,
+      onDeviceListUpdate: PropTypes.func.isRequired,
+      onRemoveCustomDevice: PropTypes.func.isRequired,
+      onUpdateDeviceDisplayed: PropTypes.func.isRequired,
+      onUpdateDeviceModal: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    deviceAdderViewportTemplate: PropTypes.shape(Types.viewport).isRequired,
-    devices: PropTypes.shape(Types.devices).isRequired,
-    onAddCustomDevice: PropTypes.func.isRequired,
-    onDeviceListUpdate: PropTypes.func.isRequired,
-    onRemoveCustomDevice: PropTypes.func.isRequired,
-    onUpdateDeviceDisplayed: PropTypes.func.isRequired,
-    onUpdateDeviceModal: PropTypes.func.isRequired,
-  },
-
-  mixins: [ addons.PureRenderMixin ],
-
-  getInitialState() {
-    return {};
-  },
+  constructor(props) {
+    super(props);
+    this.state = {};
+    this.onAddCustomDevice = this.onAddCustomDevice.bind(this);
+    this.onDeviceCheckboxChange = this.onDeviceCheckboxChange.bind(this);
+    this.onDeviceModalSubmit = this.onDeviceModalSubmit.bind(this);
+    this.onKeyDown = this.onKeyDown.bind(this);
+  }
 
   componentDidMount() {
     window.addEventListener("keydown", this.onKeyDown, true);
-  },
+  }
 
   componentWillReceiveProps(nextProps) {
     let {
       devices: oldDevices,
     } = this.props;
     let {
       devices,
     } = nextProps;
@@ -49,38 +53,38 @@ module.exports = createClass({
       for (let type of devices.types) {
         for (let device of devices[type]) {
           this.setState({
             [device.name]: device.displayed,
           });
         }
       }
     }
-  },
+  }
 
   componentWillUnmount() {
     window.removeEventListener("keydown", this.onKeyDown, true);
-  },
+  }
 
   onAddCustomDevice(device) {
     this.props.onAddCustomDevice(device);
     // Default custom devices to enabled
     this.setState({
       [device.name]: true,
     });
-  },
+  }
 
   onDeviceCheckboxChange({ nativeEvent: { button }, target }) {
     if (button !== 0) {
       return;
     }
     this.setState({
       [target.value]: !this.state[target.value]
     });
-  },
+  }
 
   onDeviceModalSubmit() {
     let {
       devices,
       onDeviceListUpdate,
       onUpdateDeviceDisplayed,
       onUpdateDeviceModal,
     } = this.props;
@@ -103,30 +107,30 @@ module.exports = createClass({
         if (this.state[device.name] != device.displayed) {
           onUpdateDeviceDisplayed(device, type, this.state[device.name]);
         }
       }
     }
 
     onDeviceListUpdate(preferredDevices);
     onUpdateDeviceModal(false);
-  },
+  }
 
   onKeyDown(event) {
     if (!this.props.devices.isModalOpen) {
       return;
     }
     // Escape keycode
     if (event.keyCode === 27) {
       let {
         onUpdateDeviceModal
       } = this.props;
       onUpdateDeviceModal(false);
     }
-  },
+  }
 
   render() {
     let {
       deviceAdderViewportTemplate,
       devices,
       onRemoveCustomDevice,
       onUpdateDeviceModal,
     } = this.props;
@@ -225,10 +229,12 @@ module.exports = createClass({
       ),
       dom.div(
         {
           className: "modal-overlay",
           onClick: () => onUpdateDeviceModal(false),
         }
       )
     );
-  },
-});
+  }
+}
+
+module.exports = DeviceModal;
--- a/devtools/client/responsive.html/components/DevicePixelRatioSelector.js
+++ b/devtools/client/responsive.html/components/DevicePixelRatioSelector.js
@@ -1,18 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env browser */
 
 "use strict";
 
-const { DOM: dom, createClass, PropTypes, addons } =
-  require("devtools/client/shared/vendor/react");
+const { PureComponent} = 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 Types = require("../types");
 const { getStr, getFormatStr } = require("../utils/l10n");
 const labelForOption = value => getFormatStr("responsive.devicePixelRatioOption", value);
 
 const PIXEL_RATIO_PRESET = [1, 2, 3];
 
 const createVisibleOption = value => {
@@ -29,44 +30,47 @@ const createHiddenOption = value => {
   return dom.option({
     value,
     title: label,
     hidden: true,
     disabled: true,
   }, label);
 };
 
-module.exports = createClass({
-  displayName: "DevicePixelRatioSelector",
+class DevicePixelRatioSelector extends PureComponent {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      displayPixelRatio: Types.pixelRatio.value.isRequired,
+      selectedDevice: PropTypes.string.isRequired,
+      selectedPixelRatio: PropTypes.shape(Types.pixelRatio).isRequired,
+      onChangePixelRatio: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    displayPixelRatio: Types.pixelRatio.value.isRequired,
-    selectedDevice: PropTypes.string.isRequired,
-    selectedPixelRatio: PropTypes.shape(Types.pixelRatio).isRequired,
-    onChangePixelRatio: PropTypes.func.isRequired,
-  },
+  constructor(props) {
+    super(props);
 
-  mixins: [ addons.PureRenderMixin ],
-
-  getInitialState() {
-    return {
+    this.state = {
       isFocused: false
     };
-  },
+
+    this.onFocusChange = this.onFocusChange.bind(this);
+    this.onSelectChange = this.onSelectChange.bind(this);
+  }
 
   onFocusChange({type}) {
     this.setState({
       isFocused: type === "focus"
     });
-  },
+  }
 
   onSelectChange({ target }) {
     this.props.onChangePixelRatio(+target.value);
-  },
+  }
 
   render() {
     let {
       devices,
       displayPixelRatio,
       selectedDevice,
       selectedPixelRatio,
     } = this.props;
@@ -121,11 +125,12 @@ module.exports = createClass({
         onChange: this.onSelectChange,
         onFocus: this.onFocusChange,
         onBlur: this.onFocusChange,
         className: selectorClass,
         title: title
       },
       ...listContent
     );
-  },
+  }
+}
 
-});
+module.exports = DevicePixelRatioSelector;
--- a/devtools/client/responsive.html/components/DeviceSelector.js
+++ b/devtools/client/responsive.html/components/DeviceSelector.js
@@ -1,34 +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 { getStr } = require("../utils/l10n");
-const { DOM: dom, createClass, PropTypes, addons } =
-  require("devtools/client/shared/vendor/react");
+const { PureComponent } = 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 Types = require("../types");
 const OPEN_DEVICE_MODAL_VALUE = "OPEN_DEVICE_MODAL";
 
-module.exports = createClass({
-  displayName: "DeviceSelector",
+class DeviceSelector extends PureComponent {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      selectedDevice: PropTypes.string.isRequired,
+      viewportId: PropTypes.number.isRequired,
+      onChangeDevice: PropTypes.func.isRequired,
+      onResizeViewport: PropTypes.func.isRequired,
+      onUpdateDeviceModal: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    selectedDevice: PropTypes.string.isRequired,
-    viewportId: PropTypes.number.isRequired,
-    onChangeDevice: PropTypes.func.isRequired,
-    onResizeViewport: PropTypes.func.isRequired,
-    onUpdateDeviceModal: PropTypes.func.isRequired,
-  },
-
-  mixins: [ addons.PureRenderMixin ],
+  constructor(props) {
+    super(props);
+    this.onSelectChange = this.onSelectChange.bind(this);
+  }
 
   onSelectChange({ target }) {
     let {
       devices,
       viewportId,
       onChangeDevice,
       onResizeViewport,
       onUpdateDeviceModal,
@@ -42,17 +46,17 @@ module.exports = createClass({
       for (let device of devices[type]) {
         if (device.name === target.value) {
           onResizeViewport(device.width, device.height);
           onChangeDevice(device, type);
           return;
         }
       }
     }
-  },
+  }
 
   render() {
     let {
       devices,
       selectedDevice,
     } = this.props;
 
     let options = [];
@@ -116,11 +120,12 @@ module.exports = createClass({
         className: selectClass,
         value: selectedDevice,
         title: selectedDevice,
         onChange: this.onSelectChange,
         disabled: (state !== Types.deviceListState.LOADED),
       },
       ...listContent
     );
-  },
+  }
+}
 
-});
+module.exports = DeviceSelector;
--- a/devtools/client/responsive.html/components/GlobalToolbar.js
+++ b/devtools/client/responsive.html/components/GlobalToolbar.js
@@ -1,41 +1,40 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { DOM: dom, createClass, createFactory, PropTypes, addons } =
-  require("devtools/client/shared/vendor/react");
+const { PureComponent, 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 { getStr } = require("../utils/l10n");
 const Types = require("../types");
 const DevicePixelRatioSelector = createFactory(require("./DevicePixelRatioSelector"));
 const NetworkThrottlingSelector = createFactory(require("./NetworkThrottlingSelector"));
 
-module.exports = createClass({
-  displayName: "GlobalToolbar",
-
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    displayPixelRatio: Types.pixelRatio.value.isRequired,
-    networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
-    screenshot: PropTypes.shape(Types.screenshot).isRequired,
-    selectedDevice: PropTypes.string.isRequired,
-    selectedPixelRatio: PropTypes.shape(Types.pixelRatio).isRequired,
-    touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
-    onChangeNetworkThrottling: PropTypes.func.isRequired,
-    onChangePixelRatio: PropTypes.func.isRequired,
-    onChangeTouchSimulation: PropTypes.func.isRequired,
-    onExit: PropTypes.func.isRequired,
-    onScreenshot: PropTypes.func.isRequired,
-  },
-
-  mixins: [ addons.PureRenderMixin ],
+class GlobalToolbar extends PureComponent {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      displayPixelRatio: Types.pixelRatio.value.isRequired,
+      networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
+      screenshot: PropTypes.shape(Types.screenshot).isRequired,
+      selectedDevice: PropTypes.string.isRequired,
+      selectedPixelRatio: PropTypes.shape(Types.pixelRatio).isRequired,
+      touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
+      onChangeNetworkThrottling: PropTypes.func.isRequired,
+      onChangePixelRatio: PropTypes.func.isRequired,
+      onChangeTouchSimulation: PropTypes.func.isRequired,
+      onExit: PropTypes.func.isRequired,
+      onScreenshot: PropTypes.func.isRequired,
+    };
+  }
 
   render() {
     let {
       devices,
       displayPixelRatio,
       networkThrottling,
       screenshot,
       selectedDevice,
@@ -91,11 +90,12 @@ module.exports = createClass({
       }),
       dom.button({
         id: "global-exit-button",
         className: "toolbar-button devtools-button",
         title: getStr("responsive.exit"),
         onClick: onExit,
       })
     );
-  },
+  }
+}
 
-});
+module.exports = GlobalToolbar;
--- a/devtools/client/responsive.html/components/NetworkThrottlingSelector.js
+++ b/devtools/client/responsive.html/components/NetworkThrottlingSelector.js
@@ -1,31 +1,34 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { DOM: dom, createClass, PropTypes, addons } =
-  require("devtools/client/shared/vendor/react");
+const { PureComponent } = 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 Types = require("../types");
 const { getStr } = require("../utils/l10n");
 const throttlingProfiles = require("devtools/client/shared/network-throttling-profiles");
 
-module.exports = createClass({
-
-  displayName: "NetworkThrottlingSelector",
+class NetworkThrottlingSelector extends PureComponent {
+  static get propTypes() {
+    return {
+      networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
+      onChangeNetworkThrottling: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
-    onChangeNetworkThrottling: PropTypes.func.isRequired,
-  },
-
-  mixins: [ addons.PureRenderMixin ],
+  constructor(props) {
+    super(props);
+    this.onSelectChange = this.onSelectChange.bind(this);
+  }
 
   onSelectChange({ target }) {
     let {
       onChangeNetworkThrottling,
     } = this.props;
 
     if (target.value == getStr("responsive.noThrottling")) {
       onChangeNetworkThrottling(false, "");
@@ -33,17 +36,17 @@ module.exports = createClass({
     }
 
     for (let profile of throttlingProfiles) {
       if (profile.id === target.value) {
         onChangeNetworkThrottling(true, profile.id);
         return;
       }
     }
-  },
+  }
 
   render() {
     let {
       networkThrottling,
     } = this.props;
 
     let selectClass = "";
     let selectedProfile;
@@ -82,11 +85,12 @@ module.exports = createClass({
       {
         id: "global-network-throttling-selector",
         className: selectClass,
         value: selectedProfile,
         onChange: this.onSelectChange,
       },
       ...listContent
     );
-  },
+  }
+}
 
-});
+module.exports = NetworkThrottlingSelector;
--- a/devtools/client/responsive.html/components/ResizableViewport.js
+++ b/devtools/client/responsive.html/components/ResizableViewport.js
@@ -1,80 +1,86 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* global window */
 
 "use strict";
 
-const { DOM: dom, createClass, 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 dom = require("devtools/client/shared/vendor/react-dom-factories");
 
 const Constants = require("../constants");
 const Types = require("../types");
 const Browser = createFactory(require("./Browser"));
 const ViewportToolbar = createFactory(require("./ViewportToolbar"));
 
 const VIEWPORT_MIN_WIDTH = Constants.MIN_VIEWPORT_DIMENSION;
 const VIEWPORT_MIN_HEIGHT = Constants.MIN_VIEWPORT_DIMENSION;
 
-module.exports = createClass({
-
-  displayName: "ResizableViewport",
+class ResizableViewport extends Component {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      screenshot: PropTypes.shape(Types.screenshot).isRequired,
+      swapAfterMount: PropTypes.bool.isRequired,
+      viewport: PropTypes.shape(Types.viewport).isRequired,
+      onBrowserMounted: PropTypes.func.isRequired,
+      onChangeDevice: PropTypes.func.isRequired,
+      onContentResize: PropTypes.func.isRequired,
+      onRemoveDeviceAssociation: PropTypes.func.isRequired,
+      onResizeViewport: PropTypes.func.isRequired,
+      onRotateViewport: PropTypes.func.isRequired,
+      onUpdateDeviceModal: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    screenshot: PropTypes.shape(Types.screenshot).isRequired,
-    swapAfterMount: PropTypes.bool.isRequired,
-    viewport: PropTypes.shape(Types.viewport).isRequired,
-    onBrowserMounted: PropTypes.func.isRequired,
-    onChangeDevice: PropTypes.func.isRequired,
-    onContentResize: PropTypes.func.isRequired,
-    onRemoveDeviceAssociation: PropTypes.func.isRequired,
-    onResizeViewport: PropTypes.func.isRequired,
-    onRotateViewport: PropTypes.func.isRequired,
-    onUpdateDeviceModal: PropTypes.func.isRequired,
-  },
+  constructor(props) {
+    super(props);
 
-  getInitialState() {
-    return {
+    this.state = {
       isResizing: false,
       lastClientX: 0,
       lastClientY: 0,
       ignoreX: false,
       ignoreY: false,
     };
-  },
+
+    this.onResizeStart = this.onResizeStart.bind(this);
+    this.onResizeStop = this.onResizeStop.bind(this);
+    this.onResizeDrag = this.onResizeDrag.bind(this);
+  }
 
   onResizeStart({ target, clientX, clientY }) {
     window.addEventListener("mousemove", this.onResizeDrag, true);
     window.addEventListener("mouseup", this.onResizeStop, true);
 
     this.setState({
       isResizing: true,
       lastClientX: clientX,
       lastClientY: clientY,
       ignoreX: target === this.refs.resizeBarY,
       ignoreY: target === this.refs.resizeBarX,
     });
-  },
+  }
 
   onResizeStop() {
     window.removeEventListener("mousemove", this.onResizeDrag, true);
     window.removeEventListener("mouseup", this.onResizeStop, true);
 
     this.setState({
       isResizing: false,
       lastClientX: 0,
       lastClientY: 0,
       ignoreX: false,
       ignoreY: false,
     });
-  },
+  }
 
   onResizeDrag({ clientX, clientY }) {
     if (!this.state.isResizing) {
       return;
     }
 
     let { lastClientX, lastClientY, ignoreX, ignoreY } = this.state;
     // the viewport is centered horizontally, so horizontal resize resizes
@@ -115,17 +121,17 @@ module.exports = createClass({
       // UA, so it's important to keep doing this for now.
       this.props.onRemoveDeviceAssociation();
     }
 
     this.setState({
       lastClientX,
       lastClientY
     });
-  },
+  }
 
   render() {
     let {
       devices,
       screenshot,
       swapAfterMount,
       viewport,
       onBrowserMounted,
@@ -182,11 +188,12 @@ module.exports = createClass({
         onMouseDown: this.onResizeStart,
       }),
       dom.div({
         ref: "resizeBarY",
         className: "viewport-vertical-resize-handle",
         onMouseDown: this.onResizeStart,
       })
     );
-  },
+  }
+}
 
-});
+module.exports = ResizableViewport;
--- a/devtools/client/responsive.html/components/Viewport.js
+++ b/devtools/client/responsive.html/components/Viewport.js
@@ -1,74 +1,82 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { DOM: dom, createClass, 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 dom = require("devtools/client/shared/vendor/react-dom-factories");
 
 const Types = require("../types");
 const ResizableViewport = createFactory(require("./ResizableViewport"));
 const ViewportDimension = createFactory(require("./ViewportDimension"));
 
-module.exports = createClass({
-
-  displayName: "Viewport",
+class Viewport extends Component {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      screenshot: PropTypes.shape(Types.screenshot).isRequired,
+      swapAfterMount: PropTypes.bool.isRequired,
+      viewport: PropTypes.shape(Types.viewport).isRequired,
+      onBrowserMounted: PropTypes.func.isRequired,
+      onChangeDevice: PropTypes.func.isRequired,
+      onContentResize: PropTypes.func.isRequired,
+      onRemoveDeviceAssociation: PropTypes.func.isRequired,
+      onResizeViewport: PropTypes.func.isRequired,
+      onRotateViewport: PropTypes.func.isRequired,
+      onUpdateDeviceModal: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    screenshot: PropTypes.shape(Types.screenshot).isRequired,
-    swapAfterMount: PropTypes.bool.isRequired,
-    viewport: PropTypes.shape(Types.viewport).isRequired,
-    onBrowserMounted: PropTypes.func.isRequired,
-    onChangeDevice: PropTypes.func.isRequired,
-    onContentResize: PropTypes.func.isRequired,
-    onRemoveDeviceAssociation: PropTypes.func.isRequired,
-    onResizeViewport: PropTypes.func.isRequired,
-    onRotateViewport: PropTypes.func.isRequired,
-    onUpdateDeviceModal: PropTypes.func.isRequired,
-  },
+  constructor(props) {
+    super(props);
+    this.onChangeDevice = this.onChangeDevice.bind(this);
+    this.onRemoveDeviceAssociation = this.onRemoveDeviceAssociation.bind(this);
+    this.onResizeViewport = this.onResizeViewport.bind(this);
+    this.onRotateViewport = this.onRotateViewport.bind(this);
+  }
 
   onChangeDevice(device, deviceType) {
     let {
       viewport,
       onChangeDevice,
     } = this.props;
 
     onChangeDevice(viewport.id, device, deviceType);
-  },
+  }
 
   onRemoveDeviceAssociation() {
     let {
       viewport,
       onRemoveDeviceAssociation,
     } = this.props;
 
     onRemoveDeviceAssociation(viewport.id);
-  },
+  }
 
   onResizeViewport(width, height) {
     let {
       viewport,
       onResizeViewport,
     } = this.props;
 
     onResizeViewport(viewport.id, width, height);
-  },
+  }
 
   onRotateViewport() {
     let {
       viewport,
       onRotateViewport,
     } = this.props;
 
     onRotateViewport(viewport.id);
-  },
+  }
 
   render() {
     let {
       devices,
       screenshot,
       swapAfterMount,
       viewport,
       onBrowserMounted,
@@ -101,11 +109,12 @@ module.exports = createClass({
         onChangeDevice,
         onContentResize,
         onRemoveDeviceAssociation,
         onResizeViewport,
         onRotateViewport,
         onUpdateDeviceModal,
       })
     );
-  },
+  }
+}
 
-});
+module.exports = Viewport;
--- a/devtools/client/responsive.html/components/ViewportDimension.js
+++ b/devtools/client/responsive.html/components/ViewportDimension.js
@@ -1,109 +1,118 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { DOM: dom, createClass, PropTypes } =
-  require("devtools/client/shared/vendor/react");
+const { 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 Constants = require("../constants");
 const Types = require("../types");
 
-module.exports = createClass({
-  displayName: "ViewportDimension",
+class ViewportDimension extends Component {
+  static get propTypes() {
+    return {
+      viewport: PropTypes.shape(Types.viewport).isRequired,
+      onChangeSize: PropTypes.func.isRequired,
+      onRemoveDeviceAssociation: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    viewport: PropTypes.shape(Types.viewport).isRequired,
-    onChangeSize: PropTypes.func.isRequired,
-    onRemoveDeviceAssociation: PropTypes.func.isRequired,
-  },
+  constructor(props) {
+    super(props);
+    let { width, height } = props.viewport;
 
-  getInitialState() {
-    let { width, height } = this.props.viewport;
-
-    return {
+    this.state = {
       width,
       height,
       isEditing: false,
       isInvalid: false,
     };
-  },
+
+    this.validateInput = this.validateInput.bind(this);
+    this.onInputBlur = this.onInputBlur.bind(this);
+    this.onInputChange = this.onInputChange.bind(this);
+    this.onInputFocus = this.onInputFocus.bind(this);
+    this.onInputKeyUp = this.onInputKeyUp.bind(this);
+    this.onInputSubmit = this.onInputSubmit.bind(this);
+  }
 
   componentWillReceiveProps(nextProps) {
     let { width, height } = nextProps.viewport;
 
     this.setState({
       width,
       height,
     });
-  },
+  }
 
   validateInput(value) {
     let isInvalid = true;
 
     // Check the value is a number and greater than MIN_VIEWPORT_DIMENSION
     if (/^\d{3,4}$/.test(value) &&
         parseInt(value, 10) >= Constants.MIN_VIEWPORT_DIMENSION) {
       isInvalid = false;
     }
 
     this.setState({
       isInvalid,
     });
-  },
+  }
 
   onInputBlur() {
     let { width, height } = this.props.viewport;
 
     if (this.state.width != width || this.state.height != height) {
       this.onInputSubmit();
     }
 
     this.setState({
       isEditing: false,
       inInvalid: false,
     });
-  },
+  }
 
   onInputChange({ target }) {
     if (target.value.length > 4) {
       return;
     }
 
     if (this.refs.widthInput == target) {
       this.setState({ width: target.value });
       this.validateInput(target.value);
     }
 
     if (this.refs.heightInput == target) {
       this.setState({ height: target.value });
       this.validateInput(target.value);
     }
-  },
+  }
 
   onInputFocus() {
     this.setState({
       isEditing: true,
     });
-  },
+  }
 
   onInputKeyUp({ target, keyCode }) {
     // On Enter, submit the input
     if (keyCode == 13) {
       this.onInputSubmit();
     }
 
     // On Esc, blur the target
     if (keyCode == 27) {
       target.blur();
     }
-  },
+  }
 
   onInputSubmit() {
     if (this.state.isInvalid) {
       let { width, height } = this.props.viewport;
 
       this.setState({
         width,
         height,
@@ -115,17 +124,17 @@ module.exports = createClass({
 
     // Change the device selector back to an unselected device
     // TODO: Bug 1332754: Logic like this probably belongs in the action creator.
     if (this.props.viewport.device) {
       this.props.onRemoveDeviceAssociation();
     }
     this.props.onChangeSize(parseInt(this.state.width, 10),
                             parseInt(this.state.height, 10));
-  },
+  }
 
   render() {
     let editableClass = "viewport-dimension-editable";
     let inputClass = "viewport-dimension-input";
 
     if (this.state.isEditing) {
       editableClass += " editing";
       inputClass += " editing";
@@ -163,11 +172,12 @@ module.exports = createClass({
           value: this.state.height,
           onBlur: this.onInputBlur,
           onChange: this.onInputChange,
           onFocus: this.onInputFocus,
           onKeyUp: this.onInputKeyUp,
         })
       )
     );
-  },
+  }
+}
 
-});
+module.exports = ViewportDimension;
--- a/devtools/client/responsive.html/components/ViewportToolbar.js
+++ b/devtools/client/responsive.html/components/ViewportToolbar.js
@@ -1,34 +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 { DOM: dom, createClass, createFactory, PropTypes, addons } =
-  require("devtools/client/shared/vendor/react");
+const { PureComponent, 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 { getStr } = require("../utils/l10n");
 const Types = require("../types");
 const DeviceSelector = createFactory(require("./DeviceSelector"));
 
-module.exports = createClass({
-  displayName: "ViewportToolbar",
-
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    viewport: PropTypes.shape(Types.viewport).isRequired,
-    onChangeDevice: PropTypes.func.isRequired,
-    onResizeViewport: PropTypes.func.isRequired,
-    onRotateViewport: PropTypes.func.isRequired,
-    onUpdateDeviceModal: PropTypes.func.isRequired,
-  },
-
-  mixins: [ addons.PureRenderMixin ],
+class ViewportToolbar extends PureComponent {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      viewport: PropTypes.shape(Types.viewport).isRequired,
+      onChangeDevice: PropTypes.func.isRequired,
+      onResizeViewport: PropTypes.func.isRequired,
+      onRotateViewport: PropTypes.func.isRequired,
+      onUpdateDeviceModal: PropTypes.func.isRequired,
+    };
+  }
 
   render() {
     let {
       devices,
       viewport,
       onChangeDevice,
       onResizeViewport,
       onRotateViewport,
@@ -48,11 +47,12 @@ module.exports = createClass({
         onUpdateDeviceModal,
       }),
       dom.button({
         className: "viewport-rotate-button toolbar-button devtools-button",
         onClick: onRotateViewport,
         title: getStr("responsive.rotate"),
       })
     );
-  },
+  }
+}
 
-});
+module.exports = ViewportToolbar;
--- a/devtools/client/responsive.html/components/Viewports.js
+++ b/devtools/client/responsive.html/components/Viewports.js
@@ -1,36 +1,36 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { DOM: dom, 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 dom = require("devtools/client/shared/vendor/react-dom-factories");
 
 const Types = require("../types");
 const Viewport = createFactory(require("./Viewport"));
 
-module.exports = createClass({
-
-  displayName: "Viewports",
-
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    screenshot: PropTypes.shape(Types.screenshot).isRequired,
-    viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
-    onBrowserMounted: PropTypes.func.isRequired,
-    onChangeDevice: PropTypes.func.isRequired,
-    onContentResize: PropTypes.func.isRequired,
-    onRemoveDeviceAssociation: PropTypes.func.isRequired,
-    onResizeViewport: PropTypes.func.isRequired,
-    onRotateViewport: PropTypes.func.isRequired,
-    onUpdateDeviceModal: PropTypes.func.isRequired,
-  },
+class Viewports extends Component {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      screenshot: PropTypes.shape(Types.screenshot).isRequired,
+      viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
+      onBrowserMounted: PropTypes.func.isRequired,
+      onChangeDevice: PropTypes.func.isRequired,
+      onContentResize: PropTypes.func.isRequired,
+      onRemoveDeviceAssociation: PropTypes.func.isRequired,
+      onResizeViewport: PropTypes.func.isRequired,
+      onRotateViewport: PropTypes.func.isRequired,
+      onUpdateDeviceModal: PropTypes.func.isRequired,
+    };
+  }
 
   render() {
     let {
       devices,
       screenshot,
       viewports,
       onBrowserMounted,
       onChangeDevice,
@@ -57,11 +57,12 @@ module.exports = createClass({
           onContentResize,
           onRemoveDeviceAssociation,
           onResizeViewport,
           onRotateViewport,
           onUpdateDeviceModal,
         });
       })
     );
-  },
+  }
+}
 
-});
+module.exports = Viewports;
--- a/devtools/client/responsive.html/types.js
+++ b/devtools/client/responsive.html/types.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 { PropTypes } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { createEnum } = require("devtools/client/shared/enum");
 
 // React PropTypes are used to describe the expected "shape" of various common
 // objects that get passed down as props to components.
 
 /* GLOBAL */
 
 /**