зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1336379 - Implement StatisticsPanel r=Honza
MozReview-Commit-ID: FNCSetNPzz6 --HG-- extra : rebase_source : e73958d192ad38f4e1ac2f4e63bb4de075a7de08
This commit is contained in:
Родитель
1ebae0840f
Коммит
b2bab02fc7
|
@ -9,5 +9,6 @@ DevToolsModules(
|
||||||
'request-list-item.js',
|
'request-list-item.js',
|
||||||
'request-list-tooltip.js',
|
'request-list-tooltip.js',
|
||||||
'request-list.js',
|
'request-list.js',
|
||||||
|
'statistics-panel.js',
|
||||||
'toolbar.js',
|
'toolbar.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);
|
|
@ -28,7 +28,6 @@ DevToolsModules(
|
||||||
'requests-menu-view.js',
|
'requests-menu-view.js',
|
||||||
'sidebar-view.js',
|
'sidebar-view.js',
|
||||||
'sort-predicates.js',
|
'sort-predicates.js',
|
||||||
'statistics-view.js',
|
|
||||||
'store.js',
|
'store.js',
|
||||||
'waterfall-background.js',
|
'waterfall-background.js',
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,18 +2,15 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/* eslint-disable mozilla/reject-some-requires */
|
|
||||||
/* globals $, gStore, NetMonitorController, dumpn */
|
/* globals $, gStore, NetMonitorController, dumpn */
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { testing: isTesting } = require("devtools/shared/flags");
|
|
||||||
const { Task } = require("devtools/shared/task");
|
const { Task } = require("devtools/shared/task");
|
||||||
const { ViewHelpers } = require("devtools/client/shared/widgets/view-helpers");
|
const { ViewHelpers } = require("devtools/client/shared/widgets/view-helpers");
|
||||||
const { RequestsMenuView } = require("./requests-menu-view");
|
const { RequestsMenuView } = require("./requests-menu-view");
|
||||||
const { CustomRequestView } = require("./custom-request-view");
|
const { CustomRequestView } = require("./custom-request-view");
|
||||||
const { SidebarView } = require("./sidebar-view");
|
const { SidebarView } = require("./sidebar-view");
|
||||||
const { StatisticsView } = require("./statistics-view");
|
|
||||||
const { ACTIVITY_TYPE } = require("./constants");
|
const { ACTIVITY_TYPE } = require("./constants");
|
||||||
const { Prefs } = require("./prefs");
|
const { Prefs } = require("./prefs");
|
||||||
const { createFactory } = require("devtools/client/shared/vendor/react");
|
const { createFactory } = require("devtools/client/shared/vendor/react");
|
||||||
|
@ -23,18 +20,9 @@ const Provider = createFactory(require("devtools/client/shared/vendor/react-redu
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
const DetailsPanel = createFactory(require("./shared/components/details-panel"));
|
const DetailsPanel = createFactory(require("./shared/components/details-panel"));
|
||||||
|
const StatisticsPanel = createFactory(require("./components/statistics-panel"));
|
||||||
const Toolbar = createFactory(require("./components/toolbar"));
|
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.
|
* Object defining the network monitor view components.
|
||||||
*/
|
*/
|
||||||
|
@ -52,6 +40,13 @@ var NetMonitorView = {
|
||||||
DetailsPanel({ toolbox: NetMonitorController._toolbox }),
|
DetailsPanel({ toolbox: NetMonitorController._toolbox }),
|
||||||
), this.detailsPanel);
|
), this.detailsPanel);
|
||||||
|
|
||||||
|
this.statisticsPanel = $("#statistics-panel");
|
||||||
|
|
||||||
|
ReactDOM.render(Provider(
|
||||||
|
{ store: gStore },
|
||||||
|
StatisticsPanel(),
|
||||||
|
), this.statisticsPanel);
|
||||||
|
|
||||||
this.toolbar = $("#react-toolbar-hook");
|
this.toolbar = $("#react-toolbar-hook");
|
||||||
|
|
||||||
ReactDOM.render(Provider(
|
ReactDOM.render(Provider(
|
||||||
|
@ -61,7 +56,6 @@ var NetMonitorView = {
|
||||||
|
|
||||||
this.RequestsMenu.initialize(gStore);
|
this.RequestsMenu.initialize(gStore);
|
||||||
this.CustomRequest.initialize();
|
this.CustomRequest.initialize();
|
||||||
this.Statistics.initialize(gStore);
|
|
||||||
|
|
||||||
// Store watcher here is for observing the statisticsOpen state change.
|
// Store watcher here is for observing the statisticsOpen state change.
|
||||||
// It should be removed once we migrate to react and apply react/redex binding.
|
// It should be removed once we migrate to react and apply react/redex binding.
|
||||||
|
@ -79,8 +73,8 @@ var NetMonitorView = {
|
||||||
this._isDestroyed = true;
|
this._isDestroyed = true;
|
||||||
this.RequestsMenu.destroy();
|
this.RequestsMenu.destroy();
|
||||||
this.CustomRequest.destroy();
|
this.CustomRequest.destroy();
|
||||||
this.Statistics.destroy();
|
|
||||||
ReactDOM.unmountComponentAtNode(this.detailsPanel);
|
ReactDOM.unmountComponentAtNode(this.detailsPanel);
|
||||||
|
ReactDOM.unmountComponentAtNode(this.statisticsPanel);
|
||||||
ReactDOM.unmountComponentAtNode(this.toolbar);
|
ReactDOM.unmountComponentAtNode(this.toolbar);
|
||||||
this.unsubscribeStore();
|
this.unsubscribeStore();
|
||||||
|
|
||||||
|
@ -149,10 +143,6 @@ var NetMonitorView = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current mode for this tool.
|
|
||||||
* @return string (e.g, "network-inspector-view" or "network-statistics-view")
|
|
||||||
*/
|
|
||||||
get currentFrontendMode() {
|
get currentFrontendMode() {
|
||||||
// The getter may be called from a timeout after the panel is destroyed.
|
// The getter may be called from a timeout after the panel is destroyed.
|
||||||
if (!this._body.selectedPanel) {
|
if (!this._body.selectedPanel) {
|
||||||
|
@ -161,9 +151,6 @@ var NetMonitorView = {
|
||||||
return this._body.selectedPanel.id;
|
return this._body.selectedPanel.id;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles between the frontend view modes ("Inspector" vs. "Statistics").
|
|
||||||
*/
|
|
||||||
toggleFrontendMode: function () {
|
toggleFrontendMode: function () {
|
||||||
if (gStore.getState().ui.statisticsOpen) {
|
if (gStore.getState().ui.statisticsOpen) {
|
||||||
this.showNetworkStatisticsView();
|
this.showNetworkStatisticsView();
|
||||||
|
@ -172,45 +159,13 @@ var NetMonitorView = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Switches to the "Inspector" frontend view mode.
|
|
||||||
*/
|
|
||||||
showNetworkInspectorView: function () {
|
showNetworkInspectorView: function () {
|
||||||
this._body.selectedPanel = $("#network-inspector-view");
|
this._body.selectedPanel = $("#inspector-panel");
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Switches to the "Statistics" frontend view mode.
|
|
||||||
*/
|
|
||||||
showNetworkStatisticsView: function () {
|
showNetworkStatisticsView: function () {
|
||||||
this._body.selectedPanel = $("#network-statistics-view");
|
this._body.selectedPanel = $("#statistics-panel");
|
||||||
|
NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
|
||||||
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);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
reloadPage: function () {
|
reloadPage: function () {
|
||||||
|
@ -222,41 +177,6 @@ var NetMonitorView = {
|
||||||
_detailsPane: 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
|
// A smart store watcher to notify store changes as necessary
|
||||||
function storeWatcher(initialValue, reduceValue, onChange) {
|
function storeWatcher(initialValue, reduceValue, onChange) {
|
||||||
let currentValue = initialValue;
|
let currentValue = initialValue;
|
||||||
|
@ -276,6 +196,5 @@ function storeWatcher(initialValue, reduceValue, onChange) {
|
||||||
NetMonitorView.Sidebar = new SidebarView();
|
NetMonitorView.Sidebar = new SidebarView();
|
||||||
NetMonitorView.RequestsMenu = new RequestsMenuView();
|
NetMonitorView.RequestsMenu = new RequestsMenuView();
|
||||||
NetMonitorView.CustomRequest = new CustomRequestView();
|
NetMonitorView.CustomRequest = new CustomRequestView();
|
||||||
NetMonitorView.Statistics = new StatisticsView();
|
|
||||||
|
|
||||||
exports.NetMonitorView = NetMonitorView;
|
exports.NetMonitorView = NetMonitorView;
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
flex="1"
|
flex="1"
|
||||||
data-localization-bundle="devtools/client/locales/netmonitor.properties">
|
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"
|
<html:div xmlns="http://www.w3.org/1999/xhtml"
|
||||||
id="react-toolbar-hook"/>
|
id="react-toolbar-hook"/>
|
||||||
<hbox id="network-table-and-sidebar"
|
<hbox id="network-table-and-sidebar"
|
||||||
|
@ -101,19 +101,8 @@
|
||||||
|
|
||||||
</vbox>
|
</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"
|
<html:div xmlns="http://www.w3.org/1999/xhtml"
|
||||||
id="react-statistics-back-hook"/>
|
id="statistics-panel">
|
||||||
</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>
|
</html:div>
|
||||||
</deck>
|
</deck>
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
|
@ -11,8 +11,8 @@ add_task(function* () {
|
||||||
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||||
info("Starting test... ");
|
info("Starting test... ");
|
||||||
|
|
||||||
let { document, NetMonitorView } = monitor.panelWin;
|
let { document, windowRequire } = monitor.panelWin;
|
||||||
const { Chart } = NetMonitorView.Statistics;
|
let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
|
||||||
|
|
||||||
let pie = Chart.Pie(document, {
|
let pie = Chart.Pie(document, {
|
||||||
width: 100,
|
width: 100,
|
||||||
|
|
|
@ -14,8 +14,8 @@ add_task(function* () {
|
||||||
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||||
info("Starting test... ");
|
info("Starting test... ");
|
||||||
|
|
||||||
let { document, NetMonitorView } = monitor.panelWin;
|
let { document, windowRequire } = monitor.panelWin;
|
||||||
let { Chart } = NetMonitorView.Statistics;
|
let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
|
||||||
|
|
||||||
let pie = Chart.Pie(document, {
|
let pie = Chart.Pie(document, {
|
||||||
data: null,
|
data: null,
|
||||||
|
|
|
@ -13,8 +13,8 @@ add_task(function* () {
|
||||||
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||||
info("Starting test... ");
|
info("Starting test... ");
|
||||||
|
|
||||||
let { document, NetMonitorView } = monitor.panelWin;
|
let { document, windowRequire } = monitor.panelWin;
|
||||||
let { Chart } = NetMonitorView.Statistics;
|
let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
|
||||||
|
|
||||||
let table = Chart.Table(document, {
|
let table = Chart.Table(document, {
|
||||||
title: "Table title",
|
title: "Table title",
|
||||||
|
|
|
@ -14,8 +14,8 @@ add_task(function* () {
|
||||||
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||||
info("Starting test... ");
|
info("Starting test... ");
|
||||||
|
|
||||||
let { document, NetMonitorView } = monitor.panelWin;
|
let { document, windowRequire } = monitor.panelWin;
|
||||||
let { Chart } = NetMonitorView.Statistics;
|
let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
|
||||||
|
|
||||||
let table = Chart.Table(document, {
|
let table = Chart.Table(document, {
|
||||||
title: "Table title",
|
title: "Table title",
|
||||||
|
|
|
@ -13,8 +13,8 @@ add_task(function* () {
|
||||||
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||||
info("Starting test... ");
|
info("Starting test... ");
|
||||||
|
|
||||||
let { document, NetMonitorView } = monitor.panelWin;
|
let { document, windowRequire } = monitor.panelWin;
|
||||||
let { Chart } = NetMonitorView.Statistics;
|
let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
|
||||||
|
|
||||||
let chart = Chart.PieTable(document, {
|
let chart = Chart.PieTable(document, {
|
||||||
title: "Table title",
|
title: "Table title",
|
||||||
|
|
|
@ -13,8 +13,8 @@ add_task(function* () {
|
||||||
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||||
info("Starting test... ");
|
info("Starting test... ");
|
||||||
|
|
||||||
let { document, NetMonitorView } = monitor.panelWin;
|
let { document, windowRequire } = monitor.panelWin;
|
||||||
let { Chart } = NetMonitorView.Statistics;
|
let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
|
||||||
|
|
||||||
let pie = Chart.Pie(document, {
|
let pie = Chart.Pie(document, {
|
||||||
data: [],
|
data: [],
|
||||||
|
|
|
@ -13,8 +13,8 @@ add_task(function* () {
|
||||||
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||||
info("Starting test... ");
|
info("Starting test... ");
|
||||||
|
|
||||||
let { document, NetMonitorView } = monitor.panelWin;
|
let { document, windowRequire } = monitor.panelWin;
|
||||||
let { Chart } = NetMonitorView.Statistics;
|
let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
|
||||||
|
|
||||||
let table = Chart.Table(document, {
|
let table = Chart.Table(document, {
|
||||||
data: [],
|
data: [],
|
||||||
|
|
|
@ -12,53 +12,44 @@ add_task(function* () {
|
||||||
info("Starting test... ");
|
info("Starting test... ");
|
||||||
|
|
||||||
let panel = monitor.panelWin;
|
let panel = monitor.panelWin;
|
||||||
let { $, $all, EVENTS, NetMonitorView, gStore, windowRequire } = panel;
|
let { document, gStore, windowRequire } = panel;
|
||||||
let Actions = windowRequire("devtools/client/netmonitor/actions/index");
|
let Actions = windowRequire("devtools/client/netmonitor/actions/index");
|
||||||
|
|
||||||
is(NetMonitorView.currentFrontendMode, "network-inspector-view",
|
let body = document.querySelector("#body");
|
||||||
"The initial frontend mode is correct.");
|
|
||||||
|
|
||||||
is($("#primed-cache-chart").childNodes.length, 0,
|
is(body.selectedPanel.id, "inspector-panel",
|
||||||
"There should be no primed cache chart created yet.");
|
"The current main panel is correct.");
|
||||||
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);
|
|
||||||
|
|
||||||
info("Displaying statistics view");
|
info("Displaying statistics view");
|
||||||
gStore.dispatch(Actions.openStatistics(true));
|
gStore.dispatch(Actions.openStatistics(true));
|
||||||
is(NetMonitorView.currentFrontendMode, "network-statistics-view",
|
is(body.selectedPanel.id, "statistics-panel",
|
||||||
"The current frontend mode is correct.");
|
"The current main panel is correct.");
|
||||||
|
|
||||||
info("Waiting for placeholder to display");
|
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.");
|
"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.");
|
"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.");
|
"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.");
|
"Two placeholder table chart appear to be rendered correctly.");
|
||||||
|
|
||||||
info("Waiting for chart to display");
|
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.");
|
"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.");
|
"There should be a real empty cache chart created now.");
|
||||||
|
|
||||||
yield waitUntil(
|
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.");
|
ok(true, "Two real pie charts appear to be rendered correctly.");
|
||||||
|
|
||||||
yield waitUntil(
|
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.");
|
ok(true, "Two real table charts appear to be rendered correctly.");
|
||||||
|
|
||||||
yield teardown(monitor);
|
yield teardown(monitor);
|
||||||
|
|
|
@ -13,31 +13,38 @@ add_task(function* () {
|
||||||
info("Starting test... ");
|
info("Starting test... ");
|
||||||
|
|
||||||
let panel = monitor.panelWin;
|
let panel = monitor.panelWin;
|
||||||
let { $, $all, EVENTS, NetMonitorView, gStore, windowRequire } = panel;
|
let { document, gStore, windowRequire } = panel;
|
||||||
let Actions = windowRequire("devtools/client/netmonitor/actions/index");
|
let Actions = windowRequire("devtools/client/netmonitor/actions/index");
|
||||||
|
|
||||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
|
EventUtils.sendMouseEvent({ type: "click" },
|
||||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
|
document.querySelector("#requests-menu-filter-html-button"));
|
||||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-js-button"));
|
EventUtils.sendMouseEvent({ type: "click" },
|
||||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-ws-button"));
|
document.querySelector("#requests-menu-filter-css-button"));
|
||||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-other-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]);
|
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.");
|
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));
|
gStore.dispatch(Actions.openStatistics(true));
|
||||||
yield onEvents;
|
|
||||||
|
|
||||||
is(NetMonitorView.currentFrontendMode, "network-statistics-view",
|
let body = document.querySelector("#body");
|
||||||
"The frontend mode is switched to the statistics view.");
|
|
||||||
|
|
||||||
EventUtils.sendMouseEvent({ type: "click" }, $(".pie-chart-slice"));
|
is(body.selectedPanel.id, "statistics-panel",
|
||||||
|
"The main panel is switched to the statistics panel.");
|
||||||
|
|
||||||
is(NetMonitorView.currentFrontendMode, "network-inspector-view",
|
yield waitUntil(
|
||||||
"The frontend mode is switched back to the inspector view.");
|
() => 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" },
|
||||||
|
document.querySelector(".pie-chart-slice"));
|
||||||
|
|
||||||
|
is(body.selectedPanel.id, "inspector-panel",
|
||||||
|
"The main panel is switched back to the inspector panel.");
|
||||||
|
|
||||||
testFilterButtons(monitor, "html");
|
testFilterButtons(monitor, "html");
|
||||||
info("The correct filtering predicate is used when exiting perf. analysis mode.");
|
info("The correct filtering predicate is used when exiting perf. analysis mode.");
|
||||||
|
|
|
@ -107,13 +107,6 @@
|
||||||
--sort-descending-image: url(chrome://devtools/skin/images/firebug/arrow-down.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 {
|
.request-list-container {
|
||||||
display: -moz-box;
|
display: -moz-box;
|
||||||
-moz-box-orient: vertical;
|
-moz-box-orient: vertical;
|
||||||
|
@ -845,19 +838,13 @@
|
||||||
|
|
||||||
/* Performance analysis view */
|
/* Performance analysis view */
|
||||||
|
|
||||||
#network-statistics-view {
|
.statistics-panel {
|
||||||
display: -moz-box;
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
#network-statistics-toolbar {
|
.statistics-panel .devtools-toolbarbutton.back-button {
|
||||||
border: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#network-statistics-back-button {
|
|
||||||
min-width: 4em;
|
min-width: 4em;
|
||||||
min-height: 100vh;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
@ -866,26 +853,30 @@
|
||||||
border-inline-start: none;
|
border-inline-start: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#network-statistics-view-splitter {
|
.statistics-panel .splitter {
|
||||||
border-color: rgba(0,0,0,0.2);
|
border-color: rgba(0,0,0,0.2);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
#network-statistics-charts {
|
.statistics-panel .charts-container {
|
||||||
min-height: 1px;
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#network-statistics-charts {
|
.statistics-panel .charts,
|
||||||
background-color: var(--theme-sidebar-background);
|
.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-start: 3vw;
|
||||||
margin-inline-end: 1vw;
|
margin-inline-end: 1vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
#network-statistics-charts .table-chart-container {
|
.statistics-panel .table-chart-container {
|
||||||
margin-inline-start: 1vw;
|
margin-inline-start: 1vw;
|
||||||
margin-inline-end: 3vw;
|
margin-inline-end: 3vw;
|
||||||
}
|
}
|
||||||
|
@ -1050,6 +1041,15 @@
|
||||||
.requests-menu-waterfall {
|
.requests-menu-waterfall {
|
||||||
display: none;
|
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) */
|
/* Platform overrides (copied in from the old platform specific files) */
|
||||||
|
@ -1290,13 +1290,19 @@
|
||||||
* FIXME: normal html block element cannot fill outer XUL element
|
* FIXME: normal html block element cannot fill outer XUL element
|
||||||
* This workaround should be removed after netmonitor is migrated to react
|
* 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 {
|
#react-details-panel-hook {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
#network-statistics-charts,
|
|
||||||
#primed-cache-chart,
|
#primed-cache-chart,
|
||||||
#empty-cache-chart {
|
#empty-cache-chart {
|
||||||
display: -moz-box;
|
display: -moz-box;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче