Bug 1572692 - Properly crop results in the Search panel; r=nchevobbe
authorJan Odvarko <odvarko@gmail.com>
Fri, 16 Aug 2019 09:04:11 +0000
changeset 488466 9f97c09349098de632bc6ac28c1446c2c06c7077
parent 488465 83efa3e53a72a21fe1048f20a3a5054681fc2f69
child 488467 47f90b1619aebaa588c039fcde7b6ca46debeb25
push id92763
push userjodvarko@mozilla.com
push dateFri, 16 Aug 2019 09:04:44 +0000
treeherderautoland@9f97c0934909 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnchevobbe
bugs1572692
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 1572692 - Properly crop results in the Search panel; r=nchevobbe Differential Revision: https://phabricator.services.mozilla.com/D41940
devtools/client/netmonitor/src/assets/styles/RequestList.css
devtools/client/netmonitor/src/assets/styles/search.css
devtools/client/netmonitor/src/components/search/SearchPanel.js
devtools/client/netmonitor/src/components/search/search-provider.js
devtools/client/shared/components/tree/LabelCell.js
devtools/client/shared/components/tree/TreeRow.js
--- a/devtools/client/netmonitor/src/assets/styles/RequestList.css
+++ b/devtools/client/netmonitor/src/assets/styles/RequestList.css
@@ -49,16 +49,17 @@
   height: 100%;
   overflow: hidden;
   color: var(--table-text-color);
 }
 
 .requests-list-scroll {
   overflow-x: hidden;
   overflow-y: auto;
+  width: 100% !important;
 }
 
 .requests-list-table,
 .ws-frames-list-table {
   /* Reset default browser style of <table> */
   border-spacing: 0;
   width: 100%;
   /* The layout must be fixed for resizing of columns to work.
--- a/devtools/client/netmonitor/src/assets/styles/search.css
+++ b/devtools/client/netmonitor/src/assets/styles/search.css
@@ -1,68 +1,56 @@
 /* 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/. */
 
-.network-monitor .monitor-panel .request-list-container .requests-list-scroll {
-    width: 100% !important;
+.search-panel {
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
 }
 
-.search-panel {
-    display: flex;
-    flex-direction: column;
-    overflow: hidden;
+.search-panel .search-panel-content {
+  width: 100%;
+  overflow: auto;
 }
 
-.search-panel-content {
-    width: 100%;
-    overflow: auto;
+.search-panel .treeTable {
+  width: 100%;
+  color: var(--table-text-color);
 }
 
 /* Custom tree styles for the Search results panel*/
 .search-panel .treeTable .treeLabelCell::after {
-    content: "";
+  content: "";
 }
 
 /* Color for resource label */
 .search-panel .resourceCell {
-    color: var(--theme-text-color-inactive);
-}
-
-/* Color for search result label */
-.search-panel .resultCell {
-    color: var(--table-text-color);
-    text-overflow: ellipsis;
+  color: var(--theme-text-color-inactive);
 }
 
-/* Color for search result label */
-.search-panel .treeLabel.resultLabel {
-    color: var(--theme-text-color-inactive);
-}
-
-/* Break the column layout and make the search result output more compact */
-.search-panel .treeTable tr {
-    display: block;
-}
-
-.search-panel .treeTable {
-    width: 100%;
+.search-panel .treeTable .treeLabelCell {
+  text-overflow: ellipsis;
+  max-width: 0;
+  overflow: hidden;
+  white-space: nowrap;
 }
 
 #devtools-network-search-close::before {
-    background-image: url("chrome://devtools/skin/images/close.svg");
+  background-image: url("chrome://devtools/skin/images/close.svg");
 }
 
 #devtools-network-search-close > button {
-    margin: 0 !important;
-    border-radius: 0 !important;
-    position: relative;
-    min-width: 26px;
+  margin: 0 !important;
+  border-radius: 0 !important;
+  position: relative;
+  min-width: 26px;
 }
 
 /* Color for query matches */
 .search-panel .resultCell .query-match {
-    background-color: var(--theme-selection-background);
-    color: white;
-    padding: 1px 4px;
-    margin: 0 2px 0 2px;
-    border-radius: 2px;
+  background-color: var(--theme-selection-background);
+  color: white;
+  padding: 1px 4px;
+  margin: 0 2px 0 2px;
+  border-radius: 2px;
 }
--- a/devtools/client/netmonitor/src/components/search/SearchPanel.js
+++ b/devtools/client/netmonitor/src/components/search/SearchPanel.js
@@ -13,19 +13,28 @@ const dom = require("devtools/client/sha
 const { div, span } = dom;
 const Actions = require("devtools/client/netmonitor/src/actions/index");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const {
   connect,
 } = require("devtools/client/shared/redux/visibility-handler-connect");
 const TreeViewClass = require("devtools/client/shared/components/tree/TreeView");
 const TreeView = createFactory(TreeViewClass);
+const LabelCell = createFactory(
+  require("devtools/client/shared/components/tree/LabelCell")
+);
 const { SearchProvider } = require("./search-provider");
 const Toolbar = createFactory(require("./Toolbar"));
 
+// There are two levels in the search panel tree hierarchy:
+// 0: Resource - represents the source request object
+// 1: Search Result - represents a match coming from the parent resource
+const RESOURCE_LEVEL = 0;
+const SEARCH_RESULT_LEVEL = 1;
+
 /**
  * This component is responsible for rendering all search results
  * coming from the current search.
  */
 class SearchPanel extends Component {
   static get propTypes() {
     return {
       clearSearchResults: PropTypes.func.isRequired,
@@ -36,64 +45,90 @@ class SearchPanel extends Component {
       addSearchQuery: PropTypes.func.isRequired,
       query: PropTypes.string.isRequired,
       results: PropTypes.array,
     };
   }
 
   constructor(props) {
     super(props);
+
     this.searchboxRef = createRef();
     this.renderValue = this.renderValue.bind(this);
+    this.renderLabel = this.renderLabel.bind(this);
+
+    this.provider = SearchProvider;
+  }
+
+  /**
+   * Custom TreeView label rendering. The search result
+   * value isn't rendered in separate column, but in the
+   * same column as the label (to save space).
+   */
+  renderLabel(props) {
+    const member = props.member;
+    const level = member.level || 0;
+    const className = level == RESOURCE_LEVEL ? "resourceCell" : "resultCell";
+
+    // Customize label rendering by adding a suffix/value
+    const renderSuffix = () => {
+      return dom.span(
+        {
+          className,
+        },
+        " ",
+        this.renderValue(props)
+      );
+    };
+
+    return LabelCell({
+      ...props,
+      renderSuffix,
+    });
   }
 
   renderTree() {
     const { results } = this.props;
     return TreeView({
       object: results,
-      provider: SearchProvider,
+      provider: this.provider,
       expandableStrings: false,
-      renderValue: this.renderValue,
-      columns: [
-        {
-          id: "value",
-          width: "100%",
-        },
-      ],
+      renderLabelCell: this.renderLabel,
+      columns: [],
     });
   }
 
   /**
    * Custom tree value rendering. This method is responsible for
-   * rendering highlighted query string within the search result.
+   * rendering highlighted query string within the search result
+   * result tree.
    */
   renderValue(props) {
     const member = props.member;
-    /**
-     * Handle only second level (zero based) that displays
-     * the search result. Find the query string inside the
-     * search result value (`props.object`) and render it
-     * within a span element with proper class name.
-     *
-     * level 0 = resource name
-     */
-    if (member.level === 1) {
+
+    // Handle only second level (zero based) that displays
+    // the search result. Find the query string inside the
+    // search result value (`props.object`) and render it
+    // within a span element with proper class name.
+    // level 0 = resource name
+    if (member.level === SEARCH_RESULT_LEVEL) {
       const { query } = this.props;
-      const indexStart = props.object.indexOf(query);
+      const value = member.object.value;
+      const indexStart = value.indexOf(query);
       const indexEnd = indexStart + query.length;
 
       return span(
         {},
-        span({}, props.object.substring(0, indexStart)),
+        span({}, value.substring(0, indexStart)),
         span({ className: "query-match" }, query),
-        span({}, props.object.substring(indexEnd, props.object.length))
+        span({}, value.substring(indexEnd, value.length))
       );
     }
 
-    return props.object;
+    return this.provider.getValue(member.object);
   }
 
   render() {
     const {
       openSearch,
       closeSearch,
       clearSearchResults,
       connector,
--- a/devtools/client/netmonitor/src/components/search/search-provider.js
+++ b/devtools/client/netmonitor/src/components/search/search-provider.js
@@ -53,17 +53,17 @@ const SearchProvider = {
           return this.getResponseHeaders();
       }
     }
     return ObjectProvider.getLabel(object);
   },
 
   getValue(object) {
     if (object.resource) {
-      return object.resource.url;
+      return "";
     } else if (object.type) {
       return object.value;
     }
     return ObjectProvider.getValue(object);
   },
 
   getKey(object) {
     if (object.resource) {
@@ -79,27 +79,24 @@ const SearchProvider = {
       return "resource";
     } else if (object.type) {
       return "result";
     }
     return ObjectProvider.getType(object);
   },
 
   getResourceLabel(object) {
-    const resourceLabel =
+    return (
       getFileName(object.resource.urlDetails.baseNameWithQuery) ||
-      object.resource.urlDetails.host;
-    if (resourceLabel.length > 30) {
-      return resourceLabel.substring(0, 30) + "…";
-    }
-    return resourceLabel;
+      object.resource.urlDetails.host
+    );
   },
 
   getUrlLabel(object) {
-    return object.label.substring(0, 100);
+    return object.label;
   },
 
   getResponseContent(object) {
     return object.line + "";
   },
 
   getRequestCookies() {
     return "Set-Cookie";
--- a/devtools/client/shared/components/tree/LabelCell.js
+++ b/devtools/client/shared/components/tree/LabelCell.js
@@ -16,23 +16,25 @@ define(function(require, exports, module
    */
   class LabelCell extends Component {
     // See the TreeView component for details related
     // to the 'member' object.
     static get propTypes() {
       return {
         id: PropTypes.string.isRequired,
         member: PropTypes.object.isRequired,
+        renderSuffix: PropTypes.func,
       };
     }
 
     render() {
       const id = this.props.id;
       const member = this.props.member;
       const level = member.level || 0;
+      const renderSuffix = this.props.renderSuffix;
 
       const iconClassList = ["treeIcon"];
       if (member.hasChildren && member.loading) {
         iconClassList.push("devtools-throbber");
       } else if (member.hasChildren) {
         iconClassList.push("theme-twisty");
       }
       if (member.open) {
@@ -56,16 +58,17 @@ define(function(require, exports, module
         }),
         dom.span(
           {
             className: "treeLabel " + member.type + "Label",
             "aria-labelledby": id,
             "data-level": level,
           },
           member.name
-        )
+        ),
+        renderSuffix && renderSuffix(member)
       );
     }
   }
 
   // Exports from this module
   module.exports = LabelCell;
 });
--- a/devtools/client/shared/components/tree/TreeRow.js
+++ b/devtools/client/shared/components/tree/TreeRow.js
@@ -57,18 +57,18 @@ define(function(require, exports, module
           open: PropTypes.bool.isRequired,
           path: PropTypes.string.isRequired,
           hidden: PropTypes.bool,
           selected: PropTypes.bool,
           active: PropTypes.bool,
           loading: PropTypes.bool,
         }),
         decorator: PropTypes.object,
-        renderCell: PropTypes.object,
-        renderLabelCell: PropTypes.object,
+        renderCell: PropTypes.func,
+        renderLabelCell: PropTypes.func,
         columns: PropTypes.array.isRequired,
         id: PropTypes.string.isRequired,
         provider: PropTypes.object.isRequired,
         onClick: PropTypes.func.isRequired,
         onContextMenu: PropTypes.func,
         onMouseOver: PropTypes.func,
         onMouseOut: PropTypes.func,
       };