Bug 1344107 - Adding shouldComponentUpdate for request-list-column-* r?honza draft
authorRicky Chien <ricky060709@gmail.com>
Fri, 03 Mar 2017 12:16:48 +0800
changeset 493022 6e294d84ef8159d9eef59960896469e62633d62f
parent 492711 c0911da94be9c74d262e6b85865540556e14a61a
child 547741 29418f290a599072b504416e859f9dc6ff9d3ffc
push id47628
push userbmo:rchien@mozilla.com
push dateFri, 03 Mar 2017 05:37:49 +0000
reviewershonza
bugs1344107
milestone54.0a1
Bug 1344107 - Adding shouldComponentUpdate for request-list-column-* r?honza MozReview-Commit-ID: FPefb7fzjMm
devtools/client/netmonitor/components/request-list-column-cause.js
devtools/client/netmonitor/components/request-list-column-domain.js
devtools/client/netmonitor/components/request-list-column-file.js
devtools/client/netmonitor/components/request-list-column-method.js
devtools/client/netmonitor/components/request-list-column-size.js
devtools/client/netmonitor/components/request-list-column-status.js
devtools/client/netmonitor/components/request-list-column-transferred.js
devtools/client/netmonitor/components/request-list-column-type.js
devtools/client/netmonitor/components/request-list-column-waterfall.js
devtools/client/netmonitor/utils/request-utils.js
--- a/devtools/client/netmonitor/components/request-list-column-cause.js
+++ b/devtools/client/netmonitor/components/request-list-column-cause.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 {
+  createClass,
   createFactory,
   DOM,
+  PropTypes,
 } = require("devtools/client/shared/vendor/react");
+const { propertiesEqual } = require("../utils/request-utils");
 
 // Components
 const Column = createFactory(require("devtools/client/shared/vendor/react-virtualized").Column);
 const RequestListColumnHeader = createFactory(require("./request-list-column-header"));
 
 const { div } = DOM;
+const UPDATED_PROPS = ["cause"];
 
 /**
  * Request list cause column component
  * Describes the header and cell contents of a table column
  */
 function RequestListColumnCause() {
   let name = "cause";
   return (
     Column({
-      cellRenderer,
+      cellRenderer: CauseColumnCell,
       className: "requests-list-subitem",
       dataKey: name,
       headerClassName: "requests-list-header",
       headerRenderer: (props) => RequestListColumnHeader(props),
       label: name,
       width: 100,
       maxWidth: 100,
       minWidth: 45,
@@ -37,41 +41,49 @@ function RequestListColumnCause() {
         justifyContent: "center",
       },
     })
   );
 }
 
 RequestListColumnCause.displayName = "RequestListColumnCause";
 
-function cellRenderer({
-  cellData,
-  dataKey,
-  rowData,
-  rowIndex,
-}) {
-  let { cause } = rowData;
-  let causeType = "";
-  let causeUri;
-  let causeHasStack = false;
+const CauseColumnCell = createFactory(createClass({
+  displayName: "CauseColumnCell",
+
+  propTypes: {
+    rowData: PropTypes.object,
+  },
+
+  shouldComponentUpdate(nextProps) {
+    return !propertiesEqual(UPDATED_PROPS, this.props.rowData, nextProps.rowData);
+  },
+
+  render() {
+    let { rowData } = this.props;
+    let { cause } = rowData;
+    let causeType = "";
+    let causeUri;
+    let causeHasStack = false;
 
-  if (cause) {
-    // Legacy server might send a numeric value. Display it as "unknown"
-    causeType = typeof cause.type === "string" ? cause.type : "unknown";
-    causeUri = cause.loadingDocumentUri;
-    causeHasStack = cause.stacktrace && cause.stacktrace.length > 0;
-  }
+    if (cause) {
+      // Legacy server might send a numeric value. Display it as "unknown"
+      causeType = typeof cause.type === "string" ? cause.type : "unknown";
+      causeUri = cause.loadingDocumentUri;
+      causeHasStack = cause.stacktrace && cause.stacktrace.length > 0;
+    }
 
-  return (
-    div({
-      className: "requests-list-subitem requests-list-cause",
-      title: causeUri,
-    },
+    return (
       div({
-        className: "requests-list-cause-stack",
-        hidden: !causeHasStack,
-      }, "JS"),
-      div({ className: "subitem-label" }, causeType),
-    )
-  );
-}
+        className: "requests-list-subitem requests-list-cause",
+        title: causeUri,
+      },
+        div({
+          className: "requests-list-cause-stack",
+          hidden: !causeHasStack,
+        }, "JS"),
+        div({ className: "subitem-label" }, causeType),
+      )
+    );
+  }
+}));
 
 module.exports = RequestListColumnCause;
--- a/devtools/client/netmonitor/components/request-list-column-domain.js
+++ b/devtools/client/netmonitor/components/request-list-column-domain.js
@@ -1,85 +1,103 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
+  createClass,
   createFactory,
   DOM,
+  PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../utils/l10n");
+const { propertiesEqual } = require("../utils/request-utils");
 
 // Components
 const Column = createFactory(require("devtools/client/shared/vendor/react-virtualized").Column);
 const RequestListColumnHeader = createFactory(require("./request-list-column-header"));
 
 const { div } = DOM;
+const UPDATED_PROPS = [
+  "remoteAddress",
+  "securityState",
+  "urlDetails",
+];
 
 /**
  * Request list domain column component
  * Describes the header and cell contents of a table column
  */
 function RequestListColumnDomain({
   selectDetailsPanelTab,
 }) {
   let name = "domain";
   return (
     Column({
-      cellRenderer: (props) => cellRenderer(props, selectDetailsPanelTab),
+      cellRenderer: (props) =>
+        DomainColumnCell(Object.assign(props, { selectDetailsPanelTab })),
       className: "requests-list-subitem",
       dataKey: name,
       headerClassName: "requests-list-header",
       headerRenderer: (props) => RequestListColumnHeader(props),
       label: name,
       width: 150,
       maxWidth: 220,
       minWidth: 75,
       style: { display: "flex" },
     })
   );
 }
 
 RequestListColumnDomain.displayName = "RequestListColumnDomain";
 
-function cellRenderer({
-  cellData,
-  dataKey,
-  rowData,
-  rowIndex,
-}, selectDetailsPanelTab) {
-  let {
-    urlDetails,
-    remoteAddress,
-    securityState,
-  } = rowData;
+const DomainColumnCell = createFactory(createClass({
+  displayName: "DomainColumnCell",
+
+  propTypes: {
+    rowData: PropTypes.object,
+    selectDetailsPanelTab: PropTypes.func.isRequired,
+  },
+
+  shouldComponentUpdate(nextProps) {
+    return !propertiesEqual(UPDATED_PROPS, this.props.rowData, nextProps.rowData);
+  },
+
+  render() {
+    let { rowData, selectDetailsPanelTab } = this.props;
+    let {
+      urlDetails,
+      remoteAddress,
+      securityState,
+    } = rowData;
 
-  let iconClassList = ["requests-domain"];
-  let iconTitle;
-  if (urlDetails.isLocal) {
-    iconClassList.push("security-state-local");
-    iconTitle = L10N.getStr("netmonitor.security.state.secure");
-  } else if (securityState) {
-    iconClassList.push(`security-state-${securityState}`);
-    iconTitle = L10N.getStr(`netmonitor.security.state.${securityState}`);
-  }
+    let iconClassList = ["requests-domain"];
+    let iconTitle;
+    if (urlDetails.isLocal) {
+      iconClassList.push("security-state-local");
+      iconTitle = L10N.getStr("netmonitor.security.state.secure");
+    } else if (securityState) {
+      iconClassList.push(`security-state-${securityState}`);
+      iconTitle = L10N.getStr(`netmonitor.security.state.${securityState}`);
+    }
 
-  let title = urlDetails.host + (remoteAddress ? ` (${remoteAddress})` : "");
+    let title = urlDetails.host + (remoteAddress ? ` (${remoteAddress})` : "");
 
-  return (
-    div({ className: "requests-list-subitem" },
-      div({
-        className: iconClassList.join(" "),
-        title: iconTitle,
-        onClick: () => {
-          if (securityState && securityState !== "insecure") {
-            selectDetailsPanelTab("security");
-          }
-        },
-      }),
-      div({ className: "requests-list-domain subitem-label", title }, urlDetails.host),
-    )
-  );
-}
+    return (
+      div({ className: "requests-list-subitem" },
+        div({
+          className: iconClassList.join(" "),
+          title: iconTitle,
+          onClick: () => {
+            if (securityState && securityState !== "insecure") {
+              selectDetailsPanelTab("security");
+            }
+          },
+        }),
+        div({ className: "requests-list-domain subitem-label", title }, urlDetails.host),
+      )
+    );
+  }
+}));
 
 module.exports = RequestListColumnDomain;
--- a/devtools/client/netmonitor/components/request-list-column-file.js
+++ b/devtools/client/netmonitor/components/request-list-column-file.js
@@ -1,71 +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/. */
 
 "use strict";
 
 const {
+  createClass,
   createFactory,
   DOM,
+  PropTypes,
 } = require("devtools/client/shared/vendor/react");
+const { propertiesEqual } = require("../utils/request-utils");
 
 // Components
 const Column = createFactory(require("devtools/client/shared/vendor/react-virtualized").Column);
 const RequestListColumnHeader = createFactory(require("./request-list-column-header"));
 
 const { div, img } = DOM;
+const UPDATED_PROPS = [
+  "responseContentDataUri",
+  "urlDetails",
+];
 
 /**
  * Request list file column component
  * Describes the header and cell contents of a table column
  */
 function RequestListColumnFile() {
   let name = "file";
   return (
     Column({
-      cellRenderer,
+      cellRenderer: FileColumnCell,
       className: "requests-list-subitem",
       dataKey: name,
       headerClassName: "requests-list-header",
       headerRenderer: (props) => RequestListColumnHeader(props),
       label: name,
       width: 300,
       maxWidth: 350,
       minWidth: 100,
       style: { display: "flex" },
     })
   );
 }
 
 RequestListColumnFile.displayName = "RequestListColumnFile";
 
-function cellRenderer({
-  cellData,
-  dataKey,
-  rowData,
-  rowIndex,
-}) {
-  let {
-    urlDetails,
-    responseContentDataUri,
-  } = rowData;
+const FileColumnCell = createFactory(createClass({
+  displayName: "FileColumnCell",
+
+  propTypes: {
+    rowData: PropTypes.object,
+  },
+
+  shouldComponentUpdate(nextProps) {
+    return !propertiesEqual(UPDATED_PROPS, this.props.rowData, nextProps.rowData);
+  },
+
+  render() {
+    let { rowData } = this.props;
+    let {
+      urlDetails,
+      responseContentDataUri,
+    } = rowData;
 
-  return (
-    div({ className: "requests-list-file requests-list-subitem" },
-      img({
-        className: "requests-list-icon",
-        src: responseContentDataUri,
-        hidden: !responseContentDataUri,
-        "data-type": responseContentDataUri ? "thumbnail" : undefined,
-      }),
-      div({
-        className: "requests-list-url subitem-label",
-        title: urlDetails.unicodeUrl,
-      },
-        urlDetails.baseNameWithQuery,
-      ),
-    )
-  );
-}
+    return (
+      div({ className: "requests-list-file requests-list-subitem" },
+        img({
+          className: "requests-list-icon",
+          src: responseContentDataUri,
+          hidden: !responseContentDataUri,
+          "data-type": responseContentDataUri ? "thumbnail" : undefined,
+        }),
+        div({
+          className: "requests-list-url subitem-label",
+          title: urlDetails.unicodeUrl,
+        },
+          urlDetails.baseNameWithQuery,
+        ),
+      )
+    );
+  }
+}));
 
 module.exports = RequestListColumnFile;
--- a/devtools/client/netmonitor/components/request-list-column-method.js
+++ b/devtools/client/netmonitor/components/request-list-column-method.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 {
+  createClass,
   createFactory,
   DOM,
+  PropTypes,
 } = require("devtools/client/shared/vendor/react");
+const { propertiesEqual } = require("../utils/request-utils");
 
 // Components
 const Column = createFactory(require("devtools/client/shared/vendor/react-virtualized").Column);
 const RequestListColumnHeader = createFactory(require("./request-list-column-header"));
 
 const { div } = DOM;
+const UPDATED_PROPS = ["method"];
 
 /**
  * Request list method column component
  * Describes the header and cell contents of a table column
  */
 function RequestListColumnMethod() {
   let name = "method";
   return (
     Column({
-      cellRenderer,
+      cellRenderer: MethodColumnCell,
       className: "requests-list-subitem requests-list-method-box",
       dataKey: name,
       headerClassName: "requests-list-header",
       headerRenderer: (props) => RequestListColumnHeader(props),
       label: name,
       width: 75,
       maxWidth: 84,
       minWidth: 55,
@@ -37,22 +41,30 @@ function RequestListColumnMethod() {
         justifyContent: "center",
       },
     })
   );
 }
 
 RequestListColumnMethod.displayName = "RequestListColumnMethod";
 
-function cellRenderer({
-  cellData,
-  dataKey,
-  rowData,
-  rowIndex,
-}) {
-  let { method } = rowData;
+const MethodColumnCell = createFactory(createClass({
+  displayName: "MethodColumnCell",
+
+  propTypes: {
+    rowData: PropTypes.object,
+  },
 
-  return (
-    div({ className: "subitem-label requests-list-method" }, method)
-  );
-}
+  shouldComponentUpdate(nextProps) {
+    return !propertiesEqual(UPDATED_PROPS, this.props.rowData, nextProps.rowData);
+  },
+
+  render() {
+    let { rowData } = this.props;
+    let { method } = rowData;
+
+    return (
+      div({ className: "subitem-label requests-list-method" }, method)
+    );
+  }
+}));
 
 module.exports = RequestListColumnMethod;
--- a/devtools/client/netmonitor/components/request-list-column-size.js
+++ b/devtools/client/netmonitor/components/request-list-column-size.js
@@ -1,35 +1,39 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
+  createClass,
   createFactory,
   DOM,
+  PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { getFormattedSize } = require("../utils/format-utils");
+const { propertiesEqual } = require("../utils/request-utils");
 
 // Components
 const Column = createFactory(require("devtools/client/shared/vendor/react-virtualized").Column);
 const RequestListColumnHeader = createFactory(require("./request-list-column-header"));
 
 const { div } = DOM;
+const UPDATED_PROPS = ["contentSize"];
 
 /**
  * Request list size column component
  * Describes the header and cell contents of a table column
  */
 function RequestListColumnsize() {
   let name = "size";
   return (
     Column({
-      cellRenderer,
+      cellRenderer: ContentSizeColumnCell,
       className: "requests-list-subitem",
       dataKey: name,
       headerClassName: "requests-list-header",
       headerRenderer: (props) => RequestListColumnHeader(props),
       label: name,
       width: 72,
       maxWidth: 72,
       minWidth: 45,
@@ -38,25 +42,33 @@ function RequestListColumnsize() {
         justifyContent: "center",
       },
     })
   );
 }
 
 RequestListColumnsize.displayName = "RequestListColumnsize";
 
-function cellRenderer({
-  cellData,
-  dataKey,
-  rowData,
-  rowIndex,
-}) {
-  let { contentSize } = rowData;
-  let title;
+const ContentSizeColumnCell = createFactory(createClass({
+  displayName: "ContentSizeColumnCell",
+
+  propTypes: {
+    rowData: PropTypes.object,
+  },
+
+  shouldComponentUpdate(nextProps) {
+    return !propertiesEqual(UPDATED_PROPS, this.props.rowData, nextProps.rowData);
+  },
 
-  if (typeof contentSize === "number") {
-    title = getFormattedSize(contentSize);
+  render() {
+    let { rowData } = this.props;
+    let { contentSize } = rowData;
+    let title;
+
+    if (typeof contentSize === "number") {
+      title = getFormattedSize(contentSize);
+    }
+
+    return div({ className: "requests-list-size subitem-label", title }, title);
   }
-
-  return div({ className: "requests-list-size subitem-label", title }, title);
-}
+}));
 
 module.exports = RequestListColumnsize;
--- a/devtools/client/netmonitor/components/request-list-column-status.js
+++ b/devtools/client/netmonitor/components/request-list-column-status.js
@@ -1,34 +1,43 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
+  createClass,
   createFactory,
   DOM,
+  PropTypes,
 } = require("devtools/client/shared/vendor/react");
+const { propertiesEqual } = require("../utils/request-utils");
 
 // Components
 const Column = createFactory(require("devtools/client/shared/vendor/react-virtualized").Column);
 const RequestListColumnHeader = createFactory(require("./request-list-column-header"));
 
 const { div } = DOM;
+const UPDATED_PROPS = [
+  "fromCache",
+  "fromServiceWorker",
+  "status",
+  "statusText",
+];
 
 /**
  * Request list status column component
  * Describes the header and cell contents of a table column
  */
 function RequestListColumnStatus() {
   let name = "status";
   return (
     Column({
-      cellRenderer,
+      cellRenderer: StatusColumnCell,
       className: "requests-list-subitem",
       dataKey: name,
       disableSort: false,
       headerClassName: "requests-list-header",
       headerRenderer: (props) => RequestListColumnHeader(props),
       label: name + "3",
       width: 72,
       maxWidth: 72,
@@ -38,46 +47,54 @@ function RequestListColumnStatus() {
         justifyContent: "center",
       },
     })
   );
 }
 
 RequestListColumnStatus.displayName = "RequestListColumnStatus";
 
-function cellRenderer({
-  cellData,
-  dataKey,
-  rowData,
-  rowIndex,
-}) {
-  let { status, statusText, fromCache, fromServiceWorker } = rowData;
-  let code, title;
+const StatusColumnCell = createFactory(createClass({
+  displayName: "StatusColumnCell",
+
+  propTypes: {
+    rowData: PropTypes.object,
+  },
+
+  shouldComponentUpdate(nextProps) {
+    return !propertiesEqual(UPDATED_PROPS, this.props.rowData, nextProps.rowData);
+  },
+
+  render() {
+    let { rowData } = this.props;
+    let { status, statusText, fromCache, fromServiceWorker } = rowData;
+    let code, title;
 
-  if (status) {
-    if (fromCache) {
-      code = "cached";
-    } else if (fromServiceWorker) {
-      code = "service worker";
-    } else {
-      code = status;
+    if (status) {
+      if (fromCache) {
+        code = "cached";
+      } else if (fromServiceWorker) {
+        code = "service worker";
+      } else {
+        code = status;
+      }
+
+      if (statusText) {
+        title = `${status} ${statusText}`;
+        if (fromCache) {
+          title += " (cached)";
+        }
+        if (fromServiceWorker) {
+          title += " (service worker)";
+        }
+      }
     }
 
-    if (statusText) {
-      title = `${status} ${statusText}`;
-      if (fromCache) {
-        title += " (cached)";
-      }
-      if (fromServiceWorker) {
-        title += " (service worker)";
-      }
-    }
+    return (
+      div({ className: "requests-list-subitem requests-list-status", title },
+        div({ className: "requests-list-status-icon", "data-code": code }),
+        div({ className: "requests-list-status-code subitem-label" }, status),
+      )
+    );
   }
-
-  return (
-    div({ className: "requests-list-subitem requests-list-status", title },
-      div({ className: "requests-list-status-icon", "data-code": code }),
-      div({ className: "requests-list-status-code subitem-label" }, status),
-    )
-  );
-}
+}));
 
 module.exports = RequestListColumnStatus;
--- a/devtools/client/netmonitor/components/request-list-column-transferred.js
+++ b/devtools/client/netmonitor/components/request-list-column-transferred.js
@@ -1,36 +1,44 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
+  createClass,
   createFactory,
   DOM,
+  PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../utils/l10n");
 const { getFormattedSize } = require("../utils/format-utils");
+const { propertiesEqual } = require("../utils/request-utils");
 
 // Components
 const Column = createFactory(require("devtools/client/shared/vendor/react-virtualized").Column);
 const RequestListColumnHeader = createFactory(require("./request-list-column-header"));
 
 const { div } = DOM;
+const UPDATED_PROPS = [
+  "fromCache",
+  "fromServiceWorker",
+  "transferredSize",
+];
 
 /**
  * Request list transferred column component
  * Describes the header and cell contents of a table column
  */
 function RequestListColumnTransferred() {
   let name = "transferred";
   return (
     Column({
-      cellRenderer,
+      cellRenderer: TransferredSizeColumnCell,
       className: "requests-list-subitem",
       dataKey: name,
       headerClassName: "requests-list-header",
       headerRenderer: (props) => RequestListColumnHeader(props),
       label: name,
       width: 96,
       maxWidth: 96,
       minWidth: 45,
@@ -39,34 +47,42 @@ function RequestListColumnTransferred() 
         justifyContent: "center",
       },
     })
   );
 }
 
 RequestListColumnTransferred.displayName = "RequestListColumnTransferred";
 
-function cellRenderer({
-  cellData,
-  dataKey,
-  rowData,
-  rowIndex,
-}) {
-  let { transferredSize, fromCache, fromServiceWorker } = rowData;
+const TransferredSizeColumnCell = createFactory(createClass({
+  displayName: "TransferredSizeColumnCell",
+
+  propTypes: {
+    rowData: PropTypes.object,
+  },
+
+  shouldComponentUpdate(nextProps) {
+    return !propertiesEqual(UPDATED_PROPS, this.props.rowData, nextProps.rowData);
+  },
+
+  render() {
+    let { rowData } = this.props;
+    let { transferredSize, fromCache, fromServiceWorker } = rowData;
 
-  let title;
-  let className = "requests-list-transferred subitem-label";
-  if (fromCache) {
-    title = L10N.getStr("networkMenu.sizeCached");
-    className += " theme-comment";
-  } else if (fromServiceWorker) {
-    title = L10N.getStr("networkMenu.sizeServiceWorker");
-    className += " theme-comment";
-  } else if (typeof transferredSize == "number") {
-    title = getFormattedSize(transferredSize);
-  } else if (transferredSize === null) {
-    title = L10N.getStr("networkMenu.sizeUnavailable");
+    let title;
+    let className = "requests-list-transferred subitem-label";
+    if (fromCache) {
+      title = L10N.getStr("networkMenu.sizeCached");
+      className += " theme-comment";
+    } else if (fromServiceWorker) {
+      title = L10N.getStr("networkMenu.sizeServiceWorker");
+      className += " theme-comment";
+    } else if (typeof transferredSize == "number") {
+      title = getFormattedSize(transferredSize);
+    } else if (transferredSize === null) {
+      title = L10N.getStr("networkMenu.sizeUnavailable");
+    }
+
+    return div({ className, title }, title);
   }
-
-  return div({ className, title }, title);
-}
+}));
 
 module.exports = RequestListColumnTransferred;
--- a/devtools/client/netmonitor/components/request-list-column-type.js
+++ b/devtools/client/netmonitor/components/request-list-column-type.js
@@ -1,41 +1,46 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
+  createClass,
   createFactory,
   DOM,
+  PropTypes,
 } = require("devtools/client/shared/vendor/react");
-const { getAbbreviatedMimeType } = require("../utils/request-utils");
+const {
+  getAbbreviatedMimeType,
+  propertiesEqual,
+} = require("../utils/request-utils");
 
 // Components
 const Column = createFactory(require("devtools/client/shared/vendor/react-virtualized").Column);
 const RequestListColumnHeader = createFactory(require("./request-list-column-header"));
 
 const { div } = DOM;
-
 const CONTENT_MIME_TYPE_ABBREVIATIONS = {
   "ecmascript": "js",
   "javascript": "js",
   "x-javascript": "js"
 };
+const UPDATED_PROPS = ["mimeType"];
 
 /**
  * Request list type column component
  * Describes the header and cell contents of a table column
  */
 function RequestListColumnType() {
   let name = "type";
   return (
     Column({
-      cellRenderer,
+      cellRenderer: TypeColumnCell,
       className: "requests-list-subitem",
       dataKey: name,
       headerClassName: "requests-list-header",
       headerRenderer: (props) => RequestListColumnHeader(props),
       label: name,
       width: 72,
       maxWidth: 72,
       minWidth: 45,
@@ -44,33 +49,41 @@ function RequestListColumnType() {
         justifyContent: "center",
       },
     })
   );
 }
 
 RequestListColumnType.displayName = "RequestListColumnType";
 
-function cellRenderer({
-  cellData,
-  dataKey,
-  rowData,
-  rowIndex,
-}) {
-  let { mimeType } = rowData;
-  let abbrevType;
+const TypeColumnCell = createFactory(createClass({
+  displayName: "TypeColumnCell",
+
+  propTypes: {
+    rowData: PropTypes.object,
+  },
+
+  shouldComponentUpdate(nextProps) {
+    return !propertiesEqual(UPDATED_PROPS, this.props.rowData, nextProps.rowData);
+  },
+
+  render() {
+    let { rowData } = this.props;
+    let { mimeType } = rowData;
+    let abbrevType;
 
-  if (mimeType) {
-    abbrevType = getAbbreviatedMimeType(mimeType);
-    abbrevType = CONTENT_MIME_TYPE_ABBREVIATIONS[abbrevType] || abbrevType;
-  }
+    if (mimeType) {
+      abbrevType = getAbbreviatedMimeType(mimeType);
+      abbrevType = CONTENT_MIME_TYPE_ABBREVIATIONS[abbrevType] || abbrevType;
+    }
 
-  return (
-    div({
-      className: "requests-list-type subitem-label",
-      title: mimeType,
-    },
-      abbrevType
-    )
-  );
-}
+    return (
+      div({
+        className: "requests-list-type subitem-label",
+        title: mimeType,
+      },
+        abbrevType
+      )
+    );
+  }
+}));
 
 module.exports = RequestListColumnType;
--- a/devtools/client/netmonitor/components/request-list-column-waterfall.js
+++ b/devtools/client/netmonitor/components/request-list-column-waterfall.js
@@ -1,66 +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/. */
 
 "use strict";
 
 const {
+  createClass,
   createFactory,
   DOM,
+  PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../utils/l10n");
+const { propertiesEqual } = require("../utils/request-utils");
 
 // Components
 const Column = createFactory(require("devtools/client/shared/vendor/react-virtualized").Column);
 const RequestListColumnHeader = createFactory(require("./request-list-column-header"));
 
 const { div } = DOM;
+const UPDATED_PROPS = [
+  "eventTimings",
+  "fromCache",
+  "fromServiceWorker",
+  "totalTime",
+];
 
 /**
  * Request list waterfall column component
  * Describes the header and cell contents of a table column
  */
 function RequestListColumnWaterfall(columnData) {
   let name = "waterfall";
+  let { firstRequestStartedMillis } = columnData;
   return (
     Column({
-      cellRenderer: (props) => cellRenderer(props, columnData.firstRequestStartedMillis),
+      cellRenderer: (props) =>
+        WaterfallColumnCell(Object.assign(props, { firstRequestStartedMillis })),
       className: "requests-list-subitem",
       columnData,
       dataKey: name,
       flexGrow: 1,
       headerClassName: "requests-list-header",
       headerRenderer: (props) => RequestListColumnHeader(props),
       label: name,
       width: 300,
     })
   );
 }
 
 RequestListColumnWaterfall.displayName = "RequestListColumnWaterfall";
 
-function cellRenderer({
-  cellData,
-  dataKey,
-  rowData,
-  rowIndex,
-}, firstRequestStartedMillis) {
-  return (
-    div({
-      className: "requests-list-timings",
-      style: {
-        paddingInlineStart: `${rowData.startedMillis - firstRequestStartedMillis}px`,
+const WaterfallColumnCell = createFactory(createClass({
+  displayName: "WaterfallColumnCell",
+
+  propTypes: {
+    rowData: PropTypes.object,
+    firstRequestStartedMillis: PropTypes.number,
+  },
+
+  shouldComponentUpdate(nextProps) {
+    return !propertiesEqual(UPDATED_PROPS, this.props.rowData, nextProps.rowData);
+  },
+
+  render() {
+    let { rowData, firstRequestStartedMillis } = this.props;
+    return (
+      div({
+        className: "requests-list-timings",
+        style: {
+          paddingInlineStart: `${rowData.startedMillis - firstRequestStartedMillis}px`,
+        },
       },
-    },
-      timingBoxes(rowData),
-    )
-  );
-}
+        timingBoxes(rowData),
+      )
+    );
+  }
+}));
 
 // List of properties of the timing info we want to create boxes for
 const TIMING_KEYS = ["blocked", "dns", "connect", "send", "wait", "receive"];
 
 function timingBoxes(item) {
   const { eventTimings, totalTime, fromCache, fromServiceWorker } = item;
   let boxes = [];
 
--- a/devtools/client/netmonitor/utils/request-utils.js
+++ b/devtools/client/netmonitor/utils/request-utils.js
@@ -217,23 +217,31 @@ function parseQueryString(query) {
     let param = e.split("=");
     return {
       name: param[0] ? decodeUnicodeUrl(param[0]) : "",
       value: param[1] ? decodeUnicodeUrl(param[1]) : "",
     };
   });
 }
 
+/**
+ * Compare two objects on a subset of their properties
+ */
+function propertiesEqual(props, item1, item2) {
+  return item1 === item2 || props.every(p => item1[p] === item2[p]);
+}
+
 module.exports = {
   getFormDataSections,
   fetchHeaders,
   formDataURI,
   writeHeaderText,
   decodeUnicodeUrl,
   getAbbreviatedMimeType,
   getUrlBaseName,
   getUrlQuery,
   getUrlBaseNameWithQuery,
   getUrlHostName,
   getUrlHost,
   getUrlDetails,
   parseQueryString,
+  propertiesEqual,
 };