Skip to content

Commit

Permalink
Bug 1454312 - Display search results when searching across all networ…
Browse files Browse the repository at this point in the history
…k resources. r=Honza,nchevobbe

Differential Revision: https://phabricator.services.mozilla.com/D38164

--HG--
extra : moz-landing-system : lando
  • Loading branch information
lloan committed Aug 9, 2019
1 parent 4939b82 commit 9abd5ad
Show file tree
Hide file tree
Showing 10 changed files with 553 additions and 1 deletion.
1 change: 1 addition & 0 deletions devtools/client/jar.mn
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ devtools.jar:
content/netmonitor/index.html (netmonitor/index.html)
content/netmonitor/src/assets/styles/StatusCode.css (netmonitor/src/assets/styles/StatusCode.css)
content/netmonitor/src/assets/styles/websockets.css (netmonitor/src/assets/styles/websockets.css)
content/netmonitor/src/assets/styles/search.css (netmonitor/src/assets/styles/search.css)

# Application panel
content/application/index.html (application/index.html)
20 changes: 20 additions & 0 deletions devtools/client/locales/en-US/netmonitor.properties
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,26 @@ netmonitor.ws.time.format=%1$S.%2$S
# in the messages panel identifying the raw data.
netmonitor.ws.rawData.header=Raw Data (%S)

# LOCALIZATION NOTE (netmonitor.search.toolbar.inputPlaceholder): This is the label
# displayed in the search toolbar for the search input as the placeholder.
netmonitor.search.toolbar.inputPlaceholder=Find in resources…

# LOCALIZATION NOTE (netmonitor.search.toolbar.close): This is the label
# displayed in the search toolbar to close the search panel.
netmonitor.search.toolbar.close=Close Search Panel

# LOCALIZATION NOTE (netmonitor.search.toolbar.clear): This is the label
# displayed in the search toolbar to clear the search panel.
netmonitor.search.toolbar.clear=Clear Search Results

# 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 (netmonitor.tab.headers): This is the label displayed
# in the network details pane identifying the headers tab.
netmonitor.tab.headers=Headers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
@import "chrome://devtools/content/netmonitor/src/assets/styles/CustomRequestPanel.css";
@import "chrome://devtools/content/netmonitor/src/assets/styles/StatusCode.css";
@import "chrome://devtools/content/netmonitor/src/assets/styles/websockets.css";
@import "chrome://devtools/content/netmonitor/src/assets/styles/search.css";

/* General */

Expand Down
68 changes: 68 additions & 0 deletions devtools/client/netmonitor/src/assets/styles/search.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

.network-monitor .monitor-panel .request-list-container .requests-list-scroll {
width: 100% !important;
}

.search-panel {
display: flex;
flex-direction: column;
overflow: hidden;
}

.search-panel-content {
width: 100%;
overflow: auto;
}

/* Custom tree styles for the Search results panel*/
.search-panel .treeTable .treeLabelCell::after {
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 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%;
}

#devtools-network-search-close::before {
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;
}

/* 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;
}
63 changes: 62 additions & 1 deletion devtools/client/netmonitor/src/components/MonitorPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ loader.lazyGetter(this, "NetworkDetailsPanel", function() {
return createFactory(require("./NetworkDetailsPanel"));
});

loader.lazyGetter(this, "SearchPanel", function() {
return createFactory(require("./search/SearchPanel"));
});

// MediaQueryList object responsible for switching sidebar splitter
// between landscape and portrait mode (depending on browser window size).
const MediaQueryVert = window.matchMedia("(min-width: 700px)");
Expand Down Expand Up @@ -62,6 +66,7 @@ class MonitorPanel extends Component {
sourceMapService: PropTypes.object,
openLink: PropTypes.func,
updateRequest: PropTypes.func.isRequired,
panelOpen: PropTypes.bool.isRequired,
};
}

Expand All @@ -80,6 +85,8 @@ class MonitorPanel extends Component {
componentDidMount() {
MediaQuerySingleRow.addListener(this.onLayoutChange);
MediaQueryVert.addListener(this.onLayoutChange);
this.persistDetailsPanelSize();
this.persistSearchPanelSize();
}

componentWillReceiveProps(nextProps) {
Expand All @@ -96,7 +103,11 @@ class MonitorPanel extends Component {
componentWillUnmount() {
MediaQuerySingleRow.removeListener(this.onLayoutChange);
MediaQueryVert.removeListener(this.onLayoutChange);
this.persistDetailsPanelSize();
this.persistSearchPanelSize();
}

persistDetailsPanelSize() {
const { clientWidth, clientHeight } = findDOMNode(this.refs.endPanel) || {};

if (this.state.isVerticalSpliter && clientWidth) {
Expand All @@ -113,6 +124,23 @@ class MonitorPanel extends Component {
}
}

persistSearchPanelSize() {
const { clientWidth, clientHeight } =
findDOMNode(this.refs.searchPanel) || {};
if (clientWidth) {
Services.prefs.setIntPref(
"devtools.netmonitor.panes-search-width",
clientWidth
);
}
if (clientHeight) {
Services.prefs.setIntPref(
"devtools.netmonitor.panes-search-height",
clientHeight
);
}
}

onLayoutChange() {
this.setState({
isSingleRow: MediaQuerySingleRow.matches,
Expand All @@ -130,6 +158,32 @@ class MonitorPanel extends Component {
);
}

renderSearchPanel(connector) {
const { isEmpty } = this.props;
const initialWidth = Services.prefs.getIntPref(
"devtools.netmonitor.panes-search-width"
);
const initialHeight = Services.prefs.getIntPref(
"devtools.netmonitor.panes-search-height"
);
return SplitBox({
className: "devtools-responsive-container",
initialWidth,
initialHeight,
minSize: "50px",
maxSize: "80%",
splitterSize: 1,
startPanel: SearchPanel({
ref: "searchPanel",
connector,
}),
endPanel: RequestList({ isEmpty, connector }),
endPanelControl: false,
vert: true,
onControlledPanelResized: () => {},
});
}

render() {
const {
actions,
Expand All @@ -139,15 +193,21 @@ class MonitorPanel extends Component {
openLink,
openSplitConsole,
sourceMapService,
panelOpen,
} = this.props;

const initialWidth = Services.prefs.getIntPref(
"devtools.netmonitor.panes-network-details-width"
);

const initialHeight = Services.prefs.getIntPref(
"devtools.netmonitor.panes-network-details-height"
);

const startPanel = panelOpen
? this.renderSearchPanel(connector)
: RequestList({ isEmpty, connector });

return div(
{ className: "monitor-panel" },
Toolbar({
Expand All @@ -163,7 +223,7 @@ class MonitorPanel extends Component {
minSize: "50px",
maxSize: "80%",
splitterSize: networkDetailsOpen ? 1 : 0,
startPanel: RequestList({ isEmpty, connector }),
startPanel,
endPanel:
networkDetailsOpen &&
NetworkDetailsPanel({
Expand All @@ -185,6 +245,7 @@ module.exports = connect(
state => ({
isEmpty: state.requests.requests.size == 0,
networkDetailsOpen: state.ui.networkDetailsOpen,
panelOpen: state.search.panelOpen,
request: getSelectedRequest(state),
selectedRequestVisible: isSelectedRequestVisible(state),
}),
Expand Down
1 change: 1 addition & 0 deletions devtools/client/netmonitor/src/components/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

DIRS += [
'search',
'websockets',
]

Expand Down
136 changes: 136 additions & 0 deletions devtools/client/netmonitor/src/components/search/SearchPanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/* 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 {
Component,
createRef,
createFactory,
} = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
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 { SearchProvider } = require("./search-provider");
const Toolbar = createFactory(require("./Toolbar"));

/**
* 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,
openSearch: PropTypes.func.isRequired,
closeSearch: PropTypes.func.isRequired,
search: PropTypes.func.isRequired,
connector: PropTypes.object.isRequired,
addSearchQuery: PropTypes.func.isRequired,
query: PropTypes.string.isRequired,
results: PropTypes.array,
};
}

constructor(props) {
super(props);
this.searchboxRef = createRef();
this.renderValue = this.renderValue.bind(this);
}

renderTree() {
const { results } = this.props;
return TreeView({
object: results,
provider: SearchProvider,
expandableStrings: false,
renderValue: this.renderValue,
columns: [
{
id: "value",
width: "100%",
},
],
});
}

/**
* Custom tree value rendering. This method is responsible for
* rendering highlighted query string within the search result.
*/
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) {
const { query } = this.props;
const indexStart = props.object.indexOf(query);
const indexEnd = indexStart + query.length;

return span(
{},
span({}, props.object.substring(0, indexStart)),
span({ className: "query-match" }, query),
span({}, props.object.substring(indexEnd, props.object.length))
);
}

return props.object;
}

render() {
const {
openSearch,
closeSearch,
clearSearchResults,
connector,
addSearchQuery,
search,
} = this.props;
return div(
{ className: "search-panel", style: { width: "100%" } },
Toolbar({
searchboxRef: this.searchboxRef,
openSearch,
closeSearch,
clearSearchResults,
addSearchQuery,
search,
connector,
}),
div(
{ className: "search-panel-content", style: { width: "100%" } },
this.renderTree()
)
);
}
}

module.exports = connect(
state => ({
query: state.search.query,
results: state.search.results,
ongoingSearch: state.search.ongoingSearch,
status: state.search.status,
}),
dispatch => ({
closeSearch: () => dispatch(Actions.closeSearch()),
openSearch: () => dispatch(Actions.openSearch()),
search: () => dispatch(Actions.search()),
clearSearchResults: () => dispatch(Actions.clearSearchResults()),
addSearchQuery: query => dispatch(Actions.addSearchQuery(query)),
})
)(SearchPanel);
Loading

0 comments on commit 9abd5ad

Please sign in to comment.