зеркало из 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-tooltip.js',
|
||||
'request-list.js',
|
||||
'statistics-panel.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',
|
||||
'sidebar-view.js',
|
||||
'sort-predicates.js',
|
||||
'statistics-view.js',
|
||||
'store.js',
|
||||
'waterfall-background.js',
|
||||
)
|
||||
|
|
|
@ -2,18 +2,15 @@
|
|||
* 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");
|
||||
|
@ -23,18 +20,9 @@ const Provider = createFactory(require("devtools/client/shared/vendor/react-redu
|
|||
|
||||
// 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.
|
||||
*/
|
||||
|
@ -52,6 +40,13 @@ var NetMonitorView = {
|
|||
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(
|
||||
|
@ -61,7 +56,6 @@ var NetMonitorView = {
|
|||
|
||||
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.
|
||||
|
@ -79,8 +73,8 @@ var NetMonitorView = {
|
|||
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();
|
||||
|
||||
|
@ -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() {
|
||||
// The getter may be called from a timeout after the panel is destroyed.
|
||||
if (!this._body.selectedPanel) {
|
||||
|
@ -161,9 +151,6 @@ var NetMonitorView = {
|
|||
return this._body.selectedPanel.id;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles between the frontend view modes ("Inspector" vs. "Statistics").
|
||||
*/
|
||||
toggleFrontendMode: function () {
|
||||
if (gStore.getState().ui.statisticsOpen) {
|
||||
this.showNetworkStatisticsView();
|
||||
|
@ -172,45 +159,13 @@ var NetMonitorView = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 () {
|
||||
|
@ -222,41 +177,6 @@ var NetMonitorView = {
|
|||
_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;
|
||||
|
@ -276,6 +196,5 @@ function storeWatcher(initialValue, reduceValue, onChange) {
|
|||
NetMonitorView.Sidebar = new SidebarView();
|
||||
NetMonitorView.RequestsMenu = new RequestsMenuView();
|
||||
NetMonitorView.CustomRequest = new CustomRequestView();
|
||||
NetMonitorView.Statistics = new StatisticsView();
|
||||
|
||||
exports.NetMonitorView = NetMonitorView;
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
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"
|
||||
|
@ -101,19 +101,8 @@
|
|||
|
||||
</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>
|
||||
id="statistics-panel">
|
||||
</html:div>
|
||||
</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);
|
||||
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,
|
||||
|
|
|
@ -14,8 +14,8 @@ add_task(function* () {
|
|||
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,
|
||||
|
|
|
@ -13,8 +13,8 @@ add_task(function* () {
|
|||
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",
|
||||
|
|
|
@ -14,8 +14,8 @@ add_task(function* () {
|
|||
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",
|
||||
|
|
|
@ -13,8 +13,8 @@ add_task(function* () {
|
|||
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",
|
||||
|
|
|
@ -13,8 +13,8 @@ add_task(function* () {
|
|||
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: [],
|
||||
|
|
|
@ -13,8 +13,8 @@ add_task(function* () {
|
|||
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: [],
|
||||
|
|
|
@ -12,53 +12,44 @@ add_task(function* () {
|
|||
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);
|
||||
|
|
|
@ -13,31 +13,38 @@ add_task(function* () {
|
|||
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;
|
||||
|
||||
is(NetMonitorView.currentFrontendMode, "network-statistics-view",
|
||||
"The frontend mode is switched to the statistics view.");
|
||||
let body = document.querySelector("#body");
|
||||
|
||||
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",
|
||||
"The frontend mode is switched back to the inspector 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" },
|
||||
document.querySelector(".pie-chart-slice"));
|
||||
|
||||
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.");
|
||||
|
|
|
@ -107,13 +107,6 @@
|
|||
--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;
|
||||
|
@ -845,19 +838,13 @@
|
|||
|
||||
/* 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;
|
||||
|
@ -866,26 +853,30 @@
|
|||
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;
|
||||
}
|
||||
|
@ -1050,6 +1041,15 @@
|
|||
.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) */
|
||||
|
@ -1290,13 +1290,19 @@
|
|||
* 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;
|
||||
|
|
Загрузка…
Ссылка в новой задаче