Bug 1336379 - Implement StatisticsPanel r=Honza

MozReview-Commit-ID: FNCSetNPzz6

--HG--
extra : rebase_source : e73958d192ad38f4e1ac2f4e63bb4de075a7de08
This commit is contained in:
Ricky Chien 2017-01-31 19:29:00 +08:00
Родитель 1ebae0840f
Коммит b2bab02fc7
16 изменённых файлов: 329 добавлений и 476 удалений

Просмотреть файл

@ -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>
<html:div xmlns="http://www.w3.org/1999/xhtml"
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;