--- a/devtools/client/netmonitor/components/moz.build
+++ b/devtools/client/netmonitor/components/moz.build
@@ -4,10 +4,11 @@
DevToolsModules(
'request-list-content.js',
'request-list-empty.js',
'request-list-header.js',
'request-list-item.js',
'request-list-tooltip.js',
'request-list.js',
+ 'statistics-panel.js',
'toolbar.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/components/statistics-panel.js
@@ -0,0 +1,229 @@
+/* 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/. */
+
+/* globals document */
+
+"use strict";
+
+const {
+ createClass,
+ DOM,
+ PropTypes,
+} = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { Chart } = require("devtools/client/shared/widgets/Chart");
+const { PluralForm } = require("devtools/shared/plural-form");
+const Actions = require("../actions/index");
+const { Filters } = require("../filter-predicates");
+const { L10N } = require("../l10n");
+const {
+ getSizeWithDecimals,
+ getTimeWithDecimals
+} = require("../utils/format-utils");
+
+const { button, div } = DOM;
+
+const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200;
+const BACK_BUTTON = L10N.getStr("netmonitor.backButton");
+const CHARTS_CACHE_ENABLED = L10N.getStr("charts.cacheEnabled");
+const CHARTS_CACHE_DISABLED = L10N.getStr("charts.cacheDisabled");
+
+/*
+ * Statistics panel component
+ * Performance analysis tool which shows you how long the browser takes to
+ * download the different parts of your site.
+ */
+const StatisticsPanel = createClass({
+ displayName: "StatisticsPanel",
+
+ propTypes: {
+ closeStatistics: PropTypes.func.isRequired,
+ enableRequestFilterTypeOnly: PropTypes.func.isRequired,
+ requests: PropTypes.object,
+ },
+
+ componentDidUpdate(prevProps) {
+ const { requests } = this.props;
+ let ready = requests && requests.every((req) =>
+ req.contentSize !== undefined && req.mimeType && req.responseHeaders &&
+ req.status !== undefined && req.totalTime !== undefined
+ );
+
+ this.createChart({
+ id: "primedCacheChart",
+ title: CHARTS_CACHE_ENABLED,
+ data: ready ? this.sanitizeChartDataSource(requests, false) : null,
+ });
+
+ this.createChart({
+ id: "emptyCacheChart",
+ title: CHARTS_CACHE_DISABLED,
+ data: ready ? this.sanitizeChartDataSource(requests, true) : null,
+ });
+ },
+
+ createChart({ id, title, data }) {
+ // Create a new chart.
+ let chart = Chart.PieTable(document, {
+ diameter: NETWORK_ANALYSIS_PIE_CHART_DIAMETER,
+ title,
+ data,
+ strings: {
+ size: (value) =>
+ L10N.getFormatStr("charts.sizeKB", getSizeWithDecimals(value / 1024)),
+ time: (value) =>
+ L10N.getFormatStr("charts.totalS", getTimeWithDecimals(value / 1000)),
+ },
+ totals: {
+ cached: (total) => L10N.getFormatStr("charts.totalCached", total),
+ count: (total) => L10N.getFormatStr("charts.totalCount", total),
+ size: (total) =>
+ L10N.getFormatStr("charts.totalSize", getSizeWithDecimals(total / 1024)),
+ time: (total) => {
+ let seconds = total / 1000;
+ let string = getTimeWithDecimals(seconds);
+ return PluralForm.get(seconds,
+ L10N.getStr("charts.totalSeconds")).replace("#1", string);
+ },
+ },
+ sorted: true,
+ });
+
+ chart.on("click", (_, { label }) => {
+ // Reset FilterButtons and enable one filter exclusively
+ this.props.closeStatistics();
+ this.props.enableRequestFilterTypeOnly(label);
+ });
+
+ let container = this.refs[id];
+
+ // Nuke all existing charts of the specified type.
+ while (container.hasChildNodes()) {
+ container.firstChild.remove();
+ }
+
+ container.appendChild(chart.node);
+ },
+
+ sanitizeChartDataSource(requests, emptyCache) {
+ let data = [
+ "html", "css", "js", "xhr", "fonts", "images", "media", "flash", "ws", "other"
+ ].map((type) => ({ cached: 0, count: 0, label: type, size: 0, time: 0 }));
+
+ for (let request of requests) {
+ let type;
+
+ if (Filters.html(request)) {
+ // "html"
+ type = 0;
+ } else if (Filters.css(request)) {
+ // "css"
+ type = 1;
+ } else if (Filters.js(request)) {
+ // "js"
+ type = 2;
+ } else if (Filters.fonts(request)) {
+ // "fonts"
+ type = 4;
+ } else if (Filters.images(request)) {
+ // "images"
+ type = 5;
+ } else if (Filters.media(request)) {
+ // "media"
+ type = 6;
+ } else if (Filters.flash(request)) {
+ // "flash"
+ type = 7;
+ } else if (Filters.ws(request)) {
+ // "ws"
+ type = 8;
+ } else if (Filters.xhr(request)) {
+ // Verify XHR last, to categorize other mime types in their own blobs.
+ // "xhr"
+ type = 3;
+ } else {
+ // "other"
+ type = 9;
+ }
+
+ if (emptyCache || !this.responseIsFresh(request)) {
+ data[type].time += request.totalTime || 0;
+ data[type].size += request.contentSize || 0;
+ } else {
+ data[type].cached++;
+ }
+ data[type].count++;
+ }
+
+ return data.filter(e => e.count > 0);
+ },
+
+ /**
+ * Checks if the "Expiration Calculations" defined in section 13.2.4 of the
+ * "HTTP/1.1: Caching in HTTP" spec holds true for a collection of headers.
+ *
+ * @param object
+ * An object containing the { responseHeaders, status } properties.
+ * @return boolean
+ * True if the response is fresh and loaded from cache.
+ */
+ responseIsFresh({ responseHeaders, status }) {
+ // Check for a "304 Not Modified" status and response headers availability.
+ if (status != 304 || !responseHeaders) {
+ return false;
+ }
+
+ let list = responseHeaders.headers;
+ let cacheControl = list.find(e => e.name.toLowerCase() === "cache-control");
+ let expires = list.find(e => e.name.toLowerCase() === "expires");
+
+ // Check the "Cache-Control" header for a maximum age value.
+ if (cacheControl) {
+ let maxAgeMatch =
+ cacheControl.value.match(/s-maxage\s*=\s*(\d+)/) ||
+ cacheControl.value.match(/max-age\s*=\s*(\d+)/);
+
+ if (maxAgeMatch && maxAgeMatch.pop() > 0) {
+ return true;
+ }
+ }
+
+ // Check the "Expires" header for a valid date.
+ if (expires && Date.parse(expires.value)) {
+ return true;
+ }
+
+ return false;
+ },
+
+ render() {
+ const { closeStatistics } = this.props;
+ return (
+ div({ className: "statistics-panel" },
+ button({
+ className: "back-button devtools-toolbarbutton",
+ "data-text-only": "true",
+ title: BACK_BUTTON,
+ onClick: closeStatistics,
+ }, BACK_BUTTON),
+ div({ className: "charts-container devtools-responsive-container" },
+ div({ ref: "primedCacheChart", className: "charts primed-cache-chart" }),
+ div({ className: "splitter devtools-side-splitter" }),
+ div({ ref: "emptyCacheChart", className: "charts empty-cache-chart" }),
+ ),
+ )
+ );
+ }
+});
+
+module.exports = connect(
+ (state) => ({
+ requests: state.requests.requests.valueSeq(),
+ }),
+ (dispatch) => ({
+ closeStatistics: () => dispatch(Actions.openStatistics(false)),
+ enableRequestFilterTypeOnly: (label) =>
+ dispatch(Actions.enableRequestFilterTypeOnly(label)),
+ })
+)(StatisticsPanel);
--- a/devtools/client/netmonitor/moz.build
+++ b/devtools/client/netmonitor/moz.build
@@ -23,17 +23,16 @@ DevToolsModules(
'netmonitor-view.js',
'panel.js',
'prefs.js',
'request-list-context-menu.js',
'request-utils.js',
'requests-menu-view.js',
'sidebar-view.js',
'sort-predicates.js',
- 'statistics-view.js',
'store.js',
'waterfall-background.js',
)
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Netmonitor')
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -1,45 +1,33 @@
/* 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/. */
-/* eslint-disable mozilla/reject-some-requires */
/* globals $, gStore, NetMonitorController, dumpn */
"use strict";
-const { testing: isTesting } = require("devtools/shared/flags");
const { Task } = require("devtools/shared/task");
const { ViewHelpers } = require("devtools/client/shared/widgets/view-helpers");
const { RequestsMenuView } = require("./requests-menu-view");
const { CustomRequestView } = require("./custom-request-view");
const { SidebarView } = require("./sidebar-view");
-const { StatisticsView } = require("./statistics-view");
const { ACTIVITY_TYPE } = require("./constants");
const { Prefs } = require("./prefs");
const { createFactory } = require("devtools/client/shared/vendor/react");
const Actions = require("./actions/index");
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
// Components
const DetailsPanel = createFactory(require("./shared/components/details-panel"));
+const StatisticsPanel = createFactory(require("./components/statistics-panel"));
const Toolbar = createFactory(require("./components/toolbar"));
-// ms
-const WDA_DEFAULT_VERIFY_INTERVAL = 50;
-
-// Use longer timeout during testing as the tests need this process to succeed
-// and two seconds is quite short on slow debug builds. The timeout here should
-// be at least equal to the general mochitest timeout of 45 seconds so that this
-// never gets hit during testing.
-// ms
-const WDA_DEFAULT_GIVE_UP_TIMEOUT = isTesting ? 45000 : 2000;
-
/**
* Object defining the network monitor view components.
*/
var NetMonitorView = {
/**
* Initializes the network monitor view.
*/
initialize: function () {
@@ -47,26 +35,32 @@ var NetMonitorView = {
this.detailsPanel = $("#react-details-panel-hook");
ReactDOM.render(Provider(
{ store: gStore },
DetailsPanel({ toolbox: NetMonitorController._toolbox }),
), this.detailsPanel);
+ this.statisticsPanel = $("#statistics-panel");
+
+ ReactDOM.render(Provider(
+ { store: gStore },
+ StatisticsPanel(),
+ ), this.statisticsPanel);
+
this.toolbar = $("#react-toolbar-hook");
ReactDOM.render(Provider(
{ store: gStore },
Toolbar(),
), this.toolbar);
this.RequestsMenu.initialize(gStore);
this.CustomRequest.initialize();
- this.Statistics.initialize(gStore);
// Store watcher here is for observing the statisticsOpen state change.
// It should be removed once we migrate to react and apply react/redex binding.
this.unsubscribeStore = gStore.subscribe(storeWatcher(
false,
() => gStore.getState().ui.statisticsOpen,
this.toggleFrontendMode.bind(this)
));
@@ -74,18 +68,18 @@ var NetMonitorView = {
/**
* Destroys the network monitor view.
*/
destroy: function () {
this._isDestroyed = true;
this.RequestsMenu.destroy();
this.CustomRequest.destroy();
- this.Statistics.destroy();
ReactDOM.unmountComponentAtNode(this.detailsPanel);
+ ReactDOM.unmountComponentAtNode(this.statisticsPanel);
ReactDOM.unmountComponentAtNode(this.toolbar);
this.unsubscribeStore();
this._destroyPanes();
},
/**
* Initializes the UI for all the displayed panes.
@@ -144,124 +138,50 @@ var NetMonitorView = {
gStore.dispatch(Actions.openSidebar(false));
}
if (tabIndex !== undefined) {
$("#event-details-pane").selectedIndex = tabIndex;
}
},
- /**
- * Gets the current mode for this tool.
- * @return string (e.g, "network-inspector-view" or "network-statistics-view")
- */
get currentFrontendMode() {
// The getter may be called from a timeout after the panel is destroyed.
if (!this._body.selectedPanel) {
return null;
}
return this._body.selectedPanel.id;
},
- /**
- * Toggles between the frontend view modes ("Inspector" vs. "Statistics").
- */
toggleFrontendMode: function () {
if (gStore.getState().ui.statisticsOpen) {
this.showNetworkStatisticsView();
} else {
this.showNetworkInspectorView();
}
},
- /**
- * Switches to the "Inspector" frontend view mode.
- */
showNetworkInspectorView: function () {
- this._body.selectedPanel = $("#network-inspector-view");
+ this._body.selectedPanel = $("#inspector-panel");
},
- /**
- * Switches to the "Statistics" frontend view mode.
- */
showNetworkStatisticsView: function () {
- this._body.selectedPanel = $("#network-statistics-view");
-
- let controller = NetMonitorController;
- let requestsView = this.RequestsMenu;
- let statisticsView = this.Statistics;
-
- Task.spawn(function* () {
- statisticsView.displayPlaceholderCharts();
- yield controller.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
-
- try {
- // • The response headers and status code are required for determining
- // whether a response is "fresh" (cacheable).
- // • The response content size and request total time are necessary for
- // populating the statistics view.
- // • The response mime type is used for categorization.
- yield whenDataAvailable(requestsView.store, [
- "responseHeaders", "status", "contentSize", "mimeType", "totalTime"
- ]);
- } catch (ex) {
- // Timed out while waiting for data. Continue with what we have.
- console.error(ex);
- }
-
- const requests = requestsView.store.getState().requests.requests.valueSeq();
- statisticsView.createPrimedCacheChart(requests);
- statisticsView.createEmptyCacheChart(requests);
- });
+ this._body.selectedPanel = $("#statistics-panel");
+ NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
},
reloadPage: function () {
NetMonitorController.triggerActivity(
ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT);
},
_body: null,
_detailsPane: null,
};
-/**
- * Makes sure certain properties are available on all objects in a data store.
- *
- * @param Store dataStore
- * A Redux store for which to check the availability of properties.
- * @param array mandatoryFields
- * A list of strings representing properties of objects in dataStore.
- * @return object
- * A promise resolved when all objects in dataStore contain the
- * properties defined in mandatoryFields.
- */
-function whenDataAvailable(dataStore, mandatoryFields) {
- return new Promise((resolve, reject) => {
- let interval = setInterval(() => {
- const { requests } = dataStore.getState().requests;
- const allFieldsPresent = !requests.isEmpty() && requests.every(
- item => mandatoryFields.every(
- field => item.get(field) !== undefined
- )
- );
-
- if (allFieldsPresent) {
- clearInterval(interval);
- clearTimeout(timer);
- resolve();
- }
- }, WDA_DEFAULT_VERIFY_INTERVAL);
-
- let timer = setTimeout(() => {
- clearInterval(interval);
- reject(new Error("Timed out while waiting for data"));
- }, WDA_DEFAULT_GIVE_UP_TIMEOUT);
- });
-}
-
// A smart store watcher to notify store changes as necessary
function storeWatcher(initialValue, reduceValue, onChange) {
let currentValue = initialValue;
return () => {
const newValue = reduceValue();
if (newValue !== currentValue) {
onChange();
@@ -271,11 +191,10 @@ function storeWatcher(initialValue, redu
}
/**
* Preliminary setup for the NetMonitorView object.
*/
NetMonitorView.Sidebar = new SidebarView();
NetMonitorView.RequestsMenu = new RequestsMenuView();
NetMonitorView.CustomRequest = new CustomRequestView();
-NetMonitorView.Statistics = new StatisticsView();
exports.NetMonitorView = NetMonitorView;
--- a/devtools/client/netmonitor/netmonitor.xul
+++ b/devtools/client/netmonitor/netmonitor.xul
@@ -14,17 +14,17 @@
src="chrome://devtools/content/shared/theme-switching.js"/>
<script type="text/javascript" src="netmonitor.js"/>
<deck id="body"
class="theme-sidebar"
flex="1"
data-localization-bundle="devtools/client/locales/netmonitor.properties">
- <vbox id="network-inspector-view" flex="1">
+ <vbox id="inspector-panel" flex="1">
<html:div xmlns="http://www.w3.org/1999/xhtml"
id="react-toolbar-hook"/>
<hbox id="network-table-and-sidebar"
class="devtools-responsive-container"
flex="1">
<html:div xmlns="http://www.w3.org/1999/xhtml"
id="network-table"
class="devtools-main-content">
@@ -96,25 +96,14 @@
</vbox>
<html:div xmlns="http://www.w3.org/1999/xhtml"
id="react-details-panel-hook"/>
</deck>
</hbox>
</vbox>
- <html:div id="network-statistics-view">
- <html:div id="network-statistics-toolbar"
- class="devtools-toolbar">
- <html:div xmlns="http://www.w3.org/1999/xhtml"
- id="react-statistics-back-hook"/>
- </html:div>
- <html:div id="network-statistics-charts"
- class="devtools-responsive-container">
- <html:div id="primed-cache-chart"/>
- <html:div id="network-statistics-view-splitter"
- class="split-box devtools-side-splitter"/>
- <html:div id="empty-cache-chart"/>
- </html:div>
+ <html:div xmlns="http://www.w3.org/1999/xhtml"
+ id="statistics-panel">
</html:div>
</deck>
</window>
deleted file mode 100644
--- a/devtools/client/netmonitor/statistics-view.js
+++ /dev/null
@@ -1,288 +0,0 @@
-/* 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/. */
-
-/* eslint-disable mozilla/reject-some-requires */
-/* globals $, window, document */
-
-"use strict";
-
-const { PluralForm } = require("devtools/shared/plural-form");
-const { Filters } = require("./filter-predicates");
-const { L10N } = require("./l10n");
-const { EVENTS } = require("./events");
-const { DOM } = require("devtools/client/shared/vendor/react");
-const { button } = DOM;
-const ReactDOM = require("devtools/client/shared/vendor/react-dom");
-const Actions = require("./actions/index");
-const { Chart } = require("devtools/client/shared/widgets/Chart");
-const {
- getSizeWithDecimals,
- getTimeWithDecimals
-} = require("./utils/format-utils");
-
-// px
-const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200;
-
-/**
- * Functions handling the performance statistics view.
- */
-function StatisticsView() {
-}
-
-StatisticsView.prototype = {
- /**
- * Initialization function, called when the statistics view is started.
- */
- initialize: function (store) {
- this.store = store;
- this.Chart = Chart;
- this._backButton = $("#react-statistics-back-hook");
-
- let backStr = L10N.getStr("netmonitor.backButton");
- ReactDOM.render(button({
- id: "network-statistics-back-button",
- className: "devtools-toolbarbutton",
- "data-text-only": "true",
- title: backStr,
- onClick: () => {
- this.store.dispatch(Actions.openStatistics(false));
- },
- }, backStr), this._backButton);
- },
-
- /**
- * Destruction function, called when the statistics view is closed.
- */
- destroy: function () {
- ReactDOM.unmountComponentAtNode(this._backButton);
- },
-
- /**
- * Initializes and displays empty charts in this container.
- */
- displayPlaceholderCharts: function () {
- this._createChart({
- id: "#primed-cache-chart",
- title: "charts.cacheEnabled"
- });
- this._createChart({
- id: "#empty-cache-chart",
- title: "charts.cacheDisabled"
- });
- window.emit(EVENTS.PLACEHOLDER_CHARTS_DISPLAYED);
- },
-
- /**
- * Populates and displays the primed cache chart in this container.
- *
- * @param array items
- * @see this._sanitizeChartDataSource
- */
- createPrimedCacheChart: function (items) {
- this._createChart({
- id: "#primed-cache-chart",
- title: "charts.cacheEnabled",
- data: this._sanitizeChartDataSource(items),
- strings: this._commonChartStrings,
- totals: this._commonChartTotals,
- sorted: true
- });
- window.emit(EVENTS.PRIMED_CACHE_CHART_DISPLAYED);
- },
-
- /**
- * Populates and displays the empty cache chart in this container.
- *
- * @param array items
- * @see this._sanitizeChartDataSource
- */
- createEmptyCacheChart: function (items) {
- this._createChart({
- id: "#empty-cache-chart",
- title: "charts.cacheDisabled",
- data: this._sanitizeChartDataSource(items, true),
- strings: this._commonChartStrings,
- totals: this._commonChartTotals,
- sorted: true
- });
- window.emit(EVENTS.EMPTY_CACHE_CHART_DISPLAYED);
- },
-
- /**
- * Common stringifier predicates used for items and totals in both the
- * "primed" and "empty" cache charts.
- */
- _commonChartStrings: {
- size: value => {
- let string = getSizeWithDecimals(value / 1024);
- return L10N.getFormatStr("charts.sizeKB", string);
- },
- time: value => {
- let string = getTimeWithDecimals(value / 1000);
- return L10N.getFormatStr("charts.totalS", string);
- }
- },
- _commonChartTotals: {
- size: total => {
- let string = getSizeWithDecimals(total / 1024);
- return L10N.getFormatStr("charts.totalSize", string);
- },
- time: total => {
- let seconds = total / 1000;
- let string = getTimeWithDecimals(seconds);
- return PluralForm.get(seconds,
- L10N.getStr("charts.totalSeconds")).replace("#1", string);
- },
- cached: total => {
- return L10N.getFormatStr("charts.totalCached", total);
- },
- count: total => {
- return L10N.getFormatStr("charts.totalCount", total);
- }
- },
-
- /**
- * Adds a specific chart to this container.
- *
- * @param object
- * An object containing all or some the following properties:
- * - id: either "#primed-cache-chart" or "#empty-cache-chart"
- * - title/data/strings/totals/sorted: @see Chart.js for details
- */
- _createChart: function ({ id, title, data, strings, totals, sorted }) {
- let container = $(id);
-
- // Nuke all existing charts of the specified type.
- while (container.hasChildNodes()) {
- container.firstChild.remove();
- }
-
- // Create a new chart.
- let chart = this.Chart.PieTable(document, {
- diameter: NETWORK_ANALYSIS_PIE_CHART_DIAMETER,
- title: L10N.getStr(title),
- data: data,
- strings: strings,
- totals: totals,
- sorted: sorted
- });
-
- chart.on("click", (_, item) => {
- // Reset FilterButtons and enable one filter exclusively
- this.store.dispatch(Actions.enableRequestFilterTypeOnly(item.label));
- this.store.dispatch(Actions.openStatistics(false));
- });
-
- container.appendChild(chart.node);
- },
-
- /**
- * Sanitizes the data source used for creating charts, to follow the
- * data format spec defined in Chart.js.
- *
- * @param array items
- * A collection of request items used as the data source for the chart.
- * @param boolean emptyCache
- * True if the cache is considered enabled, false for disabled.
- */
- _sanitizeChartDataSource: function (items, emptyCache) {
- let data = [
- "html", "css", "js", "xhr", "fonts", "images", "media", "flash", "ws", "other"
- ].map(e => ({
- cached: 0,
- count: 0,
- label: e,
- size: 0,
- time: 0
- }));
-
- for (let requestItem of items) {
- let details = requestItem;
- let type;
-
- if (Filters.html(details)) {
- // "html"
- type = 0;
- } else if (Filters.css(details)) {
- // "css"
- type = 1;
- } else if (Filters.js(details)) {
- // "js"
- type = 2;
- } else if (Filters.fonts(details)) {
- // "fonts"
- type = 4;
- } else if (Filters.images(details)) {
- // "images"
- type = 5;
- } else if (Filters.media(details)) {
- // "media"
- type = 6;
- } else if (Filters.flash(details)) {
- // "flash"
- type = 7;
- } else if (Filters.ws(details)) {
- // "ws"
- type = 8;
- } else if (Filters.xhr(details)) {
- // Verify XHR last, to categorize other mime types in their own blobs.
- // "xhr"
- type = 3;
- } else {
- // "other"
- type = 9;
- }
-
- if (emptyCache || !responseIsFresh(details)) {
- data[type].time += details.totalTime || 0;
- data[type].size += details.contentSize || 0;
- } else {
- data[type].cached++;
- }
- data[type].count++;
- }
-
- return data.filter(e => e.count > 0);
- },
-};
-
-/**
- * Checks if the "Expiration Calculations" defined in section 13.2.4 of the
- * "HTTP/1.1: Caching in HTTP" spec holds true for a collection of headers.
- *
- * @param object
- * An object containing the { responseHeaders, status } properties.
- * @return boolean
- * True if the response is fresh and loaded from cache.
- */
-function responseIsFresh({ responseHeaders, status }) {
- // Check for a "304 Not Modified" status and response headers availability.
- if (status != 304 || !responseHeaders) {
- return false;
- }
-
- let list = responseHeaders.headers;
- let cacheControl = list.find(e => e.name.toLowerCase() == "cache-control");
- let expires = list.find(e => e.name.toLowerCase() == "expires");
-
- // Check the "Cache-Control" header for a maximum age value.
- if (cacheControl) {
- let maxAgeMatch =
- cacheControl.value.match(/s-maxage\s*=\s*(\d+)/) ||
- cacheControl.value.match(/max-age\s*=\s*(\d+)/);
-
- if (maxAgeMatch && maxAgeMatch.pop() > 0) {
- return true;
- }
- }
-
- // Check the "Expires" header for a valid date.
- if (expires && Date.parse(expires.value)) {
- return true;
- }
-
- return false;
-}
-
-exports.StatisticsView = StatisticsView;
--- a/devtools/client/netmonitor/test/browser_net_charts-01.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-01.js
@@ -6,18 +6,18 @@
/**
* Makes sure Pie Charts have the right internal structure.
*/
add_task(function* () {
let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
- let { document, NetMonitorView } = monitor.panelWin;
- const { Chart } = NetMonitorView.Statistics;
+ let { document, windowRequire } = monitor.panelWin;
+ let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
let pie = Chart.Pie(document, {
width: 100,
height: 100,
data: [{
size: 1,
label: "foo"
}, {
--- a/devtools/client/netmonitor/test/browser_net_charts-02.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-02.js
@@ -9,18 +9,18 @@
*/
add_task(function* () {
let { L10N } = require("devtools/client/netmonitor/l10n");
let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
- let { document, NetMonitorView } = monitor.panelWin;
- let { Chart } = NetMonitorView.Statistics;
+ let { document, windowRequire } = monitor.panelWin;
+ let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
let pie = Chart.Pie(document, {
data: null,
width: 100,
height: 100
});
let node = pie.node;
--- a/devtools/client/netmonitor/test/browser_net_charts-03.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-03.js
@@ -8,18 +8,18 @@
*/
add_task(function* () {
let { L10N } = require("devtools/client/netmonitor/l10n");
let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
- let { document, NetMonitorView } = monitor.panelWin;
- let { Chart } = NetMonitorView.Statistics;
+ let { document, windowRequire } = monitor.panelWin;
+ let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
let table = Chart.Table(document, {
title: "Table title",
data: [{
label1: 1,
label2: 11.1
}, {
label1: 2,
--- a/devtools/client/netmonitor/test/browser_net_charts-04.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-04.js
@@ -9,18 +9,18 @@
*/
add_task(function* () {
let { L10N } = require("devtools/client/netmonitor/l10n");
let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
- let { document, NetMonitorView } = monitor.panelWin;
- let { Chart } = NetMonitorView.Statistics;
+ let { document, windowRequire } = monitor.panelWin;
+ let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
let table = Chart.Table(document, {
title: "Table title",
data: null,
totals: {
label1: value => "Hello " + L10N.numberWithDecimals(value, 2),
label2: value => "World " + L10N.numberWithDecimals(value, 2)
}
--- a/devtools/client/netmonitor/test/browser_net_charts-05.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-05.js
@@ -8,18 +8,18 @@
*/
add_task(function* () {
let { L10N } = require("devtools/client/netmonitor/l10n");
let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
- let { document, NetMonitorView } = monitor.panelWin;
- let { Chart } = NetMonitorView.Statistics;
+ let { document, windowRequire } = monitor.panelWin;
+ let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
let chart = Chart.PieTable(document, {
title: "Table title",
data: [{
size: 1,
label: 11.1
}, {
size: 2,
--- a/devtools/client/netmonitor/test/browser_net_charts-06.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-06.js
@@ -8,18 +8,18 @@
*/
add_task(function* () {
let { L10N } = require("devtools/client/netmonitor/l10n");
let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
- let { document, NetMonitorView } = monitor.panelWin;
- let { Chart } = NetMonitorView.Statistics;
+ let { document, windowRequire } = monitor.panelWin;
+ let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
let pie = Chart.Pie(document, {
data: [],
width: 100,
height: 100
});
let node = pie.node;
--- a/devtools/client/netmonitor/test/browser_net_charts-07.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-07.js
@@ -8,18 +8,18 @@
*/
add_task(function* () {
let { L10N } = require("devtools/client/netmonitor/l10n");
let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
- let { document, NetMonitorView } = monitor.panelWin;
- let { Chart } = NetMonitorView.Statistics;
+ let { document, windowRequire } = monitor.panelWin;
+ let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
let table = Chart.Table(document, {
data: [],
totals: {
label1: value => "Hello " + L10N.numberWithDecimals(value, 2),
label2: value => "World " + L10N.numberWithDecimals(value, 2)
}
});
--- a/devtools/client/netmonitor/test/browser_net_statistics-01.js
+++ b/devtools/client/netmonitor/test/browser_net_statistics-01.js
@@ -7,59 +7,50 @@
* Tests if the statistics view is populated correctly.
*/
add_task(function* () {
let { monitor } = yield initNetMonitor(STATISTICS_URL);
info("Starting test... ");
let panel = monitor.panelWin;
- let { $, $all, EVENTS, NetMonitorView, gStore, windowRequire } = panel;
+ let { document, gStore, windowRequire } = panel;
let Actions = windowRequire("devtools/client/netmonitor/actions/index");
- is(NetMonitorView.currentFrontendMode, "network-inspector-view",
- "The initial frontend mode is correct.");
+ let body = document.querySelector("#body");
- is($("#primed-cache-chart").childNodes.length, 0,
- "There should be no primed cache chart created yet.");
- is($("#empty-cache-chart").childNodes.length, 0,
- "There should be no empty cache chart created yet.");
-
- let onChartDisplayed = Promise.all([
- panel.once(EVENTS.PRIMED_CACHE_CHART_DISPLAYED),
- panel.once(EVENTS.EMPTY_CACHE_CHART_DISPLAYED)
- ]);
- let onPlaceholderDisplayed = panel.once(EVENTS.PLACEHOLDER_CHARTS_DISPLAYED);
+ is(body.selectedPanel.id, "inspector-panel",
+ "The current main panel is correct.");
info("Displaying statistics view");
gStore.dispatch(Actions.openStatistics(true));
- is(NetMonitorView.currentFrontendMode, "network-statistics-view",
- "The current frontend mode is correct.");
+ is(body.selectedPanel.id, "statistics-panel",
+ "The current main panel is correct.");
info("Waiting for placeholder to display");
- yield onPlaceholderDisplayed;
- is($("#primed-cache-chart").childNodes.length, 1,
+
+ is(document.querySelector(".primed-cache-chart").childNodes.length, 1,
"There should be a placeholder primed cache chart created now.");
- is($("#empty-cache-chart").childNodes.length, 1,
+ is(document.querySelector(".empty-cache-chart").childNodes.length, 1,
"There should be a placeholder empty cache chart created now.");
- is($all(".pie-chart-container[placeholder=true]").length, 2,
+ is(document.querySelectorAll(".pie-chart-container[placeholder=true]").length, 2,
"Two placeholder pie chart appear to be rendered correctly.");
- is($all(".table-chart-container[placeholder=true]").length, 2,
+ is(document.querySelectorAll(".table-chart-container[placeholder=true]").length, 2,
"Two placeholder table chart appear to be rendered correctly.");
info("Waiting for chart to display");
- yield onChartDisplayed;
- is($("#primed-cache-chart").childNodes.length, 1,
+
+ is(document.querySelector(".primed-cache-chart").childNodes.length, 1,
"There should be a real primed cache chart created now.");
- is($("#empty-cache-chart").childNodes.length, 1,
+ is(document.querySelector(".empty-cache-chart").childNodes.length, 1,
"There should be a real empty cache chart created now.");
yield waitUntil(
- () => $all(".pie-chart-container:not([placeholder=true])").length == 2);
+ () => document.querySelectorAll(".pie-chart-container:not([placeholder=true])").length == 2);
ok(true, "Two real pie charts appear to be rendered correctly.");
yield waitUntil(
- () => $all(".table-chart-container:not([placeholder=true])").length == 2);
+ () => document.querySelectorAll(".table-chart-container:not([placeholder=true])").length == 2);
ok(true, "Two real table charts appear to be rendered correctly.");
yield teardown(monitor);
});
--- a/devtools/client/netmonitor/test/browser_net_statistics-02.js
+++ b/devtools/client/netmonitor/test/browser_net_statistics-02.js
@@ -8,39 +8,46 @@
* the performance analysis view.
*/
add_task(function* () {
let { monitor } = yield initNetMonitor(FILTERING_URL);
info("Starting test... ");
let panel = monitor.panelWin;
- let { $, $all, EVENTS, NetMonitorView, gStore, windowRequire } = panel;
+ let { document, gStore, windowRequire } = panel;
let Actions = windowRequire("devtools/client/netmonitor/actions/index");
- EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
- EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
- EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-js-button"));
- EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-ws-button"));
- EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-other-button"));
+ EventUtils.sendMouseEvent({ type: "click" },
+ document.querySelector("#requests-menu-filter-html-button"));
+ EventUtils.sendMouseEvent({ type: "click" },
+ document.querySelector("#requests-menu-filter-css-button"));
+ EventUtils.sendMouseEvent({ type: "click" },
+ document.querySelector("#requests-menu-filter-js-button"));
+ EventUtils.sendMouseEvent({ type: "click" },
+ document.querySelector("#requests-menu-filter-ws-button"));
+ EventUtils.sendMouseEvent({ type: "click" },
+ document.querySelector("#requests-menu-filter-other-button"));
testFilterButtonsCustom(monitor, [0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1]);
info("The correct filtering predicates are used before entering perf. analysis mode.");
- let onEvents = promise.all([
- panel.once(EVENTS.PRIMED_CACHE_CHART_DISPLAYED),
- panel.once(EVENTS.EMPTY_CACHE_CHART_DISPLAYED)
- ]);
gStore.dispatch(Actions.openStatistics(true));
- yield onEvents;
+
+ let body = document.querySelector("#body");
+
+ is(body.selectedPanel.id, "statistics-panel",
+ "The main panel is switched to the statistics panel.");
- is(NetMonitorView.currentFrontendMode, "network-statistics-view",
- "The frontend mode is switched to the statistics view.");
+ yield waitUntil(
+ () => document.querySelectorAll(".pie-chart-container:not([placeholder=true])").length == 2);
+ ok(true, "Two real pie charts appear to be rendered correctly.");
- EventUtils.sendMouseEvent({ type: "click" }, $(".pie-chart-slice"));
+ EventUtils.sendMouseEvent({ type: "click" },
+ document.querySelector(".pie-chart-slice"));
- is(NetMonitorView.currentFrontendMode, "network-inspector-view",
- "The frontend mode is switched back to the inspector view.");
+ is(body.selectedPanel.id, "inspector-panel",
+ "The main panel is switched back to the inspector panel.");
testFilterButtons(monitor, "html");
info("The correct filtering predicate is used when exiting perf. analysis mode.");
yield teardown(monitor);
});
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -102,23 +102,16 @@
--timing-send-color: rgba(70, 175, 227, 0.8); /* light blue */
--timing-wait-color: rgba(94, 136, 176, 0.8); /* blue grey */
--timing-receive-color: rgba(112, 191, 83, 0.8); /* green */
--sort-ascending-image: url(chrome://devtools/skin/images/firebug/arrow-up.svg);
--sort-descending-image: url(chrome://devtools/skin/images/firebug/arrow-down.svg);
}
-#network-table {
- display: -moz-box;
- -moz-box-orient: vertical;
- -moz-box-flex: 1;
- overflow: hidden;
-}
-
.request-list-container {
display: -moz-box;
-moz-box-orient: vertical;
-moz-box-flex: 1;
}
.request-list-empty-notice {
margin: 0;
@@ -840,57 +833,55 @@
#requests-menu-network-summary-button:hover > .summary-info-icon,
#requests-menu-network-summary-button:hover > .summary-info-text {
opacity: 1;
}
/* Performance analysis view */
-#network-statistics-view {
- display: -moz-box;
+.statistics-panel {
+ display: flex;
+ height: 100vh;
}
-#network-statistics-toolbar {
- border: none;
- margin: 0;
- padding: 0;
-}
-
-#network-statistics-back-button {
+.statistics-panel .devtools-toolbarbutton.back-button {
min-width: 4em;
- min-height: 100vh;
margin: 0;
padding: 0;
border-radius: 0;
border-top: none;
border-bottom: none;
border-inline-start: none;
}
-#network-statistics-view-splitter {
+.statistics-panel .splitter {
border-color: rgba(0,0,0,0.2);
cursor: default;
pointer-events: none;
+ height: 100vh;
}
-#network-statistics-charts {
- min-height: 1px;
+.statistics-panel .charts-container {
+ display: flex;
+ width: 100%;
}
-#network-statistics-charts {
- background-color: var(--theme-sidebar-background);
+.statistics-panel .charts,
+.statistics-panel .pie-table-chart-container {
+ width: 100%;
+ height: 100%;
}
-#network-statistics-charts .pie-chart-container {
+.statistics-panel .pie-chart-container {
margin-inline-start: 3vw;
margin-inline-end: 1vw;
}
-#network-statistics-charts .table-chart-container {
+.statistics-panel .table-chart-container {
margin-inline-start: 1vw;
margin-inline-end: 3vw;
}
.chart-colored-blob[name=html] {
fill: var(--theme-highlight-bluegrey);
background: var(--theme-highlight-bluegrey);
}
@@ -1045,16 +1036,25 @@
.requests-menu-size {
max-width: none;
width: 10vw;
}
.requests-menu-waterfall {
display: none;
}
+
+ .statistics-panel .charts-container {
+ flex-direction: column;
+ }
+
+ .statistics-panel .splitter {
+ width: 100vw;
+ height: 0;
+ }
}
/* Platform overrides (copied in from the old platform specific files) */
:root[platform="win"] .requests-menu-header-button > .button-box {
padding: 0;
}
:root[platform="win"] .requests-menu-timings-division {
@@ -1285,20 +1285,26 @@
width: 100%;
height: 100%;
}
/*
* FIXME: normal html block element cannot fill outer XUL element
* This workaround should be removed after netmonitor is migrated to react
*/
+#network-table {
+ display: -moz-box;
+ -moz-box-orient: vertical;
+ -moz-box-flex: 1;
+ overflow: hidden;
+}
+#statistics-panel,
#react-details-panel-hook {
display: flex;
flex-direction: column;
}
-#network-statistics-charts,
#primed-cache-chart,
#empty-cache-chart {
display: -moz-box;
-moz-box-flex: 1;
}