Bug 1561631 - Limit number of displayed frames in WebSocket side panel. r=Harald,Honza
authortanhengyeow <E0032242@u.nus.edu>
Tue, 13 Aug 2019 16:33:23 +0000
changeset 487689 46e63c7f4609d709a78fc0f91e8abdc69270a635
parent 487688 5198dc0ba7b2113c08b0bab31e392b8afe93ce95
child 487690 98b86a3920c02df65940192780d9a2202a56ebcc
push id36430
push userdvarga@mozilla.com
push dateWed, 14 Aug 2019 04:09:17 +0000
treeherdermozilla-central@d3deef805f92 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersHarald, Honza
bugs1561631
milestone70.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 1561631 - Limit number of displayed frames in WebSocket side panel. r=Harald,Honza Truncate displayed frames if they are over the specified limit. Offers a way to disable this behaviour. Differential Revision: https://phabricator.services.mozilla.com/D39087
devtools/client/locales/en-US/netmonitor.properties
devtools/client/netmonitor/src/assets/styles/websockets.css
devtools/client/netmonitor/src/components/websockets/FrameListContent.js
devtools/client/preferences/devtools-client.js
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -753,16 +753,29 @@ netmonitor.search.toolbar.clear=Clear Se
 
 # LOCALIZATION NOTE (netmonitor.search.labels.responseHeaders): This is the label
 # displayed in the search results as the label for the response headers
 netmonitor.search.labels.responseHeaders=Response Header
 
 # LOCALIZATION NOTE (netmonitor.search.labels.requestHeaders): This is the label
 # displayed in the search results as the label for the request headers
 netmonitor.search.labels.requestHeaders=Request Header
+# LOCALIZATION NOTE (messagesTruncated): This is the text displayed
+# in the messages panel when the number of messages is over the
+# truncation limit.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+netmonitor.ws.truncated-messages.warning=One message has been truncated to conserve memory;#1 messages have been truncated to conserve memory
+
+# LOCALIZATION NOTE (disableMessagesTruncation): This is the text displayed
+# in the messages panel checkbox label for toggling message truncation.
+toggleMessagesTruncation=Keep all future messages
+
+# LOCALIZATION NOTE (toggleMessagesTruncation.title): This is the title used
+# to describe the checkbox used to toggle message truncation.
+toggleMessagesTruncation.title=Keep all future messages or continue showing truncated messages
 
 # LOCALIZATION NOTE (messageDataTruncated): This is the text displayed
 # to describe to describe data truncation in the messages panel.
 messageDataTruncated=Data has been truncated
 
 # LOCALIZATION NOTE (netmonitor.tab.headers): This is the label displayed
 # in the network details pane identifying the headers tab.
 netmonitor.tab.headers=Headers
--- a/devtools/client/netmonitor/src/assets/styles/websockets.css
+++ b/devtools/client/netmonitor/src/assets/styles/websockets.css
@@ -32,16 +32,17 @@
   color: var(--red-40);
 }
 
 #messages-panel .ws-frame-list-item.selected .ws-frames-list-type-icon {
   color: inherit;
 }
 
 /* Use lining numbers so that seconds and milliseconds align */
+
 #messages-panel .ws-frames-list-time {
   font-variant-numeric: tabular-nums;
 }
 
 /* Styles related to the Accordion items in the FramePayload component */
 
 #messages-panel .ws-frame-payload {
   width: 100%;
@@ -79,8 +80,54 @@
   background: var(--grey-70);
 }
 
 .truncated-data-message {
   border-bottom: 1px solid var(--theme-splitter-color);
   padding: 4px 8px;
   font-size: 12px;
 }
+
+/* Styles related to truncated messages */
+
+.theme-light #messages-panel .truncated-messages-header {
+  background: var(--grey-20);
+}
+
+.theme-dark #messages-panel .truncated-messages-header {
+  background: var(--grey-70);
+}
+
+.theme-dark #messages-panel .truncated-messages-warning-icon {
+  fill: var(--grey-40);
+}
+
+#messages-panel .truncated-messages-cell {
+  padding: 0; /* reset td default padding */
+}
+
+#messages-panel .truncated-messages-header {
+  border-bottom: 1px solid var(--theme-splitter-color);
+  padding: 2px 8px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+#messages-panel .truncated-messages-container,
+#messages-panel .truncated-messages-checkbox-label {
+  display: flex;
+  align-items: center;
+}
+
+#messages-panel .truncated-messages-warning-icon {
+  width: 12px;
+  height: 12px;
+  margin-right: 8px;
+  background-image: url(chrome://global/skin/icons/info.svg);
+  background-repeat: no-repeat;
+  -moz-context-properties: fill;
+  fill: inherit;
+}
+
+#messages-panel .truncation-checkbox {
+  margin-right: 5px;
+}
--- a/devtools/client/netmonitor/src/components/websockets/FrameListContent.js
+++ b/devtools/client/netmonitor/src/components/websockets/FrameListContent.js
@@ -4,26 +4,33 @@
 
 "use strict";
 
 const {
   Component,
   createFactory,
 } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const Services = require("Services");
 const {
   connect,
 } = require("devtools/client/shared/redux/visibility-handler-connect");
+const { PluralForm } = require("devtools/shared/plural-form");
 const { getDisplayedFrames } = require("../../selectors/index");
-
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
-const { table, tbody, div } = dom;
-
+const { table, tbody, tr, td, div, input, label } = dom;
 const { L10N } = require("../../utils/l10n");
 const FRAMES_EMPTY_TEXT = L10N.getStr("messagesEmptyText");
+const TOGGLE_MESSAGES_TRUNCATION = L10N.getStr("toggleMessagesTruncation");
+const TOGGLE_MESSAGES_TRUNCATION_TITLE = L10N.getStr(
+  "toggleMessagesTruncation.title"
+);
+const TRUNCATED_MESSAGES_WARNING = L10N.getStr(
+  "netmonitor.ws.truncated-messages.warning"
+);
 const Actions = require("../../actions/index");
 
 const { getSelectedFrame } = require("../../selectors/index");
 
 loader.lazyGetter(this, "FrameListHeader", function() {
   return createFactory(require("./FrameListHeader"));
 });
 loader.lazyGetter(this, "FrameListItem", function() {
@@ -41,16 +48,28 @@ class FrameListContent extends Component
       connector: PropTypes.object.isRequired,
       frames: PropTypes.array,
       selectedFrame: PropTypes.object,
       selectFrame: PropTypes.func.isRequired,
       columns: PropTypes.object.isRequired,
     };
   }
 
+  constructor(props) {
+    super(props);
+
+    this.framesLimit = Services.prefs.getIntPref(
+      "devtools.netmonitor.ws.displayed-frames.limit"
+    );
+    this.currentTruncatedNum = 0;
+    this.state = {
+      checked: false,
+    };
+  }
+
   onMouseDown(evt, item) {
     if (evt.button === LEFT_MOUSE_BUTTON) {
       this.props.selectFrame(item);
     }
   }
 
   render() {
     const { frames, selectedFrame, connector, columns } = this.props;
@@ -61,33 +80,106 @@ class FrameListContent extends Component
         FRAMES_EMPTY_TEXT
       );
     }
 
     const visibleColumns = Object.entries(columns)
       .filter(([name, isVisible]) => isVisible)
       .map(([name]) => name);
 
-    return table(
-      { className: "ws-frames-list-table" },
-      FrameListHeader(),
-      tbody(
-        {
-          className: "ws-frames-list-body",
-        },
-        frames.map((item, index) =>
-          FrameListItem({
-            key: "ws-frame-list-item-" + index,
-            item,
-            index,
-            isSelected: item === selectedFrame,
-            onMouseDown: evt => this.onMouseDown(evt, item),
-            connector,
-            visibleColumns,
-          })
+    let displayedFrames;
+    let MESSAGES_TRUNCATED;
+    const shouldTruncate = frames.length > this.framesLimit;
+    if (shouldTruncate) {
+      // If the checkbox is checked, we display all frames after the currentedTruncatedNum limit.
+      // If the checkbox is unchecked, we display all frames after the framesLimit.
+      this.currentTruncatedNum = this.state.checked
+        ? this.currentTruncatedNum
+        : frames.length - this.framesLimit;
+      displayedFrames = frames.slice(this.currentTruncatedNum);
+
+      MESSAGES_TRUNCATED = PluralForm.get(
+        this.currentTruncatedNum,
+        L10N.getStr("netmonitor.ws.truncated-messages.warning")
+      ).replace("#1", this.currentTruncatedNum);
+    } else {
+      displayedFrames = frames;
+    }
+
+    return div(
+      {},
+      table(
+        { className: "ws-frames-list-table" },
+        FrameListHeader(),
+        tr(
+          {
+            tabIndex: 0,
+          },
+          td(
+            {
+              className: "truncated-messages-cell",
+              colSpan: visibleColumns.length,
+            },
+            shouldTruncate &&
+              div(
+                {
+                  className: "truncated-messages-header",
+                },
+                div(
+                  {
+                    className: "truncated-messages-container",
+                  },
+                  div({
+                    className: "truncated-messages-warning-icon",
+                    title: TRUNCATED_MESSAGES_WARNING,
+                  }),
+                  div(
+                    {
+                      className: "truncated-message",
+                      title: MESSAGES_TRUNCATED,
+                    },
+                    MESSAGES_TRUNCATED
+                  )
+                ),
+                label(
+                  {
+                    className: "truncated-messages-checkbox-label",
+                    title: TOGGLE_MESSAGES_TRUNCATION_TITLE,
+                  },
+                  input({
+                    type: "checkbox",
+                    className: "truncation-checkbox",
+                    title: TOGGLE_MESSAGES_TRUNCATION_TITLE,
+                    checked: this.state.checked,
+                    onClick: () => {
+                      this.setState({
+                        checked: !this.state.checked,
+                      });
+                    },
+                  }),
+                  TOGGLE_MESSAGES_TRUNCATION
+                )
+              )
+          )
+        ),
+        tbody(
+          {
+            className: "ws-frames-list-body",
+          },
+          displayedFrames.map((item, index) =>
+            FrameListItem({
+              key: "ws-frame-list-item-" + index,
+              item,
+              index,
+              isSelected: item === selectedFrame,
+              onMouseDown: evt => this.onMouseDown(evt, item),
+              connector,
+              visibleColumns,
+            })
+          )
         )
       )
     );
   }
 }
 
 module.exports = connect(
   state => ({
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -179,16 +179,17 @@ pref("devtools.netmonitor.visibleColumns
   "[\"status\",\"method\",\"domain\",\"file\",\"cause\",\"type\",\"transferred\",\"contentSize\",\"waterfall\"]"
 );
 pref("devtools.netmonitor.columnsData",
   '[{"name":"status","minWidth":30,"width":5}, {"name":"method","minWidth":30,"width":5}, {"name":"domain","minWidth":30,"width":10}, {"name":"file","minWidth":30,"width":25}, {"name":"url","minWidth":30,"width":25}, {"name":"cause","minWidth":30,"width":10},{"name":"type","minWidth":30,"width":5},{"name":"transferred","minWidth":30,"width":10},{"name":"contentSize","minWidth":30,"width":5},{"name":"waterfall","minWidth":150,"width":25}]');
 pref("devtools.netmonitor.ws.payload-preview-height", 128);
 pref("devtools.netmonitor.ws.visibleColumns",
   '["data", "time"]'
 );
+pref("devtools.netmonitor.ws.displayed-frames.limit", 500);
 
 pref("devtools.netmonitor.response.ui.limit", 10240);
 
 // Save request/response bodies yes/no.
 pref("devtools.netmonitor.saveRequestAndResponseBodies", true);
 
 // The default Network monitor HAR export setting
 pref("devtools.netmonitor.har.defaultLogDir", "");