--- a/devtools/client/netmonitor/src/assets/styles/netmonitor.css
+++ b/devtools/client/netmonitor/src/assets/styles/netmonitor.css
@@ -66,18 +66,21 @@ body,
}
.toolbar-labels {
overflow: hidden;
display: flex;
flex: auto;
}
+.devtools-toolbar {
+ display: flex;
+}
+
.devtools-toolbar-container {
- display: flex;
justify-content: space-between;
}
.devtools-toolbar-group {
display: flex;
flex: 0 0 auto;
flex-wrap: nowrap;
align-items: center;
@@ -87,29 +90,58 @@ body,
overflow: auto;
}
.cropped-textbox .textbox-input {
/* workaround for textbox not supporting the @crop attribute */
text-overflow: ellipsis;
}
+/* Status bar */
+
+.status-bar-label {
+ display: inline-flex;
+ align-content: stretch;
+ margin-inline-end: 10px;
+}
+
+.status-bar-label::before {
+ content: "";
+ display: inline-block;
+ margin-inline-end: 10px;
+ margin-top: 4px;
+ margin-bottom: 4px;
+ width: 1px;
+ background: var(--theme-splitter-color);
+}
+
+.status-bar-label.dom-content-loaded {
+ color: blue;
+}
+
+.status-bar-label.load {
+ color: red;
+}
+
/* Request list */
.request-list-container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
+ overflow: hidden;
}
.request-list-empty-notice {
margin: 0;
padding: 12px;
font-size: 120%;
+ flex: 1;
+ overflow: auto;
}
.notice-perf-message {
margin-top: 2px;
}
.requests-list-perf-notice-button {
min-width: 30px;
@@ -144,16 +176,17 @@ body,
height: 19px !important;
}
.requests-list-contents {
display: flex;
flex-direction: column;
overflow-x: hidden;
overflow-y: auto;
+ flex: 1;
--timings-scale: 1;
--timings-rev-scale: 1;
}
.requests-list-subitem {
display: flex;
flex: none;
box-sizing: border-box;
--- a/devtools/client/netmonitor/src/components/moz.build
+++ b/devtools/client/netmonitor/src/components/moz.build
@@ -18,12 +18,13 @@ DevToolsModules(
'request-list-empty-notice.js',
'request-list-header.js',
'request-list-item.js',
'request-list.js',
'response-panel.js',
'security-panel.js',
'stack-trace-panel.js',
'statistics-panel.js',
+ 'status-bar.js',
'tabbox-panel.js',
'timings-panel.js',
'toolbar.js',
)
--- a/devtools/client/netmonitor/src/components/request-list-empty-notice.js
+++ b/devtools/client/netmonitor/src/components/request-list-empty-notice.js
@@ -32,17 +32,17 @@ const RequestListEmptyNotice = createCla
return div(
{
className: "request-list-empty-notice",
},
div({ className: "notice-reload-message" },
span(null, L10N.getStr("netmonitor.reloadNotice1")),
button(
{
- className: "devtools-toolbarbutton requests-list-reload-notice-button",
+ className: "devtools-button requests-list-reload-notice-button",
"data-standalone": true,
onClick: this.props.onReloadClick,
},
L10N.getStr("netmonitor.reloadNotice2")
),
span(null, L10N.getStr("netmonitor.reloadNotice3"))
),
div({ className: "notice-perf-message" },
--- a/devtools/client/netmonitor/src/components/request-list.js
+++ b/devtools/client/netmonitor/src/components/request-list.js
@@ -9,27 +9,29 @@ const {
DOM,
PropTypes,
} = require("devtools/client/shared/vendor/react");
// Components
const RequestListContent = createFactory(require("./request-list-content"));
const RequestListEmptyNotice = createFactory(require("./request-list-empty-notice"));
const RequestListHeader = createFactory(require("./request-list-header"));
+const StatusBar = createFactory(require("./status-bar"));
const { div } = DOM;
/**
* Request panel component
*/
function RequestList({ isEmpty }) {
return (
div({ className: "request-list-container" },
RequestListHeader(),
isEmpty ? RequestListEmptyNotice() : RequestListContent(),
+ StatusBar(),
)
);
}
RequestList.displayName = "RequestList";
RequestList.propTypes = {
isEmpty: PropTypes.bool.isRequired,
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/status-bar.js
@@ -0,0 +1,84 @@
+/* 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,
+ PropTypes,
+} = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { PluralForm } = require("devtools/shared/plural-form");
+
+const Actions = require("../actions/index");
+const {
+ getDisplayedRequestsSummary,
+ getDisplayedTimingMarker,
+} = require("../selectors/index");
+const {
+ getFormattedSize,
+ getFormattedTime
+} = require("../utils/format-utils");
+const { L10N } = require("../utils/l10n");
+
+// Components
+const { div, button, span } = DOM;
+
+function StatusBar({ summary, openStatistics, timingMarkers }) {
+ let { count, contentSize, transferredSize, millis } = summary;
+ let {
+ DOMContentLoaded,
+ load,
+ } = timingMarkers;
+
+ let text = (count === 0) ? L10N.getStr("networkMenu.empty") :
+ PluralForm.get(count, L10N.getStr("networkMenu.summary3"))
+ .replace("#1", count)
+ .replace("#2", getFormattedSize(contentSize))
+ .replace("#3", getFormattedSize(transferredSize))
+ .replace("#4", getFormattedTime(millis));
+
+ return (
+ div({ className: "devtools-toolbar devtools-toolbar-bottom" },
+ button({
+ className: "devtools-button requests-list-network-summary-button",
+ title: count ? text : L10N.getStr("netmonitor.toolbar.perf"),
+ onClick: openStatistics,
+ },
+ span({ className: "summary-info-icon" }),
+ span({ className: "summary-info-text" }, text),
+ ),
+
+ DOMContentLoaded > -1 &&
+ span({ className: "status-bar-label dom-content-loaded" },
+ `DOMContentLoaded: ${getFormattedTime(DOMContentLoaded)}`),
+
+ load > -1 &&
+ span({ className: "status-bar-label load" },
+ `load: ${getFormattedTime(load)}`),
+ )
+ );
+}
+
+StatusBar.displayName = "StatusBar";
+
+StatusBar.propTypes = {
+ openStatistics: PropTypes.func.isRequired,
+ summary: PropTypes.object.isRequired,
+ timingMarkers: PropTypes.object.isRequired,
+};
+
+module.exports = connect(
+ (state) => ({
+ summary: getDisplayedRequestsSummary(state),
+ timingMarkers: {
+ DOMContentLoaded:
+ getDisplayedTimingMarker(state, "firstDocumentDOMContentLoadedTimestamp"),
+ load: getDisplayedTimingMarker(state, "firstDocumentLoadTimestamp"),
+ },
+ }),
+ (dispatch) => ({
+ openStatistics: () => dispatch(Actions.openStatistics(true)),
+ }),
+)(StatusBar);
--- a/devtools/client/netmonitor/src/components/toolbar.js
+++ b/devtools/client/netmonitor/src/components/toolbar.js
@@ -6,28 +6,24 @@
const {
createClass,
createFactory,
DOM,
PropTypes,
} = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
-const { PluralForm } = require("devtools/shared/plural-form");
const Actions = require("../actions/index");
const { FILTER_SEARCH_DELAY } = require("../constants");
const {
getDisplayedRequestsSummary,
getRequestFilterTypes,
isNetworkDetailsToggleButtonDisabled,
} = require("../selectors/index");
-const {
- getFormattedSize,
- getFormattedTime
-} = require("../utils/format-utils");
+
const { L10N } = require("../utils/l10n");
// Components
const SearchBox = createFactory(require("devtools/client/shared/components/search-box"));
const { button, div, span } = DOM;
const COLLPASE_DETAILS_PANE = L10N.getStr("collapseDetailsPane");
@@ -40,61 +36,49 @@ const TOOLBAR_CLEAR = L10N.getStr("netmo
* Network monitor toolbar component
* Toolbar contains a set of useful tools to control network requests
*/
const Toolbar = createClass({
displayName: "Toolbar",
propTypes: {
clearRequests: PropTypes.func.isRequired,
- openStatistics: PropTypes.func.isRequired,
requestFilterTypes: PropTypes.array.isRequired,
setRequestFilterText: PropTypes.func.isRequired,
networkDetailsToggleDisabled: PropTypes.bool.isRequired,
networkDetailsOpen: PropTypes.bool.isRequired,
- summary: PropTypes.object.isRequired,
toggleNetworkDetails: PropTypes.func.isRequired,
toggleRequestFilterType: PropTypes.func.isRequired,
},
toggleRequestFilterType(evt) {
if (evt.type === "keydown" && (evt.key !== "" || evt.key !== "Enter")) {
return;
}
this.props.toggleRequestFilterType(evt.target.dataset.key);
},
render() {
let {
clearRequests,
- openStatistics,
requestFilterTypes,
setRequestFilterText,
networkDetailsToggleDisabled,
networkDetailsOpen,
- summary,
toggleNetworkDetails,
} = this.props;
let toggleButtonClassName = [
"network-details-panel-toggle",
"devtools-button",
];
if (!networkDetailsOpen) {
toggleButtonClassName.push("pane-collapsed");
}
- let { count, contentSize, transferredSize, millis } = summary;
- let text = (count === 0) ? L10N.getStr("networkMenu.empty") :
- PluralForm.get(count, L10N.getStr("networkMenu.summary3"))
- .replace("#1", count)
- .replace("#2", getFormattedSize(contentSize))
- .replace("#3", getFormattedSize(transferredSize))
- .replace("#4", getFormattedTime(millis));
-
let buttons = requestFilterTypes.map(([type, checked]) => {
let classList = ["devtools-button", `requests-list-filter-${type}-button`];
checked && classList.push("checked");
return (
button({
className: classList.join(" "),
key: type,
@@ -114,24 +98,16 @@ const Toolbar = createClass({
button({
className: "devtools-button devtools-clear-icon requests-list-clear-button",
title: TOOLBAR_CLEAR,
onClick: clearRequests,
}),
div({ className: "requests-list-filter-buttons" }, buttons),
),
span({ className: "devtools-toolbar-group" },
- button({
- className: "devtools-button requests-list-network-summary-button",
- title: count ? text : L10N.getStr("netmonitor.toolbar.perf"),
- onClick: openStatistics,
- },
- span({ className: "summary-info-icon" }),
- span({ className: "summary-info-text" }, text),
- ),
SearchBox({
delay: FILTER_SEARCH_DELAY,
keyShortcut: SEARCH_KEY_SHORTCUT,
placeholder: SEARCH_PLACE_HOLDER,
type: "filter",
onChange: setRequestFilterText,
}),
button({
@@ -151,14 +127,13 @@ module.exports = connect(
(state) => ({
networkDetailsToggleDisabled: isNetworkDetailsToggleButtonDisabled(state),
networkDetailsOpen: state.ui.networkDetailsOpen,
requestFilterTypes: getRequestFilterTypes(state),
summary: getDisplayedRequestsSummary(state),
}),
(dispatch) => ({
clearRequests: () => dispatch(Actions.clearRequests()),
- openStatistics: () => dispatch(Actions.openStatistics(true)),
setRequestFilterText: (text) => dispatch(Actions.setRequestFilterText(text)),
toggleRequestFilterType: (type) => dispatch(Actions.toggleRequestFilterType(type)),
toggleNetworkDetails: () => dispatch(Actions.toggleNetworkDetails()),
}),
)(Toolbar);
--- a/devtools/client/netmonitor/src/selectors/index.js
+++ b/devtools/client/netmonitor/src/selectors/index.js
@@ -1,15 +1,17 @@
/* 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 filters = require("./filters");
const requests = require("./requests");
+const timingMarkers = require("./timing-markers");
const ui = require("./ui");
Object.assign(exports,
filters,
requests,
+ timingMarkers,
ui
);
--- a/devtools/client/netmonitor/src/selectors/moz.build
+++ b/devtools/client/netmonitor/src/selectors/moz.build
@@ -1,10 +1,11 @@
# 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/.
DevToolsModules(
'filters.js',
'index.js',
'requests.js',
+ 'timing-markers.js',
'ui.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/selectors/timing-markers.js
@@ -0,0 +1,13 @@
+/* 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";
+
+function getDisplayedTimingMarker(state, marker) {
+ return state.timingMarkers.get(marker) - state.requests.get("firstStartedMillis");
+}
+
+module.exports = {
+ getDisplayedTimingMarker,
+};
--- a/devtools/client/themes/toolbars.css
+++ b/devtools/client/themes/toolbars.css
@@ -34,17 +34,17 @@
--icon-filter: none;
--checked-icon-filter: none;
}
/* Toolbars */
.devtools-toolbar,
.devtools-sidebar-tabs tabs {
- -moz-appearance: none; appearance: none;
+ -moz-appearance: none;
padding: 0;
border-width: 0;
border-bottom-width: 1px;
border-style: solid;
height: 24px;
line-height: 24px;
box-sizing: border-box;
}
@@ -69,16 +69,21 @@
border: none !important; /* overrides .checkbox-label-box from checkbox.css */
}
.devtools-toolbar checkbox .checkbox-label-box .checkbox-label {
margin: 0 6px !important; /* overrides .checkbox-label from checkbox.css */
padding: 0;
}
+.devtools-toolbar-bottom {
+ border-top-width: 1px;
+ border-bottom: none;
+}
+
.devtools-separator {
margin: 0 2px;
width: 2px;
background-image: linear-gradient(transparent 15%, var(--theme-splitter-color) 15%, var(--theme-splitter-color) 85%, transparent 85%);
background-size: 1px 100%;
background-repeat: no-repeat;
background-position: 0, 1px, 2px;
}